diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..a26354e --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,129 @@ +name: Preview Deployment + +on: + pull_request: + types: [opened, synchronize, reopened] + pull_request_target: + types: [closed] + +jobs: + deploy-preview: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + name: Deploy Preview + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Cache Rust dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Build + run: cargo install -q worker-build && worker-build --release + + - name: Deploy to Cloudflare Workers + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: deploy --name firefly-api-pr-${{ github.event.number }} --compatibility-date 2025-06-22 + workingDirectory: '.' + env: + FIREFLY_API_AUTHN_TOKEN: ${{ secrets.FIREFLY_API_AUTHN_TOKEN }} + BILIBILI_SESSDATA: ${{ secrets.BILIBILI_SESSDATA }} + SPOTIFY_WEB_API_CLIENT_ID: ${{ secrets.SPOTIFY_WEB_API_CLIENT_ID }} + SPOTIFY_WEB_API_CLIENT_SECRET: ${{ secrets.SPOTIFY_WEB_API_CLIENT_SECRET }} + SPOTIFY_WEB_API_REDIRECT_URI: ${{ secrets.SPOTIFY_WEB_API_REDIRECT_URI }} + - name: Comment PR + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.issue.number; + const previewUrl = `https://firefly-api-pr-${prNumber}.eikasia30.workers.dev`; + + // Find existing preview comment + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('🚀 Preview Deployment') + ); + + const body = `🚀 **Preview Deployment** + + Your preview deployment is ready! + + **🔗 Preview URL:** ${previewUrl} + + This preview will be updated automatically when you push new commits to this PR. + + Deployed from commit: ${context.sha.substring(0, 7)}`; + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: body + }); + } + + cleanup-preview: + if: github.event.action == 'closed' + runs-on: ubuntu-latest + name: Cleanup Preview + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Delete preview deployment + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: delete firefly-api-pr-${{ github.event.number }} + continue-on-error: true + + - name: Comment PR cleanup + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.issue.number; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `🧹 **Preview Cleaned Up** + + The preview deployment for this PR has been removed.` + }); \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1501d29..2ddbe86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ target node_modules .wrangler -.dev.vars \ No newline at end of file +.dev.vars + +# test request json file +request.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6d2cc79..e51f413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", + "serde_json", "serde_path_to_error", "serde_urlencoded", "sync_wrapper", @@ -233,11 +234,34 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflag" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beac6a55fd5b7b4f4e857cc74d09f8b3e7c063fe147a83e38a76773750fdb1b" +dependencies = [ + "bitflag-attr-macros", +] + +[[package]] +name = "bitflag-attr-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b618aab4883d58a2d2ed1f59d8d9cb2c8c735588f873f4a4b7966626f3fdad50" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -335,6 +359,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -356,6 +389,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -407,17 +446,29 @@ dependencies = [ "axum 0.8.4", "axum-cloudflare-adapter", "base64", + "bitflag", + "bitflags", "console_error_panic_hook", + "dotenv", "futures", "http", + "instant", + "jiff", + "md5", "oneshot", "reqwest", "rspotify", "serde", "serde_json", + "serde_repr", + "time", "tower-service", + "tracing", + "tracing-subscriber", + "tracing-web", "wasm-bindgen-futures", "wasm-bindgen-test", + "web-sys", "worker", ] @@ -871,6 +922,18 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -893,6 +956,47 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -903,6 +1007,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.174" @@ -950,6 +1060,12 @@ dependencies = [ "syn", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.7.5" @@ -1009,6 +1125,22 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -1083,6 +1215,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1127,6 +1265,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -1136,6 +1289,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -1170,6 +1329,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", + "futures-util", "h2", "http", "http-body", @@ -1191,12 +1351,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", ] @@ -1441,6 +1603,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1464,6 +1637,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1631,6 +1813,47 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "js-sys", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -1741,9 +1964,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -1751,6 +1986,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-web" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e6a141feebd51f8d91ebfd785af50fca223c570b86852166caa3b141defe7c" +dependencies = [ + "js-sys", + "tracing-core", + "tracing-subscriber", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -1794,6 +2082,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1958,6 +2252,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -1967,6 +2277,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 4cac1b0..3c76e65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ crate-type = ["cdylib"] [dependencies] worker = { version = "0.5.0", features = ['http', 'axum'] } -axum = { version = "0.8.4", default-features = false, features = ["macros", "query"] } +axum = { version = "0.8.4", default-features = false, features = ["json", "macros", "query"] } tower-service = "0.3.3" console_error_panic_hook = { version = "0.1.7" } rspotify = "0.14.0" @@ -28,12 +28,24 @@ serde = { version = "1.0.219", features = ["derive"] } axum-cloudflare-adapter = "0.14.0" wasm-bindgen-futures = "0.4.50" oneshot = "0.1.11" -reqwest = { version = "0.12.22", features = ["json"] } +reqwest = { version = "0.12.22", features = ["json", "stream"] } http = "1.3.1" serde_json = "1.0.140" anyhow = "1.0.98" base64 = "0.22.1" futures = "0.3.31" +md5 = "0.8.0" +dotenv = "0.15.0" +bitflag = "0.10.1" +bitflags = { version = "2.9.1", features = ["serde"] } +instant = { version = "0.1.13", features = ["wasm-bindgen"] } +serde_repr = "0.1.20" +web-sys = "0.3.77" +jiff = "0.2.15" +tracing = "0.1.41" +tracing-web = "0.1.3" +tracing-subscriber = { version = "0.3.19", features = ["time", "json"] } +time = { version = "0.3.41", features = ["wasm-bindgen"] } [dev-dependencies] wasm-bindgen-test = "0.3.50" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..dd80dce --- /dev/null +++ b/build.rs @@ -0,0 +1,7 @@ +// build.rs +fn main() { + println!("cargo::rustc-check-cfg=cfg(ci_build)"); + if std::env::var("CI").is_ok() { + println!("cargo:rustc-cfg=ci_build"); + } +} \ No newline at end of file diff --git a/src/api/v1/bilibili/mod.rs b/src/api/v1/bilibili/mod.rs new file mode 100644 index 0000000..b04b953 --- /dev/null +++ b/src/api/v1/bilibili/mod.rs @@ -0,0 +1 @@ +pub mod video; diff --git a/src/api/v1/bilibili/video/download.rs b/src/api/v1/bilibili/video/download.rs new file mode 100644 index 0000000..a69dee9 --- /dev/null +++ b/src/api/v1/bilibili/video/download.rs @@ -0,0 +1,301 @@ +use axum::extract::{Json, State}; +use axum_cloudflare_adapter::wasm_compat; +use jiff::{tz::TimeZone, Timestamp}; +use serde::Deserialize; + +use crate::{api::v1::ApiV1Response, utils::bilibili::Credential, AppState}; + +#[derive(Debug, Deserialize)] +pub struct DownloadRequest { + bvid: String, + /// Authorization token. This API is admin-only. + token: String, +} + +#[derive(Debug, serde::Serialize)] +pub struct DownloadResponse { + /// Name of the cloudflare r2 bucket where the video is stored + bucket: String, + /// A list of downloaded audio files from the given video + audios: Vec