├── .cursor └── rules │ ├── command-rules.mdc │ ├── document-rules.mdc │ ├── i18n-rules.mdc │ ├── settings-rules.mdc │ └── styles-rules.mdc ├── .github ├── ISSUE_TEMPLATE │ └── bug_issue_template.yaml └── workflows │ └── ci.yml ├── .gitignore ├── .markdownlint.yaml ├── .npmrc ├── .release-it.json ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── assets ├── anime-timetable-icons.png ├── broken-image.png ├── empty.png ├── icon-512-flat.png ├── icon-512.png ├── loading.gif ├── logo-cat.svg └── rules.json ├── docs ├── CONTRIBUTING-cmn_CN.md ├── CONTRIBUTING-cmn_TW.md └── CONTRIBUTING.md ├── eslint.config.mjs ├── knip.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── client.ts ├── manifest.ts ├── prepare.ts └── utils.ts ├── shim.d.ts ├── src ├── _locales │ ├── cmn-CN.yml │ ├── cmn-TW.yml │ ├── en.yml │ └── jyut.yml ├── auto-imports.d.ts ├── background │ ├── index.ts │ ├── messageListeners │ │ ├── api │ │ │ ├── anime.ts │ │ │ ├── auth.ts │ │ │ ├── favorite.ts │ │ │ ├── history.ts │ │ │ ├── index.ts │ │ │ ├── live.ts │ │ │ ├── moment.ts │ │ │ ├── notification.ts │ │ │ ├── ranking.ts │ │ │ ├── search.ts │ │ │ ├── user.ts │ │ │ ├── video.ts │ │ │ └── watchLater.ts │ │ └── tabs.ts │ └── utils.ts ├── components │ ├── ALink.vue │ ├── AppBackground.vue │ ├── BangumiCard │ │ ├── BangumiCard.vue │ │ └── BangumiCardSkeleton.vue │ ├── Button.vue │ ├── CodeEditor.vue │ ├── Dialog.vue │ ├── Dock │ │ ├── Dock.vue │ │ └── types.ts │ ├── Empty.vue │ ├── HorizontalScrollView.vue │ ├── IframeDrawer.vue │ ├── IframePage.vue │ ├── Input.vue │ ├── List │ │ ├── List.vue │ │ └── ListItem.vue │ ├── Loading.vue │ ├── Logo.vue │ ├── OverlayScrollbarsComponent.ts │ ├── Picture.vue │ ├── PipWindow.vue │ ├── Progress.vue │ ├── README.md │ ├── Radio.vue │ ├── SearchBar │ │ ├── SearchBar.vue │ │ └── searchHistoryProvider.ts │ ├── Select.vue │ ├── Settings │ │ ├── About │ │ │ └── About.vue │ │ ├── Appearance │ │ │ └── Appearance.vue │ │ ├── BIlibiliSettings │ │ │ └── BilibiliSettings.vue │ │ ├── BewlyPages │ │ │ ├── BewlyPages.vue │ │ │ ├── Home │ │ │ │ ├── Home.vue │ │ │ │ └── components │ │ │ │ │ ├── FilterByTitleTable.vue │ │ │ │ │ └── FilterByUserTable.vue │ │ │ └── SearchPage │ │ │ │ └── SearchPage.vue │ │ ├── Compatibility │ │ │ └── Compatibility.vue │ │ ├── DesktopAndDock │ │ │ └── DesktopAndDock.vue │ │ ├── General │ │ │ └── General.vue │ │ ├── Settings.vue │ │ ├── Shortcuts │ │ │ └── Shortcuts.vue │ │ ├── VolumeBalance │ │ │ └── VolumeBalance.vue │ │ ├── components │ │ │ ├── ChangeWallpaper.vue │ │ │ ├── SettingsItem.vue │ │ │ └── SettingsItemGroup.vue │ │ └── types.ts │ ├── SideBar │ │ ├── SideBar.vue │ │ └── types.ts │ ├── Slider.vue │ ├── Tooltip.vue │ ├── TopBar │ │ ├── BewlyOrBiliTopBarSwitcher.vue │ │ ├── TopBar.vue │ │ ├── components │ │ │ ├── BewlyOrBiliPageSwitcher.vue │ │ │ ├── NotificationsDrawer.vue │ │ │ ├── TopBarHeader.vue │ │ │ ├── TopBarLogo.vue │ │ │ ├── TopBarRight.vue │ │ │ ├── TopBarSearch.vue │ │ │ └── pops │ │ │ │ ├── ChannelsPop.vue │ │ │ │ ├── FavoritesPop.vue │ │ │ │ ├── HistoryPop.vue │ │ │ │ ├── MomentsPop.vue │ │ │ │ ├── MorePop.vue │ │ │ │ ├── NotificationsPop.vue │ │ │ │ ├── UploadPop.vue │ │ │ │ ├── UserPanelPop.vue │ │ │ │ └── WatchLaterPop.vue │ │ ├── composables │ │ │ └── useTopBarInteraction.ts │ │ ├── constants │ │ │ └── urls.ts │ │ ├── notify.ts │ │ ├── styles │ │ │ └── index.scss │ │ └── types.ts │ ├── VideoCard │ │ ├── VideoCard.vue │ │ ├── VideoCardAuthor │ │ │ └── components │ │ │ │ ├── VideoCardAuthorAvatar.vue │ │ │ │ └── VideoCardAuthorName.vue │ │ ├── VideoCardContextMenu │ │ │ ├── VideoCardContextMenu.vue │ │ │ └── components │ │ │ │ └── DislikeDialog.vue │ │ ├── VideoCardSkeleton.vue │ │ ├── types.ts │ │ └── utils.ts │ └── index.ts ├── composables │ ├── useAppProvider.ts │ ├── useDark.ts │ ├── useDelayedHover.ts │ ├── useFilter.ts │ └── useStorageLocal.ts ├── constants │ ├── globalEvents.ts │ └── imgs.ts ├── contentScripts │ ├── index.ts │ └── views │ │ ├── Anime │ │ ├── Anime.vue │ │ └── components │ │ │ └── AnimeTimeTable.vue │ │ ├── App.vue │ │ ├── Favorites │ │ └── Favorites.vue │ │ ├── History │ │ └── History.vue │ │ ├── Home │ │ ├── Home.vue │ │ ├── components │ │ │ ├── Following.vue │ │ │ ├── ForYou.vue │ │ │ ├── Live.vue │ │ │ ├── Ranking.vue │ │ │ ├── SubscribedSeries.vue │ │ │ └── Trending.vue │ │ └── types.ts │ │ ├── Moments │ │ └── Moments.vue │ │ ├── Search │ │ └── Search.vue │ │ ├── WatchLater │ │ └── WatchLater.vue │ │ └── necessarySettingsWatchers.ts ├── enums │ └── appEnums.ts ├── global.d.ts ├── inject │ ├── README.md │ └── index.ts ├── logic │ ├── common-setup.ts │ ├── index.ts │ └── storage.ts ├── manifest.ts ├── models │ ├── anime │ │ ├── popular.ts │ │ ├── recommendation.ts │ │ ├── timeTable.ts │ │ └── watchList.ts │ ├── history │ │ └── history.ts │ ├── live │ │ └── getFollowingLiveList.ts │ ├── moment │ │ └── moment.ts │ └── video │ │ ├── appForYou.ts │ │ ├── favorite.ts │ │ ├── favoriteCategory.ts │ │ ├── forYou.ts │ │ ├── historySearch.ts │ │ ├── ranking.ts │ │ ├── rankingPgc.ts │ │ ├── trending.ts │ │ ├── videoInfo.ts │ │ ├── videoPreview.ts │ │ └── watchLater.ts ├── options │ ├── Options.vue │ ├── index.html │ └── main.ts ├── popup │ ├── Popup.vue │ ├── index.html │ └── main.ts ├── stores │ ├── mainStore.ts │ ├── settingsStore.ts │ └── topBarStore.ts ├── styles │ ├── adaptedStyles │ │ ├── adaptedStyles-cmn_CN.md │ │ ├── adaptedStyles-cmn_TW.md │ │ ├── adaptedStyles-jyut.md │ │ ├── adaptedStyles.md │ │ ├── common │ │ │ ├── btn.scss │ │ │ ├── comments.scss │ │ │ ├── common.scss │ │ │ ├── footer.scss │ │ │ ├── index.ts │ │ │ ├── loginDialog.scss │ │ │ ├── modal.scss │ │ │ ├── topBar.scss │ │ │ ├── userCard.scss │ │ │ └── videoPlayer.scss │ │ ├── forceDark.scss │ │ ├── index.ts │ │ ├── pages │ │ │ ├── accountSettingsPage.scss │ │ │ ├── animePage.scss │ │ │ ├── animePlayback&MoviePage.scss │ │ │ ├── articlesPage.scss │ │ │ ├── channelPage.scss │ │ │ ├── creativeCenterPage.scss │ │ │ ├── error404Page.scss │ │ │ ├── historyPage.scss │ │ │ ├── homePage.scss │ │ │ ├── loginPage.scss │ │ │ ├── momentsPage.scss │ │ │ ├── notePage.scss │ │ │ ├── notificationsPage.scss │ │ │ ├── premiumPage.scss │ │ │ ├── searchPage.scss │ │ │ ├── topicPage.scss │ │ │ ├── userSpacePage.scss │ │ │ ├── videoPage.scss │ │ │ └── watchLaterPage.scss │ │ ├── shadowDom │ │ │ ├── comments.scss │ │ │ ├── index.ts │ │ │ └── userProfile.scss │ │ └── thirdParties │ │ │ ├── bilibiliEnhanceVideoList.scss │ │ │ ├── bilibiliEvolved.scss │ │ │ └── index.ts │ ├── blockAds.scss │ ├── fonts.scss │ ├── index.ts │ ├── main.scss │ ├── removeTopBar.scss │ ├── reset.css │ ├── transitionAndTransitionGroup.scss │ └── variables.scss ├── tests │ ├── demo.spec.ts │ └── uriParse.spec.ts └── utils │ ├── api.ts │ ├── appSign.ts │ ├── authProvider.ts │ ├── dataFormatter.ts │ ├── element.ts │ ├── i18n.ts │ ├── lazyLoad.ts │ ├── localWallpaper.ts │ ├── lvIcons.ts │ ├── main.ts │ ├── mitt.ts │ ├── player.ts │ ├── shortcuts.ts │ ├── svgIcons.ts │ ├── tabs.ts │ ├── timer.ts │ ├── transformer.ts │ ├── uriParse.ts │ ├── volumeBalance.ts │ └── volumeSliders.ts ├── tsconfig.json ├── tsup.config.ts ├── unocss.config.ts ├── vite.config.content.ts ├── vite.config.inject.ts └── vite.config.ts /.cursor/rules/command-rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 1. The project uses pnpm as the package manager. 7 | 2. Do not use the command line for validation or testing after making changes. -------------------------------------------------------------------------------- /.cursor/rules/document-rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 1. Do not create summary/test documents after each modification. 7 | 2. Do not modify markdown (.md) files in the project unless explicitly requested. 8 | 3. Do not create markdown (.md) files unless explicitly requested. -------------------------------------------------------------------------------- /.cursor/rules/i18n-rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | 1. The plugin currently uses i18n, and the correct import method is import { t } from '~/utils/dataFormatter'. 7 | 2. Only use the content of src/_locales/cmn-CN.yml as a reference. 8 | 3. Do not modify any i18n configuration files; it’s acceptable to output the translations that need to be added to cmn-CN.yml. -------------------------------------------------------------------------------- /.cursor/rules/settings-rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: The following are the requirements related to the Settings section of the browser extension. 3 | globs: 4 | alwaysApply: false 5 | --- 6 | 1. The definitions and initial values of the settings are in src/logic/storage.ts. 7 | 2. The settings components are located under src/components/Settings. 8 | 3. Related configuration items should be grouped within a SettingsItemGroup. -------------------------------------------------------------------------------- /.cursor/rules/styles-rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for defining component styles using CSS and SCSS in the project. 3 | globs: 4 | alwaysApply: false 5 | --- 6 | The project has already defined BEM-related styles. 7 | 1. The definition file is src/styles/variables.scss. 8 | 2. Theme styles should be used as a priority to ensure visual consistency. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_issue_template.yaml: -------------------------------------------------------------------------------- 1 | name: 问题报告 2 | description: 遇到错误请在此报告。 3 | labels: [bug] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 标题不填或直接随便写类似“bug”“错误”“有问题”简单带过的 issue 直接 close + lock 不解释,如果是旧版本的问题或是已经有人提问过的问题将会关闭。 10 | 11 | 功能请求不是在问题报告里面写的,请[开启空 issue](https://github.com/keleus/BewlyCat/issues/new)。 12 | 13 | 若遇到页面相关问题(比如某页面下出现了不该出现的元素),我们建议一并附上发生问题的页面链接。 14 | 15 | - type: textarea 16 | attributes: 17 | label: 环境信息 18 | description: 【请勿修改 issue 模版。】扩展版本、浏览器版本、以及你做出的自定义设置。 19 | placeholder: | 20 | - 浏览器(如 Google Chrome): 21 | - 浏览器版本(如 126.0.6478.126): 22 | - BewlyCat 版本(如 1.0.3): 23 | 24 | 如果你修改了 BewlyCat 的设置,请写在下面以方便我們排查問題(可粗略写成类似“设置了××后出现这个问题”〔将“××”替换为你的设置项〕): 25 | 26 | value: | 27 | - 浏览器(如 Google Chrome): 28 | - 浏览器版本(如 126.0.6478.126): 29 | - BewlyCat 版本(如 0.20.1): 30 | 31 | 如果你修改了 BewlyCat 的设置,请写在下面以方便我們排查問題(可粗略写成类似“设置了××后出现这个问题”〔将“××”替换为你的设置项〕): 32 | 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | attributes: 38 | label: 问题描述 39 | description: 如何重现,最好带有截图或视频以便排查。 40 | placeholder: | 41 | 请预先搜索此问题是否在其他 issue 中出现过 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | attributes: 47 | label: 预期行为 48 | description: 你认为应该是什么行为。 49 | validations: 50 | required: false 51 | 52 | - type: checkboxes 53 | attributes: 54 | label: 最终确认 55 | description: 请确认以下所有内容,否则将被 close。 56 | options: 57 | - label: 我确认在停用 BewlyCat 并强制刷新(按住 Shift 键的同时按刷新键)后,问题不再出现。 58 | required: false 59 | - label: 我确认此问题未在其他 issue 中出现过。 60 | required: false 61 | - label: 我确认我正在使用最新的 BewlyCat 版本。 62 | required: false 63 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | - main 8 | paths-ignore: 9 | - LICENSE 10 | - README-cmn_CN.md 11 | - README-cmn_TW.md 12 | - docs/** 13 | 14 | pull_request: 15 | branches: 16 | - dev 17 | - main 18 | paths-ignore: 19 | - LICENSE 20 | - README-cmn_CN.md 21 | - README-cmn_TW.md 22 | - docs/** 23 | 24 | jobs: 25 | test: 26 | name: Test 27 | strategy: 28 | matrix: 29 | node: [lts/*, lts/-1] 30 | os: [ubuntu-latest, windows-latest] 31 | fail-fast: false 32 | runs-on: ${{ matrix.os }} 33 | timeout-minutes: 10 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - name: Set node ${{ matrix.node }} 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: ${{ matrix.node }} 42 | 43 | - name: Install pnpm 44 | uses: pnpm/action-setup@v4 45 | with: 46 | run_install: | 47 | - args: [--frozen-lockfile] 48 | 49 | - name: Lint 50 | if: ${{ matrix.os == 'ubuntu-latest' }} 51 | run: pnpm run lint 52 | 53 | - name: Type check 54 | if: ${{ matrix.os == 'ubuntu-latest' }} 55 | run: pnpm run typecheck 56 | 57 | - name: Knip 58 | if: ${{ matrix.os == 'ubuntu-latest' }} 59 | run: pnpm run knip 60 | 61 | - name: Build Extension 62 | run: pnpm build 63 | 64 | - name: Upload Zip 65 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 'lts/*' && github.ref_name == 'main' }} 66 | uses: actions/upload-artifact@v4.3.1 67 | with: 68 | name: BewlyCat Zip 69 | path: extension 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .vite-ssg-dist 4 | .vite-ssg-temp 5 | *.crx 6 | *.local 7 | *.log 8 | *.pem 9 | *.xpi 10 | *.zip 11 | dist 12 | dist-ssr 13 | extension/ 14 | extension-firefox/ 15 | extension-safari/ 16 | node_modules 17 | web-ext-profile 18 | extension-safari-macos/ 19 | # src/old-extension/ -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | MD013: false 2 | 3 | MD028: false 4 | 5 | MD029: false 6 | 7 | MD033: 8 | allowed_elements: [a, br, img, p, h1, details, summary] 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | auto-install-peers=true 3 | strict-peer-dependencies=false 4 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "release-it-pnpm": { 4 | "publishCommand": "echo 'skipping publish'" 5 | } 6 | }, 7 | "git": { 8 | "commitMessage": "chore: release v${version}", 9 | "tagName": "v${version}" 10 | }, 11 | "hooks": { 12 | "before:init": [ 13 | "pnpm run lint", 14 | "pnpm run typecheck", 15 | "pnpm run test --run" 16 | ], 17 | "after:bump": [ 18 | "pnpm run build", 19 | "pnpm run build-firefox", 20 | "pnpm run pack:zip", 21 | "pnpm run pack:zip-firefox", 22 | "pnpm run pack:zip-firefox-sources" 23 | ], 24 | "after:release": [ 25 | "gh release upload v${version} extension.zip extension-firefox.zip", 26 | "pnpm run submit" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vue.volar", 4 | "antfu.iconify", 5 | "dbaeumer.vscode-eslint", 6 | "antfu.unocss", 7 | "csstools.postcss", 8 | "lokalise.i18n-ally", 9 | "streetsidesoftware.code-spell-checker", 10 | "dbaeumer.vscode-eslint" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable the ESlint flat config support 3 | "eslint.useFlatConfig": true, 4 | "typescript.enablePromptUseWorkspaceTsdk": true, 5 | // Disable the default formatter, use eslint instead 6 | "prettier.enable": false, 7 | "editor.formatOnSave": false, 8 | 9 | // Auto fix 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": "explicit", 12 | "source.organizeImports": "never" 13 | }, 14 | 15 | // Silent the stylistic rules in you IDE, but still auto fix them 16 | "eslint.rules.customizations": [ 17 | { "rule": "style/*", "severity": "off" }, 18 | { "rule": "format/*", "severity": "off" }, 19 | { "rule": "*-indent", "severity": "off" }, 20 | { "rule": "*-spacing", "severity": "off" }, 21 | { "rule": "*-spaces", "severity": "off" }, 22 | { "rule": "*-order", "severity": "off" }, 23 | { "rule": "*-dangle", "severity": "off" }, 24 | { "rule": "*-newline", "severity": "off" }, 25 | { "rule": "*quotes", "severity": "off" }, 26 | { "rule": "*semi", "severity": "off" } 27 | ], 28 | 29 | // Enable eslint for all supported languages 30 | "eslint.validate": [ 31 | "javascript", 32 | "javascriptreact", 33 | "typescript", 34 | "typescriptreact", 35 | "vue", 36 | "html", 37 | "markdown", 38 | "json", 39 | "jsonc", 40 | "yaml", 41 | "toml", 42 | "xml", 43 | "gql", 44 | "graphql", 45 | "astro", 46 | "css", 47 | "less", 48 | "scss", 49 | "pcss", 50 | "postcss" 51 | ], 52 | 53 | "cSpell.words": [ 54 | "bangumi", 55 | "bewly", 56 | "bili", 57 | "bilibili", 58 | "bvid", 59 | "Danmaku", 60 | "danmakus", 61 | "iconfont", 62 | "lightoff", 63 | "notrigger", 64 | "owari", 65 | "pagefullscreen", 66 | "pinia", 67 | "prismjs", 68 | "pubdate", 69 | "squirtle", 70 | "topbar", 71 | "unocss", 72 | "Vitesse", 73 | "vitesse-webext", 74 | "vueuse", 75 | "WATCHLATER", 76 | "webext", 77 | "webextension" 78 | ], 79 | "typescript.tsdk": "node_modules/typescript/lib", 80 | "vite.autoStart": false, 81 | "files.associations": { 82 | "*.css": "postcss" 83 | }, 84 | "i18n-ally.localesPaths": [ 85 | "src/_locales" 86 | ], 87 | "i18n-ally.keystyle": "nested" 88 | } 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hakadao(hakadao2000@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/anime-timetable-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keleus/BewlyCat/dc5f436803f9220d04270d3694ab8c0023b706f1/assets/anime-timetable-icons.png -------------------------------------------------------------------------------- /assets/broken-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keleus/BewlyCat/dc5f436803f9220d04270d3694ab8c0023b706f1/assets/broken-image.png -------------------------------------------------------------------------------- /assets/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keleus/BewlyCat/dc5f436803f9220d04270d3694ab8c0023b706f1/assets/empty.png -------------------------------------------------------------------------------- /assets/icon-512-flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keleus/BewlyCat/dc5f436803f9220d04270d3694ab8c0023b706f1/assets/icon-512-flat.png -------------------------------------------------------------------------------- /assets/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keleus/BewlyCat/dc5f436803f9220d04270d3694ab8c0023b706f1/assets/icon-512.png -------------------------------------------------------------------------------- /assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keleus/BewlyCat/dc5f436803f9220d04270d3694ab8c0023b706f1/assets/loading.gif -------------------------------------------------------------------------------- /assets/logo-cat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /assets/rules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "priority": 1, 5 | "action": { 6 | "type": "modifyHeaders", 7 | "requestHeaders": [ 8 | { 9 | "header": "origin", 10 | "operation": "set", 11 | "value": "https://www.bilibili.com" 12 | }, 13 | { 14 | "header": "referer", 15 | "operation": "set", 16 | "value": "https://www.bilibili.com" 17 | } 18 | ] 19 | }, 20 | "condition": { 21 | "domainType": "thirdParty", 22 | "urlFilter": "||api.bilibili.com", 23 | "resourceTypes": ["xmlhttprequest"], 24 | "requestMethods": ["post"] 25 | } 26 | }, 27 | { 28 | "id": 2, 29 | "priority": 1, 30 | "action": { 31 | "type": "modifyHeaders", 32 | "requestHeaders": [ 33 | { 34 | "header": "origin", 35 | "operation": "set", 36 | "value": "https://www.bilibili.com" 37 | }, 38 | { 39 | "header": "referer", 40 | "operation": "set", 41 | "value": "https://www.bilibili.com" 42 | } 43 | ] 44 | }, 45 | "condition": { 46 | "domainType": "thirdParty", 47 | "urlFilter": "||passport.bilibili.com", 48 | "resourceTypes": ["xmlhttprequest"], 49 | "requestMethods": ["post"] 50 | } 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | import simpleImportSort from 'eslint-plugin-simple-import-sort' 3 | 4 | export default antfu( 5 | { 6 | formatters: { 7 | css: 'prettier', 8 | prettierOptions: { 9 | printWidth: 120, 10 | singleQuote: false, 11 | }, 12 | }, 13 | rules: { 14 | 'vue/max-attributes-per-line': [ 15 | 'error', 16 | { 17 | singleline: { 18 | max: 5, 19 | }, 20 | multiline: { 21 | max: 5, 22 | }, 23 | }, 24 | ], 25 | 'no-alert': 'off', 26 | 'style/quote-props': 'off', 27 | }, 28 | eslint: { 29 | ignorePatterns: [ 30 | 'dist', 31 | 'node_modules', 32 | 'public', 33 | 'extension', 34 | 'extension-firefox', 35 | ], 36 | }, 37 | }, 38 | { 39 | plugins: { 40 | 'simple-import-sort': simpleImportSort, 41 | }, 42 | rules: { 43 | 'import/order': 'off', 44 | 'sort-imports': 'off', 45 | 'simple-import-sort/imports': 'error', 46 | 'simple-import-sort/exports': 'error', 47 | 'no-console': ['error', { allow: ['debug', 'warn', 'error', 'log'] }], 48 | }, 49 | }, 50 | ) 51 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/knip@5/schema.json", 3 | "entry": [ 4 | "src/contentScripts/index.ts", 5 | "src/background/index.ts", 6 | "src/options/main.ts", 7 | "src/popup/main.ts", 8 | "src/components/**", 9 | "scripts/*.ts", 10 | "*.config.*" 11 | ], 12 | "ignore": ["src/components/Settings/**", "src/inject/**"], 13 | "ignoreDependencies": [ 14 | "@iconify/json", 15 | "uno.css", 16 | "lint-staged" 17 | ], 18 | "ignoreBinaries": ["gh", "xcrun"], 19 | "rules": { 20 | "types": "off", 21 | "enumMembers": "off", 22 | "exports": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - dtrace-provider 3 | - esbuild 4 | - simple-git-hooks 5 | - spawn-sync 6 | - vue-demi 7 | -------------------------------------------------------------------------------- /scripts/manifest.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | 3 | import { getManifest } from '../src/manifest' 4 | import { isFirefox, isSafari, log, r } from './utils' 5 | 6 | export async function writeManifest() { 7 | await fs.writeJSON(r( 8 | isFirefox 9 | ? 'extension-firefox/manifest.json' 10 | : isSafari ? 'extension-safari/manifest.json' : 'extension/manifest.json', 11 | ), await getManifest(), { spaces: 2 }) 12 | log('PRE', 'write manifest.json') 13 | } 14 | 15 | writeManifest() 16 | -------------------------------------------------------------------------------- /scripts/prepare.ts: -------------------------------------------------------------------------------- 1 | // generate stub index.html files for dev entry 2 | import { execSync } from 'node:child_process' 3 | 4 | import chokidar from 'chokidar' 5 | import fs from 'fs-extra' 6 | 7 | import { isDev, isFirefox, isSafari, log, r } from './utils' 8 | 9 | /** 10 | * Stub index.html to use Vite in development 11 | */ 12 | async function stubIndexHtml() { 13 | const views = [ 14 | 'options', 15 | 'popup', 16 | ] 17 | 18 | for (const view of views) { 19 | await fs.ensureDir(r( 20 | isFirefox 21 | ? `extension-firefox/dist/${view}` 22 | : isSafari ? `extension-safari/dist/${view}` : `extension/dist/${view}`, 23 | )) 24 | let data = await fs.readFile(r(`src/${view}/index.html`), 'utf-8') 25 | data = data 26 | .replace('"./main.ts"', `"/${view}/main.ts.js"`) 27 | .replace('
', '
Vite server did not start
') 28 | await fs.writeFile(r( 29 | isFirefox 30 | ? `extension-firefox/dist/${view}/index.html` 31 | : isSafari ? `extension-safari/dist/${view}/index.html` : `extension/dist/${view}/index.html`, 32 | ), data, 'utf-8') 33 | log('PRE', `stub ${view}`) 34 | } 35 | } 36 | 37 | function writeManifest() { 38 | execSync('npx esno ./scripts/manifest.ts', { stdio: 'inherit' }) 39 | } 40 | 41 | fs.ensureDirSync(r(isFirefox ? 'extension-firefox' : isSafari ? 'extension-safari' : 'extension')) 42 | fs.copySync(r('assets'), r(isFirefox ? 'extension-firefox/assets' : isSafari ? 'extension-safari/assets' : 'extension/assets')) 43 | writeManifest() 44 | 45 | if (isDev) { 46 | stubIndexHtml() 47 | chokidar.watch(r('src/**/*.html')) 48 | .on('change', () => { 49 | stubIndexHtml() 50 | }) 51 | chokidar.watch([r('src/manifest.ts'), r('package.json')]) 52 | .on('change', () => { 53 | writeManifest() 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'node:path' 2 | import process from 'node:process' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | import { bgCyan, black } from 'kolorist' 6 | 7 | export const port = Number.parseInt(process.env.PORT || '') || 3303 8 | export const r = (...args: string[]) => resolve(dirname(fileURLToPath(import.meta.url)), '..', ...args) 9 | export const isDev = process.env.NODE_ENV !== 'production' 10 | export const isWin = process.platform === 'win32' 11 | export const isFirefox = process.env.FIREFOX === 'true' 12 | export const isSafari = process.env.SAFARI === 'true' 13 | 14 | export function log(name: string, message: string) { 15 | console.log(black(bgCyan(` ${name} `)), message) 16 | } 17 | -------------------------------------------------------------------------------- /shim.d.ts: -------------------------------------------------------------------------------- 1 | import type { AttributifyAttributes } from '@unocss/preset-attributify' 2 | import type { ProtocolWithReturn } from 'webext-bridge' 3 | 4 | declare module 'webext-bridge' { 5 | export interface ProtocolMap { 6 | // define message protocol types 7 | // see https://github.com/antfu/webext-bridge#type-safe-protocols 8 | 'tab-prev': { title: string | undefined } 9 | 'get-current-tab': ProtocolWithReturn<{ tabId: number }, { title?: string }> 10 | } 11 | } 12 | 13 | declare module '@vue/runtime-dom' { 14 | interface HTMLAttributes extends AttributifyAttributes {} 15 | } 16 | -------------------------------------------------------------------------------- /src/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | const EffectScope: typeof import('vue')['EffectScope'] 9 | const browser: typeof import('webextension-polyfill') 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 onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 29 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 30 | const onDeactivated: typeof import('vue')['onDeactivated'] 31 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 32 | const onMounted: typeof import('vue')['onMounted'] 33 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 34 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 35 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 36 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 37 | const onUnmounted: typeof import('vue')['onUnmounted'] 38 | const onUpdated: typeof import('vue')['onUpdated'] 39 | const provide: typeof import('vue')['provide'] 40 | const reactive: typeof import('vue')['reactive'] 41 | const readonly: typeof import('vue')['readonly'] 42 | const ref: typeof import('vue')['ref'] 43 | const resolveComponent: typeof import('vue')['resolveComponent'] 44 | const shallowReactive: typeof import('vue')['shallowReactive'] 45 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 46 | const shallowRef: typeof import('vue')['shallowRef'] 47 | const toRaw: typeof import('vue')['toRaw'] 48 | const toRef: typeof import('vue')['toRef'] 49 | const toRefs: typeof import('vue')['toRefs'] 50 | const toValue: typeof import('vue')['toValue'] 51 | const triggerRef: typeof import('vue')['triggerRef'] 52 | const unref: typeof import('vue')['unref'] 53 | const useAttrs: typeof import('vue')['useAttrs'] 54 | const useCssModule: typeof import('vue')['useCssModule'] 55 | const useCssVars: typeof import('vue')['useCssVars'] 56 | const useSlots: typeof import('vue')['useSlots'] 57 | const watch: typeof import('vue')['watch'] 58 | const watchEffect: typeof import('vue')['watchEffect'] 59 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 60 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 61 | } 62 | // for type re-export 63 | declare global { 64 | // @ts-ignore 65 | export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' 66 | import('vue') 67 | } 68 | -------------------------------------------------------------------------------- /src/background/index.ts: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill' 2 | 3 | import { setupApiMsgLstnrs } from './messageListeners/api' 4 | import { setupTabMsgLstnrs } from './messageListeners/tabs' 5 | 6 | // Initialize extension and set up message handlers 7 | browser.runtime.onInstalled.addListener(async () => { 8 | console.log('Extension installed') 9 | }) 10 | 11 | function isExtensionUri(url: string) { 12 | return new URL(url).origin === new URL(browser.runtime.getURL('')).origin 13 | } 14 | 15 | // Firefox specific header handling 16 | // eslint-disable-next-line node/prefer-global/process 17 | if (process.env.FIREFOX) { 18 | browser.webRequest.onBeforeSendHeaders.addListener( 19 | async (details: any) => { 20 | const requestHeaders: browser.WebRequest.HttpHeaders = [] 21 | if (details.documentUrl) { 22 | const url = new URL(details.documentUrl) 23 | const extensionUri = isExtensionUri(details.documentUrl) 24 | details.requestHeaders = details.requestHeaders || [] 25 | for (let i = 0; i < details.requestHeaders.length; i++) { 26 | if (details.requestHeaders[i].name.toLowerCase() === 'origin' || details.requestHeaders[i].name.toLowerCase() === 'referer') 27 | requestHeaders.push({ name: details.requestHeaders[i].name, value: extensionUri ? 'https://www.bilibili.com' : url.origin }) 28 | else 29 | requestHeaders.push(details.requestHeaders[i]) 30 | 31 | if (details.requestHeaders[i].name === 'firefox-multi-account-cookie') { 32 | requestHeaders.push({ name: 'cookie', value: details.requestHeaders[i].value }) 33 | } 34 | } 35 | 36 | return { ...details, requestHeaders } 37 | } 38 | }, 39 | { urls: [''] }, 40 | ['blocking', 'requestHeaders'], 41 | ) 42 | } 43 | 44 | // Setup all message listeners 45 | // 只设置一次消息监听器 46 | setupApiMsgLstnrs() 47 | setupTabMsgLstnrs() 48 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/anime.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_ANIME = { 5 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/36e250090800793b41b223b55eefdcbb9391b53e/user/space.md#%E6%9F%A5%E8%AF%A2%E7%94%A8%E6%88%B7%E8%BF%BD%E7%95%AA%E8%BF%BD%E5%89%A7%E6%98%8E%E7%BB%86 6 | getPopularAnimeList: { 7 | url: 'https://api.bilibili.com/pgc/web/rank/list', 8 | _fetch: { 9 | method: 'get', 10 | }, 11 | params: { 12 | season_type: 1, 13 | day: 3, 14 | }, 15 | afterHandle: AHS.J_D, 16 | }, 17 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/36e250090800793b41b223b55eefdcbb9391b53e/user/space.md#%E6%9F%A5%E8%AF%A2%E7%94%A8%E6%88%B7%E8%BF%BD%E7%95%AA%E8%BF%BD%E5%89%A7%E6%98%8E%E7%BB%86 18 | getAnimeWatchList: { 19 | url: 'https://api.bilibili.com/x/space/bangumi/follow/list', 20 | _fetch: { 21 | method: 'get', 22 | }, 23 | params: { 24 | pn: 1, 25 | ps: 15, 26 | type: 1, 27 | follow_status: 0, // 0: 全部, 1: 想看, 2: 在看, 3: 看过 28 | vmid: '', 29 | }, 30 | afterHandle: AHS.J_D, 31 | }, 32 | getRecommendAnimeList: { 33 | url: 'https://api.bilibili.com/pgc/page/web/v3/feed', 34 | _fetch: { 35 | method: 'get', 36 | }, 37 | params: { 38 | coursor: 0, 39 | name: 'anime', 40 | }, 41 | afterHandle: AHS.J_D, 42 | }, 43 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/bangumi/timeline.md#%E7%95%AA%E5%89%A7%E6%88%96%E5%BD%B1%E8%A7%86%E6%97%B6%E9%97%B4%E7%BA%BF 44 | getAnimeTimeTable: { 45 | url: 'https://api.bilibili.com/pgc/web/timeline', 46 | _fetch: { 47 | method: 'get', 48 | }, 49 | params: { 50 | types: 1, 51 | before: 6, 52 | after: 6, 53 | }, 54 | afterHandle: AHS.J_D, 55 | }, 56 | getAnimeDetail: { 57 | url: 'https://api.bilibili.com/pgc/view/web/season', 58 | _fetch: { 59 | method: 'get', 60 | }, 61 | params: { 62 | // ep_id: '234406', 63 | }, 64 | afterHandle: AHS.J_D, 65 | }, 66 | } satisfies APIMAP 67 | 68 | export default API_ANIME 69 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/auth.ts: -------------------------------------------------------------------------------- 1 | // 由于 sendResponse 复杂, 所以使用自定义的函数 2 | import type { APIMAP } from '../../utils' 3 | import { AHS } from '../../utils' 4 | 5 | const API_AUTH = { 6 | // biliJct 似乎没有使用 7 | logout: { 8 | url: 'https://passport.bilibili.com/login/exit/v2', 9 | _fetch: { 10 | method: 'post', 11 | headers: { 12 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 13 | }, 14 | body: { 15 | biliCSRF: '', 16 | // biliJct: '', 17 | }, 18 | }, 19 | params: { 20 | biliCSRF: '', 21 | }, 22 | afterHandle: AHS.J_S, 23 | }, 24 | getLoginQRCode: { 25 | url: 'https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code', 26 | _fetch: { 27 | method: 'post', 28 | headers: { 29 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 30 | }, 31 | }, 32 | params: { 33 | appkey: '4409e2ce8ffd12b8', 34 | local_id: '0', 35 | ts: '0', 36 | sign: 'e134154ed6add881d28fbdf68653cd9c', 37 | }, 38 | afterHandle: AHS.J_S, 39 | }, 40 | qrCodeLogin: { 41 | url: 'https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code', 42 | _fetch: { 43 | method: 'post', 44 | headers: { 45 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 46 | }, 47 | }, 48 | params: { 49 | appkey: '4409e2ce8ffd12b8', 50 | auth_code: '', 51 | local_id: '0', 52 | ts: '0', 53 | sign: 'e134154ed6add881d28fbdf68653cd9c', 54 | }, 55 | afterHandle: AHS.J_S, 56 | }, 57 | } satisfies APIMAP 58 | 59 | export default API_AUTH 60 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/favorite.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_FAVORITE = { 5 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/fav/info.md#%E8%8E%B7%E5%8F%96%E6%8C%87%E5%AE%9A%E7%94%A8%E6%88%B7%E5%88%9B%E5%BB%BA%E7%9A%84%E6%89%80%E6%9C%89%E6%94%B6%E8%97%8F%E5%A4%B9%E4%BF%A1%E6%81%AF 6 | getFavoriteCategories: { 7 | url: 'https://api.bilibili.com/x/v3/fav/folder/created/list-all', 8 | _fetch: { 9 | method: 'get', 10 | }, 11 | params: { 12 | up_mid: '', 13 | }, 14 | afterHandle: AHS.J_D, 15 | }, 16 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/fav/list.md#%E8%8E%B7%E5%8F%96%E6%94%B6%E8%97%8F%E5%A4%B9%E5%86%85%E5%AE%B9%E6%98%8E%E7%BB%86%E5%88%97%E8%A1%A8 17 | getFavoriteResources: { 18 | url: 'https://api.bilibili.com/x/v3/fav/resource/list', 19 | _fetch: { 20 | method: 'get', 21 | }, 22 | params: { 23 | media_id: -1, 24 | pn: 1, 25 | ps: 20, 26 | keyword: '', 27 | order: 'mtime', 28 | type: 0, 29 | tid: 0, 30 | platform: 'web', 31 | }, 32 | afterHandle: AHS.J_D, 33 | }, 34 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/fav/action.md#%E6%89%B9%E9%87%8F%E5%88%A0%E9%99%A4%E5%86%85%E5%AE%B9 35 | patchDelFavoriteResources: { 36 | url: 'https://api.bilibili.com/x/v3/fav/resource/batch-del', 37 | _fetch: { 38 | method: 'post', 39 | }, 40 | params: { 41 | resources: '', 42 | media_id: 0, 43 | csrf: '', 44 | }, 45 | afterHandle: AHS.J_D, 46 | }, 47 | } satisfies APIMAP 48 | 49 | export default API_FAVORITE 50 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/history.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_HISTORY = { 5 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/history.md#%E8%8E%B7%E5%8F%96%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8_web%E7%AB%AF 6 | getHistoryList: { 7 | url: 'https://api.bilibili.com/x/web-interface/history/cursor', 8 | _fetch: { 9 | method: 'get', 10 | }, 11 | params: { 12 | ps: 20, 13 | type: '', 14 | view_at: 0, 15 | }, 16 | afterHandle: AHS.J_D, 17 | }, 18 | searchHistoryList: { 19 | url: 'https://api.bilibili.com/x/web-interface/history/search', 20 | _fetch: { 21 | method: 'get', 22 | }, 23 | params: { 24 | pn: 1, 25 | keyword: '', 26 | business: 'all', 27 | }, 28 | afterHandle: AHS.J_D, 29 | }, 30 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/history.md#%E5%88%A0%E9%99%A4%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95 31 | deleteHistoryItem: { 32 | url: 'https://api.bilibili.com/x/v2/history/delete', 33 | _fetch: { 34 | method: 'post', 35 | headers: { 36 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 37 | }, 38 | body: { 39 | kid: '', 40 | csrf: '', 41 | }, 42 | }, 43 | afterHandle: AHS.J_D, 44 | }, 45 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/history.md#%E6%B8%85%E7%A9%BA%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95 46 | clearAllHistory: { 47 | url: 'https://api.bilibili.com/x/v2/history/clear', 48 | _fetch: { 49 | method: 'post', 50 | headers: { 51 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 52 | }, 53 | body: { 54 | csrf: '', 55 | }, 56 | }, 57 | afterHandle: AHS.J_D, 58 | }, 59 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/history.md#%E6%9F%A5%E8%AF%A2%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E5%81%9C%E7%94%A8%E7%8A%B6%E6%80%81 60 | getHistoryPauseStatus: { 61 | url: 'https://api.bilibili.com/x/v2/history/shadow', 62 | _fetch: { 63 | method: 'get', 64 | }, 65 | params: {}, 66 | afterHandle: AHS.J_D, 67 | }, 68 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/history.md#%E5%81%9C%E7%94%A8%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95 69 | setHistoryPauseStatus: { 70 | url: 'https://api.bilibili.com/x/v2/history/shadow/set', 71 | _fetch: { 72 | method: 'post', 73 | headers: { 74 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 75 | }, 76 | body: { 77 | switch: false, 78 | csrf: '', 79 | }, 80 | }, 81 | afterHandle: AHS.J_D, 82 | }, 83 | } satisfies APIMAP 84 | 85 | export default API_HISTORY 86 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/index.ts: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill' 2 | 3 | import { apiListenerFactory } from '../../utils' 4 | import API_ANIME from './anime' 5 | import API_AUTH from './auth' 6 | import API_FAVORITE from './favorite' 7 | import API_HISTORY from './history' 8 | import API_LIVE from './live' 9 | import API_MOMENT from './moment' 10 | import API_NOTIFICATION from './notification' 11 | import API_RANKING from './ranking' 12 | import API_SEARCH from './search' 13 | import API_USER from './user' 14 | import API_VIDEO from './video' 15 | import API_WATCHLATER from './watchLater' 16 | 17 | export const API_COLLECTION = { 18 | AUTH: API_AUTH, 19 | ANIME: API_ANIME, 20 | HISTORY: API_HISTORY, 21 | FAVORITE: API_FAVORITE, 22 | MOMENT: API_MOMENT, 23 | NOTIFICATION: API_NOTIFICATION, 24 | RANKING: API_RANKING, 25 | SEARCH: API_SEARCH, 26 | USER: API_USER, 27 | VIDEO: API_VIDEO, 28 | WATCHLATER: API_WATCHLATER, 29 | LIVE: API_LIVE, 30 | 31 | [Symbol.iterator]() { 32 | return Object.values(this).values() 33 | }, 34 | } 35 | 36 | // Merge all API objects into one 37 | const FullAPI = Object.assign({}, ...API_COLLECTION) 38 | // Create a message listener for each API 39 | const handleMessage = apiListenerFactory(FullAPI) 40 | 41 | export function setupApiMsgLstnrs() { 42 | browser.runtime.onConnect.removeListener(handleConnect) 43 | browser.runtime.onConnect.addListener(handleConnect) 44 | } 45 | 46 | function handleConnect() { 47 | browser.runtime.onMessage.removeListener(handleMessage) 48 | browser.runtime.onMessage.addListener(handleMessage) 49 | } 50 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/live.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_LIVE = { 5 | // https://socialsisteryi.github.io/bilibili-API-collect/docs/live/follow_up_live.html#%E7%94%A8%E6%88%B7%E5%85%B3%E6%B3%A8%E7%9A%84%E6%89%80%E6%9C%89up%E7%9A%84%E7%9B%B4%E6%92%AD%E6%83%85%E5%86%B5 6 | getFollowingLiveList: { 7 | url: 'https://api.live.bilibili.com/xlive/web-ucenter/user/following', 8 | _fetch: { 9 | method: 'get', 10 | }, 11 | params: { 12 | page: 1, 13 | page_size: 9, 14 | ignoreRecord: 1, 15 | hit_ab: true, 16 | }, 17 | afterHandle: AHS.J_D, 18 | }, 19 | 20 | } satisfies APIMAP 21 | 22 | export default API_LIVE 23 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/moment.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_MOMENT = { 5 | getTopBarNewMomentsCount: { 6 | url: 'https://api.bilibili.com/x/web-interface/dynamic/entrance', 7 | _fetch: { 8 | method: 'get', 9 | }, 10 | params: {}, 11 | afterHandle: AHS.J_D, 12 | }, 13 | getTopBarMoments: { 14 | url: 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/nav', 15 | _fetch: { 16 | method: 'get', 17 | }, 18 | params: { 19 | type: 'video', 20 | update_baseline: '', 21 | offset: '', 22 | }, 23 | afterHandle: AHS.J_D, 24 | }, 25 | getTopBarLiveMoments: { 26 | url: 'https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/FeedList', 27 | _fetch: { 28 | method: 'get', 29 | }, 30 | params: { 31 | page: 1, 32 | pagesize: 10, 33 | }, 34 | afterHandle: AHS.J_D, 35 | }, 36 | getMoments: { 37 | url: 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all', 38 | _fetch: { 39 | method: 'get', 40 | }, 41 | params: { 42 | type: 'all', 43 | offset: 0, 44 | update_baseline: '', 45 | }, 46 | afterHandle: AHS.J_D, 47 | }, 48 | getMomentsUpdate: { 49 | url: 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all/update', 50 | _fetch: { 51 | method: 'get', 52 | }, 53 | params: { 54 | type: 'video', 55 | offset: 0, 56 | update_baseline: '0', 57 | }, 58 | afterHandle: AHS.J_D, 59 | }, 60 | } satisfies APIMAP 61 | 62 | export default API_MOMENT 63 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/notification.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_NOTIFICATION = { 5 | getUnreadMsg: { 6 | url: 'https://api.bilibili.com/x/msgfeed/unread', 7 | _fetch: { 8 | method: 'get', 9 | }, 10 | params: { 11 | build: 0, 12 | mobi_app: 'web', 13 | }, 14 | afterHandle: AHS.J_D, 15 | }, 16 | getUnreadDm: { 17 | url: 'https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread', 18 | _fetch: { 19 | method: 'get', 20 | }, 21 | params: { 22 | build: 0, 23 | mobi_app: 'web', 24 | unread_type: 0, 25 | }, 26 | afterHandle: AHS.J_D, 27 | }, 28 | } satisfies APIMAP 29 | 30 | export default API_NOTIFICATION 31 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/ranking.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_RANKING = { 5 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/7873a79022a5606e2391d93b411a05576a0df111/docs/video_ranking/ranking.md#%E8%8E%B7%E5%8F%96%E5%88%86%E5%8C%BA%E8%A7%86%E9%A2%91%E6%8E%92%E8%A1%8C%E6%A6%9C%E5%88%97%E8%A1%A8 6 | getRankingVideos: { 7 | url: 'https://api.bilibili.com/x/web-interface/ranking/v2', 8 | _fetch: { 9 | method: 'get', 10 | }, 11 | params: { 12 | rid: 0, 13 | type: 'all', 14 | }, 15 | afterHandle: AHS.J_D, 16 | }, 17 | getRankingPgc: { 18 | url: 'https://api.bilibili.com/pgc/season/rank/web/list', 19 | _fetch: { 20 | method: 'get', 21 | }, 22 | params: { 23 | season_type: 1, 24 | day: 3, 25 | }, 26 | afterHandle: AHS.J_D, 27 | }, 28 | } satisfies APIMAP 29 | 30 | export default API_RANKING 31 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/search.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_SEARCH = { 5 | getSearchSuggestion: { 6 | url: 'https://s.search.bilibili.com/main/suggest', 7 | _fetch: { 8 | method: 'get', 9 | }, 10 | params: { 11 | term: '', 12 | highlight: '', 13 | }, 14 | afterHandle: AHS.J_D, 15 | }, 16 | } satisfies APIMAP 17 | 18 | export default API_SEARCH 19 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/user.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_USER = { 5 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/e379d904c2753fa30e9083f59016f07e89d19467/docs/login/login_info.md#%E5%AF%BC%E8%88%AA%E6%A0%8F%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF 6 | getUserInfo: { 7 | url: 'https://api.bilibili.com/x/web-interface/nav', 8 | _fetch: { 9 | method: 'get', 10 | }, 11 | afterHandle: AHS.J_D, 12 | }, 13 | getUserStat: { 14 | url: 'https://api.bilibili.com/x/web-interface/nav/stat', 15 | _fetch: { 16 | method: 'get', 17 | }, 18 | afterHandle: AHS.J_D, 19 | }, 20 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/ed9ac01b6769430aa3f12ad02c2ed337a96924eb/docs/user/relation.md#操作用户关系 21 | relationModify: { 22 | url: 'https://api.bilibili.com/x/relation/modify', 23 | _fetch: { 24 | method: 'post', 25 | }, 26 | params: { 27 | // access_key: '', // app only 28 | fid: '', 29 | act: 1, 30 | re_src: 11, 31 | }, 32 | afterHandle: AHS.J_D, 33 | }, 34 | getPrivilegeInfo: { 35 | url: 'https://api.bilibili.com/x/vip/privilege/my', 36 | _fetch: { 37 | method: 'get', 38 | }, 39 | afterHandle: AHS.J_D, 40 | }, 41 | } satisfies APIMAP 42 | 43 | export default API_USER 44 | -------------------------------------------------------------------------------- /src/background/messageListeners/api/watchLater.ts: -------------------------------------------------------------------------------- 1 | import type { APIMAP } from '../../utils' 2 | import { AHS } from '../../utils' 3 | 4 | const API_WATCHLATER = { 5 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/toview.md#%E8%A7%86%E9%A2%91%E6%B7%BB%E5%8A%A0%E7%A8%8D%E5%90%8E%E5%86%8D%E7%9C%8B 6 | saveToWatchLater: { 7 | url: 'https://api.bilibili.com/x/v2/history/toview/add', 8 | _fetch: { 9 | method: 'post', 10 | headers: { 11 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 12 | }, 13 | body: { 14 | aid: 0, 15 | csrf: '', 16 | }, 17 | }, 18 | afterHandle: AHS.J_D, 19 | }, 20 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/toview.md#%E5%88%A0%E9%99%A4%E7%A8%8D%E5%90%8E%E5%86%8D%E7%9C%8B%E8%A7%86%E9%A2%91 21 | removeFromWatchLater: { 22 | url: 'https://api.bilibili.com/x/v2/history/toview/del', 23 | _fetch: { 24 | method: 'post', 25 | headers: { 26 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 27 | }, 28 | body: { 29 | viewed: false, 30 | csrf: '', 31 | }, 32 | }, 33 | params: { 34 | aid: 0, 35 | }, 36 | afterHandle: AHS.J_D, 37 | }, 38 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/toview.md#%E8%8E%B7%E5%8F%96%E7%A8%8D%E5%90%8E%E5%86%8D%E7%9C%8B%E8%A7%86%E9%A2%91%E5%88%97%E8%A1%A8 39 | getAllWatchLaterList: { 40 | url: 'https://api.bilibili.com/x/v2/history/toview', 41 | _fetch: { 42 | method: 'get', 43 | }, 44 | afterHandle: AHS.J_D, 45 | }, 46 | // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/toview.md#%E6%B8%85%E7%A9%BA%E7%A8%8D%E5%90%8E%E5%86%8D%E7%9C%8B%E8%A7%86%E9%A2%91%E5%88%97%E8%A1%A8 47 | clearAllWatchLater: { 48 | url: 'https://api.bilibili.com/x/v2/history/toview/clear', 49 | _fetch: { 50 | method: 'post', 51 | headers: { 52 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 53 | }, 54 | body: { 55 | csrf: '', 56 | }, 57 | }, 58 | afterHandle: AHS.J_D, 59 | }, 60 | } satisfies APIMAP 61 | 62 | export default API_WATCHLATER 63 | -------------------------------------------------------------------------------- /src/background/messageListeners/tabs.ts: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill' 2 | 3 | interface Message { 4 | contentScriptQuery: string 5 | [key: string]: any 6 | } 7 | 8 | export enum TABS_MESSAGE { 9 | OPEN_LINK_IN_BACKGROUND = 'openLinkInBackground', 10 | } 11 | 12 | function handleMessage(message: Message) { 13 | if (message.contentScriptQuery === TABS_MESSAGE.OPEN_LINK_IN_BACKGROUND) { 14 | // 处理以 // 开头的 URL 15 | const url = message.url.startsWith('//') ? `https:${message.url}` : message.url 16 | return browser.tabs.create({ url, active: false }) 17 | } 18 | } 19 | 20 | export function setupTabMsgLstnrs() { 21 | browser.runtime.onMessage.removeListener(handleMessage) 22 | browser.runtime.onMessage.addListener(handleMessage) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/ALink.vue: -------------------------------------------------------------------------------- 1 | 91 | 92 | 103 | -------------------------------------------------------------------------------- /src/components/BangumiCard/BangumiCardSkeleton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 38 | -------------------------------------------------------------------------------- /src/components/CodeEditor.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 |