├── .gitignore
├── .puppeteerrc.cjs
├── CHANGELOG.md
├── LICENSE
├── README.md
├── apps
├── bilibili.js
├── help.js
├── version.js
└── weibo.js
├── components
├── dynamic
│ ├── Account.js
│ ├── Content.js
│ ├── Footer.js
│ ├── ForwardContent.js
│ ├── LogoText.js
│ └── MainPage.js
├── help
│ └── Help.js
├── index.js
├── loginQrcode
│ └── Page.js
└── version
│ └── Version.js
├── defaultConfig
├── bilibili
│ ├── config.yaml
│ └── push.yaml
├── help
│ └── help.yaml
└── weibo
│ ├── config.yaml
│ └── push.yaml
├── index.js
├── models
├── bilibili
│ ├── bilibili.main.api.js
│ ├── bilibili.main.get.web.data.js
│ ├── bilibili.main.models.js
│ ├── bilibili.main.query.js
│ ├── bilibili.main.task.js
│ ├── bilibili.risk.buid.fp.js
│ ├── bilibili.risk.dm.img.js
│ ├── bilibili.risk.ticket.js
│ └── bilibili.risk.wbi.js
├── help
│ └── help.js
├── version
│ └── version.js
└── weibo
│ ├── weibo.main.api.js
│ ├── weibo.main.get.web.data.js
│ ├── weibo.main.query.js
│ └── weibo.main.task.js
├── package.json
├── resources
├── css
│ ├── dynamic
│ │ ├── Account.css
│ │ ├── Content.box.grid.4.css
│ │ ├── Content.box.grid.9.css
│ │ ├── Content.css
│ │ ├── Footer.css
│ │ ├── ForwardContent.css
│ │ ├── LogoText.css
│ │ └── MainPage.css
│ ├── help
│ │ └── help.css
│ ├── loginQrcode
│ │ └── Page.css
│ └── version
│ │ └── version.css
├── fonts
│ └── OPSans.woff2
└── img
│ ├── background
│ └── Girl.png
│ ├── icon
│ ├── dynamic
│ │ ├── bili-dyn-card-vote__cover.svg
│ │ ├── bili-rich-text-module-goods-taobao.svg
│ │ ├── bili-rich-text-module-lottery.svg
│ │ ├── bilibili.svg
│ │ └── weibo.svg
│ └── puplic
│ │ ├── archaic_stone.png
│ │ ├── condessence_crystal.png
│ │ ├── delightful_encounter.png
│ │ ├── diagram.png
│ │ ├── essence_of_pure_sacred_dewdrop.png
│ │ ├── everamber.png
│ │ ├── flower_1.png
│ │ ├── flower_2.png
│ │ ├── kamera.png
│ │ ├── lumidouce_bell.png
│ │ ├── mora.png
│ │ ├── pluie_lotus.png
│ │ ├── restaurant_smoothie.png
│ │ ├── romaritime_flower.png
│ │ ├── shell.png
│ │ ├── spring_of_pure_sacred_dewdrop.png
│ │ ├── surging_sacred_chalice.png
│ │ ├── tourbillon_device.png
│ │ ├── unfading_silky_grace.png
│ │ ├── wisdom.png
│ │ ├── wondrous_lovely_flower.png
│ │ ├── 岩神瞳共鸣石.png
│ │ ├── 水神瞳共鸣石.png
│ │ ├── 草神瞳共鸣石.png
│ │ ├── 钓鱼.png
│ │ ├── 雷神瞳共鸣石.png
│ │ ├── 风神瞳共鸣石.png
│ │ └── 风车.png
│ └── readme
│ ├── girl.png
│ └── min-Girl.png
├── types
├── apps
│ ├── bilibili.d.ts
│ ├── help.d.ts
│ ├── version.d.ts
│ └── weibo.d.ts
├── components
│ ├── dynamic
│ │ ├── Account.d.ts
│ │ ├── Content.d.ts
│ │ ├── Footer.d.ts
│ │ ├── ForwardContent.d.ts
│ │ ├── LogoText.d.ts
│ │ └── MainPage.d.ts
│ ├── help
│ │ └── Help.d.ts
│ ├── index.d.ts
│ ├── loginQrcode
│ │ └── Page.d.ts
│ └── version
│ │ └── Version.d.ts
├── index.d.ts
├── jsxp.server.d.ts
├── models
│ ├── bilibili
│ │ ├── bilibili.main.api.d.ts
│ │ ├── bilibili.main.get.web.data.d.ts
│ │ ├── bilibili.main.models.d.ts
│ │ ├── bilibili.main.query.d.ts
│ │ ├── bilibili.main.task.d.ts
│ │ ├── bilibili.risk.buid.fp.d.ts
│ │ ├── bilibili.risk.dm.img.d.ts
│ │ ├── bilibili.risk.ticket.d.ts
│ │ └── bilibili.risk.wbi.d.ts
│ ├── help
│ │ └── help.d.ts
│ ├── version
│ │ └── version.d.ts
│ └── weibo
│ │ ├── weibo.main.api.d.ts
│ │ ├── weibo.main.get.web.data.d.ts
│ │ ├── weibo.main.query.d.ts
│ │ └── weibo.main.task.d.ts
└── utils
│ ├── config.d.ts
│ ├── image.d.ts
│ ├── paths.d.ts
│ └── puppeteer.render.d.ts
└── utils
├── config.js
├── image.js
├── paths.js
└── puppeteer.render.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | config
4 | ./config/*//**
5 |
6 | lib
7 | public
8 | yarn.lock
9 | package-lock.json
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | temp/*/**
15 | temp
16 |
17 | data
18 | data/*/**
19 |
20 | logs
21 | logs/*/**
22 |
23 | dist
24 | dist/*/**
25 |
26 | plugins
27 | plugins/*/**
--------------------------------------------------------------------------------
/.puppeteerrc.cjs:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const { existsSync } = require('fs');
3 | const { execSync } = require('child_process');
4 | const arch = os.arch();
5 |
6 | let skipDownload = false;
7 | let executablePath;
8 |
9 | if (['linux', 'android'].includes(process.platform))
10 | for (const item of ['chromium', 'chromium-browser', 'chrome', 'google-chrome'])
11 | try {
12 | const chromiumPath = execSync(`command -v ${item}`).toString().trim();
13 | if (chromiumPath && existsSync(chromiumPath)) {
14 | executablePath = chromiumPath;
15 | break;
16 | }
17 | } catch (err) {}
18 |
19 | /**
20 | * @type {string} 浏览器 "可执行文件路径" 列表,可根据需要自行修改或添加
21 | */
22 | if (!executablePath)
23 | for (const item of [
24 | // Windows
25 | 'C:/Program Files/Google/Chrome/Application/chrome.exe',
26 | 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
27 | // macOS
28 | '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
29 | '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
30 | ])
31 | if (existsSync(item)) {
32 | executablePath = item;
33 | break;
34 | }
35 |
36 | if (executablePath || arch == 'arm64' || arch == 'aarch64') {
37 | (typeof logger != 'undefined' ? logger : console).info(`[Chromium] ${executablePath}`);
38 | skipDownload = true;
39 | }
40 |
41 | /**
42 | * @type {import("puppeteer").Configuration}
43 | */
44 | module.exports = { skipDownload, executablePath };
45 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.0.7
2 | * 新增动态内容过多时转发发送功能,默认开启
3 | * 优化动态检查日志
4 | * 优化文字动态内容及排版,修复微博视频图片混合的动态显示缺失问题
5 | * 优化获取B站文章动态内容
6 | * 优化订阅数据展示
7 | * 修复同一up订阅多个群聊订阅,推送类型合并的问题
8 | * 添加白名单关键词过滤功能
9 | * 新增B站视频解析
10 |
11 | # 2.0.6
12 | * 优化屏蔽关键词功能
13 | * 新增 weibo User-Agent 配置项
14 | * 优化api请求
15 | * 优化消息发送
16 | * 优化文字动态图片资源的发送
17 | * 依赖升级
18 | * 新增哔哩直播动态@全体成员功能,开启前请检查机器人管理员权限和所在聊天类型是否支持
19 |
20 | # 2.0.5
21 | * 优化ck
22 | * 新增 bili User-Agent 配置项
23 | * 优化获取动态数据
24 | * 新增获取B站up数据的随机延迟配置项
25 | * 新增puppeteer渲染图片测试脚本
26 |
27 | # 2.0.4
28 | * 增加splitHeight配置项,其他优化
29 | * 优化B站风控相关,新增bili_tiket参数
30 | * fix Repeated Instantiation puppeteer
31 | * 优化获取B站登录ck
32 | * 添加截图列队,优化配置文件注释
33 |
34 | # 2.0.3
35 | * 优化截图、静态资源引入方式
36 | * 优化获取完整文章动态
37 | * fix addBiliSub
38 | * 优化提示信息
39 |
40 | # 2.0.2
41 | * fix DYNAMIC_TYPE_ARTICLE
42 | * 优化Next npm包插件 Task加载
43 |
44 | # 2.0.1
45 | * 规范版本号
46 | * 调整渲染出图类型为webp格式,减小发送图片消息带宽压力
47 |
48 | # 2.0.0
49 | * 优化代码,npm 包增加 typescript 类型定义文件
50 | * 统一配置文件目录:yunzai/data/yuki-plugin/config/,更新后如需使用旧配置文件,请自行移动旧配置文件到新目录
51 |
52 | # 1.4.0
53 | * 优化哔哩登录日志
54 | * 优化渲染
55 | * 增加哔哩 MAJOR_TYPE_DRAW MAJOR_TYPE_ARTICLE 动态子类型
56 |
57 | # 1.3.0
58 | * 修复群组与好友动态推送混淆问题
59 | * 更新获取B站up信息api
60 | * 优化动态字体样式
61 | * 优化文字动态内容排版
62 | * 修复转发动态内容缺失
63 |
64 | # 1.2.0
65 | * 新增支持获取完整文章动态内容
66 | * 修复宫格样式
67 |
68 | # 1.1.0
69 | * 修复了一些bug
70 | * 优化了一些功能
71 | * 新增 Yunzai-v4.1 npm包方式安装支持,yarn add yz-yuki-plugin -W
72 |
73 | # 1.0.0
74 | * 支持Yunzai-V4.1.1 初版
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2024 snowtafir
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # YUKI-PLUGIN
4 |
5 | - 一个适用于 `Yunzai 系列机器人框架` 的B站动态、微博动态订阅推送和B站视频链接解析的插件
6 |
7 | - 支持 群聊/私聊 订阅B站动态和微博动态,支持定时推送,支持手动触发推送,支持简单查询B站/微博用户信息。
8 |
9 | [](https://github.com/snowtafir/yuki-plugin)
10 |
11 |
12 | # 🚩运行环境:
13 | 1. 系统:
14 | * windows 10/11+,
15 | * Linux推荐:CentOS Stream 8 +, Debian 12+, Fedora 35+
16 |
17 | 2. node v22+ 下载地址:https://nodejs.org/zh-cn/download/
18 |
19 | 3. 推荐使用chrome或chromium浏览器,其他浏览器可能存在兼容性问题。
20 | * chrome 浏览器 v131+ win_x64下载地址:https://www.google.cn/chrome/
21 | * chromium 浏览器 v128+ Linux/win手动下载安装:https://download-chromium.appspot.com
22 |
23 | > linux命令行安装chromiun浏览器:
24 | ```sh
25 | sudo apt-get install chromium-browser # Ubuntu/Debian
26 | sudo dnf install chromium # Fedora
27 | sudo yum install chromium # CentOS Stream 8
28 |
29 | #查看版本
30 | chromium-browser --version
31 | ```
32 | > 注意,如果windows下手动安装chromium浏览器,或安装了其他修改版chrome浏览器,出现找不到浏览器,需要手动将`可执行文件路径`添加到 [./.puppeteerrc.cjs](./.puppeteerrc.cjs) 的路径列表中。
33 |
34 | # 🌰一、安装插件
35 |
36 | ## 选择安装方式
37 | 按照网络情况以及使用的bot框架是`Yunzaijs`还是`Yunzai-V3`,选择对应的安装方式。
38 |
39 | ### ***(一)Yunzai-V3***
40 |
41 | > 仅支持Yunzai-V3(TRSS/Miao)的分支,选择仓库,安装到 `yunzai/plugins`:
42 |
43 | gitee仓库:
44 | ```
45 | git clone --branch main3 https://gitee.com/snowtafir/yuki-plugin.git ./plugins/yuki-plugin
46 | ```
47 |
48 | github仓库:
49 | ```
50 | git clone --branch main3 https://github.com/snowtafir/yuki-plugin.git ./plugins/yuki-plugin
51 | ```
52 |
53 | * 安装依赖
54 |
55 | ```shell
56 | pnpm install --filter=yuki-plugin
57 | ```
58 |
59 | ### ***(二)YunzaiJS***
60 | > 选择其中一种方式安装插件:
61 |
62 | 1. npm包安装到`yunzaijs/node_modules`的方式,仅YunzaiJS支持:
63 | ```
64 | yarn add yz-yuki-plugin -W
65 | ```
66 |
67 | 选择启用插件方式:
68 |
69 | * 方式1(推荐):
70 |
71 | 手动新建 `yunzaijs/yunzai.config.json` 文件,输入如下内容,`applications`字段添加的 `"yz-yuki-plugin"`即为启用本插件:
72 |
73 | ```json
74 | {
75 | "applications": ["@yunzaijs/system", "yz-yuki-plugin"],
76 | "middlewares": ["@yunzaijs/mys/message", "@yunzaijs/mys/runtime"]
77 | }
78 | ```
79 | * 方式2:
80 |
81 | 修改 `yunzaijs/yunzai.config.js`:
82 | ```js
83 | import { defineConfig } from 'yunzaijs'
84 | export default defineConfig({
85 | applications: ["@yunzaijs/system", "yz-yuki-plugin"], //该行添加 'yz-yuki-plugin'
86 | middlewares: ["@yunzaijs/mys/message", "@yunzaijs/mys/runtime"]
87 | })
88 | ```
89 |
90 | 2. 安装到 `yunzaijs/plugins` 的方式:
91 |
92 | > 仅支持Yunzai-Next的分支,选择仓库:
93 |
94 | gitee仓库:
95 | ```shell
96 | git clone --branch main https://gitee.com/snowtafir/yuki-plugin.git ./plugins/yuki-plugin
97 | ```
98 |
99 | github仓库:
100 | ```shell
101 | git clone --branch main https://github.com/snowtafir/yuki-plugin.git ./plugins/yuki-plugin
102 | ```
103 |
104 | * 依赖安装:
105 | ```shell
106 | yarn install
107 | ```
108 |
109 | # 📦二、插件配置
110 |
111 | > [!IMPORTANT]
112 | > 统一的配置文件路径:
113 |
114 | `Yunzai/data/yuki-plugin/config/`,启动一次后,即可查看配置文件。
115 |
116 | ### (一)B站动态功能:
117 | 使用前建议先绑定B站账号或配置cookie,绑定后即可使用相关功能。
118 |
119 | > **CK优先级:** **#添加B站CK** > ***#扫码B站登录*** > 自动ck。`只有删除前一个,后一个才生效,删除方法见功能指令。`
120 |
121 | #### 1)绑定账号(推荐):
122 | `#扫码B站登录`,获取B站登录CK。取消使用登录则发送:`#删除B站登录`
123 |
124 | #### 2)手动配置本地Cookie(可选):
125 | 私聊/私信 Bot下发送`#添加B站CK: xxx` 添加本地浏览器 无痕模式下 登录b站 获取的B站cookie。
126 |
127 | 本地CK获取方法:
128 |
129 | ***注意事项:***
130 | > 你平常使用浏览器访问 b 站为普通模式,cookie会定期自动刷新而导致复制的旧ck一段时间就失效,你应该使用`隐私窗口/无痕式窗口`重新登录b站,并获取新的 cookie。
131 |
132 | > 成功复制cookie文本后应该直接点击`X`关闭 **浏览器窗口**,而`不是账号的 退出登录`,否则 cookie 会立马失效。
133 |
134 | **步骤:**
135 | * ①在浏览器打开的`无痕/隐私窗口`中登录自己的b站账号
136 | * ②如示例图操作:处于bilibili首页 -> 按 F12 (或者右键 --> 检查)打开开发者工具,切换到网络 ( network )点击 重新载入(或者按 F5,Ctrl + R 等)刷新页面,接着点击某一个请求(通常为 nav ),选中Cookie项的文本并 Ctrl + C 复制(不是 `复制值`)(包含SESSDATA)得到cookie。
137 |
138 | 
139 | 
140 |
141 |
142 |
143 | > [!TIP]
144 | > 保存目录:`Yunzai/data/yuki-plugin/biliCookie.yaml`,如需更换/更新cookie 使用新的cookie发送`#添加B站CK: xxx`覆盖绑定即可。停用手动本地ck则发送命令:`#删除B站ck`
145 |
146 | ### (二)微博动态功能:
147 | * 获取微博博主uid:
148 |
149 | 博主主页如:
150 | ```
151 | https://m.weibo.cn/u/6593199887 # 6593199887 为原神博主uid
152 | https://m.weibo.cn/u/7643376782 # 7643376782 为崩坏星穹铁道博主uid
153 | ```
154 | 或打开微博app,进入博主主页,右上角点击分享,复制分享链接,在链接里找到相应uid。
155 |
156 | 微博限制,可能连续获取动态会出现获取连接中断报错,待定时任务自动重试即可。
157 |
158 | # 🌈 三、功能列表
159 |
160 | 请使用 `#优纪帮助`或 `/yuki帮助` 获取完整帮助
161 |
162 | - [x] B站动态
163 | - [x] B站视频链接解析
164 | - [x] 微博动态
165 |
166 |
167 | # 🚀 四、指令列表
168 |
169 | 指令列表,点击展开
170 |
171 | > [!TIP]
172 | > 指令前缀:`#优纪`、`#yuki`、`/优纪`、`/yuki`,
173 |
174 | 示例:`#优纪订阅B站推送uid`、`#yuki订阅B站推送uid`、`/优纪订阅B站推送uid`、`/yuki订阅B站推送uid`。
175 |
176 | | 用途 | 描述 | 指令 |
177 | | --------- | ----------- | ------------ |
178 | ||||
179 | | **B站功能** | ------------------------- | ---------- |
180 | | 添加B站推送 | 检测up的B站动态进行推送,权限:Master。可选分类:直播、视频、图文、文章、转发,不加分类则默认全部 | `#订阅B站推送uid` `#订阅B站推送 图文 uid` |
181 | | 取消B站推送 | 删除对应up的B站对应类型的动态推送,权限:Master。可选分类:直播、视频、图文、文章、转发,不加分类则默认全部 | `#取消B站推送uid` `#取消B站推送 图文 uid` |
182 | | 查看B站订阅列表 | 查看本Bot所有的B站订阅列表,权限:Bot的Master | `#B站全部订阅列表` |
183 | | 查看本群/私聊B站订阅列表 | 查看 本群/私聊 添加的B站订阅列表 | `#B站订阅列表` |
184 | | 手动推送B站订阅 | 手动触发定时推送任务,权限:Bot的Master | `#执行B站任务` |
185 | | 查看up信息 | 通过uid查看up信息 | `#B站up主 uid` |
186 | | 搜索B站up主 | 根据昵称在b站搜索up信息 | `#搜索B站up主 xxx` |
187 | | 扫码B站登录 | app扫码获取登录ck | `#扫码B站登录` |
188 | | 取消B站登录 | 删除扫码获取的B站CK | `#取消B站登陆` |
189 | | 查看B站登录信息 | 查看app扫码登录的信息和状态 | `#我的B站登录` |
190 | | 绑定B站ck | 配置手动本地获取的B站CK,仅限私聊/私信,权限:Master | `#绑定B本地站ck: xxx` |
191 | | 删除B站ck | 删除手动获取的B站cookie,权限:Master | `#删除B站本地ck` |
192 | | 查看B站ck | 查看当前启用的B站ck,仅限私聊 | `#我的B站ck` |
193 | | 刷新B站临时ck | 重新获取并刷新redis缓存的未绑定自己的B站ck而自动获取的 临时B站cookie | `#刷新B站临时ck` |
194 | | B站视频链接解析 | 解析B站视频链接,支持av号、BV号、app分享链接,官方短链 | `链接xxxx` |
195 | ||||
196 | | **微博功能** | ------------------------- | ---------- |
197 | | 添加微博推送 | 检测博主的微博动态进行推送,权限:Master。可选分类:视频、图文、文章、转发,不加分类则默认全部 | `#订阅微博推送uid` `#订阅微博推送 图文 uid` |
198 | | 取消微博推送 | 删除对应博主的微博对应类型的动态推送,权限:Master。可选分类:视频、图文、文章、转发,不加分类则默认全部 | `#取消微博推送uid` `#取消微博推送 图文 uid` |
199 | | 查看微博订阅列表 | 查看本Bot所有的微博订阅列表,权限:Bot的Master | `#微博全部订阅列表` |
200 | | 查看本群/私聊微博订阅列表 | 查看 本群/私聊 添加的微博订阅列表 | `#微博订阅列表` |
201 | | 手动推送微博订阅 | 手动触发定时推送任务,权限:Bot的Master | `#执行微博任务` |
202 | | 查看博主信息 | 通过uid查看博主信息 | `#微博博主 uid` |
203 | | 搜索微博博主 | 根据关键词在微博搜索大V博主的信息 | `#搜索微博博主 xxx` |
204 | ||||
205 | | **其他指令** | | |
206 | | 查看版本信息 | 查看版本信息 | `#优纪版本` |
207 | | 更新yuki插件 | 系统指令更新yuki插件,yunzaiJS需安装`@yunzaijs/system` | `#更新yuki-plugin` |
208 | | 强制更新yuki插件 | 强制更新yuki插件,yunzaiJS需安装`@yunzaijs/system`| `#强制更新yuki-plugin` |
209 |
210 |
211 |
212 | # 🧩 五、支持与贡献
213 |
214 | 如果你喜欢这个项目,请不妨点个 Star🌟,这是对开发者最大的动力,呜咪~❤️
215 |
216 | 有意见或者建议也欢迎提交 [Issues](https://github.com/snowtafir/yuki-plugin/issues) 和 [Pull requests](https://github.com/snowtafir/yuki-plugin/pulls)。
217 |
218 | 参与开发,点击展开
219 |
220 | > [!TIP]
221 | > * main、main3分支为编译分支,请勿直接在该分支上进行开发,请使用dev、dev3、npm分支进行同步开发,并提交Pull requests。
222 | > * 提交PR后由管理员审核,审核成功并合并后会自动进行github actions编译,最终提交编译好的代码到main、main3分支以及发布到npm。
223 | > * 根据喜好,可使用 [vscode编辑器](https://code.visualstudio.com/) 或其他 IDE 辅助开发
224 |
225 | 1. Fork 项目到自己的仓库。
226 | 2. clone Fork 后的项目到本地。
227 | 3. 进入clone后的项目目录,打开终端或 git bash 执行 `git checkout xxx` 切换到xxx分支(请依次切换到dev3、dev、npm分支进行同步开发)。
228 | 4. 准备环境:
229 | ```sh
230 | npm config set registry https://registry.npmmirror.com
231 | npm install yarn@1.19.2 -g
232 | yarn install
233 | ```
234 | 5. 开发并推送到Fork的仓库,接着仓库页面提交pull requests。
235 |
236 |
237 |
238 | # 🌟 六、license/声明
239 | - this project is inspired by [trss-xianxin-plugin](https://github.com/snowtafir/xianxin-plugin)
240 | - 基于 `MIT` 协议开源,但有如下额外限制:
241 | 1. 其中`resources/img/icon/`目录下的素材来源于网络,不保证商业使用,请遵守相关版权法律,如有侵权请联系本人删除。
242 | 2. 其中`resources/img/background/` 以及 `resources/img/readme/`的图片素材仅供学习交流使用,禁止用于任何商业用途。
243 | 3. ```严禁用于非法行为```
244 |
245 |
246 | # 🔗 七、链接/致谢
247 |
248 | | Nickname | Contribution |
249 | | :-----------------------------------------------------------------: | ----------------------- |
250 | |Yunzai-Next||
251 | | [YunzaiJS文档](https://yunzaijs.github.io/docs/) | YunzaiJS 文档 |
252 | | [YunzaiJS 仓库](https://github.com/yunzaijs/core) | YunzaiJS |
253 | |Yunzai-V3||
254 | | [功能/插件库](https://gitee.com/yhArcadia/Yunzai-Bot-plugins-index) | Yunzai-Bot 相关内容索引 |
255 | | [TRSS-Yunzai](https://gitee.com/TimeRainStarSky/Yunzai) | 时雨🌌星空的 TRSS-Yunzai |
256 | | [Miao-Yunzai](https://gitee.com/yoimiya-kokomi/Miao-Yunzai) | 喵喵的 Miao-Yunzai |
257 | | [Yunzai-Bot](https://gitee.com/Le-niao/Yunzai-Bot) | 乐神的 Yunzai-Bot |
258 | |[jsxp](https://github.com/lemonade-lab/lvyjs/tree/main/packages/jsxp) | 一个可以在 tsx 环境中,使用 puppeteer 对 tsx 组件进行截图的库 |
--------------------------------------------------------------------------------
/apps/help.js:
--------------------------------------------------------------------------------
1 | import { renderPage } from '../utils/image.js';
2 | import Help from '../models/help/help.js';
3 | import plugin from '../../../lib/plugins/plugin.js';
4 |
5 | class YukiHelp extends plugin {
6 | constructor() {
7 | super({
8 | name: 'yuki-help',
9 | des: '优纪帮助',
10 | event: 'message',
11 | priority: 0,
12 | rule: [
13 | {
14 | reg: '^(#|/)(yuki|优纪)帮助$',
15 | fnc: 'yukiHelp'
16 | }
17 | ]
18 | });
19 | }
20 | /**
21 | * 优纪帮助
22 | */
23 | async yukiHelp() {
24 | const helpData = await Help.get();
25 | const renderData = {
26 | data: helpData.map((item) => ({
27 | group: item.group,
28 | list: item.list.map((listItem) => ({
29 | icon: listItem.icon,
30 | title: listItem.title,
31 | desc: listItem.desc
32 | }))
33 | }))
34 | };
35 | const ScreenshotOptionsData = {
36 | SOptions: {
37 | type: 'webp',
38 | quality: 90
39 | },
40 | isSplit: false,
41 | modelName: 'yukiHelp'
42 | };
43 | const helpImg = await renderPage('help', 'Help', renderData, ScreenshotOptionsData);
44 | let imgRes;
45 | if (helpImg !== false) {
46 | const { img } = helpImg;
47 | imgRes = { img };
48 | }
49 | else {
50 | return;
51 | }
52 | let msg = [];
53 | msg.push(segment.image(imgRes.img[0]));
54 | await this.e.reply(msg);
55 | }
56 | }
57 |
58 | export { YukiHelp as default };
59 |
--------------------------------------------------------------------------------
/apps/version.js:
--------------------------------------------------------------------------------
1 | import { renderPage } from '../utils/image.js';
2 | import VersionData from '../models/version/version.js';
3 | import plugin from '../../../lib/plugins/plugin.js';
4 |
5 | class YukiVersion extends plugin {
6 | constructor() {
7 | super({
8 | name: 'yuki-version',
9 | dsc: '优纪版本',
10 | event: 'message',
11 | priority: 0,
12 | rule: [
13 | {
14 | reg: '^(#|/)(yuki|优纪)版本$',
15 | fnc: 'yukiVersion'
16 | }
17 | ]
18 | });
19 | }
20 | /**
21 | * 优纪版本
22 | */
23 | async yukiVersion() {
24 | const version = new VersionData();
25 | const versionData = await version.getChangelogContent();
26 | const renderData = {
27 | data: versionData.map((item) => ({
28 | version: item.version,
29 | data: item.data
30 | }))
31 | };
32 | const ScreenshotOptionsData = {
33 | SOptions: {
34 | type: 'webp',
35 | quality: 90
36 | },
37 | isSplit: false,
38 | modelName: 'yukiVersion'
39 | };
40 | const helpImg = await renderPage('version', 'Version', renderData, ScreenshotOptionsData);
41 | let imgRes;
42 | if (helpImg !== false) {
43 | const { img } = helpImg;
44 | imgRes = { img };
45 | }
46 | else {
47 | return;
48 | }
49 | let msg = [];
50 | msg.push(segment.image(imgRes.img[0]));
51 | await this.e.reply(msg);
52 | }
53 | }
54 |
55 | export { YukiVersion as default };
56 |
--------------------------------------------------------------------------------
/apps/weibo.js:
--------------------------------------------------------------------------------
1 | import { WeiboQuery } from '../models/weibo/weibo.main.query.js';
2 | import { WeiboTask } from '../models/weibo/weibo.main.task.js';
3 | import Config from '../utils/config.js';
4 | import { WeiboWebDataFetcher } from '../models/weibo/weibo.main.get.web.data.js';
5 | import plugin from '../../../lib/plugins/plugin.js';
6 |
7 | class YukiWeibo extends plugin {
8 | constructor() {
9 | super({
10 | name: 'yuki-plugin-weibo',
11 | dsc: '微博相关指令',
12 | event: 'message',
13 | priority: 0,
14 | rule: [
15 | {
16 | reg: '^(#|/)(yuki|优纪)?执行(微博|weibo|WEIBO)任务$',
17 | fnc: 'newPushTask',
18 | permission: 'master'
19 | },
20 | {
21 | reg: '^(#|/)(yuki|优纪)?(订阅|添加|add|ADD)(微博|weibo|WEIBO)推送\\s*(视频\\s*|图文\\s*|文章\\s*|转发\\s*)*.*$',
22 | fnc: 'addDynamicSub'
23 | },
24 | {
25 | reg: '^(#|/)(yuki|优纪)?(取消|删除|del|DEL)(微博|weibo|WEIBO)推送\\s*(视频\\s*|图文\\s*|文章\\s*|转发\\s*)*.*$',
26 | fnc: 'delDynamicSub'
27 | },
28 | {
29 | reg: '^(#|/)(yuki|优纪)?(微博|weibo|WEIBO)全部(推送|动态|订阅)列表$',
30 | fnc: 'allSubDynamicPushList'
31 | },
32 | {
33 | reg: '^(#|/)(yuki|优纪)?(微博|weibo|WEIBO)(推送|动态|订阅)列表$',
34 | fnc: 'singelSubDynamicPushList'
35 | },
36 | {
37 | reg: '^(#|/)(yuki|优纪)?(微博|weibo|WEIBO)(博|bo|BO)主.*$',
38 | fnc: 'getWeiboUserInfoByUid'
39 | },
40 | {
41 | reg: '^(#|/)(yuki|优纪)?搜索(微博|weibo|WEIBO)(博|bo|BO)主.*$',
42 | fnc: 'searchWeiboUserInfoByKeyword'
43 | }
44 | ]
45 | });
46 | this.weiboConfigData = Config.getConfigData('config', 'weibo', 'config');
47 | this.weiboPushData = Config.getConfigData('config', 'weibo', 'push');
48 | /** 定时任务 */
49 | this.task = {
50 | cron: !!this.weiboConfigData.pushStatus ? (this.weiboConfigData.checkDynamicCD ? this.weiboConfigData.checkDynamicCD : '*/23 * * * *') : '',
51 | name: 'yuki插件---微博动态推送定时任务',
52 | fnc: () => this.newPushTask(),
53 | log: !!this.weiboConfigData.pushTaskLog
54 | };
55 | }
56 | weiboConfigData;
57 | weiboPushData;
58 | /** 微博动态推送定时任务 */
59 | async newPushTask() {
60 | await new WeiboTask(this.e).runTask();
61 | }
62 | /** 添加微博动态订阅 */
63 | async addDynamicSub() {
64 | if (!this.e.isMaster) {
65 | this.e.reply('未取得bot主人身份,无权限添加微博动态订阅');
66 | }
67 | else {
68 | // 从消息中提取UID
69 | const uid = this.e.msg.replace(/^(#|\/)(yuki|优纪)?(订阅|添加|add|ADD)(微博|weibo|WEIBO)推送\s*(视频\s*|图文\s*|文章\s*|转发\s*)*/g, '').trim();
70 | if (!uid) {
71 | this.e.reply(`请在指令末尾指定订阅的微博博主的UID!`);
72 | return true;
73 | }
74 | // 获取或初始化推送数据
75 | let subData = this.weiboPushData || { group: {}, private: {} };
76 | // 根据聊天类型初始化数据
77 | let chatType = this.e.isGroup ? 'group' : 'private';
78 | let chatId = this.e.isGroup ? this.e.group_id : this.e.user_id;
79 | // 初始化群组或私聊数据
80 | if (!subData[chatType][chatId]) {
81 | subData[chatType][chatId] = [];
82 | }
83 | // 检查该 uid 是否已存在
84 | const upData = subData[chatType][chatId].find(item => item.uid === uid);
85 | if (upData) {
86 | // 更新推送类型
87 | upData.type = WeiboQuery.typeHandle(upData, this.e.msg, 'add');
88 | this.weiboPushData = subData;
89 | await Config.saveConfig('config', 'weibo', 'push', subData);
90 | this.e.reply(`修改微博推送动态类型成功~\n${upData.name}:${uid}`);
91 | return;
92 | }
93 | // 获取 微博 博主信息
94 | const res = await new WeiboWebDataFetcher(this.e).getBloggerInfo(uid);
95 | if (res?.statusText !== 'OK') {
96 | this.e.reply('出了点网络问题,等会再试试吧~');
97 | return false;
98 | }
99 | const { ok, data } = res.data || {};
100 | if (ok !== 1) {
101 | this.e.reply(`订阅校验失败~\n博主uid:${uid} 可能是无效的,请检查后再试~`);
102 | return true;
103 | }
104 | const userInfo = data.userInfo || {};
105 | let name = uid;
106 | if (userInfo && userInfo.length !== 0) {
107 | name = userInfo.screen_name || uid;
108 | }
109 | // 添加新的推送数据
110 | subData[chatType][chatId].push({
111 | bot_id: this.e?.self_id, // 使用 bot_id 对应 e_self_id
112 | uid,
113 | name: name,
114 | type: WeiboQuery.typeHandle({ uid, name }, this.e.msg, 'add')
115 | });
116 | this.weiboPushData = subData;
117 | Config.saveConfig('config', 'weibo', 'push', subData);
118 | this.e.reply(`添加微博推送成功~\n${name}:${uid}`);
119 | }
120 | }
121 | /** 删除微博动态订阅 */
122 | async delDynamicSub() {
123 | if (!this.e.isMaster) {
124 | this.e.reply('未取得bot主人身份,无权限删除微博动态订阅');
125 | }
126 | else {
127 | // 提取用户输入的UID
128 | const uid = this.e.msg.replace(/^(#|\/)(yuki|优纪)?(取消|删除|del|DEL)(微博|weibo|WEIBO)推送\s*(视频\s*|图文\s*|文章\s*|转发\s*)*/g, '').trim();
129 | if (!uid) {
130 | this.e.reply(`请在指令末尾指定订阅的微博博主的UID!`);
131 | return;
132 | }
133 | // 获取或初始化微博推送数据
134 | let subData = this.weiboPushData || { group: {}, private: {} };
135 | // 根据聊天类型初始化数据
136 | let chatType = this.e.isGroup ? 'group' : 'private';
137 | let chatId = this.e.isGroup ? this.e.group_id : this.e.user_id;
138 | // 初始化群组或私聊数据
139 | if (!subData[chatType][chatId]) {
140 | subData[chatType][chatId] = [];
141 | }
142 | // 查找指定UID的订阅数据
143 | const upData = subData[chatType][chatId].find((item) => item.uid == uid);
144 | if (!upData) {
145 | this.e.reply(`订阅列表中没有找到该UID~\n${uid}可能是无效的`);
146 | return;
147 | }
148 | // 处理订阅类型
149 | const newType = WeiboQuery.typeHandle(upData, this.e.msg, 'del');
150 | let isDel = false;
151 | if (newType.length) {
152 | // 更新订阅类型
153 | subData[chatType][chatId] = subData[chatType][chatId].map(item => {
154 | if (item.uid == uid) {
155 | item.type = newType;
156 | }
157 | return item;
158 | });
159 | }
160 | else {
161 | // 删除订阅
162 | isDel = true;
163 | subData[chatType][chatId] = subData[chatType][chatId].filter((item) => item.uid !== uid);
164 | }
165 | // 保存更新后的数据
166 | this.weiboPushData = subData;
167 | Config.saveConfig('config', 'weibo', 'push', subData);
168 | // 回复用户操作结果
169 | this.e.reply(`${isDel ? '删除' : '修改'}微博推送成功~\n${uid}`);
170 | }
171 | }
172 | /** 订阅的全部微博推送列表 */
173 | async allSubDynamicPushList() {
174 | if (!this.e.isMaster) {
175 | this.e.reply('未取得bot主人身份,无权限查看Bot的全部微博推送列表');
176 | }
177 | else {
178 | let subData = this.weiboPushData || { group: {}, private: {} };
179 | // 如果聊天ID没有订阅数据,则删除该聊天ID
180 | for (let chatType in subData) {
181 | if (subData.hasOwnProperty(chatType)) {
182 | subData[chatType] = Object.keys(subData[chatType]).reduce((nonEmptyData, chatId) => {
183 | if (subData[chatType][chatId].length > 0) {
184 | nonEmptyData[chatId] = subData[chatType][chatId];
185 | }
186 | return nonEmptyData;
187 | }, {});
188 | }
189 | }
190 | const messages = [];
191 | const typeMap = {
192 | DYNAMIC_TYPE_AV: '视频',
193 | DYNAMIC_TYPE_WORD: '图文',
194 | DYNAMIC_TYPE_DRAW: '图文',
195 | DYNAMIC_TYPE_ARTICLE: '文章',
196 | DYNAMIC_TYPE_FORWARD: '转发'
197 | };
198 | // 处理群组订阅
199 | if (subData.group && Object.keys(subData.group).length > 0) {
200 | messages.push('\n>>>>>>群组微博订阅<<<<<<');
201 | Object.keys(subData.group).forEach(groupId => {
202 | messages.push(`\n<群组${groupId}>:`);
203 | subData.group[groupId].forEach((item) => {
204 | const types = new Set();
205 | if (item.type && item.type.length) {
206 | item.type.forEach((typeItem) => {
207 | if (typeMap[typeItem]) {
208 | types.add(typeMap[typeItem]);
209 | }
210 | });
211 | }
212 | messages.push(`${item.uid}:${item.name} ${types.size ? `[${Array.from(types).join('、')}]` : ' [全部动态]'}`);
213 | });
214 | });
215 | }
216 | else {
217 | messages.push('\n>>>>>>群组微博订阅<<<<<<\n当前没有任何群组订阅数据~');
218 | }
219 | // 处理私聊订阅
220 | if (subData.private && Object.keys(subData.private).length > 0) {
221 | messages.push('\n>>>>>>私聊微博订阅<<<<<<');
222 | Object.keys(subData.private).forEach(userId => {
223 | messages.push(`\n<用户${userId}>:`);
224 | subData.private[userId].forEach((item) => {
225 | const types = new Set();
226 | if (item.type && item.type.length) {
227 | item.type.forEach((typeItem) => {
228 | if (typeMap[typeItem]) {
229 | types.add(typeMap[typeItem]);
230 | }
231 | });
232 | }
233 | messages.push(`${item.uid}:${item.name} ${types.size ? `[${Array.from(types).join('、')}]` : ' [全部动态]'}`);
234 | });
235 | });
236 | }
237 | else {
238 | messages.push('\n>>>>>>私聊微博订阅<<<<<<\n当前没有任何私聊订阅数据~');
239 | }
240 | this.e.reply(`推送列表如下:\n${messages.join('\n')}`);
241 | }
242 | }
243 | /** 单独群聊或私聊的订阅的b站推送列表 */
244 | async singelSubDynamicPushList() {
245 | let subData = this.weiboPushData || { group: {}, private: {} };
246 | // 如果聊天ID没有订阅数据,则删除该聊天ID
247 | for (let chatType in subData) {
248 | if (subData.hasOwnProperty(chatType)) {
249 | subData[chatType] = Object.keys(subData[chatType]).reduce((nonEmptyData, chatId) => {
250 | if (subData[chatType][chatId].length > 0) {
251 | nonEmptyData[chatId] = subData[chatType][chatId];
252 | }
253 | return nonEmptyData;
254 | }, {});
255 | }
256 | }
257 | const messages = [];
258 | const typeMap = {
259 | DYNAMIC_TYPE_AV: '视频',
260 | DYNAMIC_TYPE_WORD: '图文',
261 | DYNAMIC_TYPE_DRAW: '图文',
262 | DYNAMIC_TYPE_ARTICLE: '文章',
263 | DYNAMIC_TYPE_FORWARD: '转发'
264 | };
265 | // 根据聊天类型初始化数据
266 | let chatType = this.e.isGroup ? 'group' : 'private';
267 | let chatId = this.e.isGroup ? this.e.group_id : this.e.user_id;
268 | if (!subData[chatType][chatId]) {
269 | subData[chatType][chatId] = [];
270 | }
271 | subData[chatType][chatId].forEach((item) => {
272 | const types = new Set();
273 | if (item.type && item.type.length) {
274 | item.type.forEach((typeItem) => {
275 | if (typeMap[typeItem]) {
276 | types.add(typeMap[typeItem]);
277 | }
278 | });
279 | }
280 | messages.push(`${item.uid}:${item.name} ${types.size ? `[${Array.from(types).join('、')}]` : ' [全部动态]'}`);
281 | });
282 | this.e.reply(`推送列表如下:\n${messages.join('\n')}`);
283 | }
284 | /**通过uid获取up主信息 */
285 | async getWeiboUserInfoByUid() {
286 | let uid = this.e.msg.replace(/^(#|\/)(yuki|优纪)?(微博|weibo|WEIBO)(博|bo|BO)主/g, '').trim();
287 | const res = await new WeiboWebDataFetcher(this.e).getBloggerInfo(uid);
288 | if (res?.statusText !== 'OK') {
289 | this.e.reply('诶嘿,出了点网络问题,等会再试试吧~');
290 | return;
291 | }
292 | const { ok, data } = res.data || {};
293 | if (ok !== 1) {
294 | this.e.reply(`订阅校验失败~\n博主uid:${uid} 可能是无效的,请检查后再试~`);
295 | return true;
296 | }
297 | const userInfo = data.userInfo || {};
298 | let sex = userInfo.gender === 'f' ? '女' : userInfo.gender === 'm' ? '男' : '未知';
299 | const message = [
300 | `-------微博-------`,
301 | `\n博主昵称:${userInfo.screen_name || ''}`,
302 | `\nUID:${userInfo.id || uid}`,
303 | `\n性别:${sex}`,
304 | `\n微博认证:${userInfo.verified_reason || '未认证'}`,
305 | `\n描述:${userInfo.description || ''}`,
306 | `\nsvip等级:${userInfo.svip || ''}`,
307 | `\nvip等级:${userInfo.mbrank || ''}`,
308 | `\n关注:${userInfo.follow_count || ''}`,
309 | `\n粉丝人数:${userInfo.followers_count_str || ''}`
310 | ];
311 | this.e.reply(message);
312 | }
313 | /** 根据昵称搜索博主信息*/
314 | async searchWeiboUserInfoByKeyword() {
315 | let keyword = this.e.msg.replace(/^(#|\/)(yuki|优纪)?搜索(微博|weibo|WEIBO)(博|bo|BO)主/g, '').trim();
316 | const res = await new WeiboWebDataFetcher(this.e).searchBloggerInfo(keyword);
317 | if (res?.statusText !== 'OK') {
318 | this.e.reply('诶嘿,出了点网络问题,等会再试试吧~');
319 | return;
320 | }
321 | const { ok, data } = res.data || {};
322 | const { user, users } = data;
323 | let info = user[0];
324 | let infos = users[0];
325 | let uid = info?.uid;
326 | let id = infos?.id;
327 | let nick = info?.nick;
328 | let screen_name = infos?.screen_name;
329 | let followers_count_str = infos?.followers_count_str;
330 | if (ok !== 1 && !info && !infos) {
331 | this.e.reply('惹~没有搜索到该用户捏,\n请换个关键词试试吧~ \nPS:该方法只能搜索到大V');
332 | return;
333 | }
334 | const messages = [];
335 | messages.push(`-----微博-----
336 | \n博主昵称:${nick || screen_name}
337 | \nUID:${uid || id}
338 | \n粉丝人数:${followers_count_str || ''}`);
339 | this.e.reply(messages.join('\n'));
340 | }
341 | }
342 |
343 | export { YukiWeibo as default };
344 |
--------------------------------------------------------------------------------
/components/dynamic/Account.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LogoText from './LogoText.js';
3 | import { createRequire } from '../../utils/paths.js';
4 |
5 | // Account
6 | // up账户组件
7 | const require = createRequire(import.meta.url);
8 | const Bilibililogo = require('./../../resources/img/icon/dynamic/bilibili.svg');
9 | const Weibilogo = require('./../../resources/img/icon/dynamic/weibo.svg');
10 | const AccountCss = require('./../../resources/css/dynamic/Account.css');
11 | const Account = ({ data }) => {
12 | const renderLogo = (logoSrc, className) => React.createElement("img", { src: logoSrc, className: className, alt: "logo" });
13 | return (React.createElement(React.Fragment, null,
14 | React.createElement("link", { rel: "stylesheet", href: AccountCss }),
15 | React.createElement("div", { className: "account" },
16 | React.createElement("div", { className: "avatar-container" },
17 | React.createElement("img", { src: data.face, alt: "\u5934\u50CF", className: "avatar" }),
18 | data.pendant && React.createElement("img", { className: "pendant", src: data.pendant, alt: "pendant" }),
19 | React.createElement("div", { className: "account-info" },
20 | React.createElement("div", { className: "nickname" }, data.name || ''),
21 | React.createElement("div", { className: "timestamp" }, data.pubTs || ''))),
22 | React.createElement("div", { className: "logo-container" },
23 | data.appName === 'bilibili' && renderLogo(Bilibililogo, 'bilibili-logo'),
24 | data.appName === 'weibo' && renderLogo(Weibilogo, 'weibo-logo'),
25 | React.createElement(LogoText, { data: data })))));
26 | };
27 |
28 | export { Account as default };
29 |
--------------------------------------------------------------------------------
/components/dynamic/Content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRequire } from '../../utils/paths.js';
3 |
4 | // DynamicContent.tsx
5 | const require = createRequire(import.meta.url);
6 | const ContentBoxGrid4Css = require('./../../resources/css/dynamic/Content.box.grid.4.css');
7 | const ContentBoxGrid9Css = require('./../../resources/css/dynamic/Content.box.grid.9.css');
8 | const ContentCss = require('./../../resources/css/dynamic/Content.css');
9 | const Content = ({ data }) => {
10 | const picItems = data.pics && (React.createElement("div", { className: "pic-content" }, data.pics.map((item, index) => {
11 | if (item) {
12 | return (React.createElement("div", { className: "pic-item", key: `${index}_0` },
13 | React.createElement("img", { key: `${index}_1`, src: item?.url, alt: " " })));
14 | }
15 | return null;
16 | })));
17 | const boxGrid_4 = React.createElement("link", { key: "0", rel: "stylesheet", href: ContentBoxGrid4Css });
18 | const boxGrid_9 = React.createElement("link", { key: "0", rel: "stylesheet", href: ContentBoxGrid9Css });
19 | /**动态宫格样式 */
20 | function getBoxGridStyle(pics) {
21 | if (!Array.isArray(pics) || pics.length === 0) {
22 | return null;
23 | }
24 | if (pics.length <= 1) {
25 | return null;
26 | }
27 | if (pics.length === 2) {
28 | for (const item of pics) {
29 | if (item.width === undefined || item.height === undefined) {
30 | continue;
31 | }
32 | if (item.width / item.height <= 0.5) {
33 | return null;
34 | }
35 | }
36 | for (const item of pics) {
37 | if (item.width === undefined) {
38 | continue;
39 | }
40 | if (item.width > 1240) {
41 | return null;
42 | }
43 | }
44 | return boxGrid_4;
45 | }
46 | if (pics.length >= 3) {
47 | for (const item of pics) {
48 | if (item.width === undefined || item.height === undefined) {
49 | continue;
50 | }
51 | if (item.width / item.height <= 0.5) {
52 | return null;
53 | }
54 | }
55 | for (const item of pics) {
56 | if (item.width === undefined) {
57 | continue;
58 | }
59 | if (item.width > 1240) {
60 | return null;
61 | }
62 | }
63 | const maxWidth = Math.max(...pics.map(item => item.width));
64 | if (maxWidth > 550 && maxWidth <= 1240) {
65 | return boxGrid_4;
66 | }
67 | return boxGrid_9;
68 | }
69 | return null;
70 | }
71 | const boxGrid = data.boxGrid && data.pics && getBoxGridStyle(data.pics);
72 | const contentCss = React.createElement("link", { rel: "stylesheet", href: ContentCss });
73 | switch (data.type) {
74 | case 'DYNAMIC_TYPE_LIVE_RCMD':
75 | return (React.createElement(React.Fragment, null,
76 | contentCss,
77 | boxGrid,
78 | React.createElement("div", { className: "content" },
79 | picItems,
80 | data.title && React.createElement("h1", null, data.title))));
81 | case 'DYNAMIC_TYPE_AV':
82 | return (React.createElement(React.Fragment, null,
83 | contentCss,
84 | boxGrid,
85 | React.createElement("div", { className: "content" },
86 | React.createElement("div", { className: "content-text-title", style: { marginBottom: '10px' } }, data.title && React.createElement("h1", null, data.title)),
87 | React.createElement("div", { className: "content-text", dangerouslySetInnerHTML: { __html: data.content || '' } }),
88 | picItems)));
89 | case 'DYNAMIC_TYPE_WORD':
90 | return (React.createElement(React.Fragment, null,
91 | contentCss,
92 | boxGrid,
93 | React.createElement("div", { className: "content" },
94 | React.createElement("div", { className: "content-text", dangerouslySetInnerHTML: { __html: data.content || '' } }),
95 | picItems)));
96 | case 'DYNAMIC_TYPE_DRAW':
97 | return (React.createElement(React.Fragment, null,
98 | contentCss,
99 | boxGrid,
100 | React.createElement("div", { className: "content" },
101 | React.createElement("div", { className: "content-text", dangerouslySetInnerHTML: { __html: data.content || '' } }),
102 | picItems)));
103 | case 'DYNAMIC_TYPE_ARTICLE':
104 | return (React.createElement(React.Fragment, null,
105 | contentCss,
106 | boxGrid,
107 | React.createElement("div", { className: "content" },
108 | React.createElement("div", { className: "content-text-title", style: { marginBottom: '10px' } }, data.title && React.createElement("h1", null, data.title)),
109 | React.createElement("div", { className: "content-text", dangerouslySetInnerHTML: { __html: data.content || '' } }),
110 | picItems)));
111 | default:
112 | return (React.createElement(React.Fragment, null,
113 | contentCss,
114 | boxGrid,
115 | React.createElement("div", { className: "content" },
116 | data.title && React.createElement("h1", null, data.title),
117 | React.createElement("div", { className: "content-text", dangerouslySetInnerHTML: { __html: data.content || '' } }),
118 | picItems)));
119 | }
120 | };
121 |
122 | export { Content as default };
123 |
--------------------------------------------------------------------------------
/components/dynamic/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Config from '../../utils/config.js';
3 | import path from 'path';
4 | import { createRequire, _paths } from '../../utils/paths.js';
5 |
6 | // Footer.tsx
7 | const require = createRequire(import.meta.url);
8 | const botPackageJsonPath = path.join(_paths.root, 'package.json');
9 | const BOT_NAME = Config.getPackageJsonKey('name', botPackageJsonPath);
10 | const botVersion = Config.getPackageJsonKey('version', botPackageJsonPath);
11 | const yukiPluginVersion = Config.getPackageJsonKey('version', path.join(_paths.pluginPath, 'package.json'));
12 | const bilibililogo = require('./../../resources/img/icon/dynamic/bilibili.svg');
13 | const weibilogo = require('./../../resources/img/icon/dynamic/weibo.svg');
14 | const FooterCss = require('./../../resources/css/dynamic/Footer.css');
15 | const Footer = ({ data }) => {
16 | return (React.createElement(React.Fragment, null,
17 | React.createElement("link", { rel: "stylesheet", href: FooterCss }),
18 | React.createElement("div", { className: "footer" },
19 | React.createElement("div", { className: "footer-text-container" },
20 | data.appName === 'bilibili' && (React.createElement("svg", { className: "w-32 h-10 bili-logo-0", style: { width: '8rem', height: '2.5rem' } },
21 | React.createElement("image", { href: bilibililogo }))),
22 | data.appName === 'weibo' && (React.createElement("svg", { className: "h-12 weibo-logo-0", style: { height: '3rem' } },
23 | React.createElement("image", { href: weibilogo, width: "55", height: "55" }))),
24 | React.createElement("div", { className: "qr-code-massage", style: { marginTop: '-4px' } },
25 | "\u8BC6\u522B\u4E8C\u7EF4\u7801\uFF0C\u67E5\u770B\u5B8C\u6574",
26 | data.category),
27 | React.createElement("div", { className: "creatde-time", style: { marginTop: '6px', color: '#a46e8a' } },
28 | "\u56FE\u7247\u751F\u6210\u4E8E\uFF1A",
29 | data.created || ''),
30 | React.createElement("div", { className: "bot-plugin-info", style: { marginTop: '6px' } },
31 | "Created By ",
32 | `${BOT_NAME}-v` + `${botVersion}`,
33 | " & ",
34 | React.createElement("span", { className: "yuki-plugin-text-title" }, "yuki-plugin"),
35 | "-v",
36 | React.createElement("span", { className: "italic" }, yukiPluginVersion))),
37 | React.createElement("img", { src: data.urlImgData, alt: "\u4E8C\u7EF4\u7801", className: "qr-code" }))));
38 | };
39 |
40 | export { Footer as default };
41 |
--------------------------------------------------------------------------------
/components/dynamic/ForwardContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Account from './Account.js';
3 | import Content from './Content.js';
4 | import { createRequire } from '../../utils/paths.js';
5 |
6 | // ForwardContent
7 | // 转发动态内容组件
8 | const require = createRequire(import.meta.url);
9 | const ForwardContentCss = require('./../../resources/css/dynamic/ForwardContent.css');
10 | const ForwardContent = ({ data }) => (React.createElement(React.Fragment, null,
11 | React.createElement("link", { rel: "stylesheet", href: ForwardContentCss }),
12 | React.createElement("div", { className: "orig" },
13 | React.createElement("div", { className: "orig-container", id: "orig-container" },
14 | React.createElement(Account, { data: data }),
15 | React.createElement(Content, { data: data })))));
16 |
17 | export { ForwardContent as default };
18 |
--------------------------------------------------------------------------------
/components/dynamic/LogoText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRequire } from '../../utils/paths.js';
3 |
4 | // LogoText
5 | // Logo 文本组件
6 | const require = createRequire(import.meta.url);
7 | const LogoTextCss = require('./../../resources/css/dynamic/LogoText.css');
8 | const LogoText = ({ data }) => (React.createElement(React.Fragment, null,
9 | React.createElement("link", { rel: "stylesheet", href: LogoTextCss }),
10 | data.appName === 'bilibili' && React.createElement("div", { className: "bilibili-logo-text" }, data.category),
11 | data.appName === 'weibo' && React.createElement("div", { className: "weibo-logo-text" }, data.category)));
12 |
13 | export { LogoText as default };
14 |
--------------------------------------------------------------------------------
/components/dynamic/MainPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Account from './Account.js';
3 | import Content from './Content.js';
4 | import ForwardContent from './ForwardContent.js';
5 | import Footer from './Footer.js';
6 | import { createRequire } from '../../utils/paths.js';
7 |
8 | // MainPage.tsx
9 | const require = createRequire(import.meta.url);
10 | const MainPageCss = require('./../../resources/css/dynamic/MainPage.css');
11 | function App({ data }) {
12 | return (React.createElement(React.Fragment, null,
13 | React.createElement("link", { rel: "stylesheet", href: MainPageCss }),
14 | React.createElement("div", { className: "outside-border" },
15 | React.createElement("div", { className: "container" },
16 | React.createElement(Account, { data: data }),
17 | React.createElement("div", { className: "dynamic-article-page-main unfold" },
18 | React.createElement(Content, { data: data }),
19 | data.orig && (React.createElement(React.Fragment, null,
20 | React.createElement(ForwardContent, { data: data.orig.data })))),
21 | React.createElement(Footer, { data: data })))));
22 | }
23 |
24 | export { App as default };
25 |
--------------------------------------------------------------------------------
/components/help/Help.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Config from '../../utils/config.js';
3 | import path from 'path';
4 | import { createRequire, _paths } from '../../utils/paths.js';
5 |
6 | //help.tsx
7 | const require = createRequire(import.meta.url);
8 | const botPackageJsonPath = path.join(_paths.root, 'package.json');
9 | const BOT_NAME = Config.getPackageJsonKey('name', botPackageJsonPath);
10 | const botVersion = Config.getPackageJsonKey('version', botPackageJsonPath);
11 | const yukiPluginVersion = Config.getPackageJsonKey('version', path.join(_paths.pluginPath, 'package.json'));
12 | const HelpCss = require('./../../resources/css/help/help.css');
13 | const iconPath = (iconName) => require(`./../../resources/img/icon/puplic/${iconName}.png`);
14 | function App({ data }) {
15 | return (React.createElement(React.Fragment, null,
16 | React.createElement("link", { rel: "stylesheet", href: HelpCss }),
17 | React.createElement("div", { className: "container", id: "container" },
18 | React.createElement("div", { className: "head_box" },
19 | React.createElement("div", { className: "id_text" }, "Yuki-Plugin"),
20 | React.createElement("h2", { className: "day_text" },
21 | "\u4F7F\u7528\u8BF4\u660E-v",
22 | yukiPluginVersion)),
23 | data.map((val, index) => (React.createElement("div", { className: "data_box", key: index },
24 | React.createElement("div", { className: "tab_lable" }, val.group),
25 | React.createElement("div", { className: "list" }, val.list.map((item, itemIndex) => (React.createElement("div", { className: "item", key: itemIndex },
26 | React.createElement("img", { className: "icon", src: iconPath(item.icon), alt: item.title }),
27 | React.createElement("div", { className: "title" },
28 | React.createElement("div", { className: "text" }, item.title),
29 | React.createElement("div", { className: "dec" }, item.desc))))))))),
30 | React.createElement("div", { className: "logo", style: { marginTop: '6px' } },
31 | "Created By ",
32 | `${BOT_NAME}-v` + `${botVersion}`,
33 | " & ",
34 | React.createElement("span", { className: "yuki-plugin-text-title" }, "yuki-plugin"),
35 | "-v",
36 | React.createElement("span", { className: "italic" }, yukiPluginVersion)))));
37 | }
38 |
39 | export { App as default };
40 |
--------------------------------------------------------------------------------
/components/index.js:
--------------------------------------------------------------------------------
1 | const MainPage = (await import('./dynamic/MainPage.js')).default;
2 | const Help = (await import('./help/Help.js')).default;
3 | const LoginQrcodePage = (await import('./loginQrcode/Page.js')).default;
4 | const Version = (await import('./version/Version.js')).default;
5 |
6 | export { Help, LoginQrcodePage, MainPage, Version };
7 |
--------------------------------------------------------------------------------
/components/loginQrcode/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRequire } from '../../utils/paths.js';
3 |
4 | // QrcodeLoginPage.tsx
5 | const require = createRequire(import.meta.url);
6 | const LoginQrcodeCss = require('./../../resources/css/loginQrcode/Page.css');
7 | function App({ data }) {
8 | return (React.createElement(React.Fragment, null,
9 | React.createElement("link", { rel: "stylesheet", href: LoginQrcodeCss }),
10 | React.createElement("div", { className: "container w-96 max-h-96 m-auto text-lg p-5" },
11 | React.createElement("div", { className: "txt-0 text-center mt-3 mb-3 p-1 text-blue-500" },
12 | "Created By yuki-plugin",
13 | React.createElement("br", null),
14 | "\u626B\u7801\u767B\u5F55B\u7AD9\u83B7\u53D6CK"),
15 | React.createElement("div", { className: "QrCode m-auto" },
16 | React.createElement("img", { className: "qr-code w-72 h-72 ml-7", src: data.url, alt: "\u4E8C\u7EF4\u7801" })),
17 | React.createElement("div", { className: "txt-1 text-center mt-3 mb-3 p-1 text-red-600" },
18 | "\u514D\u8D23\u58F0\u660E\uFF1Abot\u4EC5\u63D0\u4F9B\u529F\u80FD\u3002",
19 | React.createElement("br", null),
20 | "\u5982\u679C\u4E0D\u826F\u4F7F\u7528\u884C\u4E3A\u5BFC\u81F4\u8D26\u53F7\u51FA\u95EE\u9898\u7684\u8BF7\u81EA\u884C\u627F\u62C5\u540E\u679C\u3002"))));
21 | }
22 |
23 | export { App as default };
24 |
--------------------------------------------------------------------------------
/components/version/Version.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Config from '../../utils/config.js';
3 | import path from 'path';
4 | import { createRequire, _paths } from '../../utils/paths.js';
5 |
6 | const require = createRequire(import.meta.url);
7 | const botPackageJsonPath = path.join(_paths.root, 'package.json');
8 | const BOT_NAME = Config.getPackageJsonKey('name', botPackageJsonPath);
9 | const botVersion = Config.getPackageJsonKey('version', botPackageJsonPath);
10 | const yukiPluginVersion = Config.getPackageJsonKey('version', path.join(_paths.pluginPath, 'package.json'));
11 | const VersionCss = require('./../../resources/css/version/version.css');
12 | function App({ data }) {
13 | return (React.createElement(React.Fragment, null,
14 | React.createElement("link", { rel: "stylesheet", href: VersionCss }),
15 | React.createElement("div", { className: "container", id: "container" },
16 | data.map((item, idx) => (React.createElement("div", { key: idx, className: "version-card" },
17 | React.createElement("div", { className: "title" },
18 | item.version,
19 | idx ? '' : ' - 当前版本'),
20 | React.createElement("div", { className: "content" },
21 | React.createElement("ul", null, item.data.map((sub, subIdx) => (React.createElement("li", { key: subIdx, dangerouslySetInnerHTML: { __html: sub } })))))))),
22 | React.createElement("div", { className: "logo", style: { marginTop: '6px' } },
23 | "Created By ",
24 | `${BOT_NAME}-v` + `${botVersion}`,
25 | " & ",
26 | React.createElement("span", { className: "yuki-plugin-text-title" }, "yuki-plugin"),
27 | "-v",
28 | React.createElement("span", { className: "italic" }, yukiPluginVersion)))));
29 | }
30 |
31 | export { App as default };
32 |
--------------------------------------------------------------------------------
/defaultConfig/bilibili/config.yaml:
--------------------------------------------------------------------------------
1 | # b站推送,1 开启 0 关闭,保留添加的相关数据,但是不再推送
2 | pushStatus: 1
3 |
4 | # 检测b站动态的冷却时间 CD,Cron表达式,作用域共6位,具体方法浏览器搜索 “node-schedule cron表达式”,
5 | # 示例:
6 | # "*/15 * * * *" #每15min检测一次
7 | # "*/31 * * * *" #每31min检测一次
8 | # "0 5,35 * * * *" #每小时固定第5分0秒、第35分0秒检测一次,共2次/h
9 | # "0 5,35,51 * * * *" #每小时固定第5分0秒、第35分0秒、第51分0秒检测一次,共3次/h
10 | # ❀动态发布通常习惯整点发布,触发检测时比发布时间点稍晚几分钟即可,基本可以命中。
11 | # ❀请勿设置周期过短比如小于10分钟,以免触发风控。
12 | checkDynamicCD: '*/23 * * * *'
13 |
14 | # 请求头 User-Agent 列表。如出现 -352 风控,可尝试更换请求头,请根据需要自行添加或修改。
15 | # 可设置多个请求头,每次重启后会随机选择一个。获取方法请浏览器自行搜索。
16 | userAgentList:
17 | - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
18 | #- Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
19 | #- Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0
20 |
21 | # 筛选何时发布的动态,单位为秒,默认7200秒即2小时,即以当前时间为基准,筛选过去2小时内发布的动态,并推送。
22 | # 取值范围:3600-36000秒,即过去的1-10h。应大于checkDynamicCD的周期。
23 | dynamicTimeRange: 7200
24 |
25 | # 顺序检测相邻up主的动态并获取数据的最大随机间隔时间,单位为毫秒,默认 8000,即 8000 毫秒(8秒),
26 | # 即获取该up的动态数据后,随机等待2(内置值)-8秒后再获取下一位up的动态数据。取值范围:4000 ≦ x < checkDynamicCD的周期,单位为毫秒。
27 | # 该数值大小影响风控概率, 请谨慎调整,建议不要设置过小,否则可能被风控导致动态获取失败。
28 | getDataRandomDelay: 8000
29 |
30 | # 全部订阅的转发动态是否推送: 默认 1 - 开启推送, 0 - 关闭推送。
31 | # 如果仅仅需要关闭单个订阅的转发动态推送,使用分类订阅指令不包含 转发 分类即可,无需修改此配置。
32 | pushTransmit: 1
33 |
34 | # 推送文字和图文动态时,限制发送多少张图片
35 | pushPicCountLimit: 3
36 |
37 | # 推送文字和图文动态时,限制字数是多少
38 | pushContentLenLimit: 100
39 |
40 | # 推送文字和图文动态时,限制多少行文本
41 | pushContentLineLimit: 5
42 |
43 | # 是否展示定时任务的日志,0 不显示 1 显示
44 | pushTaskLog: 1
45 |
46 | # 白名单关键词,命中即推送,不在白名单则不推送。
47 | # 白名单与黑名单共同起作用,即命中白名单但不命中黑名单即推送,不在白名单或命中黑名单则不推送。
48 | # 白名单为空则不启用白名单功能。
49 | # 配置示例:
50 | # whiteWordslist:
51 | # - 白名单关键词1
52 | # - 白名单关键词2
53 | whiteWordslist:
54 |
55 | # 包含关键词不推送
56 | banWords:
57 | - 关键词1
58 | - 关键词2
59 |
60 | # 设置B站动态消息模式 0 文字模式 1 图片模式
61 | pushMsgMode: 1
62 |
63 | # 动态过长(>300字)或条数过多(>2条)时是否以转发形式发送动态,默认 1 开启,0 关闭。
64 | forwardSendDynamic: 1
65 |
66 | # 文字模式时,文字消息与图片附件是否合并在一起发送,默认 1 合并,0 不合并。
67 | # 如果合并时图片过多导致发送失败,可设置为 0 单独发送图片。
68 | mergeTextPic: 1
69 |
70 | # 是否启用九宫格样式:默认 1 启用,0 不启用。
71 | # 此为最高优先级,九宫格为动态模式,特定大小/长宽比的图片资源将会动态启用九宫格/四宫格/无宫格样式。
72 | boxGrid: 1
73 |
74 | # B站动态卡片分片截图模式:默认 1 启用 0 不启用。
75 | # 启用,将会推送每条动态的全部内容;不启用,动态内容过长时候将只推送noSplitHeight长度的动态卡片,需关闭宫格模式。
76 | isSplit: 1
77 |
78 | # 动态卡片非分片模式下的截图高度,默认7500px(仅填数字,无需填入单位),请勿设置过大或过小。关闭分片截图时生效。
79 | noSplitHeight: 7500
80 |
81 | # 动态卡片分页截图高度,默认8000px(仅填数字,无需填入单位),请勿设置过大或过小。启用分片截图时生效。
82 | splitHeight: 8000
83 |
84 | # 直播动态是否@全体成员,默认 0 关闭,1 开启。
85 | # 开启前请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持],某些聊天平台或类型不支持@全体成员,如qq官方机器人等。
86 | liveAtAll: 0
87 |
88 | # 直播动态@全体成员的群组/聊天/私聊列表,默认为空即不在任何群于推送直播动态中执行@全体成员。开启liveAtAll后才会生效。
89 | liveAtAllGroupList:
90 | - 1234567890 # 示例群号
91 |
92 | # 直播动态@全体成员的共享冷却时间CD,单位秒,默认 1800 秒(30分钟),
93 | # 即每个群聊30分钟内不论多少条直播动态,只会@一次。注意,qq群有 @全体成员 10次/日 的限制,所以请合理设置。
94 | liveAtAllCD: 1800
95 |
96 | # 直播动态@全体成员失败时是否发送错误消息,默认 1 发送,0 不发送。开启liveAtAll后才会生效。
97 | liveAtAllErrMsg: 1
98 |
99 | #视频链接解析开关 默认 1 开启 0 关闭
100 | parseVideoLink: 1
101 |
--------------------------------------------------------------------------------
/defaultConfig/bilibili/push.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/defaultConfig/bilibili/push.yaml
--------------------------------------------------------------------------------
/defaultConfig/help/help.yaml:
--------------------------------------------------------------------------------
1 | - group: 指令前缀
2 | list:
3 | - icon: diagram
4 | title: '#优纪 #yuki'
5 | desc: '示例:#优纪B站订阅列表 #yuki订阅B站推送UID'
6 | - icon: diagram
7 | title: '/优纪 /yuki'
8 | desc: '示例:/优纪B站订阅列表 /yuki订阅B站推送UID'
9 | - group: B站功能
10 | list:
11 | - icon: diagram
12 | title: '#订阅B站推送UID'
13 | desc: '#订阅B站推送(视频|文章|图文|直播|转发)UID'
14 | - icon: tourbillon_device
15 | title: '#取消B站推送UID'
16 | desc: '#取消B站推送(视频|文章|图文|直播|转发)UID'
17 | - icon: restaurant_smoothie
18 | title: '#B站全部订阅列表'
19 | desc: '查看本Bot所有的B站订阅列表'
20 | - icon: archaic_stone
21 | title: '#B站订阅列表'
22 | desc: '查看 本群/私聊 添加的B站订阅列表'
23 | - icon: kamera
24 | title: '#执行B站任务'
25 | desc: '手动触发定时推送任务'
26 | - icon: flower_2
27 | title: '#B站up主UID'
28 | desc: '通过uid查看up信息'
29 | - icon: shell
30 | title: '#搜索B站up主原神'
31 | desc: '用昵称在b站搜索up信息'
32 | - icon: 雷神瞳共鸣石
33 | title: '#扫码B站登录'
34 | desc: 'app扫码获取登录ck'
35 | - icon: everamber
36 | title: '#取消B站登陆'
37 | desc: '删除扫码获取的B站CK'
38 | - icon: condessence_crystal
39 | title: '#我的B站登录'
40 | desc: '查看app扫码登录的信息和状态'
41 | - icon: romaritime_flower
42 | title: '#绑定B站本地ck: ***'
43 | desc: '配置本地获取的B站CK'
44 | - icon: unfading_silky_grace
45 | title: '#删除B站本地ck'
46 | desc: '删除手动获取的B站ck'
47 | - icon: delightful_encounter
48 | title: '#我的B站ck'
49 | desc: '查看当前启用的B站ck,仅限私聊'
50 | - icon: wondrous_lovely_flower
51 | title: '#刷新B站临时ck'
52 | desc: '刷新自动获取的临时B站cookie'
53 | - group: 微博功能
54 | list:
55 | - icon: diagram
56 | title: '#订阅微博推送UID'
57 | desc: '#订阅微博推送(视频|文章|图文|转发)UID'
58 | - icon: tourbillon_device
59 | title: '#取消微博推送UID'
60 | desc: '#取消微博推送(视频|文章|图文|转发)UID'
61 | - icon: restaurant_smoothie
62 | title: '#微博全部订阅列表'
63 | desc: '查看本Bot所有的微博订阅列表'
64 | - icon: archaic_stone
65 | title: '#微博订阅列表'
66 | desc: '查看 本群/私聊 添加的微博订阅列表'
67 | - icon: kamera
68 | title: '#执行微博任务'
69 | desc: '手动触发定时推送任务'
70 | - icon: flower_2
71 | title: '#微博博主UID'
72 | desc: '通过uid查看博主信息'
73 | - icon: shell
74 | title: '#搜索微博博主原神'
75 | desc: '根据关键词在微博搜索大V博主的信息'
76 | - group: 其他指令
77 | list:
78 | - icon: 风车
79 | title: '#优纪版本'
80 | desc: 查看版本信息
81 | - icon: 钓鱼
82 | title: '#更新yuki-plugin'
83 | desc: '系统指令更新yuki插件,yunzaiJS需安装`@yunzaijs/system`'
84 | - icon: pluie_lotus
85 | title: '#强制更新yuki-plugin'
86 | desc: '强制更新yuki插件,yunzaiJS需安装`@yunzaijs/system`'
87 |
--------------------------------------------------------------------------------
/defaultConfig/weibo/config.yaml:
--------------------------------------------------------------------------------
1 | # 微博推送,1 开启 0 关闭,保留添加的相关数据,但是不再推送
2 | pushStatus: 1
3 |
4 | # 检测微博动态的冷却时间 CD,Cron表达式,作用域共6位,具体方法浏览器搜索 “node-schedule cron表达式”,
5 | # 示例:
6 | # "*/15 * * * *" #每15min检测一次
7 | # "*/31 * * * *" #每31min检测一次
8 | # "0 5,35 * * * *" #每小时固定第5分0秒、第35分0秒检测一次,共2次/h
9 | # "0 5,35,51 * * * *" #每小时固定第5分0秒、第35分0秒、第51分0秒检测一次,共3次/h
10 | # ❀动态发布通常习惯整点发布,触发检测时比发布时间点稍晚几分钟即可,基本可以命中。
11 | # ❀请勿设置周期过短比如小于10分钟,以免触发访问限制。
12 | checkDynamicCD: '*/23 * * * *'
13 |
14 | # 请求头 User-Agent 列表。请根据需要自行添加或修改。
15 | # 可设置多个请求头,每次重启后会随机选择一个。获取方法请浏览器自行搜索。
16 | userAgentList:
17 | - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
18 | #- Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
19 | #- Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0
20 |
21 | # 筛选何时发布的动态,单位为秒,默认7200秒即2小时,即以当前时间为基准,筛选过去2小时内发布的动态,并推送。
22 | # 取值范围:3600-36000秒,即过去的1-10h。应大于checkDynamicCD的周期。
23 | dynamicTimeRange: 7200
24 |
25 | # 全部订阅的转发动态是否推送: 默认 1 - 开启推送, 0 - 关闭推送。
26 | # 如果仅仅需要关闭单个订阅的转发动态推送,使用分类订阅指令不包含 转发 分类即可,无需修改此配置。
27 | pushTransmit: 1
28 |
29 | # 推送动态时,限制发送多少张图片
30 | pushPicCountLimit: 3
31 |
32 | # 推送文字和图文动态时,限制字数是多少
33 | pushContentLenLimit: 100
34 |
35 | # 推送文字和图文动态时,限制多少行文本
36 | pushContentLineLimit: 5
37 |
38 | # 是否展示定时任务的日志,0 不显示 1 显示
39 | pushTaskLog: 1
40 |
41 | # 白名单关键词,命中即推送,不在白名单则不推送。
42 | # 白名单与黑名单共同起作用,即命中白名单但不命中黑名单即推送,不在白名单或命中黑名单则不推送。
43 | # 白名单为空则不启用白名单功能。
44 | # 配置示例:
45 | # whiteWordslist:
46 | # - 白名单关键词1
47 | # - 白名单关键词2
48 | whiteWordslist:
49 |
50 | # 包含关键词不推送
51 | banWords:
52 | - 关键词1
53 | - 关键词2
54 |
55 | # 设置微博动态消息模式 0 文字模式 1 图片模式
56 | pushMsgMode: 1
57 |
58 | # 动态过长(>300字)或条数过多(>2条)时是否以转发形式发送动态,默认 1 开启,0 关闭。
59 | forwardSendDynamic: 1
60 |
61 | # 文字模式时,文字消息与图片附件是否合并在一起发送,默认 1 合并,0 不合并。
62 | # 如果合并时图片过多导致发送失败,可设置为 0 单独发送图片。
63 | mergeTextPic: 1
64 |
65 | # 是否启用九宫格样式:默认 1 启用,0 不启用。
66 | # 此为最高优先级,九宫格为动态模式,特定大小/长宽比的图片资源将会动态启用九宫格/四宫格/无宫格样式。
67 | boxGrid: 1
68 |
69 | # 微博动态卡片分片截图模式:默认 1 启用 0 不启用。
70 | # 启用,将会推送每条动态的全部内容;不启用,动态内容过长时候将只推送noSplitHeight长度的动态卡片,需关闭宫格模式。
71 | isSplit: 1
72 |
73 | # 动态卡片非分片模式下的截图高度,默认7500px(仅填数字,无需填入单位),请勿设置过大或过小。关闭分片截图时生效。
74 | noSplitHeight: 7500
75 |
76 | # 动态卡片分页截图高度,默认8000px(仅填数字,无需填入单位),请勿设置过大或过小。启用分片截图时生效。
77 | splitHeight: 8000
78 |
--------------------------------------------------------------------------------
/defaultConfig/weibo/push.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/defaultConfig/weibo/push.yaml
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import Config from './utils/config.js';
3 | import path from 'path';
4 | import { _paths } from './utils/paths.js';
5 | import YukiBili from './apps/bilibili.js';
6 | import YukiHelp from './apps/help.js';
7 | import YukiVersion from './apps/version.js';
8 | import YukiWeibo from './apps/weibo.js';
9 |
10 | const yukiPluginVersion = Config.getPackageJsonKey('version', path.join(_paths.pluginPath, 'package.json'));
11 | if (!global.segment) {
12 | try {
13 | global.segment = (await import('oicq')).segment;
14 | }
15 | catch (err) {
16 | global.segment = (await import('icqq')).segment;
17 | }
18 | }
19 | let apps = { YukiBili, YukiHelp, YukiVersion, YukiWeibo };
20 | let rules = {};
21 | let count = 0;
22 | for (let key in apps) {
23 | if (!apps[key]) {
24 | logger.error(`载入插件错误:${[key]}`);
25 | continue;
26 | }
27 | rules[`${key}`] = apps[key];
28 | count++;
29 | }
30 | logger.info(chalk.rgb(0, 190, 255)(`-----------------------------------------`));
31 | logger.info(chalk.rgb(255, 225, 255)(`|优纪插件 ${yukiPluginVersion} 初始化~`));
32 | logger.info(chalk.rgb(255, 245, 255)(`|作者:snowtafir`));
33 | logger.info(chalk.rgb(255, 225, 255)(`|仓库地址:`));
34 | logger.info(chalk.rgb(255, 245, 255)(`|https://github.com/snowtafir/yuki-plugin`));
35 | logger.info(chalk.rgb(0, 190, 255)(`-----------------------------------------`));
36 | logger.info(chalk.rgb(0, 190, 255)(`★ 优纪插件加载完成,共计加载${count}个app`));
37 |
38 | export { rules as apps };
39 |
--------------------------------------------------------------------------------
/models/bilibili/bilibili.main.get.web.data.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import lodash from 'lodash';
3 | import BiliApi from './bilibili.main.api.js';
4 | import { readSyncCookie, cookieWithBiliTicket, readSavedCookieItems, readSavedCookieOtherItems } from './bilibili.main.models.js';
5 | import { getWbiSign } from './bilibili.risk.wbi.js';
6 | import { getDmImg } from './bilibili.risk.dm.img.js';
7 |
8 | class BilibiliWebDataFetcher {
9 | constructor(e) { }
10 | /**通过uid获取up动态数据表*/
11 | async getBiliDynamicListDataByUid(uid) {
12 | const url = BiliApi.BILIBIL_API.biliDynamicInfoList;
13 | let { cookie } = await readSyncCookie();
14 | cookie = await cookieWithBiliTicket(cookie);
15 | const dmImg = await getDmImg();
16 | const data = {
17 | 'offset': '',
18 | 'host_mid': uid,
19 | 'timezone_offset': -480,
20 | 'platform': 'web',
21 | 'features': 'itemOpusStyle,listOnlyfans,opusBigCover,onlyfansVote,decorationCard,forwardListHidden,ugcDelete,onlyfansQaCard',
22 | 'web_location': '333.999',
23 | ...dmImg,
24 | 'x-bili-device-req-json': { platform: 'web', device: 'pc' },
25 | 'x-bili-web-req-json': { spm_id: '333.999' }
26 | };
27 | let signCookie = (await readSavedCookieItems(cookie, ['SESSDATA'], false)) || (await readSavedCookieOtherItems(cookie, ['SESSDATA']));
28 | const { w_rid, time_stamp } = await getWbiSign(data, BiliApi.BILIBILI_HEADERS, signCookie);
29 | const params = {
30 | ...data,
31 | w_rid: w_rid,
32 | wts: time_stamp
33 | };
34 | const res = await axios(url, {
35 | method: 'GET',
36 | params,
37 | timeout: 15000,
38 | headers: lodash.merge(BiliApi.BILIBILI_HEADERS, {
39 | Cookie: `${cookie}`,
40 | Host: `api.bilibili.com`,
41 | Origin: 'https://space.bilibili.com',
42 | Referer: `https://space.bilibili.com/${uid}/dynamic`
43 | })
44 | });
45 | return res;
46 | }
47 | /**通过uid获取up详情*/
48 | async getBilibiUserInfoByUid(uid) {
49 | const url = BiliApi.BILIBIL_API.biliSpaceUserInfoWbi;
50 | let { cookie } = await readSyncCookie();
51 | cookie = await cookieWithBiliTicket(cookie);
52 | const dmImg = await getDmImg();
53 | const data = {
54 | mid: uid,
55 | token: '',
56 | platform: 'web',
57 | web_location: 1550101,
58 | ...dmImg
59 | };
60 | let signCookie = (await readSavedCookieItems(cookie, ['SESSDATA'], false)) || (await readSavedCookieOtherItems(cookie, ['SESSDATA']));
61 | const { w_rid, time_stamp } = await getWbiSign(data, BiliApi.BILIBILI_HEADERS, signCookie);
62 | const params = {
63 | ...data,
64 | w_rid: w_rid,
65 | wts: time_stamp
66 | };
67 | const res = await axios(url, {
68 | method: 'GET',
69 | params,
70 | timeout: 10000,
71 | headers: lodash.merge(BiliApi.BILIBILI_HEADERS, {
72 | Cookie: `${cookie}`,
73 | Host: `api.bilibili.com`,
74 | Origin: 'https://space.bilibili.com',
75 | Referer: `https://space.bilibili.com/${uid}/dynamic`
76 | })
77 | });
78 | return res;
79 | }
80 | /**通过关键词搜索up*/
81 | async searchBiliUserInfoByKeyword(keyword) {
82 | const url = BiliApi.BILIBIL_API.biliSearchUpWbi;
83 | let { cookie } = await readSyncCookie();
84 | cookie = await cookieWithBiliTicket(cookie);
85 | const data = {
86 | keyword: keyword,
87 | page: 1,
88 | search_type: 'bili_user',
89 | order: 'totalrank'
90 | };
91 | let signCookie = (await readSavedCookieItems(cookie, ['SESSDATA'], false)) || (await readSavedCookieOtherItems(cookie, ['SESSDATA']));
92 | const { w_rid, time_stamp } = await getWbiSign(data, BiliApi.BILIBILI_HEADERS, signCookie);
93 | const params = {
94 | ...data,
95 | w_rid: w_rid,
96 | wts: time_stamp
97 | };
98 | const res = await axios(url, {
99 | method: 'GET',
100 | params,
101 | timeout: 10000,
102 | headers: lodash.merge(BiliApi.BILIBILI_HEADERS, {
103 | Cookie: `${cookie}`,
104 | Host: `api.bilibili.com`,
105 | Origin: 'https://www.bilibili.com',
106 | Referer: `https://www.bilibili.com/`
107 | })
108 | });
109 | return res;
110 | }
111 | /*通过aid/bvid获取视频信息*/
112 | async getBiliVideoInfoByAid_BV(vedioID) {
113 | const url = BiliApi.BILIBIL_API.biliVideoInfoWbi;
114 | let { cookie } = await readSyncCookie();
115 | cookie = await cookieWithBiliTicket(cookie);
116 | let referer = vedioID?.bvid ? `https://www.bilibili.com/video/${vedioID.bvid}` : `https://www.bilibili.com/video/av${vedioID.aid}`;
117 | let data = vedioID?.bvid ? { bvid: vedioID.bvid } : { aid: vedioID.aid };
118 | let signCookie = (await readSavedCookieItems(cookie, ['SESSDATA'], false)) || (await readSavedCookieOtherItems(cookie, ['SESSDATA']));
119 | const { w_rid, time_stamp } = await getWbiSign(data, BiliApi.BILIBILI_HEADERS, signCookie);
120 | const params = {
121 | ...data,
122 | w_rid: w_rid,
123 | wts: time_stamp
124 | };
125 | const res = await axios(url, {
126 | method: 'GET',
127 | params,
128 | timeout: 5000,
129 | headers: lodash.merge(BiliApi.BILIBILI_HEADERS, {
130 | Cookie: `${cookie}`,
131 | Host: `api.bilibili.com`,
132 | Origin: 'https://www.bilibili.com',
133 | Referer: referer
134 | })
135 | });
136 | return res;
137 | }
138 | /*通过视频短链url获取bvid*/
139 | async getBVIDByShortUrl(tvUrlID) {
140 | const ShortVideoUrlApi = BiliApi.BILIBIL_API.biliShortVideoUrl;
141 | const url = `${ShortVideoUrlApi}${tvUrlID}`;
142 | let { cookie } = await readSyncCookie();
143 | cookie = await cookieWithBiliTicket(cookie);
144 | const res = await axios(url, {
145 | method: 'GET',
146 | timeout: 5000,
147 | headers: lodash.merge(BiliApi.BILIBILI_DYNAMIC_SPACE_HEADERS, {
148 | Cookie: `${cookie}`
149 | })
150 | });
151 | const htmlContent = await res.data;
152 | const htmlContentRegex = /itemprop="url"\s*content="https:\/\/www.bilibili.com\/video\/(BV[a-zA-Z0-9]+)\/">/;
153 | const BVID = htmlContent.match(htmlContentRegex)?.[1];
154 | return `${BVID}`;
155 | }
156 | }
157 |
158 | export { BilibiliWebDataFetcher };
159 |
--------------------------------------------------------------------------------
/models/bilibili/bilibili.risk.buid.fp.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Contains code from open-source projects:
3 | * MurmurHash3 by Karan Lyons (https://github.com/karanlyons/murmurHash3.js)
4 | */
5 | class MurmurHash3 {
6 | // x64Add 函数:将两个64位整数相加
7 | //
8 | // Given two 64bit ints (as an array of two 32bit ints) returns the two
9 | // added together as a 64bit int (as an array of two 32bit ints).
10 | //
11 | static x64Add = function (m, n) {
12 | m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff];
13 | n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff];
14 | var o = [0, 0, 0, 0];
15 | o[3] += m[3] + n[3];
16 | o[2] += o[3] >>> 16;
17 | o[3] &= 0xffff;
18 | o[2] += m[2] + n[2];
19 | o[1] += o[2] >>> 16;
20 | o[2] &= 0xffff;
21 | o[1] += m[1] + n[1];
22 | o[0] += o[1] >>> 16;
23 | o[1] &= 0xffff;
24 | o[0] += m[0] + n[0];
25 | o[0] &= 0xffff;
26 | return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]];
27 | };
28 | // x64Multiply 函数:将两个64位整数相乘
29 | //
30 | // Given two 64bit ints (as an array of two 32bit ints) returns the two
31 | // multiplied together as a 64bit int (as an array of two 32bit ints).
32 | //
33 | static x64Multiply = function (m, n) {
34 | m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff];
35 | n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff];
36 | var o = [0, 0, 0, 0];
37 | o[3] += m[3] * n[3];
38 | o[2] += o[3] >>> 16;
39 | o[3] &= 0xffff;
40 | o[2] += m[2] * n[3];
41 | o[1] += o[2] >>> 16;
42 | o[2] &= 0xffff;
43 | o[2] += m[3] * n[2];
44 | o[1] += o[2] >>> 16;
45 | o[2] &= 0xffff;
46 | o[1] += m[1] * n[3];
47 | o[0] += o[1] >>> 16;
48 | o[1] &= 0xffff;
49 | o[1] += m[2] * n[2];
50 | o[0] += o[1] >>> 16;
51 | o[1] &= 0xffff;
52 | o[1] += m[3] * n[1];
53 | o[0] += o[1] >>> 16;
54 | o[1] &= 0xffff;
55 | o[0] += m[0] * n[3] + m[1] * n[2] + m[2] * n[1] + m[3] * n[0];
56 | o[0] &= 0xffff;
57 | return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]];
58 | };
59 | // x64Rotl 函数:将给定64位整数左移
60 | //
61 | // Given a 64bit int (as an array of two 32bit ints) and an int
62 | // representing a number of bit positions, returns the 64bit int (as an
63 | // array of two 32bit ints) rotated left by that number of positions.
64 | //
65 | static x64Rotl = function (m, n) {
66 | n %= 64;
67 | if (n === 32) {
68 | return [m[1], m[0]];
69 | }
70 | else if (n < 32) {
71 | return [(m[0] << n) | (m[1] >>> (32 - n)), (m[1] << n) | (m[0] >>> (32 - n))];
72 | }
73 | else {
74 | n -= 32;
75 | return [(m[1] << n) | (m[0] >>> (32 - n)), (m[0] << n) | (m[1] >>> (32 - n))];
76 | }
77 | };
78 | // x64LeftShift 函数:将64位整数左移
79 | //
80 | // Given a 64bit int (as an array of two 32bit ints) and an int
81 | // representing a number of bit positions, returns the 64bit int (as an
82 | // array of two 32bit ints) shifted left by that number of positions.
83 | //
84 | static x64LeftShift = function (m, n) {
85 | n %= 64;
86 | if (n === 0) {
87 | return m;
88 | }
89 | else if (n < 32) {
90 | return [(m[0] << n) | (m[1] >>> (32 - n)), m[1] << n];
91 | }
92 | else {
93 | return [m[1] << (n - 32), 0];
94 | }
95 | };
96 | // x64Xor 函数:返回两个64位整数的异或结果
97 | //
98 | // Given two 64bit ints (as an array of two 32bit ints) returns the two
99 | // xored together as a 64bit int (as an array of two 32bit ints).
100 | //
101 | static x64Xor = function (m, n) {
102 | return [m[0] ^ n[0], m[1] ^ n[1]];
103 | };
104 | // x64Fmix 函数:MurmurHash3的最终混合步骤
105 | //
106 | // Given a block, returns murmurHash3's final x64 mix of that block.
107 | // (`[0, h[0] >>> 1]` is a 33 bit unsigned right shift. This is the
108 | // only place where we need to right shift 64bit ints.)
109 | //
110 | static x64Fmix = function (h) {
111 | h = MurmurHash3.x64Xor(h, [0, h[0] >>> 1]);
112 | h = MurmurHash3.x64Multiply(h, [0xff51afd7, 0xed558ccd]);
113 | h = MurmurHash3.x64Xor(h, [0, h[0] >>> 1]);
114 | h = MurmurHash3.x64Multiply(h, [0xc4ceb9fe, 0x1a85ec53]);
115 | h = MurmurHash3.x64Xor(h, [0, h[0] >>> 1]);
116 | return h;
117 | };
118 | // x64hash128 函数:生成128位哈希
119 | //
120 | // Given a string and an optional seed as an int, returns a 128 bit
121 | // hash using the x64 flavor of MurmurHash3, as an unsigned hex.
122 | //
123 | static x64hash128 = function (key, seed) {
124 | key = key || '';
125 | seed = seed || 0;
126 | var remainder = key.length % 16;
127 | var bytes = key.length - remainder;
128 | var h1 = [0, seed];
129 | var h2 = [0, seed];
130 | var k1 = [0, 0];
131 | var k2 = [0, 0];
132 | var c1 = [0x87c37b91, 0x114253d5];
133 | var c2 = [0x4cf5ad43, 0x2745937f];
134 | for (var i = 0; i < bytes; i += 16) {
135 | k1 = [
136 | (key.charCodeAt(i + 4) & 0xff) |
137 | ((key.charCodeAt(i + 5) & 0xff) << 8) |
138 | ((key.charCodeAt(i + 6) & 0xff) << 16) |
139 | ((key.charCodeAt(i + 7) & 0xff) << 24),
140 | (key.charCodeAt(i) & 0xff) | ((key.charCodeAt(i + 1) & 0xff) << 8) | ((key.charCodeAt(i + 2) & 0xff) << 16) | ((key.charCodeAt(i + 3) & 0xff) << 24)
141 | ];
142 | k2 = [
143 | (key.charCodeAt(i + 12) & 0xff) |
144 | ((key.charCodeAt(i + 13) & 0xff) << 8) |
145 | ((key.charCodeAt(i + 14) & 0xff) << 16) |
146 | ((key.charCodeAt(i + 15) & 0xff) << 24),
147 | (key.charCodeAt(i + 8) & 0xff) |
148 | ((key.charCodeAt(i + 9) & 0xff) << 8) |
149 | ((key.charCodeAt(i + 10) & 0xff) << 16) |
150 | ((key.charCodeAt(i + 11) & 0xff) << 24)
151 | ];
152 | // 处理 k1 和 k2
153 | k1 = MurmurHash3.x64Multiply(k1, c1);
154 | k1 = MurmurHash3.x64Rotl(k1, 31);
155 | k1 = MurmurHash3.x64Multiply(k1, c2);
156 | h1 = MurmurHash3.x64Xor(h1, k1);
157 | h1 = MurmurHash3.x64Rotl(h1, 27);
158 | h1 = MurmurHash3.x64Add(h1, h2);
159 | h1 = MurmurHash3.x64Add(MurmurHash3.x64Multiply(h1, [0, 5]), [0, 0x52dce729]);
160 | k2 = MurmurHash3.x64Multiply(k2, c2);
161 | k2 = MurmurHash3.x64Rotl(k2, 33);
162 | k2 = MurmurHash3.x64Multiply(k2, c1);
163 | h2 = MurmurHash3.x64Xor(h2, k2);
164 | h2 = MurmurHash3.x64Rotl(h2, 31);
165 | h2 = MurmurHash3.x64Add(h2, h1);
166 | h2 = MurmurHash3.x64Add(MurmurHash3.x64Multiply(h2, [0, 5]), [0, 0x38495ab5]);
167 | }
168 | k1 = [0, 0];
169 | k2 = [0, 0];
170 | switch (remainder) {
171 | case 15:
172 | k2 = MurmurHash3.x64Xor(k2, MurmurHash3.x64LeftShift([0, String(key).charCodeAt(i + 14)], 48));
173 | // fallthrough
174 | case 14:
175 | k2 = MurmurHash3.x64Xor(k2, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 13)], 40));
176 | // fallthrough
177 | case 13:
178 | k2 = MurmurHash3.x64Xor(k2, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 12)], 32));
179 | // fallthrough
180 | case 12:
181 | k2 = MurmurHash3.x64Xor(k2, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 11)], 24));
182 | // fallthrough
183 | case 11:
184 | k2 = MurmurHash3.x64Xor(k2, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 10)], 16));
185 | // fallthrough
186 | case 10:
187 | k2 = MurmurHash3.x64Xor(k2, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 9)], 8));
188 | // fallthrough
189 | case 9:
190 | k2 = MurmurHash3.x64Xor(k2, [0, key.charCodeAt(i + 8)]);
191 | k2 = MurmurHash3.x64Multiply(k2, c2);
192 | k2 = MurmurHash3.x64Rotl(k2, 33);
193 | k2 = MurmurHash3.x64Multiply(k2, c1);
194 | h2 = MurmurHash3.x64Xor(h2, k2);
195 | // fallthrough
196 | case 8:
197 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 7)], 56));
198 | // fallthrough
199 | case 7:
200 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 6)], 48));
201 | // fallthrough
202 | case 6:
203 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 5)], 40));
204 | // fallthrough
205 | case 5:
206 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 4)], 32));
207 | // fallthrough
208 | case 4:
209 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 3)], 24));
210 | // fallthrough
211 | case 3:
212 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 2)], 16));
213 | // fallthrough
214 | case 2:
215 | k1 = MurmurHash3.x64Xor(k1, MurmurHash3.x64LeftShift([0, key.charCodeAt(i + 1)], 8));
216 | // fallthrough
217 | case 1:
218 | k1 = MurmurHash3.x64Xor(k1, [0, key.charCodeAt(i)]);
219 | k1 = MurmurHash3.x64Multiply(k1, c1);
220 | k1 = MurmurHash3.x64Rotl(k1, 31);
221 | k1 = MurmurHash3.x64Multiply(k1, c2);
222 | h1 = MurmurHash3.x64Xor(h1, k1);
223 | // fallthrough
224 | }
225 | h1 = MurmurHash3.x64Xor(h1, [0, key.length]);
226 | h2 = MurmurHash3.x64Xor(h2, [0, key.length]);
227 | h1 = MurmurHash3.x64Add(h1, h2);
228 | h2 = MurmurHash3.x64Add(h2, h1);
229 | h1 = MurmurHash3.x64Fmix(h1);
230 | h2 = MurmurHash3.x64Fmix(h2);
231 | h1 = MurmurHash3.x64Add(h1, h2);
232 | h2 = MurmurHash3.x64Add(h2, h1);
233 | return (('00000000' + (h1[0] >>> 0).toString(16)).slice(-8) +
234 | ('00000000' + (h1[1] >>> 0).toString(16)).slice(-8) +
235 | ('00000000' + (h2[0] >>> 0).toString(16)).slice(-8) +
236 | ('00000000' + (h2[1] >>> 0).toString(16)).slice(-8));
237 | };
238 | }
239 | function gen_buvid_fp(browserData) {
240 | // 将所有自定义数据整合
241 | const components = [
242 | { key: 'userAgent', value: browserData.userAgent },
243 | { key: 'webdriver', value: browserData.webdriver }, // WebDriver 信息
244 | { key: 'language', value: browserData.language },
245 | { key: 'colorDepth', value: browserData.colorDepth },
246 | { key: 'deviceMemory', value: browserData.deviceMemory },
247 | { key: 'pixelRatio', value: browserData.pixelRatio }, // 设备像素比
248 | { key: 'hardwareConcurrency', value: browserData.hardwareConcurrency },
249 | { key: 'screenResolution', value: browserData.screenResolution }, // 屏幕分辨率
250 | { key: 'availableScreenResolution', value: browserData.availableScreenResolution }, // 可用屏幕分辨率
251 | { key: 'timezoneOffset', value: browserData.timezoneOffset }, // 时区偏移
252 | { key: 'timezone', value: browserData.timezone },
253 | { key: 'sessionStorage', value: browserData.sessionStorage ? 1 : 0 }, // sessionStorage 支持
254 | { key: 'localStorage', value: browserData.localStorage ? 1 : 0 }, // localStorage 支持
255 | { key: 'indexedDb', value: browserData.indexedDb ? 1 : 0 }, // IndexedDB 支持
256 | { key: 'addBehavior', value: browserData.addBehavior ? 1 : 0 }, // addBehavior 支持
257 | { key: 'openDatabase', value: browserData.openDatabase ? 1 : 0 }, // openDatabase 支持
258 | { key: 'cpuClass', value: browserData.cpuClass },
259 | { key: 'platform', value: browserData.platform },
260 | { key: 'doNotTrack', value: browserData.doNotTrack },
261 | { key: 'plugins', value: browserData.plugins.map(p => p.name).join(',') }, // 插件名称
262 | { key: 'canvas', value: browserData.canvas },
263 | { key: 'webgl', value: browserData.webgl },
264 | { key: 'webglVendorAndRenderer', value: browserData.webglVendorAndRenderer },
265 | { key: 'adBlock', value: browserData.adBlock ? 1 : 0 }, // 是否存在广告拦截器
266 | { key: 'hasLiedLanguages', value: browserData.hasLiedLanguages ? 1 : 0 },
267 | { key: 'hasLiedResolution', value: browserData.hasLiedResolution ? 1 : 0 },
268 | { key: 'hasLiedOs', value: browserData.hasLiedOs ? 1 : 0 },
269 | { key: 'hasLiedBrowser', value: browserData.hasLiedBrowser ? 1 : 0 },
270 | { key: 'touchSupport', value: browserData.touchSupport }, // 支持的触摸点数
271 | { key: 'fonts', value: browserData.fonts.map(f => f.replace(/\s+/g, '')).join(',') }, // 字体列表
272 | { key: 'fontsFlash', value: browserData.hasLiedOs ? browserData.fonts.map(f => f.replace(/\s+/g, '')).join(',') : 'flash not installed' },
273 | { key: 'audio', value: browserData.audio }, // 音频指纹
274 | { key: 'enumerateDevices', value: browserData.enumerateDevices.map(f => f.replace(/\s+/g, '')).join(',') }
275 | ];
276 | const values = components.map(component => component.value).join('~~~');
277 | // 使用 MurmurHash3 计算指纹
278 | const fingerprint = MurmurHash3.x64hash128(values, 31); // 调用之前定义的 x64hash128 函数
279 | return fingerprint;
280 | }
281 |
282 | export { gen_buvid_fp };
283 |
--------------------------------------------------------------------------------
/models/bilibili/bilibili.risk.dm.img.js:
--------------------------------------------------------------------------------
1 | /**获取dm参数 */
2 | async function getDmImg() {
3 | const dm_img_list = `[]`;
4 | //Buffer.from("WebGL 1", 'utf-8').toString("base64") //webgl version的值 WebGL 1 的base64 编码
5 | const dm_img_str = 'V2ViR0wgMS';
6 | //webgl unmasked renderer的值拼接webgl unmasked vendor的值的base64编码
7 | const dm_cover_img_str = 'QU5HTEUgKEludGVsLCBJbnRlbChSKSBIRCBHcmFwaGljcyBEaXJlY3QzRDExIHZzXzVfMCBwc181XzApLCBvciBzaW1pbGFyR29vZ2xlIEluYy4gKEludGVsKQ';
8 | const dm_img_inter = `{ds:[],wh:[0,0,0],of:[0,0,0]}`;
9 | return {
10 | dm_img_list: dm_img_list,
11 | dm_img_str: dm_img_str,
12 | dm_cover_img_str: dm_cover_img_str,
13 | dm_img_inter: dm_img_inter
14 | };
15 | }
16 |
17 | export { getDmImg };
18 |
--------------------------------------------------------------------------------
/models/bilibili/bilibili.risk.ticket.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import { createHmac } from 'crypto';
3 | import BiliApi from './bilibili.main.api.js';
4 |
5 | /**
6 | * Generate HMAC-SHA256 signature
7 | * @param {string} key The key string to use for the HMAC-SHA256 hash
8 | * @param {string} message The message string to hash
9 | * @returns {string} The HMAC-SHA256 signature as a hex string
10 | */
11 | function hmacSha256(key, message) {
12 | return createHmac('sha256', key).update(message).digest('hex');
13 | }
14 | /**
15 | * Get Bilibili web ticket
16 | * @param {string | null} csrf CSRF token, can be empty or null, or the cookie's bili_jct value
17 | * @returns {Promise<{ code: number, ticket: string, created_at: number, ttl: number }>}
18 | * Promise that resolves to an object containing code, ticket, created_at, and ttl values
19 | */
20 | async function getBiliTicket(csrf) {
21 | const ts = Math.floor(Date.now() / 1000);
22 | const hexSign = hmacSha256('XgwSnGZ1p', `ts${ts}`);
23 | const url = 'https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket';
24 | const params = new URLSearchParams({
25 | 'key_id': 'ec02',
26 | 'hexsign': hexSign,
27 | 'context[ts]': String(ts),
28 | 'csrf': csrf ?? '' // 使用空字符串代替null
29 | });
30 | try {
31 | const response = await fetch(`${url}?${params}`, {
32 | method: 'POST',
33 | headers: {
34 | 'User-Agent': BiliApi.BILIBILI_HEADERS['User-Agent']
35 | }
36 | });
37 | if (!response.ok) {
38 | throw new Error(`get bili_jct HTTP error! status: ${response.status}`);
39 | }
40 | const data = await response.json();
41 | if (data.code !== 0) {
42 | if (data.code === 400) {
43 | throw new Error(`get bili_jct Parameter error! ${data.message}`);
44 | }
45 | throw new Error(`Failed to retrieve bili ticket: ${data.message}`);
46 | }
47 | // 返回所需的对象结构
48 | return {
49 | code: data.code,
50 | ticket: data.data?.ticket,
51 | created_at: data.data?.created_at,
52 | ttl: data.data?.ttl
53 | };
54 | }
55 | catch (error) {
56 | throw new Error(`Failed to fetch Bilibili ticket: ${error instanceof Error ? error.message : String(error)}`);
57 | }
58 | }
59 |
60 | export { getBiliTicket };
61 |
--------------------------------------------------------------------------------
/models/bilibili/bilibili.risk.wbi.js:
--------------------------------------------------------------------------------
1 | import md5 from 'md5';
2 | import fetch from 'node-fetch';
3 | import BiliApi from './bilibili.main.api.js';
4 |
5 | const mixinKeyEncTab = [
6 | 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26,
7 | 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52
8 | ];
9 | // 对 imgKey 和 subKey 进行字符顺序打乱编码
10 | const getMixinKey = (orig) => mixinKeyEncTab
11 | .map(n => orig[n])
12 | .join('')
13 | .slice(0, 32);
14 | // 为请求参数进行 wbi 签名
15 | function encWbi(params, img_key, sub_key) {
16 | const mixin_key = getMixinKey(img_key + sub_key), curr_time = Math.round(Date.now() / 1000), chr_filter = /[!'()*]/g;
17 | Object.assign(params, { wts: curr_time }); // 添加 wts 字段
18 | // 按照 key 重排参数
19 | const query = Object.keys(params)
20 | .sort()
21 | .map(key => {
22 | // 过滤 value 中的 "!'()*" 字符
23 | const value = params[key].toString().replace(chr_filter, '');
24 | return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
25 | })
26 | .join('&');
27 | const wbi_sign = md5(query + mixin_key); // 计算 w_rid
28 | //return query + "&w_rid=" + wbi_sign;
29 | return {
30 | query: query,
31 | w_rid: wbi_sign,
32 | time_stamp: curr_time
33 | };
34 | }
35 | // 获取最新的 img_key 和 sub_key
36 | async function getWbiKeys(headers, cookie) {
37 | const IMG_SUB_KEY = 'Yz:yuki:bili:wbi_img_key';
38 | const wbi_img_data = await redis.get(IMG_SUB_KEY);
39 | if (wbi_img_data) {
40 | const wbi_img_data_json = JSON.parse(wbi_img_data);
41 | return {
42 | img_key: wbi_img_data_json.img_key,
43 | sub_key: wbi_img_data_json.sub_key
44 | };
45 | }
46 | else {
47 | const res = await fetch('https://api.bilibili.com/x/web-interface/nav', {
48 | headers: {
49 | // SESSDATA 字段
50 | 'Cookie': cookie,
51 | 'User-Agent': headers['User-Agent'],
52 | 'Referer': 'https://www.bilibili.com/' //对于直接浏览器调用可能不适用
53 | }
54 | });
55 | const { data: { wbi_img: { img_url, sub_url } } } = (await res.json());
56 | const wbi_img_data = {
57 | img_key: img_url.slice(img_url.lastIndexOf('/') + 1, img_url.lastIndexOf('.')),
58 | sub_key: sub_url.slice(sub_url.lastIndexOf('/') + 1, sub_url.lastIndexOf('.'))
59 | };
60 | const { microtime } = await getTimeStamp(); // 获取的 microtime 已经是北京时间(毫秒)
61 | // 创建当前北京时间的 Date 对象
62 | const current_zh_cn_Time = new Date(microtime);
63 | // 打印当前北京时间
64 | console.log(`当前北京时间: ${current_zh_cn_Time}`);
65 | // 创建明天0点的时间对象
66 | const tomorrow = new Date(current_zh_cn_Time);
67 | tomorrow.setHours(0, 0, 0, 0); // 设置明天的0点
68 | tomorrow.setDate(tomorrow.getDate() + 1); // 日期加1,表示明天
69 | // 计算剩余秒数
70 | const secondsUntilTomorrow = Math.floor((tomorrow.getTime() - current_zh_cn_Time.getTime()) / 1000);
71 | console.log(`距离明天还剩: ${secondsUntilTomorrow} 秒`);
72 | await redis.set(IMG_SUB_KEY, JSON.stringify(wbi_img_data), { EX: secondsUntilTomorrow - 2 }); // 设置缓存,过期时间为第二天0点前2秒
73 | return wbi_img_data;
74 | }
75 | }
76 | /**获取适用于 RTC 的时间戳*/
77 | async function getTimeStamp() {
78 | const res = await fetch(BiliApi.BILIBIL_API.biliServerTimeStamp, {
79 | method: 'GET',
80 | headers: {
81 | 'Host': 'api.live.bilibili.com',
82 | 'User-Agent': `${BiliApi.USER_AGENT}`
83 | }
84 | });
85 | const { data: { timestamp, microtime } } = (await res.json());
86 | return {
87 | timestamp: timestamp,
88 | microtime: microtime
89 | };
90 | }
91 | /**
92 | * https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md#javascript
93 | * 对实际请求参数进行 wbi 签名, 生成 wbi 签名
94 | * @param {object} params 除了 wbi 签名外的全部请求参数,例如 api get请求的查询参数 { uid: 12345678, jsonp: jsonp}
95 | * @param {object} headers 必需要 referer 和 UA 两个请求头
96 | */
97 | async function getWbiSign(params, headers, cookie) {
98 | const { img_key, sub_key } = await getWbiKeys(headers, cookie);
99 | return encWbi(params, img_key, sub_key);
100 | }
101 |
102 | export { getWbiSign };
103 |
--------------------------------------------------------------------------------
/models/help/help.js:
--------------------------------------------------------------------------------
1 | import Config from '../../utils/config.js';
2 |
3 | //import { EventType } from 'yunzai';
4 | class Help {
5 | e;
6 | model;
7 | constructor(e) {
8 | this.model = 'help';
9 | this.e = e;
10 | }
11 | static async get(e) {
12 | let helpData = new Help(e);
13 | return await helpData.getData();
14 | }
15 | async getData() {
16 | let helpData = Config.getDefaultConfig('help', 'help');
17 | return helpData;
18 | }
19 | }
20 |
21 | export { Help as default };
22 |
--------------------------------------------------------------------------------
/models/version/version.js:
--------------------------------------------------------------------------------
1 | import fs__default from 'fs';
2 | import path from 'path';
3 | import { _paths } from '../../utils/paths.js';
4 |
5 | class VersionData {
6 | model;
7 | cache;
8 | versionPath;
9 | constructor() {
10 | this.model = 'versionData';
11 | this.cache = {};
12 | this.versionPath = path.resolve(_paths.pluginPath, 'CHANGELOG.md');
13 | }
14 | /**
15 | * CHANGELOG.md内容支持示例:
16 | * # 1.0.0
17 | * * 新增功能3
18 | * * 新增功能4
19 | *
20 | * # 0.1.0
21 | * * 新增功能1
22 | * * 新增功能2
23 | */
24 | async getChangelogContent() {
25 | let key = this.model;
26 | if (this.cache[key])
27 | return this.cache[key];
28 | let content = fs__default.readFileSync(this.versionPath, 'utf8');
29 | let lines = content.split('\n');
30 | let result = [];
31 | let currentVersion = null;
32 | let currentData = [];
33 | lines.forEach((line) => {
34 | line = line.trim();
35 | if (line.startsWith('# ')) {
36 | if (currentVersion) {
37 | result.push({
38 | version: currentVersion,
39 | data: currentData
40 | });
41 | }
42 | currentVersion = line.slice(2).trim();
43 | currentData = [];
44 | }
45 | else if (line.startsWith('* ')) {
46 | currentData.push(line.slice(2).trim());
47 | }
48 | });
49 | if (currentVersion) {
50 | result.push({
51 | version: currentVersion,
52 | data: currentData
53 | });
54 | }
55 | // 对版本进行排序并截取最新的5个版本
56 | result.sort((a, b) => {
57 | let aParts = a.version.split('.').map(Number);
58 | let bParts = b.version.split('.').map(Number);
59 | for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
60 | let aPart = aParts[i] || 0;
61 | let bPart = bParts[i] || 0;
62 | if (aPart !== bPart) {
63 | return bPart - aPart;
64 | }
65 | }
66 | return 0;
67 | });
68 | this.cache[key] = result.slice(0, 5);
69 | return this.cache[key];
70 | }
71 | }
72 |
73 | export { VersionData as default };
74 |
--------------------------------------------------------------------------------
/models/weibo/weibo.main.api.js:
--------------------------------------------------------------------------------
1 | import Config from '../../utils/config.js';
2 |
3 | class WeiboApi {
4 | weiboConfigData;
5 | USER_AGENT;
6 | constructor() {
7 | this.weiboConfigData = Config.getUserConfig('weibo', 'config');
8 | this.USER_AGENT = WeiboApi.WEIBO_USER_AGENT;
9 | this.initialize();
10 | }
11 | static WEIBO_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36';
12 | //初始化User-Agent
13 | async initialize() {
14 | await this.initUserAgent();
15 | }
16 | async initUserAgent() {
17 | const userAgentList = await this.weiboConfigData.userAgentList;
18 | if (userAgentList && userAgentList.length > 0) {
19 | const randomIndex = Math.floor(Math.random() * userAgentList.length);
20 | this.USER_AGENT = String(userAgentList[randomIndex]);
21 | }
22 | }
23 | get WEIBO_API() {
24 | return {
25 | weiboGetIndex: 'https://m.weibo.cn/api/container/getIndex',
26 | //通过关键词${upKeyword}搜索博主 parama = { q: 'Keyword'},
27 | weiboAjaxSearch: 'https://weibo.com/ajax/side/search'
28 | };
29 | }
30 | /**统一设置header */
31 | get WEIBO_HEADERS() {
32 | return {
33 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
34 | 'Accept-language': 'zh-CN,zh;q=0.9',
35 | 'Authority': 'm.weibo.cn',
36 | 'Cache-control': 'max-age=0',
37 | 'Sec-fetch-dest': 'empty',
38 | 'Sec-fetch-mode': 'same-origin',
39 | 'Sec-fetch-site': 'same-origin',
40 | 'Upgrade-insecure-requests': '1',
41 | 'User-agent': this.USER_AGENT
42 | };
43 | }
44 | }
45 | var WeiboApi$1 = new WeiboApi();
46 |
47 | export { WeiboApi$1 as default };
48 |
--------------------------------------------------------------------------------
/models/weibo/weibo.main.get.web.data.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import WeiboApi from './weibo.main.api.js';
3 | import { WeiboQuery } from './weibo.main.query.js';
4 |
5 | class WeiboWebDataFetcher {
6 | e;
7 | constructor(e) { }
8 | /**通过uid获取博主信息 */
9 | async getBloggerInfo(target) {
10 | const param = { containerid: '100505' + target };
11 | const url = new URL(WeiboApi.WEIBO_API.weiboGetIndex);
12 | url.search = new URLSearchParams(param).toString();
13 | const resp = await axios(url.toString(), {
14 | method: 'GET',
15 | timeout: 10000,
16 | headers: { 'accept': '*/*', 'Content-Type': 'application/json', 'referer': 'https://m.weibo.cn' }
17 | });
18 | return resp;
19 | }
20 | /**通过关键词搜索微博大v */
21 | async searchBloggerInfo(keyword) {
22 | const url = WeiboApi.WEIBO_API.weiboAjaxSearch;
23 | const params = {
24 | q: keyword
25 | };
26 | const resp = await axios(url, {
27 | method: 'GET',
28 | timeout: 10000,
29 | params,
30 | headers: { 'accept': '*/*', 'Content-Type': 'application/json', 'referer': 'https://s.weibo.com' }
31 | });
32 | return resp;
33 | }
34 | /**获取主页动态资源相关数组 */
35 | async getBloggerDynamicList(target) {
36 | const params = { containerid: '107603' + target };
37 | const url = new URL(WeiboApi.WEIBO_API.weiboGetIndex);
38 | url.search = new URLSearchParams(params).toString();
39 | await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * (6500 - 1000 + 1) + 1000)));
40 | try {
41 | const response = await axios(url.toString(), {
42 | method: 'GET',
43 | timeout: 10000,
44 | headers: { 'accept': '*/*', 'Content-Type': 'application/json', 'referer': 'https://m.weibo.cn' }
45 | });
46 | const { ok, data, msg } = response?.data;
47 | if (!ok && msg !== '这里还没有内容') {
48 | throw new Error(response.config.url);
49 | }
50 | return data.cards.filter(WeiboQuery.filterCardTypeCustom);
51 | }
52 | catch (error) {
53 | (logger ?? Bot.logger)?.mark('微博推送:Error fetching sub list:', error);
54 | return [];
55 | }
56 | }
57 | }
58 |
59 | export { WeiboWebDataFetcher };
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yuki-plugin",
3 | "version": "2.0.7-20",
4 | "author": "snowtafir",
5 | "description": "优纪插件,yunzai-V4 关于 微博推送、B站推送 等功能的拓展插件",
6 | "main": "./index",
7 | "license": "MIT",
8 | "type": "module",
9 | "scripts": {
10 | "build": "rollup --config ./rollup.config.js",
11 | "img-dev": "npx tsx watch --clear-screen=false jsxp.server.ts",
12 | "render-test": "npx tsx ./render.test.ts",
13 | "css-app": "tailwindcss -i ./input.css -o ./public/output.css",
14 | "css-dev": "tailwindcss -i ./input.css -o ./public/output.css --watch",
15 | "css-m": "tailwindcss -i ./input.css -o ./public/output.css -m",
16 | "format": "prettier --write .",
17 | "check-format": "git diff --exit-code",
18 | "prepare": "husky"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://gitee.com/snowtafir/yuki-plugin"
23 | },
24 | "dependencies": {
25 | "axios": "^1.7.9",
26 | "chokidar": "^4.0.1",
27 | "jsdom": "^25.0.1",
28 | "jsxp": "1.2.1",
29 | "lodash": "^4.17.21",
30 | "md5": "^2.3.0",
31 | "moment": "^2.30.1",
32 | "node-fetch": "^3.3.2",
33 | "puppeteer": "^24.8.2",
34 | "qrcode": "^1.5.4",
35 | "react": "^18.3.1",
36 | "react-dom": "^18.3.1",
37 | "redis": "^4.7.0",
38 | "yaml": "^2.6.1",
39 | "json5": "^2.2.3"
40 | },
41 | "devDependencies": {
42 | "@types/chokidar": "2.1.7",
43 | "@types/node-fetch": "^2.6.11",
44 | "@rollup/plugin-commonjs": "^26.0.1",
45 | "@rollup/plugin-image": "^3.0.3",
46 | "@rollup/plugin-json": "^6.1.0",
47 | "@rollup/plugin-node-resolve": "^15.2.3",
48 | "@rollup/plugin-replace": "^5.0.7",
49 | "@rollup/plugin-terser": "^0.4.4",
50 | "@rollup/plugin-typescript": "^11.1.6",
51 | "@types/rollup-plugin-auto-external": "^2.0.5",
52 | "@types/jsdom": "^21.1.7",
53 | "@types/md5": "^2.3.5",
54 | "@types/node": "^22.2.0",
55 | "@types/qrcode": "^1.5.5",
56 | "@types/react": "^18.3.3",
57 | "@types/react-dom": "^18.3.0",
58 | "@types/yaml": "1.9.7",
59 | "axios": "^1.7.9",
60 | "chokidar": "^4.0.1",
61 | "husky": "^9.1.6",
62 | "jsxp": "1.2.1",
63 | "jsdom": "^25.0.1",
64 | "lodash": "^4.17.21",
65 | "md5": "^2.3.0",
66 | "node-fetch": "^3.3.2",
67 | "pm2": "^5.4.2",
68 | "prettier": "^3.4.2",
69 | "puppeteer": "^24.8.2",
70 | "qrcode": "^1.5.4",
71 | "react": "^18.3.1",
72 | "react-dom": "^18.3.1",
73 | "redis": "^4.7.0",
74 | "rollup": "^4.20.0",
75 | "rollup-plugin-auto-external": "^2.0.0",
76 | "rollup-plugin-copy": "^3.5.0",
77 | "rollup-plugin-dts": "^6.1.1",
78 | "rollup-plugin-ignore": "^1.0.10",
79 | "typescript": "^5.6.3",
80 | "yaml": "^2.6.1",
81 | "tsx": "4.19.2",
82 | "json5": "^2.2.3"
83 | },
84 | "engines": {
85 | "node": ">=20"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/resources/css/dynamic/Account.css:
--------------------------------------------------------------------------------
1 | .account {
2 | position: relative;
3 | display: flex;
4 | margin-bottom: 20px;
5 | padding: 20px;
6 | border-bottom-width: 1px;
7 | border-bottom-color: #ffcee6;
8 | border-bottom-style: dashed;
9 | align-items: center;
10 | justify-content: space-between;
11 | }
12 |
13 | .avatar-container {
14 | position: relative;
15 | display: flex;
16 | align-items: center;
17 | margin-left: 10px;
18 | }
19 |
20 | .avatar {
21 | width: 95px;
22 | height: 95px;
23 | border-radius: 50%;
24 | margin-right: 10px;
25 | }
26 |
27 | .pendant {
28 | width: 160px;
29 | height: 160px;
30 | position: absolute;
31 | top: -32px;
32 | left: -32px;
33 | z-index: 1;
34 | }
35 |
36 | .account-info {
37 | display: flex;
38 | flex-direction: column;
39 | margin-left: 10px;
40 | }
41 |
42 | .nickname {
43 | font-size: 25px;
44 | font-weight: bold;
45 | color: #333;
46 | margin-left: 10px;
47 | }
48 |
49 | .timestamp {
50 | font-size: 16px;
51 | color: #666;
52 | margin-top: 5px;
53 | margin-left: 10px;
54 | }
55 |
56 | .logo-container {
57 | display: flex;
58 | flex-direction: column;
59 | }
60 |
61 | .bilibili-logo,
62 | .weibo-logo {
63 | margin-left: 10px;
64 | }
65 |
66 | .weibo-logo {
67 | max-width: 55px;
68 | }
69 |
--------------------------------------------------------------------------------
/resources/css/dynamic/Content.box.grid.4.css:
--------------------------------------------------------------------------------
1 | /*自适应四宫格控制*/
2 | .pic-item {
3 | flex: 1 0 calc(50% - 10px);
4 | }
5 |
--------------------------------------------------------------------------------
/resources/css/dynamic/Content.box.grid.9.css:
--------------------------------------------------------------------------------
1 | /*自适应九宫格控制*/
2 | .pic-item {
3 | flex: 1 0 calc(33.3333% - 10px);
4 | }
5 |
--------------------------------------------------------------------------------
/resources/css/dynamic/Content.css:
--------------------------------------------------------------------------------
1 | .content-text-title {
2 | margin-top: 10px;
3 | }
4 |
5 | .content {
6 | font-size: 20px;
7 | color: #333;
8 | margin-bottom: 20px;
9 | padding-left: 20px;
10 | padding-right: 20px;
11 | }
12 |
13 | .content img {
14 | max-width: 100%;
15 | height: auto;
16 | border-radius: 8px;
17 | }
18 |
19 | /*自适应九宫格*/
20 | .pic-content {
21 | margin-top: 15px;
22 | width: 100%;
23 | display: flex;
24 | flex-wrap: wrap;
25 | }
26 |
27 | .pic-item {
28 | /* flex: 1 0 calc(33.3333% - 10px); */
29 | margin: 2px;
30 | background-size: contain;
31 | background-repeat: no-repeat;
32 | background-position: center center;
33 | }
34 |
35 | .pic-item:after {
36 | display: block;
37 | padding-bottom: 100%;
38 | }
39 |
40 | /******/
41 |
42 | /**微博content图标*/
43 | .url-icon img {
44 | left: 10px;
45 | position: absolute;
46 | visibility: hidden;
47 | }
48 |
49 | .bili-rich-text-module,
50 | .surl-text {
51 | color: #008ac5;
52 | }
53 |
54 | /* 定义B站互动抽奖标签的样式 */
55 | .bili-rich-text-module.lottery {
56 | display: inline-block;
57 | padding-left: 20px;
58 | /* 为图标留出空间 */
59 | position: relative;
60 | background-repeat: no-repeat;
61 | background-position: left center;
62 | background-size: 20px 20px;
63 | /* 根据你的SVG图标大小调整 */
64 | background-image: url('./../../img/icon/dynamic/bili-rich-text-module-lottery.svg');
65 | /* 引入SVG图标 */
66 | }
67 |
68 | /* 定义B站淘宝商品推广标签样式 */
69 | .bili-rich-text-module.goods.taobao {
70 | display: inline-block;
71 | padding-left: 20px;
72 | /* 为图标留出空间 */
73 | position: relative;
74 | background-repeat: no-repeat;
75 | background-position: left center;
76 | background-size: 20px 20px;
77 | /* 根据你的SVG图标大小调整 */
78 | background-image: url('./../../img/icon/dynamic/bili-rich-text-module-goods-taobao.svg');
79 | /* 引入SVG图标 */
80 | }
81 |
82 | /* 定义B站投票标签样式 */
83 | .bili-dyn-content__orig__additional {
84 | margin-top: 12px;
85 | }
86 |
87 | .bili-dyn-card-vote {
88 | background-color: rgba(246, 247, 248, 0.8);
89 | border-radius: 6px;
90 | box-sizing: border-box;
91 | cursor: pointer;
92 | display: flex;
93 | height: 78px;
94 | height: 100%;
95 | overflow: hidden;
96 | border: 1px solid #ff69b4;
97 | }
98 |
99 | .bili-dyn-card-vote__header {
100 | flex-shrink: 0;
101 | padding: 25px;
102 | width: 78px;
103 | background-color: rgb(255, 255, 255);
104 | }
105 |
106 | .bili-dyn-card-vote__cover {
107 | align-items: center;
108 | background-color: #ffffff;
109 | border-radius: 4px;
110 | color: #008ac5;
111 | display: flex;
112 | height: 100%;
113 | justify-content: center;
114 | width: 100%;
115 | /* 引入SVG图标 */
116 | background-image: url('./../../img/icon/dynamic/bili-dyn-card-vote__cover.svg');
117 | }
118 |
119 | .bili-dyn-card-vote__body,
120 | .bili-dyn-card-vote__detail {
121 | display: flex;
122 | flex: 1;
123 | overflow: hidden;
124 | }
125 |
126 | .bili-dyn-card-vote__detail {
127 | box-sizing: border-box;
128 | flex-direction: column;
129 | padding: 19px 16px 15px 20px;
130 | }
131 |
132 | .bili-dyn-card-vote__detail__title {
133 | color: #18191c;
134 | font-size: 14px;
135 | line-height: 20px;
136 | overflow: hidden;
137 | text-overflow: ellipsis;
138 | transition: all 0.2s;
139 | white-space: nowrap;
140 | width: 100%;
141 | }
142 |
143 | .bili-dyn-card-vote__detail__desc {
144 | color: #9499a0;
145 | font-size: 12px;
146 | line-height: 17px;
147 | margin-top: 4px;
148 | }
149 |
150 | .bili-dyn-card-vote__action {
151 | align-items: center;
152 | display: flex;
153 | flex-shrink: 0;
154 | justify-content: center;
155 | margin-right: 16px;
156 | padding-right: 15px;
157 | }
158 |
159 | .bili-dyn-card-vote__action__btn_normal {
160 | background-color: #ff69b4;
161 | color: #ffffff;
162 | }
163 |
164 | .bili-dyn-card-vote__action button {
165 | border: none;
166 | border-radius: 6px;
167 | box-sizing: border-box;
168 | cursor: pointer;
169 | font-size: 12.8px;
170 | height: 30px;
171 | min-width: 72px;
172 | outline: none;
173 | }
174 |
--------------------------------------------------------------------------------
/resources/css/dynamic/Footer.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | color: #000;
6 | padding: 10px 20px;
7 | border-top-width: 1px;
8 | border-top-color: #ff69b4;
9 | border-top-style: dashed;
10 | }
11 |
12 | .qr-code {
13 | width: 148px;
14 | height: 148px;
15 | border-radius: 8px;
16 | border: 2px dashed #ff69b4;
17 | margin-left: 20px;
18 | margin-top: 20px;
19 | align-items: center;
20 | }
21 |
22 | .footer-text-container {
23 | display: flex;
24 | flex-direction: column;
25 | align-items: flex-start;
26 | font-size: 20px;
27 | }
28 |
29 | .bili-logo-0 {
30 | margin-bottom: 6px;
31 | }
32 |
33 | .weibo-logo-0 {
34 | margin-left: 15px;
35 | margin-bottom: 10px;
36 | }
37 |
--------------------------------------------------------------------------------
/resources/css/dynamic/ForwardContent.css:
--------------------------------------------------------------------------------
1 | .orig-container {
2 | background-color: #f5f5f5 !important;
3 | border-radius: 10px !important;
4 | position: relative;
5 | }
6 |
7 | /**微博content图标*/
8 | .url-icon img {
9 | left: 10px;
10 | position: absolute;
11 | }
12 |
--------------------------------------------------------------------------------
/resources/css/dynamic/LogoText.css:
--------------------------------------------------------------------------------
1 | .bilibili-logo-text {
2 | margin-left: 40px;
3 | margin-top: 2px;
4 | color: #fb7299;
5 | font-size: 18px;
6 | font-weight: bold;
7 | }
8 |
9 | .weibo-logo-text {
10 | margin-left: 2px;
11 | margin-top: 2px;
12 | color: rgba(232 146 20);
13 | font-size: 18px;
14 | font-weight: bold;
15 | }
16 |
--------------------------------------------------------------------------------
/resources/css/dynamic/MainPage.css:
--------------------------------------------------------------------------------
1 | /* 隐藏滚动条 */
2 | body {
3 | overflow: -moz-scrollbars-none;
4 | /* Firefox */
5 | -ms-overflow-style: none;
6 | /* IE 10+ */
7 | scrollbar-width: none;
8 | /* Firefox 64+ */
9 | }
10 |
11 | body::-webkit-scrollbar {
12 | display: none;
13 | /* Chrome, Safari, Edge */
14 | }
15 |
16 | ::-webkit-scrollbar {
17 | width: 0px;
18 | height: 5px;
19 | }
20 |
21 | @font-face {
22 | font-family: 'OPSans';
23 | src: url('./../../fonts/OPSans.woff2');
24 | font-weight: normal;
25 | font-style: normal;
26 | }
27 |
28 | @import url('https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css');
29 |
30 | body {
31 | font-family: 'OPSans', 'HarmonyOS_Regular', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
32 | background-color: #f9f9f9;
33 | margin: 0;
34 | padding: 0;
35 | justify-content: center;
36 | align-items: center;
37 | flex-direction: column;
38 | width: 828px !important;
39 | width: 100%;
40 | }
41 |
42 | .outside-border {
43 | /*限制页面内收缩*/
44 | width: 100%;
45 | min-width: 798px;
46 | overflow: hidden;
47 | margin: auto;
48 | }
49 |
50 | .container {
51 | max-width: 788px;
52 | width: 100%;
53 | padding: 20px;
54 | background-color: #fff;
55 | box-shadow: 0px 1px 9px 12px rgb(246 140 224 / 20%);
56 | border-radius: 8px;
57 | margin-top: 20px;
58 | margin-bottom: 20px;
59 | margin-left: 20px;
60 | margin-right: 20px;
61 | border: 1px solid #ff69b4;
62 | overflow: hidden;
63 | }
64 |
65 | .unfold {
66 | -webkit-box-sizing: border-box;
67 | box-sizing: border-box;
68 | line-height: 40px;
69 | height: 100%;
70 | outline: none;
71 | overflow-y: auto;
72 | -o-tab-size: 4;
73 | tab-size: 4;
74 | -moz-tab-size: 4;
75 | text-align: left;
76 | word-wrap: break-word;
77 | overflow-x: hidden;
78 | overflow-y: hidden;
79 | }
80 |
81 | .dynamic-article-page-main {
82 | /*限制页面布局改变*/
83 | width: 100%;
84 | max-width: 789px;
85 | min-width: 742px;
86 | }
87 |
88 | * {
89 | margin: 0;
90 | padding: 0;
91 | box-sizing: border-box;
92 | /**禁止选中文本*/
93 | user-select: none;
94 | }
95 |
--------------------------------------------------------------------------------
/resources/css/help/help.css:
--------------------------------------------------------------------------------
1 | /* 隐藏滚动条 */
2 | body {
3 | overflow: -moz-scrollbars-none;
4 | /* Firefox */
5 | -ms-overflow-style: none;
6 | /* IE 10+ */
7 | scrollbar-width: none;
8 | /* Firefox 64+ */
9 | }
10 |
11 | body::-webkit-scrollbar {
12 | display: none;
13 | /* Chrome, Safari, Edge */
14 | }
15 |
16 | ::-webkit-scrollbar {
17 | width: 0px;
18 | height: 5px;
19 | }
20 |
21 | @font-face {
22 | font-family: 'OPSans';
23 | src: url('./../../fonts/OPSans.woff2');
24 | font-weight: normal;
25 | font-style: normal;
26 | }
27 |
28 | * {
29 | margin: 0;
30 | padding: 0;
31 | box-sizing: border-box;
32 | user-select: none;
33 | }
34 |
35 | body {
36 | font-family: 'OPSans';
37 | font-size: 16px;
38 | color: #1e1f20;
39 | transform: scale(1.5);
40 | transform-origin: 0 0;
41 | width: 788px;
42 | }
43 |
44 | .container {
45 | width: 788px;
46 | padding: 15px 15px 15px 15px;
47 | background-image: url(./../../img/background/Girl.png);
48 | background-size: 100%;
49 | }
50 |
51 | .head_box {
52 | font-family: 'OPSans';
53 | border-radius: 10px;
54 | padding: 10px 20px;
55 | position: relative;
56 | color: #ff69b4;
57 | box-shadow:
58 | 0 0 1px 0 #ccc,
59 | 2px 2px 4px 0 rgb(238 160 236 / 80%);
60 | border: 0.5px solid #ff69b4;
61 | backdrop-filter: blur(10px);
62 | }
63 |
64 | .head_box .id_text {
65 | font-size: 24px;
66 | }
67 |
68 | .head_box .day_text {
69 | font-size: 20px;
70 | }
71 |
72 | .head_box .genshin_logo {
73 | position: absolute;
74 | top: -6px;
75 | right: 15px;
76 | width: 97px;
77 | }
78 |
79 | .base_info {
80 | position: relative;
81 | padding-left: 10px;
82 | }
83 |
84 | .data_box {
85 | border-radius: 15px;
86 | margin-top: 20px;
87 | margin-bottom: 15px;
88 | padding: 20px 0px 5px 0px;
89 | background: transparent;
90 | background-color: rgba(255 241 255 / 35%);
91 | box-shadow:
92 | 0 0 1px 0 #ccc,
93 | 2px 2px 4px 0 rgb(238 160 236 / 80%);
94 | border: 0.5px solid #ff69b4;
95 | position: relative;
96 | }
97 |
98 | .tab_lable {
99 | position: absolute;
100 | top: -10px;
101 | left: -8px;
102 | background-color: rgb(187 187 187 / 0%);
103 | backdrop-filter: blur(15px);
104 | box-shadow:
105 | 0 0 1px 0 #ccc,
106 | 2px 2px 4px 0 rgba(225 166 231 / 80%);
107 | color: #ff004c;
108 | font-size: 14px;
109 | padding: 3px 10px;
110 | border-radius: 15px 0px 15px 15px;
111 | z-index: 20;
112 | border: 0.5px solid #ff69b4;
113 | }
114 |
115 | .data_line {
116 | display: flex;
117 | justify-content: space-around;
118 | margin-bottom: 14px;
119 | }
120 |
121 | .data_line_item {
122 | width: 100px;
123 | text-align: center;
124 | /*margin: 0 20px;*/
125 | }
126 |
127 | .num {
128 | font-size: 24px;
129 | }
130 |
131 | .data_box .lable {
132 | font-size: 14px;
133 | color: #7f858a;
134 | line-height: 1;
135 | margin-top: 3px;
136 | }
137 |
138 | .list {
139 | display: flex;
140 | justify-content: flex-start;
141 | flex-wrap: wrap;
142 | }
143 |
144 | .list .item {
145 | width: 235px;
146 | display: flex;
147 | align-items: center;
148 | color: #151215;
149 | background: transparent;
150 | box-shadow:
151 | 0 0 1px 0 #ccc,
152 | 2px 2px 4px 0 rgb(255 213 254 / 80%);
153 | padding: 8px 6px 8px 6px;
154 | border-radius: 8px;
155 | margin: 0 0px 10px 10px;
156 | backdrop-filter: blur(4px);
157 | border: 1px solid #ffb1fa;
158 | }
159 |
160 | .list .item .icon {
161 | width: 24px;
162 | height: 24px;
163 | background-repeat: no-repeat;
164 | background-size: 100% 100%;
165 | position: relative;
166 | flex-shrink: 0;
167 | }
168 |
169 | .list .item .title {
170 | font-size: 16px;
171 | margin-left: 6px;
172 | line-height: 20px;
173 | }
174 |
175 | /* .list .item .title .text{
176 | white-space: nowrap;
177 | } */
178 | .list .item .title .dec {
179 | font-size: 12px;
180 | color: #102644;
181 | margin-top: 2px;
182 | }
183 |
184 | .logo {
185 | text-align: center;
186 | font-size: 14px;
187 | color: rgb(0, 0, 0);
188 | font-family: 'OPSans';
189 | }
190 |
--------------------------------------------------------------------------------
/resources/css/loginQrcode/Page.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'OPSans';
3 | src: url('./../../fonts/OPSans.woff2');
4 | font-weight: normal;
5 | font-style: normal;
6 | }
7 |
8 | body {
9 | width: 463px !important;
10 | font-family: 'OPSans';
11 | }
12 |
13 | .main-container {
14 | width: 463px;
15 | }
16 |
17 | .container {
18 | width: 380px;
19 | max-height: 40rem;
20 | margin: auto;
21 | font-size: 1.125rem /* 18px */;
22 | line-height: 1.75rem /* 28px */;
23 | padding: 20px;
24 | background-color: #fff;
25 | box-shadow: 0px 1px 9px 12px rgb(246 140 224 / 20%);
26 | border-radius: 8px;
27 | margin-top: 20px;
28 | margin-bottom: 20px;
29 | margin-left: 20px;
30 | margin-right: 20px;
31 | border: 1px solid #ff69b4;
32 | overflow: hidden;
33 | }
34 |
35 | .txt-0 {
36 | text-align: center;
37 | margin-top: 0.75rem;
38 | margin-bottom: 0.75rem;
39 | padding: 0.25rem;
40 | --tw-text-opacity: 1;
41 | color: rgb(59 130 246 / var(--tw-text-opacity));
42 | }
43 |
44 | .txt-1 {
45 | text-align: center;
46 | margin-top: 0.75rem;
47 | margin-bottom: 0.75rem;
48 | padding: 0.25rem;
49 | --tw-text-opacity: 1;
50 | color: rgb(220 38 38 / var(--tw-text-opacity));
51 | }
52 |
53 | .QrCode {
54 | margin: auto;
55 | }
56 |
57 | .qr-code {
58 | width: 18rem;
59 | height: 18rem;
60 | margin-left: 2.8rem;
61 | border-radius: 8px;
62 | border: 1.5px dashed #fb8bc3;
63 | }
64 |
--------------------------------------------------------------------------------
/resources/css/version/version.css:
--------------------------------------------------------------------------------
1 | /* 隐藏滚动条 */
2 | body {
3 | overflow: -moz-scrollbars-none;
4 | /* Firefox */
5 | -ms-overflow-style: none;
6 | /* IE 10+ */
7 | scrollbar-width: none;
8 | /* Firefox 64+ */
9 | }
10 |
11 | body::-webkit-scrollbar {
12 | display: none;
13 | /* Chrome, Safari, Edge */
14 | }
15 |
16 | ::-webkit-scrollbar {
17 | width: 0px;
18 | height: 5px;
19 | }
20 |
21 | @font-face {
22 | font-family: 'OPSans';
23 | src: url('./../../fonts/OPSans.woff2');
24 | font-weight: normal;
25 | font-style: normal;
26 | }
27 |
28 | * {
29 | margin: 0;
30 | padding: 0;
31 | box-sizing: border-box;
32 | user-select: none;
33 | }
34 |
35 | body {
36 | font-size: 16px;
37 | font-family: 'OPSans';
38 | transform: scale(1.5);
39 | transform-origin: 0 0;
40 | color: white;
41 | width: 788px;
42 | }
43 |
44 | .container {
45 | width: 788px;
46 | background-image: url(./../../img/background/Girl.png);
47 | background-size: 100%;
48 | padding: 15px 0 10px 0;
49 | }
50 |
51 | .version-card {
52 | background: transparent;
53 | margin: 5px 10px 8px 10px;
54 | position: relative;
55 | box-shadow:
56 | 0 0 0.5px 0 #ffd1f8,
57 | 2px 2px 4px 0 rgb(238 160 236 / 80%);
58 | border: 0.5px solid #ff69b4;
59 | overflow: hidden;
60 | color: #fff;
61 | font-size: 16px;
62 | border-radius: 4px;
63 | backdrop-filter: blur(4px);
64 | border-radius: 10px;
65 | }
66 |
67 | .version-card .title {
68 | border-bottom: 0.5px solid #ff69b4;
69 | color: #4e8eff;
70 | font-family: Number, YS;
71 | padding: 10px 20px;
72 | text-align: left;
73 | font-size: 16px;
74 | padding: 8px 20px 8px;
75 | font-weight: bold;
76 | background: transparent;
77 | }
78 |
79 | .version-card .content {
80 | padding: 10px 15px;
81 | font-size: 12px;
82 | font-weight: normal;
83 | font-family: 'OPSans';
84 | background: transparent;
85 | color: rgba(0, 0, 0);
86 | }
87 |
88 | .version-card ul {
89 | font-size: 14px;
90 | padding-left: 20px;
91 | }
92 |
93 | .version-card ul li {
94 | margin: 3px 0;
95 | }
96 |
97 | .version-card .cmd {
98 | color: #c59460;
99 | display: inline-block;
100 | border-radius: 3px;
101 | padding: 0 3px;
102 | margin: 1px 2px;
103 | }
104 |
105 | .version-card .strong {
106 | color: #67a9e4;
107 | display: inline-block;
108 | border-radius: 3px;
109 | padding: 0 3px;
110 | margin: 1px 2px;
111 | }
112 |
113 | .logo {
114 | text-align: center;
115 | font-size: 14px;
116 | color: rgb(15, 15, 15);
117 | font-family: 'OPSans';
118 | }
119 |
--------------------------------------------------------------------------------
/resources/fonts/OPSans.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/fonts/OPSans.woff2
--------------------------------------------------------------------------------
/resources/img/background/Girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/background/Girl.png
--------------------------------------------------------------------------------
/resources/img/icon/dynamic/bili-dyn-card-vote__cover.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/img/icon/dynamic/bili-rich-text-module-goods-taobao.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/img/icon/dynamic/bili-rich-text-module-lottery.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/img/icon/dynamic/bilibili.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/img/icon/dynamic/weibo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/img/icon/puplic/archaic_stone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/archaic_stone.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/condessence_crystal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/condessence_crystal.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/delightful_encounter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/delightful_encounter.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/diagram.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/essence_of_pure_sacred_dewdrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/essence_of_pure_sacred_dewdrop.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/everamber.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/everamber.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/flower_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/flower_1.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/flower_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/flower_2.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/kamera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/kamera.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/lumidouce_bell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/lumidouce_bell.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/mora.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/mora.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/pluie_lotus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/pluie_lotus.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/restaurant_smoothie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/restaurant_smoothie.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/romaritime_flower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/romaritime_flower.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/shell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/shell.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/spring_of_pure_sacred_dewdrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/spring_of_pure_sacred_dewdrop.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/surging_sacred_chalice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/surging_sacred_chalice.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/tourbillon_device.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/tourbillon_device.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/unfading_silky_grace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/unfading_silky_grace.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/wisdom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/wisdom.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/wondrous_lovely_flower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/wondrous_lovely_flower.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/岩神瞳共鸣石.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/岩神瞳共鸣石.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/水神瞳共鸣石.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/水神瞳共鸣石.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/草神瞳共鸣石.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/草神瞳共鸣石.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/钓鱼.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/钓鱼.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/雷神瞳共鸣石.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/雷神瞳共鸣石.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/风神瞳共鸣石.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/风神瞳共鸣石.png
--------------------------------------------------------------------------------
/resources/img/icon/puplic/风车.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/icon/puplic/风车.png
--------------------------------------------------------------------------------
/resources/img/readme/girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/readme/girl.png
--------------------------------------------------------------------------------
/resources/img/readme/min-Girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snowtafir/yuki-plugin/f72d1be5954e3d47560e54194b4cadeaf903c555/resources/img/readme/min-Girl.png
--------------------------------------------------------------------------------
/types/apps/bilibili.d.ts:
--------------------------------------------------------------------------------
1 | import plugin from '../../../lib/plugins/plugin.js';
2 | export default class YukiBili extends plugin {
3 | constructor();
4 | biliConfigData: any;
5 | biliPushData: any;
6 | /** B站动态推送定时任务 */
7 | newPushTask(): Promise;
8 | /** 添加B站动态订阅 */
9 | addDynamicSub(): Promise;
10 | /** 删除B站动态订阅 */
11 | delDynamicSub(): Promise;
12 | /** 扫码登录B站 */
13 | scanBiliLogin(): Promise;
14 | /** 删除登陆的B站ck */
15 | delBiliLogin(): Promise;
16 | /**验证B站登录 */
17 | myBiliLoginInfo(): Promise;
18 | /** 手动绑定本地获取的B站cookie */
19 | addLocalBiliCookie(): Promise;
20 | /** 删除绑定的本地B站ck */
21 | delLocalBiliCookie(): Promise;
22 | /** 当前正在使用的B站ck */
23 | myUsingBiliCookie(): Promise;
24 | /** 删除并刷新redis缓存的临时B站ck */
25 | reflashTempCk(): Promise;
26 | /** 订阅的全部b站推送列表 */
27 | allSubDynamicPushList(): Promise;
28 | /** 单独群聊或私聊的订阅的b站推送列表 */
29 | singelSubDynamicPushList(): Promise;
30 | /**通过uid获取up主信息 */
31 | getBilibiUserInfoByUid(): Promise;
32 | /** 根据名称搜索up的uid*/
33 | searchBiliUserInfoByKeyword(): Promise;
34 | /** 根据aid或bvid获解析频信息*/
35 | getVideoInfoByAid_BV(): Promise;
36 | }
37 |
--------------------------------------------------------------------------------
/types/apps/help.d.ts:
--------------------------------------------------------------------------------
1 | import plugin from '../../../lib/plugins/plugin.js';
2 | export default class YukiHelp extends plugin {
3 | constructor();
4 | /**
5 | * 优纪帮助
6 | */
7 | yukiHelp(): Promise;
8 | }
9 |
--------------------------------------------------------------------------------
/types/apps/version.d.ts:
--------------------------------------------------------------------------------
1 | import plugin from '../../../lib/plugins/plugin.js';
2 | export default class YukiVersion extends plugin {
3 | constructor();
4 | /**
5 | * 优纪版本
6 | */
7 | yukiVersion(): Promise;
8 | }
9 |
--------------------------------------------------------------------------------
/types/apps/weibo.d.ts:
--------------------------------------------------------------------------------
1 | import plugin from '../../../lib/plugins/plugin.js';
2 | export default class YukiWeibo extends plugin {
3 | constructor();
4 | weiboConfigData: any;
5 | weiboPushData: any;
6 | /** 微博动态推送定时任务 */
7 | newPushTask(): Promise;
8 | /** 添加微博动态订阅 */
9 | addDynamicSub(): Promise;
10 | /** 删除微博动态订阅 */
11 | delDynamicSub(): Promise;
12 | /** 订阅的全部微博推送列表 */
13 | allSubDynamicPushList(): Promise;
14 | /** 单独群聊或私聊的订阅的b站推送列表 */
15 | singelSubDynamicPushList(): Promise;
16 | /**通过uid获取up主信息 */
17 | getWeiboUserInfoByUid(): Promise;
18 | /** 根据昵称搜索博主信息*/
19 | searchWeiboUserInfoByKeyword(): Promise;
20 | }
21 |
--------------------------------------------------------------------------------
/types/components/dynamic/Account.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | type AccountProps = {
3 | data: {
4 | appName: string;
5 | face?: string;
6 | pendant?: string;
7 | name?: string;
8 | pubTs?: any;
9 | category?: string;
10 | };
11 | };
12 | declare const Account: React.FC;
13 | export default Account;
14 |
--------------------------------------------------------------------------------
/types/components/dynamic/Content.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | type ContentProps = {
3 | data: {
4 | type?: string;
5 | pics?: Array;
6 | title?: string;
7 | content?: string;
8 | boxGrid?: boolean;
9 | };
10 | };
11 | declare const Content: React.FC;
12 | export default Content;
13 |
--------------------------------------------------------------------------------
/types/components/dynamic/Footer.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | type FooterProps = {
3 | data: {
4 | appName?: string;
5 | category?: string;
6 | urlImgData?: string;
7 | created?: string;
8 | };
9 | };
10 | declare const Footer: React.FC;
11 | export default Footer;
12 |
--------------------------------------------------------------------------------
/types/components/dynamic/ForwardContent.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | type ForwardContentProps = {
3 | data?: any;
4 | };
5 | declare const ForwardContent: React.FC;
6 | export default ForwardContent;
7 |
--------------------------------------------------------------------------------
/types/components/dynamic/LogoText.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | type LogoTextProps = {
3 | data: {
4 | appName: string;
5 | category?: string;
6 | };
7 | };
8 | declare const LogoText: React.FC;
9 | export default LogoText;
10 |
--------------------------------------------------------------------------------
/types/components/dynamic/MainPage.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | export type MainProps = {
3 | data: {
4 | appName: string;
5 | boxGrid?: boolean;
6 | type?: string;
7 | face?: string;
8 | pendant?: string;
9 | name?: string;
10 | pubTs: any;
11 | title?: string;
12 | content?: string;
13 | urlImgData?: string;
14 | created?: any;
15 | pics?: Array;
16 | category?: string;
17 | orig?: {
18 | data?: {
19 | type?: string;
20 | face?: string;
21 | pendant?: string;
22 | name?: string;
23 | pubTs?: any;
24 | title?: string;
25 | content?: string;
26 | urlImgData?: string;
27 | created?: any;
28 | pics?: string[];
29 | category?: string;
30 | };
31 | };
32 | };
33 | };
34 | export default function App({ data }: MainProps): React.JSX.Element;
35 |
--------------------------------------------------------------------------------
/types/components/help/Help.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | export type HelpPageProps = {
3 | data: {
4 | group: string;
5 | list: {
6 | icon: string;
7 | title: string;
8 | desc: string;
9 | }[];
10 | }[];
11 | };
12 | export default function App({ data }: HelpPageProps): React.JSX.Element;
13 |
--------------------------------------------------------------------------------
/types/components/index.d.ts:
--------------------------------------------------------------------------------
1 | declare const MainPage: typeof import("@/components/dynamic/MainPage").default;
2 | declare const Help: typeof import("@/components/help/Help").default;
3 | declare const LoginQrcodePage: typeof import("@/components/loginQrcode/Page").default;
4 | declare const Version: typeof import("@/components/version/Version").default;
5 | export { Help, LoginQrcodePage, MainPage, Version };
6 |
--------------------------------------------------------------------------------
/types/components/loginQrcode/Page.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | export type LoginProps = {
3 | data: {
4 | url: string;
5 | };
6 | };
7 | export default function App({ data }: LoginProps): React.JSX.Element;
8 |
--------------------------------------------------------------------------------
/types/components/version/Version.d.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | export type VersionProps = {
3 | data: {
4 | version: string;
5 | data: string[];
6 | }[];
7 | };
8 | export default function App({ data }: VersionProps): React.JSX.Element;
9 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare let rules: {};
2 | export { rules as apps };
3 |
--------------------------------------------------------------------------------
/types/jsxp.server.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.main.api.d.ts:
--------------------------------------------------------------------------------
1 | declare class BiliApi {
2 | biliConfigData: any;
3 | USER_AGENT: string;
4 | constructor();
5 | static BILIBILI_USER_AGENT: string;
6 | initialize(): Promise;
7 | initUserAgent(): Promise;
8 | get BILIBIL_API(): {
9 | biliServerTimeStamp: string;
10 | biliDynamicInfoList: string;
11 | biliUpFollowFans: string;
12 | biliSpaceUserInfo: string;
13 | biliSpaceUserInfoWbi: string;
14 | biliSearchUp: string;
15 | biliSearchUpWbi: string;
16 | biliVideoInfo: string;
17 | biliVideoInfoWbi: string;
18 | biliShortVideoUrl: string;
19 | biliLiveStatus: string;
20 | biliCard: string;
21 | biliStat: string;
22 | biliLiveUserInfo: string;
23 | biliOpusDetail: string;
24 | };
25 | /**header */
26 | get BILIBILI_HEADERS(): {
27 | Accept: string;
28 | 'Accept-Language': string;
29 | Connection: string;
30 | 'Accept-Encoding': string;
31 | Cookie: string;
32 | pragma: string;
33 | 'Cache-control': string;
34 | DNT: string;
35 | 'Sec-GPC': string;
36 | 'sec-ch-ua-platform': string;
37 | 'sec-ch-ua-mobile': string;
38 | 'Sec-Fetch-Dest': string;
39 | 'Sec-Fetch-Mode': string;
40 | 'Sec-Fetch-Site': string;
41 | 'Sec-Fetch-User': string;
42 | Priority: string;
43 | TE: string;
44 | 'User-Agent': string;
45 | };
46 | /**Login header */
47 | get BIlIBILI_LOGIN_HEADERS(): {
48 | Accept: string;
49 | 'Accept-Language': string;
50 | 'Accept-Encoding': string;
51 | DNT: string;
52 | 'Sec-GPC': string;
53 | 'Upgrade-Insecure-Requests': string;
54 | 'Sec-Fetch-Dest': string;
55 | 'Sec-Fetch-Mode': string;
56 | 'Sec-Fetch-Site': string;
57 | 'Sec-Fetch-User': string;
58 | TE: string;
59 | };
60 | /**FullArticle header */
61 | get BILIBILI_ARTICLE_HEADERS(): {
62 | Accept: string;
63 | 'Accept-Language': string;
64 | 'Accept-Encoding': string;
65 | 'Content-type': string;
66 | Cookie: string;
67 | pragma: string;
68 | 'Cache-control': string;
69 | DNT: string;
70 | 'Sec-GPC': string;
71 | 'sec-ch-ua-mobile': string;
72 | 'Sec-Fetch-Dest': string;
73 | 'Sec-Fetch-Mode': string;
74 | 'Sec-Fetch-Site': string;
75 | 'Sec-Fetch-User': string;
76 | TE: string;
77 | 'Upgrade-Insecure-Requests': string;
78 | 'User-Agent': string;
79 | };
80 | get BILIBILI_DYNAMIC_SPACE_HEADERS(): {
81 | Accept: string;
82 | 'Accept-Encoding': string;
83 | 'Accept-Language': string;
84 | Connection: string;
85 | Priority: string;
86 | 'Sec-Fetch-Dest': string;
87 | 'Sec-Fetch-Mode': string;
88 | 'Sec-Fetch-Site': string;
89 | 'Sec-Fetch-User': string;
90 | 'Sec-GPC': string;
91 | 'Upgrade-Insecure-Requests': string;
92 | 'User-Agent': string;
93 | };
94 | /**返回GatWay payload */
95 | BILIBILI_BROWSER_DATA(_uuid: string): Promise<{
96 | '3064': number;
97 | '5062': string;
98 | '03bf': string;
99 | '39c8': string;
100 | '34f1': string;
101 | d402: string;
102 | '654a': string;
103 | '6e7c': string;
104 | '3c43': {
105 | '2673': number;
106 | '5766': number;
107 | '6527': number;
108 | '7003': number;
109 | '807e': number;
110 | b8ce: string;
111 | '641c': number;
112 | '07a4': string;
113 | '1c57': string;
114 | '0bd0': number;
115 | '748e': number[];
116 | d61f: number[];
117 | fc9d: number;
118 | '6aa9': string;
119 | '75b8': number;
120 | '3b21': number;
121 | '8a1c': number;
122 | d52f: string;
123 | adca: string;
124 | '80c9': (string | string[][])[][];
125 | '13ab': string;
126 | bfe9: string;
127 | a3c1: string[];
128 | '6bc5': string;
129 | ed31: number;
130 | '72bd': number;
131 | '097b': number;
132 | '52cd': number[];
133 | a658: string[];
134 | d02f: string;
135 | };
136 | '54ef': {
137 | 'in_new_ab ': boolean;
138 | 'ab_version ': {
139 | 'waterfall_article ': string;
140 | };
141 | 'ab_split_num ': {
142 | 'waterfall_article ': number;
143 | };
144 | };
145 | '8b94': string;
146 | df35: string;
147 | '07a4': string;
148 | '5f45': any;
149 | db46: number;
150 | }>;
151 | /**返回Bilibili Fingerprint data */
152 | BILIBILI_FINGERPRINT_DATA(_uuid: string): {
153 | userAgent: string;
154 | webdriver: boolean;
155 | language: string;
156 | colorDepth: number;
157 | deviceMemory: string;
158 | pixelRatio: number;
159 | hardwareConcurrency: number;
160 | screenResolution: string;
161 | availableScreenResolution: string;
162 | timezoneOffset: number;
163 | timezone: string;
164 | sessionStorage: boolean;
165 | localStorage: boolean;
166 | indexedDb: boolean;
167 | addBehavior: boolean;
168 | openDatabase: boolean;
169 | cpuClass: string;
170 | platform: string;
171 | doNotTrack: any;
172 | plugins: {
173 | name: string;
174 | description: string;
175 | mimeTypes: string[][];
176 | }[];
177 | canvas: string;
178 | webgl: string;
179 | webglVendorAndRenderer: string;
180 | adBlock: boolean;
181 | hasLiedLanguages: boolean;
182 | hasLiedResolution: boolean;
183 | hasLiedOs: boolean;
184 | hasLiedBrowser: boolean;
185 | touchSupport: number;
186 | fonts: string[];
187 | fontsFlash: boolean;
188 | audio: string;
189 | enumerateDevices: string[];
190 | };
191 | }
192 | declare const _default: BiliApi;
193 | export default _default;
194 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.main.get.web.data.d.ts:
--------------------------------------------------------------------------------
1 | export declare class BilibiliWebDataFetcher {
2 | constructor(e?: any);
3 | /**通过uid获取up动态数据表*/
4 | getBiliDynamicListDataByUid(uid: any): Promise>;
5 | /**通过uid获取up详情*/
6 | getBilibiUserInfoByUid(uid: any): Promise>;
7 | /**通过关键词搜索up*/
8 | searchBiliUserInfoByKeyword(keyword: string): Promise>;
9 | getBiliVideoInfoByAid_BV(vedioID: {
10 | aid?: number;
11 | bvid?: string;
12 | }): Promise>;
13 | getBVIDByShortUrl(tvUrlID: string): Promise;
14 | }
15 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.main.models.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * *******************************************************************
3 | * Login 相关
4 | * *******************************************************************
5 | */
6 | /**申请登陆二维码(web端) */
7 | export declare function applyLoginQRCode(e: any): Promise;
8 | /**处理扫码结果 */
9 | export declare function pollLoginQRCode(e: any, qrcodeKey: string): Promise;
10 | /**查看app扫码登陆获取的ck的有效状态*/
11 | export declare function checkBiliLogin(e: any): Promise;
12 | /**退出B站账号登录,将会删除redis缓存的LoginCK,并在服务器注销该登录 Token (SESSDATA)*/
13 | export declare function exitBiliLogin(e: any): Promise;
14 | /**
15 | * *******************************************************************
16 | * cookie相关
17 | * *******************************************************************
18 | */
19 | /**保存扫码登录的loginCK*/
20 | export declare function saveLoginCookie(e: any, biliLoginCk: string): Promise;
21 | /** 读取扫码登陆后缓存的cookie */
22 | export declare function readLoginCookie(): Promise;
23 | /** 覆盖保存手动获取绑定的B站ck */
24 | export declare function saveLocalBiliCk(data: any): Promise;
25 | /** 读取缓存的tempCK */
26 | export declare function readTempCk(): Promise;
27 | /**保存tempCK*/
28 | export declare function saveTempCk(newTempCk: any): Promise;
29 | /** 综合获取ck,返回优先级:localCK > loginCK > tempCK */
30 | export declare function readSyncCookie(): Promise<{
31 | cookie: any;
32 | mark: string;
33 | }>;
34 | /**
35 | * 综合读取、筛选 传入的或本地或redis存储的cookie的item
36 | * @param {string} mark 读取存储的CK类型,'localCK' 'tempCK' 'loginCK' 或传入值 'xxx'并进行筛选
37 | * @param {Array} items 选取获取CK的项 选全部值:items[0] = 'all' ,或选取其中的值 ['buvid3', 'buvid4', '_uuid', 'SESSDATA', 'DedeUserID', 'DedeUserID__ckMd5', 'bili_jct', 'b_nut', 'b_lsid']
38 | * @param {boolean} isInverted 控制正取和反取,true为反取,false为正取
39 | * @returns {string}
40 | **/
41 | export declare function readSavedCookieItems(mark: string, items: Array, isInverted?: boolean): Promise;
42 | export declare function readSavedCookieOtherItems(mark: string, items: Array): Promise;
43 | /** 生成 _uuid */
44 | export declare function genUUID(): Promise;
45 | /**生成 b_lsid */
46 | export declare function gen_b_lsid(): Promise;
47 | /**获取新的tempCK*/
48 | export declare function getNewTempCk(): Promise;
49 | /**
50 | * *******************************************************************
51 | * 风控相关函数
52 | * *******************************************************************
53 | */
54 | /**
55 | * 请求参数POST接口(ExClimbWuzhi)过校验
56 | * @param cookie 请求所需的cookie
57 | * @returns 返回POST请求的结果
58 | */
59 | export declare function postGateway(cookie: string): Promise>;
60 | /**生成buvid_fp
61 | * @param {string} uuid
62 | */
63 | export declare function get_buvid_fp(cookie: string): Promise;
64 | /**
65 | * 获取有效bili_ticket并添加到cookie
66 | * @param {string} cookie
67 | * @returns {Promise<{ cookie: string; }>} 返回包含最新有效的bili_ticket的cookie
68 | */
69 | export declare function cookieWithBiliTicket(cookie: string): Promise;
70 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.main.query.d.ts:
--------------------------------------------------------------------------------
1 | export declare class BiliQuery {
2 | /**
3 | * 序列化动态数据
4 | * @param data - 动态数据对象
5 | * @returns 序列化后的动态数据对象
6 | */
7 | static formatDynamicData(data: any): Promise<{
8 | uid: any;
9 | data: {
10 | [key: string]: any;
11 | };
12 | }>;
13 | /**
14 | * 动态内容富文本节点解析
15 | * @param nodes - 动态内容富文本节点
16 | * @returns 解析后的动态内容富文本
17 | */
18 | static parseRichTextNodes: (nodes: any[] | string | any, additional?: any) => any;
19 | /**获取完整B站文章内容
20 | * @param postUrl - 文章链接: https://www.bilibili.com/read/cvxxxx 或者 https://www.bilibili.com/opus/xxxx
21 | * @returns 完整的B站文章内容json数据
22 | */
23 | static getFullArticleContent(postUrl: string): Promise<{
24 | readInfo: any;
25 | articleType: string;
26 | }>;
27 | /**解析旧版完整文章内容 */
28 | static praseFullOldTypeArticleContent(content: string): string;
29 | /**
30 | * 解析新版完整文章内容,将其转换为HTML格式的正文和图片数组。
31 | * 该方法处理的是 MODULE_TYPE_CONTENT 类型的文章,文章内容由多个段落组成。
32 | * 每个段落可能包含不同类型的内容,如正文、图片、链接、表情等。
33 | *
34 | * @param paragraphs - MODULE_TYPE_CONTENT 类型文章的段落数组,每个段落是一个对象。
35 | * 每个段落对象包含一个 para_type 属性,用于标识段落类型(1表示正文,2表示图片)。
36 | * 正文段落中可能包含多个 nodes,每个 node 表示一段文本或一个富文本元素。
37 | * 图片段落中包含一个 pic 对象,其中 pics 是图片信息的数组。
38 | * @returns 返回一个对象,包含两个属性:
39 | * - content: string 类型,解析后的HTML格式的正文字符串。
40 | * - img: array 类型,包含图片信息的对象数组,每个对象有 url、width 和 height 属性。
41 | * 如果输入的 paragraphs 不是数组,则返回 null。
42 | */
43 | static praseFullNewTypeArticleContent: (paragraphs: any[] | any) => {
44 | content: string;
45 | img: any[];
46 | };
47 | static formatUrl(url: string): string;
48 | /**
49 | * 生成动态消息文字内容
50 | * @param upName - UP主名称
51 | * @param formatData - 动态数据
52 | * @param isForward - 是否为转发动态
53 | * @param setData - 设置数据
54 | * @returns 生成的动态消息文字内容
55 | */
56 | static formatTextDynamicData(upName: string, data: any, isForward: boolean, setData: any): Promise<"continue" | {
57 | msg: any[];
58 | pics: any[];
59 | dynamicType: "DYNAMIC_TYPE_AV";
60 | } | {
61 | msg: any[];
62 | pics: any[];
63 | dynamicType: "DYNAMIC_TYPE_WORD";
64 | } | {
65 | msg: any[];
66 | pics: any[];
67 | dynamicType: "DYNAMIC_TYPE_DRAW";
68 | } | {
69 | msg: any[];
70 | pics: any[];
71 | dynamicType: "DYNAMIC_TYPE_ARTICLE";
72 | } | {
73 | msg: any[];
74 | pics: any[];
75 | dynamicType: "DYNAMIC_TYPE_FORWARD";
76 | } | {
77 | msg: any[];
78 | pics: any[];
79 | dynamicType: "DYNAMIC_TYPE_LIVE_RCMD";
80 | }>;
81 | static dynamicContentLimit(content: string, setData: any): string;
82 | /**根据关键字更新 up 的动态类型 */
83 | static typeHandle(up: any, msg: string, type: string): unknown[];
84 | }
85 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.main.task.d.ts:
--------------------------------------------------------------------------------
1 | import { MainProps } from '@/components/dynamic/MainPage';
2 | import { ScreenshotOptions } from '@/utils/puppeteer.render';
3 | export declare class BiliTask {
4 | taskName: string;
5 | groupKey: string;
6 | privateKey: string;
7 | e?: any;
8 | constructor(e?: any);
9 | hendleEventDynamicData(uid: string | number, count?: number): Promise;
10 | /**
11 | * 执行动态推送任务
12 | */
13 | runTask(): Promise;
14 | /**
15 | * 处理Bilibili数据,获取动态列表并构建 uid 映射
16 | * @param biliPushData Bilibili推送数据
17 | * @param uidMap uid 映射
18 | * @param dynamicList 动态列表
19 | * @param lastLiveStatus 最后直播状态
20 | */
21 | processBiliData(biliPushData: {
22 | group?: {
23 | [chatId: string]: {
24 | bot_id: string;
25 | uid: string;
26 | name: string;
27 | type: string[];
28 | }[];
29 | };
30 | private?: {
31 | [chatId: string]: {
32 | bot_id: string;
33 | uid: string;
34 | name: string;
35 | type: string[];
36 | }[];
37 | };
38 | }, biliConfigData: any, uidMap: Map>>>, dynamicList: any): Promise;
42 | /**
43 | * 构建uid对应动态数据映射
44 | * @param uidMap uid 映射
45 | * @param dynamicList 动态列表
46 | * @param now 当前时间戳
47 | * @param dynamicTimeRange 筛选何时发布的动态
48 | * @param biliConfigData Bilibili配置数据
49 | */
50 | makeUidDynamicDataMap(uidMap: Map>>>, dynamicList: any, now: number, dynamicTimeRange: number, biliConfigData: any, messageMap: Map>>): Promise;
59 | /**
60 | * 渲染构建待发送的动态消息数据的映射数组
61 | * @param chatId 聊天 ID
62 | * @param bot_id 机器人 ID
63 | * @param upName up主用户名
64 | * @param pushDynamicData 推送动态数据
65 | * @param biliConfigData 哔哩配置数据
66 | * @param chatType 聊天类型
67 | */
68 | makeDynamicMessageMap(chatId: string | number, bot_id: string | number, upName: string, pushDynamicData: any, biliConfigData: any, chatType: string, messageMap: Map>>): Promise;
74 | /**
75 | * 构建渲染数据
76 | * @param extentData 扩展数据
77 | * @param urlQrcodeData URL 二维码数据
78 | * @param boxGrid 是否启用九宫格样式
79 | * @returns 渲染数据
80 | */
81 | buildRenderData(extentData: any, urlQrcodeData: string, boxGrid: boolean): MainProps;
82 | /**
83 | * 渲染动态卡片
84 | * @param uid 用户 ID
85 | * @param renderData 渲染数据
86 | * @param ScreenshotOptionsData 截图选项数据
87 | * @returns 图片数据
88 | */
89 | renderDynamicCard(uid: string, renderData: MainProps, ScreenshotOptionsData: ScreenshotOptions): Promise;
90 | /**
91 | * 收集消息映射
92 | * @param messageMap 消息映射
93 | * @param chatType 聊天类型
94 | * @param bot_id 机器人 ID
95 | * @param chatId 聊天 ID
96 | * @param sendMode 发送模式: SINGLE 逐条发送,MERGE 合并发送
97 | * @param dynamicUUid_str 动态 UUID
98 | * @param dynamicType 动态类型
99 | * @param message 消息内容
100 | */
101 | addMessageToMap(messageMap: Map>>, chatType: string, bot_id: string | number, chatId: string | number, sendMode: string, dynamicUUid_str: string, dynamicType: string, messages: any): Promise;
107 | /**
108 | * 推送动态消息
109 | * @param messageMap 消息映射
110 | * @param biliConfigData 哔哩配置数据
111 | */
112 | sendDynamicMessage(messageMap: Map>>, biliConfigData: {
118 | [key: string]: any;
119 | }): Promise;
120 | /**
121 | * 发送消息api
122 | * @param chatId 聊天 ID
123 | * @param bot_id 机器人 ID
124 | * @param chatType 聊天类型
125 | * @param message 消息内容
126 | */
127 | sendMsgApi(chatId: string | number, bot_id: string | number, chatType: string, message: any): Promise;
128 | /**
129 | * 发送合并转发消息
130 | * @param chatId 聊天 ID
131 | * @param bot_id 机器人 ID
132 | * @param chatType 聊天类型
133 | * @param message 消息内容
134 | * @returns 是否发送成功
135 | */
136 | sendForwardMsgApi(chatId: string | number, bot_id: string | number, chatType: string, forwardNodes: Array): Promise;
137 | /**
138 | * 随机延时
139 | * @param min 最小延时时间
140 | * @param max 最大延时时间
141 | */
142 | randomDelay(min: number, max: number): Promise;
143 | }
144 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.risk.buid.fp.d.ts:
--------------------------------------------------------------------------------
1 | declare function gen_buvid_fp(browserData: any): string;
2 | export { gen_buvid_fp };
3 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.risk.dm.img.d.ts:
--------------------------------------------------------------------------------
1 | /**获取dm参数 */
2 | export declare function getDmImg(): Promise<{
3 | dm_img_list: string;
4 | dm_img_str: string;
5 | dm_cover_img_str: string;
6 | dm_img_inter: string;
7 | }>;
8 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.risk.ticket.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get Bilibili web ticket
3 | * @param {string | null} csrf CSRF token, can be empty or null, or the cookie's bili_jct value
4 | * @returns {Promise<{ code: number, ticket: string, created_at: number, ttl: number }>}
5 | * Promise that resolves to an object containing code, ticket, created_at, and ttl values
6 | */
7 | export declare function getBiliTicket(csrf: string | null): Promise<{
8 | code?: number;
9 | ticket?: string;
10 | created_at?: number;
11 | ttl?: number;
12 | }>;
13 |
--------------------------------------------------------------------------------
/types/models/bilibili/bilibili.risk.wbi.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md#javascript
3 | * 对实际请求参数进行 wbi 签名, 生成 wbi 签名
4 | * @param {object} params 除了 wbi 签名外的全部请求参数,例如 api get请求的查询参数 { uid: 12345678, jsonp: jsonp}
5 | * @param {object} headers 必需要 referer 和 UA 两个请求头
6 | */
7 | export declare function getWbiSign(params: any, headers: any, cookie: string): Promise<{
8 | query: string;
9 | w_rid: string;
10 | time_stamp: number;
11 | }>;
12 |
--------------------------------------------------------------------------------
/types/models/help/help.d.ts:
--------------------------------------------------------------------------------
1 | export default class Help {
2 | e?: any;
3 | model: string;
4 | constructor(e?: any);
5 | static get(e?: any): Promise;
6 | getData(): Promise;
7 | }
8 |
--------------------------------------------------------------------------------
/types/models/version/version.d.ts:
--------------------------------------------------------------------------------
1 | export default class VersionData {
2 | model: string;
3 | cache: any;
4 | versionPath: string;
5 | constructor();
6 | /**
7 | * CHANGELOG.md内容支持示例:
8 | * # 1.0.0
9 | * * 新增功能3
10 | * * 新增功能4
11 | *
12 | * # 0.1.0
13 | * * 新增功能1
14 | * * 新增功能2
15 | */
16 | getChangelogContent(): Promise;
17 | }
18 |
--------------------------------------------------------------------------------
/types/models/weibo/weibo.main.api.d.ts:
--------------------------------------------------------------------------------
1 | declare class WeiboApi {
2 | weiboConfigData: any;
3 | USER_AGENT: string;
4 | constructor();
5 | static WEIBO_USER_AGENT: string;
6 | initialize(): Promise;
7 | initUserAgent(): Promise;
8 | get WEIBO_API(): {
9 | weiboGetIndex: string;
10 | weiboAjaxSearch: string;
11 | };
12 | /**统一设置header */
13 | get WEIBO_HEADERS(): {
14 | Accept: string;
15 | 'Accept-language': string;
16 | Authority: string;
17 | 'Cache-control': string;
18 | 'Sec-fetch-dest': string;
19 | 'Sec-fetch-mode': string;
20 | 'Sec-fetch-site': string;
21 | 'Upgrade-insecure-requests': string;
22 | 'User-agent': string;
23 | };
24 | }
25 | declare const _default: WeiboApi;
26 | export default _default;
27 |
--------------------------------------------------------------------------------
/types/models/weibo/weibo.main.get.web.data.d.ts:
--------------------------------------------------------------------------------
1 | export declare class WeiboWebDataFetcher {
2 | e?: any;
3 | constructor(e?: any);
4 | /**通过uid获取博主信息 */
5 | getBloggerInfo(target: any): Promise>;
6 | /**通过关键词搜索微博大v */
7 | searchBloggerInfo(keyword: string): Promise>;
8 | /**获取主页动态资源相关数组 */
9 | getBloggerDynamicList(target: any): Promise;
10 | }
11 |
--------------------------------------------------------------------------------
/types/models/weibo/weibo.main.query.d.ts:
--------------------------------------------------------------------------------
1 | export declare class WeiboQuery {
2 | /**获取文章id */
3 | static getDynamicId(post: any): any;
4 | /**获取指定动态类型的原始数据 */
5 | static filterCardTypeCustom(raw_post: any): boolean;
6 | /**转换微博动态创建时间:(created_at)转换为 UNIX 时间戳(以毫秒为单位) */
7 | static getDynamicCreatetDate(raw_post: any): number;
8 | /**分类动态,返回标识 */
9 | static MakeCategory(raw_post: any): "DYNAMIC_TYPE_AV" | "DYNAMIC_TYPE_DRAW" | "DYNAMIC_TYPE_ARTICLE" | "DYNAMIC_TYPE_FORWARD" | "DYNAMIC_TYPE_UNKNOWN";
10 | /**筛选正文 */
11 | static filterText(raw_text: string): string;
12 | /** 获取并生成微博动态渲染数据 */
13 | static formatDynamicData(raw_post: any): Promise<{
14 | uid: any;
15 | data: {
16 | [key: string]: any;
17 | };
18 | }>;
19 | /**
20 | * 动态内容富文本节点解析
21 | * @param nodes - 动态内容富文本节点
22 | * @returns 解析后的动态内容富文本
23 | */
24 | static parseRichTextNodes: (nodes: any[] | string | any) => any;
25 | /**
26 | * 生成动态消息文字内容
27 | * @param upName - UP主名称
28 | * @param formatData - 动态数据
29 | * @param isForward - 是否为转发动态
30 | * @param setData - 设置数据
31 | * @returns 生成的动态消息文字内容
32 | */
33 | static formatTextDynamicData(upName: string, raw_post: any, isForward?: boolean, setData?: any): Promise<"continue" | {
34 | msg: any[];
35 | pics: any[];
36 | dynamicType: "DYNAMIC_TYPE_AV";
37 | } | {
38 | msg: any[];
39 | pics: any[];
40 | dynamicType: "DYNAMIC_TYPE_DRAW";
41 | } | {
42 | msg: any[];
43 | pics: any[];
44 | dynamicType: "DYNAMIC_TYPE_ARTICLE";
45 | } | {
46 | msg: any[];
47 | pics: any[];
48 | dynamicType: "DYNAMIC_TYPE_FORWARD";
49 | }>;
50 | static dynamicContentLimit(content: string, setData: any): string;
51 | static formatUrl(url: string): string;
52 | /**推送类型设置 */
53 | static typeHandle(up: any, msg: string, type: string): unknown[];
54 | }
55 |
--------------------------------------------------------------------------------
/types/models/weibo/weibo.main.task.d.ts:
--------------------------------------------------------------------------------
1 | import { MainProps } from '@/components/dynamic/MainPage';
2 | import { ScreenshotOptions } from '@/utils/puppeteer.render';
3 | export declare class WeiboTask {
4 | taskName: string;
5 | groupKey: string;
6 | privateKey: string;
7 | e?: any;
8 | constructor(e?: any);
9 | /**
10 | * 执行动态推送任务
11 | */
12 | runTask(): Promise;
13 | /**
14 | * 处理微博数据,获取动态列表并构建 uid 映射
15 | * @param weiboPushData 微博推送数据
16 | * @param uidMap uid 映射
17 | * @param dynamicList 动态列表
18 | */
19 | processWeiboData(weiboPushData: {
20 | group?: {
21 | [chatId: string]: {
22 | bot_id: string;
23 | uid: string;
24 | name: string;
25 | type: string[];
26 | }[];
27 | };
28 | private?: {
29 | [chatId: string]: {
30 | bot_id: string;
31 | uid: string;
32 | name: string;
33 | type: string[];
34 | }[];
35 | };
36 | }, uidMap: Map>>>, dynamicList: any): Promise;
40 | /**
41 | * 构建uid对应动态数据映射
42 | * @param uidMap uid 映射
43 | * @param dynamicList 动态列表
44 | * @param now 当前时间戳
45 | * @param dynamicTimeRange 筛选何时发布的动态
46 | * @param weiboConfigData 微博配置数据
47 | */
48 | makeUidDynamicDataMap(uidMap: Map>>>, dynamicList: any, now: number, dynamicTimeRange: number, weiboConfigData: any, messageMap: Map>>): Promise;
57 | /**
58 | * 渲染构建待发送的动态消息数据的映射数组
59 | * @param chatId 聊天 ID
60 | * @param bot_id 机器人 ID
61 | * @param upName 博主用户名
62 | * @param pushDynamicData 推送动态数据
63 | * @param weiboConfigData 微博配置数据
64 | * @param chatType 聊天类型
65 | * @param messageMap 待发送的动态消息映射
66 | */
67 | makeDynamicMessageMap(chatId: string | number, bot_id: string | number, upName: string, pushDynamicData: any, weiboConfigData: any, chatType: string, messageMap: Map>>): Promise;
73 | /**
74 | * 构建渲染数据
75 | * @param extentData 扩展数据
76 | * @param urlQrcodeData URL 二维码数据
77 | * @param boxGrid 是否启用九宫格样式
78 | * @returns 渲染数据
79 | */
80 | buildRenderData(extentData: any, urlQrcodeData: string, boxGrid: boolean): MainProps;
81 | /**
82 | * 渲染动态卡片
83 | * @param uid 用户 ID
84 | * @param renderData 渲染数据
85 | * @param ScreenshotOptionsData 截图选项数据
86 | * @returns 图片数据
87 | */
88 | renderDynamicCard(uid: string | number, renderData: MainProps, ScreenshotOptionsData: ScreenshotOptions): Promise;
89 | /**
90 | * 收集消息映射
91 | * @param messageMap 消息映射
92 | * @param chatType 聊天类型
93 | * @param bot_id 机器人 ID
94 | * @param chatId 聊天 ID
95 | * @param sendMode 发送模式: SINGLE 逐条发送,MERGE 合并发送
96 | * @param dynamicUUid_str 动态 UUID
97 | * @param dynamicType 动态类型
98 | * @param message 消息内容
99 | */
100 | addMessageToMap(messageMap: Map>>, chatType: string, bot_id: string | number, chatId: string | number, sendMode: string, dynamicUUid_str: string, dynamicType: string, messages: any): Promise;
106 | /**
107 | * 推送动态消息
108 | * @param messageMap 消息映射
109 | * @param biliConfigData 微博配置数据
110 | */
111 | sendDynamicMessage(messageMap: Map>>, weiboConfigData: {
117 | [key: string]: string | number | boolean | any[];
118 | }): Promise;
119 | /**
120 | * 发送消息api
121 | * @param chatId 聊天 ID
122 | * @param bot_id 机器人 ID
123 | * @param chatType 聊天类型
124 | * @param message 消息内容
125 | */
126 | sendMsgApi(chatId: string | number, bot_id: string | number, chatType: string, message: any): Promise;
127 | /**
128 | * 发送合并转发消息
129 | * @param chatId 聊天 ID
130 | * @param bot_id 机器人 ID
131 | * @param chatType 聊天类型
132 | * @param message 消息内容
133 | * @returns 是否发送成功
134 | */
135 | sendForwardMsgApi(chatId: string | number, bot_id: string | number, chatType: string, forwardNodes: Array): Promise;
136 | /**
137 | * 随机延时
138 | * @param min 最小延时时间
139 | * @param max 最大延时时间
140 | */
141 | randomDelay(min: number, max: number): Promise;
142 | }
143 |
--------------------------------------------------------------------------------
/types/utils/config.d.ts:
--------------------------------------------------------------------------------
1 | import * as chokidar from 'chokidar';
2 | /**
3 | * Config 类用于管理配置文件的读取和监听
4 | */
5 | declare class Config {
6 | readonly defaultConfigPath: string;
7 | readonly userConfigPath: string;
8 | defaultConfig: Record;
9 | userConfig: Record;
10 | watcher: Record;
11 | constructor();
12 | /** 操作并创建配置文件到指定目录 */
13 | initConfigFiles(): void;
14 | /**
15 | * 通用获取配置文件数据方法
16 | * @param typeDir 配置文件目录类型对应路径 defaultConfig: defaultConfig 或 config: yunzai/data/yuki-plugin/config
17 | * @param appDir 配置app目录
18 | * @param functionName 配置文件名称,不包含.yaml后缀
19 | * @returns {object} 配置数据
20 | */
21 | getConfigData(typeDir: string, appDir: string, functionName: string): any;
22 | /**
23 | * 获取配置文件路径
24 | * @param typeDir 配置文件目录类型对应路径 defaultConfig: defaultConfig 或 config: yunzai/data/yuki-plugin/config
25 | * @param appDir 配置app目录
26 | * @param functionName 配置文件名称,不包含.yaml后缀
27 | * @returns {string} 配置文件路径
28 | */
29 | getConfigFilePath(typeDir: string, appDir: string, functionName: string): string;
30 | /**
31 | * 监听配置文件的变化
32 | * @param configFilePath 文件路径
33 | * @param typeDir 配置文件目录类型 defaultConfig: defaultConfig 或 config: yunzai/data/yuki-plugin/config
34 | * @param appDir 配置app目录
35 | * @param functionName 配置文件名称,不包含.yaml后缀
36 | */
37 | watch(configFilePath: string, typeDir: string, appDir: string, functionName: string): void;
38 | /**
39 | * 获取默认配置
40 | * @param appDir 配置app目录
41 | * @param functionName 配置文件名称,不包含.yaml后缀
42 | */
43 | getDefaultConfig(appDir: string, functionName: string): any;
44 | /**
45 | * 获取用户配置
46 | * @param appDir 配置app目录
47 | * @param functionName 配置文件名称,不包含.yaml后缀
48 | */
49 | getUserConfig(appDir: string, functionName: string): any;
50 | /**
51 | * 保存配置文件
52 | * @param typeDir 插件为起始的配置文件目录
53 | * @param appDir 配置app目录
54 | * @param functionName 配置文件名称,不包含.yaml后缀
55 | * @param data 配置数据
56 | */
57 | saveConfig(typeDir: string, appDir: string, functionName: string, data: any): void;
58 | /**
59 | * 更新并保存配置项
60 | * @param appDir 配置app目录
61 | * @param functionName 配置文件名称,不包含.yaml后缀
62 | * @param key 配置项的键
63 | * @param value 配置项的值
64 | */
65 | updateConfigItem(appDir: string, functionName: string, key: string, value: any): void;
66 | /** 读取package.json文件,获取指定key的值
67 | * @param keyName 要获取的key名称
68 | * @param path package.json文件路径
69 | */
70 | getPackageJsonKey(keyName: string, path: string): string | null;
71 | }
72 | declare const _default: Config;
73 | export default _default;
74 |
--------------------------------------------------------------------------------
/types/utils/image.d.ts:
--------------------------------------------------------------------------------
1 | import { ComponentCreateOpsionType } from 'jsxp';
2 | import { ScreenshotOptions } from '@/utils/puppeteer.render';
3 | /**
4 | * 渲染列队中的任务
5 | * @param uid 唯一标识符
6 | * @param page 组件名称
7 | * @param props 传入的组件参数
8 | * @param ComponentCreateOpsion 组件创建选项
9 | * @param ScreenshotOptions 截图选项
10 | * @returns {false | {img: buffer[]}}
11 | */
12 | declare const renderPage: (uid: number | string, page: string, props?: T, ScreenshotOptions?: ScreenshotOptions, ComponentCreateOpsion?: ComponentCreateOpsionType) => Promise;
15 | export { renderPage };
16 |
--------------------------------------------------------------------------------
/types/utils/paths.d.ts:
--------------------------------------------------------------------------------
1 | export declare const pluginName: string;
2 | export declare const _paths: {
3 | root: string;
4 | botData: string;
5 | botYukiData: string;
6 | botTempPath: string;
7 | pluginPath: string;
8 | pluginResources: string;
9 | pluginName: string;
10 | };
11 | /**
12 | * 使用import.meta.url得到require
13 | * @param basePath
14 | * @returns
15 | * 这并不是
16 | * ***
17 | * import { createRequire } from "module"
18 | * ***
19 | * 原型为
20 | * new URL(path, import.meta.url).href
21 | */
22 | export declare const createRequire: (basePath: string) => (path: string) => string;
23 |
--------------------------------------------------------------------------------
/types/utils/puppeteer.render.d.ts:
--------------------------------------------------------------------------------
1 | import { Puppeteer } from 'jsxp';
2 | export type ScreenshotOptions = {
3 | SOptions?: {
4 | type: 'jpeg' | 'png' | 'webp';
5 | quality: number;
6 | };
7 | tab?: string;
8 | timeout?: number;
9 | isSplit?: boolean;
10 | addStyle?: string;
11 | header?: {
12 | [key: string]: string;
13 | };
14 | pageSplitHeight?: number;
15 | pageWidth?: number;
16 | modelName?: string;
17 | saveHtmlfile?: boolean;
18 | };
19 | export declare class YukiPuppeteerRender {
20 | private puppeteerInstance;
21 | constructor(puppeteerInstance: Puppeteer);
22 | /**
23 | * 截图并返回buffer
24 | * @param htmlPath 绝对路径
25 | * @param tab 截图元素位 默认 body
26 | * @param type 图片类型,默认png
27 | * @param quality 清晰度,默认100
28 | * @param timeout 响应检查,默认120000
29 | * @param isSplit 是否分割图片 booelan,默认undefined,如果为undefined则截取整个页面为一张图片,如果为true则按照 pageSplitHeight 高度分割全部页面,如果为false则截取页面的第一个默认pageSplitHeight高度的页面
30 | * @param pageSplitHeight 分割图片高度,默认 8000。
31 | * @param pageWidth 页面宽度,默认 900
32 | * @param addStyle 额外的 CSS 样式 示例 '.ql-editor { max-height: 100% !important; overflow-x: hidden; }'
33 | * @param header 请求头 { [key: string]: string },示例:{ 'referer': 'https://space.bilibili.com' }
34 | * @param modelName 调用模块名称,默认yuki-plugin
35 | * @returns {false | {img: buffer[]}}
36 | */
37 | yukiScreenshot(htmlPath: string, Options?: ScreenshotOptions): Promise;
40 | }
41 |
--------------------------------------------------------------------------------
/utils/config.js:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import YAML from 'yaml';
3 | import * as chokidar from 'chokidar';
4 | import lodash from 'lodash';
5 | import path from 'path';
6 | import { _paths } from './paths.js';
7 |
8 | /**
9 | * Config 类用于管理配置文件的读取和监听
10 | */
11 | class Config {
12 | defaultConfigPath;
13 | userConfigPath;
14 | defaultConfig;
15 | userConfig;
16 | watcher;
17 | constructor() {
18 | /** 默认设置 */
19 | this.defaultConfigPath = path.join(_paths.pluginPath, 'defaultConfig');
20 | this.defaultConfig = {};
21 | /** 用户设置 */
22 | this.userConfigPath = path.join(_paths.pluginPath, 'config');
23 | this.userConfig = {};
24 | /** 监听文件 */
25 | this.watcher = {};
26 | this.initConfigFiles();
27 | }
28 | /** 操作并创建配置文件到指定目录 */
29 | initConfigFiles() {
30 | const configFiles = [
31 | {
32 | configFile: path.join(_paths.botYukiData, 'config/bilibili/config.yaml'),
33 | defaultFile: path.join(_paths.pluginPath, 'defaultConfig/bilibili/config.yaml'),
34 | dir: 'config/bilibili'
35 | },
36 | {
37 | configFile: path.join(_paths.botYukiData, 'config/bilibili/push.yaml'),
38 | defaultFile: path.join(_paths.pluginPath, 'defaultConfig/bilibili/push.yaml'),
39 | dir: 'config/bilibili'
40 | },
41 | {
42 | configFile: path.join(_paths.botYukiData, 'config/weibo/config.yaml'),
43 | defaultFile: path.join(_paths.pluginPath, 'defaultConfig/weibo/config.yaml'),
44 | dir: 'config/weibo'
45 | },
46 | {
47 | configFile: path.join(_paths.botYukiData, 'config/weibo/push.yaml'),
48 | defaultFile: path.join(_paths.pluginPath, 'defaultConfig/weibo/push.yaml'),
49 | dir: 'config/weibo'
50 | }
51 | ];
52 | for (const { configFile, defaultFile, dir } of configFiles) {
53 | if (!fs.existsSync(configFile)) {
54 | const configDir = path.join(_paths.botYukiData, dir);
55 | if (!fs.existsSync(configDir)) {
56 | fs.mkdirSync(configDir, { recursive: true });
57 | }
58 | fs.copyFileSync(defaultFile, configFile);
59 | }
60 | }
61 | }
62 | /**
63 | * 通用获取配置文件数据方法
64 | * @param typeDir 配置文件目录类型对应路径 defaultConfig: defaultConfig 或 config: yunzai/data/yuki-plugin/config
65 | * @param appDir 配置app目录
66 | * @param functionName 配置文件名称,不包含.yaml后缀
67 | * @returns {object} 配置数据
68 | */
69 | getConfigData(typeDir, appDir, functionName) {
70 | const configFilePath = this.getConfigFilePath(typeDir, appDir, functionName);
71 | const key = `${typeDir}_${appDir}_${functionName}`;
72 | if (this[key])
73 | return this[key];
74 | this[key] = YAML.parse(fs.readFileSync(configFilePath, 'utf8'));
75 | this.watch(configFilePath, typeDir, appDir, functionName);
76 | return this[key];
77 | }
78 | /**
79 | * 获取配置文件路径
80 | * @param typeDir 配置文件目录类型对应路径 defaultConfig: defaultConfig 或 config: yunzai/data/yuki-plugin/config
81 | * @param appDir 配置app目录
82 | * @param functionName 配置文件名称,不包含.yaml后缀
83 | * @returns {string} 配置文件路径
84 | */
85 | getConfigFilePath(typeDir, appDir, functionName) {
86 | if (typeDir === 'defaultConfig') {
87 | return path.join(_paths.pluginPath, `${typeDir}`, `${appDir}`, `${functionName}.yaml`);
88 | }
89 | else {
90 | return path.join(_paths.botYukiData, `${typeDir}`, `${appDir}`, `${functionName}.yaml`);
91 | }
92 | }
93 | /**
94 | * 监听配置文件的变化
95 | * @param configFilePath 文件路径
96 | * @param typeDir 配置文件目录类型 defaultConfig: defaultConfig 或 config: yunzai/data/yuki-plugin/config
97 | * @param appDir 配置app目录
98 | * @param functionName 配置文件名称,不包含.yaml后缀
99 | */
100 | watch(configFilePath, typeDir, appDir, functionName) {
101 | const key = `${typeDir}_${appDir}_${functionName}`;
102 | if (this.watcher[key])
103 | return;
104 | const watcher = chokidar.watch(configFilePath);
105 | watcher.on('change', () => {
106 | delete this[key];
107 | logger.mark(`[修改配置文件][${typeDir}][${appDir}][${functionName}]`);
108 | if (this[`change_${appDir}${functionName}`]) {
109 | this[`change_${appDir}${functionName}`]();
110 | }
111 | });
112 | this.watcher[key] = watcher;
113 | }
114 | /**
115 | * 获取默认配置
116 | * @param appDir 配置app目录
117 | * @param functionName 配置文件名称,不包含.yaml后缀
118 | */
119 | getDefaultConfig(appDir, functionName) {
120 | return this.getConfigData('defaultConfig', appDir, functionName);
121 | }
122 | /**
123 | * 获取用户配置
124 | * @param appDir 配置app目录
125 | * @param functionName 配置文件名称,不包含.yaml后缀
126 | */
127 | getUserConfig(appDir, functionName) {
128 | const userConfigData = this.getConfigData('config', appDir, functionName);
129 | const defaultConfigData = this.getDefaultConfig(appDir, functionName);
130 | return lodash.merge({}, defaultConfigData, userConfigData);
131 | }
132 | /**
133 | * 保存配置文件
134 | * @param typeDir 插件为起始的配置文件目录
135 | * @param appDir 配置app目录
136 | * @param functionName 配置文件名称,不包含.yaml后缀
137 | * @param data 配置数据
138 | */
139 | saveConfig(typeDir, appDir, functionName, data) {
140 | const filePath = this.getConfigFilePath(typeDir, appDir, functionName);
141 | if (lodash.isEmpty(data)) {
142 | fs.existsSync(filePath) && fs.unlinkSync(filePath);
143 | }
144 | else {
145 | const yamlContent = YAML.stringify(data);
146 | fs.writeFileSync(filePath, yamlContent, 'utf8');
147 | }
148 | }
149 | /**
150 | * 更新并保存配置项
151 | * @param appDir 配置app目录
152 | * @param functionName 配置文件名称,不包含.yaml后缀
153 | * @param key 配置项的键
154 | * @param value 配置项的值
155 | */
156 | updateConfigItem(appDir, functionName, key, value) {
157 | const config = this.getUserConfig(appDir, functionName);
158 | config[key] = value; // 更新配置项
159 | this.saveConfig('config', appDir, functionName, config); // 保存更新后的配置
160 | }
161 | /** 读取package.json文件,获取指定key的值
162 | * @param keyName 要获取的key名称
163 | * @param path package.json文件路径
164 | */
165 | getPackageJsonKey(keyName, path) {
166 | try {
167 | const content = fs.readFileSync(path, 'utf-8');
168 | const packageJson = JSON.parse(content);
169 | const match = packageJson[keyName];
170 | if (match) {
171 | return match;
172 | }
173 | else {
174 | return null;
175 | }
176 | }
177 | catch (error) {
178 | logger.error(`getPackageJsonKey error: ${error}`);
179 | return null;
180 | }
181 | }
182 | }
183 | var Config$1 = new Config();
184 |
185 | export { Config$1 as default };
186 |
--------------------------------------------------------------------------------
/utils/image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Picture } from 'jsxp';
3 | import { YukiPuppeteerRender } from './puppeteer.render.js';
4 | import * as index from '../components/index.js';
5 |
6 | // 定义 Image 类,继承自 Picture 类
7 | class Image extends Picture {
8 | // 私有属性,用于存储 YukiPuppeteerRender 实例
9 | yukiPuppeteerRender;
10 | /**
11 | * 构造函数,整合截图方法
12 | * @param launchOptions Puppeteer 启动选项,可选。父类有默认的设置。
13 | */
14 | constructor() {
15 | // 继承父类实例
16 | super();
17 | // 父类已经实例化组件渲染对象
18 | //this.component;
19 | // 父类已经实例化启动 Puppeteer
20 | //this.puppeteer.start();
21 | // 初始化 YukiPuppeteerRender 实例
22 | this.yukiPuppeteerRender = new YukiPuppeteerRender(this.puppeteer);
23 | }
24 | /**
25 | * 实例方法,用于执行实际的渲染和截图操作
26 | * @param uid 唯一标识符
27 | * @param page 组件名称
28 | * @param props 传入的组件参数
29 | * @param ComponentCreateOpsion 组件创建选项
30 | * @param ScreenshotOptions 截图选项
31 | * @returns {false | {img: buffer[]}}
32 | */
33 | async _renderPage(uid, page, props = {}, ScreenshotOptions, ComponentCreateOpsion) {
34 | // 根据组件名称获取对应的 React 组件
35 | const Page = index[page];
36 | // 调用 yukiPuppeteerRender 进行截图操作
37 | return this.yukiPuppeteerRender.yukiScreenshot(this.component.compile({
38 | path: page,
39 | name: `${uid}.html`,
40 | component: React.createElement(Page, { ...props }),
41 | ...ComponentCreateOpsion
42 | }), ScreenshotOptions);
43 | }
44 | }
45 | // 存储单例实例
46 | let instance;
47 | // 存储任务队列
48 | const queue = [];
49 | // 标记当前是否有任务正在处理
50 | let isProcessing = false;
51 | /**
52 | * 处理队列中的任务
53 | */
54 | const processQueue = async () => {
55 | // 如果队列为空,设置 isProcessing 为 false 并返回
56 | if (queue.length === 0) {
57 | isProcessing = false;
58 | return;
59 | }
60 | // 设置 isProcessing 为 true,表示有任务正在处理
61 | isProcessing = true;
62 | // 从队列中取出第一个任务
63 | const { uid, page, props, ScreenshotOptions, ComponentCreateOpsion, resolve, reject } = queue.shift();
64 | try {
65 | // 调用实例方法 renderPage 执行任务
66 | const img = await instance._renderPage(uid, page, props, ScreenshotOptions, ComponentCreateOpsion);
67 | // 任务成功完成,调用 resolve 回调函数
68 | resolve(img);
69 | }
70 | catch (error) {
71 | // 任务失败,打印错误信息并调用 reject 回调函数
72 | console.error(error);
73 | reject(false);
74 | }
75 | // 处理下一个任务
76 | processQueue();
77 | };
78 | /**
79 | * 渲染列队中的任务
80 | * @param uid 唯一标识符
81 | * @param page 组件名称
82 | * @param props 传入的组件参数
83 | * @param ComponentCreateOpsion 组件创建选项
84 | * @param ScreenshotOptions 截图选项
85 | * @returns {false | {img: buffer[]}}
86 | */
87 | const renderPage = async (uid, page, props = {}, ScreenshotOptions, ComponentCreateOpsion) => {
88 | if (!instance) {
89 | instance = new Image();
90 | }
91 | return new Promise((resolve, reject) => {
92 | // 将任务添加到队列中
93 | queue.push({ uid, page, props, ScreenshotOptions, ComponentCreateOpsion, resolve, reject });
94 | // 如果没有任务正在处理,则开始处理队列
95 | if (!isProcessing) {
96 | processQueue();
97 | }
98 | });
99 | };
100 |
101 | export { renderPage };
102 |
--------------------------------------------------------------------------------
/utils/paths.js:
--------------------------------------------------------------------------------
1 | import { dirname, join, basename } from 'path';
2 | import { fileURLToPath } from 'url';
3 |
4 | const _path = process.cwd();
5 | const thisFilePath = dirname(fileURLToPath(import.meta.url));
6 | const pluginPath = join(thisFilePath, '..');
7 | const pluginName = basename(pluginPath);
8 | const _paths = {
9 | root: _path, // Bot根目录
10 | botData: join(_path, 'data'), // BotData目录
11 | botYukiData: join(_path, 'data/yuki-plugin'), // yuki-Data目录
12 | botTempPath: join(_path, 'temp'), // Bot缓存目录
13 | pluginPath, // yuki-plugin根目录
14 | pluginResources: join(pluginPath, 'resources'), // yuki-plugin资源目录
15 | pluginName // 插件所在文件夹名称
16 | };
17 | /**
18 | * 使用import.meta.url得到require
19 | * @param basePath
20 | * @returns
21 | * 这并不是
22 | * ***
23 | * import { createRequire } from "module"
24 | * ***
25 | * 原型为
26 | * new URL(path, import.meta.url).href
27 | */
28 | const createRequire = (basePath) => {
29 | return (path) => {
30 | if (process.platform === 'linux' || process.platform === 'android' || process.platform === 'darwin') {
31 | return new URL(path, basePath).href.replace(/^file:\/\//, '');
32 | }
33 | else {
34 | return new URL(path, basePath).href.replace(/^file:\/\/\//, ''); // windows
35 | }
36 | };
37 | };
38 |
39 | export { _paths, createRequire, pluginName };
40 |
--------------------------------------------------------------------------------
/utils/puppeteer.render.js:
--------------------------------------------------------------------------------
1 | import fs__default from 'fs';
2 | import path from 'path';
3 | import { _paths } from './paths.js';
4 |
5 | class YukiPuppeteerRender {
6 | puppeteerInstance;
7 | constructor(puppeteerInstance) {
8 | this.puppeteerInstance = puppeteerInstance;
9 | }
10 | /**
11 | * 截图并返回buffer
12 | * @param htmlPath 绝对路径
13 | * @param tab 截图元素位 默认 body
14 | * @param type 图片类型,默认png
15 | * @param quality 清晰度,默认100
16 | * @param timeout 响应检查,默认120000
17 | * @param isSplit 是否分割图片 booelan,默认undefined,如果为undefined则截取整个页面为一张图片,如果为true则按照 pageSplitHeight 高度分割全部页面,如果为false则截取页面的第一个默认pageSplitHeight高度的页面
18 | * @param pageSplitHeight 分割图片高度,默认 8000。
19 | * @param pageWidth 页面宽度,默认 900
20 | * @param addStyle 额外的 CSS 样式 示例 '.ql-editor { max-height: 100% !important; overflow-x: hidden; }'
21 | * @param header 请求头 { [key: string]: string },示例:{ 'referer': 'https://space.bilibili.com' }
22 | * @param modelName 调用模块名称,默认yuki-plugin
23 | * @returns {false | {img: buffer[]}}
24 | */
25 | async yukiScreenshot(htmlPath, Options) {
26 | if (!(await this.puppeteerInstance.isStart()))
27 | return false;
28 | let name = Options?.modelName ?? 'yuki-plugin';
29 | let pageHeight = Options?.pageSplitHeight ?? 8000; // 分割图片高度,默认 8000
30 | try {
31 | const browser = this.puppeteerInstance.browser;
32 | const page = await browser?.newPage().catch(err => {
33 | console.error(err);
34 | });
35 | if (!page)
36 | return false;
37 | // 设置请求 Header
38 | if (Options?.header) {
39 | await page.setExtraHTTPHeaders(Options.header);
40 | }
41 | await page.goto(`file://${htmlPath}`, { timeout: Options?.timeout ?? 120000, waitUntil: ['load', 'networkidle0'] });
42 | const element = await page.$(Options?.tab ?? 'body');
43 | if (!element)
44 | return false;
45 | // 根据 style 的值来修改 CSS 样式
46 | if (Options?.addStyle) {
47 | await page.addStyleTag({ content: Options.addStyle });
48 | }
49 | const boundingBox = await element.boundingBox(); // 获取内容区域的边界框信息
50 | if (!boundingBox)
51 | return false;
52 | const num = Options?.isSplit ? Math.ceil(boundingBox.height / pageHeight) : 1; // 根据是否需要分片,计算分片数量,默认为 1
53 | pageHeight = Math.round(boundingBox.height / num); //动态调整分片高度,防止过短影响观感。
54 | await page.setViewport({ width: boundingBox.width + 50, height: pageHeight + 100 });
55 | // 禁止 GIF 动图播放
56 | await page.addStyleTag({ content: `img[src$=".gif"] {animation-play-state: paused !important;}` });
57 | // 是否保存 html 文件
58 | if (Options?.saveHtmlfile === true) {
59 | const htmlContent = await page.content();
60 | const Dir = path.join(_paths.root, `/temp/html/yuki-plugin/${name}/`);
61 | if (!fs__default.existsSync(Dir)) {
62 | fs__default.mkdirSync(Dir, { recursive: true });
63 | }
64 | fs__default.writeFileSync(`${Dir}${Date.now()}.html`, htmlContent);
65 | }
66 | console.info('[puppeteer] success');
67 | let start = Date.now();
68 | const ret = new Array();
69 | for (let i = 1; i <= num; i++) {
70 | if (i > 1) {
71 | await page.evaluate(pageHeight => {
72 | window.scrollBy(0, pageHeight);
73 | }, pageHeight); // 在页面上下文中执行滚动操作
74 | await new Promise(resolve => setTimeout(resolve, 500)); // 等待一段时间,确保页面加载完成
75 | }
76 | let renderOptions = Options?.SOptions ?? { type: 'png' };
77 | const screenshotOptions = {
78 | ...renderOptions,
79 | clip: {
80 | x: 0,
81 | y: pageHeight * (i - 1), // 根据分片序号计算截图区域的起始位置
82 | width: Math.round(boundingBox.width), // 截图区域的宽度与内容区域宽度一致
83 | height: Math.min(pageHeight, boundingBox.height - pageHeight * (i - 1)) // 截图区域的高度取决于内容区域剩余的高度或者默认的分片高度
84 | }
85 | };
86 | const buff = await element.screenshot(screenshotOptions).catch(err => {
87 | console.error('[puppeteer]', 'screenshot', err);
88 | return false;
89 | }); // 对指定区域进行截图
90 | if (buff !== false) {
91 | const imgBuff = !Buffer.isBuffer(buff) ? Buffer.from(buff) : buff;
92 | /** 计算图片大小 */
93 | const kb = (imgBuff?.length / 1024).toFixed(2) + 'kb'; // 计算图片大小
94 | console.warn(`[图片生成][${name}][${i}次] ${kb} ${`${Date.now() - start}ms`}`); // 记录日志
95 | ret.push(imgBuff); // 将截图结果添加到数组中
96 | }
97 | else {
98 | console.error(`[puppeteer]`, '截图失败');
99 | }
100 | }
101 | if (ret.length === 0 || !ret[0]) {
102 | console.error(`[图片生成][${name}] 图片生成为空`);
103 | return false;
104 | }
105 | // 关闭页面
106 | await page.close().catch(err => console.error(err));
107 | return { img: ret }; // 返回图像数组
108 | }
109 | catch (err) {
110 | console.error('[puppeteer] newPage', err);
111 | return false;
112 | }
113 | }
114 | }
115 |
116 | export { YukiPuppeteerRender };
117 |
--------------------------------------------------------------------------------