Compare commits
115 Commits
inline-ima
...
7aca5791b1
Author | SHA1 | Date | |
---|---|---|---|
7aca5791b1 | |||
8b3783096b | |||
59b702aa15 | |||
edf0109095 | |||
ecf0f91464 | |||
a5a5cbb734 | |||
5117af4137 | |||
394e975724 | |||
a67cbb99a6 | |||
ededa849f4 | |||
1d8b6f58fb | |||
4c6a71171b | |||
846377b586 | |||
2dbcc071a6 | |||
a12596e1a2 | |||
df8d36c661 | |||
5018184e7c | |||
5c73f043cd | |||
013e805ece | |||
9a7da3abce | |||
7fa37e48d5 | |||
21da37246e | |||
fb0814babd | |||
bae220d4fe | |||
97def1bab1 | |||
a6f901c094 | |||
afad65f204 | |||
c12da45059 | |||
10d5b4a583 | |||
feda45f803 | |||
1132d73975 | |||
8ece7562ec | |||
08e463fd44 | |||
f8f964741a | |||
58c736c51b | |||
336d5dcaa7 | |||
4c00e4ac05 | |||
92afbe55de | |||
ec11a5511b | |||
9133062e68 | |||
3caa0592f3 | |||
190c92339e | |||
83d50316fb | |||
e187ccd14a | |||
284e03ccf9 | |||
17c7738d6e | |||
8a3ceaf94a | |||
bd47c206a1 | |||
7a099f259f | |||
4ea911e6b2 | |||
31d68afdc9 | |||
96a7e155c5 | |||
045a8852ec | |||
4cc8bf8318 | |||
1715b36ae9 | |||
6102b08894 | |||
892b0e89c8 | |||
a64e2143f3 | |||
e13d9b7239 | |||
e597ed8f06 | |||
8c67d33721 | |||
26aa374bbd | |||
5b8b1e36ca | |||
093d240530 | |||
9048565d48 | |||
6b2aab23c8 | |||
6b11d936b3 | |||
34692359cf | |||
33411a7588 | |||
5a0f0f96f5 | |||
273d889f1b | |||
93594fc642 | |||
af6f3d3b3f | |||
62b791bb24 | |||
e9af28b6eb | |||
2f2cded671 | |||
b22fb7cd7b | |||
75b0149313 | |||
50fad938dd | |||
b6f5831d10 | |||
565b50646f | |||
6484ebf897 | |||
3f863c8991 | |||
e581f432dd | |||
2c8f5a7ac1 | |||
6d83b07aaa | |||
5f67eacf93 | |||
6684db5e82 | |||
1760f5a676 | |||
8307aa1e42 | |||
27f25e0fb1 | |||
c15f25e27b | |||
20e3129d88 | |||
54c2ffad95 | |||
6991fc6ae3 | |||
30b27aea44 | |||
63e2fadf4a | |||
4ba26285d7 | |||
89140c8cf7 | |||
99048c4683 | |||
ccfe7d0eeb | |||
f7c182dcd8 | |||
9bd540d659 | |||
4f6a14739b | |||
7c87d63d34 | |||
75b139dff2 | |||
10499df423 | |||
c86ca16c1a | |||
858299c861 | |||
75f5c6668c | |||
e6b9a2e426 | |||
d4a2b3edc6 | |||
4f0b29f930 | |||
4e2648280d | |||
35ae71302f |
@ -9,11 +9,13 @@ REDLIB_BANNER=
|
|||||||
# Disable search engine indexing
|
# Disable search engine indexing
|
||||||
REDLIB_ROBOTS_DISABLE_INDEXING=off
|
REDLIB_ROBOTS_DISABLE_INDEXING=off
|
||||||
# Set the Pushshift frontend for "removed" links
|
# Set the Pushshift frontend for "removed" links
|
||||||
REDLIB_PUSHSHIFT_FRONTEND=www.unddit.com
|
REDLIB_PUSHSHIFT_FRONTEND=undelete.pullpush.io
|
||||||
|
|
||||||
# Default user settings
|
# Default user settings
|
||||||
# Set the default theme (options: system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight)
|
# Set the default theme (options: system, light, dark, black, dracula, nord, laserwave, violet, gold, rosebox, gruvboxdark, gruvboxlight)
|
||||||
REDLIB_DEFAULT_THEME=system
|
REDLIB_DEFAULT_THEME=system
|
||||||
|
# Set the default mascot
|
||||||
|
REDLIB_DEFAULT_MASCOT=none
|
||||||
# Set the default front page (options: default, popular, all)
|
# Set the default front page (options: default, popular, all)
|
||||||
REDLIB_DEFAULT_FRONT_PAGE=default
|
REDLIB_DEFAULT_FRONT_PAGE=default
|
||||||
# Set the default layout (options: card, clean, compact)
|
# Set the default layout (options: card, clean, compact)
|
||||||
@ -38,6 +40,8 @@ REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off
|
|||||||
REDLIB_DEFAULT_SUBSCRIPTIONS=
|
REDLIB_DEFAULT_SUBSCRIPTIONS=
|
||||||
# Hide awards by default
|
# Hide awards by default
|
||||||
REDLIB_DEFAULT_HIDE_AWARDS=off
|
REDLIB_DEFAULT_HIDE_AWARDS=off
|
||||||
|
# Hide sidebar and summary
|
||||||
|
REDLIB_DEFAULT_HIDE_SIDEBAR_AND_SUMMARY=off
|
||||||
# Disable the confirmation before visiting Reddit
|
# Disable the confirmation before visiting Reddit
|
||||||
REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off
|
REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off
|
||||||
# Hide score by default
|
# Hide score by default
|
||||||
|
3
.github/workflows/build-artifacts.yaml
vendored
@ -7,6 +7,8 @@ on:
|
|||||||
- "compose.*"
|
- "compose.*"
|
||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@ -60,7 +62,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload release
|
- name: Upload release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
if: github.base_ref != 'main' && github.event_name == 'release'
|
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.version.outputs.VERSION }}
|
tag_name: ${{ steps.version.outputs.VERSION }}
|
||||||
name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }}
|
name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }}
|
||||||
|
16
.github/workflows/main-rust.yml
vendored
@ -30,9 +30,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
toolchain: stable
|
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
|
# Building actions
|
||||||
- name: Build
|
- name: Build
|
||||||
run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu
|
run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-musl
|
||||||
|
|
||||||
- name: Versions
|
- name: Versions
|
||||||
id: version
|
id: version
|
||||||
@ -45,17 +51,17 @@ jobs:
|
|||||||
run: cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
run: cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Calculate SHA512 checksum
|
- name: Calculate SHA512 checksum
|
||||||
run: sha512sum target/x86_64-unknown-linux-gnu/release/redlib > redlib.sha512
|
run: sha512sum target/x86_64-unknown-linux-musl/release/redlib > redlib.sha512
|
||||||
|
|
||||||
- name: Calculate SHA256 checksum
|
- name: Calculate SHA256 checksum
|
||||||
run: sha256sum target/x86_64-unknown-linux-gnu/release/redlib > redlib.sha256
|
run: sha256sum target/x86_64-unknown-linux-musl/release/redlib > redlib.sha256
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
name: Upload a Build Artifact
|
name: Upload a Build Artifact
|
||||||
with:
|
with:
|
||||||
name: redlib
|
name: redlib
|
||||||
path: |
|
path: |
|
||||||
target/x86_64-unknown-linux-gnu/release/redlib
|
target/x86_64-unknown-linux-musl/release/redlib
|
||||||
redlib.sha512
|
redlib.sha512
|
||||||
redlib.sha256
|
redlib.sha256
|
||||||
|
|
||||||
@ -68,7 +74,7 @@ jobs:
|
|||||||
name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }}
|
name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }}
|
||||||
draft: true
|
draft: true
|
||||||
files: |
|
files: |
|
||||||
target/x86_64-unknown-linux-gnu/release/redlib
|
target/x86_64-unknown-linux-musl/release/redlib
|
||||||
redlib.sha512
|
redlib.sha512
|
||||||
redlib.sha256
|
redlib.sha256
|
||||||
body: |
|
body: |
|
||||||
|
4
.gitignore
vendored
@ -2,3 +2,7 @@
|
|||||||
.env
|
.env
|
||||||
# Idea Files
|
# Idea Files
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# nix files
|
||||||
|
.direnv/
|
||||||
|
result
|
||||||
|
2
.replit
@ -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-gnu/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-musl/release/redlib .; chmod +x redlib; set +e; ./redlib -H 63115200; sleep 1; done"
|
||||||
language = "bash"
|
language = "bash"
|
||||||
|
37
CREDITS
@ -2,7 +2,9 @@
|
|||||||
674Y3r <87250374+674Y3r@users.noreply.github.com>
|
674Y3r <87250374+674Y3r@users.noreply.github.com>
|
||||||
accountForIssues <52367365+accountForIssues@users.noreply.github.com>
|
accountForIssues <52367365+accountForIssues@users.noreply.github.com>
|
||||||
Adrian Lebioda <adrianlebioda@gmail.com>
|
Adrian Lebioda <adrianlebioda@gmail.com>
|
||||||
|
Akanksh Chitimalla <55909985+Akanksh12@users.noreply.github.com>
|
||||||
alefvanoon <53198048+alefvanoon@users.noreply.github.com>
|
alefvanoon <53198048+alefvanoon@users.noreply.github.com>
|
||||||
|
Ales Lerch <13370338+axeII@users.noreply.github.com>
|
||||||
Alexandre Iooss <erdnaxe@crans.org>
|
Alexandre Iooss <erdnaxe@crans.org>
|
||||||
alyaeanyx <alexandra.hollmeier@mailbox.org>
|
alyaeanyx <alexandra.hollmeier@mailbox.org>
|
||||||
AndreVuillemot160 <84594011+AndreVuillemot160@users.noreply.github.com>
|
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>
|
arthomnix <35371030+arthomnix@users.noreply.github.com>
|
||||||
Arya K <73596856+gi-yt@users.noreply.github.com>
|
Arya K <73596856+gi-yt@users.noreply.github.com>
|
||||||
Austin Huang <im@austinhuang.me>
|
Austin Huang <im@austinhuang.me>
|
||||||
|
Ayaka <ayaka@kitty.community>
|
||||||
|
backfire-monism-net <development.0extl@simplelogin.com>
|
||||||
Basti <pred2k@users.noreply.github.com>
|
Basti <pred2k@users.noreply.github.com>
|
||||||
|
Ben Sherman <bennettmsherman@gmail.com>
|
||||||
Ben Smith <37027883+smithbm2316@users.noreply.github.com>
|
Ben Smith <37027883+smithbm2316@users.noreply.github.com>
|
||||||
|
beucismis <beucismis@tutamail.com>
|
||||||
BobIsMyManager <ahoumatt@yahoo.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>
|
curlpipe <11898833+curlpipe@users.noreply.github.com>
|
||||||
dacousb <53299044+dacousb@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-Valentine@users.noreply.github.com>
|
||||||
Daniel Valentine <daniel@vielle.ws>
|
Daniel Valentine <daniel@vielle.ws>
|
||||||
dbrennand <52419383+dbrennand@users.noreply.github.com>
|
dbrennand <52419383+dbrennand@users.noreply.github.com>
|
||||||
|
Dean Sallinen <deza604@gmail.com>
|
||||||
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com>
|
Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com>
|
||||||
domve <domve@posteo.net>
|
domve <domve@posteo.net>
|
||||||
Dyras <jevwmguf@duck.com>
|
Dyras <jevwmguf@duck.com>
|
||||||
Edward <101938856+EdwardLangdon@users.noreply.github.com>
|
Edward <101938856+EdwardLangdon@users.noreply.github.com>
|
||||||
|
Éli Marshal <835958+EMarshal@users.noreply.github.com>
|
||||||
elliot <75391956+ellieeet123@users.noreply.github.com>
|
elliot <75391956+ellieeet123@users.noreply.github.com>
|
||||||
erdnaxe <erdnaxe@users.noreply.github.com>
|
erdnaxe <erdnaxe@users.noreply.github.com>
|
||||||
Esmail EL BoB <github.defilable@simplelogin.co>
|
Esmail EL BoB <github.defilable@simplelogin.co>
|
||||||
|
fawn <fawn@envs.net>
|
||||||
FireMasterK <20838718+FireMasterK@users.noreply.github.com>
|
FireMasterK <20838718+FireMasterK@users.noreply.github.com>
|
||||||
George Roubos <cowkingdom@hotmail.com>
|
George Roubos <cowkingdom@hotmail.com>
|
||||||
git-bruh <e817509a-8ee9-4332-b0ad-3a6bdf9ab63f@aleeas.com>
|
git-bruh <e817509a-8ee9-4332-b0ad-3a6bdf9ab63f@aleeas.com>
|
||||||
gmnsii <95436780+gmnsii@users.noreply.github.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>
|
guaddy <67671414+guaddy@users.noreply.github.com>
|
||||||
Harsh Mishra <erbeusgriffincasper@gmail.com>
|
Harsh Mishra <erbeusgriffincasper@gmail.com>
|
||||||
|
hinto.janai <hinto.janai@protonmail.com>
|
||||||
igna <igna@intent.cool>
|
igna <igna@intent.cool>
|
||||||
imabritishcow <bcow@protonmail.com>
|
imabritishcow <bcow@protonmail.com>
|
||||||
|
invakid404 <invakid404@riseup.net>
|
||||||
|
İsmail Karslı <ismail@karsli.net>
|
||||||
Johannes Schleifenbaum <johannes@js-webcoding.de>
|
Johannes Schleifenbaum <johannes@js-webcoding.de>
|
||||||
|
Jonathan Dahan <git@jonathan.is>
|
||||||
Josiah <70736638+fres7h@users.noreply.github.com>
|
Josiah <70736638+fres7h@users.noreply.github.com>
|
||||||
JPyke3 <pyke.jacob1@gmail.com>
|
JPyke3 <pyke.jacob1@gmail.com>
|
||||||
Kavin <20838718+FireMasterK@users.noreply.github.com>
|
Kavin <20838718+FireMasterK@users.noreply.github.com>
|
||||||
Kazi <kzshantonu@users.noreply.github.com>
|
Kazi <kzshantonu@users.noreply.github.com>
|
||||||
Kieran <42723993+EnderDev@users.noreply.github.com>
|
Kieran <42723993+EnderDev@users.noreply.github.com>
|
||||||
Kieran <kieran@dothq.co>
|
Kieran <kieran@dothq.co>
|
||||||
|
Kirk1984 <christoph-m@posteo.de>
|
||||||
|
kuanhulio <66286575+kuanhulio@users.noreply.github.com>
|
||||||
Kyle Roth <kylrth@gmail.com>
|
Kyle Roth <kylrth@gmail.com>
|
||||||
laazyCmd <laazy.pr00gramming@protonmail.com>
|
laazyCmd <laazy.pr00gramming@protonmail.com>
|
||||||
Laurențiu Nicola <lnicola@users.noreply.github.com>
|
Laurențiu Nicola <lnicola@users.noreply.github.com>
|
||||||
Lena <102762572+MarshDeer@users.noreply.github.com>
|
Lena <102762572+MarshDeer@users.noreply.github.com>
|
||||||
|
Leopardus <leopardus3@pm.me>
|
||||||
Macic <46872282+Macic-Dev@users.noreply.github.com>
|
Macic <46872282+Macic-Dev@users.noreply.github.com>
|
||||||
Mario A <10923513+Midblyte@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 Crossman <matt@crossman.page>
|
||||||
Matthew E <matt@matthew.science>
|
Matthew E <matt@matthew.science>
|
||||||
Matthew Esposito <matt@matthew.science>
|
Matthew Esposito <matt@matthew.science>
|
||||||
Mennaruuk <52135169+Mennaruuk@users.noreply.github.com>
|
Mennaruuk <52135169+Mennaruuk@users.noreply.github.com>
|
||||||
|
Midou36O <midou@midou.dev>
|
||||||
mikupls <93015331+mikupls@users.noreply.github.com>
|
mikupls <93015331+mikupls@users.noreply.github.com>
|
||||||
|
Myzel394 <50424412+Myzel394@users.noreply.github.com>
|
||||||
Nainar <nainar.mb@gmail.com>
|
Nainar <nainar.mb@gmail.com>
|
||||||
Nathan Moos <moosingin3space@gmail.com>
|
Nathan Moos <moosingin3space@gmail.com>
|
||||||
|
Nazar <63452145+Tokarak@users.noreply.github.com>
|
||||||
Nicholas Christopher <nchristopher@tuta.io>
|
Nicholas Christopher <nchristopher@tuta.io>
|
||||||
Nick Lowery <ClockVapor@users.noreply.github.com>
|
Nick Lowery <ClockVapor@users.noreply.github.com>
|
||||||
Nico <github@dr460nf1r3.org>
|
Nico <github@dr460nf1r3.org>
|
||||||
NKIPSC <15067635+NKIPSC@users.noreply.github.com>
|
NKIPSC <15067635+NKIPSC@users.noreply.github.com>
|
||||||
|
nohoster <136514837+nohoster@users.noreply.github.com>
|
||||||
o69mar <119129086+o69mar@users.noreply.github.com>
|
o69mar <119129086+o69mar@users.noreply.github.com>
|
||||||
obeho <71698631+obeho@users.noreply.github.com>
|
obeho <71698631+obeho@users.noreply.github.com>
|
||||||
obscurity <z@x4.pm>
|
obscurity <z@x4.pm>
|
||||||
Om G <34579088+OxyMagnesium@users.noreply.github.com>
|
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>
|
pin <90570748+0323pin@users.noreply.github.com>
|
||||||
potatoesAreGod <118043038+potatoesAreGod@users.noreply.github.com>
|
potatoesAreGod <118043038+potatoesAreGod@users.noreply.github.com>
|
||||||
RiversideRocks <59586759+RiversideRocks@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>
|
TheFrenchGhosty <47571719+TheFrenchGhosty@users.noreply.github.com>
|
||||||
The TwilightBlood <hwengerstickel@protonmail.com>
|
The TwilightBlood <hwengerstickel@protonmail.com>
|
||||||
tirz <36501933+tirz@users.noreply.github.com>
|
tirz <36501933+tirz@users.noreply.github.com>
|
||||||
|
tmak2002 <torben@tmak2002.dev>
|
||||||
Tokarak <63452145+Tokarak@users.noreply.github.com>
|
Tokarak <63452145+Tokarak@users.noreply.github.com>
|
||||||
Tsvetomir Bonev <invakid404@riseup.net>
|
Tsvetomir Bonev <invakid404@riseup.net>
|
||||||
|
Vivek <vivek@revankar.net>
|
||||||
Vladislav Nepogodin <nepogodin.vlad@gmail.com>
|
Vladislav Nepogodin <nepogodin.vlad@gmail.com>
|
||||||
Walkx <walkxnl@gmail.com>
|
Walkx <walkxnl@gmail.com>
|
||||||
Wichai <1482605+Chengings@users.noreply.github.com>
|
Wichai <1482605+Chengings@users.noreply.github.com>
|
||||||
wsy2220 <wsy@dogben.com>
|
wsy2220 <wsy@dogben.com>
|
||||||
xatier <xatierlike@gmail.com>
|
xatier <xatierlike@gmail.com>
|
||||||
|
Yaroslav Chvanov <yaroslav.chvanov@gmail.com>
|
||||||
Zach <72994911+zachjmurphy@users.noreply.github.com>
|
Zach <72994911+zachjmurphy@users.noreply.github.com>
|
||||||
|
493
Cargo.lock
generated
12
Cargo.toml
@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "redlib"
|
name = "redsunlib"
|
||||||
description = " Alternative private front-end to Reddit"
|
description = " Alternative private front-end to Reddit"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
repository = "https://github.com/redlib-org/redlib"
|
repository = "https://git.stardust.wtf/iridium/redlib"
|
||||||
version = "0.31.2"
|
version = "0.34.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Matthew Esposito <matt+cargo@matthew.science>",
|
"Matthew Esposito <matt+cargo@matthew.science>",
|
||||||
"spikecodes <19519553+spikecodes@users.noreply.github.com>",
|
"spikecodes <19519553+spikecodes@users.noreply.github.com>",
|
||||||
@ -12,7 +12,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { version = "0.12.1", default-features = false }
|
askama = { version = "0.12.1", default-features = false }
|
||||||
cached = { version = "0.48.1", features = ["async"] }
|
cached = { version = "0.51.3", features = ["async"] }
|
||||||
clap = { version = "4.4.11", default-features = false, features = [
|
clap = { version = "4.4.11", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"env",
|
"env",
|
||||||
@ -31,13 +31,13 @@ time = { version = "0.3.31", features = ["local-offset"] }
|
|||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
rust-embed = { version = "8.1.0", features = ["include-exclude"] }
|
rust-embed = { version = "8.1.0", features = ["include-exclude"] }
|
||||||
libflate = "2.0.0"
|
libflate = "2.0.0"
|
||||||
brotli = { version = "3.4.0", features = ["std"] }
|
brotli = { version = "6.0.0", features = ["std"] }
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
serde_yaml = "0.9.29"
|
serde_yaml = "0.9.29"
|
||||||
build_html = "2.4.0"
|
build_html = "2.4.0"
|
||||||
uuid = { version = "1.6.1", features = ["v4"] }
|
uuid = { version = "1.6.1", features = ["v4"] }
|
||||||
base64 = "0.21.5"
|
base64 = "0.22.1"
|
||||||
fastrand = "2.0.1"
|
fastrand = "2.0.1"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
|
36
Dockerfile
@ -1,20 +1,34 @@
|
|||||||
FROM alpine:3.19
|
## Builder
|
||||||
|
|
||||||
ARG TARGET
|
FROM rust:alpine AS builder
|
||||||
|
|
||||||
RUN apk add --no-cache curl
|
RUN apk add --no-cache musl-dev git
|
||||||
|
|
||||||
RUN curl -L https://github.com/redlib-org/redlib/releases/latest/download/redlib-${TARGET}.tar.gz | \
|
WORKDIR /redsunlib
|
||||||
tar xz -C /usr/local/bin/
|
|
||||||
|
|
||||||
RUN adduser --home /nonexistent --no-create-home --disabled-password redlib
|
COPY . .
|
||||||
USER redlib
|
|
||||||
|
RUN cargo build --target x86_64-unknown-linux-musl --release
|
||||||
|
|
||||||
|
## Final image
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# 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 our build
|
||||||
|
COPY --from=builder /redsunlib/target/x86_64-unknown-linux-musl/release/redsunlib /usr/local/bin/redsunlib
|
||||||
|
|
||||||
|
# Use an unprivileged user.
|
||||||
|
RUN adduser --home /nonexistent --no-create-home --disabled-password redsunlib
|
||||||
|
USER redsunlib
|
||||||
|
|
||||||
# Tell Docker to expose port 8080
|
# Tell Docker to expose port 8080
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Run a healthcheck every minute to make sure redlib is functional
|
# 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
|
HEALTHCHECK --interval=1m --timeout=3s CMD wget --spider -q http://localhost:8080/settings || exit 1
|
||||||
|
|
||||||
CMD ["redlib"]
|
|
||||||
|
|
||||||
|
CMD ["redsunlib"]
|
53
README.md
@ -1,19 +1,11 @@
|
|||||||
# Redlib
|
<img align="left" width="128" height="128" src="https://git.stardust.wtf/attachments/842086e3-b718-4379-b718-c3a542842152" alt="logo">
|
||||||
|
|
||||||
> An alternative private front-end to Reddit, with its origins in [Libreddit](https://github.com/libreddit/libreddit).
|
# Redsunlib
|
||||||
|
> An alternative private front-end to Reddit, a fork of [Redlib](https://github.com/redlib-org/redlib) with some function and cosmetic changes.
|
||||||
|
|
||||||

|
<br>
|
||||||
|
|
||||||
---
|

|
||||||
|
|
||||||
**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
|
## Table of Contents
|
||||||
|
|
||||||
@ -30,7 +22,6 @@
|
|||||||
- [Reddit](#reddit)
|
- [Reddit](#reddit)
|
||||||
- [Redlib](#redlib-1)
|
- [Redlib](#redlib-1)
|
||||||
- [Server](#server)
|
- [Server](#server)
|
||||||
- [Official instance (redlib.matthew.science)](#official-instance-redlibmatthewscience)
|
|
||||||
5. [Deployment](#deployment)
|
5. [Deployment](#deployment)
|
||||||
- [Docker](#docker)
|
- [Docker](#docker)
|
||||||
- [Docker Compose](#docker-compose)
|
- [Docker Compose](#docker-compose)
|
||||||
@ -61,9 +52,6 @@ For information on instance uptime, see the [Uptime Robot status page](https://s
|
|||||||
|
|
||||||
# About
|
# 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 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).
|
Redlib currently implements most of Reddit's (signed-out) functionalities but still lacks [a few features](https://github.com/redlib-org/redlib/issues).
|
||||||
@ -158,16 +146,6 @@ 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.
|
- **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
|
# Deployment
|
||||||
@ -203,12 +181,6 @@ docker logs -f redlib
|
|||||||
|
|
||||||
### Docker CLI
|
### 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:
|
Deploy Redlib:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -380,12 +352,13 @@ 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.
|
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 |
|
| 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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
||||||
|
|
||||||
## Default user settings
|
## Default user settings
|
||||||
|
|
||||||
@ -393,7 +366,7 @@ Assign a default value for each user-modifiable setting by passing environment v
|
|||||||
|
|
||||||
| Name | Possible values | Default value |
|
| Name | Possible values | Default value |
|
||||||
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||||
| `THEME` | `["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox", "gruvboxdark", "gruvboxlight"]` | `system` |
|
| `THEME` | `["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox", "gruvboxdark", "gruvboxlight", "tokyoNight", "icebergDark"]` | `system` |
|
||||||
| `FRONT_PAGE` | `["default", "popular", "all"]` | `default` |
|
| `FRONT_PAGE` | `["default", "popular", "all"]` | `default` |
|
||||||
| `LAYOUT` | `["card", "clean", "compact"]` | `card` |
|
| `LAYOUT` | `["card", "clean", "compact"]` | `card` |
|
||||||
| `WIDE` | `["on", "off"]` | `off` |
|
| `WIDE` | `["on", "off"]` | `off` |
|
||||||
|
@ -2,25 +2,25 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
redlib:
|
redsunlib:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
container_name: "redlib"
|
container_name: "redsunlib"
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080 # Specify `127.0.0.1:8080:8080` instead if using a reverse proxy
|
- 8080:8080 # Specify `127.0.0.1:8080:8080` instead if using a reverse proxy
|
||||||
user: nobody
|
user: nobody
|
||||||
read_only: true
|
read_only: true
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
# - seccomp=seccomp-redlib.json
|
# - seccomp=seccomp-redsunlib.json
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
networks:
|
networks:
|
||||||
- redlib
|
- redsunlib
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
|
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
|
||||||
interval: 5m
|
interval: 5m
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
redlib:
|
redsunlib:
|
||||||
|
14
compose.yaml
@ -1,26 +1,24 @@
|
|||||||
services:
|
services:
|
||||||
redlib:
|
redsunlib:
|
||||||
image: quay.io/redlib/redlib:latest
|
image: git.stardust.wtf/iridium/redsunlib: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
|
restart: always
|
||||||
container_name: "redlib"
|
container_name: "redsunlib"
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080 # Specify `127.0.0.1:8080:8080` instead if using a reverse proxy
|
- 8080:8080 # Specify `127.0.0.1:8080:8080` instead if using a reverse proxy
|
||||||
user: nobody
|
user: nobody
|
||||||
read_only: true
|
read_only: true
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
# - seccomp=seccomp-redlib.json
|
# - seccomp=seccomp-redsunlib.json
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
env_file: .env
|
env_file: .env
|
||||||
networks:
|
networks:
|
||||||
- redlib
|
- redsunlib
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
|
test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"]
|
||||||
interval: 5m
|
interval: 5m
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
redlib:
|
redsunlib:
|
||||||
|
106
flake.lock
generated
Normal 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
@ -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";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
@ -170,7 +170,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
|||||||
// Construct the hyper client from the HTTPS connector.
|
// Construct the hyper client from the HTTPS connector.
|
||||||
let client: Client<_, Body> = CLIENT.clone();
|
let client: Client<_, Body> = CLIENT.clone();
|
||||||
|
|
||||||
let (token, vendor_id, device_id, user_agent, loid) = {
|
let (token, vendor_id, device_id, mut user_agent, loid) = {
|
||||||
let client = block_on(OAUTH_CLIENT.read());
|
let client = block_on(OAUTH_CLIENT.read());
|
||||||
(
|
(
|
||||||
client.token.clone(),
|
client.token.clone(),
|
||||||
@ -180,6 +180,14 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
|||||||
client.headers_map.get("x-reddit-loid").cloned().unwrap_or_default(),
|
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.
|
// Build request to Reddit. When making a GET, request gzip compression.
|
||||||
// (Reddit doesn't do brotli yet.)
|
// (Reddit doesn't do brotli yet.)
|
||||||
let builder = Request::builder()
|
let builder = Request::builder()
|
||||||
|
@ -28,6 +28,10 @@ pub struct Config {
|
|||||||
#[serde(alias = "LIBREDDIT_DEFAULT_THEME")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_THEME")]
|
||||||
pub(crate) default_theme: Option<String>,
|
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(rename = "REDLIB_DEFAULT_FRONT_PAGE")]
|
||||||
#[serde(alias = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_FRONT_PAGE")]
|
||||||
pub(crate) default_front_page: Option<String>,
|
pub(crate) default_front_page: Option<String>,
|
||||||
@ -60,6 +64,10 @@ pub struct Config {
|
|||||||
#[serde(alias = "LIBREDDIT_DEFAULT_USE_HLS")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_USE_HLS")]
|
||||||
pub(crate) default_use_hls: Option<String>,
|
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(rename = "REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
||||||
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION")]
|
||||||
pub(crate) default_hide_hls_notification: Option<String>,
|
pub(crate) default_hide_hls_notification: Option<String>,
|
||||||
@ -68,6 +76,10 @@ pub struct Config {
|
|||||||
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_AWARDS")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_AWARDS")]
|
||||||
pub(crate) default_hide_awards: Option<String>,
|
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(rename = "REDLIB_DEFAULT_HIDE_SCORE")]
|
||||||
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_SCORE")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_HIDE_SCORE")]
|
||||||
pub(crate) default_hide_score: Option<String>,
|
pub(crate) default_hide_score: Option<String>,
|
||||||
@ -117,6 +129,7 @@ impl Config {
|
|||||||
Self {
|
Self {
|
||||||
sfw_only: parse("REDLIB_SFW_ONLY"),
|
sfw_only: parse("REDLIB_SFW_ONLY"),
|
||||||
default_theme: parse("REDLIB_DEFAULT_THEME"),
|
default_theme: parse("REDLIB_DEFAULT_THEME"),
|
||||||
|
default_mascot: parse("REDLIB_DEFAULT_MASCOT"),
|
||||||
default_front_page: parse("REDLIB_DEFAULT_FRONT_PAGE"),
|
default_front_page: parse("REDLIB_DEFAULT_FRONT_PAGE"),
|
||||||
default_layout: parse("REDLIB_DEFAULT_LAYOUT"),
|
default_layout: parse("REDLIB_DEFAULT_LAYOUT"),
|
||||||
default_post_sort: parse("REDLIB_DEFAULT_POST_SORT"),
|
default_post_sort: parse("REDLIB_DEFAULT_POST_SORT"),
|
||||||
@ -125,8 +138,10 @@ impl Config {
|
|||||||
default_show_nsfw: parse("REDLIB_DEFAULT_SHOW_NSFW"),
|
default_show_nsfw: parse("REDLIB_DEFAULT_SHOW_NSFW"),
|
||||||
default_blur_nsfw: parse("REDLIB_DEFAULT_BLUR_NSFW"),
|
default_blur_nsfw: parse("REDLIB_DEFAULT_BLUR_NSFW"),
|
||||||
default_use_hls: parse("REDLIB_DEFAULT_USE_HLS"),
|
default_use_hls: parse("REDLIB_DEFAULT_USE_HLS"),
|
||||||
default_hide_hls_notification: parse("REDLIB_DEFAULT_HIDE_HLS"),
|
default_ffmpeg_video_downloads: parse("REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS"),
|
||||||
|
default_hide_hls_notification: parse("REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION"),
|
||||||
default_hide_awards: parse("REDLIB_DEFAULT_HIDE_AWARDS"),
|
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_hide_score: parse("REDLIB_DEFAULT_HIDE_SCORE"),
|
||||||
default_subscriptions: parse("REDLIB_DEFAULT_SUBSCRIPTIONS"),
|
default_subscriptions: parse("REDLIB_DEFAULT_SUBSCRIPTIONS"),
|
||||||
default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"),
|
default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"),
|
||||||
@ -141,6 +156,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
|||||||
match name {
|
match name {
|
||||||
"REDLIB_SFW_ONLY" => config.sfw_only.clone(),
|
"REDLIB_SFW_ONLY" => config.sfw_only.clone(),
|
||||||
"REDLIB_DEFAULT_THEME" => config.default_theme.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_FRONT_PAGE" => config.default_front_page.clone(),
|
||||||
"REDLIB_DEFAULT_LAYOUT" => config.default_layout.clone(),
|
"REDLIB_DEFAULT_LAYOUT" => config.default_layout.clone(),
|
||||||
"REDLIB_DEFAULT_COMMENT_SORT" => config.default_comment_sort.clone(),
|
"REDLIB_DEFAULT_COMMENT_SORT" => config.default_comment_sort.clone(),
|
||||||
@ -148,9 +164,11 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
|||||||
"REDLIB_DEFAULT_SHOW_NSFW" => config.default_show_nsfw.clone(),
|
"REDLIB_DEFAULT_SHOW_NSFW" => config.default_show_nsfw.clone(),
|
||||||
"REDLIB_DEFAULT_BLUR_NSFW" => config.default_blur_nsfw.clone(),
|
"REDLIB_DEFAULT_BLUR_NSFW" => config.default_blur_nsfw.clone(),
|
||||||
"REDLIB_DEFAULT_USE_HLS" => config.default_use_hls.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_HIDE_HLS_NOTIFICATION" => config.default_hide_hls_notification.clone(),
|
||||||
"REDLIB_DEFAULT_WIDE" => config.default_wide.clone(),
|
"REDLIB_DEFAULT_WIDE" => config.default_wide.clone(),
|
||||||
"REDLIB_DEFAULT_HIDE_AWARDS" => config.default_hide_awards.clone(),
|
"REDLIB_DEFAULT_HIDE_AWARDS" => 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_HIDE_SCORE" => config.default_hide_score.clone(),
|
||||||
"REDLIB_DEFAULT_SUBSCRIPTIONS" => config.default_subscriptions.clone(),
|
"REDLIB_DEFAULT_SUBSCRIPTIONS" => config.default_subscriptions.clone(),
|
||||||
"REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION" => config.default_disable_visit_reddit_confirmation.clone(),
|
"REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION" => config.default_disable_visit_reddit_confirmation.clone(),
|
||||||
|
@ -151,7 +151,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if have_after {
|
if have_after {
|
||||||
before = "t3_".to_owned();
|
"t3_".clone_into(&mut before);
|
||||||
before.push_str(&duplicates[0].id);
|
before.push_str(&duplicates[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
if have_before {
|
if have_before {
|
||||||
// The next batch will need to start from one after the
|
// The next batch will need to start from one after the
|
||||||
// last post in the current batch.
|
// last post in the current batch.
|
||||||
after = "t3_".to_owned();
|
"t3_".clone_into(&mut after);
|
||||||
after.push_str(&duplicates[l - 1].id);
|
after.push_str(&duplicates[l - 1].id);
|
||||||
|
|
||||||
// Here is where things get terrible. Notice that we
|
// 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 {
|
match json(new_path, true).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if !response[1]["data"]["children"].as_array().unwrap_or(&Vec::new()).is_empty() {
|
if !response[1]["data"]["children"].as_array().unwrap_or(&Vec::new()).is_empty() {
|
||||||
before = "t3_".to_owned();
|
"t3_".clone_into(&mut before);
|
||||||
before.push_str(&duplicates[0].id);
|
before.push_str(&duplicates[0].id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,7 @@ impl InstanceInfo {
|
|||||||
["Hide awards", &convert(&self.config.default_hide_awards)],
|
["Hide awards", &convert(&self.config.default_hide_awards)],
|
||||||
["Hide score", &convert(&self.config.default_hide_score)],
|
["Hide score", &convert(&self.config.default_hide_score)],
|
||||||
["Theme", &convert(&self.config.default_theme)],
|
["Theme", &convert(&self.config.default_theme)],
|
||||||
|
["Mascot", &convert(&self.config.default_mascot)],
|
||||||
["Front page", &convert(&self.config.default_front_page)],
|
["Front page", &convert(&self.config.default_front_page)],
|
||||||
["Layout", &convert(&self.config.default_layout)],
|
["Layout", &convert(&self.config.default_layout)],
|
||||||
["Wide", &convert(&self.config.default_wide)],
|
["Wide", &convert(&self.config.default_wide)],
|
||||||
@ -168,6 +169,7 @@ impl InstanceInfo {
|
|||||||
Hide awards: {:?}\n
|
Hide awards: {:?}\n
|
||||||
Hide score: {:?}\n
|
Hide score: {:?}\n
|
||||||
Default theme: {:?}\n
|
Default theme: {:?}\n
|
||||||
|
Default mascot: {:?}\n
|
||||||
Default front page: {:?}\n
|
Default front page: {:?}\n
|
||||||
Default layout: {:?}\n
|
Default layout: {:?}\n
|
||||||
Default wide: {:?}\n
|
Default wide: {:?}\n
|
||||||
@ -190,6 +192,7 @@ impl InstanceInfo {
|
|||||||
self.config.default_hide_awards,
|
self.config.default_hide_awards,
|
||||||
self.config.default_hide_score,
|
self.config.default_hide_score,
|
||||||
self.config.default_theme,
|
self.config.default_theme,
|
||||||
|
self.config.default_mascot,
|
||||||
self.config.default_front_page,
|
self.config.default_front_page,
|
||||||
self.config.default_layout,
|
self.config.default_layout,
|
||||||
self.config.default_wide,
|
self.config.default_wide,
|
||||||
|
60
src/main.rs
@ -26,7 +26,7 @@ use client::{canonical_path, proxy};
|
|||||||
use log::info;
|
use log::info;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use server::RequestExt;
|
use server::RequestExt;
|
||||||
use utils::{error, redirect, ThemeAssets};
|
use utils::{error, redirect, ThemeAssets, MascotAssets};
|
||||||
|
|
||||||
use crate::client::OAUTH_CLIENT;
|
use crate::client::OAUTH_CLIENT;
|
||||||
|
|
||||||
@ -78,6 +78,17 @@ 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> {
|
async fn resource(body: &str, content_type: &str, cache: bool) -> Result<Response<Body>, String> {
|
||||||
let mut res = Response::builder()
|
let mut res = Response::builder()
|
||||||
.status(200)
|
.status(200)
|
||||||
@ -111,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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
@ -166,7 +191,7 @@ async fn main() {
|
|||||||
|
|
||||||
let listener = [address, ":", port].concat();
|
let listener = [address, ":", port].concat();
|
||||||
|
|
||||||
println!("Starting Redlib...");
|
println!("Starting Redsunlib...");
|
||||||
|
|
||||||
// Begin constructing a server
|
// Begin constructing a server
|
||||||
let mut app = server::Server::new();
|
let mut app = server::Server::new();
|
||||||
@ -189,7 +214,7 @@ async fn main() {
|
|||||||
"Referrer-Policy" => "no-referrer",
|
"Referrer-Policy" => "no-referrer",
|
||||||
"X-Content-Type-Options" => "nosniff",
|
"X-Content-Type-Options" => "nosniff",
|
||||||
"X-Frame-Options" => "DENY",
|
"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 {
|
if let Some(expire_time) = hsts {
|
||||||
@ -224,14 +249,34 @@ async fn main() {
|
|||||||
app.at("/touch-icon-iphone.png").get(|_| iphone_logo().boxed());
|
app.at("/touch-icon-iphone.png").get(|_| iphone_logo().boxed());
|
||||||
app.at("/apple-touch-icon.png").get(|_| iphone_logo().boxed());
|
app.at("/apple-touch-icon.png").get(|_| iphone_logo().boxed());
|
||||||
app
|
app
|
||||||
.at("/playHLSVideo.js")
|
.at("/videoUtils.js")
|
||||||
.get(|_| resource(include_str!("../static/playHLSVideo.js"), "text/javascript", false).boxed());
|
.get(|_| resource(include_str!("../static/videoUtils.js"), "text/javascript", false).boxed());
|
||||||
app
|
app
|
||||||
.at("/hls.min.js")
|
.at("/hls.min.js")
|
||||||
.get(|_| resource(include_str!("../static/hls.min.js"), "text/javascript", false).boxed());
|
.get(|_| resource(include_str!("../static/hls.min.js"), "text/javascript", false).boxed());
|
||||||
app
|
app
|
||||||
.at("/highlighted.js")
|
.at("/highlighted.js")
|
||||||
.get(|_| resource(include_str!("../static/highlighted.js"), "text/javascript", false).boxed());
|
.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
|
// Proxy media through Redlib
|
||||||
app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed());
|
app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed());
|
||||||
@ -265,6 +310,9 @@ async fn main() {
|
|||||||
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
||||||
app.at("/settings/update").get(|r| settings::update(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
|
// Subreddit services
|
||||||
app
|
app
|
||||||
.at("/r/:sub")
|
.at("/r/:sub")
|
||||||
@ -373,7 +421,7 @@ async fn main() {
|
|||||||
// Default service in case no routes match
|
// Default service in case no routes match
|
||||||
app.at("/*").get(|req| error(req, "Nothing here").boxed());
|
app.at("/*").get(|req| error(req, "Nothing here").boxed());
|
||||||
|
|
||||||
println!("Running Redlib v{} on {listener}!", env!("CARGO_PKG_VERSION"));
|
println!("Running Redsunlib v{} on {listener}!", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
let server = app.listen(&listener);
|
let server = app.listen(&listener);
|
||||||
|
|
||||||
|
@ -4,6 +4,44 @@
|
|||||||
// Filled in with real app versions
|
// Filled in with real app versions
|
||||||
pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
|
pub static _IOS_APP_VERSION_LIST: &[&str; 1] = &[""];
|
||||||
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
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.28.0/Build 1046887",
|
||||||
"Version 2023.29.0/Build 1059855",
|
"Version 2023.29.0/Build 1059855",
|
||||||
"Version 2023.30.0/Build 1078734",
|
"Version 2023.30.0/Build 1078734",
|
||||||
@ -26,14 +64,14 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
|||||||
"Version 2023.44.0/Build 1268622",
|
"Version 2023.44.0/Build 1268622",
|
||||||
"Version 2023.45.0/Build 1281371",
|
"Version 2023.45.0/Build 1281371",
|
||||||
"Version 2023.47.0/Build 1303604",
|
"Version 2023.47.0/Build 1303604",
|
||||||
"Version 2023.48.0/Build 1319123",
|
"Version 2022.42.0/Build 638508",
|
||||||
"Version 2023.49.0/Build 1321715",
|
"Version 2022.43.0/Build 648277",
|
||||||
"Version 2023.49.1/Build 1322281",
|
"Version 2022.44.0/Build 664348",
|
||||||
"Version 2023.50.0/Build 1332338",
|
"Version 2022.45.0/Build 677985",
|
||||||
"Version 2023.50.1/Build 1345844",
|
"Version 2023.01.0/Build 709875",
|
||||||
"Version 2024.02.0/Build 1368985",
|
"Version 2023.02.0/Build 717912",
|
||||||
"Version 2024.03.0/Build 1379408",
|
"Version 2023.03.0/Build 729220",
|
||||||
"Version 2024.04.0/Build 1391236",
|
"Version 2023.04.0/Build 744681",
|
||||||
"Version 2023.05.0/Build 755453",
|
"Version 2023.05.0/Build 755453",
|
||||||
"Version 2023.06.0/Build 775017",
|
"Version 2023.06.0/Build 775017",
|
||||||
"Version 2023.07.0/Build 788827",
|
"Version 2023.07.0/Build 788827",
|
||||||
@ -56,14 +94,14 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
|||||||
"Version 2023.19.0/Build 927681",
|
"Version 2023.19.0/Build 927681",
|
||||||
"Version 2023.20.0/Build 943980",
|
"Version 2023.20.0/Build 943980",
|
||||||
"Version 2023.20.1/Build 946732",
|
"Version 2023.20.1/Build 946732",
|
||||||
"Version 2023.21.0/Build 956283",
|
"Version 2022.20.0/Build 487703",
|
||||||
"Version 2023.22.0/Build 968223",
|
"Version 2022.21.0/Build 492436",
|
||||||
"Version 2023.23.0/Build 983896",
|
"Version 2022.22.0/Build 498700",
|
||||||
"Version 2023.24.0/Build 998541",
|
"Version 2022.23.0/Build 502374",
|
||||||
"Version 2023.25.0/Build 1014750",
|
"Version 2022.23.1/Build 506606",
|
||||||
"Version 2023.25.1/Build 1018737",
|
"Version 2022.24.0/Build 510950",
|
||||||
"Version 2023.26.0/Build 1019073",
|
"Version 2022.24.1/Build 513462",
|
||||||
"Version 2023.27.0/Build 1031923",
|
"Version 2022.25.0/Build 515072",
|
||||||
"Version 2022.25.1/Build 516394",
|
"Version 2022.25.1/Build 516394",
|
||||||
"Version 2022.25.2/Build 519915",
|
"Version 2022.25.2/Build 519915",
|
||||||
"Version 2022.26.0/Build 521193",
|
"Version 2022.26.0/Build 521193",
|
||||||
@ -86,14 +124,14 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
|||||||
"Version 2022.40.0/Build 624782",
|
"Version 2022.40.0/Build 624782",
|
||||||
"Version 2022.41.0/Build 630468",
|
"Version 2022.41.0/Build 630468",
|
||||||
"Version 2022.41.1/Build 634168",
|
"Version 2022.41.1/Build 634168",
|
||||||
"Version 2022.42.0/Build 638508",
|
"Version 2021.39.1/Build 372418",
|
||||||
"Version 2022.43.0/Build 648277",
|
"Version 2021.41.0/Build 376052",
|
||||||
"Version 2022.44.0/Build 664348",
|
"Version 2021.42.0/Build 378193",
|
||||||
"Version 2022.45.0/Build 677985",
|
"Version 2021.43.0/Build 382019",
|
||||||
"Version 2023.01.0/Build 709875",
|
"Version 2021.44.0/Build 385129",
|
||||||
"Version 2023.02.0/Build 717912",
|
"Version 2021.45.0/Build 387663",
|
||||||
"Version 2023.03.0/Build 729220",
|
"Version 2021.46.0/Build 392043",
|
||||||
"Version 2023.04.0/Build 744681",
|
"Version 2021.47.0/Build 394342",
|
||||||
"Version 2022.10.0/Build 429896",
|
"Version 2022.10.0/Build 429896",
|
||||||
"Version 2022.1.0/Build 402829",
|
"Version 2022.1.0/Build 402829",
|
||||||
"Version 2022.11.0/Build 433004",
|
"Version 2022.11.0/Build 433004",
|
||||||
@ -106,15 +144,7 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
|||||||
"Version 2022.17.0/Build 468480",
|
"Version 2022.17.0/Build 468480",
|
||||||
"Version 2022.18.0/Build 473740",
|
"Version 2022.18.0/Build 473740",
|
||||||
"Version 2022.19.1/Build 482464",
|
"Version 2022.19.1/Build 482464",
|
||||||
"Version 2022.20.0/Build 487703",
|
|
||||||
"Version 2022.2.0/Build 405543",
|
"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.3.0/Build 408637",
|
||||||
"Version 2022.4.0/Build 411368",
|
"Version 2022.4.0/Build 411368",
|
||||||
"Version 2022.5.0/Build 414731",
|
"Version 2022.5.0/Build 414731",
|
||||||
@ -124,35 +154,5 @@ pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
|
|||||||
"Version 2022.7.0/Build 420849",
|
"Version 2022.7.0/Build 420849",
|
||||||
"Version 2022.8.0/Build 423906",
|
"Version 2022.8.0/Build 423906",
|
||||||
"Version 2022.9.0/Build 426592",
|
"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] = &[""];
|
pub static _IOS_OS_VERSION_LIST: &[&str; 1] = &[""];
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use brotli::enc::{BrotliCompress, BrotliEncoderParams};
|
use brotli::enc::{BrotliCompress, BrotliEncoderParams};
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
@ -15,6 +17,7 @@ use libflate::gzip;
|
|||||||
use route_recognizer::{Params, Router};
|
use route_recognizer::{Params, Router};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
fmt::Display,
|
||||||
io,
|
io,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
result::Result,
|
result::Result,
|
||||||
@ -65,12 +68,12 @@ impl CompressionType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for CompressionType {
|
impl Display for CompressionType {
|
||||||
fn to_string(&self) -> String {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Gzip => "gzip".to_string(),
|
Self::Gzip => write!(f, "gzip"),
|
||||||
Self::Brotli => "br".to_string(),
|
Self::Brotli => write!(f, "br"),
|
||||||
Self::Passthrough => String::new(),
|
Self::Passthrough => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,9 @@ struct SettingsTemplate {
|
|||||||
|
|
||||||
// CONSTANTS
|
// CONSTANTS
|
||||||
|
|
||||||
const PREFS: [&str; 15] = [
|
const PREFS: [&str; 17] = [
|
||||||
"theme",
|
"theme",
|
||||||
|
"mascot",
|
||||||
"front_page",
|
"front_page",
|
||||||
"layout",
|
"layout",
|
||||||
"wide",
|
"wide",
|
||||||
@ -29,8 +30,10 @@ const PREFS: [&str; 15] = [
|
|||||||
"show_nsfw",
|
"show_nsfw",
|
||||||
"blur_nsfw",
|
"blur_nsfw",
|
||||||
"use_hls",
|
"use_hls",
|
||||||
|
"ffmpeg_video_downloads",
|
||||||
"hide_hls_notification",
|
"hide_hls_notification",
|
||||||
"autoplay_videos",
|
"autoplay_videos",
|
||||||
|
"hide_sidebar_and_summary",
|
||||||
"fixed_navbar",
|
"fixed_navbar",
|
||||||
"hide_awards",
|
"hide_awards",
|
||||||
"hide_score",
|
"hide_score",
|
||||||
@ -80,7 +83,7 @@ pub async fn set(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
Some(value) => response.insert_cookie(
|
Some(value) => response.insert_cookie(
|
||||||
Cookie::build((name.to_owned(), value.clone()))
|
Cookie::build((name.to_owned(), value.clone()))
|
||||||
.path("/")
|
.path("/")
|
||||||
.http_only(true)
|
.http_only(name != "ffmpeg_video_downloads")
|
||||||
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
|
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
|
@ -7,6 +7,8 @@ use askama::Template;
|
|||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
use time::{Duration, OffsetDateTime};
|
use time::{Duration, OffsetDateTime};
|
||||||
|
|
||||||
// STRUCTS
|
// STRUCTS
|
||||||
@ -50,10 +52,13 @@ struct WallTemplate {
|
|||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GEO_FILTER_MATCH: Lazy<Regex> = Lazy::new(|| Regex::new(r"geo_filter=(?<region>\w+)").unwrap());
|
||||||
|
|
||||||
// SERVICES
|
// SERVICES
|
||||||
pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
// Build Reddit API path
|
// Build Reddit API path
|
||||||
let root = req.uri().path() == "/";
|
let root = req.uri().path() == "/";
|
||||||
|
let query = req.uri().query().unwrap_or_default().to_string();
|
||||||
let subscribed = setting(&req, "subscriptions");
|
let subscribed = setting(&req, "subscriptions");
|
||||||
let front_page = setting(&req, "front_page");
|
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());
|
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
|
||||||
@ -107,7 +112,11 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
|
|
||||||
let mut params = String::from("&raw_json=1");
|
let mut params = String::from("&raw_json=1");
|
||||||
if sub_name == "popular" {
|
if sub_name == "popular" {
|
||||||
params.push_str("&geo_filter=GLOBAL");
|
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}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = format!("/r/{sub_name}/{sort}.json?{}{params}", req.uri().query().unwrap_or_default());
|
let path = format!("/r/{sub_name}/{sort}.json?{}{params}", req.uri().query().unwrap_or_default());
|
||||||
|
92
src/utils.rs
@ -570,14 +570,18 @@ pub struct Params {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Preferences {
|
pub struct Preferences {
|
||||||
pub available_themes: Vec<String>,
|
pub available_themes: Vec<String>,
|
||||||
|
pub available_mascots: Vec<String>,
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
|
pub mascot: String,
|
||||||
pub front_page: String,
|
pub front_page: String,
|
||||||
pub layout: String,
|
pub layout: String,
|
||||||
pub wide: String,
|
pub wide: String,
|
||||||
pub show_nsfw: String,
|
pub show_nsfw: String,
|
||||||
pub blur_nsfw: String,
|
pub blur_nsfw: String,
|
||||||
pub hide_hls_notification: String,
|
pub hide_hls_notification: String,
|
||||||
|
pub hide_sidebar_and_summary: String,
|
||||||
pub use_hls: String,
|
pub use_hls: String,
|
||||||
|
pub ffmpeg_video_downloads: String,
|
||||||
pub autoplay_videos: String,
|
pub autoplay_videos: String,
|
||||||
pub fixed_navbar: String,
|
pub fixed_navbar: String,
|
||||||
pub disable_visit_reddit_confirmation: String,
|
pub disable_visit_reddit_confirmation: String,
|
||||||
@ -594,6 +598,11 @@ pub struct Preferences {
|
|||||||
#[include = "*.css"]
|
#[include = "*.css"]
|
||||||
pub struct ThemeAssets;
|
pub struct ThemeAssets;
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "static/mascots/"]
|
||||||
|
#[include = "*.png"]
|
||||||
|
pub struct MascotAssets;
|
||||||
|
|
||||||
impl Preferences {
|
impl Preferences {
|
||||||
// Build preferences from cookies
|
// Build preferences from cookies
|
||||||
pub fn new(req: &Request<Body>) -> Self {
|
pub fn new(req: &Request<Body>) -> Self {
|
||||||
@ -604,15 +613,26 @@ impl Preferences {
|
|||||||
let chunks: Vec<&str> = file.as_ref().split(".css").collect();
|
let chunks: Vec<&str> = file.as_ref().split(".css").collect();
|
||||||
themes.push(chunks[0].to_owned());
|
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 {
|
Self {
|
||||||
available_themes: themes,
|
available_themes: themes,
|
||||||
|
available_mascots: mascots,
|
||||||
theme: setting(req, "theme"),
|
theme: setting(req, "theme"),
|
||||||
|
mascot: setting(req, "mascot"),
|
||||||
front_page: setting(req, "front_page"),
|
front_page: setting(req, "front_page"),
|
||||||
layout: setting(req, "layout"),
|
layout: setting(req, "layout"),
|
||||||
wide: setting(req, "wide"),
|
wide: setting(req, "wide"),
|
||||||
show_nsfw: setting(req, "show_nsfw"),
|
show_nsfw: setting(req, "show_nsfw"),
|
||||||
|
hide_sidebar_and_summary: setting(req, "hide_sidebar_and_summary"),
|
||||||
blur_nsfw: setting(req, "blur_nsfw"),
|
blur_nsfw: setting(req, "blur_nsfw"),
|
||||||
use_hls: setting(req, "use_hls"),
|
use_hls: setting(req, "use_hls"),
|
||||||
|
ffmpeg_video_downloads: setting(req, "ffmpeg_video_downloads"),
|
||||||
hide_hls_notification: setting(req, "hide_hls_notification"),
|
hide_hls_notification: setting(req, "hide_hls_notification"),
|
||||||
autoplay_videos: setting(req, "autoplay_videos"),
|
autoplay_videos: setting(req, "autoplay_videos"),
|
||||||
fixed_navbar: setting_or_default(req, "fixed_navbar", "on".to_string()),
|
fixed_navbar: setting_or_default(req, "fixed_navbar", "on".to_string()),
|
||||||
@ -875,16 +895,18 @@ pub fn format_url(url: &str) -> String {
|
|||||||
|
|
||||||
// These are links we want to replace in-body
|
// 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_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)\.redd\.it(.*)[^?]").unwrap());
|
static REDDIT_PREVIEW_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https?://(external-preview|preview|i)\.redd\.it(.*)[^?]").unwrap());
|
||||||
static REDDIT_EMOJI_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"https?://(www|).redditstatic\.com/(.*)").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
|
// Rewrite Reddit links to Redlib in body of text
|
||||||
pub fn rewrite_urls(input_text: &str) -> String {
|
pub fn rewrite_urls(input_text: &str) -> String {
|
||||||
let text1 =
|
let mut text1 =
|
||||||
// Rewrite Reddit links to Redlib
|
// Rewrite Reddit links to Redlib
|
||||||
REDDIT_REGEX.replace_all(input_text, r#"href="/"#)
|
REDDIT_REGEX.replace_all(input_text, r#"href="/"#)
|
||||||
.to_string();
|
.to_string();
|
||||||
let text1 = REDDIT_EMOJI_REGEX
|
text1 = REDDIT_EMOJI_REGEX
|
||||||
.replace_all(&text1, format_url(REDDIT_EMOJI_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default()))
|
.replace_all(&text1, format_url(REDDIT_EMOJI_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default()))
|
||||||
.to_string()
|
.to_string()
|
||||||
// Remove (html-encoded) "\" from URLs.
|
// Remove (html-encoded) "\" from URLs.
|
||||||
@ -892,12 +914,56 @@ pub fn rewrite_urls(input_text: &str) -> String {
|
|||||||
.replace("\\_", "_");
|
.replace("\\_", "_");
|
||||||
|
|
||||||
// Rewrite external media previews to Redlib
|
// Rewrite external media previews to Redlib
|
||||||
if REDDIT_PREVIEW_REGEX.is_match(&text1) {
|
loop {
|
||||||
REDDIT_PREVIEW_REGEX
|
if REDDIT_PREVIEW_REGEX.find(&text1).is_none() {
|
||||||
.replace_all(&text1, format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default()))
|
return text1;
|
||||||
.to_string()
|
} else {
|
||||||
} else {
|
let formatted_url = format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default());
|
||||||
text1
|
|
||||||
|
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. "\"" is how the quotes are formatted within image_text beforehand
|
||||||
|
image_caption = image_caption.replace("\\"", "\"");
|
||||||
|
|
||||||
|
_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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1148,3 +1214,11 @@ async fn test_fetching_ws() {
|
|||||||
assert!(post.ws_url.starts_with("wss://k8s-lb.wss.redditmedia.com/link/"));
|
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&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc">caption 1</a></p>"#;
|
||||||
|
let output = r#"<p><figure><a href="/preview/pre/6awags382xo31.png?width=2560&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc"><img loading="lazy" src="/preview/pre/6awags382xo31.png?width=2560&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc"></a><figcaption>caption 1</figcaption></figure></p"#;
|
||||||
|
assert_eq!(rewrite_urls(input), output);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 4.3 KiB |
2
static/ffmpeg/814.ffmpeg.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
!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
|
1
static/ffmpeg/814.ffmpeg.js.map
Normal file
21
static/ffmpeg/ffmpeg-core.js
Normal file
BIN
static/ffmpeg/ffmpeg-core.wasm
Executable file
1
static/ffmpeg/ffmpeg-util.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
!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)})()));
|
2
static/ffmpeg/ffmpeg.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
!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
|
1
static/ffmpeg/ffmpeg.js.map
Normal file
1
static/hls.min.js
vendored
BIN
static/logo.png
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 60 KiB |
33
static/logo.svg
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?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>
|
After Width: | Height: | Size: 943 B |
BIN
static/logo_full.png
Normal file
After Width: | Height: | Size: 219 KiB |
BIN
static/mascots/BoymoderBlahaj.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
static/mascots/redsunlib.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
@ -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
|
|
@ -34,6 +34,7 @@
|
|||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
src: url('/Inter.var.woff2') format('woff2-variations');
|
src: url('/Inter.var.woff2') format('woff2-variations');
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Automatic theme selection */
|
/* Automatic theme selection */
|
||||||
@ -112,6 +113,16 @@ pre, form, fieldset, table, th, td, select, input {
|
|||||||
font-family: "Inter", sans-serif;
|
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 {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
padding-bottom: var(--footer-height);
|
padding-bottom: var(--footer-height);
|
||||||
@ -177,6 +188,11 @@ nav #redlib {
|
|||||||
vertical-align: -2px;
|
vertical-align: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
#settings_link {
|
#settings_link {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@ -969,10 +985,6 @@ a.search_subreddit:hover {
|
|||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery figcaption {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery .outbound_url {
|
.gallery .outbound_url {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -998,6 +1010,13 @@ a.search_subreddit:hover {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post_body img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.post_poll {
|
.post_poll {
|
||||||
grid-area: post_poll;
|
grid-area: post_poll;
|
||||||
padding: 5px 15px 5px 12px;
|
padding: 5px 15px 5px 12px;
|
||||||
@ -1084,7 +1103,7 @@ a.search_subreddit:hover {
|
|||||||
display: auto;
|
display: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 480px) {
|
@media screen and (min-width: 481px) {
|
||||||
#post_links > li.mobile_item {
|
#post_links > li.mobile_item {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -1161,6 +1180,22 @@ a.search_subreddit:hover {
|
|||||||
display: flex;
|
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 {
|
.comment_left, .comment_right {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -1406,6 +1441,7 @@ summary.comment_data {
|
|||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
background: var(--foreground);
|
background: var(--foreground);
|
||||||
|
width: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
aside.prefs {
|
aside.prefs {
|
||||||
@ -1458,10 +1494,19 @@ input[type="submit"] {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md > *:not(:first-child) {
|
.md > p:not(:first-child):not(:last-child) {
|
||||||
margin-top: 20px;
|
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 h1 { font-size: 22px; }
|
||||||
.md h2 { font-size: 20px; }
|
.md h2 { font-size: 20px; }
|
||||||
.md h3 { font-size: 18px; }
|
.md h3 { font-size: 18px; }
|
||||||
@ -1715,18 +1760,53 @@ td, th {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality-selector {
|
.video-options {
|
||||||
border: 2px var(--outside) solid;
|
border: 2px var(--outside) solid;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
float: right;
|
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);
|
background-color: var(--background);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality-selector option:hover {
|
.video-options option:hover {
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
color: var(--text);
|
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);
|
||||||
}
|
}
|
17
static/themes/catppuccin.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* 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 */
|
||||||
|
}
|
14
static/themes/icebergDark.css
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* 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);
|
||||||
|
}
|
228
static/videoUtils.js
Normal 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
|
@ -1,7 +1,7 @@
|
|||||||
{% import "utils.html" as utils %}
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="{% if prefs.fixed_navbar == "on" %}fixed_navbar{% endif %}">
|
||||||
<head>
|
<head>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<title>{% block title %}Redlib{% endblock %}</title>
|
<title>{% block title %}Redlib{% endblock %}</title>
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<nav class="
|
<nav class="
|
||||||
{% if prefs.fixed_navbar == "on" %} fixed_navbar{% endif %}">
|
{% if prefs.fixed_navbar == "on" %} fixed_navbar{% endif %}">
|
||||||
<div id="logo">
|
<div id="logo">
|
||||||
<a id="redlib" href="/"><span id="lib">red</span><span id="reddit">lib.</span></a>
|
<a id="redlib" href="/"><span id="lib">red</span><span id="reddit">sun</span><span id="lib">lib</span></a>
|
||||||
{% block subscriptions %}{% endblock %}
|
{% block subscriptions %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block search %}{% endblock %}
|
{% block search %}{% endblock %}
|
||||||
@ -59,6 +59,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{% if prefs.mascot != "none" && prefs.mascot != "" %}
|
||||||
|
<!-- MASCOT -->
|
||||||
|
<div class="mascot">
|
||||||
|
<img src="/mascot/{{ prefs.mascot }}.png">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
{% block body %}
|
{% block body %}
|
||||||
@ -76,7 +83,7 @@
|
|||||||
<a href="/info" title="View instance information">ⓘ View instance info</a>
|
<a href="/info" title="View instance information">ⓘ View instance info</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-button">
|
<div class="footer-button">
|
||||||
<a href="https://github.com/redlib-org/redlib" title="View code on GitHub"><> Code</a>
|
<a href="https://git.stardust.wtf/iridium/redsunlib" title="View code on git.stardust.wtf"><> Code</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -97,9 +97,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% 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="/hls.min.js"></script>
|
||||||
<script src="/playHLSVideo.js"></script>
|
<script src="/videoUtils.js"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if params.typed != "sr_user" %}
|
{% if params.typed != "sr_user" %}
|
||||||
|
@ -19,6 +19,12 @@
|
|||||||
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
|
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Interface</legend>
|
<legend>Interface</legend>
|
||||||
@ -71,19 +77,36 @@
|
|||||||
<input type="hidden" value="off" name="autoplay_videos">
|
<input type="hidden" value="off" name="autoplay_videos">
|
||||||
<input type="checkbox" name="autoplay_videos" id="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
|
<input type="checkbox" name="autoplay_videos" id="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
<div class="prefs-group">
|
<div class="prefs-group">
|
||||||
<label for="fixed_navbar">Keep navbar fixed</label>
|
<label for="fixed_navbar">Keep navbar fixed</label>
|
||||||
<input type="hidden" value="off" name="fixed_navbar">
|
<input type="hidden" value="off" name="fixed_navbar">
|
||||||
<input type="checkbox" name="fixed_navbar" {% if prefs.fixed_navbar == "on" %}checked{% endif %}>
|
<input type="checkbox" name="fixed_navbar" {% if prefs.fixed_navbar == "on" %}checked{% endif %}>
|
||||||
</div>
|
</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>
|
||||||
<div class="prefs-group">
|
<div class="prefs-group">
|
||||||
<label for="use_hls">Use HLS for videos</label>
|
<label for="use_hls">Use HLS for videos</label>
|
||||||
|
{% if prefs.ffmpeg_video_downloads != "on" %}
|
||||||
<details id="feeds">
|
<details id="feeds">
|
||||||
<summary>Why?</summary>
|
<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>
|
<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>
|
</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="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>
|
||||||
|
<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>
|
||||||
<div class="prefs-group">
|
<div class="prefs-group">
|
||||||
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
|
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
|
||||||
@ -142,7 +165,7 @@
|
|||||||
|
|
||||||
<div id="settings_note">
|
<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><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 }}&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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -64,25 +64,29 @@
|
|||||||
{% call utils::post_in_list(post) %}
|
{% call utils::post_in_list(post) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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="/hls.min.js"></script>
|
||||||
<script src="/playHLSVideo.js"></script>
|
<script src="/videoUtils.js"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
{% if !ends.0.is_empty() %}
|
{% 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 %}
|
{% endif %}
|
||||||
|
|
||||||
{% if !ends.1.is_empty() %}
|
{% 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 %}
|
{% endif %}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_filtered || (!sub.name.is_empty() && sub.name != "all" && sub.name != "popular" && !sub.name.contains("+")) %}
|
{% if is_filtered || (!sub.name.is_empty() && sub.name != "all" && sub.name != "popular" && !sub.name.contains("+")) && prefs.hide_sidebar_and_summary != "on" %}
|
||||||
<aside>
|
<aside>
|
||||||
{% if is_filtered %}
|
{% if is_filtered %}
|
||||||
<center>(Content from r/{{ sub.name }} has been filtered)</center>
|
<center>(Content from r/{{ sub.name }} has been filtered)</center>
|
||||||
@ -133,7 +137,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details class="panel" id="sidebar">
|
<details class="panel" id="sidebar" open>
|
||||||
<summary id="sidebar_label">Sidebar</summary>
|
<summary id="sidebar_label">Sidebar</summary>
|
||||||
<div id="sidebar_contents">
|
<div id="sidebar_contents">
|
||||||
{{ sub.info|safe }}
|
{{ sub.info|safe }}
|
||||||
|
@ -71,9 +71,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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="/hls.min.js"></script>
|
||||||
<script src="/playHLSVideo.js"></script>
|
<script src="/videoUtils.js"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -117,7 +117,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% else if post.post_type == "video" || post.post_type == "gif" %}
|
{% 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>
|
<script src="/hls.min.js"></script>
|
||||||
<div class="post_media_content">
|
<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>
|
<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" />
|
<source src="{{ post.media.url }}" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
<script src="/playHLSVideo.js"></script>
|
<script src="/videoUtils.js"></script>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="post_media_content">
|
<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>
|
<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>
|
<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>
|
</div>
|
||||||
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "video" %}
|
{% 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">
|
<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">
|
<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" />
|
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
||||||
|