diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8550d6d..b2766af 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -27,15 +27,12 @@ jobs: version: [ 'macos-latest', 'ubuntu-latest', 'windows-latest' ] runs-on: ${{ matrix.version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: setup | rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - default: true - profile: minimal components: clippy, rustfmt - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo check lint: @@ -44,21 +41,18 @@ jobs: strategy: fail-fast: false matrix: - version: [ 'macos-latest', 'ubuntu-latest', 'windows-latest' ] + version: [ 'macos-latest', 'ubuntu-20.04', 'windows-2022' ] cargo-cmd: - fmt --all -- --check - clippy --all-targets --all-features -- -D warnings runs-on: ${{ matrix.version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: setup | rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - default: true - profile: minimal components: clippy, rustfmt - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - run: cargo ${{ matrix['cargo-cmd'] }} release: @@ -69,13 +63,7 @@ jobs: outputs: version: ${{ steps.tag_name.outputs.current_version }} steps: - - uses: actions/checkout@v2 - - name: setup | rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - default: true - profile: minimal + - uses: actions/checkout@v4 - name: Get version from tag id: tag_name run: | @@ -96,54 +84,52 @@ jobs: name: release binary assets needs: release runs-on: ${{ matrix.os }} + env: + CC_aarch64_unknown_linux_musl: "clang" + AR_aarch64_unknown_linux_musl: "llvm-ar" + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld" strategy: fail-fast: false matrix: include: - target: x86_64-unknown-linux-musl - os: ubuntu-latest - cross: true + os: ubuntu-20.04 binName: cbc-sl - target: aarch64-unknown-linux-musl - os: ubuntu-latest - cross: true + os: ubuntu-20.04 binName: cbc-sl - target: x86_64-apple-darwin os: macos-latest - cross: false + binName: cbc-sl + - target: aarch64-apple-darwin + os: macos-latest binName: cbc-sl - target: i686-pc-windows-msvc - os: windows-latest - cross: false + os: windows-2022 binName: cbc-sl.exe - target: x86_64-pc-windows-msvc - os: windows-latest - cross: false + os: windows-2022 binName: cbc-sl.exe steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Install musl tools + if: ${{ contains(matrix.os, 'ubuntu') }} + run: sudo apt-get install -y musl-dev musl-tools clang llvm - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: ${{ matrix.target }} - override: true - - uses: Swatinem/rust-cache@v1 - - name: Build - uses: actions-rs/cargo@v1 + uses: dtolnay/rust-toolchain@stable with: - command: build - use-cross: ${{ matrix.cross }} - args: --release --target=${{ matrix.target }} + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - run: cargo build --release --target=${{ matrix.target }} - name: Smoke Test - uses: actions-rs/cargo@v1 - with: - command: run - use-cross: ${{ matrix.cross }} - args: --release --target=${{ matrix.target }} -- -V + if: ${{ !contains(matrix.target, 'aarch64') }} + run: cargo run --release --target=${{ matrix.target }} -- -V - name: Move Binary id: mv run: mv "target/${{ matrix.target }}/release/${{ matrix.binName }}" . + - name: chmod binary #not working? ignored by zip action? + if: ${{ matrix.os == 'ubuntu-latest' }} + run: chmod +x "${{ matrix.binName }}" - name: Zip Files uses: vimtor/action-zip@v1 id: archive @@ -151,7 +137,7 @@ jobs: files: README.md LICENSE ${{ matrix.binName }} dest: cbc-sl-${{ needs.release.outputs.version }}-${{ matrix.target }}.zip - name: Upload Archive - uses: ncipollo/release-action@v1.8.8 + uses: ncipollo/release-action@v1 with: token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true diff --git a/.github/workflows/wf.yaml b/.github/workflows/wf.yaml index c1d528d..3b06866 100644 --- a/.github/workflows/wf.yaml +++ b/.github/workflows/wf.yaml @@ -11,47 +11,33 @@ name: CI jobs: check: name: Check - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - default: true - toolchain: stable - - uses: actions-rs/cargo@v1 - with: - command: check + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo check --no-default-features + - run: cargo check --all-features fmt: name: Rustfmt - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - default: true - toolchain: stable components: rustfmt - - run: rustup component add rustfmt - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --all -- --check clippy: name: Clippy - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - default: true - toolchain: stable components: clippy - - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --no-default-features -- -D warnings + - run: cargo clippy --all-features -- -D warnings diff --git a/Cargo.lock b/Cargo.lock index 90063e3..d782fdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,110 +10,131 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] -name = "anyhow" -version = "1.0.53" +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle-parse" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.9", + "utf8parse", ] [[package]] -name = "autocfg" -version = "1.0.1" +name = "anstyle-query" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] [[package]] -name = "base64" -version = "0.13.0" +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] -name = "bitflags" -version = "1.3.2" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cbc-sl" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", - "chrono", "clap", "extend", "hls_m3u8", + "jiff", + "lazy-regex", "once_cell", "owo-colors", - "quick-xml", "regex", "serde", "serde_json", "ureq", "url", "windows", + "windows-strings", ] [[package]] name = "cc" -version = "1.0.72" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" @@ -122,54 +143,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.19" +name = "clap" +version = "4.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" dependencies = [ - "libc", - "num-integer", - "num-traits", - "time", - "winapi 0.3.9", + "clap_builder", + "clap_derive", ] [[package]] -name = "chunked_transfer" -version = "1.4.0" +name = "clap_builder" +version = "4.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] -name = "clap" -version = "3.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" dependencies = [ - "atty", - "bitflags", - "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", - "strsim 0.10.0", - "termcolor", - "textwrap", + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "3.0.14" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ - "heck 0.4.0", - "proc-macro-error", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "convert_case" version = "0.4.0" @@ -178,9 +196,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "crc32fast" -version = "1.3.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -206,7 +224,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.9.3", - "syn", + "syn 1.0.109", ] [[package]] @@ -217,7 +235,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -230,7 +248,7 @@ dependencies = [ "derive_builder_core", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -242,43 +260,40 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.72", ] [[package]] name = "extend" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5216e387a76eebaaf11f6d871ec8a4aae0b25f05456ee21f228e024b1b3610" +checksum = "311a6d2f1f9d60bff73d2c78a0af97ed27f79672f15c238192a5bbb64db56d00" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ - "cfg-if", "crc32fast", - "libc", "miniz_oxide", ] @@ -290,11 +305,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] @@ -308,10 +322,15 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.11.2" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] name = "hashmap_derive" @@ -321,7 +340,7 @@ checksum = "fb30bf173e72cc31b5265dac095423ca14e7789ff7c3b0e6096a37a996f12883" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -335,18 +354,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -377,201 +393,180 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] -name = "indexmap" -version = "1.8.0" +name = "is-terminal" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "autocfg", - "hashbrown", + "hermit-abi", + "libc", + "windows-sys", ] [[package]] name = "is_ci" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "js-sys" -version = "0.3.56" +name = "jiff" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "8fbd74d242eb38b2f0d73c9ad5e7e4371525d856e64018939874380494ce4ed6" dependencies = [ - "wasm-bindgen", + "jiff-tzdb-platform", + "windows-sys", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "jiff-tzdb" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "05fac328b3df1c0f18a3c2ab6cb7e06e4e549f366017d796e3e66b6d6889abe6" [[package]] -name = "libc" -version = "0.2.117" +name = "jiff-tzdb-platform" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" +checksum = "f8da387d5feaf355954c2c122c194d6df9c57d865125a67984bb453db5336940" +dependencies = [ + "jiff-tzdb", +] [[package]] -name = "log" -version = "0.4.14" +name = "lazy-regex" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "576c8060ecfdf2e56995cf3274b4f2d71fa5e4fa3607c1c0b63c10180ee58741" dependencies = [ - "cfg-if", + "lazy-regex-proc_macros", + "once_cell", + "regex", ] [[package]] -name = "matches" -version = "0.1.9" +name = "lazy-regex-proc_macros" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "9efb9e65d4503df81c615dc33ff07042a9408ac7f26b45abee25566f7fbfd12c" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.72", +] [[package]] -name = "memchr" -version = "2.4.1" +name = "libc" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] -name = "miniz_oxide" -version = "0.4.4" +name = "log" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "no-std-compat" -version = "0.2.0" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df270209a7f04d62459240d890ecb792714d5db12c92937823574a09930276b4" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "num-integer" -version = "0.1.44" +name = "miniz_oxide" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "autocfg", - "num-traits", + "adler", ] [[package]] -name = "num-traits" -version = "0.2.14" +name = "no-std-compat" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] +checksum = "df270209a7f04d62459240d890ecb792714d5db12c92937823574a09930276b4" [[package]] name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "os_str_bytes" -version = "6.0.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "owo-colors" -version = "3.2.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20448fd678ec04e6ea15bbe0476874af65e98a01515d667aa49f1434dc44ebf4" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" dependencies = [ "supports-color", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "unicode-ident", ] [[package]] -name = "proc-macro2" +name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "unicode-xid", + "proc-macro2", ] [[package]] -name = "quick-xml" -version = "0.22.0" +name = "regex" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ + "aho-corasick", "memchr", - "serde", -] - -[[package]] -name = "quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" -dependencies = [ - "proc-macro2", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex" -version = "1.5.4" +name = "regex-automata" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -580,23 +575,23 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -610,63 +605,73 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "log", + "once_cell", "ring", - "sct", - "webpki", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "ryu" -version = "1.0.9" +name = "rustls-pki-types" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] -name = "sct" -version = "0.7.0" +name = "rustls-webpki" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "semver" -version = "1.0.5" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -682,32 +687,31 @@ dependencies = [ "from_map", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "socks" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f86c7635fadf2814201a4f67efefb0007588ae7422ce299f354ab5c97f61ae" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" dependencies = [ "byteorder", "libc", - "winapi 0.2.8", - "ws2_32-sys", + "winapi", ] [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "stable-vec" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80dfb7bb28f3d2fa50566793349d633b33f938543154be8071610ea9f590d8ca" +checksum = "d1dff32a2ce087283bec878419027cebd888760d8760b2941ad0843531dc9ec8" dependencies = [ "no-std-compat", ] @@ -720,9 +724,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -742,344 +746,328 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "supports-color" -version = "1.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" dependencies = [ - "atty", + "is-terminal", "is_ci", ] [[package]] name = "syn" -version = "1.0.86" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] -name = "termcolor" -version = "1.1.2" +name = "syn" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "unicode-ident", ] -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" - [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi", - "winapi 0.3.9", + "syn 2.0.72", ] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.4.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64", "brotli-decompressor", - "chunked_transfer", "flate2", "log", "once_cell", "rustls", + "rustls-pki-types", "serde", "serde_json", "socks", "url", - "webpki", "webpki-roots", ] [[package]] name = "url" -version = "2.2.2" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] [[package]] -name = "version_check" -version = "0.9.4" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.79" +name = "webpki-roots" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", + "rustls-pki-types", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.79" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.79" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "wasm-bindgen-shared" -version = "0.2.79" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "web-sys" -version = "0.3.56" +name = "windows" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "js-sys", - "wasm-bindgen", + "windows-core", + "windows-targets", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "windows-core" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "ring", - "untrusted", + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", ] [[package]] -name = "webpki-roots" -version = "0.22.2" +name = "windows-implement" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ - "webpki", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] -name = "winapi" -version = "0.2.8" +name = "windows-interface" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-result" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] [[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows-strings" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "winapi 0.3.9", + "windows-result", + "windows-targets", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] [[package]] -name = "windows" -version = "0.32.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "zeroize" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index c5a5d81..347265d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cbc-sl" -version = "0.5.2" +version = "0.6.0" edition = "2021" authors = ["Malloc Voidstar <1284317+AlyoshaVasilieva@users.noreply.github.com>"] license = "Apache-2.0" @@ -9,18 +9,23 @@ description = "watch the Olympics via CBC.ca with streamlink" [dependencies] anyhow = "1.0.42" ureq = { version = "2.4", features = ["socks-proxy", "json", "brotli"] } -clap = { version = "3.0.14", features = ["derive"] } +clap = { version = "4.5.9", features = ["derive"] } regex = { version = "1.0", default-features = false, features = ["std", "perf"] } +lazy-regex = "3.1" serde = { version = "1.0.126", features = ["derive"] } serde_json = "1.0.64" once_cell = "1.8" -extend = "1.1.1" -chrono = { version = "0.4.19", default-features = false, features = ["std", "clock", "oldtime"] } -owo-colors = { version = "3.2", features = ["supports-colors"] } +extend = "1.2" +owo-colors = { version = "4.0", features = ["supports-colors"] } hls_m3u8 = "0.4.1" url = "2.2.2" -quick-xml = { version = "0.22.0", features = ["serialize"] } +windows-strings = "0.1" +jiff = "0.1" [target.'cfg(windows)'.dependencies.windows] -version = "0.32.0" -features = ["alloc", "Win32_System_Console", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Security"] \ No newline at end of file +version = "0.58" +features = ["Win32_System_Console", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Security"] + +[profile.release] +codegen-units = 1 +lto = true diff --git a/README.md b/README.md index 456aeae..52d7e4f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ so I wrote this. This is quick and very dirty. I don't intend to expand it beyond watching the Olympics or fix it if it breaks once I'm done watching them. (Update: Fixed for Beijing 2022.) +(Update: Fixed for Paris 2024.) ### Guide @@ -13,17 +14,30 @@ or fix it if it breaks once I'm done watching them. (Update: Fixed for Beijing 2 2. Build from source (`cargo install --git https://github.com/AlyoshaVasilieva/cbc-sl`) or [download a pre-built binary](https://github.com/AlyoshaVasilieva/cbc-sl/releases) 3. Run `cbc-sl --list` or `cbc-sl --replays` to see what you can watch -4. Run `cbc-sl ID` to call streamlink +4. Run `cbc-sl ID` to call streamlink. (The IDs look like `9.6441556`). -You can also use URLs, such as `cbc-sl https://www.cbc.ca/player/play/1920742467707` +You can also use URLs, such as `cbc-sl https://www.cbc.ca/player/play/video/9.6441556` -Use `-p IP:port` option to specify a proxy. Supports HTTP, SOCKS4a, SOCKS5H proxies. +Use `-p scheme://IP:port` to specify a proxy. Supports HTTP, SOCKS4a, SOCKS5H proxies. [sl]: https://github.com/streamlink/streamlink +### Streamlink configuration + +Streamlink must be set to handle the stream itself (via e.g. `player-continuous-http`), +*not* to allow the player to handle it (such as using `player-passthrough=http,hls`). + +### I get a weird error about invalid JSON or something else + +You're probably running into the geo-blocking. CBC tries to block VPNs. If you can't +watch the stream/replay on CBC.ca, it's probably this. (Note that the CBC player may report a +"timeout", rather than saying you're blocked.) + +If you *can* watch streams on the website, but *can't* with this tool, open an issue. + ### Notes * If your player fails to detect audio, look for a configuration option like "Stream Analysis Duration" and increase it. -* You can't seek, even in replays. Limitation of streamlink. -* It's 720p30 at a less-than-perfect bitrate. +* You can't seek, even in replays, unless your player has its own cache. Limitation of streamlink. +* CBC's streams are 1080p30. diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..4997440 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,321 @@ +use anyhow::Result; +use jiff::{tz::TimeZone, Span, Timestamp, Zoned}; +use owo_colors::{OwoColorize, Stream::Stdout}; +use serde::Deserialize; + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct GqlResponse { + pub(crate) data: Data, + // pub(crate) extensions: Extensions, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Data { + pub(crate) all_content_items: AllContentItems, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct AllContentItems { + pub(crate) nodes: Vec, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Node { + pub(crate) id: i64, + pub(crate) url: String, + pub(crate) title: String, + // pub(crate) section_list: Vec>, + // pub(crate) section_labels: Vec>, + // pub(crate) related_links: Vec>, + // pub(crate) deck: Option, + // pub(crate) description: String, + pub(crate) flag: Flag, + // pub(crate) image_large: String, + // pub(crate) image: HashMap, + // pub(crate) source: String, + // pub(crate) source_id: String, + pub(crate) published_at: String, + pub(crate) updated_at: String, + // pub(crate) sponsor: Option, + #[serde(rename = "type")] + // pub(crate) node_type: Type, + pub(crate) node_type: String, + // pub(crate) show_name: Option, + // pub(crate) authors: Vec>, + // pub(crate) comments_enabled: bool, + // pub(crate) contextual_headlines: Vec>, + // pub(crate) media_id: Option, + pub(crate) media: Media, + // pub(crate) headline_data: Option, + // pub(crate) components: Option, + // pub(crate) categories: Vec, +} + +impl Node { + pub(crate) fn proper_id(&self) -> &str { + self.url.split('/').last().unwrap() + } + + pub(crate) fn to_human(&self, full_urls: bool) -> Result { + let now = Zoned::now(); + let date = self.date()?; + let same_day = now.date() == date.date(); + let fmt = if same_day { "%H:%M" } else { "%b %d %H:%M" }; + let date_time = self.date()?.strftime(fmt); + + // live or upcoming + let lu = matches!(self.flag, Flag::Live); + let note = if lu { + match self.is_live()? { + true => format!( + "({} @ {}) ", + "STARTED ".if_supports_color(Stdout, |text| text.bright_white().on_black()), + date_time + ), + false => format!( + "({} @ {}) ", + "UPCOMING".if_supports_color(Stdout, |text| text.white().on_black()), + date_time + ), + } + } else { + format!("({}) ", date_time) + }; + let prefix = if full_urls { "https://www.cbc.ca/player/play/video/" } else { "" }; + Ok(format!("{prefix}{} - {note}{}", self.proper_id(), self.title)) + } + + pub(crate) fn timestamp(&self) -> Result { + Ok(Timestamp::from_millisecond(self.published_at.parse()?)?) + } + + pub(crate) fn date(&self) -> Result { + Ok(Zoned::new(self.timestamp()?, TimeZone::system())) + } + + pub(crate) fn is_live(&self) -> Result { + let start = self.timestamp()?; + let duration = self.media.duration.round() as i64; + let duration = Span::new().seconds(duration); + let end = start.checked_add(duration)?; + let now = Timestamp::now(); + Ok(start <= now && now <= end) + } +} + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// pub struct Category { +// pub(crate) name: String, +// pub(crate) slug: String, +// pub(crate) path: String, +// } + +#[derive(Copy, Debug, Clone, PartialEq, Deserialize)] +pub enum Flag { + Live, + Video, +} + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// pub struct Image { +// pub(crate) w: i64, +// pub(crate) fileurl: String, +// } + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Media { + pub(crate) duration: f64, + pub(crate) has_captions: bool, + pub(crate) stream_type: StreamType, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub enum StreamType { + Live, + #[serde(rename = "On-Demand")] + OnDemand, +} + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// #[serde(rename_all = "snake_case")] +// pub enum Type { +// Video, +// } + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// pub enum ShowName { +// #[serde(rename = "CBC Sports")] +// CbcSports, +// } + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// pub struct Extensions { +// pub(crate) warnings: Vec, +// } + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InitialState { + // #[serde(rename = "a11y")] + // pub(crate) a11_y: A11Y, + // pub(crate) app: App, + // pub(crate) author: Author, + // pub(crate) content: InitialStateContent, + // pub(crate) cookie_jar: CookieJar, + // pub(crate) detail: Detail, + // pub(crate) featureflags: Featureflags, + // pub(crate) feedback: Feedback, + // pub(crate) fixed: Fixed, + // pub(crate) flp: Flp, + // pub(crate) gdpr: Gdpr, + // pub(crate) live_radio: Option, + // pub(crate) loader: Loader, + // pub(crate) navigation: Navigation, + // pub(crate) newsletters: Newsletters, + // pub(crate) page: Page, + // pub(crate) persistent_player: PersistentPlayer, + // pub(crate) personalization: Personalization, + // pub(crate) plus: Plus, + // pub(crate) preferences: Preferences, + // pub(crate) regions: Regions, + // pub(crate) right_rail: RightRail, + // pub(crate) schedule: Schedule, + // pub(crate) search: Schedule, + // pub(crate) sectional_content: Schedule, + // pub(crate) ssr: Ssr, + // pub(crate) subject_content: SubjectContent, + // pub(crate) tracking: InitialStateTracking, + // pub(crate) trending: Trending, + pub(crate) video: Video, + // pub(crate) video_detail: VideoDetail, + // pub(crate) weather: Weather, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Video { + pub(crate) current_clip: CurrentClip, + // pub(crate) recommendations: CuratedPlaylist, + // pub(crate) trending_clips: CuratedPlaylist, + // pub(crate) curated_playlist: CuratedPlaylist, + // pub(crate) more_from_base_section: CuratedPlaylist, +} + +impl Video { + pub(crate) fn get_stream_urls(&self) -> StreamURLs { + let mut urls = StreamURLs { dai: None, medianet: None }; + for surl in &self.current_clip.media.assets { + if surl.asset_type == "platform-dai" { + // TODO https://pubads.g.doubleclick.net + // it requires a bit more work but I don't know if medianet is always present + } else if surl.asset_type == "medianet" { + urls.medianet = Some(surl.key.to_string()); + } + } + urls + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct StreamURLs { + pub(crate) dai: Option, + pub(crate) medianet: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CurrentClip { + pub(crate) source_id: String, + // pub(crate) media_id: Option, + pub(crate) source: String, + pub(crate) title: String, + // pub(crate) image: Image, + pub(crate) published_at: String, + // #[serde(rename = "type")] + // pub(crate) current_clip_type: StreamType, + // pub(crate) show_data: Option, + // pub(crate) show_name: Option, + // pub(crate) tags: Vec, + // pub(crate) concepts: Vec>, + pub(crate) media: CurrentClipMedia, + pub(crate) updated_at: String, + pub(crate) description: String, + // pub(crate) categories: Vec, + // pub(crate) section: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CurrentClipMedia { + pub(crate) id: i64, + // pub(crate) call_sign: Option, + pub(crate) assets: Vec, + // pub(crate) ad_order: String, + // pub(crate) ad_category_exclusion: Option, + // pub(crate) stream_type: StreamType, + // pub(crate) content_area: String, + // pub(crate) content_tier_id: i64, + pub(crate) duration: i64, + // pub(crate) genre: Option, + // pub(crate) clip_type: String, + // pub(crate) branded_sponsor_name: String, + // pub(crate) season: Option, + // pub(crate) episode: Option, + // pub(crate) region: String, + // pub(crate) sports: Spo, + // pub(crate) has_captions: bool, + // pub(crate) aspect_ratio: String, + // pub(crate) text_tracks: Vec>, + // pub(crate) chapters: Option, + // pub(crate) exclude_from_recommendations: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct Asset { + pub(crate) key: String, + #[serde(rename = "type")] + pub(crate) asset_type: String, + // pub(crate) options: Option, +} + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// pub struct Category2 { +// pub(crate) name: String, +// pub(crate) slug: String, +// } + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Stream { + pub(crate) url: String, + // pub(crate) message: Option, + pub(crate) error_code: i64, + pub(crate) params: Vec, + // pub(crate) bitrates: Vec, +} + +// #[derive(Debug, Clone, PartialEq, Deserialize)] +// pub struct Bitrate { +// pub(crate) bitrate: i64, +// pub(crate) width: i64, +// pub(crate) height: i64, +// pub(crate) lines: String, +// pub(crate) param: Option, +// pub(crate) max: i64, +// } + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct Param { + pub(crate) name: String, + pub(crate) value: Value, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(untagged)] +pub enum Value { + Integer(i64), + String(String), +} diff --git a/src/main.rs b/src/main.rs index 275e375..b47fc3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,30 @@ use std::path::PathBuf; use std::process::Command; -use anyhow::{anyhow, ensure, Result}; -use chrono::{DateTime, Local, NaiveDateTime, Utc}; +use anyhow::{anyhow, ensure, Context, Result}; use clap::Parser; use extend::ext; -use hls_m3u8::tags::VariantStream; -use hls_m3u8::MasterPlaylist; +use hls_m3u8::{tags::VariantStream, MasterPlaylist}; +use lazy_regex::{lazy_regex, regex}; use once_cell::sync::Lazy; use owo_colors::{OwoColorize, Stream::Stdout}; use regex::Regex; -use serde::{Deserialize, Serialize}; use serde_json::json; use ureq::{Agent, AgentBuilder, Proxy}; use url::Url; +use crate::api::{InitialState, Stream}; + +mod api; #[cfg(windows)] mod wincolors; // pretend to be a real browser const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ -(KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43"; +(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"; static ID_REGEX: Lazy = - Lazy::new(|| Regex::new(r#"https://www\.cbc\.ca/player/play/([[:digit:]]+)"#).unwrap()); + lazy_regex!(r#"(?:https://www\.cbc\.ca/player/play/video/)?([[:digit:]]+\.[[:digit:]]+)"#); #[derive(Debug, Parser)] #[clap(version)] @@ -43,13 +44,13 @@ struct Args { #[clap(short = 'a', long = "replays", conflicts_with_all(&["url", "list"]))] replays: bool, /// Streamlink log level - #[clap(long = "loglevel", possible_values(["none", "error", "warning", "info", "debug", "trace"]), default_value = "info")] + #[clap(long = "loglevel", value_parser(["none", "error", "warning", "info", "debug", "trace"]), default_value = "info")] loglevel: String, - /// Trust Streamlink to handle the master playlist. May require streamlink version < 2.3.0 or - /// > 3.1.1 - #[clap(short = 'T', long = "trust-streamlink")] - trust: bool, - /// Stream quality to request. Currently requires --trust-streamlink + /// Don't trust streamlink to handle the master playlist. Works around a bug in certain old + /// versions of streamlink. This shouldn't do anything on versions >3.1.1. + #[clap(short = 'T', long = "distrust-streamlink")] + distrust: bool, + /// Stream quality to request. Won't work if you're using --distrust-streamlink #[clap(short = 'q', long = "quality", default_value = "best")] quality: String, /// Streamlink bin name or path @@ -59,169 +60,90 @@ struct Args { #[clap(short = 'f', long = "full-urls")] full_urls: bool, /// CBC.ca URL or ID - #[clap(validator(probably_cbc), required_unless_present_any(["list", "replays"]))] + #[clap(value_parser(probably_cbc), required_unless_present_any(["list", "replays"]))] url: Option, } -fn get_live_and_upcoming(agent: &Agent) -> Result { - /// Hit "Show More" and look at the network monitor to get this - const LIVE_QUERY: &str = "query clipsFromCategory($categoryName: String, $page: Int, \ - $pageSize: Int, $onNowBeforeDate: Float, $onNowAfterDate: Float) {\n \ - mpxItems (categoryName: $categoryName, pageSize: $pageSize, page: $page, afterDate: \ - $onNowAfterDate, beforeDate: $onNowBeforeDate, sortBy: \"pubDate\") {\n \ - ...mediaItemBaseCard\n }\n } fragment mediaItemBaseCard on MediaItem {\n \ - ...mediaItemBase\n description\n sport\n showName\n captions {\n \ - src\n lang\n }\n} fragment mediaItemBase on MediaItem {\n id\n source\n \ - title\n thumbnail\n airDate\n duration\n contentArea\n categories {\n \ - name\n }\n isLive\n isVideo\n}"; +fn get_live_and_upcoming(agent: &Agent) -> Result { + const LIVE_QUERY: &str = + "query contentItemsByItemsQueryFilters($itemsQueryFilters:ItemsQueryFilters\ + ,$page:Int,$pageSize:Int,$minPubDate:String,$maxPubDate:String,$lineupOnly:Boolean,$offset:Int)\ + {allContentItems(itemsQueryFilters:$itemsQueryFilters,page:$page,pageSize:$pageSize,offset:\ + $offset,minPubDate:$minPubDate,maxPubDate:$maxPubDate,lineupOnly:$lineupOnly,targets:[WEB,ALL])\ + {nodes{...cardNode}}}fragment cardNode on ContentItem{id url title sectionList sectionLabels \ + relatedLinks{url title sourceId}deck description flag imageLarge image{_16x9_460:derivative\ + (preferredWidth:460,aspectRatio:\"16x9\"){w fileurl}_16x9_620:derivative(preferredWidth:620,\ + aspectRatio:\"16x9\"){w fileurl}_16x9_940:derivative(preferredWidth:940,aspectRatio:\"16x9\")\ + {w fileurl}square_220:derivative(preferredWidth:220,aspectRatio:\"square\"){w fileurl}}source \ + sourceId publishedAt updatedAt sponsor{name logo url external label}type showName authors{name \ + smallImageUrl}commentsEnabled contextualHeadlines{headline contextualLineupSlug}mediaId media\ + {duration hasCaptions streamType}headlineData{type title mediaId sourceId mediaDuration \ + publishedAt image}components{mainContent{url sectionList flag sourceId type}mainVisual{...on \ + ContentItem{publishedAt mediaId sourceId media{duration hasCaptions streamType}title \ + imageLarge}}primary secondary tertiary}categories{name slug path}}"; - let now = Utc::now(); - let start = now - chrono::Duration::hours(14); let query = json!({ "query": LIVE_QUERY, "variables": { - "categoryName": "Sports/Olympics/Winter/Live", - // "onNowBeforeDate": end.timestamp(), // give us the full schedule - "onNowAfterDate": start.timestamp(), - "pageSize": 24, // normally 4 - "page": 1 + "lineupOnly": false, + "page": 1, + "pageSize": 15, + "maxPubDate": "now+35d", + "minPubDate": "now-14h", + "itemsQueryFilters": { + "types": [ + "video" + ], + "categorySlugs": [ + "summer-olympics-live" + ], + "sort": "+publishedAt", + "mediaStreamType": "Live" + } } }); + Ok(agent.post("https://www.cbc.ca/graphql").send_json(query)?.into_json()?) } -fn get_replays(agent: &Agent) -> Result { - /// Built from the live query, with sorting removed so it gives proper order (recent first). - /// CBC's site uses a much different query for grabbing replays but this works and allows - /// reuse of deserialization. - const VOD_QUERY: &str = "query clipsFromCategory($categoryName: String, $page: Int, \ - $pageSize: Int, $onNowBeforeDate: Float, $onNowAfterDate: Float) {\n \ - mpxItems (categoryName: $categoryName, pageSize: $pageSize, page: $page, afterDate: \ - $onNowAfterDate, beforeDate: $onNowBeforeDate) {\n \ - ...mediaItemBaseCard\n }\n } fragment mediaItemBaseCard on MediaItem {\n \ - ...mediaItemBase\n description\n sport\n showName\n captions {\n \ - src\n lang\n }\n} fragment mediaItemBase on MediaItem {\n id\n source\n \ - title\n thumbnail\n airDate\n duration\n contentArea\n categories {\n \ - name\n }\n isLive\n isVideo\n}"; +fn get_replays(agent: &Agent) -> Result { + const VOD_QUERY: &str = "query contentItemsByItemsQueryFilters($itemsQueryFilters:\ + ItemsQueryFilters,$page:Int,$pageSize:Int,$minPubDate:String,$maxPubDate:String,\ + $lineupOnly:Boolean,$offset:Int){allContentItems(itemsQueryFilters:$itemsQueryFilters,\ + page:$page,pageSize:$pageSize,offset:$offset,minPubDate:$minPubDate,maxPubDate:$maxPubDate,\ + lineupOnly:$lineupOnly,targets:[WEB,ALL]){nodes{...cardNode}}}fragment cardNode on \ + ContentItem{id url title sectionList sectionLabels relatedLinks{url title sourceId}deck \ + description flag imageLarge image{_16x9_460:derivative(preferredWidth:460,aspectRatio:\"16x9\")\ + {w fileurl}_16x9_620:derivative(preferredWidth:620,aspectRatio:\"16x9\"){w fileurl}_16x9_940:\ + derivative(preferredWidth:940,aspectRatio:\"16x9\"){w fileurl}square_220:derivative\ + (preferredWidth:220,aspectRatio:\"square\"){w fileurl}}source sourceId publishedAt updatedAt \ + sponsor{name logo url external label}type showName authors{name smallImageUrl}commentsEnabled \ + contextualHeadlines{headline contextualLineupSlug}mediaId media{duration hasCaptions \ + streamType}headlineData{type title mediaId sourceId mediaDuration publishedAt image}components\ + {mainContent{url sectionList flag sourceId type}mainVisual{...on ContentItem{publishedAt \ + mediaId sourceId media{duration hasCaptions streamType}title imageLarge}}primary secondary \ + tertiary}categories{name slug path}}"; let query = json!({ "query": VOD_QUERY, "variables": { - "categoryName": "Sports/Olympics/Winter/Replays", - // "onNowBeforeDate": end.timestamp(), - // "onNowAfterDate": start.timestamp(), - "pageSize": 24, - "page": 1 + "lineupOnly": false, + "page": 1, + "pageSize": 16, + "itemsQueryFilters": { + "types": [ + "video" + ], + "sort": "-publishedAt", + "categorySlugs": [ + "summer-olympics-replays" + ] + } } }); Ok(agent.post("https://www.cbc.ca/graphql").send_json(query)?.into_json()?) } -#[derive(Debug, Serialize, Deserialize)] -pub struct GqlQuery { - pub(crate) query: String, - pub(crate) variables: Variables, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Variables { - #[serde(rename = "categoryName")] - pub(crate) category_name: String, - #[serde(rename = "onNowBeforeDate")] - pub(crate) on_now_before_date: i64, - #[serde(rename = "onNowAfterDate")] - pub(crate) on_now_after_date: i64, - #[serde(rename = "pageSize")] - pub(crate) page_size: i64, - pub(crate) page: i64, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GqlResponse { - pub(crate) data: GqlData, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GqlData { - #[serde(rename = "mpxItems")] - pub(crate) mpx_items: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct MpxItem { - pub(crate) id: i64, - // pub(crate) source: String, - pub(crate) title: String, - // pub(crate) thumbnail: String, - #[serde(rename = "airDate")] - pub(crate) air_date: i64, - // pub(crate) duration: i64, - // #[serde(rename = "contentArea")] - // pub(crate) content_area: String, - // pub(crate) categories: Vec, - #[serde(rename = "isLive")] - pub(crate) is_live: bool, - #[serde(rename = "isVideo")] - pub(crate) is_video: bool, - // pub(crate) description: String, - // pub(crate) sport: String, // included in title - // #[serde(rename = "showName")] - // pub(crate) show_name: String, - // pub(crate) captions: Captions, // not available -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum VidState { - LiveOrUpcoming, - Replay, -} - -impl MpxItem { - fn to_human(&self, state: VidState, full_urls: bool) -> String { - let air = self.air_date(); - let now = Local::now(); - let text = - if now.date() == air.date() { air.format("%H:%M") } else { air.format("%b %d %H:%M") }; - let is_live = Utc::now().timestamp_millis() >= self.air_date; - let note = match state { - VidState::LiveOrUpcoming => match is_live { - true => format!( - "({} @ {}) ", - "STARTED ".if_supports_color(Stdout, |text| text.bright_white().on_black()), - text - ), - false => format!( - "({} @ {}) ", - "UPCOMING".if_supports_color(Stdout, |text| text.white().on_black()), - text - ), - }, - VidState::Replay => format!("({}) ", text), - }; - // bright white: white - // white: light gray - let prefix = if full_urls { "https://www.cbc.ca/player/play/" } else { "" }; - format!("{}{} - {}{}", prefix, self.id, note, self.title) - } - - fn air_date(&self) -> DateTime { - let air = NaiveDateTime::from_timestamp(self.air_date / 1000, 0); - let air: DateTime = DateTime::from_utc(air, Utc); - air.with_timezone(&Local) - } -} - -// #[derive(Debug, Serialize, Deserialize)] -// pub struct Captions { -// pub(crate) src: Option, -// pub(crate) lang: String, -// } - -// #[derive(Debug, Serialize, Deserialize)] -// pub struct Category { -// pub(crate) name: String, -// } - fn main() -> Result<()> { let args = Args::parse(); #[cfg(windows)] @@ -232,63 +154,63 @@ fn main() -> Result<()> { } let agent = ab.build(); if args.list { - for item in get_live_and_upcoming(&agent)?.data.mpx_items { - println!("{}", item.to_human(VidState::LiveOrUpcoming, args.full_urls)); + for item in get_live_and_upcoming(&agent)?.data.all_content_items.nodes { + println!("{}", item.to_human(args.full_urls)?); } return Ok(()); } if args.replays { - for item in get_replays(&agent)?.data.mpx_items { - println!("{}", item.to_human(VidState::Replay, args.full_urls)); + for item in get_replays(&agent)?.data.all_content_items.nodes { + println!("{}", item.to_human(args.full_urls)?); } return Ok(()); } let id = parse_cbc_id(&args.url.unwrap())?; - let target = format!("https://www.cbc.ca/bistro/order?mediaId={}&limit=10&sort=dateAired", id); - let bistro: Bistro = agent.get(&target).call()?.into_json()?; - let desc = bistro - .items - .get(0) - .ok_or_else(|| anyhow!("missing item"))? - .asset_descriptors - .iter() - .find(|id| id.loader == "PlatformLoader") - .ok_or_else(|| anyhow!("couldn't find PlatformLoader - are you Canadian?"))?; - let smil = agent.get(&desc.key).call()?.into_string()?; - let smil: Smil = quick_xml::de::from_str(&smil)?; - let master_playlist = &smil.body.seq.video.first().ok_or_else(|| anyhow!("missing video"))?.src; - let stream = if args.trust { - master_playlist.to_owned() + + let target = format!("https://www.cbc.ca/player/play/video/{id}"); + let page = agent.get(&target).call()?.into_string()?; + let preload_json_regex = regex!(r#"window\.__INITIAL_STATE__ = (.*);"#); + let preload_json = preload_json_regex + .captures(&page) + .ok_or_else(|| anyhow!("couldn't find initial state!"))? + .get(1) + .unwrap() + .as_str(); + let initial_state: InitialState = serde_json::from_str(preload_json)?; + let surls = initial_state.video.get_stream_urls(); + let json_url = surls.medianet.ok_or_else(|| anyhow!("no medianet URL found"))?; + + let blocked = format!( + "grabbing stream data; an error here probably means {}", + "your IP is geo-blocked".if_supports_color(Stdout, |text| text.bright_red().on_black()), + ); + + let stream_json: Stream = agent.get(&json_url).call()?.into_json().context(blocked)?; + let master_url = stream_json.url.as_str(); + + let stream = if args.distrust { + let playlist = agent.get(master_url).call()?.into_string()?; + get_best_stream(master_url, &playlist)? } else { - let playlist = agent.get(master_playlist).call()?.into_string()?; - get_best_stream(master_playlist, &playlist)? + master_url.to_owned() }; if args.no_run { println!("User-Agent: {}", USER_AGENT); println!("URL: {}", stream); } else { let sl = args.streamlink; + let mut cmd = Command::new(sl); + cmd.arg("--loglevel") + .arg(&args.loglevel) + .arg("--http-header") + .arg(format!("User-Agent={USER_AGENT}")) + .arg("--http-header") + .arg(format!("Referer={target}")); let stat = if let Some(proxy) = args.proxy.map(|p| proxy_url_streamlink(&p)) { - Command::new(sl) - .arg("--loglevel") - .arg(&args.loglevel) - .arg("--http-header") - .arg(format!("User-Agent={USER_AGENT}")) - .arg("--http-proxy") // also proxies https - .arg(&proxy) - .arg(stream) - .arg(args.quality) - .status()? + cmd.arg("--http-proxy").arg(&proxy).arg(stream).arg(args.quality).status()? } else { - Command::new(sl) - .arg("--loglevel") - .arg(&args.loglevel) - .arg("--http-header") - .arg(format!("User-Agent={USER_AGENT}")) - .arg(stream) - .arg(args.quality) - .status()? + cmd.arg(stream).arg(args.quality).status()? }; if !stat.success() { return if stat.code().is_some() { @@ -333,32 +255,6 @@ impl VariantStream<'_> { } } -#[ext] -impl str { - fn is_numeric(&self) -> bool { - !self.is_empty() && self.chars().all(|c| c.is_ascii_digit()) - } - // good chance these do something wrong, since I wrote them in a minute and never tested them - fn substring_to_last(&self, pat: &str) -> &str { - if self.is_empty() || pat.is_empty() { - return self; - } - match self.rfind(pat) { - None => self, - Some(index) => &self[..index + 1], - } - } - fn substring_from(&self, pat: &str) -> &str { - if self.is_empty() || pat.is_empty() { - return self; - } - match self.find(pat) { - None => self, - Some(index) => &self[index..], - } - } -} - /// Rewrites proxy specifications: /// * SOCKS4 is changed to specify remote DNS /// * SOCKS5 strips the `h` if present, since ureq always does remote DNS and can't handle `SOCKS5H` @@ -384,135 +280,14 @@ fn proxy_url_streamlink(spec: &str) -> String { } /// Returns OK if the input is either numeric (ID) or a full CBC URL. -fn probably_cbc(input: &str) -> std::result::Result<(), String> { - if input.is_numeric() || ID_REGEX.is_match(input) { - Ok(()) +fn probably_cbc(input: &str) -> std::result::Result { + if let Some(cap) = ID_REGEX.captures(input) { + Ok(cap.get(1).unwrap().as_str().to_string()) } else { Err("invalid url".into()) } } -fn parse_cbc_id(input: &str) -> Result { - Ok(if input.is_numeric() { - input.parse()? - } else { - ID_REGEX.captures(input).unwrap().get(1).unwrap().as_str().parse()? - }) -} - -#[derive(Debug, Deserialize)] -pub(crate) struct Bistro { - pub(crate) items: Vec, - // pub(crate) errors: Vec>, // never seen this filled -} - -#[derive(Debug, Deserialize)] -pub(crate) struct OrderItem { - // pub(crate) title: String, - // pub(crate) description: String, - // #[serde(rename = "showName")] - // pub(crate) show_name: String, - // pub(crate) categories: Vec, - // pub(crate) thumbnail: String, - // #[serde(rename = "hostImage")] - // pub(crate) host_image: Option, - // pub(crate) chapters: Vec, - // pub(crate) duration: i64, - // #[serde(rename = "airDate")] - // pub(crate) air_date: i64, - // #[serde(rename = "addedDate")] - // pub(crate) added_date: i64, - // #[serde(rename = "contentArea")] - // pub(crate) content_area: String, - // pub(crate) season: String, - // pub(crate) episode: String, - // #[serde(rename = "type")] - // pub(crate) item_type: String, - // pub(crate) region: String, - // pub(crate) sport: String, - // pub(crate) genre: String, - // pub(crate) captions: bool, - // pub(crate) token: String, - // #[serde(rename = "pageUrl")] - // pub(crate) page_url: String, - // #[serde(rename = "adUrl")] - // pub(crate) ad_url: String, - // #[serde(rename = "adOrder")] - // pub(crate) ad_order: String, - // #[serde(rename = "isAudio")] - // pub(crate) is_audio: bool, - // #[serde(rename = "isVideo")] - // pub(crate) is_video: bool, - // #[serde(rename = "isLive")] - // pub(crate) is_live: bool, - // #[serde(rename = "isOnDemand")] - // pub(crate) is_on_demand: bool, - // #[serde(rename = "isDRM")] - // pub(crate) is_drm: bool, - // #[serde(rename = "isBlocked")] - // pub(crate) is_blocked: bool, - // #[serde(rename = "isDAI")] - // pub(crate) is_dai: bool, - // #[serde(rename = "embeddedVia")] - // pub(crate) embedded_via: bool, - // pub(crate) keywords: String, - // #[serde(rename = "brandedSponsorName")] - // pub(crate) branded_sponsor_name: String, - // #[serde(rename = "originalDepartment")] - // pub(crate) original_department: String, - // #[serde(rename = "mediaPublisherName")] - // pub(crate) media_publisher_name: String, - // #[serde(rename = "mediaPublisherType")] - // pub(crate) media_publisher_type: String, - // #[serde(rename = "adCategoryExclusion")] - // pub(crate) ad_category_exclusion: String, - // #[serde(rename = "excludeFromRecommendations")] - // pub(crate) exclude_from_recommendations: bool, - // pub(crate) id: String, - // #[serde(rename = "idType")] - // pub(crate) id_type: String, - #[serde(rename = "assetDescriptors")] - pub(crate) asset_descriptors: Vec, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct AssetDescriptor { - pub(crate) loader: String, - pub(crate) key: String, - // #[serde(rename = "mimeType")] - // pub(crate) mime_type: Option, -} - -// #[derive(Debug, Deserialize)] -// pub(crate) struct Category { -// pub(crate) name: String, -// pub(crate) scheme: String, -// pub(crate) label: String, -// } - -// #[derive(Debug, Deserialize)] -// pub struct Chapter { -// #[serde(rename = "startTime")] -// pub(crate) start_time: i64, -// pub(crate) name: String, -// } - -#[derive(Debug, Deserialize)] -pub(crate) struct Smil { - body: SmilBody, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct SmilBody { - seq: SmilSeq, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct SmilSeq { - video: Vec, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct SmilVideo { - src: String, +fn parse_cbc_id(input: &str) -> Result { + Ok(ID_REGEX.captures(input).unwrap().get(1).unwrap().as_str().to_string()) } diff --git a/src/wincolors.rs b/src/wincolors.rs index 2b3d466..5b21e19 100644 --- a/src/wincolors.rs +++ b/src/wincolors.rs @@ -1,5 +1,3 @@ -use std::ptr; - use anyhow::Result; use windows::Win32::{ Foundation::HANDLE, @@ -11,6 +9,7 @@ use windows::Win32::{ GetConsoleMode, SetConsoleMode, CONSOLE_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING, }, }; +use windows_strings::w; /// owo-colors doesn't handle this. I could just use another dep to do it, but it's not complex /// code. See @@ -19,20 +18,19 @@ use windows::Win32::{ pub(crate) fn enable_colors() -> Result<()> { let handle = unsafe { CreateFileW( - "CONOUT$", - FILE_GENERIC_READ | FILE_GENERIC_WRITE, + w!("CONOUT$"), + (FILE_GENERIC_READ | FILE_GENERIC_WRITE).0, FILE_SHARE_READ | FILE_SHARE_WRITE, - ptr::null_mut(), + None, OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES::default(), HANDLE::default(), - ) - .ok()? + )? }; let mut mode = CONSOLE_MODE::default(); - unsafe { GetConsoleMode(handle, &mut mode).ok()? }; + unsafe { GetConsoleMode(handle, &mut mode)? }; if (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING).0 == 0 { - unsafe { SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING).ok()? }; + unsafe { SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)? }; } Ok(()) }