├── .dockerignore ├── .github └── workflows │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── Dockerfile.full ├── README.md ├── docker-compose.yaml └── src ├── config.rs ├── main.rs ├── plugins ├── live.rs ├── mod.rs ├── response.rs ├── twitch.rs └── youtube.rs └── push.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .git/ 3 | .github/ 4 | .gitignore 5 | Dockerfile 6 | README.md 7 | config.yaml 8 | cookies.txt 9 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | # Only do the release on x.y.z tags. 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | # We need this to be able to create releases. 10 | permissions: 11 | contents: write 12 | 13 | env: 14 | BINARY_NAME: bilistream # 定义二进制文件名称为环境变量 15 | 16 | jobs: 17 | create-release: 18 | name: create-release 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Get the release version from the tag 23 | if: env.VERSION == '' 24 | run: echo "VERSION=${{ github.ref_name }}" >> $GITHUB_ENV 25 | - name: Get project name from repository name 26 | run: | 27 | repository="${{ github.repository }}" 28 | project_name="${repository##*/}" # 只获取仓库名,去掉所有者部分 29 | echo "PROJECT_NAME=$project_name" >> $GITHUB_ENV 30 | - name: Show the version and project name 31 | run: | 32 | echo "version is: $VERSION" 33 | echo "project name is: $PROJECT_NAME" 34 | - name: Create GitHub release 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | run: gh release create $VERSION --draft --verify-tag --title $VERSION 38 | outputs: 39 | version: ${{ env.VERSION }} 40 | project_name: ${{ env.PROJECT_NAME }} 41 | 42 | build-release: 43 | name: build-release 44 | needs: ['create-release'] 45 | runs-on: ${{ matrix.os }} 46 | env: 47 | CARGO: cargo 48 | TARGET_FLAGS: 49 | TARGET_DIR: ./target 50 | CROSS_VERSION: v0.2.5 51 | RUST_BACKTRACE: 1 52 | PCRE2_SYS_STATIC: 1 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | include: 57 | - build: linux-x64 58 | os: ubuntu-latest 59 | rust: nightly 60 | target: x86_64-unknown-linux-musl 61 | strip: x86_64-linux-musl-strip 62 | - build: linux-aarch64 63 | os: ubuntu-latest 64 | rust: stable 65 | target: aarch64-unknown-linux-gnu 66 | strip: aarch64-linux-gnu-strip 67 | qemu: qemu-aarch64 68 | - build: macos 69 | os: macos-latest 70 | rust: nightly 71 | target: x86_64-apple-darwin 72 | - build: macos-arm64 73 | os: macos-latest 74 | rust: nightly 75 | target: aarch64-apple-darwin 76 | - build: win-msvc 77 | os: windows-latest 78 | rust: nightly 79 | target: x86_64-pc-windows-msvc 80 | - build: win-arm64 81 | os: windows-latest 82 | rust: nightly 83 | target: aarch64-pc-windows-msvc 84 | 85 | steps: 86 | - name: Checkout repository 87 | uses: actions/checkout@v4 88 | 89 | - name: Install packages (Ubuntu) 90 | if: matrix.os == 'ubuntu-latest' 91 | shell: bash 92 | run: | 93 | sudo apt-get update 94 | sudo apt-get install -y build-essential curl 95 | 96 | - name: Install Rust 97 | uses: dtolnay/rust-toolchain@master 98 | with: 99 | toolchain: ${{ matrix.rust }} 100 | target: ${{ matrix.target }} 101 | 102 | - name: Use Cross 103 | if: matrix.os == 'ubuntu-latest' && matrix.target != '' 104 | shell: bash 105 | run: | 106 | dir="$RUNNER_TEMP/cross-download" 107 | mkdir "$dir" 108 | echo "$dir" >> $GITHUB_PATH 109 | cd "$dir" 110 | curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz" 111 | tar xf cross-x86_64-unknown-linux-musl.tar.gz 112 | echo "CARGO=cross" >> $GITHUB_ENV 113 | 114 | - name: Set target variables 115 | shell: bash 116 | run: | 117 | echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV 118 | echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV 119 | 120 | - name: Show command used for Cargo 121 | shell: bash 122 | run: | 123 | echo "cargo command is: ${{ env.CARGO }}" 124 | echo "target flag is: ${{ env.TARGET_FLAGS }}" 125 | echo "target dir is: ${{ env.TARGET_DIR }}" 126 | 127 | - name: Build release binary 128 | shell: bash 129 | run: | 130 | ${{ env.CARGO }} build --verbose --release ${{ env.TARGET_FLAGS }} 131 | if [ "${{ matrix.os }}" = "windows-latest" ]; then 132 | bin="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}.exe" 133 | else 134 | bin="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" 135 | fi 136 | echo "BIN=$bin" >> $GITHUB_ENV 137 | 138 | # 添加调试信息 139 | echo "=== Build Debug Info ===" 140 | echo "Binary path: $bin" 141 | echo "Target directory content:" 142 | if [ "${{ matrix.target }}" ]; then 143 | ls -la target/${{ matrix.target }}/release/ 144 | else 145 | ls -la target/release/ 146 | fi 147 | echo "=======================" 148 | 149 | # - name: Strip release binary (macos) 150 | # if: matrix.os == 'macos-latest' 151 | # shell: bash 152 | # run: strip "$BIN" 153 | 154 | - name: Strip release binary (cross) 155 | if: env.CARGO == 'cross' 156 | shell: bash 157 | run: | 158 | docker run --rm -v \ 159 | "$PWD/target:/target:Z" \ 160 | "ghcr.io/cross-rs/${{ matrix.target }}:main" \ 161 | "${{ matrix.strip }}" \ 162 | "/target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" 163 | 164 | - name: Determine archive name 165 | shell: bash 166 | run: | 167 | version="${{ needs.create-release.outputs.version }}" 168 | echo "ARCHIVE=${{ env.BINARY_NAME }}-$version-${{ matrix.target }}" >> $GITHUB_ENV 169 | 170 | - name: Creating directory for archive 171 | shell: bash 172 | run: | 173 | mkdir -p "$ARCHIVE" 174 | cp "$BIN" "$ARCHIVE"/ 175 | 176 | - name: Build archive (Windows) 177 | shell: bash 178 | if: matrix.os == 'windows-latest' 179 | run: | 180 | 7z a "$ARCHIVE.zip" "$ARCHIVE" 181 | certutil -hashfile "$ARCHIVE.zip" SHA256 > "$ARCHIVE.zip.sha256" 182 | echo "ASSET=$ARCHIVE.zip" >> $GITHUB_ENV 183 | echo "ASSET_SUM=$ARCHIVE.zip.sha256" >> $GITHUB_ENV 184 | 185 | - name: Build archive (Unix) 186 | shell: bash 187 | if: matrix.os != 'windows-latest' 188 | run: | 189 | tar czf "$ARCHIVE.tar.gz" "$ARCHIVE" 190 | shasum -a 256 "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256" 191 | echo "ASSET=$ARCHIVE.tar.gz" >> $GITHUB_ENV 192 | echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> $GITHUB_ENV 193 | 194 | - name: Upload release archive 195 | env: 196 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 197 | shell: bash 198 | run: | 199 | version="${{ needs.create-release.outputs.version }}" 200 | gh release upload "$version" ${{ env.ASSET }} ${{ env.ASSET_SUM }} 201 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | config.yaml 3 | body.html 4 | live_id.json 5 | jump.html 6 | jump_live_id.json 7 | package-lock.json 8 | update_tag.bat 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "ansi_term" 46 | version = "0.12.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 49 | dependencies = [ 50 | "winapi", 51 | ] 52 | 53 | [[package]] 54 | name = "anyhow" 55 | version = "1.0.95" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 58 | 59 | [[package]] 60 | name = "async-stream" 61 | version = "0.3.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" 64 | dependencies = [ 65 | "async-stream-impl", 66 | "futures-core", 67 | "pin-project-lite", 68 | ] 69 | 70 | [[package]] 71 | name = "async-stream-impl" 72 | version = "0.3.6" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" 75 | dependencies = [ 76 | "proc-macro2", 77 | "quote", 78 | "syn 2.0.98", 79 | ] 80 | 81 | [[package]] 82 | name = "async-trait" 83 | version = "0.1.86" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "syn 2.0.98", 90 | ] 91 | 92 | [[package]] 93 | name = "autocfg" 94 | version = "1.4.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 97 | 98 | [[package]] 99 | name = "backtrace" 100 | version = "0.3.74" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 103 | dependencies = [ 104 | "addr2line", 105 | "cfg-if", 106 | "libc", 107 | "miniz_oxide", 108 | "object", 109 | "rustc-demangle", 110 | "windows-targets 0.52.6", 111 | ] 112 | 113 | [[package]] 114 | name = "base64" 115 | version = "0.21.7" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 118 | 119 | [[package]] 120 | name = "bilistream" 121 | version = "0.1.12" 122 | dependencies = [ 123 | "async-trait", 124 | "gotify", 125 | "m3u8-rs", 126 | "prettyish-html", 127 | "rand 0.8.5", 128 | "regex", 129 | "reqwest", 130 | "reqwest-middleware", 131 | "reqwest-retry", 132 | "scraper", 133 | "serde", 134 | "serde_derive", 135 | "serde_json", 136 | "serde_yaml", 137 | "tokio", 138 | "tokio-test", 139 | "tracing", 140 | "tracing-subscriber", 141 | "urlencoding", 142 | ] 143 | 144 | [[package]] 145 | name = "bitflags" 146 | version = "1.3.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 149 | 150 | [[package]] 151 | name = "bitflags" 152 | version = "2.8.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 155 | 156 | [[package]] 157 | name = "bumpalo" 158 | version = "3.17.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 161 | 162 | [[package]] 163 | name = "byteorder" 164 | version = "1.5.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 167 | 168 | [[package]] 169 | name = "bytes" 170 | version = "1.10.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 173 | 174 | [[package]] 175 | name = "cc" 176 | version = "1.2.13" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" 179 | dependencies = [ 180 | "shlex", 181 | ] 182 | 183 | [[package]] 184 | name = "cfg-if" 185 | version = "1.0.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 188 | 189 | [[package]] 190 | name = "chrono" 191 | version = "0.4.39" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" 194 | dependencies = [ 195 | "android-tzdata", 196 | "iana-time-zone", 197 | "num-traits", 198 | "windows-targets 0.52.6", 199 | ] 200 | 201 | [[package]] 202 | name = "convert_case" 203 | version = "0.4.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 206 | 207 | [[package]] 208 | name = "cookie" 209 | version = "0.17.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" 212 | dependencies = [ 213 | "percent-encoding", 214 | "time", 215 | "version_check", 216 | ] 217 | 218 | [[package]] 219 | name = "cookie_store" 220 | version = "0.20.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" 223 | dependencies = [ 224 | "cookie", 225 | "idna 0.3.0", 226 | "log", 227 | "publicsuffix", 228 | "serde", 229 | "serde_derive", 230 | "serde_json", 231 | "time", 232 | "url", 233 | ] 234 | 235 | [[package]] 236 | name = "core-foundation" 237 | version = "0.9.4" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 240 | dependencies = [ 241 | "core-foundation-sys", 242 | "libc", 243 | ] 244 | 245 | [[package]] 246 | name = "core-foundation-sys" 247 | version = "0.8.7" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 250 | 251 | [[package]] 252 | name = "cssparser" 253 | version = "0.27.2" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 256 | dependencies = [ 257 | "cssparser-macros", 258 | "dtoa-short", 259 | "itoa 0.4.8", 260 | "matches", 261 | "phf 0.8.0", 262 | "proc-macro2", 263 | "quote", 264 | "smallvec", 265 | "syn 1.0.109", 266 | ] 267 | 268 | [[package]] 269 | name = "cssparser-macros" 270 | version = "0.6.1" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" 273 | dependencies = [ 274 | "quote", 275 | "syn 2.0.98", 276 | ] 277 | 278 | [[package]] 279 | name = "deranged" 280 | version = "0.3.11" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 283 | dependencies = [ 284 | "powerfmt", 285 | "serde", 286 | ] 287 | 288 | [[package]] 289 | name = "derive_more" 290 | version = "0.99.19" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" 293 | dependencies = [ 294 | "convert_case", 295 | "proc-macro2", 296 | "quote", 297 | "rustc_version", 298 | "syn 2.0.98", 299 | ] 300 | 301 | [[package]] 302 | name = "displaydoc" 303 | version = "0.2.5" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 306 | dependencies = [ 307 | "proc-macro2", 308 | "quote", 309 | "syn 2.0.98", 310 | ] 311 | 312 | [[package]] 313 | name = "dtoa" 314 | version = "1.0.9" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" 317 | 318 | [[package]] 319 | name = "dtoa-short" 320 | version = "0.3.5" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" 323 | dependencies = [ 324 | "dtoa", 325 | ] 326 | 327 | [[package]] 328 | name = "ego-tree" 329 | version = "0.6.3" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" 332 | 333 | [[package]] 334 | name = "encoding_rs" 335 | version = "0.8.35" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 338 | dependencies = [ 339 | "cfg-if", 340 | ] 341 | 342 | [[package]] 343 | name = "equivalent" 344 | version = "1.0.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 347 | 348 | [[package]] 349 | name = "fnv" 350 | version = "1.0.7" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 353 | 354 | [[package]] 355 | name = "form_urlencoded" 356 | version = "1.2.1" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 359 | dependencies = [ 360 | "percent-encoding", 361 | ] 362 | 363 | [[package]] 364 | name = "futf" 365 | version = "0.1.5" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 368 | dependencies = [ 369 | "mac", 370 | "new_debug_unreachable", 371 | ] 372 | 373 | [[package]] 374 | name = "futures" 375 | version = "0.3.31" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 378 | dependencies = [ 379 | "futures-channel", 380 | "futures-core", 381 | "futures-executor", 382 | "futures-io", 383 | "futures-sink", 384 | "futures-task", 385 | "futures-util", 386 | ] 387 | 388 | [[package]] 389 | name = "futures-channel" 390 | version = "0.3.31" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 393 | dependencies = [ 394 | "futures-core", 395 | "futures-sink", 396 | ] 397 | 398 | [[package]] 399 | name = "futures-core" 400 | version = "0.3.31" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 403 | 404 | [[package]] 405 | name = "futures-executor" 406 | version = "0.3.31" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 409 | dependencies = [ 410 | "futures-core", 411 | "futures-task", 412 | "futures-util", 413 | ] 414 | 415 | [[package]] 416 | name = "futures-io" 417 | version = "0.3.31" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 420 | 421 | [[package]] 422 | name = "futures-macro" 423 | version = "0.3.31" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 426 | dependencies = [ 427 | "proc-macro2", 428 | "quote", 429 | "syn 2.0.98", 430 | ] 431 | 432 | [[package]] 433 | name = "futures-sink" 434 | version = "0.3.31" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 437 | 438 | [[package]] 439 | name = "futures-task" 440 | version = "0.3.31" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 443 | 444 | [[package]] 445 | name = "futures-util" 446 | version = "0.3.31" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 449 | dependencies = [ 450 | "futures-channel", 451 | "futures-core", 452 | "futures-io", 453 | "futures-macro", 454 | "futures-sink", 455 | "futures-task", 456 | "memchr", 457 | "pin-project-lite", 458 | "pin-utils", 459 | "slab", 460 | ] 461 | 462 | [[package]] 463 | name = "fxhash" 464 | version = "0.2.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 467 | dependencies = [ 468 | "byteorder", 469 | ] 470 | 471 | [[package]] 472 | name = "getopts" 473 | version = "0.2.21" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 476 | dependencies = [ 477 | "unicode-width", 478 | ] 479 | 480 | [[package]] 481 | name = "getrandom" 482 | version = "0.1.16" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 485 | dependencies = [ 486 | "cfg-if", 487 | "libc", 488 | "wasi 0.9.0+wasi-snapshot-preview1", 489 | ] 490 | 491 | [[package]] 492 | name = "getrandom" 493 | version = "0.2.15" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 496 | dependencies = [ 497 | "cfg-if", 498 | "libc", 499 | "wasi 0.11.0+wasi-snapshot-preview1", 500 | ] 501 | 502 | [[package]] 503 | name = "gimli" 504 | version = "0.31.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 507 | 508 | [[package]] 509 | name = "gotify" 510 | version = "0.4.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "8d978c6ec78056175ab9eb18691abd1c58d6882c5126662f5049b38e75f93781" 513 | dependencies = [ 514 | "paste", 515 | "reqwest", 516 | "serde", 517 | "serde_json", 518 | "thiserror", 519 | "time", 520 | "url", 521 | ] 522 | 523 | [[package]] 524 | name = "h2" 525 | version = "0.3.26" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 528 | dependencies = [ 529 | "bytes", 530 | "fnv", 531 | "futures-core", 532 | "futures-sink", 533 | "futures-util", 534 | "http", 535 | "indexmap 2.7.1", 536 | "slab", 537 | "tokio", 538 | "tokio-util", 539 | "tracing", 540 | ] 541 | 542 | [[package]] 543 | name = "hashbrown" 544 | version = "0.12.3" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 547 | 548 | [[package]] 549 | name = "hashbrown" 550 | version = "0.15.2" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 553 | 554 | [[package]] 555 | name = "html5ever" 556 | version = "0.26.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" 559 | dependencies = [ 560 | "log", 561 | "mac", 562 | "markup5ever", 563 | "proc-macro2", 564 | "quote", 565 | "syn 1.0.109", 566 | ] 567 | 568 | [[package]] 569 | name = "http" 570 | version = "0.2.12" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 573 | dependencies = [ 574 | "bytes", 575 | "fnv", 576 | "itoa 1.0.14", 577 | ] 578 | 579 | [[package]] 580 | name = "http-body" 581 | version = "0.4.6" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 584 | dependencies = [ 585 | "bytes", 586 | "http", 587 | "pin-project-lite", 588 | ] 589 | 590 | [[package]] 591 | name = "httparse" 592 | version = "1.10.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" 595 | 596 | [[package]] 597 | name = "httpdate" 598 | version = "1.0.3" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 601 | 602 | [[package]] 603 | name = "hyper" 604 | version = "0.14.32" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 607 | dependencies = [ 608 | "bytes", 609 | "futures-channel", 610 | "futures-core", 611 | "futures-util", 612 | "h2", 613 | "http", 614 | "http-body", 615 | "httparse", 616 | "httpdate", 617 | "itoa 1.0.14", 618 | "pin-project-lite", 619 | "socket2", 620 | "tokio", 621 | "tower-service", 622 | "tracing", 623 | "want", 624 | ] 625 | 626 | [[package]] 627 | name = "hyper-rustls" 628 | version = "0.24.2" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 631 | dependencies = [ 632 | "futures-util", 633 | "http", 634 | "hyper", 635 | "rustls", 636 | "tokio", 637 | "tokio-rustls", 638 | ] 639 | 640 | [[package]] 641 | name = "iana-time-zone" 642 | version = "0.1.61" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 645 | dependencies = [ 646 | "android_system_properties", 647 | "core-foundation-sys", 648 | "iana-time-zone-haiku", 649 | "js-sys", 650 | "wasm-bindgen", 651 | "windows-core", 652 | ] 653 | 654 | [[package]] 655 | name = "iana-time-zone-haiku" 656 | version = "0.1.2" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 659 | dependencies = [ 660 | "cc", 661 | ] 662 | 663 | [[package]] 664 | name = "icu_collections" 665 | version = "1.5.0" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 668 | dependencies = [ 669 | "displaydoc", 670 | "yoke", 671 | "zerofrom", 672 | "zerovec", 673 | ] 674 | 675 | [[package]] 676 | name = "icu_locid" 677 | version = "1.5.0" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 680 | dependencies = [ 681 | "displaydoc", 682 | "litemap", 683 | "tinystr", 684 | "writeable", 685 | "zerovec", 686 | ] 687 | 688 | [[package]] 689 | name = "icu_locid_transform" 690 | version = "1.5.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 693 | dependencies = [ 694 | "displaydoc", 695 | "icu_locid", 696 | "icu_locid_transform_data", 697 | "icu_provider", 698 | "tinystr", 699 | "zerovec", 700 | ] 701 | 702 | [[package]] 703 | name = "icu_locid_transform_data" 704 | version = "1.5.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 707 | 708 | [[package]] 709 | name = "icu_normalizer" 710 | version = "1.5.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 713 | dependencies = [ 714 | "displaydoc", 715 | "icu_collections", 716 | "icu_normalizer_data", 717 | "icu_properties", 718 | "icu_provider", 719 | "smallvec", 720 | "utf16_iter", 721 | "utf8_iter", 722 | "write16", 723 | "zerovec", 724 | ] 725 | 726 | [[package]] 727 | name = "icu_normalizer_data" 728 | version = "1.5.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 731 | 732 | [[package]] 733 | name = "icu_properties" 734 | version = "1.5.1" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 737 | dependencies = [ 738 | "displaydoc", 739 | "icu_collections", 740 | "icu_locid_transform", 741 | "icu_properties_data", 742 | "icu_provider", 743 | "tinystr", 744 | "zerovec", 745 | ] 746 | 747 | [[package]] 748 | name = "icu_properties_data" 749 | version = "1.5.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 752 | 753 | [[package]] 754 | name = "icu_provider" 755 | version = "1.5.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 758 | dependencies = [ 759 | "displaydoc", 760 | "icu_locid", 761 | "icu_provider_macros", 762 | "stable_deref_trait", 763 | "tinystr", 764 | "writeable", 765 | "yoke", 766 | "zerofrom", 767 | "zerovec", 768 | ] 769 | 770 | [[package]] 771 | name = "icu_provider_macros" 772 | version = "1.5.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 775 | dependencies = [ 776 | "proc-macro2", 777 | "quote", 778 | "syn 2.0.98", 779 | ] 780 | 781 | [[package]] 782 | name = "idna" 783 | version = "0.3.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 786 | dependencies = [ 787 | "unicode-bidi", 788 | "unicode-normalization", 789 | ] 790 | 791 | [[package]] 792 | name = "idna" 793 | version = "1.0.3" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 796 | dependencies = [ 797 | "idna_adapter", 798 | "smallvec", 799 | "utf8_iter", 800 | ] 801 | 802 | [[package]] 803 | name = "idna_adapter" 804 | version = "1.2.0" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 807 | dependencies = [ 808 | "icu_normalizer", 809 | "icu_properties", 810 | ] 811 | 812 | [[package]] 813 | name = "indexmap" 814 | version = "1.9.3" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 817 | dependencies = [ 818 | "autocfg", 819 | "hashbrown 0.12.3", 820 | ] 821 | 822 | [[package]] 823 | name = "indexmap" 824 | version = "2.7.1" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 827 | dependencies = [ 828 | "equivalent", 829 | "hashbrown 0.15.2", 830 | ] 831 | 832 | [[package]] 833 | name = "ipnet" 834 | version = "2.11.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 837 | 838 | [[package]] 839 | name = "itoa" 840 | version = "0.4.8" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 843 | 844 | [[package]] 845 | name = "itoa" 846 | version = "1.0.14" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 849 | 850 | [[package]] 851 | name = "js-sys" 852 | version = "0.3.77" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 855 | dependencies = [ 856 | "once_cell", 857 | "wasm-bindgen", 858 | ] 859 | 860 | [[package]] 861 | name = "lazy_static" 862 | version = "1.5.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 865 | 866 | [[package]] 867 | name = "libc" 868 | version = "0.2.169" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 871 | 872 | [[package]] 873 | name = "linked-hash-map" 874 | version = "0.5.6" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 877 | 878 | [[package]] 879 | name = "litemap" 880 | version = "0.7.4" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 883 | 884 | [[package]] 885 | name = "lock_api" 886 | version = "0.4.12" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 889 | dependencies = [ 890 | "autocfg", 891 | "scopeguard", 892 | ] 893 | 894 | [[package]] 895 | name = "log" 896 | version = "0.4.25" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 899 | 900 | [[package]] 901 | name = "m3u8-rs" 902 | version = "4.0.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "c27f4a86278e7d10f93c8c97f0191f85a071a45fa4245c261539465729c6d947" 905 | dependencies = [ 906 | "nom", 907 | ] 908 | 909 | [[package]] 910 | name = "mac" 911 | version = "0.1.1" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 914 | 915 | [[package]] 916 | name = "markup5ever" 917 | version = "0.11.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" 920 | dependencies = [ 921 | "log", 922 | "phf 0.10.1", 923 | "phf_codegen 0.10.0", 924 | "string_cache", 925 | "string_cache_codegen", 926 | "tendril", 927 | ] 928 | 929 | [[package]] 930 | name = "matchers" 931 | version = "0.0.1" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" 934 | dependencies = [ 935 | "regex-automata 0.1.10", 936 | ] 937 | 938 | [[package]] 939 | name = "matches" 940 | version = "0.1.10" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 943 | 944 | [[package]] 945 | name = "memchr" 946 | version = "2.7.4" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 949 | 950 | [[package]] 951 | name = "mime" 952 | version = "0.3.17" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 955 | 956 | [[package]] 957 | name = "mime_guess" 958 | version = "2.0.5" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 961 | dependencies = [ 962 | "mime", 963 | "unicase", 964 | ] 965 | 966 | [[package]] 967 | name = "minimal-lexical" 968 | version = "0.2.1" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 971 | 972 | [[package]] 973 | name = "miniz_oxide" 974 | version = "0.8.4" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" 977 | dependencies = [ 978 | "adler2", 979 | ] 980 | 981 | [[package]] 982 | name = "mio" 983 | version = "1.0.3" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 986 | dependencies = [ 987 | "libc", 988 | "wasi 0.11.0+wasi-snapshot-preview1", 989 | "windows-sys 0.52.0", 990 | ] 991 | 992 | [[package]] 993 | name = "new_debug_unreachable" 994 | version = "1.0.6" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 997 | 998 | [[package]] 999 | name = "nodrop" 1000 | version = "0.1.14" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 1003 | 1004 | [[package]] 1005 | name = "nom" 1006 | version = "7.1.3" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1009 | dependencies = [ 1010 | "memchr", 1011 | "minimal-lexical", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "num-conv" 1016 | version = "0.1.0" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1019 | 1020 | [[package]] 1021 | name = "num-traits" 1022 | version = "0.2.19" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1025 | dependencies = [ 1026 | "autocfg", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "object" 1031 | version = "0.36.7" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1034 | dependencies = [ 1035 | "memchr", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "once_cell" 1040 | version = "1.20.3" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1043 | 1044 | [[package]] 1045 | name = "parking_lot" 1046 | version = "0.12.3" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1049 | dependencies = [ 1050 | "lock_api", 1051 | "parking_lot_core", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "parking_lot_core" 1056 | version = "0.9.10" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1059 | dependencies = [ 1060 | "cfg-if", 1061 | "libc", 1062 | "redox_syscall", 1063 | "smallvec", 1064 | "windows-targets 0.52.6", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "paste" 1069 | version = "1.0.15" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1072 | 1073 | [[package]] 1074 | name = "percent-encoding" 1075 | version = "2.3.1" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1078 | 1079 | [[package]] 1080 | name = "phf" 1081 | version = "0.8.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 1084 | dependencies = [ 1085 | "phf_macros", 1086 | "phf_shared 0.8.0", 1087 | "proc-macro-hack", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "phf" 1092 | version = "0.10.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 1095 | dependencies = [ 1096 | "phf_shared 0.10.0", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "phf_codegen" 1101 | version = "0.8.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 1104 | dependencies = [ 1105 | "phf_generator 0.8.0", 1106 | "phf_shared 0.8.0", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "phf_codegen" 1111 | version = "0.10.0" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 1114 | dependencies = [ 1115 | "phf_generator 0.10.0", 1116 | "phf_shared 0.10.0", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "phf_generator" 1121 | version = "0.8.0" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 1124 | dependencies = [ 1125 | "phf_shared 0.8.0", 1126 | "rand 0.7.3", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "phf_generator" 1131 | version = "0.10.0" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 1134 | dependencies = [ 1135 | "phf_shared 0.10.0", 1136 | "rand 0.8.5", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "phf_generator" 1141 | version = "0.11.3" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 1144 | dependencies = [ 1145 | "phf_shared 0.11.3", 1146 | "rand 0.8.5", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "phf_macros" 1151 | version = "0.8.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 1154 | dependencies = [ 1155 | "phf_generator 0.8.0", 1156 | "phf_shared 0.8.0", 1157 | "proc-macro-hack", 1158 | "proc-macro2", 1159 | "quote", 1160 | "syn 1.0.109", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "phf_shared" 1165 | version = "0.8.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 1168 | dependencies = [ 1169 | "siphasher 0.3.11", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "phf_shared" 1174 | version = "0.10.0" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 1177 | dependencies = [ 1178 | "siphasher 0.3.11", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "phf_shared" 1183 | version = "0.11.3" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 1186 | dependencies = [ 1187 | "siphasher 1.0.1", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "pin-project-lite" 1192 | version = "0.2.16" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1195 | 1196 | [[package]] 1197 | name = "pin-utils" 1198 | version = "0.1.0" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1201 | 1202 | [[package]] 1203 | name = "powerfmt" 1204 | version = "0.2.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1207 | 1208 | [[package]] 1209 | name = "ppv-lite86" 1210 | version = "0.2.20" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1213 | dependencies = [ 1214 | "zerocopy", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "precomputed-hash" 1219 | version = "0.1.1" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 1222 | 1223 | [[package]] 1224 | name = "prettyish-html" 1225 | version = "0.1.1" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "f1cd3663ae00d6ebb6307dfee80c9b31d75639258b22ecfebd50816be04f1bf4" 1228 | dependencies = [ 1229 | "lazy_static", 1230 | "regex", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "proc-macro-hack" 1235 | version = "0.5.20+deprecated" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 1238 | 1239 | [[package]] 1240 | name = "proc-macro2" 1241 | version = "1.0.93" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1244 | dependencies = [ 1245 | "unicode-ident", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "psl-types" 1250 | version = "2.0.11" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" 1253 | 1254 | [[package]] 1255 | name = "publicsuffix" 1256 | version = "2.3.0" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" 1259 | dependencies = [ 1260 | "idna 1.0.3", 1261 | "psl-types", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "quote" 1266 | version = "1.0.38" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1269 | dependencies = [ 1270 | "proc-macro2", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "rand" 1275 | version = "0.7.3" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1278 | dependencies = [ 1279 | "getrandom 0.1.16", 1280 | "libc", 1281 | "rand_chacha 0.2.2", 1282 | "rand_core 0.5.1", 1283 | "rand_hc", 1284 | "rand_pcg", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "rand" 1289 | version = "0.8.5" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1292 | dependencies = [ 1293 | "libc", 1294 | "rand_chacha 0.3.1", 1295 | "rand_core 0.6.4", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "rand_chacha" 1300 | version = "0.2.2" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1303 | dependencies = [ 1304 | "ppv-lite86", 1305 | "rand_core 0.5.1", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "rand_chacha" 1310 | version = "0.3.1" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1313 | dependencies = [ 1314 | "ppv-lite86", 1315 | "rand_core 0.6.4", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "rand_core" 1320 | version = "0.5.1" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1323 | dependencies = [ 1324 | "getrandom 0.1.16", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "rand_core" 1329 | version = "0.6.4" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1332 | dependencies = [ 1333 | "getrandom 0.2.15", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "rand_hc" 1338 | version = "0.2.0" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1341 | dependencies = [ 1342 | "rand_core 0.5.1", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "rand_pcg" 1347 | version = "0.2.1" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 1350 | dependencies = [ 1351 | "rand_core 0.5.1", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "redox_syscall" 1356 | version = "0.5.8" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1359 | dependencies = [ 1360 | "bitflags 2.8.0", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "regex" 1365 | version = "1.11.1" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1368 | dependencies = [ 1369 | "aho-corasick", 1370 | "memchr", 1371 | "regex-automata 0.4.9", 1372 | "regex-syntax 0.8.5", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "regex-automata" 1377 | version = "0.1.10" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1380 | dependencies = [ 1381 | "regex-syntax 0.6.29", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "regex-automata" 1386 | version = "0.4.9" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1389 | dependencies = [ 1390 | "aho-corasick", 1391 | "memchr", 1392 | "regex-syntax 0.8.5", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "regex-syntax" 1397 | version = "0.6.29" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1400 | 1401 | [[package]] 1402 | name = "regex-syntax" 1403 | version = "0.8.5" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1406 | 1407 | [[package]] 1408 | name = "reqwest" 1409 | version = "0.11.27" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1412 | dependencies = [ 1413 | "base64", 1414 | "bytes", 1415 | "cookie", 1416 | "cookie_store", 1417 | "encoding_rs", 1418 | "futures-core", 1419 | "futures-util", 1420 | "h2", 1421 | "http", 1422 | "http-body", 1423 | "hyper", 1424 | "hyper-rustls", 1425 | "ipnet", 1426 | "js-sys", 1427 | "log", 1428 | "mime", 1429 | "mime_guess", 1430 | "once_cell", 1431 | "percent-encoding", 1432 | "pin-project-lite", 1433 | "rustls", 1434 | "rustls-pemfile", 1435 | "serde", 1436 | "serde_json", 1437 | "serde_urlencoded", 1438 | "sync_wrapper", 1439 | "system-configuration", 1440 | "tokio", 1441 | "tokio-rustls", 1442 | "tower-service", 1443 | "url", 1444 | "wasm-bindgen", 1445 | "wasm-bindgen-futures", 1446 | "web-sys", 1447 | "webpki-roots", 1448 | "winreg", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "reqwest-middleware" 1453 | version = "0.1.6" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "69539cea4148dce683bec9dc95be3f0397a9bb2c248a49c8296a9d21659a8cdd" 1456 | dependencies = [ 1457 | "anyhow", 1458 | "async-trait", 1459 | "futures", 1460 | "http", 1461 | "reqwest", 1462 | "serde", 1463 | "task-local-extensions", 1464 | "thiserror", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "reqwest-retry" 1469 | version = "0.1.5" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "ce246a729eaa6aff5e215aee42845bf5fed9893cc6cd51aeeb712f34e04dd9f3" 1472 | dependencies = [ 1473 | "anyhow", 1474 | "async-trait", 1475 | "chrono", 1476 | "futures", 1477 | "http", 1478 | "hyper", 1479 | "reqwest", 1480 | "reqwest-middleware", 1481 | "retry-policies", 1482 | "task-local-extensions", 1483 | "tokio", 1484 | "tracing", 1485 | ] 1486 | 1487 | [[package]] 1488 | name = "retry-policies" 1489 | version = "0.1.2" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b" 1492 | dependencies = [ 1493 | "anyhow", 1494 | "chrono", 1495 | "rand 0.8.5", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "ring" 1500 | version = "0.17.9" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" 1503 | dependencies = [ 1504 | "cc", 1505 | "cfg-if", 1506 | "getrandom 0.2.15", 1507 | "libc", 1508 | "untrusted", 1509 | "windows-sys 0.52.0", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "rustc-demangle" 1514 | version = "0.1.24" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1517 | 1518 | [[package]] 1519 | name = "rustc_version" 1520 | version = "0.4.1" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1523 | dependencies = [ 1524 | "semver", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "rustls" 1529 | version = "0.21.12" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 1532 | dependencies = [ 1533 | "log", 1534 | "ring", 1535 | "rustls-webpki", 1536 | "sct", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "rustls-pemfile" 1541 | version = "1.0.4" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1544 | dependencies = [ 1545 | "base64", 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "rustls-webpki" 1550 | version = "0.101.7" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1553 | dependencies = [ 1554 | "ring", 1555 | "untrusted", 1556 | ] 1557 | 1558 | [[package]] 1559 | name = "rustversion" 1560 | version = "1.0.19" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1563 | 1564 | [[package]] 1565 | name = "ryu" 1566 | version = "1.0.19" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 1569 | 1570 | [[package]] 1571 | name = "scopeguard" 1572 | version = "1.2.0" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1575 | 1576 | [[package]] 1577 | name = "scraper" 1578 | version = "0.13.0" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "5684396b456f3eb69ceeb34d1b5cb1a2f6acf7ca4452131efa3ba0ee2c2d0a70" 1581 | dependencies = [ 1582 | "cssparser", 1583 | "ego-tree", 1584 | "getopts", 1585 | "html5ever", 1586 | "matches", 1587 | "selectors", 1588 | "smallvec", 1589 | "tendril", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "sct" 1594 | version = "0.7.1" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 1597 | dependencies = [ 1598 | "ring", 1599 | "untrusted", 1600 | ] 1601 | 1602 | [[package]] 1603 | name = "selectors" 1604 | version = "0.22.0" 1605 | source = "registry+https://github.com/rust-lang/crates.io-index" 1606 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 1607 | dependencies = [ 1608 | "bitflags 1.3.2", 1609 | "cssparser", 1610 | "derive_more", 1611 | "fxhash", 1612 | "log", 1613 | "matches", 1614 | "phf 0.8.0", 1615 | "phf_codegen 0.8.0", 1616 | "precomputed-hash", 1617 | "servo_arc", 1618 | "smallvec", 1619 | "thin-slice", 1620 | ] 1621 | 1622 | [[package]] 1623 | name = "semver" 1624 | version = "1.0.25" 1625 | source = "registry+https://github.com/rust-lang/crates.io-index" 1626 | checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" 1627 | 1628 | [[package]] 1629 | name = "serde" 1630 | version = "1.0.217" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1633 | dependencies = [ 1634 | "serde_derive", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "serde_derive" 1639 | version = "1.0.217" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1642 | dependencies = [ 1643 | "proc-macro2", 1644 | "quote", 1645 | "syn 2.0.98", 1646 | ] 1647 | 1648 | [[package]] 1649 | name = "serde_json" 1650 | version = "1.0.138" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 1653 | dependencies = [ 1654 | "itoa 1.0.14", 1655 | "memchr", 1656 | "ryu", 1657 | "serde", 1658 | ] 1659 | 1660 | [[package]] 1661 | name = "serde_urlencoded" 1662 | version = "0.7.1" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1665 | dependencies = [ 1666 | "form_urlencoded", 1667 | "itoa 1.0.14", 1668 | "ryu", 1669 | "serde", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "serde_yaml" 1674 | version = "0.8.26" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" 1677 | dependencies = [ 1678 | "indexmap 1.9.3", 1679 | "ryu", 1680 | "serde", 1681 | "yaml-rust", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "servo_arc" 1686 | version = "0.1.1" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 1689 | dependencies = [ 1690 | "nodrop", 1691 | "stable_deref_trait", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "sharded-slab" 1696 | version = "0.1.7" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1699 | dependencies = [ 1700 | "lazy_static", 1701 | ] 1702 | 1703 | [[package]] 1704 | name = "shlex" 1705 | version = "1.3.0" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1708 | 1709 | [[package]] 1710 | name = "signal-hook-registry" 1711 | version = "1.4.2" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1714 | dependencies = [ 1715 | "libc", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "siphasher" 1720 | version = "0.3.11" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1723 | 1724 | [[package]] 1725 | name = "siphasher" 1726 | version = "1.0.1" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 1729 | 1730 | [[package]] 1731 | name = "slab" 1732 | version = "0.4.9" 1733 | source = "registry+https://github.com/rust-lang/crates.io-index" 1734 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1735 | dependencies = [ 1736 | "autocfg", 1737 | ] 1738 | 1739 | [[package]] 1740 | name = "smallvec" 1741 | version = "1.13.2" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1744 | 1745 | [[package]] 1746 | name = "socket2" 1747 | version = "0.5.8" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1750 | dependencies = [ 1751 | "libc", 1752 | "windows-sys 0.52.0", 1753 | ] 1754 | 1755 | [[package]] 1756 | name = "stable_deref_trait" 1757 | version = "1.2.0" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1760 | 1761 | [[package]] 1762 | name = "string_cache" 1763 | version = "0.8.8" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" 1766 | dependencies = [ 1767 | "new_debug_unreachable", 1768 | "parking_lot", 1769 | "phf_shared 0.11.3", 1770 | "precomputed-hash", 1771 | "serde", 1772 | ] 1773 | 1774 | [[package]] 1775 | name = "string_cache_codegen" 1776 | version = "0.5.3" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "244292f3441c89febe5b5bdfbb6863aeaf4f64da810ea3050fd927b27b8d92ce" 1779 | dependencies = [ 1780 | "phf_generator 0.11.3", 1781 | "phf_shared 0.11.3", 1782 | "proc-macro2", 1783 | "quote", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "syn" 1788 | version = "1.0.109" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1791 | dependencies = [ 1792 | "proc-macro2", 1793 | "quote", 1794 | "unicode-ident", 1795 | ] 1796 | 1797 | [[package]] 1798 | name = "syn" 1799 | version = "2.0.98" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 1802 | dependencies = [ 1803 | "proc-macro2", 1804 | "quote", 1805 | "unicode-ident", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "sync_wrapper" 1810 | version = "0.1.2" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1813 | 1814 | [[package]] 1815 | name = "synstructure" 1816 | version = "0.13.1" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1819 | dependencies = [ 1820 | "proc-macro2", 1821 | "quote", 1822 | "syn 2.0.98", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "system-configuration" 1827 | version = "0.5.1" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1830 | dependencies = [ 1831 | "bitflags 1.3.2", 1832 | "core-foundation", 1833 | "system-configuration-sys", 1834 | ] 1835 | 1836 | [[package]] 1837 | name = "system-configuration-sys" 1838 | version = "0.5.0" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1841 | dependencies = [ 1842 | "core-foundation-sys", 1843 | "libc", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "task-local-extensions" 1848 | version = "0.1.4" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" 1851 | dependencies = [ 1852 | "pin-utils", 1853 | ] 1854 | 1855 | [[package]] 1856 | name = "tendril" 1857 | version = "0.4.3" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 1860 | dependencies = [ 1861 | "futf", 1862 | "mac", 1863 | "utf-8", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "thin-slice" 1868 | version = "0.1.1" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 1871 | 1872 | [[package]] 1873 | name = "thiserror" 1874 | version = "1.0.69" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1877 | dependencies = [ 1878 | "thiserror-impl", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "thiserror-impl" 1883 | version = "1.0.69" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1886 | dependencies = [ 1887 | "proc-macro2", 1888 | "quote", 1889 | "syn 2.0.98", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "thread_local" 1894 | version = "1.1.8" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1897 | dependencies = [ 1898 | "cfg-if", 1899 | "once_cell", 1900 | ] 1901 | 1902 | [[package]] 1903 | name = "time" 1904 | version = "0.3.37" 1905 | source = "registry+https://github.com/rust-lang/crates.io-index" 1906 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 1907 | dependencies = [ 1908 | "deranged", 1909 | "itoa 1.0.14", 1910 | "num-conv", 1911 | "powerfmt", 1912 | "serde", 1913 | "time-core", 1914 | "time-macros", 1915 | ] 1916 | 1917 | [[package]] 1918 | name = "time-core" 1919 | version = "0.1.2" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1922 | 1923 | [[package]] 1924 | name = "time-macros" 1925 | version = "0.2.19" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 1928 | dependencies = [ 1929 | "num-conv", 1930 | "time-core", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "tinystr" 1935 | version = "0.7.6" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1938 | dependencies = [ 1939 | "displaydoc", 1940 | "zerovec", 1941 | ] 1942 | 1943 | [[package]] 1944 | name = "tinyvec" 1945 | version = "1.8.1" 1946 | source = "registry+https://github.com/rust-lang/crates.io-index" 1947 | checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" 1948 | dependencies = [ 1949 | "tinyvec_macros", 1950 | ] 1951 | 1952 | [[package]] 1953 | name = "tinyvec_macros" 1954 | version = "0.1.1" 1955 | source = "registry+https://github.com/rust-lang/crates.io-index" 1956 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1957 | 1958 | [[package]] 1959 | name = "tokio" 1960 | version = "1.43.0" 1961 | source = "registry+https://github.com/rust-lang/crates.io-index" 1962 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 1963 | dependencies = [ 1964 | "backtrace", 1965 | "bytes", 1966 | "libc", 1967 | "mio", 1968 | "parking_lot", 1969 | "pin-project-lite", 1970 | "signal-hook-registry", 1971 | "socket2", 1972 | "tokio-macros", 1973 | "windows-sys 0.52.0", 1974 | ] 1975 | 1976 | [[package]] 1977 | name = "tokio-macros" 1978 | version = "2.5.0" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1981 | dependencies = [ 1982 | "proc-macro2", 1983 | "quote", 1984 | "syn 2.0.98", 1985 | ] 1986 | 1987 | [[package]] 1988 | name = "tokio-rustls" 1989 | version = "0.24.1" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 1992 | dependencies = [ 1993 | "rustls", 1994 | "tokio", 1995 | ] 1996 | 1997 | [[package]] 1998 | name = "tokio-stream" 1999 | version = "0.1.17" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 2002 | dependencies = [ 2003 | "futures-core", 2004 | "pin-project-lite", 2005 | "tokio", 2006 | ] 2007 | 2008 | [[package]] 2009 | name = "tokio-test" 2010 | version = "0.4.4" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" 2013 | dependencies = [ 2014 | "async-stream", 2015 | "bytes", 2016 | "futures-core", 2017 | "tokio", 2018 | "tokio-stream", 2019 | ] 2020 | 2021 | [[package]] 2022 | name = "tokio-util" 2023 | version = "0.7.13" 2024 | source = "registry+https://github.com/rust-lang/crates.io-index" 2025 | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" 2026 | dependencies = [ 2027 | "bytes", 2028 | "futures-core", 2029 | "futures-sink", 2030 | "pin-project-lite", 2031 | "tokio", 2032 | ] 2033 | 2034 | [[package]] 2035 | name = "tower-service" 2036 | version = "0.3.3" 2037 | source = "registry+https://github.com/rust-lang/crates.io-index" 2038 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2039 | 2040 | [[package]] 2041 | name = "tracing" 2042 | version = "0.1.41" 2043 | source = "registry+https://github.com/rust-lang/crates.io-index" 2044 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2045 | dependencies = [ 2046 | "pin-project-lite", 2047 | "tracing-attributes", 2048 | "tracing-core", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "tracing-attributes" 2053 | version = "0.1.28" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2056 | dependencies = [ 2057 | "proc-macro2", 2058 | "quote", 2059 | "syn 2.0.98", 2060 | ] 2061 | 2062 | [[package]] 2063 | name = "tracing-core" 2064 | version = "0.1.33" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2067 | dependencies = [ 2068 | "once_cell", 2069 | "valuable", 2070 | ] 2071 | 2072 | [[package]] 2073 | name = "tracing-log" 2074 | version = "0.1.4" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" 2077 | dependencies = [ 2078 | "log", 2079 | "once_cell", 2080 | "tracing-core", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "tracing-serde" 2085 | version = "0.1.3" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" 2088 | dependencies = [ 2089 | "serde", 2090 | "tracing-core", 2091 | ] 2092 | 2093 | [[package]] 2094 | name = "tracing-subscriber" 2095 | version = "0.2.25" 2096 | source = "registry+https://github.com/rust-lang/crates.io-index" 2097 | checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" 2098 | dependencies = [ 2099 | "ansi_term", 2100 | "chrono", 2101 | "lazy_static", 2102 | "matchers", 2103 | "regex", 2104 | "serde", 2105 | "serde_json", 2106 | "sharded-slab", 2107 | "smallvec", 2108 | "thread_local", 2109 | "tracing", 2110 | "tracing-core", 2111 | "tracing-log", 2112 | "tracing-serde", 2113 | ] 2114 | 2115 | [[package]] 2116 | name = "try-lock" 2117 | version = "0.2.5" 2118 | source = "registry+https://github.com/rust-lang/crates.io-index" 2119 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2120 | 2121 | [[package]] 2122 | name = "unicase" 2123 | version = "2.8.1" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 2126 | 2127 | [[package]] 2128 | name = "unicode-bidi" 2129 | version = "0.3.18" 2130 | source = "registry+https://github.com/rust-lang/crates.io-index" 2131 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 2132 | 2133 | [[package]] 2134 | name = "unicode-ident" 2135 | version = "1.0.16" 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" 2137 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 2138 | 2139 | [[package]] 2140 | name = "unicode-normalization" 2141 | version = "0.1.24" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 2144 | dependencies = [ 2145 | "tinyvec", 2146 | ] 2147 | 2148 | [[package]] 2149 | name = "unicode-width" 2150 | version = "0.1.14" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2153 | 2154 | [[package]] 2155 | name = "untrusted" 2156 | version = "0.9.0" 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" 2158 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2159 | 2160 | [[package]] 2161 | name = "url" 2162 | version = "2.5.4" 2163 | source = "registry+https://github.com/rust-lang/crates.io-index" 2164 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2165 | dependencies = [ 2166 | "form_urlencoded", 2167 | "idna 1.0.3", 2168 | "percent-encoding", 2169 | ] 2170 | 2171 | [[package]] 2172 | name = "urlencoding" 2173 | version = "2.1.3" 2174 | source = "registry+https://github.com/rust-lang/crates.io-index" 2175 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 2176 | 2177 | [[package]] 2178 | name = "utf-8" 2179 | version = "0.7.6" 2180 | source = "registry+https://github.com/rust-lang/crates.io-index" 2181 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2182 | 2183 | [[package]] 2184 | name = "utf16_iter" 2185 | version = "1.0.5" 2186 | source = "registry+https://github.com/rust-lang/crates.io-index" 2187 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2188 | 2189 | [[package]] 2190 | name = "utf8_iter" 2191 | version = "1.0.4" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2194 | 2195 | [[package]] 2196 | name = "valuable" 2197 | version = "0.1.1" 2198 | source = "registry+https://github.com/rust-lang/crates.io-index" 2199 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2200 | 2201 | [[package]] 2202 | name = "version_check" 2203 | version = "0.9.5" 2204 | source = "registry+https://github.com/rust-lang/crates.io-index" 2205 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2206 | 2207 | [[package]] 2208 | name = "want" 2209 | version = "0.3.1" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2212 | dependencies = [ 2213 | "try-lock", 2214 | ] 2215 | 2216 | [[package]] 2217 | name = "wasi" 2218 | version = "0.9.0+wasi-snapshot-preview1" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 2221 | 2222 | [[package]] 2223 | name = "wasi" 2224 | version = "0.11.0+wasi-snapshot-preview1" 2225 | source = "registry+https://github.com/rust-lang/crates.io-index" 2226 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2227 | 2228 | [[package]] 2229 | name = "wasm-bindgen" 2230 | version = "0.2.100" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2233 | dependencies = [ 2234 | "cfg-if", 2235 | "once_cell", 2236 | "rustversion", 2237 | "wasm-bindgen-macro", 2238 | ] 2239 | 2240 | [[package]] 2241 | name = "wasm-bindgen-backend" 2242 | version = "0.2.100" 2243 | source = "registry+https://github.com/rust-lang/crates.io-index" 2244 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2245 | dependencies = [ 2246 | "bumpalo", 2247 | "log", 2248 | "proc-macro2", 2249 | "quote", 2250 | "syn 2.0.98", 2251 | "wasm-bindgen-shared", 2252 | ] 2253 | 2254 | [[package]] 2255 | name = "wasm-bindgen-futures" 2256 | version = "0.4.50" 2257 | source = "registry+https://github.com/rust-lang/crates.io-index" 2258 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2259 | dependencies = [ 2260 | "cfg-if", 2261 | "js-sys", 2262 | "once_cell", 2263 | "wasm-bindgen", 2264 | "web-sys", 2265 | ] 2266 | 2267 | [[package]] 2268 | name = "wasm-bindgen-macro" 2269 | version = "0.2.100" 2270 | source = "registry+https://github.com/rust-lang/crates.io-index" 2271 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2272 | dependencies = [ 2273 | "quote", 2274 | "wasm-bindgen-macro-support", 2275 | ] 2276 | 2277 | [[package]] 2278 | name = "wasm-bindgen-macro-support" 2279 | version = "0.2.100" 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" 2281 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2282 | dependencies = [ 2283 | "proc-macro2", 2284 | "quote", 2285 | "syn 2.0.98", 2286 | "wasm-bindgen-backend", 2287 | "wasm-bindgen-shared", 2288 | ] 2289 | 2290 | [[package]] 2291 | name = "wasm-bindgen-shared" 2292 | version = "0.2.100" 2293 | source = "registry+https://github.com/rust-lang/crates.io-index" 2294 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2295 | dependencies = [ 2296 | "unicode-ident", 2297 | ] 2298 | 2299 | [[package]] 2300 | name = "web-sys" 2301 | version = "0.3.77" 2302 | source = "registry+https://github.com/rust-lang/crates.io-index" 2303 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2304 | dependencies = [ 2305 | "js-sys", 2306 | "wasm-bindgen", 2307 | ] 2308 | 2309 | [[package]] 2310 | name = "webpki-roots" 2311 | version = "0.25.4" 2312 | source = "registry+https://github.com/rust-lang/crates.io-index" 2313 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2314 | 2315 | [[package]] 2316 | name = "winapi" 2317 | version = "0.3.9" 2318 | source = "registry+https://github.com/rust-lang/crates.io-index" 2319 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2320 | dependencies = [ 2321 | "winapi-i686-pc-windows-gnu", 2322 | "winapi-x86_64-pc-windows-gnu", 2323 | ] 2324 | 2325 | [[package]] 2326 | name = "winapi-i686-pc-windows-gnu" 2327 | version = "0.4.0" 2328 | source = "registry+https://github.com/rust-lang/crates.io-index" 2329 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2330 | 2331 | [[package]] 2332 | name = "winapi-x86_64-pc-windows-gnu" 2333 | version = "0.4.0" 2334 | source = "registry+https://github.com/rust-lang/crates.io-index" 2335 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2336 | 2337 | [[package]] 2338 | name = "windows-core" 2339 | version = "0.52.0" 2340 | source = "registry+https://github.com/rust-lang/crates.io-index" 2341 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2342 | dependencies = [ 2343 | "windows-targets 0.52.6", 2344 | ] 2345 | 2346 | [[package]] 2347 | name = "windows-sys" 2348 | version = "0.48.0" 2349 | source = "registry+https://github.com/rust-lang/crates.io-index" 2350 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2351 | dependencies = [ 2352 | "windows-targets 0.48.5", 2353 | ] 2354 | 2355 | [[package]] 2356 | name = "windows-sys" 2357 | version = "0.52.0" 2358 | source = "registry+https://github.com/rust-lang/crates.io-index" 2359 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2360 | dependencies = [ 2361 | "windows-targets 0.52.6", 2362 | ] 2363 | 2364 | [[package]] 2365 | name = "windows-targets" 2366 | version = "0.48.5" 2367 | source = "registry+https://github.com/rust-lang/crates.io-index" 2368 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2369 | dependencies = [ 2370 | "windows_aarch64_gnullvm 0.48.5", 2371 | "windows_aarch64_msvc 0.48.5", 2372 | "windows_i686_gnu 0.48.5", 2373 | "windows_i686_msvc 0.48.5", 2374 | "windows_x86_64_gnu 0.48.5", 2375 | "windows_x86_64_gnullvm 0.48.5", 2376 | "windows_x86_64_msvc 0.48.5", 2377 | ] 2378 | 2379 | [[package]] 2380 | name = "windows-targets" 2381 | version = "0.52.6" 2382 | source = "registry+https://github.com/rust-lang/crates.io-index" 2383 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2384 | dependencies = [ 2385 | "windows_aarch64_gnullvm 0.52.6", 2386 | "windows_aarch64_msvc 0.52.6", 2387 | "windows_i686_gnu 0.52.6", 2388 | "windows_i686_gnullvm", 2389 | "windows_i686_msvc 0.52.6", 2390 | "windows_x86_64_gnu 0.52.6", 2391 | "windows_x86_64_gnullvm 0.52.6", 2392 | "windows_x86_64_msvc 0.52.6", 2393 | ] 2394 | 2395 | [[package]] 2396 | name = "windows_aarch64_gnullvm" 2397 | version = "0.48.5" 2398 | source = "registry+https://github.com/rust-lang/crates.io-index" 2399 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2400 | 2401 | [[package]] 2402 | name = "windows_aarch64_gnullvm" 2403 | version = "0.52.6" 2404 | source = "registry+https://github.com/rust-lang/crates.io-index" 2405 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2406 | 2407 | [[package]] 2408 | name = "windows_aarch64_msvc" 2409 | version = "0.48.5" 2410 | source = "registry+https://github.com/rust-lang/crates.io-index" 2411 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2412 | 2413 | [[package]] 2414 | name = "windows_aarch64_msvc" 2415 | version = "0.52.6" 2416 | source = "registry+https://github.com/rust-lang/crates.io-index" 2417 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2418 | 2419 | [[package]] 2420 | name = "windows_i686_gnu" 2421 | version = "0.48.5" 2422 | source = "registry+https://github.com/rust-lang/crates.io-index" 2423 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2424 | 2425 | [[package]] 2426 | name = "windows_i686_gnu" 2427 | version = "0.52.6" 2428 | source = "registry+https://github.com/rust-lang/crates.io-index" 2429 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2430 | 2431 | [[package]] 2432 | name = "windows_i686_gnullvm" 2433 | version = "0.52.6" 2434 | source = "registry+https://github.com/rust-lang/crates.io-index" 2435 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2436 | 2437 | [[package]] 2438 | name = "windows_i686_msvc" 2439 | version = "0.48.5" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2442 | 2443 | [[package]] 2444 | name = "windows_i686_msvc" 2445 | version = "0.52.6" 2446 | source = "registry+https://github.com/rust-lang/crates.io-index" 2447 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2448 | 2449 | [[package]] 2450 | name = "windows_x86_64_gnu" 2451 | version = "0.48.5" 2452 | source = "registry+https://github.com/rust-lang/crates.io-index" 2453 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2454 | 2455 | [[package]] 2456 | name = "windows_x86_64_gnu" 2457 | version = "0.52.6" 2458 | source = "registry+https://github.com/rust-lang/crates.io-index" 2459 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2460 | 2461 | [[package]] 2462 | name = "windows_x86_64_gnullvm" 2463 | version = "0.48.5" 2464 | source = "registry+https://github.com/rust-lang/crates.io-index" 2465 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2466 | 2467 | [[package]] 2468 | name = "windows_x86_64_gnullvm" 2469 | version = "0.52.6" 2470 | source = "registry+https://github.com/rust-lang/crates.io-index" 2471 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2472 | 2473 | [[package]] 2474 | name = "windows_x86_64_msvc" 2475 | version = "0.48.5" 2476 | source = "registry+https://github.com/rust-lang/crates.io-index" 2477 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2478 | 2479 | [[package]] 2480 | name = "windows_x86_64_msvc" 2481 | version = "0.52.6" 2482 | source = "registry+https://github.com/rust-lang/crates.io-index" 2483 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2484 | 2485 | [[package]] 2486 | name = "winreg" 2487 | version = "0.50.0" 2488 | source = "registry+https://github.com/rust-lang/crates.io-index" 2489 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2490 | dependencies = [ 2491 | "cfg-if", 2492 | "windows-sys 0.48.0", 2493 | ] 2494 | 2495 | [[package]] 2496 | name = "write16" 2497 | version = "1.0.0" 2498 | source = "registry+https://github.com/rust-lang/crates.io-index" 2499 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2500 | 2501 | [[package]] 2502 | name = "writeable" 2503 | version = "0.5.5" 2504 | source = "registry+https://github.com/rust-lang/crates.io-index" 2505 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2506 | 2507 | [[package]] 2508 | name = "yaml-rust" 2509 | version = "0.4.5" 2510 | source = "registry+https://github.com/rust-lang/crates.io-index" 2511 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2512 | dependencies = [ 2513 | "linked-hash-map", 2514 | ] 2515 | 2516 | [[package]] 2517 | name = "yoke" 2518 | version = "0.7.5" 2519 | source = "registry+https://github.com/rust-lang/crates.io-index" 2520 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2521 | dependencies = [ 2522 | "serde", 2523 | "stable_deref_trait", 2524 | "yoke-derive", 2525 | "zerofrom", 2526 | ] 2527 | 2528 | [[package]] 2529 | name = "yoke-derive" 2530 | version = "0.7.5" 2531 | source = "registry+https://github.com/rust-lang/crates.io-index" 2532 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2533 | dependencies = [ 2534 | "proc-macro2", 2535 | "quote", 2536 | "syn 2.0.98", 2537 | "synstructure", 2538 | ] 2539 | 2540 | [[package]] 2541 | name = "zerocopy" 2542 | version = "0.7.35" 2543 | source = "registry+https://github.com/rust-lang/crates.io-index" 2544 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2545 | dependencies = [ 2546 | "byteorder", 2547 | "zerocopy-derive", 2548 | ] 2549 | 2550 | [[package]] 2551 | name = "zerocopy-derive" 2552 | version = "0.7.35" 2553 | source = "registry+https://github.com/rust-lang/crates.io-index" 2554 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2555 | dependencies = [ 2556 | "proc-macro2", 2557 | "quote", 2558 | "syn 2.0.98", 2559 | ] 2560 | 2561 | [[package]] 2562 | name = "zerofrom" 2563 | version = "0.1.5" 2564 | source = "registry+https://github.com/rust-lang/crates.io-index" 2565 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2566 | dependencies = [ 2567 | "zerofrom-derive", 2568 | ] 2569 | 2570 | [[package]] 2571 | name = "zerofrom-derive" 2572 | version = "0.1.5" 2573 | source = "registry+https://github.com/rust-lang/crates.io-index" 2574 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2575 | dependencies = [ 2576 | "proc-macro2", 2577 | "quote", 2578 | "syn 2.0.98", 2579 | "synstructure", 2580 | ] 2581 | 2582 | [[package]] 2583 | name = "zerovec" 2584 | version = "0.10.4" 2585 | source = "registry+https://github.com/rust-lang/crates.io-index" 2586 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2587 | dependencies = [ 2588 | "yoke", 2589 | "zerofrom", 2590 | "zerovec-derive", 2591 | ] 2592 | 2593 | [[package]] 2594 | name = "zerovec-derive" 2595 | version = "0.10.3" 2596 | source = "registry+https://github.com/rust-lang/crates.io-index" 2597 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2598 | dependencies = [ 2599 | "proc-macro2", 2600 | "quote", 2601 | "syn 2.0.98", 2602 | ] 2603 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bilistream" 3 | version = "0.1.12" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | reqwest = { version = "0.11.27", features = [ 10 | "cookies", 11 | "json", 12 | "rustls-tls", 13 | ], default-features = false } 14 | reqwest-middleware = { version = "0.1.6", default-features = false } 15 | reqwest-retry = { version = "0.1.5", default-features = false } 16 | tokio = { version = "1", features = ["full"] } 17 | tokio-test = "0.4.2" 18 | serde = { version = "1.0.137", features = ["derive"] } 19 | serde_json = "1.0.81" 20 | serde_yaml = "0.8" 21 | # execute = "0.2.11" 22 | scraper = "0.13.0" 23 | serde_derive = "1.0.143" 24 | tracing = { version = "0.1.35", features = [ 25 | "max_level_debug", 26 | "release_max_level_info", 27 | ] } 28 | tracing-subscriber = "0.2.0" 29 | async-trait = "0.1.56" 30 | urlencoding = "2.1.0" 31 | m3u8-rs = "4.0.0" 32 | rand = "0.8.5" 33 | regex = "1.6.0" 34 | prettyish-html = "0.1.0" 35 | 36 | gotify = { version = "0.4.0", features = ["app", "rustls-tls"], default-features = false } 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 构建阶段 2 | FROM rust:alpine AS builder 3 | 4 | # 安装构建依赖 5 | RUN apk add --no-cache \ 6 | musl-dev \ 7 | pkgconfig 8 | 9 | # 设置工作目录 10 | WORKDIR /usr/src/bilistream 11 | 12 | # 复制项目文件 13 | COPY . . 14 | 15 | # 构建项目 16 | RUN cargo build --release && \ 17 | strip target/release/bilistream 18 | 19 | # Python 依赖安装阶段 20 | FROM alpine:latest AS python-deps 21 | 22 | # 安装 Python 和创建虚拟环境 23 | RUN apk add --no-cache python3 py3-pip && \ 24 | python3 -m venv /opt/venv && \ 25 | /opt/venv/bin/pip install --no-cache-dir yt-dlp 26 | 27 | # 最终运行阶段 28 | FROM ghcr.io/jrottenberg/ffmpeg:7.1-scratch 29 | 30 | # 清除默认的 ENTRYPOINT 31 | ENTRYPOINT [] 32 | 33 | # 从 Alpine 复制 Python 运行时和虚拟环境 34 | COPY --from=python-deps /usr/bin/python3 /usr/bin/ 35 | COPY --from=python-deps /usr/lib/python3.* /usr/lib/python3.*/ 36 | COPY --from=python-deps /opt/venv /opt/venv 37 | 38 | # 设置工作目录 39 | WORKDIR /app 40 | 41 | # 从构建阶段复制二进制文件 42 | COPY --from=builder /usr/src/bilistream/target/release/bilistream /app/ 43 | 44 | # 设置权限 45 | RUN chmod +x /app/bilistream 46 | 47 | # 设置环境变量 48 | ENV PATH="/opt/venv/bin:$PATH" 49 | ENV PYTHONPATH="/opt/venv/lib/python3.12/site-packages" 50 | 51 | # 设置容器启动命令 52 | CMD ["/app/bilistream"] 53 | -------------------------------------------------------------------------------- /Dockerfile.full: -------------------------------------------------------------------------------- 1 | # 构建阶段 2 | FROM rust:alpine AS builder 3 | 4 | # 安装构建依赖 5 | RUN apk add --no-cache \ 6 | musl-dev \ 7 | pkgconfig 8 | 9 | # 设置工作目录 10 | WORKDIR /usr/src/bilistream 11 | 12 | # 复制项目文件 13 | COPY . . 14 | 15 | # 构建项目并设置权限 16 | RUN cargo build --release && \ 17 | strip target/release/bilistream && \ 18 | chmod +x target/release/bilistream 19 | 20 | # 最终运行阶段 21 | FROM ghcr.io/jim60105/yt-dlp:latest 22 | 23 | # 清除默认的 ENTRYPOINT 24 | ENTRYPOINT [] 25 | 26 | # 设置工作目录 27 | WORKDIR /app 28 | 29 | # 从构建阶段复制二进制文件 30 | COPY --from=builder /usr/src/bilistream/target/release/bilistream /app/ 31 | 32 | # 设置容器启动命令 33 | CMD ["/app/bilistream"] 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | | |__ (_) (_)___| |_ _ __ ___ __ _ _ __ ___ 3 | | '_ \| | | / __| __| '__/ _ \/ _` | '_ ` _ \ 4 | | |_) | | | \__ \ |_| | | __/ (_| | | | | | | 5 | |_.__/|_|_|_|___/\__|_| \___|\__,_|_| |_| |_| 6 | ``` 7 | 8 | 9 | 10 | # bilistream 11 | 12 | bilistream是一个支持无人值守自动转播Twitch和Youtube(包括预告类型直播)的B站直播自动转播工具。 13 | 14 | ### QQ群: 715748617 15 | 16 | ## 使用指南 17 | 18 | ### Docker 部署(推荐) 19 | 20 | 推荐使用 Docker 来部署 bilistream,这是最简单和稳定的方式: 21 | 22 | 1. 安装 Docker 和 Docker Compose: 23 | - Windows/Mac: 安装 [Docker Desktop](https://www.docker.com/products/docker-desktop) 24 | - Linux: 参考 [Docker 安装指南](https://docs.docker.com/engine/install/) 25 | 26 | 2. 创建 docker-compose.yaml 文件: 27 | ```yaml 28 | services: 29 | bilistream: 30 | image: ghcr.io/limitcool/bilistream:latest 31 | container_name: bilistream 32 | volumes: 33 | - ./config.yaml:/app/config.yaml:ro # 挂载配置文件(只读) 34 | - ./cookies.txt:/app/cookies.txt:ro # 可选:挂载 cookies 文件(只读) 35 | restart: unless-stopped 36 | environment: 37 | - TZ=Asia/Shanghai # 设置时区 38 | ``` 39 | 40 | 3. 创建配置文件: 41 | ```bash 42 | # 创建配置文件 43 | touch config.yaml 44 | ``` 45 | 46 | 4. 编辑 config.yaml 文件(配置示例见下文) 47 | 48 | 5. 启动服务: 49 | ```bash 50 | docker-compose up -d 51 | ``` 52 | 53 | 6. 查看运行状态: 54 | ```bash 55 | # 查看日志 56 | docker-compose logs -f 57 | 58 | # 查看容器状态 59 | docker-compose ps 60 | ``` 61 | 62 | 7. 停止服务: 63 | ```bash 64 | docker-compose down 65 | ``` 66 | 67 | 注意事项: 68 | - 确保配置文件中的路径使用容器内的路径(如 cookies 文件路径应该是 `/app/cookies.txt`) 69 | - 如果需要使用代理,在 docker-compose.yaml 中添加: 70 | ```yaml 71 | network_mode: "host" # 如果使用本地代理 72 | environment: 73 | - http_proxy=http://host.docker.internal:7890 74 | - https_proxy=http://host.docker.internal:7890 75 | ``` 76 | 77 | ### 二进制部署 78 | 79 | 如果您不想使用 Docker,也可以直接下载二进制文件运行。首先需要安装以下依赖: 80 | 81 | ```bash 82 | # Debian/Ubuntu 83 | apt update 84 | apt install ffmpeg python3-pip -y 85 | 86 | # CentOS 87 | yum install ffmpeg -y 88 | 89 | # 安装 yt-dlp 90 | pip3 install -U yt-dlp 91 | ``` 92 | 93 | ## 使用指南 94 | 95 | ### 下载和安装 96 | 97 | 根据您的系统选择对应的版本: 98 | 99 | #### Windows 100 | - x64系统: 下载 `bilistream-v{版本号}-x86_64-pc-windows-msvc.zip` 101 | - ARM64系统: 下载 `bilistream-v{版本号}-aarch64-pc-windows-msvc.zip` 102 | 103 | 下载后解压,直接运行 `bilistream.exe` 即可。 104 | 105 | #### Linux 106 | - x64系统: 下载 `bilistream-v{版本号}-x86_64-unknown-linux-musl.tar.gz` 107 | - ARM64系统: 下载 `bilistream-v{版本号}-aarch64-unknown-linux-gnu.tar.gz` 108 | 109 | ```bash 110 | # 解压 111 | tar -xzf bilistream-v{版本号}-{架构}.tar.gz 112 | cd bilistream-v{版本号}-{架构} 113 | # 运行 114 | ./bilistream 115 | ``` 116 | 117 | #### macOS 118 | - Intel芯片: 下载 `bilistream-v{版本号}-x86_64-apple-darwin.tar.gz` 119 | - M系列芯片: 下载 `bilistream-v{版本号}-aarch64-apple-darwin.tar.gz` 120 | 121 | ```bash 122 | # 解压 123 | tar -xzf bilistream-v{版本号}-{架构}.tar.gz 124 | cd bilistream-v{版本号}-{架构} 125 | # 运行 126 | ./bilistream 127 | ``` 128 | 129 | ### 验证下载 130 | 131 | 每个发布包都附带了 SHA256 校验和文件(.sha256 后缀),您可以使用它来验证下载的完整性: 132 | 133 | ```bash 134 | # Windows (PowerShell) 135 | Get-FileHash bilistream-v{版本号}-{架构}.zip -Algorithm SHA256 136 | 137 | # Linux/macOS 138 | shasum -a 256 bilistream-v{版本号}-{架构}.tar.gz 139 | ``` 140 | 141 | 将输出的哈希值与对应的 .sha256 文件中的内容进行比对。 142 | 143 | ### 配置 144 | 145 | 在程序所在目录新建 `config.yaml` 文件: 146 | 147 | ``` 148 | touch config.yaml 149 | ``` 150 | 151 | 将以下内容填写至 `config.yaml` 文件内: 152 | 153 | 154 | ``` yaml 155 | # 检测直播间隔 156 | Interval: 60 157 | # 需要转播的平台 Twitch || Youtube || YoutubePreviewLive 158 | Platform: Twitch 159 | # B站推流账号Cookie 160 | BiliLive: 161 | SESSDATA: 162 | bili_jct: 163 | DedeUserID: 2235894 164 | DedeUserID__ckMd5: 165 | Room: 660428 166 | BiliRtmpUrl: rtmp://live-push.bilivideo.com/live-bvc/ 167 | # BiliRtmpUrl: B站开播设置页面的服务器地址 168 | BiliRtmpKey: "?streamname=live_0000000_0000000&key=xxxxxxxxxxb8289c6acc97xxxxxxxxx&schedule=rtmp&pflag=1" 169 | # BiliRtmpKey: B站开播设置页面的串流密钥,需注意,由于是?号开头的,本行需要对内容加双引号 170 | # Twitch 直播间Id 171 | Twitch: 172 | # Room: maximilian_dood 173 | Room: 174 | # youtube 需要使用Youtube API AK以及Yt-dlp 175 | Youtube: 176 | Room: UC1zFJrfEKvCixhsjNSb1toQ 177 | AccessToken: 178 | # youtube 预告类型直播转播请填写以下内容 179 | YoutubePreviewLive: 180 | ChannelId: UC1zFJrfEKvCixhsjNSb1toQ 181 | FfmpegProxy: http://127.0.0.1:7890 182 | # Ffmpeg代理地址,无需代理可以不填此项或者留空 183 | 184 | ### Gotify推送配置 (可选) 185 | 186 | # 如果您想使用Gotify进行推送通知,请在`config.yaml`中添加以下配置: 187 | 188 | Gotify: 189 | url: "https://example.com/gotify" 190 | token: "your_gotify_token_here" 191 | 192 | ### Cookies配置 (可选) 193 | # 如果需要转播会员限定或需要登录的内容,可以配置cookies 194 | Cookies: "/path/to/cookies.txt" # cookies文件路径,支持YouTube和Twitch 195 | ``` 196 | 197 | ## Youtube API申请地址 198 | 199 | https://developers.google.com/youtube/v3 200 | 201 | ## 常见问题FAQ 202 | 203 | - Q: 转播时出现 Input/output error 204 | - A: 可能是BiliRtmpUrl及BiliRtmpKey填写错误或使用海外机器进行推流。B站不支持海外机器推流,建议使用国内服务器+代理推流。 205 | - Q: 转播Youtube时出现Connection to tcp://manifest.googlevideo.com:443 failed: Error number -138 occurred 206 | - A: 可能是Ffmpeg拉流未通过代理,请在配置项填写 FfmpegProxy: [http://127.0.0.1:7890。](http://127.0.0.1:7890。/) 207 | - Q: 如何获取cookies文件? 208 | - A: 可以使用浏览器扩展(如Get cookies.txt)导出Netscape格式的cookies文件。对于YouTube和Twitch,需要先在浏览器中登录账号,然后导出cookies。 209 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | bilistream: 3 | image: ghcr.io/limitcool/bilistream:v0.1.12 4 | container_name: bilistream 5 | volumes: 6 | - ./config.yaml:/app/config.yaml:ro # 挂载配置文件(只读) 7 | - ./cookies.txt:/app/cookies.txt:ro # 可选:挂载 cookies 文件(只读) 8 | restart: unless-stopped 9 | environment: 10 | - TZ=Asia/Shanghai # 设置时区 11 | # 如果需要使用代理,取消下面的注释并修改地址 12 | # network_mode: "host" # 如果使用本地代理,可能需要 host 网络模式 13 | # environment: 14 | # - http_proxy=http://host.docker.internal:7890 15 | # - https_proxy=http://host.docker.internal:7890 16 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::error::Error; 3 | use std::path::Path; 4 | 5 | #[derive(Debug, Serialize, Deserialize, Clone)] 6 | pub struct Config { 7 | #[serde(rename = "BiliLive")] 8 | pub bililive: BiliLive, 9 | #[serde(rename = "Twitch")] 10 | pub twitch: TwitchC, 11 | #[serde(rename = "Interval")] 12 | pub interval: u64, 13 | #[serde(rename = "Youtube")] 14 | pub youtube: YoutubeC, 15 | #[serde(rename = "Platform")] 16 | pub platform: String, 17 | #[serde(rename = "Email")] 18 | pub email: Option, 19 | #[serde(rename = "YoutubePreviewLive")] 20 | pub youtube_preview_live: YoutubePreviewLive, 21 | #[serde(rename = "FfmpegProxy")] 22 | pub ffmpeg_proxy: Option, 23 | #[serde(rename = "Gotify")] 24 | pub gotify: Option, 25 | #[serde(rename = "Cookies")] 26 | pub cookies: Option, 27 | } 28 | 29 | #[derive(Debug, Serialize, Deserialize, Clone)] 30 | pub struct BiliLive { 31 | #[serde(rename = "SESSDATA")] 32 | pub sessdata: String, 33 | pub bili_jct: String, 34 | #[serde(rename = "DedeUserID")] 35 | pub dede_user_id: String, 36 | #[serde(rename = "DedeUserID__ckMd5")] 37 | pub dede_user_id_ckmd5: String, 38 | #[serde(rename = "Room")] 39 | pub room: i32, 40 | #[serde(rename = "BiliRtmpUrl")] 41 | pub bili_rtmp_url: String, 42 | #[serde(rename = "BiliRtmpKey")] 43 | pub bili_rtmp_key: String, 44 | } 45 | 46 | #[derive(Debug, Serialize, Deserialize, Clone)] 47 | pub struct TwitchC { 48 | #[serde(rename = "Room")] 49 | pub room: String, 50 | } 51 | 52 | #[derive(Debug, Serialize, Deserialize, Clone)] 53 | pub struct YoutubeC { 54 | #[serde(rename = "Room")] 55 | pub room: String, 56 | #[serde(rename = "AccessToken")] 57 | pub access_token: String, 58 | } 59 | 60 | #[derive(Serialize, Deserialize, Debug, Clone)] 61 | pub struct EmailConfig { 62 | #[serde(rename = "To")] 63 | pub to: String, 64 | 65 | #[serde(rename = "Subject")] 66 | pub subject: String, 67 | 68 | #[serde(rename = "Body")] 69 | pub body: String, 70 | 71 | #[serde(rename = "Host")] 72 | pub host: String, 73 | 74 | #[serde(rename = "Sender")] 75 | pub sender: String, 76 | 77 | #[serde(rename = "Password")] 78 | pub password: String, 79 | } 80 | 81 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 82 | #[serde(rename_all = "camelCase")] 83 | pub struct YoutubePreviewLive { 84 | #[serde(rename = "ChannelId")] 85 | pub channel_id: String, 86 | } 87 | 88 | // 读取配置文件 89 | pub fn load_config(config: &Path) -> Result> { 90 | let file = std::fs::File::open(config)?; 91 | let config: Config = serde_yaml::from_reader(file)?; 92 | // println!("body = {:?}", client); 93 | Ok(config) 94 | } 95 | 96 | #[derive(Debug, Serialize, Deserialize, Clone)] 97 | pub struct GotifyConfig { 98 | #[serde(rename = "Url")] 99 | pub url: String, 100 | #[serde(rename = "Token")] 101 | pub token: String, 102 | } 103 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod plugins; 3 | mod push; // 新增这行 4 | 5 | // use tracing::info; 6 | use config::{load_config, Config}; 7 | use plugins::select_live; 8 | use reqwest::{cookie::Jar, Url}; 9 | use reqwest_middleware::ClientBuilder; 10 | use reqwest_retry::policies::ExponentialBackoff; 11 | use reqwest_retry::RetryTransientMiddleware; 12 | use std::path::Path; 13 | use std::process::Command; 14 | use std::time::Duration; 15 | use tokio; 16 | use tracing_subscriber::{ 17 | filter::EnvFilter, 18 | fmt::{self}, 19 | layer::SubscriberExt, 20 | util::SubscriberInitExt, 21 | }; 22 | 23 | use crate::plugins::get_live_id_by_jump; 24 | use crate::push::send_gotify_notification; // 新增这行 25 | 26 | #[tokio::main] 27 | async fn main() { 28 | // let p = Mirai::new(host, target); 29 | // let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); 30 | // 只有注册 subscriber 后, 才能在控制台上看到日志输出 31 | tracing_subscriber::registry() 32 | // .with(env_filter) 33 | .with(fmt::layer()) 34 | .init(); 35 | let cfg = load_config(Path::new("./config.yaml")).unwrap(); 36 | let mut r = select_live(cfg.clone()).await.unwrap(); 37 | // 设置tracing日志等级为Info 38 | 39 | loop { 40 | if r.get_status().await.unwrap_or(false) { 41 | tracing::info!("{}", format!("{}直播中", r.room())); 42 | 43 | // 添加Gotify推送 44 | if let Some(ref gotify_config) = cfg.gotify { 45 | send_gotify_notification( 46 | &gotify_config, 47 | &format!("{}开始直播", r.room()), 48 | "bilistream", 49 | ) 50 | .await; 51 | } 52 | 53 | 54 | if get_bili_live_state(cfg.bililive.room.clone()).await { 55 | tracing::info!("B站直播中"); 56 | ffmpeg( 57 | cfg.bililive.bili_rtmp_url.clone(), 58 | cfg.bililive.bili_rtmp_key.clone(), 59 | r.get_real_m3u8_url().await.unwrap(), 60 | cfg.ffmpeg_proxy.clone(), 61 | ); 62 | } else { 63 | tracing::info!("B站未直播"); 64 | bili_start_live(&cfg).await; 65 | tracing::info!("B站已开播"); 66 | ffmpeg( 67 | cfg.bililive.bili_rtmp_url.clone(), 68 | cfg.bililive.bili_rtmp_key.clone(), 69 | r.get_real_m3u8_url().await.unwrap(), 70 | cfg.ffmpeg_proxy.clone(), 71 | ); 72 | loop { 73 | if r.get_status().await.unwrap() { 74 | ffmpeg( 75 | cfg.bililive.bili_rtmp_url.clone(), 76 | cfg.bililive.bili_rtmp_key.clone(), 77 | r.get_real_m3u8_url().await.unwrap(), 78 | cfg.ffmpeg_proxy.clone(), 79 | ); 80 | } else { 81 | break; 82 | } 83 | tokio::time::sleep(Duration::from_secs(5)).await; 84 | } 85 | } 86 | } else { 87 | tracing::info!("{}", format!("{}未直播", r.room())); 88 | if get_bili_live_state(cfg.bililive.room.clone()).await { 89 | tracing::info!("B站直播中"); 90 | bili_stop_live(&cfg).await; 91 | tracing::info!("B站已关播"); 92 | } 93 | } 94 | // 判断是否预告类型 95 | if cfg.platform == "YoutubePreviewLive" { 96 | tracing::info!("检测到预告类型,正在重新获取直播间"); 97 | r.set_room( 98 | get_live_id_by_jump(cfg.youtube_preview_live.channel_id.as_str()) 99 | .await 100 | .unwrap() 101 | .as_str(), 102 | ) 103 | } 104 | // 每60秒检测一下直播状态 105 | tokio::time::sleep(Duration::from_secs(cfg.interval)).await; 106 | } 107 | } 108 | 109 | // 获取B站直播状态 110 | async fn get_bili_live_state(room: i32) -> bool { 111 | // 设置最大重试次数为4294967295次 112 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(4294967295); 113 | let raw_client = reqwest::Client::builder() 114 | .cookie_store(true) 115 | // 设置超时时间为30秒 116 | .timeout(Duration::new(30, 0)) 117 | .build() 118 | .unwrap(); 119 | let client = ClientBuilder::new(raw_client.clone()) 120 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 121 | .build(); 122 | let res:serde_json::Value = client 123 | .get(format!("https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?room_id={}&platform=web",room)) 124 | 125 | .send() 126 | .await 127 | .unwrap() 128 | .json() 129 | .await 130 | .unwrap(); 131 | // println!("{:#?}",res["data"]["live_status"]); 132 | if res["data"]["live_status"] == 0 { 133 | return false; 134 | } else { 135 | return true; 136 | } 137 | } 138 | 139 | // bilibili开播 140 | async fn bili_start_live(cfg: &Config) { 141 | let cookie = format!( 142 | "SESSDATA={};bili_jct={};DedeUserID={};DedeUserID__ckMd5={}", 143 | cfg.bililive.sessdata, 144 | cfg.bililive.bili_jct, 145 | cfg.bililive.dede_user_id, 146 | cfg.bililive.dede_user_id_ckmd5 147 | ); 148 | let url = "https://api.live.bilibili.com/".parse::().unwrap(); 149 | let jar = Jar::default(); 150 | jar.add_cookie_str(cookie.as_str(), &url); 151 | // 设置最大重试次数为4294967295次 152 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(4294967295); 153 | let raw_client = reqwest::Client::builder() 154 | .cookie_store(true) 155 | .cookie_provider(jar.into()) 156 | // 设置超时时间为30秒 157 | .timeout(Duration::new(30, 0)) 158 | .build() 159 | .unwrap(); 160 | let client = ClientBuilder::new(raw_client.clone()) 161 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 162 | .build(); 163 | let _res: serde_json::Value = client 164 | .post("https://api.live.bilibili.com/room/v1/Room/startLive") 165 | .header("Accept", "application/json, text/plain, */*") 166 | .header( 167 | "content-type", 168 | "application/x-www-form-urlencoded; charset=UTF-8", 169 | ) 170 | .body(format!( 171 | "room_id={}&platform=pc&area_v2=433&csrf_token={}&csrf={}", 172 | cfg.bililive.room, cfg.bililive.bili_jct, cfg.bililive.bili_jct 173 | )) 174 | .send() 175 | .await 176 | .unwrap() 177 | .json() 178 | .await 179 | .unwrap(); 180 | // println!("{:#?}",res); 181 | } 182 | 183 | // bilibili关播 184 | async fn bili_stop_live(cfg: &Config) { 185 | let cookie = format!( 186 | "SESSDATA={};bili_jct={};DedeUserID={};DedeUserID__ckMd5={}", 187 | cfg.bililive.sessdata, 188 | cfg.bililive.bili_jct, 189 | cfg.bililive.dede_user_id, 190 | cfg.bililive.dede_user_id_ckmd5 191 | ); 192 | let url = "https://api.live.bilibili.com/".parse::().unwrap(); 193 | let jar = Jar::default(); 194 | jar.add_cookie_str(cookie.as_str(), &url); 195 | // 设置最大重试次数为4294967295次 196 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(4294967295); 197 | let raw_client = reqwest::Client::builder() 198 | .cookie_store(true) 199 | .cookie_provider(jar.into()) 200 | // 设置超时时间为30秒 201 | .timeout(Duration::new(30, 0)) 202 | .build() 203 | .unwrap(); 204 | let client = ClientBuilder::new(raw_client.clone()) 205 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 206 | .build(); 207 | let _res: serde_json::Value = client 208 | .post("https://api.live.bilibili.com/room/v1/Room/stopLive") 209 | .header("Accept", "application/json, text/plain, */*") 210 | .header( 211 | "content-type", 212 | "application/x-www-form-urlencoded; charset=UTF-8", 213 | ) 214 | .body(format!( 215 | "room_id={}&platform=pc&csrf_token={}&csrf={}", 216 | cfg.bililive.room, cfg.bililive.bili_jct, cfg.bililive.bili_jct 217 | )) 218 | .send() 219 | .await 220 | .unwrap() 221 | .json() 222 | .await 223 | .unwrap(); 224 | // println!("{:#?}",res); 225 | } 226 | 227 | pub fn ffmpeg(rtmp_url: String, rtmp_key: String, m3u8_url: String, ffmpeg_proxy: Option) { 228 | // let cmd = format!("{}&key={}",rtmp_url,rtmp_key); 229 | let cmd = format!("{}{}", rtmp_url, rtmp_key); 230 | let mut command = Command::new("ffmpeg"); 231 | // if ffmpeg_proxy.clone()!= "" { 232 | // command.arg(ffmpeg_proxy.clone()); 233 | // } 234 | if ffmpeg_proxy.is_some() { 235 | command.arg("-http_proxy"); 236 | command.arg(ffmpeg_proxy.clone().unwrap()); 237 | } 238 | command.arg("-re"); 239 | command.arg("-i"); 240 | command.arg(m3u8_url.clone()); 241 | command.arg("-vcodec"); 242 | command.arg("copy"); 243 | command.arg("-acodec"); 244 | command.arg("aac"); 245 | command.arg("-f"); 246 | command.arg("flv"); 247 | command.arg(cmd); 248 | match command.status().unwrap().code() { 249 | Some(code) => { 250 | println!("Exit Status: {}", code); 251 | if code == 0 { 252 | println!("Command executed successfully"); 253 | } else { 254 | ffmpeg(rtmp_url, rtmp_key, m3u8_url, ffmpeg_proxy) 255 | } 256 | } 257 | None => { 258 | println!("Process terminated."); 259 | } 260 | } 261 | } 262 | 263 | // #[cfg(test)] 264 | // mod tests { 265 | // use super::*; 266 | // use config::GotifyConfig; 267 | // use tokio; 268 | 269 | // #[tokio::test] 270 | // async fn test_send_gotify_notification() { 271 | // // 创建一个模拟的GotifyConfig 272 | // let config = GotifyConfig { 273 | // url: "https://gotify.com".to_string(), 274 | // token: "".to_string(), 275 | // }; 276 | 277 | // // 准备测试消息 278 | // let message = "这是一条测试通知"; 279 | 280 | // // 调用发送通知函数 281 | // send_gotify_notification(&config, message, "bilistream测试").await; 282 | // } 283 | // } 284 | -------------------------------------------------------------------------------- /src/plugins/live.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use async_trait::async_trait; 3 | use reqwest_middleware::ClientBuilder; 4 | use reqwest_retry::policies::ExponentialBackoff; 5 | use reqwest_retry::RetryTransientMiddleware; 6 | use serde_json::Value; 7 | use std::error::Error; 8 | use std::time::Duration; 9 | 10 | use super::{Twitch, Youtube}; 11 | 12 | #[allow(dead_code)] 13 | /// Status of the live stream 14 | pub enum Status { 15 | /// Stream is online. 16 | Online, 17 | /// Stream is offline. 18 | Offline, 19 | /// The status of the stream could not be determined. 20 | Unknown, 21 | } 22 | 23 | #[async_trait] 24 | pub trait Live { 25 | async fn get_status(&self) -> Result>; 26 | fn room(&self) -> &str; 27 | async fn get_real_m3u8_url(&self) -> Result>; 28 | fn set_room(&mut self, room: &str); 29 | } 30 | pub async fn select_live(cfg: Config) -> Result, Box> { 31 | // 设置最大重试次数为4294967295次 32 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(4294967295); 33 | let raw_client = reqwest::Client::builder() 34 | .cookie_store(true) 35 | // 设置超时时间为30秒 36 | .timeout(Duration::new(30, 0)) 37 | .build() 38 | .unwrap(); 39 | let client = ClientBuilder::new(raw_client.clone()) 40 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 41 | .build(); 42 | match cfg.platform.as_str() { 43 | "Youtube" => Ok(Box::new(Youtube::new( 44 | cfg.youtube.room.as_str(), 45 | cfg.youtube.access_token.clone(), 46 | client.clone(), 47 | cfg.clone(), 48 | ))), 49 | "Twitch" => Ok(Box::new(Twitch::new( 50 | cfg.twitch.room.as_str(), 51 | client.clone(), 52 | cfg.clone(), 53 | ))), 54 | "YoutubePreviewLive" => { 55 | let room_id = get_live_id_by_jump(cfg.youtube_preview_live.channel_id.as_str()) 56 | .await 57 | .unwrap(); 58 | Ok(Box::new(Youtube::new( 59 | room_id.as_str(), 60 | cfg.youtube.access_token.clone(), 61 | client.clone(), 62 | cfg.clone(), 63 | ))) 64 | } 65 | _ => Err("unknown platform".into()), 66 | } 67 | } 68 | 69 | // https://www.youtube.com/channel/UC1zFJrfEKvCixhsjNSb1toQ 70 | // 通过channel_name获取channel_id 71 | #[allow(dead_code)] 72 | async fn get_channel_id(channel_name: &str) -> Result> { 73 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(4294967295); 74 | let raw_client = reqwest::Client::builder() 75 | .cookie_store(true) 76 | // 设置超时时间为30秒 77 | .timeout(Duration::new(30, 0)) 78 | .build() 79 | .unwrap(); 80 | let client = ClientBuilder::new(raw_client.clone()) 81 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 82 | .build(); 83 | let url = format!("https://www.youtube.com/c/{}", channel_name); 84 | let res = client.get(&url).send().await?; 85 | let body = res.text().await?; 86 | let room_id = body 87 | .split("\"channelId\":\"") 88 | .nth(1) 89 | .unwrap() 90 | .split("\"") 91 | .nth(0) 92 | .unwrap(); 93 | Ok(room_id.to_string()) 94 | } 95 | 96 | #[allow(dead_code)] 97 | // 通过channel_id获取live_id 98 | pub async fn get_live_id(channel_name: &str) -> Result> { 99 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(1); 100 | let raw_client = reqwest::Client::builder() 101 | .cookie_store(true) 102 | // 设置超时时间为30秒 103 | .timeout(Duration::new(30, 0)) 104 | .build() 105 | .unwrap(); 106 | let client = ClientBuilder::new(raw_client.clone()) 107 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 108 | .build(); 109 | let url = format!("https://www.youtube.com/channel/{}", channel_name); 110 | tracing::debug!("{}", url); 111 | // println!("channel地址为:{}", url); 112 | let res = client.get(&url).send().await?; 113 | let body = res.text().await?; 114 | // 保存body为文件,后缀为html 115 | let html = prettyish_html::prettify(body.as_str()); 116 | // let mut file = std::fs::File::create("body.html").unwrap(); 117 | // std::io::Write::write_all(&mut file, html.as_bytes()).unwrap(); 118 | 119 | let re = regex::Regex::new(r#"\s*"#) 120 | .unwrap(); 121 | // if re.is_match(html.as_str()) { 122 | // let live_id = re.captures(html.as_str()).unwrap().get(1).unwrap().as_str(); 123 | // let live_id = live_id.split("\"").nth(1).unwrap(); 124 | // println!("{}", live_id); 125 | // } else { 126 | // println!("no match"); 127 | // } 128 | for cap in re.captures(html.as_str()) { 129 | let json = cap.get(1).unwrap().as_str(); 130 | // let json = json.split(";").nth(0).unwrap(); 131 | // let json = json.split("=").nth(1).unwrap(); 132 | // let json = json.split(";").nth(0).unwrap(); 133 | // let json = json.split("}").nth(0).unwrap(); 134 | // let json = json.split("{").nth(1).unwrap(); 135 | // let json = json.split("\"").nth(1).unwrap(); 136 | // let json = json.split("\"").nth(0).unwrap(); 137 | let j: Value = serde_json::from_str(json).unwrap(); 138 | let mut video_id = j["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0] 139 | ["tabRenderer"]["content"]["sectionListRenderer"]["contents"][1]["itemSectionRenderer"] 140 | ["contents"][0]["shelfRenderer"]["content"]["horizontalListRenderer"]["items"][0] 141 | ["gridVideoRenderer"]["videoId"] 142 | .to_string(); 143 | if video_id == "null" { 144 | video_id = j["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"] 145 | ["content"]["sectionListRenderer"]["contents"][2]["itemSectionRenderer"] 146 | ["contents"][0]["shelfRenderer"]["content"]["horizontalListRenderer"]["items"][0] 147 | ["gridVideoRenderer"]["videoId"] 148 | .to_string(); 149 | } 150 | // println!("获取到的videoId为:{}", video_id); 151 | tracing::debug!( 152 | "{}", 153 | j["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"] 154 | ["sectionListRenderer"]["contents"][1]["itemSectionRenderer"]["contents"][0] 155 | ["shelfRenderer"]["content"]["horizontalListRenderer"]["items"][0] 156 | ["gridVideoRenderer"]["videoId"] 157 | ); 158 | // 将结果保存为一个json文件 159 | let mut file = std::fs::File::create("live_id.json").unwrap(); 160 | std::io::Write::write_all(&mut file, json.as_bytes()).unwrap(); 161 | if video_id == "null" { 162 | } else { 163 | return Ok(video_id); 164 | } 165 | } 166 | 167 | Err("获取video_id失败".into()) 168 | } 169 | 170 | 171 | #[allow(dead_code)] 172 | // 传入materials.canvases.0.image_id,返回 ["materials"]["canvases"][0]["image_id"] 173 | fn json_path_to_map_string(path: &str) -> String { 174 | let r = path.split("."); 175 | let mut s = String::new(); 176 | r.for_each(|x| { 177 | s.push_str(&format!("[\"{}\"]", x)); 178 | }); 179 | return s; 180 | } 181 | 182 | // 通过channel_id获取live_id 183 | pub async fn get_live_id_by_jump(channel_name: &str) -> Result> { 184 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(1); 185 | let raw_client = reqwest::Client::builder() 186 | .cookie_store(true) 187 | // 设置超时时间为30秒 188 | .timeout(Duration::new(30, 0)) 189 | .build() 190 | .unwrap(); 191 | let client = ClientBuilder::new(raw_client.clone()) 192 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 193 | .build(); 194 | let url = format!("https://www.youtube.com/channel/{}/live", channel_name); 195 | tracing::debug!("{}", url); 196 | // println!("channel地址为:{}", url); 197 | let res = client.get(&url).send().await?; 198 | let body = res.text().await?; 199 | // 保存body为文件,后缀为html 200 | let html = prettyish_html::prettify(body.as_str()); 201 | // let mut file = std::fs::File::create("jump.html").unwrap(); 202 | // std::io::Write::write_all(&mut file, html.as_bytes()).unwrap(); 203 | 204 | let re = regex::Regex::new(r#"\s*"#) 205 | .unwrap(); 206 | // if re.is_match(html.as_str()) { 207 | // let live_id = re.captures(html.as_str()).unwrap().get(1).unwrap().as_str(); 208 | // let live_id = live_id.split("\"").nth(1).unwrap(); 209 | // println!("{}", live_id); 210 | // } else { 211 | // println!("no match"); 212 | // } 213 | for cap in re.captures(html.as_str()) { 214 | let json = cap.get(1).unwrap().as_str(); 215 | let j: Value = serde_json::from_str(json).unwrap(); 216 | let mut video_id = j["contents"]["twoColumnWatchNextResults"]["results"]["results"] 217 | ["contents"][0]["videoPrimaryInfoRenderer"]["videoActions"]["menuRenderer"] 218 | ["topLevelButtons"][0]["toggleButtonRenderer"]["defaultNavigationEndpoint"] 219 | ["modalEndpoint"]["modal"]["modalWithTitleAndButtonRenderer"]["button"] 220 | ["buttonRenderer"]["navigationEndpoint"]["signInEndpoint"]["nextEndpoint"] 221 | ["watchEndpoint"]["videoId"] 222 | .to_string(); 223 | if video_id == "null" { 224 | video_id = j["currentVideoEndpoint"]["watchEndpoint"]["videoId"].to_string(); 225 | } 226 | // println!("获取到的videoId为:{}", video_id); 227 | tracing::debug!( 228 | "{}", 229 | j["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][0]["tabRenderer"]["content"] 230 | ["sectionListRenderer"]["contents"][1]["itemSectionRenderer"]["contents"][0] 231 | ["shelfRenderer"]["content"]["horizontalListRenderer"]["items"][0] 232 | ["gridVideoRenderer"]["videoId"] 233 | ); 234 | // 将结果保存为一个json文件 235 | let mut file = std::fs::File::create("jump_live_id.json").unwrap(); 236 | std::io::Write::write_all(&mut file, json.as_bytes()).unwrap(); 237 | if video_id == "null" { 238 | } else { 239 | return Ok(video_id); 240 | } 241 | } 242 | 243 | Err("获取video_id失败".into()) 244 | } 245 | 246 | pub async fn get_youtube_live_status(channel_name: &str) -> Result> { 247 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(1); 248 | let raw_client = reqwest::Client::builder() 249 | .cookie_store(true) 250 | // 设置超时时间为30秒 251 | .timeout(Duration::new(30, 0)) 252 | .build() 253 | .unwrap(); 254 | let client = ClientBuilder::new(raw_client.clone()) 255 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 256 | .build(); 257 | let url = format!("https://www.youtube.com/channel/{}/live", channel_name); 258 | tracing::debug!("{}", url); 259 | // println!("channel地址为:{}", url); 260 | let res = client.get(&url).send().await?; 261 | let body = res.text().await?; 262 | // 保存body为文件,后缀为html 263 | let html = prettyish_html::prettify(body.as_str()); 264 | // let mut file = std::fs::File::create("jump.html").unwrap(); 265 | // std::io::Write::write_all(&mut file, html.as_bytes()).unwrap(); 266 | 267 | let re = regex::Regex::new(r#"\s*"#) 268 | .unwrap(); 269 | // if re.is_match(html.as_str()) { 270 | // let live_id = re.captures(html.as_str()).unwrap().get(1).unwrap().as_str(); 271 | // let live_id = live_id.split("\"").nth(1).unwrap(); 272 | // println!("{}", live_id); 273 | // } else { 274 | // println!("no match"); 275 | // } 276 | for cap in re.captures(html.as_str()) { 277 | let json = cap.get(1).unwrap().as_str(); 278 | let j: Value = serde_json::from_str(json).unwrap(); 279 | let live_status = j["contents"]["twoColumnWatchNextResults"]["results"]["results"] 280 | ["contents"][0]["videoPrimaryInfoRenderer"]["viewCount"]["videoViewCountRenderer"] 281 | ["isLive"] 282 | .to_string(); 283 | // let mut file = std::fs::File::create("jump_live_id.json").unwrap(); 284 | // std::io::Write::write_all(&mut file, json.as_bytes()).unwrap(); 285 | // println!("live status{}", live_status); 286 | if live_status != "true" { 287 | return Ok(false); 288 | } else { 289 | return Ok(true); 290 | } 291 | } 292 | 293 | // Err("获取video_id失败".into()) 294 | return Ok(false); 295 | } 296 | 297 | // 测试get_room_id 传入UC1zFJrfEKvCixhsjNSb1toQ 298 | // #[cfg(test)] 299 | // mod tests { 300 | // use super::*; 301 | 302 | // macro_rules! aw { 303 | // ($e:expr) => { 304 | // tokio_test::block_on($e) 305 | // }; 306 | // } 307 | // #[test] 308 | // fn test_get_room_id() { 309 | // let channel_id = "GameSpun"; 310 | // let r = aw!(get_channel_id(channel_id)).unwrap(); 311 | // println!("id:{}", r); 312 | // } 313 | // #[test] 314 | // fn test_get_live_id() { 315 | // let channel_id = "UC1zFJrfEKvCixhsjNSb1toQ"; 316 | // let r = aw!(get_live_id(channel_id)).unwrap(); 317 | // println!("id:{}", r); 318 | // } 319 | // #[test] 320 | // fn test_json_path_to_string() { 321 | // let re = json_path_to_map_string("x.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.videoActions.menuRenderer.topLevelButtons[0].toggleButtonRenderer.defaultNavigationEndpoint.modalEndpoint.modal.modalWithTitleAndButtonRenderer.button.buttonRenderer.navigationEndpoint.signInEndpoint.nextEndpoint.watchEndpoint.videoId"); 322 | // println!("re:{}", re); 323 | // } 324 | // #[test] 325 | // fn test_get_jump_url() { 326 | // // lofi girl 327 | // let channel_id = "UCSJ4gkVC6NrvII8umztf0Ow"; 328 | // let r: String = aw!(get_live_id_by_jump(channel_id)).unwrap(); 329 | // println!("url:{}", r); 330 | // } 331 | // #[test] 332 | // fn tes_get_youtube_status() { 333 | // let channel_id = "UCcHWhgSsMBemnyLhg6GL1vA"; 334 | // let r = aw!(get_youtube_live_status(channel_id)).unwrap(); 335 | // println!("直播状态为:{}", r); 336 | // } 337 | // } 338 | -------------------------------------------------------------------------------- /src/plugins/mod.rs: -------------------------------------------------------------------------------- 1 | mod live; 2 | mod twitch; 3 | mod youtube; 4 | // mod response; 5 | pub use live::*; 6 | pub use twitch::*; 7 | pub use youtube::*; 8 | -------------------------------------------------------------------------------- /src/plugins/response.rs: -------------------------------------------------------------------------------- 1 | // Example code that deserializes and serializes the model. 2 | // extern crate serde; 3 | // #[macro_use] 4 | // extern crate serde_derive; 5 | // extern crate serde_json; 6 | // 7 | // use generated_module::[object Object]; 8 | // 9 | // fn main() { 10 | // let json = r#"{"answer": 42}"#; 11 | // let model: [object Object] = serde_json::from_str(&json).unwrap(); 12 | // } 13 | 14 | use serde::{Serialize, Deserialize}; 15 | use serde_derive; 16 | 17 | #[derive(Serialize, Deserialize)] 18 | pub struct Welcome { 19 | #[serde(rename = "responseContext")] 20 | pub response_context: ResponseContext, 21 | 22 | #[serde(rename = "contents")] 23 | pub contents: Contents, 24 | 25 | #[serde(rename = "header")] 26 | pub header: Header, 27 | 28 | #[serde(rename = "metadata")] 29 | pub metadata: Metadata, 30 | 31 | #[serde(rename = "trackingParams")] 32 | pub tracking_params: String, 33 | 34 | #[serde(rename = "topbar")] 35 | pub topbar: Topbar, 36 | 37 | #[serde(rename = "microformat")] 38 | pub microformat: Microformat, 39 | } 40 | 41 | #[derive(Serialize, Deserialize)] 42 | pub struct Contents { 43 | #[serde(rename = "twoColumnBrowseResultsRenderer")] 44 | pub two_column_browse_results_renderer: TwoColumnBrowseResultsRenderer, 45 | } 46 | 47 | #[derive(Serialize, Deserialize)] 48 | pub struct TwoColumnBrowseResultsRenderer { 49 | #[serde(rename = "tabs")] 50 | pub tabs: Vec, 51 | } 52 | 53 | #[derive(Serialize, Deserialize)] 54 | pub struct Tab { 55 | #[serde(rename = "tabRenderer")] 56 | pub tab_renderer: Option, 57 | 58 | #[serde(rename = "expandableTabRenderer")] 59 | pub expandable_tab_renderer: Option, 60 | } 61 | 62 | #[derive(Serialize, Deserialize)] 63 | pub struct ExpandableTabRenderer { 64 | #[serde(rename = "endpoint")] 65 | pub endpoint: NextEndpointClass, 66 | 67 | #[serde(rename = "title")] 68 | pub title: String, 69 | 70 | #[serde(rename = "selected")] 71 | pub selected: bool, 72 | } 73 | 74 | #[derive(Serialize, Deserialize)] 75 | pub struct NextEndpointClass { 76 | #[serde(rename = "clickTrackingParams")] 77 | pub click_tracking_params: String, 78 | 79 | #[serde(rename = "commandMetadata")] 80 | pub command_metadata: EndpointCommandMetadata, 81 | 82 | #[serde(rename = "browseEndpoint")] 83 | pub browse_endpoint: NextEndpointBrowseEndpoint, 84 | } 85 | 86 | #[derive(Serialize, Deserialize)] 87 | pub struct NextEndpointBrowseEndpoint { 88 | #[serde(rename = "browseId")] 89 | pub browse_id: Id, 90 | 91 | #[serde(rename = "params")] 92 | pub params: Option, 93 | 94 | #[serde(rename = "canonicalBaseUrl")] 95 | pub canonical_base_url: Option, 96 | } 97 | 98 | #[derive(Serialize, Deserialize)] 99 | pub struct EndpointCommandMetadata { 100 | #[serde(rename = "webCommandMetadata")] 101 | pub web_command_metadata: PurpleWebCommandMetadata, 102 | } 103 | 104 | #[derive(Serialize, Deserialize)] 105 | pub struct PurpleWebCommandMetadata { 106 | #[serde(rename = "url")] 107 | pub url: String, 108 | 109 | #[serde(rename = "webPageType")] 110 | pub web_page_type: WebPageType, 111 | 112 | #[serde(rename = "rootVe")] 113 | pub root_ve: i64, 114 | 115 | #[serde(rename = "apiUrl")] 116 | pub api_url: Option, 117 | } 118 | 119 | #[derive(Serialize, Deserialize)] 120 | pub struct TabRenderer { 121 | #[serde(rename = "endpoint")] 122 | pub endpoint: NextEndpointClass, 123 | 124 | #[serde(rename = "title")] 125 | pub title: String, 126 | 127 | #[serde(rename = "selected")] 128 | pub selected: Option, 129 | 130 | #[serde(rename = "content")] 131 | pub content: Option, 132 | 133 | #[serde(rename = "trackingParams")] 134 | pub tracking_params: String, 135 | } 136 | 137 | #[derive(Serialize, Deserialize)] 138 | pub struct TabRendererContent { 139 | #[serde(rename = "sectionListRenderer")] 140 | pub section_list_renderer: SectionListRenderer, 141 | } 142 | 143 | #[derive(Serialize, Deserialize)] 144 | pub struct SectionListRenderer { 145 | #[serde(rename = "contents")] 146 | pub contents: Vec, 147 | 148 | #[serde(rename = "trackingParams")] 149 | pub tracking_params: String, 150 | 151 | #[serde(rename = "targetId")] 152 | pub target_id: String, 153 | } 154 | 155 | #[derive(Serialize, Deserialize)] 156 | pub struct SectionListRendererContent { 157 | #[serde(rename = "itemSectionRenderer")] 158 | pub item_section_renderer: ItemSectionRenderer, 159 | } 160 | 161 | #[derive(Serialize, Deserialize)] 162 | pub struct ItemSectionRenderer { 163 | #[serde(rename = "contents")] 164 | pub contents: Vec, 165 | 166 | #[serde(rename = "trackingParams")] 167 | pub tracking_params: String, 168 | } 169 | 170 | #[derive(Serialize, Deserialize)] 171 | pub struct ItemSectionRendererContent { 172 | #[serde(rename = "channelVideoPlayerRenderer")] 173 | pub channel_video_player_renderer: Option, 174 | 175 | #[serde(rename = "shelfRenderer")] 176 | pub shelf_renderer: Option, 177 | 178 | #[serde(rename = "reelShelfRenderer")] 179 | pub reel_shelf_renderer: Option, 180 | } 181 | 182 | #[derive(Serialize, Deserialize)] 183 | pub struct ChannelVideoPlayerRenderer { 184 | #[serde(rename = "videoId")] 185 | pub video_id: String, 186 | 187 | #[serde(rename = "title")] 188 | pub title: PurpleTitle, 189 | 190 | #[serde(rename = "description")] 191 | pub description: Description, 192 | 193 | #[serde(rename = "viewCountText")] 194 | pub view_count_text: ContentClass, 195 | 196 | #[serde(rename = "publishedTimeText")] 197 | pub published_time_text: TextClass, 198 | 199 | #[serde(rename = "readMoreText")] 200 | pub read_more_text: ReadMoreText, 201 | } 202 | 203 | #[derive(Serialize, Deserialize)] 204 | pub struct Description { 205 | #[serde(rename = "runs")] 206 | pub runs: Vec, 207 | } 208 | 209 | #[derive(Serialize, Deserialize)] 210 | pub struct DescriptionRun { 211 | #[serde(rename = "text")] 212 | pub text: String, 213 | 214 | #[serde(rename = "navigationEndpoint")] 215 | pub navigation_endpoint: Option, 216 | } 217 | 218 | #[derive(Serialize, Deserialize)] 219 | pub struct PrimaryLinkNavigationEndpoint { 220 | #[serde(rename = "clickTrackingParams")] 221 | pub click_tracking_params: TrackingParams, 222 | 223 | #[serde(rename = "commandMetadata")] 224 | pub command_metadata: EndpointCommandMetadata, 225 | 226 | #[serde(rename = "watchEndpoint")] 227 | pub watch_endpoint: Option, 228 | 229 | #[serde(rename = "urlEndpoint")] 230 | pub url_endpoint: Option, 231 | } 232 | 233 | #[derive(Serialize, Deserialize)] 234 | pub struct UrlEndpoint { 235 | #[serde(rename = "url")] 236 | pub url: String, 237 | 238 | #[serde(rename = "target")] 239 | pub target: Target, 240 | 241 | #[serde(rename = "nofollow")] 242 | pub nofollow: bool, 243 | } 244 | 245 | #[derive(Serialize, Deserialize)] 246 | pub struct PurpleWatchEndpoint { 247 | #[serde(rename = "videoId")] 248 | pub video_id: String, 249 | 250 | #[serde(rename = "startTimeSeconds")] 251 | pub start_time_seconds: i64, 252 | 253 | #[serde(rename = "watchEndpointSupportedOnesieConfig")] 254 | pub watch_endpoint_supported_onesie_config: WatchEndpointSupportedOnesieConfig, 255 | } 256 | 257 | #[derive(Serialize, Deserialize)] 258 | pub struct WatchEndpointSupportedOnesieConfig { 259 | #[serde(rename = "html5PlaybackOnesieConfig")] 260 | pub html5_playback_onesie_config: Html5PlaybackOnesieConfig, 261 | } 262 | 263 | #[derive(Serialize, Deserialize)] 264 | pub struct Html5PlaybackOnesieConfig { 265 | #[serde(rename = "commonConfig")] 266 | pub common_config: CommonConfigElement, 267 | } 268 | 269 | #[derive(Serialize, Deserialize)] 270 | pub struct CommonConfigElement { 271 | #[serde(rename = "url")] 272 | pub url: String, 273 | } 274 | 275 | #[derive(Serialize, Deserialize)] 276 | pub struct TextClass { 277 | #[serde(rename = "runs")] 278 | pub runs: Vec, 279 | } 280 | 281 | #[derive(Serialize, Deserialize)] 282 | pub struct TextRun { 283 | #[serde(rename = "text")] 284 | pub text: String, 285 | } 286 | 287 | #[derive(Serialize, Deserialize)] 288 | pub struct ReadMoreText { 289 | #[serde(rename = "runs")] 290 | pub runs: Vec, 291 | } 292 | 293 | #[derive(Serialize, Deserialize)] 294 | pub struct ReadMoreTextRun { 295 | #[serde(rename = "text")] 296 | pub text: String, 297 | 298 | #[serde(rename = "navigationEndpoint")] 299 | pub navigation_endpoint: GridVideoRendererNavigationEndpoint, 300 | } 301 | 302 | #[derive(Serialize, Deserialize)] 303 | pub struct GridVideoRendererNavigationEndpoint { 304 | #[serde(rename = "clickTrackingParams")] 305 | pub click_tracking_params: String, 306 | 307 | #[serde(rename = "commandMetadata")] 308 | pub command_metadata: EndpointCommandMetadata, 309 | 310 | #[serde(rename = "watchEndpoint")] 311 | pub watch_endpoint: FluffyWatchEndpoint, 312 | } 313 | 314 | #[derive(Serialize, Deserialize)] 315 | pub struct FluffyWatchEndpoint { 316 | #[serde(rename = "videoId")] 317 | pub video_id: String, 318 | 319 | #[serde(rename = "watchEndpointSupportedOnesieConfig")] 320 | pub watch_endpoint_supported_onesie_config: WatchEndpointSupportedOnesieConfig, 321 | } 322 | 323 | #[derive(Serialize, Deserialize)] 324 | pub struct PurpleTitle { 325 | #[serde(rename = "runs")] 326 | pub runs: Vec, 327 | 328 | #[serde(rename = "accessibility")] 329 | pub accessibility: HotkeyAccessibilityLabelClass, 330 | } 331 | 332 | #[derive(Serialize, Deserialize)] 333 | pub struct HotkeyAccessibilityLabelClass { 334 | #[serde(rename = "accessibilityData")] 335 | pub accessibility_data: AccessibilityAccessibilityData, 336 | } 337 | 338 | #[derive(Serialize, Deserialize)] 339 | pub struct AccessibilityAccessibilityData { 340 | #[serde(rename = "label")] 341 | pub label: String, 342 | } 343 | 344 | #[derive(Serialize, Deserialize)] 345 | pub struct ContentClass { 346 | #[serde(rename = "simpleText")] 347 | pub simple_text: String, 348 | } 349 | 350 | #[derive(Serialize, Deserialize)] 351 | pub struct ReelShelfRenderer { 352 | #[serde(rename = "title")] 353 | pub title: TextClass, 354 | 355 | #[serde(rename = "items")] 356 | pub items: Vec, 357 | 358 | #[serde(rename = "trackingParams")] 359 | pub tracking_params: String, 360 | 361 | #[serde(rename = "icon")] 362 | pub icon: IconImage, 363 | } 364 | 365 | #[derive(Serialize, Deserialize)] 366 | pub struct IconImage { 367 | #[serde(rename = "iconType")] 368 | pub icon_type: IconType, 369 | } 370 | 371 | #[derive(Serialize, Deserialize)] 372 | pub struct ReelShelfRendererItem { 373 | #[serde(rename = "reelItemRenderer")] 374 | pub reel_item_renderer: ReelItemRenderer, 375 | } 376 | 377 | #[derive(Serialize, Deserialize)] 378 | pub struct ReelItemRenderer { 379 | #[serde(rename = "videoId")] 380 | pub video_id: String, 381 | 382 | #[serde(rename = "headline")] 383 | pub headline: ContentClass, 384 | 385 | #[serde(rename = "thumbnail")] 386 | pub thumbnail: ReelWatchEndpointThumbnail, 387 | 388 | #[serde(rename = "viewCountText")] 389 | pub view_count_text: SubscriberCountText, 390 | 391 | #[serde(rename = "navigationEndpoint")] 392 | pub navigation_endpoint: ReelItemRendererNavigationEndpoint, 393 | 394 | #[serde(rename = "menu")] 395 | pub menu: ReelItemRendererMenu, 396 | 397 | #[serde(rename = "trackingParams")] 398 | pub tracking_params: String, 399 | 400 | #[serde(rename = "accessibility")] 401 | pub accessibility: HotkeyAccessibilityLabelClass, 402 | 403 | #[serde(rename = "style")] 404 | pub style: ReelItemRendererStyle, 405 | 406 | #[serde(rename = "videoType")] 407 | pub video_type: VideoType, 408 | 409 | #[serde(rename = "loggingDirectives")] 410 | pub logging_directives: LoggingDirectives, 411 | } 412 | 413 | #[derive(Serialize, Deserialize)] 414 | pub struct LoggingDirectives { 415 | #[serde(rename = "trackingParams")] 416 | pub tracking_params: String, 417 | 418 | #[serde(rename = "visibility")] 419 | pub visibility: Visibility, 420 | 421 | #[serde(rename = "enableDisplayloggerExperiment")] 422 | pub enable_displaylogger_experiment: bool, 423 | } 424 | 425 | #[derive(Serialize, Deserialize)] 426 | pub struct Visibility { 427 | #[serde(rename = "types")] 428 | pub types: String, 429 | } 430 | 431 | #[derive(Serialize, Deserialize)] 432 | pub struct ReelItemRendererMenu { 433 | #[serde(rename = "menuRenderer")] 434 | pub menu_renderer: PurpleMenuRenderer, 435 | } 436 | 437 | #[derive(Serialize, Deserialize)] 438 | pub struct PurpleMenuRenderer { 439 | #[serde(rename = "items")] 440 | pub items: Vec, 441 | 442 | #[serde(rename = "trackingParams")] 443 | pub tracking_params: String, 444 | 445 | #[serde(rename = "accessibility")] 446 | pub accessibility: HotkeyAccessibilityLabelClass, 447 | } 448 | 449 | #[derive(Serialize, Deserialize)] 450 | pub struct PurpleItem { 451 | #[serde(rename = "menuNavigationItemRenderer")] 452 | pub menu_navigation_item_renderer: MenuNavigationItemRenderer, 453 | } 454 | 455 | #[derive(Serialize, Deserialize)] 456 | pub struct MenuNavigationItemRenderer { 457 | #[serde(rename = "text")] 458 | pub text: TextClass, 459 | 460 | #[serde(rename = "icon")] 461 | pub icon: IconImage, 462 | 463 | #[serde(rename = "navigationEndpoint")] 464 | pub navigation_endpoint: MenuNavigationItemRendererNavigationEndpoint, 465 | 466 | #[serde(rename = "trackingParams")] 467 | pub tracking_params: String, 468 | 469 | #[serde(rename = "accessibility")] 470 | pub accessibility: HotkeyAccessibilityLabelClass, 471 | } 472 | 473 | #[derive(Serialize, Deserialize)] 474 | pub struct MenuNavigationItemRendererNavigationEndpoint { 475 | #[serde(rename = "clickTrackingParams")] 476 | pub click_tracking_params: String, 477 | 478 | #[serde(rename = "commandMetadata")] 479 | pub command_metadata: PurpleCommandMetadata, 480 | 481 | #[serde(rename = "userFeedbackEndpoint")] 482 | pub user_feedback_endpoint: UserFeedbackEndpoint, 483 | } 484 | 485 | #[derive(Serialize, Deserialize)] 486 | pub struct PurpleCommandMetadata { 487 | #[serde(rename = "webCommandMetadata")] 488 | pub web_command_metadata: FluffyWebCommandMetadata, 489 | } 490 | 491 | #[derive(Serialize, Deserialize)] 492 | pub struct FluffyWebCommandMetadata { 493 | #[serde(rename = "ignoreNavigation")] 494 | pub ignore_navigation: bool, 495 | } 496 | 497 | #[derive(Serialize, Deserialize)] 498 | pub struct UserFeedbackEndpoint { 499 | #[serde(rename = "additionalDatas")] 500 | pub additional_datas: Vec, 501 | } 502 | 503 | #[derive(Serialize, Deserialize)] 504 | pub struct AdditionalData { 505 | #[serde(rename = "userFeedbackEndpointProductSpecificValueData")] 506 | pub user_feedback_endpoint_product_specific_value_data: Param, 507 | } 508 | 509 | #[derive(Serialize, Deserialize)] 510 | pub struct Param { 511 | #[serde(rename = "key")] 512 | pub key: String, 513 | 514 | #[serde(rename = "value")] 515 | pub value: String, 516 | } 517 | 518 | #[derive(Serialize, Deserialize)] 519 | pub struct ReelItemRendererNavigationEndpoint { 520 | #[serde(rename = "clickTrackingParams")] 521 | pub click_tracking_params: String, 522 | 523 | #[serde(rename = "commandMetadata")] 524 | pub command_metadata: EndpointCommandMetadata, 525 | 526 | #[serde(rename = "reelWatchEndpoint")] 527 | pub reel_watch_endpoint: ReelWatchEndpoint, 528 | } 529 | 530 | #[derive(Serialize, Deserialize)] 531 | pub struct ReelWatchEndpoint { 532 | #[serde(rename = "videoId")] 533 | pub video_id: String, 534 | 535 | #[serde(rename = "playerParams")] 536 | pub player_params: PlayerParams, 537 | 538 | #[serde(rename = "thumbnail")] 539 | pub thumbnail: ReelWatchEndpointThumbnail, 540 | 541 | #[serde(rename = "overlay")] 542 | pub overlay: Overlay, 543 | 544 | #[serde(rename = "params")] 545 | pub params: ReelWatchEndpointParams, 546 | 547 | #[serde(rename = "sequenceProvider")] 548 | pub sequence_provider: SequenceProvider, 549 | 550 | #[serde(rename = "sequenceParams")] 551 | pub sequence_params: String, 552 | } 553 | 554 | #[derive(Serialize, Deserialize)] 555 | pub struct Overlay { 556 | #[serde(rename = "reelPlayerOverlayRenderer")] 557 | pub reel_player_overlay_renderer: ReelPlayerOverlayRenderer, 558 | } 559 | 560 | #[derive(Serialize, Deserialize)] 561 | pub struct ReelPlayerOverlayRenderer { 562 | #[serde(rename = "reelPlayerHeaderSupportedRenderers")] 563 | pub reel_player_header_supported_renderers: ReelPlayerHeaderSupportedRenderers, 564 | 565 | #[serde(rename = "nextItemButton")] 566 | pub next_item_button: ItemButton, 567 | 568 | #[serde(rename = "prevItemButton")] 569 | pub prev_item_button: ItemButton, 570 | 571 | #[serde(rename = "style")] 572 | pub style: ReelPlayerOverlayRendererStyle, 573 | 574 | #[serde(rename = "trackingParams")] 575 | pub tracking_params: String, 576 | } 577 | 578 | #[derive(Serialize, Deserialize)] 579 | pub struct ItemButton { 580 | #[serde(rename = "buttonRenderer")] 581 | pub button_renderer: NextItemButtonButtonRenderer, 582 | } 583 | 584 | #[derive(Serialize, Deserialize)] 585 | pub struct NextItemButtonButtonRenderer { 586 | #[serde(rename = "trackingParams")] 587 | pub tracking_params: String, 588 | } 589 | 590 | #[derive(Serialize, Deserialize)] 591 | pub struct ReelPlayerHeaderSupportedRenderers { 592 | #[serde(rename = "reelPlayerHeaderRenderer")] 593 | pub reel_player_header_renderer: ReelPlayerHeaderRenderer, 594 | } 595 | 596 | #[derive(Serialize, Deserialize)] 597 | pub struct ReelPlayerHeaderRenderer { 598 | #[serde(rename = "reelTitleText")] 599 | pub reel_title_text: ReelTitleText, 600 | 601 | #[serde(rename = "timestampText")] 602 | pub timestamp_text: ContentClass, 603 | 604 | #[serde(rename = "channelNavigationEndpoint")] 605 | pub channel_navigation_endpoint: NavigationEndpoint, 606 | 607 | #[serde(rename = "channelTitleText")] 608 | pub channel_title_text: EText, 609 | 610 | #[serde(rename = "channelThumbnail")] 611 | pub channel_thumbnail: Avatar, 612 | 613 | #[serde(rename = "trackingParams")] 614 | pub tracking_params: String, 615 | 616 | #[serde(rename = "accessibility")] 617 | pub accessibility: HotkeyAccessibilityLabelClass, 618 | } 619 | 620 | #[derive(Serialize, Deserialize)] 621 | pub struct NavigationEndpoint { 622 | #[serde(rename = "clickTrackingParams")] 623 | pub click_tracking_params: String, 624 | 625 | #[serde(rename = "commandMetadata")] 626 | pub command_metadata: EndpointCommandMetadata, 627 | 628 | #[serde(rename = "browseEndpoint")] 629 | pub browse_endpoint: ChannelNavigationEndpointBrowseEndpoint, 630 | } 631 | 632 | #[derive(Serialize, Deserialize)] 633 | pub struct ChannelNavigationEndpointBrowseEndpoint { 634 | #[serde(rename = "browseId")] 635 | pub browse_id: Id, 636 | 637 | #[serde(rename = "canonicalBaseUrl")] 638 | pub canonical_base_url: CanonicalBaseUrl, 639 | } 640 | 641 | #[derive(Serialize, Deserialize)] 642 | pub struct Avatar { 643 | #[serde(rename = "thumbnails")] 644 | pub thumbnails: Vec, 645 | } 646 | 647 | #[derive(Serialize, Deserialize)] 648 | pub struct AvatarThumbnail { 649 | #[serde(rename = "url")] 650 | pub url: String, 651 | 652 | #[serde(rename = "width")] 653 | pub width: i64, 654 | 655 | #[serde(rename = "height")] 656 | pub height: i64, 657 | } 658 | 659 | #[derive(Serialize, Deserialize)] 660 | pub struct EText { 661 | #[serde(rename = "runs")] 662 | pub runs: Vec, 663 | } 664 | 665 | #[derive(Serialize, Deserialize)] 666 | pub struct ShortBylineTextRun { 667 | #[serde(rename = "text")] 668 | pub text: TitleEnum, 669 | 670 | #[serde(rename = "navigationEndpoint")] 671 | pub navigation_endpoint: NavigationEndpoint, 672 | } 673 | 674 | #[derive(Serialize, Deserialize)] 675 | pub struct ReelTitleText { 676 | #[serde(rename = "runs")] 677 | pub runs: Vec, 678 | } 679 | 680 | #[derive(Serialize, Deserialize)] 681 | pub struct ReelTitleTextRun { 682 | #[serde(rename = "text")] 683 | pub text: String, 684 | 685 | #[serde(rename = "navigationEndpoint")] 686 | pub navigation_endpoint: Option, 687 | 688 | #[serde(rename = "loggingDirectives")] 689 | pub logging_directives: Option, 690 | } 691 | 692 | #[derive(Serialize, Deserialize)] 693 | pub struct PurpleNavigationEndpoint { 694 | #[serde(rename = "clickTrackingParams")] 695 | pub click_tracking_params: String, 696 | } 697 | 698 | #[derive(Serialize, Deserialize)] 699 | pub struct ReelWatchEndpointThumbnail { 700 | #[serde(rename = "thumbnails")] 701 | pub thumbnails: Vec, 702 | 703 | #[serde(rename = "isOriginalAspectRatio")] 704 | pub is_original_aspect_ratio: bool, 705 | } 706 | 707 | #[derive(Serialize, Deserialize)] 708 | pub struct SubscriberCountText { 709 | #[serde(rename = "accessibility")] 710 | pub accessibility: HotkeyAccessibilityLabelClass, 711 | 712 | #[serde(rename = "simpleText")] 713 | pub simple_text: String, 714 | } 715 | 716 | #[derive(Serialize, Deserialize)] 717 | pub struct ShelfRenderer { 718 | #[serde(rename = "title")] 719 | pub title: TentacledTitle, 720 | 721 | #[serde(rename = "endpoint")] 722 | pub endpoint: NextEndpointClass, 723 | 724 | #[serde(rename = "content")] 725 | pub content: ShelfRendererContent, 726 | 727 | #[serde(rename = "trackingParams")] 728 | pub tracking_params: String, 729 | 730 | #[serde(rename = "playAllButton")] 731 | pub play_all_button: Option, 732 | 733 | #[serde(rename = "subtitle")] 734 | pub subtitle: Option, 735 | } 736 | 737 | #[derive(Serialize, Deserialize)] 738 | pub struct ShelfRendererContent { 739 | #[serde(rename = "horizontalListRenderer")] 740 | pub horizontal_list_renderer: HorizontalListRenderer, 741 | } 742 | 743 | #[derive(Serialize, Deserialize)] 744 | pub struct HorizontalListRenderer { 745 | #[serde(rename = "items")] 746 | pub items: Vec, 747 | 748 | #[serde(rename = "trackingParams")] 749 | pub tracking_params: String, 750 | 751 | #[serde(rename = "visibleItemCount")] 752 | pub visible_item_count: i64, 753 | 754 | #[serde(rename = "nextButton")] 755 | pub next_button: VoiceSearchButtonClass, 756 | 757 | #[serde(rename = "previousButton")] 758 | pub previous_button: VoiceSearchButtonClass, 759 | } 760 | 761 | #[derive(Serialize, Deserialize)] 762 | pub struct HorizontalListRendererItem { 763 | #[serde(rename = "gridVideoRenderer")] 764 | pub grid_video_renderer: Option, 765 | 766 | #[serde(rename = "gridPlaylistRenderer")] 767 | pub grid_playlist_renderer: Option, 768 | } 769 | 770 | #[derive(Serialize, Deserialize)] 771 | pub struct GridPlaylistRenderer { 772 | #[serde(rename = "playlistId")] 773 | pub playlist_id: String, 774 | 775 | #[serde(rename = "thumbnail")] 776 | pub thumbnail: Avatar, 777 | 778 | #[serde(rename = "title")] 779 | pub title: FluffyTitle, 780 | 781 | #[serde(rename = "videoCountText")] 782 | pub video_count_text: TextClass, 783 | 784 | #[serde(rename = "navigationEndpoint")] 785 | pub navigation_endpoint: GridPlaylistRendererNavigationEndpoint, 786 | 787 | #[serde(rename = "videoCountShortText")] 788 | pub video_count_short_text: ContentClass, 789 | 790 | #[serde(rename = "trackingParams")] 791 | pub tracking_params: String, 792 | 793 | #[serde(rename = "sidebarThumbnails")] 794 | pub sidebar_thumbnails: Vec, 795 | 796 | #[serde(rename = "thumbnailText")] 797 | pub thumbnail_text: ThumbnailText, 798 | 799 | #[serde(rename = "ownerBadges")] 800 | pub owner_badges: Vec, 801 | 802 | #[serde(rename = "thumbnailRenderer")] 803 | pub thumbnail_renderer: ThumbnailRenderer, 804 | 805 | #[serde(rename = "thumbnailOverlays")] 806 | pub thumbnail_overlays: Vec, 807 | 808 | #[serde(rename = "viewPlaylistText")] 809 | pub view_playlist_text: ViewPlaylistText, 810 | 811 | #[serde(rename = "publishedTimeText")] 812 | pub published_time_text: Option, 813 | } 814 | 815 | #[derive(Serialize, Deserialize)] 816 | pub struct GridPlaylistRendererNavigationEndpoint { 817 | #[serde(rename = "clickTrackingParams")] 818 | pub click_tracking_params: String, 819 | 820 | #[serde(rename = "commandMetadata")] 821 | pub command_metadata: EndpointCommandMetadata, 822 | 823 | #[serde(rename = "watchEndpoint")] 824 | pub watch_endpoint: TentacledWatchEndpoint, 825 | } 826 | 827 | #[derive(Serialize, Deserialize)] 828 | pub struct TentacledWatchEndpoint { 829 | #[serde(rename = "videoId")] 830 | pub video_id: String, 831 | 832 | #[serde(rename = "playlistId")] 833 | pub playlist_id: String, 834 | 835 | #[serde(rename = "params")] 836 | pub params: Option, 837 | 838 | #[serde(rename = "loggingContext")] 839 | pub logging_context: LoggingContext, 840 | 841 | #[serde(rename = "watchEndpointSupportedOnesieConfig")] 842 | pub watch_endpoint_supported_onesie_config: WatchEndpointSupportedOnesieConfig, 843 | } 844 | 845 | #[derive(Serialize, Deserialize)] 846 | pub struct LoggingContext { 847 | #[serde(rename = "vssLoggingContext")] 848 | pub vss_logging_context: VssLoggingContext, 849 | } 850 | 851 | #[derive(Serialize, Deserialize)] 852 | pub struct VssLoggingContext { 853 | #[serde(rename = "serializedContextData")] 854 | pub serialized_context_data: String, 855 | } 856 | 857 | #[derive(Serialize, Deserialize)] 858 | pub struct OwnerBadgeElement { 859 | #[serde(rename = "metadataBadgeRenderer")] 860 | pub metadata_badge_renderer: OwnerBadgeMetadataBadgeRenderer, 861 | } 862 | 863 | #[derive(Serialize, Deserialize)] 864 | pub struct OwnerBadgeMetadataBadgeRenderer { 865 | #[serde(rename = "icon")] 866 | pub icon: IconImage, 867 | 868 | #[serde(rename = "style")] 869 | pub style: MetadataBadgeRendererStyle, 870 | 871 | #[serde(rename = "tooltip")] 872 | pub tooltip: Tooltip, 873 | 874 | #[serde(rename = "trackingParams")] 875 | pub tracking_params: String, 876 | 877 | #[serde(rename = "accessibilityData")] 878 | pub accessibility_data: AccessibilityAccessibilityData, 879 | } 880 | 881 | #[derive(Serialize, Deserialize)] 882 | pub struct GridPlaylistRendererThumbnailOverlay { 883 | #[serde(rename = "thumbnailOverlaySidePanelRenderer")] 884 | pub thumbnail_overlay_side_panel_renderer: Option, 885 | 886 | #[serde(rename = "thumbnailOverlayHoverTextRenderer")] 887 | pub thumbnail_overlay_hover_text_renderer: Option, 888 | 889 | #[serde(rename = "thumbnailOverlayNowPlayingRenderer")] 890 | pub thumbnail_overlay_now_playing_renderer: Option, 891 | } 892 | 893 | #[derive(Serialize, Deserialize)] 894 | pub struct ThumbnailOverlayHoverTextRenderer { 895 | #[serde(rename = "text")] 896 | pub text: TextClass, 897 | 898 | #[serde(rename = "icon")] 899 | pub icon: IconImage, 900 | } 901 | 902 | #[derive(Serialize, Deserialize)] 903 | pub struct ThumbnailOverlayNowPlayingRenderer { 904 | #[serde(rename = "text")] 905 | pub text: TextClass, 906 | } 907 | 908 | #[derive(Serialize, Deserialize)] 909 | pub struct ThumbnailOverlaySidePanelRenderer { 910 | #[serde(rename = "text")] 911 | pub text: ContentClass, 912 | 913 | #[serde(rename = "icon")] 914 | pub icon: IconImage, 915 | } 916 | 917 | #[derive(Serialize, Deserialize)] 918 | pub struct ThumbnailRenderer { 919 | #[serde(rename = "playlistVideoThumbnailRenderer")] 920 | pub playlist_video_thumbnail_renderer: PlaylistVideoThumbnailRenderer, 921 | } 922 | 923 | #[derive(Serialize, Deserialize)] 924 | pub struct PlaylistVideoThumbnailRenderer { 925 | #[serde(rename = "thumbnail")] 926 | pub thumbnail: Avatar, 927 | } 928 | 929 | #[derive(Serialize, Deserialize)] 930 | pub struct ThumbnailText { 931 | #[serde(rename = "runs")] 932 | pub runs: Vec, 933 | } 934 | 935 | #[derive(Serialize, Deserialize)] 936 | pub struct ThumbnailTextRun { 937 | #[serde(rename = "text")] 938 | pub text: String, 939 | 940 | #[serde(rename = "bold")] 941 | pub bold: Option, 942 | } 943 | 944 | #[derive(Serialize, Deserialize)] 945 | pub struct FluffyTitle { 946 | #[serde(rename = "runs")] 947 | pub runs: Vec, 948 | } 949 | 950 | #[derive(Serialize, Deserialize)] 951 | pub struct PurpleRun { 952 | #[serde(rename = "text")] 953 | pub text: String, 954 | 955 | #[serde(rename = "navigationEndpoint")] 956 | pub navigation_endpoint: GridPlaylistRendererNavigationEndpoint, 957 | } 958 | 959 | #[derive(Serialize, Deserialize)] 960 | pub struct ViewPlaylistText { 961 | #[serde(rename = "runs")] 962 | pub runs: Vec, 963 | } 964 | 965 | #[derive(Serialize, Deserialize)] 966 | pub struct ViewPlaylistTextRun { 967 | #[serde(rename = "text")] 968 | pub text: TextEnum, 969 | 970 | #[serde(rename = "navigationEndpoint")] 971 | pub navigation_endpoint: TopbarLogoRendererEndpoint, 972 | } 973 | 974 | #[derive(Serialize, Deserialize)] 975 | pub struct TopbarLogoRendererEndpoint { 976 | #[serde(rename = "clickTrackingParams")] 977 | pub click_tracking_params: String, 978 | 979 | #[serde(rename = "commandMetadata")] 980 | pub command_metadata: EndpointCommandMetadata, 981 | 982 | #[serde(rename = "browseEndpoint")] 983 | pub browse_endpoint: PurpleBrowseEndpoint, 984 | } 985 | 986 | #[derive(Serialize, Deserialize)] 987 | pub struct PurpleBrowseEndpoint { 988 | #[serde(rename = "browseId")] 989 | pub browse_id: String, 990 | } 991 | 992 | #[derive(Serialize, Deserialize)] 993 | pub struct GridVideoRenderer { 994 | #[serde(rename = "videoId")] 995 | pub video_id: String, 996 | 997 | #[serde(rename = "thumbnail")] 998 | pub thumbnail: Avatar, 999 | 1000 | #[serde(rename = "title")] 1001 | pub title: SubscriberCountText, 1002 | 1003 | #[serde(rename = "viewCountText")] 1004 | pub view_count_text: Option, 1005 | 1006 | #[serde(rename = "navigationEndpoint")] 1007 | pub navigation_endpoint: GridVideoRendererNavigationEndpoint, 1008 | 1009 | #[serde(rename = "upcomingEventData")] 1010 | pub upcoming_event_data: Option, 1011 | 1012 | #[serde(rename = "ownerBadges")] 1013 | pub owner_badges: Vec, 1014 | 1015 | #[serde(rename = "trackingParams")] 1016 | pub tracking_params: String, 1017 | 1018 | #[serde(rename = "shortViewCountText")] 1019 | pub short_view_count_text: Option, 1020 | 1021 | #[serde(rename = "menu")] 1022 | pub menu: GridVideoRendererMenu, 1023 | 1024 | #[serde(rename = "thumbnailOverlays")] 1025 | pub thumbnail_overlays: Vec, 1026 | 1027 | #[serde(rename = "publishedTimeText")] 1028 | pub published_time_text: Option, 1029 | 1030 | #[serde(rename = "badges")] 1031 | pub badges: Option>, 1032 | 1033 | #[serde(rename = "shortBylineText")] 1034 | pub short_byline_text: Option, 1035 | } 1036 | 1037 | #[derive(Serialize, Deserialize)] 1038 | pub struct PurpleBadge { 1039 | #[serde(rename = "metadataBadgeRenderer")] 1040 | pub metadata_badge_renderer: PurpleMetadataBadgeRenderer, 1041 | } 1042 | 1043 | #[derive(Serialize, Deserialize)] 1044 | pub struct PurpleMetadataBadgeRenderer { 1045 | #[serde(rename = "style")] 1046 | pub style: String, 1047 | 1048 | #[serde(rename = "label")] 1049 | pub label: String, 1050 | 1051 | #[serde(rename = "trackingParams")] 1052 | pub tracking_params: String, 1053 | 1054 | #[serde(rename = "accessibilityData")] 1055 | pub accessibility_data: AccessibilityAccessibilityData, 1056 | } 1057 | 1058 | #[derive(Serialize, Deserialize)] 1059 | pub struct GridVideoRendererMenu { 1060 | #[serde(rename = "menuRenderer")] 1061 | pub menu_renderer: FluffyMenuRenderer, 1062 | } 1063 | 1064 | #[derive(Serialize, Deserialize)] 1065 | pub struct FluffyMenuRenderer { 1066 | #[serde(rename = "items")] 1067 | pub items: Vec, 1068 | 1069 | #[serde(rename = "trackingParams")] 1070 | pub tracking_params: String, 1071 | 1072 | #[serde(rename = "accessibility")] 1073 | pub accessibility: HotkeyAccessibilityLabelClass, 1074 | } 1075 | 1076 | #[derive(Serialize, Deserialize)] 1077 | pub struct FluffyItem { 1078 | #[serde(rename = "menuServiceItemRenderer")] 1079 | pub menu_service_item_renderer: MenuServiceItemRenderer, 1080 | } 1081 | 1082 | #[derive(Serialize, Deserialize)] 1083 | pub struct MenuServiceItemRenderer { 1084 | #[serde(rename = "text")] 1085 | pub text: TextClass, 1086 | 1087 | #[serde(rename = "icon")] 1088 | pub icon: IconImage, 1089 | 1090 | #[serde(rename = "serviceEndpoint")] 1091 | pub service_endpoint: MenuServiceItemRendererServiceEndpoint, 1092 | 1093 | #[serde(rename = "trackingParams")] 1094 | pub tracking_params: String, 1095 | } 1096 | 1097 | #[derive(Serialize, Deserialize)] 1098 | pub struct MenuServiceItemRendererServiceEndpoint { 1099 | #[serde(rename = "clickTrackingParams")] 1100 | pub click_tracking_params: String, 1101 | 1102 | #[serde(rename = "commandMetadata")] 1103 | pub command_metadata: ServiceEndpointCommandMetadata, 1104 | 1105 | #[serde(rename = "signalServiceEndpoint")] 1106 | pub signal_service_endpoint: UntoggledServiceEndpointSignalServiceEndpoint, 1107 | } 1108 | 1109 | #[derive(Serialize, Deserialize)] 1110 | pub struct ServiceEndpointCommandMetadata { 1111 | #[serde(rename = "webCommandMetadata")] 1112 | pub web_command_metadata: TentacledWebCommandMetadata, 1113 | } 1114 | 1115 | #[derive(Serialize, Deserialize)] 1116 | pub struct TentacledWebCommandMetadata { 1117 | #[serde(rename = "sendPost")] 1118 | pub send_post: bool, 1119 | } 1120 | 1121 | #[derive(Serialize, Deserialize)] 1122 | pub struct UntoggledServiceEndpointSignalServiceEndpoint { 1123 | #[serde(rename = "signal")] 1124 | pub signal: Signal, 1125 | 1126 | #[serde(rename = "actions")] 1127 | pub actions: Vec, 1128 | } 1129 | 1130 | #[derive(Serialize, Deserialize)] 1131 | pub struct PurpleAction { 1132 | #[serde(rename = "clickTrackingParams")] 1133 | pub click_tracking_params: String, 1134 | 1135 | #[serde(rename = "addToPlaylistCommand")] 1136 | pub add_to_playlist_command: AddToPlaylistCommand, 1137 | } 1138 | 1139 | #[derive(Serialize, Deserialize)] 1140 | pub struct AddToPlaylistCommand { 1141 | #[serde(rename = "openMiniplayer")] 1142 | pub open_miniplayer: bool, 1143 | 1144 | #[serde(rename = "videoId")] 1145 | pub video_id: String, 1146 | 1147 | #[serde(rename = "listType")] 1148 | pub list_type: ListType, 1149 | 1150 | #[serde(rename = "onCreateListCommand")] 1151 | pub on_create_list_command: OnCreateListCommand, 1152 | 1153 | #[serde(rename = "videoIds")] 1154 | pub video_ids: Vec, 1155 | } 1156 | 1157 | #[derive(Serialize, Deserialize)] 1158 | pub struct OnCreateListCommand { 1159 | #[serde(rename = "clickTrackingParams")] 1160 | pub click_tracking_params: String, 1161 | 1162 | #[serde(rename = "commandMetadata")] 1163 | pub command_metadata: OnCreateListCommandCommandMetadata, 1164 | 1165 | #[serde(rename = "createPlaylistServiceEndpoint")] 1166 | pub create_playlist_service_endpoint: CreatePlaylistServiceEndpoint, 1167 | } 1168 | 1169 | #[derive(Serialize, Deserialize)] 1170 | pub struct OnCreateListCommandCommandMetadata { 1171 | #[serde(rename = "webCommandMetadata")] 1172 | pub web_command_metadata: StickyWebCommandMetadata, 1173 | } 1174 | 1175 | #[derive(Serialize, Deserialize)] 1176 | pub struct StickyWebCommandMetadata { 1177 | #[serde(rename = "sendPost")] 1178 | pub send_post: bool, 1179 | 1180 | #[serde(rename = "apiUrl")] 1181 | pub api_url: Option, 1182 | } 1183 | 1184 | #[derive(Serialize, Deserialize)] 1185 | pub struct CreatePlaylistServiceEndpoint { 1186 | #[serde(rename = "videoIds")] 1187 | pub video_ids: Vec, 1188 | 1189 | #[serde(rename = "params")] 1190 | pub params: CreatePlaylistServiceEndpointParams, 1191 | } 1192 | 1193 | #[derive(Serialize, Deserialize)] 1194 | pub struct ShortViewCountText { 1195 | #[serde(rename = "runs")] 1196 | pub runs: Option>, 1197 | 1198 | #[serde(rename = "accessibility")] 1199 | pub accessibility: Option, 1200 | 1201 | #[serde(rename = "simpleText")] 1202 | pub simple_text: Option, 1203 | } 1204 | 1205 | #[derive(Serialize, Deserialize)] 1206 | pub struct GridVideoRendererThumbnailOverlay { 1207 | #[serde(rename = "thumbnailOverlayTimeStatusRenderer")] 1208 | pub thumbnail_overlay_time_status_renderer: Option, 1209 | 1210 | #[serde(rename = "thumbnailOverlayToggleButtonRenderer")] 1211 | pub thumbnail_overlay_toggle_button_renderer: Option, 1212 | 1213 | #[serde(rename = "thumbnailOverlayNowPlayingRenderer")] 1214 | pub thumbnail_overlay_now_playing_renderer: Option, 1215 | } 1216 | 1217 | #[derive(Serialize, Deserialize)] 1218 | pub struct ThumbnailOverlayTimeStatusRenderer { 1219 | #[serde(rename = "text")] 1220 | pub text: SubscriberCountText, 1221 | 1222 | #[serde(rename = "style")] 1223 | pub style: ThumbnailOverlayTimeStatusRendererStyle, 1224 | } 1225 | 1226 | #[derive(Serialize, Deserialize)] 1227 | pub struct ThumbnailOverlayToggleButtonRenderer { 1228 | #[serde(rename = "isToggled")] 1229 | pub is_toggled: Option, 1230 | 1231 | #[serde(rename = "untoggledIcon")] 1232 | pub untoggled_icon: IconImage, 1233 | 1234 | #[serde(rename = "toggledIcon")] 1235 | pub toggled_icon: IconImage, 1236 | 1237 | #[serde(rename = "untoggledTooltip")] 1238 | pub untoggled_tooltip: UntoggledTooltip, 1239 | 1240 | #[serde(rename = "toggledTooltip")] 1241 | pub toggled_tooltip: ToggledTooltip, 1242 | 1243 | #[serde(rename = "untoggledServiceEndpoint")] 1244 | pub untoggled_service_endpoint: UntoggledServiceEndpoint, 1245 | 1246 | #[serde(rename = "toggledServiceEndpoint")] 1247 | pub toggled_service_endpoint: Option, 1248 | 1249 | #[serde(rename = "untoggledAccessibility")] 1250 | pub untoggled_accessibility: HotkeyAccessibilityLabelClass, 1251 | 1252 | #[serde(rename = "toggledAccessibility")] 1253 | pub toggled_accessibility: HotkeyAccessibilityLabelClass, 1254 | 1255 | #[serde(rename = "trackingParams")] 1256 | pub tracking_params: String, 1257 | } 1258 | 1259 | #[derive(Serialize, Deserialize)] 1260 | pub struct ToggledServiceEndpoint { 1261 | #[serde(rename = "clickTrackingParams")] 1262 | pub click_tracking_params: String, 1263 | 1264 | #[serde(rename = "commandMetadata")] 1265 | pub command_metadata: OnCreateListCommandCommandMetadata, 1266 | 1267 | #[serde(rename = "playlistEditEndpoint")] 1268 | pub playlist_edit_endpoint: ToggledServiceEndpointPlaylistEditEndpoint, 1269 | } 1270 | 1271 | #[derive(Serialize, Deserialize)] 1272 | pub struct ToggledServiceEndpointPlaylistEditEndpoint { 1273 | #[serde(rename = "playlistId")] 1274 | pub playlist_id: PlaylistId, 1275 | 1276 | #[serde(rename = "actions")] 1277 | pub actions: Vec, 1278 | } 1279 | 1280 | #[derive(Serialize, Deserialize)] 1281 | pub struct FluffyAction { 1282 | #[serde(rename = "action")] 1283 | pub action: HilariousAction, 1284 | 1285 | #[serde(rename = "removedVideoId")] 1286 | pub removed_video_id: String, 1287 | } 1288 | 1289 | #[derive(Serialize, Deserialize)] 1290 | pub struct UntoggledServiceEndpoint { 1291 | #[serde(rename = "clickTrackingParams")] 1292 | pub click_tracking_params: String, 1293 | 1294 | #[serde(rename = "commandMetadata")] 1295 | pub command_metadata: OnCreateListCommandCommandMetadata, 1296 | 1297 | #[serde(rename = "playlistEditEndpoint")] 1298 | pub playlist_edit_endpoint: Option, 1299 | 1300 | #[serde(rename = "signalServiceEndpoint")] 1301 | pub signal_service_endpoint: Option, 1302 | } 1303 | 1304 | #[derive(Serialize, Deserialize)] 1305 | pub struct UntoggledServiceEndpointPlaylistEditEndpoint { 1306 | #[serde(rename = "playlistId")] 1307 | pub playlist_id: PlaylistId, 1308 | 1309 | #[serde(rename = "actions")] 1310 | pub actions: Vec, 1311 | } 1312 | 1313 | #[derive(Serialize, Deserialize)] 1314 | pub struct TentacledAction { 1315 | #[serde(rename = "addedVideoId")] 1316 | pub added_video_id: String, 1317 | 1318 | #[serde(rename = "action")] 1319 | pub action: AmbitiousAction, 1320 | } 1321 | 1322 | #[derive(Serialize, Deserialize)] 1323 | pub struct UpcomingEventData { 1324 | #[serde(rename = "startTime")] 1325 | pub start_time: String, 1326 | 1327 | #[serde(rename = "isReminderSet")] 1328 | pub is_reminder_set: bool, 1329 | 1330 | #[serde(rename = "upcomingEventText")] 1331 | pub upcoming_event_text: TextClass, 1332 | } 1333 | 1334 | #[derive(Serialize, Deserialize)] 1335 | pub struct ViewCountText { 1336 | #[serde(rename = "runs")] 1337 | pub runs: Option>, 1338 | 1339 | #[serde(rename = "simpleText")] 1340 | pub simple_text: Option, 1341 | } 1342 | 1343 | #[derive(Serialize, Deserialize)] 1344 | pub struct VoiceSearchDialogRenderer { 1345 | #[serde(rename = "placeholderHeader")] 1346 | pub placeholder_header: TextClass, 1347 | 1348 | #[serde(rename = "promptHeader")] 1349 | pub prompt_header: TextClass, 1350 | 1351 | #[serde(rename = "exampleQuery1")] 1352 | pub example_query1: TextClass, 1353 | 1354 | #[serde(rename = "exampleQuery2")] 1355 | pub example_query2: TextClass, 1356 | 1357 | #[serde(rename = "promptMicrophoneLabel")] 1358 | pub prompt_microphone_label: TextClass, 1359 | 1360 | #[serde(rename = "loadingHeader")] 1361 | pub loading_header: TextClass, 1362 | 1363 | #[serde(rename = "connectionErrorHeader")] 1364 | pub connection_error_header: TextClass, 1365 | 1366 | #[serde(rename = "connectionErrorMicrophoneLabel")] 1367 | pub connection_error_microphone_label: TextClass, 1368 | 1369 | #[serde(rename = "permissionsHeader")] 1370 | pub permissions_header: TextClass, 1371 | 1372 | #[serde(rename = "permissionsSubtext")] 1373 | pub permissions_subtext: TextClass, 1374 | 1375 | #[serde(rename = "disabledHeader")] 1376 | pub disabled_header: TextClass, 1377 | 1378 | #[serde(rename = "disabledSubtext")] 1379 | pub disabled_subtext: TextClass, 1380 | 1381 | #[serde(rename = "microphoneButtonAriaLabel")] 1382 | pub microphone_button_aria_label: TextClass, 1383 | 1384 | #[serde(rename = "exitButton")] 1385 | pub exit_button: VoiceSearchButtonClass, 1386 | 1387 | #[serde(rename = "trackingParams")] 1388 | pub tracking_params: String, 1389 | 1390 | #[serde(rename = "microphoneOffPromptHeader")] 1391 | pub microphone_off_prompt_header: TextClass, 1392 | } 1393 | 1394 | #[derive(Serialize, Deserialize)] 1395 | pub struct PurplePopup { 1396 | #[serde(rename = "voiceSearchDialogRenderer")] 1397 | pub voice_search_dialog_renderer: VoiceSearchDialogRenderer, 1398 | } 1399 | 1400 | #[derive(Serialize, Deserialize)] 1401 | pub struct PurpleOpenPopupAction { 1402 | #[serde(rename = "popup")] 1403 | pub popup: PurplePopup, 1404 | 1405 | #[serde(rename = "popupType")] 1406 | pub popup_type: String, 1407 | } 1408 | 1409 | #[derive(Serialize, Deserialize)] 1410 | pub struct StickyAction { 1411 | #[serde(rename = "clickTrackingParams")] 1412 | pub click_tracking_params: String, 1413 | 1414 | #[serde(rename = "openPopupAction")] 1415 | pub open_popup_action: PurpleOpenPopupAction, 1416 | } 1417 | 1418 | #[derive(Serialize, Deserialize)] 1419 | pub struct PurpleSignalServiceEndpoint { 1420 | #[serde(rename = "signal")] 1421 | pub signal: Signal, 1422 | 1423 | #[serde(rename = "actions")] 1424 | pub actions: Vec, 1425 | } 1426 | 1427 | #[derive(Serialize, Deserialize)] 1428 | pub struct ButtonRendererServiceEndpoint { 1429 | #[serde(rename = "clickTrackingParams")] 1430 | pub click_tracking_params: String, 1431 | 1432 | #[serde(rename = "commandMetadata")] 1433 | pub command_metadata: ServiceEndpointCommandMetadata, 1434 | 1435 | #[serde(rename = "signalServiceEndpoint")] 1436 | pub signal_service_endpoint: PurpleSignalServiceEndpoint, 1437 | } 1438 | 1439 | #[derive(Serialize, Deserialize)] 1440 | pub struct VoiceSearchButtonButtonRenderer { 1441 | #[serde(rename = "style")] 1442 | pub style: ButtonRendererStyle, 1443 | 1444 | #[serde(rename = "size")] 1445 | pub size: Size, 1446 | 1447 | #[serde(rename = "isDisabled")] 1448 | pub is_disabled: bool, 1449 | 1450 | #[serde(rename = "icon")] 1451 | pub icon: IconImage, 1452 | 1453 | #[serde(rename = "accessibility")] 1454 | pub accessibility: Option, 1455 | 1456 | #[serde(rename = "trackingParams")] 1457 | pub tracking_params: String, 1458 | 1459 | #[serde(rename = "accessibilityData")] 1460 | pub accessibility_data: Option, 1461 | 1462 | #[serde(rename = "serviceEndpoint")] 1463 | pub service_endpoint: Option, 1464 | 1465 | #[serde(rename = "tooltip")] 1466 | pub tooltip: Option, 1467 | } 1468 | 1469 | #[derive(Serialize, Deserialize)] 1470 | pub struct VoiceSearchButtonClass { 1471 | #[serde(rename = "buttonRenderer")] 1472 | pub button_renderer: VoiceSearchButtonButtonRenderer, 1473 | } 1474 | 1475 | #[derive(Serialize, Deserialize)] 1476 | pub struct PlayAllButton { 1477 | #[serde(rename = "buttonRenderer")] 1478 | pub button_renderer: PlayAllButtonButtonRenderer, 1479 | } 1480 | 1481 | #[derive(Serialize, Deserialize)] 1482 | pub struct PlayAllButtonButtonRenderer { 1483 | #[serde(rename = "style")] 1484 | pub style: String, 1485 | 1486 | #[serde(rename = "size")] 1487 | pub size: Size, 1488 | 1489 | #[serde(rename = "isDisabled")] 1490 | pub is_disabled: bool, 1491 | 1492 | #[serde(rename = "text")] 1493 | pub text: TextClass, 1494 | 1495 | #[serde(rename = "icon")] 1496 | pub icon: IconImage, 1497 | 1498 | #[serde(rename = "navigationEndpoint")] 1499 | pub navigation_endpoint: GridPlaylistRendererNavigationEndpoint, 1500 | 1501 | #[serde(rename = "trackingParams")] 1502 | pub tracking_params: String, 1503 | } 1504 | 1505 | #[derive(Serialize, Deserialize)] 1506 | pub struct TentacledTitle { 1507 | #[serde(rename = "runs")] 1508 | pub runs: Vec, 1509 | } 1510 | 1511 | #[derive(Serialize, Deserialize)] 1512 | pub struct FluffyRun { 1513 | #[serde(rename = "text")] 1514 | pub text: String, 1515 | 1516 | #[serde(rename = "navigationEndpoint")] 1517 | pub navigation_endpoint: NextEndpointClass, 1518 | } 1519 | 1520 | #[derive(Serialize, Deserialize)] 1521 | pub struct Header { 1522 | #[serde(rename = "c4TabbedHeaderRenderer")] 1523 | pub c4_tabbed_header_renderer: C4TabbedHeaderRenderer, 1524 | } 1525 | 1526 | #[derive(Serialize, Deserialize)] 1527 | pub struct C4TabbedHeaderRenderer { 1528 | #[serde(rename = "channelId")] 1529 | pub channel_id: Id, 1530 | 1531 | #[serde(rename = "title")] 1532 | pub title: TitleEnum, 1533 | 1534 | #[serde(rename = "navigationEndpoint")] 1535 | pub navigation_endpoint: NavigationEndpoint, 1536 | 1537 | #[serde(rename = "avatar")] 1538 | pub avatar: Avatar, 1539 | 1540 | #[serde(rename = "banner")] 1541 | pub banner: Avatar, 1542 | 1543 | #[serde(rename = "badges")] 1544 | pub badges: Vec, 1545 | 1546 | #[serde(rename = "headerLinks")] 1547 | pub header_links: HeaderLinks, 1548 | 1549 | #[serde(rename = "subscribeButton")] 1550 | pub subscribe_button: SubscribeButtonClass, 1551 | 1552 | #[serde(rename = "subscriberCountText")] 1553 | pub subscriber_count_text: SubscriberCountText, 1554 | 1555 | #[serde(rename = "tvBanner")] 1556 | pub tv_banner: Avatar, 1557 | 1558 | #[serde(rename = "mobileBanner")] 1559 | pub mobile_banner: Avatar, 1560 | 1561 | #[serde(rename = "trackingParams")] 1562 | pub tracking_params: TrackingParams, 1563 | } 1564 | 1565 | #[derive(Serialize, Deserialize)] 1566 | pub struct HeaderLinks { 1567 | #[serde(rename = "channelHeaderLinksRenderer")] 1568 | pub channel_header_links_renderer: ChannelHeaderLinksRenderer, 1569 | } 1570 | 1571 | #[derive(Serialize, Deserialize)] 1572 | pub struct ChannelHeaderLinksRenderer { 1573 | #[serde(rename = "primaryLinks")] 1574 | pub primary_links: Vec, 1575 | 1576 | #[serde(rename = "secondaryLinks")] 1577 | pub secondary_links: Vec, 1578 | } 1579 | 1580 | #[derive(Serialize, Deserialize)] 1581 | pub struct AryLink { 1582 | #[serde(rename = "navigationEndpoint")] 1583 | pub navigation_endpoint: PrimaryLinkNavigationEndpoint, 1584 | 1585 | #[serde(rename = "icon")] 1586 | pub icon: Icon, 1587 | 1588 | #[serde(rename = "title")] 1589 | pub title: ContentClass, 1590 | } 1591 | 1592 | #[derive(Serialize, Deserialize)] 1593 | pub struct Icon { 1594 | #[serde(rename = "thumbnails")] 1595 | pub thumbnails: Vec, 1596 | } 1597 | 1598 | #[derive(Serialize, Deserialize)] 1599 | pub struct SubscribeButtonClass { 1600 | #[serde(rename = "buttonRenderer")] 1601 | pub button_renderer: SubscribeButtonButtonRenderer, 1602 | } 1603 | 1604 | #[derive(Serialize, Deserialize)] 1605 | pub struct SubscribeButtonButtonRenderer { 1606 | #[serde(rename = "style")] 1607 | pub style: String, 1608 | 1609 | #[serde(rename = "size")] 1610 | pub size: Size, 1611 | 1612 | #[serde(rename = "isDisabled")] 1613 | pub is_disabled: bool, 1614 | 1615 | #[serde(rename = "text")] 1616 | pub text: TextClass, 1617 | 1618 | #[serde(rename = "navigationEndpoint")] 1619 | pub navigation_endpoint: Option, 1620 | 1621 | #[serde(rename = "trackingParams")] 1622 | pub tracking_params: String, 1623 | 1624 | #[serde(rename = "command")] 1625 | pub command: Option, 1626 | } 1627 | 1628 | #[derive(Serialize, Deserialize)] 1629 | pub struct Command { 1630 | #[serde(rename = "clickTrackingParams")] 1631 | pub click_tracking_params: String, 1632 | 1633 | #[serde(rename = "commandMetadata")] 1634 | pub command_metadata: ServiceEndpointCommandMetadata, 1635 | 1636 | #[serde(rename = "signalServiceEndpoint")] 1637 | pub signal_service_endpoint: CommandSignalServiceEndpoint, 1638 | } 1639 | 1640 | #[derive(Serialize, Deserialize)] 1641 | pub struct CommandSignalServiceEndpoint { 1642 | #[serde(rename = "signal")] 1643 | pub signal: Signal, 1644 | 1645 | #[serde(rename = "actions")] 1646 | pub actions: Vec, 1647 | } 1648 | 1649 | #[derive(Serialize, Deserialize)] 1650 | pub struct IndigoAction { 1651 | #[serde(rename = "clickTrackingParams")] 1652 | pub click_tracking_params: String, 1653 | 1654 | #[serde(rename = "signalAction")] 1655 | pub signal_action: SignalAction, 1656 | } 1657 | 1658 | #[derive(Serialize, Deserialize)] 1659 | pub struct SignalAction { 1660 | #[serde(rename = "signal")] 1661 | pub signal: String, 1662 | } 1663 | 1664 | #[derive(Serialize, Deserialize)] 1665 | pub struct FluffyNavigationEndpoint { 1666 | #[serde(rename = "clickTrackingParams")] 1667 | pub click_tracking_params: String, 1668 | 1669 | #[serde(rename = "commandMetadata")] 1670 | pub command_metadata: PurpleCommandMetadata, 1671 | 1672 | #[serde(rename = "modalEndpoint")] 1673 | pub modal_endpoint: ModalEndpoint, 1674 | } 1675 | 1676 | #[derive(Serialize, Deserialize)] 1677 | pub struct ModalEndpoint { 1678 | #[serde(rename = "modal")] 1679 | pub modal: Modal, 1680 | } 1681 | 1682 | #[derive(Serialize, Deserialize)] 1683 | pub struct Modal { 1684 | #[serde(rename = "modalWithTitleAndButtonRenderer")] 1685 | pub modal_with_title_and_button_renderer: ModalWithTitleAndButtonRenderer, 1686 | } 1687 | 1688 | #[derive(Serialize, Deserialize)] 1689 | pub struct ModalWithTitleAndButtonRenderer { 1690 | #[serde(rename = "title")] 1691 | pub title: ContentClass, 1692 | 1693 | #[serde(rename = "content")] 1694 | pub content: ContentClass, 1695 | 1696 | #[serde(rename = "button")] 1697 | pub button: Button, 1698 | } 1699 | 1700 | #[derive(Serialize, Deserialize)] 1701 | pub struct Button { 1702 | #[serde(rename = "buttonRenderer")] 1703 | pub button_renderer: ButtonButtonRenderer, 1704 | } 1705 | 1706 | #[derive(Serialize, Deserialize)] 1707 | pub struct ButtonButtonRenderer { 1708 | #[serde(rename = "style")] 1709 | pub style: String, 1710 | 1711 | #[serde(rename = "size")] 1712 | pub size: Size, 1713 | 1714 | #[serde(rename = "isDisabled")] 1715 | pub is_disabled: bool, 1716 | 1717 | #[serde(rename = "text")] 1718 | pub text: ContentClass, 1719 | 1720 | #[serde(rename = "navigationEndpoint")] 1721 | pub navigation_endpoint: TentacledNavigationEndpoint, 1722 | 1723 | #[serde(rename = "trackingParams")] 1724 | pub tracking_params: String, 1725 | } 1726 | 1727 | #[derive(Serialize, Deserialize)] 1728 | pub struct TentacledNavigationEndpoint { 1729 | #[serde(rename = "clickTrackingParams")] 1730 | pub click_tracking_params: String, 1731 | 1732 | #[serde(rename = "commandMetadata")] 1733 | pub command_metadata: EndpointCommandMetadata, 1734 | 1735 | #[serde(rename = "signInEndpoint")] 1736 | pub sign_in_endpoint: PurpleSignInEndpoint, 1737 | } 1738 | 1739 | #[derive(Serialize, Deserialize)] 1740 | pub struct PurpleSignInEndpoint { 1741 | #[serde(rename = "nextEndpoint")] 1742 | pub next_endpoint: NextEndpointClass, 1743 | 1744 | #[serde(rename = "continueAction")] 1745 | pub continue_action: String, 1746 | 1747 | #[serde(rename = "idamTag")] 1748 | pub idam_tag: String, 1749 | } 1750 | 1751 | #[derive(Serialize, Deserialize)] 1752 | pub struct Metadata { 1753 | #[serde(rename = "channelMetadataRenderer")] 1754 | pub channel_metadata_renderer: ChannelMetadataRenderer, 1755 | } 1756 | 1757 | #[derive(Serialize, Deserialize)] 1758 | pub struct ChannelMetadataRenderer { 1759 | #[serde(rename = "title")] 1760 | pub title: TitleEnum, 1761 | 1762 | #[serde(rename = "description")] 1763 | pub description: String, 1764 | 1765 | #[serde(rename = "rssUrl")] 1766 | pub rss_url: String, 1767 | 1768 | #[serde(rename = "externalId")] 1769 | pub external_id: Id, 1770 | 1771 | #[serde(rename = "keywords")] 1772 | pub keywords: String, 1773 | 1774 | #[serde(rename = "ownerUrls")] 1775 | pub owner_urls: Vec, 1776 | 1777 | #[serde(rename = "avatar")] 1778 | pub avatar: Avatar, 1779 | 1780 | #[serde(rename = "channelUrl")] 1781 | pub channel_url: String, 1782 | 1783 | #[serde(rename = "isFamilySafe")] 1784 | pub is_family_safe: bool, 1785 | 1786 | #[serde(rename = "availableCountryCodes")] 1787 | pub available_country_codes: Vec, 1788 | 1789 | #[serde(rename = "androidDeepLink")] 1790 | pub android_deep_link: String, 1791 | 1792 | #[serde(rename = "androidAppindexingLink")] 1793 | pub android_appindexing_link: String, 1794 | 1795 | #[serde(rename = "iosAppindexingLink")] 1796 | pub ios_appindexing_link: String, 1797 | 1798 | #[serde(rename = "vanityChannelUrl")] 1799 | pub vanity_channel_url: String, 1800 | } 1801 | 1802 | #[derive(Serialize, Deserialize)] 1803 | pub struct Microformat { 1804 | #[serde(rename = "microformatDataRenderer")] 1805 | pub microformat_data_renderer: MicroformatDataRenderer, 1806 | } 1807 | 1808 | #[derive(Serialize, Deserialize)] 1809 | pub struct MicroformatDataRenderer { 1810 | #[serde(rename = "urlCanonical")] 1811 | pub url_canonical: String, 1812 | 1813 | #[serde(rename = "title")] 1814 | pub title: TitleEnum, 1815 | 1816 | #[serde(rename = "description")] 1817 | pub description: String, 1818 | 1819 | #[serde(rename = "thumbnail")] 1820 | pub thumbnail: Avatar, 1821 | 1822 | #[serde(rename = "siteName")] 1823 | pub site_name: String, 1824 | 1825 | #[serde(rename = "appName")] 1826 | pub app_name: String, 1827 | 1828 | #[serde(rename = "androidPackage")] 1829 | pub android_package: String, 1830 | 1831 | #[serde(rename = "iosAppStoreId")] 1832 | pub ios_app_store_id: String, 1833 | 1834 | #[serde(rename = "iosAppArguments")] 1835 | pub ios_app_arguments: String, 1836 | 1837 | #[serde(rename = "ogType")] 1838 | pub og_type: String, 1839 | 1840 | #[serde(rename = "urlApplinksWeb")] 1841 | pub url_applinks_web: String, 1842 | 1843 | #[serde(rename = "urlApplinksIos")] 1844 | pub url_applinks_ios: String, 1845 | 1846 | #[serde(rename = "urlApplinksAndroid")] 1847 | pub url_applinks_android: String, 1848 | 1849 | #[serde(rename = "urlTwitterIos")] 1850 | pub url_twitter_ios: String, 1851 | 1852 | #[serde(rename = "urlTwitterAndroid")] 1853 | pub url_twitter_android: String, 1854 | 1855 | #[serde(rename = "twitterCardType")] 1856 | pub twitter_card_type: String, 1857 | 1858 | #[serde(rename = "twitterSiteHandle")] 1859 | pub twitter_site_handle: String, 1860 | 1861 | #[serde(rename = "schemaDotOrgType")] 1862 | pub schema_dot_org_type: String, 1863 | 1864 | #[serde(rename = "noindex")] 1865 | pub noindex: bool, 1866 | 1867 | #[serde(rename = "unlisted")] 1868 | pub unlisted: bool, 1869 | 1870 | #[serde(rename = "familySafe")] 1871 | pub family_safe: bool, 1872 | 1873 | #[serde(rename = "tags")] 1874 | pub tags: Vec, 1875 | 1876 | #[serde(rename = "availableCountries")] 1877 | pub available_countries: Vec, 1878 | 1879 | #[serde(rename = "linkAlternates")] 1880 | pub link_alternates: Vec, 1881 | } 1882 | 1883 | #[derive(Serialize, Deserialize)] 1884 | pub struct LinkAlternate { 1885 | #[serde(rename = "hrefUrl")] 1886 | pub href_url: String, 1887 | } 1888 | 1889 | #[derive(Serialize, Deserialize)] 1890 | pub struct ResponseContext { 1891 | #[serde(rename = "serviceTrackingParams")] 1892 | pub service_tracking_params: Vec, 1893 | 1894 | #[serde(rename = "maxAgeSeconds")] 1895 | pub max_age_seconds: i64, 1896 | 1897 | #[serde(rename = "mainAppWebResponseContext")] 1898 | pub main_app_web_response_context: MainAppWebResponseContext, 1899 | 1900 | #[serde(rename = "webResponseContextExtensionData")] 1901 | pub web_response_context_extension_data: WebResponseContextExtensionData, 1902 | } 1903 | 1904 | #[derive(Serialize, Deserialize)] 1905 | pub struct MainAppWebResponseContext { 1906 | #[serde(rename = "loggedOut")] 1907 | pub logged_out: bool, 1908 | } 1909 | 1910 | #[derive(Serialize, Deserialize)] 1911 | pub struct ServiceTrackingParam { 1912 | #[serde(rename = "service")] 1913 | pub service: String, 1914 | 1915 | #[serde(rename = "params")] 1916 | pub params: Vec, 1917 | } 1918 | 1919 | #[derive(Serialize, Deserialize)] 1920 | pub struct WebResponseContextExtensionData { 1921 | #[serde(rename = "ytConfigData")] 1922 | pub yt_config_data: YtConfigData, 1923 | 1924 | #[serde(rename = "hasDecorated")] 1925 | pub has_decorated: bool, 1926 | } 1927 | 1928 | #[derive(Serialize, Deserialize)] 1929 | pub struct YtConfigData { 1930 | #[serde(rename = "visitorData")] 1931 | pub visitor_data: String, 1932 | 1933 | #[serde(rename = "rootVisualElementType")] 1934 | pub root_visual_element_type: i64, 1935 | } 1936 | 1937 | #[derive(Serialize, Deserialize)] 1938 | pub struct Topbar { 1939 | #[serde(rename = "desktopTopbarRenderer")] 1940 | pub desktop_topbar_renderer: DesktopTopbarRenderer, 1941 | } 1942 | 1943 | #[derive(Serialize, Deserialize)] 1944 | pub struct DesktopTopbarRenderer { 1945 | #[serde(rename = "logo")] 1946 | pub logo: Logo, 1947 | 1948 | #[serde(rename = "searchbox")] 1949 | pub searchbox: Searchbox, 1950 | 1951 | #[serde(rename = "trackingParams")] 1952 | pub tracking_params: String, 1953 | 1954 | #[serde(rename = "topbarButtons")] 1955 | pub topbar_buttons: Vec, 1956 | 1957 | #[serde(rename = "hotkeyDialog")] 1958 | pub hotkey_dialog: HotkeyDialog, 1959 | 1960 | #[serde(rename = "backButton")] 1961 | pub back_button: BackButtonClass, 1962 | 1963 | #[serde(rename = "forwardButton")] 1964 | pub forward_button: BackButtonClass, 1965 | 1966 | #[serde(rename = "a11ySkipNavigationButton")] 1967 | pub a11_y_skip_navigation_button: SubscribeButtonClass, 1968 | 1969 | #[serde(rename = "voiceSearchButton")] 1970 | pub voice_search_button: VoiceSearchButtonClass, 1971 | } 1972 | 1973 | #[derive(Serialize, Deserialize)] 1974 | pub struct BackButtonClass { 1975 | #[serde(rename = "buttonRenderer")] 1976 | pub button_renderer: BackButtonButtonRenderer, 1977 | } 1978 | 1979 | #[derive(Serialize, Deserialize)] 1980 | pub struct BackButtonButtonRenderer { 1981 | #[serde(rename = "trackingParams")] 1982 | pub tracking_params: String, 1983 | 1984 | #[serde(rename = "command")] 1985 | pub command: Command, 1986 | } 1987 | 1988 | #[derive(Serialize, Deserialize)] 1989 | pub struct HotkeyDialog { 1990 | #[serde(rename = "hotkeyDialogRenderer")] 1991 | pub hotkey_dialog_renderer: HotkeyDialogRenderer, 1992 | } 1993 | 1994 | #[derive(Serialize, Deserialize)] 1995 | pub struct HotkeyDialogRenderer { 1996 | #[serde(rename = "title")] 1997 | pub title: TextClass, 1998 | 1999 | #[serde(rename = "sections")] 2000 | pub sections: Vec
, 2001 | 2002 | #[serde(rename = "dismissButton")] 2003 | pub dismiss_button: SubscribeButtonClass, 2004 | 2005 | #[serde(rename = "trackingParams")] 2006 | pub tracking_params: String, 2007 | } 2008 | 2009 | #[derive(Serialize, Deserialize)] 2010 | pub struct Section { 2011 | #[serde(rename = "hotkeyDialogSectionRenderer")] 2012 | pub hotkey_dialog_section_renderer: HotkeyDialogSectionRenderer, 2013 | } 2014 | 2015 | #[derive(Serialize, Deserialize)] 2016 | pub struct HotkeyDialogSectionRenderer { 2017 | #[serde(rename = "title")] 2018 | pub title: TextClass, 2019 | 2020 | #[serde(rename = "options")] 2021 | pub options: Vec