diff --git a/.env.example b/.env.example index 5e60b082..5fbae763 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 +# 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 # Default user settings # Set the default theme (options: system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight) diff --git a/README.md b/README.md index 7ea145c5..ef01265b 100644 --- a/README.md +++ b/README.md @@ -408,15 +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) | +| 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 diff --git a/src/client.rs b/src/client.rs index 5fafa7d3..9c6ddd56 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,15 +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_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(|| { @@ -39,10 +31,12 @@ 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] { + [ + (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. @@ -89,7 +83,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() { @@ -148,7 +142,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 +206,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. @@ -271,7 +265,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( @@ -291,8 +285,8 @@ 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(ALTERNATIVE_REDDIT_URL_BASE) + .trim_start_matches(utils::get_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 { "?" }) }) @@ -309,7 +303,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/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/oauth.rs b/src/oauth.rs index 166e29f7..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}, }; use base64::{engine::general_purpose, Engine as _}; use log::{error, info, trace, warn}; @@ -11,10 +12,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 #[derive(Debug, Clone)] pub struct OauthResponse { @@ -111,12 +108,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 @@ -219,7 +216,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 +332,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 +401,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 { diff --git a/src/utils.rs b/src/utils.rs index 36b798f4..dde863f8 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]; @@ -220,7 +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" { + if data["domain"] == "i.redd.it" || data["domain"] == "i.redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" { ("image", &data["url"], None) } else { ("image", &preview["source"]["url"], None) @@ -242,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" { + } 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 { @@ -1000,20 +1008,37 @@ 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_([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 { @@ -1027,6 +1052,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(), }) }; @@ -1054,18 +1080,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), - "v.redd.it" => chain!(capture(®EX_URL_VIDEOS, "/vid/", 2), capture(®EX_URL_VIDEOS_HLS, "/hls/", 2)), + "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(), } }) @@ -1084,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)/"#).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()); @@ -1381,6 +1423,80 @@ 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() { + "https://oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" + } else { + "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() { + "oauth.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" + } else { + "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() { + "https://redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" + } else { + "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() { + "redditdotzhmh3mao6r5i2j7speppwqkizwo7vksy3mbz5iz7rlhocyd.onion" + } else { + "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() { + "https://www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" + } else { + "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() { + "www.reddittorjg6rue252oqsxryoxengawnmo46qy4kyii5wtqnwfj4ooad.onion" + } else { + "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() { + core::time::Duration::from_secs(90) + } else { + 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() @@ -1501,7 +1617,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"