├── .github ├── dependabot.yml └── workflows │ ├── build_release.yml │ ├── generate_release.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── apkmirror ├── mod.rs └── tests.rs ├── errors.rs ├── lib.rs ├── main.rs └── utils.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/build_release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | native-build: 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | include: 20 | - os: ubuntu-latest 21 | target: x86_64-unknown-linux-gnu 22 | 23 | - os: windows-latest 24 | target: x86_64-pc-windows-msvc 25 | extension: .exe 26 | 27 | - os: windows-latest 28 | target: i686-pc-windows-msvc 29 | extension: .exe 30 | 31 | - os: windows-latest 32 | target: aarch64-pc-windows-msvc 33 | extension: .exe 34 | 35 | - os: macos-latest 36 | target: x86_64-apple-darwin 37 | 38 | - os: macos-latest 39 | target: aarch64-apple-darwin 40 | 41 | 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@v4 45 | 46 | - name: Setup Rust 47 | run: | 48 | rustup update --no-self-update stable 49 | rustup target add ${{ matrix.target }} 50 | rustup default stable 51 | 52 | - name: Setup Cache 53 | uses: Swatinem/rust-cache@v2 54 | env: 55 | cache-name: cache-cargo-${{ matrix.target }} 56 | with: 57 | prefix-key: "downapk-v1" 58 | shared-key: ${{ runner.os }}-cargo- 59 | key: ${{ runner.os }}-cargo-${{ matrix.target }}- 60 | cache-directories: | 61 | C:\Users\runneradmin\.cargo 62 | /Users/runner/.cargo 63 | 64 | - name: Build for ${{ matrix.target }} 65 | run: cargo build --release --target ${{ matrix.target }} 66 | 67 | - name: Upload binaries to release 68 | if: ${{ github.event_name == 'release' }} 69 | run: | 70 | mv target/${{ matrix.target }}/release/downapk${{ matrix.extension }} target/${{ matrix.target }}/release/downapk-${{ matrix.target }}${{ matrix.extension }} 71 | gh release upload ${{ github.ref_name }} target/${{ matrix.target }}/release/downapk-${{ matrix.target }}${{ matrix.extension }} 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | 75 | cross-build: 76 | runs-on: ubuntu-latest 77 | strategy: 78 | matrix: 79 | include: 80 | - target: aarch64-unknown-linux-gnu 81 | - target: aarch64-linux-android 82 | - target: armv7-unknown-linux-gnueabihf 83 | - target: armv7-linux-androideabi 84 | - target: x86_64-linux-android 85 | - target: i686-unknown-linux-gnu 86 | 87 | 88 | steps: 89 | - name: Checkout code 90 | uses: actions/checkout@v4 91 | 92 | - name: Setup Rust 93 | run: | 94 | rustup update --no-self-update stable 95 | rustup target add ${{ matrix.target }} 96 | 97 | - name: Setup Cache 98 | uses: Swatinem/rust-cache@v2 99 | env: 100 | cache-name: cache-cargo-${{ matrix.target }} 101 | with: 102 | prefix-key: "downapk-v1" 103 | shared-key: ${{ runner.os }}-cargo- 104 | key: ${{ runner.os }}-cargo-${{ matrix.target }}- 105 | 106 | - name: Install dependencies 107 | run: | 108 | cargo install cross --git https://github.com/cross-rs/cross 109 | 110 | - name: Build ${{ matrix.target }} 111 | run: cross build --release --target ${{ matrix.target }} 112 | 113 | - name: Upload binaries to release 114 | if: ${{ github.event_name == 'release' }} 115 | shell: bash 116 | run: | 117 | mv target/${{ matrix.target }}/release/downapk target/${{ matrix.target }}/release/downapk-${{ matrix.target }} 118 | gh release upload ${{ github.ref_name }} target/${{ matrix.target }}/release/downapk-${{ matrix.target }} 119 | env: 120 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 121 | 122 | publish: 123 | needs: [native-build, cross-build] 124 | if: ${{ github.event_name == 'release' }} 125 | runs-on: ubuntu-latest 126 | name: "Publish to Cargo" 127 | environment: 128 | name: Cargo 129 | url: https://crates.io/crates/downapk 130 | 131 | steps: 132 | - uses: actions/checkout@master 133 | 134 | # Use caching to speed up your build 135 | - name: Setup Cache 136 | uses: Swatinem/rust-cache@v2 137 | env: 138 | cache-name: cache-cargo 139 | with: 140 | prefix-key: "downapk-v1" 141 | shared-key: ${{ runner.os }}-cargo- 142 | key: ${{ runner.os }}-cargo- 143 | 144 | - name: Setup cargo 145 | run: | 146 | rustup update --no-self-update stable 147 | 148 | - name: Cargo publish 149 | run: cargo publish 150 | env: 151 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 152 | 153 | -------------------------------------------------------------------------------- /.github/workflows/generate_release.yml: -------------------------------------------------------------------------------- 1 | name: Create release from version 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'Cargo.toml' 9 | 10 | jobs: 11 | check-version-and-release: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | token: ${{ secrets.PAT }} 19 | 20 | - name: Check if version exists as tag 21 | id: check-version 22 | run: | 23 | VERSION=v$(grep -oP '^version = "\K[^"]+' Cargo.toml) 24 | if git tag --list | grep -q "^$VERSION$"; then 25 | echo "Tag $VERSION already exists" 26 | else 27 | gh release create $VERSION --generate-notes 28 | echo "Created release $VERSION" 29 | fi 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.PAT }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | paths-ignore: 7 | - "README.md" 8 | - "LICENSE" 9 | - "CONTRIBUTING.md" 10 | - "CODE_OF_CONDUCT.md" 11 | - "SECURITY.md" 12 | - "PULL_REQUEST_TEMPLATE.md" 13 | - "ISSUE_TEMPLATE.md" 14 | - "docs/**" 15 | 16 | pull_request: 17 | branches: [ "main" ] 18 | paths-ignore: 19 | - "README.md" 20 | - "LICENSE" 21 | - "CONTRIBUTING.md" 22 | - "CODE_OF_CONDUCT.md" 23 | - "SECURITY.md" 24 | - "PULL_REQUEST_TEMPLATE.md" 25 | - "ISSUE_TEMPLATE.md" 26 | - "docs/**" 27 | 28 | env: 29 | CARGO_TERM_COLOR: always 30 | 31 | jobs: 32 | build: 33 | 34 | runs-on: ubuntu-latest 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Setup Cache 39 | uses: Swatinem/rust-cache@v2 40 | env: 41 | cache-name: cache-cargo-x86_64-unknown-linux-gnu 42 | with: 43 | prefix-key: "downapk-v1" 44 | shared-key: ${{ runner.os }}-cargo- 45 | key: ${{ runner.os }}-cargo-x86_64-unknown-linux-gnu- 46 | - name: Build 47 | run: cargo build --verbose 48 | - name: Run tests 49 | run: cargo test --verbose 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/downloads 3 | .vscode -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" 25 | dependencies = [ 26 | "cfg-if", 27 | "getrandom", 28 | "once_cell", 29 | "version_check", 30 | "zerocopy", 31 | ] 32 | 33 | [[package]] 34 | name = "anstream" 35 | version = "0.6.11" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" 38 | dependencies = [ 39 | "anstyle", 40 | "anstyle-parse", 41 | "anstyle-query", 42 | "anstyle-wincon", 43 | "colorchoice", 44 | "utf8parse", 45 | ] 46 | 47 | [[package]] 48 | name = "anstyle" 49 | version = "1.0.8" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 52 | 53 | [[package]] 54 | name = "anstyle-parse" 55 | version = "0.2.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 58 | dependencies = [ 59 | "utf8parse", 60 | ] 61 | 62 | [[package]] 63 | name = "anstyle-query" 64 | version = "1.0.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 67 | dependencies = [ 68 | "windows-sys 0.52.0", 69 | ] 70 | 71 | [[package]] 72 | name = "anstyle-wincon" 73 | version = "3.0.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 76 | dependencies = [ 77 | "anstyle", 78 | "windows-sys 0.52.0", 79 | ] 80 | 81 | [[package]] 82 | name = "autocfg" 83 | version = "1.1.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 86 | 87 | [[package]] 88 | name = "backtrace" 89 | version = "0.3.69" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 92 | dependencies = [ 93 | "addr2line", 94 | "cc", 95 | "cfg-if", 96 | "libc", 97 | "miniz_oxide", 98 | "object", 99 | "rustc-demangle", 100 | ] 101 | 102 | [[package]] 103 | name = "base64" 104 | version = "0.22.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" 107 | 108 | [[package]] 109 | name = "bitflags" 110 | version = "1.3.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 113 | 114 | [[package]] 115 | name = "bitflags" 116 | version = "2.4.2" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 119 | 120 | [[package]] 121 | name = "bumpalo" 122 | version = "3.14.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 125 | 126 | [[package]] 127 | name = "byteorder" 128 | version = "1.5.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 131 | 132 | [[package]] 133 | name = "bytes" 134 | version = "1.5.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 137 | 138 | [[package]] 139 | name = "cc" 140 | version = "1.0.83" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 143 | dependencies = [ 144 | "libc", 145 | ] 146 | 147 | [[package]] 148 | name = "cfg-if" 149 | version = "1.0.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 152 | 153 | [[package]] 154 | name = "clap" 155 | version = "4.5.20" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 158 | dependencies = [ 159 | "clap_builder", 160 | "clap_derive", 161 | ] 162 | 163 | [[package]] 164 | name = "clap_builder" 165 | version = "4.5.20" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 168 | dependencies = [ 169 | "anstream", 170 | "anstyle", 171 | "clap_lex", 172 | "strsim", 173 | ] 174 | 175 | [[package]] 176 | name = "clap_derive" 177 | version = "4.5.18" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 180 | dependencies = [ 181 | "heck", 182 | "proc-macro2", 183 | "quote", 184 | "syn 2.0.48", 185 | ] 186 | 187 | [[package]] 188 | name = "clap_lex" 189 | version = "0.7.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 192 | 193 | [[package]] 194 | name = "colorchoice" 195 | version = "1.0.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 198 | 199 | [[package]] 200 | name = "console" 201 | version = "0.15.8" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 204 | dependencies = [ 205 | "encode_unicode", 206 | "lazy_static", 207 | "libc", 208 | "unicode-width", 209 | "windows-sys 0.52.0", 210 | ] 211 | 212 | [[package]] 213 | name = "cookie" 214 | version = "0.18.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 217 | dependencies = [ 218 | "percent-encoding", 219 | "time", 220 | "version_check", 221 | ] 222 | 223 | [[package]] 224 | name = "cookie_store" 225 | version = "0.21.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" 228 | dependencies = [ 229 | "cookie", 230 | "idna 0.5.0", 231 | "log", 232 | "publicsuffix", 233 | "serde", 234 | "serde_derive", 235 | "serde_json", 236 | "time", 237 | "url", 238 | ] 239 | 240 | [[package]] 241 | name = "core-foundation" 242 | version = "0.9.4" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 245 | dependencies = [ 246 | "core-foundation-sys", 247 | "libc", 248 | ] 249 | 250 | [[package]] 251 | name = "core-foundation-sys" 252 | version = "0.8.6" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 255 | 256 | [[package]] 257 | name = "cssparser" 258 | version = "0.31.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" 261 | dependencies = [ 262 | "cssparser-macros", 263 | "dtoa-short", 264 | "itoa", 265 | "phf 0.11.2", 266 | "smallvec", 267 | ] 268 | 269 | [[package]] 270 | name = "cssparser-macros" 271 | version = "0.6.1" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" 274 | dependencies = [ 275 | "quote", 276 | "syn 2.0.48", 277 | ] 278 | 279 | [[package]] 280 | name = "deranged" 281 | version = "0.3.11" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 284 | dependencies = [ 285 | "powerfmt", 286 | ] 287 | 288 | [[package]] 289 | name = "derive_more" 290 | version = "0.99.17" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 293 | dependencies = [ 294 | "proc-macro2", 295 | "quote", 296 | "syn 1.0.109", 297 | ] 298 | 299 | [[package]] 300 | name = "downapk" 301 | version = "0.6.4" 302 | dependencies = [ 303 | "clap", 304 | "console", 305 | "indicatif", 306 | "reqwest", 307 | "scraper", 308 | "tokio", 309 | ] 310 | 311 | [[package]] 312 | name = "dtoa" 313 | version = "1.0.9" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" 316 | 317 | [[package]] 318 | name = "dtoa-short" 319 | version = "0.3.4" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" 322 | dependencies = [ 323 | "dtoa", 324 | ] 325 | 326 | [[package]] 327 | name = "ego-tree" 328 | version = "0.6.2" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" 331 | 332 | [[package]] 333 | name = "encode_unicode" 334 | version = "0.3.6" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 337 | 338 | [[package]] 339 | name = "fnv" 340 | version = "1.0.7" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 343 | 344 | [[package]] 345 | name = "form_urlencoded" 346 | version = "1.2.1" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 349 | dependencies = [ 350 | "percent-encoding", 351 | ] 352 | 353 | [[package]] 354 | name = "futf" 355 | version = "0.1.5" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 358 | dependencies = [ 359 | "mac", 360 | "new_debug_unreachable", 361 | ] 362 | 363 | [[package]] 364 | name = "futures-channel" 365 | version = "0.3.30" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 368 | dependencies = [ 369 | "futures-core", 370 | ] 371 | 372 | [[package]] 373 | name = "futures-core" 374 | version = "0.3.30" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 377 | 378 | [[package]] 379 | name = "futures-task" 380 | version = "0.3.30" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 383 | 384 | [[package]] 385 | name = "futures-util" 386 | version = "0.3.30" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 389 | dependencies = [ 390 | "futures-core", 391 | "futures-task", 392 | "pin-project-lite", 393 | "pin-utils", 394 | ] 395 | 396 | [[package]] 397 | name = "fxhash" 398 | version = "0.2.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 401 | dependencies = [ 402 | "byteorder", 403 | ] 404 | 405 | [[package]] 406 | name = "getopts" 407 | version = "0.2.21" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 410 | dependencies = [ 411 | "unicode-width", 412 | ] 413 | 414 | [[package]] 415 | name = "getrandom" 416 | version = "0.2.12" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 419 | dependencies = [ 420 | "cfg-if", 421 | "libc", 422 | "wasi", 423 | ] 424 | 425 | [[package]] 426 | name = "gimli" 427 | version = "0.28.1" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 430 | 431 | [[package]] 432 | name = "heck" 433 | version = "0.5.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 436 | 437 | [[package]] 438 | name = "hermit-abi" 439 | version = "0.3.9" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 442 | 443 | [[package]] 444 | name = "html5ever" 445 | version = "0.27.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" 448 | dependencies = [ 449 | "log", 450 | "mac", 451 | "markup5ever", 452 | "proc-macro2", 453 | "quote", 454 | "syn 2.0.48", 455 | ] 456 | 457 | [[package]] 458 | name = "http" 459 | version = "1.1.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 462 | dependencies = [ 463 | "bytes", 464 | "fnv", 465 | "itoa", 466 | ] 467 | 468 | [[package]] 469 | name = "http-body" 470 | version = "1.0.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 473 | dependencies = [ 474 | "bytes", 475 | "http", 476 | ] 477 | 478 | [[package]] 479 | name = "http-body-util" 480 | version = "0.1.1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" 483 | dependencies = [ 484 | "bytes", 485 | "futures-core", 486 | "http", 487 | "http-body", 488 | "pin-project-lite", 489 | ] 490 | 491 | [[package]] 492 | name = "httparse" 493 | version = "1.8.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 496 | 497 | [[package]] 498 | name = "hyper" 499 | version = "1.2.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" 502 | dependencies = [ 503 | "bytes", 504 | "futures-channel", 505 | "futures-util", 506 | "http", 507 | "http-body", 508 | "httparse", 509 | "itoa", 510 | "pin-project-lite", 511 | "smallvec", 512 | "tokio", 513 | "want", 514 | ] 515 | 516 | [[package]] 517 | name = "hyper-rustls" 518 | version = "0.27.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" 521 | dependencies = [ 522 | "futures-util", 523 | "http", 524 | "hyper", 525 | "hyper-util", 526 | "rustls", 527 | "rustls-native-certs", 528 | "rustls-pki-types", 529 | "tokio", 530 | "tokio-rustls", 531 | "tower-service", 532 | ] 533 | 534 | [[package]] 535 | name = "hyper-util" 536 | version = "0.1.3" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 539 | dependencies = [ 540 | "bytes", 541 | "futures-channel", 542 | "futures-util", 543 | "http", 544 | "http-body", 545 | "hyper", 546 | "pin-project-lite", 547 | "socket2", 548 | "tokio", 549 | "tower", 550 | "tower-service", 551 | "tracing", 552 | ] 553 | 554 | [[package]] 555 | name = "idna" 556 | version = "0.3.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 559 | dependencies = [ 560 | "unicode-bidi", 561 | "unicode-normalization", 562 | ] 563 | 564 | [[package]] 565 | name = "idna" 566 | version = "0.5.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 569 | dependencies = [ 570 | "unicode-bidi", 571 | "unicode-normalization", 572 | ] 573 | 574 | [[package]] 575 | name = "indicatif" 576 | version = "0.17.8" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 579 | dependencies = [ 580 | "console", 581 | "instant", 582 | "number_prefix", 583 | "portable-atomic", 584 | "unicode-width", 585 | ] 586 | 587 | [[package]] 588 | name = "instant" 589 | version = "0.1.12" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 592 | dependencies = [ 593 | "cfg-if", 594 | ] 595 | 596 | [[package]] 597 | name = "ipnet" 598 | version = "2.9.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 601 | 602 | [[package]] 603 | name = "itoa" 604 | version = "1.0.10" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 607 | 608 | [[package]] 609 | name = "js-sys" 610 | version = "0.3.67" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" 613 | dependencies = [ 614 | "wasm-bindgen", 615 | ] 616 | 617 | [[package]] 618 | name = "lazy_static" 619 | version = "1.4.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 622 | 623 | [[package]] 624 | name = "libc" 625 | version = "0.2.152" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" 628 | 629 | [[package]] 630 | name = "lock_api" 631 | version = "0.4.11" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 634 | dependencies = [ 635 | "autocfg", 636 | "scopeguard", 637 | ] 638 | 639 | [[package]] 640 | name = "log" 641 | version = "0.4.20" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 644 | 645 | [[package]] 646 | name = "mac" 647 | version = "0.1.1" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 650 | 651 | [[package]] 652 | name = "markup5ever" 653 | version = "0.12.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" 656 | dependencies = [ 657 | "log", 658 | "phf 0.11.2", 659 | "phf_codegen 0.11.2", 660 | "string_cache", 661 | "string_cache_codegen", 662 | "tendril", 663 | ] 664 | 665 | [[package]] 666 | name = "memchr" 667 | version = "2.7.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 670 | 671 | [[package]] 672 | name = "mime" 673 | version = "0.3.17" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 676 | 677 | [[package]] 678 | name = "miniz_oxide" 679 | version = "0.7.1" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 682 | dependencies = [ 683 | "adler", 684 | ] 685 | 686 | [[package]] 687 | name = "mio" 688 | version = "1.0.2" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 691 | dependencies = [ 692 | "hermit-abi", 693 | "libc", 694 | "wasi", 695 | "windows-sys 0.52.0", 696 | ] 697 | 698 | [[package]] 699 | name = "new_debug_unreachable" 700 | version = "1.0.4" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 703 | 704 | [[package]] 705 | name = "number_prefix" 706 | version = "0.4.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 709 | 710 | [[package]] 711 | name = "object" 712 | version = "0.32.2" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 715 | dependencies = [ 716 | "memchr", 717 | ] 718 | 719 | [[package]] 720 | name = "once_cell" 721 | version = "1.19.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 724 | 725 | [[package]] 726 | name = "openssl-probe" 727 | version = "0.1.5" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 730 | 731 | [[package]] 732 | name = "parking_lot" 733 | version = "0.12.1" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 736 | dependencies = [ 737 | "lock_api", 738 | "parking_lot_core", 739 | ] 740 | 741 | [[package]] 742 | name = "parking_lot_core" 743 | version = "0.9.9" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 746 | dependencies = [ 747 | "cfg-if", 748 | "libc", 749 | "redox_syscall", 750 | "smallvec", 751 | "windows-targets 0.48.5", 752 | ] 753 | 754 | [[package]] 755 | name = "percent-encoding" 756 | version = "2.3.1" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 759 | 760 | [[package]] 761 | name = "phf" 762 | version = "0.10.1" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 765 | dependencies = [ 766 | "phf_shared 0.10.0", 767 | ] 768 | 769 | [[package]] 770 | name = "phf" 771 | version = "0.11.2" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 774 | dependencies = [ 775 | "phf_macros", 776 | "phf_shared 0.11.2", 777 | ] 778 | 779 | [[package]] 780 | name = "phf_codegen" 781 | version = "0.10.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 784 | dependencies = [ 785 | "phf_generator 0.10.0", 786 | "phf_shared 0.10.0", 787 | ] 788 | 789 | [[package]] 790 | name = "phf_codegen" 791 | version = "0.11.2" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 794 | dependencies = [ 795 | "phf_generator 0.11.2", 796 | "phf_shared 0.11.2", 797 | ] 798 | 799 | [[package]] 800 | name = "phf_generator" 801 | version = "0.10.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 804 | dependencies = [ 805 | "phf_shared 0.10.0", 806 | "rand", 807 | ] 808 | 809 | [[package]] 810 | name = "phf_generator" 811 | version = "0.11.2" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 814 | dependencies = [ 815 | "phf_shared 0.11.2", 816 | "rand", 817 | ] 818 | 819 | [[package]] 820 | name = "phf_macros" 821 | version = "0.11.2" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" 824 | dependencies = [ 825 | "phf_generator 0.11.2", 826 | "phf_shared 0.11.2", 827 | "proc-macro2", 828 | "quote", 829 | "syn 2.0.48", 830 | ] 831 | 832 | [[package]] 833 | name = "phf_shared" 834 | version = "0.10.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 837 | dependencies = [ 838 | "siphasher", 839 | ] 840 | 841 | [[package]] 842 | name = "phf_shared" 843 | version = "0.11.2" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 846 | dependencies = [ 847 | "siphasher", 848 | ] 849 | 850 | [[package]] 851 | name = "pin-project" 852 | version = "1.1.5" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 855 | dependencies = [ 856 | "pin-project-internal", 857 | ] 858 | 859 | [[package]] 860 | name = "pin-project-internal" 861 | version = "1.1.5" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 864 | dependencies = [ 865 | "proc-macro2", 866 | "quote", 867 | "syn 2.0.48", 868 | ] 869 | 870 | [[package]] 871 | name = "pin-project-lite" 872 | version = "0.2.13" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 875 | 876 | [[package]] 877 | name = "pin-utils" 878 | version = "0.1.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 881 | 882 | [[package]] 883 | name = "portable-atomic" 884 | version = "1.6.0" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 887 | 888 | [[package]] 889 | name = "powerfmt" 890 | version = "0.2.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 893 | 894 | [[package]] 895 | name = "ppv-lite86" 896 | version = "0.2.17" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 899 | 900 | [[package]] 901 | name = "precomputed-hash" 902 | version = "0.1.1" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 905 | 906 | [[package]] 907 | name = "proc-macro2" 908 | version = "1.0.78" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 911 | dependencies = [ 912 | "unicode-ident", 913 | ] 914 | 915 | [[package]] 916 | name = "psl-types" 917 | version = "2.0.11" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" 920 | 921 | [[package]] 922 | name = "publicsuffix" 923 | version = "2.2.3" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" 926 | dependencies = [ 927 | "idna 0.3.0", 928 | "psl-types", 929 | ] 930 | 931 | [[package]] 932 | name = "quinn" 933 | version = "0.11.3" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" 936 | dependencies = [ 937 | "bytes", 938 | "pin-project-lite", 939 | "quinn-proto", 940 | "quinn-udp", 941 | "rustc-hash", 942 | "rustls", 943 | "socket2", 944 | "thiserror", 945 | "tokio", 946 | "tracing", 947 | ] 948 | 949 | [[package]] 950 | name = "quinn-proto" 951 | version = "0.11.6" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" 954 | dependencies = [ 955 | "bytes", 956 | "rand", 957 | "ring", 958 | "rustc-hash", 959 | "rustls", 960 | "slab", 961 | "thiserror", 962 | "tinyvec", 963 | "tracing", 964 | ] 965 | 966 | [[package]] 967 | name = "quinn-udp" 968 | version = "0.5.4" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" 971 | dependencies = [ 972 | "libc", 973 | "once_cell", 974 | "socket2", 975 | "tracing", 976 | "windows-sys 0.52.0", 977 | ] 978 | 979 | [[package]] 980 | name = "quote" 981 | version = "1.0.35" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 984 | dependencies = [ 985 | "proc-macro2", 986 | ] 987 | 988 | [[package]] 989 | name = "rand" 990 | version = "0.8.5" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 993 | dependencies = [ 994 | "libc", 995 | "rand_chacha", 996 | "rand_core", 997 | ] 998 | 999 | [[package]] 1000 | name = "rand_chacha" 1001 | version = "0.3.1" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1004 | dependencies = [ 1005 | "ppv-lite86", 1006 | "rand_core", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "rand_core" 1011 | version = "0.6.4" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1014 | dependencies = [ 1015 | "getrandom", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "redox_syscall" 1020 | version = "0.4.1" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1023 | dependencies = [ 1024 | "bitflags 1.3.2", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "reqwest" 1029 | version = "0.12.7" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" 1032 | dependencies = [ 1033 | "base64", 1034 | "bytes", 1035 | "cookie", 1036 | "cookie_store", 1037 | "futures-core", 1038 | "futures-util", 1039 | "http", 1040 | "http-body", 1041 | "http-body-util", 1042 | "hyper", 1043 | "hyper-rustls", 1044 | "hyper-util", 1045 | "ipnet", 1046 | "js-sys", 1047 | "log", 1048 | "mime", 1049 | "once_cell", 1050 | "percent-encoding", 1051 | "pin-project-lite", 1052 | "quinn", 1053 | "rustls", 1054 | "rustls-native-certs", 1055 | "rustls-pemfile", 1056 | "rustls-pki-types", 1057 | "serde", 1058 | "serde_json", 1059 | "serde_urlencoded", 1060 | "sync_wrapper", 1061 | "tokio", 1062 | "tokio-rustls", 1063 | "tower-service", 1064 | "url", 1065 | "wasm-bindgen", 1066 | "wasm-bindgen-futures", 1067 | "web-sys", 1068 | "windows-registry", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "ring" 1073 | version = "0.17.7" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" 1076 | dependencies = [ 1077 | "cc", 1078 | "getrandom", 1079 | "libc", 1080 | "spin", 1081 | "untrusted", 1082 | "windows-sys 0.48.0", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "rustc-demangle" 1087 | version = "0.1.23" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1090 | 1091 | [[package]] 1092 | name = "rustc-hash" 1093 | version = "2.0.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" 1096 | 1097 | [[package]] 1098 | name = "rustls" 1099 | version = "0.23.12" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" 1102 | dependencies = [ 1103 | "once_cell", 1104 | "ring", 1105 | "rustls-pki-types", 1106 | "rustls-webpki", 1107 | "subtle", 1108 | "zeroize", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "rustls-native-certs" 1113 | version = "0.7.0" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" 1116 | dependencies = [ 1117 | "openssl-probe", 1118 | "rustls-pemfile", 1119 | "rustls-pki-types", 1120 | "schannel", 1121 | "security-framework", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "rustls-pemfile" 1126 | version = "2.1.2" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 1129 | dependencies = [ 1130 | "base64", 1131 | "rustls-pki-types", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "rustls-pki-types" 1136 | version = "1.8.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" 1139 | 1140 | [[package]] 1141 | name = "rustls-webpki" 1142 | version = "0.102.6" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" 1145 | dependencies = [ 1146 | "ring", 1147 | "rustls-pki-types", 1148 | "untrusted", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "ryu" 1153 | version = "1.0.16" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 1156 | 1157 | [[package]] 1158 | name = "schannel" 1159 | version = "0.1.23" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 1162 | dependencies = [ 1163 | "windows-sys 0.52.0", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "scopeguard" 1168 | version = "1.2.0" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1171 | 1172 | [[package]] 1173 | name = "scraper" 1174 | version = "0.20.0" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0" 1177 | dependencies = [ 1178 | "ahash", 1179 | "cssparser", 1180 | "ego-tree", 1181 | "getopts", 1182 | "html5ever", 1183 | "once_cell", 1184 | "selectors", 1185 | "tendril", 1186 | ] 1187 | 1188 | [[package]] 1189 | name = "security-framework" 1190 | version = "2.9.2" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 1193 | dependencies = [ 1194 | "bitflags 1.3.2", 1195 | "core-foundation", 1196 | "core-foundation-sys", 1197 | "libc", 1198 | "security-framework-sys", 1199 | ] 1200 | 1201 | [[package]] 1202 | name = "security-framework-sys" 1203 | version = "2.9.1" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 1206 | dependencies = [ 1207 | "core-foundation-sys", 1208 | "libc", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "selectors" 1213 | version = "0.25.0" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" 1216 | dependencies = [ 1217 | "bitflags 2.4.2", 1218 | "cssparser", 1219 | "derive_more", 1220 | "fxhash", 1221 | "log", 1222 | "new_debug_unreachable", 1223 | "phf 0.10.1", 1224 | "phf_codegen 0.10.0", 1225 | "precomputed-hash", 1226 | "servo_arc", 1227 | "smallvec", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "serde" 1232 | version = "1.0.195" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" 1235 | dependencies = [ 1236 | "serde_derive", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "serde_derive" 1241 | version = "1.0.195" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" 1244 | dependencies = [ 1245 | "proc-macro2", 1246 | "quote", 1247 | "syn 2.0.48", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "serde_json" 1252 | version = "1.0.111" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" 1255 | dependencies = [ 1256 | "itoa", 1257 | "ryu", 1258 | "serde", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "serde_urlencoded" 1263 | version = "0.7.1" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1266 | dependencies = [ 1267 | "form_urlencoded", 1268 | "itoa", 1269 | "ryu", 1270 | "serde", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "servo_arc" 1275 | version = "0.3.0" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" 1278 | dependencies = [ 1279 | "stable_deref_trait", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "signal-hook-registry" 1284 | version = "1.4.1" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1287 | dependencies = [ 1288 | "libc", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "siphasher" 1293 | version = "0.3.11" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1296 | 1297 | [[package]] 1298 | name = "slab" 1299 | version = "0.4.9" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1302 | dependencies = [ 1303 | "autocfg", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "smallvec" 1308 | version = "1.13.1" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 1311 | 1312 | [[package]] 1313 | name = "socket2" 1314 | version = "0.5.5" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1317 | dependencies = [ 1318 | "libc", 1319 | "windows-sys 0.48.0", 1320 | ] 1321 | 1322 | [[package]] 1323 | name = "spin" 1324 | version = "0.9.8" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1327 | 1328 | [[package]] 1329 | name = "stable_deref_trait" 1330 | version = "1.2.0" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1333 | 1334 | [[package]] 1335 | name = "string_cache" 1336 | version = "0.8.7" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 1339 | dependencies = [ 1340 | "new_debug_unreachable", 1341 | "once_cell", 1342 | "parking_lot", 1343 | "phf_shared 0.10.0", 1344 | "precomputed-hash", 1345 | "serde", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "string_cache_codegen" 1350 | version = "0.5.2" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" 1353 | dependencies = [ 1354 | "phf_generator 0.10.0", 1355 | "phf_shared 0.10.0", 1356 | "proc-macro2", 1357 | "quote", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "strsim" 1362 | version = "0.11.0" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 1365 | 1366 | [[package]] 1367 | name = "subtle" 1368 | version = "2.5.0" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1371 | 1372 | [[package]] 1373 | name = "syn" 1374 | version = "1.0.109" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1377 | dependencies = [ 1378 | "proc-macro2", 1379 | "quote", 1380 | "unicode-ident", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "syn" 1385 | version = "2.0.48" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 1388 | dependencies = [ 1389 | "proc-macro2", 1390 | "quote", 1391 | "unicode-ident", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "sync_wrapper" 1396 | version = "1.0.1" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1399 | dependencies = [ 1400 | "futures-core", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "tendril" 1405 | version = "0.4.3" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 1408 | dependencies = [ 1409 | "futf", 1410 | "mac", 1411 | "utf-8", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "thiserror" 1416 | version = "1.0.63" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 1419 | dependencies = [ 1420 | "thiserror-impl", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "thiserror-impl" 1425 | version = "1.0.63" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 1428 | dependencies = [ 1429 | "proc-macro2", 1430 | "quote", 1431 | "syn 2.0.48", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "time" 1436 | version = "0.3.31" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" 1439 | dependencies = [ 1440 | "deranged", 1441 | "itoa", 1442 | "powerfmt", 1443 | "serde", 1444 | "time-core", 1445 | "time-macros", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "time-core" 1450 | version = "0.1.2" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1453 | 1454 | [[package]] 1455 | name = "time-macros" 1456 | version = "0.2.16" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" 1459 | dependencies = [ 1460 | "time-core", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "tinyvec" 1465 | version = "1.6.0" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1468 | dependencies = [ 1469 | "tinyvec_macros", 1470 | ] 1471 | 1472 | [[package]] 1473 | name = "tinyvec_macros" 1474 | version = "0.1.1" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1477 | 1478 | [[package]] 1479 | name = "tokio" 1480 | version = "1.41.0" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" 1483 | dependencies = [ 1484 | "backtrace", 1485 | "bytes", 1486 | "libc", 1487 | "mio", 1488 | "parking_lot", 1489 | "pin-project-lite", 1490 | "signal-hook-registry", 1491 | "socket2", 1492 | "tokio-macros", 1493 | "windows-sys 0.52.0", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "tokio-macros" 1498 | version = "2.4.0" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1501 | dependencies = [ 1502 | "proc-macro2", 1503 | "quote", 1504 | "syn 2.0.48", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "tokio-rustls" 1509 | version = "0.26.0" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1512 | dependencies = [ 1513 | "rustls", 1514 | "rustls-pki-types", 1515 | "tokio", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "tower" 1520 | version = "0.4.13" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1523 | dependencies = [ 1524 | "futures-core", 1525 | "futures-util", 1526 | "pin-project", 1527 | "pin-project-lite", 1528 | "tokio", 1529 | "tower-layer", 1530 | "tower-service", 1531 | "tracing", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "tower-layer" 1536 | version = "0.3.2" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1539 | 1540 | [[package]] 1541 | name = "tower-service" 1542 | version = "0.3.2" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1545 | 1546 | [[package]] 1547 | name = "tracing" 1548 | version = "0.1.40" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1551 | dependencies = [ 1552 | "log", 1553 | "pin-project-lite", 1554 | "tracing-core", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "tracing-core" 1559 | version = "0.1.32" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1562 | dependencies = [ 1563 | "once_cell", 1564 | ] 1565 | 1566 | [[package]] 1567 | name = "try-lock" 1568 | version = "0.2.5" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1571 | 1572 | [[package]] 1573 | name = "unicode-bidi" 1574 | version = "0.3.15" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1577 | 1578 | [[package]] 1579 | name = "unicode-ident" 1580 | version = "1.0.12" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1583 | 1584 | [[package]] 1585 | name = "unicode-normalization" 1586 | version = "0.1.22" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1589 | dependencies = [ 1590 | "tinyvec", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "unicode-width" 1595 | version = "0.1.11" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 1598 | 1599 | [[package]] 1600 | name = "untrusted" 1601 | version = "0.9.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1604 | 1605 | [[package]] 1606 | name = "url" 1607 | version = "2.5.0" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1610 | dependencies = [ 1611 | "form_urlencoded", 1612 | "idna 0.5.0", 1613 | "percent-encoding", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "utf-8" 1618 | version = "0.7.6" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1621 | 1622 | [[package]] 1623 | name = "utf8parse" 1624 | version = "0.2.1" 1625 | source = "registry+https://github.com/rust-lang/crates.io-index" 1626 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1627 | 1628 | [[package]] 1629 | name = "version_check" 1630 | version = "0.9.4" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1633 | 1634 | [[package]] 1635 | name = "want" 1636 | version = "0.3.1" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1639 | dependencies = [ 1640 | "try-lock", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "wasi" 1645 | version = "0.11.0+wasi-snapshot-preview1" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1648 | 1649 | [[package]] 1650 | name = "wasm-bindgen" 1651 | version = "0.2.90" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" 1654 | dependencies = [ 1655 | "cfg-if", 1656 | "wasm-bindgen-macro", 1657 | ] 1658 | 1659 | [[package]] 1660 | name = "wasm-bindgen-backend" 1661 | version = "0.2.90" 1662 | source = "registry+https://github.com/rust-lang/crates.io-index" 1663 | checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" 1664 | dependencies = [ 1665 | "bumpalo", 1666 | "log", 1667 | "once_cell", 1668 | "proc-macro2", 1669 | "quote", 1670 | "syn 2.0.48", 1671 | "wasm-bindgen-shared", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "wasm-bindgen-futures" 1676 | version = "0.4.40" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" 1679 | dependencies = [ 1680 | "cfg-if", 1681 | "js-sys", 1682 | "wasm-bindgen", 1683 | "web-sys", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "wasm-bindgen-macro" 1688 | version = "0.2.90" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" 1691 | dependencies = [ 1692 | "quote", 1693 | "wasm-bindgen-macro-support", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "wasm-bindgen-macro-support" 1698 | version = "0.2.90" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" 1701 | dependencies = [ 1702 | "proc-macro2", 1703 | "quote", 1704 | "syn 2.0.48", 1705 | "wasm-bindgen-backend", 1706 | "wasm-bindgen-shared", 1707 | ] 1708 | 1709 | [[package]] 1710 | name = "wasm-bindgen-shared" 1711 | version = "0.2.90" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" 1714 | 1715 | [[package]] 1716 | name = "web-sys" 1717 | version = "0.3.67" 1718 | source = "registry+https://github.com/rust-lang/crates.io-index" 1719 | checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" 1720 | dependencies = [ 1721 | "js-sys", 1722 | "wasm-bindgen", 1723 | ] 1724 | 1725 | [[package]] 1726 | name = "windows-registry" 1727 | version = "0.2.0" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1730 | dependencies = [ 1731 | "windows-result", 1732 | "windows-strings", 1733 | "windows-targets 0.52.6", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "windows-result" 1738 | version = "0.2.0" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1741 | dependencies = [ 1742 | "windows-targets 0.52.6", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "windows-strings" 1747 | version = "0.1.0" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1750 | dependencies = [ 1751 | "windows-result", 1752 | "windows-targets 0.52.6", 1753 | ] 1754 | 1755 | [[package]] 1756 | name = "windows-sys" 1757 | version = "0.48.0" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1760 | dependencies = [ 1761 | "windows-targets 0.48.5", 1762 | ] 1763 | 1764 | [[package]] 1765 | name = "windows-sys" 1766 | version = "0.52.0" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1769 | dependencies = [ 1770 | "windows-targets 0.52.6", 1771 | ] 1772 | 1773 | [[package]] 1774 | name = "windows-targets" 1775 | version = "0.48.5" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1778 | dependencies = [ 1779 | "windows_aarch64_gnullvm 0.48.5", 1780 | "windows_aarch64_msvc 0.48.5", 1781 | "windows_i686_gnu 0.48.5", 1782 | "windows_i686_msvc 0.48.5", 1783 | "windows_x86_64_gnu 0.48.5", 1784 | "windows_x86_64_gnullvm 0.48.5", 1785 | "windows_x86_64_msvc 0.48.5", 1786 | ] 1787 | 1788 | [[package]] 1789 | name = "windows-targets" 1790 | version = "0.52.6" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1793 | dependencies = [ 1794 | "windows_aarch64_gnullvm 0.52.6", 1795 | "windows_aarch64_msvc 0.52.6", 1796 | "windows_i686_gnu 0.52.6", 1797 | "windows_i686_gnullvm", 1798 | "windows_i686_msvc 0.52.6", 1799 | "windows_x86_64_gnu 0.52.6", 1800 | "windows_x86_64_gnullvm 0.52.6", 1801 | "windows_x86_64_msvc 0.52.6", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "windows_aarch64_gnullvm" 1806 | version = "0.48.5" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1809 | 1810 | [[package]] 1811 | name = "windows_aarch64_gnullvm" 1812 | version = "0.52.6" 1813 | source = "registry+https://github.com/rust-lang/crates.io-index" 1814 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1815 | 1816 | [[package]] 1817 | name = "windows_aarch64_msvc" 1818 | version = "0.48.5" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1821 | 1822 | [[package]] 1823 | name = "windows_aarch64_msvc" 1824 | version = "0.52.6" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1827 | 1828 | [[package]] 1829 | name = "windows_i686_gnu" 1830 | version = "0.48.5" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1833 | 1834 | [[package]] 1835 | name = "windows_i686_gnu" 1836 | version = "0.52.6" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1839 | 1840 | [[package]] 1841 | name = "windows_i686_gnullvm" 1842 | version = "0.52.6" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1845 | 1846 | [[package]] 1847 | name = "windows_i686_msvc" 1848 | version = "0.48.5" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1851 | 1852 | [[package]] 1853 | name = "windows_i686_msvc" 1854 | version = "0.52.6" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1857 | 1858 | [[package]] 1859 | name = "windows_x86_64_gnu" 1860 | version = "0.48.5" 1861 | source = "registry+https://github.com/rust-lang/crates.io-index" 1862 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1863 | 1864 | [[package]] 1865 | name = "windows_x86_64_gnu" 1866 | version = "0.52.6" 1867 | source = "registry+https://github.com/rust-lang/crates.io-index" 1868 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1869 | 1870 | [[package]] 1871 | name = "windows_x86_64_gnullvm" 1872 | version = "0.48.5" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1875 | 1876 | [[package]] 1877 | name = "windows_x86_64_gnullvm" 1878 | version = "0.52.6" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1881 | 1882 | [[package]] 1883 | name = "windows_x86_64_msvc" 1884 | version = "0.48.5" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1887 | 1888 | [[package]] 1889 | name = "windows_x86_64_msvc" 1890 | version = "0.52.6" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1893 | 1894 | [[package]] 1895 | name = "zerocopy" 1896 | version = "0.7.32" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" 1899 | dependencies = [ 1900 | "zerocopy-derive", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "zerocopy-derive" 1905 | version = "0.7.32" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" 1908 | dependencies = [ 1909 | "proc-macro2", 1910 | "quote", 1911 | "syn 2.0.48", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "zeroize" 1916 | version = "1.7.0" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 1919 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "downapk" 3 | version = "0.6.4" 4 | edition = "2021" 5 | categories = ["command-line-utilities"] 6 | description = "Program to download APKs of given Android package" 7 | homepage = "https://github.com/rabilrbl/downapk" 8 | keywords = ["apk", "android", "download", "cli"] 9 | license = "MIT" 10 | readme = "README.md" 11 | repository = "https://github.com/rabilrbl/downapk" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | clap = { version = "^4.5", features = ["derive"] } 17 | console = "^0.15" 18 | indicatif = "^0.17" 19 | reqwest = { version = "^0.12", default-features = false, features = ["cookies", "rustls-tls-native-roots"] } 20 | scraper = "^0.20" 21 | tokio = { version = "^1.41", features = ["full"] } 22 | 23 | [profile.release] 24 | strip = true 25 | 26 | [package.metadata.binstall] 27 | bin-dir = "{ bin }-{ target }{ binary-ext }" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mohammed Rabil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DownAPK 2 | 3 | Program to download APKs of given Android package 4 | 5 | # Installation 6 | 7 | ## Pre-built binaries 8 | 9 | Download the latest binary release from [here](https://github.com/rabilrbl/downapk/releases/latest). 10 | 11 | ```shell 12 | # Unix-like (Linux, macOS, Android) 13 | chmod +x downapk-linux 14 | ./downapk-linux --help 15 | 16 | # Windows 17 | downapk-windows.exe --help 18 | ``` 19 | 20 | ## Cargo 21 | 22 | ``` 23 | cargo install downapk 24 | downapk --help 25 | ``` 26 | 27 | ## Build from source 28 | 29 | ``` 30 | cargo build --release 31 | ./target/release/downapk --help 32 | ``` 33 | 34 | ## Usage 35 | 36 | ```shell 37 | downapk [OPTIONS] --package-id 38 | ``` 39 | 40 | ### Options 41 | 42 | | Option | Description | Default Value | 43 | | --- | --- | --- | 44 | | `-p, --package-id ` | Android package ID | - | 45 | | `-o, --output-dir ` | Optional: Output file name | downloads | 46 | | `-a, --arch ` | Optional: Architecture. Possible values: arm64-v8a, armeabi-v7a, x86, x86_64, universal | all | 47 | | `-v, --version-code ` | Optional: Version code. Possible values: latest, x.x.x (e.g. 1.0.0 | latest | 48 | | `-t, --type ` | Optional: Type of APK. Possible values: bundle, apk | all | 49 | | `-d, --dpi ` | Optional: Screen DPI. Possible values: nodpi, 120-320, ..., | all | 50 | | `-h, --help` | Print help | - | 51 | | `-V, --version` | Print version | - | 52 | 53 | ### Examples 54 | 55 | 1. *Download all APKs of package com.google.android.youtube of universal architecture and latest version with nodpi* 56 | 57 | ```shell 58 | downapk -p com.google.android.youtube -t apk -a universal -d nodpi 59 | ``` 60 | 61 | 2. *Download all APKs of package com.google.android.youtube of universal architecture and version 14.21.54 with nodpi* 62 | 63 | ```shell 64 | downapk -p com.google.android.youtube -t apk -a universal -d nodpi -v 14.21.54 65 | ``` 66 | 67 | For library usage, see [Rust docs](https://docs.rs/downapk) with examples. 68 | 69 | ## License 70 | 71 | MIT License. See [LICENSE](LICENSE) file for details. 72 | 73 | ## Author 74 | 75 | [Mohammed Rabil](https://github.com/rabilrbl) 76 | 77 | ## Contributors 78 | 79 | [![Contributors](https://contributors-img.web.app/image?repo=rabilrbl/downapk)](https://github.com/rabilrbl/downapk/graphs/contributors) 80 | -------------------------------------------------------------------------------- /src/apkmirror/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::DownApkError; 2 | use crate::utils::selector; 3 | use clap::ValueEnum; 4 | use console::Emoji; 5 | use core::time::Duration; 6 | use indicatif::{ProgressBar, ProgressStyle}; 7 | use reqwest::header::{HeaderMap, HeaderValue}; 8 | use reqwest::Client; 9 | use scraper::Html; 10 | use std::cmp::min; 11 | use tokio::fs::File; 12 | use tokio::io::AsyncWriteExt; 13 | 14 | static LOOKING_GLASS: Emoji<'_, '_> = Emoji("🔍 ", ""); 15 | static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)"); 16 | static DOWNLOAD_EMOJI: Emoji<'_, '_> = Emoji("📥 ", ":-)"); 17 | static TRUCK: Emoji<'_, '_> = Emoji("🚚 ", ""); 18 | 19 | /// Represents a structure for downloading APK files from ApkMirror. 20 | pub struct DownloadApkMirror { 21 | /// The version of the APK file. 22 | pub version: String, 23 | /// The download link of the APK file. 24 | pub download_link: String, 25 | /// The type of the APK file. It can be either APK or BUNDLE. 26 | pub apk_type: ApkType, 27 | /// The architecture of the APK file. It can be either arm64-v8a, armeabi-v7a, x86, x86_64, universal. 28 | pub arch: String, 29 | /// The minimum version of Android required to run the APK file. 30 | pub min_version: String, 31 | /// The screen dpi of the APK file. It can be either nodpi, 120-640dpi, ... 32 | pub screen_dpi: String, 33 | } 34 | 35 | /// Represents the extracted links from a source. 36 | pub struct ExtractedLink { 37 | /// The version of the extracted link. 38 | pub version: String, 39 | /// The number of downloads for the extracted link. 40 | pub downloads: String, 41 | /// The file size of the extracted link. 42 | pub file_size: String, 43 | /// The date and time when the link was uploaded. 44 | pub uploaded: String, 45 | /// The actual link extracted. 46 | pub link: String, 47 | /// The title of the extracted link. 48 | pub title: String, 49 | } 50 | 51 | /// Implements the `Default` trait for the `ExtractedLinks` struct. 52 | /// 53 | /// This allows creating a default instance of `ExtractedLinks` using the `default()` method. 54 | /// The default instance has all fields initialized with empty strings. 55 | impl Default for ExtractedLink { 56 | fn default() -> Self { 57 | ExtractedLink { 58 | version: String::new(), 59 | downloads: String::new(), 60 | file_size: String::new(), 61 | uploaded: String::new(), 62 | link: String::new(), 63 | title: String::new(), 64 | } 65 | } 66 | } 67 | 68 | /// Represents the type of APK file. This can either be a standard 69 | /// APK file or an Android App Bundle. Implements Display and AsRef 70 | /// traits to easily get the string representation. 71 | #[derive(PartialEq, Debug, Clone, Copy, ValueEnum)] 72 | pub enum ApkType { 73 | Bundle, 74 | Apk, 75 | } 76 | 77 | /// Implements the Display trait for ApkType. This allows 78 | /// printing the ApkType variants as strings. 79 | impl std::fmt::Display for ApkType { 80 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 81 | match self { 82 | ApkType::Bundle => write!(f, "BUNDLE"), 83 | ApkType::Apk => write!(f, "APK"), 84 | } 85 | } 86 | } 87 | 88 | /// Implements the `From<&str>` trait for `ApkType`. 89 | /// 90 | /// This allows creating an `ApkType` variant from a string. 91 | /// Matches on the string and returns the corresponding variant. 92 | /// Panics if an unknown string is provided. 93 | impl From for ApkType { 94 | fn from(s: String) -> Self { 95 | match s { 96 | s if s == "BUNDLE" => ApkType::Bundle, 97 | s if s == "APK" => ApkType::Apk, 98 | _ => panic!("Unknown apk type: {}", s), 99 | } 100 | } 101 | } 102 | 103 | /// Represents an ApkMirror instance. This struct contains: 104 | /// - `client`: The reqwest client used to make HTTP requests. 105 | /// - `host`: The host URL of the ApkMirror website. 106 | /// - `spinner`: The progress spinner style for loading animations. 107 | /// 108 | /// This is exported as part of the public API. 109 | pub struct ApkMirror { 110 | /// The reqwest client. 111 | client: Client, 112 | /// The host of the ApkMirror instance. 113 | host: String, 114 | /// The spinner style for loading animations. 115 | spinner: ProgressStyle, 116 | } 117 | 118 | impl ApkMirror { 119 | /// Initializes a new ApkMirror instance with a reqwest client, host URL, 120 | /// and progress spinner style. 121 | /// 122 | /// Sends a request to apkmirror.com to get valid cookies before creating 123 | /// the client. Configures the client with headers and enables cookie storage. 124 | /// Creates a progress spinner style template. Validates that the homepage 125 | /// loads correctly. 126 | /// 127 | /// Returns the constructed ApkMirror instance to use for making requests. 128 | /// 129 | /// # Example 130 | /// 131 | /// ```rust 132 | /// use downapk::apkmirror::ApkMirror; 133 | /// 134 | /// #[tokio::main] 135 | /// async fn main() { 136 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 137 | /// } 138 | /// ``` 139 | pub async fn new() -> Result> { 140 | let mut headers = HeaderMap::new(); 141 | headers.insert(reqwest::header::ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")); 142 | headers.insert( 143 | reqwest::header::ACCEPT_ENCODING, 144 | HeaderValue::from_static("text"), 145 | ); 146 | headers.insert( 147 | reqwest::header::ACCEPT_LANGUAGE, 148 | HeaderValue::from_static("en-IN,en-US;q=0.9,en;q=0.8"), 149 | ); 150 | headers.insert( 151 | reqwest::header::HOST, 152 | HeaderValue::from_static("www.apkmirror.com"), 153 | ); 154 | headers.insert("Proxy-Connection", HeaderValue::from_static("keep-alive")); 155 | headers.insert( 156 | reqwest::header::UPGRADE_INSECURE_REQUESTS, 157 | HeaderValue::from_static("1"), 158 | ); 159 | headers.insert(reqwest::header::USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Linux; Android 13; Pixel 5 Build/TQ3A.230901.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/118.0.0.0 Safari/537.36")); 160 | headers.insert( 161 | "X-Requested-With", 162 | HeaderValue::from_static("cf.vojtechh.apkmirror"), 163 | ); 164 | 165 | let client = Client::builder() 166 | .cookie_store(true) 167 | .default_headers(headers) 168 | .build()?; 169 | 170 | let spinner_style = 171 | ProgressStyle::with_template("{prefix:.bold.dim} {spinner} {wide_msg}")? 172 | .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "); 173 | 174 | let pb = ProgressBar::new(40); 175 | pb.set_style(spinner_style.clone()); 176 | pb.set_prefix(format!(" {} Intialise", SPARKLE)); 177 | 178 | pb.set_message("Heading to apkmirror.com for valid cookies"); 179 | pb.enable_steady_tick(Duration::from_millis(100)); 180 | let url = "https://www.apkmirror.com".to_string(); 181 | let res = client 182 | .get(&(url.clone() + "/")) 183 | .send() 184 | .await? 185 | .text() 186 | .await?; 187 | 188 | pb.set_message("Got some cookies, parsing html"); 189 | let document = Html::parse_document(&res); 190 | 191 | pb.set_message("Parsing html to check if page is valid"); 192 | let selector = selector("button[class='searchButton']")?; 193 | 194 | assert_eq!(1, document.select(&selector).count()); 195 | 196 | pb.finish_with_message("Finished getting valid cookies"); 197 | 198 | Ok(ApkMirror { 199 | client, 200 | host: url, 201 | spinner: spinner_style, 202 | }) 203 | } 204 | 205 | /// Constructs an absolute URL by prepending the host if the provided 206 | /// URL does not already start with http. 207 | /// 208 | /// This handles cases where a relative URL path is provided and constructs 209 | /// a full, absolute URL to use for requests. 210 | fn absolute_url(&self, url: &str) -> String { 211 | if url.starts_with("http") { 212 | url.to_string() 213 | } else { 214 | self.host.to_string() + url 215 | } 216 | } 217 | 218 | /// Extracts the root links from the specified URL. 219 | /// 220 | /// # Arguments 221 | /// 222 | /// * `url` - The URL to extract the root links from. 223 | /// * `version` - Optional version to filter the results by. 224 | /// 225 | /// # Returns 226 | /// 227 | /// A `Result` containing a vector of `ExtractedLinks` or an `Error` if the extraction fails. 228 | /// 229 | /// # Example 230 | /// 231 | /// ```rust 232 | /// use downapk::apkmirror::ApkMirror; 233 | /// 234 | /// #[tokio::main] 235 | /// async fn main() { 236 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 237 | /// let links = apk_mirror.extract_root_links("https://www.apkmirror.com/apk/instagram/instagram-lite/", Some("390.0.0.9.116")).await; 238 | /// } 239 | /// ``` 240 | pub async fn extract_root_links( 241 | &self, 242 | url: &str, 243 | version: Option<&str>, 244 | ) -> Result, DownApkError> { 245 | let pb = ProgressBar::new(40); 246 | pb.set_style(self.spinner.clone()); 247 | pb.set_prefix(format!(" {} Search", LOOKING_GLASS)); 248 | match version { 249 | Some(version) => { 250 | pb.set_message(format!("Searching in {} for version {}", url, version)) 251 | } 252 | None => pb.set_message(format!("Searching in {}", url)), 253 | } 254 | pb.enable_steady_tick(Duration::from_millis(100)); 255 | 256 | pb.set_message(format!("Making request to {}", url)); 257 | let res = self.client.get(url).send().await?.text().await?; 258 | 259 | pb.set_message("Parsing html"); 260 | let document = Html::parse_document(&res); 261 | 262 | let list_widget_selector = selector("div.listWidget")?; 263 | let div_without_class_selector = selector("div:not([class])")?; 264 | let link_selector = selector("a[class='fontBlack']")?; 265 | let info_selector = selector("div.infoSlide.t-height")?; 266 | let paragraph_selector = selector("p")?; 267 | let info_name_selector = selector("span.infoSlide-name")?; 268 | let info_value_selector = selector("span.infoSlide-value")?; 269 | 270 | let mut results: Vec = vec![]; 271 | 272 | pb.set_message("Processing each APK result"); 273 | for element in document.select(&list_widget_selector).take(1) { 274 | for element in element.select(&div_without_class_selector) { 275 | let mut extracted_link = ExtractedLink::default(); 276 | let link = element.select(&link_selector).next(); 277 | let info = element.select(&info_selector).next(); 278 | 279 | let text = match link { 280 | Some(link) => link.text().collect::(), 281 | None => continue, 282 | }; 283 | 284 | let link = match link { 285 | Some(link) => self.absolute_url( 286 | link.value() 287 | .attr("href") 288 | .expect("Could not get attribute href"), 289 | ), 290 | None => continue, 291 | }; 292 | 293 | match info { 294 | Some(info) => { 295 | for element in info.select(¶graph_selector) { 296 | let name = element.select(&info_name_selector).next(); 297 | let value = element.select(&info_value_selector).next(); 298 | 299 | let name = match name { 300 | Some(name) => { 301 | let name = name 302 | .text() 303 | .collect::() 304 | .trim() 305 | .strip_suffix(':') 306 | .expect("Could not strip suffix") 307 | .to_owned(); 308 | name 309 | } 310 | None => continue, 311 | }; 312 | 313 | let value = match value { 314 | Some(value) => value.text().collect::().trim().to_string(), 315 | None => continue, 316 | }; 317 | 318 | match name.as_str() { 319 | "Version" => extracted_link.version = value, 320 | "Downloads" => extracted_link.downloads = value, 321 | "File Size" => extracted_link.file_size = value, 322 | "Uploaded" => extracted_link.uploaded = value, 323 | _ => continue, 324 | } 325 | } 326 | } 327 | None => continue, 328 | }; 329 | 330 | if let Some(version) = version { 331 | if extracted_link.version != version { 332 | continue; 333 | } 334 | } 335 | 336 | extracted_link.title = text; 337 | extracted_link.link = link; 338 | 339 | results.push(extracted_link); 340 | } 341 | } 342 | pb.finish_with_message("Finished search"); 343 | 344 | Ok(results) 345 | } 346 | 347 | /// Searches for APKs on ApkMirror based on the specified search query. 348 | /// 349 | /// # Arguments 350 | /// 351 | /// * `search_query` - The search query to use. 352 | /// 353 | /// # Returns 354 | /// 355 | /// A `Result` containing a vector of `ExtractedLinks` or an `Error` if the search fails. 356 | /// 357 | /// # Example 358 | /// 359 | /// ```rust 360 | /// use downapk::apkmirror::ApkMirror; 361 | /// 362 | /// #[tokio::main] 363 | /// async fn main() { 364 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 365 | /// let results = apk_mirror.search("com.instagram.lite").await; 366 | /// } 367 | /// ``` 368 | pub async fn search(&self, search_query: &str) -> Result, DownApkError> { 369 | let url = self.absolute_url(&format!( 370 | "/?post_type=app_release&searchtype=apk&s={}", 371 | search_query 372 | )); 373 | 374 | self.extract_root_links(&url, None).await 375 | } 376 | 377 | /// Searches for APKs on ApkMirror based on the specified search query and version. 378 | /// 379 | /// # Arguments 380 | /// 381 | /// * `search_query` - The search query to use. 382 | /// * `version` - The version to filter the results by. 383 | /// 384 | /// # Returns 385 | /// 386 | /// A `Result` containing a vector of `ExtractedLinks` or an `Error` if the search fails. 387 | /// 388 | /// # Example 389 | /// 390 | /// ```rust 391 | /// use downapk::apkmirror::ApkMirror; 392 | /// 393 | /// #[tokio::main] 394 | /// async fn main() { 395 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 396 | /// let results = apk_mirror.search_by_version("com.instagram.lite", "390.0.0.9.116").await; 397 | /// } 398 | /// ``` 399 | pub async fn search_by_version( 400 | &self, 401 | search_query: &str, 402 | version: &str, 403 | ) -> Result, DownApkError> { 404 | let url = self.absolute_url(&format!( 405 | "/?post_type=app_release&searchtype=apk&s={}", 406 | search_query 407 | )); 408 | 409 | self.extract_root_links(&url, Some(version)).await 410 | } 411 | 412 | /// Downloads APKs from ApkMirror based on the specified URL and optional parameters. 413 | /// 414 | /// # Arguments 415 | /// 416 | /// * `url` - The URL of the APK to download. 417 | /// * `apk_type` - Optional type of the APK (e.g., arm64-v8a). 418 | /// * `arch_` - Optional architecture of the APK (e.g., arm64). 419 | /// * `dpi` - Optional DPI (dots per inch) of the APK. 420 | /// 421 | /// # Returns 422 | /// 423 | /// A `Result` containing a vector of `DownloadApkMirror` or an `Error` if the download fails. 424 | /// 425 | /// # Example 426 | /// 427 | /// ```rust 428 | /// use downapk::apkmirror::{ApkMirror, ApkType}; 429 | /// 430 | /// #[tokio::main] 431 | /// async fn main() { 432 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 433 | /// let downloads = apk_mirror.download_by_specifics("https://www.apkmirror.com/apk/instagram/instagram-lite/instagram-lite-390-0-0-9-116-release/", Some(ApkType::Apk), Some("arm64-v8a"), Some("nodpi")).await; 434 | /// } 435 | /// ``` 436 | pub async fn download_by_specifics( 437 | &self, 438 | url: &str, 439 | apk_type: Option, 440 | arch_: Option<&str>, 441 | dpi: Option<&str>, 442 | ) -> Result, DownApkError> { 443 | let pb = ProgressBar::new(40); 444 | pb.set_style(self.spinner.clone()); 445 | pb.set_prefix(format!(" {} Get file download links", TRUCK)); 446 | pb.set_message(format!("Trying to get all download links from {}", url)); 447 | pb.enable_steady_tick(Duration::from_millis(100)); 448 | let res = self.client.get(url).send().await?.text().await?; 449 | 450 | let document = Html::parse_document(&res); 451 | 452 | let table_row_selector = selector("div[class='table-row headerFont']")?; 453 | let table_head_selector = 454 | selector("div[class='table-cell rowheight addseparator expand pad dowrap']")?; 455 | let span_apkm_badge_selector = selector("span.apkm-badge")?; 456 | let a_accent_color_download_button_selector = selector("a[class='accent_color']")?; 457 | let metadata_selector = &selector("div")?; 458 | let mut results: Vec = vec![]; 459 | 460 | pb.set_message("Processing each link"); 461 | for table_row_element in document.select(&table_row_selector) { 462 | pb.set_message("Processing a link"); 463 | for table_head_element in table_row_element.select(&table_head_selector) { 464 | let badge_text = table_head_element 465 | .select(&span_apkm_badge_selector) 466 | .next() 467 | .map(|element| element.text().collect::()) 468 | .unwrap_or_default(); 469 | 470 | let badge = match badge_text.as_str() { 471 | "APK" => ApkType::Apk, 472 | "BUNDLE" => ApkType::Bundle, 473 | _ => continue, 474 | }; 475 | 476 | let anchor_elem = match table_head_element 477 | .select(&a_accent_color_download_button_selector) 478 | .next() 479 | { 480 | Some(anchor_elem) => anchor_elem, 481 | None => continue, 482 | }; 483 | 484 | let version = anchor_elem.text().collect::().trim().to_string(); 485 | 486 | let download_link = self.absolute_url( 487 | anchor_elem 488 | .value() 489 | .attr("href") 490 | .expect("Could not get attribute href"), 491 | ); 492 | 493 | if !badge_text.is_empty() && !version.is_empty() && !download_link.is_empty() { 494 | if let Some(apk_type) = apk_type { 495 | if apk_type != badge { 496 | pb.set_message(format!("Skipping type {}", badge_text)); 497 | continue; 498 | } 499 | } 500 | let arch: String = table_row_element 501 | .select(metadata_selector) 502 | .nth(1) 503 | .expect("Could not get arch string") 504 | .text() 505 | .collect::() 506 | .trim() 507 | .to_string(); 508 | if let Some(arch_) = arch_ { 509 | if arch_ != arch { 510 | pb.set_message(format!("Skipping arch {}", arch)); 511 | continue; 512 | } 513 | } 514 | let screen_dpi = table_row_element 515 | .select(metadata_selector) 516 | .nth(3) 517 | .expect("Could not get screen dpi") 518 | .text() 519 | .collect::() 520 | .trim() 521 | .to_string(); 522 | if let Some(dpi) = dpi { 523 | if dpi != screen_dpi { 524 | pb.set_message(format!("Skipping dpi {}", screen_dpi)); 525 | continue; 526 | } 527 | } 528 | let min_version = table_row_element 529 | .select(metadata_selector) 530 | .nth(2) 531 | .expect("Could not get min version") 532 | .text() 533 | .collect::() 534 | .trim() 535 | .to_string(); 536 | pb.set_message(format!("Found version: {} with type: {} and arch: {} and min_version: {} and screen_dpi: {}", version, badge_text, arch, min_version, screen_dpi)); 537 | results.push(DownloadApkMirror { 538 | version, 539 | download_link: match self.download_link(&download_link, &pb).await { 540 | Ok(download_link) => download_link, 541 | Err(_) => { 542 | println!("Could not get download link for {}", download_link); 543 | continue; 544 | } 545 | }, 546 | apk_type: ApkType::from(badge_text), 547 | arch, 548 | min_version, 549 | screen_dpi, 550 | }); 551 | } 552 | } 553 | } 554 | pb.finish_with_message("Finished getting all download links"); 555 | Ok(results) 556 | } 557 | 558 | /// Gets the download link of the specified URL with specific arch. 559 | /// This method is a shorthand for `download_by_specifics(url, None, arch, None)`. 560 | pub async fn _download_by_arch( 561 | &self, 562 | url: &str, 563 | arch: Option<&str>, 564 | ) -> Result, DownApkError> { 565 | self.download_by_specifics(url, None, arch, None).await 566 | } 567 | 568 | /// Gets the download link of the specified URL with specific type. 569 | /// This method is a shorthand for `download_by_specifics(url, type_, None, None)`. 570 | pub async fn _download_by_type( 571 | &self, 572 | url: &str, 573 | apk_type: Option, 574 | ) -> Result, DownApkError> { 575 | self.download_by_specifics(url, apk_type, None, None).await 576 | } 577 | 578 | /// Gets the download link of the specified URL with specific dpi. 579 | /// This method is a shorthand for `download_by_specifics(url, None, None, dpi)`. 580 | pub async fn _download_by_dpi( 581 | &self, 582 | url: &str, 583 | dpi: Option<&str>, 584 | ) -> Result, DownApkError> { 585 | self.download_by_specifics(url, None, None, dpi).await 586 | } 587 | 588 | /// Gets the download link of the specified URL without any specific parameters. 589 | /// This method is a shorthand for `download_by_specifics(url, None, None, None)`. 590 | pub async fn _download(&self, url: &str) -> Result, DownApkError> { 591 | self.download_by_specifics(url, None, None, None).await 592 | } 593 | 594 | /// Gets the final direct file download link from the specified URL. 595 | /// This method is used internally by `download_by_specifics`. 596 | /// It is recommended to use `download_by_specifics` instead of this method. 597 | /// 598 | /// # Arguments 599 | /// 600 | /// * `url` - The URL to get the final download link from. 601 | /// * `pb` - The progress bar to use. 602 | /// 603 | /// # Returns 604 | /// 605 | /// A `Result` containing the final download link or an `Error` if the download link could not be found. 606 | async fn download_link(&self, url: &str, pb: &ProgressBar) -> Result { 607 | pb.set_message(format!("Trying to get download page link from {}", url)); 608 | let res = self.client.get(url).send().await?.text().await?; 609 | 610 | let document = Html::parse_document(&res); 611 | 612 | let download_button_selector = selector("a.accent_bg.btn.btn-flat.downloadButton")?; 613 | let final_download_link_selector = 614 | selector("a[rel='nofollow'][data-google-vignette='false']")?; 615 | 616 | let download_link = document.select(&download_button_selector).next(); 617 | 618 | let final_download_link = match download_link { 619 | Some(download_link) => { 620 | pb.set_message("Found download link page, trying to get final download link"); 621 | let download_link = self.absolute_url(download_link.value().attr("href").unwrap()); 622 | 623 | let res = self.client.get(download_link).send().await?.text().await?; 624 | 625 | let document = Html::parse_document(&res); 626 | 627 | let final_download_link = document.select(&final_download_link_selector).next(); 628 | 629 | match final_download_link { 630 | Some(final_download_link) => { 631 | let final_download_link = self.absolute_url( 632 | final_download_link 633 | .value() 634 | .attr("href") 635 | .expect("Could not get final download link"), 636 | ); 637 | pb.set_message(format!( 638 | "Found final download link: {}", 639 | final_download_link 640 | )); 641 | final_download_link.to_string() 642 | } 643 | None => Err(DownApkError::from("No final download link found"))?, 644 | } 645 | } 646 | None => Err(DownApkError::from("No download link found"))?, 647 | }; 648 | pb.set_message("Finished getting download link"); 649 | Ok(final_download_link) 650 | } 651 | 652 | // ... other methods here ... 653 | } 654 | 655 | /// Downloads multiple APK files from APKMirror based on the provided vector of DownloadApkMirror structs. 656 | /// Iterates over the vector and calls single_file_download for each item. 657 | /// 658 | /// # Arguments 659 | /// 660 | /// * `downlinks` - The vector of DownloadApkMirror structs to download. 661 | /// * `package_name` - The package name of the APK file. 662 | /// * `output_dir` - The output directory to download the APK files to. 663 | /// 664 | /// # Returns 665 | /// 666 | /// A `Result` containing `()` or an `Error` if the download fails. 667 | /// 668 | /// # Example 669 | /// 670 | /// ```no_run 671 | /// use downapk::apkmirror::{ApkMirror, multiple_file_download}; 672 | /// 673 | /// #[tokio::main] 674 | /// async fn main() { 675 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 676 | /// let downloads = apk_mirror._download("https://www.apkmirror.com/apk/instagram/instagram-lite/instagram-lite-390-0-0-9-116-release/").await.unwrap(); 677 | /// multiple_file_download(&downloads, "com.instagram.lite", "downloads").await.unwrap(); 678 | /// } 679 | /// ``` 680 | pub async fn multiple_file_download( 681 | downlinks: &Vec, 682 | package_name: &str, 683 | output_dir: &str, 684 | ) -> Result<(), DownApkError<'static>> { 685 | for item in downlinks { 686 | single_file_download(item, package_name, output_dir).await?; 687 | } 688 | 689 | Ok(()) 690 | } 691 | 692 | /// Downloads APK files from APKMirror based on the provided DownloadApkMirror. 693 | /// Creates the output directory if it doesn't exist. 694 | /// Downloads each file to the output directory, using the package name, version, arch, dpi 695 | /// and extension to construct a filename. 696 | /// Shows a progress bar while downloading. 697 | /// 698 | /// # Arguments 699 | /// 700 | /// * `item` - The DownloadApkMirror struct to download. 701 | /// * `package_name` - The package name of the APK file. 702 | /// * `output_dir` - The output directory to download the APK files to. 703 | /// 704 | /// # Returns 705 | /// 706 | /// A `Result` containing `()` or an `Error` if the download fails. 707 | /// 708 | /// # Example 709 | /// 710 | /// ```rust 711 | /// use downapk::apkmirror::{ApkMirror, single_file_download}; 712 | /// 713 | /// #[tokio::main] 714 | /// async fn main() { 715 | /// let apk_mirror = ApkMirror::new().await.unwrap(); 716 | /// let downloads = apk_mirror._download_by_arch("https://www.apkmirror.com/apk/instagram/instagram-lite/instagram-lite-390-0-0-9-116-release/", Some("arm64-v8a")).await.unwrap(); 717 | /// single_file_download(&downloads[0], "com.instagram.lite", "downloads").await.unwrap(); 718 | /// } 719 | /// ``` 720 | pub async fn single_file_download( 721 | item: &DownloadApkMirror, 722 | package_name: &str, 723 | output_dir: &str, 724 | ) -> Result<(), DownApkError<'static>> { 725 | // if output_dir is not present, create it 726 | match tokio::fs::create_dir(output_dir).await { 727 | Ok(_) => {} 728 | Err(e) => { 729 | if e.kind() != std::io::ErrorKind::AlreadyExists { 730 | return Err(DownApkError::from(e)); 731 | } 732 | } 733 | }; 734 | 735 | let url = &item.download_link; 736 | let version = &item.version; 737 | let arch = &item.arch; 738 | let dpi = &item.screen_dpi; 739 | let extension = match item.apk_type { 740 | ApkType::Apk => "apk", 741 | ApkType::Bundle => "apkm", 742 | }; 743 | 744 | let mut res = reqwest::get(url).await?; 745 | let total_size = res.content_length().unwrap_or_default(); 746 | 747 | let pb = match total_size { 748 | 0 => ProgressBar::new(100), 749 | _ => ProgressBar::new(total_size), 750 | }; 751 | pb.set_prefix(format!(" {} Downloading", DOWNLOAD_EMOJI)); 752 | pb.set_style(ProgressStyle::default_bar().template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})").unwrap()); 753 | 754 | let output_file = format!( 755 | "{}_{}_{}_{}.{}", 756 | package_name, version, arch, dpi, extension 757 | ); 758 | let output_path = format!("{}/{}", output_dir, output_file); 759 | pb.set_message(format!("File {}", output_file)); 760 | let mut file = File::create(output_path) 761 | .await 762 | .expect("Failed to create file"); 763 | 764 | let mut downloaded: u64 = 0; 765 | 766 | while let Some(chunk) = res.chunk().await.expect("Error while downloading file") { 767 | file.write_all(&chunk) 768 | .await 769 | .expect("Error while writing to file"); 770 | 771 | let new = min(downloaded + (chunk.len() as u64), total_size); 772 | downloaded = new; 773 | pb.set_position(new); 774 | } 775 | 776 | pb.finish_with_message(format!("Finished downloading file {}", output_file)); 777 | 778 | Ok(()) 779 | } 780 | 781 | #[cfg(test)] 782 | mod tests; 783 | -------------------------------------------------------------------------------- /src/apkmirror/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[tokio::test] 4 | async fn test_search() { 5 | let downloader = ApkMirror::new() 6 | .await 7 | .unwrap_or_else(|err| panic!("Error while creating ApkMirror instance. Err: {}", err)); 8 | let search_query = "com.google.android.youtube"; 9 | let version = "19.02.34"; 10 | let result = downloader.search_by_version(search_query, version).await; 11 | assert!(result.is_ok()); 12 | let extracted_links = result.unwrap(); 13 | assert!(!extracted_links.is_empty()); 14 | for item in extracted_links { 15 | assert!(!item.title.is_empty()); 16 | assert!(!item.link.is_empty()); 17 | assert!(!item.version.is_empty()); 18 | assert_eq!(item.version, version.to_string()); 19 | } 20 | } 21 | 22 | #[tokio::test] 23 | async fn test_extract_root_links() { 24 | let downloader = ApkMirror::new() 25 | .await 26 | .unwrap_or_else(|err| panic!("Error while creating ApkMirror instance. Err: {}", err)); 27 | let url = "https://www.apkmirror.com/?post_type=app_release&searchtype=apk&s=com.google.android.youtube"; 28 | let result = downloader.extract_root_links(url, None).await; 29 | assert!(result.is_ok()); 30 | let extracted_links = result.unwrap(); 31 | assert!(!extracted_links.is_empty()); 32 | for item in extracted_links { 33 | assert!(!item.title.is_empty()); 34 | assert!(!item.link.is_empty()); 35 | assert!(!item.version.is_empty()); 36 | } 37 | } 38 | 39 | #[tokio::test] 40 | async fn test_download() { 41 | let downloader = ApkMirror::new() 42 | .await 43 | .unwrap_or_else(|err| panic!("Error while creating ApkMirror instance. Err: {}", err)); 44 | let url = "https://www.apkmirror.com/apk/instagram/instagram-lite/instagram-lite-390-0-0-9-116-release/"; 45 | let arch = "arm64-v8a"; 46 | let apk_type = ApkType::Apk; 47 | let dpi = "nodpi"; 48 | let result = downloader 49 | .download_by_specifics(url, Some(apk_type), Some(arch), Some(dpi)) 50 | .await; 51 | assert!(result.is_ok()); 52 | let download_apkmirror_result = 53 | result.unwrap_or_else(|e| panic!("Error while unwrapping result. Err: {}", e)); 54 | assert!(!download_apkmirror_result.is_empty()); 55 | for item in &download_apkmirror_result { 56 | assert!(!item.version.is_empty()); 57 | assert!(!item.download_link.is_empty()); 58 | assert!(!item.arch.is_empty()); 59 | assert!(!item.min_version.is_empty()); 60 | assert!(!item.screen_dpi.is_empty()); 61 | assert_eq!(item.apk_type, apk_type); 62 | assert_eq!(item.arch, arch.to_string()); 63 | assert_eq!(item.screen_dpi, dpi.to_string()); 64 | } 65 | 66 | match single_file_download( 67 | &download_apkmirror_result[0], 68 | "com.instagram.lite", 69 | "downloads", 70 | ) 71 | .await 72 | { 73 | Ok(_) => { 74 | // check if file exists in output directory 75 | let partial_filename = "com.instagram.lite_"; 76 | // open output directory 77 | let mut dir = tokio::fs::read_dir("downloads").await.unwrap(); 78 | // iterate over files in output directory 79 | while let Some(entry) = dir.next_entry().await.unwrap() { 80 | // get file name 81 | let filename = entry.file_name(); 82 | // convert filename to string 83 | let filename = filename.into_string().unwrap(); 84 | // check if filename contains partial_filename 85 | if filename.contains(partial_filename) { 86 | assert!(filename.ends_with(".apk")); 87 | // delete file after test 88 | tokio::fs::remove_file(format!("downloads/{}", filename)) 89 | .await 90 | .unwrap(); 91 | break; 92 | } 93 | } 94 | } 95 | Err(e) => panic!("Error while downloading file. Err: {}", e), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use indicatif::style::TemplateError; 3 | use reqwest::Error as ReqwestError; 4 | use scraper::error::SelectorErrorKind as ScraperSelectorErrorKind; 5 | 6 | #[derive(Debug)] 7 | pub enum DownApkError<'a> { 8 | Reqwest(ReqwestError), 9 | Selector(ScraperSelectorErrorKind<'a>), 10 | Template(TemplateError), 11 | IoError(std::io::Error), 12 | Other(String), 13 | } 14 | 15 | impl fmt::Display for DownApkError<'_> { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | match self { 18 | DownApkError::Reqwest(e) => write!(f, "Reqwest error: {}", e), 19 | DownApkError::Selector(e) => write!(f, "Selector error: {}", e), 20 | DownApkError::Template(e) => write!(f, "Template error: {}", e), 21 | DownApkError::IoError(e) => write!(f, "IO error: {}", e), 22 | DownApkError::Other(e) => write!(f, "Other error: {}", e), 23 | } 24 | } 25 | } 26 | 27 | impl From for DownApkError<'_> { 28 | fn from(e: ReqwestError) -> Self { 29 | DownApkError::Reqwest(e) 30 | } 31 | } 32 | 33 | impl<'a> From> for DownApkError<'a> { 34 | fn from(e: ScraperSelectorErrorKind<'a>) -> Self { 35 | DownApkError::Selector(e) 36 | } 37 | } 38 | 39 | impl From for DownApkError<'_> { 40 | fn from(e: TemplateError) -> Self { 41 | DownApkError::Template(e) 42 | } 43 | } 44 | 45 | impl From for DownApkError<'_> { 46 | fn from(e: String) -> Self { 47 | DownApkError::Other(e) 48 | } 49 | } 50 | 51 | impl From<&str> for DownApkError<'_> { 52 | fn from(e: &str) -> Self { 53 | DownApkError::Other(e.to_string()) 54 | } 55 | } 56 | 57 | impl From for DownApkError<'_> { 58 | fn from(e: std::io::Error) -> Self { 59 | DownApkError::IoError(e) 60 | } 61 | } 62 | 63 | impl std::error::Error for DownApkError<'static> { 64 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 65 | match self { 66 | DownApkError::Reqwest(e) => Some(e), 67 | DownApkError::Selector(e) => Some(e), 68 | DownApkError::Template(e) => Some(e), 69 | DownApkError::IoError(e) => Some(e), 70 | DownApkError::Other(_) => None, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Module for downloading apks from apkmirror.com 2 | pub mod apkmirror; 3 | /// Module for handling errors in downapk 4 | pub mod errors; 5 | /// Utility functions for downapk 6 | mod utils; 7 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod apkmirror; 2 | mod errors; 3 | mod utils; 4 | 5 | use apkmirror::{multiple_file_download, single_file_download, ApkMirror, ApkType}; 6 | use clap::{Parser, ValueEnum}; 7 | 8 | #[derive(Debug, Clone, ValueEnum)] 9 | enum DownloadOption { 10 | One, 11 | All, 12 | } 13 | 14 | /// Program to download APKs of given Android package ID 15 | #[derive(Parser, Debug)] 16 | #[command(author, version, about, long_about = None)] 17 | struct Args { 18 | /// Android package ID 19 | #[arg(short, long)] 20 | package_id: String, 21 | 22 | /// Optional: Output file name 23 | #[arg(short, long, default_value_t = String::from("downloads"))] 24 | output_dir: String, 25 | 26 | /// Optional: Architecture 27 | /// Possible values: arm64-v8a, armeabi-v7a, x86, x86_64, universal, all 28 | #[arg(short, long, default_value_t = String::from("all"))] 29 | arch: String, 30 | 31 | /// Optional: Version code 32 | /// Possible values: latest, x.x.x (e.g. 1.0.0) 33 | #[arg(short, long, default_value_t = String::from("latest"))] 34 | version_code: String, 35 | 36 | /// Optional: Type of APK 37 | /// Default: Both 38 | #[arg(short('t'), long)] 39 | apk_type: Option, 40 | 41 | /// Optional: Screen DPI 42 | /// Possible values: nodpi, 120-320, ..., all 43 | #[arg(long, default_value_t = String::from("all"))] 44 | dpi: String, 45 | 46 | /// Optional: Search Index to download 47 | /// Possible values: 1, 2, 3, ... 48 | /// Default: None. User will be prompted to choose an index 49 | #[arg(short, long)] 50 | search_index: Option, 51 | 52 | /// Optional: Whether to download all apks or one from final download page 53 | /// Default: None. User will be prompted to choose an index 54 | #[arg(short, long)] 55 | download_option: Option, 56 | 57 | /// If download option is `one` then this is the index of the apk to download 58 | /// Possible values: 1, 2, 3, ... 59 | /// Default: None. User will be prompted to choose an index 60 | #[arg(short('i'), long)] 61 | download_index: Option, 62 | } 63 | 64 | #[tokio::main] 65 | async fn main() { 66 | let args = Args::parse(); 67 | 68 | let apkmirror = ApkMirror::new() 69 | .await 70 | .unwrap_or_else(|err| panic!("Error while creating ApkMirror instance. Err: {}", err)); 71 | 72 | let package_id = args.package_id; 73 | let output_dir = args.output_dir; 74 | let arch = match args.arch.as_str() { 75 | "all" | "ALL" => None, 76 | _ => Some(args.arch.as_str()), 77 | }; 78 | let dpi = match args.dpi.as_str() { 79 | "all" | "ALL" => None, 80 | _ => Some(args.dpi.as_str()), 81 | }; 82 | 83 | let version_code = args.version_code; 84 | let results = match version_code.as_str() { 85 | "latest" => { 86 | let result = apkmirror.search(&package_id).await; 87 | result.unwrap() 88 | } 89 | 90 | _ => { 91 | let result = apkmirror 92 | .search_by_version(&package_id, &version_code) 93 | .await; 94 | result.unwrap() 95 | } 96 | }; 97 | 98 | let choice = args.search_index.unwrap_or_else(|| { 99 | // print all results.i.link with number 100 | for (i, result) in results.iter().enumerate() { 101 | println!( 102 | "{}. {} {} {}", 103 | i + 1, 104 | result.title, 105 | result.uploaded, 106 | result.file_size 107 | ); 108 | } 109 | read_input("Choose a number from above to download:") 110 | }); 111 | 112 | // make sure choice is within range of results 113 | if choice == 0 || choice > results.len() { 114 | panic!( 115 | "Invalid search index. Choose a number from 1 to {}", 116 | results.len() 117 | ); 118 | } 119 | let download_url = results[choice - 1].link.clone(); 120 | let download_result = apkmirror 121 | .download_by_specifics(&download_url, args.apk_type, arch, dpi) 122 | .await 123 | .unwrap_or_else(|err| { 124 | panic!("Error while calling download_by_specifics. Err {}", err); 125 | }); 126 | 127 | let choice: usize = match download_result.len() { 128 | 0 => { 129 | panic!("No apk files found for download. Retry again after some time"); 130 | } 131 | 1 => 2, 132 | _ => match args.download_option { 133 | Some(ref option) => match option { 134 | DownloadOption::All => 2, 135 | DownloadOption::One => 1, 136 | }, 137 | None => { 138 | println!("There are multiple apk files available for download"); 139 | println!("1. Download one specific file"); 140 | println!("2. Download all files"); 141 | read_input("Choose a number from above:") 142 | } 143 | }, 144 | }; 145 | 146 | println!(); 147 | match choice { 148 | 1 => { 149 | let choice = args.download_index.unwrap_or_else(|| { 150 | for (i, result) in download_result.iter().enumerate() { 151 | println!( 152 | "{}. {} {} {} {} {}", 153 | i + 1, 154 | result.version, 155 | result.apk_type, 156 | result.arch, 157 | result.screen_dpi, 158 | result.min_version 159 | ); 160 | } 161 | read_input("Choose a number from above to download:") 162 | }); 163 | 164 | // make sure choice is within range of download_result 165 | if choice > download_result.len() { 166 | panic!( 167 | "Invalid download index. Choose a number from 1 to {}", 168 | download_result.len() 169 | ); 170 | } 171 | 172 | match single_file_download(&download_result[choice - 1], &package_id, &output_dir).await 173 | { 174 | Ok(_) => println!("Downloaded successfully"), 175 | Err(e) => panic!("Error while downloading. Err: {}", e), 176 | } 177 | } 178 | 2 => match multiple_file_download(&download_result, &package_id, &output_dir).await { 179 | Ok(_) => println!("Downloaded successfully"), 180 | Err(e) => panic!("Error while downloading. Err: {}", e), 181 | }, 182 | _ => println!("Invalid choice"), 183 | } 184 | } 185 | 186 | fn read_input(msg: &str) -> usize { 187 | println!("{}", msg); 188 | let mut input = String::new(); 189 | std::io::stdin() 190 | .read_line(&mut input) 191 | .expect("Failed to read input"); 192 | input.trim().parse().unwrap_or_else(|err| { 193 | panic!("Error parsing input: {}", err); 194 | }) 195 | } 196 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::DownApkError; 2 | use scraper::Selector; 3 | 4 | /// Returns a `Selector` from a given `&str` 5 | pub fn selector(selector: &str) -> Result { 6 | Selector::parse(selector).map_err(|e| e.into()) 7 | } 8 | --------------------------------------------------------------------------------