Compare commits

..

40 Commits

Author SHA1 Message Date
9bf6194b09 v0.28.1
Remove font-weight associated with instance info button, which made the
icon look ghastly in Chrome.
2023-01-31 00:14:23 -07:00
f405f509c4 v0.28.0 2023-01-30 02:07:32 -07:00
8be5fdee2d Implement instance info endpoint (JSON, YAML, TXT) (#685)
Co-authored-by: Daniel Valentine <daniel@vielle.ws>
Co-authored-by: spikecodes <19519553+spikecodes@users.noreply.github.com>
2023-01-30 02:02:43 -07:00
7efa26e811 Fix #699 2023-01-21 00:35:49 -08:00
755fff0818 Use Markdown Highlights in README 2023-01-19 18:28:24 -08:00
53e1e302d5 Register Dockerfile.* as Dockerfiles for Linguist (#694)
This allows GitHub Linguist to generate slightly more accurate language
stats for this repository, and also enable syntax highlighting in the
GitHub web UI. Due to caching, it may take a few days for this change to
have a visible effect on github.com.
2023-01-16 21:57:55 -07:00
3d0287f04f Add comment count in post (#659)
* Add comment count in post

* Restyle comment count
2023-01-16 12:05:53 -08:00
7cb132af01 Update packages 2023-01-16 11:09:57 -08:00
63b0b936aa Update CREDITS file. 2023-01-12 02:19:09 -07:00
412122d7d9 v0.27.1 2023-01-12 01:57:03 -07:00
27091db53b Create rust-tests.yml (#690)
This will run tests on every push and PR to master.
2023-01-12 01:43:08 -07:00
2a54043afc Simplify listener definition (#681)
This simplifies the logic to build the listener by using more clap
features instead of manually accessing the PORT environment variable.
This also removes unnecessary `unwrap_or` calls that set defaults that
are already set by clap.
2023-01-12 01:41:59 -07:00
e238a7b168 Bump tokio from 1.23.0 to 1.23.1 (#691)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.23.0 to 1.23.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.23.0...tokio-1.23.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-12 01:39:23 -07:00
1e554acd20 Merge pull request #687 from jojosch/fix-cfg-test
Fix tests
2023-01-04 16:03:48 -05:00
dff91da877 config: fix SFW test 2023-01-04 11:12:19 +01:00
f6bb53e388 Mark search query as safe in askama template (#686) 2023-01-03 20:55:17 -08:00
709292339a Merge pull request #674 from spenserblack/codespace 2023-01-03 20:12:36 -05:00
799e5b882b Merge pull request #667 from erdnaxe/scrollbar_theme 2023-01-03 19:34:40 -05:00
0ff92cbfe3 v0.27.0 2023-01-03 11:21:27 -07:00
e9891236cd Remove unnecessary SFW-only disclosure in settings in SFW-only mode. 2023-01-03 11:20:55 -07:00
e2c48c3438 Add hide_awards config option (fixes #442) 2023-01-03 11:16:22 -07:00
9a7b3b29f5 Merge remote-tracking branch 'origin/master' into hide_awards 2023-01-03 11:12:27 -07:00
10add895fb Merge pull request #683 from Tokarak/master 2023-01-03 09:28:13 -07:00
050eaedf15 Remove unused dep "async-recursion"
Found using cargo-udeps. Checked.
2023-01-03 14:56:17 +00:00
5b06a3fc64 Add config system to read from file (#664)
Co-authored-by: Daniel Valentine <daniel@vielle.ws>
2023-01-03 02:55:22 -07:00
4817f51bc0 v0.26.0 2023-01-03 02:40:44 -07:00
c83a4e0cc8 Landing page for NSFW content, SFW-only mode (#656)
Co-authored-by: Matt <matt@matthew.science>
Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2023-01-03 02:39:45 -07:00
c15f305be0 v0.25.3 2023-01-01 23:54:35 -07:00
222d216854 Merge pull request #673 from spenserblack/code-in-new-tab 2023-01-01 23:08:15 -05:00
6a785baa2c Add hide_awards config 2023-01-01 21:39:38 -05:00
6d8aaba8bb Merge pull request #676 from ellieeet123/master 2023-01-01 19:23:30 -05:00
6cf3748642 Fix for #675
/:id route now accepts 7 character post IDs.
2023-01-01 17:06:58 -06:00
9c938c6210 build: enable LTO, set codegen-unit to 1 and strip the binary (#467)
Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2023-01-01 14:33:31 -07:00
b1182e7cf5 Create devcontainer.json 2023-01-01 14:52:33 -05:00
a49d399f72 Link to libreddit/libreddit and open in new tab
This sets the target of the "code" link to `_blank`, so that it will
open in a new tab in browsers. Because the GitHub page is a different
context from libreddit, and accessing the repository doesn't imply that
the user is finished browsing libreddit, this seemed reasonable. This
also changes the link from `spikecodes/libreddit` to
`libreddit/libreddit`.
2023-01-01 13:43:33 -05:00
9178b50b73 fix a11y and HTML issues on settings page (#662)
- connect labels with corresponding form controls
- use fieldsets to group form sections
- don't nest details/summary element into label
2023-01-01 01:56:09 -07:00
b5d04f1a50 v0.25.2 2022-12-31 21:34:15 -07:00
9e434e7db6 Search - add support for raw reddit links (#663)
* Search - add support for raw reddit links

If a search query starts with 'https://www.reddit.com/' or 'https://old.reddit.com/',
this prefix will be truncated and the query will be processed normally.
For example, a search query 'https://www.reddit.com/r/rust' will redirect to
r/rust.

* Search - support a wider variety of reddit links.

Add once cell dependency for static regex support (avoid compiling the
same regex multiple times).
All search queries are now matched against a regex (provided by @Daniel-Valentine)
that determines if it is a reddit link. If it is, the prefix specifying
the reddit instance will be truncated from the query that will then be
processed normally.
For example, the query 'https://www.reddit.com/r/rust' will be treated
the same way as the query 'r/rust'.
2022-12-31 20:57:42 -07:00
ab30b8bbec Bugfix: 'all posts are hidden because NSFW' when no posts where found (#666)
* Fix 'all_posts_hidden_nsfw' when there are no posts.

If a search query yielded no results and the user set nsfw posts to be
hidden, libreddit would show 'All posts are hidden because they are NSFW.
Enable "Show NSFW posts" in settings to view'. This is fixed by
verifying tnat posts.len > 0 before setting 'all_posts_hidden_nsfw' to
true.

* Add a message when no posts were found.

* Delete 2
2022-12-31 19:11:59 -07:00
1fa9f27619 Theme browser scrollbar
Hint current color-scheme to the browser. This enables chromium-based
browsers to change the scrollbar color according to the current theme.
2022-12-26 22:57:04 +01:00
33 changed files with 1180 additions and 327 deletions

View 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
View File

@ -0,0 +1 @@
Dockerfile.* linguist-language=Dockerfile

22
.github/workflows/rust-tests.yml vendored Normal file
View 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

View File

@ -3,6 +3,7 @@
accountForIssues <52367365+accountForIssues@users.noreply.github.com>
Adrian Lebioda <adrianlebioda@gmail.com>
alefvanoon <53198048+alefvanoon@users.noreply.github.com>
Alexandre Iooss <erdnaxe@crans.org>
alyaeanyx <alexandra.hollmeier@mailbox.org>
AndreVuillemot160 <84594011+AndreVuillemot160@users.noreply.github.com>
Andrew Kaufman <57281817+andrew-kaufman@users.noreply.github.com>
@ -18,18 +19,22 @@ dacousb <53299044+dacousb@users.noreply.github.com>
Daniel Valentine <Daniel-Valentine@users.noreply.github.com>
Daniel Valentine <daniel@vielle.ws>
dbrennand <52419383+dbrennand@users.noreply.github.com>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com>
Dyras <jevwmguf@duck.com>
Edward <101938856+EdwardLangdon@users.noreply.github.com>
elliot <75391956+ellieeet123@users.noreply.github.com>
erdnaxe <erdnaxe@users.noreply.github.com>
Esmail EL BoB <github.defilable@simplelogin.co>
FireMasterK <20838718+FireMasterK@users.noreply.github.com>
George Roubos <cowkingdom@hotmail.com>
git-bruh <e817509a-8ee9-4332-b0ad-3a6bdf9ab63f@aleeas.com>
gmnsii <95436780+gmnsii@users.noreply.github.com>
guaddy <67671414+guaddy@users.noreply.github.com>
Harsh Mishra <erbeusgriffincasper@gmail.com>
igna <igna@intent.cool>
imabritishcow <bcow@protonmail.com>
Johannes Schleifenbaum <johannes@js-webcoding.de>
Josiah <70736638+fres7h@users.noreply.github.com>
JPyke3 <pyke.jacob1@gmail.com>
Kavin <20838718+FireMasterK@users.noreply.github.com>
@ -44,6 +49,7 @@ Macic <46872282+Macic-Dev@users.noreply.github.com>
Mario A <10923513+Midblyte@users.noreply.github.com>
Matthew Crossman <matt@crossman.page>
Matthew E <matt@matthew.science>
Matthew Esposito <matt@matthew.science>
Mennaruuk <52135169+Mennaruuk@users.noreply.github.com>
mikupls <93015331+mikupls@users.noreply.github.com>
Nainar <nainar.mb@gmail.com>
@ -62,11 +68,13 @@ robrobinbin <>
robrobinbin <8597693+robrobinbin@users.noreply.github.com>
robrobinbin <robindepril@gmail.com>
Ruben Elshof <15641671+rubenelshof@users.noreply.github.com>
Rupert Angermeier <rangermeier@users.noreply.github.com>
Scoder12 <34356756+Scoder12@users.noreply.github.com>
Slayer <51095261+GhostSlayer@users.noreply.github.com>
Soheb <somoso@users.noreply.github.com>
somini <somini@users.noreply.github.com>
somoso <github@soheb.anonaddy.com>
Spenser Black <spenserblack01@gmail.com>
Spike <19519553+spikecodes@users.noreply.github.com>
spikecodes <19519553+spikecodes@users.noreply.github.com>
sybenx <syb@duck.com>
@ -74,6 +82,7 @@ TheCultLeader666 <65368815+TheCultLeader666@users.noreply.github.com>
TheFrenchGhosty <47571719+TheFrenchGhosty@users.noreply.github.com>
The TwilightBlood <hwengerstickel@protonmail.com>
tirz <36501933+tirz@users.noreply.github.com>
Tokarak <63452145+Tokarak@users.noreply.github.com>
Tsvetomir Bonev <invakid404@riseup.net>
Vladislav Nepogodin <nepogodin.vlad@gmail.com>
Walkx <walkxnl@gmail.com>

440
Cargo.lock generated
View File

@ -10,9 +10,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aho-corasick"
version = "0.7.19"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
@ -75,22 +75,11 @@ dependencies = [
"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]]
name = "async-trait"
version = "0.1.58"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
dependencies = [
"proc-macro2",
"quote",
@ -111,9 +100,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.1"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bitflags"
@ -143,9 +132,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
version = "2.3.2"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@ -153,37 +142,44 @@ dependencies = [
[[package]]
name = "bstr"
version = "0.2.17"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.11.1"
name = "build_html"
version = "2.2.0"
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]]
name = "bytes"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cached"
version = "0.40.0"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b4147cd94d5fbdc2ab71b11d50a2f45493625576b3bb70257f59eedea69f3d"
checksum = "5e5877db5d1af7fae60d06b5db9430b68056a69b3582a0be8e3691e87654aeb6"
dependencies = [
"async-trait",
"async_once",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
"hashbrown",
"hashbrown 0.13.2",
"instant",
"lazy_static",
"once_cell",
@ -193,12 +189,13 @@ dependencies = [
[[package]]
name = "cached_proc_macro"
version = "0.15.0"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "751f7f4e7a091545e7f6c65bacc404eaee7e87bfb1f9ece234a1caa173dc16f2"
checksum = "e10ca87c81aaa3a949dbbe2b5e6c2c45dbc94ba4897e45ea31ff9ec5087be3dc"
dependencies = [
"cached_proc_macro_types",
"darling",
"proc-macro2",
"quote",
"syn",
]
@ -211,9 +208,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
[[package]]
name = "cc"
version = "1.0.76"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
@ -223,9 +220,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.0.24"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03"
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
dependencies = [
"bitflags",
"clap_lex",
@ -233,18 +230,18 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "cookie"
version = "0.16.1"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
dependencies = [
"time",
"version_check",
@ -296,9 +293,9 @@ dependencies = [
[[package]]
name = "darling"
version = "0.13.4"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
dependencies = [
"darling_core",
"darling_macro",
@ -306,9 +303,9 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.13.4"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
dependencies = [
"fnv",
"ident_case",
@ -320,9 +317,9 @@ dependencies = [
[[package]]
name = "darling_macro"
version = "0.13.4"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
dependencies = [
"darling_core",
"quote",
@ -331,9 +328,9 @@ dependencies = [
[[package]]
name = "digest"
version = "0.10.5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
@ -363,6 +360,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]]
name = "futures"
version = "0.3.25"
@ -462,9 +465,9 @@ dependencies = [
[[package]]
name = "globset"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr",
@ -499,10 +502,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.1.19"
name = "hashbrown"
version = "0.13.2"
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 = [
"libc",
]
@ -567,9 +576,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.23.0"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
dependencies = [
"http",
"hyper",
@ -598,12 +607,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
@ -617,9 +626,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
@ -638,9 +647,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.137"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libflate"
@ -664,11 +673,11 @@ dependencies = [
[[package]]
name = "libreddit"
version = "0.25.1"
version = "0.28.1"
dependencies = [
"askama",
"async-recursion",
"brotli",
"build_html",
"cached",
"clap",
"cookie",
@ -677,14 +686,18 @@ dependencies = [
"hyper-rustls",
"libflate",
"lipsum",
"once_cell",
"percent-encoding",
"regex",
"route-recognizer",
"rust-embed",
"sealed_test",
"serde",
"serde_json",
"serde_yaml",
"time",
"tokio",
"toml",
"url",
]
@ -754,14 +767,14 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.42.0",
"windows-sys",
]
[[package]]
name = "nom"
version = "7.1.1"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
@ -769,19 +782,28 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.14.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.16.0"
name = "num_threads"
version = "0.1.6"
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]]
name = "openssl-probe"
@ -791,9 +813,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "os_str_bytes"
version = "6.4.0"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "parking"
@ -813,15 +835,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.4"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.42.0",
"windows-sys",
]
[[package]]
@ -850,18 +872,24 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.47"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
name = "quick-error"
version = "1.2.3"
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 = [
"proc-macro2",
]
@ -907,9 +935,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
@ -922,6 +950,15 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ring"
version = "0.16.20"
@ -986,9 +1023,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.20.7"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
dependencies = [
"log",
"ring",
@ -1010,18 +1047,30 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
"base64",
]
[[package]]
name = "ryu"
version = "1.0.11"
name = "rusty-forkfork"
version = "0.4.0"
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]]
name = "same-file"
@ -1034,12 +1083,11 @@ dependencies = [
[[package]]
name = "schannel"
version = "0.1.20"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
dependencies = [
"lazy_static",
"windows-sys 0.36.1",
"windows-sys",
]
[[package]]
@ -1058,6 +1106,28 @@ dependencies = [
"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]]
name = "security-framework"
version = "2.7.0"
@ -1083,18 +1153,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.147"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.147"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
@ -1103,15 +1173,28 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.87"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [
"itoa",
"ryu",
"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]]
name = "sha2"
version = "0.10.6"
@ -1171,9 +1254,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.103"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
@ -1181,19 +1264,33 @@ dependencies = [
]
[[package]]
name = "thiserror"
version = "1.0.37"
name = "tempfile"
version = "3.3.0"
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 = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
@ -1207,6 +1304,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [
"itoa",
"libc",
"num_threads",
"serde",
"time-core",
"time-macros",
@ -1244,9 +1343,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.21.2"
version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [
"autocfg",
"bytes",
@ -1259,14 +1358,14 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "1.8.0"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
@ -1298,6 +1397,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.2"
@ -1326,15 +1434,15 @@ dependencies = [
[[package]]
name = "try-lock"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "typenum"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicase"
@ -1353,9 +1461,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
@ -1366,6 +1474,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
[[package]]
name = "untrusted"
version = "0.7.1"
@ -1389,6 +1503,15 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "waker-fn"
version = "1.1.0"
@ -1527,19 +1650,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "windows-sys"
version = "0.42.0"
@ -1547,82 +1657,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

View File

@ -3,30 +3,39 @@ name = "libreddit"
description = " Alternative private front-end to Reddit"
license = "AGPL-3.0"
repository = "https://github.com/spikecodes/libreddit"
version = "0.25.1"
version = "0.28.1"
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
edition = "2021"
[dependencies]
askama = { version = "0.11.1", default-features = false }
async-recursion = "1.0.0"
cached = "0.40.0"
clap = { version = "4.0.24", default-features = false, features = ["std"] }
regex = "1.7.0"
serde = { version = "1.0.147", features = ["derive"] }
cookie = "0.16.1"
cached = "0.42.0"
clap = { version = "4.1.1", default-features = false, features = ["std", "env"] }
regex = "1.7.1"
serde = { version = "1.0.152", features = ["derive"] }
cookie = "0.16.2"
futures-lite = "1.12.0"
hyper = { version = "0.14.23", features = ["full"] }
hyper-rustls = "0.23.0"
hyper-rustls = "0.23.2"
percent-encoding = "2.2.0"
route-recognizer = "0.3.1"
serde_json = "1.0.87"
tokio = { version = "1.21.2", features = ["full"] }
time = "0.3.17"
serde_json = "1.0.91"
tokio = { version = "1.24.2", features = ["full"] }
time = { version = "0.3.17", features = ["local-offset"] }
url = "2.3.1"
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
libflate = "1.2.0"
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]
lipsum = "0.8.2"
sealed_test = "1.0.0"
[profile.release]
codegen-units = 1
lto = true
strip = "symbols"

View File

@ -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
**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>
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](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
```
## 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 |
|-------------------------|-----------------------------------------------------------------------------------------------------|---------------|
@ -199,6 +209,14 @@ Assign a default value for each setting by passing environment variables to Libr
| `USE_HLS` | `["on", "off"]` | `off` |
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
| `HIDE_AWARDS` | `["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
@ -212,11 +230,12 @@ LIBREDDIT_DEFAULT_WIDE=on LIBREDDIT_DEFAULT_THEME=dark libreddit -r
## Proxying using NGINX
**NOTE** If you're [proxying Libreddit through an NGINX Reverse Proxy](https://github.com/libreddit/libreddit/issues/122#issuecomment-782226853), add
```nginx
proxy_http_version 1.1;
```
to your NGINX configuration file above your `proxy_pass` line.
> **Note**
> If you're [proxying Libreddit through an NGINX Reverse Proxy](https://github.com/libreddit/libreddit/issues/122#issuecomment-782226853), add
> ```nginx
> proxy_http_version 1.1;
> ```
> to your NGINX configuration file above your `proxy_pass` line.
## systemd

View File

@ -40,6 +40,15 @@
},
"LIBREDDIT_HIDE_HLS_NOTIFICATION": {
"required": false
},
"LIBREDDIT_SFW_ONLY": {
"required": false
},
"LIBREDDIT_DEFAULT_HIDE_AWARDS": {
"required": false
}
"LIBREDDIT_BANNER": {
"required": false
}
}
}

20
build.rs Normal file
View 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
View 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()))
}

View File

@ -3,7 +3,7 @@
use crate::client::json;
use crate::server::RequestExt;
use crate::subreddit::{can_access_quarantine, quarantine};
use crate::utils::{error, filter_posts, get_filters, parse_post, template, Post, Preferences};
use crate::utils::{error, filter_posts, get_filters, nsfw_landing, parse_post, setting, template, Post, Preferences};
use askama::Template;
use hyper::{Body, Request, Response};
@ -65,8 +65,16 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
match json(path, quarantined).await {
// Process response JSON.
Ok(response) => {
let filters = get_filters(&req);
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="
@ -193,7 +201,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
params: DuplicatesParams { before, after, sort },
post,
duplicates,
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
num_posts_filtered,
all_posts_filtered,

205
src/instance_info.rs Normal file
View 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,
}

View File

@ -3,7 +3,9 @@
#![allow(clippy::cmp_owned)]
// Reference local files
mod config;
mod duplicates;
mod instance_info;
mod post;
mod search;
mod settings;
@ -12,13 +14,14 @@ mod user;
mod utils;
// Import Crates
use clap::{Arg, Command};
use clap::{Arg, ArgAction, Command};
use futures_lite::FutureExt;
use hyper::{header::HeaderValue, Body, Request, Response};
mod client;
use client::{canonical_path, proxy};
use once_cell::sync::Lazy;
use server::RequestExt;
use utils::{error, redirect, ThemeAssets};
@ -129,8 +132,10 @@ async fn main() {
.short('p')
.long("port")
.value_name("PORT")
.env("PORT")
.help("Port to listen on")
.default_value("8080")
.action(ArgAction::Set)
.num_args(1),
)
.arg(
@ -144,17 +149,24 @@ async fn main() {
)
.get_matches();
let address = matches.get_one("address").map(|m: &String| m.as_str()).unwrap_or("0.0.0.0");
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 address = matches.get_one::<String>("address").unwrap();
let port = matches.get_one::<String>("port").unwrap();
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...");
// Begin constructing a server
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)
app.default_headers = headers! {
"Referrer-Policy" => "no-referrer",
@ -282,6 +294,10 @@ async fn main() {
// Handle about pages
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>| {
Box::pin(async move {
match req.param("id").as_deref() {
@ -289,7 +305,7 @@ async fn main() {
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
// 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 {
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,

View File

@ -3,7 +3,7 @@ use crate::client::json;
use crate::server::RequestExt;
use crate::subreddit::{can_access_quarantine, quarantine};
use crate::utils::{
error, format_num, get_filters, param, parse_post, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flair, FlairPart, 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};
@ -55,7 +55,15 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
Ok(response) => {
// Parse the JSON into Post and Comment structs
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();
// 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,
post,
sort,
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
single_thread,
url,
})
@ -81,7 +89,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
}
// 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
let comments = json["data"]["children"].as_array().map_or(Vec::new(), std::borrow::ToOwned::to_owned);
@ -101,7 +109,7 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
// If this comment contains replies, handle those too
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 {
Vec::new()
};
@ -169,6 +177,7 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
awards,
collapsed,
is_filtered,
prefs: Preferences::new(req),
}
})
.collect()

View File

@ -1,5 +1,5 @@
// 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::{
client::json,
subreddit::{can_access_quarantine, quarantine},
@ -7,6 +7,8 @@ use crate::{
};
use askama::Template;
use hyper::{Body, Request, Response};
use once_cell::sync::Lazy;
use regex::Regex;
// STRUCTS
struct SearchParams {
@ -44,13 +46,23 @@ struct SearchTemplate {
all_posts_filtered: bool,
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
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
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 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() {
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(),
typed,
},
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
is_filtered: true,
all_posts_filtered: false,
all_posts_hidden_nsfw: false,
no_posts: false,
})
} else {
match Post::fetch(&path, quarantined).await {
Ok((mut posts, after)) => {
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 {
posts,
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(),
typed,
},
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
is_filtered: false,
all_posts_filtered,
all_posts_hidden_nsfw,
no_posts,
})
}
Err(msg) => {

View File

@ -19,7 +19,7 @@ struct SettingsTemplate {
// CONSTANTS
const PREFS: [&str; 11] = [
const PREFS: [&str; 12] = [
"theme",
"front_page",
"layout",
@ -31,6 +31,7 @@ const PREFS: [&str; 11] = [
"use_hls",
"hide_hls_notification",
"autoplay_videos",
"hide_awards",
];
// FUNCTIONS
@ -39,7 +40,7 @@ const PREFS: [&str; 11] = [
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
let url = req.uri().to_string();
template(SettingsTemplate {
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
})
}

View File

@ -1,6 +1,6 @@
// CRATES
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 askama::Template;
@ -26,6 +26,7 @@ struct SubredditTemplate {
all_posts_filtered: bool,
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
all_posts_hidden_nsfw: bool,
no_posts: bool,
}
#[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 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");
@ -108,29 +115,32 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
posts: Vec::new(),
sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
redirect_url,
is_filtered: true,
all_posts_filtered: false,
all_posts_hidden_nsfw: false,
no_posts: false,
})
} else {
match Post::fetch(&path, quarantined).await {
Ok((mut posts, after)) => {
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 {
sub,
posts,
sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after").unwrap_or_default(), after),
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
redirect_url,
is_filtered: false,
all_posts_filtered,
all_posts_hidden_nsfw,
no_posts,
})
}
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(),
url: req.uri().to_string(),
sub,
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
};
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 preferences = Preferences::new(req);
let preferences = Preferences::new(&req);
let mut sub_list = preferences.subscriptions;
let mut filters = preferences.filters;
@ -309,7 +319,7 @@ pub async fn wiki(req: Request<Body>) -> Result<Response<Body>, String> {
sub,
wiki: rewrite_urls(response["data"]["content_html"].as_str().unwrap_or("<h3>Wiki not found</h3>")),
page,
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
}),
Err(msg) => {
@ -347,7 +357,7 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
// ),
sub,
page: "Sidebar".to_string(),
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
}),
Err(msg) => {
@ -420,5 +430,6 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
members: format_num(members),
active: format_num(active),
wiki: res["data"]["wiki_enabled"].as_bool().unwrap_or_default(),
nsfw: res["data"]["over18"].as_bool().unwrap_or_default(),
})
}

View File

@ -1,7 +1,7 @@
// CRATES
use crate::client::json;
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 hyper::{Body, Request, Response};
use time::{macros::format_description, OffsetDateTime};
@ -26,6 +26,7 @@ struct UserTemplate {
all_posts_filtered: bool,
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
all_posts_hidden_nsfw: bool,
no_posts: bool,
}
// FUNCTIONS
@ -45,8 +46,17 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
// Retrieve other variables from Libreddit request
let sort = param(&path, "sort").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();
// 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);
if filters.contains(&["u_", &username].concat()) {
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()),
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
listing,
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
redirect_url,
is_filtered: true,
all_posts_filtered: false,
all_posts_hidden_nsfw: false,
no_posts: false,
})
} else {
// Request user posts/comments from Reddit
match Post::fetch(&path, false).await {
Ok((mut posts, after)) => {
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 {
user,
posts,
sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after").unwrap_or_default(), after),
listing,
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
redirect_url,
is_filtered: false,
all_posts_filtered,
all_posts_hidden_nsfw,
no_posts,
})
}
// 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(),
banner: about("banner_img"),
description: about("public_description"),
nsfw: res["data"]["subreddit"]["over_18"].as_bool().unwrap_or_default(),
}
})
}

View File

@ -9,6 +9,7 @@ use regex::Regex;
use rust_embed::RustEmbed;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::env;
use std::str::FromStr;
use time::{macros::format_description, Duration, OffsetDateTime};
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
pub struct Flair {
pub flair_parts: Vec<FlairPart>,
@ -229,6 +240,7 @@ pub struct Post {
pub comments: (String, String),
pub gallery: Vec<GalleryMedia>,
pub awards: Awards,
pub nsfw: bool,
}
impl Post {
@ -329,6 +341,7 @@ impl Post {
comments: format_num(data["num_comments"].as_i64().unwrap_or_default()),
gallery,
awards,
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
});
}
@ -357,6 +370,7 @@ pub struct Comment {
pub awards: Awards,
pub collapsed: bool,
pub is_filtered: bool,
pub prefs: Preferences,
}
#[derive(Default, Clone)]
@ -420,6 +434,27 @@ pub struct ErrorTemplate {
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)]
// User struct containing metadata about user
pub struct User {
@ -430,6 +465,7 @@ pub struct User {
pub created: String,
pub banner: String,
pub description: String,
pub nsfw: bool,
}
#[derive(Default)]
@ -444,6 +480,7 @@ pub struct Subreddit {
pub members: (String, String),
pub active: (String, String),
pub wiki: bool,
pub nsfw: bool,
}
// Parser for query params, used in sorting (eg. /r/rust/?sort=hot)
@ -472,6 +509,7 @@ pub struct Preferences {
pub post_sort: String,
pub subscriptions: Vec<String>,
pub filters: Vec<String>,
pub hide_awards: String,
}
#[derive(RustEmbed)]
@ -481,7 +519,7 @@ pub struct ThemeAssets;
impl Preferences {
// 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.
// Always make the default "system" theme available.
let mut themes = vec!["system".to_string()];
@ -504,6 +542,7 @@ impl Preferences {
post_sort: setting(&req, "post_sort"),
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(),
hide_awards: setting(&req, "hide_awards"),
}
}
}
@ -617,6 +656,7 @@ pub async fn parse_post(post: &serde_json::Value) -> Post {
comments: format_num(post["data"]["num_comments"].as_i64().unwrap_or_default()),
gallery,
awards,
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
}
}
@ -643,8 +683,8 @@ pub fn setting(req: &Request<Body>, name: &str) -> String {
req
.cookie(name)
.unwrap_or_else(|| {
// If there is no cookie for this setting, try receiving a default from an environment variable
if let Ok(default) = std::env::var(format!("LIBREDDIT_DEFAULT_{}", name.to_uppercase())) {
// If there is no cookie for this setting, try receiving a default from the config
if let Some(default) = crate::config::get_setting(&format!("LIBREDDIT_DEFAULT_{}", name.to_uppercase())) {
Cookie::new(name, default)
} else {
Cookie::named(name)
@ -820,7 +860,7 @@ pub async fn error(req: Request<Body>, msg: impl ToString) -> Result<Response<Bo
let url = req.uri().to_string();
let body = ErrorTemplate {
msg: msg.to_string(),
prefs: Preferences::new(req),
prefs: Preferences::new(&req),
url,
}
.render()
@ -829,6 +869,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())
}
/// 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)]
mod tests {
use super::{format_num, format_url, rewrite_urls};

View File

@ -26,6 +26,9 @@
--highlighted: #333;
--visited: #aaa;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
/* Hint color theme to browser for scrollbar */
color-scheme: dark;
}
/* Browser-defined light theme */
@ -42,6 +45,9 @@
--highlighted: white;
--visited: #555;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
/* Hint color theme to browser for scrollbar */
color-scheme: light;
}
}
@ -160,16 +166,43 @@ main {
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: 300%;
padding: 0.5em;
}
.info-button > a:hover {
text-decoration: none;
}
/* / Body footer. */
/* Footer in content block. */
main > * > footer {
display: flex;
justify-content: center;
margin-top: 20px;
}
footer > a {
main > * > footer > a {
margin-right: 5px;
}
/* / Footer in content block. */
button {
background: none;
border: none;
@ -485,7 +518,7 @@ button.submit:hover > svg { stroke: var(--accent); }
overflow-x: auto;
}
#sort_options, #listing_options, footer > a {
#sort_options, #listing_options, main > * > footer > a {
border-radius: 5px;
align-items: center;
box-shadow: var(--shadow);
@ -494,7 +527,7 @@ button.submit:hover > svg { stroke: var(--accent); }
overflow: hidden;
}
#sort_options > a, #listing_options > a, footer > a {
#sort_options > a, #listing_options > a, main > * > footer > a {
color: var(--text);
padding: 10px 20px;
text-align: center;
@ -824,6 +857,17 @@ a.search_subreddit:hover {
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 {
display: flex;
list-style: none;
@ -1118,22 +1162,16 @@ summary.comment_data {
}
.prefs {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 20px;
padding: 10px 20px 20px;
background: var(--post);
border-radius: 5px;
margin-bottom: 20px;
}
.prefs > div {
display: flex;
justify-content: space-between;
width: 100%;
height: 35px;
align-items: center;
margin-top: 7px;
.prefs fieldset {
border: 0;
padding: 10px 0;
margin: 0 0 5px;
}
.prefs legend {
@ -1141,11 +1179,25 @@ summary.comment_data {
border-bottom: 1px solid var(--highlighted);
font-size: 18px;
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) {
padding-top: 10px;
margin-top: 15px;
.prefs-group {
display: flex;
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 {
@ -1163,7 +1215,8 @@ aside.prefs {
background: var(--highlighted);
padding: 10px 15px;
border-radius: 5px;
margin-top: 20px;
margin-top: 5px;
width: 100%
}
input[type="submit"] {
@ -1193,6 +1246,10 @@ input[type="submit"] {
width: 250px;
background: var(--highlighted) !important;
}
/* Info page */
.unset {
color: lightslategrey;
}
/* Markdown */
@ -1306,6 +1363,31 @@ td, th {
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 */
@media screen and (max-width: 800px) {

View File

@ -11,3 +11,8 @@
--highlighted: #fbf1c7;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
html:has(> .gruvboxlight) {
/* Hint color theme to browser for scrollbar */
color-scheme: light;
}

View File

@ -12,3 +12,8 @@
--visited: #555;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
html:has(> .light) {
/* Hint color theme to browser for scrollbar */
color-scheme: light;
}

View File

@ -48,7 +48,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"/>
</svg>
</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>
<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>
@ -65,5 +65,12 @@
{% endblock %}
</main>
{% endblock %}
{% block footer %}
<footer>
<div class="info-button">
<a href="/info" title="View instance information">&#x24D8;</a>
</div>
</footer>
{% endblock %}
</body>
</html>

View File

@ -20,7 +20,7 @@
{% endif %}
<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 !awards.is_empty() %}
{% if !awards.is_empty() && prefs.hide_awards != "on" %}
<span class="dot">&bull;</span>
{% for award in awards.clone() %}
<span class="award" title="{{ award.name }}">

View File

@ -65,7 +65,7 @@
<a class="post_author {{ post.author.distinguished }}" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
<span class="dot">&bull;</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() %}
<span class="award" title="{{ award.name }}">
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>

10
templates/message.html Normal file
View 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 %}

View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block title %}NSFW content gated{% endblock %}
{% block sortstyle %}{% endblock %}
{% block content %}
<div id="nsfw_landing">
<h1>
&#128561;
{% 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 %}

View File

@ -44,6 +44,7 @@
<!-- SORT FORM -->
<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">
{% call utils::options(sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
</select><button id="sort_submit" class="submit">

View File

@ -10,7 +10,7 @@
{% block content %}
<div id="column_one">
<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 != "" %}
<div id="inside">
<input type="checkbox" name="restrict_sr" id="restrict_sr" {% if params.restrict_sr != "" %}checked{% endif %}>
@ -61,6 +61,10 @@
<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 %}
{% if all_posts_filtered %}
<span class="listing_warn">(All content on this page has been filtered)</span>
{% else if is_filtered %}

View File

@ -11,74 +11,86 @@
<div id="settings">
<form action="/settings" method="POST">
<div class="prefs">
<legend>Appearance</legend>
<div id="theme">
<label for="theme">Theme:</label>
<select name="theme">
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
</select>
</div>
<legend>Interface</legend>
<div id="front_page">
<label for="front_page">Front page:</label>
<select name="front_page">
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
</select>
</div>
<div id="layout">
<label for="layout">Layout:</label>
<select name="layout">
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
</select>
</div>
<div id="wide">
<label for="wide">Wide UI:</label>
<input type="hidden" value="off" name="wide">
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
<legend>Content</legend>
<div id="post_sort">
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
<select name="post_sort">
{% call utils::options(prefs.post_sort, ["hot", "new", "top", "rising", "controversial"], "hot") %}
</select>
</div>
<div id="comment_sort">
<label for="comment_sort">Default comment sort:</label>
<select name="comment_sort">
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
</select>
</div>
<div id="show_nsfw">
<label for="show_nsfw">Show NSFW posts:</label>
<input type="hidden" value="off" name="show_nsfw">
<input type="checkbox" name="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
</div>
<div id="blur_nsfw">
<label for="blur_nsfw">Blur NSFW previews:</label>
<input type="hidden" value="off" name="blur_nsfw">
<input type="checkbox" name="blur_nsfw" {% if prefs.blur_nsfw == "on" %}checked{% endif %}>
</div>
<div id="autoplay_videos">
<label for="autoplay_videos">Autoplay videos</label>
<input type="hidden" value="off" name="autoplay_videos">
<input type="checkbox" name="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
</div>
<div id="use_hls">
<label for="use_hls">Use HLS for videos
<fieldset>
<legend>Appearance</legend>
<div class="prefs-group">
<label for="theme">Theme:</label>
<select name="theme" id="theme">
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
</select>
</div>
</fieldset>
<fieldset>
<legend>Interface</legend>
<div class="prefs-group">
<label for="front_page">Front page:</label>
<select name="front_page" id="front_page">
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
</select>
</div>
<div class="prefs-group">
<label for="layout">Layout:</label>
<select name="layout" id="layout">
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
</select>
</div>
<div class="prefs-group">
<label for="wide">Wide UI:</label>
<input type="hidden" value="off" name="wide">
<input type="checkbox" name="wide" id="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
</fieldset>
<fieldset>
<legend>Content</legend>
<div class="prefs-group">
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
<select name="post_sort">
{% call utils::options(prefs.post_sort, ["hot", "new", "top", "rising", "controversial"], "hot") %}
</select>
</div>
<div class="prefs-group">
<label for="comment_sort">Default comment sort:</label>
<select name="comment_sort" id="comment_sort">
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
</select>
</div>
{% if !crate::utils::sfw_only() %}
<div class="prefs-group">
<label for="show_nsfw">Show NSFW posts:</label>
<input type="hidden" value="off" name="show_nsfw">
<input type="checkbox" name="show_nsfw" id="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="blur_nsfw">Blur NSFW previews:</label>
<input type="hidden" value="off" name="blur_nsfw">
<input type="checkbox" name="blur_nsfw" id="blur_nsfw" {% if prefs.blur_nsfw == "on" %}checked{% endif %}>
</div>
{% endif %}
<div class="prefs-group">
<label for="autoplay_videos">Autoplay videos</label>
<input type="hidden" value="off" name="autoplay_videos">
<input type="checkbox" name="autoplay_videos" id="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="use_hls">Use HLS for videos</label>
<details id="feeds">
<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>
</details>
</label>
<input type="hidden" value="off" name="use_hls">
<input type="checkbox" name="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
</div>
<div id="hide_hls_notification">
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
<input type="hidden" value="off" name="hide_hls_notification">
<input type="checkbox" name="hide_hls_notification" {% if prefs.hide_hls_notification == "on" %}checked{% endif %}>
</div>
<input type="hidden" value="off" name="use_hls">
<input type="checkbox" name="use_hls" id="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
<input type="hidden" value="off" name="hide_hls_notification">
<input type="checkbox" name="hide_hls_notification" id="hide_hls_notification" {% if prefs.hide_hls_notification == "on" %}checked{% endif %}>
</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>
</fieldset>
<input id="save" type="submit" value="Save">
</div>
</form>
@ -115,7 +127,7 @@
<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>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 }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&autoplay_videos={{ prefs.autoplay_videos }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
</div>
</div>

View File

@ -50,6 +50,10 @@
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
{% endif %}
{% if no_posts %}
<center>No posts were found.</center>
{% endif %}
{% if all_posts_filtered %}
<center>(All content on this page has been filtered)</center>
{% else %}

View File

@ -36,6 +36,10 @@
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
{% endif %}
{% if no_posts %}
<center>No posts were found.</center>
{% endif %}
{% if all_posts_filtered %}
<center>(All content on this page has been filtered)</center>
{% else %}

View File

@ -73,7 +73,7 @@
{% endif %}
<span class="dot">&bull;</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" %}
<span class="dot">&bull;</span>
<span class="awards">
{% for award in post.awards.clone() %}
@ -178,7 +178,7 @@
<a class="post_author {{ post.author.distinguished }}" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
<span class="dot">&bull;</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() %}
<span class="award" title="{{ award.name }}">
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>