Merge pull request #5 from redlib-org/improve_spoofing
Improve spoofing
This commit is contained in:
commit
d4c4d61ce8
1
build.rs
1
build.rs
@ -7,6 +7,7 @@ use std::os::unix::process::ExitStatusExt;
|
||||
use std::os::windows::process::ExitStatusExt;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
let output = String::from_utf8(
|
||||
Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
|
104
scripts/update_oauth_resources.sh
Executable file
104
scripts/update_oauth_resources.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Requirements
|
||||
# - curl
|
||||
# - rg
|
||||
# - jq
|
||||
|
||||
# Fetch iOS app versions
|
||||
ios_version_list=$(curl -s "https://ipaarchive.com/app/usa/1064216828" | rg "(20\d{2}\.\d+.\d+) / (\d+)" --only-matching -r "Version \$1/Build \$2" | sort | uniq)
|
||||
|
||||
# Count the number of lines in the version list
|
||||
ios_app_count=$(echo "$ios_version_list" | wc -l)
|
||||
|
||||
echo -e "Fetching \e[34m$ios_app_count iOS app versions...\e[0m"
|
||||
|
||||
|
||||
# Specify the filename as a variable
|
||||
filename="src/oauth_resources.rs"
|
||||
|
||||
# Add comment that it is user generated
|
||||
echo "// This file was generated by scripts/update_oauth_resources.sh" >> "$filename"
|
||||
echo "// Rerun scripts/update_oauth_resources.sh to update this file" >> "$filename"
|
||||
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"
|
||||
|
||||
|
||||
# Append the version list to the source file
|
||||
echo "$ios_version_list" | while IFS= read -r line; do
|
||||
echo " \"$line\"," >> "$filename"
|
||||
echo -e "Fetched \e[34m$line\e[0m."
|
||||
done
|
||||
|
||||
# Close the array in the source file
|
||||
echo "];" >> "$filename"
|
||||
|
||||
# Fetch Android app versions
|
||||
page_1=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions/" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
|
||||
# Append with pages
|
||||
page_2=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=2" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
|
||||
page_3=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=3" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
|
||||
page_4=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=4" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
|
||||
page_5=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=5" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
|
||||
|
||||
# Concatenate all pages
|
||||
versions="${page_1}"
|
||||
versions+=$'\n'
|
||||
versions+="${page_2}"
|
||||
versions+=$'\n'
|
||||
versions+="${page_3}"
|
||||
versions+=$'\n'
|
||||
versions+="${page_4}"
|
||||
versions+=$'\n'
|
||||
versions+="${page_5}"
|
||||
|
||||
# Count the number of lines in the version list
|
||||
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"
|
||||
|
||||
# For each in versions, curl the page and extract the build number
|
||||
echo "$versions" | while IFS= read -r line; do
|
||||
fetch_page=$(curl -s "$line")
|
||||
build=$(echo "$fetch_page" | rg "<span class=\"vercode\">\((\d+)\)</span>" --only-matching -r "\$1" | head -n1)
|
||||
version=$(echo "$fetch_page" | rg "<span class=\"vername\">Reddit (20\d{2}\.\d+\.\d+)</span>" --only-matching -r "\$1" | head -n1)
|
||||
echo " \"Version $version/Build $build\"," >> "$filename"
|
||||
echo -e "Fetched \e[32mVersion $version/Build $build\e[0m"
|
||||
done
|
||||
|
||||
# Close the array in the source file
|
||||
echo "];" >> "$filename"
|
||||
|
||||
# Retrieve iOS versions
|
||||
table=$(curl -s "https://en.wikipedia.org/w/api.php?action=parse&page=IOS_17&prop=wikitext§ion=31&format=json" | jq ".parse.wikitext.\"*\"" | rg "(17\.[\d\.]*)\\\n\|(\w*)\\\n\|" --only-matching -r "Version \$1 (Build \$2)")
|
||||
|
||||
# Count the number of lines in the version list
|
||||
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"
|
||||
|
||||
# For each in versions, curl the page and extract the build number
|
||||
echo "$table" | while IFS= read -r line; do
|
||||
echo " \"$line\"," >> "$filename"
|
||||
echo -e "Fetched $line\e[0m"
|
||||
done
|
||||
|
||||
# Close the array in the source file
|
||||
echo "];" >> "$filename"
|
||||
|
||||
echo -e "\e[34mRetrieved $ios_app_count iOS app versions.\e[0m"
|
||||
echo -e "\e[32mRetrieved $android_count Android app versions.\e[0m"
|
||||
echo -e "\e[34mRetrieved $ios_count iOS versions.\e[0m"
|
||||
|
||||
echo -e "\e[34mTotal: $((ios_app_count + android_count + ios_count))\e[0m"
|
||||
|
||||
echo -e "\e[32mSuccess!\e[0m"
|
@ -20,12 +20,12 @@ use crate::server::RequestExt;
|
||||
|
||||
const REDDIT_URL_BASE: &str = "https://oauth.reddit.com";
|
||||
|
||||
pub(crate) static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| {
|
||||
pub static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| {
|
||||
let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build();
|
||||
client::Client::builder().build(https)
|
||||
});
|
||||
|
||||
pub(crate) static OAUTH_CLIENT: Lazy<RwLock<Oauth>> = Lazy::new(|| {
|
||||
pub static OAUTH_CLIENT: Lazy<RwLock<Oauth>> = Lazy::new(|| {
|
||||
let client = block_on(Oauth::new());
|
||||
tokio::spawn(token_daemon());
|
||||
RwLock::new(client)
|
||||
@ -332,7 +332,7 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_localization_popular() {
|
||||
let val = json("/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL".to_string(), false).await.unwrap();
|
||||
assert_eq!("GLOBAL", val["data"]["geo_filter"].as_str().unwrap());
|
||||
|
@ -7,11 +7,11 @@ use std::{env::var, fs::read_to_string};
|
||||
//
|
||||
// This is the local static that is initialized at runtime (technically at
|
||||
// first request) and contains the instance settings.
|
||||
pub(crate) static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
||||
pub static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
||||
|
||||
// This serves as the frontend for the Pushshift API - on removed comments, this URL will
|
||||
// be the base of a link, to display removed content (on another site).
|
||||
pub(crate) const DEFAULT_PUSHSHIFT_FRONTEND: &str = "www.unddit.com";
|
||||
pub const DEFAULT_PUSHSHIFT_FRONTEND: &str = "www.unddit.com";
|
||||
|
||||
/// Stores the configuration parsed from the environment variables and the
|
||||
/// config file. `Config::Default()` contains None for each setting.
|
||||
@ -104,7 +104,7 @@ impl Config {
|
||||
pub fn load() -> Self {
|
||||
let load_config = |name: &str| {
|
||||
let new_file = read_to_string(name);
|
||||
new_file.ok().and_then(|new_file| toml::from_str::<Config>(&new_file).ok())
|
||||
new_file.ok().and_then(|new_file| toml::from_str::<Self>(&new_file).ok())
|
||||
};
|
||||
|
||||
let config = load_config("redlib.toml").or(load_config("libreddit.toml")).unwrap_or_default();
|
||||
@ -168,7 +168,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
||||
}
|
||||
|
||||
/// Retrieves setting from environment variable or config file.
|
||||
pub(crate) fn get_setting(name: &str) -> Option<String> {
|
||||
pub fn get_setting(name: &str) -> Option<String> {
|
||||
get_setting_from_config(name, &CONFIG)
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ use time::OffsetDateTime;
|
||||
// This is the local static that is intialized at runtime (technically at
|
||||
// the first request to the info endpoint) and contains the data
|
||||
// retrieved from the info endpoint.
|
||||
pub(crate) static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
|
||||
pub static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
|
||||
|
||||
/// Handles instance info endpoint
|
||||
pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
@ -84,7 +84,7 @@ fn info_html(req: Request<Body>) -> Result<Response<Body>, Error> {
|
||||
Response::builder().status(200).header("content-type", "text/html; charset=utf8").body(Body::from(message))
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub(crate) struct InstanceInfo {
|
||||
pub struct InstanceInfo {
|
||||
package_name: String,
|
||||
crate_version: String,
|
||||
git_commit: String,
|
||||
|
@ -7,6 +7,7 @@ mod config;
|
||||
mod duplicates;
|
||||
mod instance_info;
|
||||
mod oauth;
|
||||
mod oauth_resources;
|
||||
mod post;
|
||||
mod search;
|
||||
mod settings;
|
||||
|
132
src/oauth.rs
132
src/oauth.rs
@ -1,6 +1,9 @@
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use crate::client::{CLIENT, OAUTH_CLIENT};
|
||||
use crate::{
|
||||
client::{CLIENT, OAUTH_CLIENT},
|
||||
oauth_resources::{ANDROID_APP_VERSION_LIST, IOS_APP_VERSION_LIST, IOS_OS_VERSION_LIST},
|
||||
};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use hyper::{client, Body, Method, Request};
|
||||
use log::info;
|
||||
@ -12,26 +15,10 @@ static REDDIT_IOS_OAUTH_CLIENT_ID: &str = "LNDo9k1o8UAEUw";
|
||||
|
||||
static AUTH_ENDPOINT: &str = "https://accounts.reddit.com";
|
||||
|
||||
// Various Android user agents - build numbers from valid APK variants
|
||||
pub(crate) static ANDROID_USER_AGENT: [&str; 3] = [
|
||||
"Reddit/Version 2023.21.0/Build 956283/Android 13",
|
||||
"Reddit/Version 2023.21.0/Build 968223/Android 10",
|
||||
"Reddit/Version 2023.21.0/Build 946732/Android 12",
|
||||
];
|
||||
|
||||
// Various iOS user agents - iOS versions.
|
||||
pub(crate) static IOS_USER_AGENT: [&str; 3] = [
|
||||
"Reddit/Version 2023.22.0/Build 613580/iOS Version 17.0 (Build 21A5248V)",
|
||||
"Reddit/Version 2023.22.0/Build 613580/iOS Version 16.0 (Build 20A5328h)",
|
||||
"Reddit/Version 2023.22.0/Build 613580/iOS Version 16.5",
|
||||
];
|
||||
// Various iOS device codes. iPhone 11 displays as `iPhone12,1`
|
||||
// I just changed the number a few times for some plausible values
|
||||
pub(crate) static IOS_DEVICES: [&str; 5] = ["iPhone8,1", "iPhone11,1", "iPhone12,1", "iPhone13,1", "iPhone14,1"];
|
||||
|
||||
// Spoofed client for Android and iOS devices
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct Oauth {
|
||||
// Currently unused, may be necessary if we decide to support GQL in the future
|
||||
pub struct Oauth {
|
||||
pub(crate) initial_headers: HashMap<String, String>,
|
||||
pub(crate) headers_map: HashMap<String, String>,
|
||||
pub(crate) token: String,
|
||||
expires_in: u64,
|
||||
@ -40,17 +27,19 @@ pub(crate) struct Oauth {
|
||||
|
||||
impl Oauth {
|
||||
pub(crate) async fn new() -> Self {
|
||||
let mut oauth = Oauth::default();
|
||||
let mut oauth = Self::default();
|
||||
oauth.login().await;
|
||||
oauth
|
||||
}
|
||||
pub(crate) fn default() -> Self {
|
||||
// Generate a random device to spoof
|
||||
let device = Device::random();
|
||||
let headers = device.headers.clone();
|
||||
let headers_map = device.headers.clone();
|
||||
let initial_headers = device.initial_headers.clone();
|
||||
// For now, just insert headers - no token request
|
||||
Oauth {
|
||||
headers_map: headers,
|
||||
Self {
|
||||
headers_map,
|
||||
initial_headers,
|
||||
token: String::new(),
|
||||
expires_in: 0,
|
||||
device,
|
||||
@ -62,16 +51,8 @@ impl Oauth {
|
||||
let mut builder = Request::builder().method(Method::POST).uri(&url);
|
||||
|
||||
// Add headers from spoofed client
|
||||
for (key, value) in self.headers_map.iter() {
|
||||
// Skip Authorization header - won't be present in `Device` struct
|
||||
// and will only be there in subsequent token refreshes.
|
||||
// Sending a bearer auth token when requesting one is a bad idea
|
||||
// Normally, you'd want to send it along to authenticate a refreshed token,
|
||||
// but neither Android nor iOS does this - it just requests a new token.
|
||||
// We try to match behavior as closely as possible.
|
||||
if key != "Authorization" {
|
||||
builder = builder.header(key, value);
|
||||
}
|
||||
for (key, value) in self.initial_headers.iter() {
|
||||
builder = builder.header(key, value);
|
||||
}
|
||||
// Set up HTTP Basic Auth - basically just the const OAuth ID's with no password,
|
||||
// Base64-encoded. https://en.wikipedia.org/wiki/Basic_access_authentication
|
||||
@ -82,7 +63,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","pii"]
|
||||
"scopes": ["*","email"]
|
||||
});
|
||||
let body = Body::from(json.to_string());
|
||||
|
||||
@ -100,6 +81,11 @@ impl Oauth {
|
||||
self.headers_map.insert("x-reddit-loid".to_owned(), header.to_str().ok()?.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());
|
||||
}
|
||||
|
||||
// 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()?;
|
||||
@ -109,7 +95,7 @@ impl Oauth {
|
||||
self.expires_in = json.get("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);
|
||||
info!("[✅] Success - Retrieved token \"{}...\", expires in {}", &self.token[..32], self.expires_in);
|
||||
|
||||
Some(())
|
||||
}
|
||||
@ -123,7 +109,7 @@ impl Oauth {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn token_daemon() {
|
||||
pub async fn token_daemon() {
|
||||
// Monitor for refreshing token
|
||||
loop {
|
||||
// Get expiry time - be sure to not hold the read lock
|
||||
@ -132,22 +118,22 @@ pub(crate) async fn token_daemon() {
|
||||
// sleep for the expiry time minus 2 minutes
|
||||
let duration = Duration::from_secs(expires_in - 120);
|
||||
|
||||
info!("Waiting for {duration:?} seconds before refreshing OAuth token...");
|
||||
info!("[⏳] Waiting for {duration:?} seconds before refreshing OAuth token...");
|
||||
|
||||
tokio::time::sleep(duration).await;
|
||||
|
||||
info!("[{duration:?} ELAPSED] Refreshing OAuth token...");
|
||||
info!("[⌛] {duration:?} Elapsed! Refreshing OAuth token...");
|
||||
|
||||
// Refresh token - in its own scope
|
||||
{
|
||||
let mut client = OAUTH_CLIENT.write().await;
|
||||
client.refresh().await;
|
||||
OAUTH_CLIENT.write().await.refresh().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Device {
|
||||
oauth_id: String,
|
||||
initial_headers: HashMap<String, String>,
|
||||
headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@ -156,8 +142,11 @@ impl Device {
|
||||
// Generate uuid
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
// Select random user agent from ANDROID_USER_AGENT
|
||||
let android_user_agent = choose(&ANDROID_USER_AGENT).to_string();
|
||||
// Generate random user-agent
|
||||
let android_app_version = choose(ANDROID_APP_VERSION_LIST).to_string();
|
||||
let android_version = fastrand::u8(9..=14);
|
||||
|
||||
let android_user_agent = format!("Reddit/{android_app_version}/Android {android_version}");
|
||||
|
||||
// Android device headers
|
||||
let headers = HashMap::from([
|
||||
@ -166,61 +155,84 @@ impl Device {
|
||||
("User-Agent".into(), android_user_agent),
|
||||
]);
|
||||
|
||||
info!("Spoofing Android client with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_ANDROID_OAUTH_CLIENT_ID}\"");
|
||||
info!("[🔄] Spoofing Android client with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_ANDROID_OAUTH_CLIENT_ID}\"");
|
||||
|
||||
Device {
|
||||
Self {
|
||||
oauth_id: REDDIT_ANDROID_OAUTH_CLIENT_ID.to_string(),
|
||||
headers,
|
||||
headers: headers.clone(),
|
||||
initial_headers: headers,
|
||||
}
|
||||
}
|
||||
fn ios() -> Self {
|
||||
// Generate uuid
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
// Select random user agent from IOS_USER_AGENT
|
||||
let ios_user_agent = choose(&IOS_USER_AGENT).to_string();
|
||||
// Generate random user-agent
|
||||
let ios_app_version = choose(IOS_APP_VERSION_LIST).to_string();
|
||||
let ios_os_version = choose(IOS_OS_VERSION_LIST).to_string();
|
||||
let ios_user_agent = format!("Reddit/{ios_app_version}/iOS {ios_os_version}");
|
||||
|
||||
// Select random iOS device from IOS_DEVICES
|
||||
let ios_device = choose(&IOS_DEVICES).to_string();
|
||||
// Generate random device
|
||||
let ios_device_num = fastrand::u8(8..=15).to_string();
|
||||
let ios_device = format!("iPhone{ios_device_num},1").to_string();
|
||||
|
||||
// iOS device headers
|
||||
let initial_headers = HashMap::from([
|
||||
("X-Reddit-DPR".into(), "2".into()),
|
||||
("User-Agent".into(), ios_user_agent.clone()),
|
||||
("Device-Name".into(), ios_device.clone()),
|
||||
]);
|
||||
let headers = HashMap::from([
|
||||
("X-Reddit-DPR".into(), "2".into()),
|
||||
("Device-Name".into(), ios_device.clone()),
|
||||
("X-Reddit-Device-Id".into(), uuid.clone()),
|
||||
("User-Agent".into(), ios_user_agent),
|
||||
("Client-Vendor-Id".into(), uuid.clone()),
|
||||
("x-dev-ad-id".into(), "00000000-0000-0000-0000-000000000000".into()),
|
||||
("Reddit-User_Id".into(), "anonymous_browsing_mode".into()),
|
||||
("x-reddit-device-id".into(), uuid.clone()),
|
||||
]);
|
||||
|
||||
info!("Spoofing iOS client {ios_device} with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_IOS_OAUTH_CLIENT_ID}\"");
|
||||
info!("[🔄] Spoofing iOS client {ios_device} with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_IOS_OAUTH_CLIENT_ID}\"");
|
||||
|
||||
Device {
|
||||
Self {
|
||||
oauth_id: REDDIT_IOS_OAUTH_CLIENT_ID.to_string(),
|
||||
initial_headers,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
// Randomly choose a device
|
||||
fn random() -> Self {
|
||||
if fastrand::bool() {
|
||||
Device::android()
|
||||
Self::android()
|
||||
} else {
|
||||
Device::ios()
|
||||
Self::ios()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Waiting on fastrand 2.0.0 for the `choose` function
|
||||
// https://github.com/smol-rs/fastrand/pull/59/
|
||||
fn choose<T: Copy>(list: &[T]) -> T {
|
||||
*fastrand::choose_multiple(list.iter(), 1)[0]
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_oauth_client() {
|
||||
assert!(!OAUTH_CLIENT.read().await.token.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_oauth_client_refresh() {
|
||||
OAUTH_CLIENT.write().await.refresh().await.unwrap();
|
||||
}
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_oauth_token_exists() {
|
||||
assert!(!OAUTH_CLIENT.read().await.token.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_oauth_headers_len() {
|
||||
assert!(OAUTH_CLIENT.read().await.headers_map.len() >= 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_creating_device() {
|
||||
Device::random();
|
||||
}
|
||||
|
231
src/oauth_resources.rs
Normal file
231
src/oauth_resources.rs
Normal file
@ -0,0 +1,231 @@
|
||||
pub static IOS_APP_VERSION_LIST: &[&str; 67] = &[
|
||||
"Version 2020.0.0/Build 306960",
|
||||
"Version 2020.10.0/Build 307041",
|
||||
"Version 2020.10.1/Build 307047",
|
||||
"Version 2020.1.0/Build 306966",
|
||||
"Version 2020.11.0/Build 307049",
|
||||
"Version 2020.11.1/Build 307063",
|
||||
"Version 2020.12.0/Build 307070",
|
||||
"Version 2020.13.0/Build 307072",
|
||||
"Version 2020.13.1/Build 307075",
|
||||
"Version 2020.14.0/Build 307077",
|
||||
"Version 2020.14.1/Build 307080",
|
||||
"Version 2020.15.0/Build 307084",
|
||||
"Version 2020.16.0/Build 307090",
|
||||
"Version 2020.17.0/Build 307093",
|
||||
"Version 2020.19.0/Build 307137",
|
||||
"Version 2020.20.0/Build 307156",
|
||||
"Version 2020.20.1/Build 307159",
|
||||
"Version 2020.2.0/Build 306969",
|
||||
"Version 2020.21.0/Build 307162",
|
||||
"Version 2020.21.1/Build 307165",
|
||||
"Version 2020.22.0/Build 307177",
|
||||
"Version 2020.22.1/Build 307181",
|
||||
"Version 2020.23.0/Build 307183",
|
||||
"Version 2020.24.0/Build 307189",
|
||||
"Version 2020.25.0/Build 307198",
|
||||
"Version 2020.26.0/Build 307205",
|
||||
"Version 2020.26.1/Build 307213",
|
||||
"Version 2020.27.0/Build 307229",
|
||||
"Version 2020.28.0/Build 307233",
|
||||
"Version 2020.29.0/Build 307235",
|
||||
"Version 2020.30.0/Build 307238",
|
||||
"Version 2020.3.0/Build 306971",
|
||||
"Version 2020.31.0/Build 307240",
|
||||
"Version 2020.31.1/Build 307246",
|
||||
"Version 2020.32.0/Build 307250",
|
||||
"Version 2020.33.0/Build 307252",
|
||||
"Version 2020.34.0/Build 307260",
|
||||
"Version 2020.35.0/Build 307262",
|
||||
"Version 2020.36.0/Build 307265",
|
||||
"Version 2020.37.0/Build 307272",
|
||||
"Version 2020.38.0/Build 307286",
|
||||
"Version 2020.39.0/Build 307306",
|
||||
"Version 2020.4.0/Build 306978",
|
||||
"Version 2020.5.0/Build 306993",
|
||||
"Version 2020.5.1/Build 307005",
|
||||
"Version 2020.6.0/Build 307007",
|
||||
"Version 2020.7.0/Build 307012",
|
||||
"Version 2020.8.0/Build 307014",
|
||||
"Version 2020.8.1/Build 307017",
|
||||
"Version 2020.9.0/Build 307035",
|
||||
"Version 2020.9.1/Build 307039",
|
||||
"Version 2023.18.0/Build 310494",
|
||||
"Version 2023.19.0/Build 310507",
|
||||
"Version 2023.20.0/Build 310535",
|
||||
"Version 2023.21.0/Build 310560",
|
||||
"Version 2023.22.0/Build 613580",
|
||||
"Version 2023.23.0/Build 310613",
|
||||
"Version 2023.23.1/Build 613639",
|
||||
"Version 2023.24.0/Build 613663",
|
||||
"Version 2023.25.0/Build 613739",
|
||||
"Version 2023.26.0/Build 613749",
|
||||
"Version 2023.27.0/Build 613771",
|
||||
"Version 2023.28.0/Build 613803",
|
||||
"Version 2023.28.1/Build 613809",
|
||||
"Version 2023.29.0/Build 613825",
|
||||
"Version 2023.30.0/Build 613849",
|
||||
"Version 2023.31.0/Build 613864",
|
||||
];
|
||||
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
||||
"Version 2023.25.1/Build 1018737",
|
||||
"Version 2023.26.0/Build 1019073",
|
||||
"Version 2023.27.0/Build 1031923",
|
||||
"Version 2023.28.0/Build 1046887",
|
||||
"Version 2023.29.0/Build 1059855",
|
||||
"Version 2023.30.0/Build 1078734",
|
||||
"Version 2023.31.0/Build 1091027",
|
||||
"Version 2023.32.0/Build 1109919",
|
||||
"Version 2023.32.1/Build 1114141",
|
||||
"Version 2023.33.1/Build 1129741",
|
||||
"Version 2023.34.0/Build 1144243",
|
||||
"Version 2023.35.0/Build 1157967",
|
||||
"Version 2023.36.0/Build 1168982",
|
||||
"Version 2023.37.0/Build 1182743",
|
||||
"Version 2023.38.0/Build 1198522",
|
||||
"Version 2023.39.0/Build 1211607",
|
||||
"Version 2023.39.1/Build 1221505",
|
||||
"Version 2023.40.0/Build 1221521",
|
||||
"Version 2023.41.0/Build 1233125",
|
||||
"Version 2023.41.1/Build 1239615",
|
||||
"Version 2023.42.0/Build 1245088",
|
||||
"Version 2023.43.0/Build 1257426",
|
||||
"Version 2023.44.0/Build 1268622",
|
||||
"Version 2023.45.0/Build 1281371",
|
||||
"Version 2023.47.0/Build 1303604",
|
||||
"Version 2023.48.0/Build 1319123",
|
||||
"Version 2023.49.0/Build 1321715",
|
||||
"Version 2023.49.1/Build 1322281",
|
||||
"Version 2023.50.0/Build 1332338",
|
||||
"Version 2023.50.1/Build 1345844",
|
||||
"Version 2023.02.0/Build 717912",
|
||||
"Version 2023.03.0/Build 729220",
|
||||
"Version 2023.04.0/Build 744681",
|
||||
"Version 2023.05.0/Build 755453",
|
||||
"Version 2023.06.0/Build 775017",
|
||||
"Version 2023.07.0/Build 788827",
|
||||
"Version 2023.07.1/Build 790267",
|
||||
"Version 2023.08.0/Build 798718",
|
||||
"Version 2023.09.0/Build 812015",
|
||||
"Version 2023.09.1/Build 816833",
|
||||
"Version 2023.10.0/Build 821148",
|
||||
"Version 2023.11.0/Build 830610",
|
||||
"Version 2023.12.0/Build 841150",
|
||||
"Version 2023.13.0/Build 852246",
|
||||
"Version 2023.14.0/Build 861593",
|
||||
"Version 2023.14.1/Build 864826",
|
||||
"Version 2023.15.0/Build 870628",
|
||||
"Version 2023.16.0/Build 883294",
|
||||
"Version 2023.16.1/Build 886269",
|
||||
"Version 2023.17.0/Build 896030",
|
||||
"Version 2023.17.1/Build 900542",
|
||||
"Version 2023.18.0/Build 911877",
|
||||
"Version 2023.19.0/Build 927681",
|
||||
"Version 2023.20.0/Build 943980",
|
||||
"Version 2023.20.1/Build 946732",
|
||||
"Version 2023.21.0/Build 956283",
|
||||
"Version 2023.22.0/Build 968223",
|
||||
"Version 2023.23.0/Build 983896",
|
||||
"Version 2023.24.0/Build 998541",
|
||||
"Version 2023.25.0/Build 1014750",
|
||||
"Version 2022.24.0/Build 510950",
|
||||
"Version 2022.24.1/Build 513462",
|
||||
"Version 2022.25.0/Build 515072",
|
||||
"Version 2022.25.1/Build 516394",
|
||||
"Version 2022.25.2/Build 519915",
|
||||
"Version 2022.26.0/Build 521193",
|
||||
"Version 2022.27.0/Build 527406",
|
||||
"Version 2022.27.1/Build 529687",
|
||||
"Version 2022.28.0/Build 533235",
|
||||
"Version 2022.30.0/Build 548620",
|
||||
"Version 2022.31.0/Build 556666",
|
||||
"Version 2022.31.1/Build 562612",
|
||||
"Version 2022.32.0/Build 567875",
|
||||
"Version 2022.33.0/Build 572600",
|
||||
"Version 2022.34.0/Build 579352",
|
||||
"Version 2022.35.0/Build 588016",
|
||||
"Version 2022.35.1/Build 589034",
|
||||
"Version 2022.36.0/Build 593102",
|
||||
"Version 2022.37.0/Build 601691",
|
||||
"Version 2022.38.0/Build 607460",
|
||||
"Version 2022.39.0/Build 615385",
|
||||
"Version 2022.39.1/Build 619019",
|
||||
"Version 2022.40.0/Build 624782",
|
||||
"Version 2022.41.0/Build 630468",
|
||||
"Version 2022.41.1/Build 634168",
|
||||
"Version 2022.42.0/Build 638508",
|
||||
"Version 2022.43.0/Build 648277",
|
||||
"Version 2022.44.0/Build 664348",
|
||||
"Version 2022.45.0/Build 677985",
|
||||
"Version 2023.01.0/Build 709875",
|
||||
"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.20.0/Build 487703",
|
||||
"Version 2022.2.0/Build 405543",
|
||||
"Version 2022.21.0/Build 492436",
|
||||
"Version 2022.22.0/Build 498700",
|
||||
"Version 2022.23.0/Build 502374",
|
||||
"Version 2022.23.1/Build 506606",
|
||||
"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",
|
||||
"Version 2021.17.0/Build 323213",
|
||||
"Version 2021.18.0/Build 324849",
|
||||
"Version 2021.19.0/Build 325762",
|
||||
"Version 2021.20.0/Build 326964",
|
||||
"Version 2021.21.0/Build 327703",
|
||||
"Version 2021.21.1/Build 328461",
|
||||
"Version 2021.22.0/Build 329696",
|
||||
"Version 2021.23.0/Build 331631",
|
||||
"Version 2021.24.0/Build 333951",
|
||||
"Version 2021.25.0/Build 335451",
|
||||
"Version 2021.26.0/Build 336739",
|
||||
"Version 2021.27.0/Build 338857",
|
||||
"Version 2021.28.0/Build 340747",
|
||||
"Version 2021.29.0/Build 342342",
|
||||
"Version 2021.30.0/Build 343820",
|
||||
"Version 2021.31.0/Build 346485",
|
||||
"Version 2021.32.0/Build 349507",
|
||||
"Version 2021.33.0/Build 351843",
|
||||
"Version 2021.34.0/Build 353911",
|
||||
"Version 2021.35.0/Build 355878",
|
||||
"Version 2021.36.0/Build 359254",
|
||||
"Version 2021.36.1/Build 360572",
|
||||
"Version 2021.37.0/Build 361905",
|
||||
"Version 2021.38.0/Build 365032",
|
||||
"Version 2021.39.0/Build 369068",
|
||||
"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",
|
||||
];
|
||||
pub static IOS_OS_VERSION_LIST: &[&str; 8] = &[
|
||||
"Version 17.0.1 (Build 21A340)",
|
||||
"Version 17.0.2 (Build 21A350)",
|
||||
"Version 17.0.3 (Build 21A360)",
|
||||
"Version 17.1 (Build 21B74)",
|
||||
"Version 17.1.1 (Build 21B91)",
|
||||
"Version 17.1.2 (Build 21B101)",
|
||||
"Version 17.2 (Build 21C62)",
|
||||
"Version 17.2.1 (Build 21C66)",
|
||||
];
|
@ -47,11 +47,11 @@ impl CompressionType {
|
||||
/// Returns a `CompressionType` given a content coding
|
||||
/// in [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4)
|
||||
/// format.
|
||||
fn parse(s: &str) -> Option<CompressionType> {
|
||||
fn parse(s: &str) -> Option<Self> {
|
||||
let c = match s {
|
||||
// Compressors we support.
|
||||
"gzip" => CompressionType::Gzip,
|
||||
"br" => CompressionType::Brotli,
|
||||
"gzip" => Self::Gzip,
|
||||
"br" => Self::Brotli,
|
||||
|
||||
// The wildcard means that we can choose whatever
|
||||
// compression we prefer. In this case, use the
|
||||
@ -69,8 +69,8 @@ impl CompressionType {
|
||||
impl ToString for CompressionType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
CompressionType::Gzip => "gzip".to_string(),
|
||||
CompressionType::Brotli => "br".to_string(),
|
||||
Self::Gzip => "gzip".to_string(),
|
||||
Self::Brotli => "br".to_string(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@ -195,7 +195,7 @@ impl Route<'_> {
|
||||
|
||||
impl Server {
|
||||
pub fn new() -> Self {
|
||||
Server {
|
||||
Self {
|
||||
default_headers: HeaderMap::new(),
|
||||
router: Router::new(),
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_fetching_subreddit() {
|
||||
let subreddit = subreddit("rust", false).await;
|
||||
assert!(subreddit.is_ok());
|
||||
|
@ -130,7 +130,7 @@ async fn user(name: &str) -> Result<User, String> {
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
|
||||
async fn test_fetching_user() {
|
||||
let user = user("spez").await;
|
||||
assert!(user.is_ok());
|
||||
|
Loading…
Reference in New Issue
Block a user