Compare commits

..

4 Commits

Author SHA1 Message Date
e18d8eb471 fix style 2024-04-05 00:35:14 +13:00
f2c7454bf4 quick build for a quick fix 2024-04-05 00:35:01 +13:00
992257a8b1 indent fuck brain not working 2024-04-04 23:01:12 +13:00
d49d32fbc6 embed images 2024-04-04 22:59:20 +13:00
39 changed files with 460 additions and 757 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
target
Dockerfile
.dockerignore
.git
.gitignore

View File

@ -9,7 +9,7 @@ REDLIB_BANNER=
# Disable search engine indexing
REDLIB_ROBOTS_DISABLE_INDEXING=off
# Set the Pushshift frontend for "removed" links
REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io
REDLIB_PUSHSHIFT_FRONTEND=www.unddit.com
# Default user settings
# Set the default theme (options: system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight)
@ -38,8 +38,6 @@ REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off
REDLIB_DEFAULT_SUBSCRIPTIONS=
# Hide awards by default
REDLIB_DEFAULT_HIDE_AWARDS=off
# Hide sidebar and summary
REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=off
# Disable the confirmation before visiting Reddit
REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off
# Hide score by default

View File

@ -30,15 +30,9 @@ jobs:
with:
toolchain: stable
- name: Install musl-gcc
run: sudo apt-get install musl-tools
- name: Install cargo musl target
run: rustup target add x86_64-unknown-linux-musl
# Building actions
- name: Build
run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-musl
run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu
- name: Versions
id: version
@ -51,17 +45,17 @@ jobs:
run: cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Calculate SHA512 checksum
run: sha512sum target/x86_64-unknown-linux-musl/release/redlib > redlib.sha512
run: sha512sum target/x86_64-unknown-linux-gnu/release/redlib > redlib.sha512
- name: Calculate SHA256 checksum
run: sha256sum target/x86_64-unknown-linux-musl/release/redlib > redlib.sha256
run: sha256sum target/x86_64-unknown-linux-gnu/release/redlib > redlib.sha256
- uses: actions/upload-artifact@v3
name: Upload a Build Artifact
with:
name: redlib
path: |
target/x86_64-unknown-linux-musl/release/redlib
target/x86_64-unknown-linux-gnu/release/redlib
redlib.sha512
redlib.sha256
@ -74,7 +68,7 @@ jobs:
name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }}
draft: true
files: |
target/x86_64-unknown-linux-musl/release/redlib
target/x86_64-unknown-linux-gnu/release/redlib
redlib.sha512
redlib.sha256
body: |

View File

@ -1,2 +1,2 @@
run = "while :; do set -ex; nix-env -iA nixpkgs.unzip; curl -o./redlib.zip -fsSL -- https://nightly.link/redlib-org/redlib/workflows/main-rust/main/redlib.zip; unzip -n redlib.zip; mv target/x86_64-unknown-linux-musl/release/redlib .; chmod +x redlib; set +e; ./redlib -H 63115200; sleep 1; done"
run = "while :; do set -ex; nix-env -iA nixpkgs.unzip; curl -o./redlib.zip -fsSL -- https://nightly.link/redlib-org/redlib/workflows/main-rust/main/redlib.zip; unzip -n redlib.zip; mv target/x86_64-unknown-linux-gnu/release/redlib .; chmod +x redlib; set +e; ./redlib -H 63115200; sleep 1; done"
language = "bash"

497
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
[package]
name = "redsunlib"
name = "redlib"
description = " Alternative private front-end to Reddit"
license = "AGPL-3.0"
repository = "https://git.stardust.wtf/iridium/redlib"
version = "0.34.0"
repository = "https://github.com/redlib-org/redlib"
version = "0.31.2"
authors = [
"Matthew Esposito <matt+cargo@matthew.science>",
"spikecodes <19519553+spikecodes@users.noreply.github.com>",
@ -12,7 +12,7 @@ edition = "2021"
[dependencies]
askama = { version = "0.12.1", default-features = false }
cached = { version = "0.51.3", features = ["async"] }
cached = { version = "0.48.1", features = ["async"] }
clap = { version = "4.4.11", default-features = false, features = [
"std",
"env",
@ -31,13 +31,13 @@ time = { version = "0.3.31", features = ["local-offset"] }
url = "2.5.0"
rust-embed = { version = "8.1.0", features = ["include-exclude"] }
libflate = "2.0.0"
brotli = { version = "6.0.0", features = ["std"] }
brotli = { version = "3.4.0", features = ["std"] }
toml = "0.8.8"
once_cell = "1.19.0"
serde_yaml = "0.9.29"
build_html = "2.4.0"
uuid = { version = "1.6.1", features = ["v4"] }
base64 = "0.22.1"
base64 = "0.21.5"
fastrand = "2.0.1"
log = "0.4.20"
pretty_env_logger = "0.5.0"

View File

@ -1,34 +1,32 @@
## Builder
FROM rust:1.77.1-buster AS builder
FROM rust:alpine AS builder
WORKDIR /app
RUN apk add --no-cache musl-dev git
COPY ./ ./
WORKDIR /redsunlib
RUN cargo build --release
COPY . .
FROM debian:stable
RUN cargo build --target x86_64-unknown-linux-musl --release
ARG TARGET
## Final image
RUN apt-get update
RUN apt-get install -y ca-certificates
FROM alpine:latest
RUN apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Import ca-certificates from builder
COPY --from=builder /usr/share/ca-certificates /usr/share/ca-certificates
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
COPY --from=builder /app/target/release/ /usr/local/bin
# Copy our build
COPY --from=builder /redsunlib/target/x86_64-unknown-linux-musl/release/redsunlib /usr/local/bin/redsunlib
RUN adduser --home /nonexistent --no-create-home --disabled-password redlib
# Use an unprivileged user.
RUN adduser --home /nonexistent --no-create-home --disabled-password redsunlib
USER redsunlib
USER redlib
# Tell Docker to expose port 8080
# Tell Docker to expose port 808
EXPOSE 8080
# Run a healthcheck every minute to make sure redsunlib is functional
HEALTHCHECK --interval=1m --timeout=3s CMD wget --spider -q http://localhost:8080/settings || exit 1
# Run a healthcheck every minute to make sure redlib is functional
HEALTHCHECK --interval=1m --timeout=3s CMD wget --spider --q http://localhost:8080/settings || exit 1
CMD ["redlib"]
CMD ["redsunlib"]

View File

@ -1,11 +1,19 @@
<img align="left" width="128" height="128" src="https://git.stardust.wtf/attachments/842086e3-b718-4379-b718-c3a542842152" alt="logo">
# Redlib
# Redsunlib
> An alternative private front-end to Reddit, a fork of [Redlib](https://github.com/redlib-org/redlib) with some function and cosmetic changes.
> An alternative private front-end to Reddit, with its origins in [Libreddit](https://github.com/libreddit/libreddit).
<br>
![screenshot](https://i.ibb.co/QYbqTQt/libreddit-rust.png)
![screenshot](https://git.stardust.wtf/attachments/7667e4e2-a32c-4269-9b5f-1d29cb3baf20)
---
**10-second pitch:** Redlib is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://redlib.matthew.science/r/unpopularopinion) without being [tracked](#reddit).
- 🚀 Fast: written in Rust for blazing-fast speeds and memory safety
- ☁️ Light: no JavaScript, no ads, no tracking, no bloat
- 🕵 Private: all requests are proxied through the server, including media
- 🔒 Secure: strong [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents browser requests to Reddit
---
## Table of Contents
@ -22,6 +30,7 @@
- [Reddit](#reddit)
- [Redlib](#redlib-1)
- [Server](#server)
- [Official instance (redlib.matthew.science)](#official-instance-redlibmatthewscience)
5. [Deployment](#deployment)
- [Docker](#docker)
- [Docker Compose](#docker-compose)
@ -52,6 +61,9 @@ For information on instance uptime, see the [Uptime Robot status page](https://s
# About
> [!NOTE]
> Find Redlib on 💬 [Matrix](https://matrix.to/#/#redlib:matrix.org), 🐋 [Quay.io](https://quay.io/repository/redlib/redlib), :octocat: [GitHub](https://github.com/redlib-org/redlib), and 🦊 [GitLab](https://gitlab.com/redlib/redlib).
Redlib hopes to provide an easier way to browse Reddit, without the ads, trackers, and bloat. Redlib was inspired by other alternative front-ends to popular services such as [Invidious](https://github.com/iv-org/invidious) for YouTube, [Nitter](https://github.com/zedeus/nitter) for Twitter, and [Bibliogram](https://sr.ht/~cadence/bibliogram/) for Instagram.
Redlib currently implements most of Reddit's (signed-out) functionalities but still lacks [a few features](https://github.com/redlib-org/redlib/issues).
@ -146,6 +158,16 @@ For transparency, I hope to describe all the ways Redlib handles user privacy.
- **Cookies:** Redlib uses optional cookies to store any configured settings in [the settings menu](https://redlib.matthew.science/settings). These are not cross-site cookies and the cookies hold no personal data.
#### Official instance (redlib.matthew.science)
The official instance is hosted at https://redlib.matthew.science.
- **Server:** The official instance runs a production binary, and thus logs nothing.
- **DNS:** The domain for the official instance uses Cloudflare as the DNS resolver. However, this site is not proxied through Cloudflare, and thus Cloudflare doesn't have access to user traffic.
- **Hosting:** The official instance is hosted on [Replit](https://replit.com/), which monitors usage to prevent abuse. I can understand if this invalidates certain users' threat models, and therefore, self-hosting, using unofficial instances, and browsing through Tor are welcomed.
---
# Deployment
@ -358,13 +380,12 @@ REDLIB_DEFAULT_USE_HLS = "on"
Assign a default value for each instance-specific setting by passing environment variables to Redlib in the format `REDLIB_{X}`. Replace `{X}` with the setting name (see list below) in capital letters.
| Name | Possible values | Default value | Description |
| ------------------------- | --------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. |
| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. |
| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. |
| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. |
| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. |
| Name | Possible values | Default value | Description |
| ------------------------- | --------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. |
| `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. |
| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. |
| `PUSHSHIFT_FRONTEND` | String | `www.unddit.com` | Allows the server to set the Pushshift frontend to be used with "removed" links. |
## Default user settings
@ -372,7 +393,7 @@ Assign a default value for each user-modifiable setting by passing environment v
| Name | Possible values | Default value |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `THEME` | `["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox", "gruvboxdark", "gruvboxlight", "tokyoNight", "icebergDark"]` | `system` |
| `THEME` | `["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox", "gruvboxdark", "gruvboxlight"]` | `system` |
| `FRONT_PAGE` | `["default", "popular", "all"]` | `default` |
| `LAYOUT` | `["card", "clean", "compact"]` | `card` |
| `WIDE` | `["on", "off"]` | `off` |

View File

@ -2,25 +2,25 @@
version: "3.8"
services:
redsunlib:
redlib:
build: .
restart: always
container_name: "redsunlib"
container_name: "redlib"
ports:
- 8080:8080 # Specify `127.0.0.1:8080:8080` instead if using a reverse proxy
user: nobody
read_only: true
security_opt:
- no-new-privileges:true
# - seccomp=seccomp-redsunlib.json
# - seccomp=seccomp-redlib.json
cap_drop:
- ALL
networks:
- redsunlib
- redlib
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
interval: 5m
timeout: 3s
networks:
redsunlib:
redlib:

View File

@ -1,24 +1,26 @@
services:
redsunlib:
image: git.stardust.wtf/iridium/redsunlib:latest
redlib:
image: quay.io/redlib/redlib:latest
# image: quay.io/redlib/redlib:latest-arm # uncomment if you use arm64
# image: quay.io/redlib/redlib:latest-armv7 # uncomment if you use armv7
restart: always
container_name: "redsunlib"
container_name: "redlib"
ports:
- 8080:8080 # Specify `127.0.0.1:8080:8080` instead if using a reverse proxy
user: nobody
read_only: true
security_opt:
- no-new-privileges:true
# - seccomp=seccomp-redsunlib.json
# - seccomp=seccomp-redlib.json
cap_drop:
- ALL
env_file: .env
networks:
- redsunlib
- redlib
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
interval: 5m
timeout: 3s
networks:
redsunlib:
redlib:

View File

@ -170,7 +170,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
// Construct the hyper client from the HTTPS connector.
let client: Client<_, Body> = CLIENT.clone();
let (token, vendor_id, device_id, mut user_agent, loid) = {
let (token, vendor_id, device_id, user_agent, loid) = {
let client = block_on(OAUTH_CLIENT.read());
(
client.token.clone(),
@ -180,14 +180,6 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
client.headers_map.get("x-reddit-loid").cloned().unwrap_or_default(),
)
};
// Replace "Android" with a tricky word.
// Issues: #78/#115, #116
// If you include the word "Android", you will get a number of different errors
// I guess they don't expect mobile traffic on the endpoints we use
// Scrawled on wall for next poor soul: Run the test suite.
user_agent = user_agent.replace("Android", "Andr\u{200B}oid");
// Build request to Reddit. When making a GET, request gzip compression.
// (Reddit doesn't do brotli yet.)
let builder = Request::builder()

View File

@ -60,10 +60,6 @@ pub struct Config {
#[serde(alias = "LIBREDDIT_DEFAULT_USE_HLS")]
pub(crate) default_use_hls: Option<String>,
#[serde(rename = "REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS")]
#[serde(alias = "LIBREDDIT_DEFAULT_FFMPEG_VIDEO_DOWNLOADS")]
pub(crate) default_ffmpeg_video_downloads: Option<String>,
#[serde(rename = "REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION")]
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
pub(crate) default_hide_hls_notification: Option<String>,
@ -72,10 +68,6 @@ pub struct Config {
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_AWARDS")]
pub(crate) default_hide_awards: Option<String>,
#[serde(rename = "REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY")]
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY")]
pub(crate) default_hide_sidebar_and_summary: Option<String>,
#[serde(rename = "REDLIB_DEFAULT_HIDE_SCORE")]
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_SCORE")]
pub(crate) default_hide_score: Option<String>,
@ -133,10 +125,8 @@ impl Config {
default_show_nsfw: parse("REDLIB_DEFAULT_SHOW_NSFW"),
default_blur_nsfw: parse("REDLIB_DEFAULT_BLUR_NSFW"),
default_use_hls: parse("REDLIB_DEFAULT_USE_HLS"),
default_ffmpeg_video_downloads: parse("REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS"),
default_hide_hls_notification: parse("REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION"),
default_hide_hls_notification: parse("REDLIB_DEFAULT_HIDE_HLS"),
default_hide_awards: parse("REDLIB_DEFAULT_HIDE_AWARDS"),
default_hide_sidebar_and_summary: parse("REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY"),
default_hide_score: parse("REDLIB_DEFAULT_HIDE_SCORE"),
default_subscriptions: parse("REDLIB_DEFAULT_SUBSCRIPTIONS"),
default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"),
@ -158,11 +148,9 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
"REDLIB_DEFAULT_SHOW_NSFW" => config.default_show_nsfw.clone(),
"REDLIB_DEFAULT_BLUR_NSFW" => config.default_blur_nsfw.clone(),
"REDLIB_DEFAULT_USE_HLS" => config.default_use_hls.clone(),
"REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS" => config.default_ffmpeg_video_downloads.clone(),
"REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION" => config.default_hide_hls_notification.clone(),
"REDLIB_DEFAULT_WIDE" => config.default_wide.clone(),
"REDLIB_DEFAULT_HIDE_AWARDS" => config.default_hide_awards.clone(),
"REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY" => config.default_hide_awards.clone(),
"REDLIB_DEFAULT_HIDE_SCORE" => config.default_hide_score.clone(),
"REDLIB_DEFAULT_SUBSCRIPTIONS" => config.default_subscriptions.clone(),
"REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION" => config.default_disable_visit_reddit_confirmation.clone(),

View File

@ -151,7 +151,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
}
if have_after {
"t3_".clone_into(&mut before);
before = "t3_".to_owned();
before.push_str(&duplicates[0].id);
}
@ -161,7 +161,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
if have_before {
// The next batch will need to start from one after the
// last post in the current batch.
"t3_".clone_into(&mut after);
after = "t3_".to_owned();
after.push_str(&duplicates[l - 1].id);
// Here is where things get terrible. Notice that we
@ -182,7 +182,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
match json(new_path, true).await {
Ok(response) => {
if !response[1]["data"]["children"].as_array().unwrap_or(&Vec::new()).is_empty() {
"t3_".clone_into(&mut before);
before = "t3_".to_owned();
before.push_str(&duplicates[0].id);
}
}

View File

@ -78,17 +78,6 @@ async fn font() -> Result<Response<Body>, String> {
)
}
async fn ffmpeg() -> Result<Response<Body>, String> {
Ok(
Response::builder()
.status(200)
.header("content-type", "application/wasm")
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
.body(include_bytes!("../static/ffmpeg/ffmpeg-core.wasm").as_ref().into())
.unwrap_or_default(),
)
}
async fn resource(body: &str, content_type: &str, cache: bool) -> Result<Response<Body>, String> {
let mut res = Response::builder()
.status(200)
@ -177,7 +166,7 @@ async fn main() {
let listener = [address, ":", port].concat();
println!("Starting Redsunlib...");
println!("Starting Redlib...");
// Begin constructing a server
let mut app = server::Server::new();
@ -243,26 +232,6 @@ async fn main() {
app
.at("/highlighted.js")
.get(|_| resource(include_str!("../static/highlighted.js"), "text/javascript", false).boxed());
// FFmpeg
app
.at("/ffmpeg/814.ffmpeg.js")
.get(|_| resource(include_str!("../static/ffmpeg/814.ffmpeg.js"), "text/javascript", false).boxed());
app
.at("/ffmpeg/814.ffmpeg.js.map")
.get(|_| resource(include_str!("../static/ffmpeg/814.ffmpeg.js.map"), "text/javascript", false).boxed());
app
.at("/ffmpeg/ffmpeg-core.js")
.get(|_| resource(include_str!("../static/ffmpeg/ffmpeg-core.js"), "text/javascript", false).boxed());
app.at("/ffmpeg/ffmpeg-core.wasm").get(|_| ffmpeg().boxed());
app
.at("/ffmpeg/ffmpeg-util.js")
.get(|_| resource(include_str!("../static/ffmpeg/ffmpeg-util.js"), "text/javascript", false).boxed());
app
.at("/ffmpeg/ffmpeg.js")
.get(|_| resource(include_str!("../static/ffmpeg/ffmpeg.js"), "text/javascript", false).boxed());
app
.at("/ffmpeg/ffmpeg.js.map")
.get(|_| resource(include_str!("../static/ffmpeg/ffmpeg.js.map"), "text/javascript", false).boxed());
// Proxy media through Redlib
app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed());
@ -404,7 +373,7 @@ async fn main() {
// Default service in case no routes match
app.at("/*").get(|req| error(req, "Nothing here").boxed());
println!("Running Redsunlib v{} on {listener}!", env!("CARGO_PKG_VERSION"));
println!("Running Redlib v{} on {listener}!", env!("CARGO_PKG_VERSION"));
let server = app.listen(&listener);

View File

@ -4,44 +4,6 @@
// Filled in with real app versions
pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"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 2024.02.0/Build 1368985",
"Version 2024.03.0/Build 1379408",
"Version 2024.04.0/Build 1391236",
"Version 2024.05.0/Build 1403584",
"Version 2024.06.0/Build 1418489",
"Version 2024.07.0/Build 1429651",
"Version 2024.08.0/Build 1439531",
"Version 2024.10.0/Build 1470045",
"Version 2024.10.1/Build 1478645",
"Version 2024.11.0/Build 1480707",
"Version 2024.12.0/Build 1494694",
"Version 2024.13.0/Build 1505187",
"Version 2024.14.0/Build 1520556",
"Version 2024.15.0/Build 1536823",
"Version 2024.16.0/Build 1551366",
"Version 2024.17.0/Build 1568106",
"Version 2024.18.0/Build 1577901",
"Version 2024.18.1/Build 1585304",
"Version 2024.19.0/Build 1593346",
"Version 2024.20.0/Build 1612800",
"Version 2024.20.1/Build 1615586",
"Version 2024.20.2/Build 1624969",
"Version 2024.21.0/Build 1631686",
"Version 2024.22.0/Build 1645257",
"Version 2024.22.1/Build 1652272",
"Version 2023.21.0/Build 956283",
"Version 2023.22.0/Build 968223",
"Version 2023.23.0/Build 983896",
"Version 2023.24.0/Build 998541",
"Version 2023.25.0/Build 1014750",
"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",
@ -64,14 +26,14 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2023.44.0/Build 1268622",
"Version 2023.45.0/Build 1281371",
"Version 2023.47.0/Build 1303604",
"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 2023.02.0/Build 717912",
"Version 2023.03.0/Build 729220",
"Version 2023.04.0/Build 744681",
"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 2024.02.0/Build 1368985",
"Version 2024.03.0/Build 1379408",
"Version 2024.04.0/Build 1391236",
"Version 2023.05.0/Build 755453",
"Version 2023.06.0/Build 775017",
"Version 2023.07.0/Build 788827",
@ -94,14 +56,14 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2023.19.0/Build 927681",
"Version 2023.20.0/Build 943980",
"Version 2023.20.1/Build 946732",
"Version 2022.20.0/Build 487703",
"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.24.0/Build 510950",
"Version 2022.24.1/Build 513462",
"Version 2022.25.0/Build 515072",
"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 2023.25.1/Build 1018737",
"Version 2023.26.0/Build 1019073",
"Version 2023.27.0/Build 1031923",
"Version 2022.25.1/Build 516394",
"Version 2022.25.2/Build 519915",
"Version 2022.26.0/Build 521193",
@ -124,14 +86,14 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2022.40.0/Build 624782",
"Version 2022.41.0/Build 630468",
"Version 2022.41.1/Build 634168",
"Version 2021.39.1/Build 372418",
"Version 2021.41.0/Build 376052",
"Version 2021.42.0/Build 378193",
"Version 2021.43.0/Build 382019",
"Version 2021.44.0/Build 385129",
"Version 2021.45.0/Build 387663",
"Version 2021.46.0/Build 392043",
"Version 2021.47.0/Build 394342",
"Version 2022.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 2023.02.0/Build 717912",
"Version 2023.03.0/Build 729220",
"Version 2023.04.0/Build 744681",
"Version 2022.10.0/Build 429896",
"Version 2022.1.0/Build 402829",
"Version 2022.11.0/Build 433004",
@ -144,7 +106,15 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"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.24.0/Build 510950",
"Version 2022.24.1/Build 513462",
"Version 2022.25.0/Build 515072",
"Version 2022.3.0/Build 408637",
"Version 2022.4.0/Build 411368",
"Version 2022.5.0/Build 414731",
@ -154,5 +124,35 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2022.7.0/Build 420849",
"Version 2022.8.0/Build 423906",
"Version 2022.9.0/Build 426592",
"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",
"Version 2021.45.0/Build 387663",
"Version 2021.46.0/Build 392043",
"Version 2021.47.0/Build 394342",
];
pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];

View File

@ -1,5 +1,3 @@
#![allow(dead_code)]
use brotli::enc::{BrotliCompress, BrotliEncoderParams};
use cached::proc_macro::cached;
use cookie::Cookie;
@ -17,7 +15,6 @@ use libflate::gzip;
use route_recognizer::{Params, Router};
use std::{
cmp::Ordering,
fmt::Display,
io,
pin::Pin,
result::Result,
@ -68,12 +65,12 @@ impl CompressionType {
}
}
impl Display for CompressionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl ToString for CompressionType {
fn to_string(&self) -> String {
match self {
Self::Gzip => write!(f, "gzip"),
Self::Brotli => write!(f, "br"),
Self::Passthrough => Ok(()),
Self::Gzip => "gzip".to_string(),
Self::Brotli => "br".to_string(),
Self::Passthrough => String::new(),
}
}
}

View File

@ -19,7 +19,7 @@ struct SettingsTemplate {
// CONSTANTS
const PREFS: [&str; 17] = [
const PREFS: [&str; 15] = [
"theme",
"front_page",
"layout",
@ -29,10 +29,8 @@ const PREFS: [&str; 17] = [
"show_nsfw",
"blur_nsfw",
"use_hls",
"ffmpeg_video_downloads",
"hide_hls_notification",
"autoplay_videos",
"hide_sidebar_and_summary",
"fixed_navbar",
"hide_awards",
"hide_score",

View File

@ -7,8 +7,6 @@ use askama::Template;
use cookie::Cookie;
use hyper::{Body, Request, Response};
use once_cell::sync::Lazy;
use regex::Regex;
use time::{Duration, OffsetDateTime};
// STRUCTS
@ -52,13 +50,10 @@ struct WallTemplate {
url: String,
}
static GEO_FILTER_MATCH: Lazy<Regex> = Lazy::new(|| Regex::new(r"geo_filter=(?<region>\w+)").unwrap());
// SERVICES
pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
// Build Reddit API path
let root = req.uri().path() == "/";
let query = req.uri().query().unwrap_or_default().to_string();
let subscribed = setting(&req, "subscriptions");
let front_page = setting(&req, "front_page");
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
@ -112,11 +107,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
let mut params = String::from("&raw_json=1");
if sub_name == "popular" {
let geo_filter = match GEO_FILTER_MATCH.captures(&query) {
Some(geo_filter) => geo_filter["region"].to_string(),
None => "GLOBAL".to_owned(),
};
params.push_str(&format!("&geo_filter={geo_filter}"));
params.push_str("&geo_filter=GLOBAL");
}
let path = format!("/r/{sub_name}/{sort}.json?{}{params}", req.uri().query().unwrap_or_default());

View File

@ -577,9 +577,7 @@ pub struct Preferences {
pub show_nsfw: String,
pub blur_nsfw: String,
pub hide_hls_notification: String,
pub hide_sidebar_and_summary: String,
pub use_hls: String,
pub ffmpeg_video_downloads: String,
pub autoplay_videos: String,
pub fixed_navbar: String,
pub disable_visit_reddit_confirmation: String,
@ -613,10 +611,8 @@ impl Preferences {
layout: setting(req, "layout"),
wide: setting(req, "wide"),
show_nsfw: setting(req, "show_nsfw"),
hide_sidebar_and_summary: setting(req, "hide_sidebar_and_summary"),
blur_nsfw: setting(req, "blur_nsfw"),
use_hls: setting(req, "use_hls"),
ffmpeg_video_downloads: setting(req, "ffmpeg_video_downloads"),
hide_hls_notification: setting(req, "hide_hls_notification"),
autoplay_videos: setting(req, "autoplay_videos"),
fixed_navbar: setting_or_default(req, "fixed_navbar", "on".to_string()),
@ -879,18 +875,17 @@ pub fn format_url(url: &str) -> String {
// These are links we want to replace in-body
static REDDIT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|new\.|)(reddit\.com|redd\.it)/"#).unwrap());
static REDDIT_PREVIEW_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https?://(external-preview|preview|i)\.redd\.it(.*)[^?]").unwrap());
static REDDIT_PREVIEW_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https?://(external-preview|preview)\.redd\.it(.*)[^?]").unwrap());
static REDDIT_EMOJI_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https?://(www|).redditstatic\.com/(.*)").unwrap());
static REDLIB_PREVIEW_LINK_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r#"/(img|preview/)(pre|external-pre)?/(.*?)>"#).unwrap());
static REDLIB_PREVIEW_TEXT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r">(.*?)</a>").unwrap());
// Rewrite Reddit links to Redlib in body of text
pub fn rewrite_urls(input_text: &str) -> String {
let mut text1 =
let text1 =
// Rewrite Reddit links to Redlib
REDDIT_REGEX.replace_all(input_text, r#"href="/"#)
.to_string();
text1 = REDDIT_EMOJI_REGEX
.to_string()
.replace("a href=\"https://preview.redd.it", "img src=\"https://preview.redd.it");
let text1 = REDDIT_EMOJI_REGEX
.replace_all(&text1, format_url(REDDIT_EMOJI_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default()))
.to_string()
// Remove (html-encoded) "\" from URLs.
@ -898,56 +893,12 @@ pub fn rewrite_urls(input_text: &str) -> String {
.replace("\\_", "_");
// Rewrite external media previews to Redlib
loop {
if REDDIT_PREVIEW_REGEX.find(&text1).is_none() {
return text1;
} else {
let formatted_url = format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default());
let image_url = REDLIB_PREVIEW_LINK_REGEX.find(&formatted_url).map_or("", |m| m.as_str()).to_string();
let mut image_caption = REDLIB_PREVIEW_TEXT_REGEX.find(&formatted_url).map_or("", |m| m.as_str()).to_string();
/* As long as image_caption isn't empty remove first and last four characters of image_text to leave us with just the text in the caption without any HTML.
This makes it possible to enclose it in a <figcaption> later on without having stray HTML breaking it */
if !image_caption.is_empty() {
image_caption = image_caption[1..image_caption.len() - 4].to_string();
}
// image_url contains > at the end of it, and right above this we remove image_text's front >, leaving us with just a single > between them
let image_to_replace = format!("<a href=\"{image_url}{image_caption}</a>");
// _image_replacement needs to be in scope for the replacement at the bottom of the loop
let mut _image_replacement = String::new();
/* We don't want to show a caption that's just the image's link, so we check if we find a Reddit preview link within the image's caption.
If we don't find one we must have actual text, so we include a <figcaption> block that contains it.
Otherwise we don't include the <figcaption> block as we don't need it. */
if REDDIT_PREVIEW_REGEX.find(&image_caption).is_none() {
// Without this " would show as \" instead. "\&quot;" is how the quotes are formatted within image_text beforehand
image_caption = image_caption.replace("\\&quot;", "\"");
_image_replacement = format!("<figure><a href=\"{image_url}<img loading=\"lazy\" src=\"{image_url}</a><figcaption>{image_caption}</figcaption></figure>");
} else {
_image_replacement = format!("<figure><a href=\"{image_url}<img loading=\"lazy\" src=\"{image_url}</a></figure>");
}
/* In order to know if we're dealing with a normal or external preview we need to take a look at the first capture group of REDDIT_PREVIEW_REGEX
if it's preview we're dealing with something that needs /preview/pre, external-preview is /preview/external-pre, and i is /img */
let reddit_preview_regex_capture = REDDIT_PREVIEW_REGEX.captures(&text1).unwrap().get(1).map_or("", |m| m.as_str()).to_string();
let mut _preview_type = String::new();
if reddit_preview_regex_capture == "preview" {
_preview_type = "/preview/pre".to_string();
} else if reddit_preview_regex_capture == "external-preview" {
_preview_type = "/preview/external-pre".to_string();
} else {
_preview_type = "/img".to_string();
}
text1 = REDDIT_PREVIEW_REGEX
.replace(&text1, format!("{_preview_type}$2"))
.replace(&image_to_replace, &_image_replacement)
.to_string()
}
if REDDIT_PREVIEW_REGEX.is_match(&text1) {
REDDIT_PREVIEW_REGEX
.replace_all(&text1, format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default()))
.to_string()
} else {
text1
}
}
@ -1198,11 +1149,3 @@ async fn test_fetching_ws() {
assert!(post.ws_url.starts_with("wss://k8s-lb.wss.redditmedia.com/link/"));
}
}
#[test]
fn test_rewriting_image_links() {
let input =
r#"<p><a href="https://preview.redd.it/6awags382xo31.png?width=2560&amp;format=png&amp;auto=webp&amp;s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc">caption 1</a></p>"#;
let output = r#"<p><figure><a href="/preview/pre/6awags382xo31.png?width=2560&amp;format=png&amp;auto=webp&amp;s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc"><img loading="lazy" src="/preview/pre/6awags382xo31.png?width=2560&amp;format=png&amp;auto=webp&amp;s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc"></a><figcaption>caption 1</figcaption></figure></p"#;
assert_eq!(rewrite_urls(input), output);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 969 B

View File

@ -1,2 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,(()=>(()=>{var e={454:e=>{function t(e){return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}))}t.keys=()=>[],t.resolve=t,t.id=454,e.exports=t}},t={};function r(a){var o=t[a];if(void 0!==o)return o.exports;var s=t[a]={exports:{}};return e[a](s,s.exports,r),s.exports}return r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";var e;!function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"}(e||(e={}));const t=new Error("unknown message type"),a=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),o=(new Error("called FFmpeg.terminate()"),new Error("failed to import ffmpeg-core.js"));let s;self.onmessage=async({data:{id:n,type:E,data:i}})=>{const c=[];let p;try{if(E!==e.LOAD&&!s)throw a;switch(E){case e.LOAD:p=await(async({coreURL:t="https://unpkg.com/@ffmpeg/core@0.12.1/dist/umd/ffmpeg-core.js",wasmURL:a,workerURL:n})=>{const E=!s,i=t,c=a||t.replace(/.js$/g,".wasm"),p=n||t.replace(/.js$/g,".worker.js");try{importScripts(i)}catch{if(self.createFFmpegCore=(await r(454)(i)).default,!self.createFFmpegCore)throw o}return s=await self.createFFmpegCore({mainScriptUrlOrBlob:`${i}#${btoa(JSON.stringify({wasmURL:c,workerURL:p}))}`}),s.setLogger((t=>self.postMessage({type:e.LOG,data:t}))),s.setProgress((t=>self.postMessage({type:e.PROGRESS,data:t}))),E})(i);break;case e.EXEC:p=(({args:e,timeout:t=-1})=>{s.setTimeout(t),s.exec(...e);const r=s.ret;return s.reset(),r})(i);break;case e.WRITE_FILE:p=(({path:e,data:t})=>(s.FS.writeFile(e,t),!0))(i);break;case e.READ_FILE:p=(({path:e,encoding:t})=>s.FS.readFile(e,{encoding:t}))(i);break;case e.DELETE_FILE:p=(({path:e})=>(s.FS.unlink(e),!0))(i);break;case e.RENAME:p=(({oldPath:e,newPath:t})=>(s.FS.rename(e,t),!0))(i);break;case e.CREATE_DIR:p=(({path:e})=>(s.FS.mkdir(e),!0))(i);break;case e.LIST_DIR:p=(({path:e})=>{const t=s.FS.readdir(e),r=[];for(const a of t){const t=s.FS.stat(`${e}/${a}`),o=s.FS.isDir(t.mode);r.push({name:a,isDir:o})}return r})(i);break;case e.DELETE_DIR:p=(({path:e})=>(s.FS.rmdir(e),!0))(i);break;case e.MOUNT:p=(({fsType:e,options:t,mountPoint:r})=>{let a=e,o=s.FS.filesystems[a];return!!o&&(s.FS.mount(o,t,r),!0)})(i);break;case e.UNMOUNT:p=(({mountPoint:e})=>(s.FS.unmount(e),!0))(i);break;default:throw t}}catch(t){return void self.postMessage({id:n,type:e.ERROR,data:t.toString()})}p instanceof Uint8Array&&c.push(p.buffer),self.postMessage({id:n,type:E,data:p},c)}})(),{}})()));
//# sourceMappingURL=814.ffmpeg.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegUtil=t():e.FFmpegUtil=t()}(self,(()=>(()=>{"use strict";var e={591:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.HeaderContentLength=void 0,t.HeaderContentLength="Content-Length"},431:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ERROR_INCOMPLETED_DOWNLOAD=t.ERROR_RESPONSE_BODY_READER=void 0,t.ERROR_RESPONSE_BODY_READER=new Error("failed to get response body reader"),t.ERROR_INCOMPLETED_DOWNLOAD=new Error("failed to complete download")},915:function(e,t,o){var r=this&&this.__awaiter||function(e,t,o,r){return new(o||(o=Promise))((function(n,i){function d(e){try{l(r.next(e))}catch(e){i(e)}}function a(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?n(e.value):(t=e.value,t instanceof o?t:new o((function(e){e(t)}))).then(d,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.toBlobURL=t.downloadWithProgress=t.importScript=t.fetchFile=void 0;const n=o(431),i=o(591);t.fetchFile=e=>r(void 0,void 0,void 0,(function*(){let t;if("string"==typeof e)t=/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(e)?atob(e.split(",")[1]).split("").map((e=>e.charCodeAt(0))):yield(yield fetch(e)).arrayBuffer();else if(e instanceof URL)t=yield(yield fetch(e)).arrayBuffer();else{if(!(e instanceof File||e instanceof Blob))return new Uint8Array;t=yield(o=e,new Promise(((e,t)=>{const r=new FileReader;r.onload=()=>{const{result:t}=r;t instanceof ArrayBuffer?e(new Uint8Array(t)):e(new Uint8Array)},r.onerror=e=>{var o,r;t(Error(`File could not be read! Code=${(null===(r=null===(o=null==e?void 0:e.target)||void 0===o?void 0:o.error)||void 0===r?void 0:r.code)||-1}`))},r.readAsArrayBuffer(o)})))}var o;return new Uint8Array(t)})),t.importScript=e=>r(void 0,void 0,void 0,(function*(){return new Promise((t=>{const o=document.createElement("script"),r=()=>{o.removeEventListener("load",r),t()};o.src=e,o.type="text/javascript",o.addEventListener("load",r),document.getElementsByTagName("head")[0].appendChild(o)}))})),t.downloadWithProgress=(e,t)=>r(void 0,void 0,void 0,(function*(){var o;const r=yield fetch(e);let d;try{const a=parseInt(r.headers.get(i.HeaderContentLength)||"-1"),l=null===(o=r.body)||void 0===o?void 0:o.getReader();if(!l)throw n.ERROR_RESPONSE_BODY_READER;const c=[];let s=0;for(;;){const{done:o,value:r}=yield l.read(),i=r?r.length:0;if(o){if(-1!=a&&a!==s)throw n.ERROR_INCOMPLETED_DOWNLOAD;t&&t({url:e,total:a,received:s,delta:i,done:o});break}c.push(r),s+=i,t&&t({url:e,total:a,received:s,delta:i,done:o})}const f=new Uint8Array(s);let u=0;for(const e of c)f.set(e,u),u+=e.length;d=f.buffer}catch(o){console.log("failed to send download progress event: ",o),d=yield r.arrayBuffer(),t&&t({url:e,total:d.byteLength,received:d.byteLength,delta:0,done:!0})}return d})),t.toBlobURL=(e,o,n=!1,i)=>r(void 0,void 0,void 0,(function*(){const r=n?yield(0,t.downloadWithProgress)(e,i):yield(yield fetch(e)).arrayBuffer(),d=new Blob([r],{type:o});return URL.createObjectURL(d)}))}},t={};return function o(r){var n=t[r];if(void 0!==n)return n.exports;var i=t[r]={exports:{}};return e[r].call(i.exports,i,i.exports,o),i.exports}(915)})()));

View File

@ -1,2 +0,0 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,(()=>(()=>{"use strict";var e={m:{},d:(t,s)=>{for(var r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})},u:e=>e+".ffmpeg.js"};e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var t;e.g.importScripts&&(t=e.g.location+"");var s=e.g.document;if(!t&&s&&(s.currentScript&&(t=s.currentScript.src),!t)){var r=s.getElementsByTagName("script");if(r.length)for(var a=r.length-1;a>-1&&!t;)t=r[a--].src}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=t})(),e.b=document.baseURI||self.location.href;var t,s={};e.r(s),e.d(s,{FFmpeg:()=>i}),function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"}(t||(t={}));const r=(()=>{let e=0;return()=>e++})(),a=(new Error("unknown message type"),new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first")),o=new Error("called FFmpeg.terminate()");new Error("failed to import ffmpeg-core.js");class i{#e=null;#t={};#s={};#r=[];#a=[];loaded=!1;#o=()=>{this.#e&&(this.#e.onmessage=({data:{id:e,type:s,data:r}})=>{switch(s){case t.LOAD:this.loaded=!0,this.#t[e](r);break;case t.MOUNT:case t.UNMOUNT:case t.EXEC:case t.WRITE_FILE:case t.READ_FILE:case t.DELETE_FILE:case t.RENAME:case t.CREATE_DIR:case t.LIST_DIR:case t.DELETE_DIR:this.#t[e](r);break;case t.LOG:this.#r.forEach((e=>e(r)));break;case t.PROGRESS:this.#a.forEach((e=>e(r)));break;case t.ERROR:this.#s[e](r)}delete this.#t[e],delete this.#s[e]})};#i=({type:e,data:t},s=[],o)=>this.#e?new Promise(((a,i)=>{const n=r();this.#e&&this.#e.postMessage({id:n,type:e,data:t},s),this.#t[n]=a,this.#s[n]=i,o?.addEventListener("abort",(()=>{i(new DOMException(`Message # ${n} was aborted`,"AbortError"))}),{once:!0})})):Promise.reject(a);on(e,t){"log"===e?this.#r.push(t):"progress"===e&&this.#a.push(t)}off(e,t){"log"===e?this.#r=this.#r.filter((e=>e!==t)):"progress"===e&&(this.#a=this.#a.filter((e=>e!==t)))}load=(s={},{signal:r}={})=>(this.#e||(this.#e=new Worker(new URL(e.p+e.u(814),e.b),{type:void 0}),this.#o()),this.#i({type:t.LOAD,data:s},void 0,r));exec=(e,s=-1,{signal:r}={})=>this.#i({type:t.EXEC,data:{args:e,timeout:s}},void 0,r);terminate=()=>{const e=Object.keys(this.#s);for(const t of e)this.#s[t](o),delete this.#s[t],delete this.#t[t];this.#e&&(this.#e.terminate(),this.#e=null,this.loaded=!1)};writeFile=(e,s,{signal:r}={})=>{const a=[];return s instanceof Uint8Array&&a.push(s.buffer),this.#i({type:t.WRITE_FILE,data:{path:e,data:s}},a,r)};mount=(e,s,r)=>this.#i({type:t.MOUNT,data:{fsType:e,options:s,mountPoint:r}},[]);unmount=e=>this.#i({type:t.UNMOUNT,data:{mountPoint:e}},[]);readFile=(e,s="binary",{signal:r}={})=>this.#i({type:t.READ_FILE,data:{path:e,encoding:s}},void 0,r);deleteFile=(e,{signal:s}={})=>this.#i({type:t.DELETE_FILE,data:{path:e}},void 0,s);rename=(e,s,{signal:r}={})=>this.#i({type:t.RENAME,data:{oldPath:e,newPath:s}},void 0,r);createDir=(e,{signal:s}={})=>this.#i({type:t.CREATE_DIR,data:{path:e}},void 0,s);listDir=(e,{signal:s}={})=>this.#i({type:t.LIST_DIR,data:{path:e}},void 0,s);deleteDir=(e,{signal:s}={})=>this.#i({type:t.DELETE_DIR,data:{path:e}},void 0,s)}return s})()));
//# sourceMappingURL=ffmpeg.js.map

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="0 0 512 512"
id="svg2"
width="512"
height="512"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs2" />
<rect width="512" height="512" fill="#4c082a" />
<g
transform="matrix(0.75272,0,0,0.75272,-1.1596187,-0.37987125)"
id="g2">
<circle
fill="#1a1a1a"
id="circle1"
style="fill:#4c082a;fill-opacity:0"
r="340.10001"
cy="340.32001"
cx="341.10999" />
<path
d="m 320.64,126.73 v 300.8 h 92.264 V 219.61 h 75.803 v -92.83 h -75.803 v -0.0508 z"
fill="#f83240"
id="path1"
style="fill:#f83240;fill-opacity:1" />
<path
d="M 193.1,126.74 V 510.7 h 0.006 v 43.543 h 295.82 v -92.338 h -202.74 v -335.16 z"
fill="#f83240"
id="path2"
style="fill:#f83240;fill-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

View File

@ -112,16 +112,6 @@ pre, form, fieldset, table, th, td, select, input {
font-family: "Inter", sans-serif;
}
html.fixed_navbar {
scroll-padding-top: 50px;
}
@media screen and (max-width: 800px) {
html.fixed_navbar {
scroll-padding-top: 100px;
}
}
body {
background: var(--background);
padding-bottom: var(--footer-height);
@ -187,11 +177,6 @@ nav #redlib {
vertical-align: -2px;
}
figcaption {
margin-top: 5px;
text-align: center;
}
#settings_link {
opacity: 0.8;
margin-left: 10px;
@ -984,6 +969,10 @@ a.search_subreddit:hover {
vertical-align: bottom;
}
.gallery figcaption {
margin-top: 5px;
}
.gallery .outbound_url {
color: var(--accent);
text-overflow: ellipsis;
@ -1009,13 +998,6 @@ a.search_subreddit:hover {
overflow-wrap: anywhere;
}
.post_body img {
max-width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
}
.post_poll {
grid-area: post_poll;
padding: 5px 15px 5px 12px;
@ -1102,7 +1084,7 @@ a.search_subreddit:hover {
display: auto;
}
@media screen and (min-width: 481px) {
@media screen and (min-width: 480px) {
#post_links > li.mobile_item {
display: none;
}
@ -1179,22 +1161,6 @@ a.search_subreddit:hover {
display: flex;
}
.comment img {
max-width: 50%;
height: auto;
}
@media screen and (max-width: 500px) {
.comment img {
max-width: 80%;
height: auto;
}
}
.comment figure {
margin: 0;
}
.comment_left, .comment_right {
display: flex;
flex-direction: column;
@ -1243,6 +1209,10 @@ a.search_subreddit:hover {
min-width: 0;
}
.comment_right img {
width: 50%;
}
.comment_data > * {
margin-right: 5px;
}
@ -1325,83 +1295,6 @@ summary.comment_data {
/* Layouts */
.old .post {
border-radius: 5px;
background: var(--post);
box-shadow: var(--shadow);
display: grid;
transition: 0.2s background;
grid-template: "post_score post_thumbnail post_header" auto
"post_score post_thumbnail post_title" 1fr
"post_score post_thumbnail post_media" auto
"post_score post_thumbnail post_body" auto
"post_score post_thumbnail post_poll" auto
"post_score post_thumbnail post_notification" auto
"post_score post_thumbnail post_footer" auto
/ 4.5em minmax(0, 140px) fit-content(min(90%, 90%))
}
/* .old .post_thumbnail {
margin-left: 20px;
} */
.old .post_score {
background-color: #ff93da08;
border-radius: 8px;
align-self: center;
width: 53px;
padding-left: 5px; /* REMINDER:pls compact testing only */
border-top-width: 0px;
border-top-style: solid;
padding-top: 5px;
padding-bottom: 5px;
padding-right: 5px;
margin-left: 5px;
margin-right: 5px;
}
.old .post_media_content {
max-width: calc(65% - 40px);
}
.old .highlighted > .post_score {
background-color: unset;
align-self: unset;
margin-top: 20px;
margin-left: 20px;
}
.old .post_score .label {
display: block;
text-align: center;
text-anchor: middle;
color: gray;
}
.old .post_preview {
max-height: 72px;
}
.old #column_one {
width: 100%;
max-width: 100%;
}
.old #commentQueryForms {
display: flex;
justify-content: center;
justify-self: start;
}
.old .commentQuery {
margin-left: 20px;
}
.old main {
max-width: 100% !important;
}
.compact .post:not(.highlighted) {
border-radius: 0;
margin: 0;
@ -1517,7 +1410,6 @@ summary.comment_data {
box-shadow: var(--shadow);
margin-left: 20px;
background: var(--foreground);
width: 30%;
}
aside.prefs {
@ -1570,19 +1462,10 @@ input[type="submit"] {
width: 100%;
}
.md > p:not(:first-child):not(:last-child) {
.md > *:not(:first-child) {
margin-top: 20px;
}
.md > figure:first-of-type {
margin-top: 5px;
margin-bottom: 0px;
}
.md > figure:not(:first-of-type) {
margin-top: 10px;
}
.md h1 { font-size: 22px; }
.md h2 { font-size: 20px; }
.md h3 { font-size: 18px; }
@ -1850,4 +1733,4 @@ td, th {
.quality-selector option:hover {
background-color: var(--accent);
color: var(--text);
}
}

View File

@ -1,17 +0,0 @@
/* Catppuccin theme setting */
.catppuccin {
--accent: #b4befe; /* lavender */
--green: #a6e3a1; /* green */
--text: #cdd6f4; /* text */
--foreground: #181825; /* mantle */
--background: #1e1e2e; /* base */
--outside: #11111b; /* crust */
--post: #11111b; /* crust */
--panel-border: none;
--highlighted: #313244; /* surface0 */
--visited: #6c7086; /* overlay0 */
--shadow: 0 0 0 transparent;
--nsfw: #fab387; /* peach */
--admin: #eba0ac; /* maroon */
}

View File

@ -1,14 +0,0 @@
/* icebergDark theme setting */
.icebergDark {
--accent: #85a0c7;
--green: #b5bf82;
--text: #c6c8d1;
--foreground: #454d73;
--background: #161821;
--outside: #1f2233;
--post: #1f2233;
--panel-border: 1px solid #454d73;
--highlighted: #0f1117;
--visited: #0f1117;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

View File

@ -1,7 +1,7 @@
{% import "utils.html" as utils %}
<!DOCTYPE html>
<html lang="en" class="{% if prefs.fixed_navbar == "on" %}fixed_navbar{% endif %}">
<html lang="en">
<head>
{% block head %}
<title>{% block title %}Redlib{% endblock %}</title>
@ -28,14 +28,14 @@
</head>
<body class="
{% if prefs.layout != "" %}{{ prefs.layout }}{% endif %}
{% if prefs.wide == "on" && prefs.layout != "old" %} wide{% endif %}
{% if prefs.wide == "on" %} wide{% endif %}
{% if prefs.theme != "system" %} {{ prefs.theme }}{% endif %}
{% if prefs.fixed_navbar == "on" %} fixed_navbar{% endif %}">
<!-- NAVIGATION BAR -->
<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">red</span><span id="reddit">lib.</span></a>
{% block subscriptions %}{% endblock %}
</div>
{% block search %}{% endblock %}
@ -76,7 +76,7 @@
<a href="/info" title="View instance information">ⓘ View instance info</a>
</div>
<div class="footer-button">
<a href="https://git.stardust.wtf/iridium/redsunlib" title="View code on git.stardust.wtf">&lt;&gt; Code</a>
<a href="https://github.com/redlib-org/redlib" title="View code on GitHub">&lt;&gt; Code</a>
</div>
</footer>
{% endblock %}

View File

@ -31,14 +31,13 @@
<div class="prefs-group">
<label for="layout">Layout:</label>
<select name="layout" id="layout">
{% call utils::options(prefs.layout, ["card", "clean", "compact","old"], "card") %}
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
</select>
</div>
<div class="prefs-group">
<label for="wide">Wide UI:</label>
<input type="hidden" value="off" name="wide">
<input type="checkbox" name="wide" id="wide" {% if prefs.layout == "old" %}disabled{% endif %} {% if prefs.wide == "on" || prefs.layout == "old" %}checked{% endif %}>
{% if prefs.layout == "old" %}<span>ⓘ Wide UI is always enabled with this layout</span>{% endif %}
<input type="checkbox" name="wide" id="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
</fieldset>
<fieldset>
@ -72,16 +71,11 @@
<input type="hidden" value="off" name="autoplay_videos">
<input type="checkbox" name="autoplay_videos" id="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<div class="prefs-group">
<label for="fixed_navbar">Keep navbar fixed</label>
<input type="hidden" value="off" name="fixed_navbar">
<input type="checkbox" name="fixed_navbar" {% if prefs.fixed_navbar == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="hide_sidebar_and_summary">Hide the summary and sidebar</label>
<input type="hidden" value="off" name="hide_sidebar_and_summary">
<input type="checkbox" name="hide_sidebar_and_summary" {% if prefs.hide_sidebar_and_summary == "on" %}checked{% endif %}>
</div>
<input type="hidden" value="off" name="fixed_navbar">
<input type="checkbox" name="fixed_navbar" {% if prefs.fixed_navbar == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="use_hls">Use HLS for videos</label>
<details id="feeds">
@ -91,15 +85,6 @@
<input type="hidden" value="off" name="use_hls">
<input type="checkbox" name="use_hls" id="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="ffmpeg_video_downloads">Use FFmpeg to download videos</label>
<details id="feeds">
<summary>Why?</summary>
<div id="feed_list" class="helper">Downloading videos with audio requires ffmpeg (via ffmpeg.wasm) to be enabled to combine video and audio tracks. Therefore, this toggle lets you either use Redlib WebAssembly-free or utilize this feature. (videos will still play with audio)</div>
</details>
<input type="hidden" value="off" name="ffmpeg_video_downloads">
<input type="checkbox" name="ffmpeg_video_downloads" id="ffmpeg_video_downloads" {% if prefs.ffmpeg_video_downloads == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
<input type="hidden" value="off" name="hide_hls_notification">
@ -157,7 +142,7 @@
<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 }}&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 }}&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 }}&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 }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&fixed_navbar={{ prefs.fixed_navbar }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
</div>
</div>

View File

@ -73,16 +73,16 @@
<footer>
{% if !ends.0.is_empty() %}
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}" accesskey="P"> PREV</a>
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}" accesskey="P">PREV</a>
{% endif %}
{% if !ends.1.is_empty() %}
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}" accesskey="N">NEXT </a>
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}" accesskey="N">NEXT</a>
{% endif %}
</footer>
</div>
{% endif %}
{% if is_filtered || (!sub.name.is_empty() && sub.name != "all" && sub.name != "popular" && !sub.name.contains("+")) && prefs.hide_sidebar_and_summary != "on" %}
{% if is_filtered || (!sub.name.is_empty() && sub.name != "all" && sub.name != "popular" && !sub.name.contains("+")) %}
<aside>
{% if is_filtered %}
<center>(Content from r/{{ sub.name }} has been filtered)</center>
@ -133,7 +133,7 @@
</div>
</div>
</details>
<details class="panel" id="sidebar" open>
<details class="panel" id="sidebar">
<summary id="sidebar_label">Sidebar</summary>
<div id="sidebar_contents">
{{ sub.info|safe }}