diff --git a/.github/workflows/docker-arm.yml b/.github/workflows/docker-arm.yml deleted file mode 100644 index 251e325..0000000 --- a/.github/workflows/docker-arm.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Docker ARM Build - -on: - push: - paths-ignore: - - "**.md" - branches: - - master - -jobs: - build-docker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - with: - platforms: all - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - with: - version: latest - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: . - file: ./Dockerfile.arm - platforms: linux/arm64 - push: true - tags: libreddit/libreddit:arm - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/docker-armv7.yml b/.github/workflows/docker-armv7.yml deleted file mode 100644 index d2817d8..0000000 --- a/.github/workflows/docker-armv7.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Docker ARM V7 Build - -on: - push: - paths-ignore: - - "**.md" - branches: - - master - -jobs: - build-docker: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up QEMU - id: qemu - uses: docker/setup-qemu-action@v1 - with: - platforms: all - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - with: - version: latest - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push - id: build_push - uses: docker/build-push-action@v2 - with: - context: . - file: ./Dockerfile.armv7 - platforms: linux/arm/v7 - push: true - tags: libreddit/libreddit:armv7 - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/docker.yml b/.github/workflows/main-docker.yml similarity index 50% rename from .github/workflows/docker.yml rename to .github/workflows/main-docker.yml index c90bd4d..9438945 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/main-docker.yml @@ -1,44 +1,58 @@ -name: Docker amd64 Build +name: Docker Build on: push: paths-ignore: - "**.md" branches: - - master + - 'main' + - 'master' jobs: build-docker: runs-on: ubuntu-latest + strategy: + matrix: + config: + - { platform: 'linux/amd64', tag: 'latest', dockerfile: 'Dockerfile' } + - { platform: 'linux/arm64', tag: 'latest-arm', dockerfile: 'Dockerfile.arm' } + - { platform: 'linux/arm/v7', tag: 'latest-armv7', dockerfile: 'Dockerfile.armv7' } steps: - - uses: actions/checkout@v2 + - name: Checkout sources + uses: actions/checkout@v3 + - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 with: platforms: all + - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 with: version: latest + - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} + - name: Docker Hub Description uses: peter-evans/dockerhub-description@v3 + if: matrix.config.platform == 'linux/amd64' with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: libreddit/libreddit + - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: . - file: ./Dockerfile - platforms: linux/amd64 + file: ./${{ matrix.config.dockerfile }} + platforms: ${{ matrix.config.platform }} push: true - tags: libreddit/libreddit:latest + tags: libreddit/libreddit:${{ matrix.config.tag }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.github/workflows/rust.yml b/.github/workflows/main-rust.yml similarity index 56% rename from .github/workflows/rust.yml rename to .github/workflows/main-rust.yml index c233140..13f5e33 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/main-rust.yml @@ -1,33 +1,44 @@ -name: Rust +name: Rust Build & Publish on: push: paths-ignore: - "**.md" + branches: - - master + - 'main' + - 'master' + + release: + types: [published] env: CARGO_TERM_COLOR: always jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout sources + uses: actions/checkout@v3 - name: Cache Packages - uses: Swatinem/rust-cache@v1.0.1 + uses: Swatinem/rust-cache@v2 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable - name: Build run: cargo build --release - name: Publish to crates.io - continue-on-error: true + if: github.event_name == 'release' run: cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }} - - uses: actions/upload-artifact@v2.2.1 + - uses: actions/upload-artifact@v3 name: Upload a Build Artifact with: name: libreddit @@ -35,23 +46,25 @@ jobs: - name: Versions id: version - run: | - echo "::set-output name=version::$(cargo metadata --format-version 1 --no-deps | jq .packages[0].version -r | sed 's/^/v/')" - echo "::set-output name=tag::$(git describe --tags)" + run: echo "VERSION=$(cargo metadata --format-version 1 --no-deps | jq .packages[0].version -r | sed 's/^/v/')" >> "$GITHUB_OUTPUT" - name: Calculate SHA512 checksum run: sha512sum target/release/libreddit > libreddit.sha512 + + - name: Calculate SHA256 checksum + run: sha256sum target/release/libreddit > libreddit.sha256 - name: Release uses: softprops/action-gh-release@v1 if: github.base_ref != 'master' with: - tag_name: ${{ steps.version.outputs.version }} - name: ${{ steps.version.outputs.version }} - ${{ github.event.head_commit.message }} + tag_name: ${{ steps.version.outputs.VERSION }} + name: ${{ steps.version.outputs.VERSION }} - ${{ github.event.head_commit.message }} draft: true files: | target/release/libreddit libreddit.sha512 + libreddit.sha256 body: | - ${{ github.event.head_commit.message }} ${{ github.sha }} generate_release_notes: true diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..5a9491d --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,62 @@ +name: Pull Request + +on: + push: + branches: + - 'main' + - 'master' + + pull_request: + branches: + - 'main' + - 'master' + +jobs: + test: + name: cargo test + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Run cargo test + run: cargo test + + format: + name: cargo fmt --all -- --check + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain with rustfmt component + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt + + - name: Run cargo fmt + run: cargo fmt --all -- --check + + clippy: + name: cargo clippy -- -D warnings + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain with clippy component + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: clippy + + - name: Run cargo clippy + run: cargo clippy -- -D warnings \ No newline at end of file diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml deleted file mode 100644 index c93aadf..0000000 --- a/.github/workflows/rust-tests.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Tests - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/.gitignore b/.gitignore index c41cc9e..3076ba8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -/target \ No newline at end of file +/target + +# Idea Files +.idea/ \ No newline at end of file diff --git a/CREDITS b/CREDITS index 5c7e3c2..9529880 100644 --- a/CREDITS +++ b/CREDITS @@ -21,6 +21,7 @@ Daniel Valentine dbrennand <52419383+dbrennand@users.noreply.github.com> dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com> +domve Dyras Edward <101938856+EdwardLangdon@users.noreply.github.com> elliot <75391956+ellieeet123@users.noreply.github.com> @@ -58,9 +59,12 @@ Nicholas Christopher Nick Lowery Nico NKIPSC <15067635+NKIPSC@users.noreply.github.com> +o69mar <119129086+o69mar@users.noreply.github.com> obeho <71698631+obeho@users.noreply.github.com> obscurity Om G <34579088+OxyMagnesium@users.noreply.github.com> +pin <90570748+0323pin@users.noreply.github.com> +potatoesAreGod <118043038+potatoesAreGod@users.noreply.github.com> RiversideRocks <59586759+RiversideRocks@users.noreply.github.com> robin <8597693+robrobinbin@users.noreply.github.com> Robin <8597693+robrobinbin@users.noreply.github.com> @@ -87,5 +91,6 @@ Tsvetomir Bonev Vladislav Nepogodin Walkx Wichai <1482605+Chengings@users.noreply.github.com> +wsy2220 xatier Zach <72994911+zachjmurphy@users.noreply.github.com> diff --git a/Cargo.lock b/Cargo.lock index 7d900c7..8815726 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + [[package]] name = "askama" version = "0.11.1" @@ -51,7 +57,7 @@ checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" dependencies = [ "askama_shared", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -72,18 +78,18 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "async-trait" -version = "0.1.61" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -112,9 +118,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -142,9 +148,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", "serde", @@ -152,9 +158,9 @@ dependencies = [ [[package]] name = "build_html" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ef018b44d829e1b3364b4969059c098743595ec57a7eed176fbc9d909ac217" +checksum = "3108fe6fe7ac796fb7625bdde8fa2b67b5a7731496251ca57c7b8cadd78a16a1" [[package]] name = "bumpalo" @@ -164,9 +170,9 @@ checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cached" @@ -197,7 +203,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -208,9 +214,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -220,22 +226,29 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.1" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +checksum = "49f9152d70e42172fdb87de2efd7327160beee37886027cf86f30a233d5b30b4" dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e067b220911598876eb55d52725ddcc201ffe3f0904018195973bc5b012ea2ca" +dependencies = [ + "anstyle", "bitflags", "clap_lex", ] [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" -dependencies = [ - "os_str_bytes", -] +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "cookie" @@ -259,15 +272,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] @@ -293,9 +306,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", @@ -303,27 +316,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -337,10 +350,31 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "errno" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -362,15 +396,15 @@ dependencies = [ [[package]] name = "fs_extra" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -382,9 +416,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -392,21 +426,21 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -419,21 +453,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-sink", @@ -444,9 +478,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -454,9 +488,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -478,9 +512,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -517,10 +551,16 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.8" +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -552,9 +592,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -607,9 +647,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -625,16 +665,27 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.5" +name = "io-lifetimes" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -647,15 +698,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libflate" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05605ab2bce11bcfc0e9c635ff29ef8b2ea83f29be257ee7d730cac3ee373093" +checksum = "97822bf791bd4d5b403713886a5fbe8bf49520fe78e323b0dc480ca1a03e50b0" dependencies = [ "adler32", "crc32fast", @@ -664,16 +715,16 @@ dependencies = [ [[package]] name = "libflate_lz77" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a734c0493409afcd49deee13c006a04e3586b9761a03543c6272c9c51f2f5a" +checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" dependencies = [ "rle-decode-fast", ] [[package]] name = "libreddit" -version = "0.28.0" +version = "0.30.1" dependencies = [ "askama", "brotli", @@ -701,6 +752,12 @@ dependencies = [ "url", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f508063cc7bb32987c71511216bd5a32be15bccb6a80b52df8b9d7f01fc3aa2" + [[package]] name = "lipsum" version = "0.8.2" @@ -738,9 +795,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -760,14 +817,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -786,7 +843,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -801,9 +858,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl-probe" @@ -811,17 +868,11 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -835,15 +886,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -872,9 +923,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -887,9 +938,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -934,10 +985,19 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.7.1" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -946,18 +1006,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "ring" @@ -988,9 +1039,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rust-embed" -version = "6.4.2" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -999,28 +1050,42 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.3.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 1.0.109", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "7.3.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" dependencies = [ "globset", "sha2", "walkdir", ] +[[package]] +name = "rustix" +version = "0.37.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.20.8" @@ -1068,9 +1133,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -1087,7 +1152,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1125,14 +1190,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b672e005ae58fef5da619d90b9f1c5b44b061890f4a371b3c96257a8a15e697" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -1143,9 +1208,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -1153,29 +1218,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1184,9 +1249,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.17" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ "indexmap", "itoa", @@ -1208,18 +1273,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -1232,9 +1297,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1254,9 +1319,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -1265,43 +1341,42 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", "libc", @@ -1319,9 +1394,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -1337,20 +1412,19 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.2" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -1358,18 +1432,18 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -1385,9 +1459,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -1399,9 +1473,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -1455,15 +1529,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -1476,9 +1550,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" [[package]] name = "untrusted" @@ -1520,12 +1594,11 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -1547,9 +1620,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1557,24 +1630,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1582,28 +1655,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -1656,53 +1729,143 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 78e93e2..fe0d8df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "libreddit" description = " Alternative private front-end to Reddit" license = "AGPL-3.0" repository = "https://github.com/spikecodes/libreddit" -version = "0.28.0" +version = "0.30.1" authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"] edition = "2021" diff --git a/Dockerfile.arm b/Dockerfile.arm index 098bf13..ea849b4 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -7,6 +7,10 @@ RUN apk add --no-cache g++ git WORKDIR /usr/src/libreddit +# cache dependencies in their own layer +COPY Cargo.lock Cargo.toml . +RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo install --config net.git-fetch-with-cli=true --path . && rm -rf ./src + COPY . . # net.git-fetch-with-cli is specified in order to prevent a potential OOM kill diff --git a/README.md b/README.md index c81aaeb..0330486 100644 --- a/README.md +++ b/README.md @@ -114,13 +114,21 @@ Results from Google PageSpeed Insights ([Libreddit Report](https://pagespeed.web For transparency, I hope to describe all the ways Libreddit handles user privacy. -**Logging:** In production (when running the binary, hosting with docker, or using the official instances), Libreddit logs nothing. When debugging (running from source without `--release`), Libreddit logs post IDs fetched to aid with troubleshooting. +#### Server -**DNS:** Both official domains (`libredd.it` and `libreddit.spike.codes`) use Cloudflare as the DNS resolver. Though, the sites are not proxied through Cloudflare meaning Cloudflare doesn't have access to user traffic. +* **Logging:** In production (when running the binary, hosting with docker, or using the official instances), Libreddit logs nothing. When debugging (running from source without `--release`), Libreddit logs post IDs fetched to aid with troubleshooting. -**Cookies:** Libreddit uses optional cookies to store any configured settings in [the settings menu](https://libreddit.spike.codes/settings). These are not cross-site cookies and the cookies hold no personal data. +* **Cookies:** Libreddit uses optional cookies to store any configured settings in [the settings menu](https://libreddit.spike.codes/settings). These are not cross-site cookies and the cookies hold no personal data. -**Hosting:** The official instances are 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. +#### Official instance (libreddit.spike.codes) + +The official instance is hosted at https://libreddit.spike.codes. + +* **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. --- @@ -159,12 +167,26 @@ For ArchLinux users, Libreddit is available from the AUR as [`libreddit-git`](ht ``` yay -S libreddit-git ``` +## 4) NetBSD/pkgsrc -## 4) GitHub Releases +For NetBSD users, Libreddit is available from the official repositories. + +``` +pkgin install libreddit +``` + +Or, if you prefer to build from source + +``` +cd /usr/pkgsrc/libreddit +make install +``` + +## 5) GitHub Releases If you're on Linux and none of these methods work for you, you can grab a Linux binary from [the newest release](https://github.com/libreddit/libreddit/releases/latest). -## 5) Replit/Heroku/Glitch +## 6) Replit/Heroku/Glitch > **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. @@ -191,6 +213,7 @@ Assign a default value for each instance-specific setting by passing environment |-|-|-|-| | `SFW_ONLY` | `["on", "off"]` | `off` | Enables SFW-only mode for the instance, i.e. all NSFW content is filtered. | | `BANNER` | String | (empty) | Allows the server to set a banner to be displayed. Currently this is displayed on the instance info page. | +| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. | | `PUSHSHIFT_FRONTEND` | String | `www.unddit.com` | Allows the server to set the Pushshift frontend to be used with "removed" links. ## Default User Settings @@ -210,8 +233,9 @@ Assign a default value for each user-modifiable setting by passing environment v | `USE_HLS` | `["on", "off"]` | `off` | | `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` | | `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` | -| `HIDE_AWARDS` | `["on", "off"]` | `off` | -| `SUBSCRIPTIONS` | Array of subreddit names (`["sub1", "sub2"]`) | `[]` | +| `SUBSCRIPTIONS` | `+`-delimited list of subreddits (`sub1+sub2+sub3+...`) | _(none)_ | +| `HIDE_AWARDS` | `["on", "off"]` | `off` +| `DISABLE_VISIT_REDDIT_CONFIRMATION` | `["on", "off"]` | `off` | You can also configure Libreddit with a configuration file. An example `libreddit.toml` can be found below: diff --git a/app.json b/app.json index 96d8424..c6b60c5 100644 --- a/app.json +++ b/app.json @@ -50,9 +50,15 @@ "LIBREDDIT_BANNER": { "required": false }, + "LIBREDDIT_ROBOTS_DISABLE_INDEXING": { + "required": false + }, "LIBREDDIT_DEFAULT_SUBSCRIPTIONS": { "required": false }, + "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION": { + "required": false + }, "LIBREDDIT_PUSHSHIFT_FRONTEND": { "required": false } diff --git a/build.rs b/build.rs index 8700cce..26b1ed9 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,11 @@ -use std::{ - os::unix::process::ExitStatusExt, - process::{Command, ExitStatus, Output}, -}; +use std::process::{Command, ExitStatus, Output}; + +#[cfg(not(target_os = "windows"))] +use std::os::unix::process::ExitStatusExt; + +#[cfg(target_os = "windows")] +use std::os::windows::process::ExitStatusExt; + fn main() { let output = String::from_utf8( Command::new("git") diff --git a/contrib/libreddit.conf b/contrib/libreddit.conf index dd91e3b..e1bb5e7 100644 --- a/contrib/libreddit.conf +++ b/contrib/libreddit.conf @@ -1,2 +1,16 @@ ADDRESS=0.0.0.0 PORT=12345 +#LIBREDDIT_DEFAULT_THEME=default +#LIBREDDIT_DEFAULT_FRONT_PAGE=default +#LIBREDDIT_DEFAULT_LAYOUT=card +#LIBREDDIT_DEFAULT_WIDE=off +#LIBREDDIT_DEFAULT_POST_SORT=hot +#LIBREDDIT_DEFAULT_COMMENT_SORT=confidence +#LIBREDDIT_DEFAULT_SHOW_NSFW=off +#LIBREDDIT_DEFAULT_BLUR_NSFW=off +#LIBREDDIT_DEFAULT_USE_HLS=off +#LIBREDDIT_DEFAULT_HIDE_HLS_NOTIFICATION=off +#LIBREDDIT_DEFAULT_AUTOPLAY_VIDEOS=off +#LIBREDDIT_DEFAULT_SUBSCRIPTIONS=off (sub1+sub2+sub3) +#LIBREDDIT_DEFAULT_HIDE_AWARDS=off +#LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION=off diff --git a/contrib/libreddit.service b/contrib/libreddit.service index 8ed5da7..494971b 100644 --- a/contrib/libreddit.service +++ b/contrib/libreddit.service @@ -5,8 +5,8 @@ After=network.service [Service] DynamicUser=yes # Default Values -Environment=ADDRESS=0.0.0.0 -Environment=PORT=8080 +#Environment=ADDRESS=0.0.0.0 +#Environment=PORT=8080 # Optional Override EnvironmentFile=-/etc/libreddit.conf ExecStart=/usr/bin/libreddit -a ${ADDRESS} -p ${PORT} diff --git a/docker-compose.yml b/docker-compose.yml index 2688e9d..ad0fd1f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,18 @@ services: container_name: "libreddit" ports: - 8080:8080 + user: nobody + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + networks: + - libreddit healthcheck: test: ["CMD", "wget", "--spider", "-q", "--tries=1", "http://localhost:8080/settings"] interval: 5m timeout: 3s + +networks: + libreddit: diff --git a/src/client.rs b/src/client.rs index f577f95..4c174cd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,10 @@ use cached::proc_macro::cached; use futures_lite::{future::Boxed, FutureExt}; -use hyper::{body, body::Buf, client, header, Body, Method, Request, Response, Uri}; +use hyper::client::HttpConnector; +use hyper::{body, body::Buf, client, header, Body, Client, Method, Request, Response, Uri}; +use hyper_rustls::HttpsConnector; use libflate::gzip; +use once_cell::sync::Lazy; use percent_encoding::{percent_encode, CONTROLS}; use serde_json::Value; use std::{io, result::Result}; @@ -11,6 +14,11 @@ use crate::server::RequestExt; const REDDIT_URL_BASE: &str = "https://www.reddit.com"; +static CLIENT: Lazy>> = Lazy::new(|| { + let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build(); + client::Client::builder().build(https) +}); + /// 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`. /// @@ -66,11 +74,8 @@ async fn stream(url: &str, req: &Request) -> Result, String // First parameter is target URL (mandatory). let uri = url.parse::().map_err(|_| "Couldn't parse URL".to_string())?; - // Prepare the HTTPS connector. - let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build(); - // Build the hyper client from the HTTPS connector. - let client: client::Client<_, hyper::Body> = client::Client::builder().build(https); + let client: client::Client<_, hyper::Body> = CLIENT.clone(); let mut builder = Request::get(uri); @@ -123,11 +128,8 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo // Build Reddit URL from path. let url = format!("{}{}", REDDIT_URL_BASE, path); - // Prepare the HTTPS connector. - let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_or_http().enable_http1().build(); - // Construct the hyper client from the HTTPS connector. - let client: client::Client<_, hyper::Body> = client::Client::builder().build(https); + let client: client::Client<_, hyper::Body> = CLIENT.clone(); // Build request to Reddit. When making a GET, request gzip compression. // (Reddit doesn't do brotli yet.) @@ -140,7 +142,14 @@ fn request(method: &'static Method, path: String, redirect: bool, quarantine: bo .header("Accept-Encoding", if method == Method::GET { "gzip" } else { "identity" }) .header("Accept-Language", "en-US,en;q=0.5") .header("Connection", "keep-alive") - .header("Cookie", if quarantine { "_options=%7B%22pref_quarantine_optin%22%3A%20true%7D" } else { "" }) + .header( + "Cookie", + if quarantine { + "_options=%7B%22pref_quarantine_optin%22%3A%20true%2C%20%22pref_gated_sr_optin%22%3A%20true%7D" + } else { + "" + }, + ) .body(Body::empty()); async move { diff --git a/src/config.rs b/src/config.rs index e2891ea..478923a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,9 +59,15 @@ pub struct Config { #[serde(rename = "LIBREDDIT_DEFAULT_SUBSCRIPTIONS")] pub(crate) default_subscriptions: Option, + #[serde(rename = "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION")] + pub(crate) default_disable_visit_reddit_confirmation: Option, + #[serde(rename = "LIBREDDIT_BANNER")] pub(crate) banner: Option, + #[serde(rename = "LIBREDDIT_ROBOTS_DISABLE_INDEXING")] + pub(crate) robots_disable_indexing: Option, + #[serde(rename = "LIBREDDIT_PUSHSHIFT_FRONTEND")] pub(crate) pushshift: Option, } @@ -93,7 +99,9 @@ impl Config { default_hide_hls_notification: parse("LIBREDDIT_DEFAULT_HIDE_HLS"), default_hide_awards: parse("LIBREDDIT_DEFAULT_HIDE_AWARDS"), default_subscriptions: parse("LIBREDDIT_DEFAULT_SUBSCRIPTIONS"), + default_disable_visit_reddit_confirmation: parse("LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION"), banner: parse("LIBREDDIT_BANNER"), + robots_disable_indexing: parse("LIBREDDIT_ROBOTS_DISABLE_INDEXING"), pushshift: parse("LIBREDDIT_PUSHSHIFT_FRONTEND"), } } @@ -114,7 +122,9 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option { "LIBREDDIT_DEFAULT_WIDE" => config.default_wide.clone(), "LIBREDDIT_DEFAULT_HIDE_AWARDS" => config.default_hide_awards.clone(), "LIBREDDIT_DEFAULT_SUBSCRIPTIONS" => config.default_subscriptions.clone(), + "LIBREDDIT_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION" => config.default_disable_visit_reddit_confirmation.clone(), "LIBREDDIT_BANNER" => config.banner.clone(), + "LIBREDDIT_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(), "LIBREDDIT_PUSHSHIFT_FRONTEND" => config.pushshift.clone(), _ => None, } @@ -160,3 +170,8 @@ fn test_alt_env_config_precedence() { write("libreddit.toml", config_to_write).unwrap(); assert_eq!(get_setting("LIBREDDIT_DEFAULT_COMMENT_SORT"), Some("top".into())) } +#[test] +#[sealed_test(env = [("LIBREDDIT_DEFAULT_SUBSCRIPTIONS", "news+bestof")])] +fn test_default_subscriptions() { + assert_eq!(get_setting("LIBREDDIT_DEFAULT_SUBSCRIPTIONS"), Some("news+bestof".into())); +} diff --git a/src/duplicates.rs b/src/duplicates.rs index 93f4df6..b099acf 100644 --- a/src/duplicates.rs +++ b/src/duplicates.rs @@ -3,7 +3,7 @@ use crate::client::json; use crate::server::RequestExt; use crate::subreddit::{can_access_quarantine, quarantine}; -use crate::utils::{error, filter_posts, get_filters, nsfw_landing, parse_post, setting, template, Post, Preferences}; +use crate::utils::{error, filter_posts, get_filters, nsfw_landing, parse_post, template, Post, Preferences}; use askama::Template; use hyper::{Body, Request, Response}; @@ -67,11 +67,12 @@ pub async fn item(req: Request) -> Result, String> { Ok(response) => { let post = parse_post(&response[0]["data"]["children"][0]).await; + let req_url = req.uri().to_string(); // Return landing page if this post if this Reddit deems this post // NSFW, but we have also disabled the display of NSFW content - // or if the instance is SFW-only. - if post.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) { - return Ok(nsfw_landing(req).await.unwrap_or_default()); + // or if the instance is SFW-only + if post.nsfw && crate::utils::should_be_nsfw_gated(&req, &req_url) { + return Ok(nsfw_landing(req, req_url).await.unwrap_or_default()); } let filters = get_filters(&req); @@ -195,14 +196,13 @@ pub async fn item(req: Request) -> Result, String> { after = response[1]["data"]["after"].as_str().unwrap_or_default().to_string(); } } - let url = req.uri().to_string(); template(DuplicatesTemplate { params: DuplicatesParams { before, after, sort }, post, duplicates, prefs: Preferences::new(&req), - url, + url: req_url, num_posts_filtered, all_posts_filtered, }) @@ -210,9 +210,9 @@ pub async fn item(req: Request) -> Result, String> { // Process error. Err(msg) => { - if msg == "quarantined" { + if msg == "quarantined" || msg == "gated" { let sub = req.param("sub").unwrap_or_default(); - quarantine(req, sub) + quarantine(req, sub, msg) } else { error(req, msg).await } diff --git a/src/main.rs b/src/main.rs index 4f5d3d2..d1ebf85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -186,9 +186,21 @@ async fn main() { app .at("/manifest.json") .get(|_| resource(include_str!("../static/manifest.json"), "application/json", false).boxed()); - app - .at("/robots.txt") - .get(|_| resource("User-agent: *\nDisallow: /u/\nDisallow: /user/", "text/plain", true).boxed()); + app.at("/robots.txt").get(|_| { + resource( + if match config::get_setting("LIBREDDIT_ROBOTS_DISABLE_INDEXING") { + Some(val) => val == "on", + None => false, + } { + "User-agent: *\nDisallow: /" + } else { + "User-agent: *\nDisallow: /u/\nDisallow: /user/" + }, + "text/plain", + true, + ) + .boxed() + }); app.at("/favicon.ico").get(|_| favicon().boxed()); app.at("/logo.png").get(|_| pwa_logo().boxed()); app.at("/Inter.var.woff2").get(|_| font().boxed()); diff --git a/src/post.rs b/src/post.rs index 5478d22..98dcac3 100644 --- a/src/post.rs +++ b/src/post.rs @@ -9,6 +9,8 @@ use crate::utils::{ use hyper::{Body, Request, Response}; use askama::Template; +use once_cell::sync::Lazy; +use regex::Regex; use std::collections::HashSet; // STRUCTS @@ -21,13 +23,18 @@ struct PostTemplate { prefs: Preferences, single_thread: bool, url: String, + url_without_query: String, + comment_query: String, } +static COMMENT_SEARCH_CAPTURE: Lazy = Lazy::new(|| Regex::new(r#"\?q=(.*)&type=comment"#).unwrap()); + pub async fn item(req: Request) -> Result, String> { // Build Reddit API path let mut path: String = format!("{}.json?{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default()); let sub = req.param("sub").unwrap_or_default(); let quarantined = can_access_quarantine(&req, &sub); + let url = req.uri().to_string(); // Set sort to sort query parameter let sort = param(&path, "sort").unwrap_or_else(|| { @@ -57,31 +64,41 @@ pub async fn item(req: Request) -> Result, String> { // Parse the JSON into Post and Comment structs let post = parse_post(&response[0]["data"]["children"][0]).await; + let req_url = req.uri().to_string(); // Return landing page if this post if this Reddit deems this post // NSFW, but we have also disabled the display of NSFW content // or if the instance is SFW-only. - if post.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) { - return Ok(nsfw_landing(req).await.unwrap_or_default()); + if post.nsfw && crate::utils::should_be_nsfw_gated(&req, &req_url) { + return Ok(nsfw_landing(req, req_url).await.unwrap_or_default()); } - let comments = parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req), &req); - let url = req.uri().to_string(); + let query = match COMMENT_SEARCH_CAPTURE.captures(&url) { + Some(captures) => captures.get(1).unwrap().as_str().replace("%20", " ").replace('+', " "), + None => String::new(), + }; + + 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), + }; // Use the Post and Comment structs to generate a website to show users template(PostTemplate { comments, post, + url_without_query: url.clone().trim_end_matches(&format!("?q={query}&type=comment")).to_string(), sort, prefs: Preferences::new(&req), single_thread, - url, + url: req_url, + comment_query: query, }) } // If the Reddit API returns an error, exit and send error page to user Err(msg) => { - if msg == "quarantined" { + if msg == "quarantined" || msg == "gated" { let sub = req.param("sub").unwrap_or_default(); - quarantine(req, sub) + quarantine(req, sub, msg) } else { error(req, msg).await } @@ -90,6 +107,7 @@ pub async fn item(req: Request) -> Result, String> { } // COMMENTS + fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str, highlighted_comment: &str, filters: &HashSet, req: &Request) -> Vec { // Parse the comment JSON into a Vector of Comments let comments = json["data"]["children"].as_array().map_or(Vec::new(), std::borrow::ToOwned::to_owned); @@ -98,90 +116,138 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str, comments .into_iter() .map(|comment| { - let kind = comment["kind"].as_str().unwrap_or_default().to_string(); let data = &comment["data"]; - - let unix_time = data["created_utc"].as_f64().unwrap_or_default(); - let (rel_time, created) = time(unix_time); - - let edited = data["edited"].as_f64().map_or((String::new(), String::new()), time); - - let score = data["score"].as_i64().unwrap_or(0); - - // If this comment contains replies, handle those too let replies: Vec = if data["replies"].is_object() { parse_comments(&data["replies"], post_link, post_author, highlighted_comment, filters, req) } else { Vec::new() }; - - let awards: Awards = Awards::parse(&data["all_awardings"]); - - let parent_kind_and_id = val(&comment, "parent_id"); - let parent_info = parent_kind_and_id.split('_').collect::>(); - - let id = val(&comment, "id"); - let highlighted = id == highlighted_comment; - - let body = if (val(&comment, "author") == "[deleted]" && val(&comment, "body") == "[removed]") || val(&comment, "body") == "[ Removed by Reddit ]" { - format!( - "

[removed] — view removed comment

", - get_setting("LIBREDDIT_PUSHSHIFT_FRONTEND").unwrap_or(String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)), - post_link, - id - ) - } else { - rewrite_urls(&val(&comment, "body_html")) - }; - - let author = Author { - name: val(&comment, "author"), - flair: Flair { - flair_parts: FlairPart::parse( - data["author_flair_type"].as_str().unwrap_or_default(), - data["author_flair_richtext"].as_array(), - data["author_flair_text"].as_str(), - ), - text: val(&comment, "link_flair_text"), - background_color: val(&comment, "author_flair_background_color"), - foreground_color: val(&comment, "author_flair_text_color"), - }, - distinguished: val(&comment, "distinguished"), - }; - let is_filtered = filters.contains(&["u_", author.name.as_str()].concat()); - - // Many subreddits have a default comment posted about the sub's rules etc. - // Many libreddit users do not wish to see this kind of comment by default. - // Reddit does not tell us which users are "bots", so a good heuristic is to - // collapse stickied moderator comments. - let is_moderator_comment = data["distinguished"].as_str().unwrap_or_default() == "moderator"; - let is_stickied = data["stickied"].as_bool().unwrap_or_default(); - let collapsed = (is_moderator_comment && is_stickied) || is_filtered; - - Comment { - id, - kind, - parent_id: parent_info[1].to_string(), - parent_kind: parent_info[0].to_string(), - post_link: post_link.to_string(), - post_author: post_author.to_string(), - body, - author, - score: if data["score_hidden"].as_bool().unwrap_or_default() { - ("\u{2022}".to_string(), "Hidden".to_string()) - } else { - format_num(score) - }, - rel_time, - created, - edited, - replies, - highlighted, - awards, - collapsed, - is_filtered, - prefs: Preferences::new(req), - } + build_comment(&comment, data, replies, post_link, post_author, highlighted_comment, filters, req) }) .collect() } + +fn query_comments( + json: &serde_json::Value, + post_link: &str, + post_author: &str, + highlighted_comment: &str, + filters: &HashSet, + query: &str, + req: &Request, +) -> Vec { + let comments = json["data"]["children"].as_array().map_or(Vec::new(), std::borrow::ToOwned::to_owned); + let mut results = Vec::new(); + + comments.into_iter().for_each(|comment| { + let data = &comment["data"]; + + // If this comment contains replies, handle those too + if data["replies"].is_object() { + results.append(&mut query_comments(&data["replies"], post_link, post_author, highlighted_comment, filters, query, req)) + } + + let c = build_comment(&comment, data, Vec::new(), post_link, post_author, highlighted_comment, filters, req); + if c.body.to_lowercase().contains(&query.to_lowercase()) { + results.push(c); + } + }); + + results +} +#[allow(clippy::too_many_arguments)] +fn build_comment( + comment: &serde_json::Value, + data: &serde_json::Value, + replies: Vec, + post_link: &str, + post_author: &str, + highlighted_comment: &str, + filters: &HashSet, + req: &Request, +) -> Comment { + let id = val(comment, "id"); + + let body = if (val(comment, "author") == "[deleted]" && val(comment, "body") == "[removed]") || val(comment, "body") == "[ Removed by Reddit ]" { + format!( + "

[removed] — view removed comment

", + get_setting("LIBREDDIT_PUSHSHIFT_FRONTEND").unwrap_or(String::from(crate::config::DEFAULT_PUSHSHIFT_FRONTEND)), + post_link, + id + ) + } else { + rewrite_urls(&val(comment, "body_html")) + }; + let kind = comment["kind"].as_str().unwrap_or_default().to_string(); + + let unix_time = data["created_utc"].as_f64().unwrap_or_default(); + let (rel_time, created) = time(unix_time); + + let edited = data["edited"].as_f64().map_or((String::new(), String::new()), time); + + let score = data["score"].as_i64().unwrap_or(0); + + // The JSON API only provides comments up to some threshold. + // Further comments have to be loaded by subsequent requests. + // The "kind" value will be "more" and the "count" + // shows how many more (sub-)comments exist in the respective nesting level. + // Note that in certain (seemingly random) cases, the count is simply wrong. + let more_count = data["count"].as_i64().unwrap_or_default(); + + let awards: Awards = Awards::parse(&data["all_awardings"]); + + let parent_kind_and_id = val(comment, "parent_id"); + let parent_info = parent_kind_and_id.split('_').collect::>(); + + let highlighted = id == highlighted_comment; + + let author = Author { + name: val(comment, "author"), + flair: Flair { + flair_parts: FlairPart::parse( + data["author_flair_type"].as_str().unwrap_or_default(), + data["author_flair_richtext"].as_array(), + data["author_flair_text"].as_str(), + ), + text: val(comment, "link_flair_text"), + background_color: val(comment, "author_flair_background_color"), + foreground_color: val(comment, "author_flair_text_color"), + }, + distinguished: val(comment, "distinguished"), + }; + let is_filtered = filters.contains(&["u_", author.name.as_str()].concat()); + + // Many subreddits have a default comment posted about the sub's rules etc. + // Many libreddit users do not wish to see this kind of comment by default. + // Reddit does not tell us which users are "bots", so a good heuristic is to + // collapse stickied moderator comments. + let is_moderator_comment = data["distinguished"].as_str().unwrap_or_default() == "moderator"; + let is_stickied = data["stickied"].as_bool().unwrap_or_default(); + let collapsed = (is_moderator_comment && is_stickied) || is_filtered; + + Comment { + id, + kind, + parent_id: parent_info[1].to_string(), + parent_kind: parent_info[0].to_string(), + post_link: post_link.to_string(), + post_author: post_author.to_string(), + body, + author, + score: if data["score_hidden"].as_bool().unwrap_or_default() { + ("\u{2022}".to_string(), "Hidden".to_string()) + } else { + format_num(score) + }, + rel_time, + created, + edited, + replies, + highlighted, + awards, + collapsed, + is_filtered, + more_count, + prefs: Preferences::new(req), + } +} diff --git a/src/search.rs b/src/search.rs index a8ac55f..35c0f96 100644 --- a/src/search.rs +++ b/src/search.rs @@ -145,9 +145,9 @@ pub async fn find(req: Request) -> Result, String> { }) } Err(msg) => { - if msg == "quarantined" { + if msg == "quarantined" || msg == "gated" { let sub = req.param("sub").unwrap_or_default(); - quarantine(req, sub) + quarantine(req, sub, msg) } else { error(req, msg).await } diff --git a/src/server.rs b/src/server.rs index 501b933..b1d293b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -253,7 +253,7 @@ impl Server { .boxed() } // If there was a routing error - Err(e) => async move { new_boilerplate(def_headers, req_headers, 404, e.into()).await }.boxed(), + Err(e) => new_boilerplate(def_headers, req_headers, 404, e.into()).boxed(), } })) } @@ -379,7 +379,7 @@ fn determine_compressor(accept_encoding: String) -> Option { // This loop reads the requested compressors and keeps track of whichever // one has the highest priority per our heuristic. - for val in accept_encoding.to_string().split(',') { + for val in accept_encoding.split(',') { let mut q: f64 = 1.0; // The compressor and q-value (if the latter is defined) diff --git a/src/settings.rs b/src/settings.rs index 4aa0a09..3dd4e45 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -19,7 +19,7 @@ struct SettingsTemplate { // CONSTANTS -const PREFS: [&str; 12] = [ +const PREFS: [&str; 13] = [ "theme", "front_page", "layout", @@ -32,6 +32,7 @@ const PREFS: [&str; 12] = [ "hide_hls_notification", "autoplay_videos", "hide_awards", + "disable_visit_reddit_confirmation", ]; // FUNCTIONS diff --git a/src/subreddit.rs b/src/subreddit.rs index 71692c4..ee57ea5 100644 --- a/src/subreddit.rs +++ b/src/subreddit.rs @@ -97,10 +97,11 @@ pub async fn community(req: Request) -> Result, String> { } }; + let req_url = req.uri().to_string(); // Return landing page if this post if this is NSFW community but the user // has disabled the display of NSFW content or if the instance is SFW-only. - if sub.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) { - return Ok(nsfw_landing(req).await.unwrap_or_default()); + if sub.nsfw && crate::utils::should_be_nsfw_gated(&req, &req_url) { + return Ok(nsfw_landing(req, req_url).await.unwrap_or_default()); } let path = format!("/r/{}/{}.json?{}&raw_json=1", sub_name.clone(), sort, req.uri().query().unwrap_or_default()); @@ -144,7 +145,7 @@ pub async fn community(req: Request) -> Result, String> { }) } Err(msg) => match msg.as_str() { - "quarantined" => quarantine(req, sub_name), + "quarantined" | "gated" => quarantine(req, sub_name, msg), "private" => error(req, format!("r/{} is a private community", sub_name)).await, "banned" => error(req, format!("r/{} has been banned from Reddit", sub_name)).await, _ => error(req, msg).await, @@ -153,9 +154,9 @@ pub async fn community(req: Request) -> Result, String> { } } -pub fn quarantine(req: Request, sub: String) -> Result, String> { +pub fn quarantine(req: Request, sub: String, restriction: String) -> Result, String> { let wall = WallTemplate { - title: format!("r/{} is quarantined", sub), + title: format!("r/{} is {}", sub, restriction), msg: "Please click the button below to continue to this subreddit.".to_string(), url: req.uri().to_string(), sub, @@ -323,8 +324,8 @@ pub async fn wiki(req: Request) -> Result, String> { url, }), Err(msg) => { - if msg == "quarantined" { - quarantine(req, sub) + if msg == "quarantined" || msg == "gated" { + quarantine(req, sub, msg) } else { error(req, msg).await } @@ -361,8 +362,8 @@ pub async fn sidebar(req: Request) -> Result, String> { url, }), Err(msg) => { - if msg == "quarantined" { - quarantine(req, sub) + if msg == "quarantined" || msg == "gated" { + quarantine(req, sub, msg) } else { error(req, msg).await } diff --git a/src/user.rs b/src/user.rs index 53f4090..efa70a9 100644 --- a/src/user.rs +++ b/src/user.rs @@ -50,11 +50,12 @@ pub async fn profile(req: Request) -> Result, String> { // Retrieve info from user about page. let user = user(&username).await.unwrap_or_default(); + let req_url = req.uri().to_string(); // Return landing page if this post if this Reddit deems this user NSFW, // but we have also disabled the display of NSFW content or if the instance // is SFW-only. - if user.nsfw && (setting(&req, "show_nsfw") != "on" || crate::utils::sfw_only()) { - return Ok(nsfw_landing(req).await.unwrap_or_default()); + if user.nsfw && crate::utils::should_be_nsfw_gated(&req, &req_url) { + return Ok(nsfw_landing(req, req_url).await.unwrap_or_default()); } let filters = get_filters(&req); diff --git a/src/utils.rs b/src/utils.rs index 06d7f92..5cf4b7d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use crate::{client::json, server::RequestExt}; use askama::Template; use cookie::Cookie; use hyper::{Body, Request, Response}; +use once_cell::sync::Lazy; use regex::Regex; use rust_embed::RustEmbed; use serde_json::Value; @@ -97,6 +98,61 @@ pub struct Author { pub distinguished: String, } +pub struct Poll { + pub poll_options: Vec, + pub voting_end_timestamp: (String, String), + pub total_vote_count: u64, +} + +impl Poll { + pub fn parse(poll_data: &Value) -> Option { + poll_data.as_object()?; + + let total_vote_count = poll_data["total_vote_count"].as_u64()?; + // voting_end_timestamp is in the format of milliseconds + let voting_end_timestamp = time(poll_data["voting_end_timestamp"].as_f64()? / 1000.0); + let poll_options = PollOption::parse(&poll_data["options"])?; + + Some(Self { + poll_options, + total_vote_count, + voting_end_timestamp, + }) + } + + pub fn most_votes(&self) -> u64 { + self.poll_options.iter().filter_map(|o| o.vote_count).max().unwrap_or(0) + } +} + +pub struct PollOption { + pub id: u64, + pub text: String, + pub vote_count: Option, +} + +impl PollOption { + pub fn parse(options: &Value) -> Option> { + Some( + options + .as_array()? + .iter() + .filter_map(|option| { + // For each poll option + + // we can't just use as_u64() because "id": String("...") and serde would parse it as None + let id = option["id"].as_str()?.parse::().ok()?; + let text = option["text"].as_str()?.to_owned(); + let vote_count = option["vote_count"].as_u64(); + + // Construct PollOption items + Some(Self { id, text, vote_count }) + }) + .collect::>(), + ) + } +} + // Post flags with nsfw and stickied pub struct Flags { pub nsfw: bool, @@ -205,10 +261,17 @@ impl GalleryMedia { // For each image in gallery let media_id = item["media_id"].as_str().unwrap_or_default(); let image = &metadata[media_id]["s"]; + let image_type = &metadata[media_id]["m"]; + + let url = if image_type == "image/gif" { + image["gif"].as_str().unwrap_or_default() + } else { + image["u"].as_str().unwrap_or_default() + }; // Construct gallery items Self { - url: format_url(image["u"].as_str().unwrap_or_default()), + url: format_url(url), width: image["x"].as_i64().unwrap_or_default(), height: image["y"].as_i64().unwrap_or_default(), caption: item["caption"].as_str().unwrap_or_default().to_string(), @@ -227,6 +290,7 @@ pub struct Post { pub body: String, pub author: Author, pub permalink: String, + pub poll: Option, pub score: (String, String), pub upvote_ratio: i64, pub post_type: String, @@ -336,6 +400,7 @@ impl Post { stickied: data["stickied"].as_bool().unwrap_or_default() || data["pinned"].as_bool().unwrap_or_default(), }, permalink: val(post, "permalink"), + poll: Poll::parse(&data["poll_data"]), rel_time, created, num_duplicates: post["data"]["num_duplicates"].as_u64().unwrap_or(0), @@ -371,6 +436,7 @@ pub struct Comment { pub awards: Awards, pub collapsed: bool, pub is_filtered: bool, + pub more_count: i64, pub prefs: Preferences, } @@ -506,6 +572,7 @@ pub struct Preferences { pub hide_hls_notification: String, pub use_hls: String, pub autoplay_videos: String, + pub disable_visit_reddit_confirmation: String, pub comment_sort: String, pub post_sort: String, pub subscriptions: Vec, @@ -530,20 +597,21 @@ impl Preferences { } Self { available_themes: themes, - theme: setting(&req, "theme"), - front_page: setting(&req, "front_page"), - layout: setting(&req, "layout"), - wide: setting(&req, "wide"), - show_nsfw: setting(&req, "show_nsfw"), - blur_nsfw: setting(&req, "blur_nsfw"), - use_hls: setting(&req, "use_hls"), - hide_hls_notification: setting(&req, "hide_hls_notification"), - autoplay_videos: setting(&req, "autoplay_videos"), - comment_sort: setting(&req, "comment_sort"), - post_sort: setting(&req, "post_sort"), - subscriptions: setting(&req, "subscriptions").split('+').map(String::from).filter(|s| !s.is_empty()).collect(), - filters: setting(&req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect(), - hide_awards: setting(&req, "hide_awards"), + theme: setting(req, "theme"), + front_page: setting(req, "front_page"), + layout: setting(req, "layout"), + wide: setting(req, "wide"), + show_nsfw: setting(req, "show_nsfw"), + blur_nsfw: setting(req, "blur_nsfw"), + use_hls: setting(req, "use_hls"), + hide_hls_notification: setting(req, "hide_hls_notification"), + autoplay_videos: setting(req, "autoplay_videos"), + disable_visit_reddit_confirmation: setting(req, "disable_visit_reddit_confirmation"), + comment_sort: setting(req, "comment_sort"), + post_sort: setting(req, "post_sort"), + subscriptions: setting(req, "subscriptions").split('+').map(String::from).filter(|s| !s.is_empty()).collect(), + filters: setting(req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect(), + hide_awards: setting(req, "hide_awards"), } } } @@ -591,6 +659,8 @@ pub async fn parse_post(post: &serde_json::Value) -> Post { let permalink = val(post, "permalink"); + let poll = Poll::parse(&post["data"]["poll_data"]); + let body = if val(post, "removed_by_category") == "moderator" { format!( "

[removed] — view removed post

", @@ -622,6 +692,7 @@ pub async fn parse_post(post: &serde_json::Value) -> Post { distinguished: val(post, "distinguished"), }, permalink, + poll, score: format_num(score), upvote_ratio: ratio as i64, post_type, @@ -709,6 +780,21 @@ pub async fn catch_random(sub: &str, additional: &str) -> Result, } } +static REGEX_URL_WWW: Lazy = Lazy::new(|| Regex::new(r"https://www\.reddit\.com/(.*)").unwrap()); +static REGEX_URL_OLD: Lazy = Lazy::new(|| Regex::new(r"https://old\.reddit\.com/(.*)").unwrap()); +static REGEX_URL_NP: Lazy = Lazy::new(|| Regex::new(r"https://np\.reddit\.com/(.*)").unwrap()); +static REGEX_URL_PLAIN: Lazy = Lazy::new(|| Regex::new(r"https://reddit\.com/(.*)").unwrap()); +static REGEX_URL_VIDEOS: Lazy = Lazy::new(|| Regex::new(r"https://v\.redd\.it/(.*)/DASH_([0-9]{2,4}(\.mp4|$|\?source=fallback))").unwrap()); +static REGEX_URL_VIDEOS_HLS: Lazy = Lazy::new(|| Regex::new(r"https://v\.redd\.it/(.+)/(HLSPlaylist\.m3u8.*)$").unwrap()); +static REGEX_URL_IMAGES: Lazy = Lazy::new(|| Regex::new(r"https://i\.redd\.it/(.*)").unwrap()); +static REGEX_URL_THUMBS_A: Lazy = Lazy::new(|| Regex::new(r"https://a\.thumbs\.redditmedia\.com/(.*)").unwrap()); +static REGEX_URL_THUMBS_B: Lazy = Lazy::new(|| Regex::new(r"https://b\.thumbs\.redditmedia\.com/(.*)").unwrap()); +static REGEX_URL_EMOJI: Lazy = Lazy::new(|| Regex::new(r"https://emoji\.redditmedia\.com/(.*)/(.*)").unwrap()); +static REGEX_URL_PREVIEW: Lazy = Lazy::new(|| Regex::new(r"https://preview\.redd\.it/(.*)").unwrap()); +static REGEX_URL_EXTERNAL_PREVIEW: Lazy = Lazy::new(|| Regex::new(r"https://external\-preview\.redd\.it/(.*)").unwrap()); +static REGEX_URL_STYLES: Lazy = Lazy::new(|| Regex::new(r"https://styles\.redditmedia\.com/(.*)").unwrap()); +static REGEX_URL_STATIC_MEDIA: Lazy = Lazy::new(|| Regex::new(r"https://www\.redditstatic\.com/(.*)").unwrap()); + // Direct urls to proxy if proxy is enabled pub fn format_url(url: &str) -> String { if url.is_empty() || url == "self" || url == "default" || url == "nsfw" || url == "spoiler" { @@ -717,13 +803,11 @@ pub fn format_url(url: &str) -> String { Url::parse(url).map_or(url.to_string(), |parsed| { let domain = parsed.domain().unwrap_or_default(); - let capture = |regex: &str, format: &str, segments: i16| { - Regex::new(regex).map_or(String::new(), |re| { - re.captures(url).map_or(String::new(), |caps| match segments { - 1 => [format, &caps[1]].join(""), - 2 => [format, &caps[1], "/", &caps[2]].join(""), - _ => String::new(), - }) + let capture = |regex: &Regex, format: &str, segments: i16| { + regex.captures(url).map_or(String::new(), |caps| match segments { + 1 => [format, &caps[1]].join(""), + 2 => [format, &caps[1], "/", &caps[2]].join(""), + _ => String::new(), }) }; @@ -749,44 +833,46 @@ pub fn format_url(url: &str) -> String { } match domain { - "www.reddit.com" => capture(r"https://www\.reddit\.com/(.*)", "/", 1), - "old.reddit.com" => capture(r"https://old\.reddit\.com/(.*)", "/", 1), - "np.reddit.com" => capture(r"https://np\.reddit\.com/(.*)", "/", 1), - "reddit.com" => capture(r"https://reddit\.com/(.*)", "/", 1), - "v.redd.it" => chain!( - capture(r"https://v\.redd\.it/(.*)/DASH_([0-9]{2,4}(\.mp4|$|\?source=fallback))", "/vid/", 2), - capture(r"https://v\.redd\.it/(.+)/(HLSPlaylist\.m3u8.*)$", "/hls/", 2) - ), - "i.redd.it" => capture(r"https://i\.redd\.it/(.*)", "/img/", 1), - "a.thumbs.redditmedia.com" => capture(r"https://a\.thumbs\.redditmedia\.com/(.*)", "/thumb/a/", 1), - "b.thumbs.redditmedia.com" => capture(r"https://b\.thumbs\.redditmedia\.com/(.*)", "/thumb/b/", 1), - "emoji.redditmedia.com" => capture(r"https://emoji\.redditmedia\.com/(.*)/(.*)", "/emoji/", 2), - "preview.redd.it" => capture(r"https://preview\.redd\.it/(.*)", "/preview/pre/", 1), - "external-preview.redd.it" => capture(r"https://external\-preview\.redd\.it/(.*)", "/preview/external-pre/", 1), - "styles.redditmedia.com" => capture(r"https://styles\.redditmedia\.com/(.*)", "/style/", 1), - "www.redditstatic.com" => capture(r"https://www\.redditstatic\.com/(.*)", "/static/", 1), + "www.reddit.com" => capture(®EX_URL_WWW, "/", 1), + "old.reddit.com" => capture(®EX_URL_OLD, "/", 1), + "np.reddit.com" => capture(®EX_URL_NP, "/", 1), + "reddit.com" => capture(®EX_URL_PLAIN, "/", 1), + "v.redd.it" => chain!(capture(®EX_URL_VIDEOS, "/vid/", 2), capture(®EX_URL_VIDEOS_HLS, "/hls/", 2)), + "i.redd.it" => capture(®EX_URL_IMAGES, "/img/", 1), + "a.thumbs.redditmedia.com" => capture(®EX_URL_THUMBS_A, "/thumb/a/", 1), + "b.thumbs.redditmedia.com" => capture(®EX_URL_THUMBS_B, "/thumb/b/", 1), + "emoji.redditmedia.com" => capture(®EX_URL_EMOJI, "/emoji/", 2), + "preview.redd.it" => capture(®EX_URL_PREVIEW, "/preview/pre/", 1), + "external-preview.redd.it" => capture(®EX_URL_EXTERNAL_PREVIEW, "/preview/external-pre/", 1), + "styles.redditmedia.com" => capture(®EX_URL_STYLES, "/style/", 1), + "www.redditstatic.com" => capture(®EX_URL_STATIC_MEDIA, "/static/", 1), _ => url.to_string(), } }) } } +static REDDIT_REGEX: Lazy = Lazy::new(|| Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|)(reddit\.com|redd\.it)/"#).unwrap()); +static REDDIT_PREVIEW_REGEX: Lazy = Lazy::new(|| Regex::new(r"https://external-preview\.redd\.it(.*)[^?]").unwrap()); + // Rewrite Reddit links to Libreddit in body of text pub fn rewrite_urls(input_text: &str) -> String { - let text1 = Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|)(reddit\.com|redd\.it)/"#) - .map_or(String::new(), |re| re.replace_all(input_text, r#"href="/"#).to_string()) - // Remove (html-encoded) "\" from URLs. - .replace("%5C", "") - .replace('\\', ""); + let text1 = + // Rewrite Reddit links to Libreddit + REDDIT_REGEX.replace_all(input_text, r#"href="/"#) + .to_string() + // Remove (html-encoded) "\" from URLs. + .replace("%5C", "") + .replace('\\', ""); // Rewrite external media previews to Libreddit - Regex::new(r"https://external-preview\.redd\.it(.*)[^?]").map_or(String::new(), |re| { - if re.is_match(&text1) { - re.replace_all(&text1, format_url(re.find(&text1).map(|x| x.as_str()).unwrap_or_default())).to_string() - } else { - text1 - } - }) + if REDDIT_PREVIEW_REGEX.is_match(&text1) { + REDDIT_PREVIEW_REGEX + .replace_all(&text1, format_url(REDDIT_PREVIEW_REGEX.find(&text1).map(|x| x.as_str()).unwrap_or_default())) + .to_string() + } else { + text1 + } } // Format vote count to a string that will be displayed. @@ -807,20 +893,31 @@ pub fn format_num(num: i64) -> (String, String) { // Parse a relative and absolute time from a UNIX timestamp pub fn time(created: f64) -> (String, String) { let time = OffsetDateTime::from_unix_timestamp(created.round() as i64).unwrap_or(OffsetDateTime::UNIX_EPOCH); - let time_delta = OffsetDateTime::now_utc() - time; + let now = OffsetDateTime::now_utc(); + let min = time.min(now); + let max = time.max(now); + let time_delta = max - min; // If the time difference is more than a month, show full date - let rel_time = if time_delta > Duration::days(30) { + let mut rel_time = if time_delta > Duration::days(30) { time.format(format_description!("[month repr:short] [day] '[year repr:last_two]")).unwrap_or_default() // Otherwise, show relative date/time } else if time_delta.whole_days() > 0 { - format!("{}d ago", time_delta.whole_days()) + format!("{}d", time_delta.whole_days()) } else if time_delta.whole_hours() > 0 { - format!("{}h ago", time_delta.whole_hours()) + format!("{}h", time_delta.whole_hours()) } else { - format!("{}m ago", time_delta.whole_minutes()) + format!("{}m", time_delta.whole_minutes()) }; + if time_delta <= Duration::days(30) { + if now < time { + rel_time += " left"; + } else { + rel_time += " ago"; + } + } + ( rel_time, time @@ -885,11 +982,21 @@ pub fn sfw_only() -> bool { } } +// 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(); + let gate_nsfw = (setting(req, "show_nsfw") != "on") || sfw_instance; + + // Nsfw landing gate should not be bypassed on a sfw only instance, + let bypass_gate = !sfw_instance && req_url.contains("&bypass_nsfw_landing"); + + gate_nsfw && !bypass_gate +} + /// Renders the landing page for NSFW content when the user has not enabled /// "show NSFW posts" in settings. -pub async fn nsfw_landing(req: Request) -> Result, String> { +pub async fn nsfw_landing(req: Request, req_url: String) -> Result, String> { let res_type: ResourceType; - let url = req.uri().to_string(); // Determine from the request URL if the resource is a subreddit, a user // page, or a post. @@ -908,7 +1015,7 @@ pub async fn nsfw_landing(req: Request) -> Result, String> res, res_type, prefs: Preferences::new(&req), - url, + url: req_url, } .render() .unwrap_or_default(); diff --git a/static/style.css b/static/style.css index f0877c3..52c14b8 100644 --- a/static/style.css +++ b/static/style.css @@ -4,6 +4,30 @@ :root { --nsfw: #ff5c5d; --admin: #ea0027; + + /* Reddit redirect warning constants */ + --popup-red: #ea0027; + --popup-black: #111; + --popup-text: #fff; + --popup-background-1: #0f0f0f; + --popup-background-2: #220f0f; + --popup-reddit-url: var(--popup-red); + + --popup-background: repeating-linear-gradient( + -45deg, + var(--popup-background-1), + var(--popup-background-1) 50px, + var(--popup-background-2) 50px, + var(--popup-background-2) 100px + ); + + --popup-toreddit-background: var(--popup-black); + --popup-toreddit-text: var(--popup-red); + --popup-goback-background: var(--popup-red); + --popup-goback-text: #222; + --popup-border: 1px solid var(--popup-red); + + --footer-height: 30px; } @font-face { @@ -26,6 +50,7 @@ --highlighted: #333; --visited: #aaa; --shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + --popup: #b80a27; /* Hint color theme to browser for scrollbar */ color-scheme: dark; @@ -76,6 +101,9 @@ body { background: var(--background); font-size: 15px; padding-top: 60px; + padding-bottom: var(--footer-height); + min-height: calc(100vh - 60px); + position: relative; } nav { @@ -119,12 +147,6 @@ nav #links svg { display: none; } -nav #version { - opacity: 50%; - vertical-align: -2px; - margin-right: 10px; -} - nav #libreddit { vertical-align: -2px; } @@ -134,10 +156,109 @@ nav #libreddit { margin-left: 10px; } -#reddit_link { +.popup { + display: flex; + align-items: center; + justify-content: center; + overflow: clip; + opacity: 0; + position: fixed; + width: 100vw; + height: 100vh; + bottom: 0; + right: 0; + visibility: hidden; + transition: all 0.1s ease-in-out; + z-index: 2; +} + +/* fallback for firefox esr */ +.popup { + background-color: #000000fd; +} + +/* all other browsers */ +@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) { + .popup { + -webkit-backdrop-filter: blur(.25rem) brightness(15%); + backdrop-filter: blur(.25rem) brightness(15%); + } +} + +.popup-inner { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + max-width: 600px; + max-height: 500px; + width: fit-content; + height: fit-content; + padding: 1rem; + background: var(--popup-background); + border: var(--popup-border); + border-radius: 5px; + transition: all 0.1s ease-in-out; +} + +.popup-inner svg { + display: unset !important; + width: 35%; + stroke: none; + margin: 1rem; +} + +.popup-inner h1 { + color: var(--popup-text); + margin: 1.5rem 1.5rem 1rem; +} + +.popup-inner p { + color: var(--popup-text); +} + +.popup-inner a { + border-radius: 5px; + padding: 2%; + width: 80%; + margin: 0.5rem; + cursor: pointer; + transition: all 0.1s ease-in-out; +} + +#goback { + background: var(--popup-goback-background); + color: var(--popup-goback-text); +} + +#goback:not(.selected):hover { opacity: 0.8; } +#toreddit { + background: var(--popup-toreddit-background); + color: var(--popup-toreddit-text); + border: 1px solid var(--popup-red); +} + +#toreddit:not(.selected):hover { + background: var(--popup-toreddit-text); + color: var(--popup-toreddit-background); +} + +.popup:target { + visibility: visible; + opacity: 1; +} + +#reddit_url { + width: 80%; + color: var(--popup-reddit-url); + font-weight: 600; + line-break: anywhere; + margin-top: 1rem; +} + #code { margin-left: 10px; } @@ -148,6 +269,7 @@ main { max-width: 1000px; padding: 10px 20px; margin: 0 auto; + padding-bottom: 4em; } .wide main { @@ -170,23 +292,22 @@ main { body > footer { display: flex; justify-content: center; - margin: 20px; + align-items: center; + width: 100%; + background: var(--post); + position: absolute; + bottom: 0; } -.info-button { +.footer-button { align-items: center; border-radius: .25rem; box-sizing: border-box; color: var(--text); cursor: pointer; display: inline-flex; - font-size: 300%; - font-weight: bold; - padding: 0.5em; -} - -.info-button > a:hover { - text-decoration: none; + padding-left: 1em; + opacity: 0.8; } /* / Body footer. */ @@ -208,6 +329,7 @@ button { background: none; border: none; font-weight: bold; + cursor: pointer; } hr { @@ -276,7 +398,6 @@ aside { } #user_title, #sub_title { - margin: 0 20px; font-size: 20px; font-weight: bold; } @@ -420,6 +541,7 @@ select, #search, #sort_options, #listing_options, #inside, #searchbox > *, #sort select { background: var(--outside); transition: 0.2s background; + cursor: pointer; } select, #search { @@ -432,6 +554,10 @@ select, #search { border-radius: 5px 0px 0px 5px; } +.commentQuery { + background: var(--post); +} + #searchbox { grid-area: searchbox; display: flex; @@ -509,23 +635,28 @@ button.submit:hover > svg { stroke: var(--accent); } background: transparent; } +#commentQueryForms { + display: flex; + justify-content: space-between; +} + +#allCommentsLink { + color: var(--green); +} + #sort, #search_sort { display: flex; align-items: center; margin-bottom: 20px; } -#listing_options { - overflow-x: auto; -} - #sort_options, #listing_options, main > * > footer > a { border-radius: 5px; align-items: center; box-shadow: var(--shadow); background: var(--outside); display: flex; - overflow: hidden; + overflow-y: hidden; } #sort_options > a, #listing_options > a, main > * > footer > a { @@ -636,6 +767,7 @@ a.search_subreddit:hover { "post_score post_title post_thumbnail" 1fr "post_score post_media post_thumbnail" auto "post_score post_body post_thumbnail" auto + "post_score post_poll post_thumbnail" auto "post_score post_notification post_thumbnail" auto "post_score post_footer post_thumbnail" auto / minmax(40px, auto) minmax(0, 1fr) fit-content(min(20%, 152px)); @@ -836,6 +968,44 @@ a.search_subreddit:hover { overflow-wrap: anywhere; } +.post_poll { + grid-area: post_poll; + padding: 5px 15px 5px 12px; +} + +.poll_option { + position: relative; + margin-right: 15px; + margin-top: 14px; + z-index: 0; + display: flex; + align-items: center; +} + +.poll_chart { + padding: 14px 0; + background-color: var(--accent); + opacity: 0.2; + border-radius: 5px; + z-index: -1; + position: absolute; +} + +.poll_option span { + margin-left: 8px; + color: var(--text); +} + +.poll_option span:nth-of-type(1) { + min-width: 10%; + font-weight: bold; +} + +.most_voted { + opacity: 0.45; + width: 100%; +} + /* Used only for text post preview */ .post_preview { -webkit-mask-image: linear-gradient(180deg,#000 60%,transparent);; @@ -1392,7 +1562,10 @@ td, th { /* Mobile */ @media screen and (max-width: 800px) { - body { padding-top: 120px } + body { + padding-top: 120px; + padding-bottom: var(--footer-height); + } main { flex-direction: column-reverse; @@ -1431,17 +1604,21 @@ td, th { #user, #sidebar { margin: 20px 0; } #logo, #links { margin-bottom: 5px; } #searchbox { width: calc(100vw - 35px); } + } @media screen and (max-width: 480px) { - body { padding-top: 100px; } - #version { display: none; } + body { + padding-top: 100px; + padding-bottom: var(--footer-height); + } .post { grid-template: "post_header post_header post_thumbnail" auto "post_title post_title post_thumbnail" 1fr "post_media post_media post_thumbnail" auto "post_body post_body post_thumbnail" auto + "post_poll post_poll post_thumbnail" auto "post_notification post_notification post_thumbnail" auto "post_score post_footer post_thumbnail" auto / auto 1fr fit-content(min(20%, 152px)); @@ -1451,6 +1628,10 @@ td, th { margin: 5px 0px 20px 15px; padding: 0; } + + .post_poll { + padding: 5px 15px 10px 12px; + } .compact .post_score { padding: 0; } @@ -1494,4 +1675,17 @@ td, th { #post_links > li.desktop_item { display: none } #post_links > li.mobile_item { display: auto } .post_footer > p > span#upvoted { display: none } + + .popup { + width: auto; + } + + .popup-inner { + max-width: 80%; + } + + #commentQueryForms { + display: initial; + justify-content: initial; + } } diff --git a/templates/base.html b/templates/base.html index 63c76a4..af539f8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,3 +1,5 @@ +{% import "utils.html" as utils %} + @@ -30,17 +32,20 @@ @@ -65,10 +63,16 @@ {% endblock %} {% endblock %} + + {% block footer %} {% endblock %} diff --git a/templates/comment.html b/templates/comment.html index d9b9c68..e75b888 100644 --- a/templates/comment.html +++ b/templates/comment.html @@ -1,7 +1,7 @@ {% import "utils.html" as utils %} {% if kind == "more" && parent_kind == "t1" %} -→ More replies +→ More replies ({{ more_count }}) {% else if kind == "t1" %}
@@ -35,7 +35,7 @@
{{ body|safe }}
{% endif %}
{% for c in replies -%}{{ c.render().unwrap()|safe }}{%- endfor %} -
+
{% endif %} diff --git a/templates/nsfwlanding.html b/templates/nsfwlanding.html index f6287a3..4dc8c86 100644 --- a/templates/nsfwlanding.html +++ b/templates/nsfwlanding.html @@ -19,10 +19,12 @@ {% if crate::utils::sfw_only() %} This instance of Libreddit is SFW-only.

{% else %} - Enable "Show NSFW posts" in settings 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 %}. + Enable "Show NSFW posts" in settings 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 %}.
+ {% if res_type == crate::utils::ResourceType::Post %} You can also temporarily bypass this gate and view the post by clicking on this link.{% endif %} {% endif %}

{% endblock %} {% block footer %} {% endblock %} + diff --git a/templates/post.html b/templates/post.html index c79a18d..8d3c232 100644 --- a/templates/post.html +++ b/templates/post.html @@ -43,18 +43,32 @@ {% call utils::post(post) %} +

{{post.comments.0}} {% if post.comments.0 == "1" %}comment{% else %}comments{% endif %} sorted by

- {% call utils::options(sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %} - -
+ + + + +
+ + +
+
+ +
+ {% if comment_query != "" %} + Comments containing "{{ comment_query }}" | All comments + {% endif %} +
{% for c in comments -%} diff --git a/templates/search.html b/templates/search.html index f4ecc58..53528e7 100644 --- a/templates/search.html +++ b/templates/search.html @@ -29,7 +29,7 @@ → - + {% if !is_filtered %} {% if subreddits.len() > 0 || params.typed == "sr_user" %} @@ -99,13 +99,13 @@ {% if params.typed != "sr_user" %}