├── docs ├── install.md ├── use.md ├── about.md ├── public │ ├── icon.ico │ └── icon.png ├── develop.md ├── .vitepress │ └── config.ts └── index.md ├── .husky └── pre-commit ├── src-tauri ├── src │ ├── core.rs │ ├── utils.rs │ ├── main.rs │ ├── utils │ │ ├── resolve.rs │ │ ├── download.rs │ │ └── dirs.rs │ ├── core │ │ ├── handle.rs │ │ └── tray.rs │ └── lib.rs ├── build.rs ├── gen │ └── android │ │ ├── settings.gradle │ │ ├── app │ │ ├── src │ │ │ └── main │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── minibili │ │ │ │ │ └── app │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── themes.xml │ │ │ │ │ └── colors.xml │ │ │ │ ├── xml │ │ │ │ │ └── file_paths.xml │ │ │ │ ├── values-night │ │ │ │ │ └── themes.xml │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ └── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ └── AndroidManifest.xml │ │ ├── .gitignore │ │ ├── proguard-rules.pro │ │ └── build.gradle.kts │ │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── buildSrc │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── minibili │ │ │ └── app │ │ │ └── kotlin │ │ │ ├── BuildTask.kt │ │ │ └── RustPlugin.kt │ │ ├── build.gradle.kts │ │ ├── gradle.properties │ │ └── gradlew.bat ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square310x310Logo.png │ └── ios │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-40x40@2x-1.png │ │ └── AppIcon-83.5x83.5@2x.png ├── .gitignore ├── templates │ └── minibili.desktop ├── tauri.windows.config.json ├── capabilities │ └── default.json ├── tauri.linux.config.json ├── tauri.macos.conf.json ├── tauri.conf.json └── Cargo.toml ├── .stylelintrc.json ├── public ├── icon.ico └── icon.png ├── src ├── styles │ └── global.scss ├── common │ ├── types │ │ ├── emits.ts │ │ └── props.ts │ └── constants.ts ├── vite-env.d.ts ├── apis │ ├── live │ │ ├── area.ts │ │ ├── follow.ts │ │ ├── live.ts │ │ ├── stream.ts │ │ └── info.ts │ ├── video_ranking │ │ ├── ranking.ts │ │ └── popular.ts │ ├── user │ │ └── info.ts │ ├── types │ │ ├── password-login.ts │ │ ├── search-hot.ts │ │ ├── phone-login.ts │ │ ├── live-stream.ts │ │ ├── video-recommendations.ts │ │ ├── live-follow.ts │ │ ├── geetest.ts │ │ ├── apis.ts │ │ ├── play-url.ts │ │ ├── player-info.ts │ │ ├── live.ts │ │ ├── space-info.ts │ │ └── video-popular.ts │ ├── login │ │ ├── captcha.ts │ │ ├── password.ts │ │ ├── phone.ts │ │ ├── qrcode.ts │ │ └── geetest.ts │ ├── search │ │ ├── hot.ts │ │ └── search.ts │ └── video │ │ ├── stream.ts │ │ ├── info.ts │ │ ├── player.ts │ │ └── recommend.ts ├── components │ ├── pages │ │ ├── NotFound.vue │ │ ├── Space.vue │ │ ├── Setting.vue │ │ └── Search.vue │ ├── live │ │ ├── DanmuCard.vue │ │ ├── LiveRoomCard.vue │ │ └── LiveRoom.vue │ ├── player │ │ ├── LivePlayer.vue │ │ └── VideoPlayer.vue │ ├── layout │ │ └── SideBar.vue │ ├── BangumiCard.vue │ ├── home │ │ ├── Ranking.vue │ │ ├── PopularPrecious.vue │ │ ├── Popular.vue │ │ ├── Recommend.vue │ │ └── PopularSeries.vue │ ├── search │ │ ├── SearchBox.vue │ │ ├── SearchAll.vue │ │ ├── SearchBanguni.vue │ │ └── SearchVideo.vue │ └── VideoCard.vue ├── App.vue ├── service │ ├── request.ts │ └── tauri-store.ts ├── store │ ├── theme.ts │ └── locale.ts ├── main.ts └── router │ └── index.ts ├── .prettierrc ├── .editorconfig ├── .vscode └── extensions.json ├── _config.yml ├── tsconfig.node.json ├── .gitignore ├── index.html ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── test-android.yml │ └── release.yml ├── .prettierignore ├── eslint.config.js ├── tsconfig.json ├── README.md ├── .eslintrc-auto-import.js ├── components.d.ts ├── package.json ├── vite.config.ts └── auto-imports.d.ts /docs/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /docs/use.md: -------------------------------------------------------------------------------- 1 | # 使用 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm exec lint-staged 2 | -------------------------------------------------------------------------------- /src-tauri/src/core.rs: -------------------------------------------------------------------------------- 1 | pub mod handle; 2 | pub mod tray; 3 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard-scss"] 3 | } 4 | -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/public/icon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/public/icon.png -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | MiniBili 是使用 [Tauri v2](https://v2.tauri.app) 框架开发的跨端应用。 4 | -------------------------------------------------------------------------------- /docs/public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/docs/public/icon.ico -------------------------------------------------------------------------------- /docs/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/docs/public/icon.png -------------------------------------------------------------------------------- /src-tauri/gen/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | apply from: 'tauri.settings.gradle' 4 | -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | html { 2 | overflow: auto; 3 | } 4 | 5 | body { 6 | overflow: auto; 7 | } 8 | -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/java/com/minibili/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.minibili.app 2 | 3 | class MainActivity : TauriActivity() -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src-tauri/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod dirs; 2 | pub mod download; 3 | pub mod request; 4 | pub mod resolve; 5 | pub mod socket; 6 | pub mod wbi; 7 | -------------------------------------------------------------------------------- /src/common/types/emits.ts: -------------------------------------------------------------------------------- 1 | export interface CaptchaCardData { 2 | token: string 3 | challenge: string 4 | validate: string 5 | } 6 | -------------------------------------------------------------------------------- /src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | insert_final_newline = true 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 4 7 | max_line_length = 80 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "tauri-apps.tauri-vscode", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | minibili 3 | minibili 4 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Z-Only/minibili/HEAD/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /src/main/java/com/minibili/app/generated 2 | /src/main/jniLibs/**/*.so 3 | /src/main/assets/tauri.conf.json 4 | /tauri.build.gradle.kts 5 | /proguard-tauri.pro 6 | /tauri.properties -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | lsi: false 2 | safe: true 3 | source: docs 4 | incremental: false 5 | highlighter: rouge 6 | gist: 7 | noscript: false 8 | kramdown: 9 | math_engine: mathjax 10 | syntax_highlighter: rouge 11 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | minibili_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /src-tauri/templates/minibili.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Categories={{{categories}}} 3 | Comment={{{comment}}} 4 | Exec={{{exec}}} %u 5 | StartupWMClass={{{exec}}} 6 | Icon={{{icon}}} 7 | Name={{{name}}} 8 | Terminal=false 9 | Type=Application -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue May 10 19:22:52 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /src/apis/live/area.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { LiveAreaData } from '@/apis/types/live' 3 | 4 | export const fetchLiveArea = async (): Promise => { 5 | return await get( 6 | 'https://api.live.bilibili.com/room/v1/Area/getList' 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /src-tauri/gen/android/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /src/components/pages/NotFound.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /src-tauri/tauri.windows.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2.0.0-rc", 3 | "identifier": "com.minibili.app", 4 | "bundle": { 5 | "targets": ["nsis", "msi"], 6 | "windows": { 7 | "wix": { 8 | "language": ["zh-CN", "en-US"] 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src-tauri/gen/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | key.properties 17 | 18 | /.tauri 19 | /tauri.settings.gradle -------------------------------------------------------------------------------- /src/apis/live/follow.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { FollowUpLiveParams, FollowUpLiveData } from '@/apis/types/live-follow' 3 | 4 | export const fetchFollowUpLive = async ( 5 | params: FollowUpLiveParams 6 | ): Promise => { 7 | return await get( 8 | 'https://api.live.bilibili.com/room/v1/Area/getList', 9 | params 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # docs 27 | docs/.vitepress/dist 28 | docs/.vitepress/cache 29 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:default", 8 | "shell:allow-open", 9 | "store:default", 10 | "log:default", 11 | "clipboard-manager:default", 12 | "dialog:default" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/apis/live/live.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { LiveRoomListParams, LiveRoomListData } from '@/apis/types/live' 3 | 4 | export const fetchLiveRoomListData = async ( 5 | params: LiveRoomListParams 6 | ): Promise => { 7 | return await get( 8 | 'https://api.live.bilibili.com/xlive/web-interface/v1/second/getList', 9 | params 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /src/apis/video_ranking/ranking.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { RankingData } from '@/apis/types/video-popular' 3 | 4 | // FIXME: 设置分区无效,只能获取全站排行榜,有可能是 referer 的问题 5 | export const fetchRanking = async (rid?: number): Promise => { 6 | return await get( 7 | 'https://api.bilibili.com/x/web-interface/ranking/v2', 8 | { rid: rid ?? 0, type: 'all' } 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src-tauri/gen/android/buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | gradlePlugin { 6 | plugins { 7 | create("pluginsForCoolKids") { 8 | id = "rust" 9 | implementationClass = "RustPlugin" 10 | } 11 | } 12 | } 13 | 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | compileOnly(gradleApi()) 21 | implementation("com.android.tools.build:gradle:8.5.1") 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src-tauri/tauri.linux.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2.0.0-rc", 3 | "identifier": "com.minibili.app", 4 | "bundle": { 5 | "targets": ["deb", "rpm", "appimage"], 6 | "linux": { 7 | "deb": { 8 | "desktopTemplate": "./templates/minibili.desktop" 9 | }, 10 | "rpm": { 11 | "desktopTemplate": "./templates/minibili.desktop" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src-tauri/gen/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath("com.android.tools.build:gradle:8.5.1") 8 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | } 18 | 19 | tasks.register("clean").configure { 20 | delete("build") 21 | } 22 | 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | MiniBili 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src-tauri/src/utils/resolve.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{handle, tray}; 2 | use crate::utils::request::init_cookie; 3 | use anyhow::{Ok, Result}; 4 | use tauri::{App, Manager}; 5 | 6 | pub async fn resolve_setup(app: &mut App) { 7 | let app_handle = app.app_handle(); 8 | handle::Handle::global().init(app_handle); 9 | tray::Tray::init_tray(app_handle); 10 | 11 | init_resource().await.unwrap(); 12 | } 13 | 14 | pub async fn init_resource() -> Result<()> { 15 | init_cookie().await; 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /src/apis/user/info.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { SpaceInfoData } from '@/apis/types/space-info' 3 | 4 | type SpaceInfoParams = { mid: number } 5 | 6 | // FIXME: {code: -352data: {v_voucher: "voucher_40266720-3834-40f5-868c-5a20d2e584e7"} message: "风控校验失败"} 7 | export const fetchSpaceInfo = async (mid: number): Promise => { 8 | return await get( 9 | 'https://api.bilibili.com/x/space/wbi/acc/info', 10 | { mid } 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /docs/develop.md: -------------------------------------------------------------------------------- 1 | # 开发 2 | 3 | ## Getting Started 4 | 5 | 1. [Tauri 前置依赖](https://tauri.app/zh-cn/start/prerequisites/) 6 | 2. 配置 node 环境 7 | 3. 安装依赖,推荐 pnpm 8 | 4. 本地启动 9 | 10 | ```shell 11 | pnpm install 12 | pnpm tauri dev 13 | ``` 14 | 15 | ## ToDoList 16 | 17 | - [ ] 界面 18 | - [x] 首页 19 | - [x] 视频页 20 | - [x] 搜索页 21 | - [x] 设置页 22 | - [ ] 用户页 23 | - [ ] ...... 24 | - [ ] 功能 25 | - [ ] 登录 26 | - [x] 扫码登录 27 | - [ ] 密码登录 28 | - [ ] 验证码登录 29 | - [x] 播放 30 | - [x] 下载 31 | - [ ] 弹幕 32 | - [ ] 评论 33 | - [ ] ...... 34 | -------------------------------------------------------------------------------- /src/common/types/props.ts: -------------------------------------------------------------------------------- 1 | import { VideoFormatFlag } from '@/common/constants' 2 | 3 | export interface VideoCardData { 4 | aid: number 5 | bvid: string 6 | mid: number 7 | author_name: string 8 | avatar_url: string 9 | title: string 10 | duration: number | string 11 | pic_url: string 12 | view: number 13 | danmaku: number 14 | pubdate: number 15 | is_followed: number 16 | } 17 | 18 | export interface PlayerData { 19 | format?: VideoFormatFlag 20 | src: string 21 | title: string 22 | pic: string 23 | } 24 | -------------------------------------------------------------------------------- /src/apis/types/password-login.ts: -------------------------------------------------------------------------------- 1 | export interface PasswordLoginKeyData { 2 | hash: string 3 | key: string 4 | } 5 | 6 | export interface PasswordLoginParams { 7 | challenge: string 8 | go_url?: string 9 | keep: number 10 | password: string 11 | seccode: string 12 | source?: string 13 | token: string 14 | username: string 15 | validate: string 16 | } 17 | 18 | export interface PasswordLoginData { 19 | message: string 20 | refresh_token: string 21 | status: number 22 | timestamp: number 23 | url: string 24 | } 25 | -------------------------------------------------------------------------------- /src/components/pages/Space.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | -------------------------------------------------------------------------------- /src/apis/login/captcha.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | 3 | interface CaptchaParams { 4 | source: string 5 | } 6 | 7 | export interface Captcha { 8 | geetest: Geetest 9 | tencent: Tencent 10 | token: string 11 | type: string 12 | } 13 | 14 | export interface Geetest { 15 | challenge: string 16 | gt: string 17 | } 18 | 19 | export interface Tencent { 20 | appid: string 21 | } 22 | 23 | export const fetchCaptcha = async (): Promise => { 24 | return await get( 25 | 'https://passport.bilibili.com/x/passport-login/captcha', 26 | { source: 'main_web' } 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/apis/login/password.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/service/request' 2 | import { 3 | PasswordLoginKeyData, 4 | PasswordLoginParams, 5 | PasswordLoginData, 6 | } from '@/apis/types/password-login' 7 | 8 | export const fetchPasswordLoginKey = async (): Promise => 9 | await get( 10 | 'https://passport.bilibili.com/x/passport-login/key' 11 | ) 12 | 13 | export const passwordLogin = async ( 14 | params: PasswordLoginParams 15 | ): Promise => 16 | await post( 17 | 'https://passport.bilibili.com/x/passport-login/web/login', 18 | params 19 | ) 20 | -------------------------------------------------------------------------------- /src-tauri/src/core/handle.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use once_cell::sync::OnceCell; 4 | use tauri::AppHandle; 5 | 6 | #[derive(Debug, Default, Clone)] 7 | pub struct Handle { 8 | pub app_handle: Arc>>, 9 | } 10 | 11 | impl Handle { 12 | pub fn global() -> &'static Handle { 13 | static HANDLE: OnceCell = OnceCell::new(); 14 | 15 | HANDLE.get_or_init(|| Handle { 16 | app_handle: Arc::new(Mutex::new(None)), 17 | }) 18 | } 19 | 20 | pub fn init(&self, app_handle: &AppHandle) { 21 | if let Ok(mut handle) = self.app_handle.lock() { 22 | *handle = Some(app_handle.clone()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/apis/live/stream.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { 3 | DanmuInfoData, 4 | LivePlayUrlParams, 5 | LivePlayUrlData, 6 | } from '@/apis/types/live-stream' 7 | 8 | export const fetchDanmuInfo = async (id: number): Promise => { 9 | return await get( 10 | 'https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo', 11 | { id } 12 | ) 13 | } 14 | 15 | export const fetchLivePlayUrl = async ( 16 | id: number 17 | ): Promise => { 18 | return await get( 19 | 'https://api.live.bilibili.com/room/v1/Room/playUrl', 20 | { cid: id } 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/apis/types/search-hot.ts: -------------------------------------------------------------------------------- 1 | export interface SearchDefaultData { 2 | goto_type: number 3 | goto_value: string 4 | id: string 5 | name: string 6 | seid: string 7 | show_name: string 8 | type: number 9 | url: string 10 | } 11 | 12 | export interface SearchSquareParams { 13 | limit: number 14 | platform?: string 15 | } 16 | 17 | export interface SearchSquareData { 18 | trending: Trending 19 | } 20 | 21 | export interface Trending { 22 | list: List[] 23 | title: string 24 | top_list: string[] 25 | trackid: string 26 | } 27 | 28 | export interface List { 29 | goto: string 30 | icon: string 31 | keyword: string 32 | show_name: string 33 | uri: string 34 | } 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | # Enable version updates for npm 9 | - package-ecosystem: 'npm' 10 | directory: '/' 11 | schedule: 12 | interval: 'weekly' 13 | 14 | # Enable version updates for cargo 15 | - package-ecosystem: 'cargo' 16 | directory: '/src-tauri' 17 | schedule: 18 | interval: 'weekly' 19 | ignore: 20 | - dependency-name: 'tauri*' 21 | -------------------------------------------------------------------------------- /src/service/request.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from '@/service/commands' 2 | 3 | /** 4 | * 发起GET请求 5 | * @param url - 请求URL 6 | * @param params - URL参数 7 | * @returns 返回请求数据 8 | */ 9 | export const get = async (url: string, params?: P): Promise => { 10 | return await fetch('GET', url, params).then( 11 | (res) => (res?.data ?? res?.result) as T 12 | ) 13 | } 14 | 15 | /** 16 | * 发起POST请求 17 | * @param url - 请求URL 18 | * @param params - URL参数 19 | * @param data - 请求数据 20 | * @returns 返回请求数据 21 | */ 22 | export const post = async ( 23 | url: string, 24 | params?: P, 25 | data?: D 26 | ): Promise => { 27 | return await fetch('POST', url, params, data).then( 28 | (res) => (res?.data ?? res?.result) as T 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/apis/search/hot.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { 3 | SearchDefaultData, 4 | SearchSquareParams, 5 | SearchSquareData, 6 | } from '@/apis/types/search-hot' 7 | 8 | export const fetchSearchDefault = async (): Promise => { 9 | return await get( 10 | 'https://api.bilibili.com/x/web-interface/wbi/search/default' 11 | ) 12 | } 13 | 14 | export const fetchSearchSquare = async ( 15 | limit: number = 10 16 | ): Promise => { 17 | if (!Number.isInteger(limit) || limit < 1 || limit > 50) { 18 | limit = 10 19 | } 20 | return await get( 21 | 'https://api.bilibili.com/x/web-interface/search/square', 22 | { limit, platform: 'web' } 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /src/apis/types/phone-login.ts: -------------------------------------------------------------------------------- 1 | export interface GenericCountryList { 2 | common: CountryData[] 3 | others: CountryData[] 4 | } 5 | 6 | export interface CountryData { 7 | cname: string 8 | country_id: string 9 | id: number 10 | } 11 | 12 | export interface SendSmsForm { 13 | challenge: string 14 | cid: number 15 | seccode: string 16 | source: string 17 | tel: number 18 | token: string 19 | validate: string 20 | } 21 | 22 | export interface SendSmsData { 23 | captcha_key: string 24 | } 25 | 26 | export interface PhoneLoginForm { 27 | captcha_key: string 28 | cid: number 29 | code: number 30 | go_url?: string 31 | keep?: string 32 | source: string 33 | tel: number 34 | } 35 | 36 | export interface PhoneLoginData { 37 | is_new: boolean 38 | status: number 39 | url: string 40 | } 41 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /src/apis/video/stream.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { PlayUrl } from '@/apis/types/play-url' 3 | 4 | export interface PlayUrlParams { 5 | avid?: number 6 | bvid?: string 7 | cid: number 8 | fnval?: number 9 | fnver?: number 10 | fourk?: number 11 | high_quality?: number 12 | otype?: string 13 | platform?: string 14 | qn?: number 15 | session?: string 16 | type?: string 17 | } 18 | 19 | /** 20 | * Fetches the play URL for a video. 21 | * 22 | * @param {PlayUrlParams} params - The parameters required to fetch the play URL. 23 | * @returns {Promise} A promise that resolves to the play URL data. 24 | */ 25 | export const fetchPlayUrl = async (params: PlayUrlParams): Promise => { 26 | return await get( 27 | 'https://api.bilibili.com/x/player/wbi/playurl', 28 | params 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src-tauri/tauri.macos.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2.0.0-rc", 3 | "identifier": "com.minibili.app", 4 | "bundle": { 5 | "targets": ["app", "dmg"], 6 | "macOS": { 7 | "minimumSystemVersion": "10.15", 8 | "exceptionDomain": "hdslb.com", 9 | "dmg": { 10 | "appPosition": { 11 | "x": 180, 12 | "y": 170 13 | }, 14 | "applicationFolderPosition": { 15 | "x": 480, 16 | "y": 170 17 | }, 18 | "windowSize": { 19 | "height": 400, 20 | "width": 660 21 | }, 22 | "windowPosition": { 23 | "x": 200, 24 | "y": 180 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/apis/video/info.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { VideoDetails } from '@/apis/types/video-details' 3 | 4 | /** 5 | * Parameters for fetching video details. 6 | * 7 | * @property {number} [aid] - The unique identifier for the video. 8 | * @property {string} [bvid] - The unique string identifier for the video. 9 | */ 10 | export interface VideoDetailsParams { 11 | aid?: number 12 | bvid?: string 13 | } 14 | 15 | /** 16 | * Fetches the details of a video from the specified endpoint. 17 | * 18 | * @param params - The parameters required to fetch the video details. 19 | * @returns A promise that resolves to the video details. 20 | */ 21 | export const fetchVideoDetails = async ( 22 | params: VideoDetailsParams 23 | ): Promise => { 24 | return await get( 25 | 'https://api.bilibili.com/x/web-interface/wbi/view', 26 | params 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/live/DanmuCard.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 34 | -------------------------------------------------------------------------------- /src/apis/login/phone.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/service/request' 2 | import { 3 | GenericCountryList, 4 | SendSmsForm, 5 | SendSmsData, 6 | PhoneLoginForm, 7 | PhoneLoginData, 8 | } from '@/apis/types/phone-login' 9 | 10 | export const fetchGenericCountryList = async (): Promise => 11 | await get( 12 | 'https://passport.bilibili.com/web/generic/country/list' 13 | ) 14 | 15 | export const sendSms = async (form: SendSmsForm): Promise => 16 | await post( 17 | 'https://passport.bilibili.com/x/passport-login/web/sms/send', 18 | null, 19 | form 20 | ) 21 | 22 | export const phoneLogin = async ( 23 | form: PhoneLoginForm 24 | ): Promise => 25 | await post( 26 | 'https://passport.bilibili.com/x/passport-login/web/login/sms', 27 | null, 28 | form 29 | ) 30 | -------------------------------------------------------------------------------- /src/apis/types/live-stream.ts: -------------------------------------------------------------------------------- 1 | export interface DanmuInfoData { 2 | business_id: number 3 | group: string 4 | host_list: HostList[] 5 | max_delay: number 6 | refresh_rate: number 7 | refresh_row_factor: number 8 | token: string 9 | } 10 | 11 | export interface HostList { 12 | host: string 13 | port: number 14 | ws_port: number 15 | wss_port: number 16 | } 17 | 18 | // 根据真实直播间号获取直播视频流 19 | export interface LivePlayUrlParams { 20 | cid: number 21 | platform?: string 22 | qn?: string 23 | quality?: number 24 | } 25 | 26 | export interface LivePlayUrlData { 27 | accept_quality: string[] 28 | current_qn: number 29 | current_quality: number 30 | durl: Durl[] 31 | quality_description: QualityDescription[] 32 | } 33 | 34 | export interface Durl { 35 | length: number 36 | order: number 37 | p2p_type: number 38 | stream_type: number 39 | url: string 40 | } 41 | 42 | export interface QualityDescription { 43 | desc: string 44 | qn: number 45 | } 46 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | /audits 5 | /.vscode 6 | 7 | # change files are hand-written and shouldn't be formatted 8 | /.changes/* 9 | !/.changes/config.json 10 | 11 | # dependcies and artifacts directories 12 | node_modules/ 13 | target/ 14 | dist/ 15 | 16 | # lock files 17 | pnpm-lock.yaml 18 | 19 | # autogenerated and minimized js file 20 | crates/tauri/scripts/bundle.global.js 21 | 22 | # this file is an IIFE, shouldn't be formatted 23 | crates/tauri/scripts/process-ipc-message-fn.js 24 | 25 | # cli templates should be formatted manually 26 | # it also includes invalid json files that 27 | # prettier can't handle 28 | crates/tauri-cli/templates 29 | 30 | # autogenerated files 31 | **/autogenerated/**/*.md 32 | packages/cli/index.js 33 | packages/cli/index.d.ts 34 | crates/tauri-schema-worker/.wrangler 35 | CHANGELOG.md 36 | README.md 37 | *schema.json 38 | 39 | # WiX templates 40 | *.wxs 41 | 42 | # examples /gen directory 43 | examples/**/src-tauri/gen 44 | bench/**/src-tauri/gen 45 | 46 | # docs 47 | docs/ -------------------------------------------------------------------------------- /src/store/theme.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { setStoreTheme, getStoreTheme } from '@/service/tauri-store' 3 | 4 | export type Theme = 'auto' | 'light' | 'dark' 5 | 6 | const themes: Theme[] = ['light', 'dark', 'auto'] 7 | 8 | const isTheme = (value: string): value is Theme => { 9 | return themes.includes(value as Theme) 10 | } 11 | 12 | export const useThemeStore = defineStore('theme', () => { 13 | const theme: Ref = ref('auto') 14 | 15 | const isAuto = computed(() => theme.value === 'auto') 16 | 17 | const getTheme = async () => { 18 | const storeTheme = await getStoreTheme() 19 | if (storeTheme && isTheme(storeTheme)) { 20 | theme.value = storeTheme 21 | } else { 22 | setTheme('auto') 23 | } 24 | 25 | return theme.value 26 | } 27 | 28 | const setTheme = async (config: Theme) => { 29 | theme.value = config 30 | setStoreTheme(config) 31 | } 32 | 33 | return { theme, isAuto, getTheme, setTheme } 34 | }) 35 | -------------------------------------------------------------------------------- /src/apis/login/qrcode.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | 3 | export interface QrCode { 4 | qrcode_key: string 5 | url: string 6 | } 7 | 8 | export const fetchQrcode = async () => { 9 | return await get( 10 | 'https://passport.bilibili.com/x/passport-login/web/qrcode/generate' 11 | ) 12 | } 13 | 14 | export interface PollQrcodeParams { 15 | qrcode_key: string 16 | } 17 | 18 | export enum ScanCode { 19 | Success = 0, // 扫码登录成功 20 | QrCodeExpired = 86038, // 二维码已失效 21 | QrCodeScannedNotConfirmed = 86090, // 二维码已扫码未确认 22 | NotScanned = 86101, // 未扫码 23 | } 24 | 25 | export interface PollQrcode { 26 | code: ScanCode 27 | message: string 28 | refresh_token: string 29 | timestamp: number 30 | url: string 31 | } 32 | 33 | export const pollQrcode = async ( 34 | pollQrcodeParams: PollQrcodeParams 35 | ): Promise => { 36 | return await get( 37 | 'https://passport.bilibili.com/x/passport-login/web/qrcode/poll', 38 | pollQrcodeParams 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/store/locale.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { setStoreLocale, getStoreLocale } from '@/service/tauri-store' 3 | 4 | export type Locale = 'auto' | 'zh-Hans' | 'en' | 'ja' | 'ko' 5 | 6 | const Locales: Locale[] = ['auto', 'zh-Hans', 'en', 'ja', 'ko'] 7 | 8 | const isLocale = (value: string): value is Locale => { 9 | return Locales.includes(value as Locale) 10 | } 11 | 12 | export const useLocaleStore = defineStore('Locale', () => { 13 | const locale: Ref = ref('auto') 14 | 15 | const isAuto = computed(() => locale.value === 'auto') 16 | 17 | const getLocale = async () => { 18 | const storeLocale = await getStoreLocale() 19 | if (storeLocale && isLocale(storeLocale)) { 20 | locale.value = storeLocale 21 | } else { 22 | setLocale('auto') 23 | } 24 | 25 | return locale.value 26 | } 27 | 28 | const setLocale = async (config: Locale) => { 29 | locale.value = config 30 | setStoreLocale(config) 31 | } 32 | 33 | return { locale, isAuto, getLocale, setLocale } 34 | }) 35 | -------------------------------------------------------------------------------- /src/apis/types/video-recommendations.ts: -------------------------------------------------------------------------------- 1 | import { VideoDetails, Owner, Stat } from '@/apis/types/video-details' 2 | 3 | /** 4 | * @description 首页视频推荐列表(web端) 5 | */ 6 | export interface VideoRecommendations { 7 | item: Item[] 8 | business_card: null 9 | floor_info: null 10 | user_feature: null 11 | preload_expose_pct: number 12 | preload_floor_expose_pct: number 13 | mid: number 14 | } 15 | 16 | export interface Item extends VideoDetails { 17 | id: number 18 | bvid: string 19 | cid: number 20 | goto: string 21 | uri: string 22 | pic: string 23 | pic_4_3: string 24 | title: string 25 | duration: number 26 | pubdate: number 27 | owner: Owner 28 | stat: Stat 29 | av_feature: null 30 | is_followed: number 31 | rcmd_reason: null 32 | show_info: number 33 | track_id: string 34 | pos: number 35 | room_info: null 36 | ogv_info: null 37 | business_info: null 38 | is_stock: number 39 | enable_vt: number 40 | vt_display: string 41 | dislike_switch: number 42 | dislike_switch_pc: number 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - dev 9 | 10 | jobs: 11 | CI: 12 | strategy: 13 | fail-fast: false 14 | 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: install pnpm 21 | uses: pnpm/action-setup@v4 22 | with: 23 | run_install: false 24 | 25 | - name: setup node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: lts/* 29 | cache: 'pnpm' 30 | 31 | - name: install frontend dependencies 32 | run: pnpm i --frozen-lockfile 33 | 34 | - name: check formatting 35 | run: pnpm format:check 36 | 37 | - name: run linter 38 | run: pnpm lint 39 | 40 | - name: build 41 | run: pnpm build 42 | 43 | #- name: run tests 44 | # run: pnpm test 45 | -------------------------------------------------------------------------------- /src/apis/types/live-follow.ts: -------------------------------------------------------------------------------- 1 | export interface FollowUpLiveParams { 2 | hit_ab?: boolean 3 | ignoreRecord?: number 4 | page?: number 5 | page_size?: number 6 | } 7 | 8 | export interface FollowUpLiveData { 9 | count: number 10 | list: List[] 11 | live_count: number 12 | never_lived_count: number 13 | never_lived_faces: string[] 14 | pageSize: number 15 | title: string 16 | totalPage: number 17 | } 18 | 19 | export interface List { 20 | area_id: number 21 | area_name: string 22 | area_name_v2: string 23 | area_value: string 24 | clipnum: number 25 | face: string 26 | fans_num: number 27 | is_attention: number 28 | live_status: number 29 | parent_area_id: number 30 | recent_record_id: string 31 | recent_record_id_v2: string 32 | record_live_time: number 33 | record_num: number 34 | record_num_v2: number 35 | room_cover: string 36 | room_news: string 37 | roomid: number 38 | switch: boolean 39 | tags: string 40 | text_small: string 41 | title: string 42 | uid: number 43 | uname: string 44 | watch_icon: string 45 | } 46 | -------------------------------------------------------------------------------- /src/components/player/LivePlayer.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals' 2 | import pluginJs from '@eslint/js' 3 | import tseslint from 'typescript-eslint' 4 | import pluginVue from 'eslint-plugin-vue' 5 | import eslintConfigPrettier from 'eslint-config-prettier' 6 | import autoImport from './.eslintrc-auto-import.js' 7 | 8 | export default [ 9 | { files: ['**/*.{js,mjs,cjs,ts,vue}'] }, 10 | { 11 | ignores: [ 12 | 'dist/**', 13 | 'src/vite-env.d.ts', 14 | 'src-tauri/**', 15 | 'src/common/geetest/click.js', 16 | 'docs/**', 17 | ], 18 | }, 19 | { 20 | languageOptions: { 21 | globals: { ...globals.browser, ...autoImport?.globals }, 22 | }, 23 | }, 24 | pluginJs.configs.recommended, 25 | ...tseslint.configs.recommended, 26 | ...pluginVue.configs['flat/essential'], 27 | { 28 | files: ['**/*.vue'], 29 | languageOptions: { parserOptions: { parser: tseslint.parser } }, 30 | }, 31 | { 32 | rules: { 33 | 'vue/multi-word-component-names': 'off', 34 | '@typescript-eslint/no-explicit-any': 'warn', 35 | }, 36 | }, 37 | eslintConfigPrettier, 38 | ] 39 | -------------------------------------------------------------------------------- /src/apis/login/geetest.ts: -------------------------------------------------------------------------------- 1 | import { geetestGet } from '@/service/commands' 2 | import { 3 | GeetestGetTypeParams, 4 | GeetestGetTypeData, 5 | GeetestGetParams, 6 | GeetestGetData, 7 | GeetestAjaxParams, 8 | GeetestAjaxData, 9 | } from '@/apis/types/geetest' 10 | 11 | const API_SERVER = 'https://api.geetest.com' 12 | 13 | export const geetestGetTypePhp = async ( 14 | params: GeetestGetTypeParams 15 | ): Promise => { 16 | return await geetestGet( 17 | `${API_SERVER}/gettype.php`, 18 | params 19 | ).then((result) => result.data) 20 | } 21 | 22 | export const geetestGetPhp = async ( 23 | params: GeetestGetParams 24 | ): Promise => { 25 | return await geetestGet( 26 | `${API_SERVER}/get.php`, 27 | params 28 | ).then((result) => result.data) 29 | } 30 | 31 | export const geetestAjaxPhp = async ( 32 | params: GeetestAjaxParams 33 | ): Promise => { 34 | return await geetestGet( 35 | `${API_SERVER}/ajax.php`, 36 | params 37 | ).then((result) => result.data) 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | /* Path alias */ 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["src/*"] 27 | }, 28 | 29 | /* js */ 30 | "allowJs": true, 31 | "checkJs": false 32 | }, 33 | "include": [ 34 | "src/**/*.ts", 35 | "src/**/*.d.ts", 36 | "src/**/*.tsx", 37 | "src/**/*.vue", 38 | "./auto-imports.d.ts", 39 | "components.d.ts", 40 | "src/**/*.js" 41 | ], 42 | "references": [{ "path": "./tsconfig.node.json" }] 43 | } 44 | -------------------------------------------------------------------------------- /src/service/tauri-store.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@tauri-apps/plugin-store' 2 | 3 | let store: Store 4 | const initStore = async () => { 5 | if (!store) { 6 | console.log('store init') 7 | store = await Store.load('settings.json') 8 | } 9 | } 10 | 11 | export type StoreValueType = string | null | undefined 12 | 13 | export const setStoreTheme = async (value: string) => { 14 | await initStore() 15 | return await store.set('theme', value) 16 | } 17 | 18 | export const getStoreTheme = async (): Promise => { 19 | await initStore() 20 | return await store.get('theme') 21 | } 22 | 23 | export const setStoreLocale = async (value: string) => { 24 | await initStore() 25 | return await store.set('locale', value) 26 | } 27 | 28 | export const getStoreLocale = async (): Promise => { 29 | await initStore() 30 | return await store.get('locale') 31 | } 32 | 33 | export const setStoreDownloadPath = async (value: string) => { 34 | await initStore() 35 | return await store.set('download_path', value) 36 | } 37 | 38 | export const getStoreDownloadPath = async (): Promise => { 39 | await initStore() 40 | return await store.get('download_path') 41 | } 42 | -------------------------------------------------------------------------------- /src/apis/video_ranking/popular.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { 3 | PopularData, 4 | PopularSeriesList, 5 | PopularSeriesOneData, 6 | PopularPreciousData, 7 | } from '@/apis/types/video-popular' 8 | 9 | export interface PopularParams { 10 | pn: number 11 | ps: number 12 | } 13 | 14 | export const fetchPopular = async ( 15 | params: PopularParams 16 | ): Promise => { 17 | return await get( 18 | 'https://api.bilibili.com/x/web-interface/popular', 19 | params 20 | ) 21 | } 22 | 23 | export const fetchPopularSeriesList = async (): Promise => { 24 | return await get( 25 | 'https://api.bilibili.com/x/web-interface/popular/series/list' 26 | ) 27 | } 28 | 29 | export const fetchPopularSeriesOne = async ( 30 | number: number 31 | ): Promise => { 32 | return await get( 33 | 'https://api.bilibili.com/x/web-interface/popular/series/one', 34 | { number } 35 | ) 36 | } 37 | 38 | export const fetchPopularPrecious = async (): Promise => { 39 | return await get( 40 | 'https://api.bilibili.com/x/web-interface/popular/precious' 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2.0.0-rc", 3 | "productName": "minibili", 4 | "version": "0.1.0", 5 | "identifier": "com.minibili.app", 6 | "build": { 7 | "beforeDevCommand": "pnpm dev", 8 | "devUrl": "http://localhost:1420", 9 | "beforeBuildCommand": "pnpm build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "windows": [ 14 | { 15 | "title": "minibili", 16 | "width": 800, 17 | "height": 600 18 | } 19 | ], 20 | "security": { 21 | "assetProtocol": { 22 | "enable": true, 23 | "scope": ["$APPDATA/**", "$RESOURCE/../**", "**"] 24 | }, 25 | "csp": null 26 | } 27 | }, 28 | "bundle": { 29 | "active": true, 30 | "targets": "all", 31 | "icon": [ 32 | "icons/32x32.png", 33 | "icons/128x128.png", 34 | "icons/128x128@2x.png", 35 | "icons/icon.icns", 36 | "icons/icon.ico" 37 | ], 38 | "publisher": "MiniBili", 39 | "copyright": "GNU General Public License v3.0", 40 | "category": "Video", 41 | "shortDescription": "A Third-party Bilibili client." 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/apis/video/player.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { PlayerInfo } from '../types/player-info' 3 | 4 | /** 5 | * Parameters for retrieving player information. 6 | * 7 | * @property {number} [aid] - The avid of the manuscript. Note: Either `aid` or `bvid` is required. 8 | * @property {string} [bvid] - The bvid of the manuscript. Note: Either `aid` or `bvid` is required. 9 | * @property {number} cid - The cid of the manuscript. 10 | * @property {number} [ep_id] - The episode id of the series. 11 | * @property {number} [season_id] - The season id of the series. 12 | * @property {string} [w_rid] - The WBI signature. 13 | * @property {number} [wts] - The current unix timestamp. 14 | */ 15 | export interface PlayerInfoParams { 16 | aid?: number 17 | bvid?: string 18 | cid: number 19 | ep_id?: number 20 | season_id?: number 21 | w_rid?: string 22 | wts?: number 23 | } 24 | 25 | /** 26 | * Fetches player information from the specified endpoint. 27 | * 28 | * @param params - The parameters required to fetch player information. 29 | * @returns A promise that resolves to the player information. 30 | */ 31 | export const fetchPlayerInfo = async ( 32 | params: PlayerInfoParams 33 | ): Promise => { 34 | return await get( 35 | 'https://api.bilibili.com/x/player/wbi/v2', 36 | params 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/layout/SideBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 44 | -------------------------------------------------------------------------------- /src/components/player/VideoPlayer.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /src-tauri/gen/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /src-tauri/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::resolve; 2 | use tauri_plugin_log::{Target, TargetKind}; 3 | 4 | mod commands; 5 | mod core; 6 | mod utils; 7 | 8 | #[cfg_attr(mobile, tauri::mobile_entry_point)] 9 | pub fn run() { 10 | tauri::Builder::default() 11 | .plugin(tauri_plugin_dialog::init()) 12 | .plugin(tauri_plugin_clipboard_manager::init()) 13 | .plugin( 14 | tauri_plugin_log::Builder::new() 15 | .targets([ 16 | Target::new(TargetKind::Stdout), 17 | Target::new(TargetKind::LogDir { file_name: None }), 18 | Target::new(TargetKind::Webview), 19 | ]) 20 | .build(), 21 | ) 22 | .plugin(tauri_plugin_store::Builder::new().build()) 23 | .plugin(tauri_plugin_shell::init()) 24 | .invoke_handler(tauri::generate_handler![ 25 | commands::fetch, 26 | commands::download, 27 | commands::geetest_get, 28 | commands::open_devtools, 29 | commands::resolve_risk_check_issue, 30 | commands::monitor_live_msg_stream, 31 | commands::stop_monitor_live_msg_stream 32 | ]) 33 | .setup(|app| { 34 | tauri::async_runtime::block_on(async move { 35 | resolve::resolve_setup(app).await; 36 | }); 37 | Ok(()) 38 | }) 39 | .run(tauri::generate_context!()) 40 | .expect("error while running tauri application"); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/live/LiveRoomCard.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 44 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import { createPinia } from 'pinia' 5 | import 'vuetify/styles' 6 | import '@mdi/font/css/materialdesignicons.css' 7 | import { createVuetify } from 'vuetify' 8 | import { md3 } from 'vuetify/blueprints' 9 | import { zhHans, en, ja, ko } from 'vuetify/locale' 10 | import '@/styles/global.scss' 11 | 12 | /** 13 | * 创建 Vue 应用实例 14 | */ 15 | const app = createApp(App) 16 | 17 | /** 18 | * 定义自定义警告处理器,过滤并打印特定类型的警告信息 19 | */ 20 | app.config.warnHandler = function (msg, _vm, trace) { 21 | const ignoredWarningKey = 'Vuetify: Translation key' 22 | 23 | // 忽略包含特定关键词的警告 24 | if (msg.includes(ignoredWarningKey)) { 25 | return 26 | } 27 | 28 | // 打印其他警告信息 29 | console.warn(`[Vue warn]: ${msg}${trace}`) 30 | } 31 | 32 | /** 33 | * 使用路由、pinia 和 Vuetify 插件配置应用 34 | */ 35 | app.use(router) 36 | .use(createPinia()) 37 | .use( 38 | createVuetify({ 39 | blueprint: md3, 40 | locale: { 41 | locale: 'zhHans', // 默认语言设置为简体中文 42 | fallback: 'en', // 备选语言为英文 43 | messages: { 44 | // 支持的语言包 45 | zhHans, 46 | en, 47 | ja, 48 | ko, 49 | }, 50 | }, 51 | theme: { 52 | defaultTheme: 'light', // 默认主题为亮色 53 | }, 54 | }) 55 | ) 56 | 57 | /** 58 | * 挂载应用到指定 DOM 节点 59 | */ 60 | app.mount('#app') 61 | -------------------------------------------------------------------------------- /src-tauri/src/utils/download.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Ok, Result}; 2 | use serde::Serialize; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::time::Instant; 6 | use tauri::ipc::Channel; 7 | 8 | #[derive(Clone, Serialize)] 9 | #[serde(rename_all = "camelCase", tag = "event", content = "data")] 10 | pub enum DownloadEvent<'a> { 11 | #[serde(rename_all = "camelCase")] 12 | Started { 13 | url: &'a str, 14 | download_id: usize, 15 | content_length: usize, 16 | }, 17 | #[serde(rename_all = "camelCase")] 18 | Progress { 19 | download_id: usize, 20 | downloaded_bytes: usize, 21 | speed: f64, 22 | cost_time: f64, 23 | estimated_time: f64, 24 | }, 25 | #[serde(rename_all = "camelCase")] 26 | Finished { download_id: usize }, 27 | } 28 | 29 | pub fn write_buffer_to_file(file: &mut File, buffer: &[u8]) -> Result<()> { 30 | file.write_all(buffer)?; 31 | Ok(()) 32 | } 33 | 34 | pub fn send_progress_event( 35 | on_event: &Channel>, 36 | download_id: usize, 37 | content_length: usize, 38 | downloaded_bytes: usize, 39 | start_time: Instant, 40 | ) -> Result<()> { 41 | let elapsed = start_time.elapsed().as_secs_f64(); 42 | let speed = downloaded_bytes as f64 / elapsed; 43 | 44 | on_event.send(DownloadEvent::Progress { 45 | download_id, 46 | downloaded_bytes, 47 | speed, 48 | cost_time: elapsed, 49 | estimated_time: elapsed + ((content_length - downloaded_bytes) as f64) / speed, 50 | })?; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minibili" 3 | version = "0.1.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | name = "minibili_lib" 12 | crate-type = ["staticlib", "cdylib", "rlib"] 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "2", features = [] } 16 | 17 | [dependencies] 18 | tauri = { version = "2", features = [ 19 | "protocol-asset", 20 | "tray-icon", 21 | "devtools", 22 | ] } 23 | tauri-plugin-clipboard-manager = "2" 24 | tauri-plugin-dialog = "2" 25 | tauri-plugin-log = "2" 26 | tauri-plugin-shell = "2" 27 | tauri-plugin-store = "2" 28 | serde = { version = "1", features = ["derive"] } 29 | serde_json = "1" 30 | tokio = "1.41.1" 31 | reqwest = { version = "0.12.9", features = [ 32 | "json", 33 | "cookies", 34 | "gzip", 35 | "brotli", 36 | "deflate", 37 | ] } 38 | md5 = "0.7.0" 39 | url = "2.5.4" 40 | http = "1.1.0" 41 | once_cell = "1.20.2" 42 | futures-util = "0.3.31" 43 | regex = "1.11.1" 44 | log = "^0.4" 45 | reqwest-websocket = "0.4.3" 46 | bincode = "1.3.3" 47 | flate2 = "1.0.35" 48 | brotli = "7.0.0" 49 | anyhow = "1.0.93" 50 | fs = "0.0.5" 51 | 52 | [profile.dev] 53 | incremental = true # Compile your binary in smaller steps. 54 | 55 | [profile.release] 56 | codegen-units = 1 # Allows LLVM to perform better optimization. 57 | lto = true # Enables link-time-optimizations. 58 | opt-level = "s" # Prioritizes small binary size. Use `3` if you prefer speed. 59 | panic = "abort" # Higher performance by disabling panic handlers. 60 | strip = true # Ensures debug symbols are removed. 61 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import Home from '@/components/pages/Home.vue' 3 | 4 | const routes = [ 5 | { 6 | path: '/', 7 | name: 'Home', 8 | component: Home, 9 | }, 10 | // 重定向到主页的路由配置 11 | { path: '/home', redirect: '/' }, 12 | { 13 | path: '/setting', 14 | name: 'Setting', 15 | component: () => import('@/components/pages/Setting.vue'), 16 | }, 17 | { 18 | path: '/video/:bvid', 19 | name: 'Video', 20 | component: () => import('@/components/pages/Video.vue'), 21 | }, 22 | { 23 | path: '/search/:zone', 24 | name: 'Search', 25 | component: () => import('@/components/pages/Search.vue'), 26 | }, 27 | { 28 | path: '/login', 29 | name: 'Login', 30 | component: () => import('@/components/pages/Login.vue'), 31 | }, 32 | { 33 | path: '/space/:mid', 34 | name: 'Space', 35 | component: () => import('@/components/pages/Space.vue'), 36 | }, 37 | { 38 | path: '/live', 39 | name: 'Live', 40 | component: () => import('@/components/live/Live.vue'), 41 | }, 42 | { 43 | path: '/live/:id', 44 | name: 'LiveRoom', 45 | component: () => import('@/components/live/LiveRoom.vue'), 46 | }, 47 | { 48 | path: '/:pathMatch(.*)*', // 匹配所有未命中的路径 49 | name: 'NotFound', 50 | component: () => import('@/components/pages/NotFound.vue'), 51 | }, 52 | ] 53 | 54 | const router = createRouter({ 55 | history: createWebHistory(), 56 | routes, 57 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 58 | scrollBehavior(_to, _from, _savedPosition) { 59 | // 始终滚动到顶部 60 | return { top: 0 } 61 | }, 62 | }) 63 | 64 | export default router 65 | -------------------------------------------------------------------------------- /src-tauri/gen/android/buildSrc/src/main/java/com/minibili/app/kotlin/BuildTask.kt: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | import org.apache.tools.ant.taskdefs.condition.Os 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.logging.LogLevel 6 | import org.gradle.api.tasks.Input 7 | import org.gradle.api.tasks.TaskAction 8 | 9 | open class BuildTask : DefaultTask() { 10 | @Input 11 | var rootDirRel: String? = null 12 | @Input 13 | var target: String? = null 14 | @Input 15 | var release: Boolean? = null 16 | 17 | @TaskAction 18 | fun assemble() { 19 | val executable = """pnpm"""; 20 | try { 21 | runTauriCli(executable) 22 | } catch (e: Exception) { 23 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 24 | runTauriCli("$executable.cmd") 25 | } else { 26 | throw e; 27 | } 28 | } 29 | } 30 | 31 | fun runTauriCli(executable: String) { 32 | val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") 33 | val target = target ?: throw GradleException("target cannot be null") 34 | val release = release ?: throw GradleException("release cannot be null") 35 | val args = listOf("tauri", "android", "android-studio-script"); 36 | 37 | project.exec { 38 | workingDir(File(project.projectDir, rootDirRel)) 39 | executable(executable) 40 | args(args) 41 | if (project.logger.isEnabled(LogLevel.DEBUG)) { 42 | args("-vv") 43 | } else if (project.logger.isEnabled(LogLevel.INFO)) { 44 | args("-v") 45 | } 46 | if (release) { 47 | args("--release") 48 | } 49 | args(listOf("--target", target)) 50 | }.assertNormalExitValue() 51 | } 52 | } -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md 2 | export enum VideoQuality { 3 | QN_240P = 6, // 240P 极速 - 仅 MP4 格式支持,仅 platform=html5 时有效 4 | QN_360P = 16, // 360P 流畅 5 | QN_480P = 32, // 480P 清晰 6 | QN_720P = 64, // 720P 高清 - WEB 端默认值,B站前端需要登录才能选择,但是直接发送请求可以不登录就拿到 720P 的取流地址;无 720P 时则为 720P60 7 | QN_720P60 = 74, // 720P60 高帧率 - 登录认证 8 | QN_1080P = 80, // 1080P 高清 - TV 端与 APP 端默认值,登录认证 9 | QN_1080P_PLUS = 112, // 1080P+ 高码率 - 大会员认证 10 | QN_1080P60 = 116, // 1080P60 高帧率 - 大会员认证 11 | QN_4K = 120, // 4K 超清 - 需要 fnval&128=128 且 fourk=1,大会员认证 12 | QN_HDR = 125, // HDR 真彩色 - 仅支持 DASH 格式,需要 fnval&64=64,大会员认证 13 | QN_DOLBY_VISION = 126, // 杜比视界 - 仅支持 DASH 格式,需要 fnval&512=512,大会员认证 14 | QN_8K = 127, // 8K 超高清 - 仅支持 DASH 格式,需要 fnval&1024=1024,大会员认证 15 | } 16 | 17 | export enum VideoFormatFlag { 18 | MP4 = 1, // MP4 格式 - 仅 H.264 编码,与 DASH 格式互斥 19 | DASH = 16, // DASH 格式 - 与 MP4 格式互斥 20 | HDR = 64, // 是否需求 HDR 视频 - 需求 DASH 格式,仅 H.265 编码,需要 qn=125,大会员认证 21 | FOURK = 128, // 是否需求 4K 分辨率 - 该值与 fourk 字段协同作用,需要 qn=120,大会员认证 22 | DOLBY_AUDIO = 256, // 是否需求杜比音频 - 需求 DASH 格式,大会员认证 23 | DOLBY_VISION = 512, // 是否需求杜比视界 - 需求 DASH 格式,大会员认证 24 | EIGHTK = 1024, // 是否需求 8K 分辨率 - 需求 DASH 格式,需要 qn=127,大会员认证 25 | AV1 = 2048, // 是否需求 AV1 编码 - 需求 DASH 格式 26 | } 27 | 28 | export enum VideoCodec { 29 | AVC = 7, // AVC 编码 - 8K 视频不支持该格式 30 | HEVC = 12, // HEVC 编码 31 | AV1 = 13, // AV1 编码 32 | } 33 | 34 | export enum AudioQuality { 35 | Q64K = 30216, // 64K 36 | Q132K = 30232, // 132K 37 | Q192K = 30280, // 192K 38 | DOLBY_ATMOS = 30250, // 杜比全景声 39 | HI_RES_LOSSLESS = 30251, // Hi-Res无损 40 | } 41 | 42 | export enum LiveQuality { 43 | DOLBY = 30000, // 杜比 44 | FOUR_K = 20000, // 4K 45 | ORIGINAL = 10000, // 原画 46 | BLUERAY = 400, // 蓝光 47 | ULTRA_CLEAR = 250, // 超清 48 | HD = 150, // 高清 49 | SMOOTH = 80, // 流畅 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 2 | # 3 | name: Deploy VitePress site to Pages 4 | 5 | on: 6 | # 在针对 `main` 分支的推送上运行。如果你 7 | # 使用 `master` 分支作为默认分支,请将其更改为 `master` 8 | push: 9 | branches: [main] 10 | 11 | # 允许你从 Actions 选项卡手动运行此工作流程 12 | workflow_dispatch: 13 | 14 | # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 21 | # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # 构建工作 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 35 | - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消此区域注释 36 | with: 37 | version: 9 38 | - name: Setup Node 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: 20 42 | cache: pnpm 43 | - name: Setup Pages 44 | uses: actions/configure-pages@v4 45 | - name: Install dependencies 46 | run: pnpm install 47 | - name: Build with VitePress 48 | run: pnpm docs:build 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v3 51 | with: 52 | path: docs/.vitepress/dist 53 | 54 | # 部署工作 55 | deploy: 56 | environment: 57 | name: github-pages 58 | url: ${{ steps.deployment.outputs.page_url }} 59 | needs: build 60 | runs-on: ubuntu-latest 61 | name: Deploy 62 | steps: 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v4 66 | -------------------------------------------------------------------------------- /src/apis/search/search.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { 3 | SearchAll, 4 | SearchSuggestParams, 5 | SearchSuggestData, 6 | TypeSearchParams, 7 | TypeSearchData, 8 | } from '@/apis/types/search' 9 | 10 | /** 11 | * Fetches search results for all categories. 12 | * 13 | * @param params - The search parameters. 14 | * @returns A promise that resolves to the search results for all categories. 15 | */ 16 | export const fetchSearchAll = async (keyword: string): Promise => { 17 | return await get< 18 | SearchAll, 19 | { 20 | keyword: string 21 | } 22 | >('https://api.bilibili.com/x/web-interface/wbi/search/all/v2', { 23 | keyword, 24 | }) 25 | } 26 | 27 | export const SearchType = [ 28 | { 29 | value: 'video', 30 | name: '视频', 31 | }, 32 | { value: 'media_bangumi', name: '番剧' }, 33 | { value: 'media_ft', name: '影视' }, 34 | { value: 'live', name: '直播间及主播' }, 35 | { 36 | value: 'live_room', 37 | name: '直播间', 38 | }, 39 | { 40 | value: 'live_user', 41 | name: '主播', 42 | }, 43 | { 44 | value: 'article', 45 | name: '专栏', 46 | }, 47 | { 48 | value: 'topic', 49 | name: '话题', 50 | }, 51 | { 52 | value: 'bili_user', 53 | name: '用户', 54 | }, 55 | { 56 | value: 'photo', 57 | name: '相簿', 58 | }, 59 | ] 60 | 61 | export const fetchTypeSearch = async ( 62 | params: TypeSearchParams 63 | ): Promise => { 64 | return await get( 65 | 'https://api.bilibili.com/x/web-interface/wbi/search/type', 66 | params 67 | ) 68 | } 69 | 70 | export const fetchSearchSuggest = async ( 71 | params: SearchSuggestParams 72 | ): Promise => { 73 | return await get( 74 | 'https://s.search.bilibili.com/main/suggest', 75 | params 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /src/apis/types/geetest.ts: -------------------------------------------------------------------------------- 1 | export interface GeetestGetTypeParams { 2 | callback: string 3 | gt: string 4 | } 5 | 6 | export interface GeetestGetTypeData { 7 | type: string 8 | static_servers: string[] 9 | beeline: string 10 | voice: string 11 | click: string 12 | fullpage: string 13 | slide: string 14 | geetest: string 15 | aspect_radio: AspectRadio 16 | } 17 | 18 | export interface AspectRadio { 19 | slide: number 20 | click: number 21 | voice: number 22 | beeline: number 23 | } 24 | 25 | export interface GeetestGetParams { 26 | api_server?: string 27 | autoReset?: string 28 | callback?: string 29 | challenge?: string 30 | gt?: string 31 | https?: string 32 | is_next?: string 33 | isPC?: string 34 | lang?: string 35 | offline?: string 36 | product?: string 37 | protocol?: string 38 | type?: string 39 | width?: string 40 | } 41 | 42 | export interface GeetestGetData { 43 | api_server: string 44 | c: number[] 45 | feedback: string 46 | gct_path: string 47 | i18n_labels: I18NLabels 48 | image_servers: string[] 49 | logo: boolean 50 | num: number 51 | pic: string 52 | pic_type: string 53 | resource_servers: string[] 54 | s: string 55 | sign: string 56 | spec: string 57 | static_servers: string[] 58 | theme: string 59 | theme_version: string 60 | } 61 | 62 | export interface I18NLabels { 63 | atip: string 64 | cancel: string 65 | close: string 66 | commit: string 67 | fail: string 68 | fail_short: string 69 | feedback: string 70 | forbidden: string 71 | loading: string 72 | maze: string 73 | read_reversed: boolean 74 | refresh: string 75 | small_tip: string 76 | success: string 77 | success_short: string 78 | tip: string 79 | voice: string 80 | } 81 | 82 | export interface GeetestAjaxParams { 83 | callback?: string 84 | challenge?: string 85 | client_type?: string 86 | gt?: string 87 | lang?: string 88 | pt?: number 89 | w?: string 90 | } 91 | 92 | export interface GeetestAjaxData { 93 | result: string 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 | 7 | # MiniBili 8 | 9 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/Z-Only/minibili?style=flat)](https://github.com/Z-Only/minibili/releases) 10 | ![GitHub Release Date](https://img.shields.io/github/release-date/Z-Only/minibili?style=flat) 11 | ![GitHub All Releases](https://img.shields.io/github/downloads/Z-Only/minibili/total?style=flat) 12 | ![GitHub issues](https://img.shields.io/github/issues/Z-Only/minibili?style=flat) 13 | ![GitHub stars](https://img.shields.io/github/stars/Z-Only/minibili?style=flat) 14 | ![GitHub forks](https://img.shields.io/github/forks/Z-Only/minibili?style=flat) 15 | ![Github license](https://img.shields.io/github/license/Z-Only/minibili?style=flat) 16 | 17 | `MiniBili` 是 [哔哩哔哩](https://www.bilibili.com) 的第三方客户端,适用于 Windows、MacOS、Linux 等。 18 | 19 |
20 | 21 |

22 | 概述  •  23 | 开发  •  24 | 安装  •  25 | 使用  •  26 | 鸣谢 27 |

28 | 29 | ## 概述 30 | 31 | MiniBili 是使用 [Tauri v2](https://v2.tauri.app) 框架开发的跨端应用。 32 | 33 | 文档地址:[MiniBili](https://z-only.github.io/minibili/) 34 | 35 | ## 开发 36 | 37 | ### Getting Started 38 | 39 | 1. [Tauri 前置依赖](https://tauri.app/zh-cn/start/prerequisites/) 40 | 2. 配置 node 环境 41 | 3. 安装依赖,推荐 pnpm 42 | 4. 本地启动 43 | 44 | ```shell 45 | pnpm install 46 | pnpm tauri dev 47 | ``` 48 | 49 | ### ToDoList 50 | 51 | - [ ] 界面 52 | - [x] 首页 53 | - [x] 视频页 54 | - [x] 搜索页 55 | - [x] 设置页 56 | - [x] 排行榜、每周必看、入站必刷 57 | - [ ] 用户页 58 | - [ ] ...... 59 | - [ ] 功能 60 | - [ ] 登录 61 | - [x] 扫码登录 62 | - [ ] 密码登录 63 | - [ ] 验证码登录 64 | - [x] 播放 65 | - [x] 下载 66 | - [ ] 弹幕 67 | - [ ] 评论 68 | - [ ] ...... 69 | 70 | ## 安装 71 | 72 | TODO 73 | 74 | ## 使用 75 | 76 | TODO 77 | 78 | ## 鸣谢 79 | 80 | - [Tauri](https://v2.tauri.app) 81 | - [BiliBili](https://www.bilibili.com/) 82 | - [哔哩哔哩-API收集整理](https://github.com/SocialSisterYi/bilibili-API-collect) 83 | - [geetest 验证](https://github.com/Amorter/biliTicker_gt) 84 | -------------------------------------------------------------------------------- /src/components/BangumiCard.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src-tauri/src/utils/dirs.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::path::PathBuf; 3 | use tauri::Manager; 4 | 5 | use crate::core::handle; 6 | 7 | pub static APP_ID: &str = "com.minibili.app"; 8 | 9 | /// get the verge app home dir 10 | pub fn app_home_dir() -> Result { 11 | let handle = handle::Handle::global(); 12 | let app_handle = handle.app_handle.lock().unwrap(); 13 | 14 | if let Some(app_handle) = app_handle.as_ref() { 15 | match app_handle.path().data_dir() { 16 | Ok(dir) => { 17 | return Ok(dir.join(APP_ID)); 18 | } 19 | Err(e) => { 20 | log::error!("Failed to get the app home directory: {}", e); 21 | return Err(anyhow::anyhow!("Failed to get the app homedirectory")); 22 | } 23 | } 24 | } 25 | Err(anyhow::anyhow!("failed to get the app home dir")) 26 | } 27 | 28 | /// get the resources dir 29 | pub fn app_resources_dir() -> Result { 30 | let handle = handle::Handle::global(); 31 | let app_handle = handle.app_handle.lock().unwrap(); 32 | if let Some(app_handle) = app_handle.as_ref() { 33 | match app_handle.path().resource_dir() { 34 | Ok(dir) => { 35 | return Ok(dir.join("resources")); 36 | } 37 | Err(e) => { 38 | log::error!("Failed to get the resource directory: {}", e); 39 | return Err(anyhow::anyhow!("Failed to get the resource directory")); 40 | } 41 | }; 42 | }; 43 | Err(anyhow::anyhow!("failed to get the resource dir")) 44 | } 45 | 46 | /// get the cache dir 47 | pub fn app_cache_dir() -> Result { 48 | let handle = handle::Handle::global(); 49 | let app_handle = handle.app_handle.lock().unwrap(); 50 | if let Some(app_handle) = app_handle.as_ref() { 51 | match app_handle.path().app_cache_dir() { 52 | Ok(dir) => { 53 | return Ok(dir.join("cache")); 54 | } 55 | Err(e) => { 56 | log::error!("Failed to get the cache directory: {}", e); 57 | return Err(anyhow::anyhow!("Failed to get the cache directory")); 58 | } 59 | }; 60 | }; 61 | Err(anyhow::anyhow!("failed to get the cache dir")) 62 | } 63 | -------------------------------------------------------------------------------- /src-tauri/gen/android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | 3 | plugins { 4 | id("com.android.application") 5 | id("org.jetbrains.kotlin.android") 6 | id("rust") 7 | } 8 | 9 | val tauriProperties = Properties().apply { 10 | val propFile = file("tauri.properties") 11 | if (propFile.exists()) { 12 | propFile.inputStream().use { load(it) } 13 | } 14 | } 15 | 16 | android { 17 | compileSdk = 34 18 | namespace = "com.minibili.app" 19 | defaultConfig { 20 | manifestPlaceholders["usesCleartextTraffic"] = "false" 21 | applicationId = "com.minibili.app" 22 | minSdk = 24 23 | targetSdk = 34 24 | versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() 25 | versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") 26 | } 27 | buildTypes { 28 | getByName("debug") { 29 | manifestPlaceholders["usesCleartextTraffic"] = "true" 30 | isDebuggable = true 31 | isJniDebuggable = true 32 | isMinifyEnabled = false 33 | packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") 34 | jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") 35 | jniLibs.keepDebugSymbols.add("*/x86/*.so") 36 | jniLibs.keepDebugSymbols.add("*/x86_64/*.so") 37 | } 38 | } 39 | getByName("release") { 40 | isMinifyEnabled = true 41 | proguardFiles( 42 | *fileTree(".") { include("**/*.pro") } 43 | .plus(getDefaultProguardFile("proguard-android-optimize.txt")) 44 | .toList().toTypedArray() 45 | ) 46 | } 47 | } 48 | kotlinOptions { 49 | jvmTarget = "1.8" 50 | } 51 | buildFeatures { 52 | buildConfig = true 53 | } 54 | } 55 | 56 | rust { 57 | rootDirRel = "../../../" 58 | } 59 | 60 | dependencies { 61 | implementation("androidx.webkit:webkit:1.6.1") 62 | implementation("androidx.appcompat:appcompat:1.6.1") 63 | implementation("com.google.android.material:material:1.8.0") 64 | testImplementation("junit:junit:4.13.2") 65 | androidTestImplementation("androidx.test.ext:junit:1.1.4") 66 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") 67 | } 68 | 69 | apply(from = "tauri.build.gradle.kts") -------------------------------------------------------------------------------- /.eslintrc-auto-import.js: -------------------------------------------------------------------------------- 1 | export default { 2 | globals: { 3 | Component: true, 4 | ComponentPublicInstance: true, 5 | ComputedRef: true, 6 | DirectiveBinding: true, 7 | EffectScope: true, 8 | ExtractDefaultPropTypes: true, 9 | ExtractPropTypes: true, 10 | ExtractPublicPropTypes: true, 11 | InjectionKey: true, 12 | MaybeRef: true, 13 | MaybeRefOrGetter: true, 14 | PropType: true, 15 | Ref: true, 16 | VNode: true, 17 | WritableComputedRef: true, 18 | computed: true, 19 | createApp: true, 20 | customRef: true, 21 | defineAsyncComponent: true, 22 | defineComponent: true, 23 | effectScope: true, 24 | getCurrentInstance: true, 25 | getCurrentScope: true, 26 | h: true, 27 | inject: true, 28 | isProxy: true, 29 | isReactive: true, 30 | isReadonly: true, 31 | isRef: true, 32 | markRaw: true, 33 | nextTick: true, 34 | onActivated: true, 35 | onBeforeMount: true, 36 | onBeforeRouteLeave: true, 37 | onBeforeRouteUpdate: true, 38 | onBeforeUnmount: true, 39 | onBeforeUpdate: true, 40 | onDeactivated: true, 41 | onErrorCaptured: true, 42 | onMounted: true, 43 | onRenderTracked: true, 44 | onRenderTriggered: true, 45 | onScopeDispose: true, 46 | onServerPrefetch: true, 47 | onUnmounted: true, 48 | onUpdated: true, 49 | onWatcherCleanup: true, 50 | provide: true, 51 | reactive: true, 52 | readonly: true, 53 | ref: true, 54 | resolveComponent: true, 55 | shallowReactive: true, 56 | shallowReadonly: true, 57 | shallowRef: true, 58 | toRaw: true, 59 | toRef: true, 60 | toRefs: true, 61 | toValue: true, 62 | triggerRef: true, 63 | unref: true, 64 | useAttrs: true, 65 | useCssModule: true, 66 | useCssVars: true, 67 | useId: true, 68 | useLink: true, 69 | useModel: true, 70 | useRoute: true, 71 | useRouter: true, 72 | useSlots: true, 73 | useTemplateRef: true, 74 | watch: true, 75 | watchEffect: true, 76 | watchPostEffect: true, 77 | watchSyncEffect: true, 78 | }, 79 | } 80 | -------------------------------------------------------------------------------- /src/apis/video/recommend.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { VideoRecommendations } from '@/apis/types/video-recommendations' 3 | 4 | /** 5 | * Interface representing the parameters for the recommendation API. 6 | * 7 | * @property {number} [brush] - Optional parameter for brush. 8 | * @property {string} [feed_version] - Optional parameter for feed version. 9 | * @property {number} [fetch_row] - Optional parameter for fetch row. 10 | * @property {number} [fresh_idx] - Optional parameter for fresh index. 11 | * @property {number} [fresh_idx_1h] - Optional parameter for fresh index in 1 hour. 12 | * @property {number} [fresh_type] - Optional parameter for fresh type. 13 | * @property {number} [homepage_ver] - Optional parameter for homepage version. 14 | * @property {string} [last_showlist] - Optional parameter for last show list. 15 | * @property {number} [last_y_num] - Optional parameter for last Y number. 16 | * @property {number} [ps] - Optional parameter for PS. 17 | * @property {string} [screen] - Optional parameter for screen. 18 | * @property {string} [seo_info] - Optional parameter for SEO information. 19 | * @property {string} [uniq_id] - Optional parameter for unique ID. 20 | * @property {string} [w_rid] - Optional parameter for W RID. 21 | * @property {number} [web_location] - Optional parameter for web location. 22 | * @property {number} [wts] - Optional parameter for WTS. 23 | * @property {number} [y_num] - Optional parameter for Y number. 24 | */ 25 | export interface RecommendParams { 26 | brush?: number 27 | feed_version?: string 28 | fetch_row?: number 29 | fresh_idx?: number 30 | fresh_idx_1h?: number 31 | fresh_type?: number 32 | homepage_ver?: number 33 | last_showlist?: string 34 | last_y_num?: number 35 | ps?: number 36 | screen?: string 37 | seo_info?: string 38 | uniq_id?: string 39 | w_rid?: string 40 | web_location?: number 41 | wts?: number 42 | y_num?: number 43 | } 44 | 45 | /** 46 | * Fetches video recommendations based on the provided parameters. 47 | * 48 | * @param params - The parameters used to fetch video recommendations. 49 | * @returns A promise that resolves to video recommendations. 50 | */ 51 | export const fetchVideoRecommendations = async ( 52 | params: RecommendParams 53 | ): Promise => { 54 | return await get( 55 | 'https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd', 56 | params 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src-tauri/src/core/tray.rs: -------------------------------------------------------------------------------- 1 | use log::warn; 2 | use tauri::menu::Menu; 3 | use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; 4 | use tauri::{ 5 | menu::{MenuEvent, MenuItem}, 6 | Wry, 7 | }; 8 | use tauri::{AppHandle, Manager}; 9 | 10 | pub struct Tray {} 11 | 12 | impl Tray { 13 | pub fn init_tray(app_handle: &AppHandle) { 14 | // 托盘图标设置 15 | let _tray = TrayIconBuilder::new() 16 | .on_tray_icon_event(|tray, event| match event { 17 | TrayIconEvent::Click { 18 | button: MouseButton::Left, 19 | button_state: MouseButtonState::Up, 20 | .. 21 | } => { 22 | // 单击左键时显示主窗口 23 | let app = tray.app_handle(); 24 | if let Some(window) = app.get_webview_window("main") { 25 | let _ = window.show(); 26 | let _ = window.set_focus(); 27 | } 28 | } 29 | _ => {} 30 | }) 31 | .icon(app_handle.default_window_icon().unwrap().clone()) // 设置图标 32 | .tooltip("MiniBili") // 设置提示信息 33 | .menu(&create_menu(app_handle)) // 绑定菜单 34 | .menu_on_left_click(false) // 左键点击不打开菜单 35 | .on_menu_event(on_menu_event) 36 | .build(app_handle); 37 | } 38 | } 39 | 40 | fn on_menu_event(app_handle: &AppHandle, event: MenuEvent) { 41 | match event.id.as_ref() { 42 | "open" => { 43 | app_handle 44 | .get_webview_window("main") 45 | .unwrap() 46 | .show() 47 | .unwrap(); 48 | } 49 | "hide" => { 50 | app_handle 51 | .get_webview_window("main") 52 | .unwrap() 53 | .hide() 54 | .unwrap(); 55 | } 56 | "quit" => { 57 | app_handle.exit(0); 58 | } 59 | _ => { 60 | warn!("未处理的菜单项 {:?}", event.id); 61 | } 62 | } 63 | } 64 | 65 | fn create_menu(app_handle: &AppHandle) -> Menu { 66 | // 创建菜单项 67 | let open_i = MenuItem::with_id(app_handle, "open", "Open", true, None::<&str>).unwrap(); 68 | let hide_i = MenuItem::with_id(app_handle, "hide", "Hide", true, None::<&str>).unwrap(); 69 | let quit_i = MenuItem::with_id(app_handle, "quit", "Quit", true, None::<&str>).unwrap(); 70 | Menu::with_items(app_handle, &[&open_i, &hide_i, &quit_i]).unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /src/components/home/Ranking.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 78 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | export {} 6 | 7 | /* prettier-ignore */ 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | AppBar: typeof import('./src/components/layout/AppBar.vue')['default'] 11 | BangumiCard: typeof import('./src/components/BangumiCard.vue')['default'] 12 | CaptchaCard: typeof import('./src/components/CaptchaCard.vue')['default'] 13 | DanmuCard: typeof import('./src/components/live/DanmuCard.vue')['default'] 14 | Home: typeof import('./src/components/pages/Home.vue')['default'] 15 | Live: typeof import('./src/components/live/Live.vue')['default'] 16 | LivePlayer: typeof import('./src/components/player/LivePlayer.vue')['default'] 17 | LiveRoom: typeof import('./src/components/live/LiveRoom.vue')['default'] 18 | LiveRoomCard: typeof import('./src/components/live/LiveRoomCard.vue')['default'] 19 | Login: typeof import('./src/components/pages/Login.vue')['default'] 20 | NotFound: typeof import('./src/components/pages/NotFound.vue')['default'] 21 | Popular: typeof import('./src/components/home/Popular.vue')['default'] 22 | PopularPrecious: typeof import('./src/components/home/PopularPrecious.vue')['default'] 23 | PopularSeries: typeof import('./src/components/home/PopularSeries.vue')['default'] 24 | Ranking: typeof import('./src/components/home/Ranking.vue')['default'] 25 | Recommend: typeof import('./src/components/home/Recommend.vue')['default'] 26 | RouterLink: typeof import('vue-router')['RouterLink'] 27 | RouterView: typeof import('vue-router')['RouterView'] 28 | Search: typeof import('./src/components/pages/Search.vue')['default'] 29 | SearchAll: typeof import('./src/components/search/SearchAll.vue')['default'] 30 | SearchBanguni: typeof import('./src/components/search/SearchBanguni.vue')['default'] 31 | SearchBox: typeof import('./src/components/search/SearchBox.vue')['default'] 32 | SearchVideo: typeof import('./src/components/search/SearchVideo.vue')['default'] 33 | Setting: typeof import('./src/components/pages/Setting.vue')['default'] 34 | SideBar: typeof import('./src/components/layout/SideBar.vue')['default'] 35 | Space: typeof import('./src/components/pages/Space.vue')['default'] 36 | Video: typeof import('./src/components/pages/Video.vue')['default'] 37 | VideoCard: typeof import('./src/components/VideoCard.vue')['default'] 38 | VideoPlayer: typeof import('./src/components/player/VideoPlayer.vue')['default'] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/home/PopularPrecious.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 76 | -------------------------------------------------------------------------------- /src/apis/types/apis.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the result of an API call. 3 | * 4 | * @template T - The type of the data returned by the API. 5 | * @property {number} code - The status code of the API response. 6 | * @property {string} message - A message accompanying the API response. 7 | * @property {number} ttl - Time to live for the API response. 8 | * @property {T} data - The data returned by the API. 9 | * @property {T} result - The data returned by the API. 10 | */ 11 | export interface ApiResult { 12 | code: number 13 | ttl?: number 14 | message?: string 15 | msg?: string 16 | data?: T 17 | // 兼容搜索建议接口 18 | result?: T 19 | } 20 | 21 | // 错误码枚举 22 | export enum ErrorCode { 23 | // 权限类 24 | AppNotExistOrBanned = -1, 25 | AccessKeyError = -2, 26 | ApiValidationKeyError = -3, 27 | NoPermissionForMethod = -4, 28 | AccountNotLoggedIn = -101, 29 | AccountBanned = -102, 30 | InsufficientPoints = -103, 31 | InsufficientCoins = -104, 32 | CaptchaError = -105, 33 | AccountNotFormalMemberOrInTrial = -106, 34 | AppNotExistOrBannedAlt = -107, // 与 -1 重复,建议删除或合并 35 | UnboundPhone = -108, 36 | UnboundPhoneAlt = -110, // 与 -108 重复,建议删除或合并 37 | CsrfCheckFailed = -111, 38 | SystemUpgrading = -112, 39 | AccountNotVerified = -113, 40 | PleaseBindPhone = -114, 41 | PleaseCompleteVerification = -115, 42 | 43 | // 请求类 44 | NotModified = -304, 45 | RedirectOnCollision = -307, 46 | RiskControlCheckFailed = -352, 47 | BadRequest = -400, 48 | Unauthorized = -401, 49 | Forbidden = -403, 50 | NotFound = -404, 51 | MethodNotAllowed = -405, 52 | Conflict = -409, 53 | RequestIntercepted = -412, 54 | InternalServerError = -500, 55 | ServiceUnavailable = -503, 56 | GatewayTimeout = -504, 57 | ExceedLimit = -509, 58 | FileNotFound = -616, 59 | FileTooLarge = -617, 60 | TooManyLoginFailures = -625, 61 | UserNotExist = -626, 62 | WeakPassword = -628, 63 | InvalidUsernameOrPassword = -629, 64 | OperationObjectLimitExceeded = -632, 65 | Locked = -643, 66 | LowUserLevel = -650, 67 | DuplicateUser = -652, 68 | TokenExpired = -658, 69 | PasswordTimestampExpired = -662, 70 | GeographicalRestriction = -688, 71 | CopyrightRestriction = -689, 72 | DeductMoralityFailed = -701, 73 | RequestTooFrequent = -799, 74 | ServerError = -8888, 75 | } 76 | 77 | /** 78 | * Represents the result of a Geetest API call. 79 | * 80 | * @template T - The type of the data returned by the API. 81 | * @property {('success' | 'error')} status - The status of the API call. 82 | * @property {T} data - The data returned by the API. 83 | */ 84 | export interface GeetestApiResult { 85 | status: 'success' | 'error' 86 | data: T 87 | error?: string 88 | user_error?: string 89 | error_code?: string 90 | } 91 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | lang: 'zh-Hans', 6 | title: "MiniBili Docs", 7 | description: "MiniBili 文档", 8 | 9 | base: '/minibili/', 10 | 11 | lastUpdated: true, 12 | cleanUrls: true, 13 | metaChunk: true, 14 | 15 | themeConfig: { 16 | logo: '/icon.ico', 17 | 18 | // https://vitepress.dev/reference/default-theme-config 19 | nav: [ 20 | { text: '主页', link: '/', activeMatch: '/' }, 21 | { text: '指南', link: '/about', activeMatch: '/about/' }, 22 | { text: 'Release', link: 'https://github.com/Z-Only/minibili/releases' }, 23 | ], 24 | 25 | sidebar: [ 26 | { 27 | text: '指南', 28 | collapsed: false, 29 | items: [ 30 | { text: '简介', link: '/about' }, 31 | { text: '开发', link: '/develop' }, 32 | { text: '安装', link: '/install' }, 33 | { text: '使用', link: '/use' } 34 | ] 35 | } 36 | ], 37 | 38 | socialLinks: [ 39 | { icon: 'github', link: 'https://github.com/Z-Only/minibili' } 40 | ], 41 | 42 | footer: { 43 | message: '基于 GPL-3.0 许可发布', 44 | copyright: `版权所有 © 2024-${new Date().getFullYear()} Z` 45 | }, 46 | 47 | outline: { 48 | label: '页面导航' 49 | }, 50 | 51 | editLink: { 52 | pattern: 'https://github.com/Z-Only/minibili/edit/main/docs/:path', 53 | text: '在 GitHub 上编辑此页面' 54 | }, 55 | 56 | lastUpdated: { 57 | text: '最后更新于', 58 | formatOptions: { 59 | dateStyle: 'short', 60 | timeStyle: 'medium' 61 | } 62 | }, 63 | 64 | docFooter: { 65 | prev: '上一页', 66 | next: '下一页' 67 | }, 68 | 69 | langMenuLabel: '多语言', 70 | returnToTopLabel: '回到顶部', 71 | sidebarMenuLabel: '菜单', 72 | darkModeSwitchLabel: '主题', 73 | lightModeSwitchTitle: '切换到浅色模式', 74 | darkModeSwitchTitle: '切换到深色模式', 75 | 76 | search: { 77 | provider: 'local', 78 | options: { 79 | miniSearch: { 80 | searchOptions: { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } } 81 | }, 82 | locales: { 83 | zh: { 84 | translations: { 85 | button: { 86 | buttonText: '搜索文档', 87 | buttonAriaLabel: '搜索文档' 88 | }, 89 | modal: { 90 | noResultsText: '无法找到相关结果', 91 | resetButtonTitle: '清除查询条件', 92 | footer: { 93 | selectText: '选择', 94 | navigateText: '切换', 95 | closeText: '关闭' 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | }, 103 | } 104 | }) 105 | -------------------------------------------------------------------------------- /src/components/pages/Setting.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minibili", 3 | "private": true, 4 | "version": "0.1.0", 5 | "author": "z", 6 | "description": "A Third-party Bilibili client.", 7 | "type": "module", 8 | "license": "GPL-3.0-only", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Z-Only/minibili" 12 | }, 13 | "scripts": { 14 | "dev": "vite --host", 15 | "build": "vue-tsc --noEmit && vite build", 16 | "preview": "vite preview", 17 | "tauri": "tauri", 18 | "test": "vitest", 19 | "lint": "eslint", 20 | "format": "prettier . --write --ignore-unknown", 21 | "format:check": "prettier . --check --ignore-unknown", 22 | "prepare": "husky", 23 | "docs:dev": "vitepress dev docs --host", 24 | "docs:build": "vitepress build docs", 25 | "docs:preview": "vitepress preview docs" 26 | }, 27 | "lint-staged": { 28 | "**/*": "prettier --write --ignore-unknown" 29 | }, 30 | "dependencies": { 31 | "@tauri-apps/api": "^2.1.1", 32 | "@tauri-apps/plugin-clipboard-manager": "^2.0.1", 33 | "@tauri-apps/plugin-dialog": "~2.0.1", 34 | "@tauri-apps/plugin-log": "~2.0.1", 35 | "@tauri-apps/plugin-shell": "^2.0.1", 36 | "@tauri-apps/plugin-store": "~2.1.0", 37 | "jsrsasign": "^11.1.0", 38 | "jsrsasign-util": "^1.0.5", 39 | "pinia": "^2.3.0", 40 | "qrcode": "^1.5.4", 41 | "vite-plugin-vuetify": "^2.0.4", 42 | "vue": "^3.5.13", 43 | "vue-router": "^4.5.0", 44 | "vuetify": "^3.7.5", 45 | "xgplayer": "^3.0.20", 46 | "xgplayer-flv": "^3.0.20" 47 | }, 48 | "devDependencies": { 49 | "@eslint/js": "^9.16.0", 50 | "@mdi/font": "^7.4.47", 51 | "@tauri-apps/cli": "^2.1.0", 52 | "@types/jsrsasign": "^10.5.15", 53 | "@types/node": "^22.10.1", 54 | "@types/qrcode": "^1.5.5", 55 | "@vitejs/plugin-vue": "^5.2.1", 56 | "eslint": "^9.16.0", 57 | "eslint-config-prettier": "^9.1.0", 58 | "eslint-plugin-vue": "^9.32.0", 59 | "globals": "^15.13.0", 60 | "husky": "^9.1.7", 61 | "lint-staged": "^15.2.10", 62 | "prettier": "3.3.3", 63 | "rollup-plugin-visualizer": "^5.12.0", 64 | "sass": "^1.82.0", 65 | "sass-loader": "^16.0.4", 66 | "stylelint": "^16.11.0", 67 | "stylelint-config-standard-scss": "^13.1.0", 68 | "typescript": "5.6.3", 69 | "typescript-eslint": "^8.17.0", 70 | "unplugin-auto-import": "^0.18.6", 71 | "unplugin-vue-components": "^0.27.5", 72 | "vite": "^5.4.11", 73 | "vite-plugin-chunk-split": "^0.5.0", 74 | "vite-plugin-eslint": "^1.8.1", 75 | "vitepress": "^1.5.0", 76 | "vitest": "^2.1.8", 77 | "vue-tsc": "2.1.10" 78 | }, 79 | "packageManager": "pnpm@9.14.4" 80 | } 81 | -------------------------------------------------------------------------------- /src/apis/types/play-url.ts: -------------------------------------------------------------------------------- 1 | // MP4 or DASH 2 | export interface PlayUrl { 3 | accept_description: string[] 4 | accept_format: string 5 | accept_quality: number[] 6 | durl?: Durl[] 7 | dash?: Dash 8 | format: string 9 | from: string 10 | high_format: null 11 | last_play_cid: number 12 | last_play_time: number 13 | message: string 14 | quality: number 15 | result: string 16 | seek_param: string 17 | seek_type: string 18 | support_formats: SupportFormat[] 19 | timelength: number 20 | video_codecid: number 21 | view_info: null 22 | } 23 | 24 | // MP4 25 | export interface Durl { 26 | ahead?: string 27 | backup_url?: string[] 28 | length?: number 29 | order?: number 30 | size?: number 31 | url?: string 32 | vhead?: string 33 | } 34 | 35 | // DASH 36 | export interface Dash { 37 | audio: Audio[] 38 | dolby: Dolby 39 | duration: number 40 | flac: null 41 | min_buffer_time: number 42 | minBufferTime: number 43 | video: Video[] 44 | } 45 | 46 | export interface Audio { 47 | backup_url: string[] 48 | backupUrl: string[] 49 | bandwidth: number 50 | base_url: string 51 | baseUrl: string 52 | codecid: number 53 | codecs: string 54 | frame_rate: string 55 | frameRate: string 56 | height: number 57 | id: number 58 | mime_type: string 59 | mimeType: string 60 | sar: string 61 | segment_base: AudioSegmentBaseObject 62 | SegmentBase: AudioSegmentBase 63 | start_with_sap: number 64 | startWithSap: number 65 | width: number 66 | } 67 | 68 | export interface AudioSegmentBase { 69 | indexRange: string 70 | Initialization: string 71 | } 72 | 73 | export interface AudioSegmentBaseObject { 74 | index_range: string 75 | initialization: string 76 | } 77 | 78 | export interface Dolby { 79 | audio: null 80 | type: number 81 | } 82 | 83 | export interface Video { 84 | backup_url: string[] 85 | backupUrl: string[] 86 | bandwidth: number 87 | base_url: string 88 | baseUrl: string 89 | codecid: number 90 | codecs: string 91 | frame_rate: string 92 | frameRate: string 93 | height: number 94 | id: number 95 | mime_type: string 96 | mimeType: string 97 | sar: string 98 | segment_base: VideoSegmentBaseObject 99 | SegmentBase: VideoSegmentBase 100 | start_with_sap: number 101 | startWithSap: number 102 | width: number 103 | } 104 | 105 | export interface VideoSegmentBase { 106 | indexRange: string 107 | Initialization: string 108 | } 109 | 110 | export interface VideoSegmentBaseObject { 111 | index_range: string 112 | initialization: string 113 | } 114 | 115 | export interface SupportFormat { 116 | codecs?: string[] 117 | display_desc: string 118 | format: string 119 | new_description: string 120 | quality: number 121 | superscript: string 122 | } 123 | -------------------------------------------------------------------------------- /src-tauri/gen/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/components/pages/Search.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 98 | 99 | 104 | -------------------------------------------------------------------------------- /.github/workflows/test-android.yml: -------------------------------------------------------------------------------- 1 | name: test android 2 | 3 | # TODO: remove auto trigger, waiting for avaliable action for android, now can only build. 4 | on: 5 | workflow_dispatch: 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.platform }} 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | platform: [ubuntu-latest, macos-latest, windows-latest] 19 | 20 | steps: 21 | - name: checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: install Rust stable 25 | uses: dtolnay/rust-toolchain@stable 26 | 27 | - name: install Linux dependencies 28 | if: matrix.platform == 'ubuntu-latest' 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get install -y libgtk-3-dev webkit2gtk-4.1 32 | 33 | - name: install pnpm 34 | uses: pnpm/action-setup@v4 35 | with: 36 | run_install: false 37 | 38 | - name: setup node 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: lts/* 42 | cache: 'pnpm' 43 | 44 | - uses: actions/setup-java@v3 45 | with: 46 | distribution: temurin 47 | java-version: 17 48 | cache: 'gradle' 49 | 50 | - name: Setup NDK 51 | uses: nttld/setup-ndk@v1 52 | id: setup-ndk 53 | with: 54 | ndk-version: r25b 55 | local-cache: true 56 | 57 | - name: Restore Android Symlinks 58 | if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'macos-latest' 59 | run: | 60 | directory="${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin" 61 | find "$directory" -type l | while read link; do 62 | current_target=$(readlink "$link") 63 | new_target="$directory/$(basename "$current_target")" 64 | ln -sf "$new_target" "$link" 65 | echo "Changed $(basename "$link") from $current_target to $new_target" 66 | done 67 | 68 | - uses: Swatinem/rust-cache@v2 69 | 70 | - name: install frontend dependencies 71 | run: pnpm i --frozen-lockfile 72 | 73 | - name: init Android Studio project 74 | run: pnpm tauri android init 75 | env: 76 | NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} 77 | 78 | - name: build APK universal 79 | run: pnpm tauri android build 80 | env: 81 | NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | 84 | - name: build APK split per architecture 85 | run: pnpm tauri android build --split-per-abi 86 | env: 87 | NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | -------------------------------------------------------------------------------- /src/components/search/SearchBox.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 99 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { resolve } from 'path' 4 | import vuetify from 'vite-plugin-vuetify' 5 | import eslint from 'vite-plugin-eslint' 6 | import AutoImport from 'unplugin-auto-import/vite' 7 | import Components from 'unplugin-vue-components/vite' 8 | import { visualizer } from 'rollup-plugin-visualizer' 9 | import { chunkSplitPlugin } from 'vite-plugin-chunk-split' 10 | 11 | const host = process.env.TAURI_DEV_HOST 12 | 13 | // https://vitejs.dev/config/ 14 | export default defineConfig(({ mode }) => { 15 | const env = loadEnv(mode, process.cwd(), '') 16 | return { 17 | plugins: [ 18 | vue(), 19 | vuetify({ autoImport: true }), 20 | eslint(), 21 | AutoImport({ 22 | imports: ['vue', 'vue-router'], 23 | dts: false, 24 | eslintrc: { 25 | enabled: false, 26 | filepath: './.eslintrc-auto-import.js', 27 | }, 28 | }), 29 | Components({ dts: true }), 30 | visualizer({ 31 | open: false, 32 | gzipSize: true, 33 | brotliSize: true, 34 | filename: './dist/stats.html', 35 | }), 36 | chunkSplitPlugin({ 37 | strategy: 'default', 38 | customSplitting: { 39 | // `vue` and `vue-router` 会被打包到一个名为`vue-vendor`的 chunk 里面 40 | // 'vue-chunk': ['vue', 'vue-router'], 41 | // 将 vuetify 拆分到单独的 chunk 42 | // 'vuetify-chunk': ['vuetify'], 43 | // 源码中 apis 目录的代码都会打包进 `apis` 这个 chunk 中 44 | // apis: [/src\/apis/], 45 | }, 46 | }), 47 | ], 48 | resolve: { 49 | alias: { 50 | '@': resolve(__dirname, 'src'), 51 | }, 52 | }, 53 | test: { 54 | globals: true, 55 | environment: 'jsdom', 56 | server: { 57 | deps: { 58 | inline: ['vuetify'], 59 | }, 60 | }, 61 | }, 62 | 63 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 64 | // 65 | // 1. prevent vite from obscuring rust errors 66 | clearScreen: false, 67 | // 2. tauri expects a fixed port, fail if that port is not available 68 | server: { 69 | port: 1420, 70 | strictPort: true, 71 | host: host || false, 72 | hmr: host 73 | ? { 74 | protocol: 'ws', 75 | host, 76 | port: 1421, 77 | } 78 | : undefined, 79 | watch: { 80 | // 3. tell vite to ignore watching `src-tauri` 81 | ignored: ['**/src-tauri/**'], 82 | }, 83 | }, 84 | esbuild: { 85 | drop: env.MODE === 'production' ? ['console', 'debugger'] : [], 86 | }, 87 | css: { 88 | preprocessorOptions: { 89 | scss: { 90 | api: 'modern-compiler', 91 | }, 92 | }, 93 | }, 94 | } 95 | }) 96 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | # This workflow is triggered on push to the release branch or manually. 4 | on: 5 | push: 6 | branches: 7 | - release 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | publish-tauri: 16 | permissions: 17 | contents: write 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - platform: 'macos-latest' # for Arm based macs (M1 and above). 23 | args: '--target aarch64-apple-darwin' 24 | - platform: 'macos-latest' # for Intel based macs. 25 | args: '--target x86_64-apple-darwin' 26 | - platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04. 27 | args: '' 28 | - platform: 'windows-latest' 29 | args: '' 30 | 31 | runs-on: ${{ matrix.platform }} 32 | steps: 33 | - name: checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: install pnpm 37 | uses: pnpm/action-setup@v4 38 | with: 39 | run_install: false 40 | 41 | - name: setup node 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: lts/* 45 | cache: 'pnpm' 46 | 47 | - name: install Rust stable 48 | uses: dtolnay/rust-toolchain@stable 49 | with: 50 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. 51 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 52 | 53 | - name: install dependencies (ubuntu only) 54 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. 55 | run: | 56 | sudo apt-get update 57 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf 58 | # webkitgtk 4.1 is for Tauri v2. 59 | 60 | - name: install frontend dependencies 61 | run: pnpm i --frozen-lockfile 62 | 63 | - uses: tauri-apps/tauri-action@v0 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. 68 | releaseName: 'MiniBili nightly v__VERSION__' 69 | releaseBody: | 70 | ## 我应该下载哪个版本? 71 | 72 | ### MacOS 73 | - MacOS intel芯片: x64.dmg/x64.app.tar.gz 74 | - MacOS apple M芯片: aarch64.dmg/aarch64.app.tar.gz 75 | 76 | ### Linux 77 | - Linux 64位: amd64.deb/x86_64.rpm/amd64.AppImage 78 | 79 | ### Windows 80 | - Windows 64位: x64-setup.exe/x64.msi 81 | releaseDraft: true 82 | prerelease: false 83 | args: ${{ matrix.args }} 84 | -------------------------------------------------------------------------------- /src/apis/types/player-info.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: web 播放器信息 3 | */ 4 | export interface PlayerInfo { 5 | aid: number 6 | bvid: string 7 | allow_bp: boolean 8 | no_share: boolean 9 | cid: number 10 | max_limit: number 11 | page_no: number 12 | has_next: boolean 13 | ip_info: Ipinfo 14 | login_mid: number 15 | login_mid_hash: string 16 | is_owner: boolean 17 | name: string 18 | permission: string 19 | level_info: Levelinfo 20 | vip: Vip 21 | answer_status: number 22 | block_time: number 23 | role: string 24 | last_play_time: number 25 | last_play_cid: number 26 | now_time: number 27 | online_count: number 28 | need_login_subtitle: boolean 29 | subtitle: Subtitle 30 | view_points: any[] 31 | preview_toast: string 32 | options: Options 33 | guide_attention: any[] 34 | jump_card: any[] 35 | operation_card: any[] 36 | online_switch: Onlineswitch 37 | fawkes: Fawkes 38 | show_switch: Showswitch 39 | bgm_info: null 40 | toast_block: boolean 41 | is_upower_exclusive: boolean 42 | is_upower_play: boolean 43 | is_ugc_pay_preview: boolean 44 | elec_high_level: Elechighlevel 45 | disable_show_up_info: boolean 46 | } 47 | 48 | interface Elechighlevel { 49 | privilege_type: number 50 | title: string 51 | sub_title: string 52 | show_button: boolean 53 | button_text: string 54 | jump_url: string 55 | intro: string 56 | new: boolean 57 | } 58 | 59 | interface Showswitch { 60 | long_progress: boolean 61 | } 62 | 63 | interface Fawkes { 64 | config_version: number 65 | ff_version: number 66 | } 67 | 68 | interface Onlineswitch { 69 | enable_gray_dash_playback: string 70 | new_broadcast: string 71 | realtime_dm: string 72 | subtitle_submit_switch: string 73 | } 74 | 75 | interface Options { 76 | is_360: boolean 77 | without_vip: boolean 78 | } 79 | 80 | interface Subtitle { 81 | allow_submit: boolean 82 | lan: string 83 | lan_doc: string 84 | subtitles: any[] 85 | } 86 | 87 | interface Vip { 88 | type: number 89 | status: number 90 | due_date: number 91 | vip_pay_type: number 92 | theme_type: number 93 | label: Label 94 | avatar_subscript: number 95 | nickname_color: string 96 | role: number 97 | avatar_subscript_url: string 98 | tv_vip_status: number 99 | tv_vip_pay_type: number 100 | tv_due_date: number 101 | avatar_icon: Avataricon 102 | } 103 | 104 | interface Avataricon { 105 | icon_resource: Iconresource 106 | } 107 | 108 | interface Iconresource { 109 | [key: string]: string 110 | } 111 | 112 | interface Label { 113 | path: string 114 | text: string 115 | label_theme: string 116 | text_color: string 117 | bg_style: number 118 | bg_color: string 119 | border_color: string 120 | use_img_label: boolean 121 | img_label_uri_hans: string 122 | img_label_uri_hant: string 123 | img_label_uri_hans_static: string 124 | img_label_uri_hant_static: string 125 | } 126 | 127 | interface Levelinfo { 128 | current_level: number 129 | current_min: number 130 | current_exp: number 131 | next_exp: number 132 | level_up: number 133 | } 134 | 135 | interface Ipinfo { 136 | ip: string 137 | zone_ip: string 138 | zone_id: number 139 | country: string 140 | province: string 141 | city: string 142 | } 143 | -------------------------------------------------------------------------------- /src-tauri/gen/android/buildSrc/src/main/java/com/minibili/app/kotlin/RustPlugin.kt: -------------------------------------------------------------------------------- 1 | import com.android.build.api.dsl.ApplicationExtension 2 | import org.gradle.api.DefaultTask 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.kotlin.dsl.configure 6 | import org.gradle.kotlin.dsl.get 7 | 8 | const val TASK_GROUP = "rust" 9 | 10 | open class Config { 11 | lateinit var rootDirRel: String 12 | } 13 | 14 | open class RustPlugin : Plugin { 15 | private lateinit var config: Config 16 | 17 | override fun apply(project: Project) = with(project) { 18 | config = extensions.create("rust", Config::class.java) 19 | 20 | val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); 21 | val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList 22 | 23 | val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); 24 | val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList 25 | 26 | val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") 27 | 28 | extensions.configure { 29 | @Suppress("UnstableApiUsage") 30 | flavorDimensions.add("abi") 31 | productFlavors { 32 | create("universal") { 33 | dimension = "abi" 34 | ndk { 35 | abiFilters += abiList 36 | } 37 | } 38 | defaultArchList.forEachIndexed { index, arch -> 39 | create(arch) { 40 | dimension = "abi" 41 | ndk { 42 | abiFilters.add(defaultAbiList[index]) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | afterEvaluate { 50 | for (profile in listOf("debug", "release")) { 51 | val profileCapitalized = profile.replaceFirstChar { it.uppercase() } 52 | val buildTask = tasks.maybeCreate( 53 | "rustBuildUniversal$profileCapitalized", 54 | DefaultTask::class.java 55 | ).apply { 56 | group = TASK_GROUP 57 | description = "Build dynamic library in $profile mode for all targets" 58 | } 59 | 60 | tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) 61 | 62 | for (targetPair in targetsList.withIndex()) { 63 | val targetName = targetPair.value 64 | val targetArch = archList[targetPair.index] 65 | val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } 66 | val targetBuildTask = project.tasks.maybeCreate( 67 | "rustBuild$targetArchCapitalized$profileCapitalized", 68 | BuildTask::class.java 69 | ).apply { 70 | group = TASK_GROUP 71 | description = "Build dynamic library in $profile mode for $targetArch" 72 | rootDirRel = config.rootDirRel 73 | target = targetName 74 | release = profile == "release" 75 | } 76 | 77 | buildTask.dependsOn(targetBuildTask) 78 | tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( 79 | targetBuildTask 80 | ) 81 | } 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/components/home/Popular.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 117 | -------------------------------------------------------------------------------- /src/apis/live/info.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/service/request' 2 | import { 3 | LiveInfoData, 4 | LiveRoomInfoWithUserData, 5 | LiveRoomInitInfoData, 6 | LiveUserInfoData, 7 | RoomBaseInfoParams, 8 | RoomBaseInfoData, 9 | RoomStatusInfoParams, 10 | RoomStatusInfoData, 11 | RoomHistoryDanmakuData, 12 | RoomPlayInfoParams, 13 | RoomPlayInfoData, 14 | RoomAnchorData, 15 | } from '@/apis/types/live-info' 16 | 17 | /** 18 | * 获取直播间信息 19 | * @param room_id 直播间号,可以为短号 20 | * @returns 21 | */ 22 | export const fetchRoomInfo = async (room_id: number): Promise => { 23 | return await get( 24 | 'https://api.live.bilibili.com/room/v1/Room/get_info', 25 | { room_id } 26 | ) 27 | } 28 | 29 | /** 30 | * 获取用户对应的直播间状态 31 | * @param mid 目标用户mid 32 | * @returns 33 | */ 34 | export const fetchRoomInfoWithUser = async ( 35 | mid: number 36 | ): Promise => { 37 | return await get( 38 | 'https://api.live.bilibili.com/room/v1/Room/getRoomInfoOld', 39 | { mid } 40 | ) 41 | } 42 | 43 | /** 44 | * 获取房间页初始化信息 45 | * @param id 目标直播间号(短号) 46 | * @returns 47 | */ 48 | export const fetchRoomInitInfo = async ( 49 | id: number 50 | ): Promise => { 51 | return await get( 52 | 'https://api.live.bilibili.com/room/v1/Room/room_init', 53 | { id } 54 | ) 55 | } 56 | 57 | /** 58 | * 获取主播信息 59 | * @param uid 目标用户mid 60 | * @returns 61 | */ 62 | export const fetchLiveUserInfo = async ( 63 | uid: number 64 | ): Promise => { 65 | return await get( 66 | 'https://api.live.bilibili.com/live_user/v1/Master/info', 67 | { uid } 68 | ) 69 | } 70 | 71 | /** 72 | * 获取直播间基本信息 73 | * @param params 请求参数 74 | * @returns 75 | */ 76 | export const fetchRoomBaseInfo = async ( 77 | params: RoomBaseInfoParams 78 | ): Promise => { 79 | return await get( 80 | 'https://api.live.bilibili.com/xlive/web-room/v1/index/getRoomBaseInfo', 81 | params 82 | ) 83 | } 84 | 85 | /** 86 | * 批量查询直播间状态 87 | * @param uids 要查询的主播 mid 88 | * @returns 89 | */ 90 | export const fetchRoomStatusInfoByUids = async ( 91 | uids: number[] 92 | ): Promise => { 93 | return await get( 94 | 'https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids', 95 | { 'uids[]': uids } 96 | ) 97 | } 98 | 99 | /** 100 | * 获取直播间最近历史弹幕 101 | * @param roomid 直播间短ID 102 | * @returns 103 | */ 104 | export const fetchRoomHistoryDanmaku = async ( 105 | roomid: number 106 | ): Promise => { 107 | return await get( 108 | 'https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory', 109 | { roomid } 110 | ) 111 | } 112 | 113 | /** 114 | * 获取直播间信息 115 | * @param params 请求参数 116 | * @returns 117 | */ 118 | export const fetchRoomPlayInfo = async ( 119 | params: RoomPlayInfoParams 120 | ): Promise => { 121 | return await get( 122 | 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo', 123 | params 124 | ) 125 | } 126 | 127 | /** 128 | * 获取直播间主播信息 129 | * @param roomid 直播间号,可以为短号 130 | * @returns 131 | */ 132 | export const fetchRoomAnchor = async ( 133 | roomid: number 134 | ): Promise => { 135 | return await get( 136 | 'https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room', 137 | { roomid } 138 | ) 139 | } 140 | -------------------------------------------------------------------------------- /src/apis/types/live.ts: -------------------------------------------------------------------------------- 1 | export interface LiveAreaData { 2 | id: number 3 | list: LiveAreaInfo[] 4 | name: string 5 | } 6 | 7 | export interface LiveAreaInfo { 8 | act_id: string 9 | area_type: number 10 | cate_id: string 11 | complex_area_name: string 12 | hot_status: number 13 | id: string 14 | lock_status: string 15 | name: string 16 | old_area_id: string 17 | parent_id: string 18 | parent_name: string 19 | pic: string 20 | pk_status: string 21 | } 22 | 23 | export interface LiveRoomListParams { 24 | area_id: string 25 | page: string 26 | parent_area_id: string 27 | platform: string 28 | sort_type?: string 29 | vajra_business_key?: string 30 | } 31 | 32 | export interface LiveRoomListData { 33 | banner: Banner[] 34 | count: number 35 | has_more: number 36 | list: LiveRoomInfo[] 37 | new_tags: NewTag[] 38 | vajra: Vajra[] 39 | } 40 | 41 | export interface Banner { 42 | ad_transparent_content: null | AdTransparentContent 43 | area_id: number 44 | av_id: number 45 | id: number 46 | is_ad: boolean 47 | link: string 48 | live_status: number 49 | location: string 50 | parent_area_id: number 51 | pic: string 52 | position: number 53 | room_id: number 54 | show_ad_icon: boolean 55 | title: string 56 | up_id: number 57 | weight: number 58 | } 59 | 60 | export interface AdTransparentContent { 61 | client_ip: string 62 | index: number 63 | is_ad_loc: boolean 64 | request_id: string 65 | resource_id: number 66 | server_type: number 67 | source_id: number 68 | src_id: number 69 | } 70 | 71 | export interface LiveRoomInfo { 72 | area_id: number 73 | area_name: string 74 | area_v2_id: number 75 | area_v2_name: string 76 | area_v2_parent_id: number 77 | area_v2_parent_name: string 78 | click_callback: string 79 | cover: string 80 | face: string 81 | flag: number 82 | group_id: number 83 | head_box: null | HeadBox 84 | head_box_type: number 85 | is_auto_play: number 86 | is_nft: number 87 | link: string 88 | nft_dmark: string 89 | online: number 90 | parent_id: number 91 | parent_name: string 92 | pendant_info: PendantInfo 93 | pk_id: number 94 | play_together_goods: null 95 | roomid: number 96 | session_id: string 97 | show_callback: string 98 | show_cover: string 99 | system_cover: string 100 | title: string 101 | uid: number 102 | uname: string 103 | user_cover: string 104 | user_cover_flag: number 105 | verify: Verify 106 | watched_show: WatchedShow 107 | watermark: string 108 | web_pendent: string 109 | } 110 | 111 | export interface HeadBox { 112 | desc: string 113 | name: string 114 | value: string 115 | } 116 | 117 | export interface PendantInfo { 118 | '1': The1 119 | } 120 | 121 | export interface The1 { 122 | color: string 123 | content: string 124 | name: string 125 | pendent_id: number 126 | pic: string 127 | position: number 128 | type: string 129 | } 130 | 131 | export interface Verify { 132 | desc: string 133 | role: number 134 | type: number 135 | } 136 | 137 | export interface WatchedShow { 138 | icon: string 139 | icon_location: number 140 | icon_web: string 141 | num: number 142 | switch: boolean 143 | text_large: string 144 | text_small: string 145 | } 146 | 147 | export interface NewTag { 148 | hero_list: string[] 149 | icon: string 150 | id: number 151 | name: string 152 | sort: number 153 | sort_type: string 154 | sub: string[] 155 | type: number 156 | } 157 | 158 | export interface Vajra { 159 | desc: string 160 | icon: string 161 | living_num: number 162 | name: string 163 | vajra_business_key: string 164 | } 165 | -------------------------------------------------------------------------------- /src/components/VideoCard.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 104 | 105 | 110 | -------------------------------------------------------------------------------- /src/components/home/Recommend.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 133 | -------------------------------------------------------------------------------- /src/components/home/PopularSeries.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 133 | -------------------------------------------------------------------------------- /src/components/search/SearchAll.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 124 | -------------------------------------------------------------------------------- /src/components/live/LiveRoom.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 122 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "MiniBili" 7 | text: "基于 Tauri 的 Bilibili 第三方客户端" 8 | tagline: "一个轻量级、高性能的 Bilibili 第三方客户端" 9 | actions: 10 | - theme: brand 11 | text: 简介 12 | link: /about 13 | - theme: alt 14 | text: Github 15 | link: https://github.com/Z-Only/minibili 16 | image: 17 | src: /icon.png 18 | alt: MiniBili 19 | 20 | features: 21 | - icon: 22 | title: Tauri 23 | details: Web 应用构建框架,跨平台,由 Rust 驱动,高安全性 24 | - icon: 25 | title: Vue 26 | details: 主流的 Web 前端框架,性能出色,灵活多变 27 | - icon: 28 | title: Bilibili 29 | details: 取之于B,用之于B 30 | --- 31 | 52 | -------------------------------------------------------------------------------- /src/apis/types/space-info.ts: -------------------------------------------------------------------------------- 1 | export interface SpaceInfoData { 2 | birthday: string 3 | coins: number 4 | contract: Contract 5 | elec: Elec 6 | face: string 7 | face_nft: number 8 | face_nft_type: number 9 | fans_badge: boolean 10 | fans_medal: FansMedal 11 | gaia_data: null 12 | gaia_res_type: number 13 | is_followed: boolean 14 | is_risk: boolean 15 | is_senior_member: number 16 | jointime: number 17 | level: number 18 | live_room: LiveRoom 19 | mcn_info: null 20 | mid: number 21 | moral: number 22 | name: string 23 | nameplate: Nameplate 24 | official: Official 25 | pendant: Pendant 26 | profession: Profession 27 | rank: number 28 | school: School 29 | series: Series 30 | sex: string 31 | sign: string 32 | silence: number 33 | sys_notice: SysNotice 34 | tags: null 35 | theme: { [key: string]: any } 36 | top_photo: string 37 | user_honour_info: UserHonourInfo 38 | vip: Vip 39 | } 40 | 41 | export interface Contract { 42 | is_display: boolean 43 | is_follow_display: boolean 44 | } 45 | 46 | export interface Elec { 47 | show_info: ShowInfo 48 | } 49 | 50 | export interface ShowInfo { 51 | icon: string 52 | jump_url: string 53 | show: boolean 54 | state: number 55 | title: string 56 | } 57 | 58 | export interface FansMedal { 59 | medal: Medal 60 | show: boolean 61 | wear: boolean 62 | } 63 | 64 | export interface Medal { 65 | day_limit: number 66 | intimacy: number 67 | is_lighted: number 68 | level: number 69 | light_status: number 70 | medal_color: number 71 | medal_color_border: number 72 | medal_color_end: number 73 | medal_color_start: number 74 | medal_id: number 75 | medal_name: string 76 | next_intimacy: number 77 | score: number 78 | target_id: number 79 | uid: number 80 | wearing_status: number 81 | } 82 | 83 | export interface LiveRoom { 84 | broadcast_type: number 85 | cover: string 86 | liveStatus: number 87 | roomid: number 88 | roomStatus: number 89 | roundStatus: number 90 | title: string 91 | url: string 92 | watched_show: WatchedShow 93 | } 94 | 95 | export interface WatchedShow { 96 | icon: string 97 | icon_location: string 98 | icon_web: string 99 | num: number 100 | switch: boolean 101 | text_large: string 102 | text_small: string 103 | } 104 | 105 | export interface Nameplate { 106 | condition: string 107 | image: string 108 | image_small: string 109 | level: string 110 | name: string 111 | nid: number 112 | } 113 | 114 | export interface Official { 115 | desc: string 116 | role: number 117 | title: string 118 | type: number 119 | } 120 | 121 | export interface Pendant { 122 | expire: number 123 | image: string 124 | image_enhance: string 125 | image_enhance_frame: string 126 | name: string 127 | pid: number 128 | } 129 | 130 | export interface Profession { 131 | department: string 132 | is_show: number 133 | name: string 134 | title: string 135 | } 136 | 137 | export interface SysNotice { 138 | id: number 139 | content: string 140 | url: string 141 | notice_type: number 142 | icon: string 143 | text_color: string 144 | bg_color: string 145 | } 146 | 147 | export interface School { 148 | name: string 149 | } 150 | 151 | export interface Series { 152 | show_upgrade_window: boolean 153 | user_upgrade_status: number 154 | } 155 | 156 | export interface UserHonourInfo { 157 | colour: null 158 | mid: number 159 | tags: string[] 160 | } 161 | 162 | export interface Vip { 163 | avatar_subscript: number 164 | avatar_subscript_url: string 165 | due_date: number 166 | label: Label 167 | nickname_color: string 168 | role: number 169 | status: number 170 | theme_type: number 171 | tv_due_date: number 172 | tv_vip_pay_type: number 173 | tv_vip_status: number 174 | type: number 175 | vip_pay_type: number 176 | } 177 | 178 | export interface Label { 179 | bg_color: string 180 | bg_style: number 181 | border_color: string 182 | img_label_uri_hans: string 183 | img_label_uri_hans_static: string 184 | img_label_uri_hant: string 185 | img_label_uri_hant_static: string 186 | label_theme: string 187 | path: string 188 | text: string 189 | text_color: string 190 | use_img_label: boolean 191 | } 192 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | // biome-ignore lint: disable 7 | export {} 8 | declare global { 9 | const EffectScope: (typeof import('vue'))['EffectScope'] 10 | const computed: (typeof import('vue'))['computed'] 11 | const createApp: (typeof import('vue'))['createApp'] 12 | const customRef: (typeof import('vue'))['customRef'] 13 | const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'] 14 | const defineComponent: (typeof import('vue'))['defineComponent'] 15 | const effectScope: (typeof import('vue'))['effectScope'] 16 | const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'] 17 | const getCurrentScope: (typeof import('vue'))['getCurrentScope'] 18 | const h: (typeof import('vue'))['h'] 19 | const inject: (typeof import('vue'))['inject'] 20 | const isProxy: (typeof import('vue'))['isProxy'] 21 | const isReactive: (typeof import('vue'))['isReactive'] 22 | const isReadonly: (typeof import('vue'))['isReadonly'] 23 | const isRef: (typeof import('vue'))['isRef'] 24 | const markRaw: (typeof import('vue'))['markRaw'] 25 | const nextTick: (typeof import('vue'))['nextTick'] 26 | const onActivated: (typeof import('vue'))['onActivated'] 27 | const onBeforeMount: (typeof import('vue'))['onBeforeMount'] 28 | const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave'] 29 | const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate'] 30 | const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'] 31 | const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'] 32 | const onDeactivated: (typeof import('vue'))['onDeactivated'] 33 | const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'] 34 | const onMounted: (typeof import('vue'))['onMounted'] 35 | const onRenderTracked: (typeof import('vue'))['onRenderTracked'] 36 | const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'] 37 | const onScopeDispose: (typeof import('vue'))['onScopeDispose'] 38 | const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'] 39 | const onUnmounted: (typeof import('vue'))['onUnmounted'] 40 | const onUpdated: (typeof import('vue'))['onUpdated'] 41 | const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'] 42 | const provide: (typeof import('vue'))['provide'] 43 | const reactive: (typeof import('vue'))['reactive'] 44 | const readonly: (typeof import('vue'))['readonly'] 45 | const ref: (typeof import('vue'))['ref'] 46 | const resolveComponent: (typeof import('vue'))['resolveComponent'] 47 | const shallowReactive: (typeof import('vue'))['shallowReactive'] 48 | const shallowReadonly: (typeof import('vue'))['shallowReadonly'] 49 | const shallowRef: (typeof import('vue'))['shallowRef'] 50 | const toRaw: (typeof import('vue'))['toRaw'] 51 | const toRef: (typeof import('vue'))['toRef'] 52 | const toRefs: (typeof import('vue'))['toRefs'] 53 | const toValue: (typeof import('vue'))['toValue'] 54 | const triggerRef: (typeof import('vue'))['triggerRef'] 55 | const unref: (typeof import('vue'))['unref'] 56 | const useAttrs: (typeof import('vue'))['useAttrs'] 57 | const useCssModule: (typeof import('vue'))['useCssModule'] 58 | const useCssVars: (typeof import('vue'))['useCssVars'] 59 | const useId: (typeof import('vue'))['useId'] 60 | const useLink: (typeof import('vue-router'))['useLink'] 61 | const useModel: (typeof import('vue'))['useModel'] 62 | const useRoute: (typeof import('vue-router'))['useRoute'] 63 | const useRouter: (typeof import('vue-router'))['useRouter'] 64 | const useSlots: (typeof import('vue'))['useSlots'] 65 | const useTemplateRef: (typeof import('vue'))['useTemplateRef'] 66 | const watch: (typeof import('vue'))['watch'] 67 | const watchEffect: (typeof import('vue'))['watchEffect'] 68 | const watchPostEffect: (typeof import('vue'))['watchPostEffect'] 69 | const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'] 70 | } 71 | // for type re-export 72 | declare global { 73 | // @ts-ignore 74 | export type { 75 | Component, 76 | ComponentPublicInstance, 77 | ComputedRef, 78 | DirectiveBinding, 79 | ExtractDefaultPropTypes, 80 | ExtractPropTypes, 81 | ExtractPublicPropTypes, 82 | InjectionKey, 83 | PropType, 84 | Ref, 85 | MaybeRef, 86 | MaybeRefOrGetter, 87 | VNode, 88 | WritableComputedRef, 89 | } from 'vue' 90 | import('vue') 91 | } 92 | -------------------------------------------------------------------------------- /src/components/search/SearchBanguni.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 145 | -------------------------------------------------------------------------------- /src/apis/types/video-popular.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Owner, 3 | Stat, 4 | Dimension, 5 | VideoDetails, 6 | Rights, 7 | } from '@/apis/types/video-details' 8 | 9 | export interface PopularData { 10 | list: PopularItem[] 11 | no_more: boolean 12 | } 13 | 14 | export interface PopularItem extends VideoDetails { 15 | ai_rcmd: null 16 | aid: number 17 | bvid: string 18 | cid: number 19 | copyright: number 20 | cover43: string 21 | ctime: number 22 | desc: string 23 | dimension: Dimension 24 | duration: number 25 | dynamic: string 26 | enable_vt: number 27 | first_frame: string 28 | is_ogv: boolean 29 | mission_id: number 30 | ogv_info: null 31 | owner: Owner 32 | pic: string 33 | pub_location: string 34 | pubdate: number 35 | rcmd_reason: RcmdReason 36 | rights: Rights 37 | season_id: number 38 | season_type: number 39 | short_link_v2: string 40 | stat: Stat 41 | state: number 42 | tid: number 43 | title: string 44 | tname: string 45 | up_from_v2: number 46 | videos: number 47 | } 48 | 49 | export interface RcmdReason { 50 | content: string 51 | corner_mark: number 52 | } 53 | 54 | export interface PopularSeriesList { 55 | list: PopularSeries[] 56 | } 57 | 58 | export interface PopularSeries { 59 | name: string 60 | number: number 61 | status: number 62 | subject: string 63 | } 64 | 65 | export interface PopularSeriesOneData { 66 | config: Config 67 | list: PopularSeriesOne[] 68 | reminder: string 69 | } 70 | 71 | export interface Config { 72 | color: number 73 | cover: string 74 | etime: number 75 | hint: string 76 | id: number 77 | label: string 78 | media_id: number 79 | name: string 80 | number: number 81 | share_subtitle: string 82 | share_title: string 83 | status: number 84 | stime: number 85 | subject: string 86 | type: string 87 | } 88 | 89 | export interface PopularSeriesOne extends VideoDetails { 90 | ai_rcmd: null 91 | aid: number 92 | bvid: string 93 | cid: number 94 | copyright: number 95 | cover43: string 96 | ctime: number 97 | desc: string 98 | dimension: Dimension 99 | duration: number 100 | dynamic: string 101 | enable_vt: number 102 | first_frame?: string 103 | is_ogv: boolean 104 | mission_id: number 105 | ogv_info: null 106 | owner: Owner 107 | pic: string 108 | pub_location?: string 109 | pubdate: number 110 | rcmd_reason: string 111 | rights: Rights 112 | season_id: number 113 | season_type: number 114 | short_link_v2: string 115 | stat: Stat 116 | state: number 117 | tid: number 118 | title: string 119 | tname: string 120 | up_from_v2?: number 121 | videos: number 122 | } 123 | 124 | export interface PopularPreciousData { 125 | explain: string 126 | list: PopularPrecious[] 127 | media_id: number 128 | title: string 129 | } 130 | 131 | export interface PopularPrecious extends VideoDetails { 132 | achievement: string 133 | ai_rcmd: null 134 | aid: number 135 | bvid: string 136 | cid: number 137 | copyright: number 138 | cover43: string 139 | ctime: number 140 | desc: string 141 | dimension: Dimension 142 | duration: number 143 | dynamic: string 144 | enable_vt: number 145 | first_frame: string 146 | is_ogv: boolean 147 | mission_id: number 148 | ogv_info: null 149 | order_id?: number 150 | owner: Owner 151 | pic: string 152 | pub_location: string 153 | pubdate: number 154 | rcmd_reason: string 155 | rights: Rights 156 | season_id: number 157 | season_type: number 158 | short_link_v2: string 159 | stat: Stat 160 | state: number 161 | tid: number 162 | title: string 163 | tname: string 164 | up_from_v2: number 165 | videos: number 166 | } 167 | 168 | export interface RankingData { 169 | list: Ranking[] 170 | note: string 171 | } 172 | 173 | export interface Ranking extends VideoDetails { 174 | aid: number 175 | bvid: string 176 | cid: number 177 | copyright: number 178 | ctime: number 179 | desc: string 180 | dimension: Dimension 181 | duration: number 182 | dynamic: string 183 | first_frame: string 184 | mission_id: number 185 | owner: Owner 186 | pic: string 187 | pub_location: string 188 | pubdate: number 189 | rights: Rights 190 | score: number 191 | season_id: number 192 | short_link: string 193 | short_link_v2: string 194 | stat: Stat 195 | state: number 196 | tid: number 197 | title: string 198 | tname: string 199 | up_from_v2: number 200 | videos: number 201 | } 202 | -------------------------------------------------------------------------------- /src/components/search/SearchVideo.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 154 | --------------------------------------------------------------------------------