diff --git a/.env.example b/.env.example index 1836796..8988edf 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,8 @@ REDLIB_DEFAULT_WIDE=off REDLIB_DEFAULT_POST_SORT=hot # Set the default comment sort method (options: confidence, top, new, controversial, old) REDLIB_DEFAULT_COMMENT_SORT=confidence +# Enable blurring Spoiler content by default +REDLIB_DEFAULT_BLUR_SPOILER=off # Enable showing NSFW content by default REDLIB_DEFAULT_SHOW_NSFW=off # Enable blurring NSFW content by default @@ -38,6 +40,8 @@ REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off # Define a default list of subreddit subscriptions (format: sub1+sub2+sub3) REDLIB_DEFAULT_SUBSCRIPTIONS= +# Define a default list of subreddit filters (format: sub1+sub2+sub3) +REDLIB_DEFAULT_FILTERS= # Hide awards by default REDLIB_DEFAULT_HIDE_AWARDS=off # Hide sidebar and summary diff --git a/.gitignore b/.gitignore index a8d371a..6ca325c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /target .env +redlib.toml + # Idea Files .idea/ diff --git a/Cargo.lock b/Cargo.lock index 4138fa5..0553a2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ dependencies = [ "mime_guess", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -118,7 +118,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -129,9 +129,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.72" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -150,9 +150,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -208,9 +208,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cached" -version = "0.51.3" +version = "0.51.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd93a9f06ec296ca66b4c26fafa9ed63f32c473d7a708a5f28563ee64c948515" +checksum = "0feb64151eed3da6107fddd2d717a6ca4b9dbd74e43784c55c841d1abfe5a295" dependencies = [ "ahash", "async-trait", @@ -233,7 +233,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -244,9 +244,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cc" -version = "1.0.98" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" [[package]] name = "cfg-if" @@ -256,18 +256,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstyle", "clap_lex", @@ -275,9 +275,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "cookie" @@ -363,7 +363,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.66", + "syn", ] [[package]] @@ -374,7 +374,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -637,9 +637,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -655,9 +655,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -810,9 +810,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -838,9 +838,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -893,9 +893,9 @@ dependencies = [ [[package]] name = "object" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1077,9 +1077,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1089,9 +1089,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1100,9 +1100,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "ring" @@ -1151,7 +1151,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.66", + "syn", "walkdir", ] @@ -1283,9 +1283,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sealed_test" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a608d94641cc17fe203b102db2ae86d47a236630192f0244ddbbbb0044c0272" +checksum = "2a1867f8f005bd7fb73c367e2e45dd628417906a2ca27597fe59cbf04279a222" dependencies = [ "fs_extra", "rusty-forkfork", @@ -1295,12 +1295,12 @@ dependencies = [ [[package]] name = "sealed_test_derive" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b672e005ae58fef5da619d90b9f1c5b44b061890f4a371b3c96257a8a15e697" +checksum = "77253fb2d4451418d07025826028bcb96ee42d3e58859689a70ce62908009db6" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1343,14 +1343,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "itoa", "ryu", @@ -1438,26 +1438,15 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -1502,7 +1491,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -1540,9 +1529,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -1555,9 +1544,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -1574,13 +1563,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -1609,9 +1598,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", @@ -1630,9 +1619,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap", "serde", @@ -1722,9 +1711,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -1733,9 +1722,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "getrandom", ] @@ -1930,9 +1919,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.9" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -1954,7 +1943,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] diff --git a/README.md b/README.md index c5f9640..19f4234 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,7 @@ Assign a default value for each user-modifiable setting by passing environment v | `WIDE` | `["on", "off"]` | `off` | | `POST_SORT` | `["hot", "new", "top", "rising", "controversial"]` | `hot` | | `COMMENT_SORT` | `["confidence", "top", "new", "controversial", "old"]` | `confidence` | +| `BLUR_SPOILER` | `["on", "off"]` | `off` | | `SHOW_NSFW` | `["on", "off"]` | `off` | | `BLUR_NSFW` | `["on", "off"]` | `off` | | `USE_HLS` | `["on", "off"]` | `off` | diff --git a/app.json b/app.json index e1ae650..d73780c 100644 --- a/app.json +++ b/app.json @@ -29,6 +29,9 @@ "REDLIB_DEFAULT_POST_SORT": { "required": false }, + "REDLIB_DEFAULT_BLUR_SPOILER": { + "required": false + }, "REDLIB_DEFAULT_SHOW_NSFW": { "required": false }, @@ -59,6 +62,9 @@ "REDLIB_DEFAULT_SUBSCRIPTIONS": { "required": false }, + "REDLIB_DEFAULT_FILTERS": { + "required": false + }, "REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION": { "required": false }, diff --git a/contrib/redlib.conf b/contrib/redlib.conf index 880bc7a..43c1d86 100644 --- a/contrib/redlib.conf +++ b/contrib/redlib.conf @@ -6,6 +6,7 @@ PORT=12345 #REDLIB_DEFAULT_WIDE=off #REDLIB_DEFAULT_POST_SORT=hot #REDLIB_DEFAULT_COMMENT_SORT=confidence +#REDLIB_DEFAULT_BLUR_SPOILER=off #REDLIB_DEFAULT_SHOW_NSFW=off #REDLIB_DEFAULT_BLUR_NSFW=off #REDLIB_DEFAULT_USE_HLS=off diff --git a/scripts/load_test.py b/scripts/load_test.py new file mode 100644 index 0000000..00e4793 --- /dev/null +++ b/scripts/load_test.py @@ -0,0 +1,31 @@ +import requests +from bs4 import BeautifulSoup +from concurrent.futures import ThreadPoolExecutor + +base_url = "http://localhost:8080" + +full_path = f"{base_url}/r/politics" + +ctr = 0 + +def fetch_url(url): + global ctr + response = requests.get(url) + ctr += 1 + print(f"Request count: {ctr}") + return response + +while full_path: + response = requests.get(full_path) + ctr += 1 + print(f"Request count: {ctr}") + soup = BeautifulSoup(response.text, 'html.parser') + comment_links = soup.find_all('a', class_='post_comments') + comment_urls = [base_url + link['href'] for link in comment_links] + with ThreadPoolExecutor(max_workers=10) as executor: + executor.map(fetch_url, comment_urls) + next_link = soup.find('a', accesskey='N') + if next_link: + full_path = base_url + next_link['href'] + else: + break diff --git a/src/client.rs b/src/client.rs index 5ea9d1c..fff2cf0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,11 +5,13 @@ use hyper::client::HttpConnector; use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri}; use hyper_rustls::HttpsConnector; use libflate::gzip; -use log::error; +use log::{error, trace, warn}; use once_cell::sync::Lazy; use percent_encoding::{percent_encode, CONTROLS}; use serde_json::Value; +use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicU16, Ordering::SeqCst}; use std::{io, result::Result}; use tokio::sync::RwLock; @@ -36,6 +38,8 @@ pub static OAUTH_CLIENT: Lazy> = Lazy::new(|| { RwLock::new(client) }); +pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99); + /// Gets the canonical path for a resource on Reddit. This is accomplished by /// making a `HEAD` request to Reddit at the path given in `path`. /// @@ -170,7 +174,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo // Construct the hyper client from the HTTPS connector. let client: Client<_, Body> = CLIENT.clone(); - let (token, vendor_id, device_id, mut user_agent, loid) = { + let (token, vendor_id, device_id, user_agent, loid) = { let client = block_on(OAUTH_CLIENT.read()); ( client.token.clone(), @@ -181,13 +185,6 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo ) }; - // Replace "Android" with a tricky word. - // Issues: #78/#115, #116 - // If you include the word "Android", you will get a number of different errors - // I guess they don't expect mobile traffic on the endpoints we use - // Scrawled on wall for next poor soul: Run the test suite. - user_agent = user_agent.replace("Android", "Andr\u{200B}oid"); - // Build request to Reddit. When making a GET, request gzip compression. // (Reddit doesn't do brotli yet.) let builder = Request::builder() @@ -314,19 +311,59 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo #[cached(size = 100, time = 30, result = true)] pub async fn json(path: String, quarantine: bool) -> Result { // Closure to quickly build errors - let err = |msg: &str, e: String| -> Result { + let err = |msg: &str, e: String, path: String| -> Result { // eprintln!("{} - {}: {}", url, msg, e); - Err(format!("{msg}: {e}")) + Err(format!("{msg}: {e} | {path}")) }; + // First, handle rolling over the OAUTH_CLIENT if need be. + let current_rate_limit = OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst); + if current_rate_limit < 10 { + warn!("Rate limit {current_rate_limit} is low. Spawning force_refresh_token()"); + OAUTH_RATELIMIT_REMAINING.store(99, Ordering::SeqCst); + tokio::spawn(force_refresh_token()); + } + // Fetch the url... match reddit_get(path.clone(), quarantine).await { Ok(response) => { let status = response.status(); + // Ratelimit remaining + if let Some(Ok(remaining)) = response.headers().get("x-ratelimit-remaining").map(|val| val.to_str()) { + trace!("Ratelimit remaining: {}", remaining); + if let Ok(remaining) = remaining.parse::().map(|f| f.round() as u16) { + OAUTH_RATELIMIT_REMAINING.store(remaining, SeqCst); + } else { + warn!("Failed to parse rate limit {remaining} from header."); + } + } + + // Ratelimit used + if let Some(Ok(used)) = response.headers().get("x-ratelimit-used").map(|val| val.to_str()) { + trace!("Ratelimit used: {}", used); + } + + // Ratelimit reset + let reset = if let Some(Ok(reset)) = response.headers().get("x-ratelimit-reset").map(|val| val.to_str()) { + trace!("Ratelimit reset: {}", reset); + Some(reset.to_string()) + } else { + None + }; + // asynchronously aggregate the chunks of the body match hyper::body::aggregate(response).await { Ok(body) => { + let has_remaining = body.has_remaining(); + + if !has_remaining { + return match reset { + Some(val) => Err(format!("Reddit rate limit exceeded. Will reset in: {val}")), + None => Err("Reddit rate limit exceeded".to_string()), + }; + } + // Parse the response from Reddit as JSON match serde_json::from_reader(body.reader()) { Ok(value) => { @@ -339,7 +376,7 @@ pub async fn json(path: String, quarantine: bool) -> Result { let () = force_refresh_token().await; return Err("OAuth token has expired. Please refresh the page!".to_string()); } - Err(format!("Reddit error {} \"{}\": {}", json["error"], json["reason"], json["message"])) + Err(format!("Reddit error {} \"{}\": {} | {path}", json["error"], json["reason"], json["message"])) } else { Ok(json) } @@ -349,21 +386,24 @@ pub async fn json(path: String, quarantine: bool) -> Result { if status.is_server_error() { Err("Reddit is having issues, check if there's an outage".to_string()) } else { - err("Failed to parse page JSON data", e.to_string()) + err("Failed to parse page JSON data", e.to_string(), path) } } } } - Err(e) => err("Failed receiving body from Reddit", e.to_string()), + Err(e) => err("Failed receiving body from Reddit", e.to_string(), path), } } - Err(e) => err("Couldn't send request to Reddit", e), + Err(e) => err("Couldn't send request to Reddit", e, path), } } +#[cfg(test)] +static POPULAR_URL: &str = "/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL"; + #[tokio::test(flavor = "multi_thread")] async fn test_localization_popular() { - let val = json("/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL".to_string(), false).await.unwrap(); + let val = json(POPULAR_URL.to_string(), false).await.unwrap(); assert_eq!("GLOBAL", val["data"]["geo_filter"].as_str().unwrap()); } diff --git a/src/config.rs b/src/config.rs index e8140a2..0976c72 100644 --- a/src/config.rs +++ b/src/config.rs @@ -52,6 +52,10 @@ pub struct Config { #[serde(alias = "LIBREDDIT_DEFAULT_POST_SORT")] pub(crate) default_post_sort: Option, + #[serde(rename = "REDLIB_DEFAULT_BLUR_SPOILER")] + #[serde(alias = "LIBREDDIT_DEFAULT_BLUR_SPOILER")] + pub(crate) default_blur_spoiler: Option, + #[serde(rename = "REDLIB_DEFAULT_SHOW_NSFW")] #[serde(alias = "LIBREDDIT_DEFAULT_SHOW_NSFW")] pub(crate) default_show_nsfw: Option, @@ -88,6 +92,10 @@ pub struct Config { #[serde(alias = "LIBREDDIT_DEFAULT_SUBSCRIPTIONS")] pub(crate) default_subscriptions: Option, + #[serde(rename = "REDLIB_DEFAULT_FILTERS")] + #[serde(alias = "LIBREDDIT_DEFAULT_FILTERS")] + pub(crate) default_filters: Option, + #[serde(rename = "REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")] #[serde(alias = "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")] pub(crate) default_disable_visit_reddit_confirmation: Option, @@ -135,6 +143,7 @@ impl Config { default_post_sort: parse("REDLIB_DEFAULT_POST_SORT"), default_wide: parse("REDLIB_DEFAULT_WIDE"), default_comment_sort: parse("REDLIB_DEFAULT_COMMENT_SORT"), + default_blur_spoiler: parse("REDLIB_DEFAULT_BLUR_SPOILER"), default_show_nsfw: parse("REDLIB_DEFAULT_SHOW_NSFW"), default_blur_nsfw: parse("REDLIB_DEFAULT_BLUR_NSFW"), default_use_hls: parse("REDLIB_DEFAULT_USE_HLS"), @@ -144,6 +153,7 @@ impl Config { default_hide_sidebar_and_summary: parse("REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY"), default_hide_score: parse("REDLIB_DEFAULT_HIDE_SCORE"), default_subscriptions: parse("REDLIB_DEFAULT_SUBSCRIPTIONS"), + default_filters: parse("REDLIB_DEFAULT_FILTERS"), default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"), banner: parse("REDLIB_BANNER"), robots_disable_indexing: parse("REDLIB_ROBOTS_DISABLE_INDEXING"), @@ -161,6 +171,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option { "REDLIB_DEFAULT_LAYOUT" => config.default_layout.clone(), "REDLIB_DEFAULT_COMMENT_SORT" => config.default_comment_sort.clone(), "REDLIB_DEFAULT_POST_SORT" => config.default_post_sort.clone(), + "REDLIB_DEFAULT_BLUR_SPOILER" => config.default_blur_spoiler.clone(), "REDLIB_DEFAULT_SHOW_NSFW" => config.default_show_nsfw.clone(), "REDLIB_DEFAULT_BLUR_NSFW" => config.default_blur_nsfw.clone(), "REDLIB_DEFAULT_USE_HLS" => config.default_use_hls.clone(), @@ -171,6 +182,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option { "REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY" => config.default_hide_sidebar_and_summary.clone(), "REDLIB_DEFAULT_HIDE_SCORE" => config.default_hide_score.clone(), "REDLIB_DEFAULT_SUBSCRIPTIONS" => config.default_subscriptions.clone(), + "REDLIB_DEFAULT_FILTERS" => config.default_filters.clone(), "REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION" => config.default_disable_visit_reddit_confirmation.clone(), "REDLIB_BANNER" => config.banner.clone(), "REDLIB_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(), @@ -243,6 +255,12 @@ fn test_default_subscriptions() { assert_eq!(get_setting("REDLIB_DEFAULT_SUBSCRIPTIONS"), Some("news+bestof".into())); } +#[test] +#[sealed_test(env = [("REDLIB_DEFAULT_FILTERS", "news+bestof")])] +fn test_default_filters() { + assert_eq!(get_setting("REDLIB_DEFAULT_FILTERS"), Some("news+bestof".into())); +} + #[test] #[sealed_test] fn test_pushshift() { diff --git a/src/instance_info.rs b/src/instance_info.rs index a798d89..7d1c9d9 100644 --- a/src/instance_info.rs +++ b/src/instance_info.rs @@ -142,11 +142,13 @@ impl InstanceInfo { ["Wide", &convert(&self.config.default_wide)], ["Comment sort", &convert(&self.config.default_comment_sort)], ["Post sort", &convert(&self.config.default_post_sort)], + ["Blur Spoiler", &convert(&self.config.default_blur_spoiler)], ["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)], ["Subscriptions", &convert(&self.config.default_subscriptions)], + ["Filters", &convert(&self.config.default_filters)], ]) .with_header_row(["Default preferences"]), ); @@ -175,11 +177,13 @@ impl InstanceInfo { Default wide: {:?}\n Default comment sort: {:?}\n Default post sort: {:?}\n + Default blur Spoiler: {:?}\n Default show NSFW: {:?}\n Default blur NSFW: {:?}\n Default use HLS: {:?}\n Default hide HLS notification: {:?}\n - Default subscriptions: {:?}\n", + Default subscriptions: {:?}\n + Default filters: {:?}\n", self.package_name, self.crate_version, self.git_commit, @@ -198,11 +202,13 @@ impl InstanceInfo { self.config.default_wide, self.config.default_comment_sort, self.config.default_post_sort, + self.config.default_blur_spoiler, self.config.default_show_nsfw, self.config.default_blur_nsfw, self.config.default_use_hls, self.config.default_hide_hls_notification, self.config.default_subscriptions, + self.config.default_filters, ) } StringType::Html => self.to_table(), diff --git a/src/main.rs b/src/main.rs index 90846f3..946ae26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -160,7 +160,7 @@ async fn main() { .long("address") .value_name("ADDRESS") .help("Sets address to listen on") - .default_value("0.0.0.0") + .default_value("[::]") .num_args(1), ) .arg( diff --git a/src/oauth.rs b/src/oauth.rs index cea7693..03b56f6 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -1,12 +1,12 @@ -use std::{collections::HashMap, time::Duration}; +use std::{collections::HashMap, sync::atomic::Ordering, time::Duration}; use crate::{ - client::{CLIENT, OAUTH_CLIENT}, + client::{CLIENT, OAUTH_CLIENT, OAUTH_RATELIMIT_REMAINING}, oauth_resources::ANDROID_APP_VERSION_LIST, }; use base64::{engine::general_purpose, Engine as _}; use hyper::{client, Body, Method, Request}; -use log::info; +use log::{info, trace}; use serde_json::json; @@ -131,6 +131,7 @@ pub async fn token_daemon() { } pub async fn force_refresh_token() { + trace!("Rolling over refresh token. Current rate limit: {}", OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst)); OAUTH_CLIENT.write().await.refresh().await; } diff --git a/src/settings.rs b/src/settings.rs index f36240a..7b8f0cd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -19,7 +19,7 @@ struct SettingsTemplate { // CONSTANTS -const PREFS: [&str; 18] = [ +const PREFS: [&str; 19] = [ "theme", "mascot", "front_page", @@ -27,6 +27,7 @@ const PREFS: [&str; 18] = [ "wide", "comment_sort", "post_sort", + "blur_spoiler", "show_nsfw", "blur_nsfw", "use_hls", diff --git a/src/subreddit.rs b/src/subreddit.rs index 4df81b4..00edcae 100644 --- a/src/subreddit.rs +++ b/src/subreddit.rs @@ -64,7 +64,7 @@ pub async fn community(req: Request) -> Result, String> { let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string()); let sort = req.param("sort").unwrap_or_else(|| req.param("id").unwrap_or(post_sort)); - let sub_name = req.param("sub").unwrap_or(if front_page == "default" || front_page.is_empty() { + let mut sub_name = req.param("sub").unwrap_or(if front_page == "default" || front_page.is_empty() { if subscribed.is_empty() { "popular".to_string() } else { @@ -84,6 +84,11 @@ pub async fn community(req: Request) -> Result, String> { return Ok(redirect(&["/user/", &sub_name[2..]].concat())); } + // If multi-sub, replace + with url encoded + + if sub_name.contains('+') { + sub_name = sub_name.replace('+', "%2B"); + } + // Request subreddit metadata let sub = if !sub_name.contains('+') && sub_name != subscribed && sub_name != "popular" && sub_name != "all" { // Regular subreddit diff --git a/src/utils.rs b/src/utils.rs index ec9e296..1e283ae 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use crate::config::get_setting; // // CRATES @@ -156,6 +157,7 @@ impl PollOption { // Post flags with nsfw and stickied pub struct Flags { + pub spoiler: bool, pub nsfw: bool, pub stickied: bool, } @@ -402,6 +404,7 @@ impl Post { }, }, flags: Flags { + spoiler: data["spoiler"].as_bool().unwrap_or_default(), nsfw: data["over_18"].as_bool().unwrap_or_default(), stickied: data["stickied"].as_bool().unwrap_or_default() || data["pinned"].as_bool().unwrap_or_default(), }, @@ -576,6 +579,7 @@ pub struct Preferences { pub front_page: String, pub layout: String, pub wide: String, + pub blur_spoiler: String, pub show_nsfw: String, pub blur_nsfw: String, pub hide_hls_notification: String, @@ -628,6 +632,7 @@ impl Preferences { front_page: setting(req, "front_page"), layout: setting(req, "layout"), wide: setting(req, "wide"), + blur_spoiler: setting(req, "blur_spoiler"), show_nsfw: setting(req, "show_nsfw"), hide_sidebar_and_summary: setting(req, "hide_sidebar_and_summary"), blur_nsfw: setting(req, "blur_nsfw"), @@ -749,6 +754,7 @@ pub async fn parse_post(post: &Value) -> Post { }, }, flags: Flags { + spoiler: post["data"]["spoiler"].as_bool().unwrap_or_default(), nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(), stickied: post["data"]["stickied"].as_bool().unwrap_or_default() || post["data"]["pinned"].as_bool().unwrap_or(false), }, @@ -1046,7 +1052,7 @@ pub fn redirect(path: &str) -> Response { /// Renders a generic error landing page. pub async fn error(req: Request, msg: &str) -> Result, String> { - error!("Error page rendered: {msg}"); + error!("Error page rendered: {}", msg.split('|').next().unwrap_or_default()); let url = req.uri().to_string(); let body = ErrorTemplate { msg: msg.to_string(), diff --git a/static/style.css b/static/style.css index 0aae3e8..522177c 100644 --- a/static/style.css +++ b/static/style.css @@ -52,6 +52,7 @@ --visited: #aaa; --shadow: 0 1px 3px rgba(0, 0, 0, 0.5); --popup: #b80a27; + --spoiler: #ddd; /* Hint color theme to browser for scrollbar */ color-scheme: dark; @@ -71,6 +72,7 @@ --highlighted: white; --visited: #555; --shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + --spoiler: #0f0f0f; /* Hint color theme to browser for scrollbar */ color-scheme: light; @@ -929,6 +931,15 @@ a.search_subreddit:hover { font-weight: bold; } +.spoiler { + color: var(--spoiler); + margin-left: 5px; + border: 1px solid var(--spoiler); + padding: 3px; + font-size: 12px; + border-radius: 5px; +} + .post_media_content, .post .__NoScript_PlaceHolder__, .gallery { max-width: calc(100% - 40px); grid-area: post_media; diff --git a/templates/error.html b/templates/error.html index a16fc74..e831200 100644 --- a/templates/error.html +++ b/templates/error.html @@ -2,10 +2,14 @@ {% block title %}Error: {{ msg }}{% endblock %} {% block sortstyle %}{% endblock %} {% block content %} -
-

{{ msg }}

-

Reddit Status

-
-

Head back home?

-
+
+

{{ msg }}

+

Reddit Status

+
+

Expected something to work? Report + an issue

+
+

Head back home?

+
{% endblock %} \ No newline at end of file diff --git a/templates/settings.html b/templates/settings.html index 730c04d..ebd26a5 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -60,6 +60,11 @@ {% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %} +
+ + + +
{% if !crate::utils::sfw_only() %}
diff --git a/templates/utils.html b/templates/utils.html index d3b0541..b380964 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -62,6 +62,7 @@ {%- endmacro %} {% macro post(post) -%} +{% set post_should_be_blurred = post.flags.spoiler && prefs.blur_spoiler=="on" -%}

@@ -93,6 +94,7 @@ style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};">{% call render_flair(post.flair.flair_parts) %} {% endif %} {% if post.flags.nsfw %} NSFW{% endif %} + {% if post.flags.spoiler %} Spoiler{% endif %} @@ -101,12 +103,13 @@

{% if post.media.height == 0 || post.media.width == 0 %} - - Post image + + Post image {% else %} @@ -124,7 +127,7 @@ {% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() || prefs.ffmpeg_video_downloads == "on" && !post.media.alt_url.is_empty() %}
-