Compare commits

...

28 Commits

Author SHA1 Message Date
ff2e274e5e v0.35.0 2024-06-23 20:48:26 +12:00
7c821d541a fix PREFS size
whoops
2024-06-23 20:44:39 +12:00
7aca5791b1 Merge branch 'hls-video-downloads' 2024-06-23 20:41:27 +12:00
8b3783096b revert ugly-layouts from hls-video-downloads
This reverts commits
97def1bab1
feda45f803
2024-06-23 20:33:32 +12:00
59b702aa15 kinda horrific download implementation 2024-06-23 19:57:23 +12:00
edf0109095 allow wasm to run 2024-06-23 19:31:30 +12:00
ecf0f91464 remove failed hls.js source map request 2024-06-23 19:30:44 +12:00
a5a5cbb734 add defaults for mascot fixes some of #7 2024-06-18 21:00:02 +12:00
5117af4137 add hide_sidebar_and_summary to restore link 2024-06-18 20:51:02 +12:00
394e975724 Merge remote-tracking branch 'upstream/main' 2024-06-18 19:57:42 +12:00
a67cbb99a6 sync upstream 2024-06-18 19:56:04 +12:00
ededa849f4 Merge pull request 'mascots' (#6) from mascots into main solves #5
Reviewed-on: #6
2024-06-17 22:44:22 +12:00
1d8b6f58fb fix mascot css 2024-06-17 22:40:58 +12:00
4c6a71171b add boymoder 2024-06-17 22:29:58 +12:00
846377b586 implement mascots 2024-06-17 22:29:52 +12:00
2dbcc071a6 update CREDITS 2024-06-17 18:00:58 +12:00
9a7da3abce Merge pull request #142 from runofthemillgeek/fix/font-weight-in-font-face
Add font-weight range in @font-face rule
2024-06-13 23:13:27 -04:00
7fa37e48d5 Add font-weight range in @font-face rule
This makes Safari render the bold weights correctly which otherwise
looks wrong.
2024-06-11 21:16:44 +05:30
a6f901c094 Merge pull request #128 from arulagrawal/nix-flake
add nix flake
2024-06-04 22:49:33 -04:00
afad65f204 Merge pull request #127 from pimlie/patch-1
fix: healthcheck in Dockerfile
2024-06-04 22:48:51 -04:00
c12da45059 Merge pull request #121 from bennettmsherman/patch-1
Properly apply REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY
2024-06-04 22:48:33 -04:00
1132d73975 Merge pull request #134 from EMarshal/main 2024-06-02 22:08:38 -04:00
8ece7562ec Remove obsolete references to latest-arm and latest-armv7 images 2024-06-02 18:45:49 -06:00
08e463fd44 Merge pull request #126 from Harm133/main
Create build-artifacts on release
2024-06-02 18:17:05 -04:00
ec11a5511b add nix flake 2024-05-31 17:10:36 +04:00
Pim
3caa0592f3 fix: healthcheck in Dockerfile
Fix typo causing wget to return a warning:

```
# wget --q
wget: option '--q' is ambiguous; possibilities: '--quiet' '--quota'
```
2024-05-31 11:40:01 +02:00
190c92339e Update build-artifacts.yaml
Only build artifacts on published release
2024-05-31 10:19:44 +02:00
17c7738d6e Properly apply REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY
This change fixes the REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY environment variable being ignored.
2024-05-30 20:40:16 -07:00
27 changed files with 588 additions and 222 deletions

View File

@ -14,6 +14,8 @@ REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io
# Default user settings
# Set the default theme (options: system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight)
REDLIB_DEFAULT_THEME=system
# Set the default mascot
REDLIB_DEFAULT_MASCOT=none
# Set the default front page (options: default, popular, all)
REDLIB_DEFAULT_FRONT_PAGE=default
# Set the default layout (options: card, clean, compact)

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

View File

@ -7,6 +7,8 @@ on:
- "compose.*"
branches:
- "main"
release:
types: [published]
env:
CARGO_TERM_COLOR: always
@ -60,7 +62,6 @@ jobs:
- name: Upload release
uses: softprops/action-gh-release@v1
if: github.base_ref != 'main' && github.event_name == 'release'
with:
tag_name: ${{ steps.version.outputs.VERSION }}
name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }}

4
.gitignore vendored
View File

@ -2,3 +2,7 @@
.env
# Idea Files
.idea/
# nix files
.direnv/
result

37
CREDITS
View File

@ -2,7 +2,9 @@
674Y3r <87250374+674Y3r@users.noreply.github.com>
accountForIssues <52367365+accountForIssues@users.noreply.github.com>
Adrian Lebioda <adrianlebioda@gmail.com>
Akanksh Chitimalla <55909985+Akanksh12@users.noreply.github.com>
alefvanoon <53198048+alefvanoon@users.noreply.github.com>
Ales Lerch <13370338+axeII@users.noreply.github.com>
Alexandre Iooss <erdnaxe@crans.org>
alyaeanyx <alexandra.hollmeier@mailbox.org>
AndreVuillemot160 <84594011+AndreVuillemot160@users.noreply.github.com>
@ -11,58 +13,90 @@ Artemis <51862164+artemislena@users.noreply.github.com>
arthomnix <35371030+arthomnix@users.noreply.github.com>
Arya K <73596856+gi-yt@users.noreply.github.com>
Austin Huang <im@austinhuang.me>
Ayaka <ayaka@kitty.community>
backfire-monism-net <development.0extl@simplelogin.com>
Basti <pred2k@users.noreply.github.com>
Ben Sherman <bennettmsherman@gmail.com>
Ben Smith <37027883+smithbm2316@users.noreply.github.com>
beucismis <beucismis@tutamail.com>
BobIsMyManager <ahoumatt@yahoo.com>
Butter Cat <butteredcats@protonmail.com>
Butter Cat <ButteredCats@protonmail.com>
Carbrex <95964955+Carbrex@users.noreply.github.com>
ccuser44 <68124053+ccuser44@users.noreply.github.com>
Connor Holloway <c.holloway314@outlook.com>
curlpipe <11898833+curlpipe@users.noreply.github.com>
dacousb <53299044+dacousb@users.noreply.github.com>
Daniel Nathan Gray <dng@disroot.org>
Daniel Valentine <Daniel-Valentine@users.noreply.github.com>
Daniel Valentine <daniel@vielle.ws>
dbrennand <52419383+dbrennand@users.noreply.github.com>
Dean Sallinen <deza604@gmail.com>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com>
domve <domve@posteo.net>
Dyras <jevwmguf@duck.com>
Edward <101938856+EdwardLangdon@users.noreply.github.com>
Éli Marshal <835958+EMarshal@users.noreply.github.com>
elliot <75391956+ellieeet123@users.noreply.github.com>
erdnaxe <erdnaxe@users.noreply.github.com>
Esmail EL BoB <github.defilable@simplelogin.co>
fawn <fawn@envs.net>
FireMasterK <20838718+FireMasterK@users.noreply.github.com>
George Roubos <cowkingdom@hotmail.com>
git-bruh <e817509a-8ee9-4332-b0ad-3a6bdf9ab63f@aleeas.com>
gmnsii <95436780+gmnsii@users.noreply.github.com>
gmnsii <github.gmnsii@pm.me>
gmnsii <gmnsii@void.noreply>
Gonçalo Valério <dethos@users.noreply.github.com>
guaddy <67671414+guaddy@users.noreply.github.com>
Harsh Mishra <erbeusgriffincasper@gmail.com>
hinto.janai <hinto.janai@protonmail.com>
igna <igna@intent.cool>
imabritishcow <bcow@protonmail.com>
invakid404 <invakid404@riseup.net>
İsmail Karslı <ismail@karsli.net>
Johannes Schleifenbaum <johannes@js-webcoding.de>
Jonathan Dahan <git@jonathan.is>
Josiah <70736638+fres7h@users.noreply.github.com>
JPyke3 <pyke.jacob1@gmail.com>
Kavin <20838718+FireMasterK@users.noreply.github.com>
Kazi <kzshantonu@users.noreply.github.com>
Kieran <42723993+EnderDev@users.noreply.github.com>
Kieran <kieran@dothq.co>
Kirk1984 <christoph-m@posteo.de>
kuanhulio <66286575+kuanhulio@users.noreply.github.com>
Kyle Roth <kylrth@gmail.com>
laazyCmd <laazy.pr00gramming@protonmail.com>
Laurențiu Nicola <lnicola@users.noreply.github.com>
Lena <102762572+MarshDeer@users.noreply.github.com>
Leopardus <leopardus3@pm.me>
Macic <46872282+Macic-Dev@users.noreply.github.com>
Mario A <10923513+Midblyte@users.noreply.github.com>
Márton <marton2@gmail.com>
Mathew Davies <ThePixelDeveloper@users.noreply.github.com>
Matthew Crossman <matt@crossman.page>
Matthew E <matt@matthew.science>
Matthew Esposito <matt@matthew.science>
Mennaruuk <52135169+Mennaruuk@users.noreply.github.com>
Midou36O <midou@midou.dev>
mikupls <93015331+mikupls@users.noreply.github.com>
Myzel394 <50424412+Myzel394@users.noreply.github.com>
Nainar <nainar.mb@gmail.com>
Nathan Moos <moosingin3space@gmail.com>
Nazar <63452145+Tokarak@users.noreply.github.com>
Nicholas Christopher <nchristopher@tuta.io>
Nick Lowery <ClockVapor@users.noreply.github.com>
Nico <github@dr460nf1r3.org>
NKIPSC <15067635+NKIPSC@users.noreply.github.com>
nohoster <136514837+nohoster@users.noreply.github.com>
o69mar <119129086+o69mar@users.noreply.github.com>
obeho <71698631+obeho@users.noreply.github.com>
obscurity <z@x4.pm>
Om G <34579088+OxyMagnesium@users.noreply.github.com>
Ondřej Pešek <iTzBoboCz@users.noreply.github.com>
perennial <mail@perennialte.ch>
Peter Sawyer <petersawyer314@gmail.com>
pin <90570748+0323pin@users.noreply.github.com>
potatoesAreGod <118043038+potatoesAreGod@users.noreply.github.com>
RiversideRocks <59586759+RiversideRocks@users.noreply.github.com>
@ -86,11 +120,14 @@ TheCultLeader666 <65368815+TheCultLeader666@users.noreply.github.com>
TheFrenchGhosty <47571719+TheFrenchGhosty@users.noreply.github.com>
The TwilightBlood <hwengerstickel@protonmail.com>
tirz <36501933+tirz@users.noreply.github.com>
tmak2002 <torben@tmak2002.dev>
Tokarak <63452145+Tokarak@users.noreply.github.com>
Tsvetomir Bonev <invakid404@riseup.net>
Vivek <vivek@revankar.net>
Vladislav Nepogodin <nepogodin.vlad@gmail.com>
Walkx <walkxnl@gmail.com>
Wichai <1482605+Chengings@users.noreply.github.com>
wsy2220 <wsy@dogben.com>
xatier <xatierlike@gmail.com>
Yaroslav Chvanov <yaroslav.chvanov@gmail.com>
Zach <72994911+zachjmurphy@users.noreply.github.com>

2
Cargo.lock generated
View File

@ -1041,7 +1041,7 @@ dependencies = [
[[package]]
name = "redsunlib"
version = "0.34.0"
version = "0.35.0"
dependencies = [
"askama",
"base64",

View File

@ -3,7 +3,7 @@ name = "redsunlib"
description = " Alternative private front-end to Reddit"
license = "AGPL-3.0"
repository = "https://git.stardust.wtf/iridium/redlib"
version = "0.34.0"
version = "0.35.0"
authors = [
"Matthew Esposito <matt+cargo@matthew.science>",
"spikecodes <19519553+spikecodes@users.noreply.github.com>",

View File

@ -181,12 +181,6 @@ docker logs -f redlib
### Docker CLI
> [!IMPORTANT]
> If deploying on:
>
> - an `arm64` platform, use the `quay.io/redlib/redlib:latest-arm` image instead.
> - an `armv7` platform, use the `quay.io/redlib/redlib:latest-armv7` image instead.
Deploy Redlib:
```bash

106
flake.lock generated Normal file
View File

@ -0,0 +1,106 @@
{
"nodes": {
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1717025063,
"narHash": "sha256-dIubLa56W9sNNz0e8jGxrX3CAkPXsq7snuFA/Ie6dn8=",
"owner": "ipetkov",
"repo": "crane",
"rev": "480dff0be03dac0e51a8dfc26e882b0d123a450e",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1717112898,
"narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1717121863,
"narHash": "sha256-/3sxIe7MZqF/jw1RTQCSmgTjwVod43mmrk84m50MJQ4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "2a7b53172ed08f856b8382d7dcfd36a4e0cbd866",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

71
flake.nix Normal file
View File

@ -0,0 +1,71 @@
{
description = "Redlib: Private front-end for Reddit";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
};
outputs = { nixpkgs, crane, flake-utils, rust-overlay, ... }:
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
inherit (pkgs) lib;
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
targets = [ "x86_64-unknown-linux-musl" ];
};
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
src = lib.cleanSourceWith {
src = craneLib.path ./.;
filter = path: type:
(lib.hasInfix "/templates/" path) ||
(lib.hasInfix "/static/" path) ||
(craneLib.filterCargoSources path type);
};
redlib = craneLib.buildPackage {
inherit src;
strictDeps = true;
doCheck = false;
CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
};
in
{
checks = {
my-crate = redlib;
};
packages.default = redlib;
packages.docker = pkgs.dockerTools.buildImage {
name = "quay.io/redlib/redlib";
tag = "latest";
created = "now";
copyToRoot = with pkgs.dockerTools; [ caCertificates fakeNss ];
config.Cmd = "${redlib}/bin/redlib";
};
});
}

View File

@ -28,6 +28,10 @@ pub struct Config {
#[serde(alias = "LIBREDDIT_DEFAULT_THEME")]
pub(crate) default_theme: Option<String>,
#[serde(rename = "REDLIB_DEFAULT_MASCOT")]
#[serde(alias = "LIBREDDIT_DEFAULT_MASCOT")]
pub(crate) default_mascot: Option<String>,
#[serde(rename = "REDLIB_DEFAULT_FRONT_PAGE")]
#[serde(alias = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
pub(crate) default_front_page: Option<String>,
@ -125,6 +129,7 @@ impl Config {
Self {
sfw_only: parse("REDLIB_SFW_ONLY"),
default_theme: parse("REDLIB_DEFAULT_THEME"),
default_mascot: parse("REDLIB_DEFAULT_MASCOT"),
default_front_page: parse("REDLIB_DEFAULT_FRONT_PAGE"),
default_layout: parse("REDLIB_DEFAULT_LAYOUT"),
default_post_sort: parse("REDLIB_DEFAULT_POST_SORT"),
@ -151,6 +156,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
match name {
"REDLIB_SFW_ONLY" => config.sfw_only.clone(),
"REDLIB_DEFAULT_THEME" => config.default_theme.clone(),
"REDLIB_DEFAULT_MASCOT" => config.default_mascot.clone(),
"REDLIB_DEFAULT_FRONT_PAGE" => config.default_front_page.clone(),
"REDLIB_DEFAULT_LAYOUT" => config.default_layout.clone(),
"REDLIB_DEFAULT_COMMENT_SORT" => config.default_comment_sort.clone(),
@ -162,7 +168,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
"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_SIDEBAR_AND_SUMMARY" => config.default_hide_sidebar_and_summary.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

@ -136,6 +136,7 @@ impl InstanceInfo {
["Hide awards", &convert(&self.config.default_hide_awards)],
["Hide score", &convert(&self.config.default_hide_score)],
["Theme", &convert(&self.config.default_theme)],
["Mascot", &convert(&self.config.default_mascot)],
["Front page", &convert(&self.config.default_front_page)],
["Layout", &convert(&self.config.default_layout)],
["Wide", &convert(&self.config.default_wide)],
@ -168,6 +169,7 @@ impl InstanceInfo {
Hide awards: {:?}\n
Hide score: {:?}\n
Default theme: {:?}\n
Default mascot: {:?}\n
Default front page: {:?}\n
Default layout: {:?}\n
Default wide: {:?}\n
@ -190,6 +192,7 @@ impl InstanceInfo {
self.config.default_hide_awards,
self.config.default_hide_score,
self.config.default_theme,
self.config.default_mascot,
self.config.default_front_page,
self.config.default_layout,
self.config.default_wide,

View File

@ -26,7 +26,7 @@ use client::{canonical_path, proxy};
use log::info;
use once_cell::sync::Lazy;
use server::RequestExt;
use utils::{error, redirect, ThemeAssets};
use utils::{error, redirect, ThemeAssets, MascotAssets};
use crate::client::OAUTH_CLIENT;
@ -122,6 +122,20 @@ async fn style() -> Result<Response<Body>, String> {
)
}
/// Serve mascot
async fn mascot_image(req: Request<Body>) -> Result<Response<Body>, String> {
let res = MascotAssets::get(&req.param("name").unwrap())
.unwrap_or(MascotAssets::get("redsunlib.png").unwrap());
Ok(
Response::builder()
.status(200)
.header("content-type", "image/png")
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
.body(res.data.into())
.unwrap_or_default(),
)
}
#[tokio::main]
async fn main() {
// Load environment variables
@ -200,7 +214,7 @@ async fn main() {
"Referrer-Policy" => "no-referrer",
"X-Content-Type-Options" => "nosniff",
"X-Frame-Options" => "DENY",
"Content-Security-Policy" => "default-src 'none'; font-src 'self'; script-src 'self' blob:; manifest-src 'self'; media-src 'self' data: blob: about:; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'; connect-src 'self'; worker-src blob:;"
"Content-Security-Policy" => "default-src 'none'; font-src 'self'; script-src 'self' 'wasm-unsafe-eval' blob:; manifest-src 'self'; media-src 'self' data: blob: about:; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'; connect-src 'self'; worker-src 'self' blob:;"
};
if let Some(expire_time) = hsts {
@ -235,8 +249,8 @@ async fn main() {
app.at("/touch-icon-iphone.png").get(|_| iphone_logo().boxed());
app.at("/apple-touch-icon.png").get(|_| iphone_logo().boxed());
app
.at("/playHLSVideo.js")
.get(|_| resource(include_str!("../static/playHLSVideo.js"), "text/javascript", false).boxed());
.at("/videoUtils.js")
.get(|_| resource(include_str!("../static/videoUtils.js"), "text/javascript", false).boxed());
app
.at("/hls.min.js")
.get(|_| resource(include_str!("../static/hls.min.js"), "text/javascript", false).boxed());
@ -296,6 +310,9 @@ async fn main() {
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
app.at("/settings/update").get(|r| settings::update(r).boxed());
// Mascots
app.at("/mascot/:name").get(|r| mascot_image(r).boxed());
// Subreddit services
app
.at("/r/:sub")

View File

@ -19,8 +19,9 @@ struct SettingsTemplate {
// CONSTANTS
const PREFS: [&str; 17] = [
const PREFS: [&str; 18] = [
"theme",
"mascot",
"front_page",
"layout",
"wide",
@ -82,7 +83,7 @@ pub async fn set(req: Request<Body>) -> Result<Response<Body>, String> {
Some(value) => response.insert_cookie(
Cookie::build((name.to_owned(), value.clone()))
.path("/")
.http_only(true)
.http_only(name != "ffmpeg_video_downloads")
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
.into(),
),

View File

@ -570,7 +570,9 @@ pub struct Params {
#[derive(Default)]
pub struct Preferences {
pub available_themes: Vec<String>,
pub available_mascots: Vec<String>,
pub theme: String,
pub mascot: String,
pub front_page: String,
pub layout: String,
pub wide: String,
@ -596,6 +598,11 @@ pub struct Preferences {
#[include = "*.css"]
pub struct ThemeAssets;
#[derive(RustEmbed)]
#[folder = "static/mascots/"]
#[include = "*.png"]
pub struct MascotAssets;
impl Preferences {
// Build preferences from cookies
pub fn new(req: &Request<Body>) -> Self {
@ -606,9 +613,18 @@ impl Preferences {
let chunks: Vec<&str> = file.as_ref().split(".css").collect();
themes.push(chunks[0].to_owned());
}
// Read available mascot names from embedded png files.
// Always make default "none" option available.
let mut mascots = vec!["none".to_string()];
for file in MascotAssets::iter() {
let chunks: Vec<&str> = file.as_ref().split(".png").collect();
mascots.push(chunks[0].to_owned());
}
Self {
available_themes: themes,
available_mascots: mascots,
theme: setting(req, "theme"),
mascot: setting(req, "mascot"),
front_page: setting(req, "front_page"),
layout: setting(req, "layout"),
wide: setting(req, "wide"),

1
static/hls.min.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,110 +0,0 @@
// @license http://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
(function () {
if (Hls.isSupported()) {
var videoSources = document.querySelectorAll("video source[type='application/vnd.apple.mpegurl']");
videoSources.forEach(function (source) {
var playlist = source.src;
var oldVideo = source.parentNode;
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 (autoplay) {
oldVideo.play();
}
return;
}
// Replace video with copy that will have all "source" elements removed
var newVideo = oldVideo.cloneNode(true);
var allSources = newVideo.querySelectorAll("source");
allSources.forEach(function (source) {
source.remove();
});
// Empty source to enable play event
newVideo.src = "about:blank";
oldVideo.parentNode.replaceChild(newVideo, oldVideo);
function initializeHls() {
newVideo.removeEventListener('play', initializeHls);
var hls = new Hls({ autoStartLoad: false });
hls.loadSource(playlist);
hls.attachMedia(newVideo);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
hls.loadLevel = hls.levels.length - 1;
var availableLevels = hls.levels.map(function(level) {
return {
height: level.height,
width: level.width,
bitrate: level.bitrate,
};
});
addQualitySelector(newVideo, hls, availableLevels);
hls.startLoad();
newVideo.play();
});
hls.on(Hls.Events.ERROR, function (event, data) {
var errorType = data.type;
var errorFatal = data.fatal;
if (errorFatal) {
switch (errorType) {
case Hls.ErrorType.NETWORK_ERROR:
hls.startLoad();
break;
case Hls.ErrorType.MEDIA_ERROR:
hls.recoverMediaError();
break;
default:
hls.destroy();
break;
}
}
console.error("HLS error", data);
});
}
function addQualitySelector(videoElement, hlsInstance, availableLevels) {
var qualitySelector = document.createElement('select');
qualitySelector.classList.add('quality-selector');
var last = availableLevels.length - 1;
availableLevels.forEach(function (level, index) {
var option = document.createElement('option');
option.value = index.toString();
var bitrate = (level.bitrate / 1_000).toFixed(0);
option.text = level.height + 'p (' + bitrate + ' kbps)';
if (index === last) {
option.selected = "selected";
}
qualitySelector.appendChild(option);
});
qualitySelector.selectedIndex = availableLevels.length - 1;
qualitySelector.addEventListener('change', function () {
var selectedIndex = qualitySelector.selectedIndex;
hlsInstance.nextLevel = selectedIndex;
hlsInstance.startLoad();
});
videoElement.parentNode.appendChild(qualitySelector);
}
newVideo.addEventListener('play', initializeHls);
if (autoplay) {
newVideo.play();
}
});
} else {
var videos = document.querySelectorAll("video.hls_autoplay");
videos.forEach(function (video) {
video.setAttribute("autoplay", "");
});
}
})();
// @license-end

View File

@ -34,6 +34,7 @@
font-family: 'Inter';
src: url('/Inter.var.woff2') format('woff2-variations');
font-style: normal;
font-weight: 100 900;
}
/* Automatic theme selection */
@ -1325,83 +1326,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;
@ -1836,18 +1760,53 @@ td, th {
}
}
.quality-selector {
.video-options {
border: 2px var(--outside) solid;
margin-top: 8px;
float: right;
border-radius: 5px;
height: 35px;
height: 35px;
margin: 2px;
box-sizing: border-box;
}
.quality-selector option {
.video-options option {
background-color: var(--background);
color: var(--text);
}
.quality-selector option:hover {
.video-options option:hover {
background-color: var(--accent);
color: var(--text);
}
.mascot {
position: fixed;
right: 1em;
bottom: 1em;
pointer-events: none;
opacity: 0.5;
z-index: 1;
}
.mascot > img {
max-width: 20em;
}
.download {
padding-left: 8px;
padding-right: 8px;
font-size: 20px;
font-weight: 900;
color: var(--accent);
background-color: var(--outside);
}
.download:hover {
background-color: var(--foreground);
/*color: var(--);*/
}
.download:active {
background-color: var(--background);
}

228
static/videoUtils.js Normal file
View File

@ -0,0 +1,228 @@
// @license http://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
let ffmpeg = null;
(function () {
if (Hls.isSupported()) {
var downloadsEnabled = document.cookie.split("; ").find((row) => row.startsWith("ffmpeg_video_downloads="))?.split("=")[1] == "on";
var videoSources = document.querySelectorAll("video source[type='application/vnd.apple.mpegurl']");
videoSources.forEach(function (source) {
var playlist = source.src;
var oldVideo = source.parentNode;
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 (autoplay) {
oldVideo.play();
}
return;
}
// Replace video with copy that will have all "source" elements removed
var newVideo = oldVideo.cloneNode(true);
var allSources = newVideo.querySelectorAll("source");
allSources.forEach(function (source) {
source.remove();
});
// Empty source to enable play event
newVideo.src = "about:blank";
oldVideo.parentNode.replaceChild(newVideo, oldVideo);
function initializeHls() {
newVideo.removeEventListener('play', initializeHls);
var hls = new Hls({ autoStartLoad: false });
hls.loadSource(playlist);
hls.attachMedia(newVideo);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
hls.loadLevel = hls.levels.length - 1;
var availableLevels = hls.levels.map(function(level) {
return {
height: level.height,
width: level.width,
bitrate: level.bitrate,
};
});
addQualitySelector(newVideo, hls, availableLevels);
if (downloadsEnabled){ addVideoDownload(newVideo, hls); }
hls.startLoad();
newVideo.play();
});
hls.on(Hls.Events.ERROR, function (event, data) {
var errorType = data.type;
var errorFatal = data.fatal;
if (errorFatal) {
switch (errorType) {
case Hls.ErrorType.NETWORK_ERROR:
hls.startLoad();
break;
case Hls.ErrorType.MEDIA_ERROR:
hls.recoverMediaError();
break;
default:
hls.destroy();
break;
}
}
console.error("HLS error", data);
});
}
if (downloadsEnabled){
const { fetchFile } = FFmpegUtil;
const { FFmpeg } = FFmpegWASM;
function addVideoDownload(videoElement, hlsInstance) {
var mediaStream = [];
var downloadButton = document.createElement('button');
downloadButton.classList.add('video-options','download');
downloadButton.innerText = "⏳"
const mergeStreams = async () => {
if (ffmpeg === null) {
ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: "/ffmpeg/ffmpeg-core.js",
});
ffmpeg.on("log", ({ message }) => {
console.log(message); // This is quite noisy but i will include it
})
ffmpeg.on("progress", ({ progress, time }) => { // Progress TODO: show progress ring around button not just ⏳
// console.log("ffmpeg prog:",progress * 100)
});
}
// Combine Video Audio Streams
await ffmpeg.writeFile("video", await fetchFile(concatBlob(mediaStream['video'])));
await ffmpeg.writeFile("audio", await fetchFile(concatBlob(mediaStream['audio'])));
console.time('ffmpeg-exec');
await ffmpeg.exec(['-i', "video", '-i', "audio",'-c:v', "copy", '-c:a', "aac", 'output.mp4']);
console.timeEnd('ffmpeg-exec')
// Save
const toSlug = (str) => {
return str
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[\W_]+/g, '-')
.toLowerCase()
.replace(/^-+|-+$/g, '');
}
var filename = toSlug(videoElement.parentNode.parentNode.id || document.title)
const data = await ffmpeg.readFile('output.mp4');
saveAs(new Blob([data.buffer]),filename);
return
}
function saveAs(blob, filename) { // Yeah ok...
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}
function concatBlob(inputArray) {
var totalLength = inputArray.reduce(function (prev, cur) {
return prev + cur.length
}, 0);
var result = new Uint8Array(totalLength);
var offset = 0;
inputArray.forEach(function (element) {
result.set(element, offset);
offset += element.length;
});
return new Blob([result], {
type: 'application/octet-stream'
});
}
function getStreams() {
var video = document.createElement('video');
video.autoplay = true;
var dataStreams = {
'video': [],
'audio': []
};
mediaStream = dataStreams; // Update stream
hlsInstance.on(Hls.Events.BUFFER_APPENDING, function (event, data) {
dataStreams[data.type].push(data.data);
});
var isDownloading = false
function startDownload() {
if (!isDownloading) { isDownloading = true } else { return }
downloadButton.innerText = "⏳"
mergeStreams()
.then(_ => {
isDownloading = false
downloadButton.innerText = "⭳"
});
}
function waitForLoad() {
const poll = resolve => {
if(hlsInstance._media.buffered.length === 1 &&
hlsInstance._media.buffered.start(0) === 0 &&
hlsInstance._media.buffered.end(0) === hlsInstance._media.duration)
resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
waitForLoad(_ => flag === true)
.then(_ => {
downloadButton.innerText = "⭳"
downloadButton.addEventListener('click', startDownload);
});
}
videoElement.parentNode.appendChild(downloadButton);
getStreams()
}
}
function addQualitySelector(videoElement, hlsInstance, availableLevels) {
var qualitySelector = document.createElement('select');
qualitySelector.classList.add('video-options');
var last = availableLevels.length - 1;
availableLevels.forEach(function (level, index) {
var option = document.createElement('option');
option.value = index.toString();
var bitrate = (level.bitrate / 1_000).toFixed(0);
option.text = level.height + 'p (' + bitrate + ' kbps)';
if (index === last) {
option.selected = "selected";
}
qualitySelector.appendChild(option);
});
qualitySelector.selectedIndex = availableLevels.length - 1;
qualitySelector.addEventListener('change', function () {
var selectedIndex = qualitySelector.selectedIndex;
hlsInstance.nextLevel = selectedIndex;
hlsInstance.startLoad();
});
videoElement.parentNode.appendChild(qualitySelector);
}
newVideo.addEventListener('play', initializeHls);
if (autoplay) {
newVideo.play();
}
});
} else {
var videos = document.querySelectorAll("video.hls_autoplay");
videos.forEach(function (video) {
video.setAttribute("autoplay", "");
});
}
})();
// @license-end

View File

@ -28,7 +28,7 @@
</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 -->
@ -59,6 +59,13 @@
</a>
</div>
</nav>
{% if prefs.mascot != "none" && prefs.mascot != "" %}
<!-- MASCOT -->
<div class="mascot">
<img src="/mascot/{{ prefs.mascot }}.png">
</div>
{% endif %}
<!-- MAIN CONTENT -->
{% block body %}

View File

@ -97,9 +97,13 @@
{% endif %}
{% endfor %}
{% endif %}
{% if prefs.use_hls == "on" %}
{% if prefs.ffmpeg_video_downloads == "on" %}
<script src="/ffmpeg/ffmpeg.js"></script>
<script src="/ffmpeg/ffmpeg-util.js"></script>
{% endif %}
{% if prefs.use_hls == "on" || prefs.ffmpeg_video_downloads == "on" %}
<script src="/hls.min.js"></script>
<script src="/playHLSVideo.js"></script>
<script src="/videoUtils.js"></script>
{% endif %}
{% if params.typed != "sr_user" %}

View File

@ -19,6 +19,12 @@
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
</select>
</div>
<div class="prefs-group">
<label for="mascot">Mascot:</label>
<select name="mascot" id="mascot">
{% call utils::options(prefs.mascot, prefs.available_mascots, "system") %}
</select>
</div>
</fieldset>
<fieldset>
<legend>Interface</legend>
@ -31,14 +37,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>
@ -84,12 +89,15 @@
</div>
<div class="prefs-group">
<label for="use_hls">Use HLS for videos</label>
{% if prefs.ffmpeg_video_downloads != "on" %}
<details id="feeds">
<summary>Why?</summary>
<div id="feed_list" class="helper">Reddit videos require JavaScript (via HLS.js) to be enabled to be played with audio. Therefore, this toggle lets you either use Redlib JS-free or utilize this feature.</div>
</details>
{% endif %}
{% if prefs.ffmpeg_video_downloads == "on" %}<u>ⓘ HLS is required for downloads</u>{% endif %}
<input type="hidden" value="off" name="use_hls">
<input type="checkbox" name="use_hls" id="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
<input type="checkbox" name="use_hls" id="use_hls" {% if prefs.ffmpeg_video_downloads == "on" %}disabled{% endif %} {% if prefs.use_hls == "on" || prefs.ffmpeg_video_downloads == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="ffmpeg_video_downloads">Use FFmpeg to download videos</label>
@ -157,7 +165,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 }}&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>
</div>
</div>

View File

@ -64,9 +64,13 @@
{% call utils::post_in_list(post) %}
{% endif %}
{% endfor %}
{% if prefs.use_hls == "on" %}
{% if prefs.ffmpeg_video_downloads == "on" %}
<script src="/ffmpeg/ffmpeg.js"></script>
<script src="/ffmpeg/ffmpeg-util.js"></script>
{% endif %}
{% if prefs.use_hls == "on" || prefs.ffmpeg_video_downloads == "on" %}
<script src="/hls.min.js"></script>
<script src="/playHLSVideo.js"></script>
<script src="/videoUtils.js"></script>
{% endif %}
</div>
{% endif %}

View File

@ -71,9 +71,13 @@
</div>
{% endif %}
{% endfor %}
{% if prefs.use_hls == "on" %}
{% if prefs.ffmpeg_video_downloads == "on" %}
<script src="/ffmpeg/ffmpeg.js"></script>
<script src="/ffmpeg/ffmpeg-util.js"></script>
{% endif %}
{% if prefs.use_hls == "on" || prefs.ffmpeg_video_downloads == "on" %}
<script src="/hls.min.js"></script>
<script src="/playHLSVideo.js"></script>
<script src="/videoUtils.js"></script>
{% endif %}
</div>
{% endif %}

View File

@ -117,7 +117,11 @@
</a>
</div>
{% else if post.post_type == "video" || post.post_type == "gif" %}
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
{% if prefs.ffmpeg_video_downloads == "on" %}
<script src="/ffmpeg/ffmpeg.js"></script>
<script src="/ffmpeg/ffmpeg-util.js"></script>
{% endif %}
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() || prefs.ffmpeg_video_downloads == "on" && !post.media.alt_url.is_empty() %}
<script src="/hls.min.js"></script>
<div class="post_media_content">
<video class="post_media_video short {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls>
@ -125,7 +129,7 @@
<source src="{{ post.media.url }}" type="video/mp4" />
</video>
</div>
<script src="/playHLSVideo.js"></script>
<script src="/videoUtils.js"></script>
{% else %}
<div class="post_media_content">
<video class="post_media_video" src="{{ post.media.url }}" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %} loop><a href={{ post.media.url }}>Video</a></video>
@ -250,7 +254,7 @@
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls loop {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
</div>
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "video" %}
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() || prefs.ffmpeg_video_downloads == "on" && !post.media.alt_url.is_empty() %}
<div class="post_media_content">
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %} {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" controls preload="none">
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />