Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
d7cff4203a | |||
86cafe134e | |||
fedd76877c | |||
fcfd4dd196 | |||
0081205ace | |||
482b528f49 | |||
b6d828727c | |||
fe9128a5e2 | |||
836cb15946 | |||
3a5abd6c9d | |||
fd0b0968dd | |||
e94d07274d | |||
85dab5e070 | |||
5959464bbe | |||
dbb13f73f4 | |||
10c7327c39 | |||
ad39254ddc | |||
9856b7fd47 | |||
e478575706 | |||
dc84d9b503 | |||
7afc0d006c | |||
d3ba5f3efb | |||
cb9a2a3c39 | |||
6ecdedd2ed | |||
18efb8c714 | |||
0bc36d529c | |||
96ebfd2d3a | |||
3e1718bfc9 | |||
96e40e8887 | |||
f8a9ad363d | |||
f7240208f1 | |||
0714d58efe | |||
a96bebb099 | |||
6c64ebd56b | |||
62717ef6b2 | |||
a301afc383 | |||
6a18ea17ec | |||
feedc572cd | |||
a3bc16f7d2 | |||
bd4cb96c0f | |||
a9c99cc752 | |||
f03bdcf472 | |||
2fd358f3ed | |||
5ef57812f8 | |||
d17d097b12 | |||
a96894c743 | |||
9aea9c90a2 | |||
efdf1848ac | |||
bc9530821d | |||
f3d2f0cc59 | |||
49ef59e000 | |||
6773e3756b |
@ -16,6 +16,8 @@ REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io
|
||||
REDLIB_DEFAULT_THEME=system
|
||||
# Set the default mascot
|
||||
REDLIB_DEFAULT_MASCOT=none
|
||||
# Enable showing redsunlib colorway by default
|
||||
REDLIB_DEFAULT_REDSUNLIB_COLORWAY=off
|
||||
# Set the default front page (options: default, popular, all)
|
||||
REDLIB_DEFAULT_FRONT_PAGE=default
|
||||
# Set the default layout (options: card, clean, compact)
|
||||
|
1171
Cargo.lock
generated
1171
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
23
Cargo.toml
@ -3,29 +3,30 @@ name = "redsunlib"
|
||||
description = " Alternative private front-end to Reddit"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://git.stardust.wtf/iridium/redsunlib"
|
||||
version = "0.35.3"
|
||||
version = "0.35.4"
|
||||
authors = [
|
||||
"Matthew Esposito <matt+cargo@matthew.science>",
|
||||
"spikecodes <19519553+spikecodes@users.noreply.github.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
default-run = "redsunlib"
|
||||
|
||||
[dependencies]
|
||||
rinja = { version = "0.3.4", default-features = false }
|
||||
cached = { version = "0.51.3", features = ["async"] }
|
||||
cached = { version = "0.54.0", features = ["async"] }
|
||||
clap = { version = "4.4.11", default-features = false, features = [
|
||||
"std",
|
||||
"env",
|
||||
"derive",
|
||||
] }
|
||||
regex = "1.10.2"
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
cookie = "0.18.0"
|
||||
futures-lite = "2.2.0"
|
||||
hyper = { version = "0.14.28", features = ["full"] }
|
||||
hyper-rustls = "0.24.2"
|
||||
hyper = { version = "0.14.31", features = ["full"] }
|
||||
percent-encoding = "2.3.1"
|
||||
route-recognizer = "0.3.1"
|
||||
serde_json = "1.0.108"
|
||||
serde_json = "1.0.133"
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
time = { version = "0.3.31", features = ["local-offset"] }
|
||||
url = "2.5.0"
|
||||
@ -44,8 +45,10 @@ pretty_env_logger = "0.5.0"
|
||||
dotenvy = "0.15.7"
|
||||
rss = "2.0.7"
|
||||
arc-swap = "1.7.1"
|
||||
serde_json_path = "0.6.7"
|
||||
serde_json_path = "0.7.1"
|
||||
async-recursion = "1.1.1"
|
||||
common-words-all = { version = "0.0.2", default-features = false, features = ["english", "one"] }
|
||||
hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
@ -56,3 +59,11 @@ sealed_test = "1.0.0"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
strip = "symbols"
|
||||
|
||||
[[bin]]
|
||||
name = "redsunlib"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "scraper"
|
||||
path = "src/scraper/main.rs"
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<br>
|
||||
|
||||

|
||||

|
||||
|
||||
### Disclaimer
|
||||
|
||||
@ -64,8 +64,7 @@ Redlib currently implements most of Reddit's (signed-out) functionalities but st
|
||||
|
||||
**Red sun** in the sky + Red**lib** = Redsunlib
|
||||
|
||||
And at the time, I was reading an excerpt from Mao Zedong, so the name seemed appropriate. But paradoxically named since Reddit is basically the sinophobia capital of the internet :/
|
||||
|
||||
<sup>I do self criticism constantly, because I'm trapped in a Maoist *cult* where comrades (white terrorists) criticize me merciloussly for having a fascist credit card (VISA Silver Signature Rewards) They won't let me order vegan pizza anymore because the phone is fascist and "summoning my pizza slave with bourgeois app" is "bad vibes"</sup>
|
||||
|
||||
## Built with
|
||||
|
||||
|
16
redlib.container
Normal file
16
redlib.container
Normal file
@ -0,0 +1,16 @@
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
||||
[Container]
|
||||
AutoUpdate=registry
|
||||
ContainerName=redlib
|
||||
DropCapability=ALL
|
||||
EnvironmentFile=.env
|
||||
HealthCmd=["wget","--spider","-q","--tries=1","http://localhost:8080/settings"]
|
||||
HealthInterval=5m
|
||||
HealthTimeout=3s
|
||||
Image=quay.io/redlib/redlib:latest
|
||||
NoNewPrivileges=true
|
||||
PublishPort=8080:8080
|
||||
ReadOnly=true
|
||||
User=nobody
|
@ -4,7 +4,7 @@ use futures_lite::future::block_on;
|
||||
use futures_lite::{future::Boxed, FutureExt};
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri};
|
||||
use hyper::{body, body::Buf, header, Body, Client, Method, Request, Response, Uri};
|
||||
use hyper_rustls::HttpsConnector;
|
||||
use libflate::gzip;
|
||||
use log::{error, trace, warn};
|
||||
@ -30,10 +30,10 @@ 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: 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 static HTTPS_CONNECTOR: Lazy<HttpsConnector<HttpConnector>> =
|
||||
Lazy::new(|| hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http2().build());
|
||||
|
||||
pub static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| Client::builder().build::<_, Body>(HTTPS_CONNECTOR.clone()));
|
||||
|
||||
pub static OAUTH_CLIENT: Lazy<ArcSwap<Oauth>> = Lazy::new(|| {
|
||||
let client = block_on(Oauth::new());
|
||||
@ -154,7 +154,7 @@ async fn stream(url: &str, req: &Request<Body>) -> Result<Response<Body>, String
|
||||
let parsed_uri = url.parse::<Uri>().map_err(|_| "Couldn't parse URL".to_string())?;
|
||||
|
||||
// Build the hyper client from the HTTPS connector.
|
||||
let client: Client<_, Body> = CLIENT.clone();
|
||||
let client: &Lazy<Client<_, Body>> = &CLIENT;
|
||||
|
||||
let mut builder = Request::get(parsed_uri);
|
||||
|
||||
@ -216,7 +216,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
||||
let url = format!("{base_path}{path}");
|
||||
|
||||
// Construct the hyper client from the HTTPS connector.
|
||||
let client: Client<_, Body> = CLIENT.clone();
|
||||
let client: &Lazy<Client<_, Body>> = &CLIENT;
|
||||
|
||||
let (token, vendor_id, device_id, user_agent, loid) = {
|
||||
let client = OAUTH_CLIENT.load_full();
|
||||
@ -231,27 +231,37 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
||||
|
||||
// Build request to Reddit. When making a GET, request gzip compression.
|
||||
// (Reddit doesn't do brotli yet.)
|
||||
let builder = Request::builder()
|
||||
.method(method)
|
||||
.uri(&url)
|
||||
.header("User-Agent", user_agent)
|
||||
.header("Client-Vendor-Id", vendor_id)
|
||||
.header("X-Reddit-Device-Id", device_id)
|
||||
.header("x-reddit-loid", loid)
|
||||
.header("Host", host)
|
||||
.header("Authorization", &format!("Bearer {token}"))
|
||||
.header("Accept-Encoding", if method == Method::GET { "gzip" } else { "identity" })
|
||||
.header("Accept-Language", "en-US,en;q=0.5")
|
||||
.header("Connection", "keep-alive")
|
||||
.header(
|
||||
let mut headers = vec![
|
||||
("User-Agent", user_agent),
|
||||
("Client-Vendor-Id", vendor_id),
|
||||
("X-Reddit-Device-Id", device_id),
|
||||
("x-reddit-loid", loid),
|
||||
("Host", host.to_string()),
|
||||
("Authorization", format!("Bearer {token}")),
|
||||
("Accept-Encoding", if method == Method::GET { "gzip".into() } else { "identity".into() }),
|
||||
(
|
||||
"Cookie",
|
||||
if quarantine {
|
||||
"_options=%7B%22pref_quarantine_optin%22%3A%20true%2C%20%22pref_gated_sr_optin%22%3A%20true%7D"
|
||||
"_options=%7B%22pref_quarantine_optin%22%3A%20true%2C%20%22pref_gated_sr_optin%22%3A%20true%7D".into()
|
||||
} else {
|
||||
""
|
||||
"".into()
|
||||
},
|
||||
)
|
||||
.body(Body::empty());
|
||||
),
|
||||
("X-Reddit-Width", fastrand::u32(300..500).to_string()),
|
||||
("X-Reddit-DPR", "2".to_owned()),
|
||||
("Device-Name", format!("Android {}", fastrand::u8(9..=14))),
|
||||
];
|
||||
|
||||
// shuffle headers: https://github.com/redlib-org/redlib/issues/324
|
||||
fastrand::shuffle(&mut headers);
|
||||
|
||||
let mut builder = Request::builder().method(method).uri(&url);
|
||||
|
||||
for (key, value) in headers {
|
||||
builder = builder.header(key, value);
|
||||
}
|
||||
|
||||
let builder = builder.body(Body::empty());
|
||||
|
||||
async move {
|
||||
match builder {
|
||||
|
@ -96,6 +96,10 @@ pub struct Config {
|
||||
#[serde(alias = "LIBREDDIT_DEFAULT_FILTERS")]
|
||||
pub(crate) default_filters: Option<String>,
|
||||
|
||||
#[serde(rename = "REDLIB_DEFAULT_QUICKLIST")]
|
||||
#[serde(alias = "LIBREDDIT_DEFAULT_QUICKLIST")]
|
||||
pub(crate) default_quicklist: Option<String>,
|
||||
|
||||
#[serde(rename = "REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")]
|
||||
#[serde(alias = "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")]
|
||||
pub(crate) default_disable_visit_reddit_confirmation: Option<String>,
|
||||
@ -160,6 +164,7 @@ impl Config {
|
||||
default_hide_score: parse("REDLIB_DEFAULT_HIDE_SCORE"),
|
||||
default_subscriptions: parse("REDLIB_DEFAULT_SUBSCRIPTIONS"),
|
||||
default_filters: parse("REDLIB_DEFAULT_FILTERS"),
|
||||
default_quicklist: parse("REDLIB_DEFAULT_QUICKLIST"),
|
||||
default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"),
|
||||
banner: parse("REDLIB_BANNER"),
|
||||
robots_disable_indexing: parse("REDLIB_ROBOTS_DISABLE_INDEXING"),
|
||||
@ -191,6 +196,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
||||
"REDLIB_DEFAULT_HIDE_SCORE" => config.default_hide_score.clone(),
|
||||
"REDLIB_DEFAULT_SUBSCRIPTIONS" => config.default_subscriptions.clone(),
|
||||
"REDLIB_DEFAULT_FILTERS" => config.default_filters.clone(),
|
||||
"REDLIB_DEFAULT_QUICKLIST" => config.default_quicklist.clone(),
|
||||
"REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION" => config.default_disable_visit_reddit_confirmation.clone(),
|
||||
"REDLIB_BANNER" => config.banner.clone(),
|
||||
"REDLIB_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(),
|
||||
@ -271,6 +277,12 @@ fn test_default_filters() {
|
||||
assert_eq!(get_setting("REDLIB_DEFAULT_FILTERS"), Some("news+bestof".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[sealed_test(env = [("REDLIB_DEFAULT_QUICKLIST", "news+popular")])]
|
||||
fn test_default_quicklist() {
|
||||
assert_eq!(get_setting("REDLIB_DEFAULT_QUICKLIST"), Some("news+popular".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[sealed_test]
|
||||
fn test_pushshift() {
|
||||
|
@ -85,7 +85,7 @@ fn info_html(req: &Request<Body>) -> Result<Response<Body>, Error> {
|
||||
pub struct InstanceInfo {
|
||||
package_name: String,
|
||||
crate_version: String,
|
||||
git_commit: String,
|
||||
pub git_commit: String,
|
||||
deploy_date: String,
|
||||
compile_mode: String,
|
||||
deploy_unix_ts: i64,
|
||||
@ -152,6 +152,7 @@ impl InstanceInfo {
|
||||
["Hide HLS notification", &convert(&self.config.default_hide_hls_notification)],
|
||||
["Subscriptions", &convert(&self.config.default_subscriptions)],
|
||||
["Filters", &convert(&self.config.default_filters)],
|
||||
["Quick Access Feeds", &convert(&self.config.default_quicklist)],
|
||||
])
|
||||
.with_header_row(["Default preferences"]),
|
||||
);
|
||||
@ -189,7 +190,8 @@ impl InstanceInfo {
|
||||
Default use FFmpeg: {:?}\n
|
||||
Default hide HLS notification: {:?}\n
|
||||
Default subscriptions: {:?}\n
|
||||
Default filters: {:?}\n",
|
||||
Default filters: {:?}\n
|
||||
Default quicklist: {:?}\n",
|
||||
self.package_name,
|
||||
self.crate_version,
|
||||
self.git_commit,
|
||||
@ -218,6 +220,7 @@ impl InstanceInfo {
|
||||
self.config.default_hide_hls_notification,
|
||||
self.config.default_subscriptions,
|
||||
self.config.default_filters,
|
||||
self.config.default_quicklist,
|
||||
)
|
||||
}
|
||||
StringType::Html => self.to_table(),
|
||||
|
13
src/lib.rs
Normal file
13
src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod duplicates;
|
||||
pub mod instance_info;
|
||||
pub mod oauth;
|
||||
pub mod oauth_resources;
|
||||
pub mod post;
|
||||
pub mod search;
|
||||
pub mod server;
|
||||
pub mod settings;
|
||||
pub mod subreddit;
|
||||
pub mod user;
|
||||
pub mod utils;
|
85
src/main.rs
85
src/main.rs
@ -2,35 +2,21 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
// Reference local files
|
||||
mod config;
|
||||
mod duplicates;
|
||||
mod instance_info;
|
||||
mod oauth;
|
||||
mod oauth_resources;
|
||||
mod post;
|
||||
mod search;
|
||||
mod settings;
|
||||
mod subreddit;
|
||||
mod user;
|
||||
mod utils;
|
||||
|
||||
// Import Crates
|
||||
use cached::proc_macro::cached;
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::str::FromStr;
|
||||
|
||||
use futures_lite::FutureExt;
|
||||
use hyper::Uri;
|
||||
use hyper::{header::HeaderValue, Body, Request, Response};
|
||||
|
||||
mod client;
|
||||
use client::{canonical_path, proxy};
|
||||
use log::info;
|
||||
use once_cell::sync::Lazy;
|
||||
use server::RequestExt;
|
||||
use utils::{error, redirect, ThemeAssets, MascotAssets};
|
||||
use redsunlib::client::{canonical_path, proxy, CLIENT};
|
||||
use redsunlib::server::{self, RequestExt};
|
||||
use redsunlib::utils::{error, redirect, ThemeAssets, MascotAssets};
|
||||
use redsunlib::{config, duplicates, headers, instance_info, post, search, settings, subreddit, user};
|
||||
|
||||
use crate::client::OAUTH_CLIENT;
|
||||
|
||||
mod server;
|
||||
use redsunlib::client::OAUTH_CLIENT;
|
||||
|
||||
// Create Services
|
||||
|
||||
@ -257,6 +243,13 @@ async fn main() {
|
||||
app
|
||||
.at("/highlighted.js")
|
||||
.get(|_| resource(include_str!("../static/highlighted.js"), "text/javascript", false).boxed());
|
||||
app
|
||||
.at("/check_update.js")
|
||||
.get(|_| resource(include_str!("../static/check_update.js"), "text/javascript", false).boxed());
|
||||
|
||||
app.at("/commits.json").get(|_| async move { proxy_commit_info().await }.boxed());
|
||||
app.at("/instances.json").get(|_| async move { proxy_instances().await }.boxed());
|
||||
|
||||
// FFmpeg
|
||||
app
|
||||
.at("/ffmpeg/814.ffmpeg.js")
|
||||
@ -330,10 +323,12 @@ async fn main() {
|
||||
.at("/r/u_:name")
|
||||
.get(|r| async move { Ok(redirect(&format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
||||
|
||||
app.at("/r/:sub/subscribe").post(|r| subreddit::subscriptions_filters(r).boxed());
|
||||
app.at("/r/:sub/unsubscribe").post(|r| subreddit::subscriptions_filters(r).boxed());
|
||||
app.at("/r/:sub/filter").post(|r| subreddit::subscriptions_filters(r).boxed());
|
||||
app.at("/r/:sub/unfilter").post(|r| subreddit::subscriptions_filters(r).boxed());
|
||||
app.at("/r/:sub/subscribe").post(|r| subreddit::subscriptions_filters_quicklists(r).boxed());
|
||||
app.at("/r/:sub/unsubscribe").post(|r| subreddit::subscriptions_filters_quicklists(r).boxed());
|
||||
app.at("/r/:sub/filter").post(|r| subreddit::subscriptions_filters_quicklists(r).boxed());
|
||||
app.at("/r/:sub/unfilter").post(|r| subreddit::subscriptions_filters_quicklists(r).boxed());
|
||||
app.at("/r/:sub/quicklist").post(|r| subreddit::subscriptions_filters_quicklists(r).boxed());
|
||||
app.at("/r/:sub/unquicklist").post(|r| subreddit::subscriptions_filters_quicklists(r).boxed());
|
||||
|
||||
app.at("/r/:sub/comments/:id").get(|r| post::item(r).boxed());
|
||||
app.at("/r/:sub/comments/:id/:title").get(|r| post::item(r).boxed());
|
||||
@ -437,3 +432,41 @@ async fn main() {
|
||||
eprintln!("Server error: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn proxy_commit_info() -> Result<Response<Body>, String> {
|
||||
Ok(
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "application/atom+xml")
|
||||
.body(Body::from(fetch_commit_info().await))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cached(time = 600)]
|
||||
async fn fetch_commit_info() -> String {
|
||||
let uri = Uri::from_str("https://git.stardust.wtf/api/v1/repos/iridium/redsunlib/commits?verification=false&stat=false").expect("Invalid URI");
|
||||
|
||||
let resp: Body = CLIENT.get(uri).await.expect("Failed to request git.stardust.wtf").into_body();
|
||||
|
||||
hyper::body::to_bytes(resp).await.expect("Failed to read body").iter().copied().map(|x| x as char).collect()
|
||||
}
|
||||
|
||||
pub async fn proxy_instances() -> Result<Response<Body>, String> {
|
||||
Ok(
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(fetch_instances().await))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cached(time = 600)]
|
||||
async fn fetch_instances() -> String {
|
||||
let uri = Uri::from_str("https://raw.githubusercontent.com/redlib-org/redlib-instances/refs/heads/main/instances.json").expect("Invalid URI");
|
||||
|
||||
let resp: Body = CLIENT.get(uri).await.expect("Failed to request GitHub").into_body();
|
||||
|
||||
hyper::body::to_bytes(resp).await.expect("Failed to read body").iter().copied().map(|x| x as char).collect()
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ impl Oauth {
|
||||
trace!("Sending token request...");
|
||||
|
||||
// Send request
|
||||
let client: client::Client<_, Body> = CLIENT.clone();
|
||||
let client: &once_cell::sync::Lazy<client::Client<_, Body>> = &CLIENT;
|
||||
let resp = client.request(request).await.ok()?;
|
||||
|
||||
trace!("Received response with status {} and length {:?}", resp.status(), resp.headers().get("content-length"));
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
// CRATES
|
||||
use crate::client::json;
|
||||
use crate::config::get_setting;
|
||||
|
132
src/scraper/main.rs
Normal file
132
src/scraper/main.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use std::{collections::HashMap, fmt::Display, io::Write};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use common_words_all::{get_top, Language, NgramSize};
|
||||
use redsunlib::utils::Post;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "my_cli")]
|
||||
#[command(about = "A simple CLI example", long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(short = 's', long = "sub")]
|
||||
sub: String,
|
||||
|
||||
#[arg(long = "sort")]
|
||||
sort: SortOrder,
|
||||
|
||||
#[arg(short = 'f', long = "format", value_enum)]
|
||||
format: Format,
|
||||
#[arg(short = 'o', long = "output")]
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
enum SortOrder {
|
||||
Hot,
|
||||
Rising,
|
||||
New,
|
||||
Top,
|
||||
Controversial,
|
||||
}
|
||||
|
||||
impl Display for SortOrder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SortOrder::Hot => write!(f, "hot"),
|
||||
SortOrder::Rising => write!(f, "rising"),
|
||||
SortOrder::New => write!(f, "new"),
|
||||
SortOrder::Top => write!(f, "top"),
|
||||
SortOrder::Controversial => write!(f, "controversial"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ValueEnum)]
|
||||
enum Format {
|
||||
Json,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
let cli = Cli::parse();
|
||||
let (sub, sort, format, output) = (cli.sub, cli.sort, cli.format, cli.output);
|
||||
let initial = format!("/r/{sub}/{sort}.json?&raw_json=1");
|
||||
let (posts, mut after) = Post::fetch(&initial, false).await.unwrap();
|
||||
let mut hashmap = HashMap::new();
|
||||
hashmap.extend(posts.into_iter().map(|post| (post.id.clone(), post)));
|
||||
loop {
|
||||
print!("\r");
|
||||
let path = format!("/r/{sub}/{sort}.json?sort={sort}&t=&after={after}&raw_json=1");
|
||||
let (new_posts, new_after) = Post::fetch(&path, false).await.unwrap();
|
||||
let old_len = hashmap.len();
|
||||
// convert to hashmap and extend hashmap
|
||||
let new_posts = new_posts.into_iter().map(|post| (post.id.clone(), post)).collect::<HashMap<String, Post>>();
|
||||
let len = new_posts.len();
|
||||
hashmap.extend(new_posts);
|
||||
if hashmap.len() - old_len < 3 {
|
||||
break;
|
||||
}
|
||||
|
||||
let x = hashmap.len() - old_len;
|
||||
after = new_after;
|
||||
// Print number of posts fetched
|
||||
print!("Fetched {len} posts (+{x})",);
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
println!("\n\n");
|
||||
// additionally search if final count not reached
|
||||
|
||||
for word in get_top(Language::English, 10_000, NgramSize::One) {
|
||||
let mut retrieved_posts_from_search = 0;
|
||||
let initial = format!("/r/{sub}/search.json?q={word}&restrict_sr=on&include_over_18=on&raw_json=1&sort={sort}");
|
||||
println!("Grabbing posts with word {word}.");
|
||||
let (posts, mut after) = Post::fetch(&initial, false).await.unwrap();
|
||||
hashmap.extend(posts.into_iter().map(|post| (post.id.clone(), post)));
|
||||
'search: loop {
|
||||
let path = format!("/r/{sub}/search.json?q={word}&restrict_sr=on&include_over_18=on&raw_json=1&sort={sort}&after={after}");
|
||||
let (new_posts, new_after) = Post::fetch(&path, false).await.unwrap();
|
||||
if new_posts.is_empty() || new_after.is_empty() {
|
||||
println!("No more posts for word {word}");
|
||||
break 'search;
|
||||
}
|
||||
retrieved_posts_from_search += new_posts.len();
|
||||
let old_len = hashmap.len();
|
||||
let new_posts = new_posts.into_iter().map(|post| (post.id.clone(), post)).collect::<HashMap<String, Post>>();
|
||||
let len = new_posts.len();
|
||||
hashmap.extend(new_posts);
|
||||
let delta = hashmap.len() - old_len;
|
||||
after = new_after;
|
||||
// Print number of posts fetched
|
||||
println!("Fetched {len} posts (+{delta})",);
|
||||
|
||||
if retrieved_posts_from_search > 1000 {
|
||||
println!("Reached 1000 posts from search");
|
||||
break 'search;
|
||||
}
|
||||
}
|
||||
// Need to save incrementally. atomic save + move
|
||||
let tmp_file = output.clone().unwrap_or_else(|| format!("{sub}.json.tmp"));
|
||||
let perm_file = output.clone().unwrap_or_else(|| format!("{sub}.json"));
|
||||
write_posts(&hashmap.values().collect(), tmp_file.clone());
|
||||
// move file
|
||||
std::fs::rename(tmp_file, perm_file).unwrap();
|
||||
}
|
||||
|
||||
println!("\n\n");
|
||||
|
||||
println!("Size of hashmap: {}", hashmap.len());
|
||||
|
||||
let posts: Vec<&Post> = hashmap.values().collect();
|
||||
match format {
|
||||
Format::Json => {
|
||||
let filename: String = output.unwrap_or_else(|| format!("{sub}.json"));
|
||||
write_posts(&posts, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_posts(posts: &Vec<&Post>, filename: String) {
|
||||
let json = serde_json::to_string(&posts).unwrap();
|
||||
std::fs::write(filename, json).unwrap();
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
// CRATES
|
||||
use crate::utils::{self, catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, setting, template, val, Post, Preferences};
|
||||
use crate::{
|
||||
client::json,
|
||||
server::RequestExt,
|
||||
subreddit::{can_access_quarantine, quarantine},
|
||||
RequestExt,
|
||||
};
|
||||
use hyper::{Body, Request, Response};
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
use brotli::enc::{BrotliCompress, BrotliEncoderParams};
|
||||
use cached::proc_macro::cached;
|
||||
@ -195,6 +196,12 @@ impl Route<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Server {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -723,7 +730,7 @@ mod tests {
|
||||
|
||||
CompressionType::Brotli => Box::new(BrotliDecompressor::new(body_cursor, expected_lorem_ipsum.len())),
|
||||
|
||||
_ => panic!("no decompressor for {}", expected_encoding.to_string()),
|
||||
_ => panic!("no decompressor for {}", expected_encoding),
|
||||
};
|
||||
|
||||
let mut decompressed = Vec::<u8>::new();
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
// CRATES
|
||||
@ -19,9 +21,10 @@ struct SettingsTemplate {
|
||||
|
||||
// CONSTANTS
|
||||
|
||||
const PREFS: [&str; 19] = [
|
||||
const PREFS: [&str; 21] = [
|
||||
"theme",
|
||||
"mascot",
|
||||
"redsunlib_colorway",
|
||||
"front_page",
|
||||
"layout",
|
||||
"wide",
|
||||
@ -39,6 +42,7 @@ const PREFS: [&str; 19] = [
|
||||
"hide_awards",
|
||||
"hide_score",
|
||||
"disable_visit_reddit_confirmation",
|
||||
"video_quality",
|
||||
];
|
||||
|
||||
// FUNCTIONS
|
||||
@ -118,7 +122,7 @@ fn set_cookies_method(req: Request<Body>, remove_cookies: bool) -> Response<Body
|
||||
|
||||
let mut response = redirect(&path);
|
||||
|
||||
for name in [PREFS.to_vec(), vec!["subscriptions", "filters"]].concat() {
|
||||
for name in [PREFS.to_vec(), vec!["subscriptions", "filters", "quicklist"]].concat() {
|
||||
match form.get(name) {
|
||||
Some(value) => response.insert_cookie(
|
||||
Cookie::build((name.to_owned(), value.clone()))
|
||||
|
@ -1,9 +1,11 @@
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
use crate::{config, utils};
|
||||
// CRATES
|
||||
use crate::utils::{
|
||||
catch_random, error, filter_posts, format_num, format_url, get_filters, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
||||
};
|
||||
use crate::{client::json, server::ResponseExt, RequestExt};
|
||||
use crate::{client::json, server::RequestExt, server::ResponseExt};
|
||||
use cookie::Cookie;
|
||||
use hyper::{Body, Request, Response};
|
||||
use rinja::Template;
|
||||
@ -209,8 +211,8 @@ pub fn can_access_quarantine(req: &Request<Body>, sub: &str) -> bool {
|
||||
setting(req, &format!("allow_quaran_{}", sub.to_lowercase())).parse().unwrap_or_default()
|
||||
}
|
||||
|
||||
// Sub, filter, unfilter, or unsub by setting subscription cookie using response "Set-Cookie" header
|
||||
pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
// Sub, filter, unfilter, quicklist, unquicklist or unsub by setting subscription cookie using response "Set-Cookie" header
|
||||
pub async fn subscriptions_filters_quicklists(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
let action: Vec<String> = req.uri().path().split('/').map(String::from).collect();
|
||||
|
||||
@ -227,6 +229,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
let preferences = Preferences::new(&req);
|
||||
let mut sub_list = preferences.subscriptions;
|
||||
let mut filters = preferences.filters;
|
||||
let mut quicklist = preferences.quicklist;
|
||||
|
||||
// Retrieve list of posts for these subreddits to extract display names
|
||||
|
||||
@ -288,7 +291,16 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
} else if action.contains(&"unfilter".to_string()) {
|
||||
// Remove sub name from filtered list
|
||||
filters.retain(|s| s.to_lowercase() != part.to_lowercase());
|
||||
}
|
||||
} else if action.contains(&"quicklist".to_string()) && !quicklist.contains(&part.to_owned()) {
|
||||
// Add each sub name to the filtered list
|
||||
quicklist.push(part.to_owned());
|
||||
// Reorder quicklist alphabetically
|
||||
quicklist.sort_by_key(|a| a.to_lowercase());
|
||||
} else if action.contains(&"unquicklist".to_string()) {
|
||||
// Remove sub name from filtered list
|
||||
quicklist.retain(|s| s.to_lowercase() != part.to_lowercase());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Redirect back to subreddit
|
||||
@ -324,6 +336,17 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if quicklist.is_empty() {
|
||||
response.remove_cookie("quicklist".to_string());
|
||||
} else {
|
||||
response.insert_cookie(
|
||||
Cookie::build(("quicklist", quicklist.join("+")))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
// CRATES
|
||||
use crate::client::json;
|
||||
use crate::server::RequestExt;
|
||||
|
25
src/utils.rs
25
src/utils.rs
@ -1,4 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::cmp_owned)]
|
||||
|
||||
use crate::config::{self, get_setting};
|
||||
//
|
||||
// CRATES
|
||||
@ -11,6 +13,7 @@ use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rinja::Template;
|
||||
use rust_embed::RustEmbed;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use serde_json_path::{JsonPath, JsonPathExt};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@ -46,6 +49,7 @@ pub enum ResourceType {
|
||||
}
|
||||
|
||||
// Post flair with content, background color and foreground color
|
||||
#[derive(Serialize)]
|
||||
pub struct Flair {
|
||||
pub flair_parts: Vec<FlairPart>,
|
||||
pub text: String,
|
||||
@ -54,7 +58,7 @@ pub struct Flair {
|
||||
}
|
||||
|
||||
// Part of flair, either emoji or text
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct FlairPart {
|
||||
pub flair_part_type: String,
|
||||
pub value: String,
|
||||
@ -96,12 +100,14 @@ impl FlairPart {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Author {
|
||||
pub name: String,
|
||||
pub flair: Flair,
|
||||
pub distinguished: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Poll {
|
||||
pub poll_options: Vec<PollOption>,
|
||||
pub voting_end_timestamp: (String, String),
|
||||
@ -129,6 +135,7 @@ impl Poll {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PollOption {
|
||||
pub id: u64,
|
||||
pub text: String,
|
||||
@ -158,13 +165,14 @@ impl PollOption {
|
||||
}
|
||||
|
||||
// Post flags with nsfw and stickied
|
||||
#[derive(Serialize)]
|
||||
pub struct Flags {
|
||||
pub spoiler: bool,
|
||||
pub nsfw: bool,
|
||||
pub stickied: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Media {
|
||||
pub url: String,
|
||||
pub alt_url: String,
|
||||
@ -264,6 +272,7 @@ impl Media {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GalleryMedia {
|
||||
pub url: String,
|
||||
pub width: i64,
|
||||
@ -304,6 +313,7 @@ impl GalleryMedia {
|
||||
}
|
||||
|
||||
// Post containing content, metadata and media
|
||||
#[derive(Serialize)]
|
||||
pub struct Post {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
@ -470,7 +480,7 @@ pub struct Comment {
|
||||
pub prefs: Preferences,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Default, Clone, Serialize)]
|
||||
pub struct Award {
|
||||
pub name: String,
|
||||
pub icon_url: String,
|
||||
@ -484,6 +494,7 @@ impl std::fmt::Display for Award {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Awards(pub Vec<Award>);
|
||||
|
||||
impl std::ops::Deref for Awards {
|
||||
@ -496,7 +507,7 @@ impl std::ops::Deref for Awards {
|
||||
|
||||
impl std::fmt::Display for Awards {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.iter().fold(Ok(()), |result, award| result.and_then(|()| writeln!(f, "{award}")))
|
||||
self.iter().try_fold((), |_, award| writeln!(f, "{award}"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,6 +607,7 @@ pub struct Preferences {
|
||||
pub available_mascots: Vec<String>,
|
||||
pub theme: String,
|
||||
pub mascot: String,
|
||||
pub redsunlib_colorway: String,
|
||||
pub front_page: String,
|
||||
pub layout: String,
|
||||
pub wide: String,
|
||||
@ -603,6 +615,7 @@ pub struct Preferences {
|
||||
pub show_nsfw: String,
|
||||
pub blur_nsfw: String,
|
||||
pub hide_hls_notification: String,
|
||||
pub video_quality: String,
|
||||
pub hide_sidebar_and_summary: String,
|
||||
pub use_hls: String,
|
||||
pub ffmpeg_video_downloads: String,
|
||||
@ -612,6 +625,7 @@ pub struct Preferences {
|
||||
pub comment_sort: String,
|
||||
pub post_sort: String,
|
||||
pub subscriptions: Vec<String>,
|
||||
pub quicklist: Vec<String>,
|
||||
pub filters: Vec<String>,
|
||||
pub hide_awards: String,
|
||||
pub hide_score: String,
|
||||
@ -649,6 +663,7 @@ impl Preferences {
|
||||
available_mascots: mascots,
|
||||
theme: setting(req, "theme"),
|
||||
mascot: setting(req, "mascot"),
|
||||
redsunlib_colorway: setting(req, "redsunlib_colorway"),
|
||||
front_page: setting(req, "front_page"),
|
||||
layout: setting(req, "layout"),
|
||||
wide: setting(req, "wide"),
|
||||
@ -659,6 +674,7 @@ impl Preferences {
|
||||
use_hls: setting(req, "use_hls"),
|
||||
ffmpeg_video_downloads: setting(req, "ffmpeg_video_downloads"),
|
||||
hide_hls_notification: setting(req, "hide_hls_notification"),
|
||||
video_quality: setting(req, "video_quality"),
|
||||
autoplay_videos: setting(req, "autoplay_videos"),
|
||||
fixed_navbar: setting_or_default(req, "fixed_navbar", "on".to_string()),
|
||||
disable_visit_reddit_confirmation: setting(req, "disable_visit_reddit_confirmation"),
|
||||
@ -666,6 +682,7 @@ impl Preferences {
|
||||
post_sort: setting(req, "post_sort"),
|
||||
subscriptions: setting(req, "subscriptions").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
||||
filters: setting(req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
||||
quicklist: setting(req, "quicklist").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
|
||||
hide_awards: setting(req, "hide_awards"),
|
||||
hide_score: setting(req, "hide_score"),
|
||||
}
|
||||
|
55
static/check_update.js
Normal file
55
static/check_update.js
Normal file
@ -0,0 +1,55 @@
|
||||
async function checkInstanceUpdateStatus() {
|
||||
try {
|
||||
const response = await fetch('/commits.json');
|
||||
const text = await response.text();
|
||||
const entries = JSON.parse(text);
|
||||
const localCommit = document.getElementById('git_commit').dataset.value;
|
||||
|
||||
let statusMessage = '';
|
||||
|
||||
if (entries.length > 0) {
|
||||
const commitHashes = Array.from(entries).map(entry => {
|
||||
return entry.sha
|
||||
});
|
||||
|
||||
const commitIndex = commitHashes.indexOf(localCommit);
|
||||
|
||||
if (commitIndex === 0) {
|
||||
statusMessage = '✅ Instance is up to date.';
|
||||
} else if (commitIndex > 0) {
|
||||
statusMessage = `⚠️ This instance is not up to date and is ${commitIndex} commits old. Test and confirm on an up-to-date instance before reporting.`;
|
||||
document.getElementById('error-318').remove();
|
||||
} else {
|
||||
statusMessage = `⚠️ This instance is not up to date and is at least ${commitHashes.length} commits old. Test and confirm on an up-to-date instance before reporting.`;
|
||||
document.getElementById('error-318').remove();
|
||||
}
|
||||
} else {
|
||||
statusMessage = '⚠️ Unable to fetch commit information.';
|
||||
}
|
||||
|
||||
document.getElementById('update-status').innerText = statusMessage;
|
||||
} catch (error) {
|
||||
console.error('Error fetching commits:', error);
|
||||
document.getElementById('update-status').innerText = '⚠️ Error checking update status.';
|
||||
}
|
||||
}
|
||||
|
||||
async function checkOtherInstances() {
|
||||
try {
|
||||
const response = await fetch('/instances.json');
|
||||
const data = await response.json();
|
||||
const randomInstance = data.instances[Math.floor(Math.random() * data.instances.length)];
|
||||
const instanceUrl = randomInstance.url;
|
||||
// Set the href of the <a> tag to the instance URL with path included
|
||||
document.getElementById('random-instance').href = instanceUrl + window.location.pathname;
|
||||
//document.getElementById('random-instance').innerText = "Visit Random Instance";
|
||||
} catch (error) {
|
||||
console.error('Error fetching instances:', error);
|
||||
document.getElementById('update-status').innerText = '⚠️ Error checking update status.';
|
||||
}
|
||||
}
|
||||
|
||||
// Set the target URL when the page loads
|
||||
window.addEventListener('load', checkOtherInstances);
|
||||
|
||||
checkInstanceUpdateStatus();
|
BIN
static/mascots/BoymoderHoodie.png
Normal file
BIN
static/mascots/BoymoderHoodie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
static/mascots/dprksoldier.png
Normal file
BIN
static/mascots/dprksoldier.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
@ -216,7 +216,9 @@ nav #links {
|
||||
}
|
||||
|
||||
nav #links svg {
|
||||
display: none;
|
||||
/*display: none;*/
|
||||
position: relative;
|
||||
/*What's nine plus ten?*/ top: /*Twenty one*/21%; /*You stupid.*/
|
||||
}
|
||||
|
||||
nav #redlib {
|
||||
@ -429,7 +431,7 @@ a:hover {
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: var(--text);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
img[src=""] {
|
||||
@ -523,10 +525,14 @@ aside {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#user_actions,
|
||||
#sub_actions {
|
||||
display: grid;
|
||||
grid-template-columns: auto 4fr 1fr;
|
||||
}
|
||||
|
||||
#user_details,
|
||||
#sub_details,
|
||||
#sub_actions,
|
||||
#user_actions {
|
||||
#sub_details {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
@ -545,6 +551,8 @@ aside {
|
||||
#user_subscription,
|
||||
#sub_filter,
|
||||
#user_filter,
|
||||
#sub_quicklist,
|
||||
#user_quicklist,
|
||||
#sub_rss,
|
||||
#user_rss {
|
||||
margin-top: 20px;
|
||||
@ -554,9 +562,16 @@ aside {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#sub_quicklist button,
|
||||
#user_quicklist button {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.subscribe,
|
||||
.unsubscribe,
|
||||
.filter,
|
||||
.unquick,
|
||||
.quick,
|
||||
.unfilter {
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
@ -568,12 +583,22 @@ aside {
|
||||
color: var(--foreground);
|
||||
background-color: var(--accent);
|
||||
}
|
||||
.quick {
|
||||
color: var(--foreground);
|
||||
background-color: var(--accent);
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.unsubscribe,
|
||||
.unfilter {
|
||||
color: var(--text);
|
||||
background-color: var(--highlighted);
|
||||
}
|
||||
.unquick {
|
||||
color: var(--text);
|
||||
background-color: var(--highlighted);
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Feeds */
|
||||
|
||||
@ -1456,6 +1481,7 @@ a.search_subreddit:hover {
|
||||
|
||||
.comment:not([id]) .comment_data > .comment_link {
|
||||
display: -webkit-box;
|
||||
line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-all;
|
||||
@ -1965,6 +1991,13 @@ th {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
#issue_warning {
|
||||
color: var(--popup-toreddit-text);
|
||||
background: var(--popup-background);
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
#duplicates_msg h3 {
|
||||
@ -2261,8 +2294,10 @@ th {
|
||||
max-width: 20em;
|
||||
}
|
||||
.download {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 1px;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
color: var(--accent);
|
||||
@ -2276,4 +2311,11 @@ th {
|
||||
|
||||
.download:active {
|
||||
background-color: var(--background);
|
||||
}
|
||||
.rotate{
|
||||
animation: spinner 1.5s linear infinite;
|
||||
padding: 0px;
|
||||
}
|
||||
@keyframes spinner {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
// @license http://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
|
||||
let ffmpeg = null;
|
||||
|
||||
let loadingsvg = `<svg class="rotate" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M12 4.5a7.5 7.5 0 1 0 0 15a7.5 7.5 0 0 0 0-15M1.5 12C1.5 6.201 6.201 1.5 12 1.5S22.5 6.201 22.5 12S17.799 22.5 12 22.5S1.5 17.799 1.5 12" opacity="0.1"/><path fill="currentColor" d="M12 4.5a7.46 7.46 0 0 0-5.187 2.083a1.5 1.5 0 0 1-2.075-2.166A10.46 10.46 0 0 1 12 1.5a1.5 1.5 0 0 1 0 3"/></g></svg>`;
|
||||
let downloadsvg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M20 15a1 1 0 0 1 1 1v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4a1 1 0 1 1 2 0v4h14v-4a1 1 0 0 1 1-1M12 2a1 1 0 0 1 1 1v10.243l2.536-2.536a1 1 0 1 1 1.414 1.414l-4.066 4.066a1.25 1.25 0 0 1-1.768 0L7.05 12.121a1 1 0 1 1 1.414-1.414L11 13.243V3a1 1 0 0 1 1-1"/></g></svg>`;
|
||||
|
||||
(function () {
|
||||
if (Hls.isSupported()) {
|
||||
|
||||
@ -13,7 +17,7 @@ let ffmpeg = null;
|
||||
var autoplay = oldVideo.classList.contains("hls_autoplay");
|
||||
|
||||
// If HLS is supported natively then don't use hls.js
|
||||
if (oldVideo.canPlayType(source.type) === "probably") {
|
||||
if (oldVideo.canPlayType(source.type) === "probably" && !downloadsEnabled) {
|
||||
if (autoplay) {
|
||||
oldVideo.play();
|
||||
}
|
||||
@ -82,7 +86,7 @@ let ffmpeg = null;
|
||||
var mediaStream = [];
|
||||
var downloadButton = document.createElement('button');
|
||||
downloadButton.classList.add('video-options','download');
|
||||
downloadButton.innerText = "⏳"
|
||||
downloadButton.innerHTML = loadingsvg
|
||||
const mergeStreams = async () => {
|
||||
if (ffmpeg === null) {
|
||||
ffmpeg = new FFmpeg();
|
||||
@ -115,7 +119,7 @@ let ffmpeg = null;
|
||||
|
||||
var filename = toSlug(videoElement.parentNode.parentNode.id || document.title)
|
||||
const data = await ffmpeg.readFile('output.mp4');
|
||||
saveAs(new Blob([data.buffer]),filename, {type: 'video/mp4'});
|
||||
saveAs(new Blob([data.buffer], {type: 'video/mp4'}),filename);
|
||||
return
|
||||
}
|
||||
function saveAs(blob, filename) { // Yeah ok...
|
||||
@ -157,11 +161,11 @@ let ffmpeg = null;
|
||||
var isDownloading = false
|
||||
function startDownload() {
|
||||
if (!isDownloading) { isDownloading = true } else { return }
|
||||
downloadButton.innerText = "⏳"
|
||||
downloadButton.innerHTML = loadingsvg
|
||||
mergeStreams()
|
||||
.then(_ => {
|
||||
isDownloading = false
|
||||
downloadButton.innerText = "⭳"
|
||||
downloadButton.innerHTML = downloadsvg
|
||||
});
|
||||
}
|
||||
|
||||
@ -178,7 +182,7 @@ let ffmpeg = null;
|
||||
|
||||
waitForLoad(_ => flag === true)
|
||||
.then(_ => {
|
||||
downloadButton.innerText = "⭳"
|
||||
downloadButton.innerHTML = downloadsvg
|
||||
downloadButton.addEventListener('click', startDownload);
|
||||
});
|
||||
}
|
||||
|
@ -27,6 +27,8 @@
|
||||
<link rel="manifest" type="application/json" href="/manifest.json">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="/style.css?v={{ env!("CARGO_PKG_VERSION") }}">
|
||||
<!-- Video quality -->
|
||||
<div id="video_quality" data-value="{{ prefs.video_quality }}"></div>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="
|
||||
@ -38,27 +40,35 @@
|
||||
<nav class="
|
||||
{% if prefs.fixed_navbar == "on" %} fixed_navbar{% endif %}">
|
||||
<div id="logo">
|
||||
<a id="redlib" href="/"><span id="lib">red</span><span id="reddit">sun</span><span id="lib">lib.</span></a>
|
||||
<a id="redlib" href="/">
|
||||
<span id="lib" {% if prefs.redsunlib_colorway == "on" %}style="color: #ff8585;"{% endif %}">red</span><span id="reddit" {% if prefs.redsunlib_colorway == "on" %}style="color: #ffbfbf;"{% endif %}>sun</span><span id="lib" {% if prefs.redsunlib_colorway == "on" %}style="color: #ff8585;"{% endif %}>lib.</span>
|
||||
</a>
|
||||
{% block subscriptions %}{% endblock %}
|
||||
</div>
|
||||
{% block search %}{% endblock %}
|
||||
<div id="links">
|
||||
<a id="reddit_link" {% if prefs.disable_visit_reddit_confirmation != "on" %}href="#popup"{% else %}href="https://www.reddit.com{{ url }}" rel="nofollow"{% endif %}>
|
||||
<span>reddit</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 2L12 22"/>
|
||||
<path d="M2 6.70587C3.33333 8.07884 3.33333 11.5971 3.33333 11.5971M3.33333 19.647V11.5971M3.33333 11.5971C3.33333 11.5971 5.125 7.47817 8 7.47817C10.875 7.47817 12 8.85114 12 8.85114"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<title>redirect to reddit</title>
|
||||
<g fill="none">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M6.301 6a4 4 0 0 1 3.312 1.756l.118.186l4.253 7.087a2 2 0 0 0 1.553.965L15.7 16h1.194l.02-.415l.022-.36l.012-.159c.027-.346.352-.557.631-.41l.306.164l.36.203l.198.117l.43.263l.229.147l.463.31l.21.147l.377.273l.315.24l.133.104c.236.188.225.566-.023.762l-.28.217l-.34.252l-.4.282l-.456.305l-.462.291l-.416.249l-.365.205l-.307.165c-.275.143-.572-.036-.598-.36l-.025-.347l-.024-.415l-.01-.23H15.7a4 4 0 0 1-3.312-1.756l-.118-.186l-4.253-7.087a2 2 0 0 0-1.553-.965L6.3 8H4a1 1 0 0 1-.117-1.993L4 6zm3.714 7.643a1 1 0 0 1 .342 1.371l-.626 1.044A4 4 0 0 1 6.301 18H4a1 1 0 1 1 0-2h2.301a2 2 0 0 0 1.715-.971l.627-1.043a1 1 0 0 1 1.371-.344Zm7.563-8.988l.306.165l.36.203l.198.117l.43.263l.229.147l.463.31l.21.147l.377.273l.315.24l.133.104c.236.188.225.566-.023.762l-.28.217l-.34.252q-.186.135-.4.282l-.456.305l-.462.291l-.416.249l-.365.206l-.307.164c-.275.143-.572-.036-.598-.36l-.025-.347l-.024-.415l-.01-.23H15.7a2 2 0 0 0-1.627.836l-.088.135l-.626 1.043a1 1 0 0 1-1.77-.925l.055-.104l.626-1.043a4 4 0 0 1 3.209-1.936l.22-.006h1.195l.02-.415l.022-.36l.012-.159c.027-.346.352-.557.631-.41Z" />
|
||||
</g>
|
||||
</svg>
|
||||
<span>reddit</span>
|
||||
</a>
|
||||
{% if prefs.disable_visit_reddit_confirmation != "on" %}
|
||||
{% call utils::visit_reddit_confirmation(url) %}
|
||||
{% endif %}
|
||||
<a id="settings_link" href="/settings">
|
||||
<span>settings</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<title>settings</title>
|
||||
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<title>settings</title>
|
||||
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M14.035 2.809c.37-.266.89-.39 1.401-.203a10 10 0 0 1 2.982 1.725c.417.35.57.861.524 1.313c-.075.753.057 1.48.42 2.106c.32.557.802.997 1.39 1.307l.225.11c.414.187.782.576.875 1.113a10 10 0 0 1 0 3.44c-.083.484-.39.847-.753 1.051l-.122.063c-.69.31-1.254.79-1.616 1.416c-.362.627-.494 1.353-.419 2.106c.045.452-.107.964-.524 1.313a10 10 0 0 1-2.982 1.725a1.51 1.51 0 0 1-1.4-.203C13.42 20.75 12.723 20.5 12 20.5s-1.42.249-2.035.691a1.51 1.51 0 0 1-1.401.203a10 10 0 0 1-2.982-1.725a1.51 1.51 0 0 1-.524-1.313c.075-.753-.058-1.48-.42-2.106a3.4 3.4 0 0 0-1.39-1.307l-.225-.11a1.51 1.51 0 0 1-.875-1.113a10 10 0 0 1 0-3.44c.083-.484.39-.847.753-1.051l.122-.062c.69-.311 1.254-.79 1.616-1.417c.361-.626.494-1.353.419-2.106a1.51 1.51 0 0 1 .524-1.313a10 10 0 0 1 2.982-1.725a1.51 1.51 0 0 1 1.4.203c.615.442 1.312.691 2.036.691s1.42-.249 2.035-.691m.957 1.769c-.866.57-1.887.922-2.992.922s-2.126-.353-2.992-.922A8 8 0 0 0 7.068 5.7c.06 1.033-.145 2.093-.697 3.05c-.553.956-1.368 1.663-2.293 2.128a8 8 0 0 0 0 2.242c.925.465 1.74 1.172 2.293 2.13c.552.955.757 2.015.697 3.048a8 8 0 0 0 1.94 1.123c.866-.57 1.887-.922 2.992-.922s2.126.353 2.992.922a8 8 0 0 0 1.94-1.122c-.06-1.034.145-2.094.697-3.05c.552-.957 1.368-1.664 2.293-2.13a8 8 0 0 0 0-2.24c-.925-.466-1.74-1.173-2.293-2.13c-.552-.956-.757-2.016-.697-3.05a8 8 0 0 0-1.94-1.122ZM12 8a4 4 0 1 1 0 8a4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4a2 2 0 0 0 0-4" />
|
||||
</g>
|
||||
</svg>
|
||||
<span>settings</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -6,10 +6,19 @@
|
||||
<h1>{{ msg }}</h1>
|
||||
<h3><a href="https://www.redditstatus.com/">Reddit Status</a></h3>
|
||||
<br />
|
||||
<h3>Expected something to work? <a
|
||||
href="https://github.com/redlib-org/redlib/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=%F0%9F%90%9B+Bug+Report%3A+{{ msg }}">Report
|
||||
an issue</a></h3>
|
||||
<h3 id="update-status"></h3>
|
||||
<br />
|
||||
<h3 id="update-status"></h3>
|
||||
<br>
|
||||
<div id="git_commit" data-value="{{ crate::instance_info::INSTANCE_INFO.git_commit }}"></div>
|
||||
<script src="/check_update.js"></script>
|
||||
|
||||
<h3>Expected something to work? Try a random <a id="random-instance">upstream instance.</a></h3>
|
||||
<br />
|
||||
<h3 id="issue_warning" >!! Do <b>NOT</b> open an issue on the <a href="https://github.com/redlib-org/redlib/">redlib repository</a> with a redsunlib specific issue !!</h3>
|
||||
<br />
|
||||
<p id="error-318">If you're getting a "Failed to parse page JSON data" error, please check <a href="https://github.com/redlib-org/redlib/issues/318" target="_blank">#318</a></p>
|
||||
<br />
|
||||
<h3>Head back <a href="/">home</a>?</h3>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -29,6 +29,11 @@
|
||||
{% call utils::options(prefs.mascot, prefs.available_mascots, "system") %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="prefs-group">
|
||||
<label for="redsunlib_colorway">Force redsunlib colorway</label>
|
||||
<input type="hidden" value="off" name="redsunlib_colorway">
|
||||
<input type="checkbox" name="redsunlib_colorway" {% if prefs.redsunlib_colorway == "on" %}checked{% endif %}>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Interface</legend>
|
||||
@ -68,6 +73,12 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Content</legend>
|
||||
<div class="prefs-group">
|
||||
<label for="video_quality">Video quality:</label>
|
||||
<select name="video_quality" id="video_quality">
|
||||
{% call utils::options(prefs.video_quality, ["best", "medium", "worst"], "best") %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="prefs-group">
|
||||
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
|
||||
<select name="post_sort">
|
||||
@ -147,7 +158,7 @@
|
||||
</form>
|
||||
<div id="settings_note">
|
||||
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
||||
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&mascot={{ prefs.mascot }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&ffmpeg_video_downloads={{ prefs.ffmpeg_video_downloads }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&fixed_navbar={{ prefs.fixed_navbar }}&hide_sidebar_and_summary={{ prefs.hide_sidebar_and_summary}}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
||||
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&mascot={{ prefs.mascot }}&redsunlib_colorway={{ prefs.redsunlib_colorway }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&ffmpeg_video_downloads={{ prefs.ffmpeg_video_downloads }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&fixed_navbar={{ prefs.fixed_navbar }}&hide_sidebar_and_summary={{ prefs.hide_sidebar_and_summary}}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}&quicklist={{ prefs.quicklist.join("%2B") }}">this link</a>.</p>
|
||||
</div>
|
||||
{% if prefs.subscriptions.len() > 0 %}
|
||||
<div class="prefs" id="settings_subs">
|
||||
|
@ -104,6 +104,18 @@
|
||||
<img loading="lazy" id="sub_icon" src="{{ sub.icon }}" alt="Icon for r/{{ sub.name }}">
|
||||
<h1 id="sub_title">{{ sub.title }}</h1>
|
||||
<p id="sub_name">r/{{ sub.name }}</p>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<a href="/r/{{ sub.name }}.rss" title="RSS feed for r/{{ sub.name }}">
|
||||
<button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3zM8.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3M8 10.5a1 1 0 1 0 0 2a3.5 3.5 0 0 1 3.5 3.5a1 1 0 1 0 2 0A5.5 5.5 0 0 0 8 10.5M8.5 7q-.285 0-.566.019a1 1 0 0 0 .132 1.995a6.5 6.5 0 0 1 6.92 6.92a1 1 0 1 0 1.995.132A8.5 8.5 0 0 0 8.5 7" />
|
||||
</g>
|
||||
</svg>
|
||||
</button >
|
||||
</a>
|
||||
{% endif %}
|
||||
<p id="sub_description">{{ sub.description }}</p>
|
||||
<div id="sub_details">
|
||||
<label>Members</label>
|
||||
@ -134,13 +146,31 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<div id="sub_rss">
|
||||
<a href="/r/{{ sub.name }}.rss" title="RSS feed for r/{{ sub.name }}">
|
||||
<button class="subscribe">RSS feed</button >
|
||||
</a>
|
||||
<div id="sub_quicklist">
|
||||
{% if prefs.quicklist.contains(sub.name) %}
|
||||
<form action="/r/{{ sub.name }}/unquicklist?redirect={{ redirect_url }}" method="POST">
|
||||
<button>
|
||||
<svg class="unquick" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M16 2a3 3 0 0 1 3 3v11h-3a3 3 0 0 0-2.997 2.87L12 18.202l-4.668 3.112C6.335 21.978 5 21.264 5 20.066V5a3 3 0 0 1 3-3zm6 16a1 1 0 0 1 .117 1.993L22 20h-6a1 1 0 0 1-.117-1.993L16 18z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/r/{{ sub.name }}/quicklist?redirect={{ redirect_url }}" method="POST">
|
||||
<button>
|
||||
<svg class="quick" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M10 2a3 3 0 0 0-3 3a3 3 0 0 0-3 3v13.018c0 1.226 1.39 1.934 2.382 1.213l4.118-2.995l4.118 2.995c.991.721 2.382.013 2.382-1.213v-2.236l.618.45c.991.72 2.382.012 2.382-1.214V5a3 3 0 0 0-3-3zm7 14.309l1 .727V5a1 1 0 0 0-1-1h-7a1 1 0 0 0-1 1h5a3 3 0 0 1 3 3z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
<details class="panel" id="sidebar" open>
|
||||
|
@ -123,6 +123,18 @@ body %}
|
||||
/>
|
||||
<h1 id="user_title">{{ user.title }}</h1>
|
||||
<p id="user_name">u/{{ user.name }}</p>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<a href="/r/{{ user.name }}.rss" title="RSS feed for r/{{ user.name }}">
|
||||
<button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3zM8.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3M8 10.5a1 1 0 1 0 0 2a3.5 3.5 0 0 1 3.5 3.5a1 1 0 1 0 2 0A5.5 5.5 0 0 0 8 10.5M8.5 7q-.285 0-.566.019a1 1 0 0 0 .132 1.995a6.5 6.5 0 0 1 6.92 6.92a1 1 0 1 0 1.995.132A8.5 8.5 0 0 0 8.5 7" />
|
||||
</g>
|
||||
</svg>
|
||||
</button >
|
||||
</a>
|
||||
{% endif %}
|
||||
<div id="user_description">{{ user.description }}</div>
|
||||
<div id="user_details">
|
||||
<label>Karma</label>
|
||||
@ -166,16 +178,31 @@ body %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<div id="user_rss">
|
||||
<a
|
||||
href="/u/{{ user.name }}.rss"
|
||||
title="RSS feed for u/{{ user.name }}"
|
||||
>
|
||||
<button class="subscribe">RSS feed</button>
|
||||
</a>
|
||||
<div id="user_quicklist">
|
||||
{% if prefs.quicklist.contains(name) %}
|
||||
<form action="/r/{{ name }}/unquicklist?redirect={{ redirect_url }}" method="POST">
|
||||
<button>
|
||||
<svg class="unquick" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M16 2a3 3 0 0 1 3 3v11h-3a3 3 0 0 0-2.997 2.87L12 18.202l-4.668 3.112C6.335 21.978 5 21.264 5 20.066V5a3 3 0 0 1 3-3zm6 16a1 1 0 0 1 .117 1.993L22 20h-6a1 1 0 0 1-.117-1.993L16 18z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/r/{{ name }}/quicklist?redirect={{ redirect_url }}" method="POST">
|
||||
<button>
|
||||
<svg class="quick" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036q-.016-.004-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z" />
|
||||
<path fill="currentColor" d="M10 2a3 3 0 0 0-3 3a3 3 0 0 0-3 3v13.018c0 1.226 1.39 1.934 2.382 1.213l4.118-2.995l4.118 2.995c.991.721 2.382.013 2.382-1.213v-2.236l.618.45c.991.72 2.382.012 2.382-1.214V5a3 3 0 0 0-3-3zm7 14.309l1 .727V5a1 1 0 0 0-1-1h-7a1 1 0 0 0-1 1h5a3 3 0 0 1 3 3z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
@ -41,6 +41,12 @@
|
||||
<details id="feeds">
|
||||
<summary>Feeds</summary>
|
||||
<div id="feed_list">
|
||||
{% if prefs.quicklist.len() > 0 %}
|
||||
<p>QUICK ACCESS FEEDS</p>
|
||||
{% for sub in prefs.quicklist %}
|
||||
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{% if sub.starts_with("u_") -%}{%let sub = format!("u/{}", &sub[2..]) -%}{{ sub }}{% else -%}{{ sub }}{% endif -%}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<p>MAIN FEEDS</p>
|
||||
<a href="/">Home</a>
|
||||
<a href="/r/popular">Popular</a>
|
||||
@ -48,7 +54,7 @@
|
||||
{% if prefs.subscriptions.len() > 0 %}
|
||||
<p>REDDIT FEEDS</p>
|
||||
{% for sub in prefs.subscriptions %}
|
||||
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{{ sub }}</a>
|
||||
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{% if sub.starts_with("u_") -%}{%let sub = format!("u/{}", &sub[2..]) -%}{{ sub }}{% else -%}{{ sub }}{% endif -%}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user