From 95ab6c53856291a4d9cebb9fa5c5045031836120 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Wed, 20 Nov 2024 18:50:06 -0500
Subject: [PATCH 01/49] fix(oauth): update oauth resources and script
---
scripts/update_oauth_resources.sh | 10 ++---
src/oauth_resources.rs | 70 ++++++++++++++++---------------
2 files changed, 42 insertions(+), 38 deletions(-)
diff --git a/scripts/update_oauth_resources.sh b/scripts/update_oauth_resources.sh
index 1d6b486..a3014ae 100755
--- a/scripts/update_oauth_resources.sh
+++ b/scripts/update_oauth_resources.sh
@@ -39,12 +39,12 @@ done
echo "];" >> "$filename"
# Fetch Android app versions
-page_1=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions/" | rg "" -r "https://apkcombo.com\$1" | sort | uniq)
+page_1=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions/" | rg " " -r "https://apkcombo.com\$1" | sort | uniq | sed 's/ //g')
# Append with pages
-page_2=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=2" | rg " " -r "https://apkcombo.com\$1" | sort | uniq)
-page_3=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=3" | rg " " -r "https://apkcombo.com\$1" | sort | uniq)
-page_4=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=4" | rg " " -r "https://apkcombo.com\$1" | sort | uniq)
-page_5=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=5" | rg " " -r "https://apkcombo.com\$1" | sort | uniq)
+page_2=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=2" | rg " " -r "https://apkcombo.com\$1" | sort | uniq | sed 's/ //g')
+page_3=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=3" | rg " " -r "https://apkcombo.com\$1" | sort | uniq | sed 's/ //g')
+page_4=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=4" | rg " " -r "https://apkcombo.com\$1" | sort | uniq | sed 's/ //g')
+page_5=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=5" | rg " " -r "https://apkcombo.com\$1" | sort | uniq | sed 's/ //g')
# Concatenate all pages
versions="${page_1}"
diff --git a/src/oauth_resources.rs b/src/oauth_resources.rs
index 3272939..a5dc2f3 100644
--- a/src/oauth_resources.rs
+++ b/src/oauth_resources.rs
@@ -2,8 +2,40 @@
// Rerun scripts/update_oauth_resources.sh to update this file
// Please do not edit manually
// Filled in with real app versions
-pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
+pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[
+ "",
+];
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
+ "Version 2024.22.1/Build 1652272",
+ "Version 2024.23.1/Build 1665606",
+ "Version 2024.24.1/Build 1682520",
+ "Version 2024.25.0/Build 1693595",
+ "Version 2024.25.2/Build 1700401",
+ "Version 2024.25.3/Build 1703490",
+ "Version 2024.26.0/Build 1710470",
+ "Version 2024.26.1/Build 1717435",
+ "Version 2024.28.0/Build 1737665",
+ "Version 2024.28.1/Build 1741165",
+ "Version 2024.30.0/Build 1770787",
+ "Version 2024.31.0/Build 1786202",
+ "Version 2024.32.0/Build 1809095",
+ "Version 2024.32.1/Build 1813258",
+ "Version 2024.33.0/Build 1819908",
+ "Version 2024.34.0/Build 1837909",
+ "Version 2024.35.0/Build 1861437",
+ "Version 2024.36.0/Build 1875012",
+ "Version 2024.37.0/Build 1888053",
+ "Version 2024.38.0/Build 1902791",
+ "Version 2024.39.0/Build 1916713",
+ "Version 2024.40.0/Build 1928580",
+ "Version 2024.41.0/Build 1941199",
+ "Version 2024.41.1/Build 1947805",
+ "Version 2024.42.0/Build 1952440",
+ "Version 2024.43.0/Build 1972250",
+ "Version 2024.44.0/Build 1988458",
+ "Version 2024.45.0/Build 2001943",
+ "Version 2024.46.0/Build 2012731",
+ "Version 2024.47.0/Build 2029755",
"Version 2023.48.0/Build 1319123",
"Version 2023.49.0/Build 1321715",
"Version 2023.49.1/Build 1322281",
@@ -31,9 +63,9 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2024.20.0/Build 1612800",
"Version 2024.20.1/Build 1615586",
"Version 2024.20.2/Build 1624969",
+ "Version 2024.20.3/Build 1624970",
"Version 2024.21.0/Build 1631686",
"Version 2024.22.0/Build 1645257",
- "Version 2024.22.1/Build 1652272",
"Version 2023.21.0/Build 956283",
"Version 2023.22.0/Build 968223",
"Version 2023.23.0/Build 983896",
@@ -124,35 +156,7 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2022.40.0/Build 624782",
"Version 2022.41.0/Build 630468",
"Version 2022.41.1/Build 634168",
- "Version 2021.39.1/Build 372418",
- "Version 2021.41.0/Build 376052",
- "Version 2021.42.0/Build 378193",
- "Version 2021.43.0/Build 382019",
- "Version 2021.44.0/Build 385129",
- "Version 2021.45.0/Build 387663",
- "Version 2021.46.0/Build 392043",
- "Version 2021.47.0/Build 394342",
- "Version 2022.10.0/Build 429896",
- "Version 2022.1.0/Build 402829",
- "Version 2022.11.0/Build 433004",
- "Version 2022.12.0/Build 436848",
- "Version 2022.13.0/Build 442084",
- "Version 2022.13.1/Build 444621",
- "Version 2022.14.1/Build 452742",
- "Version 2022.15.0/Build 455453",
- "Version 2022.16.0/Build 462377",
- "Version 2022.17.0/Build 468480",
- "Version 2022.18.0/Build 473740",
- "Version 2022.19.1/Build 482464",
- "Version 2022.2.0/Build 405543",
- "Version 2022.3.0/Build 408637",
- "Version 2022.4.0/Build 411368",
- "Version 2022.5.0/Build 414731",
- "Version 2022.6.0/Build 418391",
- "Version 2022.6.1/Build 419585",
- "Version 2022.6.2/Build 420562",
- "Version 2022.7.0/Build 420849",
- "Version 2022.8.0/Build 423906",
- "Version 2022.9.0/Build 426592",
];
-pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];
+pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[
+ "",
+];
From 6be6f892a4eb159f5a27ce48f0ce615298071eac Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Wed, 20 Nov 2024 19:19:29 -0500
Subject: [PATCH 02/49] feat(oauth): better oauth client matching
---
Cargo.lock | 15 +++++++++++++++
Cargo.toml | 1 +
src/client.rs | 36 +++++++++++++-----------------------
src/oauth.rs | 20 +++++++++++++++-----
src/oauth_resources.rs | 8 ++------
5 files changed, 46 insertions(+), 34 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 4143b80..057234f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1327,6 +1327,8 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
+ "libc",
+ "rand_chacha",
"rand_core",
]
@@ -1345,6 +1347,9 @@ name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
[[package]]
name = "redlib"
@@ -1380,6 +1385,7 @@ dependencies = [
"serde_json",
"serde_json_path",
"serde_yaml",
+ "tegen",
"time",
"tokio",
"toml",
@@ -1895,6 +1901,15 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tegen"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a2d5a357b7c859b410139734a875136473c3b18b1bbd8d5bdc1769d9002acd"
+dependencies = [
+ "rand",
+]
+
[[package]]
name = "tempfile"
version = "3.14.0"
diff --git a/Cargo.toml b/Cargo.toml
index 7bb7e93..616d8e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,6 +49,7 @@ serde_json_path = "0.7.1"
async-recursion = "1.1.1"
common-words-all = { version = "0.0.2", default-features = false, features = ["english", "one"] }
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
+tegen = "0.1.4"
[dev-dependencies]
diff --git a/src/client.rs b/src/client.rs
index 248fc88..1e1661d 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -218,40 +218,30 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
// Construct the hyper client from the HTTPS connector.
let client: &Lazy> = &CLIENT;
- let (token, vendor_id, device_id, user_agent, loid) = {
- let client = OAUTH_CLIENT.load_full();
- (
- client.token.clone(),
- client.headers_map.get("Client-Vendor-Id").cloned().unwrap_or_default(),
- client.headers_map.get("X-Reddit-Device-Id").cloned().unwrap_or_default(),
- client.headers_map.get("User-Agent").cloned().unwrap_or_default(),
- client.headers_map.get("x-reddit-loid").cloned().unwrap_or_default(),
- )
- };
-
// Build request to Reddit. When making a GET, request gzip compression.
// (Reddit doesn't do brotli yet.)
- let mut headers = vec![
- ("User-Agent", user_agent),
- ("Client-Vendor-Id", vendor_id),
- ("X-Reddit-Device-Id", device_id),
- ("x-reddit-loid", loid),
- ("Host", host.to_string()),
- ("Authorization", format!("Bearer {token}")),
- ("Accept-Encoding", if method == Method::GET { "gzip".into() } else { "identity".into() }),
+ let mut headers: Vec<(String, String)> = vec![
+ ("Host".into(), host.into()),
+ ("Accept-Encoding".into(), if method == Method::GET { "gzip".into() } else { "identity".into() }),
(
- "Cookie",
+ "Cookie".into(),
if quarantine {
"_options=%7B%22pref_quarantine_optin%22%3A%20true%2C%20%22pref_gated_sr_optin%22%3A%20true%7D".into()
} else {
"".into()
},
),
- ("X-Reddit-Width", fastrand::u32(300..500).to_string()),
- ("X-Reddit-DPR", "2".to_owned()),
- ("Device-Name", format!("Android {}", fastrand::u8(9..=14))),
];
+ {
+ let client = OAUTH_CLIENT.load_full();
+ for (key, value) in client.initial_headers.clone() {
+ headers.push((key, value));
+ }
+ }
+
+ trace!("Headers: {:#?}", headers);
+
// shuffle headers: https://github.com/redlib-org/redlib/issues/324
fastrand::shuffle(&mut headers);
diff --git a/src/oauth.rs b/src/oauth.rs
index 80bf318..576b647 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -7,8 +7,8 @@ use crate::{
use base64::{engine::general_purpose, Engine as _};
use hyper::{client, Body, Method, Request};
use log::{error, info, trace};
-
use serde_json::json;
+use tegen::tegen::TextGenerator;
use tokio::time::{error::Elapsed, timeout};
static REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg";
@@ -84,7 +84,7 @@ impl Oauth {
// Set JSON body. I couldn't tell you what this means. But that's what the client sends
let json = json!({
- "scopes": ["*","email"]
+ "scopes": ["*","email", "pii"]
});
let body = Body::from(json.to_string());
@@ -185,11 +185,21 @@ impl Device {
let android_user_agent = format!("Reddit/{android_app_version}/Android {android_version}");
+ let qos = fastrand::u32(1000..=100_000);
+ let qos: f32 = qos as f32 / 1000.0;
+ let qos = format!("{:.3}", qos);
+
+ let codecs = TextGenerator::new().generate("available-codecs=video/avc, video/hevc{, video/x-vnd.on2.vp9|}");
+
// Android device headers
- let headers = HashMap::from([
- ("Client-Vendor-Id".into(), uuid.clone()),
- ("X-Reddit-Device-Id".into(), uuid.clone()),
+ let headers: HashMap = HashMap::from([
("User-Agent".into(), android_user_agent),
+ ("x-reddit-retry".into(), "algo=no-retries".into()),
+ ("x-reddit-compression".into(), "1".into()),
+ ("x-reddit-qos".into(), qos),
+ ("x-reddit-media-codecs".into(), codecs),
+ ("Content-Type".into(), "application/json; charset=UTF-8".into()),
+ ("client-vendor-id".into(), uuid.clone()),
]);
info!("[🔄] Spoofing Android client with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_ANDROID_OAUTH_CLIENT_ID}\"");
diff --git a/src/oauth_resources.rs b/src/oauth_resources.rs
index a5dc2f3..faf7873 100644
--- a/src/oauth_resources.rs
+++ b/src/oauth_resources.rs
@@ -2,9 +2,7 @@
// Rerun scripts/update_oauth_resources.sh to update this file
// Please do not edit manually
// Filled in with real app versions
-pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[
- "",
-];
+pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2024.22.1/Build 1652272",
"Version 2024.23.1/Build 1665606",
@@ -157,6 +155,4 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2022.41.0/Build 630468",
"Version 2022.41.1/Build 634168",
];
-pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[
- "",
-];
+pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];
From 100a7b65a6a79968cbc8548f4ce12d722dfb0cba Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sat, 23 Nov 2024 21:17:52 -0500
Subject: [PATCH 03/49] fix(client): update headers management, add self check
(fix #334, fix #318)
---
src/client.rs | 41 ++++++++++++++++++++++++++++++++++++++---
src/main.rs | 12 +++++++++++-
src/oauth.rs | 6 ++++--
3 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/src/client.rs b/src/client.rs
index 1e1661d..0e2c301 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -19,6 +19,7 @@ use std::{io, result::Result};
use crate::dbg_msg;
use crate::oauth::{force_refresh_token, token_daemon, Oauth};
use crate::server::RequestExt;
+use crate::subreddit::community;
use crate::utils::format_url;
const REDDIT_URL_BASE: &str = "https://oauth.reddit.com";
@@ -235,13 +236,11 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
{
let client = OAUTH_CLIENT.load_full();
- for (key, value) in client.initial_headers.clone() {
+ for (key, value) in client.headers_map.clone() {
headers.push((key, value));
}
}
- trace!("Headers: {:#?}", headers);
-
// shuffle headers: https://github.com/redlib-org/redlib/issues/324
fastrand::shuffle(&mut headers);
@@ -390,6 +389,12 @@ pub async fn json(path: String, quarantine: bool) -> Result {
"Ratelimit remaining: Header says {remaining}, we have {current_rate_limit}. Resets in {reset}. Rollover: {}. Ratelimit used: {used}",
if is_rolling_over { "yes" } else { "no" },
);
+
+ // If can parse remaining as a float, round to a u16 and save
+ if let Ok(val) = remaining.parse::() {
+ OAUTH_RATELIMIT_REMAINING.store(val.round() as u16, Ordering::SeqCst);
+ }
+
Some(reset)
} else {
None
@@ -474,6 +479,36 @@ pub async fn json(path: String, quarantine: bool) -> Result {
}
}
+async fn self_check(sub: &str) -> Result<(), String> {
+ let request = Request::get(format!("/r/{sub}/")).body(Body::empty()).unwrap();
+
+ match community(request).await {
+ Ok(sub) if sub.status().is_success() => Ok(()),
+ Ok(sub) => Err(sub.status().to_string()),
+ Err(e) => Err(e),
+ }
+}
+
+pub async fn rate_limit_check() -> Result<(), String> {
+ // First, check a subreddit.
+ self_check("reddit").await?;
+ // This will reduce the rate limit to 99. Assert this check.
+ if OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst) != 99 {
+ return Err(format!("Rate limit check failed: expected 99, got {}", OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst)));
+ }
+ // Now, we switch out the OAuth client.
+ // This checks for the IP rate limit association.
+ force_refresh_token().await;
+ // Now, check a new sub to break cache.
+ self_check("rust").await?;
+ // Again, assert the rate limit check.
+ if OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst) != 99 {
+ return Err(format!("Rate limit check failed: expected 99, got {}", OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst)));
+ }
+
+ Ok(())
+}
+
#[cfg(test)]
static POPULAR_URL: &str = "/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL";
diff --git a/src/main.rs b/src/main.rs
index 7342597..abae968 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,7 @@ use hyper::Uri;
use hyper::{header::HeaderValue, Body, Request, Response};
use log::info;
use once_cell::sync::Lazy;
-use redlib::client::{canonical_path, proxy, CLIENT};
+use redlib::client::{canonical_path, proxy, rate_limit_check, CLIENT};
use redlib::server::{self, RequestExt};
use redlib::utils::{error, redirect, ThemeAssets};
use redlib::{config, duplicates, headers, instance_info, post, search, settings, subreddit, user};
@@ -146,6 +146,16 @@ async fn main() {
)
.get_matches();
+ match rate_limit_check().await {
+ Ok(()) => {
+ info!("[✅] Rate limit check passed");
+ },
+ Err(e) => {
+ log::error!("[❌] Rate limit check failed: {}", e);
+ std::process::exit(1);
+ }
+ }
+
let address = matches.get_one::("address").unwrap();
let port = matches.get_one::("port").unwrap();
let hsts = matches.get_one("hsts").map(|m: &String| m.as_str());
diff --git a/src/oauth.rs b/src/oauth.rs
index 576b647..12b0f37 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -38,12 +38,12 @@ impl Oauth {
}
Ok(None) => {
error!("Failed to create OAuth client. Retrying in 5 seconds...");
- continue;
}
Err(duration) => {
error!("Failed to create OAuth client in {duration:?}. Retrying in 5 seconds...");
}
}
+ tokio::time::sleep(Duration::from_secs(5)).await;
}
}
@@ -91,13 +91,14 @@ impl Oauth {
// Build request
let request = builder.body(body).unwrap();
- trace!("Sending token request...");
+ trace!("Sending token request...\n\n{request:?}");
// Send request
let client: &once_cell::sync::Lazy> = &CLIENT;
let resp = client.request(request).await.ok()?;
trace!("Received response with status {} and length {:?}", resp.status(), resp.headers().get("content-length"));
+ trace!("OAuth headers: {:#?}", resp.headers());
// Parse headers - loid header _should_ be saved sent on subsequent token refreshes.
// Technically it's not needed, but it's easy for Reddit API to check for this.
@@ -200,6 +201,7 @@ impl Device {
("x-reddit-media-codecs".into(), codecs),
("Content-Type".into(), "application/json; charset=UTF-8".into()),
("client-vendor-id".into(), uuid.clone()),
+ ("X-Reddit-Device-Id".into(), uuid.clone()),
]);
info!("[🔄] Spoofing Android client with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_ANDROID_OAUTH_CLIENT_ID}\"");
From 7fe109df2267f292459c2154554c7bb40e77908d Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sat, 23 Nov 2024 21:41:30 -0500
Subject: [PATCH 04/49] style(clippy)
---
src/main.rs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index abae968..9c8de97 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -149,9 +149,10 @@ async fn main() {
match rate_limit_check().await {
Ok(()) => {
info!("[✅] Rate limit check passed");
- },
+ }
Err(e) => {
- log::error!("[❌] Rate limit check failed: {}", e);
+ log::error!(" Rate limit check failed: {}", e);
+ println!("[❌] Rate limit check failed: {}", e);
std::process::exit(1);
}
}
From a4f511f67e350fb4e4792416d42dbeaa9f1d544b Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sun, 24 Nov 2024 10:50:21 -0500
Subject: [PATCH 05/49] fix(client): update rate limit self-check (fix #335)
---
src/client.rs | 30 ++++++++++++++++++++++++------
src/subreddit.rs | 3 +++
2 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/src/client.rs b/src/client.rs
index 0e2c301..ba08531 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -19,8 +19,7 @@ use std::{io, result::Result};
use crate::dbg_msg;
use crate::oauth::{force_refresh_token, token_daemon, Oauth};
use crate::server::RequestExt;
-use crate::subreddit::community;
-use crate::utils::format_url;
+use crate::utils::{format_url, Post};
const REDDIT_URL_BASE: &str = "https://oauth.reddit.com";
const REDDIT_URL_BASE_HOST: &str = "oauth.reddit.com";
@@ -480,11 +479,10 @@ pub async fn json(path: String, quarantine: bool) -> Result {
}
async fn self_check(sub: &str) -> Result<(), String> {
- let request = Request::get(format!("/r/{sub}/")).body(Body::empty()).unwrap();
+ let query = format!("/r/{sub}/hot.json?&raw_json=1");
- match community(request).await {
- Ok(sub) if sub.status().is_success() => Ok(()),
- Ok(sub) => Err(sub.status().to_string()),
+ match Post::fetch(&query, true).await {
+ Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
@@ -509,6 +507,26 @@ pub async fn rate_limit_check() -> Result<(), String> {
Ok(())
}
+#[cfg(test)]
+use {crate::config::get_setting, sealed_test::prelude::*};
+
+#[tokio::test(flavor = "multi_thread")]
+async fn test_rate_limit_check() {
+ rate_limit_check().await.unwrap();
+}
+
+#[test]
+#[sealed_test(env = [("REDLIB_DEFAULT_SUBSCRIPTIONS", "rust")])]
+fn test_default_subscriptions() {
+ tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async {
+ let subscriptions = get_setting("REDLIB_DEFAULT_SUBSCRIPTIONS");
+ assert!(subscriptions.is_some());
+
+ // check rate limit
+ rate_limit_check().await.unwrap();
+ });
+}
+
#[cfg(test)]
static POPULAR_URL: &str = "/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL";
diff --git a/src/subreddit.rs b/src/subreddit.rs
index 3a07bdc..88aa542 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -8,6 +8,7 @@ use crate::utils::{
use crate::{client::json, server::RequestExt, server::ResponseExt};
use cookie::Cookie;
use hyper::{Body, Request, Response};
+use log::{debug, trace};
use rinja::Template;
use once_cell::sync::Lazy;
@@ -62,6 +63,7 @@ pub async fn community(req: Request) -> Result, String> {
// Build Reddit API path
let root = req.uri().path() == "/";
let query = req.uri().query().unwrap_or_default().to_string();
+ trace!("query: {}", query);
let subscribed = setting(&req, "subscriptions");
let front_page = setting(&req, "front_page");
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
@@ -123,6 +125,7 @@ pub async fn community(req: Request) -> Result, String> {
}
let path = format!("/r/{}/{sort}.json?{}{params}", sub_name.replace('+', "%2B"), req.uri().query().unwrap_or_default());
+ debug!("Path: {}", path);
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26").replace('+', "%2B");
let filters = get_filters(&req);
From 9f6b08cbb2d0f43644a34f5d0210ac32b9add30c Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Tue, 26 Nov 2024 22:55:48 -0500
Subject: [PATCH 06/49] fix(main): reduce rate limit check fail to warned error
---
src/main.rs | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 9c8de97..8732d20 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,7 +9,7 @@ use std::str::FromStr;
use futures_lite::FutureExt;
use hyper::Uri;
use hyper::{header::HeaderValue, Body, Request, Response};
-use log::info;
+use log::{info, warn};
use once_cell::sync::Lazy;
use redlib::client::{canonical_path, proxy, rate_limit_check, CLIENT};
use redlib::server::{self, RequestExt};
@@ -151,9 +151,12 @@ async fn main() {
info!("[✅] Rate limit check passed");
}
Err(e) => {
- log::error!(" Rate limit check failed: {}", e);
- println!("[❌] Rate limit check failed: {}", e);
- std::process::exit(1);
+ let mut message = format!("Rate limit check failed: {}", e);
+ message += "\nThis may cause issues with the rate limit.";
+ message += "\nPlease report this error with the above information.";
+ message += "\nhttps://github.com/redlib-org/redlib/issues/new?assignees=sigaloid&labels=bug&title=%F0%9F%90%9B+Bug+Report%3A+Rate+limit+mismatch";
+ warn!("{}", message);
+ eprintln!("{}", message);
}
}
From e4fc22cf906b7e8213e0b96108106a9ad34c0e29 Mon Sep 17 00:00:00 2001
From: Integral
Date: Tue, 3 Dec 2024 00:28:31 +0800
Subject: [PATCH 07/49] refactor: replace static with const for global
constants (#340)
---
scripts/update_oauth_resources.sh | 6 +++---
src/client.rs | 6 +++---
src/oauth.rs | 4 ++--
src/oauth_resources.rs | 6 +++---
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/scripts/update_oauth_resources.sh b/scripts/update_oauth_resources.sh
index a3014ae..7eeb959 100755
--- a/scripts/update_oauth_resources.sh
+++ b/scripts/update_oauth_resources.sh
@@ -24,7 +24,7 @@ echo "// Please do not edit manually" >> "$filename"
echo "// Filled in with real app versions" >> "$filename"
# Open the array in the source file
-echo "pub static _IOS_APP_VERSION_LIST: &[&str; $ios_app_count] = &[" >> "$filename"
+echo "pub const _IOS_APP_VERSION_LIST: &[&str; $ios_app_count] = &[" >> "$filename"
num=0
@@ -63,7 +63,7 @@ android_count=$(echo "$versions" | wc -l)
echo -e "Fetching \e[32m$android_count Android app versions...\e[0m"
# Append to the source file
-echo "pub static ANDROID_APP_VERSION_LIST: &[&str; $android_count] = &[" >> "$filename"
+echo "pub const ANDROID_APP_VERSION_LIST: &[&str; $android_count] = &[" >> "$filename"
num=0
@@ -89,7 +89,7 @@ ios_count=$(echo "$table" | wc -l)
echo -e "Fetching \e[34m$ios_count iOS versions...\e[0m"
# Append to the source file
-echo "pub static _IOS_OS_VERSION_LIST: &[&str; $ios_count] = &[" >> "$filename"
+echo "pub const _IOS_OS_VERSION_LIST: &[&str; $ios_count] = &[" >> "$filename"
num=0
diff --git a/src/client.rs b/src/client.rs
index ba08531..fa32fc0 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -45,7 +45,7 @@ pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99);
pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false);
-static URL_PAIRS: [(&str, &str); 2] = [
+const URL_PAIRS: [(&str, &str); 2] = [
(ALTERNATIVE_REDDIT_URL_BASE, ALTERNATIVE_REDDIT_URL_BASE_HOST),
(REDDIT_SHORT_URL_BASE, REDDIT_SHORT_URL_BASE_HOST),
];
@@ -262,7 +262,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
return Ok(response);
};
let location_header = response.headers().get(header::LOCATION);
- if location_header == Some(&HeaderValue::from_static("https://www.reddit.com/")) {
+ if location_header == Some(&HeaderValue::from_static(ALTERNATIVE_REDDIT_URL_BASE)) {
return Err("Reddit response was invalid".to_string());
}
return request(
@@ -528,7 +528,7 @@ fn test_default_subscriptions() {
}
#[cfg(test)]
-static POPULAR_URL: &str = "/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL";
+const POPULAR_URL: &str = "/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL";
#[tokio::test(flavor = "multi_thread")]
async fn test_localization_popular() {
diff --git a/src/oauth.rs b/src/oauth.rs
index 12b0f37..5627900 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -11,9 +11,9 @@ use serde_json::json;
use tegen::tegen::TextGenerator;
use tokio::time::{error::Elapsed, timeout};
-static REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg";
+const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg";
-static AUTH_ENDPOINT: &str = "https://www.reddit.com";
+const AUTH_ENDPOINT: &str = "https://www.reddit.com";
// Spoofed client for Android devices
#[derive(Debug, Clone, Default)]
diff --git a/src/oauth_resources.rs b/src/oauth_resources.rs
index faf7873..01928c3 100644
--- a/src/oauth_resources.rs
+++ b/src/oauth_resources.rs
@@ -2,8 +2,8 @@
// Rerun scripts/update_oauth_resources.sh to update this file
// Please do not edit manually
// Filled in with real app versions
-pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
-pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
+pub const _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
+pub const ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2024.22.1/Build 1652272",
"Version 2024.23.1/Build 1665606",
"Version 2024.24.1/Build 1682520",
@@ -155,4 +155,4 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2022.41.0/Build 630468",
"Version 2022.41.1/Build 634168",
];
-pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];
+pub const _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];
From d7ec07cd0d713fc308e1004663b0053db8f00a0f Mon Sep 17 00:00:00 2001
From: Jeidnx
Date: Mon, 2 Dec 2024 17:29:57 +0100
Subject: [PATCH 08/49] Implement a serializer for user preferences (#336)
---
Cargo.lock | 13 +++++++++++
Cargo.toml | 1 +
src/utils.rs | 49 ++++++++++++++++++++++++++++++++++++++---
templates/settings.html | 12 ++++++++--
4 files changed, 70 insertions(+), 5 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 057234f..819d4bc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1384,6 +1384,7 @@ dependencies = [
"serde",
"serde_json",
"serde_json_path",
+ "serde_urlencoded",
"serde_yaml",
"tegen",
"time",
@@ -1797,6 +1798,18 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
diff --git a/Cargo.toml b/Cargo.toml
index 616d8e9..a1d3ec0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,6 +50,7 @@ async-recursion = "1.1.1"
common-words-all = { version = "0.0.2", default-features = false, features = ["english", "one"] }
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
tegen = "0.1.4"
+serde_urlencoded = "0.7.1"
[dev-dependencies]
diff --git a/src/utils.rs b/src/utils.rs
index 1edb528..c15dcea 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -13,7 +13,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use rinja::Template;
use rust_embed::RustEmbed;
-use serde::Serialize;
+use serde::{Serialize, Serializer};
use serde_json::Value;
use serde_json_path::{JsonPath, JsonPathExt};
use std::collections::{HashMap, HashSet};
@@ -601,8 +601,9 @@ pub struct Params {
pub before: Option,
}
-#[derive(Default)]
+#[derive(Default, Serialize)]
pub struct Preferences {
+ #[serde(skip)]
pub available_themes: Vec,
pub theme: String,
pub front_page: String,
@@ -620,12 +621,21 @@ pub struct Preferences {
pub disable_visit_reddit_confirmation: String,
pub comment_sort: String,
pub post_sort: String,
+ #[serde(serialize_with = "serialize_vec_with_plus")]
pub subscriptions: Vec,
+ #[serde(serialize_with = "serialize_vec_with_plus")]
pub filters: Vec,
pub hide_awards: String,
pub hide_score: String,
}
+fn serialize_vec_with_plus(vec: &Vec, serializer: S) -> Result
+where
+ S: Serializer,
+{
+ serializer.serialize_str(&vec.join("+"))
+}
+
#[derive(RustEmbed)]
#[folder = "static/themes/"]
#[include = "*.css"]
@@ -665,6 +675,10 @@ impl Preferences {
hide_score: setting(req, "hide_score"),
}
}
+
+ pub fn to_urlencoded(&self) -> Result {
+ serde_urlencoded::to_string(self).map_err(|e| e.to_string())
+ }
}
/// Gets a `HashSet` of filters from the cookie in the given `Request`.
@@ -1277,7 +1291,7 @@ pub fn get_post_url(post: &Post) -> String {
#[cfg(test)]
mod tests {
- use super::{format_num, format_url, rewrite_urls};
+ use super::{format_num, format_url, rewrite_urls, Preferences};
#[test]
fn format_num_works() {
@@ -1344,6 +1358,35 @@ mod tests {
assert_eq!(format_url("nsfw"), "");
assert_eq!(format_url("spoiler"), "");
}
+ #[test]
+ fn serialize_prefs() {
+ let prefs = Preferences {
+ available_themes: vec![],
+ theme: "laserwave".to_owned(),
+ front_page: "default".to_owned(),
+ layout: "compact".to_owned(),
+ wide: "on".to_owned(),
+ blur_spoiler: "on".to_owned(),
+ show_nsfw: "off".to_owned(),
+ blur_nsfw: "on".to_owned(),
+ hide_hls_notification: "off".to_owned(),
+ video_quality: "best".to_owned(),
+ hide_sidebar_and_summary: "off".to_owned(),
+ use_hls: "on".to_owned(),
+ autoplay_videos: "on".to_owned(),
+ fixed_navbar: "on".to_owned(),
+ disable_visit_reddit_confirmation: "on".to_owned(),
+ comment_sort: "confidence".to_owned(),
+ post_sort: "top".to_owned(),
+ subscriptions: vec!["memes".to_owned(), "mildlyinteresting".to_owned()],
+ filters: vec![],
+ hide_awards: "off".to_owned(),
+ hide_score: "off".to_owned(),
+ };
+ let urlencoded = serde_urlencoded::to_string(prefs).expect("Failed to serialize Prefs");
+
+ assert_eq!(urlencoded, "theme=laserwave&front_page=default&layout=compact&wide=on&blur_spoiler=on&show_nsfw=off&blur_nsfw=on&hide_hls_notification=off&video_quality=best&hide_sidebar_and_summary=off&use_hls=on&autoplay_videos=on&fixed_navbar=on&disable_visit_reddit_confirmation=on&comment_sort=confidence&post_sort=top&subscriptions=memes%2Bmildlyinteresting&filters=&hide_awards=off&hide_score=off")
+ }
}
#[test]
diff --git a/templates/settings.html b/templates/settings.html
index a7d6615..fef91cf 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -161,8 +161,16 @@
{% endif %}
-
Note: settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.
-
You can restore your current settings and subscriptions after clearing your cookies using this link .
+
Note: settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.
+
+ {% match prefs.to_urlencoded() %}
+ {% when Ok with (encoded_prefs) %}
+
You can restore your current settings and subscriptions after clearing your cookies using this link .
+ {% when Err with (err) %}
+
There was an error creating your restore link: {{ err }}
+
Please report this issue
+ {% endmatch %}
From 5c1e15c359e7b01316baab27d410130f1557cdf8 Mon Sep 17 00:00:00 2001
From: Butter Cat
Date: Sun, 2 Feb 2025 21:48:46 -0500
Subject: [PATCH 09/49] Make subscription and filter cookies split into
multiple cookies if they're too large (#288)
* Split subscriptions and filters cookies into multiple cookies and make old cookies properly delete
* Cleanup
* Fix mispelling for removing subscription cookies
* Fix many subscription misspellings
* Fix subreddits and filters that were at the end and beginning of the cookies getting merged
* Make join_until_size_limit take the +'s into account when calculating length
* Start cookies without number to be backwards compatible
* Fix old split cookies not being removed and subreddits/filters between cookies occasionally getting merged
* Make updating subscription/filters cookies safer
* Small cleanup
* Make restore properly add new subscriptions/filters cookies and delete old unused subscriptions/filters cookies
* Fix misspellings on variable name
---
src/server.rs | 8 ++-
src/settings.rs | 116 ++++++++++++++++++++++++++++++++++++++-
src/subreddit.rs | 138 +++++++++++++++++++++++++++++++++++++++++------
src/utils.rs | 78 ++++++++++++++++++++++-----
4 files changed, 307 insertions(+), 33 deletions(-)
diff --git a/src/server.rs b/src/server.rs
index 15c56ad..e1f464d 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -25,7 +25,7 @@ use std::{
str::{from_utf8, Split},
string::ToString,
};
-use time::Duration;
+use time::OffsetDateTime;
use crate::dbg_msg;
@@ -170,10 +170,8 @@ impl ResponseExt for Response {
}
fn remove_cookie(&mut self, name: String) {
- let mut cookie = Cookie::from(name);
- cookie.set_path("/");
- cookie.set_max_age(Duration::seconds(1));
- if let Ok(val) = header::HeaderValue::from_str(&cookie.to_string()) {
+ let removal_cookie = Cookie::build(name).path("/").http_only(true).expires(OffsetDateTime::now_utc());
+ if let Ok(val) = header::HeaderValue::from_str(&removal_cookie.to_string()) {
self.headers_mut().append("Set-Cookie", val);
}
}
diff --git a/src/settings.rs b/src/settings.rs
index 4404912..34718c2 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -4,6 +4,7 @@ use std::collections::HashMap;
// CRATES
use crate::server::ResponseExt;
+use crate::subreddit::join_until_size_limit;
use crate::utils::{redirect, template, Preferences};
use cookie::Cookie;
use futures_lite::StreamExt;
@@ -119,7 +120,7 @@ fn set_cookies_method(req: Request, remove_cookies: bool) -> Response response.insert_cookie(
Cookie::build((name.to_owned(), value.clone()))
@@ -136,6 +137,119 @@ fn set_cookies_method(req: Request, remove_cookies: bool) -> Response = subscriptions.expect("Subscriptions").split('+').map(str::to_string).collect();
+
+ // Start at 0 to keep track of what number we need to start deleting old subscription cookies from
+ let mut subscriptions_number_to_delete_from = 0;
+
+ // Starting at 0 so we handle the subscription cookie without a number first
+ for (subscriptions_number, list) in join_until_size_limit(&sub_list).into_iter().enumerate() {
+ let subscriptions_cookie = if subscriptions_number == 0 {
+ "subscriptions".to_string()
+ } else {
+ format!("subscriptions{}", subscriptions_number)
+ };
+
+ response.insert_cookie(
+ Cookie::build((subscriptions_cookie, list))
+ .path("/")
+ .http_only(true)
+ .expires(OffsetDateTime::now_utc() + Duration::weeks(52))
+ .into(),
+ );
+
+ subscriptions_number_to_delete_from += 1;
+ }
+
+ // While subscriptionsNUMBER= is in the string of cookies add a response removing that cookie
+ while cookies_string.contains(&format!("subscriptions{subscriptions_number_to_delete_from}=")) {
+ // Remove that subscriptions cookie
+ response.remove_cookie(format!("subscriptions{subscriptions_number_to_delete_from}"));
+
+ // Increment subscriptions cookie number
+ subscriptions_number_to_delete_from += 1;
+ }
+ } else {
+ // Remove unnumbered subscriptions cookie
+ response.remove_cookie("subscriptions".to_string());
+
+ // Starts at one to deal with the first numbered subscription cookie and onwards
+ let mut subscriptions_number_to_delete_from = 1;
+
+ // While subscriptionsNUMBER= is in the string of cookies add a response removing that cookie
+ while cookies_string.contains(&format!("subscriptions{subscriptions_number_to_delete_from}=")) {
+ // Remove that subscriptions cookie
+ response.remove_cookie(format!("subscriptions{subscriptions_number_to_delete_from}"));
+
+ // Increment subscriptions cookie number
+ subscriptions_number_to_delete_from += 1;
+ }
+ }
+
+ // If there are filters to restore set them and delete any old filters cookies, otherwise delete them all
+ if filters.is_some() {
+ let filters_list: Vec = filters.expect("Filters").split('+').map(str::to_string).collect();
+
+ // Start at 0 to keep track of what number we need to start deleting old subscription cookies from
+ let mut filters_number_to_delete_from = 0;
+
+ // Starting at 0 so we handle the subscription cookie without a number first
+ for (filters_number, list) in join_until_size_limit(&filters_list).into_iter().enumerate() {
+ let filters_cookie = if filters_number == 0 {
+ "filters".to_string()
+ } else {
+ format!("filters{}", filters_number)
+ };
+
+ response.insert_cookie(
+ Cookie::build((filters_cookie, list))
+ .path("/")
+ .http_only(true)
+ .expires(OffsetDateTime::now_utc() + Duration::weeks(52))
+ .into(),
+ );
+
+ filters_number_to_delete_from += 1;
+ }
+
+ // While filtersNUMBER= is in the string of cookies add a response removing that cookie
+ while cookies_string.contains(&format!("filters{filters_number_to_delete_from}=")) {
+ // Remove that filters cookie
+ response.remove_cookie(format!("filters{filters_number_to_delete_from}"));
+
+ // Increment filters cookie number
+ filters_number_to_delete_from += 1;
+ }
+ } else {
+ // Remove unnumbered filters cookie
+ response.remove_cookie("filters".to_string());
+
+ // Starts at one to deal with the first numbered subscription cookie and onwards
+ let mut filters_number_to_delete_from = 1;
+
+ // While filtersNUMBER= is in the string of cookies add a response removing that cookie
+ while cookies_string.contains(&format!("filters{filters_number_to_delete_from}=")) {
+ // Remove that sfilters cookie
+ response.remove_cookie(format!("filters{filters_number_to_delete_from}"));
+
+ // Increment filters cookie number
+ filters_number_to_delete_from += 1;
+ }
+ }
+
response
}
diff --git a/src/subreddit.rs b/src/subreddit.rs
index 88aa542..2362a12 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -214,6 +214,41 @@ pub fn can_access_quarantine(req: &Request, sub: &str) -> bool {
setting(req, &format!("allow_quaran_{}", sub.to_lowercase())).parse().unwrap_or_default()
}
+// Join items in chunks of 4000 bytes in length for cookies
+pub fn join_until_size_limit(vec: &[T]) -> Vec {
+ let mut result = Vec::new();
+ let mut list = String::new();
+ let mut current_size = 0;
+
+ for item in vec {
+ // Size in bytes
+ let item_size = item.to_string().len();
+ // Use 4000 bytes to leave us some headroom because the name and options of the cookie count towards the 4096 byte cap
+ if current_size + item_size > 4000 {
+ // If last item add a seperator on the end of the list so it's interpreted properly in tanden with the next cookie
+ list.push('+');
+
+ // Push current list to result vector
+ result.push(list);
+
+ // Reset the list variable so we can continue with only new items
+ list = String::new();
+ }
+ // Add separator if not the first item
+ if !list.is_empty() {
+ list.push('+');
+ }
+ // Add current item to list
+ list.push_str(&item.to_string());
+ current_size = list.len() + item_size;
+ }
+ // Make sure to push whatever the remaining subreddits are there into the result vector
+ result.push(list);
+
+ // Return resulting vector
+ result
+}
+
// Sub, filter, unfilter, or unsub by setting subscription cookie using response "Set-Cookie" header
pub async fn subscriptions_filters(req: Request) -> Result, String> {
let sub = req.param("sub").unwrap_or_default();
@@ -306,28 +341,101 @@ pub async fn subscriptions_filters(req: Request) -> Result,
let mut response = redirect(&path);
- // Delete cookie if empty, else set
+ // If sub_list is empty remove all subscriptions cookies, otherwise update them and remove old ones
if sub_list.is_empty() {
+ // Remove subscriptions cookie
response.remove_cookie("subscriptions".to_string());
+
+ // Start with first numbered subscriptions cookie
+ let mut subscriptions_number = 1;
+
+ // While whatever subscriptionsNUMBER cookie we're looking at has a value
+ while req.cookie(&format!("subscriptions{}", subscriptions_number)).is_some() {
+ // Remove that subscriptions cookie
+ response.remove_cookie(format!("subscriptions{}", subscriptions_number));
+
+ // Increment subscriptions cookie number
+ subscriptions_number += 1;
+ }
} else {
- response.insert_cookie(
- Cookie::build(("subscriptions", sub_list.join("+")))
- .path("/")
- .http_only(true)
- .expires(OffsetDateTime::now_utc() + Duration::weeks(52))
- .into(),
- );
+ // Start at 0 to keep track of what number we need to start deleting old subscription cookies from
+ let mut subscriptions_number_to_delete_from = 0;
+
+ // Starting at 0 so we handle the subscription cookie without a number first
+ for (subscriptions_number, list) in join_until_size_limit(&sub_list).into_iter().enumerate() {
+ let subscriptions_cookie = if subscriptions_number == 0 {
+ "subscriptions".to_string()
+ } else {
+ format!("subscriptions{}", subscriptions_number)
+ };
+
+ response.insert_cookie(
+ Cookie::build((subscriptions_cookie, list))
+ .path("/")
+ .http_only(true)
+ .expires(OffsetDateTime::now_utc() + Duration::weeks(52))
+ .into(),
+ );
+
+ subscriptions_number_to_delete_from += 1;
+ }
+
+ // While whatever subscriptionsNUMBER cookie we're looking at has a value
+ while req.cookie(&format!("subscriptions{}", subscriptions_number_to_delete_from)).is_some() {
+ // Remove that subscriptions cookie
+ response.remove_cookie(format!("subscriptions{}", subscriptions_number_to_delete_from));
+
+ // Increment subscriptions cookie number
+ subscriptions_number_to_delete_from += 1;
+ }
}
+
+ // If filters is empty remove all filters cookies, otherwise update them and remove old ones
if filters.is_empty() {
+ // Remove filters cookie
response.remove_cookie("filters".to_string());
+
+ // Start with first numbered filters cookie
+ let mut filters_number = 1;
+
+ // While whatever filtersNUMBER cookie we're looking at has a value
+ while req.cookie(&format!("filters{}", filters_number)).is_some() {
+ // Remove that filters cookie
+ response.remove_cookie(format!("filters{}", filters_number));
+
+ // Increment filters cookie number
+ filters_number += 1;
+ }
} else {
- response.insert_cookie(
- Cookie::build(("filters", filters.join("+")))
- .path("/")
- .http_only(true)
- .expires(OffsetDateTime::now_utc() + Duration::weeks(52))
- .into(),
- );
+ // Start at 0 to keep track of what number we need to start deleting old filters cookies from
+ let mut filters_number_to_delete_from = 0;
+
+ for (filters_number, list) in join_until_size_limit(&filters).into_iter().enumerate() {
+ let filters_cookie = if filters_number == 0 {
+ "filters".to_string()
+ } else {
+ format!("filters{}", filters_number)
+ };
+
+ response.insert_cookie(
+ Cookie::build((filters_cookie, list))
+ .path("/")
+ .http_only(true)
+ .expires(OffsetDateTime::now_utc() + Duration::weeks(52))
+ .into(),
+ );
+
+ filters_number_to_delete_from += 1;
+ }
+
+ // While whatever filtersNUMBER cookie we're looking at has a value
+ while req.cookie(&format!("filters{}", filters_number_to_delete_from)).is_some() {
+ // Remove that filters cookie
+ response.remove_cookie(format!("filters{}", filters_number_to_delete_from));
+
+ // Increment filters cookie number
+ filters_number_to_delete_from += 1;
+ }
}
Ok(response)
diff --git a/src/utils.rs b/src/utils.rs
index c15dcea..e2cefd1 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -825,18 +825,72 @@ pub fn param(path: &str, value: &str) -> Option {
// Retrieve the value of a setting by name
pub fn setting(req: &Request, name: &str) -> String {
// Parse a cookie value from request
- req
- .cookie(name)
- .unwrap_or_else(|| {
- // If there is no cookie for this setting, try receiving a default from the config
- if let Some(default) = get_setting(&format!("REDLIB_DEFAULT_{}", name.to_uppercase())) {
- Cookie::new(name, default)
- } else {
- Cookie::from(name)
- }
- })
- .value()
- .to_string()
+
+ // If this was called with "subscriptions" and the "subscriptions" cookie has a value
+ if name == "subscriptions" && req.cookie("subscriptions").is_some() {
+ // Create subscriptions string
+ let mut subscriptions = String::new();
+
+ // Default subscriptions cookie
+ if req.cookie("subscriptions").is_some() {
+ subscriptions.push_str(req.cookie("subscriptions").unwrap().value());
+ }
+
+ // Start with first numbered subscription cookie
+ let mut subscriptions_number = 1;
+
+ // While whatever subscriptionsNUMBER cookie we're looking at has a value
+ while req.cookie(&format!("subscriptions{}", subscriptions_number)).is_some() {
+ // Push whatever subscriptionsNUMBER cookie we're looking at into the subscriptions string
+ subscriptions.push_str(req.cookie(&format!("subscriptions{}", subscriptions_number)).unwrap().value());
+
+ // Increment subscription cookie number
+ subscriptions_number += 1;
+ }
+
+ // Return the subscriptions cookies as one large string
+ subscriptions
+ }
+ // If this was called with "filters" and the "filters" cookie has a value
+ else if name == "filters" && req.cookie("filters").is_some() {
+ // Create filters string
+ let mut filters = String::new();
+
+ // Default filters cookie
+ if req.cookie("filters").is_some() {
+ filters.push_str(req.cookie("filters").unwrap().value());
+ }
+
+ // Start with first numbered filters cookie
+ let mut filters_number = 1;
+
+ // While whatever filtersNUMBER cookie we're looking at has a value
+ while req.cookie(&format!("filters{}", filters_number)).is_some() {
+ // Push whatever filtersNUMBER cookie we're looking at into the filters string
+ filters.push_str(req.cookie(&format!("filters{}", filters_number)).unwrap().value());
+
+ // Increment filters cookie number
+ filters_number += 1;
+ }
+
+ // Return the filters cookies as one large string
+ filters
+ }
+ // The above two still come to this if there was no existing value
+ else {
+ req
+ .cookie(name)
+ .unwrap_or_else(|| {
+ // If there is no cookie for this setting, try receiving a default from the config
+ if let Some(default) = get_setting(&format!("REDLIB_DEFAULT_{}", name.to_uppercase())) {
+ Cookie::new(name, default)
+ } else {
+ Cookie::from(name)
+ }
+ })
+ .value()
+ .to_string()
+ }
}
// Retrieve the value of a setting by name or the default value
From 9e47bc37c7e3d1b5b929926d84459d5ca4a244a9 Mon Sep 17 00:00:00 2001
From: Kot C
Date: Sun, 2 Feb 2025 20:49:46 -0600
Subject: [PATCH 10/49] Support HEAD requests (resolves #292) (#363)
* Support HEAD requests
* Remove body from error responses too
---
src/server.rs | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/server.rs b/src/server.rs
index e1f464d..5297c22 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -238,8 +238,14 @@ impl Server {
path.pop();
}
+ // Replace HEAD with GET for routing
+ let (method, is_head) = match req.method() {
+ &Method::HEAD => (&Method::GET, true),
+ method => (method, false),
+ };
+
// Match the visited path with an added route
- match router.recognize(&format!("/{}{}", req.method().as_str(), path)) {
+ match router.recognize(&format!("/{}{}", method.as_str(), path)) {
// If a route was configured for this path
Ok(found) => {
let mut parammed = req;
@@ -251,17 +257,21 @@ impl Server {
match func.await {
Ok(mut res) => {
res.headers_mut().extend(def_headers);
- let _ = compress_response(&req_headers, &mut res).await;
+ if is_head {
+ *res.body_mut() = Body::empty();
+ } else {
+ let _ = compress_response(&req_headers, &mut res).await;
+ }
Ok(res)
}
- Err(msg) => new_boilerplate(def_headers, req_headers, 500, Body::from(msg)).await,
+ Err(msg) => new_boilerplate(def_headers, req_headers, 500, if is_head { Body::empty() } else { Body::from(msg) }).await,
}
}
.boxed()
}
// If there was a routing error
- Err(e) => new_boilerplate(def_headers, req_headers, 404, e.into()).boxed(),
+ Err(e) => new_boilerplate(def_headers, req_headers, 404, if is_head { Body::empty() } else { e.into() }).boxed(),
}
}))
}
From adf25cb15b61984581422ac798cc7c1364ad8e75 Mon Sep 17 00:00:00 2001
From: Martin Lindhe
Date: Mon, 3 Feb 2025 03:56:47 +0100
Subject: [PATCH 11/49] unescape selftext_html from json api, fixes #354 (#357)
* unescape selftext_html from json api, fixes #354
* fix(fmt)
---------
Co-authored-by: Matthew Esposito
---
Cargo.lock | 7 +++++++
Cargo.toml | 1 +
src/utils.rs | 3 ++-
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/Cargo.lock b/Cargo.lock
index 819d4bc..20d528b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -770,6 +770,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+[[package]]
+name = "htmlescape"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
+
[[package]]
name = "http"
version = "0.2.12"
@@ -1367,6 +1373,7 @@ dependencies = [
"dotenvy",
"fastrand",
"futures-lite",
+ "htmlescape",
"hyper",
"hyper-rustls",
"libflate",
diff --git a/Cargo.toml b/Cargo.toml
index a1d3ec0..a4d0170 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,6 +51,7 @@ common-words-all = { version = "0.0.2", default-features = false, features = ["e
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
tegen = "0.1.4"
serde_urlencoded = "0.7.1"
+htmlescape = "0.3.1"
[dev-dependencies]
diff --git a/src/utils.rs b/src/utils.rs
index e2cefd1..ea14dac 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -7,6 +7,7 @@ use crate::config::{self, get_setting};
//
use crate::{client::json, server::RequestExt};
use cookie::Cookie;
+use htmlescape::decode_html;
use hyper::{Body, Request, Response};
use log::error;
use once_cell::sync::Lazy;
@@ -376,7 +377,7 @@ impl Post {
let awards = Awards::parse(&data["all_awardings"]);
// selftext_html is set for text posts when browsing.
- let mut body = rewrite_urls(&val(post, "selftext_html"));
+ let mut body = rewrite_urls(&decode_html(&val(post, "selftext_html")).unwrap());
if body.is_empty() {
body = rewrite_urls(&val(post, "body_html"));
}
From fd1c32f5552cc116e1cb4c95fcd7cc7a7b069335 Mon Sep 17 00:00:00 2001
From: Martin Lindhe
Date: Mon, 3 Feb 2025 04:00:44 +0100
Subject: [PATCH 12/49] rss: add field, fixes #356 (#358)
* rss: add field, fixes #356
* rss: also add pub_date on user feed
* fix(fmt)
---------
Co-authored-by: Matthew Esposito
---
Cargo.lock | 5 +++--
Cargo.toml | 1 +
src/subreddit.rs | 2 ++
src/user.rs | 2 ++
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 20d528b..24791b4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -274,9 +274,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"num-traits",
]
@@ -1367,6 +1367,7 @@ dependencies = [
"brotli",
"build_html",
"cached",
+ "chrono",
"clap",
"common-words-all",
"cookie",
diff --git a/Cargo.toml b/Cargo.toml
index a4d0170..843b9c9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,6 +51,7 @@ common-words-all = { version = "0.0.2", default-features = false, features = ["e
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
tegen = "0.1.4"
serde_urlencoded = "0.7.1"
+chrono = { version = "0.4.39", default-features = false, features = [ "std" ] }
htmlescape = "0.3.1"
diff --git a/src/subreddit.rs b/src/subreddit.rs
index 2362a12..d5d5196 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -11,6 +11,7 @@ use hyper::{Body, Request, Response};
use log::{debug, trace};
use rinja::Template;
+use chrono::DateTime;
use once_cell::sync::Lazy;
use regex::Regex;
use time::{Duration, OffsetDateTime};
@@ -607,6 +608,7 @@ pub async fn rss(req: Request) -> Result, String> {
link: Some(utils::get_post_url(&post)),
author: Some(post.author.name),
content: Some(rewrite_urls(&post.body)),
+ pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
description: Some(format!(
"Comments ",
config::get_setting("REDLIB_FULL_URL").unwrap_or_default(),
diff --git a/src/user.rs b/src/user.rs
index 50a4daa..2fb8b0d 100644
--- a/src/user.rs
+++ b/src/user.rs
@@ -5,6 +5,7 @@ use crate::client::json;
use crate::server::RequestExt;
use crate::utils::{error, filter_posts, format_url, get_filters, nsfw_landing, param, setting, template, Post, Preferences, User};
use crate::{config, utils};
+use chrono::DateTime;
use hyper::{Body, Request, Response};
use rinja::Template;
use time::{macros::format_description, OffsetDateTime};
@@ -165,6 +166,7 @@ pub async fn rss(req: Request) -> Result, String> {
title: Some(post.title.to_string()),
link: Some(utils::get_post_url(&post)),
author: Some(post.author.name),
+ pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
content: Some(rewrite_urls(&post.body)),
..Default::default()
})
From cb659cc8a3fa8e85e7fcd6c5e96d67fa83081c7b Mon Sep 17 00:00:00 2001
From: Martin Lindhe
Date: Mon, 3 Feb 2025 04:00:58 +0100
Subject: [PATCH 13/49] rss: proxy links in users and subreddit feeds, fixes
#359 (#361)
---
src/subreddit.rs | 2 +-
src/user.rs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/subreddit.rs b/src/subreddit.rs
index d5d5196..0db4f77 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -605,7 +605,7 @@ pub async fn rss(req: Request) -> Result, String> {
.into_iter()
.map(|post| Item {
title: Some(post.title.to_string()),
- link: Some(utils::get_post_url(&post)),
+ link: Some(format_url(&utils::get_post_url(&post))),
author: Some(post.author.name),
content: Some(rewrite_urls(&post.body)),
pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
diff --git a/src/user.rs b/src/user.rs
index 2fb8b0d..818f368 100644
--- a/src/user.rs
+++ b/src/user.rs
@@ -164,7 +164,7 @@ pub async fn rss(req: Request) -> Result, String> {
.into_iter()
.map(|post| Item {
title: Some(post.title.to_string()),
- link: Some(utils::get_post_url(&post)),
+ link: Some(format_url(&utils::get_post_url(&post))),
author: Some(post.author.name),
pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
content: Some(rewrite_urls(&post.body)),
From 0703fa103611786637328bf569928c0619aff759 Mon Sep 17 00:00:00 2001
From: Vivek
Date: Sun, 2 Feb 2025 19:10:12 -0800
Subject: [PATCH 14/49] [build] add new dockerfiles for building from source
(#244)
* add new dockerfiles
* update default ubuntu base images
* updates
* update comment
* update cargo command
Co-authored-by: Pim
* update cargo command
Co-authored-by: Pim
* specify binary
* use label instead of maintainer
---------
Co-authored-by: Pim
---
Dockerfile.alpine | 45 +++++++++++++++++++++++++++++++++++++++++
Dockerfile.ubuntu | 51 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+)
create mode 100644 Dockerfile.alpine
create mode 100644 Dockerfile.ubuntu
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
new file mode 100644
index 0000000..051476a
--- /dev/null
+++ b/Dockerfile.alpine
@@ -0,0 +1,45 @@
+# supported versions here: https://hub.docker.com/_/rust
+ARG ALPINE_VERSION=3.20
+
+########################
+## builder image
+########################
+FROM rust:alpine${ALPINE_VERSION} AS builder
+
+RUN apk add --no-cache musl-dev
+
+WORKDIR /redlib
+
+# download (most) dependencies in their own layer
+COPY Cargo.lock Cargo.toml ./
+RUN mkdir src && echo "fn main() { panic!(\"why am i running?\") }" > src/main.rs
+RUN cargo build --release --locked --bin redlib
+RUN rm ./src/main.rs && rmdir ./src
+
+# copy the source and build the redlib binary
+COPY . ./
+RUN cargo build --release --locked --bin redlib
+RUN echo "finished building redlib!"
+
+########################
+## release image
+########################
+FROM alpine:${ALPINE_VERSION} AS release
+
+# Import redlib binary from builder
+COPY --from=builder /redlib/target/release/redlib /usr/local/bin/redlib
+
+# Add non-root user for running redlib
+RUN adduser --home /nonexistent --no-create-home --disabled-password redlib
+USER redlib
+
+# Document that we intend to expose port 8080 to whoever runs the container
+EXPOSE 8080
+
+# Run a healthcheck every minute to make sure redlib is functional
+HEALTHCHECK --interval=1m --timeout=3s CMD wget --spider --q http://localhost:8080/settings || exit 1
+
+# Add container metadata
+LABEL org.opencontainers.image.authors="sigaloid"
+
+CMD ["redlib"]
diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu
new file mode 100644
index 0000000..2e277c5
--- /dev/null
+++ b/Dockerfile.ubuntu
@@ -0,0 +1,51 @@
+# supported versions here: https://hub.docker.com/_/rust
+ARG RUST_BUILDER_VERSION=slim-bookworm
+ARG UBUNTU_RELEASE_VERSION=noble
+
+########################
+## builder image
+########################
+FROM rust:${RUST_BUILDER_VERSION} AS builder
+
+WORKDIR /redlib
+
+# download (most) dependencies in their own layer
+COPY Cargo.lock Cargo.toml ./
+RUN mkdir src && echo "fn main() { panic!(\"why am i running?\") }" > src/main.rs
+RUN cargo build --release --locked --bin redlib
+RUN rm ./src/main.rs && rmdir ./src
+
+# copy the source and build the redlib binary
+COPY . ./
+RUN cargo build --release --locked --bin redlib
+RUN echo "finished building redlib!"
+
+########################
+## release image
+########################
+FROM ubuntu:${UBUNTU_RELEASE_VERSION} AS release
+
+# Install ca-certificates
+RUN apt-get update && apt-get install -y ca-certificates
+
+# Import redlib binary from builder
+COPY --from=builder /redlib/target/release/redlib /usr/local/bin/redlib
+
+# Add non-root user for running redlib
+RUN useradd \
+ --no-create-home \
+ --password "!" \
+ --comment "user for running redlib" \
+ redlib
+USER redlib
+
+# Document that we intend to expose port 8080 to whoever runs the container
+EXPOSE 8080
+
+# Run a healthcheck every minute to make sure redlib is functional
+HEALTHCHECK --interval=1m --timeout=3s CMD wget --spider --q http://localhost:8080/settings || exit 1
+
+# Add container metadata
+LABEL org.opencontainers.image.authors="sigaloid"
+
+CMD ["redlib"]
From 9e39a75e82cbf0c83b09e051c13073fa4a5e3f5a Mon Sep 17 00:00:00 2001
From: Joel Koen
Date: Mon, 3 Feb 2025 14:16:59 +1000
Subject: [PATCH 15/49] build(nix): update deps (#331)
---
flake.lock | 32 ++++++++++++--------------------
flake.nix | 10 ++--------
2 files changed, 14 insertions(+), 28 deletions(-)
diff --git a/flake.lock b/flake.lock
index 4569244..2b0b585 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,17 +1,12 @@
{
"nodes": {
"crane": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
"locked": {
- "lastModified": 1717025063,
- "narHash": "sha256-dIubLa56W9sNNz0e8jGxrX3CAkPXsq7snuFA/Ie6dn8=",
+ "lastModified": 1731974733,
+ "narHash": "sha256-enYSSZVVl15FI5p+0Y5/Ckf5DZAvXe6fBrHxyhA/njc=",
"owner": "ipetkov",
"repo": "crane",
- "rev": "480dff0be03dac0e51a8dfc26e882b0d123a450e",
+ "rev": "3cb338ce81076ce5e461cf77f7824476addb0e1c",
"type": "github"
},
"original": {
@@ -25,11 +20,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1710146030,
- "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -40,11 +35,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1717112898,
- "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
+ "lastModified": 1731890469,
+ "narHash": "sha256-D1FNZ70NmQEwNxpSSdTXCSklBH1z2isPR84J6DQrJGs=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
+ "rev": "5083ec887760adfe12af64830a66807423a859a7",
"type": "github"
},
"original": {
@@ -64,19 +59,16 @@
},
"rust-overlay": {
"inputs": {
- "flake-utils": [
- "flake-utils"
- ],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
- "lastModified": 1717121863,
- "narHash": "sha256-/3sxIe7MZqF/jw1RTQCSmgTjwVod43mmrk84m50MJQ4=",
+ "lastModified": 1732069891,
+ "narHash": "sha256-moKx8AVJrViCSdA0e0nSsG8b1dAsObI4sRAtbqbvBY8=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "2a7b53172ed08f856b8382d7dcfd36a4e0cbd866",
+ "rev": "8509a51241c407d583b1963d5079585a992506e8",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 8bcacf6..0180c8d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -4,19 +4,13 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
- crane = {
- url = "github:ipetkov/crane";
- inputs.nixpkgs.follows = "nixpkgs";
- };
+ crane.url = "github:ipetkov/crane";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- flake-utils.follows = "flake-utils";
- };
+ inputs.nixpkgs.follows = "nixpkgs";
};
};
From 96ad7bf1632ef4bafeba148288422ffe4cf9bfb2 Mon Sep 17 00:00:00 2001
From: mooons <10822203+mooons@users.noreply.github.com>
Date: Sun, 2 Feb 2025 20:26:36 -0800
Subject: [PATCH 16/49] feat: render bullet lists (#321)
* feat: render bullet lists
* tests: add tests
---------
Co-authored-by: Matthew Esposito
---
src/utils.rs | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/src/utils.rs b/src/utils.rs
index ea14dac..1bc70b9 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -989,6 +989,17 @@ pub fn format_url(url: &str) -> String {
}
}
+static REGEX_BULLET: Lazy = Lazy::new(|| Regex::new(r"(?m)^- (.*)$").unwrap());
+static REGEX_BULLET_CONSECUTIVE_LINES: Lazy = Lazy::new(|| Regex::new(r"\n").unwrap());
+
+pub fn render_bullet_lists(input_text: &str) -> String {
+ // ref: https://stackoverflow.com/a/4902622
+ // First enclose each bullet with tags
+ let text1 = REGEX_BULLET.replace_all(&input_text, "").to_string();
+ // Then remove any consecutive tags
+ REGEX_BULLET_CONSECUTIVE_LINES.replace_all(&text1, "").to_string()
+}
+
// These are links we want to replace in-body
static REDDIT_REGEX: Lazy = Lazy::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it)/"#).unwrap());
static REDDIT_PREVIEW_REGEX: Lazy = Lazy::new(|| Regex::new(r"https?://(external-preview|preview|i)\.redd\.it(.*)[^?]").unwrap());
@@ -1143,6 +1154,10 @@ pub fn rewrite_emotes(media_metadata: &Value, comment: String) -> String {
}
}
}
+
+ // render bullet (unordered) lists
+ comment = render_bullet_lists(&comment);
+
// Call rewrite_urls() to transform any other Reddit links
rewrite_urls(&comment)
}
@@ -1505,3 +1520,28 @@ fn test_rewriting_emotes() {
let output = r#""#;
assert_eq!(rewrite_emotes(&json_input, comment_input.to_string()), output);
}
+
+#[test]
+fn test_rewriting_bullet_list() {
+ let input = r#"Hi, I've bought this very same monitor and found no calibration whatsoever. I have an ICC profile that has been set up since I've installed its driver from the LG website and it works ok. I also used http://www.lagom.nl/lcd-test/ to calibrate it. After some good tinkering I've found the following settings + the color profile from the driver gets me past all the tests perfectly:
+- Brightness 50 (still have to settle on this one, it's personal preference, it controls the backlight, not the colors)
+- Contrast 70 (which for me was the default one)
+- Picture mode Custom
+- Super resolution + Off (it looks horrible anyway)
+- Sharpness 50 (default one I think)
+- Black level High (low messes up gray colors)
+- DFC Off
+- Response Time Middle (personal preference, https://www.blurbusters.com/ show horrible overdrive with it on high)
+- Freesync doesn't matter
+- Black stabilizer 50
+- Gamma setting on 0
+- Color Temp Medium
+How`s your monitor by the way? Any IPS bleed whatsoever? I either got lucky or the panel is pretty good, 0 bleed for me, just the usual IPS glow. How about the pixels? I see the pixels even at one meter away, especially on Microsoft Edge's icon for example, the blue background is just blocky, don't know why.
+
"#;
+let output = r#"Hi, I've bought this very same monitor and found no calibration whatsoever. I have an ICC profile that has been set up since I've installed its driver from the LG website and it works ok. I also used http://www.lagom.nl/lcd-test/ to calibrate it. After some good tinkering I've found the following settings + the color profile from the driver gets me past all the tests perfectly:
+
Brightness 50 (still have to settle on this one, it's personal preference, it controls the backlight, not the colors) Contrast 70 (which for me was the default one) Picture mode Custom Super resolution + Off (it looks horrible anyway) Sharpness 50 (default one I think) Black level High (low messes up gray colors) DFC Off Response Time Middle (personal preference, https://www.blurbusters.com/ show horrible overdrive with it on high) Freesync doesn't matter Black stabilizer 50 Gamma setting on 0 Color Temp Medium
+How`s your monitor by the way? Any IPS bleed whatsoever? I either got lucky or the panel is pretty good, 0 bleed for me, just the usual IPS glow. How about the pixels? I see the pixels even at one meter away, especially on Microsoft Edge's icon for example, the blue background is just blocky, don't know why.
+
"#;
+
+ assert_eq!(render_bullet_lists(input), output);
+}
\ No newline at end of file
From 23cda23d013e4a2866afd1cad0649cb40bb612d0 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sun, 2 Feb 2025 23:30:33 -0500
Subject: [PATCH 17/49] feat: add environment variables and dedicated flags for
ipv4/6 only (#307)
* feat: add environment variables and dedicated flags for ipv4/6 only
* fix(readme): mention all flags on README
---
README.md | 11 +++++++++++
src/main.rs | 25 ++++++++++++++++++++++++-
2 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4026791..6fabfcc 100644
--- a/README.md
+++ b/README.md
@@ -404,6 +404,17 @@ REDLIB_DEFAULT_USE_HLS = "on"
>
> If using Docker Compose, no changes are needed as the `.env` file is already referenced in `compose.yaml` via the `env_file: .env` line.
+## Command Line Flags
+
+Redlib supports the following command line flags:
+
+- `-4`, `--ipv4-only`: Listen on IPv4 only.
+- `-6`, `--ipv6-only`: Listen on IPv6 only.
+- `-r`, `--redirect-https`: Redirect all HTTP requests to HTTPS (no longer functional).
+- `-a`, `--address `: Sets address to listen on. Default is `[::]`.
+- `-p`, `--port `: Port to listen on. Default is `8080`.
+- `-H`, `--hsts `: HSTS header to tell browsers that this site should only be accessed over HTTPS. Default is `604800`.
+
## Instance settings
Assign a default value for each instance-specific setting by passing environment variables to Redlib in the format `REDLIB_{X}`. Replace `{X}` with the setting name (see list below) in capital letters.
diff --git a/src/main.rs b/src/main.rs
index 8732d20..a109560 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -108,6 +108,20 @@ async fn main() {
let matches = Command::new("Redlib")
.version(env!("CARGO_PKG_VERSION"))
.about("Private front-end for Reddit written in Rust ")
+ .arg(
+ Arg::new("ipv4-only")
+ .short('4')
+ .long("ipv4-only")
+ .help("Listen on IPv4 only")
+ .num_args(0),
+ )
+ .arg(
+ Arg::new("ipv6-only")
+ .short('6')
+ .long("ipv6-only")
+ .help("Listen on IPv6 only")
+ .num_args(0),
+ )
.arg(
Arg::new("redirect-https")
.short('r')
@@ -164,7 +178,16 @@ async fn main() {
let port = matches.get_one::("port").unwrap();
let hsts = matches.get_one("hsts").map(|m: &String| m.as_str());
- let listener = [address, ":", port].concat();
+ let ipv4_only = std::env::var("IPV4_ONLY").is_ok() || matches.get_flag("ipv4-only");
+ let ipv6_only = std::env::var("IPV6_ONLY").is_ok() || matches.get_flag("ipv6-only");
+
+ let listener = if ipv4_only {
+ format!("0.0.0.0:{}", port)
+ } else if ipv6_only {
+ format!("[::]:{}", port)
+ } else {
+ [address, ":", port].concat()
+ };
println!("Starting Redlib...");
From 2e0e1a1aaab5e020365e06343e5d9def30a46466 Mon Sep 17 00:00:00 2001
From: Butter Cat
Date: Sun, 2 Feb 2025 23:31:37 -0500
Subject: [PATCH 18/49] Fix crossposted galleries not working (#293)
---
src/utils.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/utils.rs b/src/utils.rs
index 1bc70b9..c4991d8 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -234,6 +234,14 @@ impl Media {
// If this post contains a gallery of images
gallery = GalleryMedia::parse(&data["gallery_data"]["items"], &data["media_metadata"]);
+ ("gallery", &data["url"], None)
+ } else if data["crosspost_parent_list"][0]["is_gallery"].as_bool().unwrap_or_default() {
+ // If this post contains a gallery of images
+ gallery = GalleryMedia::parse(
+ &data["crosspost_parent_list"][0]["gallery_data"]["items"],
+ &data["crosspost_parent_list"][0]["media_metadata"],
+ );
+
("gallery", &data["url"], None)
} else if data["is_reddit_media_domain"].as_bool().unwrap_or_default() && data["domain"] == "i.redd.it" {
// If this post contains a reddit media (image) URL.
From 68a0517115b31d5188c01d0c5c30575bf877a6a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gon=C3=A7alo=20Val=C3=A9rio?=
Date: Mon, 3 Feb 2025 04:32:23 +0000
Subject: [PATCH 19/49] update devcontainer image, that includes a more recent
version of rust (#294)
---
.devcontainer/devcontainer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 3a941de..3ec1ead 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,6 +1,6 @@
{
"name": "Rust",
- "image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye",
+ "image": "mcr.microsoft.com/devcontainers/rust:1.0.9-bookworm",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
From 51386671d3474b5020cd43db8d7536ba1ac7979d Mon Sep 17 00:00:00 2001
From: Butter Cat
Date: Sun, 2 Feb 2025 23:38:52 -0500
Subject: [PATCH 20/49] Fix embedded images sometimes having gaps around them
(#295)
* Fix images embedded by rewrite_urls() having an empty
above and below them that caused weird gaps in some scenarios
* Fix test for new embedding behavior
* fix: remove println
---------
Co-authored-by: Matthew Esposito
---
src/utils.rs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/utils.rs b/src/utils.rs
index c4991d8..4ed7664 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1010,7 +1010,7 @@ pub fn render_bullet_lists(input_text: &str) -> String {
// These are links we want to replace in-body
static REDDIT_REGEX: Lazy = Lazy::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it)/"#).unwrap());
-static REDDIT_PREVIEW_REGEX: Lazy = Lazy::new(|| Regex::new(r"https?://(external-preview|preview|i)\.redd\.it(.*)[^?]").unwrap());
+static REDDIT_PREVIEW_REGEX: Lazy = Lazy::new(|| Regex::new(r"https?://(external-preview|preview|i)\.redd\.it(.*)").unwrap());
static REDDIT_EMOJI_REGEX: Lazy = Lazy::new(|| Regex::new(r"https?://(www|).redditstatic\.com/(.*)").unwrap());
static REDLIB_PREVIEW_LINK_REGEX: Lazy = Lazy::new(|| Regex::new(r#"/(img|preview/)(pre|external-pre)?/(.*?)>"#).unwrap());
static REDLIB_PREVIEW_TEXT_REGEX: Lazy = Lazy::new(|| Regex::new(r">(.*?) ").unwrap());
@@ -1052,7 +1052,7 @@ pub fn rewrite_urls(input_text: &str) -> String {
}
// image_url contains > at the end of it, and right above this we remove image_text's front >, leaving us with just a single > between them
- let image_to_replace = format!("");
+ let image_to_replace = format!("
");
// _image_replacement needs to be in scope for the replacement at the bottom of the loop
let mut _image_replacement = String::new();
@@ -1501,7 +1501,7 @@ async fn test_fetching_ws() {
fn test_rewriting_image_links() {
let input =
r#"caption 1
"#;
- let output = r#"caption 1
caption 1 "#;
assert_eq!(rewrite_urls(input), output);
}
From bbe5f8191475ea20e41e5a4eb1572fbd2a45d8b3 Mon Sep 17 00:00:00 2001
From: internationalcrisis
<97415475+internationalcrisis@users.noreply.github.com>
Date: Sun, 2 Feb 2025 22:40:19 -0600
Subject: [PATCH 21/49] fix: gracefully shutdown on CTRL+C and SIGTERM (#273)
Fixes #205
---
src/server.rs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/server.rs b/src/server.rs
index 5297c22..a287de2 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -282,8 +282,19 @@ impl Server {
// Bind server to address specified above. Gracefully shut down if CTRL+C is pressed
let server = HyperServer::bind(address).serve(make_svc).with_graceful_shutdown(async {
+ #[cfg(windows)]
// Wait for the CTRL+C signal
tokio::signal::ctrl_c().await.expect("Failed to install CTRL+C signal handler");
+
+ #[cfg(unix)]
+ {
+ // Wait for CTRL+C or SIGTERM signals
+ let mut signal_terminate = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).expect("Failed to install SIGTERM signal handler");
+ tokio::select! {
+ _ = tokio::signal::ctrl_c() => (),
+ _ = signal_terminate.recv() => ()
+ }
+ }
});
server.boxed()
From 257871b56ca1d5612482df05fed14b1c45e04de9 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Mon, 3 Feb 2025 00:30:48 -0500
Subject: [PATCH 22/49] fix(tests)
---
src/utils.rs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/utils.rs b/src/utils.rs
index 4ed7664..cf226b9 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1483,7 +1483,10 @@ async fn test_fetching_subreddit_quarantined() {
#[tokio::test(flavor = "multi_thread")]
async fn test_fetching_nsfw_subreddit() {
- let subreddit = Post::fetch("/r/randnsfw", false).await;
+ // Gonwild is a place for closed, Euclidean Geometric shapes to exchange their nth terms for karma; showing off their edges in a comfortable environment without pressure.
+ // Find a good sub that is tagged NSFW but that actually isn't in case my future employers are watching (they probably are)
+ // switched from randnsfw as it is no longer functional.
+ let subreddit = Post::fetch("/r/gonwild", false).await;
assert!(subreddit.is_ok());
assert!(!subreddit.unwrap().0.is_empty());
}
From 7930b19809f00c99705949027550add74068da3b Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Mon, 3 Feb 2025 00:47:25 -0500
Subject: [PATCH 23/49] fix: fix clippy + tests
---
src/client.rs | 6 ------
src/main.rs | 18 +++---------------
src/utils.rs | 4 ++--
3 files changed, 5 insertions(+), 23 deletions(-)
diff --git a/src/client.rs b/src/client.rs
index fa32fc0..76369ca 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -544,12 +544,6 @@ async fn test_obfuscated_share_link() {
assert_eq!(canonical_path(share_link, 3).await, Ok(Some(canonical_link)));
}
-#[tokio::test(flavor = "multi_thread")]
-async fn test_share_link_strip_json() {
- let link = "/17krzvz".into();
- let canonical_link = "/comments/17krzvz".into();
- assert_eq!(canonical_path(link, 3).await, Ok(Some(canonical_link)));
-}
#[tokio::test(flavor = "multi_thread")]
async fn test_private_sub() {
let link = json("/r/suicide/about.json?raw_json=1".into(), true).await;
diff --git a/src/main.rs b/src/main.rs
index a109560..165f0cb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -108,20 +108,8 @@ async fn main() {
let matches = Command::new("Redlib")
.version(env!("CARGO_PKG_VERSION"))
.about("Private front-end for Reddit written in Rust ")
- .arg(
- Arg::new("ipv4-only")
- .short('4')
- .long("ipv4-only")
- .help("Listen on IPv4 only")
- .num_args(0),
- )
- .arg(
- Arg::new("ipv6-only")
- .short('6')
- .long("ipv6-only")
- .help("Listen on IPv6 only")
- .num_args(0),
- )
+ .arg(Arg::new("ipv4-only").short('4').long("ipv4-only").help("Listen on IPv4 only").num_args(0))
+ .arg(Arg::new("ipv6-only").short('6').long("ipv6-only").help("Listen on IPv6 only").num_args(0))
.arg(
Arg::new("redirect-https")
.short('r')
@@ -392,7 +380,7 @@ async fn main() {
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
// Short link for post
- Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{id}"), 3).await {
+ Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/comments/{id}"), 3).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,
diff --git a/src/utils.rs b/src/utils.rs
index cf226b9..867f73f 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1549,10 +1549,10 @@ fn test_rewriting_bullet_list() {
- Color Temp Medium
How`s your monitor by the way? Any IPS bleed whatsoever? I either got lucky or the panel is pretty good, 0 bleed for me, just the usual IPS glow. How about the pixels? I see the pixels even at one meter away, especially on Microsoft Edge's icon for example, the blue background is just blocky, don't know why.
"#;
-let output = r#"Hi, I've bought this very same monitor and found no calibration whatsoever. I have an ICC profile that has been set up since I've installed its driver from the LG website and it works ok. I also used http://www.lagom.nl/lcd-test/ to calibrate it. After some good tinkering I've found the following settings + the color profile from the driver gets me past all the tests perfectly:
+ let output = r#"
Hi, I've bought this very same monitor and found no calibration whatsoever. I have an ICC profile that has been set up since I've installed its driver from the LG website and it works ok. I also used http://www.lagom.nl/lcd-test/ to calibrate it. After some good tinkering I've found the following settings + the color profile from the driver gets me past all the tests perfectly:
Brightness 50 (still have to settle on this one, it's personal preference, it controls the backlight, not the colors) Contrast 70 (which for me was the default one) Picture mode Custom Super resolution + Off (it looks horrible anyway) Sharpness 50 (default one I think) Black level High (low messes up gray colors) DFC Off Response Time Middle (personal preference, https://www.blurbusters.com/ show horrible overdrive with it on high) Freesync doesn't matter Black stabilizer 50 Gamma setting on 0 Color Temp Medium
How`s your monitor by the way? Any IPS bleed whatsoever? I either got lucky or the panel is pretty good, 0 bleed for me, just the usual IPS glow. How about the pixels? I see the pixels even at one meter away, especially on Microsoft Edge's icon for example, the blue background is just blocky, don't know why.
"#;
assert_eq!(render_bullet_lists(input), output);
-}
\ No newline at end of file
+}
From ef2cc01bf7f43018dddf60d9b575b1b66934c2c1 Mon Sep 17 00:00:00 2001
From: Integral
Date: Mon, 3 Feb 2025 13:51:54 +0800
Subject: [PATCH 24/49] refactor(utils): avoid redundant String conversions &
use match (#347)
* refactor(utils): avoid redundant String conversions & use match
* ci: fix clippy lint
---
src/utils.rs | 57 ++++++++++++++++++++++++----------------------------
1 file changed, 26 insertions(+), 31 deletions(-)
diff --git a/src/utils.rs b/src/utils.rs
index 867f73f..21461a7 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -638,7 +638,7 @@ pub struct Preferences {
pub hide_score: String,
}
-fn serialize_vec_with_plus(vec: &Vec, serializer: S) -> Result
+fn serialize_vec_with_plus(vec: &[String], serializer: S) -> Result
where
S: Serializer,
{
@@ -915,11 +915,12 @@ pub fn setting_or_default(req: &Request, name: &str, default: String) -> S
// Detect and redirect in the event of a random subreddit
pub async fn catch_random(sub: &str, additional: &str) -> Result, String> {
if sub == "random" || sub == "randnsfw" {
- let new_sub = json(format!("/r/{sub}/about.json?raw_json=1"), false).await?["data"]["display_name"]
- .as_str()
- .unwrap_or_default()
- .to_string();
- Ok(redirect(&format!("/r/{new_sub}{additional}")))
+ Ok(redirect(&format!(
+ "/r/{}{additional}",
+ json(format!("/r/{sub}/about.json?raw_json=1"), false).await?["data"]["display_name"]
+ .as_str()
+ .unwrap_or_default()
+ )))
} else {
Err("No redirect needed".to_string())
}
@@ -1019,8 +1020,7 @@ static REDLIB_PREVIEW_TEXT_REGEX: Lazy = Lazy::new(|| Regex::new(r">(.*?)
pub fn rewrite_urls(input_text: &str) -> String {
let mut text1 =
// Rewrite Reddit links to Redlib
- REDDIT_REGEX.replace_all(input_text, r#"href="/"#)
- .to_string();
+ REDDIT_REGEX.replace_all(input_text, r#"href="/"#).to_string();
loop {
if REDDIT_EMOJI_REGEX.find(&text1).is_none() {
@@ -1042,49 +1042,44 @@ pub fn rewrite_urls(input_text: &str) -> String {
} else {
let formatted_url = format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default());
- let image_url = REDLIB_PREVIEW_LINK_REGEX.find(&formatted_url).map_or("", |m| m.as_str()).to_string();
- let mut image_caption = REDLIB_PREVIEW_TEXT_REGEX.find(&formatted_url).map_or("", |m| m.as_str()).to_string();
+ let image_url = REDLIB_PREVIEW_LINK_REGEX.find(&formatted_url).map_or("", |m| m.as_str());
+ let mut image_caption = REDLIB_PREVIEW_TEXT_REGEX.find(&formatted_url).map_or("", |m| m.as_str());
/* As long as image_caption isn't empty remove first and last four characters of image_text to leave us with just the text in the caption without any HTML.
This makes it possible to enclose it in a later on without having stray HTML breaking it */
if !image_caption.is_empty() {
- image_caption = image_caption[1..image_caption.len() - 4].to_string();
+ image_caption = &image_caption[1..image_caption.len() - 4];
}
// image_url contains > at the end of it, and right above this we remove image_text's front >, leaving us with just a single > between them
let image_to_replace = format!("
");
- // _image_replacement needs to be in scope for the replacement at the bottom of the loop
- let mut _image_replacement = String::new();
-
/* We don't want to show a caption that's just the image's link, so we check if we find a Reddit preview link within the image's caption.
If we don't find one we must have actual text, so we include a block that contains it.
Otherwise we don't include the block as we don't need it. */
- if REDDIT_PREVIEW_REGEX.find(&image_caption).is_none() {
+ let _image_replacement = if REDDIT_PREVIEW_REGEX.find(image_caption).is_none() {
// Without this " would show as \" instead. "\"" is how the quotes are formatted within image_text beforehand
- image_caption = image_caption.replace("\\"", "\"");
-
- _image_replacement = format!("{image_caption} ");
+ format!(
+ "{} ",
+ image_caption.replace("\\"", "\"")
+ )
} else {
- _image_replacement = format!(" ");
- }
+ format!(" ")
+ };
/* In order to know if we're dealing with a normal or external preview we need to take a look at the first capture group of REDDIT_PREVIEW_REGEX
if it's preview we're dealing with something that needs /preview/pre, external-preview is /preview/external-pre, and i is /img */
- let reddit_preview_regex_capture = REDDIT_PREVIEW_REGEX.captures(&text1).unwrap().get(1).map_or("", |m| m.as_str()).to_string();
- let mut _preview_type = String::new();
- if reddit_preview_regex_capture == "preview" {
- _preview_type = "/preview/pre".to_string();
- } else if reddit_preview_regex_capture == "external-preview" {
- _preview_type = "/preview/external-pre".to_string();
- } else {
- _preview_type = "/img".to_string();
- }
+ let reddit_preview_regex_capture = REDDIT_PREVIEW_REGEX.captures(&text1).unwrap().get(1).map_or("", |m| m.as_str());
+
+ let _preview_type = match reddit_preview_regex_capture {
+ "preview" => "/preview/pre",
+ "external-preview" => "/preview/external-pre",
+ _ => "/img",
+ };
text1 = REDDIT_PREVIEW_REGEX
.replace(&text1, format!("{_preview_type}$2"))
.replace(&image_to_replace, &_image_replacement)
- .to_string()
}
}
}
@@ -1158,7 +1153,7 @@ pub fn rewrite_emotes(media_metadata: &Value, comment: String) -> String {
);
// Inside the comment replace the ID we found with the string that will embed the image
- comment = comment.replace(&id, &to_replace_with).to_string();
+ comment = comment.replace(&id, &to_replace_with);
}
}
}
From c7f55c146a641540c293967e53ab6ee55eac2516 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Mon, 3 Feb 2025 00:53:13 -0500
Subject: [PATCH 25/49] fix(clippy): minor clippy changes
---
src/utils.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/utils.rs b/src/utils.rs
index 21461a7..23db3d0 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1004,7 +1004,7 @@ static REGEX_BULLET_CONSECUTIVE_LINES: Lazy = Lazy::new(|| Regex::new(r"<
pub fn render_bullet_lists(input_text: &str) -> String {
// ref: https://stackoverflow.com/a/4902622
// First enclose each bullet with tags
- let text1 = REGEX_BULLET.replace_all(&input_text, "").to_string();
+ let text1 = REGEX_BULLET.replace_all(input_text, "").to_string();
// Then remove any consecutive tags
REGEX_BULLET_CONSECUTIVE_LINES.replace_all(&text1, "").to_string()
}
@@ -1344,7 +1344,7 @@ pub fn url_path_basename(path: &str) -> String {
let mut url = url_result.unwrap();
url.path_segments_mut().unwrap().pop_if_empty();
- url.path_segments().unwrap().last().unwrap().to_string()
+ url.path_segments().unwrap().next_back().unwrap().to_string()
}
}
From 7770c57856f2ee805372cc610d5aa71fe7df5fa2 Mon Sep 17 00:00:00 2001
From: freedit-dev
Date: Mon, 3 Feb 2025 13:58:14 +0800
Subject: [PATCH 26/49] fix Code blocks err #227 (#323)
* fix Code blocks https://github.com/redlib-org/redlib/issues/227
* add pulldown-cmark
* add pulldown-cmark
* fix Code blocks err #227
* add pre style for post codeblock
* Update style.css (fix Code blocks err #227 )
---------
Co-authored-by: Matthew Esposito
---
Cargo.lock | 19 +++++++++++++++++++
Cargo.toml | 1 +
src/utils.rs | 10 +++++++++-
static/style.css | 7 +++++++
4 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/Cargo.lock b/Cargo.lock
index 24791b4..6578d17 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1302,6 +1302,24 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "pulldown-cmark"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "pulldown-cmark-escape",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-escape"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
+
[[package]]
name = "quick-error"
version = "1.2.3"
@@ -1383,6 +1401,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pretty_env_logger",
+ "pulldown-cmark",
"regex",
"rinja",
"route-recognizer",
diff --git a/Cargo.toml b/Cargo.toml
index 843b9c9..fc9074b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -47,6 +47,7 @@ rss = "2.0.7"
arc-swap = "1.7.1"
serde_json_path = "0.7.1"
async-recursion = "1.1.1"
+pulldown-cmark = { version = "0.12.0", features = ["simd", "html"], default-features = false }
common-words-all = { version = "0.0.2", default-features = false, features = ["english", "one"] }
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
tegen = "0.1.4"
diff --git a/src/utils.rs b/src/utils.rs
index 23db3d0..0aa3159 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -743,7 +743,15 @@ pub async fn parse_post(post: &Value) -> Post {
get_setting("REDLIB_PUSHSHIFT_FRONTEND").unwrap_or_else(|| String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)),
)
} else {
- rewrite_urls(&val(post, "selftext_html"))
+ let selftext = val(post, "selftext");
+ if selftext.contains("```") {
+ let mut html_output = String::new();
+ let parser = pulldown_cmark::Parser::new(&selftext);
+ pulldown_cmark::html::push_html(&mut html_output, parser);
+ rewrite_urls(&html_output)
+ } else {
+ rewrite_urls(&val(post, "selftext_html"))
+ }
};
// Build a post using data parsed from Reddit post API
diff --git a/static/style.css b/static/style.css
index a9d893a..76b55dd 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1199,6 +1199,13 @@ a.search_subreddit:hover {
overflow-wrap: anywhere;
}
+.post_body pre {
+ background: var(--background);
+ overflow-x: auto;
+ margin: 10px 0;
+ padding: 10px;
+}
+
.post_body img {
max-width: 100%;
display: block;
From a732f181430c14b3a292b54fd372e069018ab03c Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Mon, 3 Feb 2025 14:25:16 -0500
Subject: [PATCH 27/49] chore: remove scraper cli
---
Cargo.toml | 8 ---
src/scraper/main.rs | 132 --------------------------------------------
2 files changed, 140 deletions(-)
delete mode 100644 src/scraper/main.rs
diff --git a/Cargo.toml b/Cargo.toml
index fc9074b..759b148 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -64,11 +64,3 @@ sealed_test = "1.0.0"
codegen-units = 1
lto = true
strip = "symbols"
-
-[[bin]]
-name = "redlib"
-path = "src/main.rs"
-
-[[bin]]
-name = "scraper"
-path = "src/scraper/main.rs"
diff --git a/src/scraper/main.rs b/src/scraper/main.rs
deleted file mode 100644
index f2e48d6..0000000
--- a/src/scraper/main.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-use std::{collections::HashMap, fmt::Display, io::Write};
-
-use clap::{Parser, ValueEnum};
-use common_words_all::{get_top, Language, NgramSize};
-use redlib::utils::Post;
-
-#[derive(Parser)]
-#[command(name = "my_cli")]
-#[command(about = "A simple CLI example", long_about = None)]
-struct Cli {
- #[arg(short = 's', long = "sub")]
- sub: String,
-
- #[arg(long = "sort")]
- sort: SortOrder,
-
- #[arg(short = 'f', long = "format", value_enum)]
- format: Format,
- #[arg(short = 'o', long = "output")]
- output: Option,
-}
-
-#[derive(Debug, Clone, ValueEnum)]
-enum SortOrder {
- Hot,
- Rising,
- New,
- Top,
- Controversial,
-}
-
-impl Display for SortOrder {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- SortOrder::Hot => write!(f, "hot"),
- SortOrder::Rising => write!(f, "rising"),
- SortOrder::New => write!(f, "new"),
- SortOrder::Top => write!(f, "top"),
- SortOrder::Controversial => write!(f, "controversial"),
- }
- }
-}
-
-#[derive(Debug, Clone, ValueEnum)]
-enum Format {
- Json,
-}
-
-#[tokio::main]
-async fn main() {
- pretty_env_logger::init();
- let cli = Cli::parse();
- let (sub, sort, format, output) = (cli.sub, cli.sort, cli.format, cli.output);
- let initial = format!("/r/{sub}/{sort}.json?&raw_json=1");
- let (posts, mut after) = Post::fetch(&initial, false).await.unwrap();
- let mut hashmap = HashMap::new();
- hashmap.extend(posts.into_iter().map(|post| (post.id.clone(), post)));
- loop {
- print!("\r");
- let path = format!("/r/{sub}/{sort}.json?sort={sort}&t=&after={after}&raw_json=1");
- let (new_posts, new_after) = Post::fetch(&path, false).await.unwrap();
- let old_len = hashmap.len();
- // convert to hashmap and extend hashmap
- let new_posts = new_posts.into_iter().map(|post| (post.id.clone(), post)).collect::>();
- let len = new_posts.len();
- hashmap.extend(new_posts);
- if hashmap.len() - old_len < 3 {
- break;
- }
-
- let x = hashmap.len() - old_len;
- after = new_after;
- // Print number of posts fetched
- print!("Fetched {len} posts (+{x})",);
- std::io::stdout().flush().unwrap();
- }
- println!("\n\n");
- // additionally search if final count not reached
-
- for word in get_top(Language::English, 10_000, NgramSize::One) {
- let mut retrieved_posts_from_search = 0;
- let initial = format!("/r/{sub}/search.json?q={word}&restrict_sr=on&include_over_18=on&raw_json=1&sort={sort}");
- println!("Grabbing posts with word {word}.");
- let (posts, mut after) = Post::fetch(&initial, false).await.unwrap();
- hashmap.extend(posts.into_iter().map(|post| (post.id.clone(), post)));
- 'search: loop {
- let path = format!("/r/{sub}/search.json?q={word}&restrict_sr=on&include_over_18=on&raw_json=1&sort={sort}&after={after}");
- let (new_posts, new_after) = Post::fetch(&path, false).await.unwrap();
- if new_posts.is_empty() || new_after.is_empty() {
- println!("No more posts for word {word}");
- break 'search;
- }
- retrieved_posts_from_search += new_posts.len();
- let old_len = hashmap.len();
- let new_posts = new_posts.into_iter().map(|post| (post.id.clone(), post)).collect::>();
- let len = new_posts.len();
- hashmap.extend(new_posts);
- let delta = hashmap.len() - old_len;
- after = new_after;
- // Print number of posts fetched
- println!("Fetched {len} posts (+{delta})",);
-
- if retrieved_posts_from_search > 1000 {
- println!("Reached 1000 posts from search");
- break 'search;
- }
- }
- // Need to save incrementally. atomic save + move
- let tmp_file = output.clone().unwrap_or_else(|| format!("{sub}.json.tmp"));
- let perm_file = output.clone().unwrap_or_else(|| format!("{sub}.json"));
- write_posts(&hashmap.values().collect(), tmp_file.clone());
- // move file
- std::fs::rename(tmp_file, perm_file).unwrap();
- }
-
- println!("\n\n");
-
- println!("Size of hashmap: {}", hashmap.len());
-
- let posts: Vec<&Post> = hashmap.values().collect();
- match format {
- Format::Json => {
- let filename: String = output.unwrap_or_else(|| format!("{sub}.json"));
- write_posts(&posts, filename);
- }
- }
-}
-
-fn write_posts(posts: &Vec<&Post>, filename: String) {
- let json = serde_json::to_string(&posts).unwrap();
- std::fs::write(filename, json).unwrap();
-}
From 85329c96a79c87b2be7477d4982746e294e2ee00 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 6 Feb 2025 09:02:55 -0500
Subject: [PATCH 28/49] fix: remove stray trace
---
src/subreddit.rs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/subreddit.rs b/src/subreddit.rs
index 0db4f77..f7fe01d 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -64,7 +64,6 @@ pub async fn community(req: Request) -> Result, String> {
// Build Reddit API path
let root = req.uri().path() == "/";
let query = req.uri().query().unwrap_or_default().to_string();
- trace!("query: {}", query);
let subscribed = setting(&req, "subscriptions");
let front_page = setting(&req, "front_page");
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
From 5265ccb0332f03f01eb37ccc648d17608b148bd8 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 6 Feb 2025 13:03:42 -0500
Subject: [PATCH 29/49] feat: hide default feeds option (#370)
---
README.md | 3 ++-
src/settings.rs | 3 ++-
src/subreddit.rs | 21 +++++++++++++++++++--
src/utils.rs | 27 ++++++++++++++++++++++++++-
templates/info.html | 20 ++++++++++++++++++++
templates/settings.html | 5 +++++
templates/utils.html | 6 ++++--
7 files changed, 78 insertions(+), 7 deletions(-)
create mode 100644 templates/info.html
diff --git a/README.md b/README.md
index 6fabfcc..5aac1e8 100644
--- a/README.md
+++ b/README.md
@@ -440,7 +440,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` |
+| `BLUR_SPOILER` | `["on", "off"]` | `off` |
| `SHOW_NSFW` | `["on", "off"]` | `off` |
| `BLUR_NSFW` | `["on", "off"]` | `off` |
| `USE_HLS` | `["on", "off"]` | `off` |
@@ -452,3 +452,4 @@ Assign a default value for each user-modifiable setting by passing environment v
| `HIDE_SCORE` | `["on", "off"]` | `off` |
| `HIDE_SIDEBAR_AND_SUMMARY` | `["on", "off"]` | `off` |
| `FIXED_NAVBAR` | `["on", "off"]` | `on` |
+| `REMOVE_DEFAULT_FEEDS` | `["on", "off"]` | `off` |
\ No newline at end of file
diff --git a/src/settings.rs b/src/settings.rs
index 34718c2..4b30da3 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -22,7 +22,7 @@ struct SettingsTemplate {
// CONSTANTS
-const PREFS: [&str; 18] = [
+const PREFS: [&str; 19] = [
"theme",
"front_page",
"layout",
@@ -41,6 +41,7 @@ const PREFS: [&str; 18] = [
"hide_score",
"disable_visit_reddit_confirmation",
"video_quality",
+ "remove_default_feeds",
];
// FUNCTIONS
diff --git a/src/subreddit.rs b/src/subreddit.rs
index f7fe01d..d03a9dd 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -3,12 +3,13 @@
use crate::{config, utils};
// CRATES
use crate::utils::{
- catch_random, error, filter_posts, format_num, format_url, get_filters, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
+ catch_random, error, filter_posts, format_num, format_url, get_filters, info, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences,
+ Subreddit,
};
use crate::{client::json, server::RequestExt, server::ResponseExt};
use cookie::Cookie;
use hyper::{Body, Request, Response};
-use log::{debug, trace};
+use log::debug;
use rinja::Template;
use chrono::DateTime;
@@ -66,6 +67,7 @@ pub async fn community(req: Request) -> Result, String> {
let query = req.uri().query().unwrap_or_default().to_string();
let subscribed = setting(&req, "subscriptions");
let front_page = setting(&req, "front_page");
+ let remove_default_feeds = setting(&req, "remove_default_feeds") == "on";
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));
@@ -78,6 +80,21 @@ pub async fn community(req: Request) -> Result, String> {
} else {
front_page.clone()
});
+
+ if (sub_name == "popular" || sub_name == "all") && remove_default_feeds {
+ if subscribed.is_empty() {
+ return info(req, "Subscribe to some subreddits! (Default feeds disabled in settings)").await;
+ } else {
+ // If there are subscribed subs, but we get here, then the problem is that front_page pref is set to something besides default.
+ // Tell user to go to settings and change front page to default.
+ return info(
+ req,
+ "You have subscribed to some subreddits, but your front page is not set to default. Visit settings and change front page to default.",
+ )
+ .await;
+ }
+ }
+
let quarantined = can_access_quarantine(&req, &sub_name) || root;
// Handle random subreddits
diff --git a/src/utils.rs b/src/utils.rs
index 0aa3159..2747449 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -551,6 +551,14 @@ pub struct ErrorTemplate {
pub url: String,
}
+#[derive(Template)]
+#[template(path = "info.html")]
+pub struct InfoTemplate {
+ pub msg: String,
+ pub prefs: Preferences,
+ 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.
@@ -636,6 +644,7 @@ pub struct Preferences {
pub filters: Vec,
pub hide_awards: String,
pub hide_score: String,
+ pub remove_default_feeds: String,
}
fn serialize_vec_with_plus(vec: &[String], serializer: S) -> Result
@@ -682,6 +691,7 @@ impl Preferences {
filters: setting(req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
hide_awards: setting(req, "hide_awards"),
hide_score: setting(req, "hide_score"),
+ remove_default_feeds: setting(req, "remove_default_feeds"),
}
}
@@ -1265,6 +1275,20 @@ pub async fn error(req: Request, msg: &str) -> Result, Stri
Ok(Response::builder().status(404).header("content-type", "text/html").body(body.into()).unwrap_or_default())
}
+/// Renders a generic info landing page.
+pub async fn info(req: Request, msg: &str) -> Result, String> {
+ let url = req.uri().to_string();
+ let body = InfoTemplate {
+ msg: msg.to_string(),
+ prefs: Preferences::new(&req),
+ url,
+ }
+ .render()
+ .unwrap_or_default();
+
+ Ok(Response::builder().status(200).header("content-type", "text/html").body(body.into()).unwrap_or_default())
+}
+
/// Returns true if the config/env variable `REDLIB_SFW_ONLY` carries the
/// value `on`.
///
@@ -1463,10 +1487,11 @@ mod tests {
filters: vec![],
hide_awards: "off".to_owned(),
hide_score: "off".to_owned(),
+ remove_default_feeds: "off".to_owned(),
};
let urlencoded = serde_urlencoded::to_string(prefs).expect("Failed to serialize Prefs");
- assert_eq!(urlencoded, "theme=laserwave&front_page=default&layout=compact&wide=on&blur_spoiler=on&show_nsfw=off&blur_nsfw=on&hide_hls_notification=off&video_quality=best&hide_sidebar_and_summary=off&use_hls=on&autoplay_videos=on&fixed_navbar=on&disable_visit_reddit_confirmation=on&comment_sort=confidence&post_sort=top&subscriptions=memes%2Bmildlyinteresting&filters=&hide_awards=off&hide_score=off")
+ assert_eq!(urlencoded, "theme=laserwave&front_page=default&layout=compact&wide=on&blur_spoiler=on&show_nsfw=off&blur_nsfw=on&hide_hls_notification=off&video_quality=best&hide_sidebar_and_summary=off&use_hls=on&autoplay_videos=on&fixed_navbar=on&disable_visit_reddit_confirmation=on&comment_sort=confidence&post_sort=top&subscriptions=memes%2Bmildlyinteresting&filters=&hide_awards=off&hide_score=off&remove_default_feeds=off");
}
}
diff --git a/templates/info.html b/templates/info.html
new file mode 100644
index 0000000..a14c170
--- /dev/null
+++ b/templates/info.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+{% import "utils.html" as utils %}
+
+{% block title %}Info: {{ msg }}{% endblock %}
+{% block sortstyle %}{% endblock %}
+
+{% block subscriptions %}
+ {% call utils::sub_list("") %}
+{% endblock %}
+
+{% block search %}
+ {% call utils::search("".to_owned(), "") %}
+{% endblock %}
+
+{% block content %}
+
+
{{ msg }}
+
+
+{% endblock %}
diff --git a/templates/settings.html b/templates/settings.html
index fef91cf..7995312 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -26,6 +26,11 @@
Interface
+
+ Remove default feeds
+
+
+
Front page:
diff --git a/templates/utils.html b/templates/utils.html
index e0b3589..4cee979 100644
--- a/templates/utils.html
+++ b/templates/utils.html
@@ -43,8 +43,10 @@
MAIN FEEDS
Home
-
Popular
-
All
+ {% if prefs.remove_default_feeds != "on" %}
+
Popular
+
All
+ {% endif %}
{% if prefs.subscriptions.len() > 0 %}
REDDIT FEEDS
{% for sub in prefs.subscriptions %}
From 7d3160c149195b4692c34f832e22bb605605f663 Mon Sep 17 00:00:00 2001
From: LucifersCircle
Date: Thu, 6 Feb 2025 13:45:39 -0800
Subject: [PATCH 30/49] style(theme): add MidnightPurple (#346)
* Create midnightPurple.css
a pure black theme with midnight purple accent
* Update midnightPurple.css
changed the purple accent to a lighter lavender
---
static/themes/midnightPurple.css | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 static/themes/midnightPurple.css
diff --git a/static/themes/midnightPurple.css b/static/themes/midnightPurple.css
new file mode 100644
index 0000000..3ba38cd
--- /dev/null
+++ b/static/themes/midnightPurple.css
@@ -0,0 +1,14 @@
+/* midnightpurple theme setting */
+.midnightPurple{
+ --accent: #be6ede;
+ --green: #268F02;
+ --text: white;
+ --foreground: #222;
+ --background: #000000;
+ --outside: #1f1f1f;
+ --post: #000000;
+ --panel-border: 1px solid #4E1764;
+ --highlighted: #333;
+ --visited: #aaa;
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
+}
\ No newline at end of file
From 2e95e1fc6e2064ccfae87964b4860bda55eddb9a Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 6 Feb 2025 20:34:12 -0500
Subject: [PATCH 31/49] feat: smaller imports and exports (#373)
* feat: smaller imports and exports
* test(prefs): extend tests
* style(clippy)
* style: bubble up error
* style: update some wording
---
Cargo.lock | 84 +++++++++++------------
Cargo.toml | 4 +-
src/main.rs | 2 +
src/settings.rs | 25 ++++++-
src/utils.rs | 119 +++++++++++++++++++++++++++++++--
static/copy.js | 9 +++
static/style.css | 8 ++-
templates/settings.html | 143 +++++++++++++++++++++++++---------------
8 files changed, 285 insertions(+), 109 deletions(-)
create mode 100644 static/copy.js
diff --git a/Cargo.lock b/Cargo.lock
index 6578d17..a29b750 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -71,12 +71,6 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
-[[package]]
-name = "anyhow"
-version = "1.0.93"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
-
[[package]]
name = "arc-swap"
version = "1.7.1"
@@ -139,6 +133,12 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "base2048"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71f4fe417e8cc3bb9b437dfa9290ce92bd2730ba5374719bdfd9147fbc8f17cd"
+
[[package]]
name = "base64"
version = "0.21.7"
@@ -151,6 +151,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "2.6.0"
@@ -319,18 +328,6 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
-[[package]]
-name = "common-words-all"
-version = "0.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84a6ff47eb813c9e315610ceca0ddd247827e22f2cdadc4189e4676a81470c77"
-dependencies = [
- "anyhow",
- "csv",
- "glob",
- "serde",
-]
-
[[package]]
name = "cookie"
version = "0.18.1"
@@ -394,27 +391,6 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "csv"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
-dependencies = [
- "csv-core",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "csv-core"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "darling"
version = "0.20.10"
@@ -698,12 +674,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-
[[package]]
name = "globset"
version = "0.4.15"
@@ -1381,13 +1351,14 @@ version = "0.35.1"
dependencies = [
"arc-swap",
"async-recursion",
+ "base2048",
"base64 0.22.1",
+ "bincode",
"brotli",
"build_html",
"cached",
"chrono",
"clap",
- "common-words-all",
"cookie",
"dotenvy",
"fastrand",
@@ -1403,6 +1374,7 @@ dependencies = [
"pretty_env_logger",
"pulldown-cmark",
"regex",
+ "revision",
"rinja",
"route-recognizer",
"rss",
@@ -1459,6 +1431,26 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+[[package]]
+name = "revision"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22f53179a035f881adad8c4d58a2c599c6b4a8325b989c68d178d7a34d1b1e4c"
+dependencies = [
+ "revision-derive",
+]
+
+[[package]]
+name = "revision-derive"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0ec466e5d8dca9965eb6871879677bef5590cf7525ad96cae14376efb75073"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "ring"
version = "0.17.8"
diff --git a/Cargo.toml b/Cargo.toml
index 759b148..c7b6d4a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,12 +48,14 @@ arc-swap = "1.7.1"
serde_json_path = "0.7.1"
async-recursion = "1.1.1"
pulldown-cmark = { version = "0.12.0", features = ["simd", "html"], default-features = false }
-common-words-all = { version = "0.0.2", default-features = false, features = ["english", "one"] }
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
tegen = "0.1.4"
serde_urlencoded = "0.7.1"
chrono = { version = "0.4.39", default-features = false, features = [ "std" ] }
htmlescape = "0.3.1"
+bincode = "1.3.3"
+base2048 = "2.0.2"
+revision = "0.10.0"
[dev-dependencies]
diff --git a/src/main.rs b/src/main.rs
index 165f0cb..bcfcd51 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -246,6 +246,7 @@ async fn main() {
app
.at("/check_update.js")
.get(|_| resource(include_str!("../static/check_update.js"), "text/javascript", false).boxed());
+ app.at("/copy.js").get(|_| resource(include_str!("../static/copy.js"), "text/javascript", false).boxed());
app.at("/commits.atom").get(|_| async move { proxy_commit_info().await }.boxed());
app.at("/instances.json").get(|_| async move { proxy_instances().await }.boxed());
@@ -284,6 +285,7 @@ async fn main() {
// Configure settings
app.at("/settings").get(|r| settings::get(r).boxed()).post(|r| settings::set(r).boxed());
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
+ app.at("/settings/encoded-restore").post(|r| settings::encoded_restore(r).boxed());
app.at("/settings/update").get(|r| settings::update(r).boxed());
// RSS Subscriptions
diff --git a/src/settings.rs b/src/settings.rs
index 4b30da3..6649f69 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -5,12 +5,13 @@ use std::collections::HashMap;
// CRATES
use crate::server::ResponseExt;
use crate::subreddit::join_until_size_limit;
-use crate::utils::{redirect, template, Preferences};
+use crate::utils::{deflate_decompress, redirect, template, Preferences};
use cookie::Cookie;
use futures_lite::StreamExt;
use hyper::{Body, Request, Response};
use rinja::Template;
use time::{Duration, OffsetDateTime};
+use url::form_urlencoded;
// STRUCTS
#[derive(Template)]
@@ -262,3 +263,25 @@ pub async fn restore(req: Request) -> Result, String> {
pub async fn update(req: Request) -> Result, String> {
Ok(set_cookies_method(req, false))
}
+
+pub async fn encoded_restore(req: Request) -> Result, String> {
+ let body = hyper::body::to_bytes(req.into_body())
+ .await
+ .map_err(|e| format!("Failed to get bytes from request body: {}", e))?;
+
+ let encoded_prefs = form_urlencoded::parse(&body)
+ .find(|(key, _)| key == "encoded_prefs")
+ .map(|(_, value)| value)
+ .ok_or_else(|| "encoded_prefs parameter not found in request body".to_string())?;
+
+ let bytes = base2048::decode(&encoded_prefs).ok_or_else(|| "Failed to decode base2048 encoded preferences".to_string())?;
+
+ let out = deflate_decompress(bytes)?;
+
+ let mut prefs: Preferences = bincode::deserialize(&out).map_err(|e| format!("Failed to deserialize bytes into Preferences struct: {}", e))?;
+ prefs.available_themes = vec![];
+
+ let url = format!("/settings/restore/?{}", prefs.to_urlencoded()?);
+
+ Ok(redirect(&url))
+}
diff --git a/src/utils.rs b/src/utils.rs
index 2747449..c4ab679 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -9,16 +9,19 @@ use crate::{client::json, server::RequestExt};
use cookie::Cookie;
use htmlescape::decode_html;
use hyper::{Body, Request, Response};
+use libflate::deflate::{Decoder, Encoder};
use log::error;
use once_cell::sync::Lazy;
use regex::Regex;
+use revision::revisioned;
use rinja::Template;
use rust_embed::RustEmbed;
-use serde::{Serialize, Serializer};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use serde_json_path::{JsonPath, JsonPathExt};
use std::collections::{HashMap, HashSet};
use std::env;
+use std::io::{Read, Write};
use std::str::FromStr;
use std::string::ToString;
use time::{macros::format_description, Duration, OffsetDateTime};
@@ -618,32 +621,55 @@ pub struct Params {
pub before: Option,
}
-#[derive(Default, Serialize)]
+#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq)]
+#[revisioned(revision = 1)]
pub struct Preferences {
- #[serde(skip)]
+ #[revision(start = 1)]
+ #[serde(skip_serializing, skip_deserializing)]
pub available_themes: Vec,
+ #[revision(start = 1)]
pub theme: String,
+ #[revision(start = 1)]
pub front_page: String,
+ #[revision(start = 1)]
pub layout: String,
+ #[revision(start = 1)]
pub wide: String,
+ #[revision(start = 1)]
pub blur_spoiler: String,
+ #[revision(start = 1)]
pub show_nsfw: String,
+ #[revision(start = 1)]
pub blur_nsfw: String,
+ #[revision(start = 1)]
pub hide_hls_notification: String,
+ #[revision(start = 1)]
pub video_quality: String,
+ #[revision(start = 1)]
pub hide_sidebar_and_summary: String,
+ #[revision(start = 1)]
pub use_hls: String,
+ #[revision(start = 1)]
pub autoplay_videos: String,
+ #[revision(start = 1)]
pub fixed_navbar: String,
+ #[revision(start = 1)]
pub disable_visit_reddit_confirmation: String,
+ #[revision(start = 1)]
pub comment_sort: String,
+ #[revision(start = 1)]
pub post_sort: String,
- #[serde(serialize_with = "serialize_vec_with_plus")]
+ #[revision(start = 1)]
+ #[serde(serialize_with = "serialize_vec_with_plus", deserialize_with = "deserialize_vec_with_plus")]
pub subscriptions: Vec,
- #[serde(serialize_with = "serialize_vec_with_plus")]
+ #[revision(start = 1)]
+ #[serde(serialize_with = "serialize_vec_with_plus", deserialize_with = "deserialize_vec_with_plus")]
pub filters: Vec,
+ #[revision(start = 1)]
pub hide_awards: String,
+ #[revision(start = 1)]
pub hide_score: String,
+ #[revision(start = 1)]
pub remove_default_feeds: String,
}
@@ -654,6 +680,17 @@ where
serializer.serialize_str(&vec.join("+"))
}
+fn deserialize_vec_with_plus<'de, D>(deserializer: D) -> Result, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let string = String::deserialize(deserializer)?;
+ if string.is_empty() {
+ return Ok(Vec::new());
+ }
+ Ok(string.split('+').map(|s| s.to_string()).collect())
+}
+
#[derive(RustEmbed)]
#[folder = "static/themes/"]
#[include = "*.css"]
@@ -698,6 +735,29 @@ impl Preferences {
pub fn to_urlencoded(&self) -> Result {
serde_urlencoded::to_string(self).map_err(|e| e.to_string())
}
+
+ pub fn to_bincode(&self) -> Result, String> {
+ bincode::serialize(self).map_err(|e| e.to_string())
+ }
+ pub fn to_compressed_bincode(&self) -> Result, String> {
+ deflate_compress(self.to_bincode()?)
+ }
+ pub fn to_bincode_str(&self) -> Result {
+ Ok(base2048::encode(&self.to_compressed_bincode()?))
+ }
+}
+
+pub fn deflate_compress(i: Vec) -> Result, String> {
+ let mut e = Encoder::new(Vec::new());
+ e.write_all(&i).map_err(|e| e.to_string())?;
+ e.finish().into_result().map_err(|e| e.to_string())
+}
+
+pub fn deflate_decompress(i: Vec) -> Result, String> {
+ let mut decoder = Decoder::new(&i[..]);
+ let mut out = Vec::new();
+ decoder.read_to_end(&mut out).map_err(|e| format!("Failed to read from gzip decoder: {}", e))?;
+ Ok(out)
}
/// Gets a `HashSet` of filters from the cookie in the given `Request`.
@@ -1584,3 +1644,52 @@ How`s your monitor by the way? Any IPS bleed whatsoever? I either got lucky or t
assert_eq!(render_bullet_lists(input), output);
}
+
+#[test]
+fn test_default_prefs_serialization_loop_json() {
+ let prefs = Preferences::default();
+ let serialized = serde_json::to_string(&prefs).unwrap();
+ let deserialized: Preferences = serde_json::from_str(&serialized).unwrap();
+ assert_eq!(prefs, deserialized);
+}
+
+#[test]
+fn test_default_prefs_serialization_loop_bincode() {
+ let prefs = Preferences::default();
+ test_round_trip(&prefs, false);
+ test_round_trip(&prefs, true);
+}
+
+static KNOWN_GOOD_CONFIGS: &[&str] = &[
+ "ఴӅβØØҞÉဏႢձĬ༧ȒʯऌԔӵ୮༏",
+ "ਧՊΥÀÃǎƱГ۸ඣമĖฤ႙ʟาúໜϾௐɥঀĜໃહཞઠѫҲɂఙ࿔DzઉƲӟӻĻฅΜδ໖ԜǗဖငƦơ৶Ą௩ԹʛใЛʃශаΏ",
+ "ਧԩΥÀÃΊ౭൩ඔႠϼҭöҪƸռઇԾॐნɔາǒՍҰच௨ಖມŃЉŐདƦ๙ϩএఠȝഽйʮჯඒϰळՋ௮ສ৵ऎΦѧਹಧଟƙŃ३î༦ŌပղयƟแҜ།",
+];
+
+#[test]
+fn test_known_good_configs_deserialization() {
+ for config in KNOWN_GOOD_CONFIGS {
+ let bytes = base2048::decode(config).unwrap();
+ let decompressed = deflate_decompress(bytes).unwrap();
+ assert!(bincode::deserialize::(&decompressed).is_ok());
+ }
+}
+
+#[test]
+fn test_known_good_configs_full_round_trip() {
+ for config in KNOWN_GOOD_CONFIGS {
+ let bytes = base2048::decode(config).unwrap();
+ let decompressed = deflate_decompress(bytes).unwrap();
+ let prefs: Preferences = bincode::deserialize(&decompressed).unwrap();
+ test_round_trip(&prefs, false);
+ test_round_trip(&prefs, true);
+ }
+}
+
+fn test_round_trip(input: &Preferences, compression: bool) {
+ let serialized = bincode::serialize(input).unwrap();
+ let compressed = if compression { deflate_compress(serialized).unwrap() } else { serialized };
+ let decompressed = if compression { deflate_decompress(compressed).unwrap() } else { compressed };
+ let deserialized: Preferences = bincode::deserialize(&decompressed).unwrap();
+ assert_eq!(*input, deserialized);
+}
diff --git a/static/copy.js b/static/copy.js
new file mode 100644
index 0000000..d7c7dd4
--- /dev/null
+++ b/static/copy.js
@@ -0,0 +1,9 @@
+async function copy() {
+ await navigator.clipboard.writeText(document.getElementById('bincode_str').value);
+}
+
+async function set_listener() {
+ document.getElementById('copy').addEventListener('click', copy);
+}
+
+window.addEventListener('load', set_listener);
\ No newline at end of file
diff --git a/static/style.css b/static/style.css
index 76b55dd..545567e 100644
--- a/static/style.css
+++ b/static/style.css
@@ -553,14 +553,18 @@ aside {
.subscribe,
.unsubscribe,
.filter,
-.unfilter {
+.unfilter,
+.copy,
+.import {
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
.subscribe,
-.filter {
+.filter,
+.copy,
+.import {
color: var(--foreground);
background-color: var(--accent);
}
diff --git a/templates/settings.html b/templates/settings.html
index 7995312..c3d8086 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -4,22 +4,22 @@
{% block title %}Redlib Settings{% endblock %}
{% block subscriptions %}
- {% call utils::sub_list("") %}
+{% call utils::sub_list("") %}
{% endblock %}
{% block search %}
- {% call utils::search("".to_owned(), "") %}
+{% call utils::search("".to_owned(), "") %}
{% endblock %}
{% block content %}
-
+
{% if prefs.subscriptions.len() > 0 %}
-
-
Subscribed Feeds
- {% for sub in prefs.subscriptions %}
-
- {% let feed -%}
- {% if sub.starts_with("u_") -%}{% let feed = format!("u/{}", &sub[2..]) -%}{% else -%}{% let feed = format!("r/{}", sub) -%}{% endif -%}
-
{{ feed }}
-
-
- {% endfor %}
+
+
Subscribed Feeds
+ {% for sub in prefs.subscriptions %}
+
+ {% let feed -%}
+ {% if sub.starts_with("u_") -%}{% let feed = format!("u/{}", &sub[2..]) -%}{% else -%}{% let feed =
+ format!("r/{}", sub) -%}{% endif -%}
+
{{ feed }}
+
+ {% endfor %}
+
{% endif %}
{% if !prefs.filters.is_empty() %}
-
-
Filtered Feeds
- {% for sub in prefs.filters %}
-
- {% let feed -%}
- {% if sub.starts_with("u_") -%}{% let feed = format!("u/{}", &sub[2..]) -%}{% else -%}{% let feed = format!("r/{}", sub) -%}{% endif -%}
-
{{ feed }}
-
-
- {% endfor %}
+
+
Filtered Feeds
+ {% for sub in prefs.filters %}
+
+ {% let feed -%}
+ {% if sub.starts_with("u_") -%}{% let feed = format!("u/{}", &sub[2..]) -%}{% else -%}{% let feed =
+ format!("r/{}", sub) -%}{% endif -%}
+
{{ feed }}
+
+ {% endfor %}
+
{% endif %}
-
Note: settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.
+
Note: settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.
+
{% match prefs.to_urlencoded() %}
{% when Ok with (encoded_prefs) %}
@@ -176,7 +194,24 @@
There was an error creating your restore link: {{ err }}
Please report this issue
{% endmatch %}
+
+
+
+ Or, export/import here (be sure to save first):
+
+
+ Copy
+
+
+
+
-{% endblock %}
+{% endblock %}
\ No newline at end of file
From ebc682da2d01f3ddf412040eb4a2614b286be730 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sat, 8 Feb 2025 16:59:32 -0500
Subject: [PATCH 32/49] chore: update error page
---
static/check_update.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/static/check_update.js b/static/check_update.js
index b747203..4cd745b 100644
--- a/static/check_update.js
+++ b/static/check_update.js
@@ -33,7 +33,7 @@ async function checkInstanceUpdateStatus() {
document.getElementById('update-status').innerText = statusMessage;
} catch (error) {
console.error('Error fetching commits:', error);
- document.getElementById('update-status').innerText = '⚠️ Error checking update status.';
+ document.getElementById('update-status').innerText = '⚠️ Error checking update status: ' + error;
}
}
@@ -48,7 +48,7 @@ async function checkOtherInstances() {
document.getElementById('random-instance').innerText = "Visit Random Instance";
} catch (error) {
console.error('Error fetching instances:', error);
- document.getElementById('update-status').innerText = '⚠️ Error checking update status.';
+ document.getElementById('update-status').innerText = '⚠️ Error checking other instances: ' + error;
}
}
From bb20190555d42df97695451ead8aff2b0be994bb Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sun, 9 Feb 2025 17:10:12 -0500
Subject: [PATCH 33/49] fix: control rendering behavior based on routing
---
src/subreddit.rs | 3 ++-
src/user.rs | 3 ++-
src/utils.rs | 3 +--
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/subreddit.rs b/src/subreddit.rs
index d03a9dd..e6d1cca 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -8,6 +8,7 @@ use crate::utils::{
};
use crate::{client::json, server::RequestExt, server::ResponseExt};
use cookie::Cookie;
+use htmlescape::decode_html;
use hyper::{Body, Request, Response};
use log::debug;
use rinja::Template;
@@ -623,7 +624,7 @@ pub async fn rss(req: Request) -> Result, String> {
title: Some(post.title.to_string()),
link: Some(format_url(&utils::get_post_url(&post))),
author: Some(post.author.name),
- content: Some(rewrite_urls(&post.body)),
+ content: Some(rewrite_urls(&decode_html(&post.body).unwrap())),
pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
description: Some(format!(
"Comments ",
diff --git a/src/user.rs b/src/user.rs
index 818f368..592389d 100644
--- a/src/user.rs
+++ b/src/user.rs
@@ -6,6 +6,7 @@ use crate::server::RequestExt;
use crate::utils::{error, filter_posts, format_url, get_filters, nsfw_landing, param, setting, template, Post, Preferences, User};
use crate::{config, utils};
use chrono::DateTime;
+use htmlescape::decode_html;
use hyper::{Body, Request, Response};
use rinja::Template;
use time::{macros::format_description, OffsetDateTime};
@@ -167,7 +168,7 @@ pub async fn rss(req: Request) -> Result, String> {
link: Some(format_url(&utils::get_post_url(&post))),
author: Some(post.author.name),
pub_date: Some(DateTime::from_timestamp(post.created_ts as i64, 0).unwrap_or_default().to_rfc2822()),
- content: Some(rewrite_urls(&post.body)),
+ content: Some(rewrite_urls(&decode_html(&post.body).unwrap())),
..Default::default()
})
.collect::>(),
diff --git a/src/utils.rs b/src/utils.rs
index c4ab679..f5046cb 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -7,7 +7,6 @@ use crate::config::{self, get_setting};
//
use crate::{client::json, server::RequestExt};
use cookie::Cookie;
-use htmlescape::decode_html;
use hyper::{Body, Request, Response};
use libflate::deflate::{Decoder, Encoder};
use log::error;
@@ -388,7 +387,7 @@ impl Post {
let awards = Awards::parse(&data["all_awardings"]);
// selftext_html is set for text posts when browsing.
- let mut body = rewrite_urls(&decode_html(&val(post, "selftext_html")).unwrap());
+ let mut body = rewrite_urls(&val(post, "selftext_html"));
if body.is_empty() {
body = rewrite_urls(&val(post, "body_html"));
}
From c9dbd7a3ccc9fb0bf8b29c7234b5ab344487026b Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 13 Feb 2025 21:42:09 -0500
Subject: [PATCH 34/49] fix: debug string
---
src/subreddit.rs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/subreddit.rs b/src/subreddit.rs
index e6d1cca..1609ead 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -143,7 +143,6 @@ pub async fn community(req: Request) -> Result, String> {
}
let path = format!("/r/{}/{sort}.json?{}{params}", sub_name.replace('+', "%2B"), req.uri().query().unwrap_or_default());
- debug!("Path: {}", path);
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26").replace('+', "%2B");
let filters = get_filters(&req);
From 9afe886c2c120bd5b9c24dd33341a3a70792b25b Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 13 Feb 2025 21:42:29 -0500
Subject: [PATCH 35/49] chore(clippy)
---
src/subreddit.rs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/subreddit.rs b/src/subreddit.rs
index 1609ead..f84cca3 100644
--- a/src/subreddit.rs
+++ b/src/subreddit.rs
@@ -10,7 +10,6 @@ use crate::{client::json, server::RequestExt, server::ResponseExt};
use cookie::Cookie;
use htmlescape::decode_html;
use hyper::{Body, Request, Response};
-use log::debug;
use rinja::Template;
use chrono::DateTime;
From efcf2fc24c455acdf2ddc4adc8d20606883aa118 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Mon, 17 Feb 2025 22:41:01 -0500
Subject: [PATCH 36/49] feat: display contexted title if link is single-thread
(#383)
---
templates/post.html | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/templates/post.html b/templates/post.html
index 17ad456..adbe6d6 100644
--- a/templates/post.html
+++ b/templates/post.html
@@ -1,7 +1,13 @@
{% extends "base.html" %}
{% import "utils.html" as utils %}
-{% block title %}{{ post.title }} - r/{{ post.community }}{% endblock %}
+{% block title %}
+ {% if single_thread %}
+ {{ comments[0].author.name }} comments on {{ post.title }} - r/{{ post.community }}
+ {% else %}
+ {{ post.title }} - r/{{ post.community }}
+ {% endif %}
+{% endblock %}
{% block search %}
{% call utils::search(["/r/", post.community.as_str()].concat(), "") %}
From 35688e4af7397086df3ada1721ceb9cd6c24ba7e Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Wed, 26 Feb 2025 13:17:08 -0500
Subject: [PATCH 37/49] fix: update default setting for removing default feeds
---
app.json | 3 +++
src/config.rs | 5 +++++
src/instance_info.rs | 3 +++
3 files changed, 11 insertions(+)
diff --git a/app.json b/app.json
index c05b26d..4af7cfe 100644
--- a/app.json
+++ b/app.json
@@ -76,6 +76,9 @@
},
"REDLIB_FULL_URL": {
"required": false
+ },
+ "REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS": {
+ "required": false
}
}
}
diff --git a/src/config.rs b/src/config.rs
index 034afc7..7b1c95c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -109,6 +109,9 @@ pub struct Config {
#[serde(rename = "REDLIB_FULL_URL")]
pub(crate) full_url: Option,
+
+ #[serde(rename = "REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS")]
+ pub(crate) default_remove_default_feeds: Option,
}
impl Config {
@@ -156,6 +159,7 @@ impl Config {
pushshift: parse("REDLIB_PUSHSHIFT_FRONTEND"),
enable_rss: parse("REDLIB_ENABLE_RSS"),
full_url: parse("REDLIB_FULL_URL"),
+ default_remove_default_feeds: parse("REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS"),
}
}
}
@@ -185,6 +189,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option {
"REDLIB_PUSHSHIFT_FRONTEND" => config.pushshift.clone(),
"REDLIB_ENABLE_RSS" => config.enable_rss.clone(),
"REDLIB_FULL_URL" => config.full_url.clone(),
+ "REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS" => config.default_remove_default_feeds.clone(),
_ => None,
}
}
diff --git a/src/instance_info.rs b/src/instance_info.rs
index 5f82ce6..a573953 100644
--- a/src/instance_info.rs
+++ b/src/instance_info.rs
@@ -128,6 +128,7 @@ impl InstanceInfo {
["Pushshift frontend", &convert(&self.config.pushshift)],
["RSS enabled", &convert(&self.config.enable_rss)],
["Full URL", &convert(&self.config.full_url)],
+ ["Remove default feeds", &convert(&self.config.default_remove_default_feeds)],
//TODO: fallback to crate::config::DEFAULT_PUSHSHIFT_FRONTEND
])
.with_header_row(["Settings"]),
@@ -169,6 +170,7 @@ impl InstanceInfo {
Pushshift frontend: {:?}\n
RSS enabled: {:?}\n
Full URL: {:?}\n
+ Remove default feeds: {:?}\n
Config:\n
Banner: {:?}\n
Hide awards: {:?}\n
@@ -195,6 +197,7 @@ impl InstanceInfo {
self.config.sfw_only,
self.config.enable_rss,
self.config.full_url,
+ self.config.default_remove_default_feeds,
self.config.pushshift,
self.config.banner,
self.config.default_hide_awards,
From f3ca7bb7d1f03d4eb1e19be5f1bb1b66b4c25f89 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 27 Feb 2025 14:35:09 -0500
Subject: [PATCH 38/49] feat: allow for case-insensitive search redirects (fix
#389)
---
src/search.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/search.rs b/src/search.rs
index 5962c2e..bf3e65b 100644
--- a/src/search.rs
+++ b/src/search.rs
@@ -71,11 +71,11 @@ pub async fn find(req: Request) -> Result, String> {
return Ok(redirect("/"));
}
- if query.starts_with("r/") || query.starts_with("user/") {
+ if query.starts_with("r/") || query.starts_with("R/") || query.starts_with("user/") {
return Ok(redirect(&format!("/{query}")));
}
- if query.starts_with("u/") {
+ if query.starts_with("u/") || query.starts_with("U/") {
return Ok(redirect(&format!("/user{}", &query[1..])));
}
From d097495a4182d0c9ba52ad9833171b2cab6d1a22 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Thu, 27 Feb 2025 20:10:01 -0500
Subject: [PATCH 39/49] fix: handle case insensitivity for subs
---
src/search.rs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/search.rs b/src/search.rs
index bf3e65b..1b5d1a9 100644
--- a/src/search.rs
+++ b/src/search.rs
@@ -71,9 +71,13 @@ pub async fn find(req: Request) -> Result, String> {
return Ok(redirect("/"));
}
- if query.starts_with("r/") || query.starts_with("R/") || query.starts_with("user/") {
+ if query.starts_with("r/") || query.starts_with("user/") {
return Ok(redirect(&format!("/{query}")));
}
+
+ if query.starts_with("R/") {
+ return Ok(redirect(&format!("/r{}", &query[1..])));
+ }
if query.starts_with("u/") || query.starts_with("U/") {
return Ok(redirect(&format!("/user{}", &query[1..])));
From 357e7c2e096c1aa3bb871e42860dfd3be62e0bfb Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Sat, 1 Mar 2025 12:35:43 -0500
Subject: [PATCH 40/49] chore: remove "official" instance
---
README.md | 15 ++-------------
1 file changed, 2 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index 5aac1e8..fcae126 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
---
-**10-second pitch:** Redlib is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://redlib.matthew.science/r/unpopularopinion) without being [tracked](#reddit).
+**10-second pitch:** Redlib is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://farside.link/redlib/r/unpopularopinion) without being [tracked](#reddit).
- 🚀 Fast: written in Rust for blazing-fast speeds and memory safety
- ☁️ Light: no JavaScript, no ads, no tracking, no bloat
@@ -30,7 +30,6 @@
- [Reddit](#reddit)
- [Redlib](#redlib-1)
- [Server](#server)
- - [Official instance (redlib.matthew.science)](#official-instance-redlibmatthewscience)
5. [Deployment](#deployment)
- [Docker](#docker)
- [Docker Compose](#docker-compose)
@@ -159,17 +158,7 @@ For transparency, I hope to describe all the ways Redlib handles user privacy.
- **Logging:** In production (when running the binary, hosting with docker, or using the official instances), Redlib logs nothing. When debugging (running from source without `--release`), Redlib logs post IDs fetched to aid with troubleshooting.
-- **Cookies:** Redlib uses optional cookies to store any configured settings in [the settings menu](https://redlib.matthew.science/settings). These are not cross-site cookies and the cookies hold no personal data.
-
-#### Official instance (redlib.matthew.science)
-
-The official instance is hosted at https://redlib.matthew.science.
-
-- **Server:** The official instance runs a production binary, and thus logs nothing.
-
-- **DNS:** The domain for the official instance uses Cloudflare as the DNS resolver. However, this site is not proxied through Cloudflare, and thus Cloudflare doesn't have access to user traffic.
-
-- **Hosting:** The official instance is hosted on [Replit](https://replit.com/), which monitors usage to prevent abuse. I can understand if this invalidates certain users' threat models, and therefore, self-hosting, using unofficial instances, and browsing through Tor are welcomed.
+- **Cookies:** Redlib uses optional cookies to store any configured settings in the settings menu. These are not cross-site cookies and the cookies hold no personal data.
---
From 526c0d0797859d8f238036010c75b9b8a835a489 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Fri, 7 Mar 2025 17:08:27 -0500
Subject: [PATCH 41/49] fix: opensearch.xml route
---
src/main.rs | 12 ++++++++++++
static/opensearch.xml | 6 +++---
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index bcfcd51..e1b010d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -64,6 +64,17 @@ async fn font() -> Result, String> {
)
}
+async fn opensearch() -> Result, String> {
+ Ok(
+ Response::builder()
+ .status(200)
+ .header("content-type", "application/opensearchdescription+xml")
+ .header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
+ .body(include_bytes!("../static/opensearch.xml").as_ref().into())
+ .unwrap_or_default(),
+ )
+}
+
async fn resource(body: &str, content_type: &str, cache: bool) -> Result, String> {
let mut res = Response::builder()
.status(200)
@@ -234,6 +245,7 @@ async fn main() {
app.at("/Inter.var.woff2").get(|_| font().boxed());
app.at("/touch-icon-iphone.png").get(|_| iphone_logo().boxed());
app.at("/apple-touch-icon.png").get(|_| iphone_logo().boxed());
+ app.at("/opensearch.xml").get(|_| opensearch().boxed());
app
.at("/playHLSVideo.js")
.get(|_| resource(include_str!("../static/playHLSVideo.js"), "text/javascript", false).boxed());
diff --git a/static/opensearch.xml b/static/opensearch.xml
index 4bbef00..5864ef7 100644
--- a/static/opensearch.xml
+++ b/static/opensearch.xml
@@ -3,9 +3,9 @@
Search Redlib
Search for whatever you want on Redlib, awesome Reddit frontend
UTF-8
- /favicon.ico
-
+ https://localhost:8080/favicon.ico
+
- /search
+ https://localhost:8080/search
From c9e6ffd33cec460795f8fef27e00a92b25794429 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Fri, 7 Mar 2025 17:08:58 -0500
Subject: [PATCH 42/49] clippy fix
---
src/search.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/search.rs b/src/search.rs
index 1b5d1a9..88dcfdd 100644
--- a/src/search.rs
+++ b/src/search.rs
@@ -74,7 +74,7 @@ pub async fn find(req: Request) -> Result, String> {
if query.starts_with("r/") || query.starts_with("user/") {
return Ok(redirect(&format!("/{query}")));
}
-
+
if query.starts_with("R/") {
return Ok(redirect(&format!("/r{}", &query[1..])));
}
From 3d85df5044d062d353d8d0bee4da39a733db61c9 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Fri, 7 Mar 2025 17:10:21 -0500
Subject: [PATCH 43/49] chore(deps): resolve dependabot, other cargo updates
---
Cargo.lock | 467 +++++++++++++++++++++++++++++------------------------
1 file changed, 258 insertions(+), 209 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index a29b750..d03edad 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -61,9 +61,9 @@ dependencies = [
[[package]]
name = "allocator-api2"
-version = "0.2.20"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "anstyle"
@@ -90,9 +90,9 @@ dependencies = [
[[package]]
name = "async-trait"
-version = "0.1.83"
+version = "0.1.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
dependencies = [
"proc-macro2",
"quote",
@@ -101,9 +101,9 @@ dependencies = [
[[package]]
name = "atom_syndication"
-version = "0.12.5"
+version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ee79fb83c725eae67b55218870813d2fc39fd85e4f1583848ef9f4f823cfe7c"
+checksum = "d2f68d23e2cb4fd958c705b91a6b4c80ceeaf27a9e11651272a8389d5ce1a4a3"
dependencies = [
"chrono",
"derive_builder",
@@ -162,9 +162,9 @@ dependencies = [
[[package]]
name = "bitflags"
-version = "2.6.0"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block-buffer"
@@ -188,9 +188,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "4.0.1"
+version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
+checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -198,9 +198,9 @@ dependencies = [
[[package]]
name = "bstr"
-version = "1.11.0"
+version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
+checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
dependencies = [
"memchr",
"serde",
@@ -208,15 +208,15 @@ dependencies = [
[[package]]
name = "build_html"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225eb82ce9e70dcc0cfa6e404d0f353326b6e163bf500ec4711cec317d11935c"
+checksum = "01b01f54cbdd56298a506b086691594ded3b68dcbc9437adc87c616a35e7fc89"
[[package]]
name = "bumpalo"
-version = "3.16.0"
+version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
@@ -226,9 +226,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.8.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cached"
@@ -243,7 +243,7 @@ dependencies = [
"futures",
"hashbrown 0.14.5",
"once_cell",
- "thiserror",
+ "thiserror 1.0.69",
"tokio",
"web-time",
]
@@ -268,9 +268,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "cc"
-version = "1.2.1"
+version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
+checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
@@ -283,18 +283,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.39"
+version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
+checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"num-traits",
]
[[package]]
name = "clap"
-version = "4.5.21"
+version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
+checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [
"clap_builder",
"clap_derive",
@@ -302,9 +302,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.21"
+version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
+checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [
"anstyle",
"clap_lex",
@@ -312,9 +312,9 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.18"
+version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [
"heck",
"proc-macro2",
@@ -324,9 +324,9 @@ dependencies = [
[[package]]
name = "clap_lex"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "cookie"
@@ -365,9 +365,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
-version = "0.2.15"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
@@ -532,25 +532,25 @@ dependencies = [
[[package]]
name = "equivalent"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
-version = "0.3.9"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "fastrand"
-version = "2.2.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
@@ -611,9 +611,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
+checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
dependencies = [
"fastrand",
"futures-core",
@@ -665,7 +665,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.13.3+wasi-0.2.2",
+ "windows-targets",
]
[[package]]
@@ -676,9 +688,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "globset"
-version = "0.4.15"
+version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
+checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
dependencies = [
"aho-corasick",
"bstr",
@@ -718,9 +730,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.15.1"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@@ -730,15 +742,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
-version = "0.3.9"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
-[[package]]
-name = "hermit-abi"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
[[package]]
name = "htmlescape"
@@ -770,9 +776,9 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.9.5"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
@@ -788,9 +794,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
-version = "0.14.31"
+version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
+checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes",
"futures-channel",
@@ -973,51 +979,55 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.6.0"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
- "hashbrown 0.15.1",
+ "hashbrown 0.15.2",
]
[[package]]
name = "inventory"
-version = "0.3.15"
+version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
+checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
+dependencies = [
+ "rustversion",
+]
[[package]]
name = "is-terminal"
-version = "0.4.13"
+version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
- "hermit-abi 0.4.0",
+ "hermit-abi",
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "itoa"
-version = "1.0.11"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
-version = "0.3.72"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
-version = "0.2.164"
+version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
+checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "libflate"
@@ -1045,9 +1055,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.4.14"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
[[package]]
name = "lipsum"
@@ -1061,9 +1071,9 @@ dependencies = [
[[package]]
name = "litemap"
-version = "0.7.3"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "lock_api"
@@ -1077,9 +1087,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.22"
+version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
@@ -1111,22 +1121,21 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.8.0"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
- "hermit-abi 0.3.9",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
@@ -1172,24 +1181,24 @@ dependencies = [
[[package]]
name = "object"
-version = "0.36.5"
+version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
-version = "1.20.2"
+version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "openssl-probe"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "parking"
@@ -1228,9 +1237,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@@ -1265,9 +1274,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.89"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@@ -1298,9 +1307,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
-version = "0.37.1"
+version = "0.37.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03"
+checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003"
dependencies = [
"encoding_rs",
"memchr",
@@ -1308,9 +1317,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.37"
+version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
dependencies = [
"proc-macro2",
]
@@ -1342,7 +1351,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom",
+ "getrandom 0.2.15",
]
[[package]]
@@ -1395,9 +1404,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.5.7"
+version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags",
]
@@ -1453,15 +1462,14 @@ dependencies = [
[[package]]
name = "ring"
-version = "0.17.8"
+version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
dependencies = [
"cc",
"cfg-if",
- "getrandom",
+ "getrandom 0.2.15",
"libc",
- "spin",
"untrusted",
"windows-sys 0.52.0",
]
@@ -1516,9 +1524,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
[[package]]
name = "rss"
-version = "2.0.10"
+version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "554a62b3dd5450fcbb0435b3db809f9dd3c6e9f5726172408f7ad3b57ed59057"
+checksum = "b2107738f003660f0a91f56fd3e3bd3ab5d918b2ddaf1e1ec2136fb1c46f71bf"
dependencies = [
"atom_syndication",
"derive_builder",
@@ -1528,9 +1536,9 @@ dependencies = [
[[package]]
name = "rust-embed"
-version = "8.5.0"
+version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
+checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -1539,9 +1547,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
-version = "8.5.0"
+version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
+checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
dependencies = [
"proc-macro2",
"quote",
@@ -1552,9 +1560,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
-version = "8.5.0"
+version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
+checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
dependencies = [
"globset",
"sha2",
@@ -1569,21 +1577,21 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
-version = "0.38.41"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
+checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1629,6 +1637,12 @@ dependencies = [
"untrusted",
]
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
[[package]]
name = "rusty-forkfork"
version = "0.4.0"
@@ -1643,9 +1657,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.18"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
@@ -1718,9 +1732,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.12.1"
+version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1728,18 +1742,18 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.215"
+version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
+checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.215"
+version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
+checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
@@ -1748,9 +1762,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.133"
+version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
@@ -1760,9 +1774,9 @@ dependencies = [
[[package]]
name = "serde_json_path"
-version = "0.7.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e176fbf9bd62f75c2d8be33207fa13af2f800a506635e89759e46f934c520f4d"
+checksum = "b992cea3194eea663ba99a042d61cea4bd1872da37021af56f6a37e0359b9d33"
dependencies = [
"inventory",
"nom",
@@ -1771,26 +1785,26 @@ dependencies = [
"serde_json",
"serde_json_path_core",
"serde_json_path_macros",
- "thiserror",
+ "thiserror 2.0.12",
]
[[package]]
name = "serde_json_path_core"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea3bfd54a421bec8328aefede43ac9f18c8c7ded3b2afc8addd44b4813d99fd0"
+checksum = "dde67d8dfe7d4967b5a95e247d4148368ddd1e753e500adb34b3ffe40c6bc1bc"
dependencies = [
"inventory",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 2.0.12",
]
[[package]]
name = "serde_json_path_macros"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee05bac728cc5232af5c23896b34fbdd17cf0bb0c113440588aeeb1b57c6ba1f"
+checksum = "517acfa7f77ddaf5c43d5f119c44a683774e130b4247b7d3210f8924506cfac8"
dependencies = [
"inventory",
"serde_json_path_core",
@@ -1879,26 +1893,20 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.13.2"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
-[[package]]
-name = "spin"
-version = "0.9.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
-
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -1913,9 +1921,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
-version = "2.0.87"
+version = "2.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
dependencies = [
"proc-macro2",
"quote",
@@ -1944,12 +1952,13 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.14.0"
+version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
+checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
dependencies = [
"cfg-if",
"fastrand",
+ "getrandom 0.3.1",
"once_cell",
"rustix",
"windows-sys 0.59.0",
@@ -1970,7 +1979,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
]
[[package]]
@@ -1985,10 +2003,21 @@ dependencies = [
]
[[package]]
-name = "time"
-version = "0.3.36"
+name = "thiserror-impl"
+version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
dependencies = [
"deranged",
"itoa",
@@ -2003,15 +2032,15 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.2"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
[[package]]
name = "time-macros"
-version = "0.2.18"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
dependencies = [
"num-conv",
"time-core",
@@ -2029,9 +2058,9 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.41.1"
+version = "1.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
+checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
dependencies = [
"backtrace",
"bytes",
@@ -2047,9 +2076,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
@@ -2068,9 +2097,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.12"
+version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
@@ -2081,9 +2110,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.8.19"
+version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
+checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
@@ -2102,9 +2131,9 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.22.22"
+version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
@@ -2121,9 +2150,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-core",
@@ -2131,9 +2160,9 @@ dependencies = [
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
@@ -2146,21 +2175,21 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
-version = "1.17.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicase"
-version = "2.8.0"
+version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
+checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unsafe-libyaml"
@@ -2176,9 +2205,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.5.3"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
@@ -2199,11 +2228,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.11.0"
+version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
+checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [
- "getrandom",
+ "getrandom 0.3.1",
]
[[package]]
@@ -2214,9 +2243,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wait-timeout"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
@@ -2247,10 +2276,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
-name = "wasm-bindgen"
-version = "0.2.95"
+name = "wasi"
+version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
@@ -2259,13 +2297,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
"syn",
@@ -2274,9 +2311,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2284,9 +2321,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
@@ -2297,9 +2334,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
name = "web-time"
@@ -2404,13 +2444,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
-version = "0.6.20"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
dependencies = [
"memchr",
]
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "write16"
version = "1.0.0"
@@ -2425,9 +2474,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yoke"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
@@ -2437,9 +2486,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
@@ -2470,18 +2519,18 @@ dependencies = [
[[package]]
name = "zerofrom"
-version = "0.1.4"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.4"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
From 15147cea8e42f6569a11603d661d71122f6a02dc Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Wed, 19 Mar 2025 22:58:51 -0400
Subject: [PATCH 44/49] fix: add resource limits on encoded prefs route
---
src/settings.rs | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/src/settings.rs b/src/settings.rs
index 6649f69..2efbbba 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -11,6 +11,7 @@ use futures_lite::StreamExt;
use hyper::{Body, Request, Response};
use rinja::Template;
use time::{Duration, OffsetDateTime};
+use tokio::time::timeout;
use url::form_urlencoded;
// STRUCTS
@@ -269,6 +270,10 @@ pub async fn encoded_restore(req: Request) -> Result, Strin
.await
.map_err(|e| format!("Failed to get bytes from request body: {}", e))?;
+ if body.len() > 1024 * 1024 {
+ return Err("Request body too large".to_string());
+ }
+
let encoded_prefs = form_urlencoded::parse(&body)
.find(|(key, _)| key == "encoded_prefs")
.map(|(_, value)| value)
@@ -276,9 +281,15 @@ pub async fn encoded_restore(req: Request) -> Result, Strin
let bytes = base2048::decode(&encoded_prefs).ok_or_else(|| "Failed to decode base2048 encoded preferences".to_string())?;
- let out = deflate_decompress(bytes)?;
+ let out = timeout(std::time::Duration::from_secs(1), async { deflate_decompress(bytes) })
+ .await
+ .map_err(|e| format!("Failed to decompress bytes: {}", e))??;
+
+ let mut prefs: Preferences = timeout(std::time::Duration::from_secs(1), async { bincode::deserialize(&out) })
+ .await
+ .map_err(|e| format!("Failed to deserialize preferences: {}", e))?
+ .map_err(|e| format!("Failed to deserialize bytes into Preferences struct: {}", e))?;
- let mut prefs: Preferences = bincode::deserialize(&out).map_err(|e| format!("Failed to deserialize bytes into Preferences struct: {}", e))?;
prefs.available_themes = vec![];
let url = format!("/settings/restore/?{}", prefs.to_urlencoded()?);
From cbc3e49923cbca61243bfc1face6f6a48bb62632 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Wed, 19 Mar 2025 23:05:28 -0400
Subject: [PATCH 45/49] v0.36.0
---
Cargo.lock | 2 +-
Cargo.toml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index d03edad..6722b4d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1356,7 +1356,7 @@ dependencies = [
[[package]]
name = "redlib"
-version = "0.35.1"
+version = "0.36.0"
dependencies = [
"arc-swap",
"async-recursion",
diff --git a/Cargo.toml b/Cargo.toml
index c7b6d4a..bb76d0a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ name = "redlib"
description = " Alternative private front-end to Reddit"
license = "AGPL-3.0-only"
repository = "https://github.com/redlib-org/redlib"
-version = "0.35.1"
+version = "0.36.0"
authors = [
"Matthew Esposito ",
"spikecodes <19519553+spikecodes@users.noreply.github.com>",
From dbdc4fc2a3c8e0d288ee5b16f21a457a5eaa54bd Mon Sep 17 00:00:00 2001
From: ryanshanz <92600751+ryanshanz@users.noreply.github.com>
Date: Mon, 31 Mar 2025 00:33:39 -0400
Subject: [PATCH 46/49] fix: Copy and Import button layouts (#402)
---
static/style.css | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/static/style.css b/static/style.css
index 545567e..608ae17 100644
--- a/static/style.css
+++ b/static/style.css
@@ -561,6 +561,10 @@ aside {
cursor: pointer;
}
+.copy,
+.import {
+ margin: 5px;
+}
.subscribe,
.filter,
.copy,
From 1fc36694932e90cef216700919c848fcea12792d Mon Sep 17 00:00:00 2001
From: Peter Lafreniere
Date: Tue, 8 Apr 2025 21:17:26 -0400
Subject: [PATCH 47/49] fix: correct typo bockquote -> blockquote in template
---
templates/comment.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/comment.html b/templates/comment.html
index 36c9b60..3c008c7 100644
--- a/templates/comment.html
+++ b/templates/comment.html
@@ -41,7 +41,7 @@
{{ body|safe }}
{% endif %}
{% for c in replies -%}{{ c.render().unwrap()|safe }}{%- endfor %}
-
+
{% endif %}
From e80ec5d16b856f19072e199beb7258414cce16a4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 9 Apr 2025 01:45:01 +0000
Subject: [PATCH 48/49] build(deps): bump tokio from 1.44.0 to 1.44.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.0 to 1.44.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.0...tokio-1.44.2)
---
updated-dependencies:
- dependency-name: tokio
dependency-version: 1.44.2
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
Cargo.lock | 4 ++--
Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 6722b4d..1d881f8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2058,9 +2058,9 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.44.0"
+version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
+checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",
diff --git a/Cargo.toml b/Cargo.toml
index bb76d0a..0596bc2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,7 +27,7 @@ hyper = { version = "0.14.31", features = ["full"] }
percent-encoding = "2.3.1"
route-recognizer = "0.3.1"
serde_json = "1.0.133"
-tokio = { version = "1.35.1", features = ["full"] }
+tokio = { version = "1.44.2", features = ["full"] }
time = { version = "0.3.31", features = ["local-offset"] }
url = "2.5.0"
rust-embed = { version = "8.1.0", features = ["include-exclude"] }
From dcb507d56710a047c743fe79a5326e4f1ca930a6 Mon Sep 17 00:00:00 2001
From: Matthew Esposito
Date: Mon, 21 Apr 2025 14:17:27 -0400
Subject: [PATCH 49/49] feat: Improve OAuth error handling with custom
AuthError type and better timeout management
---
src/oauth.rs | 70 +++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 53 insertions(+), 17 deletions(-)
diff --git a/src/oauth.rs b/src/oauth.rs
index 5627900..f5659da 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -15,6 +15,8 @@ const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg";
const AUTH_ENDPOINT: &str = "https://www.reddit.com";
+const OAUTH_TIMEOUT: Duration = Duration::from_secs(5);
+
// Spoofed client for Android devices
#[derive(Debug, Clone, Default)]
pub struct Oauth {
@@ -32,24 +34,30 @@ impl Oauth {
loop {
let attempt = Self::new_with_timeout().await;
match attempt {
- Ok(Some(oauth)) => {
+ Ok(Ok(oauth)) => {
info!("[✅] Successfully created OAuth client");
return oauth;
}
- Ok(None) => {
- error!("Failed to create OAuth client. Retrying in 5 seconds...");
+ Ok(Err(e)) => {
+ error!("Failed to create OAuth client: {}. Retrying in 5 seconds...", {
+ match e {
+ AuthError::Hyper(error) => error.to_string(),
+ AuthError::SerdeDeserialize(error) => error.to_string(),
+ AuthError::Field((value, error)) => format!("{error}\n{value}"),
+ }
+ });
}
- Err(duration) => {
- error!("Failed to create OAuth client in {duration:?}. Retrying in 5 seconds...");
+ Err(_) => {
+ error!("Failed to create OAuth client before timeout. Retrying in 5 seconds...");
}
}
- tokio::time::sleep(Duration::from_secs(5)).await;
+ tokio::time::sleep(OAUTH_TIMEOUT).await;
}
}
- async fn new_with_timeout() -> Result, Elapsed> {
+ async fn new_with_timeout() -> Result, Elapsed> {
let mut oauth = Self::default();
- timeout(Duration::from_secs(5), oauth.login()).await.map(|result| result.map(|_| oauth))
+ timeout(OAUTH_TIMEOUT, oauth.login()).await.map(|result: Result<(), AuthError>| result.map(|_| oauth))
}
pub(crate) fn default() -> Self {
@@ -66,7 +74,7 @@ impl Oauth {
device,
}
}
- async fn login(&mut self) -> Option<()> {
+ async fn login(&mut self) -> Result<(), AuthError> {
// Construct URL for OAuth token
let url = format!("{AUTH_ENDPOINT}/auth/v2/oauth/access-token/loid");
let mut builder = Request::builder().method(Method::POST).uri(&url);
@@ -95,7 +103,7 @@ impl Oauth {
// Send request
let client: &once_cell::sync::Lazy> = &CLIENT;
- let resp = client.request(request).await.ok()?;
+ let resp = client.request(request).await?;
trace!("Received response with status {} and length {:?}", resp.status(), resp.headers().get("content-length"));
trace!("OAuth headers: {:#?}", resp.headers());
@@ -106,30 +114,58 @@ impl Oauth {
// Not worried about the privacy implications, since this is randomly changed
// and really only as privacy-concerning as the OAuth token itself.
if let Some(header) = resp.headers().get("x-reddit-loid") {
- self.headers_map.insert("x-reddit-loid".to_owned(), header.to_str().ok()?.to_string());
+ self.headers_map.insert("x-reddit-loid".to_owned(), header.to_str().unwrap().to_string());
}
// Same with x-reddit-session
if let Some(header) = resp.headers().get("x-reddit-session") {
- self.headers_map.insert("x-reddit-session".to_owned(), header.to_str().ok()?.to_string());
+ self.headers_map.insert("x-reddit-session".to_owned(), header.to_str().unwrap().to_string());
}
trace!("Serializing response...");
// Serialize response
- let body_bytes = hyper::body::to_bytes(resp.into_body()).await.ok()?;
- let json: serde_json::Value = serde_json::from_slice(&body_bytes).ok()?;
+ let body_bytes = hyper::body::to_bytes(resp.into_body()).await?;
+ let json: serde_json::Value = serde_json::from_slice(&body_bytes)?;
trace!("Accessing relevant fields...");
// Save token and expiry
- self.token = json.get("access_token")?.as_str()?.to_string();
- self.expires_in = json.get("expires_in")?.as_u64()?;
+ self.token = json
+ .get("access_token")
+ .ok_or_else(|| AuthError::Field((json.clone(), "access_token")))?
+ .as_str()
+ .ok_or_else(|| AuthError::Field((json.clone(), "access_token: as_str")))?
+ .to_string();
+ self.expires_in = json
+ .get("expires_in")
+ .ok_or_else(|| AuthError::Field((json.clone(), "expires_in")))?
+ .as_u64()
+ .ok_or_else(|| AuthError::Field((json.clone(), "expires_in: as_u64")))?;
self.headers_map.insert("Authorization".to_owned(), format!("Bearer {}", self.token));
info!("[✅] Success - Retrieved token \"{}...\", expires in {}", &self.token[..32], self.expires_in);
- Some(())
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+enum AuthError {
+ Hyper(hyper::Error),
+ SerdeDeserialize(serde_json::Error),
+ Field((serde_json::Value, &'static str)),
+}
+
+impl From for AuthError {
+ fn from(err: hyper::Error) -> Self {
+ AuthError::Hyper(err)
+ }
+}
+
+impl From for AuthError {
+ fn from(err: serde_json::Error) -> Self {
+ AuthError::SerdeDeserialize(err)
}
}