Compare commits
132 Commits
284e03ccf9
...
v0.35.1
Author | SHA1 | Date | |
---|---|---|---|
1644e68e43 | |||
f5d4cc49fb | |||
f22f7841a8 | |||
b6c6e64cfe | |||
6d3db31b11 | |||
f0eb496d6a | |||
e984646ae6 | |||
60c0d63583 | |||
3bd8b511a7 | |||
8c5aaaa33d | |||
bd0a312487 | |||
023cc8505b | |||
2e476dea63 | |||
f398d16c22 | |||
d045a5760a | |||
07bf20dbc0 | |||
518bf03e04 | |||
00dee52320 | |||
48873c01b9 | |||
951fe400ae | |||
bacc9e35df | |||
724b960112 | |||
ff2e274e5e | |||
7c821d541a | |||
7aca5791b1 | |||
8b3783096b | |||
59b702aa15 | |||
edf0109095 | |||
ecf0f91464 | |||
69f9d9ff3c | |||
1d44bd180e | |||
3301da1ef1 | |||
138934f30c | |||
5396c0783e | |||
213481ef53 | |||
2d5cfcb41d | |||
f460895cc0 | |||
5193164719 | |||
9013e589dd | |||
997cd8f829 | |||
5a13b9892b | |||
91975865b8 | |||
3491e754ac | |||
a5a5cbb734 | |||
5117af4137 | |||
394e975724 | |||
a67cbb99a6 | |||
1408c32a4d | |||
30944579d7 | |||
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 | |||
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 | |||
27f25e0fb1 | |||
89140c8cf7 | |||
99048c4683 | |||
ccfe7d0eeb | |||
f7c182dcd8 | |||
9bd540d659 | |||
4f6a14739b |
10
.env.example
@ -14,6 +14,8 @@ 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)
|
||||||
@ -24,20 +26,28 @@ REDLIB_DEFAULT_WIDE=off
|
|||||||
REDLIB_DEFAULT_POST_SORT=hot
|
REDLIB_DEFAULT_POST_SORT=hot
|
||||||
# Set the default comment sort method (options: confidence, top, new, controversial, old)
|
# Set the default comment sort method (options: confidence, top, new, controversial, old)
|
||||||
REDLIB_DEFAULT_COMMENT_SORT=confidence
|
REDLIB_DEFAULT_COMMENT_SORT=confidence
|
||||||
|
# Enable blurring Spoiler content by default
|
||||||
|
REDLIB_DEFAULT_BLUR_SPOILER=off
|
||||||
# Enable showing NSFW content by default
|
# Enable showing NSFW content by default
|
||||||
REDLIB_DEFAULT_SHOW_NSFW=off
|
REDLIB_DEFAULT_SHOW_NSFW=off
|
||||||
# Enable blurring NSFW content by default
|
# Enable blurring NSFW content by default
|
||||||
REDLIB_DEFAULT_BLUR_NSFW=off
|
REDLIB_DEFAULT_BLUR_NSFW=off
|
||||||
# Enable HLS video format by default
|
# Enable HLS video format by default
|
||||||
REDLIB_DEFAULT_USE_HLS=off
|
REDLIB_DEFAULT_USE_HLS=off
|
||||||
|
# Enable audio+video downloads with ffmpeg.wasm
|
||||||
|
REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS=off
|
||||||
# Hide HLS notification by default
|
# Hide HLS notification by default
|
||||||
REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off
|
REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off
|
||||||
# Disable autoplay videos by default
|
# Disable autoplay videos by default
|
||||||
REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off
|
REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off
|
||||||
# Define a default list of subreddit subscriptions (format: sub1+sub2+sub3)
|
# Define a default list of subreddit subscriptions (format: sub1+sub2+sub3)
|
||||||
REDLIB_DEFAULT_SUBSCRIPTIONS=
|
REDLIB_DEFAULT_SUBSCRIPTIONS=
|
||||||
|
# Define a default list of subreddit filters (format: sub1+sub2+sub3)
|
||||||
|
REDLIB_DEFAULT_FILTERS=
|
||||||
# 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: |
|
||||||
|
6
.gitignore
vendored
@ -1,4 +1,10 @@
|
|||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
|
redlib.toml
|
||||||
|
|
||||||
# Idea Files
|
# Idea Files
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# nix files
|
||||||
|
.direnv/
|
||||||
|
result
|
||||||
|
2
.replit
@ -1,2 +0,0 @@
|
|||||||
run = "while :; do set -ex; nix-env -iA nixpkgs.unzip; curl -o./redlib.zip -fsSL -- https://nightly.link/redlib-org/redlib/workflows/main-rust/main/redlib.zip; unzip -n redlib.zip; mv target/x86_64-unknown-linux-gnu/release/redlib .; chmod +x redlib; set +e; ./redlib -H 63115200; sleep 1; done"
|
|
||||||
language = "bash"
|
|
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>
|
||||||
|
546
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://git.stardust.wtf/Ayaka/redlib"
|
repository = "https://git.stardust.wtf/iridium/redlib"
|
||||||
version = "0.31.2"
|
version = "0.35.1"
|
||||||
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"
|
||||||
|
16
Dockerfile
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
FROM rust:alpine AS builder
|
FROM rust:alpine AS builder
|
||||||
|
|
||||||
RUN apk add --no-cache musl-dev
|
RUN apk add --no-cache musl-dev git
|
||||||
|
|
||||||
WORKDIR /redlib
|
WORKDIR /redsunlib
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@ -19,16 +19,16 @@ COPY --from=builder /usr/share/ca-certificates /usr/share/ca-certificates
|
|||||||
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
||||||
|
|
||||||
# Copy our build
|
# Copy our build
|
||||||
COPY --from=builder /redlib/target/x86_64-unknown-linux-musl/release/redlib /usr/local/bin/redlib
|
COPY --from=builder /redsunlib/target/x86_64-unknown-linux-musl/release/redsunlib /usr/local/bin/redsunlib
|
||||||
|
|
||||||
# Use an unprivileged user.
|
# Use an unprivileged user.
|
||||||
RUN adduser --home /nonexistent --no-create-home --disabled-password redlib
|
RUN adduser --home /nonexistent --no-create-home --disabled-password redsunlib
|
||||||
USER redlib
|
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"]
|
@ -1,20 +0,0 @@
|
|||||||
FROM alpine:3.19
|
|
||||||
|
|
||||||
ARG TARGET
|
|
||||||
|
|
||||||
RUN apk add --no-cache curl
|
|
||||||
|
|
||||||
RUN curl -L https://github.com/redlib-org/redlib/releases/latest/download/redlib-${TARGET}.tar.gz | \
|
|
||||||
tar xz -C /usr/local/bin/
|
|
||||||
|
|
||||||
RUN adduser --home /nonexistent --no-create-home --disabled-password redlib
|
|
||||||
USER redlib
|
|
||||||
|
|
||||||
# Tell Docker to expose port 8080
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Run a healthcheck every minute to make sure redlib is functional
|
|
||||||
HEALTHCHECK --interval=1m --timeout=3s CMD wget --spider --q http://localhost:8080/settings || exit 1
|
|
||||||
|
|
||||||
CMD ["redlib"]
|
|
||||||
|
|
165
README.md
@ -1,46 +1,45 @@
|
|||||||
# 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 <sup><sub>(minor)</sub></sup> 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).
|
### Disclaimer
|
||||||
|
|
||||||
- 🚀 Fast: written in Rust for blazing-fast speeds and memory safety
|
There are rapid changes/features in this fork that can<sup>(will)</sup> change without notice. If you want to host this version, be aware that it's likely to break at some point. I still wouldn't recommend it in a production environment unless you know what you're doing. Or like living on the edge.......
|
||||||
- ☁️ Light: no JavaScript, no ads, no tracking, no bloat
|
|
||||||
- 🕵 Private: all requests are proxied through the server, including media
|
> I would also like to thank the maintainers and contributors of both [Redlib](https://github.com/redlib-org/redlib) and [Libreddit](https://github.com/libreddit/libreddit) for all the work they did while I just added some low quality tacky features. ❤️
|
||||||
- 🔒 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
|
||||||
|
1. [Redsunlib](#redsunlib)
|
||||||
1. [Redlib](#redlib)
|
- [Disclaimer](#disclaimer)
|
||||||
2. [Instances](#instances)
|
2. [Table of Contents](#table-of-contents)
|
||||||
3. [About](#about)
|
3. [Instances](#instances)
|
||||||
|
4. [About](#about)
|
||||||
|
- [The Name](#the-name)
|
||||||
- [Built with](#built-with)
|
- [Built with](#built-with)
|
||||||
- [How is it different from other Reddit front ends?](#how-is-it-different-from-other-reddit-front-ends)
|
- [How is it different from other Reddit front ends?](#how-is-it-different-from-other-reddit-front-ends)
|
||||||
- [Teddit](#teddit)
|
- [Teddit](#teddit)
|
||||||
- [Libreddit](#libreddit)
|
- [Libreddit](#libreddit)
|
||||||
4. [Comparison](#comparison)
|
5. [Comparison](#comparison)
|
||||||
- [Speed](#speed)
|
- [Speed](#speed)
|
||||||
- [Privacy](#privacy)
|
- [Privacy](#privacy)
|
||||||
- [Reddit](#reddit)
|
- [Reddit](#reddit)
|
||||||
- [Redlib](#redlib-1)
|
- [Redlib](#redlib-1)
|
||||||
- [Server](#server)
|
- [Server](#server)
|
||||||
- [Official instance (redlib.matthew.science)](#official-instance-redlibmatthewscience)
|
6. [Deployment](#deployment)
|
||||||
5. [Deployment](#deployment)
|
|
||||||
- [Docker](#docker)
|
- [Docker](#docker)
|
||||||
- [Docker Compose](#docker-compose)
|
- [Docker Compose](#docker-compose)
|
||||||
- [Docker CLI](#docker-cli)
|
- [Docker CLI](#docker-cli)
|
||||||
- [Binary](#binary)
|
- [Binary](#binary)
|
||||||
- [Running as a systemd service](#running-as-a-systemd-service)
|
- [Running as a systemd service](#running-as-a-systemd-service)
|
||||||
- [Building from source](#building-from-source)
|
- [Building from source](#building-from-source)
|
||||||
- [Replit/Heroku/Glitch](#replit-heroku-glitch)
|
7. [Configuration](#configuration)
|
||||||
- [launchd (macOS)](#launchd-macos)
|
|
||||||
6. [Configuration](#configuration)
|
|
||||||
- [Instance settings](#instance-settings)
|
- [Instance settings](#instance-settings)
|
||||||
- [Default user settings](#default-user-settings)
|
- [Default user settings](#default-user-settings)
|
||||||
|
|
||||||
@ -48,26 +47,26 @@
|
|||||||
|
|
||||||
# Instances
|
# Instances
|
||||||
|
|
||||||
> [!TIP]
|
> [!WARNING]
|
||||||
> 🔗 **Want to automatically redirect Reddit links to Redlib? Use [LibRedirect](https://github.com/libredirect/libredirect) or [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect)!**
|
> 🔗 **Currently public Redsunlib instance are not available, consider using a [redlib](https://github.com/redlib-org/redlib-instances/blob/main/instances.md) instance if you are not comfortable running your own**
|
||||||
|
|
||||||
An up-to-date table of instances is available in [Markdown](https://github.com/redlib-org/redlib-instances/blob/main/instances.md) and [machine-readable JSON](https://github.com/redlib-org/redlib-instances/blob/main/instances.json).
|
You are more than welcome to host an instance and submit an issue if you want it added. That is, if you've read the [Disclaimer](#disclaimer) and it's within your "personal risk tolerance." ;)
|
||||||
|
|
||||||
Both files are part of the [redlib-instances](https://github.com/redlib-org/redlib-instances) repository. To contribute your [self-hosted instance](#deployment) to the list, see the [redlib-instances README](https://github.com/redlib-org/redlib-instances/blob/main/README.md).
|
|
||||||
|
|
||||||
For information on instance uptime, see the [Uptime Robot status page](https://stats.uptimerobot.com/mpmqAs1G2Q).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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).
|
||||||
|
|
||||||
|
## The Name
|
||||||
|
|
||||||
|
**Red sun** in the sky + Red**lib** = Redsunlib
|
||||||
|
|
||||||
|
And at the time, I was reading an excerpt from Mao Zedong, so the name seemed appropriate. But paradoxically named since Reddit is basically the sinophobia capital of the internet :/
|
||||||
|
|
||||||
|
|
||||||
## Built with
|
## Built with
|
||||||
|
|
||||||
- [Rust](https://www.rust-lang.org/) - Programming language
|
- [Rust](https://www.rust-lang.org/) - Programming language
|
||||||
@ -158,16 +157,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
|
||||||
@ -180,7 +169,7 @@ For configuration options, see the [Configuration section](#Configuration).
|
|||||||
|
|
||||||
[Docker](https://www.docker.com) lets you run containerized applications. Containers are loosely isolated environments that are lightweight and contain everything needed to run the application, so there's no need to rely on what's installed on the host.
|
[Docker](https://www.docker.com) lets you run containerized applications. Containers are loosely isolated environments that are lightweight and contain everything needed to run the application, so there's no need to rely on what's installed on the host.
|
||||||
|
|
||||||
Docker images for Redlib are available at [quay.io](https://quay.io/repository/redlib/redlib), with support for `amd64`, `arm64`, and `armv7` platforms.
|
Docker images for Redsunlib are available at our [Gitea container registry](https://git.stardust.wtf/iridium/-/packages/container/redsunlib/latest), currently only with support for `amd64`, if you need `arm64`, or `armv7` platforms you can either build Redsunlib yourself or open an [issue](https://git.stardust.wtf/iridium/redsunlib/issues) :)
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
|
|
||||||
@ -203,24 +192,18 @@ 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
|
||||||
docker pull quay.io/redlib/redlib:latest
|
docker pull git.stardust.wtf/iridium/redsunlib:latest
|
||||||
docker run -d --name redlib -p 8080:8080 quay.io/redlib/redlib:latest
|
docker run -d --name redlib -p 8080:8080 git.stardust.wtf/iridium/redsunlib:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy using a different port on the host (in this case, port 80):
|
Deploy using a different port on the host (in this case, port 80):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull quay.io/redlib/redlib:latest
|
docker pull git.stardust.wtf/iridium/redsunlib:latest
|
||||||
docker run -d --name redlib -p 80:8080 quay.io/redlib/redlib:latest
|
docker run -d --name redlib -p 80:8080 git.stardust.wtf/iridium/redsunlib:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're using a reverse proxy in front of Redlib, prefix the port numbers with `127.0.0.1` so that Redlib only listens on the host port **locally**. For example, if the host port for Redlib is `8080`, specify `127.0.0.1:8080:8080`.
|
If you're using a reverse proxy in front of Redlib, prefix the port numbers with `127.0.0.1` so that Redlib only listens on the host port **locally**. For example, if the host port for Redlib is `8080`, specify `127.0.0.1:8080:8080`.
|
||||||
@ -233,19 +216,7 @@ docker logs -f redlib
|
|||||||
|
|
||||||
## Binary
|
## Binary
|
||||||
|
|
||||||
If you're on Linux, you can grab a binary from [the newest release](https://github.com/redlib-org/redlib/releases/latest) from GitHub.
|
Currently binaries are not supplied at this moment but will be at some point in the future but can be [built from source](#building-from-source)
|
||||||
|
|
||||||
Download the binary using [Wget](https://www.gnu.org/software/wget/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wget https://github.com/redlib-org/redlib/releases/download/v0.31.0/redlib
|
|
||||||
```
|
|
||||||
|
|
||||||
Make the binary executable and change its ownership to `root`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo chmod +x redlib && sudo chown root:root redlib
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the binary to `/usr/bin`:
|
Copy the binary to `/usr/bin`:
|
||||||
|
|
||||||
@ -289,59 +260,13 @@ Before=nginx.service
|
|||||||
|
|
||||||
## Building from source
|
## Building from source
|
||||||
|
|
||||||
To deploy Redlib with changes not yet included in the latest release, you can build the application from source.
|
To deploy Redsunlib with changes not yet included in the latest release, you can build the application from source.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/redlib-org/redlib && cd redlib
|
git clone https://git.stardust.wtf/iridium/redsunlib && cd redsunlib
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
## Replit/Heroku
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> These are free hosting options, but they are _not_ private and will monitor server usage to prevent abuse. If you need a free and easy setup, this method may work best for you.
|
|
||||||
|
|
||||||
<a href="https://repl.it/github/redlib-org/redlib"><img src="https://repl.it/badge/github/redlib-org/redlib" alt="Run on Repl.it" height="32" /></a>
|
|
||||||
[](https://heroku.com/deploy?template=https://github.com/redlib-org/redlib)
|
|
||||||
|
|
||||||
## launchd (macOS)
|
|
||||||
|
|
||||||
If you are on macOS, you can use the [launchd](https://en.wikipedia.org/wiki/Launchd) service available in `contrib/redlib.plist`.
|
|
||||||
|
|
||||||
Install it with `cp contrib/redlib.plist ~/Library/LaunchAgents/`.
|
|
||||||
|
|
||||||
Load and start it with `launchctl load ~/Library/LaunchAgents/redlib.plist`.
|
|
||||||
|
|
||||||
<!-- ## Cargo
|
|
||||||
|
|
||||||
Make sure Rust stable is installed along with `cargo`, Rust's package manager.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install libreddit
|
|
||||||
``` -->
|
|
||||||
|
|
||||||
<!-- ## AUR
|
|
||||||
|
|
||||||
For ArchLinux users, Redlib is available from the AUR as [`libreddit-git`](https://aur.archlinux.org/packages/libreddit-git).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yay -S libreddit-git
|
|
||||||
```
|
|
||||||
## NetBSD/pkgsrc
|
|
||||||
|
|
||||||
For NetBSD users, Redlib is available from the official repositories.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pkgin install libreddit
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, if you prefer to build from source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /usr/pkgsrc/libreddit
|
|
||||||
make install
|
|
||||||
``` -->
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@ -371,7 +296,7 @@ REDLIB_DEFAULT_USE_HLS = "on"
|
|||||||
> If using the Docker CLI, add ` --env-file .env` to the command that runs Redlib. For example:
|
> If using the Docker CLI, add ` --env-file .env` to the command that runs Redlib. For example:
|
||||||
>
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> docker run -d --name redlib -p 8080:8080 --env-file .env quay.io/redlib/redlib:latest
|
> docker run -d --name redlib -p 8080:8080 --env-file .env git.stardust.wtf/iridium/redsunlib:latest
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
> If using Docker Compose, no changes are needed as the `.env` file is already referenced in `compose.yaml` via the `env_file: .env` line.
|
> If using Docker Compose, no changes are needed as the `.env` file is already referenced in `compose.yaml` via the `env_file: .env` line.
|
||||||
@ -380,12 +305,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 | `undelete.pullpush.io` | 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,15 +319,18 @@ 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` |
|
||||||
|
| `MASCOT` | `["BoymoderBlahaj", "redsunlib" ... Add more at ./static/mascots] ` | _(none)_ |
|
||||||
| `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` |
|
||||||
| `POST_SORT` | `["hot", "new", "top", "rising", "controversial"]` | `hot` |
|
| `POST_SORT` | `["hot", "new", "top", "rising", "controversial"]` | `hot` |
|
||||||
| `COMMENT_SORT` | `["confidence", "top", "new", "controversial", "old"]` | `confidence` |
|
| `COMMENT_SORT` | `["confidence", "top", "new", "controversial", "old"]` | `confidence` |
|
||||||
|
| `BLUR_SPOILER` | `["on", "off"]` | `off` |
|
||||||
| `SHOW_NSFW` | `["on", "off"]` | `off` |
|
| `SHOW_NSFW` | `["on", "off"]` | `off` |
|
||||||
| `BLUR_NSFW` | `["on", "off"]` | `off` |
|
| `BLUR_NSFW` | `["on", "off"]` | `off` |
|
||||||
| `USE_HLS` | `["on", "off"]` | `off` |
|
| `USE_HLS` | `["on", "off"]` | `off` |
|
||||||
|
| `FFMPEG_VIDEO_DOWNLOADS` | `["on", "off"]` | `off` |
|
||||||
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
|
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
|
||||||
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
|
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
|
||||||
| `SUBSCRIPTIONS` | `+`-delimited list of subreddits (`sub1+sub2+sub3+...`) | _(none)_ |
|
| `SUBSCRIPTIONS` | `+`-delimited list of subreddits (`sub1+sub2+sub3+...`) | _(none)_ |
|
||||||
|
18
app.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "Redlib",
|
"name": "Redsunlib",
|
||||||
"description": "Private front-end for Reddit",
|
"description": "Private front-end for Reddit",
|
||||||
"buildpacks": [
|
"buildpacks": [
|
||||||
{
|
{
|
||||||
@ -14,6 +14,9 @@
|
|||||||
"REDLIB_DEFAULT_THEME": {
|
"REDLIB_DEFAULT_THEME": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
|
"REDLIB_DEFAULT_MASCOT": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
"REDLIB_DEFAULT_FRONT_PAGE": {
|
"REDLIB_DEFAULT_FRONT_PAGE": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
@ -29,13 +32,19 @@
|
|||||||
"REDLIB_DEFAULT_POST_SORT": {
|
"REDLIB_DEFAULT_POST_SORT": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
|
"REDLIB_DEFAULT_BLUR_SPOILER": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
"REDLIB_DEFAULT_SHOW_NSFW": {
|
"REDLIB_DEFAULT_SHOW_NSFW": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"REDLIB_DEFAULT_BLUR_NSFW": {
|
"REDLIB_DEFAULT_BLUR_NSFW": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"REDLIB_USE_HLS": {
|
"REDLIB_DEFAULT_USE_HLS": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"REDLIB_HIDE_HLS_NOTIFICATION": {
|
"REDLIB_HIDE_HLS_NOTIFICATION": {
|
||||||
@ -54,11 +63,14 @@
|
|||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"REDLIB_ROBOTS_DISABLE_INDEXING": {
|
"REDLIB_ROBOTS_DISABLE_INDEXING": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"REDLIB_DEFAULT_SUBSCRIPTIONS": {
|
"REDLIB_DEFAULT_SUBSCRIPTIONS": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
|
"REDLIB_DEFAULT_FILTERS": {
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
"REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION": {
|
"REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION": {
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
|
@ -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:
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
ADDRESS=0.0.0.0
|
ADDRESS=0.0.0.0
|
||||||
PORT=12345
|
PORT=12345
|
||||||
#REDLIB_DEFAULT_THEME=default
|
#REDLIB_DEFAULT_THEME=default
|
||||||
|
#REDLIB_DEFAULT_MASCOT=none
|
||||||
#REDLIB_DEFAULT_FRONT_PAGE=default
|
#REDLIB_DEFAULT_FRONT_PAGE=default
|
||||||
#REDLIB_DEFAULT_LAYOUT=card
|
#REDLIB_DEFAULT_LAYOUT=card
|
||||||
#REDLIB_DEFAULT_WIDE=off
|
#REDLIB_DEFAULT_WIDE=off
|
||||||
#REDLIB_DEFAULT_POST_SORT=hot
|
#REDLIB_DEFAULT_POST_SORT=hot
|
||||||
#REDLIB_DEFAULT_COMMENT_SORT=confidence
|
#REDLIB_DEFAULT_COMMENT_SORT=confidence
|
||||||
|
#REDLIB_DEFAULT_BLUR_SPOILER=off
|
||||||
#REDLIB_DEFAULT_SHOW_NSFW=off
|
#REDLIB_DEFAULT_SHOW_NSFW=off
|
||||||
#REDLIB_DEFAULT_BLUR_NSFW=off
|
#REDLIB_DEFAULT_BLUR_NSFW=off
|
||||||
#REDLIB_DEFAULT_USE_HLS=off
|
#REDLIB_DEFAULT_USE_HLS=off
|
||||||
|
#REDLIB_DEFAULT_FFMPEG_VIDEO_DOWNLOADS=off
|
||||||
#REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off
|
#REDLIB_DEFAULT_HIDE_HLS_NOTIFICATION=off
|
||||||
#REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off
|
#REDLIB_DEFAULT_AUTOPLAY_VIDEOS=off
|
||||||
#REDLIB_DEFAULT_SUBSCRIPTIONS=off (sub1+sub2+sub3)
|
#REDLIB_DEFAULT_SUBSCRIPTIONS=off (sub1+sub2+sub3)
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
31
scripts/load_test.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
base_url = "http://localhost:8080"
|
||||||
|
|
||||||
|
full_path = f"{base_url}/r/politics"
|
||||||
|
|
||||||
|
ctr = 0
|
||||||
|
|
||||||
|
def fetch_url(url):
|
||||||
|
global ctr
|
||||||
|
response = requests.get(url)
|
||||||
|
ctr += 1
|
||||||
|
print(f"Request count: {ctr}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
while full_path:
|
||||||
|
response = requests.get(full_path)
|
||||||
|
ctr += 1
|
||||||
|
print(f"Request count: {ctr}")
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
comment_links = soup.find_all('a', class_='post_comments')
|
||||||
|
comment_urls = [base_url + link['href'] for link in comment_links]
|
||||||
|
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||||
|
executor.map(fetch_url, comment_urls)
|
||||||
|
next_link = soup.find('a', accesskey='N')
|
||||||
|
if next_link:
|
||||||
|
full_path = base_url + next_link['href']
|
||||||
|
else:
|
||||||
|
break
|
@ -5,11 +5,13 @@ use hyper::client::HttpConnector;
|
|||||||
use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri};
|
use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri};
|
||||||
use hyper_rustls::HttpsConnector;
|
use hyper_rustls::HttpsConnector;
|
||||||
use libflate::gzip;
|
use libflate::gzip;
|
||||||
use log::error;
|
use log::{error, trace, warn};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use percent_encoding::{percent_encode, CONTROLS};
|
use percent_encoding::{percent_encode, CONTROLS};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::sync::atomic::{AtomicU16, Ordering::SeqCst};
|
||||||
use std::{io, result::Result};
|
use std::{io, result::Result};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
@ -36,6 +38,8 @@ pub static OAUTH_CLIENT: Lazy<RwLock<Oauth>> = Lazy::new(|| {
|
|||||||
RwLock::new(client)
|
RwLock::new(client)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99);
|
||||||
|
|
||||||
/// Gets the canonical path for a resource on Reddit. This is accomplished by
|
/// Gets the canonical path for a resource on Reddit. This is accomplished by
|
||||||
/// making a `HEAD` request to Reddit at the path given in `path`.
|
/// making a `HEAD` request to Reddit at the path given in `path`.
|
||||||
///
|
///
|
||||||
@ -180,6 +184,7 @@ 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(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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()
|
||||||
@ -306,19 +311,59 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo
|
|||||||
#[cached(size = 100, time = 30, result = true)]
|
#[cached(size = 100, time = 30, result = true)]
|
||||||
pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
||||||
// Closure to quickly build errors
|
// Closure to quickly build errors
|
||||||
let err = |msg: &str, e: String| -> Result<Value, String> {
|
let err = |msg: &str, e: String, path: String| -> Result<Value, String> {
|
||||||
// eprintln!("{} - {}: {}", url, msg, e);
|
// eprintln!("{} - {}: {}", url, msg, e);
|
||||||
Err(format!("{msg}: {e}"))
|
Err(format!("{msg}: {e} | {path}"))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// First, handle rolling over the OAUTH_CLIENT if need be.
|
||||||
|
let current_rate_limit = OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst);
|
||||||
|
if current_rate_limit < 10 {
|
||||||
|
warn!("Rate limit {current_rate_limit} is low. Spawning force_refresh_token()");
|
||||||
|
OAUTH_RATELIMIT_REMAINING.store(99, Ordering::SeqCst);
|
||||||
|
tokio::spawn(force_refresh_token());
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the url...
|
// Fetch the url...
|
||||||
match reddit_get(path.clone(), quarantine).await {
|
match reddit_get(path.clone(), quarantine).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
|
// Ratelimit remaining
|
||||||
|
if let Some(Ok(remaining)) = response.headers().get("x-ratelimit-remaining").map(|val| val.to_str()) {
|
||||||
|
trace!("Ratelimit remaining: {}", remaining);
|
||||||
|
if let Ok(remaining) = remaining.parse::<f32>().map(|f| f.round() as u16) {
|
||||||
|
OAUTH_RATELIMIT_REMAINING.store(remaining, SeqCst);
|
||||||
|
} else {
|
||||||
|
warn!("Failed to parse rate limit {remaining} from header.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ratelimit used
|
||||||
|
if let Some(Ok(used)) = response.headers().get("x-ratelimit-used").map(|val| val.to_str()) {
|
||||||
|
trace!("Ratelimit used: {}", used);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ratelimit reset
|
||||||
|
let reset = if let Some(Ok(reset)) = response.headers().get("x-ratelimit-reset").map(|val| val.to_str()) {
|
||||||
|
trace!("Ratelimit reset: {}", reset);
|
||||||
|
Some(reset.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// asynchronously aggregate the chunks of the body
|
// asynchronously aggregate the chunks of the body
|
||||||
match hyper::body::aggregate(response).await {
|
match hyper::body::aggregate(response).await {
|
||||||
Ok(body) => {
|
Ok(body) => {
|
||||||
|
let has_remaining = body.has_remaining();
|
||||||
|
|
||||||
|
if !has_remaining {
|
||||||
|
return match reset {
|
||||||
|
Some(val) => Err(format!("Reddit rate limit exceeded. Will reset in: {val}")),
|
||||||
|
None => Err("Reddit rate limit exceeded".to_string()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the response from Reddit as JSON
|
// Parse the response from Reddit as JSON
|
||||||
match serde_json::from_reader(body.reader()) {
|
match serde_json::from_reader(body.reader()) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
@ -331,7 +376,7 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
|||||||
let () = force_refresh_token().await;
|
let () = force_refresh_token().await;
|
||||||
return Err("OAuth token has expired. Please refresh the page!".to_string());
|
return Err("OAuth token has expired. Please refresh the page!".to_string());
|
||||||
}
|
}
|
||||||
Err(format!("Reddit error {} \"{}\": {}", json["error"], json["reason"], json["message"]))
|
Err(format!("Reddit error {} \"{}\": {} | {path}", json["error"], json["reason"], json["message"]))
|
||||||
} else {
|
} else {
|
||||||
Ok(json)
|
Ok(json)
|
||||||
}
|
}
|
||||||
@ -341,21 +386,24 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
|
|||||||
if status.is_server_error() {
|
if status.is_server_error() {
|
||||||
Err("Reddit is having issues, check if there's an outage".to_string())
|
Err("Reddit is having issues, check if there's an outage".to_string())
|
||||||
} else {
|
} else {
|
||||||
err("Failed to parse page JSON data", e.to_string())
|
err("Failed to parse page JSON data", e.to_string(), path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => err("Failed receiving body from Reddit", e.to_string()),
|
Err(e) => err("Failed receiving body from Reddit", e.to_string(), path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => err("Couldn't send request to Reddit", e),
|
Err(e) => err("Couldn't send request to Reddit", e, path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
static POPULAR_URL: &str = "/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL";
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_localization_popular() {
|
async fn test_localization_popular() {
|
||||||
let val = json("/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL".to_string(), false).await.unwrap();
|
let val = json(POPULAR_URL.to_string(), false).await.unwrap();
|
||||||
assert_eq!("GLOBAL", val["data"]["geo_filter"].as_str().unwrap());
|
assert_eq!("GLOBAL", val["data"]["geo_filter"].as_str().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>,
|
||||||
@ -48,6 +52,10 @@ pub struct Config {
|
|||||||
#[serde(alias = "LIBREDDIT_DEFAULT_POST_SORT")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_POST_SORT")]
|
||||||
pub(crate) default_post_sort: Option<String>,
|
pub(crate) default_post_sort: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "REDLIB_DEFAULT_BLUR_SPOILER")]
|
||||||
|
#[serde(alias = "LIBREDDIT_DEFAULT_BLUR_SPOILER")]
|
||||||
|
pub(crate) default_blur_spoiler: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "REDLIB_DEFAULT_SHOW_NSFW")]
|
#[serde(rename = "REDLIB_DEFAULT_SHOW_NSFW")]
|
||||||
#[serde(alias = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_SHOW_NSFW")]
|
||||||
pub(crate) default_show_nsfw: Option<String>,
|
pub(crate) default_show_nsfw: Option<String>,
|
||||||
@ -60,6 +68,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 +80,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>,
|
||||||
@ -76,6 +92,10 @@ pub struct Config {
|
|||||||
#[serde(alias = "LIBREDDIT_DEFAULT_SUBSCRIPTIONS")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_SUBSCRIPTIONS")]
|
||||||
pub(crate) default_subscriptions: Option<String>,
|
pub(crate) default_subscriptions: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "REDLIB_DEFAULT_FILTERS")]
|
||||||
|
#[serde(alias = "LIBREDDIT_DEFAULT_FILTERS")]
|
||||||
|
pub(crate) default_filters: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")]
|
#[serde(rename = "REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")]
|
||||||
#[serde(alias = "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")]
|
#[serde(alias = "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")]
|
||||||
pub(crate) default_disable_visit_reddit_confirmation: Option<String>,
|
pub(crate) default_disable_visit_reddit_confirmation: Option<String>,
|
||||||
@ -117,18 +137,23 @@ 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"),
|
||||||
default_wide: parse("REDLIB_DEFAULT_WIDE"),
|
default_wide: parse("REDLIB_DEFAULT_WIDE"),
|
||||||
default_comment_sort: parse("REDLIB_DEFAULT_COMMENT_SORT"),
|
default_comment_sort: parse("REDLIB_DEFAULT_COMMENT_SORT"),
|
||||||
|
default_blur_spoiler: parse("REDLIB_DEFAULT_BLUR_SPOILER"),
|
||||||
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_filters: parse("REDLIB_DEFAULT_FILTERS"),
|
||||||
default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"),
|
default_disable_visit_reddit_confirmation: parse("REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"),
|
||||||
banner: parse("REDLIB_BANNER"),
|
banner: parse("REDLIB_BANNER"),
|
||||||
robots_disable_indexing: parse("REDLIB_ROBOTS_DISABLE_INDEXING"),
|
robots_disable_indexing: parse("REDLIB_ROBOTS_DISABLE_INDEXING"),
|
||||||
@ -141,18 +166,23 @@ 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(),
|
||||||
"REDLIB_DEFAULT_POST_SORT" => config.default_post_sort.clone(),
|
"REDLIB_DEFAULT_POST_SORT" => config.default_post_sort.clone(),
|
||||||
|
"REDLIB_DEFAULT_BLUR_SPOILER" => config.default_blur_spoiler.clone(),
|
||||||
"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_FILTERS" => config.default_filters.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(),
|
||||||
"REDLIB_BANNER" => config.banner.clone(),
|
"REDLIB_BANNER" => config.banner.clone(),
|
||||||
"REDLIB_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(),
|
"REDLIB_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(),
|
||||||
@ -225,6 +255,12 @@ fn test_default_subscriptions() {
|
|||||||
assert_eq!(get_setting("REDLIB_DEFAULT_SUBSCRIPTIONS"), Some("news+bestof".into()));
|
assert_eq!(get_setting("REDLIB_DEFAULT_SUBSCRIPTIONS"), Some("news+bestof".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[sealed_test(env = [("REDLIB_DEFAULT_FILTERS", "news+bestof")])]
|
||||||
|
fn test_default_filters() {
|
||||||
|
assert_eq!(get_setting("REDLIB_DEFAULT_FILTERS"), Some("news+bestof".into()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[sealed_test]
|
#[sealed_test]
|
||||||
fn test_pushshift() {
|
fn test_pushshift() {
|
||||||
|
@ -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,16 +136,20 @@ 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)],
|
||||||
["Comment sort", &convert(&self.config.default_comment_sort)],
|
["Comment sort", &convert(&self.config.default_comment_sort)],
|
||||||
["Post sort", &convert(&self.config.default_post_sort)],
|
["Post sort", &convert(&self.config.default_post_sort)],
|
||||||
|
["Blur Spoiler", &convert(&self.config.default_blur_spoiler)],
|
||||||
["Show NSFW", &convert(&self.config.default_show_nsfw)],
|
["Show NSFW", &convert(&self.config.default_show_nsfw)],
|
||||||
["Blur NSFW", &convert(&self.config.default_blur_nsfw)],
|
["Blur NSFW", &convert(&self.config.default_blur_nsfw)],
|
||||||
["Use HLS", &convert(&self.config.default_use_hls)],
|
["Use HLS", &convert(&self.config.default_use_hls)],
|
||||||
|
["Use FFmpeg", &convert(&self.config.default_ffmpeg_video_downloads)],
|
||||||
["Hide HLS notification", &convert(&self.config.default_hide_hls_notification)],
|
["Hide HLS notification", &convert(&self.config.default_hide_hls_notification)],
|
||||||
["Subscriptions", &convert(&self.config.default_subscriptions)],
|
["Subscriptions", &convert(&self.config.default_subscriptions)],
|
||||||
|
["Filters", &convert(&self.config.default_filters)],
|
||||||
])
|
])
|
||||||
.with_header_row(["Default preferences"]),
|
.with_header_row(["Default preferences"]),
|
||||||
);
|
);
|
||||||
@ -168,16 +172,20 @@ 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
|
||||||
Default comment sort: {:?}\n
|
Default comment sort: {:?}\n
|
||||||
Default post sort: {:?}\n
|
Default post sort: {:?}\n
|
||||||
|
Default blur Spoiler: {:?}\n
|
||||||
Default show NSFW: {:?}\n
|
Default show NSFW: {:?}\n
|
||||||
Default blur NSFW: {:?}\n
|
Default blur NSFW: {:?}\n
|
||||||
Default use HLS: {:?}\n
|
Default use HLS: {:?}\n
|
||||||
|
Default use FFmpeg: {:?}\n
|
||||||
Default hide HLS notification: {:?}\n
|
Default hide HLS notification: {:?}\n
|
||||||
Default subscriptions: {:?}\n",
|
Default subscriptions: {:?}\n
|
||||||
|
Default filters: {:?}\n",
|
||||||
self.package_name,
|
self.package_name,
|
||||||
self.crate_version,
|
self.crate_version,
|
||||||
self.git_commit,
|
self.git_commit,
|
||||||
@ -190,16 +198,20 @@ 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,
|
||||||
self.config.default_comment_sort,
|
self.config.default_comment_sort,
|
||||||
self.config.default_post_sort,
|
self.config.default_post_sort,
|
||||||
|
self.config.default_blur_spoiler,
|
||||||
self.config.default_show_nsfw,
|
self.config.default_show_nsfw,
|
||||||
self.config.default_blur_nsfw,
|
self.config.default_blur_nsfw,
|
||||||
self.config.default_use_hls,
|
self.config.default_use_hls,
|
||||||
|
self.config.default_ffmpeg_video_downloads,
|
||||||
self.config.default_hide_hls_notification,
|
self.config.default_hide_hls_notification,
|
||||||
self.config.default_subscriptions,
|
self.config.default_subscriptions,
|
||||||
|
self.config.default_filters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
StringType::Html => self.to_table(),
|
StringType::Html => self.to_table(),
|
||||||
|
62
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
|
||||||
@ -135,7 +160,7 @@ async fn main() {
|
|||||||
.long("address")
|
.long("address")
|
||||||
.value_name("ADDRESS")
|
.value_name("ADDRESS")
|
||||||
.help("Sets address to listen on")
|
.help("Sets address to listen on")
|
||||||
.default_value("0.0.0.0")
|
.default_value("[::]")
|
||||||
.num_args(1),
|
.num_args(1),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -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);
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, sync::atomic::Ordering, time::Duration};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{CLIENT, OAUTH_CLIENT},
|
client::{CLIENT, OAUTH_CLIENT, OAUTH_RATELIMIT_REMAINING},
|
||||||
oauth_resources::ANDROID_APP_VERSION_LIST,
|
oauth_resources::ANDROID_APP_VERSION_LIST,
|
||||||
};
|
};
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use hyper::{client, Body, Method, Request};
|
use hyper::{client, Body, Method, Request};
|
||||||
use log::info;
|
use log::{info, trace};
|
||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
@ -131,6 +131,7 @@ pub async fn token_daemon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn force_refresh_token() {
|
pub async fn force_refresh_token() {
|
||||||
|
trace!("Rolling over refresh token. Current rate limit: {}", OAUTH_RATELIMIT_REMAINING.load(Ordering::SeqCst));
|
||||||
OAUTH_CLIENT.write().await.refresh().await;
|
OAUTH_CLIENT.write().await.refresh().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,18 +19,22 @@ struct SettingsTemplate {
|
|||||||
|
|
||||||
// CONSTANTS
|
// CONSTANTS
|
||||||
|
|
||||||
const PREFS: [&str; 15] = [
|
const PREFS: [&str; 19] = [
|
||||||
"theme",
|
"theme",
|
||||||
|
"mascot",
|
||||||
"front_page",
|
"front_page",
|
||||||
"layout",
|
"layout",
|
||||||
"wide",
|
"wide",
|
||||||
"comment_sort",
|
"comment_sort",
|
||||||
"post_sort",
|
"post_sort",
|
||||||
|
"blur_spoiler",
|
||||||
"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 +84,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(),
|
||||||
),
|
),
|
||||||
@ -119,7 +123,7 @@ fn set_cookies_method(req: Request<Body>, remove_cookies: bool) -> Response<Body
|
|||||||
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,16 +52,19 @@ 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());
|
||||||
let sort = req.param("sort").unwrap_or_else(|| req.param("id").unwrap_or(post_sort));
|
let sort = req.param("sort").unwrap_or_else(|| req.param("id").unwrap_or(post_sort));
|
||||||
|
|
||||||
let sub_name = req.param("sub").unwrap_or(if front_page == "default" || front_page.is_empty() {
|
let mut sub_name = req.param("sub").unwrap_or(if front_page == "default" || front_page.is_empty() {
|
||||||
if subscribed.is_empty() {
|
if subscribed.is_empty() {
|
||||||
"popular".to_string()
|
"popular".to_string()
|
||||||
} else {
|
} else {
|
||||||
@ -79,6 +84,11 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||||||
return Ok(redirect(&["/user/", &sub_name[2..]].concat()));
|
return Ok(redirect(&["/user/", &sub_name[2..]].concat()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If multi-sub, replace + with url encoded +
|
||||||
|
if sub_name.contains('+') {
|
||||||
|
sub_name = sub_name.replace('+', "%2B");
|
||||||
|
}
|
||||||
|
|
||||||
// Request subreddit metadata
|
// Request subreddit metadata
|
||||||
let sub = if !sub_name.contains('+') && sub_name != subscribed && sub_name != "popular" && sub_name != "all" {
|
let sub = if !sub_name.contains('+') && sub_name != subscribed && sub_name != "popular" && sub_name != "all" {
|
||||||
// Regular subreddit
|
// Regular subreddit
|
||||||
@ -107,7 +117,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());
|
||||||
|
84
src/utils.rs
@ -1,3 +1,4 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
use crate::config::get_setting;
|
use crate::config::get_setting;
|
||||||
//
|
//
|
||||||
// CRATES
|
// CRATES
|
||||||
@ -156,6 +157,7 @@ impl PollOption {
|
|||||||
|
|
||||||
// Post flags with nsfw and stickied
|
// Post flags with nsfw and stickied
|
||||||
pub struct Flags {
|
pub struct Flags {
|
||||||
|
pub spoiler: bool,
|
||||||
pub nsfw: bool,
|
pub nsfw: bool,
|
||||||
pub stickied: bool,
|
pub stickied: bool,
|
||||||
}
|
}
|
||||||
@ -402,6 +404,7 @@ impl Post {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
flags: Flags {
|
flags: Flags {
|
||||||
|
spoiler: data["spoiler"].as_bool().unwrap_or_default(),
|
||||||
nsfw: data["over_18"].as_bool().unwrap_or_default(),
|
nsfw: data["over_18"].as_bool().unwrap_or_default(),
|
||||||
stickied: data["stickied"].as_bool().unwrap_or_default() || data["pinned"].as_bool().unwrap_or_default(),
|
stickied: data["stickied"].as_bool().unwrap_or_default() || data["pinned"].as_bool().unwrap_or_default(),
|
||||||
},
|
},
|
||||||
@ -570,14 +573,19 @@ 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 blur_spoiler: 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 +602,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 +617,27 @@ 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"),
|
||||||
|
blur_spoiler: setting(req, "blur_spoiler"),
|
||||||
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()),
|
||||||
@ -729,6 +754,7 @@ pub async fn parse_post(post: &Value) -> Post {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
flags: Flags {
|
flags: Flags {
|
||||||
|
spoiler: post["data"]["spoiler"].as_bool().unwrap_or_default(),
|
||||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||||
stickied: post["data"]["stickied"].as_bool().unwrap_or_default() || post["data"]["pinned"].as_bool().unwrap_or(false),
|
stickied: post["data"]["stickied"].as_bool().unwrap_or_default() || post["data"]["pinned"].as_bool().unwrap_or(false),
|
||||||
},
|
},
|
||||||
@ -875,9 +901,9 @@ 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#"/preview/(pre|external-pre)/(.*?)>"#).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());
|
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
|
||||||
@ -901,14 +927,47 @@ pub fn rewrite_urls(input_text: &str) -> String {
|
|||||||
let formatted_url = format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default());
|
let formatted_url = format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default());
|
||||||
|
|
||||||
let image_url = REDLIB_PREVIEW_LINK_REGEX.find(&formatted_url).map_or("", |m| m.as_str()).to_string();
|
let image_url = REDLIB_PREVIEW_LINK_REGEX.find(&formatted_url).map_or("", |m| m.as_str()).to_string();
|
||||||
let image_text = REDLIB_PREVIEW_TEXT_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();
|
||||||
|
|
||||||
let image_to_replace = format!("<a href=\"{image_url}{image_text}").replace(">>", ">");
|
/* 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.
|
||||||
let image_replacement = format!("<a href=\"{image_url}<img src=\"{image_url}</a>");
|
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
|
text1 = REDDIT_PREVIEW_REGEX
|
||||||
.replace(&text1, formatted_url)
|
.replace(&text1, format!("{_preview_type}$2"))
|
||||||
.replace(&image_to_replace, &image_replacement)
|
.replace(&image_to_replace, &_image_replacement)
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -993,7 +1052,7 @@ pub fn redirect(path: &str) -> Response<Body> {
|
|||||||
|
|
||||||
/// Renders a generic error landing page.
|
/// Renders a generic error landing page.
|
||||||
pub async fn error(req: Request<Body>, msg: &str) -> Result<Response<Body>, String> {
|
pub async fn error(req: Request<Body>, msg: &str) -> Result<Response<Body>, String> {
|
||||||
error!("Error page rendered: {msg}");
|
error!("Error page rendered: {}", msg.split('|').next().unwrap_or_default());
|
||||||
let url = req.uri().to_string();
|
let url = req.uri().to_string();
|
||||||
let body = ErrorTemplate {
|
let body = ErrorTemplate {
|
||||||
msg: msg.to_string(),
|
msg: msg.to_string(),
|
||||||
@ -1164,11 +1223,8 @@ async fn test_fetching_ws() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rewriting_image_links() {
|
fn test_rewriting_image_links() {
|
||||||
let input = r#"<p><a href="https://preview.redd.it/zq21ggkj2xo31.png?width=2560&format=png&auto=webp&s=539d8050628ec1190cac26468fe99cc66b6071ab">https://preview.redd.it/zq21ggkj2xo31.png?width=2560&format=png&auto=webp&s=539d8050628ec1190cac26468fe99cc66b6071ab</a></p>
|
let input =
|
||||||
<p><a href="https://preview.redd.it/vty9ocij2xo31.png?width=2560&format=png&auto=webp&s=fc7c7ef993a5e9ef656d5f5d9cf8290a0a1df877">https://preview.redd.it/vty9ocij2xo31.png?width=2560&format=png&auto=webp&s=fc7c7ef993a5e9ef656d5f5d9cf8290a0a1df877</a></p>
|
r#"<p><a href="https://preview.redd.it/6awags382xo31.png?width=2560&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc">caption 1</a></p>"#;
|
||||||
<p><a href="https://preview.redd.it/bdfdxkjj2xo31.png?width=2560&format=png&auto=webp&s=d0fa420ece27605e882e89cb4711d75d774322ac">https://preview.redd.it/bdfdxkjj2xo31.png?width=2560&format=png&auto=webp&s=d0fa420ece27605e882e89cb4711d75d774322ac</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"#;
|
||||||
<p><a href="https://preview.redd.it/6awags382xo31.png?width=2560&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc">caption 1</a></p>
|
|
||||||
<p><a href="https://preview.redd.it/rbu2ca2b2xo31.png?width=2560&format=png&auto=webp&s=afb538cf784d2e339de9a91aba5dc9c92e47988f">caption 2</a></p>"#;
|
|
||||||
let output = r#"<p><a href="/preview/pre/zq21ggkj2xo31.png?width=2560&format=png&auto=webp&s=539d8050628ec1190cac26468fe99cc66b6071ab"><img src="/preview/pre/zq21ggkj2xo31.png?width=2560&format=png&auto=webp&s=539d8050628ec1190cac26468fe99cc66b6071ab"></a></p> <p><a href="/preview/pre/vty9ocij2xo31.png?width=2560&format=png&auto=webp&s=fc7c7ef993a5e9ef656d5f5d9cf8290a0a1df877"><img src="/preview/pre/vty9ocij2xo31.png?width=2560&format=png&auto=webp&s=fc7c7ef993a5e9ef656d5f5d9cf8290a0a1df877"></a></p> <p><a href="/preview/pre/bdfdxkjj2xo31.png?width=2560&format=png&auto=webp&s=d0fa420ece27605e882e89cb4711d75d774322ac"><img src="/preview/pre/bdfdxkjj2xo31.png?width=2560&format=png&auto=webp&s=d0fa420ece27605e882e89cb4711d75d774322ac"></a></p> <p><a href="/preview/pre/6awags382xo31.png?width=2560&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc"><img src="/preview/pre/6awags382xo31.png?width=2560&format=png&auto=webp&s=9c563aed4f07a91bdd249b5a3cea43a79710dcfc"></a></p> <p><a href="/preview/pre/rbu2ca2b2xo31.png?width=2560&format=png&auto=webp&s=afb538cf784d2e339de9a91aba5dc9c92e47988f"><img src="/preview/pre/rbu2ca2b2xo31.png?width=2560&format=png&auto=webp&s=afb538cf784d2e339de9a91aba5dc9c92e47988f"></a></p>"#;
|
|
||||||
assert_eq!(rewrite_urls(input), output);
|
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: 11 KiB After Width: | Height: | Size: 60 KiB |
@ -1 +1,33 @@
|
|||||||
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.75272 0 0 .75272 -.75945 -.16518)"><circle cx="341.11" cy="340.32" r="340.1" fill="#1a1a1a"/><path d="m320.64 126.73v300.8h92.264v-207.92h75.803v-92.83h-75.803v-0.0508z" fill="#0ff"/><path d="m193.1 126.74v383.96h6e-3v43.543h295.82v-92.338h-202.74v-335.16z" fill="#f9f9f9"/></g></svg>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
id="svg2"
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs2" />
|
||||||
|
<rect width="512" height="512" fill="#4c082a" />
|
||||||
|
<g
|
||||||
|
transform="matrix(0.75272,0,0,0.75272,-1.1596187,-0.37987125)"
|
||||||
|
id="g2">
|
||||||
|
<circle
|
||||||
|
fill="#1a1a1a"
|
||||||
|
id="circle1"
|
||||||
|
style="fill:#4c082a;fill-opacity:0"
|
||||||
|
r="340.10001"
|
||||||
|
cy="340.32001"
|
||||||
|
cx="341.10999" />
|
||||||
|
<path
|
||||||
|
d="m 320.64,126.73 v 300.8 h 92.264 V 219.61 h 75.803 v -92.83 h -75.803 v -0.0508 z"
|
||||||
|
fill="#f83240"
|
||||||
|
id="path1"
|
||||||
|
style="fill:#f83240;fill-opacity:1" />
|
||||||
|
<path
|
||||||
|
d="M 193.1,126.74 V 510.7 h 0.006 v 43.543 h 295.82 v -92.338 h -202.74 v -335.16 z"
|
||||||
|
fill="#f83240"
|
||||||
|
id="path2"
|
||||||
|
style="fill:#f83240;fill-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 383 B 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 */
|
||||||
@ -51,6 +52,7 @@
|
|||||||
--visited: #aaa;
|
--visited: #aaa;
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||||
--popup: #b80a27;
|
--popup: #b80a27;
|
||||||
|
--spoiler: #ddd;
|
||||||
|
|
||||||
/* Hint color theme to browser for scrollbar */
|
/* Hint color theme to browser for scrollbar */
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
@ -70,6 +72,7 @@
|
|||||||
--highlighted: white;
|
--highlighted: white;
|
||||||
--visited: #555;
|
--visited: #555;
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
--spoiler: #0f0f0f;
|
||||||
|
|
||||||
/* Hint color theme to browser for scrollbar */
|
/* Hint color theme to browser for scrollbar */
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
@ -187,6 +190,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;
|
||||||
@ -923,6 +931,15 @@ a.search_subreddit:hover {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spoiler {
|
||||||
|
color: var(--spoiler);
|
||||||
|
margin-left: 5px;
|
||||||
|
border: 1px solid var(--spoiler);
|
||||||
|
padding: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.post_media_content, .post .__NoScript_PlaceHolder__, .gallery {
|
.post_media_content, .post .__NoScript_PlaceHolder__, .gallery {
|
||||||
max-width: calc(100% - 40px);
|
max-width: calc(100% - 40px);
|
||||||
grid-area: post_media;
|
grid-area: post_media;
|
||||||
@ -979,10 +996,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;
|
||||||
@ -1010,6 +1023,9 @@ a.search_subreddit:hover {
|
|||||||
|
|
||||||
.post_body img {
|
.post_body img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post_poll {
|
.post_poll {
|
||||||
@ -1098,7 +1114,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;
|
||||||
}
|
}
|
||||||
@ -1187,6 +1203,10 @@ a.search_subreddit:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.comment_left, .comment_right {
|
.comment_left, .comment_right {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -1382,6 +1402,11 @@ summary.comment_data {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
background: var(--post);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 5px 10px 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings_note a {
|
#settings_note a {
|
||||||
@ -1432,6 +1457,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 {
|
||||||
@ -1484,10 +1510,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; }
|
||||||
@ -1615,6 +1650,14 @@ td, th {
|
|||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nsfw-tag {
|
||||||
|
color: var(--nsfw);
|
||||||
|
border: 2px solid var(--nsfw);
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
|
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
@ -1741,18 +1784,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);
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
|
/* Catppuccin theme setting */
|
||||||
.catppuccin {
|
.catppuccin {
|
||||||
--accent: #b4befe; /* lavender */
|
--accent: #b4befe; /* lavender */
|
||||||
--green: #a6e3a1; /* green */
|
--green: #a6e3a1; /* green */
|
||||||
--text: #cdd6f4; /* text */
|
--text: #cdd6f4; /* text */
|
||||||
--foreground: #11111b; /* crust */
|
--foreground: #181825; /* mantle */
|
||||||
--background: #1e1e2e; /* base */
|
--background: #1e1e2e; /* base */
|
||||||
--outside: #11111b; /* crust */
|
--outside: #11111b; /* crust */
|
||||||
--post: #11111b; /* crust */
|
--post: #11111b; /* crust */
|
||||||
|
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
|
@ -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 %}
|
||||||
@ -60,6 +60,13 @@
|
|||||||
</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 %}
|
||||||
<main>
|
<main>
|
||||||
@ -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://git.stardust.wtf/Ayaka/redlib" title="View code on git.stardust.wtf"><> 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 %}
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
{% block title %}Error: {{ msg }}{% endblock %}
|
{% block title %}Error: {{ msg }}{% endblock %}
|
||||||
{% block sortstyle %}{% endblock %}
|
{% block sortstyle %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="error">
|
<div id="error">
|
||||||
<h1>{{ msg }}</h1>
|
<h1>{{ msg }}</h1>
|
||||||
<h3><a href="https://www.redditstatus.com/">Reddit Status</a></h3>
|
<h3><a href="https://www.redditstatus.com/">Reddit Status</a></h3>
|
||||||
<br />
|
<br />
|
||||||
<h3>Head back <a href="/">home</a>?</h3>
|
<h3>Expected something to work? <a
|
||||||
</div>
|
href="https://github.com/redlib-org/redlib/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=%F0%9F%90%9B+Bug+Report%3A+{{ msg }}">Report
|
||||||
|
an issue</a></h3>
|
||||||
|
<br />
|
||||||
|
<h3>Head back <a href="/">home</a>?</h3>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -6,11 +6,11 @@
|
|||||||
<h1>
|
<h1>
|
||||||
😱
|
😱
|
||||||
{% if res_type == crate::utils::ResourceType::Subreddit %}
|
{% if res_type == crate::utils::ResourceType::Subreddit %}
|
||||||
r/{{ res }} is a NSFW community!
|
r/{{ res }} is a <b class="nsfw-tag">NSFW</b> community!
|
||||||
{% else if res_type == crate::utils::ResourceType::User %}
|
{% else if res_type == crate::utils::ResourceType::User %}
|
||||||
u/{{ res }}'s content is NSFW!
|
u/{{ res }}'s content is <b class="nsfw-tag">NSFW</b>!
|
||||||
{% else if res_type == crate::utils::ResourceType::Post %}
|
{% else if res_type == crate::utils::ResourceType::Post %}
|
||||||
This post is NSFW!
|
This post is <b class="nsfw-tag">NSFW</b>!
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
<br />
|
<br />
|
||||||
@ -20,6 +20,7 @@
|
|||||||
This instance of Redlib is SFW-only.</p>
|
This instance of Redlib is SFW-only.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
Enable "Show NSFW posts" in <a href="/settings">settings</a> to view this {% if res_type == crate::utils::ResourceType::Subreddit %}subreddit{% else if res_type == crate::utils::ResourceType::User %}user's posts or comments{% else if res_type == crate::utils::ResourceType::Post %}post{% endif %}. <br>
|
Enable "Show NSFW posts" in <a href="/settings">settings</a> to view this {% if res_type == crate::utils::ResourceType::Subreddit %}subreddit{% else if res_type == crate::utils::ResourceType::User %}user's posts or comments{% else if res_type == crate::utils::ResourceType::Post %}post{% endif %}. <br>
|
||||||
|
<div>Alternatively <a href="/settings/update/?show_nsfw=on&redirect={{self.url[1..self.url.len()]}}">enable NSFW posts</a> now and view this {% if res_type == crate::utils::ResourceType::Subreddit %}subreddit{% else if res_type == crate::utils::ResourceType::User %}profile{% else if res_type == crate::utils::ResourceType::Post %}post{% endif %} immediately</div>
|
||||||
{% if res_type == crate::utils::ResourceType::Post %} You can also temporarily bypass this gate and view the post by clicking on this <a href="{{url}}&bypass_nsfw_landing">link</a>.{% endif %}
|
{% if res_type == crate::utils::ResourceType::Post %} You can also temporarily bypass this gate and view the post by clicking on this <a href="{{url}}&bypass_nsfw_landing">link</a>.{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -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>
|
||||||
@ -54,6 +60,11 @@
|
|||||||
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="prefs-group">
|
||||||
|
<label for="blur_spoiler">Blur spoiler previews:</label>
|
||||||
|
<input type="hidden" value="off" name="blur_spoiler">
|
||||||
|
<input type="checkbox" name="blur_spoiler" id="blur_spoiler" {% if prefs.blur_spoiler == "on" %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
{% if !crate::utils::sfw_only() %}
|
{% if !crate::utils::sfw_only() %}
|
||||||
<div class="prefs-group">
|
<div class="prefs-group">
|
||||||
<label for="show_nsfw">Show NSFW posts:</label>
|
<label for="show_nsfw">Show NSFW posts:</label>
|
||||||
@ -71,19 +82,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>
|
||||||
@ -109,6 +137,10 @@
|
|||||||
<input id="save" type="submit" value="Save">
|
<input id="save" type="submit" value="Save">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="settings_note">
|
||||||
|
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
||||||
|
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&mascot={{ prefs.mascot }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&ffmpeg_video_downloads={{ prefs.ffmpeg_video_downloads }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&fixed_navbar={{ prefs.fixed_navbar }}&hide_sidebar_and_summary={{ prefs.hide_sidebar_and_summary}}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
||||||
|
</div>
|
||||||
{% if prefs.subscriptions.len() > 0 %}
|
{% if prefs.subscriptions.len() > 0 %}
|
||||||
<div class="prefs" id="settings_subs">
|
<div class="prefs" id="settings_subs">
|
||||||
<legend>Subscribed Feeds</legend>
|
<legend>Subscribed Feeds</legend>
|
||||||
@ -139,11 +171,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div id="settings_note">
|
|
||||||
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
|
||||||
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&fixed_navbar={{ prefs.fixed_navbar }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -64,9 +64,13 @@
|
|||||||
{% 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 %}
|
||||||
@ -82,7 +86,7 @@
|
|||||||
</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>
|
||||||
|
@ -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 %}
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro post(post) -%}
|
{% macro post(post) -%}
|
||||||
|
{% set post_should_be_blurred = post.flags.spoiler && prefs.blur_spoiler=="on" -%}
|
||||||
<!-- POST CONTENT -->
|
<!-- POST CONTENT -->
|
||||||
<div class="post highlighted">
|
<div class="post highlighted">
|
||||||
<p class="post_header">
|
<p class="post_header">
|
||||||
@ -93,6 +94,7 @@
|
|||||||
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};">{% call render_flair(post.flair.flair_parts) %}</a>
|
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};">{% call render_flair(post.flair.flair_parts) %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}
|
{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}
|
||||||
|
{% if post.flags.spoiler %} <small class="spoiler">Spoiler</small>{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- POST MEDIA -->
|
<!-- POST MEDIA -->
|
||||||
@ -101,12 +103,13 @@
|
|||||||
<div class="post_media_content">
|
<div class="post_media_content">
|
||||||
<a href="{{ post.media.url }}" class="post_media_image" >
|
<a href="{{ post.media.url }}" class="post_media_image" >
|
||||||
{% if post.media.height == 0 || post.media.width == 0 %}
|
{% if post.media.height == 0 || post.media.width == 0 %}
|
||||||
<!-- i.redd.it images speical case -->
|
<!-- i.redd.it images special case -->
|
||||||
<img width="100%" height="100%" loading="lazy" alt="Post image" src="{{ post.media.url }}"/>
|
<img width="100%" height="100%" loading="lazy" alt="Post image" src="{{ post.media.url }}"{%if post_should_be_blurred %} class="post_nsfw_blur"{% endif %}/>
|
||||||
{% else %}
|
{% else %}
|
||||||
<svg
|
<svg
|
||||||
width="{{ post.media.width }}px"
|
width="{{ post.media.width }}px"
|
||||||
height="{{ post.media.height }}px"
|
height="{{ post.media.height }}px"
|
||||||
|
{%if post_should_be_blurred %}class="post_nsfw_blur"{% endif %}
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<image width="100%" height="100%" href="{{ post.media.url }}"/>
|
<image width="100%" height="100%" href="{{ post.media.url }}"/>
|
||||||
<desc>
|
<desc>
|
||||||
@ -117,18 +120,22 @@
|
|||||||
</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_should_be_blurred %} post_nsfw_blur{% 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>
|
||||||
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
|
||||||
<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{%if post_should_be_blurred %} post_nsfw_blur{% endif %}" src="{{ post.media.url }}" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %} loop><a href={{ post.media.url }}>Video</a></video>
|
||||||
</div>
|
</div>
|
||||||
{% call render_hls_notification(post.permalink[1..]) %}
|
{% call render_hls_notification(post.permalink[1..]) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -194,6 +201,7 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro post_in_list(post) -%}
|
{% macro post_in_list(post) -%}
|
||||||
|
{% set post_should_be_blurred = (post.flags.nsfw && prefs.blur_nsfw=="on") || (post.flags.spoiler && prefs.blur_spoiler=="on") -%}
|
||||||
<div class="post {% if post.flags.stickied %}stickied{% endif %}" id="{{ post.id }}">
|
<div class="post {% if post.flags.stickied %}stickied{% endif %}" id="{{ post.id }}">
|
||||||
<p class="post_header">
|
<p class="post_header">
|
||||||
{% let community -%}
|
{% let community -%}
|
||||||
@ -222,7 +230,7 @@
|
|||||||
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};"
|
style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }};"
|
||||||
dir="ltr">{% call render_flair(post.flair.flair_parts) %}</a>
|
dir="ltr">{% call render_flair(post.flair.flair_parts) %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ post.permalink }}">{{ post.title }}</a>{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}
|
<a href="{{ post.permalink }}">{{ post.title }}</a>{% if post.flags.nsfw %} <small class="nsfw">NSFW</small>{% endif %}{% if post.flags.spoiler %} <small class="spoiler">Spoiler</small>{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
<!-- POST MEDIA/THUMBNAIL -->
|
<!-- POST MEDIA/THUMBNAIL -->
|
||||||
{% if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "image" %}
|
{% if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "image" %}
|
||||||
@ -233,7 +241,7 @@
|
|||||||
<img width="100%" height="100%" loading="lazy" alt="Post image" src="{{ post.media.url }}"/>
|
<img width="100%" height="100%" loading="lazy" alt="Post image" src="{{ post.media.url }}"/>
|
||||||
{% else %}
|
{% else %}
|
||||||
<svg
|
<svg
|
||||||
{%if post.flags.nsfw && prefs.blur_nsfw=="on" %}class="post_nsfw_blur"{% endif %}
|
{%if post_should_be_blurred %}class="post_nsfw_blur"{% endif %}
|
||||||
width="{{ post.media.width }}px"
|
width="{{ post.media.width }}px"
|
||||||
height="{{ post.media.height }}px"
|
height="{{ post.media.height }}px"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
@ -247,19 +255,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "gif" %}
|
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "gif" %}
|
||||||
<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 %}" 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_should_be_blurred %}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_should_be_blurred %}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" />
|
||||||
<source src="{{ post.media.url }}" type="video/mp4" />
|
<source src="{{ post.media.url }}" type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<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 %}" 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 {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
|
<video class="post_media_video short {%if post_should_be_blurred %}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 {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
|
||||||
</div>
|
</div>
|
||||||
{% call render_hls_notification(format!("{}%23{}", &self.url[1..].replace("&", "%26").replace("+", "%2B"), post.id)) %}
|
{% call render_hls_notification(format!("{}%23{}", &self.url[1..].replace("&", "%26").replace("+", "%2B"), post.id)) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -272,7 +280,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div style="max-width:{{ post.thumbnail.width }}px;max-height:{{ post.thumbnail.height }}px;">
|
<div style="max-width:{{ post.thumbnail.width }}px;max-height:{{ post.thumbnail.height }}px;">
|
||||||
<svg {% if post.flags.nsfw && prefs.blur_nsfw=="on" %} class="thumb_nsfw_blur" {% endif %} width="{{ post.thumbnail.width }}px" height="{{ post.thumbnail.height }}px" xmlns="http://www.w3.org/2000/svg">
|
<svg {% if post_should_be_blurred %} class="thumb_nsfw_blur" {% endif %} width="{{ post.thumbnail.width }}px" height="{{ post.thumbnail.height }}px" xmlns="http://www.w3.org/2000/svg">
|
||||||
<image width="100%" height="100%" href="{{ post.thumbnail.url }}"/>
|
<image width="100%" height="100%" href="{{ post.thumbnail.url }}"/>
|
||||||
<desc>
|
<desc>
|
||||||
<img loading="lazy" alt="Thumbnail" src="{{ post.thumbnail.url }}"/>
|
<img loading="lazy" alt="Thumbnail" src="{{ post.thumbnail.url }}"/>
|
||||||
|