Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
a05cfe60fe | |||
2774d15298 | |||
f544daf8c0 | |||
089315f9bb | |||
1f7e14dd4e | |||
37f71c48d1 | |||
fa68bf561b | |||
a4eecb251e | |||
9bf6194b09 | |||
f405f509c4 | |||
8be5fdee2d | |||
7efa26e811 | |||
755fff0818 | |||
53e1e302d5 | |||
3d0287f04f | |||
7cb132af01 | |||
63b0b936aa | |||
412122d7d9 | |||
eb9ef9f6d9 | |||
27091db53b | |||
2a54043afc | |||
e238a7b168 | |||
1e554acd20 | |||
dff91da877 | |||
f6bb53e388 | |||
709292339a | |||
799e5b882b | |||
0ff92cbfe3 | |||
e9891236cd | |||
e2c48c3438 | |||
9a7b3b29f5 | |||
10add895fb | |||
050eaedf15 | |||
5b06a3fc64 | |||
4817f51bc0 | |||
c83a4e0cc8 | |||
c15f305be0 | |||
222d216854 | |||
6a785baa2c | |||
6d8aaba8bb | |||
6cf3748642 | |||
9c938c6210 | |||
b1182e7cf5 | |||
a49d399f72 | |||
9178b50b73 | |||
b5d04f1a50 | |||
9e434e7db6 | |||
ab30b8bbec | |||
1fa9f27619 | |||
37d1939dc0 | |||
08a20b89a6 | |||
5d518cfc18 | |||
7e752b3d81 | |||
87729d0daa | |||
dc06ae3b29 | |||
225380b7d9 | |||
7391a5bc7a | |||
3ff5aff32f | |||
e579b97442 |
14
.devcontainer/devcontainer.json
Normal file
14
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "Rust",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
||||||
|
},
|
||||||
|
"portsAttributes": {
|
||||||
|
"8080": {
|
||||||
|
"label": "libreddit",
|
||||||
|
"onAutoForward": "notify"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "cargo build"
|
||||||
|
}
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Dockerfile.* linguist-language=Dockerfile
|
2
.github/workflows/docker-arm.yml
vendored
2
.github/workflows/docker-arm.yml
vendored
@ -33,6 +33,6 @@ jobs:
|
|||||||
file: ./Dockerfile.arm
|
file: ./Dockerfile.arm
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: spikecodes/libreddit:arm
|
tags: libreddit/libreddit:arm
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
2
.github/workflows/docker-armv7.yml
vendored
2
.github/workflows/docker-armv7.yml
vendored
@ -36,6 +36,6 @@ jobs:
|
|||||||
file: ./Dockerfile.armv7
|
file: ./Dockerfile.armv7
|
||||||
platforms: linux/arm/v7
|
platforms: linux/arm/v7
|
||||||
push: true
|
push: true
|
||||||
tags: spikecodes/libreddit:armv7
|
tags: libreddit/libreddit:armv7
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
8
.github/workflows/docker.yml
vendored
8
.github/workflows/docker.yml
vendored
@ -26,6 +26,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Docker Hub Description
|
||||||
|
uses: peter-evans/dockerhub-description@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
repository: libreddit/libreddit
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
@ -33,6 +39,6 @@ jobs:
|
|||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: spikecodes/libreddit:latest
|
tags: libreddit/libreddit:latest
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
22
.github/workflows/rust-tests.yml
vendored
Normal file
22
.github/workflows/rust-tests.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --verbose
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose
|
11
CREDITS
11
CREDITS
@ -3,6 +3,7 @@
|
|||||||
accountForIssues <52367365+accountForIssues@users.noreply.github.com>
|
accountForIssues <52367365+accountForIssues@users.noreply.github.com>
|
||||||
Adrian Lebioda <adrianlebioda@gmail.com>
|
Adrian Lebioda <adrianlebioda@gmail.com>
|
||||||
alefvanoon <53198048+alefvanoon@users.noreply.github.com>
|
alefvanoon <53198048+alefvanoon@users.noreply.github.com>
|
||||||
|
Alexandre Iooss <erdnaxe@crans.org>
|
||||||
alyaeanyx <alexandra.hollmeier@mailbox.org>
|
alyaeanyx <alexandra.hollmeier@mailbox.org>
|
||||||
AndreVuillemot160 <84594011+AndreVuillemot160@users.noreply.github.com>
|
AndreVuillemot160 <84594011+AndreVuillemot160@users.noreply.github.com>
|
||||||
Andrew Kaufman <57281817+andrew-kaufman@users.noreply.github.com>
|
Andrew Kaufman <57281817+andrew-kaufman@users.noreply.github.com>
|
||||||
@ -16,19 +17,24 @@ BobIsMyManager <ahoumatt@yahoo.com>
|
|||||||
curlpipe <11898833+curlpipe@users.noreply.github.com>
|
curlpipe <11898833+curlpipe@users.noreply.github.com>
|
||||||
dacousb <53299044+dacousb@users.noreply.github.com>
|
dacousb <53299044+dacousb@users.noreply.github.com>
|
||||||
Daniel Valentine <Daniel-Valentine@users.noreply.github.com>
|
Daniel Valentine <Daniel-Valentine@users.noreply.github.com>
|
||||||
|
Daniel Valentine <daniel@vielle.ws>
|
||||||
dbrennand <52419383+dbrennand@users.noreply.github.com>
|
dbrennand <52419383+dbrennand@users.noreply.github.com>
|
||||||
|
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com>
|
Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com>
|
||||||
Dyras <jevwmguf@duck.com>
|
Dyras <jevwmguf@duck.com>
|
||||||
Edward <101938856+EdwardLangdon@users.noreply.github.com>
|
Edward <101938856+EdwardLangdon@users.noreply.github.com>
|
||||||
|
elliot <75391956+ellieeet123@users.noreply.github.com>
|
||||||
erdnaxe <erdnaxe@users.noreply.github.com>
|
erdnaxe <erdnaxe@users.noreply.github.com>
|
||||||
Esmail EL BoB <github.defilable@simplelogin.co>
|
Esmail EL BoB <github.defilable@simplelogin.co>
|
||||||
FireMasterK <20838718+FireMasterK@users.noreply.github.com>
|
FireMasterK <20838718+FireMasterK@users.noreply.github.com>
|
||||||
George Roubos <cowkingdom@hotmail.com>
|
George Roubos <cowkingdom@hotmail.com>
|
||||||
git-bruh <e817509a-8ee9-4332-b0ad-3a6bdf9ab63f@aleeas.com>
|
git-bruh <e817509a-8ee9-4332-b0ad-3a6bdf9ab63f@aleeas.com>
|
||||||
|
gmnsii <95436780+gmnsii@users.noreply.github.com>
|
||||||
guaddy <67671414+guaddy@users.noreply.github.com>
|
guaddy <67671414+guaddy@users.noreply.github.com>
|
||||||
Harsh Mishra <erbeusgriffincasper@gmail.com>
|
Harsh Mishra <erbeusgriffincasper@gmail.com>
|
||||||
igna <igna@intent.cool>
|
igna <igna@intent.cool>
|
||||||
imabritishcow <bcow@protonmail.com>
|
imabritishcow <bcow@protonmail.com>
|
||||||
|
Johannes Schleifenbaum <johannes@js-webcoding.de>
|
||||||
Josiah <70736638+fres7h@users.noreply.github.com>
|
Josiah <70736638+fres7h@users.noreply.github.com>
|
||||||
JPyke3 <pyke.jacob1@gmail.com>
|
JPyke3 <pyke.jacob1@gmail.com>
|
||||||
Kavin <20838718+FireMasterK@users.noreply.github.com>
|
Kavin <20838718+FireMasterK@users.noreply.github.com>
|
||||||
@ -43,6 +49,7 @@ Macic <46872282+Macic-Dev@users.noreply.github.com>
|
|||||||
Mario A <10923513+Midblyte@users.noreply.github.com>
|
Mario A <10923513+Midblyte@users.noreply.github.com>
|
||||||
Matthew Crossman <matt@crossman.page>
|
Matthew Crossman <matt@crossman.page>
|
||||||
Matthew E <matt@matthew.science>
|
Matthew E <matt@matthew.science>
|
||||||
|
Matthew Esposito <matt@matthew.science>
|
||||||
Mennaruuk <52135169+Mennaruuk@users.noreply.github.com>
|
Mennaruuk <52135169+Mennaruuk@users.noreply.github.com>
|
||||||
mikupls <93015331+mikupls@users.noreply.github.com>
|
mikupls <93015331+mikupls@users.noreply.github.com>
|
||||||
Nainar <nainar.mb@gmail.com>
|
Nainar <nainar.mb@gmail.com>
|
||||||
@ -54,6 +61,7 @@ NKIPSC <15067635+NKIPSC@users.noreply.github.com>
|
|||||||
obeho <71698631+obeho@users.noreply.github.com>
|
obeho <71698631+obeho@users.noreply.github.com>
|
||||||
obscurity <z@x4.pm>
|
obscurity <z@x4.pm>
|
||||||
Om G <34579088+OxyMagnesium@users.noreply.github.com>
|
Om G <34579088+OxyMagnesium@users.noreply.github.com>
|
||||||
|
potatoesAreGod <118043038+potatoesAreGod@users.noreply.github.com>
|
||||||
RiversideRocks <59586759+RiversideRocks@users.noreply.github.com>
|
RiversideRocks <59586759+RiversideRocks@users.noreply.github.com>
|
||||||
robin <8597693+robrobinbin@users.noreply.github.com>
|
robin <8597693+robrobinbin@users.noreply.github.com>
|
||||||
Robin <8597693+robrobinbin@users.noreply.github.com>
|
Robin <8597693+robrobinbin@users.noreply.github.com>
|
||||||
@ -61,11 +69,13 @@ robrobinbin <>
|
|||||||
robrobinbin <8597693+robrobinbin@users.noreply.github.com>
|
robrobinbin <8597693+robrobinbin@users.noreply.github.com>
|
||||||
robrobinbin <robindepril@gmail.com>
|
robrobinbin <robindepril@gmail.com>
|
||||||
Ruben Elshof <15641671+rubenelshof@users.noreply.github.com>
|
Ruben Elshof <15641671+rubenelshof@users.noreply.github.com>
|
||||||
|
Rupert Angermeier <rangermeier@users.noreply.github.com>
|
||||||
Scoder12 <34356756+Scoder12@users.noreply.github.com>
|
Scoder12 <34356756+Scoder12@users.noreply.github.com>
|
||||||
Slayer <51095261+GhostSlayer@users.noreply.github.com>
|
Slayer <51095261+GhostSlayer@users.noreply.github.com>
|
||||||
Soheb <somoso@users.noreply.github.com>
|
Soheb <somoso@users.noreply.github.com>
|
||||||
somini <somini@users.noreply.github.com>
|
somini <somini@users.noreply.github.com>
|
||||||
somoso <github@soheb.anonaddy.com>
|
somoso <github@soheb.anonaddy.com>
|
||||||
|
Spenser Black <spenserblack01@gmail.com>
|
||||||
Spike <19519553+spikecodes@users.noreply.github.com>
|
Spike <19519553+spikecodes@users.noreply.github.com>
|
||||||
spikecodes <19519553+spikecodes@users.noreply.github.com>
|
spikecodes <19519553+spikecodes@users.noreply.github.com>
|
||||||
sybenx <syb@duck.com>
|
sybenx <syb@duck.com>
|
||||||
@ -73,6 +83,7 @@ TheCultLeader666 <65368815+TheCultLeader666@users.noreply.github.com>
|
|||||||
TheFrenchGhosty <47571719+TheFrenchGhosty@users.noreply.github.com>
|
TheFrenchGhosty <47571719+TheFrenchGhosty@users.noreply.github.com>
|
||||||
The TwilightBlood <hwengerstickel@protonmail.com>
|
The TwilightBlood <hwengerstickel@protonmail.com>
|
||||||
tirz <36501933+tirz@users.noreply.github.com>
|
tirz <36501933+tirz@users.noreply.github.com>
|
||||||
|
Tokarak <63452145+Tokarak@users.noreply.github.com>
|
||||||
Tsvetomir Bonev <invakid404@riseup.net>
|
Tsvetomir Bonev <invakid404@riseup.net>
|
||||||
Vladislav Nepogodin <nepogodin.vlad@gmail.com>
|
Vladislav Nepogodin <nepogodin.vlad@gmail.com>
|
||||||
Walkx <walkxnl@gmail.com>
|
Walkx <walkxnl@gmail.com>
|
||||||
|
440
Cargo.lock
generated
440
Cargo.lock
generated
@ -10,9 +10,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.19"
|
version = "0.7.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@ -75,22 +75,11 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "async-recursion"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.58"
|
version = "0.1.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
|
checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -111,9 +100,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.1"
|
version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@ -143,9 +132,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli-decompressor"
|
name = "brotli-decompressor"
|
||||||
version = "2.3.2"
|
version = "2.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
|
checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
@ -153,37 +142,44 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "0.2.17"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "build_html"
|
||||||
version = "3.11.1"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
checksum = "f3ef018b44d829e1b3364b4969059c098743595ec57a7eed176fbc9d909ac217"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.2.1"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cached"
|
name = "cached"
|
||||||
version = "0.40.0"
|
version = "0.42.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b4147cd94d5fbdc2ab71b11d50a2f45493625576b3bb70257f59eedea69f3d"
|
checksum = "5e5877db5d1af7fae60d06b5db9430b68056a69b3582a0be8e3691e87654aeb6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async_once",
|
"async_once",
|
||||||
"cached_proc_macro",
|
"cached_proc_macro",
|
||||||
"cached_proc_macro_types",
|
"cached_proc_macro_types",
|
||||||
"futures",
|
"futures",
|
||||||
"hashbrown",
|
"hashbrown 0.13.2",
|
||||||
"instant",
|
"instant",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -193,12 +189,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cached_proc_macro"
|
name = "cached_proc_macro"
|
||||||
version = "0.15.0"
|
version = "0.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "751f7f4e7a091545e7f6c65bacc404eaee7e87bfb1f9ece234a1caa173dc16f2"
|
checksum = "e10ca87c81aaa3a949dbbe2b5e6c2c45dbc94ba4897e45ea31ff9ec5087be3dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cached_proc_macro_types",
|
"cached_proc_macro_types",
|
||||||
"darling",
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
@ -211,9 +208,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.76"
|
version = "1.0.78"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
|
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@ -223,9 +220,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.0.24"
|
version = "4.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03"
|
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
@ -233,18 +230,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
|
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.16.1"
|
version = "0.16.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"time",
|
"time",
|
||||||
"version_check",
|
"version_check",
|
||||||
@ -296,9 +293,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.13.4"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
@ -306,9 +303,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.13.4"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
@ -320,9 +317,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.13.4"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
@ -331,9 +328,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.5"
|
version = "0.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
@ -363,6 +360,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs_extra"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
@ -462,9 +465,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.9"
|
version = "0.4.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"bstr",
|
"bstr",
|
||||||
@ -499,10 +502,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hashbrown"
|
||||||
version = "0.1.19"
|
version = "0.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@ -567,9 +576,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.23.0"
|
version = "0.23.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
|
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
@ -598,12 +607,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.1"
|
version = "1.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown 0.12.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -617,9 +626,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
@ -638,9 +647,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.137"
|
version = "0.2.139"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libflate"
|
name = "libflate"
|
||||||
@ -664,11 +673,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libreddit"
|
name = "libreddit"
|
||||||
version = "0.24.3"
|
version = "0.29.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
"async-recursion",
|
|
||||||
"brotli",
|
"brotli",
|
||||||
|
"build_html",
|
||||||
"cached",
|
"cached",
|
||||||
"clap",
|
"clap",
|
||||||
"cookie",
|
"cookie",
|
||||||
@ -677,14 +686,18 @@ dependencies = [
|
|||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
"libflate",
|
"libflate",
|
||||||
"lipsum",
|
"lipsum",
|
||||||
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"regex",
|
"regex",
|
||||||
"route-recognizer",
|
"route-recognizer",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
|
"sealed_test",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -754,14 +767,14 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.42.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
@ -769,19 +782,28 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.14.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "num_threads"
|
||||||
version = "1.16.0"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
@ -791,9 +813,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.4.0"
|
version = "6.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
|
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
@ -813,15 +835,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.4"
|
version = "0.9.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
|
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys 0.42.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -850,18 +872,24 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.47"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quick-error"
|
||||||
version = "1.0.21"
|
version = "1.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@ -907,9 +935,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.7.0"
|
version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -922,6 +950,15 @@ version = "0.6.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remove_dir_all"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.20"
|
version = "0.16.20"
|
||||||
@ -986,9 +1023,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.20.7"
|
version = "0.20.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
|
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
@ -1010,18 +1047,30 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "rusty-forkfork"
|
||||||
version = "1.0.11"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
checksum = "7ce85af4dfa2fb0c0143121ab5e424c71ea693867357c9159b8777b59984c218"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"quick-error",
|
||||||
|
"tempfile",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@ -1034,12 +1083,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
|
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"windows-sys",
|
||||||
"windows-sys 0.36.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1058,6 +1106,28 @@ dependencies = [
|
|||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sealed_test"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a608d94641cc17fe203b102db2ae86d47a236630192f0244ddbbbb0044c0272"
|
||||||
|
dependencies = [
|
||||||
|
"fs_extra",
|
||||||
|
"rusty-forkfork",
|
||||||
|
"sealed_test_derive",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sealed_test_derive"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b672e005ae58fef5da619d90b9f1c5b44b061890f4a371b3c96257a8a15e697"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@ -1083,18 +1153,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.147"
|
version = "1.0.152"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
|
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.147"
|
version = "1.0.152"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
|
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1103,15 +1173,28 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.87"
|
version = "1.0.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
|
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -1171,9 +1254,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.103"
|
version = "1.0.107"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
|
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1181,19 +1264,33 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "tempfile"
|
||||||
version = "1.0.37"
|
version = "3.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"remove_dir_all",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.37"
|
version = "1.0.38"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1207,6 +1304,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
@ -1244,9 +1343,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.21.2"
|
version = "1.24.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
|
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -1259,14 +1358,14 @@ dependencies = [
|
|||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"winapi",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.8.0"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1298,6 +1397,15 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -1326,15 +1434,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.15.0"
|
version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
@ -1353,9 +1461,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
@ -1366,6 +1474,12 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -1389,6 +1503,15 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "waker-fn"
|
name = "waker-fn"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -1527,19 +1650,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_msvc 0.36.1",
|
|
||||||
"windows_i686_gnu 0.36.1",
|
|
||||||
"windows_i686_msvc 0.36.1",
|
|
||||||
"windows_x86_64_gnu 0.36.1",
|
|
||||||
"windows_x86_64_msvc 0.36.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
@ -1547,82 +1657,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc 0.42.0",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu 0.42.0",
|
"windows_i686_gnu",
|
||||||
"windows_i686_msvc 0.42.0",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu 0.42.0",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm",
|
||||||
"windows_x86_64_msvc 0.42.0",
|
"windows_x86_64_msvc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.42.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.36.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.42.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.36.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.42.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.36.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.42.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.42.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
|
||||||
|
31
Cargo.toml
31
Cargo.toml
@ -3,30 +3,39 @@ name = "libreddit"
|
|||||||
description = " Alternative private front-end to Reddit"
|
description = " Alternative private front-end to Reddit"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
repository = "https://github.com/spikecodes/libreddit"
|
repository = "https://github.com/spikecodes/libreddit"
|
||||||
version = "0.24.3"
|
version = "0.29.2"
|
||||||
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
|
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { version = "0.11.1", default-features = false }
|
askama = { version = "0.11.1", default-features = false }
|
||||||
async-recursion = "1.0.0"
|
cached = "0.42.0"
|
||||||
cached = "0.40.0"
|
clap = { version = "4.1.1", default-features = false, features = ["std", "env"] }
|
||||||
clap = { version = "4.0.24", default-features = false, features = ["std"] }
|
regex = "1.7.1"
|
||||||
regex = "1.7.0"
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde = { version = "1.0.147", features = ["derive"] }
|
cookie = "0.16.2"
|
||||||
cookie = "0.16.1"
|
|
||||||
futures-lite = "1.12.0"
|
futures-lite = "1.12.0"
|
||||||
hyper = { version = "0.14.23", features = ["full"] }
|
hyper = { version = "0.14.23", features = ["full"] }
|
||||||
hyper-rustls = "0.23.0"
|
hyper-rustls = "0.23.2"
|
||||||
percent-encoding = "2.2.0"
|
percent-encoding = "2.2.0"
|
||||||
route-recognizer = "0.3.1"
|
route-recognizer = "0.3.1"
|
||||||
serde_json = "1.0.87"
|
serde_json = "1.0.91"
|
||||||
tokio = { version = "1.21.2", features = ["full"] }
|
tokio = { version = "1.24.2", features = ["full"] }
|
||||||
time = "0.3.17"
|
time = { version = "0.3.17", features = ["local-offset"] }
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
||||||
libflate = "1.2.0"
|
libflate = "1.2.0"
|
||||||
brotli = { version = "3.3.4", features = ["std"] }
|
brotli = { version = "3.3.4", features = ["std"] }
|
||||||
|
toml = "0.5.10"
|
||||||
|
once_cell = "1.17.0"
|
||||||
|
serde_yaml = "0.9.16"
|
||||||
|
build_html = "2.2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lipsum = "0.8.2"
|
lipsum = "0.8.2"
|
||||||
|
sealed_test = "1.0.0"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
|
strip = "symbols"
|
||||||
|
52
README.md
52
README.md
@ -39,7 +39,7 @@ Both files are part of the [libreddit-instances](https://github.com/libreddit/li
|
|||||||
|
|
||||||
# About
|
# About
|
||||||
|
|
||||||
Find Libreddit on 💬 [Matrix](https://matrix.to/#/#libreddit:kde.org), 🐋 [Docker](https://hub.docker.com/r/spikecodes/libreddit), :octocat: [GitHub](https://github.com/libreddit/libreddit), and 🦊 [GitLab](https://gitlab.com/spikecodes/libreddit).
|
Find Libreddit on 💬 [Matrix](https://matrix.to/#/#libreddit:kde.org), 🐋 [Docker](https://hub.docker.com/r/libreddit/libreddit), :octocat: [GitHub](https://github.com/libreddit/libreddit), and 🦊 [GitLab](https://gitlab.com/libreddit/libreddit).
|
||||||
|
|
||||||
## Built with
|
## Built with
|
||||||
|
|
||||||
@ -136,21 +136,21 @@ cargo install libreddit
|
|||||||
|
|
||||||
## 2) Docker
|
## 2) Docker
|
||||||
|
|
||||||
Deploy the [Docker image](https://hub.docker.com/r/spikecodes/libreddit) of Libreddit:
|
Deploy the [Docker image](https://hub.docker.com/r/libreddit/libreddit) of Libreddit:
|
||||||
```
|
```
|
||||||
docker pull spikecodes/libreddit
|
docker pull libreddit/libreddit
|
||||||
docker run -d --name libreddit -p 8080:8080 spikecodes/libreddit
|
docker run -d --name libreddit -p 8080:8080 libreddit/libreddit
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy using a different port (in this case, port 80):
|
Deploy using a different port (in this case, port 80):
|
||||||
```
|
```
|
||||||
docker pull spikecodes/libreddit
|
docker pull libreddit/libreddit
|
||||||
docker run -d --name libreddit -p 80:8080 spikecodes/libreddit
|
docker run -d --name libreddit -p 80:8080 libreddit/libreddit
|
||||||
```
|
```
|
||||||
|
|
||||||
To deploy on `arm64` platforms, simply replace `spikecodes/libreddit` in the commands above with `spikecodes/libreddit:arm`.
|
To deploy on `arm64` platforms, simply replace `libreddit/libreddit` in the commands above with `libreddit/libreddit:arm`.
|
||||||
|
|
||||||
To deploy on `armv7` platforms, simply replace `spikecodes/libreddit` in the commands above with `spikecodes/libreddit:armv7`.
|
To deploy on `armv7` platforms, simply replace `libreddit/libreddit` in the commands above with `libreddit/libreddit:armv7`.
|
||||||
|
|
||||||
## 3) AUR
|
## 3) AUR
|
||||||
|
|
||||||
@ -166,7 +166,8 @@ If you're on Linux and none of these methods work for you, you can grab a Linux
|
|||||||
|
|
||||||
## 5) Replit/Heroku/Glitch
|
## 5) Replit/Heroku/Glitch
|
||||||
|
|
||||||
**Note:** These are free hosting options but they are *not* private and will monitor server usage to prevent abuse. If you need a free and easy setup, this method may work best for you.
|
> **Warning**
|
||||||
|
> These are free hosting options but they are *not* private and will monitor server usage to prevent abuse. If you need a free and easy setup, this method may work best for you.
|
||||||
|
|
||||||
<a href="https://repl.it/github/libreddit/libreddit"><img src="https://repl.it/badge/github/libreddit/libreddit" alt="Run on Repl.it" height="32" /></a>
|
<a href="https://repl.it/github/libreddit/libreddit"><img src="https://repl.it/badge/github/libreddit/libreddit" alt="Run on Repl.it" height="32" /></a>
|
||||||
[](https://heroku.com/deploy?template=https://github.com/libreddit/libreddit)
|
[](https://heroku.com/deploy?template=https://github.com/libreddit/libreddit)
|
||||||
@ -182,9 +183,18 @@ Once installed, deploy Libreddit to `0.0.0.0:8080` by running:
|
|||||||
libreddit
|
libreddit
|
||||||
```
|
```
|
||||||
|
|
||||||
## Change Default Settings
|
## Instance settings
|
||||||
|
|
||||||
Assign a default value for each setting by passing environment variables to Libreddit in the format `LIBREDDIT_DEFAULT_{X}`. Replace `{X}` with the setting name (see list below) in capital letters.
|
Assign a default value for each instance-specific setting by passing environment variables to Libreddit in the format `LIBREDDIT_{X}`. Replace `{X}` with the setting name (see list below) in capital letters.
|
||||||
|
|
||||||
|
|Name|Possible values|Default value|Description|
|
||||||
|
|-|-|-|-|
|
||||||
|
| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. |
|
||||||
|
| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. |
|
||||||
|
|
||||||
|
## Default User Settings
|
||||||
|
|
||||||
|
Assign a default value for each user-modifiable setting by passing environment variables to Libreddit in the format `LIBREDDIT_DEFAULT_{Y}`. Replace `{Y}` with the setting name (see list below) in capital letters.
|
||||||
|
|
||||||
| Name | Possible values | Default value |
|
| Name | Possible values | Default value |
|
||||||
|-------------------------|-----------------------------------------------------------------------------------------------------|---------------|
|
|-------------------------|-----------------------------------------------------------------------------------------------------|---------------|
|
||||||
@ -199,6 +209,15 @@ Assign a default value for each setting by passing environment variables to Libr
|
|||||||
| `USE_HLS` | `["on", "off"]` | `off` |
|
| `USE_HLS` | `["on", "off"]` | `off` |
|
||||||
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
|
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
|
||||||
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
|
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
|
||||||
|
| `HIDE_AWARDS` | `["on", "off"]` | `off`
|
||||||
|
| `DISABLE_VISIT_REDDIT_CONFIRMATION` | `["on", "off"]` | `off` |
|
||||||
|
|
||||||
|
You can also configure Libreddit with a configuration file. An example `libreddit.toml` can be found below:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
LIBREDDIT_DEFAULT_WIDE = "on"
|
||||||
|
LIBREDDIT_DEFAULT_USE_HLS = "on"
|
||||||
|
```
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@ -212,11 +231,12 @@ LIBREDDIT_DEFAULT_WIDE=on LIBREDDIT_DEFAULT_THEME=dark libreddit -r
|
|||||||
|
|
||||||
## Proxying using NGINX
|
## Proxying using NGINX
|
||||||
|
|
||||||
**NOTE** If you're [proxying Libreddit through an NGINX Reverse Proxy](https://github.com/libreddit/libreddit/issues/122#issuecomment-782226853), add
|
> **Note**
|
||||||
```nginx
|
> If you're [proxying Libreddit through an NGINX Reverse Proxy](https://github.com/libreddit/libreddit/issues/122#issuecomment-782226853), add
|
||||||
proxy_http_version 1.1;
|
> ```nginx
|
||||||
```
|
> proxy_http_version 1.1;
|
||||||
to your NGINX configuration file above your `proxy_pass` line.
|
> ```
|
||||||
|
> to your NGINX configuration file above your `proxy_pass` line.
|
||||||
|
|
||||||
## systemd
|
## systemd
|
||||||
|
|
||||||
|
12
app.json
12
app.json
@ -40,6 +40,18 @@
|
|||||||
},
|
},
|
||||||
"LIBREDDIT_HIDE_HLS_NOTIFICATION": {
|
"LIBREDDIT_HIDE_HLS_NOTIFICATION": {
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
"LIBREDDIT_SFW_ONLY": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"LIBREDDIT_DEFAULT_HIDE_AWARDS": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"LIBREDDIT_BANNER": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION": {
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
build.rs
Normal file
20
build.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use std::{
|
||||||
|
os::unix::process::ExitStatusExt,
|
||||||
|
process::{Command, ExitStatus, Output},
|
||||||
|
};
|
||||||
|
fn main() {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
Command::new("git")
|
||||||
|
.args(["rev-parse", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.unwrap_or(Output {
|
||||||
|
stdout: vec![],
|
||||||
|
stderr: vec![],
|
||||||
|
status: ExitStatus::from_raw(0),
|
||||||
|
})
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let git_hash = if output == String::default() { "dev".into() } else { output };
|
||||||
|
println!("cargo:rustc-env=GIT_HASH={git_hash}");
|
||||||
|
}
|
144
src/config.rs
Normal file
144
src/config.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{env::var, fs::read_to_string};
|
||||||
|
|
||||||
|
// Waiting for https://github.com/rust-lang/rust/issues/74465 to land, so we
|
||||||
|
// can reduce reliance on once_cell.
|
||||||
|
//
|
||||||
|
// This is the local static that is initialized at runtime (technically at
|
||||||
|
// first request) and contains the instance settings.
|
||||||
|
pub(crate) static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
||||||
|
|
||||||
|
/// Stores the configuration parsed from the environment variables and the
|
||||||
|
/// config file. `Config::Default()` contains None for each setting.
|
||||||
|
/// When adding more config settings, add it to `Config::load`,
|
||||||
|
/// `get_setting_from_config`, both below, as well as
|
||||||
|
/// instance_info::InstanceInfo.to_string(), README.md and app.json.
|
||||||
|
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(rename = "LIBREDDIT_SFW_ONLY")]
|
||||||
|
pub(crate) sfw_only: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_THEME")]
|
||||||
|
pub(crate) default_theme: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
||||||
|
pub(crate) default_front_page: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_LAYOUT")]
|
||||||
|
pub(crate) default_layout: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_WIDE")]
|
||||||
|
pub(crate) default_wide: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_COMMENT_SORT")]
|
||||||
|
pub(crate) default_comment_sort: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_POST_SORT")]
|
||||||
|
pub(crate) default_post_sort: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
||||||
|
pub(crate) default_show_nsfw: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_BLUR_NSFW")]
|
||||||
|
pub(crate) default_blur_nsfw: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_USE_HLS")]
|
||||||
|
pub(crate) default_use_hls: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
||||||
|
pub(crate) default_hide_hls_notification: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_DEFAULT_HIDE_AWARDS")]
|
||||||
|
pub(crate) default_hide_awards: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "LIBREDDIT_BANNER")]
|
||||||
|
pub(crate) banner: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Load the configuration from the environment variables and the config file.
|
||||||
|
/// In the case that there are no environment variables set and there is no
|
||||||
|
/// config file, this function returns a Config that contains all None values.
|
||||||
|
pub fn load() -> Self {
|
||||||
|
// Read from libreddit.toml config file. If for any reason, it fails, the
|
||||||
|
// default `Config` is used (all None values)
|
||||||
|
let config: Config = toml::from_str(&read_to_string("libreddit.toml").unwrap_or_default()).unwrap_or_default();
|
||||||
|
// This function defines the order of preference - first check for
|
||||||
|
// environment variables with "LIBREDDIT", then check the config, then if
|
||||||
|
// both are `None`, return a `None` via the `map_or_else` function
|
||||||
|
let parse = |key: &str| -> Option<String> { var(key).ok().map_or_else(|| get_setting_from_config(key, &config), Some) };
|
||||||
|
Self {
|
||||||
|
sfw_only: parse("LIBREDDIT_SFW_ONLY"),
|
||||||
|
default_theme: parse("LIBREDDIT_DEFAULT_THEME"),
|
||||||
|
default_front_page: parse("LIBREDDIT_DEFAULT_FRONT_PAGE"),
|
||||||
|
default_layout: parse("LIBREDDIT_DEFAULT_LAYOUT"),
|
||||||
|
default_post_sort: parse("LIBREDDIT_DEFAULT_POST_SORT"),
|
||||||
|
default_wide: parse("LIBREDDIT_DEFAULT_WIDE"),
|
||||||
|
default_comment_sort: parse("LIBREDDIT_DEFAULT_COMMENT_SORT"),
|
||||||
|
default_show_nsfw: parse("LIBREDDIT_DEFAULT_SHOW_NSFW"),
|
||||||
|
default_blur_nsfw: parse("LIBREDDIT_DEFAULT_BLUR_NSFW"),
|
||||||
|
default_use_hls: parse("LIBREDDIT_DEFAULT_USE_HLS"),
|
||||||
|
default_hide_hls_notification: parse("LIBREDDIT_DEFAULT_HIDE_HLS"),
|
||||||
|
default_hide_awards: parse("LIBREDDIT_DEFAULT_HIDE_AWARDS"),
|
||||||
|
banner: parse("LIBREDDIT_BANNER"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
||||||
|
match name {
|
||||||
|
"LIBREDDIT_SFW_ONLY" => config.sfw_only.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_THEME" => config.default_theme.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_FRONT_PAGE" => config.default_front_page.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_LAYOUT" => config.default_layout.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_COMMENT_SORT" => config.default_comment_sort.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_POST_SORT" => config.default_post_sort.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_SHOW_NSFW" => config.default_show_nsfw.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_BLUR_NSFW" => config.default_blur_nsfw.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_USE_HLS" => config.default_use_hls.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION" => config.default_hide_hls_notification.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_WIDE" => config.default_wide.clone(),
|
||||||
|
"LIBREDDIT_DEFAULT_HIDE_AWARDS" => config.default_hide_awards.clone(),
|
||||||
|
"LIBREDDIT_BANNER" => config.banner.clone(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves setting from environment variable or config file.
|
||||||
|
pub(crate) fn get_setting(name: &str) -> Option<String> {
|
||||||
|
get_setting_from_config(name, &CONFIG)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use {sealed_test::prelude::*, std::fs::write};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[sealed_test(env = [("LIBREDDIT_SFW_ONLY", "on")])]
|
||||||
|
fn test_env_var() {
|
||||||
|
assert!(crate::utils::sfw_only())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[sealed_test]
|
||||||
|
fn test_config() {
|
||||||
|
let config_to_write = r#"LIBREDDIT_DEFAULT_COMMENT_SORT = "best""#;
|
||||||
|
write("libreddit.toml", config_to_write).unwrap();
|
||||||
|
assert_eq!(get_setting("LIBREDDIT_DEFAULT_COMMENT_SORT"), Some("best".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[sealed_test(env = [("LIBREDDIT_DEFAULT_COMMENT_SORT", "top")])]
|
||||||
|
fn test_env_config_precedence() {
|
||||||
|
let config_to_write = r#"LIBREDDIT_DEFAULT_COMMENT_SORT = "best""#;
|
||||||
|
write("libreddit.toml", config_to_write).unwrap();
|
||||||
|
assert_eq!(get_setting("LIBREDDIT_DEFAULT_COMMENT_SORT"), Some("top".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[sealed_test(env = [("LIBREDDIT_DEFAULT_COMMENT_SORT", "top")])]
|
||||||
|
fn test_alt_env_config_precedence() {
|
||||||
|
let config_to_write = r#"LIBREDDIT_DEFAULT_COMMENT_SORT = "best""#;
|
||||||
|
write("libreddit.toml", config_to_write).unwrap();
|
||||||
|
assert_eq!(get_setting("LIBREDDIT_DEFAULT_COMMENT_SORT"), Some("top".into()))
|
||||||
|
}
|
236
src/duplicates.rs
Normal file
236
src/duplicates.rs
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Handler for post duplicates.
|
||||||
|
|
||||||
|
use crate::client::json;
|
||||||
|
use crate::server::RequestExt;
|
||||||
|
use crate::subreddit::{can_access_quarantine, quarantine};
|
||||||
|
use crate::utils::{error, filter_posts, get_filters, nsfw_landing, parse_post, setting, template, Post, Preferences};
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
use hyper::{Body, Request, Response};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::borrow::ToOwned;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
/// DuplicatesParams contains the parameters in the URL.
|
||||||
|
struct DuplicatesParams {
|
||||||
|
before: String,
|
||||||
|
after: String,
|
||||||
|
sort: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DuplicatesTemplate defines an Askama template for rendering duplicate
|
||||||
|
/// posts.
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "duplicates.html")]
|
||||||
|
struct DuplicatesTemplate {
|
||||||
|
/// params contains the relevant request parameters.
|
||||||
|
params: DuplicatesParams,
|
||||||
|
|
||||||
|
/// post is the post whose ID is specified in the reqeust URL. Note that
|
||||||
|
/// this is not necessarily the "original" post.
|
||||||
|
post: Post,
|
||||||
|
|
||||||
|
/// duplicates is the list of posts that, per Reddit, are duplicates of
|
||||||
|
/// Post above.
|
||||||
|
duplicates: Vec<Post>,
|
||||||
|
|
||||||
|
/// prefs are the user preferences.
|
||||||
|
prefs: Preferences,
|
||||||
|
|
||||||
|
/// url is the request URL.
|
||||||
|
url: String,
|
||||||
|
|
||||||
|
/// num_posts_filtered counts how many posts were filtered from the
|
||||||
|
/// duplicates list.
|
||||||
|
num_posts_filtered: u64,
|
||||||
|
|
||||||
|
/// all_posts_filtered is true if every duplicate was filtered. This is an
|
||||||
|
/// edge case but can still happen.
|
||||||
|
all_posts_filtered: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make the GET request to Reddit. It assumes `req` is the appropriate Reddit
|
||||||
|
/// REST endpoint for enumerating post duplicates.
|
||||||
|
pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
|
let path: String = format!("{}.json?{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default());
|
||||||
|
let sub = req.param("sub").unwrap_or_default();
|
||||||
|
let quarantined = can_access_quarantine(&req, &sub);
|
||||||
|
|
||||||
|
// Log the request in debugging mode
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
dbg!(req.param("id").unwrap_or_default());
|
||||||
|
|
||||||
|
// Send the GET, and await JSON.
|
||||||
|
match json(path, quarantined).await {
|
||||||
|
// Process response JSON.
|
||||||
|
Ok(response) => {
|
||||||
|
let post = parse_post(&response[0]["data"]["children"][0]).await;
|
||||||
|
|
||||||
|
// Return landing page if this post if this Reddit deems this post
|
||||||
|
// NSFW, but we have also disabled the display of NSFW content
|
||||||
|
// or if the instance is SFW-only.
|
||||||
|
if post.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) {
|
||||||
|
return Ok(nsfw_landing(req).await.unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let filters = get_filters(&req);
|
||||||
|
let (duplicates, num_posts_filtered, all_posts_filtered) = parse_duplicates(&response[1], &filters).await;
|
||||||
|
|
||||||
|
// These are the values for the "before=", "after=", and "sort="
|
||||||
|
// query params, respectively.
|
||||||
|
let mut before: String = String::new();
|
||||||
|
let mut after: String = String::new();
|
||||||
|
let mut sort: String = String::new();
|
||||||
|
|
||||||
|
// FIXME: We have to perform a kludge to work around a Reddit API
|
||||||
|
// bug.
|
||||||
|
//
|
||||||
|
// The JSON object in "data" will never contain a "before" value so
|
||||||
|
// it is impossible to use it to determine our position in a
|
||||||
|
// listing. We'll make do by getting the ID of the first post in
|
||||||
|
// the listing, setting that as our "before" value, and ask Reddit
|
||||||
|
// to give us a batch of duplicate posts up to that post.
|
||||||
|
//
|
||||||
|
// Likewise, if we provide a "before" request in the GET, the
|
||||||
|
// result won't have an "after" in the JSON, in addition to missing
|
||||||
|
// the "before." So we will have to use the final post in the list
|
||||||
|
// of duplicates.
|
||||||
|
//
|
||||||
|
// That being said, we'll also need to capture the value of the
|
||||||
|
// "sort=" parameter as well, so we will need to inspect the
|
||||||
|
// query key-value pairs anyway.
|
||||||
|
let l = duplicates.len();
|
||||||
|
if l > 0 {
|
||||||
|
// This gets set to true if "before=" is one of the GET params.
|
||||||
|
let mut have_before: bool = false;
|
||||||
|
|
||||||
|
// This gets set to true if "after=" is one of the GET params.
|
||||||
|
let mut have_after: bool = false;
|
||||||
|
|
||||||
|
// Inspect the query key-value pairs. We will need to record
|
||||||
|
// the value of "sort=", along with checking to see if either
|
||||||
|
// one of "before=" or "after=" are given.
|
||||||
|
//
|
||||||
|
// If we're in the middle of the batch (evidenced by the
|
||||||
|
// presence of a "before=" or "after=" parameter in the GET),
|
||||||
|
// then use the first post as the "before" reference.
|
||||||
|
//
|
||||||
|
// We'll do this iteratively. Better than with .map_or()
|
||||||
|
// since a closure will continue to operate on remaining
|
||||||
|
// elements even after we've determined one of "before=" or
|
||||||
|
// "after=" (or both) are in the GET request.
|
||||||
|
//
|
||||||
|
// In practice, here should only ever be one of "before=" or
|
||||||
|
// "after=" and never both.
|
||||||
|
let query_str = req.uri().query().unwrap_or_default().to_string();
|
||||||
|
|
||||||
|
if !query_str.is_empty() {
|
||||||
|
for param in query_str.split('&') {
|
||||||
|
let kv: Vec<&str> = param.split('=').collect();
|
||||||
|
if kv.len() < 2 {
|
||||||
|
// Reject invalid query parameter.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key: &str = kv[0];
|
||||||
|
match key {
|
||||||
|
"before" => have_before = true,
|
||||||
|
"after" => have_after = true,
|
||||||
|
"sort" => {
|
||||||
|
let val: &str = kv[1];
|
||||||
|
match val {
|
||||||
|
"new" | "num_comments" => sort = val.to_string(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if have_after {
|
||||||
|
before = "t3_".to_owned();
|
||||||
|
before.push_str(&duplicates[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address potentially missing "after". If "before=" is in the
|
||||||
|
// GET, then "after" will be null in the JSON (see FIXME
|
||||||
|
// above).
|
||||||
|
if have_before {
|
||||||
|
// The next batch will need to start from one after the
|
||||||
|
// last post in the current batch.
|
||||||
|
after = "t3_".to_owned();
|
||||||
|
after.push_str(&duplicates[l - 1].id);
|
||||||
|
|
||||||
|
// Here is where things get terrible. Notice that we
|
||||||
|
// haven't set `before`. In order to do so, we will
|
||||||
|
// need to know if there is a batch that exists before
|
||||||
|
// this one, and doing so requires actually fetching the
|
||||||
|
// previous batch. In other words, we have to do yet one
|
||||||
|
// more GET to Reddit. There is no other way to determine
|
||||||
|
// whether or not to define `before`.
|
||||||
|
//
|
||||||
|
// We'll mitigate that by requesting at most one duplicate.
|
||||||
|
let new_path: String = format!(
|
||||||
|
"{}.json?before=t3_{}&sort={}&limit=1&raw_json=1",
|
||||||
|
req.uri().path(),
|
||||||
|
&duplicates[0].id,
|
||||||
|
if sort.is_empty() { "num_comments".to_string() } else { sort.clone() }
|
||||||
|
);
|
||||||
|
match json(new_path, true).await {
|
||||||
|
Ok(response) => {
|
||||||
|
if !response[1]["data"]["children"].as_array().unwrap_or(&Vec::new()).is_empty() {
|
||||||
|
before = "t3_".to_owned();
|
||||||
|
before.push_str(&duplicates[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(msg) => {
|
||||||
|
// Abort entirely if we couldn't get the previous
|
||||||
|
// batch.
|
||||||
|
return error(req, msg).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
after = response[1]["data"]["after"].as_str().unwrap_or_default().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let url = req.uri().to_string();
|
||||||
|
|
||||||
|
template(DuplicatesTemplate {
|
||||||
|
params: DuplicatesParams { before, after, sort },
|
||||||
|
post,
|
||||||
|
duplicates,
|
||||||
|
prefs: Preferences::new(&req),
|
||||||
|
url,
|
||||||
|
num_posts_filtered,
|
||||||
|
all_posts_filtered,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process error.
|
||||||
|
Err(msg) => {
|
||||||
|
if msg == "quarantined" {
|
||||||
|
let sub = req.param("sub").unwrap_or_default();
|
||||||
|
quarantine(req, sub)
|
||||||
|
} else {
|
||||||
|
error(req, msg).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DUPLICATES
|
||||||
|
async fn parse_duplicates(json: &serde_json::Value, filters: &HashSet<String>) -> (Vec<Post>, u64, bool) {
|
||||||
|
let post_duplicates: &Vec<Value> = &json["data"]["children"].as_array().map_or(Vec::new(), ToOwned::to_owned);
|
||||||
|
let mut duplicates: Vec<Post> = Vec::new();
|
||||||
|
|
||||||
|
// Process each post and place them in the Vec<Post>.
|
||||||
|
for val in post_duplicates.iter() {
|
||||||
|
let post: Post = parse_post(val).await;
|
||||||
|
duplicates.push(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (num_posts_filtered, all_posts_filtered) = filter_posts(&mut duplicates, filters);
|
||||||
|
(duplicates, num_posts_filtered, all_posts_filtered)
|
||||||
|
}
|
205
src/instance_info.rs
Normal file
205
src/instance_info.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
use crate::{
|
||||||
|
config::{Config, CONFIG},
|
||||||
|
server::RequestExt,
|
||||||
|
utils::{ErrorTemplate, Preferences},
|
||||||
|
};
|
||||||
|
use askama::Template;
|
||||||
|
use build_html::{Container, Html, HtmlContainer, Table};
|
||||||
|
use hyper::{http::Error, Body, Request, Response};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
// This is the local static that is intialized at runtime (technically at
|
||||||
|
// the first request to the info endpoint) and contains the data
|
||||||
|
// retrieved from the info endpoint.
|
||||||
|
pub(crate) static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
|
||||||
|
|
||||||
|
/// Handles instance info endpoint
|
||||||
|
pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
|
// This will retrieve the extension given, or create a new string - which will
|
||||||
|
// simply become the last option, an HTML page.
|
||||||
|
let extension = req.param("extension").unwrap_or(String::new());
|
||||||
|
let response = match extension.as_str() {
|
||||||
|
"yaml" | "yml" => info_yaml(),
|
||||||
|
"txt" => info_txt(),
|
||||||
|
"json" => info_json(),
|
||||||
|
"html" | "" => info_html(req),
|
||||||
|
_ => {
|
||||||
|
let error = ErrorTemplate {
|
||||||
|
msg: "Error: Invalid info extension".into(),
|
||||||
|
prefs: Preferences::new(&req),
|
||||||
|
url: req.uri().to_string(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap();
|
||||||
|
Response::builder().status(404).header("content-type", "text/html; charset=utf-8").body(error.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
response.map_err(|err| format!("{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_json() -> Result<Response<Body>, Error> {
|
||||||
|
if let Ok(body) = serde_json::to_string(&*INSTANCE_INFO) {
|
||||||
|
Response::builder().status(200).header("content-type", "application/json").body(body.into())
|
||||||
|
} else {
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body(Body::from("Error serializing JSON"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_yaml() -> Result<Response<Body>, Error> {
|
||||||
|
if let Ok(body) = serde_yaml::to_string(&*INSTANCE_INFO) {
|
||||||
|
// We can use `application/yaml` as media type, though there is no guarantee
|
||||||
|
// that browsers will honor it. But we'll do it anyway. See:
|
||||||
|
// https://github.com/ietf-wg-httpapi/mediatypes/blob/main/draft-ietf-httpapi-yaml-mediatypes.md#media-type-applicationyaml-application-yaml
|
||||||
|
Response::builder().status(200).header("content-type", "application/yaml").body(body.into())
|
||||||
|
} else {
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body(Body::from("Error serializing YAML."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_txt() -> Result<Response<Body>, Error> {
|
||||||
|
Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body(Body::from(INSTANCE_INFO.to_string(StringType::Raw)))
|
||||||
|
}
|
||||||
|
fn info_html(req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||||
|
let message = MessageTemplate {
|
||||||
|
title: String::from("Instance information"),
|
||||||
|
body: INSTANCE_INFO.to_string(StringType::Html),
|
||||||
|
prefs: Preferences::new(&req),
|
||||||
|
url: req.uri().to_string(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap();
|
||||||
|
Response::builder().status(200).header("content-type", "text/html; charset=utf8").body(Body::from(message))
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub(crate) struct InstanceInfo {
|
||||||
|
crate_version: String,
|
||||||
|
git_commit: String,
|
||||||
|
deploy_date: String,
|
||||||
|
compile_mode: String,
|
||||||
|
deploy_unix_ts: i64,
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceInfo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
crate_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
git_commit: env!("GIT_HASH").to_string(),
|
||||||
|
deploy_date: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()).to_string(),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
compile_mode: "Debug".into(),
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
compile_mode: "Release".into(),
|
||||||
|
deploy_unix_ts: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()).unix_timestamp(),
|
||||||
|
config: CONFIG.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_table(&self) -> String {
|
||||||
|
let mut container = Container::default();
|
||||||
|
let convert = |o: &Option<String>| -> String { o.clone().unwrap_or("<span class=\"unset\"><i>Unset</i></span>".to_owned()) };
|
||||||
|
if let Some(banner) = &self.config.banner {
|
||||||
|
container.add_header(3, "Instance banner");
|
||||||
|
container.add_raw("<br />");
|
||||||
|
container.add_paragraph(banner);
|
||||||
|
container.add_raw("<br />");
|
||||||
|
}
|
||||||
|
container.add_table(
|
||||||
|
Table::from([
|
||||||
|
["Crate version", &self.crate_version],
|
||||||
|
["Git commit", &self.git_commit],
|
||||||
|
["Deploy date", &self.deploy_date],
|
||||||
|
["Deploy timestamp", &self.deploy_unix_ts.to_string()],
|
||||||
|
["Compile mode", &self.compile_mode],
|
||||||
|
["SFW only", &convert(&self.config.sfw_only)],
|
||||||
|
])
|
||||||
|
.with_header_row(["Settings"]),
|
||||||
|
);
|
||||||
|
container.add_raw("<br />");
|
||||||
|
container.add_table(
|
||||||
|
Table::from([
|
||||||
|
["Hide awards", &convert(&self.config.default_hide_awards)],
|
||||||
|
["Theme", &convert(&self.config.default_theme)],
|
||||||
|
["Front page", &convert(&self.config.default_front_page)],
|
||||||
|
["Layout", &convert(&self.config.default_layout)],
|
||||||
|
["Wide", &convert(&self.config.default_wide)],
|
||||||
|
["Comment sort", &convert(&self.config.default_comment_sort)],
|
||||||
|
["Post sort", &convert(&self.config.default_post_sort)],
|
||||||
|
["Show NSFW", &convert(&self.config.default_show_nsfw)],
|
||||||
|
["Blur NSFW", &convert(&self.config.default_blur_nsfw)],
|
||||||
|
["Use HLS", &convert(&self.config.default_use_hls)],
|
||||||
|
["Hide HLS notification", &convert(&self.config.default_hide_hls_notification)],
|
||||||
|
])
|
||||||
|
.with_header_row(["Default preferences"]),
|
||||||
|
);
|
||||||
|
container.to_html_string().replace("<th>", "<th colspan=\"2\">")
|
||||||
|
}
|
||||||
|
fn to_string(&self, string_type: StringType) -> String {
|
||||||
|
match string_type {
|
||||||
|
StringType::Raw => {
|
||||||
|
format!(
|
||||||
|
"Crate version: {}\n
|
||||||
|
Git commit: {}\n
|
||||||
|
Deploy date: {}\n
|
||||||
|
Deploy timestamp: {}\n
|
||||||
|
Compile mode: {}\n
|
||||||
|
Config:\n
|
||||||
|
Banner: {:?}\n
|
||||||
|
Hide awards: {:?}\n
|
||||||
|
SFW only: {:?}\n
|
||||||
|
Default theme: {:?}\n
|
||||||
|
Default front page: {:?}\n
|
||||||
|
Default layout: {:?}\n
|
||||||
|
Default wide: {:?}\n
|
||||||
|
Default comment sort: {:?}\n
|
||||||
|
Default post sort: {:?}\n
|
||||||
|
Default show NSFW: {:?}\n
|
||||||
|
Default blur NSFW: {:?}\n
|
||||||
|
Default use HLS: {:?}\n
|
||||||
|
Default hide HLS notification: {:?}\n",
|
||||||
|
self.crate_version,
|
||||||
|
self.git_commit,
|
||||||
|
self.deploy_date,
|
||||||
|
self.deploy_unix_ts,
|
||||||
|
self.compile_mode,
|
||||||
|
self.config.banner,
|
||||||
|
self.config.default_hide_awards,
|
||||||
|
self.config.sfw_only,
|
||||||
|
self.config.default_theme,
|
||||||
|
self.config.default_front_page,
|
||||||
|
self.config.default_layout,
|
||||||
|
self.config.default_wide,
|
||||||
|
self.config.default_comment_sort,
|
||||||
|
self.config.default_post_sort,
|
||||||
|
self.config.default_show_nsfw,
|
||||||
|
self.config.default_blur_nsfw,
|
||||||
|
self.config.default_use_hls,
|
||||||
|
self.config.default_hide_hls_notification
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StringType::Html => self.to_table(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum StringType {
|
||||||
|
Raw,
|
||||||
|
Html,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "message.html")]
|
||||||
|
struct MessageTemplate {
|
||||||
|
title: String,
|
||||||
|
body: String,
|
||||||
|
prefs: Preferences,
|
||||||
|
url: String,
|
||||||
|
}
|
32
src/main.rs
32
src/main.rs
@ -3,6 +3,9 @@
|
|||||||
#![allow(clippy::cmp_owned)]
|
#![allow(clippy::cmp_owned)]
|
||||||
|
|
||||||
// Reference local files
|
// Reference local files
|
||||||
|
mod config;
|
||||||
|
mod duplicates;
|
||||||
|
mod instance_info;
|
||||||
mod post;
|
mod post;
|
||||||
mod search;
|
mod search;
|
||||||
mod settings;
|
mod settings;
|
||||||
@ -11,13 +14,14 @@ mod user;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
// Import Crates
|
// Import Crates
|
||||||
use clap::{Arg, Command};
|
use clap::{Arg, ArgAction, Command};
|
||||||
|
|
||||||
use futures_lite::FutureExt;
|
use futures_lite::FutureExt;
|
||||||
use hyper::{header::HeaderValue, Body, Request, Response};
|
use hyper::{header::HeaderValue, Body, Request, Response};
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
use client::{canonical_path, proxy};
|
use client::{canonical_path, proxy};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use server::RequestExt;
|
use server::RequestExt;
|
||||||
use utils::{error, redirect, ThemeAssets};
|
use utils::{error, redirect, ThemeAssets};
|
||||||
|
|
||||||
@ -128,8 +132,10 @@ async fn main() {
|
|||||||
.short('p')
|
.short('p')
|
||||||
.long("port")
|
.long("port")
|
||||||
.value_name("PORT")
|
.value_name("PORT")
|
||||||
|
.env("PORT")
|
||||||
.help("Port to listen on")
|
.help("Port to listen on")
|
||||||
.default_value("8080")
|
.default_value("8080")
|
||||||
|
.action(ArgAction::Set)
|
||||||
.num_args(1),
|
.num_args(1),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -143,17 +149,24 @@ async fn main() {
|
|||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let address = matches.get_one("address").map(|m: &String| m.as_str()).unwrap_or("0.0.0.0");
|
let address = matches.get_one::<String>("address").unwrap();
|
||||||
let port = std::env::var("PORT").unwrap_or_else(|_| matches.get_one("port").map(|m: &String| m.as_str()).unwrap_or("8080").to_string());
|
let port = matches.get_one::<String>("port").unwrap();
|
||||||
let hsts = matches.get_one("hsts").map(|m: &String| m.as_str());
|
let hsts = matches.get_one("hsts").map(|m: &String| m.as_str());
|
||||||
|
|
||||||
let listener = [address, ":", &port].concat();
|
let listener = [address, ":", port].concat();
|
||||||
|
|
||||||
println!("Starting Libreddit...");
|
println!("Starting Libreddit...");
|
||||||
|
|
||||||
// Begin constructing a server
|
// Begin constructing a server
|
||||||
let mut app = server::Server::new();
|
let mut app = server::Server::new();
|
||||||
|
|
||||||
|
// Force evaluation of statics. In instance_info case, we need to evaluate
|
||||||
|
// the timestamp so deploy date is accurate - in config case, we need to
|
||||||
|
// evaluate the configuration to avoid paying penalty at first request.
|
||||||
|
|
||||||
|
Lazy::force(&config::CONFIG);
|
||||||
|
Lazy::force(&instance_info::INSTANCE_INFO);
|
||||||
|
|
||||||
// Define default headers (added to all responses)
|
// Define default headers (added to all responses)
|
||||||
app.default_headers = headers! {
|
app.default_headers = headers! {
|
||||||
"Referrer-Policy" => "no-referrer",
|
"Referrer-Policy" => "no-referrer",
|
||||||
@ -244,6 +257,11 @@ async fn main() {
|
|||||||
app.at("/comments/:id/:title").get(|r| post::item(r).boxed());
|
app.at("/comments/:id/:title").get(|r| post::item(r).boxed());
|
||||||
app.at("/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
app.at("/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
||||||
|
|
||||||
|
app.at("/r/:sub/duplicates/:id").get(|r| duplicates::item(r).boxed());
|
||||||
|
app.at("/r/:sub/duplicates/:id/:title").get(|r| duplicates::item(r).boxed());
|
||||||
|
app.at("/duplicates/:id").get(|r| duplicates::item(r).boxed());
|
||||||
|
app.at("/duplicates/:id/:title").get(|r| duplicates::item(r).boxed());
|
||||||
|
|
||||||
app.at("/r/:sub/search").get(|r| search::find(r).boxed());
|
app.at("/r/:sub/search").get(|r| search::find(r).boxed());
|
||||||
|
|
||||||
app
|
app
|
||||||
@ -276,6 +294,10 @@ async fn main() {
|
|||||||
// Handle about pages
|
// Handle about pages
|
||||||
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
||||||
|
|
||||||
|
// Instance info page
|
||||||
|
app.at("/info").get(|r| instance_info::instance_info(r).boxed());
|
||||||
|
app.at("/info.:extension").get(|r| instance_info::instance_info(r).boxed());
|
||||||
|
|
||||||
app.at("/:id").get(|req: Request<Body>| {
|
app.at("/:id").get(|req: Request<Body>| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match req.param("id").as_deref() {
|
match req.param("id").as_deref() {
|
||||||
@ -283,7 +305,7 @@ async fn main() {
|
|||||||
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
|
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
|
||||||
|
|
||||||
// Short link for post
|
// Short link for post
|
||||||
Some(id) if (5..7).contains(&id.len()) => match canonical_path(format!("/{}", id)).await {
|
Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{}", id)).await {
|
||||||
Ok(path_opt) => match path_opt {
|
Ok(path_opt) => match path_opt {
|
||||||
Some(path) => Ok(redirect(path)),
|
Some(path) => Ok(redirect(path)),
|
||||||
None => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await,
|
None => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await,
|
||||||
|
108
src/post.rs
108
src/post.rs
@ -3,7 +3,7 @@ use crate::client::json;
|
|||||||
use crate::server::RequestExt;
|
use crate::server::RequestExt;
|
||||||
use crate::subreddit::{can_access_quarantine, quarantine};
|
use crate::subreddit::{can_access_quarantine, quarantine};
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
error, format_num, format_url, get_filters, param, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flags, Flair, FlairPart, Media, Post, Preferences,
|
error, format_num, get_filters, nsfw_landing, param, parse_post, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flair, FlairPart, Post, Preferences,
|
||||||
};
|
};
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
|
|
||||||
@ -54,8 +54,16 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
// Otherwise, grab the JSON output from the request
|
// Otherwise, grab the JSON output from the request
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Parse the JSON into Post and Comment structs
|
// Parse the JSON into Post and Comment structs
|
||||||
let post = parse_post(&response[0]).await;
|
let post = parse_post(&response[0]["data"]["children"][0]).await;
|
||||||
let comments = parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req));
|
|
||||||
|
// Return landing page if this post if this Reddit deems this post
|
||||||
|
// NSFW, but we have also disabled the display of NSFW content
|
||||||
|
// or if the instance is SFW-only.
|
||||||
|
if post.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) {
|
||||||
|
return Ok(nsfw_landing(req).await.unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let comments = parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req), &req);
|
||||||
let url = req.uri().to_string();
|
let url = req.uri().to_string();
|
||||||
|
|
||||||
// Use the Post and Comment structs to generate a website to show users
|
// Use the Post and Comment structs to generate a website to show users
|
||||||
@ -63,7 +71,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
comments,
|
comments,
|
||||||
post,
|
post,
|
||||||
sort,
|
sort,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
single_thread,
|
single_thread,
|
||||||
url,
|
url,
|
||||||
})
|
})
|
||||||
@ -80,95 +88,8 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// POSTS
|
|
||||||
async fn parse_post(json: &serde_json::Value) -> Post {
|
|
||||||
// Retrieve post (as opposed to comments) from JSON
|
|
||||||
let post: &serde_json::Value = &json["data"]["children"][0];
|
|
||||||
|
|
||||||
// Grab UTC time as unix timestamp
|
|
||||||
let (rel_time, created) = time(post["data"]["created_utc"].as_f64().unwrap_or_default());
|
|
||||||
// Parse post score and upvote ratio
|
|
||||||
let score = post["data"]["score"].as_i64().unwrap_or_default();
|
|
||||||
let ratio: f64 = post["data"]["upvote_ratio"].as_f64().unwrap_or(1.0) * 100.0;
|
|
||||||
|
|
||||||
// Determine the type of media along with the media URL
|
|
||||||
let (post_type, media, gallery) = Media::parse(&post["data"]).await;
|
|
||||||
|
|
||||||
let awards: Awards = Awards::parse(&post["data"]["all_awardings"]);
|
|
||||||
|
|
||||||
let permalink = val(post, "permalink");
|
|
||||||
|
|
||||||
let body = if val(post, "removed_by_category") == "moderator" {
|
|
||||||
format!(
|
|
||||||
"<div class=\"md\"><p>[removed] — <a href=\"https://www.unddit.com{}\">view removed post</a></p></div>",
|
|
||||||
permalink
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
rewrite_urls(&val(post, "selftext_html"))
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build a post using data parsed from Reddit post API
|
|
||||||
Post {
|
|
||||||
id: val(post, "id"),
|
|
||||||
title: val(post, "title"),
|
|
||||||
community: val(post, "subreddit"),
|
|
||||||
body,
|
|
||||||
author: Author {
|
|
||||||
name: val(post, "author"),
|
|
||||||
flair: Flair {
|
|
||||||
flair_parts: FlairPart::parse(
|
|
||||||
post["data"]["author_flair_type"].as_str().unwrap_or_default(),
|
|
||||||
post["data"]["author_flair_richtext"].as_array(),
|
|
||||||
post["data"]["author_flair_text"].as_str(),
|
|
||||||
),
|
|
||||||
text: val(post, "link_flair_text"),
|
|
||||||
background_color: val(post, "author_flair_background_color"),
|
|
||||||
foreground_color: val(post, "author_flair_text_color"),
|
|
||||||
},
|
|
||||||
distinguished: val(post, "distinguished"),
|
|
||||||
},
|
|
||||||
permalink,
|
|
||||||
score: format_num(score),
|
|
||||||
upvote_ratio: ratio as i64,
|
|
||||||
post_type,
|
|
||||||
media,
|
|
||||||
thumbnail: Media {
|
|
||||||
url: format_url(val(post, "thumbnail").as_str()),
|
|
||||||
alt_url: String::new(),
|
|
||||||
width: post["data"]["thumbnail_width"].as_i64().unwrap_or_default(),
|
|
||||||
height: post["data"]["thumbnail_height"].as_i64().unwrap_or_default(),
|
|
||||||
poster: "".to_string(),
|
|
||||||
},
|
|
||||||
flair: Flair {
|
|
||||||
flair_parts: FlairPart::parse(
|
|
||||||
post["data"]["link_flair_type"].as_str().unwrap_or_default(),
|
|
||||||
post["data"]["link_flair_richtext"].as_array(),
|
|
||||||
post["data"]["link_flair_text"].as_str(),
|
|
||||||
),
|
|
||||||
text: val(post, "link_flair_text"),
|
|
||||||
background_color: val(post, "link_flair_background_color"),
|
|
||||||
foreground_color: if val(post, "link_flair_text_color") == "dark" {
|
|
||||||
"black".to_string()
|
|
||||||
} else {
|
|
||||||
"white".to_string()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
flags: Flags {
|
|
||||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or(false),
|
|
||||||
stickied: post["data"]["stickied"].as_bool().unwrap_or(false)
|
|
||||||
|| post["data"]["pinned"].as_bool().unwrap_or(false),
|
|
||||||
},
|
|
||||||
domain: val(post, "domain"),
|
|
||||||
rel_time,
|
|
||||||
created,
|
|
||||||
comments: format_num(post["data"]["num_comments"].as_i64().unwrap_or_default()),
|
|
||||||
gallery,
|
|
||||||
awards,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// COMMENTS
|
// COMMENTS
|
||||||
fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str, highlighted_comment: &str, filters: &HashSet<String>) -> Vec<Comment> {
|
fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str, highlighted_comment: &str, filters: &HashSet<String>, req: &Request<Body>) -> Vec<Comment> {
|
||||||
// Parse the comment JSON into a Vector of Comments
|
// Parse the comment JSON into a Vector of Comments
|
||||||
let comments = json["data"]["children"].as_array().map_or(Vec::new(), std::borrow::ToOwned::to_owned);
|
let comments = json["data"]["children"].as_array().map_or(Vec::new(), std::borrow::ToOwned::to_owned);
|
||||||
|
|
||||||
@ -188,7 +109,7 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
|
|||||||
|
|
||||||
// If this comment contains replies, handle those too
|
// If this comment contains replies, handle those too
|
||||||
let replies: Vec<Comment> = if data["replies"].is_object() {
|
let replies: Vec<Comment> = if data["replies"].is_object() {
|
||||||
parse_comments(&data["replies"], post_link, post_author, highlighted_comment, filters)
|
parse_comments(&data["replies"], post_link, post_author, highlighted_comment, filters, req)
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
@ -256,6 +177,7 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
|
|||||||
awards,
|
awards,
|
||||||
collapsed,
|
collapsed,
|
||||||
is_filtered,
|
is_filtered,
|
||||||
|
prefs: Preferences::new(req),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::utils::{catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, setting, template, val, Post, Preferences};
|
use crate::utils::{self, catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, setting, template, val, Post, Preferences};
|
||||||
use crate::{
|
use crate::{
|
||||||
client::json,
|
client::json,
|
||||||
subreddit::{can_access_quarantine, quarantine},
|
subreddit::{can_access_quarantine, quarantine},
|
||||||
@ -7,6 +7,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
// STRUCTS
|
// STRUCTS
|
||||||
struct SearchParams {
|
struct SearchParams {
|
||||||
@ -44,13 +46,23 @@ struct SearchTemplate {
|
|||||||
all_posts_filtered: bool,
|
all_posts_filtered: bool,
|
||||||
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
||||||
all_posts_hidden_nsfw: bool,
|
all_posts_hidden_nsfw: bool,
|
||||||
|
no_posts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regex matched against search queries to determine if they are reddit urls.
|
||||||
|
static REDDIT_URL_MATCH: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://([^\./]+\.)*reddit.com/").unwrap());
|
||||||
|
|
||||||
// SERVICES
|
// SERVICES
|
||||||
pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
let nsfw_results = if setting(&req, "show_nsfw") == "on" { "&include_over_18=on" } else { "" };
|
// This ensures that during a search, no NSFW posts are fetched at all
|
||||||
|
let nsfw_results = if setting(&req, "show_nsfw") == "on" && !utils::sfw_only() {
|
||||||
|
"&include_over_18=on"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
let path = format!("{}.json?{}{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results);
|
let path = format!("{}.json?{}{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results);
|
||||||
let query = param(&path, "q").unwrap_or_default();
|
let mut query = param(&path, "q").unwrap_or_default();
|
||||||
|
query = REDDIT_URL_MATCH.replace(&query, "").to_string();
|
||||||
|
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
return Ok(redirect("/".to_string()));
|
return Ok(redirect("/".to_string()));
|
||||||
@ -98,17 +110,19 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
||||||
typed,
|
typed,
|
||||||
},
|
},
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
is_filtered: true,
|
is_filtered: true,
|
||||||
all_posts_filtered: false,
|
all_posts_filtered: false,
|
||||||
all_posts_hidden_nsfw: false,
|
all_posts_hidden_nsfw: false,
|
||||||
|
no_posts: false,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
match Post::fetch(&path, quarantined).await {
|
match Post::fetch(&path, quarantined).await {
|
||||||
Ok((mut posts, after)) => {
|
Ok((mut posts, after)) => {
|
||||||
let all_posts_filtered = filter_posts(&mut posts, &filters);
|
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
||||||
let all_posts_hidden_nsfw = posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on";
|
let no_posts = posts.is_empty();
|
||||||
|
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
||||||
template(SearchTemplate {
|
template(SearchTemplate {
|
||||||
posts,
|
posts,
|
||||||
subreddits,
|
subreddits,
|
||||||
@ -122,11 +136,12 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
restrict_sr: param(&path, "restrict_sr").unwrap_or_default(),
|
||||||
typed,
|
typed,
|
||||||
},
|
},
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
all_posts_filtered,
|
all_posts_filtered,
|
||||||
all_posts_hidden_nsfw,
|
all_posts_hidden_nsfw,
|
||||||
|
no_posts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
|
@ -243,7 +243,7 @@ impl Server {
|
|||||||
match func.await {
|
match func.await {
|
||||||
Ok(mut res) => {
|
Ok(mut res) => {
|
||||||
res.headers_mut().extend(def_headers);
|
res.headers_mut().extend(def_headers);
|
||||||
let _ = compress_response(req_headers, &mut res).await;
|
let _ = compress_response(&req_headers, &mut res).await;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@ -282,7 +282,7 @@ async fn new_boilerplate(
|
|||||||
) -> Result<Response<Body>, String> {
|
) -> Result<Response<Body>, String> {
|
||||||
match Response::builder().status(status).body(body) {
|
match Response::builder().status(status).body(body) {
|
||||||
Ok(mut res) => {
|
Ok(mut res) => {
|
||||||
let _ = compress_response(req_headers, &mut res).await;
|
let _ = compress_response(&req_headers, &mut res).await;
|
||||||
|
|
||||||
res.headers_mut().extend(default_headers.clone());
|
res.headers_mut().extend(default_headers.clone());
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@ -306,7 +306,8 @@ async fn new_boilerplate(
|
|||||||
/// Accept-Encoding: gzip, compress, br
|
/// Accept-Encoding: gzip, compress, br
|
||||||
/// Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
|
/// Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
|
||||||
/// ```
|
/// ```
|
||||||
fn determine_compressor(accept_encoding: &str) -> Option<CompressionType> {
|
#[cached]
|
||||||
|
fn determine_compressor(accept_encoding: String) -> Option<CompressionType> {
|
||||||
if accept_encoding.is_empty() {
|
if accept_encoding.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
@ -473,7 +474,7 @@ fn determine_compressor(accept_encoding: &str) -> Option<CompressionType> {
|
|||||||
///
|
///
|
||||||
/// This function logs errors to stderr, but only in debug mode. No information
|
/// This function logs errors to stderr, but only in debug mode. No information
|
||||||
/// is logged in release builds.
|
/// is logged in release builds.
|
||||||
async fn compress_response(req_headers: HeaderMap<header::HeaderValue>, res: &mut Response<Body>) -> Result<(), String> {
|
async fn compress_response(req_headers: &HeaderMap<header::HeaderValue>, res: &mut Response<Body>) -> Result<(), String> {
|
||||||
// Check if the data is eligible for compression.
|
// Check if the data is eligible for compression.
|
||||||
if let Some(hdr) = res.headers().get(header::CONTENT_TYPE) {
|
if let Some(hdr) = res.headers().get(header::CONTENT_TYPE) {
|
||||||
match from_utf8(hdr.as_bytes()) {
|
match from_utf8(hdr.as_bytes()) {
|
||||||
@ -503,30 +504,22 @@ async fn compress_response(req_headers: HeaderMap<header::HeaderValue>, res: &mu
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Quick and dirty closure for extracting a header from the request and
|
// Check to see which compressor is requested, and if we can use it.
|
||||||
// returning it as a &str.
|
let accept_encoding: String = match req_headers.get(header::ACCEPT_ENCODING) {
|
||||||
let get_req_header = |k: header::HeaderName| -> Option<&str> {
|
None => return Ok(()), // Client requested no compression.
|
||||||
match req_headers.get(k) {
|
|
||||||
Some(hdr) => match from_utf8(hdr.as_bytes()) {
|
Some(hdr) => match String::from_utf8(hdr.as_bytes().into()) {
|
||||||
Ok(val) => Some(val),
|
Ok(val) => val,
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
dbg_msg!(e);
|
dbg_msg!(e);
|
||||||
None
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
Err(_) => None,
|
Err(_) => return Ok(()),
|
||||||
},
|
},
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check to see which compressor is requested, and if we can use it.
|
|
||||||
let accept_encoding: &str = match get_req_header(header::ACCEPT_ENCODING) {
|
|
||||||
Some(val) => val,
|
|
||||||
None => return Ok(()), // Client requested no compression.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let compressor: CompressionType = match determine_compressor(accept_encoding) {
|
let compressor: CompressionType = match determine_compressor(accept_encoding) {
|
||||||
@ -636,18 +629,18 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_determine_compressor() {
|
fn test_determine_compressor() {
|
||||||
// Single compressor given.
|
// Single compressor given.
|
||||||
assert_eq!(determine_compressor("unsupported"), None);
|
assert_eq!(determine_compressor("unsupported".to_string()), None);
|
||||||
assert_eq!(determine_compressor("gzip"), Some(CompressionType::Gzip));
|
assert_eq!(determine_compressor("gzip".to_string()), Some(CompressionType::Gzip));
|
||||||
assert_eq!(determine_compressor("*"), Some(DEFAULT_COMPRESSOR));
|
assert_eq!(determine_compressor("*".to_string()), Some(DEFAULT_COMPRESSOR));
|
||||||
|
|
||||||
// Multiple compressors.
|
// Multiple compressors.
|
||||||
assert_eq!(determine_compressor("gzip, br"), Some(CompressionType::Brotli));
|
assert_eq!(determine_compressor("gzip, br".to_string()), Some(CompressionType::Brotli));
|
||||||
assert_eq!(determine_compressor("gzip;q=0.8, br;q=0.3"), Some(CompressionType::Gzip));
|
assert_eq!(determine_compressor("gzip;q=0.8, br;q=0.3".to_string()), Some(CompressionType::Gzip));
|
||||||
assert_eq!(determine_compressor("br, gzip"), Some(CompressionType::Brotli));
|
assert_eq!(determine_compressor("br, gzip".to_string()), Some(CompressionType::Brotli));
|
||||||
assert_eq!(determine_compressor("br;q=0.3, gzip;q=0.4"), Some(CompressionType::Gzip));
|
assert_eq!(determine_compressor("br;q=0.3, gzip;q=0.4".to_string()), Some(CompressionType::Gzip));
|
||||||
|
|
||||||
// Invalid q-values.
|
// Invalid q-values.
|
||||||
assert_eq!(determine_compressor("gzip;q=NAN"), None);
|
assert_eq!(determine_compressor("gzip;q=NAN".to_string()), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -672,9 +665,9 @@ mod tests {
|
|||||||
] {
|
] {
|
||||||
// Determine what the expected encoding should be based on both the
|
// Determine what the expected encoding should be based on both the
|
||||||
// specific encodings we accept.
|
// specific encodings we accept.
|
||||||
let expected_encoding: CompressionType = match determine_compressor(accept_encoding) {
|
let expected_encoding: CompressionType = match determine_compressor(accept_encoding.to_string()) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => panic!("determine_compressor(accept_encoding) => None"),
|
None => panic!("determine_compressor(accept_encoding.to_string()) => None"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build headers with our Accept-Encoding.
|
// Build headers with our Accept-Encoding.
|
||||||
@ -691,8 +684,8 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Perform the compression.
|
// Perform the compression.
|
||||||
if let Err(e) = block_on(compress_response(req_headers, &mut res)) {
|
if let Err(e) = block_on(compress_response(&req_headers, &mut res)) {
|
||||||
panic!("compress_response(req_headers, &mut res) => Err(\"{}\")", e);
|
panic!("compress_response(&req_headers, &mut res) => Err(\"{}\")", e);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the content was compressed, we expect the Content-Encoding
|
// If the content was compressed, we expect the Content-Encoding
|
||||||
@ -739,9 +732,8 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut decompressed = Vec::<u8>::new();
|
let mut decompressed = Vec::<u8>::new();
|
||||||
match io::copy(&mut decoder, &mut decompressed) {
|
if let Err(e) = io::copy(&mut decoder, &mut decompressed) {
|
||||||
Ok(_) => {}
|
panic!("{}", e);
|
||||||
Err(e) => panic!("{}", e),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(decompressed.eq(&expected_lorem_ipsum));
|
assert!(decompressed.eq(&expected_lorem_ipsum));
|
||||||
|
@ -19,7 +19,7 @@ struct SettingsTemplate {
|
|||||||
|
|
||||||
// CONSTANTS
|
// CONSTANTS
|
||||||
|
|
||||||
const PREFS: [&str; 11] = [
|
const PREFS: [&str; 13] = [
|
||||||
"theme",
|
"theme",
|
||||||
"front_page",
|
"front_page",
|
||||||
"layout",
|
"layout",
|
||||||
@ -31,6 +31,8 @@ const PREFS: [&str; 11] = [
|
|||||||
"use_hls",
|
"use_hls",
|
||||||
"hide_hls_notification",
|
"hide_hls_notification",
|
||||||
"autoplay_videos",
|
"autoplay_videos",
|
||||||
|
"hide_awards",
|
||||||
|
"disable_visit_reddit_confirmation",
|
||||||
];
|
];
|
||||||
|
|
||||||
// FUNCTIONS
|
// FUNCTIONS
|
||||||
@ -39,7 +41,7 @@ const PREFS: [&str; 11] = [
|
|||||||
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
|
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
let url = req.uri().to_string();
|
let url = req.uri().to_string();
|
||||||
template(SettingsTemplate {
|
template(SettingsTemplate {
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
catch_random, error, filter_posts, format_num, format_url, get_filters, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
||||||
};
|
};
|
||||||
use crate::{client::json, server::ResponseExt, RequestExt};
|
use crate::{client::json, server::ResponseExt, RequestExt};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
@ -26,6 +26,7 @@ struct SubredditTemplate {
|
|||||||
all_posts_filtered: bool,
|
all_posts_filtered: bool,
|
||||||
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
||||||
all_posts_hidden_nsfw: bool,
|
all_posts_hidden_nsfw: bool,
|
||||||
|
no_posts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
@ -96,6 +97,12 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Return landing page if this post if this is NSFW community but the user
|
||||||
|
// has disabled the display of NSFW content or if the instance is SFW-only.
|
||||||
|
if sub.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) {
|
||||||
|
return Ok(nsfw_landing(req).await.unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
let path = format!("/r/{}/{}.json?{}&raw_json=1", sub_name.clone(), sort, req.uri().query().unwrap_or_default());
|
let path = format!("/r/{}/{}.json?{}&raw_json=1", sub_name.clone(), sort, req.uri().query().unwrap_or_default());
|
||||||
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
||||||
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26").replace('+', "%2B");
|
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26").replace('+', "%2B");
|
||||||
@ -108,29 +115,32 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
posts: Vec::new(),
|
posts: Vec::new(),
|
||||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: true,
|
is_filtered: true,
|
||||||
all_posts_filtered: false,
|
all_posts_filtered: false,
|
||||||
all_posts_hidden_nsfw: false,
|
all_posts_hidden_nsfw: false,
|
||||||
|
no_posts: false,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
match Post::fetch(&path, quarantined).await {
|
match Post::fetch(&path, quarantined).await {
|
||||||
Ok((mut posts, after)) => {
|
Ok((mut posts, after)) => {
|
||||||
let all_posts_filtered = filter_posts(&mut posts, &filters);
|
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
||||||
let all_posts_hidden_nsfw = posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on";
|
let no_posts = posts.is_empty();
|
||||||
|
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
||||||
template(SubredditTemplate {
|
template(SubredditTemplate {
|
||||||
sub,
|
sub,
|
||||||
posts,
|
posts,
|
||||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), after),
|
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
all_posts_filtered,
|
all_posts_filtered,
|
||||||
all_posts_hidden_nsfw,
|
all_posts_hidden_nsfw,
|
||||||
|
no_posts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(msg) => match msg.as_str() {
|
Err(msg) => match msg.as_str() {
|
||||||
@ -149,7 +159,7 @@ pub fn quarantine(req: Request<Body>, sub: String) -> Result<Response<Body>, Str
|
|||||||
msg: "Please click the button below to continue to this subreddit.".to_string(),
|
msg: "Please click the button below to continue to this subreddit.".to_string(),
|
||||||
url: req.uri().to_string(),
|
url: req.uri().to_string(),
|
||||||
sub,
|
sub,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
@ -196,7 +206,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
|||||||
|
|
||||||
let query = req.uri().query().unwrap_or_default().to_string();
|
let query = req.uri().query().unwrap_or_default().to_string();
|
||||||
|
|
||||||
let preferences = Preferences::new(req);
|
let preferences = Preferences::new(&req);
|
||||||
let mut sub_list = preferences.subscriptions;
|
let mut sub_list = preferences.subscriptions;
|
||||||
let mut filters = preferences.filters;
|
let mut filters = preferences.filters;
|
||||||
|
|
||||||
@ -309,7 +319,7 @@ pub async fn wiki(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
sub,
|
sub,
|
||||||
wiki: rewrite_urls(response["data"]["content_html"].as_str().unwrap_or("<h3>Wiki not found</h3>")),
|
wiki: rewrite_urls(response["data"]["content_html"].as_str().unwrap_or("<h3>Wiki not found</h3>")),
|
||||||
page,
|
page,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
}),
|
}),
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
@ -347,7 +357,7 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
// ),
|
// ),
|
||||||
sub,
|
sub,
|
||||||
page: "Sidebar".to_string(),
|
page: "Sidebar".to_string(),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
}),
|
}),
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
@ -420,5 +430,6 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
|
|||||||
members: format_num(members),
|
members: format_num(members),
|
||||||
active: format_num(active),
|
active: format_num(active),
|
||||||
wiki: res["data"]["wiki_enabled"].as_bool().unwrap_or_default(),
|
wiki: res["data"]["wiki_enabled"].as_bool().unwrap_or_default(),
|
||||||
|
nsfw: res["data"]["over18"].as_bool().unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
24
src/user.rs
24
src/user.rs
@ -1,7 +1,7 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::client::json;
|
use crate::client::json;
|
||||||
use crate::server::RequestExt;
|
use crate::server::RequestExt;
|
||||||
use crate::utils::{error, filter_posts, format_url, get_filters, param, setting, template, Post, Preferences, User};
|
use crate::utils::{error, filter_posts, format_url, get_filters, nsfw_landing, param, setting, template, Post, Preferences, User};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
use time::{macros::format_description, OffsetDateTime};
|
use time::{macros::format_description, OffsetDateTime};
|
||||||
@ -26,6 +26,7 @@ struct UserTemplate {
|
|||||||
all_posts_filtered: bool,
|
all_posts_filtered: bool,
|
||||||
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
|
||||||
all_posts_hidden_nsfw: bool,
|
all_posts_hidden_nsfw: bool,
|
||||||
|
no_posts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FUNCTIONS
|
// FUNCTIONS
|
||||||
@ -45,8 +46,17 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
// Retrieve other variables from Libreddit request
|
// Retrieve other variables from Libreddit request
|
||||||
let sort = param(&path, "sort").unwrap_or_default();
|
let sort = param(&path, "sort").unwrap_or_default();
|
||||||
let username = req.param("name").unwrap_or_default();
|
let username = req.param("name").unwrap_or_default();
|
||||||
|
|
||||||
|
// Retrieve info from user about page.
|
||||||
let user = user(&username).await.unwrap_or_default();
|
let user = user(&username).await.unwrap_or_default();
|
||||||
|
|
||||||
|
// Return landing page if this post if this Reddit deems this user NSFW,
|
||||||
|
// but we have also disabled the display of NSFW content or if the instance
|
||||||
|
// is SFW-only.
|
||||||
|
if user.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) {
|
||||||
|
return Ok(nsfw_landing(req).await.unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
let filters = get_filters(&req);
|
let filters = get_filters(&req);
|
||||||
if filters.contains(&["u_", &username].concat()) {
|
if filters.contains(&["u_", &username].concat()) {
|
||||||
template(UserTemplate {
|
template(UserTemplate {
|
||||||
@ -55,31 +65,34 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||||
listing,
|
listing,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: true,
|
is_filtered: true,
|
||||||
all_posts_filtered: false,
|
all_posts_filtered: false,
|
||||||
all_posts_hidden_nsfw: false,
|
all_posts_hidden_nsfw: false,
|
||||||
|
no_posts: false,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Request user posts/comments from Reddit
|
// Request user posts/comments from Reddit
|
||||||
match Post::fetch(&path, false).await {
|
match Post::fetch(&path, false).await {
|
||||||
Ok((mut posts, after)) => {
|
Ok((mut posts, after)) => {
|
||||||
let all_posts_filtered = filter_posts(&mut posts, &filters);
|
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
|
||||||
let all_posts_hidden_nsfw = posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on";
|
let no_posts = posts.is_empty();
|
||||||
|
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
|
||||||
template(UserTemplate {
|
template(UserTemplate {
|
||||||
user,
|
user,
|
||||||
posts,
|
posts,
|
||||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), after),
|
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||||
listing,
|
listing,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
all_posts_filtered,
|
all_posts_filtered,
|
||||||
all_posts_hidden_nsfw,
|
all_posts_hidden_nsfw,
|
||||||
|
no_posts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// If there is an error show error page
|
// If there is an error show error page
|
||||||
@ -111,6 +124,7 @@ async fn user(name: &str) -> Result<User, String> {
|
|||||||
created: created.format(format_description!("[month repr:short] [day] '[year repr:last_two]")).unwrap_or_default(),
|
created: created.format(format_description!("[month repr:short] [day] '[year repr:last_two]")).unwrap_or_default(),
|
||||||
banner: about("banner_img"),
|
banner: about("banner_img"),
|
||||||
description: about("public_description"),
|
description: about("public_description"),
|
||||||
|
nsfw: res["data"]["subreddit"]["over_18"].as_bool().unwrap_or_default(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
206
src/utils.rs
206
src/utils.rs
@ -9,6 +9,7 @@ use regex::Regex;
|
|||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use time::{macros::format_description, Duration, OffsetDateTime};
|
use time::{macros::format_description, Duration, OffsetDateTime};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -28,6 +29,16 @@ macro_rules! dbg_msg {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identifies whether or not the page is a subreddit, a user page, or a post.
|
||||||
|
/// This is used by the NSFW landing template to determine the mesage to convey
|
||||||
|
/// to the user.
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum ResourceType {
|
||||||
|
Subreddit,
|
||||||
|
User,
|
||||||
|
Post,
|
||||||
|
}
|
||||||
|
|
||||||
// Post flair with content, background color and foreground color
|
// Post flair with content, background color and foreground color
|
||||||
pub struct Flair {
|
pub struct Flair {
|
||||||
pub flair_parts: Vec<FlairPart>,
|
pub flair_parts: Vec<FlairPart>,
|
||||||
@ -225,9 +236,11 @@ pub struct Post {
|
|||||||
pub domain: String,
|
pub domain: String,
|
||||||
pub rel_time: String,
|
pub rel_time: String,
|
||||||
pub created: String,
|
pub created: String,
|
||||||
|
pub num_duplicates: u64,
|
||||||
pub comments: (String, String),
|
pub comments: (String, String),
|
||||||
pub gallery: Vec<GalleryMedia>,
|
pub gallery: Vec<GalleryMedia>,
|
||||||
pub awards: Awards,
|
pub awards: Awards,
|
||||||
|
pub nsfw: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Post {
|
impl Post {
|
||||||
@ -324,9 +337,11 @@ impl Post {
|
|||||||
permalink: val(post, "permalink"),
|
permalink: val(post, "permalink"),
|
||||||
rel_time,
|
rel_time,
|
||||||
created,
|
created,
|
||||||
|
num_duplicates: post["data"]["num_duplicates"].as_u64().unwrap_or(0),
|
||||||
comments: format_num(data["num_comments"].as_i64().unwrap_or_default()),
|
comments: format_num(data["num_comments"].as_i64().unwrap_or_default()),
|
||||||
gallery,
|
gallery,
|
||||||
awards,
|
awards,
|
||||||
|
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,6 +370,7 @@ pub struct Comment {
|
|||||||
pub awards: Awards,
|
pub awards: Awards,
|
||||||
pub collapsed: bool,
|
pub collapsed: bool,
|
||||||
pub is_filtered: bool,
|
pub is_filtered: bool,
|
||||||
|
pub prefs: Preferences,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@ -418,6 +434,27 @@ pub struct ErrorTemplate {
|
|||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Template for NSFW landing page. The landing page is displayed when a page's
|
||||||
|
/// content is wholly NSFW, but a user has not enabled the option to view NSFW
|
||||||
|
/// posts.
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "nsfwlanding.html")]
|
||||||
|
pub struct NSFWLandingTemplate {
|
||||||
|
/// Identifier for the resource. This is either a subreddit name or a
|
||||||
|
/// username. (In the case of the latter, set is_user to true.)
|
||||||
|
pub res: String,
|
||||||
|
|
||||||
|
/// Identifies whether or not the resource is a subreddit, a user page,
|
||||||
|
/// or a post.
|
||||||
|
pub res_type: ResourceType,
|
||||||
|
|
||||||
|
/// User preferences.
|
||||||
|
pub prefs: Preferences,
|
||||||
|
|
||||||
|
/// Request URL.
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
// User struct containing metadata about user
|
// User struct containing metadata about user
|
||||||
pub struct User {
|
pub struct User {
|
||||||
@ -428,6 +465,7 @@ pub struct User {
|
|||||||
pub created: String,
|
pub created: String,
|
||||||
pub banner: String,
|
pub banner: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
pub nsfw: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -442,6 +480,7 @@ pub struct Subreddit {
|
|||||||
pub members: (String, String),
|
pub members: (String, String),
|
||||||
pub active: (String, String),
|
pub active: (String, String),
|
||||||
pub wiki: bool,
|
pub wiki: bool,
|
||||||
|
pub nsfw: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser for query params, used in sorting (eg. /r/rust/?sort=hot)
|
// Parser for query params, used in sorting (eg. /r/rust/?sort=hot)
|
||||||
@ -466,10 +505,12 @@ pub struct Preferences {
|
|||||||
pub hide_hls_notification: String,
|
pub hide_hls_notification: String,
|
||||||
pub use_hls: String,
|
pub use_hls: String,
|
||||||
pub autoplay_videos: String,
|
pub autoplay_videos: String,
|
||||||
|
pub disable_visit_reddit_confirmation: String,
|
||||||
pub comment_sort: String,
|
pub comment_sort: String,
|
||||||
pub post_sort: String,
|
pub post_sort: String,
|
||||||
pub subscriptions: Vec<String>,
|
pub subscriptions: Vec<String>,
|
||||||
pub filters: Vec<String>,
|
pub filters: Vec<String>,
|
||||||
|
pub hide_awards: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
@ -479,7 +520,7 @@ pub struct ThemeAssets;
|
|||||||
|
|
||||||
impl Preferences {
|
impl Preferences {
|
||||||
// Build preferences from cookies
|
// Build preferences from cookies
|
||||||
pub fn new(req: Request<Body>) -> Self {
|
pub fn new(req: &Request<Body>) -> Self {
|
||||||
// Read available theme names from embedded css files.
|
// Read available theme names from embedded css files.
|
||||||
// Always make the default "system" theme available.
|
// Always make the default "system" theme available.
|
||||||
let mut themes = vec!["system".to_string()];
|
let mut themes = vec!["system".to_string()];
|
||||||
@ -498,10 +539,12 @@ impl Preferences {
|
|||||||
use_hls: setting(&req, "use_hls"),
|
use_hls: setting(&req, "use_hls"),
|
||||||
hide_hls_notification: setting(&req, "hide_hls_notification"),
|
hide_hls_notification: setting(&req, "hide_hls_notification"),
|
||||||
autoplay_videos: setting(&req, "autoplay_videos"),
|
autoplay_videos: setting(&req, "autoplay_videos"),
|
||||||
|
disable_visit_reddit_confirmation: setting(&req, "disable_visit_reddit_confirmation"),
|
||||||
comment_sort: setting(&req, "comment_sort"),
|
comment_sort: setting(&req, "comment_sort"),
|
||||||
post_sort: setting(&req, "post_sort"),
|
post_sort: setting(&req, "post_sort"),
|
||||||
subscriptions: setting(&req, "subscriptions").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
subscriptions: setting(&req, "subscriptions").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
||||||
filters: setting(&req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
filters: setting(&req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
||||||
|
hide_awards: setting(&req, "hide_awards"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,15 +554,111 @@ pub fn get_filters(req: &Request<Body>) -> HashSet<String> {
|
|||||||
setting(req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect::<HashSet<String>>()
|
setting(req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect::<HashSet<String>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters a `Vec<Post>` by the given `HashSet` of filters (each filter being a subreddit name or a user name). If a
|
/// Filters a `Vec<Post>` by the given `HashSet` of filters (each filter being
|
||||||
/// `Post`'s subreddit or author is found in the filters, it is removed. Returns `true` if _all_ posts were filtered
|
/// a subreddit name or a user name). If a `Post`'s subreddit or author is
|
||||||
/// out, or `false` otherwise.
|
/// found in the filters, it is removed.
|
||||||
pub fn filter_posts(posts: &mut Vec<Post>, filters: &HashSet<String>) -> bool {
|
///
|
||||||
|
/// The first value of the return tuple is the number of posts filtered. The
|
||||||
|
/// second return value is `true` if all posts were filtered.
|
||||||
|
pub fn filter_posts(posts: &mut Vec<Post>, filters: &HashSet<String>) -> (u64, bool) {
|
||||||
|
// This is the length of the Vec<Post> prior to applying the filter.
|
||||||
|
let lb: u64 = posts.len().try_into().unwrap_or(0);
|
||||||
|
|
||||||
if posts.is_empty() {
|
if posts.is_empty() {
|
||||||
false
|
(0, false)
|
||||||
} else {
|
} else {
|
||||||
posts.retain(|p| !filters.contains(&p.community) && !filters.contains(&["u_", &p.author.name].concat()));
|
posts.retain(|p| !(filters.contains(&p.community) || filters.contains(&["u_", &p.author.name].concat())));
|
||||||
posts.is_empty()
|
|
||||||
|
// Get the length of the Vec<Post> after applying the filter.
|
||||||
|
// If lb > la, then at least one post was removed.
|
||||||
|
let la: u64 = posts.len().try_into().unwrap_or(0);
|
||||||
|
|
||||||
|
(lb - la, posts.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Post`] from a provided JSON.
|
||||||
|
pub async fn parse_post(post: &serde_json::Value) -> Post {
|
||||||
|
// Grab UTC time as unix timestamp
|
||||||
|
let (rel_time, created) = time(post["data"]["created_utc"].as_f64().unwrap_or_default());
|
||||||
|
// Parse post score and upvote ratio
|
||||||
|
let score = post["data"]["score"].as_i64().unwrap_or_default();
|
||||||
|
let ratio: f64 = post["data"]["upvote_ratio"].as_f64().unwrap_or(1.0) * 100.0;
|
||||||
|
|
||||||
|
// Determine the type of media along with the media URL
|
||||||
|
let (post_type, media, gallery) = Media::parse(&post["data"]).await;
|
||||||
|
|
||||||
|
let awards: Awards = Awards::parse(&post["data"]["all_awardings"]);
|
||||||
|
|
||||||
|
let permalink = val(post, "permalink");
|
||||||
|
|
||||||
|
let body = if val(post, "removed_by_category") == "moderator" {
|
||||||
|
format!(
|
||||||
|
"<div class=\"md\"><p>[removed] — <a href=\"https://www.unddit.com{}\">view removed post</a></p></div>",
|
||||||
|
permalink
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
rewrite_urls(&val(post, "selftext_html"))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build a post using data parsed from Reddit post API
|
||||||
|
Post {
|
||||||
|
id: val(post, "id"),
|
||||||
|
title: val(post, "title"),
|
||||||
|
community: val(post, "subreddit"),
|
||||||
|
body,
|
||||||
|
author: Author {
|
||||||
|
name: val(post, "author"),
|
||||||
|
flair: Flair {
|
||||||
|
flair_parts: FlairPart::parse(
|
||||||
|
post["data"]["author_flair_type"].as_str().unwrap_or_default(),
|
||||||
|
post["data"]["author_flair_richtext"].as_array(),
|
||||||
|
post["data"]["author_flair_text"].as_str(),
|
||||||
|
),
|
||||||
|
text: val(post, "link_flair_text"),
|
||||||
|
background_color: val(post, "author_flair_background_color"),
|
||||||
|
foreground_color: val(post, "author_flair_text_color"),
|
||||||
|
},
|
||||||
|
distinguished: val(post, "distinguished"),
|
||||||
|
},
|
||||||
|
permalink,
|
||||||
|
score: format_num(score),
|
||||||
|
upvote_ratio: ratio as i64,
|
||||||
|
post_type,
|
||||||
|
media,
|
||||||
|
thumbnail: Media {
|
||||||
|
url: format_url(val(post, "thumbnail").as_str()),
|
||||||
|
alt_url: String::new(),
|
||||||
|
width: post["data"]["thumbnail_width"].as_i64().unwrap_or_default(),
|
||||||
|
height: post["data"]["thumbnail_height"].as_i64().unwrap_or_default(),
|
||||||
|
poster: String::new(),
|
||||||
|
},
|
||||||
|
flair: Flair {
|
||||||
|
flair_parts: FlairPart::parse(
|
||||||
|
post["data"]["link_flair_type"].as_str().unwrap_or_default(),
|
||||||
|
post["data"]["link_flair_richtext"].as_array(),
|
||||||
|
post["data"]["link_flair_text"].as_str(),
|
||||||
|
),
|
||||||
|
text: val(post, "link_flair_text"),
|
||||||
|
background_color: val(post, "link_flair_background_color"),
|
||||||
|
foreground_color: if val(post, "link_flair_text_color") == "dark" {
|
||||||
|
"black".to_string()
|
||||||
|
} else {
|
||||||
|
"white".to_string()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flags: Flags {
|
||||||
|
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||||
|
stickied: post["data"]["stickied"].as_bool().unwrap_or_default() || post["data"]["pinned"].as_bool().unwrap_or(false),
|
||||||
|
},
|
||||||
|
domain: val(post, "domain"),
|
||||||
|
rel_time,
|
||||||
|
created,
|
||||||
|
num_duplicates: post["data"]["num_duplicates"].as_u64().unwrap_or(0),
|
||||||
|
comments: format_num(post["data"]["num_comments"].as_i64().unwrap_or_default()),
|
||||||
|
gallery,
|
||||||
|
awards,
|
||||||
|
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,8 +685,8 @@ pub fn setting(req: &Request<Body>, name: &str) -> String {
|
|||||||
req
|
req
|
||||||
.cookie(name)
|
.cookie(name)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// If there is no cookie for this setting, try receiving a default from an environment variable
|
// If there is no cookie for this setting, try receiving a default from the config
|
||||||
if let Ok(default) = std::env::var(format!("LIBREDDIT_DEFAULT_{}", name.to_uppercase())) {
|
if let Some(default) = crate::config::get_setting(&format!("LIBREDDIT_DEFAULT_{}", name.to_uppercase())) {
|
||||||
Cookie::new(name, default)
|
Cookie::new(name, default)
|
||||||
} else {
|
} else {
|
||||||
Cookie::named(name)
|
Cookie::named(name)
|
||||||
@ -723,7 +862,7 @@ pub async fn error(req: Request<Body>, msg: impl ToString) -> Result<Response<Bo
|
|||||||
let url = req.uri().to_string();
|
let url = req.uri().to_string();
|
||||||
let body = ErrorTemplate {
|
let body = ErrorTemplate {
|
||||||
msg: msg.to_string(),
|
msg: msg.to_string(),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(&req),
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
@ -732,6 +871,51 @@ pub async fn error(req: Request<Body>, msg: impl ToString) -> Result<Response<Bo
|
|||||||
Ok(Response::builder().status(404).header("content-type", "text/html").body(body.into()).unwrap_or_default())
|
Ok(Response::builder().status(404).header("content-type", "text/html").body(body.into()).unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the config/env variable `LIBREDDIT_SFW_ONLY` carries the
|
||||||
|
/// value `on`.
|
||||||
|
///
|
||||||
|
/// If this variable is set as such, the instance will operate in SFW-only
|
||||||
|
/// mode; all NSFW content will be filtered. Attempts to access NSFW
|
||||||
|
/// subreddits or posts or userpages for users Reddit has deemed NSFW will
|
||||||
|
/// be denied.
|
||||||
|
pub fn sfw_only() -> bool {
|
||||||
|
match crate::config::get_setting("LIBREDDIT_SFW_ONLY") {
|
||||||
|
Some(val) => val == "on",
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the landing page for NSFW content when the user has not enabled
|
||||||
|
/// "show NSFW posts" in settings.
|
||||||
|
pub async fn nsfw_landing(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
|
let res_type: ResourceType;
|
||||||
|
let url = req.uri().to_string();
|
||||||
|
|
||||||
|
// Determine from the request URL if the resource is a subreddit, a user
|
||||||
|
// page, or a post.
|
||||||
|
let res: String = if !req.param("name").unwrap_or_default().is_empty() {
|
||||||
|
res_type = ResourceType::User;
|
||||||
|
req.param("name").unwrap_or_default()
|
||||||
|
} else if !req.param("id").unwrap_or_default().is_empty() {
|
||||||
|
res_type = ResourceType::Post;
|
||||||
|
req.param("id").unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
res_type = ResourceType::Subreddit;
|
||||||
|
req.param("sub").unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = NSFWLandingTemplate {
|
||||||
|
res,
|
||||||
|
res_type,
|
||||||
|
prefs: Preferences::new(&req),
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(Response::builder().status(403).header("content-type", "text/html").body(body.into()).unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{format_num, format_url, rewrite_urls};
|
use super::{format_num, format_url, rewrite_urls};
|
||||||
|
298
static/style.css
298
static/style.css
@ -4,6 +4,28 @@
|
|||||||
:root {
|
:root {
|
||||||
--nsfw: #ff5c5d;
|
--nsfw: #ff5c5d;
|
||||||
--admin: #ea0027;
|
--admin: #ea0027;
|
||||||
|
|
||||||
|
/* Reddit redirect warning constants */
|
||||||
|
--popup-red: #ea0027;
|
||||||
|
--popup-black: #111;
|
||||||
|
--popup-text: #fff;
|
||||||
|
--popup-background-1: #0f0f0f;
|
||||||
|
--popup-background-2: #220f0f;
|
||||||
|
--popup-reddit-url: var(--popup-red);
|
||||||
|
|
||||||
|
--popup-background: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
var(--popup-background-1),
|
||||||
|
var(--popup-background-1) 50px,
|
||||||
|
var(--popup-background-2) 50px,
|
||||||
|
var(--popup-background-2) 100px
|
||||||
|
);
|
||||||
|
|
||||||
|
--popup-toreddit-background: var(--popup-black);
|
||||||
|
--popup-toreddit-text: var(--popup-red);
|
||||||
|
--popup-goback-background: var(--popup-red);
|
||||||
|
--popup-goback-text: #222;
|
||||||
|
--popup-border: 1px solid var(--popup-red);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@ -26,6 +48,10 @@
|
|||||||
--highlighted: #333;
|
--highlighted: #333;
|
||||||
--visited: #aaa;
|
--visited: #aaa;
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||||
|
--popup: #b80a27;
|
||||||
|
|
||||||
|
/* Hint color theme to browser for scrollbar */
|
||||||
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Browser-defined light theme */
|
/* Browser-defined light theme */
|
||||||
@ -42,6 +68,9 @@
|
|||||||
--highlighted: white;
|
--highlighted: white;
|
||||||
--visited: #555;
|
--visited: #555;
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
/* Hint color theme to browser for scrollbar */
|
||||||
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,10 +157,109 @@ nav #libreddit {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#reddit_link {
|
.popup {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: clip;
|
||||||
|
opacity: 0;
|
||||||
|
position: fixed;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fallback for firefox esr */
|
||||||
|
.popup {
|
||||||
|
background-color: #000000fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* all other browsers */
|
||||||
|
@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) {
|
||||||
|
.popup {
|
||||||
|
-webkit-backdrop-filter: blur(.25rem) brightness(15%);
|
||||||
|
backdrop-filter: blur(.25rem) brightness(15%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 500px;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--popup-background);
|
||||||
|
border: var(--popup-border);
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inner svg {
|
||||||
|
display: unset !important;
|
||||||
|
width: 35%;
|
||||||
|
stroke: none;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inner h1 {
|
||||||
|
color: var(--popup-text);
|
||||||
|
margin: 1.5rem 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inner p {
|
||||||
|
color: var(--popup-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inner a {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 2%;
|
||||||
|
width: 80%;
|
||||||
|
margin: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#goback {
|
||||||
|
background: var(--popup-goback-background);
|
||||||
|
color: var(--popup-goback-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#goback:not(.selected):hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toreddit {
|
||||||
|
background: var(--popup-toreddit-background);
|
||||||
|
color: var(--popup-toreddit-text);
|
||||||
|
border: 1px solid var(--popup-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
#toreddit:not(.selected):hover {
|
||||||
|
background: var(--popup-toreddit-text);
|
||||||
|
color: var(--popup-toreddit-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup:target {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reddit_url {
|
||||||
|
width: 80%;
|
||||||
|
color: var(--popup-reddit-url);
|
||||||
|
font-weight: 600;
|
||||||
|
line-break: anywhere;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
#code {
|
#code {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
@ -160,16 +288,43 @@ main {
|
|||||||
overflow: inherit;
|
overflow: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
/* Body footer. */
|
||||||
|
body > footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-button {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: .25rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: 150%;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-button > a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* / Body footer. */
|
||||||
|
|
||||||
|
/* Footer in content block. */
|
||||||
|
main > * > footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer > a {
|
main > * > footer > a {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* / Footer in content block. */
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@ -242,7 +397,6 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#user_title, #sub_title {
|
#user_title, #sub_title {
|
||||||
margin: 0 20px;
|
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@ -481,20 +635,16 @@ button.submit:hover > svg { stroke: var(--accent); }
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing_options {
|
#sort_options, #listing_options, main > * > footer > a {
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sort_options, #listing_options, footer > a {
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
background: var(--outside);
|
background: var(--outside);
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort_options > a, #listing_options > a, footer > a {
|
#sort_options > a, #listing_options > a, main > * > footer > a {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -824,6 +974,17 @@ a.search_subreddit:hover {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#comment_count {
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment_count > #sorted_by {
|
||||||
|
font-weight: normal;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-right: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
#post_links {
|
#post_links {
|
||||||
display: flex;
|
display: flex;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@ -835,6 +996,16 @@ a.search_subreddit:hover {
|
|||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#post_links > li.desktop_item {
|
||||||
|
display: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 480px) {
|
||||||
|
#post_links > li.mobile_item {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.post_thumbnail {
|
.post_thumbnail {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: var(--panel-border);
|
border: var(--panel-border);
|
||||||
@ -1108,22 +1279,16 @@ summary.comment_data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prefs {
|
.prefs {
|
||||||
display: flex;
|
padding: 10px 20px 20px;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--post);
|
background: var(--post);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs > div {
|
.prefs fieldset {
|
||||||
display: flex;
|
border: 0;
|
||||||
justify-content: space-between;
|
padding: 10px 0;
|
||||||
width: 100%;
|
margin: 0 0 5px;
|
||||||
height: 35px;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs legend {
|
.prefs legend {
|
||||||
@ -1131,11 +1296,25 @@ summary.comment_data {
|
|||||||
border-bottom: 1px solid var(--highlighted);
|
border-bottom: 1px solid var(--highlighted);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
width: 100%;
|
||||||
|
float: left; /* places the legend inside the (invisible) border, instead of vertically centered on top border*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs legend:not(:first-child) {
|
.prefs-group {
|
||||||
padding-top: 10px;
|
display: flex;
|
||||||
margin-top: 15px;
|
width: 100%;
|
||||||
|
height: 35px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefs-group > *:not(:last-child) {
|
||||||
|
margin-right: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prefs-group > *:last-child {
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefs select {
|
.prefs select {
|
||||||
@ -1153,7 +1332,8 @@ aside.prefs {
|
|||||||
background: var(--highlighted);
|
background: var(--highlighted);
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-top: 20px;
|
margin-top: 5px;
|
||||||
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
@ -1183,6 +1363,10 @@ input[type="submit"] {
|
|||||||
width: 250px;
|
width: 250px;
|
||||||
background: var(--highlighted) !important;
|
background: var(--highlighted) !important;
|
||||||
}
|
}
|
||||||
|
/* Info page */
|
||||||
|
.unset {
|
||||||
|
color: lightslategrey;
|
||||||
|
}
|
||||||
|
|
||||||
/* Markdown */
|
/* Markdown */
|
||||||
|
|
||||||
@ -1273,6 +1457,54 @@ td, th {
|
|||||||
#error h3 { opacity: 0.85; }
|
#error h3 { opacity: 0.85; }
|
||||||
#error a { color: var(--accent); }
|
#error a { color: var(--accent); }
|
||||||
|
|
||||||
|
/* Messages */
|
||||||
|
|
||||||
|
#duplicates_msg h3 {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warnings */
|
||||||
|
|
||||||
|
.listing_warn {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listing_warn a {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NSFW Landing Page */
|
||||||
|
|
||||||
|
#nsfw_landing {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nsfw_landing h1 {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nsfw_landing p {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nsfw_landing a {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
|
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
@ -1373,4 +1605,18 @@ td, th {
|
|||||||
padding: 7px 0px;
|
padding: 7px 0px;
|
||||||
margin-right: -5px;
|
margin-right: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#post_links > li { margin-right: 10px }
|
||||||
|
#post_links > li.desktop_item { display: none }
|
||||||
|
#post_links > li.mobile_item { display: auto }
|
||||||
|
.post_footer > p > span#upvoted { display: none }
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
width: auto;
|
||||||
|
bottom: 10vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inner > a, h1, p, img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,8 @@
|
|||||||
--highlighted: #fbf1c7;
|
--highlighted: #fbf1c7;
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html:has(> .gruvboxlight) {
|
||||||
|
/* Hint color theme to browser for scrollbar */
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
@ -12,3 +12,8 @@
|
|||||||
--visited: #555;
|
--visited: #555;
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html:has(> .light) {
|
||||||
|
/* Hint color theme to browser for scrollbar */
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -35,12 +37,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{% block search %}{% endblock %}
|
{% block search %}{% endblock %}
|
||||||
<div id="links">
|
<div id="links">
|
||||||
<a id="reddit_link" href="https://www.reddit.com{{ url }}" rel="nofollow">
|
<a id="reddit_link" {% if prefs.disable_visit_reddit_confirmation != "on" %}href="#popup"{% else %}href="https://www.reddit.com{{ url }}" rel="nofollow"{% endif %}>
|
||||||
<span>reddit</span>
|
<span>reddit</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M23 12.0737C23 10.7308 21.9222 9.64226 20.5926 9.64226C19.9435 9.64226 19.3557 9.90274 18.923 10.3244C17.2772 9.12492 15.0099 8.35046 12.4849 8.26135L13.5814 3.05002L17.1643 3.8195C17.2081 4.73947 17.9539 5.47368 18.8757 5.47368C19.8254 5.47368 20.5951 4.69626 20.5951 3.73684C20.5951 2.77769 19.8254 2 18.8758 2C18.2001 2 17.6214 2.39712 17.3404 2.96952L13.3393 2.11066C13.2279 2.08679 13.1116 2.10858 13.016 2.17125C12.9204 2.23393 12.8533 2.33235 12.8295 2.44491L11.6051 8.25987C9.04278 8.33175 6.73904 9.10729 5.07224 10.3201C4.63988 9.90099 4.05398 9.64226 3.40757 9.64226C2.0781 9.64226 1 10.7308 1 12.0737C1 13.0618 1.58457 13.9105 2.4225 14.2909C2.38466 14.5342 2.36545 14.78 2.36505 15.0263C2.36505 18.7673 6.67626 21.8 11.9945 21.8C17.3131 21.8 21.6243 18.7673 21.6243 15.0263C21.6243 14.7794 21.6043 14.5359 21.5678 14.2957C22.4109 13.9175 23 13.0657 23 12.0737Z"/>
|
<path d="M22 2L12 22"/>
|
||||||
|
<path d="M2 6.70587C3.33333 8.07884 3.33333 11.5971 3.33333 11.5971M3.33333 19.647V11.5971M3.33333 11.5971C3.33333 11.5971 5.125 7.47817 8 7.47817C10.875 7.47817 12 8.85114 12 8.85114"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
{% if prefs.disable_visit_reddit_confirmation != "on" %}
|
||||||
|
{% call utils::visit_reddit_confirmation(url) %}
|
||||||
|
{% endif %}
|
||||||
<a id="settings_link" href="/settings">
|
<a id="settings_link" href="/settings">
|
||||||
<span>settings</span>
|
<span>settings</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
@ -48,7 +54,7 @@
|
|||||||
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a id="code" href="https://github.com/spikecodes/libreddit">
|
<a id="code" href="https://github.com/libreddit/libreddit" target="_blank" rel="noopener noreferrer">
|
||||||
<span>code</span>
|
<span>code</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<title>code</title>
|
<title>code</title>
|
||||||
@ -65,5 +71,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block footer %}
|
||||||
|
<footer>
|
||||||
|
<div class="info-button">
|
||||||
|
<a href="/info" title="View instance information">ⓘ</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ post_link }}{{ id }}/?context=3" class="created" title="{{ created }}">{{ rel_time }}</a>
|
<a href="{{ post_link }}{{ id }}/?context=3" class="created" title="{{ created }}">{{ rel_time }}</a>
|
||||||
{% if edited.0 != "".to_string() %}<span class="edited" title="{{ edited.1 }}">edited {{ edited.0 }}</span>{% endif %}
|
{% if edited.0 != "".to_string() %}<span class="edited" title="{{ edited.1 }}">edited {{ edited.0 }}</span>{% endif %}
|
||||||
{% if !awards.is_empty() %}
|
{% if !awards.is_empty() && prefs.hide_awards != "on" %}
|
||||||
<span class="dot">•</span>
|
<span class="dot">•</span>
|
||||||
{% for award in awards.clone() %}
|
{% for award in awards.clone() %}
|
||||||
<span class="award" title="{{ award.name }}">
|
<span class="award" title="{{ award.name }}">
|
||||||
|
107
templates/duplicates.html
Normal file
107
templates/duplicates.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
|
{% block title %}{{ post.title }} - r/{{ post.community }}{% endblock %}
|
||||||
|
|
||||||
|
{% block search %}
|
||||||
|
{% call utils::search(["/r/", post.community.as_str()].concat(), "") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block root %}/r/{{ post.community }}{% endblock %}{% block location %}r/{{ post.community }}{% endblock %}
|
||||||
|
{% block head %}
|
||||||
|
{% call super() %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subscriptions %}
|
||||||
|
{% call utils::sub_list(post.community.as_str()) %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="column_one">
|
||||||
|
{% call utils::post(post) %}
|
||||||
|
|
||||||
|
<!-- DUPLICATES -->
|
||||||
|
{% if post.num_duplicates == 0 %}
|
||||||
|
<span class="listing_warn">(No duplicates found)</span>
|
||||||
|
{% else if post.flags.nsfw && prefs.show_nsfw != "on" %}
|
||||||
|
<span class="listing_warn">(Enable "Show NSFW posts" in <a href="/settings">settings</a> to show duplicates)</span>
|
||||||
|
{% else %}
|
||||||
|
<div id="duplicates_msg"><h3>Duplicates</h3></div>
|
||||||
|
{% if num_posts_filtered > 0 %}
|
||||||
|
<span class="listing_warn">
|
||||||
|
{% if all_posts_filtered %}
|
||||||
|
(All posts have been filtered)
|
||||||
|
{% else %}
|
||||||
|
(Some posts have been filtered)
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="sort">
|
||||||
|
<div id="sort_options">
|
||||||
|
<a {% if params.sort.is_empty() || params.sort.eq("num_comments") %}class="selected"{% endif %} href="?sort=num_comments">
|
||||||
|
Number of comments
|
||||||
|
</a>
|
||||||
|
<a {% if params.sort.eq("new") %}class="selected"{% endif %} href="?sort=new">
|
||||||
|
New
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="posts">
|
||||||
|
{% for post in duplicates -%}
|
||||||
|
{# TODO: utils::post should be reworked to permit a truncated display of a post as below #}
|
||||||
|
{% if !(post.flags.nsfw) || prefs.show_nsfw == "on" %}
|
||||||
|
<div class="post {% if post.flags.stickied %}stickied{% endif %}" id="{{ post.id }}">
|
||||||
|
<p class="post_header">
|
||||||
|
{% let community -%}
|
||||||
|
{% if post.community.starts_with("u_") -%}
|
||||||
|
{% let community = format!("u/{}", &post.community[2..]) -%}
|
||||||
|
{% else -%}
|
||||||
|
{% let community = format!("r/{}", post.community) -%}
|
||||||
|
{% endif -%}
|
||||||
|
<a class="post_subreddit" href="/r/{{ post.community }}">{{ post.community }}</a>
|
||||||
|
<span class="dot">•</span>
|
||||||
|
<a class="post_author {{ post.author.distinguished }}" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
||||||
|
<span class="dot">•</span>
|
||||||
|
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
||||||
|
{% if !post.awards.is_empty() && prefs.hide_awards != "on" %}
|
||||||
|
{% for award in post.awards.clone() %}
|
||||||
|
<span class="award" title="{{ award.name }}">
|
||||||
|
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<h2 class="post_title">
|
||||||
|
{% if post.flair.flair_parts.len() > 0 %}
|
||||||
|
<a href="/r/{{ post.community }}/search?q=flair_name%3A%22{{ post.flair.text }}%22&restrict_sr=on"
|
||||||
|
class="post_flair"
|
||||||
|
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};"
|
||||||
|
dir="ltr">{% call utils::render_flair(post.flair.flair_parts) %}</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ post.permalink }}">{{ post.title }}</a>{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="post_score" title="{{ post.score.1 }}">{{ post.score.0 }}<span class="label"> Upvotes</span></div>
|
||||||
|
<div class="post_footer">
|
||||||
|
<a href="{{ post.permalink }}" class="post_comments" title="{{ post.comments.1 }} comments">{{ post.comments.0 }} comments</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
{% if params.before != "" %}
|
||||||
|
<a href="?before={{ params.before }}{% if !params.sort.is_empty() %}&sort={{ params.sort }}{% endif %}" accesskey="P">PREV</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if params.after != "" %}
|
||||||
|
<a href="?after={{ params.after }}{% if !params.sort.is_empty() %}&sort={{ params.sort }}{% endif %}" accesskey="N">NEXT</a>
|
||||||
|
{% endif %}
|
||||||
|
</footer>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
10
templates/message.html
Normal file
10
templates/message.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
{% block sortstyle %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div id="message">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
{{ body|safe }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
28
templates/nsfwlanding.html
Normal file
28
templates/nsfwlanding.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}NSFW content gated{% endblock %}
|
||||||
|
{% block sortstyle %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div id="nsfw_landing">
|
||||||
|
<h1>
|
||||||
|
😱
|
||||||
|
{% if res_type == crate::utils::ResourceType::Subreddit %}
|
||||||
|
r/{{ res }} is a NSFW community!
|
||||||
|
{% else if res_type == crate::utils::ResourceType::User %}
|
||||||
|
u/{{ res }}'s content is NSFW!
|
||||||
|
{% else if res_type == crate::utils::ResourceType::Post %}
|
||||||
|
This post is NSFW!
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% if crate::utils::sfw_only() %}
|
||||||
|
This instance of Libreddit is SFW-only.</p>
|
||||||
|
{% else %}
|
||||||
|
Enable "Show NSFW posts" in <a href="/settings">settings</a> to view this {% if res_type == crate::utils::ResourceType::Subreddit %}subreddit{% else if res_type == crate::utils::ResourceType::User %}user's posts or comments{% else if res_type == crate::utils::ResourceType::Post %}post{% endif %}.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block footer %}
|
||||||
|
{% endblock %}
|
@ -40,104 +40,11 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
|
{% call utils::post(post) %}
|
||||||
<!-- POST CONTENT -->
|
|
||||||
<div class="post highlighted">
|
|
||||||
<p class="post_header">
|
|
||||||
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
|
|
||||||
<span class="dot">•</span>
|
|
||||||
<a class="post_author {{ post.author.distinguished }}" href="/user/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
|
||||||
{% if post.author.flair.flair_parts.len() > 0 %}
|
|
||||||
<small class="author_flair">{% call utils::render_flair(post.author.flair.flair_parts) %}</small>
|
|
||||||
{% endif %}
|
|
||||||
<span class="dot">•</span>
|
|
||||||
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
|
||||||
{% if !post.awards.is_empty() %}
|
|
||||||
<span class="dot">•</span>
|
|
||||||
<span class="awards">
|
|
||||||
{% for award in post.awards.clone() %}
|
|
||||||
<span class="award" title="{{ award.name }}">
|
|
||||||
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
|
||||||
{{ award.count }}
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<h1 class="post_title">
|
|
||||||
{{ post.title }}
|
|
||||||
{% if post.flair.flair_parts.len() > 0 %}
|
|
||||||
<a href="/r/{{ post.community }}/search?q=flair_name%3A%22{{ post.flair.text }}%22&restrict_sr=on"
|
|
||||||
class="post_flair"
|
|
||||||
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};">{% call utils::render_flair(post.flair.flair_parts) %}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<!-- POST MEDIA -->
|
|
||||||
<!-- post_type: {{ post.post_type }} -->
|
|
||||||
{% if post.post_type == "image" %}
|
|
||||||
<div class="post_media_content">
|
|
||||||
<a href="{{ post.media.url }}" class="post_media_image" >
|
|
||||||
<svg
|
|
||||||
width="{{ post.media.width }}px"
|
|
||||||
height="{{ post.media.height }}px"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<image width="100%" height="100%" href="{{ post.media.url }}"/>
|
|
||||||
<desc>
|
|
||||||
<img loading="lazy" alt="Post image" src="{{ post.media.url }}"/>
|
|
||||||
</desc>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% else if post.post_type == "video" || post.post_type == "gif" %}
|
|
||||||
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
|
|
||||||
<script src="/hls.min.js"></script>
|
|
||||||
<div class="post_media_content">
|
|
||||||
<video class="post_media_video short {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" preload="none" controls>
|
|
||||||
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
|
||||||
<source src="{{ post.media.url }}" type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
<script src="/playHLSVideo.js"></script>
|
|
||||||
{% else %}
|
|
||||||
<div class="post_media_content">
|
|
||||||
<video class="post_media_video" src="{{ post.media.url }}" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %} loop><a href={{ post.media.url }}>Video</a></video>
|
|
||||||
</div>
|
|
||||||
{% call utils::render_hls_notification(post.permalink[1..]) %}
|
|
||||||
{% endif %}
|
|
||||||
{% else if post.post_type == "gallery" %}
|
|
||||||
<div class="gallery">
|
|
||||||
{% for image in post.gallery -%}
|
|
||||||
<figure>
|
|
||||||
<a href="{{ image.url }}" ><img loading="lazy" alt="Gallery image" src="{{ image.url }}"/></a>
|
|
||||||
<figcaption>
|
|
||||||
<p>{{ image.caption }}</p>
|
|
||||||
{% if image.outbound_url.len() > 0 %}
|
|
||||||
<p><a class="outbound_url" href="{{ image.outbound_url }}" rel="nofollow">{{ image.outbound_url }}</a>
|
|
||||||
{% endif %}
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
|
||||||
{%- endfor %}
|
|
||||||
</div>
|
|
||||||
{% else if post.post_type == "link" %}
|
|
||||||
<a id="post_url" href="{{ post.media.url }}" rel="nofollow">{{ post.media.url }}</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- POST BODY -->
|
|
||||||
<div class="post_body">{{ post.body|safe }}</div>
|
|
||||||
<div class="post_score" title="{{ post.score.1 }}">{{ post.score.0 }}<span class="label"> Upvotes</span></div>
|
|
||||||
<div class="post_footer">
|
|
||||||
<ul id="post_links">
|
|
||||||
<li><a href="{{ post.permalink }}">permalink</a></li>
|
|
||||||
<li><a href="https://reddit.com{{ post.permalink }}" rel="nofollow">reddit</a></li>
|
|
||||||
</ul>
|
|
||||||
<p>{{ post.upvote_ratio }}% Upvoted</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SORT FORM -->
|
<!-- SORT FORM -->
|
||||||
<form id="sort">
|
<form id="sort">
|
||||||
|
<p id="comment_count">{{post.comments.0}} {% if post.comments.0 == "1" %}comment{% else %}comments{% endif %} <span id="sorted_by">sorted by </span></p>
|
||||||
<select name="sort" title="Sort comments by">
|
<select name="sort" title="Sort comments by">
|
||||||
{% call utils::options(sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
{% call utils::options(sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
||||||
</select><button id="sort_submit" class="submit">
|
</select><button id="sort_submit" class="submit">
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
<form id="search_sort">
|
<form id="search_sort">
|
||||||
<input id="search" type="text" name="q" placeholder="Search" value="{{ params.q }}" title="Search libreddit">
|
<input id="search" type="text" name="q" placeholder="Search" value="{{ params.q|safe }}" title="Search libreddit">
|
||||||
{% if sub != "" %}
|
{% if sub != "" %}
|
||||||
<div id="inside">
|
<div id="inside">
|
||||||
<input type="checkbox" name="restrict_sr" id="restrict_sr" {% if params.restrict_sr != "" %}checked{% endif %}>
|
<input type="checkbox" name="restrict_sr" id="restrict_sr" {% if params.restrict_sr != "" %}checked{% endif %}>
|
||||||
@ -58,13 +58,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if all_posts_hidden_nsfw %}
|
{% if all_posts_hidden_nsfw %}
|
||||||
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
<span class="listing_warn">All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if no_posts %}
|
||||||
|
<center>No posts were found.</center>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if all_posts_filtered %}
|
{% if all_posts_filtered %}
|
||||||
<center>(All content on this page has been filtered)</center>
|
<span class="listing_warn">(All content on this page has been filtered)</span>
|
||||||
{% else if is_filtered %}
|
{% else if is_filtered %}
|
||||||
<center>(Content from r/{{ sub }} has been filtered)</center>
|
<span class="listing_warn">(Content from r/{{ sub }} has been filtered)</span>
|
||||||
{% else if params.typed != "sr_user" %}
|
{% else if params.typed != "sr_user" %}
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
{% if post.flags.nsfw && prefs.show_nsfw != "on" %}
|
{% if post.flags.nsfw && prefs.show_nsfw != "on" %}
|
||||||
|
@ -11,74 +11,91 @@
|
|||||||
<div id="settings">
|
<div id="settings">
|
||||||
<form action="/settings" method="POST">
|
<form action="/settings" method="POST">
|
||||||
<div class="prefs">
|
<div class="prefs">
|
||||||
|
<fieldset>
|
||||||
<legend>Appearance</legend>
|
<legend>Appearance</legend>
|
||||||
<div id="theme">
|
<div class="prefs-group">
|
||||||
<label for="theme">Theme:</label>
|
<label for="theme">Theme:</label>
|
||||||
<select name="theme">
|
<select name="theme" id="theme">
|
||||||
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
|
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
<legend>Interface</legend>
|
<legend>Interface</legend>
|
||||||
<div id="front_page">
|
<div class="prefs-group">
|
||||||
<label for="front_page">Front page:</label>
|
<label for="front_page">Front page:</label>
|
||||||
<select name="front_page">
|
<select name="front_page" id="front_page">
|
||||||
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
|
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="layout">
|
<div class="prefs-group">
|
||||||
<label for="layout">Layout:</label>
|
<label for="layout">Layout:</label>
|
||||||
<select name="layout">
|
<select name="layout" id="layout">
|
||||||
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
|
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="wide">
|
<div class="prefs-group">
|
||||||
<label for="wide">Wide UI:</label>
|
<label for="wide">Wide UI:</label>
|
||||||
<input type="hidden" value="off" name="wide">
|
<input type="hidden" value="off" name="wide">
|
||||||
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
|
<input type="checkbox" name="wide" id="wide" {% if prefs.wide == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
<legend>Content</legend>
|
<legend>Content</legend>
|
||||||
<div id="post_sort">
|
<div class="prefs-group">
|
||||||
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
|
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
|
||||||
<select name="post_sort">
|
<select name="post_sort">
|
||||||
{% call utils::options(prefs.post_sort, ["hot", "new", "top", "rising", "controversial"], "hot") %}
|
{% call utils::options(prefs.post_sort, ["hot", "new", "top", "rising", "controversial"], "hot") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="comment_sort">
|
<div class="prefs-group">
|
||||||
<label for="comment_sort">Default comment sort:</label>
|
<label for="comment_sort">Default comment sort:</label>
|
||||||
<select name="comment_sort">
|
<select name="comment_sort" id="comment_sort">
|
||||||
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="show_nsfw">
|
{% if !crate::utils::sfw_only() %}
|
||||||
|
<div class="prefs-group">
|
||||||
<label for="show_nsfw">Show NSFW posts:</label>
|
<label for="show_nsfw">Show NSFW posts:</label>
|
||||||
<input type="hidden" value="off" name="show_nsfw">
|
<input type="hidden" value="off" name="show_nsfw">
|
||||||
<input type="checkbox" name="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
|
<input type="checkbox" name="show_nsfw" id="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
<div id="blur_nsfw">
|
<div class="prefs-group">
|
||||||
<label for="blur_nsfw">Blur NSFW previews:</label>
|
<label for="blur_nsfw">Blur NSFW previews:</label>
|
||||||
<input type="hidden" value="off" name="blur_nsfw">
|
<input type="hidden" value="off" name="blur_nsfw">
|
||||||
<input type="checkbox" name="blur_nsfw" {% if prefs.blur_nsfw == "on" %}checked{% endif %}>
|
<input type="checkbox" name="blur_nsfw" id="blur_nsfw" {% if prefs.blur_nsfw == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
<div id="autoplay_videos">
|
{% endif %}
|
||||||
|
<div class="prefs-group">
|
||||||
<label for="autoplay_videos">Autoplay videos</label>
|
<label for="autoplay_videos">Autoplay videos</label>
|
||||||
<input type="hidden" value="off" name="autoplay_videos">
|
<input type="hidden" value="off" name="autoplay_videos">
|
||||||
<input type="checkbox" name="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
|
<input type="checkbox" name="autoplay_videos" id="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
<div id="use_hls">
|
<div class="prefs-group">
|
||||||
<label for="use_hls">Use HLS for videos
|
<label for="use_hls">Use HLS for videos</label>
|
||||||
<details id="feeds">
|
<details id="feeds">
|
||||||
<summary>Why?</summary>
|
<summary>Why?</summary>
|
||||||
<div id="feed_list" class="helper">Reddit videos require JavaScript (via HLS.js) to be enabled to be played with audio. Therefore, this toggle lets you either use Libreddit JS-free or utilize this feature.</div>
|
<div id="feed_list" class="helper">Reddit videos require JavaScript (via HLS.js) to be enabled to be played with audio. Therefore, this toggle lets you either use Libreddit JS-free or utilize this feature.</div>
|
||||||
</details>
|
</details>
|
||||||
</label>
|
|
||||||
<input type="hidden" value="off" name="use_hls">
|
<input type="hidden" value="off" name="use_hls">
|
||||||
<input type="checkbox" name="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
|
<input type="checkbox" name="use_hls" id="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
<div id="hide_hls_notification">
|
<div class="prefs-group">
|
||||||
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
|
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
|
||||||
<input type="hidden" value="off" name="hide_hls_notification">
|
<input type="hidden" value="off" name="hide_hls_notification">
|
||||||
<input type="checkbox" name="hide_hls_notification" {% if prefs.hide_hls_notification == "on" %}checked{% endif %}>
|
<input type="checkbox" name="hide_hls_notification" id="hide_hls_notification" {% if prefs.hide_hls_notification == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="prefs-group">
|
||||||
|
<label for="hide_awards">Hide awards</label>
|
||||||
|
<input type="hidden" value="off" name="hide_awards">
|
||||||
|
<input type="checkbox" name="hide_awards" id="hide_awards" {% if prefs.hide_awards == "on" %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
|
<div class="prefs-group">
|
||||||
|
<label for="disable_visit_reddit_confirmation">Do not confirm before visiting content on Reddit</label>
|
||||||
|
<input type="hidden" value="off" name="disable_visit_reddit_confirmation">
|
||||||
|
<input type="checkbox" name="disable_visit_reddit_confirmation" {% if prefs.disable_visit_reddit_confirmation == "on" %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
<input id="save" type="submit" value="Save">
|
<input id="save" type="submit" value="Save">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -115,7 +132,7 @@
|
|||||||
|
|
||||||
<div id="settings_note">
|
<div id="settings_note">
|
||||||
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
||||||
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&blur_nsfw={{ prefs.blur_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&blur_nsfw={{ prefs.blur_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&disable_visit_reddit_confirmation={{ prefs.disable_visit_reddit_confirmation }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&autoplay_videos={{ prefs.autoplay_videos }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -50,6 +50,10 @@
|
|||||||
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if no_posts %}
|
||||||
|
<center>No posts were found.</center>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if all_posts_filtered %}
|
{% if all_posts_filtered %}
|
||||||
<center>(All content on this page has been filtered)</center>
|
<center>(All content on this page has been filtered)</center>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -36,6 +36,10 @@
|
|||||||
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if no_posts %}
|
||||||
|
<center>No posts were found.</center>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if all_posts_filtered %}
|
{% if all_posts_filtered %}
|
||||||
<center>(All content on this page has been filtered)</center>
|
<center>(All content on this page has been filtered)</center>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -61,6 +61,127 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro post(post) -%}
|
||||||
|
<!-- POST CONTENT -->
|
||||||
|
<div class="post highlighted">
|
||||||
|
<p class="post_header">
|
||||||
|
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
|
||||||
|
<span class="dot">•</span>
|
||||||
|
<a class="post_author {{ post.author.distinguished }}" href="/user/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
||||||
|
{% if post.author.flair.flair_parts.len() > 0 %}
|
||||||
|
<small class="author_flair">{% call render_flair(post.author.flair.flair_parts) %}</small>
|
||||||
|
{% endif %}
|
||||||
|
<span class="dot">•</span>
|
||||||
|
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
||||||
|
{% if !post.awards.is_empty() && prefs.hide_awards != "on" %}
|
||||||
|
<span class="dot">•</span>
|
||||||
|
<span class="awards">
|
||||||
|
{% for award in post.awards.clone() %}
|
||||||
|
<span class="award" title="{{ award.name }}">
|
||||||
|
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
||||||
|
{{ award.count }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<h1 class="post_title">
|
||||||
|
{{ post.title }}
|
||||||
|
{% if post.flair.flair_parts.len() > 0 %}
|
||||||
|
<a href="/r/{{ post.community }}/search?q=flair_name%3A%22{{ post.flair.text }}%22&restrict_sr=on"
|
||||||
|
class="post_flair"
|
||||||
|
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};">{% call render_flair(post.flair.flair_parts) %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- POST MEDIA -->
|
||||||
|
<!-- post_type: {{ post.post_type }} -->
|
||||||
|
{% if post.post_type == "image" %}
|
||||||
|
<div class="post_media_content">
|
||||||
|
<a href="{{ post.media.url }}" class="post_media_image" >
|
||||||
|
<svg
|
||||||
|
width="{{ post.media.width }}px"
|
||||||
|
height="{{ post.media.height }}px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<image width="100%" height="100%" href="{{ post.media.url }}"/>
|
||||||
|
<desc>
|
||||||
|
<img loading="lazy" alt="Post image" src="{{ post.media.url }}"/>
|
||||||
|
</desc>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% else if post.post_type == "video" || post.post_type == "gif" %}
|
||||||
|
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
|
||||||
|
<script src="/hls.min.js"></script>
|
||||||
|
<div class="post_media_content">
|
||||||
|
<video class="post_media_video short {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls>
|
||||||
|
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
||||||
|
<source src="{{ post.media.url }}" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
<script src="/playHLSVideo.js"></script>
|
||||||
|
{% else %}
|
||||||
|
<div class="post_media_content">
|
||||||
|
<video class="post_media_video" src="{{ post.media.url }}" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %} loop><a href={{ post.media.url }}>Video</a></video>
|
||||||
|
</div>
|
||||||
|
{% call render_hls_notification(post.permalink[1..]) %}
|
||||||
|
{% endif %}
|
||||||
|
{% else if post.post_type == "gallery" %}
|
||||||
|
<div class="gallery">
|
||||||
|
{% for image in post.gallery -%}
|
||||||
|
<figure>
|
||||||
|
<a href="{{ image.url }}" ><img loading="lazy" alt="Gallery image" src="{{ image.url }}"/></a>
|
||||||
|
<figcaption>
|
||||||
|
<p>{{ image.caption }}</p>
|
||||||
|
{% if image.outbound_url.len() > 0 %}
|
||||||
|
<p><a class="outbound_url" href="{{ image.outbound_url }}" rel="nofollow">{{ image.outbound_url }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
{% else if post.post_type == "link" %}
|
||||||
|
<a id="post_url" href="{{ post.media.url }}" rel="nofollow">{{ post.media.url }}</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- POST BODY -->
|
||||||
|
<div class="post_body">{{ post.body|safe }}</div>
|
||||||
|
<div class="post_score" title="{{ post.score.1 }}">{{ post.score.0 }}<span class="label"> Upvotes</span></div>
|
||||||
|
<div class="post_footer">
|
||||||
|
<ul id="post_links">
|
||||||
|
<li class="desktop_item"><a href="{{ post.permalink }}">permalink</a></li>
|
||||||
|
<li class="mobile_item"><a href="{{ post.permalink }}">link</a></li>
|
||||||
|
{% if post.num_duplicates > 0 %}
|
||||||
|
<li class="desktop_item"><a href="/r/{{ post.community }}/duplicates/{{ post.id }}">duplicates</a></li>
|
||||||
|
<li class="mobile_item"><a href="/r/{{ post.community }}/duplicates/{{ post.id }}">dupes</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% call external_reddit_link(post.permalink) %}
|
||||||
|
</ul>
|
||||||
|
<p>{{ post.upvote_ratio }}%<span id="upvoted"> Upvoted</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro external_reddit_link(permalink) %}
|
||||||
|
{% for dev_type in ["desktop", "mobile"] %}
|
||||||
|
<li class="{{ dev_type }}_item">
|
||||||
|
<a
|
||||||
|
{% if prefs.disable_visit_reddit_confirmation != "on" %}
|
||||||
|
href="#popup"
|
||||||
|
{% else %}
|
||||||
|
href="https://reddit.com{{ permalink }}"
|
||||||
|
rel="nofollow"
|
||||||
|
{% endif %}
|
||||||
|
>reddit</a>
|
||||||
|
|
||||||
|
{% if prefs.disable_visit_reddit_confirmation != "on" %}
|
||||||
|
{% call visit_reddit_confirmation(permalink) %}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro post_in_list(post) -%}
|
{% macro post_in_list(post) -%}
|
||||||
<div class="post {% if post.flags.stickied %}stickied{% endif %}" id="{{ post.id }}">
|
<div class="post {% if post.flags.stickied %}stickied{% endif %}" id="{{ post.id }}">
|
||||||
<p class="post_header">
|
<p class="post_header">
|
||||||
@ -75,7 +196,7 @@
|
|||||||
<a class="post_author {{ post.author.distinguished }}" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
<a class="post_author {{ post.author.distinguished }}" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
||||||
<span class="dot">•</span>
|
<span class="dot">•</span>
|
||||||
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
||||||
{% if !post.awards.is_empty() %}
|
{% if !post.awards.is_empty() && prefs.hide_awards != "on" %}
|
||||||
{% for award in post.awards.clone() %}
|
{% for award in post.awards.clone() %}
|
||||||
<span class="award" title="{{ award.name }}">
|
<span class="award" title="{{ award.name }}">
|
||||||
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
||||||
@ -110,19 +231,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "gif" %}
|
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "gif" %}
|
||||||
<div class="post_media_content">
|
<div class="post_media_content">
|
||||||
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" preload="none" controls loop {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
|
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls loop {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
|
||||||
</div>
|
</div>
|
||||||
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "video" %}
|
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "video" %}
|
||||||
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
|
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
|
||||||
<div class="post_media_content">
|
<div class="post_media_content">
|
||||||
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %} {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" controls preload="none">
|
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %} {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" controls preload="none">
|
||||||
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
||||||
<source src="{{ post.media.url }}" type="video/mp4" />
|
<source src="{{ post.media.url }}" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="post_media_content">
|
<div class="post_media_content">
|
||||||
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" preload="none" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
|
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
|
||||||
</div>
|
</div>
|
||||||
{% call render_hls_notification(format!("{}%23{}", &self.url[1..].replace("&", "%26").replace("+", "%2B"), post.id)) %}
|
{% call render_hls_notification(format!("{}%23{}", &self.url[1..].replace("&", "%26").replace("+", "%2B"), post.id)) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -156,3 +277,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro visit_reddit_confirmation(url) -%}
|
||||||
|
<div class="popup" id="popup">
|
||||||
|
<div class="popup-inner">
|
||||||
|
<h1>You are about to leave Libreddit</h1>
|
||||||
|
<p>Do you want to continue?</p>
|
||||||
|
<p id="reddit_url">https://www.reddit.com{{ url }}</p>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 639.24 563">
|
||||||
|
<defs>
|
||||||
|
<style>.cls-1{fill:#000000;}.cls-2{fill:#f8aa00;}</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-2" d="M322.03,0c1.95,2.5,4.88,.9,7.33,1.65,10.5,3.21,17.65,10.39,22.83,19.35,93.64,162.06,186.98,324.29,280.25,486.56,15.73,20.19,2.49,51.27-22.92,54.37-1.21,.19-2.72-.54-3.49,1.08H239.03c-70.33-2.43-141.6,.79-212.08-1.74-17.49-4.92-23.16-15.88-26.91-32.26l-.04-1.97C88.74,354.76,194.49,188.2,289.92,18.43c6.2-10.66,15.03-16.94,27.61-17.36,.95-.03,2.05,.18,2.51-1.07h2Zm-2.43,545c94.95-.02,189.9,.04,284.85-.02,11.84-.73,20.75-13.19,16.68-23.55C523.83,355.97,430.74,187.62,332.05,23.07c-7.93-9.02-22.2-6.58-27.23,3.22C230.28,156.11,155.21,285.64,80.41,415.31c-19.88,34.41-39.31,69.07-59.78,103.14-2.43,4.05-4.24,8.8-1.68,14.18,3.92,8.24,9.59,12.37,18.82,12.37,93.95,0,187.9,0,281.85,0Z"/>
|
||||||
|
<path class="cls-1" d="M319.61,545c-93.95,0-187.9,0-281.85,0-9.22,0-14.89-4.13-18.82-12.37-2.56-5.38-.75-10.13,1.68-14.18,20.47-34.07,39.9-68.73,59.78-103.14C155.21,285.64,230.28,156.11,304.82,26.29c5.03-9.8,19.3-12.24,27.23-3.22,98.7,164.55,191.79,332.9,289.1,498.35,4.06,10.36-4.85,22.82-16.68,23.55-94.94,.06-189.9,0-284.85,.02Zm.44-462.31C238.88,223.22,158.17,362.95,77.28,503h485.54c-80.94-140.13-161.61-279.79-242.77-420.31Z"/>
|
||||||
|
<path class="cls-2" d="M320.05,82.69c81.16,140.52,161.83,280.18,242.77,420.31H77.28C158.17,362.95,238.88,223.22,320.05,82.69Zm36.05,118.99c-.14-46.75-68.32-52.32-74.66-4.76,.73,51.49,9.2,102.97,12.63,154.49,1.18,13.14,10.53,21.81,23.32,22.76,13.12,.97,23.89-9.13,24.96-21.58,4.44-49.99,9.4-101.22,13.76-150.91Zm-36.56,271.4c48.8,.76,49.24-74.7-.31-75.47-53.45,3-46.02,78.12,.31,75.47Z"/>
|
||||||
|
<path class="cls-1" d="M356.1,201.67c-4.36,49.69-9.31,100.91-13.76,150.91-1.07,12.45-11.84,22.56-24.96,21.58-12.79-.95-22.14-9.63-23.31-22.76-3.43-51.52-11.9-103-12.63-154.49,6.33-47.53,74.51-42.03,74.66,4.76Z"/>
|
||||||
|
<path class="cls-1" d="M319.54,473.08c-46.34,2.64-53.75-72.47-.31-75.47,49.56,.78,49.1,76.24,.31,75.47Z"/>
|
||||||
|
</svg>
|
||||||
|
<a id="goback" href="#">No, go back!</a>
|
||||||
|
<a id="toreddit" href="https://www.reddit.com{{ url }}" rel="nofollow">Yes, take me to Reddit</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
Reference in New Issue
Block a user