From 0b8ea707fa2acb9e9b3bf1194446d79c37e7eeaf Mon Sep 17 00:00:00 2001 From: Diego Munhoz Date: Mon, 20 Apr 2026 13:00:47 -0600 Subject: [PATCH 01/19] fix: support rich:video (transcoded external media) and CMAF format --- src/main.rs | 2 +- src/utils.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 399c0c9c..7f98d19b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -262,7 +262,7 @@ async fn main() { app.at("/instances.json").get(|_| async move { proxy_instances().await }.boxed()); // Proxy media through Redlib - app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed()); + app.at("/vid/:id/:prefix/:size").get(|r| proxy(r, "https://v.redd.it/{id}/{prefix}_{size}").boxed()); app.at("/hls/:id/*path").get(|r| proxy(r, "https://v.redd.it/{id}/{path}").boxed()); app.at("/img/*path").get(|r| proxy(r, "https://i.redd.it/{path}").boxed()); app.at("/thumb/:point/:id").get(|r| proxy(r, "https://{point}.thumbs.redditmedia.com/{id}").boxed()); diff --git a/src/utils.rs b/src/utils.rs index 36b798f4..5213fd2a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -210,6 +210,12 @@ impl Media { &crosspost_parent_media["fallback_url"], Some(&crosspost_parent_media["hls_url"]), ) + } else if data["post_hint"].as_str().unwrap_or("") == "rich:video" && data_preview["fallback_url"].is_string() { + ( + if data_preview["is_gif"].as_bool().unwrap_or(false) { "gif" } else { "video" }, + &data_preview["fallback_url"], + Some(&data_preview["hls_url"]), + ) } else if data["post_hint"].as_str().unwrap_or("") == "image" { // Handle images, whether GIFs or pics let preview = &data["preview"]["images"][0]; @@ -1004,7 +1010,7 @@ static REGEX_URL_WWW: LazyLock = LazyLock::new(|| Regex::new(r"https?://w static REGEX_URL_OLD: LazyLock = LazyLock::new(|| Regex::new(r"https?://old\.reddit\.com/(.*)").unwrap()); static REGEX_URL_NP: LazyLock = LazyLock::new(|| Regex::new(r"https?://np\.reddit\.com/(.*)").unwrap()); static REGEX_URL_PLAIN: LazyLock = LazyLock::new(|| Regex::new(r"https?://reddit\.com/(.*)").unwrap()); -static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.redd\.it/(.*)/DASH_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); +static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.redd\.it/(.*)/(DASH|CMAF)_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); static REGEX_URL_VIDEOS_HLS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.redd\.it/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); static REGEX_URL_IMAGES: LazyLock = LazyLock::new(|| Regex::new(r"https?://i\.redd\.it/(.*)").unwrap()); static REGEX_URL_THUMBS_A: LazyLock = LazyLock::new(|| Regex::new(r"https?://a\.thumbs\.redditmedia\.com/(.*)").unwrap()); @@ -1027,6 +1033,7 @@ pub fn format_url(url: &str) -> String { regex.captures(url).map_or(String::new(), |caps| match segments { 1 => [format, &caps[1]].join(""), 2 => [format, &caps[1], "/", &caps[2]].join(""), + 3 => [format, &caps[1], "/", &caps[2], "/", &caps[3]].join(""), _ => String::new(), }) }; @@ -1057,7 +1064,7 @@ pub fn format_url(url: &str) -> String { "old.reddit.com" => capture(®EX_URL_OLD, "/", 1), "np.reddit.com" => capture(®EX_URL_NP, "/", 1), "reddit.com" => capture(®EX_URL_PLAIN, "/", 1), - "v.redd.it" => chain!(capture(®EX_URL_VIDEOS, "/vid/", 2), capture(®EX_URL_VIDEOS_HLS, "/hls/", 2)), + "v.redd.it" => chain!(capture(®EX_URL_VIDEOS, "/vid/", 3), capture(®EX_URL_VIDEOS_HLS, "/hls/", 2)), "i.redd.it" => capture(®EX_URL_IMAGES, "/img/", 1), "a.thumbs.redditmedia.com" => capture(®EX_URL_THUMBS_A, "/thumb/a/", 1), "b.thumbs.redditmedia.com" => capture(®EX_URL_THUMBS_B, "/thumb/b/", 1), @@ -1501,7 +1508,7 @@ mod tests { format_url("https://preview.redd.it/qwerty.jpg?auto=webp&s=asdf"), "/preview/pre/qwerty.jpg?auto=webp&s=asdf" ); - assert_eq!(format_url("https://v.redd.it/foo/DASH_360.mp4?source=fallback"), "/vid/foo/360.mp4"); + assert_eq!(format_url("https://v.redd.it/foo/DASH_360.mp4?source=fallback"), "/vid/foo/DASH/360.mp4"); assert_eq!( format_url("https://v.redd.it/foo/HLSPlaylist.m3u8?a=bar&v=1&f=sd"), "/hls/foo/HLSPlaylist.m3u8?a=bar&v=1&f=sd" From 62726549cf2f6a651989747f08433707db587889 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Wed, 9 Sep 2026 19:35:23 -0400 Subject: [PATCH 02/19] Merge remote-tracking branch 'cmaf_fix/fix-dash-cmaf-bugs-live' into catsarch-tor From 66d0235fa3d1326b919dc296550084765b02db7a Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Wed, 9 Sep 2026 20:55:38 -0400 Subject: [PATCH 03/19] feat: allow using Reddit over Tor to avoid blocks Thanks to baalajimaestro's work on their fork with Tor support as a reference https://git.ptr.moe/baalajimaestro/redlib --- src/client.rs | 16 ++++++---- src/config.rs | 5 ++++ src/utils.rs | 81 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5fafa7d3..3735f4b1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,7 @@ use crate::dbg_msg; use crate::oauth::{force_refresh_token, token_daemon, Oauth, OauthBackendImpl}; use crate::server::RequestExt; +use crate::utils; use crate::utils::{format_url, Post}; use arc_swap::ArcSwap; use cached::proc_macro::cached; @@ -18,8 +19,11 @@ use wreq::redirect::Policy; use wreq::{header as wreq_header, Client as WreqClient, EmulationFactory, Method, Response as WreqResponse}; use wreq_util::{Emulation, EmulationOS, EmulationOption}; -const REDDIT_URL_BASE: &str = "https://oauth.reddit.com"; -const REDDIT_URL_BASE_HOST: &str = "oauth.reddit.com"; +// const REDDIT_URL_BASE: &str = "https://oauth.reddit.com"; +//const REDDIT_URL_BASE_HOST: &str = "oauth.reddit.com"; +// +// const REDDIT_TOR_URL_BASE: &str = "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; +// const REDDIT_TOR_URL_BASE_HOST: &str = "oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; const REDDIT_SHORT_URL_BASE: &str = "https://redd.it"; const REDDIT_SHORT_URL_BASE_HOST: &str = "redd.it"; @@ -148,7 +152,7 @@ pub async fn canonical_path(path: String, tries: i8) -> Result, S res .headers() .get(wreq_header::LOCATION) - .map(|val| percent_encode(val.as_bytes(), CONTROLS).to_string().trim_start_matches(REDDIT_URL_BASE).to_string()), + .map(|val| percent_encode(val.as_bytes(), CONTROLS).to_string().trim_start_matches(utils::get_reddit_url_base()).to_string()), ), } } @@ -212,7 +216,7 @@ pub async fn proxy(req: HyperRequest, format: &str) -> Result Boxed> { - request(&Method::GET, path, true, quarantine, REDDIT_URL_BASE, REDDIT_URL_BASE_HOST) + request(&Method::GET, path, true, quarantine, utils::get_reddit_url_base(), utils::get_reddit_url_base_host()) } /// Makes a HEAD request to Reddit at `path, using the short URL base. This will not follow redirects. @@ -291,7 +295,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo // 2. Percent-encode the path. let new_path = percent_encode(val.as_bytes(), CONTROLS) .to_string() - .trim_start_matches(REDDIT_URL_BASE) + .trim_start_matches(utils::get_reddit_url_base()) .trim_start_matches(ALTERNATIVE_REDDIT_URL_BASE) .to_string(); format!("{new_path}{}raw_json=1", if new_path.contains('?') { "&" } else { "?" }) @@ -309,7 +313,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo Ok(response) } Err(e) => { - dbg_msg!("{method} {REDDIT_URL_BASE}{path}: {}", e); + dbg_msg!("{method} {}{path}: {}", utils::get_reddit_url_base(), e); Err(e.to_string()) } diff --git a/src/config.rs b/src/config.rs index 8f92c9b7..c3ea4cc9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -108,6 +108,9 @@ pub struct Config { #[serde(rename = "REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS")] pub(crate) default_remove_default_feeds: Option, + + #[serde(rename = "REDLIB_USE_TOR")] + pub(crate) use_tor: Option, } impl Config { @@ -156,6 +159,7 @@ impl Config { enable_rss: parse("REDLIB_ENABLE_RSS"), full_url: parse("REDLIB_FULL_URL"), default_remove_default_feeds: parse("REDLIB_DEFAULT_REMOVE_DEFAULT_FEEDS"), + use_tor: parse("REDLIB_USE_TOR"), } } } @@ -186,6 +190,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option { "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(), + "REDLIB_USE_TOR" => config.use_tor.clone(), _ => None, } } diff --git a/src/utils.rs b/src/utils.rs index 5213fd2a..b00509f3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -228,6 +228,8 @@ impl Media { // Return the picture if the media is an image if data["domain"] == "i.redd.it" { ("image", &data["url"], None) + } else if data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" { + ("image", &data["url"], None) } else { ("image", &preview["source"]["url"], None) } @@ -251,6 +253,9 @@ impl Media { } 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. ("image", &data["url"], None) + } else if data["is_reddit_media_domain"].as_bool().unwrap_or_default() && data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" { + // If this post contains a reddit media (image) URL. + ("image", &data["url"], None) } else { // If type can't be determined, return url ("link", &data["url"], None) @@ -1006,20 +1011,22 @@ pub async fn catch_random(sub: &str, additional: &str) -> Result, } } -static REGEX_URL_WWW: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.reddit\.com/(.*)").unwrap()); -static REGEX_URL_OLD: LazyLock = LazyLock::new(|| Regex::new(r"https?://old\.reddit\.com/(.*)").unwrap()); -static REGEX_URL_NP: LazyLock = LazyLock::new(|| Regex::new(r"https?://np\.reddit\.com/(.*)").unwrap()); -static REGEX_URL_PLAIN: LazyLock = LazyLock::new(|| Regex::new(r"https?://reddit\.com/(.*)").unwrap()); -static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.redd\.it/(.*)/(DASH|CMAF)_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); -static REGEX_URL_VIDEOS_HLS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.redd\.it/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); -static REGEX_URL_IMAGES: LazyLock = LazyLock::new(|| Regex::new(r"https?://i\.redd\.it/(.*)").unwrap()); -static REGEX_URL_THUMBS_A: LazyLock = LazyLock::new(|| Regex::new(r"https?://a\.thumbs\.redditmedia\.com/(.*)").unwrap()); -static REGEX_URL_THUMBS_B: LazyLock = LazyLock::new(|| Regex::new(r"https?://b\.thumbs\.redditmedia\.com/(.*)").unwrap()); -static REGEX_URL_EMOJI: LazyLock = LazyLock::new(|| Regex::new(r"https?://emoji\.redditmedia\.com/(.*)/(.*)").unwrap()); -static REGEX_URL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://preview\.redd\.it/(.*)").unwrap()); -static REGEX_URL_EXTERNAL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://external\-preview\.redd\.it/(.*)").unwrap()); -static REGEX_URL_STYLES: LazyLock = LazyLock::new(|| Regex::new(r"https?://styles\.redditmedia\.com/(.*)").unwrap()); -static REGEX_URL_STATIC_MEDIA: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.redditstatic\.com/(.*)").unwrap()); +/* Thank you to baalajimaestro for mapping out the Tor domains that are set up here and in format_url() +https://git.ptr.moe/baalajimaestro/redlib/src/branch/main/src/reddit_urls.rs */ +static REGEX_URL_WWW: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_OLD: LazyLock = LazyLock::new(|| Regex::new(r"https?://old\.(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_NP: LazyLock = LazyLock::new(|| Regex::new(r"https?://np\.(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_PLAIN: LazyLock = LazyLock::new(|| Regex::new(r"https?://(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)/(DASH|CMAF)_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); +static REGEX_URL_VIDEOS_HLS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); +static REGEX_URL_IMAGES: LazyLock = LazyLock::new(|| Regex::new(r"https?://i\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); +static REGEX_URL_THUMBS_A: LazyLock = LazyLock::new(|| Regex::new(r"https?://a\.thumbs\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); +static REGEX_URL_THUMBS_B: LazyLock = LazyLock::new(|| Regex::new(r"https?://b\.thumbs\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); +static REGEX_URL_EMOJI: LazyLock = LazyLock::new(|| Regex::new(r"https?://emoji\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)/(.*)").unwrap()); +static REGEX_URL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); +static REGEX_URL_EXTERNAL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://external\-preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); +static REGEX_URL_STYLES: LazyLock = LazyLock::new(|| Regex::new(r"https?://styles\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); +static REGEX_URL_STATIC_MEDIA: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(redditstatic\.com|reddittic34i5gtjcnm2fb7fv2eyop4vbxquuc36prnbs7d2kp3saoqd\.onion)/(.*)").unwrap()); /// Direct urls to proxy if proxy is enabled pub fn format_url(url: &str) -> String { @@ -1061,18 +1068,31 @@ pub fn format_url(url: &str) -> String { match domain { "www.reddit.com" => capture(®EX_URL_WWW, "/", 1), + "www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" => capture(®EX_URL_WWW, "/", 1), "old.reddit.com" => capture(®EX_URL_OLD, "/", 1), + "old.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" => capture(®EX_URL_OLD, "/", 1), "np.reddit.com" => capture(®EX_URL_NP, "/", 1), + "np.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" => capture(®EX_URL_NP, "/", 1), "reddit.com" => capture(®EX_URL_PLAIN, "/", 1), + "reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" => capture(®EX_URL_PLAIN, "/", 1), "v.redd.it" => chain!(capture(®EX_URL_VIDEOS, "/vid/", 3), capture(®EX_URL_VIDEOS_HLS, "/hls/", 2)), + "v.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" => chain!(capture(®EX_URL_VIDEOS, "/vid/", 3), capture(®EX_URL_VIDEOS_HLS, "/hls/", 2)), "i.redd.it" => capture(®EX_URL_IMAGES, "/img/", 1), + "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" => capture(®EX_URL_IMAGES, "/img/", 1), "a.thumbs.redditmedia.com" => capture(®EX_URL_THUMBS_A, "/thumb/a/", 1), + "a.thumbs.reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd.onion" => capture(®EX_URL_THUMBS_A, "/thumb/a/", 1), "b.thumbs.redditmedia.com" => capture(®EX_URL_THUMBS_B, "/thumb/b/", 1), + "b.thumbs.reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd.onion" => capture(®EX_URL_THUMBS_B, "/thumb/b/", 1), "emoji.redditmedia.com" => capture(®EX_URL_EMOJI, "/emoji/", 2), + "emoji.reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd.onion" => capture(®EX_URL_EMOJI, "/emoji/", 2), "preview.redd.it" => capture(®EX_URL_PREVIEW, "/preview/pre/", 1), + "preview.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" => capture(®EX_URL_PREVIEW, "/preview/pre/", 1), "external-preview.redd.it" => capture(®EX_URL_EXTERNAL_PREVIEW, "/preview/external-pre/", 1), + "external-preview.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" => capture(®EX_URL_EXTERNAL_PREVIEW, "/preview/external-pre/", 1), "styles.redditmedia.com" => capture(®EX_URL_STYLES, "/style/", 1), + "styles.reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd.onion" => capture(®EX_URL_STYLES, "/style/", 1), "www.redditstatic.com" => capture(®EX_URL_STATIC_MEDIA, "/static/", 1), + "www.reddittic34i5gtjcnm2fb7fv2eyop4vbxquuc36prnbs7d2kp3saoqd.onion" => capture(®EX_URL_STATIC_MEDIA, "/static/", 1), _ => url.to_string(), } }) @@ -1091,8 +1111,8 @@ pub fn render_bullet_lists(input_text: &str) -> String { } // These are links we want to replace in-body -static REDDIT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it)/"#).unwrap()); -static REDDIT_PREVIEW_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"https?://(external-preview|preview|i)\.redd\.it(.*)").unwrap()); +static REDDIT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/"#).unwrap()); +static REDDIT_PREVIEW_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"https?://(external-preview|preview|i)\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)(.*)").unwrap()); static REDDIT_EMOJI_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"https?://(www|).redditstatic\.com/(.*)").unwrap()); static REDLIB_PREVIEW_LINK_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"/(img|preview/)(pre|external-pre)?/(.*?)>"#).unwrap()); static REDLIB_PREVIEW_TEXT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r">(.*?)").unwrap()); @@ -1388,6 +1408,35 @@ pub fn disable_indexing() -> bool { } } +/// Returns true if the config/env variable `REDLIB_USE_TOR` carries the value `on`. +/// +/// If this variable is set as such, the instance will use Tor for oauth. +pub fn using_tor() -> bool { + match get_setting("REDLIB_USE_TOR") { + Some(val) => val == "on", + None => false, + } +} + +// Returns the correct oauth url for either clearnet or Tor usage +pub fn get_reddit_url_base() -> &'static str { + if using_tor() { + println!("Tor"); + return "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + } else { + return "https://oauth.reddit.com"; + }; +} + +// Returns the correct oauth domain for either clearnet or Tor usage +pub fn get_reddit_url_base_host() -> &'static str { + if using_tor() { + return "oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + } else { + return "oauth.reddit.com"; + }; +} + /// Determines if a request should redirect to a NSFW landing gate. pub fn should_be_nsfw_gated(req: &Request, _req_url: &str) -> bool { (setting(req, "show_nsfw") != "on") || sfw_only() From 87994c491a467e5eaa7ee3ab810459b3c46e6e92 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Wed, 9 Sep 2026 21:00:20 -0400 Subject: [PATCH 04/19] fix: update .env.example to show new variable and ALL_PROXY --- .env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.example b/.env.example index 5e60b082..83532087 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,10 @@ REDLIB_BANNER= REDLIB_ROBOTS_DISABLE_INDEXING=off # Set the Pushshift frontend for "removed" links REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io +# Use Reddit over Tor to avoid blocks (requires ALL_PROXY below) +REDLIB_USE_TOR=off +# Set proxy for Redlib to use +#ALL_PROXY=socks5h://127.0.0.1:9050 # Default user settings # Set the default theme (options: system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight) From 97e8e3e38527a884a0c01e99849386403074fd56 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Wed, 9 Sep 2026 21:01:12 -0400 Subject: [PATCH 05/19] fix: remove leftover println!() from testing --- src/utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index b00509f3..a4939f14 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1421,7 +1421,6 @@ pub fn using_tor() -> bool { // Returns the correct oauth url for either clearnet or Tor usage pub fn get_reddit_url_base() -> &'static str { if using_tor() { - println!("Tor"); return "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; } else { return "https://oauth.reddit.com"; From c4bc6ed84cb6289566e6bd3989e0b02c65fda009 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Wed, 9 Sep 2026 21:28:01 -0400 Subject: [PATCH 06/19] fix: remove leftover comments from testing --- src/client.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index 3735f4b1..5415ca94 100644 --- a/src/client.rs +++ b/src/client.rs @@ -19,12 +19,6 @@ use wreq::redirect::Policy; use wreq::{header as wreq_header, Client as WreqClient, EmulationFactory, Method, Response as WreqResponse}; use wreq_util::{Emulation, EmulationOS, EmulationOption}; -// const REDDIT_URL_BASE: &str = "https://oauth.reddit.com"; -//const REDDIT_URL_BASE_HOST: &str = "oauth.reddit.com"; -// -// const REDDIT_TOR_URL_BASE: &str = "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; -// const REDDIT_TOR_URL_BASE_HOST: &str = "oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; - const REDDIT_SHORT_URL_BASE: &str = "https://redd.it"; const REDDIT_SHORT_URL_BASE_HOST: &str = "redd.it"; From 9cd255481aa94bb747d15164d605697c00b7341d Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Wed, 9 Sep 2026 22:10:29 -0400 Subject: [PATCH 07/19] fix: make alternative and short Reddit URLs change when using Tor --- src/client.rs | 20 +++++++------------- src/utils.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5415ca94..1efa4585 100644 --- a/src/client.rs +++ b/src/client.rs @@ -19,12 +19,6 @@ use wreq::redirect::Policy; use wreq::{header as wreq_header, Client as WreqClient, EmulationFactory, Method, Response as WreqResponse}; use wreq_util::{Emulation, EmulationOS, EmulationOption}; -const REDDIT_SHORT_URL_BASE: &str = "https://redd.it"; -const REDDIT_SHORT_URL_BASE_HOST: &str = "redd.it"; - -const ALTERNATIVE_REDDIT_URL_BASE: &str = "https://www.reddit.com"; -const ALTERNATIVE_REDDIT_URL_BASE_HOST: &str = "www.reddit.com"; - pub static CLIENT: LazyLock = LazyLock::new(build_client); pub static OAUTH_CLIENT: LazyLock> = LazyLock::new(|| { @@ -37,10 +31,10 @@ pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99); pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false); -const URL_PAIRS: [(&str, &str); 2] = [ - (ALTERNATIVE_REDDIT_URL_BASE, ALTERNATIVE_REDDIT_URL_BASE_HOST), - (REDDIT_SHORT_URL_BASE, REDDIT_SHORT_URL_BASE_HOST), -]; +pub fn url_pairs() -> [(&'static str, &'static str); 2] { + return [(utils::get_alternative_reddit_url_base(), utils::get_alternative_reddit_url_base_host()), + (utils::get_short_reddit_url_base(), utils::get_short_reddit_url_base_host())] +} pub fn build_client() -> WreqClient { // Keeping this list short to aid in privacy. @@ -87,7 +81,7 @@ pub async fn canonical_path(path: String, tries: i8) -> Result, S let res = { // for url base and host in URL_PAIRS, try reddit_short_head(path.clone(), true, url_base, url_base_host) and if it succeeds, set res. else, res = None let mut res = None; - for (url_base, url_base_host) in URL_PAIRS { + for (url_base, url_base_host) in url_pairs() { res = reddit_short_head(path.clone(), true, url_base, url_base_host).await.ok(); if let Some(res) = &res { if !res.status().is_client_error() { @@ -269,7 +263,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo return Ok(response); }; let location_header = response.headers().get(wreq::header::LOCATION); - if location_header.and_then(|h| h.to_str().ok()) == Some(ALTERNATIVE_REDDIT_URL_BASE) { + if location_header.and_then(|h| h.to_str().ok()) == Some(utils::get_alternative_reddit_url_base()) { return Err("Reddit response was invalid".to_string()); } return request( @@ -290,7 +284,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo let new_path = percent_encode(val.as_bytes(), CONTROLS) .to_string() .trim_start_matches(utils::get_reddit_url_base()) - .trim_start_matches(ALTERNATIVE_REDDIT_URL_BASE) + .trim_start_matches(utils::get_alternative_reddit_url_base()) .to_string(); format!("{new_path}{}raw_json=1", if new_path.contains('?') { "&" } else { "?" }) }) diff --git a/src/utils.rs b/src/utils.rs index a4939f14..7ff21e3e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1436,6 +1436,42 @@ pub fn get_reddit_url_base_host() -> &'static str { }; } +// Returns the correct short Reddit url for either clearnet or Tor usage +pub fn get_short_reddit_url_base() -> &'static str { + if using_tor() { + return "https://redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion"; + } else { + return "https://www.redd.it"; + }; +} + +// Returns the correct short Reddit domain for either clearnet or Tor usage +pub fn get_short_reddit_url_base_host() -> &'static str { + if using_tor() { + return "redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion"; + } else { + return "redd.it"; + }; +} + +// Returns the correct alternative Reddit url for either clearnet or Tor usage +pub fn get_alternative_reddit_url_base() -> &'static str { + if using_tor() { + return "https://www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + } else { + return "https://www.reddit.com"; + }; +} + +// Returns the correct alternative domain for either clearnet or Tor usage +pub fn get_alternative_reddit_url_base_host() -> &'static str { + if using_tor() { + return "www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + } else { + return "www.reddit.com"; + }; +} + /// Determines if a request should redirect to a NSFW landing gate. pub fn should_be_nsfw_gated(req: &Request, _req_url: &str) -> bool { (setting(req, "show_nsfw") != "on") || sfw_only() From 5f737d76245c64ed0c91ee556c71160c5e9f4d2f Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Sat, 12 Sep 2026 16:11:56 -0400 Subject: [PATCH 08/19] fix: Correct clearnet short reddit url base --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 7ff21e3e..19851aaf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1441,7 +1441,7 @@ pub fn get_short_reddit_url_base() -> &'static str { if using_tor() { return "https://redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion"; } else { - return "https://www.redd.it"; + return "https://redd.it"; }; } From 87eec40f764119927f27e003963d06f84899ab4e Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Sat, 12 Sep 2026 16:21:24 -0400 Subject: [PATCH 09/19] fix: Use correct auth_endpoint based on if we're using Tor or not --- src/oauth.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/oauth.rs b/src/oauth.rs index 166e29f7..4aaed6b3 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -1,6 +1,6 @@ use crate::{ client::{CLIENT, OAUTH_CLIENT, OAUTH_IS_ROLLING_OVER, OAUTH_RATELIMIT_REMAINING}, - oauth_resources::ANDROID_APP_VERSION_LIST, + oauth_resources::ANDROID_APP_VERSION_LIST, utils, }; use base64::{engine::general_purpose, Engine as _}; use log::{error, info, trace, warn}; @@ -11,8 +11,6 @@ use tokio::time::{error::Elapsed, timeout}; const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg"; -const AUTH_ENDPOINT: &str = "https://www.reddit.com"; - const OAUTH_TIMEOUT: Duration = Duration::from_secs(5); // Response from OAuth backend authentication @@ -219,7 +217,8 @@ impl MobileSpoofAuth { impl OauthBackend for MobileSpoofAuth { async fn authenticate(&mut self) -> Result { // Construct URL for OAuth token - let url = format!("{AUTH_ENDPOINT}/auth/v2/oauth/access-token/loid"); + let auth_endpoint = utils::get_alternative_reddit_url_base(); + let url = format!("{auth_endpoint}/auth/v2/oauth/access-token/loid"); let mut builder = CLIENT.post(&url); // Add headers from spoofed client @@ -334,11 +333,12 @@ impl GenericWebAuth { impl OauthBackend for GenericWebAuth { async fn authenticate(&mut self) -> Result { // Construct URL for OAuth token - let url = "https://www.reddit.com/api/v1/access_token"; - let mut builder = CLIENT.post(url); + let auth_endpoint = utils::get_alternative_reddit_url_base(); + let url = format!("{auth_endpoint}/api/v1/access_token"); + let mut builder = CLIENT.post(&url); // Add minimal headers - builder = builder.header("Host", "www.reddit.com"); + builder = builder.header("Host", auth_endpoint); builder = builder.header("User-Agent", &self.user_agent); builder = builder.header("Accept", "*/*"); builder = builder.header("Accept-Language", "en-US,en;q=0.5"); @@ -402,7 +402,7 @@ impl OauthBackend for GenericWebAuth { ); // Insert a few necessary headers - self.additional_headers.insert("Origin".to_owned(), "https://www.reddit.com".to_owned()); + self.additional_headers.insert("Origin".to_owned(), auth_endpoint.to_owned()); self.additional_headers.insert("User-Agent".to_owned(), self.user_agent.to_owned()); Ok(OauthResponse { From c0def4e1473ca77c845791b2d6873d0a7db8c6c0 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Sat, 12 Sep 2026 16:29:19 -0400 Subject: [PATCH 10/19] fix: Up oauth timeout to prevent oauth issues when Tor is slow --- src/oauth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oauth.rs b/src/oauth.rs index 4aaed6b3..f0266a83 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -11,7 +11,7 @@ use tokio::time::{error::Elapsed, timeout}; const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg"; -const OAUTH_TIMEOUT: Duration = Duration::from_secs(5); +const OAUTH_TIMEOUT: Duration = Duration::from_secs(15); // Response from OAuth backend authentication #[derive(Debug, Clone)] From 2b006020d13e7cc9c3c7c8c417ee474bf360ee42 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Sat, 12 Sep 2026 16:41:24 -0400 Subject: [PATCH 11/19] fix: Make or between clearnet and Tor domain for REGEX_URL_WWW, OLD, NP, and PLAIN a non capturing group This fixes an issue with share urls not working --- src/utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 19851aaf..b8b192cf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1013,10 +1013,10 @@ pub async fn catch_random(sub: &str, additional: &str) -> Result, /* Thank you to baalajimaestro for mapping out the Tor domains that are set up here and in format_url() https://git.ptr.moe/baalajimaestro/redlib/src/branch/main/src/reddit_urls.rs */ -static REGEX_URL_WWW: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_OLD: LazyLock = LazyLock::new(|| Regex::new(r"https?://old\.(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_NP: LazyLock = LazyLock::new(|| Regex::new(r"https?://np\.(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_PLAIN: LazyLock = LazyLock::new(|| Regex::new(r"https?://(reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_WWW: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_OLD: LazyLock = LazyLock::new(|| Regex::new(r"https?://old\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_NP: LazyLock = LazyLock::new(|| Regex::new(r"https?://np\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_PLAIN: LazyLock = LazyLock::new(|| Regex::new(r"https?://(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)/(DASH|CMAF)_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); static REGEX_URL_VIDEOS_HLS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); static REGEX_URL_IMAGES: LazyLock = LazyLock::new(|| Regex::new(r"https?://i\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); From b9ab9d05de36e5ce09690a3c6069332e539ef3d1 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Sat, 12 Sep 2026 22:51:39 -0400 Subject: [PATCH 12/19] fix: significantly raise oauth timeout to prevent failure when Tor is being extremely slow --- src/oauth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oauth.rs b/src/oauth.rs index f0266a83..8ee24fc2 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -11,7 +11,7 @@ use tokio::time::{error::Elapsed, timeout}; const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg"; -const OAUTH_TIMEOUT: Duration = Duration::from_secs(15); +const OAUTH_TIMEOUT: Duration = Duration::from_secs(90); // Response from OAuth backend authentication #[derive(Debug, Clone)] From bf6d2e80021656f22f6a9725638eb64459c8dcbb Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 15:59:43 -0400 Subject: [PATCH 13/19] docs: Add USE_TOR to environment variable list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7ea145c5..1e6f784e 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,7 @@ Assign a default value for each instance-specific setting by passing environment | `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. | | `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. | | `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS) | +| `USE_TOR` | String | `off` | Does OAuth over Tor to avoid ratelimiting and other blocks. Requires a socks5h proxy. | ## Default user settings From ff7d97d8d9baedbc8ca089cb162d6d8fc6fce296 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 16:25:04 -0400 Subject: [PATCH 14/19] docs: Make description for USE_TOR the same in the example env and the README --- .env.example | 2 +- README.md | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 83532087..ac530dd4 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ REDLIB_BANNER= REDLIB_ROBOTS_DISABLE_INDEXING=off # Set the Pushshift frontend for "removed" links REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io -# Use Reddit over Tor to avoid blocks (requires ALL_PROXY below) +# Does OAuth over Tor to avoid ratelimiting and other blocks. Requires ALL_PROXY set to a Tor socks5h proxy. REDLIB_USE_TOR=off # Set proxy for Redlib to use #ALL_PROXY=socks5h://127.0.0.1:9050 diff --git a/README.md b/README.md index 1e6f784e..28aaa0e3 100644 --- a/README.md +++ b/README.md @@ -408,16 +408,16 @@ Redlib supports the following command line flags: 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. -| Name | Possible values | Default value | Description | -|---------------------------|-----------------|------------------------|-----------------------------------------------------------------------------------------------------------| -| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. | -| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. | -| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. | -| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. | -| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. | -| `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. | -| `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS) | -| `USE_TOR` | String | `off` | Does OAuth over Tor to avoid ratelimiting and other blocks. Requires a socks5h proxy. | +| Name | Possible values | Default value | Description | +|---------------------------|-----------------|------------------------|------------------------------------------------------------------------------------------------------------| +| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. | +| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. | +| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. | +| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. | +| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. | +| `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. | +| `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS) | +| `USE_TOR` | String | `off` | Does OAuth over Tor to avoid ratelimiting and other blocks. Requires ALL_PROXY set to a Tor socks5h proxy. | ## Default user settings From aad8f4dcb5fe472d7ce91829777fe51fc6c57b2e Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 16:39:03 -0400 Subject: [PATCH 15/19] docs: Make description for USE_TOR more accurate --- .env.example | 2 +- README.md | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index ac530dd4..5fbae763 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ REDLIB_BANNER= REDLIB_ROBOTS_DISABLE_INDEXING=off # Set the Pushshift frontend for "removed" links REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io -# Does OAuth over Tor to avoid ratelimiting and other blocks. Requires ALL_PROXY set to a Tor socks5h proxy. +# Uses Reddit's onion service to avoid ratelimiting and other blocks. Requires ALL_PROXY set to a Tor socks5h proxy. REDLIB_USE_TOR=off # Set proxy for Redlib to use #ALL_PROXY=socks5h://127.0.0.1:9050 diff --git a/README.md b/README.md index 28aaa0e3..ef01265b 100644 --- a/README.md +++ b/README.md @@ -408,16 +408,16 @@ Redlib supports the following command line flags: 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. -| Name | Possible values | Default value | Description | -|---------------------------|-----------------|------------------------|------------------------------------------------------------------------------------------------------------| -| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. | -| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. | -| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. | -| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. | -| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. | -| `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. | -| `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS) | -| `USE_TOR` | String | `off` | Does OAuth over Tor to avoid ratelimiting and other blocks. Requires ALL_PROXY set to a Tor socks5h proxy. | +| Name | Possible values | Default value | Description | +|---------------------------|-----------------|------------------------|--------------------------------------------------------------------------------------------------------------------| +| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. | +| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. | +| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. | +| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. | +| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. | +| `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. | +| `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS) | +| `USE_TOR` | String | `off` | Uses Reddit's onion service to avoid ratelimiting and other blocks. Requires ALL_PROXY set to a Tor socks5h proxy. | ## Default user settings From 73d924d1b9c32eff206bd21127525701f9028901 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 16:52:13 -0400 Subject: [PATCH 16/19] fix: Make or between clearnet and Tor domain for REGEX_URL_STATIC_MEDIA a non capturing group This fixes an issue where these domains wouldn't get formatted properly and just end up as the domain itself instead of the link we need --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index b8b192cf..91196d62 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1026,7 +1026,7 @@ static REGEX_URL_EMOJI: LazyLock = LazyLock::new(|| Regex::new(r"https?:/ static REGEX_URL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); static REGEX_URL_EXTERNAL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://external\-preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); static REGEX_URL_STYLES: LazyLock = LazyLock::new(|| Regex::new(r"https?://styles\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); -static REGEX_URL_STATIC_MEDIA: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(redditstatic\.com|reddittic34i5gtjcnm2fb7fv2eyop4vbxquuc36prnbs7d2kp3saoqd\.onion)/(.*)").unwrap()); +static REGEX_URL_STATIC_MEDIA: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(?:redditstatic\.com|reddittic34i5gtjcnm2fb7fv2eyop4vbxquuc36prnbs7d2kp3saoqd\.onion)/(.*)").unwrap()); /// Direct urls to proxy if proxy is enabled pub fn format_url(url: &str) -> String { From fee2aea1feb1909fdacfdd17388b5230dfe20fa7 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 17:06:18 -0400 Subject: [PATCH 17/19] feat: make oauth timeout dependent on if we're using Tor or not --- src/oauth.rs | 8 +++----- src/utils.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/oauth.rs b/src/oauth.rs index 8ee24fc2..2327e25e 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -1,6 +1,6 @@ use crate::{ client::{CLIENT, OAUTH_CLIENT, OAUTH_IS_ROLLING_OVER, OAUTH_RATELIMIT_REMAINING}, - oauth_resources::ANDROID_APP_VERSION_LIST, utils, + oauth_resources::ANDROID_APP_VERSION_LIST, utils::{self, get_oauth_timeout}, }; use base64::{engine::general_purpose, Engine as _}; use log::{error, info, trace, warn}; @@ -11,8 +11,6 @@ use tokio::time::{error::Elapsed, timeout}; const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg"; -const OAUTH_TIMEOUT: Duration = Duration::from_secs(90); - // Response from OAuth backend authentication #[derive(Debug, Clone)] pub struct OauthResponse { @@ -109,12 +107,12 @@ impl Oauth { std::process::exit(1); } - tokio::time::sleep(OAUTH_TIMEOUT).await; + tokio::time::sleep(get_oauth_timeout()).await; } } async fn new_with_timeout_with_backend(mut backend: OauthBackendImpl) -> Result, Elapsed> { - timeout(OAUTH_TIMEOUT, async move { + timeout(get_oauth_timeout(), async move { let response = backend.authenticate().await?; // Build headers_map from backend headers + Authorization header diff --git a/src/utils.rs b/src/utils.rs index 91196d62..abd9b57c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1472,6 +1472,16 @@ pub fn get_alternative_reddit_url_base_host() -> &'static str { }; } +// Returns the oauth timeout to use for either clearnet or Tor usage +// There's another Duration import that isn't the correct one, so it needs to be fully defined +pub fn get_oauth_timeout() -> core::time::Duration { + if using_tor() { + return core::time::Duration::from_secs(90); + } else { + return core::time::Duration::from_secs(5); + }; +} + /// Determines if a request should redirect to a NSFW landing gate. pub fn should_be_nsfw_gated(req: &Request, _req_url: &str) -> bool { (setting(req, "show_nsfw") != "on") || sfw_only() From 32ad2887c6661ff3bff171330c3887102bd73a90 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 18:27:55 -0400 Subject: [PATCH 18/19] style: fix relevant clippy warnings --- src/client.rs | 2 +- src/utils.rs | 51 +++++++++++++++++++++++---------------------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/client.rs b/src/client.rs index 1efa4585..fc32b61b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -32,7 +32,7 @@ pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99); pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false); pub fn url_pairs() -> [(&'static str, &'static str); 2] { - return [(utils::get_alternative_reddit_url_base(), utils::get_alternative_reddit_url_base_host()), + [(utils::get_alternative_reddit_url_base(), utils::get_alternative_reddit_url_base_host()), (utils::get_short_reddit_url_base(), utils::get_short_reddit_url_base_host())] } diff --git a/src/utils.rs b/src/utils.rs index abd9b57c..980b6414 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -226,9 +226,7 @@ impl Media { ("gif", &mp4["source"]["url"], None) } else { // Return the picture if the media is an image - if data["domain"] == "i.redd.it" { - ("image", &data["url"], None) - } else if data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" { + if data["domain"] == "i.redd.it" || data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" { ("image", &data["url"], None) } else { ("image", &preview["source"]["url"], None) @@ -250,10 +248,7 @@ impl Media { ); ("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. - ("image", &data["url"], None) - } else if data["is_reddit_media_domain"].as_bool().unwrap_or_default() && data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" { + } else if data["is_reddit_media_domain"].as_bool().unwrap_or_default() && (data["domain"] == "i.redd.it" || data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion") { // If this post contains a reddit media (image) URL. ("image", &data["url"], None) } else { @@ -1421,65 +1416,65 @@ pub fn using_tor() -> bool { // Returns the correct oauth url for either clearnet or Tor usage pub fn get_reddit_url_base() -> &'static str { if using_tor() { - return "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" } else { - return "https://oauth.reddit.com"; - }; + "https://oauth.reddit.com" + } } // Returns the correct oauth domain for either clearnet or Tor usage pub fn get_reddit_url_base_host() -> &'static str { if using_tor() { - return "oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + "oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" } else { - return "oauth.reddit.com"; - }; + "oauth.reddit.com" + } } // Returns the correct short Reddit url for either clearnet or Tor usage pub fn get_short_reddit_url_base() -> &'static str { if using_tor() { - return "https://redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion"; + "https://redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" } else { - return "https://redd.it"; - }; + "https://redd.it" + } } // Returns the correct short Reddit domain for either clearnet or Tor usage pub fn get_short_reddit_url_base_host() -> &'static str { if using_tor() { - return "redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion"; + "redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" } else { - return "redd.it"; - }; + "redd.it" + } } // Returns the correct alternative Reddit url for either clearnet or Tor usage pub fn get_alternative_reddit_url_base() -> &'static str { if using_tor() { - return "https://www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + "https://www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" } else { - return "https://www.reddit.com"; - }; + "https://www.reddit.com" + } } // Returns the correct alternative domain for either clearnet or Tor usage pub fn get_alternative_reddit_url_base_host() -> &'static str { if using_tor() { - return "www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion"; + "www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" } else { - return "www.reddit.com"; - }; + "www.reddit.com" + } } // Returns the oauth timeout to use for either clearnet or Tor usage // There's another Duration import that isn't the correct one, so it needs to be fully defined pub fn get_oauth_timeout() -> core::time::Duration { if using_tor() { - return core::time::Duration::from_secs(90); + core::time::Duration::from_secs(90) } else { - return core::time::Duration::from_secs(5); - }; + core::time::Duration::from_secs(5) + } } /// Determines if a request should redirect to a NSFW landing gate. From 62956acbdb78a3bfa9ad460c08187e451e0f609a Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 24 Sep 2026 18:40:27 -0400 Subject: [PATCH 19/19] style: fix relevant fmt warnings --- src/client.rs | 6 ++++-- src/oauth.rs | 3 ++- src/utils.rs | 54 +++++++++++++++++++++++++++++++++++---------------- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/client.rs b/src/client.rs index fc32b61b..9c6ddd56 100644 --- a/src/client.rs +++ b/src/client.rs @@ -32,8 +32,10 @@ pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99); pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false); pub fn url_pairs() -> [(&'static str, &'static str); 2] { - [(utils::get_alternative_reddit_url_base(), utils::get_alternative_reddit_url_base_host()), - (utils::get_short_reddit_url_base(), utils::get_short_reddit_url_base_host())] + [ + (utils::get_alternative_reddit_url_base(), utils::get_alternative_reddit_url_base_host()), + (utils::get_short_reddit_url_base(), utils::get_short_reddit_url_base_host()), + ] } pub fn build_client() -> WreqClient { diff --git a/src/oauth.rs b/src/oauth.rs index 2327e25e..6acf735f 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -1,6 +1,7 @@ use crate::{ client::{CLIENT, OAUTH_CLIENT, OAUTH_IS_ROLLING_OVER, OAUTH_RATELIMIT_REMAINING}, - oauth_resources::ANDROID_APP_VERSION_LIST, utils::{self, get_oauth_timeout}, + oauth_resources::ANDROID_APP_VERSION_LIST, + utils::{self, get_oauth_timeout}, }; use base64::{engine::general_purpose, Engine as _}; use log::{error, info, trace, warn}; diff --git a/src/utils.rs b/src/utils.rs index 980b6414..dde863f8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -248,7 +248,9 @@ impl Media { ); ("gallery", &data["url"], None) - } else if data["is_reddit_media_domain"].as_bool().unwrap_or_default() && (data["domain"] == "i.redd.it" || data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion") { + } else if data["is_reddit_media_domain"].as_bool().unwrap_or_default() + && (data["domain"] == "i.redd.it" || data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion") + { // If this post contains a reddit media (image) URL. ("image", &data["url"], None) } else { @@ -1008,20 +1010,35 @@ pub async fn catch_random(sub: &str, additional: &str) -> Result, /* Thank you to baalajimaestro for mapping out the Tor domains that are set up here and in format_url() https://git.ptr.moe/baalajimaestro/redlib/src/branch/main/src/reddit_urls.rs */ -static REGEX_URL_WWW: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_OLD: LazyLock = LazyLock::new(|| Regex::new(r"https?://old\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_NP: LazyLock = LazyLock::new(|| Regex::new(r"https?://np\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_PLAIN: LazyLock = LazyLock::new(|| Regex::new(r"https?://(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); -static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)/(DASH|CMAF)_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); -static REGEX_URL_VIDEOS_HLS: LazyLock = LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); -static REGEX_URL_IMAGES: LazyLock = LazyLock::new(|| Regex::new(r"https?://i\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); -static REGEX_URL_THUMBS_A: LazyLock = LazyLock::new(|| Regex::new(r"https?://a\.thumbs\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); -static REGEX_URL_THUMBS_B: LazyLock = LazyLock::new(|| Regex::new(r"https?://b\.thumbs\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); -static REGEX_URL_EMOJI: LazyLock = LazyLock::new(|| Regex::new(r"https?://emoji\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)/(.*)").unwrap()); -static REGEX_URL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); -static REGEX_URL_EXTERNAL_PREVIEW: LazyLock = LazyLock::new(|| Regex::new(r"https?://external\-preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); -static REGEX_URL_STYLES: LazyLock = LazyLock::new(|| Regex::new(r"https?://styles\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); -static REGEX_URL_STATIC_MEDIA: LazyLock = LazyLock::new(|| Regex::new(r"https?://www\.(?:redditstatic\.com|reddittic34i5gtjcnm2fb7fv2eyop4vbxquuc36prnbs7d2kp3saoqd\.onion)/(.*)").unwrap()); +static REGEX_URL_WWW: LazyLock = + LazyLock::new(|| Regex::new(r"https?://www\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_OLD: LazyLock = + LazyLock::new(|| Regex::new(r"https?://old\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_NP: LazyLock = + LazyLock::new(|| Regex::new(r"https?://np\.(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_PLAIN: LazyLock = + LazyLock::new(|| Regex::new(r"https?://(?:reddit\.com|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion)/(.*)").unwrap()); +static REGEX_URL_VIDEOS: LazyLock = LazyLock::new(|| { + Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)/(DASH|CMAF)_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap() +}); +static REGEX_URL_VIDEOS_HLS: LazyLock = + LazyLock::new(|| Regex::new(r"https?://v\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); +static REGEX_URL_IMAGES: LazyLock = + LazyLock::new(|| Regex::new(r"https?://i\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); +static REGEX_URL_THUMBS_A: LazyLock = + LazyLock::new(|| Regex::new(r"https?://a\.thumbs\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); +static REGEX_URL_THUMBS_B: LazyLock = + LazyLock::new(|| Regex::new(r"https?://b\.thumbs\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); +static REGEX_URL_EMOJI: LazyLock = + LazyLock::new(|| Regex::new(r"https?://emoji\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)/(.*)").unwrap()); +static REGEX_URL_PREVIEW: LazyLock = + LazyLock::new(|| Regex::new(r"https?://preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); +static REGEX_URL_EXTERNAL_PREVIEW: LazyLock = + LazyLock::new(|| Regex::new(r"https?://external\-preview\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/(.*)").unwrap()); +static REGEX_URL_STYLES: LazyLock = + LazyLock::new(|| Regex::new(r"https?://styles\.(?:redditmedia\.com|reddit4hkhcpcf2mkmuotdlk3gknuzcatsw4f7dx7twdkwmtrt6ax4qd\.onion)/(.*)").unwrap()); +static REGEX_URL_STATIC_MEDIA: LazyLock = + LazyLock::new(|| Regex::new(r"https?://www\.(?:redditstatic\.com|reddittic34i5gtjcnm2fb7fv2eyop4vbxquuc36prnbs7d2kp3saoqd\.onion)/(.*)").unwrap()); /// Direct urls to proxy if proxy is enabled pub fn format_url(url: &str) -> String { @@ -1106,8 +1123,11 @@ pub fn render_bullet_lists(input_text: &str) -> String { } // These are links we want to replace in-body -static REDDIT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/"#).unwrap()); -static REDDIT_PREVIEW_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"https?://(external-preview|preview|i)\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)(.*)").unwrap()); +static REDDIT_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it|reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad\.onion|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)/"#).unwrap() +}); +static REDDIT_PREVIEW_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"https?://(external-preview|preview|i)\.(?:redd\.it|redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd\.onion)(.*)").unwrap()); static REDDIT_EMOJI_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"https?://(www|).redditstatic\.com/(.*)").unwrap()); static REDLIB_PREVIEW_LINK_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"/(img|preview/)(pre|external-pre)?/(.*?)>"#).unwrap()); static REDLIB_PREVIEW_TEXT_REGEX: LazyLock = LazyLock::new(|| Regex::new(r">(.*?)").unwrap());