├── .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://profile-counter.glitch.me/yuki-plugin/count.svg)](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 | ![](https://github.com/snowtafir/xianxin-plugin/raw/main/resources/img/REDME/redme00.png) 139 | ![](https://github.com/snowtafir/xianxin-plugin/raw/main/resources/img/REDME/redme01.png) 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 | --------------------------------------------------------------------------------