├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── logo.png ├── images ├── live-status-1.png ├── live-status-2.png ├── notifications-1.png ├── notifications-2.png └── notifications-3.png ├── .vscode ├── extensions.json ├── tasks.json ├── launch.json └── settings.json ├── .vscodeignore ├── CHANGELOG.md ├── .github ├── dependabot.yml └── workflows │ ├── publish.yml │ └── ci.yml ├── src ├── test │ ├── suite │ │ ├── douyinVideos.test.ts │ │ ├── extension.test.ts │ │ ├── index.ts │ │ ├── bilibiliLiveStatus.test.ts │ │ └── bilibiliNewDynamics.test.ts │ └── runTest.ts ├── notification.ts ├── douyinVideos.ts ├── bilibiliDynamics.ts ├── types.ts ├── bilibiliLiveStatus.ts └── extension.ts ├── tsconfig.json ├── LICENSE ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | out/ 2 | 3 | src/test/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@luooooob" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luooooob/vscode-asoul-notifications/HEAD/logo.png -------------------------------------------------------------------------------- /images/live-status-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luooooob/vscode-asoul-notifications/HEAD/images/live-status-1.png -------------------------------------------------------------------------------- /images/live-status-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luooooob/vscode-asoul-notifications/HEAD/images/live-status-2.png -------------------------------------------------------------------------------- /images/notifications-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luooooob/vscode-asoul-notifications/HEAD/images/notifications-1.png -------------------------------------------------------------------------------- /images/notifications-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luooooob/vscode-asoul-notifications/HEAD/images/notifications-2.png -------------------------------------------------------------------------------- /images/notifications-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luooooob/vscode-asoul-notifications/HEAD/images/notifications-3.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | 3 | out/test/** 4 | src/** 5 | 6 | node_modules 7 | 8 | .gitignore 9 | .yarnrc 10 | vsc-extension-quickstart.md 11 | **/tsconfig.json 12 | **/.eslintrc.json 13 | **/.eslintrc 14 | **/*.ts 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "asoul-notifications" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: "npm" 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: "/" 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /src/test/suite/douyinVideos.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import axios from 'axios'; 3 | import { requestVideos } from "../../douyinVideos"; 4 | import { BilibiliDynamic, BilibiliDynamicsResponse } from '../../types'; 5 | 6 | suite('DouyinVideos Functions Test Suite', () => { 7 | 8 | test('Test requestDouyinVideos', async () => { 9 | }); 10 | 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | import * as vscode from 'vscode'; 5 | import * as myExtension from '../../extension'; 6 | 7 | suite('Extension Test Suite', () => { 8 | vscode.window.showInformationMessage('Start all tests.'); 9 | 10 | // test('Test configuration', async () => { 11 | // }); 12 | }); 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/notification.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | export const createNotification = (message: string, ...commands: vscode.Command[]) => { 4 | vscode.window 5 | .showInformationMessage(message, ...commands.map(item => item.title)) 6 | .then(selection => { 7 | const command = commands.find(item => item.title === selection) 8 | if (command) { 9 | vscode.commands.executeCommand(command.command, ...command.arguments ?? []) 10 | } 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: windows-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 17.x 19 | - name: Install dependencies 20 | run: npm i 21 | - name: Run Publish 22 | if: success() 23 | run: npx vsce publish 24 | env: 25 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "esnext", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Test 14 | strategy: 15 | matrix: 16 | os: [macos-latest] 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: 17.x 25 | - name: Install dependencies 26 | run: npm i 27 | - name: Run test on linux 28 | if: runner.os == 'Linux' 29 | run: xvfb-run -a npm test 30 | - name: Run test on windows, macos 31 | if: runner.os != 'Linux' 32 | run: npm test 33 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/douyinVideos.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import * as vscode from "vscode" 3 | import type { DouyinVideo, DouyinVideosResponse } from "./types" 4 | 5 | export const requestVideos = async (did: string): Promise => { 6 | const url = `https://www.iesdouyin.com/web/api/v2/aweme/post/?sec_uid=${did}` 7 | return axios 8 | .get(url) 9 | .then(res => res.data) 10 | } 11 | 12 | export const getVideosFromResponse = (res: DouyinVideosResponse, nickname?: string): DouyinVideo[] => { 13 | return res.aweme_list.map(aweme => { 14 | const { aweme_id, author } = aweme 15 | const videoId = aweme_id 16 | const name = nickname ?? author.nickname 17 | 18 | const message = `${name} 投稿了新抖音短视频` 19 | const commands: vscode.Command[] = [{ 20 | title: "前往视频", 21 | command: "vscode.open", 22 | arguments: [vscode.Uri.parse(`https://www.douyin.com/video/${aweme_id}`)] 23 | }] 24 | return { videoId, message, commands } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "typescript.tsdk": "node_modules/typescript/lib", 12 | "editor.formatOnSave": true, 13 | "eslint.format.enable": true, 14 | "eslint.validate": [ 15 | "javascript", 16 | "javascriptreact", 17 | "typescript", 18 | "typescriptreact", 19 | "vue", 20 | "json" 21 | ], 22 | "[javascript]": { 23 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 24 | }, 25 | "[typescript]": { 26 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 27 | }, 28 | "[json]": { 29 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 30 | }, 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jiang Yan 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 | -------------------------------------------------------------------------------- /src/bilibiliDynamics.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import * as vscode from "vscode" 3 | import type { BilibiliDynamic, BilibiliDynamicsResponse, RequstOptions } from "./types" 4 | 5 | export const requestDynamics = async (bid: number): Promise => { 6 | const url = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid=${bid}` 7 | return axios 8 | .get(url) 9 | .then(res => res.data) 10 | } 11 | 12 | export const getDynamicsFromResponse = (res: BilibiliDynamicsResponse, nickname?: string): BilibiliDynamic[] => { 13 | return res.data.cards.map(card => { 14 | const { type, dynamic_id_str, user_profile } = card.desc 15 | const dynamicId = dynamic_id_str 16 | 17 | const { uname } = user_profile.info 18 | const name = nickname ?? uname 19 | const isVideo = type === 8 || type === 16 20 | const isArticle = type === 64 21 | const message = isVideo 22 | ? `${name}投稿了新视频` 23 | : isArticle 24 | ? `${name}投稿了新专栏` 25 | : `${name}有了新动态` 26 | 27 | const commands: vscode.Command[] = [{ 28 | title: "前往动态", 29 | command: "vscode.open", 30 | arguments: [vscode.Uri.parse(`https://t.bilibili.com/${dynamic_id_str}`)] 31 | }] 32 | return { dynamicId, message, commands } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/test/suite/bilibiliLiveStatus.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | // import { 3 | // requestLiveStatus, getLiveStatusFromResponse, 4 | // } from '../../bilibiliLiveStatus'; 5 | import type { BilibiliLiveStatusResponse } from "../../types"; 6 | 7 | suite('BilibiliLiveStatus Functions Test Suite', () => { 8 | 9 | // test('Test fetchBilibiliLiveStatus', async () => { 10 | // const res = await requestLiveStatus(672328094); 11 | // const { name, live_room } = res.data; 12 | // const { liveStatus, url } = live_room; 13 | 14 | // assert(name.length > 1); 15 | // assert(liveStatus === 1 || liveStatus === 0); 16 | // assert(url.startsWith("https")); 17 | // }); 18 | 19 | // test('Test getBilibiliLiveStatusFromResponse', async () => { 20 | // const res: BilibiliLiveStatusResponse = { 21 | // data: { 22 | // name: "嘉然今天吃什么", 23 | // live_room: { 24 | // liveStatus: 1, 25 | // url: "https://live.bilibili.com/22637261" 26 | // } 27 | // } 28 | // }; 29 | 30 | // const parseResult = getLiveStatusFromResponse(res); 31 | // // assert.strictEqual(parseResult.name, "嘉然今天吃什么"); 32 | // assert.strictEqual(parseResult.isLive, true); 33 | // // assert.strictEqual(parseResult.liveroomUrl, "https://live.bilibili.com/22637261"); 34 | // }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from "vscode" 2 | 3 | export type RequstOptions = { 4 | url: string 5 | } 6 | 7 | export type AsoulMember = { 8 | nickname?: string, 9 | bilibiliId?: number, 10 | douyinId?: string 11 | } 12 | 13 | export type BilibiliUser = { 14 | nickname?: string 15 | bilibiliId: number 16 | } 17 | 18 | export type BilibiliDynamicsResponse = { 19 | data: { 20 | cards: { 21 | desc: { 22 | dynamic_id_str: string 23 | timestamp: number 24 | type: number 25 | user_profile: { 26 | info: { 27 | uid: number 28 | uname: string 29 | } 30 | } 31 | } 32 | }[] 33 | } 34 | } 35 | 36 | export type BilibiliDynamic = { 37 | dynamicId: string 38 | message: string 39 | commands: Command[] 40 | } 41 | 42 | export type BilibiliLiveStatusResponse = { 43 | data: { 44 | name: string 45 | live_room: { 46 | liveStatus: 0 | 1, 47 | url: string 48 | } 49 | } 50 | } 51 | 52 | export type BilibiliLiveStatus = { 53 | name: string 54 | } & ({ 55 | isLive: false 56 | } | { 57 | isLive: true, 58 | url: string 59 | }) 60 | 61 | export type DouyinUser = { 62 | nickname?: string 63 | douyinId: string 64 | } 65 | 66 | export type DouyinVideosResponse = { 67 | aweme_list: { 68 | aweme_id: string 69 | desc: string 70 | author: { 71 | nickname: string 72 | } 73 | }[] 74 | } 75 | 76 | export type DouyinVideo = { 77 | videoId: string 78 | message: string 79 | commands: Command[] 80 | } 81 | -------------------------------------------------------------------------------- /src/bilibiliLiveStatus.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import axios from "axios" 3 | import type { 4 | BilibiliLiveStatusResponse, 5 | BilibiliLiveStatus 6 | } from "./types" 7 | 8 | // export const makeLiveStatusRequstOptions = (bid: number) => ({ 9 | // url: `https://api.bilibili.com/x/space/acc/info?mid=${bid}` 10 | // }); 11 | 12 | export const requestLiveStatus = async (bid: number): Promise => { 13 | const url = `https://api.bilibili.com/x/space/acc/info?mid=${bid}` 14 | return axios 15 | .get(url) 16 | .then(res => res.data) 17 | } 18 | 19 | export const getLiveStatusFromResponse = (res: BilibiliLiveStatusResponse, nickname: string | undefined): BilibiliLiveStatus => { 20 | const name = nickname ?? res.data.name 21 | const isLive = res.data.live_room.liveStatus === 1 22 | if (isLive) { 23 | const { url } = res.data.live_room 24 | return { name, isLive, url } 25 | } 26 | return { name, isLive } 27 | } 28 | 29 | export const displayStatusBar = (statusBarItem: vscode.StatusBarItem, liveStatus: BilibiliLiveStatus) => { 30 | if (liveStatus.isLive) { 31 | statusBarItem.text = `--${liveStatus.name}正在直播--` 32 | statusBarItem.tooltip = "前往直播间" 33 | statusBarItem.command = { 34 | title: "To Liveroom", 35 | command: "vscode.open", 36 | arguments: [vscode.Uri.parse(liveStatus.url)] 37 | } 38 | statusBarItem.show() 39 | } else { 40 | statusBarItem.hide() 41 | } 42 | } 43 | 44 | // export const getLiveroomUrl = (id: number) => { 45 | // return `https://live.bilibili.com/${id}`; 46 | // }; 47 | 48 | // export const createLiveStatusNotificationOptions = (liveStatus: BilibiliLiveStatus): NotificationOptions => { 49 | // const message = `在?你为什么不看直播?`; 50 | // const commands: vscode.Command[] = [{ 51 | // title: `${liveStatus.name}直播间`, 52 | // command: "vscode.open", 53 | // arguments: [liveStatus.liveroomUrl] 54 | // }]; 55 | // return [ message, ...commands]; 56 | // }; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A-SOUL 提醒小助手 2 | 3 | [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/luooooob/vscode-asoul-notifications?include_prereleases&label=Visual%20Studio%20Marketplace)](https://marketplace.visualstudio.com/items?itemName=JiangYan.asoul-notifications) 4 | [![Visual Studio Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/jiangyan.asoul-notifications)](https://marketplace.visualstudio.com/items?itemName=JiangYan.asoul-notifications) 5 | [![Visual Studio Marketplace Rating](https://img.shields.io/visual-studio-marketplace/r/jiangyan.asoul-notifications)](https://marketplace.visualstudio.com/items?itemName=JiangYan.asoul-notifications) 6 | 7 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/luooooob/vscode-asoul-notifications/CI)](https://github.com/luooooob/vscode-asoul-notifications/actions/workflows/ci.yml) 8 | [![GitHub top language](https://img.shields.io/github/languages/top/luooooob/vscode-asoul-notifications)](https://github.com/luooooob/vscode-asoul-notifications) 9 | [![GitHub](https://img.shields.io/github/license/luooooob/vscode-asoul-notifications)](https://github.com/luooooob/vscode-asoul-notifications/blob/master/LICENSE) 10 | 11 | > 12 | 13 | ## 功能 14 | 15 | - ### Bilibili 新动态提醒 16 | 17 | ![Bilibili 新动态提醒1](./images/notifications-1.png) 18 | 19 | ![Bilibili 新动态提醒2](./images/notifications-2.png) 20 | 21 | - ### 抖音新视频提醒 22 | 23 | ![抖音新视频提醒](./images/notifications-3.png) 24 | 25 | - ### 正在直播提醒(点击前往直播间) 26 | 27 | ![正在直播提醒1](./images/live-status-1.png) 28 | 29 | ![正在直播提醒2](./images/live-status-2.png) 30 | 31 | ## 设置 32 | 33 | - `asoulNotifications.asoulMembers` 34 | 35 | ```json 36 | [ 37 | { 38 | "nickname": "向晚", 39 | "bilibiliId": 672346917, 40 | "douyinId": "MS4wLjABAAAAxCiIYlaaKaMz_J1QaIAmHGgc3bTerIpgTzZjm0na8w5t2KTPrCz4bm_5M5EMPy92" 41 | }, 42 | { 43 | "nickname": "拉姐", 44 | "bilibiliId": 672353429, 45 | "douyinId": "MS4wLjABAAAA5ZrIrbgva_HMeHuNn64goOD2XYnk4ItSypgRHlbSh1c" 46 | }, 47 | 48 | // ... 49 | 50 | ] 51 | ``` 52 | 53 | 一个数组, 记录所有成员的 bilibili 和抖音的 id 以及自定义昵称. 可以自行添加更多项(不推荐添加太多,本插件采用轮询 Bilibili 和抖音公开接口的方式工作, 如果任务太多可能会被gank). 三个键都是可选的, 如果没有`bilibiliId` 或 `douyinId`, 则没有该平台的相关功能, 如果没有`nickname`, 会默认使用该平台的昵称. 54 | 55 | - `asoulNotifications.bilibiliLiveStatus.enabled` 56 | 57 | 打开/关闭正在直播提醒 58 | 59 | - `asoulNotifications.bilibiliDynamics.enabled` 60 | 61 | 打开/关闭新动态提醒 62 | - `asoulNotifications.douyinVideos.enabled` 63 | 64 | 打开/关闭抖音新视频提醒 65 | 66 | 注意: 所有选项的更改都需要重启 VS Code 才能生效. 67 | 68 | ## 反馈 69 | 70 | 插件正在开发中, 可能会出现不稳定的现象, 任何任何 bug 或建议可以通过 [issue](https://github.com/luooooob/vscode-asoul-notifications/issues/new) 反馈, 关于本插件的功能有更好的想法和建议也欢迎告知 71 | 72 | ## 友情链接 73 | 74 | [Windows 10/11 版提醒小助手](https://github.com/skykeyjoker/A-Soul-Notification) 75 | 76 | [VSCode插件:A-SOUL 鼓励师](https://marketplace.visualstudio.com/items?itemName=AS042971.asoul) 77 | 78 | [Typora主题:Typora-theme-jiaran](https://github.com/q19980722/Typora-theme-jiaran) 79 | -------------------------------------------------------------------------------- /src/test/suite/bilibiliNewDynamics.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | // import { 3 | // fetchDynamics, 4 | // getNewDynamicsFromResponse, 5 | // } from '../../bilibiliDynamics'; 6 | import { BilibiliDynamic, BilibiliDynamicsResponse } from '../../types'; 7 | 8 | suite('BilibiliNewDynamics Functions Test Suite', () => { 9 | 10 | // test('Test fetchBilibiliDynamics', async () => { 11 | // const res = await fetchDynamics(672328094); 12 | // const cards = res.data?.cards; 13 | // assert(cards); 14 | // assert(cards.length > 0); 15 | // assert(BigInt(cards[0].desc.dynamic_id_str) > 0); 16 | // assert(typeof cards[0].desc.timestamp === "number"); 17 | // assert(typeof cards[0].desc.user_profile.info.uid === "number"); 18 | // assert(typeof cards[0].desc.user_profile.info.uname === "string"); 19 | // }); 20 | 21 | // test('Test getNewBilibiliDynamicsFromResponse', async () => { 22 | // const res: BilibiliDynamicsResponse = { 23 | // data: { 24 | // cards: [{ 25 | // desc: { 26 | // dynamic_id_str: "569453517462599026", 27 | // timestamp: 1600052300, 28 | // type: 1, 29 | // user_profile: { 30 | // info: { 31 | // uid: 672346917, 32 | // uname: "向晚大魔王" 33 | // } 34 | // } 35 | // } 36 | // }, { 37 | // desc: { 38 | // dynamic_id_str: "569453517462599025", 39 | // timestamp: 1600042300, 40 | // type: 8, 41 | // user_profile: { 42 | // info: { 43 | // uid: 672346917, 44 | // uname: "向晚大魔王" 45 | // } 46 | // } 47 | // } 48 | // }, { 49 | // desc: { 50 | // dynamic_id_str: "569453517462599024", 51 | // timestamp: 1600032300, 52 | // type: 16, 53 | // user_profile: { 54 | // info: { 55 | // uid: 672346917, 56 | // uname: "向晚大魔王" 57 | // } 58 | // } 59 | // } 60 | // }, { 61 | // desc: { 62 | // dynamic_id_str: "569453517462599020", 63 | // timestamp: 1600022300, 64 | // type: 64, 65 | // user_profile: { 66 | // info: { 67 | // uid: 672346917, 68 | // uname: "向晚大魔王" 69 | // } 70 | // } 71 | // } 72 | // }] 73 | // } 74 | // }; 75 | 76 | // const dynamics = getNewDynamicsFromResponse(res, "向晚今天吃什么", 0n, 1600032300000); 77 | 78 | // assert.strictEqual(dynamics.length, 2); 79 | // assert.strictEqual(dynamics[0].name, "向晚今天吃什么"); 80 | // assert.strictEqual(dynamics[0].timestamp, 1600052300); 81 | // assert.strictEqual(dynamics[0].dynamicId, 569453517462599026n); 82 | // assert.strictEqual(dynamics[0].dynamicUrl, "https://t.bilibili.com/569453517462599026"); 83 | // }); 84 | }); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asoul-notifications", 3 | "displayName": "A-SOUL 提醒小助手", 4 | "version": "0.8.1", 5 | "description": "A-SOUL 成员新动态提醒, 直播状态提醒", 6 | "publisher": "JiangYan", 7 | "icon": "logo.png", 8 | "keywords": [ 9 | "asoul", 10 | "bilibili", 11 | "嘉然", 12 | "向晚", 13 | "贝拉", 14 | "乃琳", 15 | "珈乐" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/luooooob/vscode-asoul-notifications.git" 20 | }, 21 | "engines": { 22 | "vscode": "^1.60.0" 23 | }, 24 | "categories": [ 25 | "Other" 26 | ], 27 | "activationEvents": [ 28 | "onStartupFinished" 29 | ], 30 | "main": "./out/extension.js", 31 | "contributes": { 32 | "configuration": { 33 | "type": "object", 34 | "title": "A-SOUL 提醒小助手", 35 | "properties": { 36 | "asoulNotifications.asoulMembers": { 37 | "type": "array", 38 | "default": [ 39 | { 40 | "nickname": "向晚", 41 | "bilibiliId": 672346917, 42 | "douyinId": "MS4wLjABAAAAxOXMMwlShWjp4DONMwfEEfloRYiC1rXwQ64eydoZ0ORPFVGysZEd4zMt8AjsTbyt" 43 | }, 44 | { 45 | "nickname": "贝拉", 46 | "bilibiliId": 672353429, 47 | "douyinId": "MS4wLjABAAAAlpnJ0bXVDV6BNgbHUYVWnnIagRqeeZyNyXB84JXTqAS5tgGjAtw0ZZkv0KSHYyhP" 48 | }, 49 | { 50 | "nickname": "珈乐", 51 | "bilibiliId": 351609538, 52 | "douyinId": "MS4wLjABAAAAuZHC7vwqRhPzdeTb24HS7So91u9ucl9c8JjpOS2CPK-9Kg2D32Sj7-mZYvUCJCya" 53 | }, 54 | { 55 | "nickname": "嘉然", 56 | "bilibiliId": 672328094, 57 | "douyinId": "MS4wLjABAAAA5ZrIrbgva_HMeHuNn64goOD2XYnk4ItSypgRHlbSh1c" 58 | }, 59 | { 60 | "nickname": "乃琳", 61 | "bilibiliId": 672342685, 62 | "douyinId": "MS4wLjABAAAAxCiIYlaaKaMz_J1QaIAmHGgc3bTerIpgTzZjm0na8w5t2KTPrCz4bm_5M5EMPy92" 63 | }, 64 | { 65 | "nickname": "A-SOUL_Official", 66 | "bilibiliId": 703007996, 67 | "douyinId": "MS4wLjABAAAAflgvVQ5O1K4RfgUu3k0A2erAZSK7RsdiqPAvxcObn93x2vk4SKk1eUb6l_D4MX-n" 68 | } 69 | ], 70 | "description": "成员信息" 71 | }, 72 | "asoulNotifications.bilibiliLiveStatus.enabled": { 73 | "type": "boolean", 74 | "default": true, 75 | "description": "打开/关闭 Bilibili 正在直播提醒" 76 | }, 77 | "asoulNotifications.bilibiliDynamics.enabled": { 78 | "type": "boolean", 79 | "default": true, 80 | "description": "打开/关闭新动态提醒" 81 | }, 82 | "asoulNotifications.douyinVideos.enabled": { 83 | "type": "boolean", 84 | "default": false, 85 | "description": "打开/关闭抖音新视频提醒(测试中)" 86 | } 87 | } 88 | } 89 | }, 90 | "scripts": { 91 | "lint": "eslint .", 92 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", 93 | "esbuild": "npm run esbuild-base -- --sourcemap", 94 | "vscode:prepublish": "npm run esbuild-base -- --minify", 95 | "watch": "npm run esbuild-base -- --sourcemap", 96 | "test": "tsc -p ./ && node ./out/test/runTest.js" 97 | }, 98 | "dependencies": { 99 | "axios": "^0.24.0", 100 | "esbuild": "^0.14.8", 101 | "node-cron": "^3.0.0" 102 | }, 103 | "devDependencies": { 104 | "@luooooob/eslint-config": "^0.5.0", 105 | "@types/glob": "^7.2.0", 106 | "@types/mocha": "^8.2.2", 107 | "@types/node": "17.x", 108 | "@types/node-cron": "^3.0.0", 109 | "@types/vscode": "^1.60.0", 110 | "eslint": "^8.5.0", 111 | "glob": "^7.2.0", 112 | "mocha": "^8.4.0", 113 | "typescript": "^4.5.4", 114 | "vsce": "^2.5.3", 115 | "vscode-test": "^1.5.2" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as cron from "node-cron" 2 | import * as vscode from "vscode" 3 | import * as bilibiliLiveStatus from "./bilibiliLiveStatus" 4 | import * as bilibiliDynamics from "./bilibiliDynamics" 5 | import * as douyinVideos from "./douyinVideos" 6 | import type { AsoulMember, BilibiliUser, DouyinUser } from "./types" 7 | import { createNotification } from "./notification" 8 | 9 | // this method is called when your extension is activated 10 | export const activate = (context: vscode.ExtensionContext) => { 11 | const configuration = vscode.workspace.getConfiguration("asoulNotifications") 12 | const asoulMembers = configuration.get("asoulMembers", []) 13 | 14 | const bilibiliDynamicsNotificationsEnabled = configuration.get("bilibiliDynamics.enabled", false) 15 | const bilibiliLiveStatusNotificationsEnabled = configuration.get("bilibiliLiveStatus.enabled", false) 16 | const douyinVideosNotificationsEnabled = configuration.get("douyinVideos.enabled", false) 17 | 18 | const bilibiliDynamicsCronExpression = "*/30 * * * * *" 19 | const bilibiliLiveStatusCronExpression = "*/30 * * * * *" 20 | const douyinVideosCronExpression = "*/30 * * * * *" 21 | 22 | const bilibiliUsers: BilibiliUser[] = asoulMembers 23 | .reduce((users, { bilibiliId, nickname }) => { 24 | return bilibiliId 25 | ? [...users, { nickname, bilibiliId }] 26 | : users 27 | }, [] as BilibiliUser[]) 28 | 29 | const douyinUsers: DouyinUser[] = asoulMembers 30 | .reduce((users, { douyinId, nickname }) => { 31 | return douyinId 32 | ? [...users, { nickname, douyinId }] 33 | : users 34 | 35 | }, [] as DouyinUser[]) 36 | 37 | const bilibiliDynamicsNotificationsTask = cron.schedule(bilibiliDynamicsCronExpression, () => { 38 | bilibiliUsers.map(async ({ bilibiliId, nickname }) => { 39 | try { 40 | const oldDynamicIdsKey = `old-dynamic-ids-${bilibiliId}` 41 | const res = await bilibiliDynamics.requestDynamics(bilibiliId) 42 | const oldDynamicIds = context.globalState.get(oldDynamicIdsKey) ?? [] 43 | const currentDynamics = bilibiliDynamics.getDynamicsFromResponse(res, nickname) 44 | const newDynamics = currentDynamics.filter(({ dynamicId }) => oldDynamicIds.length > 0 && !oldDynamicIds.includes(dynamicId)) 45 | newDynamics.forEach(({ message, commands }) => createNotification(message, ...commands)) 46 | const currentDynamicIds = currentDynamics.map(({ dynamicId }) => dynamicId) 47 | context.globalState.update(oldDynamicIdsKey, [...oldDynamicIds, ...currentDynamicIds]) 48 | } catch (err) { 49 | console.log(err) 50 | } 51 | }) 52 | }, { scheduled: false }) 53 | 54 | if (bilibiliDynamicsNotificationsEnabled) { 55 | bilibiliDynamicsNotificationsTask.start() 56 | } 57 | 58 | const statusBarItems = bilibiliUsers.map(user => vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left)) 59 | 60 | const bilibiliLiveStatusNotificationsTask = cron.schedule(bilibiliLiveStatusCronExpression, () => { 61 | bilibiliUsers.map(async ({ bilibiliId, nickname }, index) => { 62 | const res = await bilibiliLiveStatus.requestLiveStatus(bilibiliId) 63 | const liveStatus = bilibiliLiveStatus.getLiveStatusFromResponse(res, nickname) 64 | bilibiliLiveStatus.displayStatusBar(statusBarItems[index], liveStatus) 65 | }) 66 | }, { scheduled: false }) 67 | 68 | if (bilibiliLiveStatusNotificationsEnabled) { 69 | bilibiliLiveStatusNotificationsTask.start() 70 | } 71 | 72 | const douyinVideosNotificationsTask = cron.schedule(douyinVideosCronExpression, () => { 73 | douyinUsers.map(async ({ douyinId, nickname }) => { 74 | try { 75 | const oldVideoIdsKey = `old-video-ids-${douyinId}` 76 | const res = await douyinVideos.requestVideos(douyinId) 77 | const oldVideoIds = context.globalState.get(oldVideoIdsKey) ?? [] 78 | const currentVideos = douyinVideos.getVideosFromResponse(res, nickname) 79 | const newVideos = currentVideos.filter(({ videoId }) => oldVideoIds.length > 0 && !oldVideoIds.includes(videoId)) 80 | newVideos.forEach(({ message, commands }) => createNotification(message, ...commands)) 81 | const currentVideoIds = currentVideos.map(({ videoId }) => videoId) 82 | context.globalState.update(oldVideoIdsKey, [...oldVideoIds, ...currentVideoIds]) 83 | } catch (err) { 84 | console.log(err) 85 | } 86 | }) 87 | }, { scheduled: false }) 88 | 89 | if (douyinVideosNotificationsEnabled) { 90 | douyinVideosNotificationsTask.start() 91 | } 92 | 93 | vscode.workspace.onDidChangeConfiguration(ds => { 94 | if (ds.affectsConfiguration("asoulNotifications")) { 95 | vscode.window 96 | .showInformationMessage("A-SOUL 提醒小助手的配置需要在 VS Code 重启之后生效", "立即重启") 97 | .then(selection => { 98 | if (selection === "立即重启") { 99 | vscode.commands.executeCommand("workbench.action.reloadWindow") 100 | } 101 | }) 102 | } 103 | }) 104 | } 105 | 106 | // this method is called when your extension is deactivated 107 | export const deactivate = () => { } 108 | --------------------------------------------------------------------------------