From d0e081e6a0f9eb35aa481cb931a4e6ea3389fc58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:21:11 -0400 Subject: [PATCH 01/28] chore(deps): bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#214) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 9a10ac7..ea521b1 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -76,7 +76,7 @@ jobs: steps: - name: Download digests - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 with: name: digests path: /tmp/digests From 438e412be3b038bf2d6f600778e8a13a2791e018 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Tue, 3 Sep 2024 19:21:33 -0400 Subject: [PATCH 02/28] Add anchor to comment link (#212) --- templates/comment.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/comment.html b/templates/comment.html index ef1f4d8..36c9b60 100644 --- a/templates/comment.html +++ b/templates/comment.html @@ -24,7 +24,7 @@ {% if author.flair.flair_parts.len() > 0 %} {% call utils::render_flair(author.flair.flair_parts) %} {% endif %} - {{ rel_time }} + {{ rel_time }} {% if edited.0 != "".to_string() %}edited {{ edited.0 }}{% endif %} {% if !awards.is_empty() && prefs.hide_awards != "on" %} From c494fbec318c8bc93a81756f7a3308f55435d379 Mon Sep 17 00:00:00 2001 From: Kot C Date: Tue, 3 Sep 2024 18:44:04 -0500 Subject: [PATCH 03/28] Insert noindex meta for ROBOTS_DISABLE_INDEXING (#199) (#207) --- src/utils.rs | 12 ++++++++++++ templates/base.html | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index ee1fc66..ff968f8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1096,6 +1096,18 @@ pub fn enable_rss() -> bool { } } +/// Returns true if the config/env variable `REDLIB_ROBOTS_DISABLE_INDEXING` carries the +/// value `on`. +/// +/// If this variable is set as such, the instance will block all robots in robots.txt and +/// insert the noindex, nofollow meta tag on every page. +pub fn disable_indexing() -> bool { + match get_setting("REDLIB_ROBOTS_DISABLE_INDEXING") { + Some(val) => val == "on", + None => false, + } +} + // Determines if a request shoud redirect to a nsfw landing gate. pub fn should_be_nsfw_gated(req: &Request, req_url: &str) -> bool { let sfw_instance = sfw_only(); diff --git a/templates/base.html b/templates/base.html index 139c76f..1c3ef18 100644 --- a/templates/base.html +++ b/templates/base.html @@ -8,6 +8,9 @@ + {% if crate::utils::disable_indexing() %} + + {% endif %} From db8b92ea5574f1f66a8599c868723779ed492ea2 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Thu, 5 Sep 2024 11:59:21 -0400 Subject: [PATCH 04/28] Fix a whole bunch of styling bugs (#193) * Fix a whole bunch of mobile styling bugs * Make searchbox scroll fix only apply in mobile mode to prevent bug * Remove the min-width requirement for the main column This was meant to be removed already, this is what fixes posts having an odd right side gap before swapping to the mobile layout * Make margins consistent between fixed and unfixed navbar settings * Remove some empty space from deleted option * Make mobile layout post width fix only apply in mobile mode to prevent bug * Make sure some options only get applied to the elements that need them, also fix the margins on the settings page * Move search comments option before it starts touching the sort options and wrapping the x amount of comments text * Trigger the even further compacted layout a little earlier, right before text begins wrapping in odd ways * In the extra small mobile layout make give up/downvote numbers enough room so they aren't clipping out of their box * Fix https://github.com/redlib-org/redlib/issues/172 * Properly center search box instead of having it slightly skewed * Undo word wrapping since it breaks the sorting options and the only other viable setting has an absolute conniption on Chrome for some reason * Readd word wrapping and just force it to normal for the sorting section * Make post flair line up with title * Make post flair position consistent * Make footer text properly horizontally centered in mobile mode and fix slight vertical misalignment issues * Make feeds button appear in settings menu to keep navbar looking consistent * Fix extra navbar padding on search page * Reduce gap between navbar and content in mobile mode * Reduce gap between navbar and content in mobile mode --- static/style.css | 93 +++++++++++++++++++++++++---------------- templates/base.html | 8 +--- templates/settings.html | 4 ++ templates/utils.html | 2 +- 4 files changed, 63 insertions(+), 44 deletions(-) diff --git a/static/style.css b/static/style.css index 3ab98b4..3592552 100644 --- a/static/style.css +++ b/static/style.css @@ -137,6 +137,7 @@ input { margin: 0; color: var(--text); font-family: "Inter", sans-serif; + word-wrap: anywhere; } html.fixed_navbar { @@ -166,10 +167,8 @@ body.fixed_navbar { } nav { - display: grid; - grid-template-areas: "logo searchbox links"; + display: flex; - justify-content: space-between; align-items: center; color: var(--accent); @@ -202,15 +201,18 @@ nav #code > svg { } nav #logo { - grid-area: logo; white-space: nowrap; margin-right: 5px; + flex-grow: 1; + flex-basis: 0; } nav #links { - grid-area: links; margin-left: 10px; display: flex; + flex-grow: 1; + flex-basis: 0; + justify-content: flex-end; } nav #links svg { @@ -358,10 +360,13 @@ main { #column_one { width: 100%; - max-width: 750px; border-radius: 5px; + max-width: 750px; overflow: inherit; } +@media screen and (max-width: 800px) { + #column_one { max-width: unset; } +} /* Body footer. */ body > footer { @@ -374,14 +379,13 @@ body > footer { bottom: 0; } -.footer-button { +.footer-buttons { align-items: center; border-radius: 0.25rem; box-sizing: border-box; color: var(--text); cursor: pointer; display: inline-flex; - padding-left: 1em; opacity: 0.8; } @@ -458,9 +462,8 @@ aside { border-radius: 5px; overflow: hidden; } -#subreddit, -#sidebar { - min-width: 350px; +@media screen and (min-width: 800px) { + #subreddit, #sidebar { min-width: 350px; } } #user *, @@ -522,7 +525,9 @@ aside { #user_actions { display: grid; grid-template-columns: repeat(2, 1fr); - grid-column-gap: 20px; +} +@media screen and (max-width: 279px) { + #sub_actions { display: unset; } } #user_details > label, @@ -689,6 +694,7 @@ select, display: flex; box-shadow: var(--shadow); border-radius: 5px; + margin-bottom: 0; } #searchbox > *, @@ -698,9 +704,12 @@ select, #search { border-right: 2px var(--outside) solid; - min-width: 0; flex-grow: 1; } +@media screen and (max-width: 800px) { + #search { width: 0; } + #search.commentQuery { width: unset; } +} #inside { display: flex; @@ -779,7 +788,7 @@ button.submit:hover > svg { #sort, #search_sort { display: flex; - align-items: center; + align-items: stretch; margin-bottom: 20px; } @@ -806,10 +815,6 @@ button.submit:hover > svg { /* When screen size is smaller than 480px we switch to a design better suited for mobile devices */ @media screen and (max-width: 480px) { - #search_sort { - align-items: unset; - } - .search_widget_divider_box > #search { flex: 1; min-width: unset; @@ -834,7 +839,7 @@ button.submit:hover > svg { } #sort_submit { - height: unset; + height: auto; border-left: 2px var(--outside) solid; } } @@ -858,6 +863,7 @@ main > * > footer > a { text-align: center; cursor: pointer; transition: 0.2s background; + word-wrap: normal; } #sort_options > a.selected, @@ -1035,6 +1041,8 @@ a.search_subreddit:hover { border-radius: 5px; font-size: 12px; font-weight: bold; + vertical-align: text-top; + line-height: 1.6; } .awards { @@ -1259,6 +1267,7 @@ a.search_subreddit:hover { #comment_count { font-weight: 500; opacity: 0.9; + align-self: center; } #comment_count > #sorted_by { @@ -1282,7 +1291,7 @@ a.search_subreddit:hover { display: auto; } -@media screen and (min-width: 481px) { +@media screen and (min-width: 508px) { .mobile_item { display: none; } @@ -1894,10 +1903,6 @@ th { /* Mobile */ @media screen and (max-width: 800px) { - body.fixed_navbar { - padding-top: 120px; - } - main { flex-direction: column-reverse; padding: 10px; @@ -1906,9 +1911,11 @@ th { } nav { + display: grid; grid-template-areas: "logo links" "searchbox searchbox"; - padding: 10px; + padding: 5px 10px 0; width: calc(100% - 20px); + margin: 0; } nav #links { @@ -1941,8 +1948,19 @@ th { margin: 0; max-width: 100%; } + #user { margin: 0 0 20px; } + + body.fixed_navbar { + min-height: calc(100vh - 75px); + padding-top: 45px; + #subreddit { margin: 49px 0 0; } + #user { margin: 49px 0 20px 0; } + #settings { margin-top: 48px; } + div.post.highlighted { margin-top: 49px; } + main:not(:has(div + aside)) { #sort { margin-top: 49px; } } + #column_one:has(div.post.highlighted + #commentQueryForms) { #sort { margin-top: 0 } } + } - #user, #sidebar { margin: 20px 0; } @@ -1951,14 +1969,19 @@ th { margin-bottom: 5px; } #searchbox { - width: calc(100vw - 35px); + width: calc(100vw - 20px); + margin-bottom: 10px; } } -@media screen and (max-width: 480px) { - body.fixed_navbar { - padding-top: 100px; +@media screen and (max-width: 580px) { + #commentQueryForms { + display: initial; + justify-content: initial; } +} + +@media screen and (max-width: 507px) { #version { display: none; } @@ -2052,11 +2075,12 @@ th { } .comment_score { - min-width: 32px; + min-width: 34px; height: 20px; - font-size: 15px; + font-size: 12px; padding: 7px 0px; margin-right: -5px; + align-content: center; } #post_links > li { @@ -2080,11 +2104,6 @@ th { .popup-inner { max-width: 80%; } - - #commentQueryForms { - display: initial; - justify-content: initial; - } } .quality-selector { diff --git a/templates/base.html b/templates/base.html index 1c3ef18..bb11560 100644 --- a/templates/base.html +++ b/templates/base.html @@ -74,12 +74,8 @@ {% block footer %} {% endblock %} diff --git a/templates/settings.html b/templates/settings.html index 434f0d2..3940cc7 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -3,6 +3,10 @@ {% block title %}Redlib Settings{% endblock %} +{% block subscriptions %} + {% call utils::sub_list("") %} +{% endblock %} + {% block search %} {% call utils::search("".to_owned(), "") %} {% endblock %} diff --git a/templates/utils.html b/templates/utils.html index b88702b..879f138 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -87,12 +87,12 @@ {% endif %}

- {{ post.title }} {% if post.flair.flair_parts.len() > 0 %} {% call render_flair(post.flair.flair_parts) %} {% endif %} + {{ post.title }} {% if post.flags.nsfw %} NSFW{% endif %} {% if post.flags.spoiler %} Spoiler{% endif %}

From 7a0ea1fbd3c2ce2c06e7a47762fecc21fa9161d4 Mon Sep 17 00:00:00 2001 From: Pim Date: Thu, 5 Sep 2024 17:59:43 +0200 Subject: [PATCH 05/28] fix: spoiler hover and video size (#196) * fix: unblur both media as body when hoevering over either one * fix: set min size for video when the video is not loaded, the size is determined by the poster image. But when the poster image returns a 404, then the video had a size of 0x0 * chore: tab/space --- static/style.css | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/static/style.css b/static/style.css index 3592552..214af55 100644 --- a/static/style.css +++ b/static/style.css @@ -1111,6 +1111,8 @@ a.search_subreddit:hover { } .post_media_video { + min-width: 100px; + max-width: 100px; width: auto; height: auto; max-width: 100%; @@ -1129,9 +1131,7 @@ a.search_subreddit:hover { margin: auto; } -.post_blurred img, -.post_blurred svg, -.post_blurred video { +.post_blurred .post_media_content * { filter: blur(1.5rem); } @@ -1139,16 +1139,16 @@ a.search_subreddit:hover { filter: blur(0.25rem); } -.post_blurred .post_thumbnail svg { +.post_blurred .post_thumbnail * { filter: blur(0.3rem); } -.post_blurred img:hover, -.post_blurred svg:hover, -.post_blurred video:hover, +.post_blurred .post_media_content:hover *, +.post_blurred .post_media_content:hover ~ .post_body, +.post_blurred .post_media_content:has(~ .post_body:hover) *, .post_blurred .post_body:hover, -.post_blurred .post_thumbnail:hover svg { - filter: none; +.post_blurred .post_thumbnail:hover * { + filter: none; } .post_media_image svg { From 408ebe6ef1fc1be9933424c3b3f49cfb1fdbb125 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Thu, 5 Sep 2024 12:00:09 -0400 Subject: [PATCH 06/28] feat(post): add archive.is link for link posts (#165) --- templates/utils.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/templates/utils.html b/templates/utils.html index 879f138..13cf0e5 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -175,6 +175,10 @@ {% endif %} + {% if post.post_type == "link" %} +
  • archive.is
  • +
  • archive
  • + {% endif %} {% call external_reddit_link(post.permalink) %} {% if post.media.download_name != "" %} From 0b15250cc83776d48c6247c553d40adeb79ac9cf Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Mon, 16 Sep 2024 16:16:08 -0400 Subject: [PATCH 07/28] fix(oauth): catch network policy violation and rate limit (#233) --- src/client.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 6e73a59..33ea6fe 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,6 +4,7 @@ use futures_lite::future::block_on; use futures_lite::{future::Boxed, FutureExt}; use hyper::client::HttpConnector; use hyper::header::HeaderValue; +use hyper::StatusCode; use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri}; use hyper_rustls::HttpsConnector; use libflate::gzip; @@ -60,10 +61,9 @@ pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false); pub async fn canonical_path(path: String) -> Result, String> { let res = reddit_head(path.clone(), true).await?; let status = res.status().as_u16(); + let policy_error = res.headers().get(header::RETRY_AFTER).is_some(); match status { - 429 => Err("Too many requests.".to_string()), - // If Reddit responds with a 2xx, then the path is already canonical. 200..=299 => Ok(Some(path)), @@ -94,6 +94,12 @@ pub async fn canonical_path(path: String) -> Result, String> { // as above), return a None. 300..=399 => Ok(None), + // Rate limiting + 429 => Err("Too many requests.".to_string()), + + // Special condition rate limiting - https://github.com/redlib-org/redlib/issues/229 + 403 if policy_error => Err("Too many requests.".to_string()), + _ => Ok( res .headers() @@ -257,6 +263,12 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo .await; }; + // Special condition rate limiting - https://github.com/redlib-org/redlib/issues/229 + if response.status() == StatusCode::FORBIDDEN && response.headers().get("retry-after").unwrap_or(&HeaderValue::from_static("0")).to_str().unwrap_or("0") == "0" { + force_refresh_token().await; + return Err("Rate limit - try refreshing soon".to_string()); + } + match response.headers().get(header::CONTENT_ENCODING) { // Content not compressed. None => Ok(response), From 28e72c9058d20cd6665923f715b0d9b13a5dd5c9 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Mon, 16 Sep 2024 16:29:52 -0400 Subject: [PATCH 08/28] fix(ci): bump versions (#234) --- .github/workflows/main-docker.yml | 2 +- .github/workflows/main-rust.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index ea521b1..885931d 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -63,7 +63,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: digests path: /tmp/digests/* diff --git a/.github/workflows/main-rust.yml b/.github/workflows/main-rust.yml index ea44565..f38c01d 100644 --- a/.github/workflows/main-rust.yml +++ b/.github/workflows/main-rust.yml @@ -56,7 +56,7 @@ jobs: - name: Calculate SHA256 checksum run: sha256sum target/x86_64-unknown-linux-musl/release/redlib > redlib.sha256 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 name: Upload a Build Artifact with: name: redlib From 118708400ad37c5ddfb8d54566d429c0f43b024a Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Mon, 16 Sep 2024 16:36:24 -0400 Subject: [PATCH 09/28] fix(ci): unique name --- .github/workflows/main-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 885931d..a46c5b2 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -65,7 +65,7 @@ jobs: name: Upload digest uses: actions/upload-artifact@v4 with: - name: digests + name: digests-${{ matrix.target }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 From 8ef45456d696b254b0378b75ff4db0345d6bc217 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Mon, 16 Sep 2024 16:39:07 -0400 Subject: [PATCH 10/28] actions trigger From f18d135045b8d9d48e41200c7199992435ebcfbf Mon Sep 17 00:00:00 2001 From: wuchyi <49750312+wuchyi@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:09:32 +0800 Subject: [PATCH 11/28] Update main-docker.yml (digests) (#238) Updated digest name on upload (as per 1187084) --- .github/workflows/main-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index a46c5b2..134e341 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -78,7 +78,7 @@ jobs: name: Download digests uses: actions/download-artifact@v4.1.7 with: - name: digests + name: digests-${{ matrix.target }} path: /tmp/digests - name: Set up Docker Buildx From 7be29f609cbaf0a9ca00c078f1e8a02232072981 Mon Sep 17 00:00:00 2001 From: wuchyi <49750312+wuchyi@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:37:39 +0800 Subject: [PATCH 12/28] Attempting to fix main-docker.yml for downloading digests (#243) * Update main-docker.yml (digests) Updated digest name on upload (as per 1187084) * Update main-docker.yml (digests download) Trying another fix based on the template provided here https://github.com/actions/download-artifact for downloading multiple (filtered) Artifacts to the same directory --- .github/workflows/main-docker.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 134e341..9b7701a 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -78,8 +78,10 @@ jobs: name: Download digests uses: actions/download-artifact@v4.1.7 with: - name: digests-${{ matrix.target }} path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 From 28f85f2599d72b10e747295f7f1f73e42abe3380 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 17 Sep 2024 14:39:42 -0400 Subject: [PATCH 13/28] Attempting to fix main-docker.yml for downloading digests From 3625fdfdbe149a76922a8da5a41e8245a5f5cbbe Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 17 Sep 2024 14:42:09 -0400 Subject: [PATCH 14/28] fix(ci): temporarily disable README updates --- .github/workflows/main-docker.yml | 67 ++++++++++++------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 9b7701a..5442775 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -15,15 +15,13 @@ jobs: fail-fast: false matrix: include: - - { platform: linux/amd64, target: x86_64-unknown-linux-musl} - - { platform: linux/arm64, target: aarch64-unknown-linux-musl} - - { platform: linux/arm/v7, target: armv7-unknown-linux-musleabihf} + - { platform: linux/amd64, target: x86_64-unknown-linux-musl } + - { platform: linux/arm64, target: aarch64-unknown-linux-musl } + - { platform: linux/arm/v7, target: armv7-unknown-linux-musleabihf } steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v4 - - - name: Docker meta + - name: Docker meta id: meta uses: docker/metadata-action@v5 with: @@ -31,21 +29,17 @@ jobs: tags: | type=sha type=raw,value=latest,enable={{is_default_branch}} - - - name: Set up QEMU + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - - name: Login to Quay.io Container Registry + - name: Login to Quay.io Container Registry uses: docker/login-action@v3 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_ROBOT_TOKEN }} - - - name: Build and push + - name: Build and push id: build uses: docker/build-push-action@v5 with: @@ -55,14 +49,12 @@ jobs: outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true file: Dockerfile build-args: TARGET=${{ matrix.target }} - - - name: Export digest + - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest + - name: Upload digest uses: actions/upload-artifact@v4 with: name: digests-${{ matrix.target }} @@ -74,19 +66,16 @@ jobs: needs: - build steps: - - - name: Download digests + - name: Download digests uses: actions/download-artifact@v4.1.7 with: path: /tmp/digests pattern: digests-* merge-multiple: true - - - - name: Set up Docker Buildx + + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - - name: Docker meta + - name: Docker meta id: meta uses: docker/metadata-action@v5 with: @@ -94,31 +83,27 @@ jobs: tags: | type=sha type=raw,value=latest,enable={{is_default_branch}} - - - name: Login to Quay.io Container Registry + - name: Login to Quay.io Container Registry uses: docker/login-action@v3 with: registry: quay.io username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_ROBOT_TOKEN }} - - - name: Create manifest list and push + - name: Create manifest list and push working-directory: /tmp/digests run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - - name: Push README to Quay.io - uses: christian-korneck/update-container-description-action@v1 - env: - DOCKER_APIKEY: ${{ secrets.APIKEY__QUAY_IO }} - with: - destination_container_repo: quay.io/redlib/redlib - provider: quay - readme_file: 'README.md' + # - name: Push README to Quay.io + # uses: christian-korneck/update-container-description-action@v1 + # env: + # DOCKER_APIKEY: ${{ secrets.APIKEY__QUAY_IO }} + # with: + # destination_container_repo: quay.io/redlib/redlib + # provider: quay + # readme_file: 'README.md' - - - name: Inspect image + - name: Inspect image run: | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} - From 793047f63f0f603e342c919bbfc469c7569276fa Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Wed, 18 Sep 2024 11:24:00 -0400 Subject: [PATCH 15/28] fix(client): revert to hyper-rustls=0.24.2 --- Cargo.lock | 71 ++++++++++++++++++++++----------------------------- Cargo.toml | 2 +- src/client.rs | 7 +---- 3 files changed, 33 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18750b8..a7dfbc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -822,9 +828,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.25.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -832,7 +838,6 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", ] @@ -1214,7 +1219,7 @@ version = "0.35.1" dependencies = [ "arc-swap", "askama", - "base64", + "base64 0.22.1", "brotli", "build_html", "cached", @@ -1379,55 +1384,44 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-pki-types", "rustls-webpki", - "subtle", - "zeroize", + "sct", ] [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", - "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", - "rustls-pki-types", + "base64 0.21.7", ] -[[package]] -name = "rustls-pki-types" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" - [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", - "rustls-pki-types", "untrusted", ] @@ -1473,6 +1467,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sealed_test" version = "1.1.0" @@ -1634,12 +1638,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "1.0.109" @@ -1783,12 +1781,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -2154,9 +2151,3 @@ dependencies = [ "quote", "syn 2.0.68", ] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 5cf4881..451a83a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ serde = { version = "1.0.193", features = ["derive"] } cookie = "0.18.0" futures-lite = "2.2.0" hyper = { version = "0.14.28", features = ["full"] } -hyper-rustls = "0.25.0" +hyper-rustls = "0.24.2" percent-encoding = "2.3.1" route-recognizer = "0.3.1" serde_json = "1.0.108" diff --git a/src/client.rs b/src/client.rs index 33ea6fe..d4701ba 100644 --- a/src/client.rs +++ b/src/client.rs @@ -26,12 +26,7 @@ const REDDIT_URL_BASE: &str = "https://oauth.reddit.com"; const ALTERNATIVE_REDDIT_URL_BASE: &str = "https://www.reddit.com"; pub static CLIENT: Lazy>> = Lazy::new(|| { - let https = hyper_rustls::HttpsConnectorBuilder::new() - .with_native_roots() - .expect("No native root certificates found") - .https_only() - .enable_http1() - .build(); + let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build(); client::Client::builder().build(https) }); From 7156be6ad069f38c652c79387b496ee070ce8075 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Fri, 20 Sep 2024 23:57:18 -0400 Subject: [PATCH 16/28] fix(client): fix failing tests, retries for canonical_path --- src/client.rs | 69 ++++++++++++++++++++++++++++++++++++++++----------- src/main.rs | 4 +-- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/client.rs b/src/client.rs index d4701ba..6a202ff 100644 --- a/src/client.rs +++ b/src/client.rs @@ -23,7 +23,13 @@ use crate::server::RequestExt; use crate::utils::format_url; const REDDIT_URL_BASE: &str = "https://oauth.reddit.com"; +const REDDIT_URL_BASE_HOST: &str = "oauth.reddit.com"; + +const REDDIT_SHORT_URL_BASE: &str = "https://redd.it"; +const REDDIT_SHORT_URL_BASE_HOST: &str = "redd.it"; + const ALTERNATIVE_REDDIT_URL_BASE: &str = "https://www.reddit.com"; +const ALTERNATIVE_REDDIT_URL_BASE_HOST: &str = "www.reddit.com"; pub static CLIENT: Lazy>> = Lazy::new(|| { let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build(); @@ -40,6 +46,11 @@ pub static OAUTH_RATELIMIT_REMAINING: AtomicU16 = AtomicU16::new(99); pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false); +static URL_PAIRS: [(&str, &str); 2] = [ + (ALTERNATIVE_REDDIT_URL_BASE, ALTERNATIVE_REDDIT_URL_BASE_HOST), + (REDDIT_SHORT_URL_BASE, REDDIT_SHORT_URL_BASE_HOST), +]; + /// 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`. /// @@ -53,8 +64,27 @@ pub static OAUTH_IS_ROLLING_OVER: AtomicBool = AtomicBool::new(false); /// `Location` header. An `Err(String)` is returned if Reddit responds with a /// 429, or if we were unable to decode the value in the `Location` header. #[cached(size = 1024, time = 600, result = true)] -pub async fn canonical_path(path: String) -> Result, String> { - let res = reddit_head(path.clone(), true).await?; +pub async fn canonical_path(path: String, tries: i8) -> Result, String> { + if tries == 0 { + return Ok(None); + } + + // for each URL pair, try the HEAD request + let res = { + // for url base and host in URL_PAIRS, try reddit_short_head(path.clone(), true, url_base, url_base_host) and if it succeeds, set res. else, res = None + let mut res = None; + for (url_base, url_base_host) in URL_PAIRS { + res = reddit_short_head(path.clone(), true, url_base, url_base_host).await.ok(); + if let Some(res) = &res { + if !res.status().is_client_error() { + break; + } + } + } + res + }; + + let res = res.ok_or_else(|| "Unable to make HEAD request to Reddit.".to_string())?; let status = res.status().as_u16(); let policy_error = res.headers().get(header::RETRY_AFTER).is_some(); @@ -68,6 +98,7 @@ pub async fn canonical_path(path: String) -> Result, String> { let Ok(original) = val.to_str() else { return Err("Unable to decode Location header.".to_string()); }; + // We need to strip the .json suffix from the original path. // In addition, we want to remove share parameters. // Cut it off here instead of letting it propagate all the way @@ -80,7 +111,9 @@ pub async fn canonical_path(path: String) -> Result, String> { // also remove all Reddit domain parts with format_url. // Otherwise, it will literally redirect to Reddit.com. let uri = format_url(stripped_uri); - Ok(Some(uri)) + + // Decrement tries and try again + Box::pin(canonical_path(uri, tries - 1)).await } None => Ok(None), }, @@ -161,20 +194,26 @@ async fn stream(url: &str, req: &Request) -> Result, String /// Makes a GET request to Reddit at `path`. By default, this will honor HTTP /// 3xx codes Reddit returns and will automatically redirect. fn reddit_get(path: String, quarantine: bool) -> Boxed, String>> { - request(&Method::GET, path, true, quarantine) + request(&Method::GET, path, true, quarantine, REDDIT_URL_BASE, REDDIT_URL_BASE_HOST) } -/// Makes a HEAD request to Reddit at `path`. This will not follow redirects. -fn reddit_head(path: String, quarantine: bool) -> Boxed, String>> { - request(&Method::HEAD, path, false, quarantine) +/// Makes a HEAD request to Reddit at `path, using the short URL base. This will not follow redirects. +fn reddit_short_head(path: String, quarantine: bool, base_path: &'static str, host: &'static str) -> Boxed, String>> { + request(&Method::HEAD, path, false, quarantine, base_path, host) } +// /// Makes a HEAD request to Reddit at `path`. This will not follow redirects. +// fn reddit_head(path: String, quarantine: bool) -> Boxed, String>> { +// request(&Method::HEAD, path, false, quarantine, false) +// } +// Unused - reddit_head is only ever called in the context of a short URL + /// Makes a request to Reddit. If `redirect` is `true`, `request_with_redirect` /// will recurse on the URL that Reddit provides in the Location HTTP header /// in its response. -fn request(method: &'static Method, path: String, redirect: bool, quarantine: bool) -> Boxed, String>> { +fn request(method: &'static Method, path: String, redirect: bool, quarantine: bool, base_path: &'static str, host: &'static str) -> Boxed, String>> { // Build Reddit URL from path. - let url = format!("{REDDIT_URL_BASE}{path}"); + let url = format!("{base_path}{path}"); // Construct the hyper client from the HTTPS connector. let client: Client<_, Body> = CLIENT.clone(); @@ -199,7 +238,7 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo .header("Client-Vendor-Id", vendor_id) .header("X-Reddit-Device-Id", device_id) .header("x-reddit-loid", loid) - .header("Host", "oauth.reddit.com") + .header("Host", host) .header("Authorization", &format!("Bearer {token}")) .header("Accept-Encoding", if method == Method::GET { "gzip" } else { "identity" }) .header("Accept-Language", "en-US,en;q=0.5") @@ -254,6 +293,8 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo .to_string(), true, quarantine, + base_path, + host, ) .await; }; @@ -432,13 +473,13 @@ async fn test_localization_popular() { async fn test_obfuscated_share_link() { let share_link = "/r/rust/s/kPgq8WNHRK".into(); // Correct link without share parameters - let canonical_link = "/r/rust/comments/18t5968/why_use_tuple_struct_over_standard_struct/kfbqlbc".into(); - assert_eq!(canonical_path(share_link).await, Ok(Some(canonical_link))); + let canonical_link = "/r/rust/comments/18t5968/why_use_tuple_struct_over_standard_struct/kfbqlbc/".into(); + assert_eq!(canonical_path(share_link, 3).await, Ok(Some(canonical_link))); } #[tokio::test(flavor = "multi_thread")] async fn test_share_link_strip_json() { let link = "/17krzvz".into(); - let canonical_link = "/r/nfl/comments/17krzvz/rapoport_sources_former_no_2_overall_pick/".into(); - assert_eq!(canonical_path(link).await, Ok(Some(canonical_link))); + let canonical_link = "/comments/17krzvz".into(); + assert_eq!(canonical_path(link, 3).await, Ok(Some(canonical_link))); } diff --git a/src/main.rs b/src/main.rs index 406a0d3..515a2a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -341,7 +341,7 @@ async fn main() { let sub = req.param("sub").unwrap_or_default(); match req.param("id").as_deref() { // Share link - Some(id) if (8..12).contains(&id.len()) => match canonical_path(format!("/r/{sub}/s/{id}")).await { + Some(id) if (8..12).contains(&id.len()) => match canonical_path(format!("/r/{sub}/s/{id}"), 3).await { Ok(Some(path)) => Ok(redirect(&path)), Ok(None) => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await, Err(e) => error(req, &e).await, @@ -360,7 +360,7 @@ async fn main() { Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await, // Short link for post - Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{id}")).await { + Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{id}"), 3).await { Ok(path_opt) => match path_opt { Some(path) => Ok(redirect(&path)), None => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await, From 2991813c2ddacbf0e53124f026890d88d3580704 Mon Sep 17 00:00:00 2001 From: Butter Cat Date: Sat, 21 Sep 2024 00:02:34 -0400 Subject: [PATCH 17/28] Make poll results appear inside of a post (#218) --- static/style.css | 4 ++++ templates/utils.html | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/static/style.css b/static/style.css index 214af55..888d2ff 100644 --- a/static/style.css +++ b/static/style.css @@ -1242,6 +1242,10 @@ a.search_subreddit:hover { width: 100%; } +.highlighted .post_poll { + padding: 15px 0 5px; +} + /* Used only for text post preview */ .post_preview { -webkit-mask-image: linear-gradient(180deg, #000 60%, transparent); diff --git a/templates/utils.html b/templates/utils.html index 13cf0e5..e0b3589 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -153,7 +153,10 @@ {% endif %} -
    {{ post.body|safe }}
    +
    + {{ post.body|safe }} + {% call poll(post) %} +
    {% if prefs.hide_score != "on" %} {{ post.score.0 }} From 69c7a69afd04e8dbf9eafb250a110fd5c329242a Mon Sep 17 00:00:00 2001 From: freedit-dev Date: Sat, 21 Sep 2024 12:05:32 +0800 Subject: [PATCH 18/28] =?UTF-8?q?add=20description=20for=20rss=20item=20(j?= =?UTF-8?q?ust=20like=20https://news.ycombinator.com/=E2=80=A6=20(#220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #201 --- src/subreddit.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/subreddit.rs b/src/subreddit.rs index 5d05f96..8a9270e 100644 --- a/src/subreddit.rs +++ b/src/subreddit.rs @@ -494,6 +494,11 @@ pub async fn rss(req: Request) -> Result, String> { link: Some(utils::get_post_url(&post)), author: Some(post.author.name), content: Some(rewrite_urls(&post.body)), + description: Some(format!( + "Comments", + config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), + post.permalink + )), ..Default::default() }) .collect::>(), From b54620b5aaf6d83246e1bad6e19c8da6095ae5ac Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Sat, 21 Sep 2024 15:44:27 -0400 Subject: [PATCH 19/28] fix(client): use async_recursion crate --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 1 + src/client.rs | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a7dfbc0..df122ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,17 @@ dependencies = [ "nom", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -1219,6 +1230,7 @@ version = "0.35.1" dependencies = [ "arc-swap", "askama", + "async-recursion", "base64 0.22.1", "brotli", "build_html", diff --git a/Cargo.toml b/Cargo.toml index 451a83a..c6f8ac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ pretty_env_logger = "0.5.0" dotenvy = "0.15.7" rss = "2.0.7" arc-swap = "1.7.1" +async-recursion = "1.1.1" [dev-dependencies] diff --git a/src/client.rs b/src/client.rs index 6a202ff..72e6bae 100644 --- a/src/client.rs +++ b/src/client.rs @@ -64,6 +64,7 @@ static URL_PAIRS: [(&str, &str); 2] = [ /// `Location` header. An `Err(String)` is returned if Reddit responds with a /// 429, or if we were unable to decode the value in the `Location` header. #[cached(size = 1024, time = 600, result = true)] +#[async_recursion::async_recursion] pub async fn canonical_path(path: String, tries: i8) -> Result, String> { if tries == 0 { return Ok(None); @@ -113,7 +114,7 @@ pub async fn canonical_path(path: String, tries: i8) -> Result, S let uri = format_url(stripped_uri); // Decrement tries and try again - Box::pin(canonical_path(uri, tries - 1)).await + canonical_path(uri, tries - 1).await } None => Ok(None), }, From 245fd9d408f9c6a438600410c28c3eb235db1c8b Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Sat, 21 Sep 2024 15:47:44 -0400 Subject: [PATCH 20/28] fix(funding): update funding --- .github/FUNDING.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 5723180..fedc3e9 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -liberapay: spike -custom: ['https://www.buymeacoffee.com/spikecodes'] +liberapay: sigaloid +buy_me_a_coffee: sigaloid \ No newline at end of file From d5f137ce47de39e2c8c4ed09d13ba1f809bee560 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Sat, 21 Sep 2024 15:48:19 -0400 Subject: [PATCH 21/28] fix(funding): add sponsor link --- .github/FUNDING.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index fedc3e9..262c5e9 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ liberapay: sigaloid -buy_me_a_coffee: sigaloid \ No newline at end of file +buy_me_a_coffee: sigaloid +github: sigaloid \ No newline at end of file From 1e54c639d372bbfd41cc9e6d2e7471f7854a2fda Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 21:02:12 -0400 Subject: [PATCH 22/28] fix(client): add a timeout and retry logic to oauth daemon (#256) * fix(client): add a timeout and retry logic to oauth daemon * fix(client): add a timeout and retry logic to oauth daemon --- src/oauth.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/oauth.rs b/src/oauth.rs index efdf41e..be7437f 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -6,9 +6,10 @@ use crate::{ }; use base64::{engine::general_purpose, Engine as _}; use hyper::{client, Body, Method, Request}; -use log::{info, trace}; +use log::{error, info, trace}; use serde_json::json; +use tokio::time::{error::Elapsed, timeout}; static REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg"; @@ -25,11 +26,32 @@ pub struct Oauth { } impl Oauth { + /// Create a new OAuth client pub(crate) async fn new() -> Self { - let mut oauth = Self::default(); - oauth.login().await; - oauth + // Call new_internal until it succeeds + loop { + let attempt = Self::new_with_timeout().await; + match attempt { + Ok(Some(oauth)) => { + info!("[✅] Successfully created OAuth client"); + return oauth; + } + Ok(None) => { + error!("Failed to create OAuth client. Retrying in 5 seconds..."); + continue; + } + Err(duration) => { + error!("Failed to create OAuth client in {duration:?}. Retrying in 5 seconds..."); + } + } + } } + + async fn new_with_timeout() -> Result, Elapsed> { + let mut oauth = Self::default(); + timeout(Duration::from_secs(5), oauth.login()).await.map(|result| result.map(|_| oauth)) + } + pub(crate) fn default() -> Self { // Generate a device to spoof let device = Device::new(); From 2d6ac78acf633c04e1538c015fd5be96ae62fe0b Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 21:28:54 -0400 Subject: [PATCH 23/28] chore(client): update new oauth path (#258) --- src/oauth.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/oauth.rs b/src/oauth.rs index be7437f..8173fa5 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -13,7 +13,7 @@ use tokio::time::{error::Elapsed, timeout}; static REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg"; -static AUTH_ENDPOINT: &str = "https://accounts.reddit.com"; +static AUTH_ENDPOINT: &str = "https://www.reddit.com"; // Spoofed client for Android devices #[derive(Debug, Clone, Default)] @@ -68,7 +68,7 @@ impl Oauth { } async fn login(&mut self) -> Option<()> { // Construct URL for OAuth token - let url = format!("{AUTH_ENDPOINT}/api/access_token"); + let url = format!("{AUTH_ENDPOINT}/auth/v2/oauth/access-token/loid"); let mut builder = Request::builder().method(Method::POST).uri(&url); // Add headers from spoofed client @@ -98,6 +98,8 @@ impl Oauth { // Parse headers - loid header _should_ be saved sent on subsequent token refreshes. // Technically it's not needed, but it's easy for Reddit API to check for this. // It's some kind of header that uniquely identifies the device. + // Not worried about the privacy implications, since this is randomly changed + // and really only as privacy-concerning as the OAuth token itself. if let Some(header) = resp.headers().get("x-reddit-loid") { self.headers_map.insert("x-reddit-loid".to_owned(), header.to_str().ok()?.to_string()); } From e0d7837c0234f1be61f1ad1b0caff2ab19815ea9 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 21:45:47 -0400 Subject: [PATCH 24/28] =?UTF-8?q?fix(client):=20don't=20catch=20network=20?= =?UTF-8?q?policy=20errors,=20since=20they=20indicate=20q=E2=80=A6=20(#259?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index 72e6bae..fb84ae0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -300,12 +300,6 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo .await; }; - // Special condition rate limiting - https://github.com/redlib-org/redlib/issues/229 - if response.status() == StatusCode::FORBIDDEN && response.headers().get("retry-after").unwrap_or(&HeaderValue::from_static("0")).to_str().unwrap_or("0") == "0" { - force_refresh_token().await; - return Err("Rate limit - try refreshing soon".to_string()); - } - match response.headers().get(header::CONTENT_ENCODING) { // Content not compressed. None => Ok(response), From f1d4e6a4178699a1081977042d483af44898a712 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 23:01:28 -0400 Subject: [PATCH 25/28] fix(client): catch various json errors to properly render error page (#261) * fix(client): catch various json errors to properly render error page * fix(client): catch various json errors to properly render error page --- src/client.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index fb84ae0..80db7af 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,7 +4,6 @@ use futures_lite::future::block_on; use futures_lite::{future::Boxed, FutureExt}; use hyper::client::HttpConnector; use hyper::header::HeaderValue; -use hyper::StatusCode; use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri}; use hyper_rustls::HttpsConnector; use libflate::gzip; @@ -433,6 +432,15 @@ pub async fn json(path: String, quarantine: bool) -> Result { if json["reason"] == "gated" { return Err("gated".into()); } + // Handle private subs + if json["reason"] == "private" { + return Err("private".into()); + } + // Handle banned subs + if json["reason"] == "banned" { + return Err("banned".into()); + } + Err(format!("Reddit error {} \"{}\": {} | {path}", json["error"], json["reason"], json["message"])) } else { Ok(json) @@ -478,3 +486,24 @@ async fn test_share_link_strip_json() { let canonical_link = "/comments/17krzvz".into(); assert_eq!(canonical_path(link, 3).await, Ok(Some(canonical_link))); } +#[tokio::test(flavor = "multi_thread")] +async fn test_private_sub() { + let link = json("/r/suicide/about.json?raw_json=1".into(), true).await; + assert!(link.is_err()); + assert_eq!(link, Err("private".into())); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_banned_sub() { + let link = json("/r/aaa/about.json?raw_json=1".into(), true).await; + assert!(link.is_err()); + assert_eq!(link, Err("banned".into())); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_gated_sub() { + // quarantine to false to specifically catch when we _don't_ catch it + let link = json("/r/drugs/about.json?raw_json=1".into(), false).await; + assert!(link.is_err()); + assert_eq!(link, Err("gated".into())); +} From e6273e2ed5c76efe703f45ea81297253442361f0 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 23:13:36 -0400 Subject: [PATCH 26/28] fix(client): catch json suspended user error (#262) * fix(client): catch json suspended user error --- src/client.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/client.rs b/src/client.rs index 80db7af..ba1ff8c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -416,6 +416,16 @@ pub async fn json(path: String, quarantine: bool) -> Result { match serde_json::from_reader(body.reader()) { Ok(value) => { let json: Value = value; + + // If user is suspended + if let Some(data) = json.get("data") { + if let Some(is_suspended) = data.get("is_suspended").and_then(Value::as_bool) { + if is_suspended { + return Err("suspended".into()); + } + } + } + // If Reddit returned an error if json["error"].is_i64() { // OAuth token has expired; http status 401 @@ -424,6 +434,7 @@ pub async fn json(path: String, quarantine: bool) -> Result { let () = force_refresh_token().await; return Err("OAuth token has expired. Please refresh the page!".to_string()); } + // Handle quarantined if json["reason"] == "quarantined" { return Err("quarantined".into()); From 72f7d9d08c484e43806a66d488e0e77ef16a4f80 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 23:20:12 -0400 Subject: [PATCH 27/28] fix(search): handle multi-sub search (#263) --- src/search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.rs b/src/search.rs index 2b25983..6c8c173 100644 --- a/src/search.rs +++ b/src/search.rs @@ -60,7 +60,8 @@ pub async fn find(req: Request) -> Result, String> { } else { "" }; - let path = format!("{}.json?{}{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results); + let uri_path = req.uri().path().replace("+", "%2B"); + let path = format!("{}.json?{}{}&raw_json=1", uri_path, req.uri().query().unwrap_or_default(), nsfw_results); let mut query = param(&path, "q").unwrap_or_default(); query = REDDIT_URL_MATCH.replace(&query, "").to_string(); From 403513ac4c42d2650098000768da944547caeef1 Mon Sep 17 00:00:00 2001 From: Matthew Esposito Date: Tue, 24 Sep 2024 23:30:06 -0400 Subject: [PATCH 28/28] fix(search): handle queries' urlencoding (#264) * fix(search): handle queries' urlencoding * fix(search): handle queries' urlencoding --- src/post.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/post.rs b/src/post.rs index cdf13a6..e9aa820 100644 --- a/src/post.rs +++ b/src/post.rs @@ -11,7 +11,7 @@ use hyper::{Body, Request, Response}; use askama::Template; use once_cell::sync::Lazy; use regex::Regex; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; // STRUCTS #[derive(Template)] @@ -72,11 +72,15 @@ pub async fn item(req: Request) -> Result, String> { return Ok(nsfw_landing(req, req_url).await.unwrap_or_default()); } - let query = match COMMENT_SEARCH_CAPTURE.captures(&url) { + let query_body = match COMMENT_SEARCH_CAPTURE.captures(&url) { Some(captures) => captures.get(1).unwrap().as_str().replace("%20", " ").replace('+', " "), None => String::new(), }; + let query_string = format!("q={query_body}&type=comment"); + let form = url::form_urlencoded::parse(query_string.as_bytes()).collect::>(); + let query = form.get("q").unwrap().clone().to_string(); + let comments = match query.as_str() { "" => parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req), &req), _ => query_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req), &query, &req),