21 |
22 | [](https://github.com/ikenxuan/kkkkkk-10086/blob/master/LICENSE)
23 | [](https://github.com/ikenxuan/kkkkkk-10086/releases)
24 |
25 |
26 |
27 | # [点击立即阅读插件文档](https://ikenxuan.github.io/kkkkkk-10086/) 📖
28 |
29 | ## 贡献者 🌟
30 |
31 | > 🌟 星光闪烁,你们的智慧如同璀璨的夜空。感谢所有为 **kkkkkk-10086** 做出贡献的人!
32 |
33 | [](https://github.com/ikenxuan/kkkkkk-10086/graphs/contributors)
34 |
35 | 
36 |
37 | [](https://star-history.com/#ikenxuan/kkkkkk-10086&Date)
38 |
39 | ## 免责声明 ❗
40 |
41 | 本项目提供的开源代码是出于学习进行开发。如果您认为该项目侵犯了您的知识产权或其他合法权益,请通过 **[QQ](https://qm.qq.com/q/k6Up32hdWE)** 向我们提供书面通知。我们将在收到有效通知后,尽快进行审查,并采取必要的措施。
42 |
43 | > [!CAUTION]
44 | >
45 | > **未经同意,禁止将本项目的开源代码用于任何商业目的。因使用本项目产生的一切问题与后果由使用者自行承担,项目开发者不承担任何责任**
46 |
47 | 我们保留随时修改本免责声明的权利,并且这些修改将立即生效。
48 |
49 | ## 鸣谢 😊
50 |
51 | **业务站点**
52 |
53 | - [wwww.douyin.com](https://www.douyin.com) & [www.bilibili.com](https://www.bilibili.com) & [www.kuaishou.com](https://www.kuaishou.com)
54 |
55 | 本项目的开发参考了以下开源项目部分代码,排名不分先后
56 |
57 | **部分代码借鉴**
58 |
59 | - [xfdown/xiaofei-plugin](https://gitee.com/xfdown/xiaofei-plugin)
60 | - [ikechan8370/chatgpt-plugin](https://github.com/ikechan8370/chatgpt-plugin)
61 | - [kyrzy0416/rconsole-plugin](https://gitee.com/kyrzy0416/rconsole-plugin)
62 | - [think-first-sxs/reset-qianyu-plugin](https://gitee.com/think-first-sxs/reset-qianyu-plugin)
63 | - [yeyang52/yenai-plugin](https://github.com/yeyang52/yenai-plugin)
64 | - [XasYer/Shiranai-Plugin](https://github.com/XasYer/Shiranai-Plugin)
65 | - ...
66 |
67 | **接口文档与加密参数算法**
68 |
69 | - [SocialSisterYi/bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
70 | - [NearHuiwen/TiktokDouyinCrawler](https://github.com/NearHuiwen/TiktokDouyinCrawler)
71 | - [B1gM8c/X-Bogus](https://github.com/B1gM8c/X-Bogus)
72 |
73 | **友情链接**
74 |
75 | - Miao-Yunzai ☞ [**GitHub**](https://github.com/yoimiya-kokomi/Miao-Yunzai) | [**Gitee**](https://gitee.com/yoimiya-kokomi/Miao-Yunzai)
76 | - TRSS-Yunzai ☞ [**GitHub**](https://github.com/TimeRainStarSky/Yunzai) | [**Gitee**](https://gitee.com/TimeRainStarSky/Yunzai)
77 | - Yunzai-Bot 插件库 ☞ [**Github**](https://github.com/yhArcadia/Yunzai-Bot-plugins-index) | [**Gitee**](https://gitee.com/yhArcadia/Yunzai-Bot-plugins-index)
78 | - ~~Lain-plugin ☞ [**GitHub**](https://github.com/Loli-Lain/Lain-plugin) | [**Gitee**](https://gitee.com/Zyy955/Lain-plugin)~~
79 | - icqq 协议 ☞ [**GitHub**](https://github.com/icqqjs/icqq)
80 | - Karin 框架 ☞ [**GitHub**](https://github.com/Karinjs/Karin) | [**文档**](https://karin.fun)
81 | - Yunzai NEXT [**GitHub**](https://github.com/yunzai-org/yunzaijs) | [**文档**](https://yunzaijs.com/docs/)
82 |
--------------------------------------------------------------------------------
/apps/admin.js:
--------------------------------------------------------------------------------
1 | import { Config, Render, Version } from '../module/utils/index.js'
2 | import { dylogin } from '../module/platform/douyin/login.js'
3 | import BiLogin from '../module/platform/bilibili/login.js'
4 | import path from 'path'
5 | import fs from 'fs'
6 |
7 | const APPType = {
8 | 缓存删除: 'rmmp4',
9 | 视频解析: 'videotool',
10 | 默认解析: 'defaulttool',
11 | 转发: 'sendforwardmsg',
12 | 上传拦截: 'usefilelimit',
13 | API服务: 'APIServer',
14 | API服务日志: 'APIServerLog'
15 | }
16 |
17 | const DouYinType = {
18 | 抖音评论: 'comments',
19 | 抖音推送: 'douyinpush',
20 | 抖音评论图: 'commentsimg',
21 | 抖音推送日志: 'douyinpushlog',
22 | 抖音推送表达式: 'douyinpushcron',
23 | 抖音解析提示: 'douyintip',
24 | 抖音解析: 'douyintool',
25 | 抖音高清语音: 'sendHDrecord',
26 | 抖音推送解析: 'senddynamicwork'
27 | }
28 |
29 | const BilibiliType = {
30 | 设置B站评论: 'bilibilicommentsimg',
31 | B站推送: 'bilibilipush',
32 | B站推送日志: 'bilibilipushlog',
33 | B站推送表达式: 'bilibilipushcron',
34 | B站解析提示: 'bilibilitip',
35 | B站解析: 'bilibilitool',
36 | B站推送解析: 'senddynamicvideo',
37 | B站内容优先: 'videopriority'
38 | }
39 |
40 | const KuaiShouType = {
41 | 快手解析: 'kuaishoutool',
42 | 快手解析提示: 'kuaishoutip'
43 | }
44 |
45 | /** 数字相关设置 */
46 | const NumberCfgType = {
47 | 抖音评论数量: { type: 'douyin', key: 'numcomments', limit: '0-50' },
48 | B站评论数量: { type: 'bilibili', key: 'bilibilinumcomments', limit: '0-20' },
49 | 抖音推送设置权限: { type: 'douyin', key: 'douyinpushGroup', limit: '0-2' },
50 | B站推送设置权限: { type: 'bilibili', key: 'bilibilipushGroup', limit: '0-2' },
51 | 渲染精度: { type: 'app', key: 'renderScale', limit: '50-200' },
52 | 优先级: { type: 'app', key: 'priority', limit: '0-114514' },
53 | 上传拦截阈值: { type: 'app', key: 'filelimit', limit: '5-114514' },
54 | 快手评论数量: { type: 'kuaishou', key: 'kuaishounumcomments', limit: '0-30' }
55 | }
56 |
57 | /** 开关相关设置 */
58 | const SwitchCfgType = {
59 | ...APPType,
60 | ...DouYinType,
61 | ...BilibiliType,
62 | ...KuaiShouType
63 | }
64 |
65 | const FileWitch = {
66 | app: APPType,
67 | douyin: DouYinType,
68 | bilibili: BilibiliType
69 | }
70 |
71 | const SwitchCfgReg = new RegExp(`^#kkk设置(${Object.keys(SwitchCfgType).join('|')})(开启|关闭)$`)
72 | const NumberCfgReg = new RegExp(`^#kkk设置(${Object.keys(NumberCfgType).join('|')})(\\d+)$`)
73 | export class kkkAdmin extends plugin {
74 | constructor () {
75 | super({
76 | name: 'kkkkkk-10086-设置',
77 | event: 'message',
78 | priority: 100,
79 | rule: [
80 | {
81 | reg: SwitchCfgReg,
82 | fnc: 'ConfigSwitch',
83 | permission: 'master'
84 | },
85 | {
86 | reg: NumberCfgReg,
87 | fnc: 'ConfigNumber',
88 | permission: 'master'
89 | },
90 | {
91 | reg: /^#kkk设置$/,
92 | fnc: 'index_Settings',
93 | permission: 'master'
94 | },
95 | {
96 | reg: /^#?(kkk)?s*设置抖音ck$/i,
97 | fnc: 'setdyck',
98 | permission: 'master'
99 | },
100 | {
101 | reg: /^#?(kkk)?s*设置s*(B站)ck$/i,
102 | fnc: 'setbilick',
103 | permission: 'master'
104 | },
105 | {
106 | reg: /^#?(kkk)?s*设置快手ck$/i,
107 | fnc: 'setksck',
108 | permission: 'master'
109 | },
110 | {
111 | reg: /^#?(kkk)?\s*B站\s*(扫码)?\s*登录$/i,
112 | fnc: 'Blogin',
113 | permission: 'master'
114 | },
115 | {
116 | reg: /^#?(kkk)?\s*抖音(扫码)?\s*登录$/i,
117 | fnc: 'dylogin',
118 | permission: 'master'
119 | },
120 | {
121 | reg: /^#?kkk删除缓存$/,
122 | fnc: 'deltemp',
123 | permission: 'master'
124 | }
125 | ]
126 | })
127 | this.task = []
128 | if (Config.app.rmmp4) {
129 | this.task.push({
130 | cron: '0 0 4 * * *',
131 | name: '[kkkkkk-10086] 视频缓存自动删除',
132 | fnc: () => this.deltemp(),
133 | log: true
134 | })
135 | }
136 | }
137 |
138 | async deltemp () {
139 | removeAllFiles(process.cwd() + '/resources/kkkdownload/video/')
140 | .then(() => this.reply('/resources/kkkdownload/video/' + '所有文件已删除'))
141 | .catch((err) => console.error('删除文件时出错:', err))
142 | }
143 |
144 | /** 修改开关设置 */
145 | async ConfigSwitch (e) {
146 | // 解析消息
147 | const regRet = SwitchCfgReg.exec(e.msg)
148 | const key = regRet[1]
149 | const file = Object.entries(FileWitch)
150 | .find(([, values]) => Object.keys(values).includes(key))[0]
151 | const is = regRet[2] == '开启'
152 | const _key = SwitchCfgType[key]
153 | Config.modify(file, _key?.key ?? _key, is)
154 | // 渲染图片
155 | await this.index_Settings(e)
156 | return true
157 | }
158 |
159 | // 修改数字设置
160 | async ConfigNumber (e) {
161 | const regRet = e.msg.match(NumberCfgReg)
162 | const type = NumberCfgType[regRet[1]]
163 | const number = checkNumberValue(regRet[2], type.limit)
164 | if (type.key === 'douyinpushGroup' || type.key === 'bilibilipushGroup') {
165 | const groupMapping = { 0: 'all', 1: 'admin', 2: 'owner', 3: 'master' }
166 |
167 | if (groupMapping.hasOwnProperty(number)) {
168 | Config.modify(type.type, type.key, groupMapping[number])
169 | } else {
170 | Config.modify(type.type, type.key, groupMapping['3'])
171 | }
172 | } else {
173 | Config.modify(type.type, type.key, number)
174 | }
175 | await this.index_Settings(e)
176 | return true
177 | }
178 |
179 | // 渲染发送图片
180 | async index_Settings (e) {
181 | const data = {}
182 | let _cfg = Config.All()
183 | _cfg = (function () {
184 | const { douyin, bilibili, kuaishou } = _cfg.cookies
185 | _cfg.cookies.b = bilibili
186 | _cfg.cookies.d = douyin
187 | _cfg.cookies.k = kuaishou
188 | delete _cfg.cookies.bilibili
189 | delete _cfg.cookies.douyin
190 | delete _cfg.cookies.kuaishou
191 | return _cfg
192 | })()
193 | for (const item in _cfg) {
194 | for (const key in _cfg[item]) {
195 | data[key] = getStatus(_cfg[item][key])
196 | }
197 | }
198 |
199 | const img = await Render.render('admin/index', { data })
200 | await e.reply(img)
201 | return true
202 | }
203 |
204 | async Blogin (e) {
205 | await new BiLogin(e).Login()
206 | return true
207 | }
208 |
209 | async dylogin (e) {
210 | await dylogin(e)
211 | return true
212 | }
213 |
214 | async setdyck () {
215 | this.setContext('savedyck')
216 | await this.reply('请发在120秒内送抖音ck\n教程:https://ikenxuan.github.io/kkkkkk-10086/docs/intro/other#%E9%85%8D%E7%BD%AE%E4%B8%8D%E5%90%8C%E5%B9%B3%E5%8F%B0%E7%9A%84-cookies\n', true)
217 | return true
218 | }
219 |
220 | async savedyck () {
221 | Config.modify('cookies', 'douyin', String(this.e.msg))
222 | this.reply('设置成功!')
223 | this.finish('savedyck')
224 | return true
225 | }
226 |
227 | async setbilick () {
228 | this.setContext('savebilick')
229 | await this.reply('请发在120秒内送B站ck\n教程:https://ikenxuan.github.io/kkkkkk-10086/docs/intro/other#%E9%85%8D%E7%BD%AE%E4%B8%8D%E5%90%8C%E5%B9%B3%E5%8F%B0%E7%9A%84-cookies\n')
230 | return true
231 | }
232 |
233 | async savebilick () {
234 | Config.modify('cookies', 'bilibili', String(this.e.msg))
235 | this.reply('设置成功!')
236 | this.finish('savebilick')
237 | return true
238 | }
239 |
240 | async setksck () {
241 | this.setContext('saveksck')
242 | const img = `${Version}/plugins/kkkkkk-10086/resources/image/pic1.png`
243 | await this.reply(['请发送快手ck\n', '教程:https://docs.qq.com/doc/DRExRWUh1a3l4bnlI\n', segment.image(img)])
244 | return true
245 | }
246 |
247 | async saveksck () {
248 | Config.modify('cookies', 'kuaishou', String(this.e.msg))
249 | this.reply('设置成功!')
250 | this.finish('saveksck')
251 | return true
252 | }
253 | }
254 |
255 | const getStatus = function (rote) {
256 | switch (true) {
257 | case Array.isArray(rote):
258 | if (rote.length === 0) return `
已配置 ${rote.length} 项
`
259 | else return `
已配置 ${rote.length} 项
`
260 | case rote === true:
261 | return '
已开启
'
262 | case rote === false:
263 | return '
已关闭
'
264 | default:
265 | if (rote == null || rote === '') return '
未配置
'
266 | else return `
${String(rote).length > 10 ? String(rote).substring(0, 10) + '...' : String(rote)}
`
267 | }
268 | }
269 |
270 | function checkNumberValue (value, limit) {
271 | // 检查是否存在限制条件
272 | if (!limit) {
273 | return value
274 | }
275 | // 解析限制条件
276 | const [symbol, limitValue] = limit.match(/^([<>])?(.+)$/).slice(1)
277 | const parsedLimitValue = parseFloat(limitValue)
278 |
279 | // 检查比较限制条件
280 | if ((symbol === '<' && value > parsedLimitValue) || (symbol === '>' && value < parsedLimitValue)) {
281 | return parsedLimitValue
282 | }
283 |
284 | // 检查范围限制条件
285 | if (!isNaN(value)) {
286 | const [lowerLimit, upperLimit] = limit.split('-').map(parseFloat)
287 | const clampedValue = Math.min(Math.max(value, lowerLimit || -Infinity), upperLimit || Infinity)
288 | return clampedValue
289 | }
290 |
291 | // 如果不符合以上任何条件,则返回原值
292 | return parseFloat(value)
293 | }
294 | async function removeAllFiles (dir) {
295 | const files = await fs.promises.readdir(dir)
296 | for (const file of files) {
297 | const filePath = path.join(dir, file)
298 | const stats = await fs.promises.stat(filePath)
299 | if (stats.isDirectory()) {
300 | // 如果是目录,则递归调用removeAllFiles
301 | await removeAllFiles(filePath)
302 | await fs.promises.rmdir(filePath) // 删除空目录
303 | } else if (stats.isFile()) {
304 | // 如果是文件,则直接删除
305 | await fs.promises.unlink(filePath)
306 | }
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/apps/help.js:
--------------------------------------------------------------------------------
1 | import { Render, Version, Common } from '../module/utils/index.js'
2 | import { markdown } from '@karinjs/md-html'
3 | import { join } from 'node:path'
4 | import fs from 'node:fs'
5 | import _ from 'lodash'
6 |
7 | export class kkkHelp extends plugin {
8 | constructor () {
9 | super({
10 | name: 'kkk帮助',
11 | event: 'message',
12 | priority: 2000,
13 | rule: [
14 | {
15 | reg: '^#?kkk帮助$',
16 | fnc: 'help'
17 | },
18 | {
19 | reg: '^#?kkk版本$',
20 | fnc: 'version'
21 | }
22 | ]
23 | })
24 | }
25 |
26 | async version (e) {
27 | const changelogs = fs.readFileSync(Version.pluginPath + '/CHANGELOG.md', 'utf8')
28 | const html = markdown(changelogs, {
29 | gitcss: Common.useDarkTheme() ? 'github-markdown-dark.css' : 'github-markdown-light.css'
30 | })
31 | fs.mkdirSync(join(Version.pluginPath, 'resources', 'template', 'version', 'html'), { recursive: true })
32 | const htmlPath = join(Version.pluginPath, 'resources', 'template', 'version', 'html', 'index.html')
33 | fs.writeFileSync(htmlPath, html)
34 | const img = await Render.render('version/index')
35 | await e.reply(img)
36 | return true
37 | }
38 |
39 | async help (e) {
40 | const img = await Render.render('help/index')
41 | await e.reply(img)
42 | return true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/apps/tools.js:
--------------------------------------------------------------------------------
1 | import { BiLiBiLi, Bilidata, Bilibilipush, GetBilibiliID } from '../module/platform/bilibili/index.js'
2 | import { DouYin, DouYinpush, DouyinData, GetDouyinID } from '../module/platform/douyin/index.js'
3 | import { KuaiShou, GetKuaishouID, KuaishouData } from '../module/platform/kuaishou/index.js'
4 | import { Config, Pushlist, Common } from '../module/utils/index.js'
5 |
6 | export class Tools extends plugin {
7 | constructor () {
8 | if (Config.bilibili.bilibilipush) {
9 | task.push({
10 | cron: Config.bilibili.bilibilipushcron,
11 | name: '哔哩哔哩更新推送',
12 | fnc: () => this.pushbili(),
13 | log: Config.bilibili.bilibilipushlog
14 | })
15 | }
16 |
17 | if (Config.douyin.douyinpush) {
18 | task.push({
19 | cron: Config.douyin.douyinpushcron,
20 | name: '抖音更新推送',
21 | fnc: () => this.pushdouy(),
22 | log: Config.douyin.douyinpushlog
23 | })
24 | }
25 |
26 | super({
27 | name: 'kkkkkk-10086-视频功能',
28 | dsc: '视频',
29 | event: 'message',
30 | priority: Config.app.defaulttool ? -Infinity : Config.app.priority,
31 | rule: [
32 | ...rule,
33 | { reg: '^#设置抖音推送', fnc: 'setpushdouy', permission: Config.douyin.douyinpushGroup },
34 | { reg: /^#设置[bB]站推送(?:[Uu][Ii][Dd]:)?(\d+)$/, fnc: 'setpushbili', permission: Config.douyin.douyinpushGroup },
35 | { reg: '^#抖音强制推送$', fnc: 'pushdouy', permission: 'master' },
36 | { reg: '^#B站强制推送$', fnc: 'pushbili', permission: 'master' },
37 | { reg: /^#(抖音|[bB]站)推送列表$/, fnc: 'pushlist' },
38 | { reg: '^#?第(\\d{1,3})集$', fnc: 'next' },
39 | { reg: '^#?BGM', fnc: 'uploadRecord' },
40 | { reg: '^#?(解析|kkk解析)', fnc: 'prefix' },
41 | { reg:/^#kkk设置推送机器人/, fnc: 'changeBotID', permission: 'master' }
42 | ]
43 | })
44 | this.task = task
45 | }
46 |
47 | async changeBotID (e) {
48 | const newDouyinlist = Config.pushlist.douyin.map(item => {
49 | // 操作每个 group_id
50 | const modifiedGroupIds = item.group_id.map(groupId => {
51 | const [group_id, uin] = groupId.split(':')
52 | return `${group_id}:${e.msg.replace(/^#kkk设置推送机器人/, '')}`
53 | })
54 | return {
55 | ...item,
56 | group_id: modifiedGroupIds
57 | }
58 | })
59 | const newBilibililist = Config.pushlist.bilibili.map(item => {
60 | // 操作每个 group_id
61 | const modifiedGroupIds = item.group_id.map(groupId => {
62 | const [group_id, uin] = groupId.split(':')
63 | return `${group_id}:${e.msg.replace(/^#kkk设置推送机器人/, '')}`
64 | })
65 | return {
66 | ...item,
67 | group_id: modifiedGroupIds
68 | }
69 | })
70 | Config.modify('pushlist', 'douyin', newDouyinlist)
71 | Config.modify('pushlist', 'bilibili', newBilibililist)
72 | await e.reply('推送机器人已修改为' + e.msg.replace(/^#kkk设置推送机器人/, ''))
73 | return true
74 | }
75 |
76 | async prefix (e) {
77 | e.msg = await Common.getReplyMessage(e)
78 |
79 | if (reg.douyin.test(e.msg)) {
80 | return await this.douy(e)
81 | } else if (reg.bilibili.test(e.msg)) {
82 | return await this.bilib(e)
83 | } else if (reg.kuaishou.test(e.msg)) {
84 | return await this.kuais(e)
85 | }
86 | return true
87 | }
88 |
89 | async kuais (e) {
90 | const Iddata = await GetKuaishouID(String(e.msg.replaceAll('\\', '')).match(/https:\/\/v\.kuaishou\.com\/\w+/g))
91 | const WorkData = await new KuaishouData(Iddata.type).GetData({ photoId: Iddata.id })
92 | await new KuaiShou(e, Iddata).Action(WorkData)
93 | return true
94 | }
95 |
96 | async pushlist (e) {
97 | // 根据消息内容判断显示哪个平台的推送列表
98 | const platform = e.msg.includes('抖音') ? 'douyin' : 'bilibili'
99 | const obj = {
100 | [platform]: []
101 | }
102 |
103 | const list = Config.pushlist[platform] || []
104 |
105 | // 遍历推送列表,只获取当前群的推送配置
106 | for (const item of list) {
107 | const key = platform === 'douyin' ? 'sec_uid' : 'host_mid'
108 | // 检查group_id是否包含当前群号
109 | if (item[key] && item.group_id.some(gid => gid.split(':')[0] === String(e.group_id))) {
110 | obj[platform].push({
111 | group_id: item.group_id.filter(gid => gid.split(':')[0] === String(e.group_id)),
112 | remark: item.remark,
113 | [key]: item[key]
114 | })
115 | }
116 | }
117 |
118 | const img = await Pushlist(e, obj, platform)
119 | if (img) await e.reply(img)
120 | return true
121 | }
122 |
123 | async pushdouy () {
124 | if (String(this.e?.msg).match('强制')) await new DouYinpush(this.e, true).action()
125 | else await new DouYinpush(this.e).action()
126 | return true
127 | }
128 |
129 | async pushbili () {
130 | if (String(this.e?.msg).match('强制')) await new Bilibilipush(this.e, true).action()
131 | else await new Bilibilipush(this.e).action()
132 | return true
133 | }
134 |
135 | async next (e) {
136 | if (user[this.e.user_id] === 'bilib') {
137 | const regex = String(e.msg).match(/第(\d+)集/)
138 | e.reply(`收到请求,第${regex[1]}集正在下载中`)
139 | const BILIBILIOBJECT = global.BILIBILIOBJECT
140 | BILIBILIOBJECT.Episode = regex[1]
141 | await new BiLiBiLi(e, BILIBILIOBJECT).RESOURCES(BILIBILIOBJECT, true)
142 | }
143 | return true
144 | }
145 |
146 | async bilib (e) {
147 | const urlRex = /(?:https?:\/\/)?(?:www\.bilibili\.com|m\.bilibili\.com|bili2233\.cn)\/[A-Za-z\d._?%&+\-=\/#]*/g
148 | const bShortRex = /(http:|https:)\/\/b23.tv\/[A-Za-z\d._?%&+\-=\/#]*/g
149 | let url = e.msg === undefined ? e.message.shift().data.replaceAll('\\', '') : e.msg.trim().replaceAll('\\', '')
150 | if (url.includes('b23.tv')) {
151 | url = bShortRex.exec(url)[0]
152 | } else if (url.includes('www.bilibili.com') || url.includes('m.bilibili.com') || url.includes('bili2233.cn')) {
153 | url = urlRex.exec(url)?.[0]
154 | } else if (/^BV[1-9a-zA-Z]{10}$/.exec(url)?.[0]) {
155 | url = `https://www.bilibili.com/video/${ url }`
156 | }
157 | const bvid = await GetBilibiliID(url)
158 | const data = await new Bilidata(bvid.type).GetData(bvid)
159 | await new BiLiBiLi(e, data).RESOURCES(data)
160 | user[this.e.user_id] = 'bilib'
161 | setTimeout(() => {
162 | delete user[this.e.user_id]
163 | }, 1000 * 60)
164 | return true
165 | }
166 |
167 | async uploadRecord (e) {
168 | const music_id = String(e.msg).match(/BGM(\d+)/)
169 | await new DouYin(e).uploadRecord(music_id[1])
170 | return true
171 | }
172 |
173 | async douy (e) {
174 | const url = String(e.msg).match(/(http|https):\/\/.*\.(douyin|iesdouyin)\.com\/[^ ]+/g)
175 | const iddata = await GetDouyinID(url)
176 | const data = await new DouyinData(iddata.type).GetData(iddata)
177 | const res = await new DouYin(e, iddata).RESOURCES(data)
178 | if (res) return true
179 | }
180 |
181 | async setpushbili (e) {
182 | if (e.isPrivate) return true
183 | const data = await new Bilidata('用户名片信息').GetData({ host_mid: /^#设置[bB]站推送(?:UID:)?(\d+)$/.exec(e.msg)[1] })
184 | await e.reply(await new Bilibilipush(e).setting(data))
185 | return true
186 | }
187 |
188 | async setpushdouy (e) {
189 | if (e.isPrivate) return true
190 | const data = await new DouyinData('Search').GetData({ query: e.msg.replace(/^#设置抖音推送/, '') })
191 | await e.reply(await new DouYinpush(e).setting(data))
192 | return true
193 | }
194 | }
195 |
196 | const user = {}
197 | const task = []
198 | const rule = []
199 |
200 | const reg = {
201 | douyin: new RegExp('^.*((www|v|jx)\\.(douyin|iesdouyin)\\.com|douyin\\.com\\/(video|note)).*'),
202 | bilibili: new RegExp(/(bilibili.com|b23.tv|t.bilibili.com|bili2233.cn|BV[a-zA-Z0-9]{10}$)/),
203 | kuaishou: new RegExp('^((.*)快手(.*)快手(.*)|(.*)v.kuaishou(.*))$')
204 | }
205 |
206 | if (Config.app.videotool) {
207 | if (Config.douyin.douyintool) {
208 | rule.push({
209 | reg: reg.douyin,
210 | fnc: 'douy'
211 | })
212 | }
213 |
214 | if (Config.bilibili.bilibilitool) {
215 | rule.push({
216 | reg: reg.bilibili,
217 | fnc: 'bilib'
218 | })
219 | }
220 |
221 | if (Config.kuaishou.kuaishoutool) {
222 | rule.push({
223 | reg: reg.kuaishou,
224 | fnc: 'kuais'
225 | })
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/apps/update.js:
--------------------------------------------------------------------------------
1 | import { update } from '../../../plugins/other/update.js'
2 | import Version from '../module/utils/Version.js'
3 | import _ from 'lodash'
4 | export class kkkUpdate extends plugin {
5 | constructor () {
6 | super({
7 | name: '更新',
8 | event: 'message',
9 | priority: 1000,
10 | rule: [
11 | {
12 | reg: /^#kkk(插件)?(强制)?更新(日志)?$/,
13 | fnc: 'update'
14 | }
15 | ]
16 | })
17 | }
18 |
19 | async update (e) {
20 | let msg = e.msg
21 | if (!msg.includes('日志') && !e.isMaster) return false
22 | if (msg.includes('强制') && msg.includes('日志')) {
23 | msg = msg.replace('强制', '')
24 | }
25 | msg = msg.replace(/kkk(插件)?/, '')
26 | msg += Version.pluginName
27 | e.msg = msg
28 | const up = new update(e)
29 | up.e = e
30 | e.msg.includes('日志') ? up.updateLog() : up.update()
31 | return true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/config/default_config/app.yaml:
--------------------------------------------------------------------------------
1 | # 视频解析工具总开关,修改后重启生效
2 | videotool: true
3 |
4 | # 默认解析,即识别最高优先级,修改后重启生效
5 | defaulttool: true
6 |
7 | # 自定义优先级,「默认解析」关闭后才会生效。修改后重启生效
8 | priority: 800
9 |
10 | # 发送合并转发消息,可能多用于抖音解析
11 | sendforwardmsg: true
12 |
13 | # 视频文件上传限制,开启后会根据解析的视频文件大小判断是否需要上传(B站番剧无影响)
14 | usefilelimit: true
15 |
16 | # 视频文件大小限制(填数字),视频文件大于该数值则不会上传 单位: MB,「视频文件上传限制」开启后才会生效(B站番剧无影响)
17 | filelimit: 200
18 |
19 | # 缓存删除,非必要不修改!
20 | rmmp4: true
21 |
22 | # 评论图、推送图是否使用深色主题 0为根据时间自动切换 1为浅色 2为深色
23 | Theme: 0
24 |
25 | # 渲染精度,可选值50~200,建议100。设置高精度会提高图片的精细度,过高可能会影响渲染与发送速度
26 | renderScale: 100
27 |
28 | # 放出API服务(本地部署一个抖音、B站的api服务)
29 | APIServer: false
30 |
31 | # API服务端口
32 | APIServerPort: 4567
33 |
--------------------------------------------------------------------------------
/config/default_config/bilibili.yaml:
--------------------------------------------------------------------------------
1 | # B站解析开关,单独开关,受「总开关」影响
2 | bilibilitool: true
3 |
4 | # B站解析提示,发送提示信息:“检测到B站链接,开始解析”
5 | bilibilitip: true
6 |
7 | # B站评论图,发送哔哩哔哩作品评论图
8 | bilibilicommentsimg: true
9 |
10 | # B站评论数量,设置接口返回的评论数量,范围1~无限条
11 | bilibilinumcomments: 5
12 |
13 | # B站推送,开启后需重启;使用[#设置B站推送+用户UID]配置推送列表
14 | bilibilipush: false
15 |
16 | # B站推送日志,打开或关闭定时任务日志
17 | bilibilipushlog: true
18 |
19 | # B站推送设置权限,(all)所有人;(admin)群主和管理员;(owner)群主;(master)主人
20 | bilibilipushGroup: master
21 |
22 | # B站推送表达式
23 | bilibilipushcron: '*/10 * * * *'
24 |
25 | # 是否发送视频动态的视频
26 | senddynamicvideo: false
27 |
28 | # 解析视频是否优先保内容,true为优先保证上传将使用最低分辨率,false为优先保清晰度将使用最高分辨率
29 | videopriority: false
--------------------------------------------------------------------------------
/config/default_config/cookies.yaml:
--------------------------------------------------------------------------------
1 | # 抖音ck
2 | douyin:
3 |
4 | # B站ck
5 | bilibili:
6 |
7 | # 快手ck
8 | kuaishou:
--------------------------------------------------------------------------------
/config/default_config/douyin.yaml:
--------------------------------------------------------------------------------
1 | # 抖音解析开关,单独开关,受「总开关」影响
2 | douyintool: true
3 |
4 | # 抖音解析提示,发送提示信息:“检测到抖音链接,开始解析”
5 | douyintip: true
6 |
7 | # 抖音评论解析
8 | comments: true
9 |
10 | # 抖音评论数量,范围1~无限条
11 | numcomments: 5
12 |
13 | # 发送抖音作品评论图
14 | commentsimg: true
15 |
16 | # 抖音推送,开启后需重启;使用[#设置抖音推送+抖音号]配置推送列表
17 | douyinpush: false
18 |
19 | # 抖音推送日志,打开或关闭定时任务日志
20 | douyinpushlog: true
21 |
22 | # 抖音推送设置权限,(all)所有人;(admin)群主和管理员;(owner)群主;(master)主人
23 | douyinpushGroup: master
24 |
25 | # 抖音推送表达式
26 | douyinpushcron: '*/10 * * * *'
27 |
28 | # 图集BGM是否使用高清语音发送,高清语音「ios/PC」系统均无法播放,自行衡量开关(仅icqq)
29 | sendHDrecord: true
30 |
31 | # 抖音推送是否一同发送作品视频
32 | senddynamicwork: false
--------------------------------------------------------------------------------
/config/default_config/kuaishou.yaml:
--------------------------------------------------------------------------------
1 | # 快手解析开关,单独开关,受「总开关」影响
2 | kuaishoutool: true
3 |
4 | # 快手解析提示,发送提示信息:“检测到快手链接,开始解析”
5 | kuaishoutip: true
6 |
7 | # 快手评论数量,范围1~30条
8 | kuaishounumcomments: 5
--------------------------------------------------------------------------------
/config/default_config/pushlist.yaml:
--------------------------------------------------------------------------------
1 | # # 抖音推送列表
2 | # douyin:
3 | # -
4 | # # 抖音用户(可不填,执行推送时会自动填上)
5 | # sec_uid:
6 | # # 抖音号(必填)
7 | # short_id:
8 | # # 推送群号和机器人账号,多个则使用逗号隔开(必填,例子:[12345678:87654321, 11451419:88888888],群号就是11451419,机器人账号就是88888888)
9 | # group_id:
10 | # - 1145141919810:8888888888
11 | # # 这个博主的名字信息(可不填,执行推送时会自动填上)
12 | # remark:
13 |
14 | # # B站推送列表
15 | # bilibili:
16 | # -
17 | # # B站用户(必填)
18 | # host_mid:
19 | # # 推送群号和机器人账号,多个则使用逗号隔开(必填,例子:[12345678:87654321, 11451419:88888888],群号就是11451419,机器人账号就是88888888)
20 | # group_id:
21 | # - 1145141919810:8888888888
22 | # # 这个UP主的名字信息(可不填,执行推送时会自动填上)
23 | # remark:
24 |
25 | # 抖音推送列表
26 | douyin:
27 |
28 | # B站推送列表
29 | bilibili:
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import globals from 'globals'
2 | import neostandard from 'neostandard'
3 |
4 | export default [{
5 | languageOptions: {
6 | globals: {
7 | ...globals.node
8 | },
9 | },
10 | files: ['app/**/*.js', 'module/**/*.js'],
11 | rules: {
12 | ...neostandard.rules,
13 | // 禁用驼峰命名命名规则,允许使用下划线命名法。
14 | 'camelcase': ['off'],
15 | // 禁用等号严格比较规则,允许使用==和!=进行比较。
16 | 'eqeqeq': ['off'],
17 | // 禁用优先使用const声明变量的规则,允许使用let和var。
18 | 'prefer-const': ['off'],
19 | // 要求对象属性的末尾不能有逗号,设置为错误等级1。
20 | 'comma-dangle': [1, 'never'],
21 | // 禁用箭头函数体风格规则,允许使用任意风格。
22 | 'arrow-body-style': 'off',
23 | // 启用缩进规则,要求使用两个空格进行缩进,并且switch语句的case子句缩进增加一层。
24 | 'indent': [1, 2, { "SwitchCase": 1 }],
25 | // 要求函数参数列表前的空格,设置为错误等级1。
26 | 'space-before-function-paren': 1,
27 | // 要求语句末尾不使用分号,设置为错误等级1。
28 | "semi": [1, 'never'],
29 | // 要求代码中不能有尾随空格,设置为错误等级1。
30 | "no-trailing-spaces": 1,
31 | // 要求对象字面量大括号内两侧必须有空格,设置为错误等级1。
32 | "object-curly-spacing": [1, 'always'],
33 | }
34 | }]
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { Config, Version } from './module/utils/index.js'
2 | import amagi from '@ikenxuan/amagi'
3 | import fs from 'fs'
4 |
5 | let apps = {}
6 | const files = fs.readdirSync(`${Version.pluginPath}/apps`).filter(file => file.endsWith('.js'))
7 | let ret = []
8 | files.forEach(file => {
9 | ret.push(import(`./apps/${file}`))
10 | })
11 | ret = await Promise.allSettled(ret)
12 | for (const i in files) {
13 | const name = files[i].replace('.js', '')
14 |
15 | if (ret[i].status != 'fulfilled') {
16 | logger.error(`载入插件错误:${logger.red(name)}`)
17 | logger.error(ret[i].reason)
18 | continue
19 | }
20 | apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]
21 | }
22 | export { apps }
23 |
24 |
25 | logger.info('---------- ₍˄·͈༝·͈˄*₎◞ ̑̑ -----------')
26 | logger.info('kkkkkk-10086初始化~')
27 | logger.info('Created By ikenxuan')
28 | logger.info('---------------------------------')
29 |
30 | const client = new amagi({
31 | douyin: Config.cookies.douyin,
32 | bilibili: Config.cookies.bilibili
33 | })
34 |
35 | if (Config.app.APIServer) client.startClient(Config.app.APIServerPort)
36 |
--------------------------------------------------------------------------------
/module/db/base.js:
--------------------------------------------------------------------------------
1 | import { Sequelize, DataTypes } from 'sequelize'
2 | import Version from '../utils/Version.js'
3 | import { join } from 'path'
4 | import fs from 'fs'
5 |
6 | const mkdirSync = (...dirPath) => {
7 | let path = Version.pluginPath
8 | for (const i of dirPath) {
9 | path = join(path, i)
10 | if (!fs.existsSync(path)) {
11 | fs.mkdirSync(path)
12 | }
13 | }
14 | }
15 |
16 | mkdirSync('data', 'db', 'sqlite')
17 |
18 | const dataPath = join(Version.pluginPath, 'data', 'db', 'sqlite', 'data.db')
19 |
20 | /** 创建 Sequelize 实例,需要传入配置对象。 */
21 | const sequelize = new Sequelize({
22 | dialect: 'sqlite',
23 | storage: dataPath,
24 | logging: false
25 | })
26 |
27 | /** 测试数据库连接是否成功 */
28 | await sequelize.authenticate()
29 |
30 | export { sequelize, DataTypes }
31 |
--------------------------------------------------------------------------------
/module/db/index.js:
--------------------------------------------------------------------------------
1 | import { sequelize, DataTypes } from './base.js'
2 |
3 | sequelize.define(
4 | 'douyin',
5 | {
6 | id: {
7 | type: DataTypes.INTEGER,
8 | primaryKey: true,
9 | autoIncrement: true,
10 | comment: '主键ID'
11 | },
12 | group_id: {
13 | type: DataTypes.STRING,
14 | comment: '群组标识符'
15 | },
16 | data: {
17 | type: DataTypes.STRING, // 存储为字符串,JSON 格式
18 | defaultValue: '{}',
19 | comment: '缓存数据'
20 | }
21 | },
22 | {
23 | timestamps: true
24 | }
25 | )
26 |
27 | sequelize.define(
28 | 'bilibili',
29 | {
30 | id: {
31 | type: DataTypes.INTEGER,
32 | primaryKey: true,
33 | autoIncrement: true,
34 | comment: '主键ID'
35 | },
36 | group_id: {
37 | type: DataTypes.STRING,
38 | comment: '群组标识符'
39 | },
40 | data: {
41 | type: DataTypes.STRING, // 存储为字符串,JSON 格式
42 | defaultValue: '{}',
43 | comment: '缓存数据'
44 | }
45 | },
46 | {
47 | timestamps: true
48 | }
49 | )
50 |
51 | /**
52 | * 创建一个新的群组记录,具有默认值的新条目
53 | * @param {string} ModelName 表单名称
54 | * @param {string} group_id 推送群唯一标识符
55 | * @param {string} data 数据对象
56 | * @returns {Promise
}
57 | */
58 | async function CreateSheet (ModelName, group_id, data = {}) {
59 | const Model = sequelize.models[ModelName]
60 | return (
61 | await Model.create(
62 | {
63 | group_id: String(group_id),
64 | data: JSON.stringify(data)
65 | },
66 | {
67 | raw: true
68 | }
69 | )
70 | ).dataValues
71 | }
72 |
73 | /**
74 | * 获取对应表单的所有群组原始数据
75 | * @param {string} ModelName 表单名称
76 | * @returns 获取对应表单的所有群组原始数据
77 | */
78 | async function FindAll (ModelName) {
79 | const Model = sequelize.models[ModelName]
80 | const groups = await Model.findAll({
81 | raw: true
82 | })
83 |
84 | // 使用reduce方法将数组转换为对象
85 | const result = groups.reduce((accumulator, group) => {
86 | // 将group_id作为键名,data作为键值
87 | accumulator[group.group_id] = JSON.parse(group.data)
88 | return accumulator
89 | }, {})
90 |
91 | return result
92 | }
93 |
94 | /**
95 | * 查找指定群号的数据
96 | * @param {string} ModelName 表单名称
97 | * @param {string} Group_ID 群号
98 | * @returns {Promise} 包含指定群号数据的Promise对象
99 | */
100 | async function FindGroup (ModelName, Group_ID) {
101 | const AllData = await FindAll(ModelName)
102 | // 检查传入的 Group_ID 是否存在于 AllData 中
103 |
104 | if (AllData.hasOwnProperty(Group_ID)) {
105 | // 直接返回找到的群号对应的对象
106 | return AllData[Group_ID]
107 | } else {
108 | return null
109 | }
110 | }
111 |
112 | /**
113 | * 更新指定群组的数据
114 | * @param {string} ModelName 表单名称
115 | * @param {number} Group_ID 推送群唯一标识符
116 | * @param {object} NewData 数据对象
117 | * @returns
118 | */
119 | async function UpdateGroupData (ModelName, Group_ID, NewData = {}) {
120 | const Model = sequelize.models[ModelName]
121 |
122 |
123 | const [ affectedRows, affectedRowsData ] = await Model.update(
124 | {
125 | data: JSON.stringify(NewData)
126 | },
127 | {
128 | where: {
129 | group_id: Group_ID
130 | },
131 | individualHooks: true
132 | }
133 | )
134 |
135 | return affectedRowsData
136 | }
137 |
138 | const DB = {
139 | /** 创建一个新的群组记录,具有默认值的新条目 */
140 | CreateSheet,
141 | /** 更新指定群组的数据 */
142 | UpdateGroupData,
143 | /** 获取对应表单的所有群组原始数据 */
144 | FindAll,
145 | /** 查找指定群号的数据 */
146 | FindGroup
147 | }
148 |
149 | export default DB
150 |
151 | /** 每次调用都将强制同步已定义的模型 */
152 | await sequelize.sync({ alter: true })
153 |
--------------------------------------------------------------------------------
/module/platform/bilibili/cookie.js:
--------------------------------------------------------------------------------
1 | import { getBilibiliData, wbi_sign } from '@ikenxuan/amagi'
2 | import Config from '../../utils/Config.js'
3 |
4 | export default async function checkuser (BASEURL) {
5 | if (Config.cookies.bilibili === '' || Config.cookies.bilibili === null) return { QUERY: '&platform=html5', STATUS: '!isLogin' }
6 | const logininfo = await getBilibiliData('登录基本信息', Config.cookies.bilibili)
7 | const sign = await wbi_sign(BASEURL, Config.cookies.bilibili)
8 |
9 | const qn = [ 6, 16, 32, 64, 74, 80, 112, 116, 120, 125, 126, 127 ]
10 | let isvip
11 | logininfo.data.vipStatus === 1 ? (isvip = true) : (isvip = false)
12 | if (isvip) {
13 | return { QUERY: `&fnval=16&fourk=1&${sign}`, STATUS: 'isLogin', isvip }
14 | } else return { QUERY: `&qn=${qn[3]}&fnval=16`, STATUS: 'isLogin', isvip }
15 | }
16 |
--------------------------------------------------------------------------------
/module/platform/bilibili/getdata.js:
--------------------------------------------------------------------------------
1 | import { bilibiliAPI, getBilibiliData, wbi_sign, av2bv } from '@ikenxuan/amagi'
2 | import Config from '../../utils/Config.js'
3 | import Base from '../../utils/Base.js'
4 | import checkuser from './cookie.js'
5 | export default class Bilidata extends Base {
6 | constructor (type) {
7 | super()
8 | this.type = type
9 | this.headers.Referer = 'https://api.bilibili.com/'
10 | this.headers.Cookie = Config.cookies.bilibili
11 | }
12 |
13 | async GetData (data) {
14 | let result, COMMENTSDATA, EMOJIDATA, PARAM
15 | switch (this.type) {
16 | case 'bilibilivideo': {
17 | if (data.id_type === 'aid') data.id = av2bv(data.id)
18 | const INFODATA = await getBilibiliData('单个视频作品数据', Config.cookies.bilibili, { bvid: data.id })
19 | const DATA = await getBilibiliData('单个视频下载信息数据', Config.cookies.bilibili, { avid: INFODATA.data.aid, cid: INFODATA.data.cid })
20 | const BASEURL = bilibiliAPI.视频流信息({ avid: INFODATA.data.aid, cid: INFODATA.data.cid })
21 | const SIGN = await checkuser(BASEURL)
22 | PARAM = await wbi_sign(bilibiliAPI.评论区明细({ number: Config.bilibili.bilibilinumcomments, type: 1, oid: INFODATA.data.aid }), Config.cookies.bilibili)
23 | COMMENTSDATA = await getBilibiliData('评论数据', Config.cookies.bilibili, { number: Config.bilibili.bilibilinumcomments, type: 1, oid: INFODATA.data.aid })
24 | EMOJIDATA = await getBilibiliData('emoji数据', Config.cookies.bilibili)
25 | return { INFODATA, DATA, COMMENTSDATA, EMOJIDATA, USER: SIGN, TYPE: 'bilibilivideo' }
26 | }
27 | case 'workonly': {
28 | const data = await getBilibiliData('单个视频下载信息数据', '', { avid: data.avid, cid: data.cid })
29 | }
30 | case 'COMMENTS': {
31 | if (data.id_type === 'aid') data.id = av2bv(data.id)
32 | const INFODATA = await getBilibiliData('单个视频作品数据', Config.cookies.bilibili, { bvid: data.id })
33 | const aCOMMENTSDATA = await getBilibiliData('评论数据', Config.cookies.bilibili, { number: Config.bilibili.bilibilinumcomments, type: 1, oid: INFODATA.data.aid })
34 | return aCOMMENTSDATA
35 | }
36 | case 'EMOJI':
37 | return await getBilibiliData('Emoji数据')
38 | case '申请二维码':
39 | return await getBilibiliData('申请二维码', Config.cookies.bilibili)
40 |
41 | case '判断二维码状态': {
42 | result = await getBilibiliData('二维码状态', Config.cookies.bilibili, { qrcode_key: data.qrcode_key })
43 | return result
44 | }
45 |
46 | case 'bangumivideo': {
47 | const INFO = await getBilibiliData('番剧基本信息数据', Config.cookies.bilibili, { id: data.id })
48 | let isep
49 | if (data.id.startsWith('ss')) {
50 | data.id = data.id.replace('ss', '')
51 | isep = false
52 | } else if (data.id.startsWith('ep')) {
53 | data.id = data.id.replace('ep', '')
54 | isep = true
55 | }
56 | const QUERY = await checkuser(bilibiliAPI.番剧明细({ [isep ? ep_id : season_id]: data.id }))
57 | result = { INFODATA: INFO, USER: QUERY, TYPE: 'bangumivideo' }
58 | return result
59 | }
60 | case '获取用户空间动态': {
61 | result = await getBilibiliData('用户主页动态列表数据', Config.cookies.bilibili, { host_mid: data.host_mid })
62 | return result
63 | }
64 |
65 | case 'bilibilidynamic': {
66 | delete this.headers.Referer
67 | const dynamicINFO = await getBilibiliData('动态详情数据', Config.cookies.bilibili, { dynamic_id: data.dynamic_id })
68 | const dynamicINFO_CARD = await getBilibiliData('动态卡片数据', Config.cookies.bilibili, { dynamic_id: dynamicINFO.data.item.id_str })
69 | PARAM = await wbi_sign(bilibiliAPI.评论区明细({ type: 1, oid: dynamicINFO_CARD.data.card.desc.rid, number: Config.bilibili.bilibilinumcomments }), Config.cookies.bilibili)
70 | this.headers.Referer = 'https://api.bilibili.com/'
71 | COMMENTSDATA = await getBilibiliData('评论数据', Config.cookies.bilibili, { type: mapping_table(dynamicINFO.data.item.type), oid: oid(dynamicINFO, dynamicINFO_CARD), number: Config.bilibili.bilibilinumcomments })
72 | EMOJIDATA = await getBilibiliData('emoji数据')
73 | const USERDATA = await getBilibiliData('用户主页数据', Config.cookies.bilibili, { host_mid: dynamicINFO.data.item.modules.module_author.mid })
74 | return { dynamicINFO, dynamicINFO_CARD, COMMENTSDATA, EMOJIDATA, USERDATA, TYPE: 'bilibilidynamic' }
75 | }
76 |
77 | case '用户名片信息': {
78 | result = await getBilibiliData('用户主页数据', Config.cookies.bilibili, { host_mid: data.host_mid })
79 | return result
80 | }
81 |
82 | case '动态详情': {
83 | delete this.headers.Referer
84 | result = await getBilibiliData('动态详情数据', Config.cookies.bilibili, { dynamic_id: data.dynamic_id })
85 | return result
86 | }
87 |
88 | case '动态卡片信息': {
89 | delete this.headers.Referer
90 | result = await getBilibiliData('动态卡片数据', Config.cookies.bilibili, { dynamic_id: data.dynamic_id })
91 | return result
92 | }
93 |
94 | case '直播live': {
95 | const live_info = await getBilibiliData('直播间信息', Config.cookies.bilibili, { room_id: data.room_id })
96 | const room_init_info = await getBilibiliData('直播间初始化信息', Config.cookies.bilibili, { room_id: data.room_id })
97 | const USERDATA = await getBilibiliData('用户主页数据', Config.cookies.bilibili, { host_mid: room_init_info.data.uid })
98 | return { TYPE: this.type, live_info, room_init_info, USERDATA }
99 | }
100 | default:
101 | break
102 | }
103 | }
104 | }
105 | function mapping_table (type) {
106 | const Array = {
107 | 1: ['DYNAMIC_TYPE_AV', 'DYNAMIC_TYPE_PGC', 'DYNAMIC_TYPE_UGC_SEASON'],
108 | 11: ['DYNAMIC_TYPE_DRAW'],
109 | 12: ['DYNAMIC_TYPE_ARTICLE'],
110 | 17: ['DYNAMIC_TYPE_LIVE_RCMD', 'DYNAMIC_TYPE_FORWARD', 'DYNAMIC_TYPE_WORD', 'DYNAMIC_TYPE_COMMON_SQUARE'],
111 | 19: ['DYNAMIC_TYPE_MEDIALIST']
112 | }
113 | for (const key in Array) {
114 | if (Array[key].includes(type)) {
115 | return key
116 | }
117 | }
118 | return 1
119 | }
120 |
121 | function oid (dynamicINFO, dynamicINFO_CARD) {
122 | if (dynamicINFO.data.item.type == 'DYNAMIC_TYPE_WORD') {
123 | return dynamicINFO.data.item.id_str
124 | } else return dynamicINFO_CARD.data.card.desc.rid
125 | }
126 |
--------------------------------------------------------------------------------
/module/platform/bilibili/getid.js:
--------------------------------------------------------------------------------
1 | import Networks from '../../utils/Networks.js'
2 |
3 | /**
4 | * return aweme_id
5 | * @param {string} url 分享连接
6 | * @returns
7 | */
8 | export default async function GetBilibiliID (url) {
9 | const longLink = await new Networks({ url }).getLongLink()
10 | let result
11 |
12 | switch (true) {
13 | case /(video\/|video\-)([A-Za-z0-9]+)/.test(longLink): {
14 | const bvideoMatch = longLink.match(/video\/([A-Za-z0-9]+)|bvid=([A-Za-z0-9]+)/)
15 | result = {
16 | type: 'bilibilivideo',
17 | id_type: 'bvid',
18 | id: bvideoMatch[1] || bvideoMatch[2],
19 | P: '哔哩哔哩'
20 | }
21 | if (result.id.startsWith('av')) {
22 | result.id_type = 'aid'
23 | result.id = result.id.replace('av', '')
24 | }
25 | break
26 | }
27 | case /play\/(\S+?)\??/.test(longLink): {
28 | const playMatch = longLink.match(/play\/(\w+)/)
29 | result = {
30 | type: 'bangumivideo',
31 | id: playMatch[1],
32 | P: '哔哩哔哩'
33 | }
34 | break
35 | }
36 | case /^https:\/\/t\.bilibili\.com\/(\d+)/.test(longLink) || /^https:\/\/www\.bilibili\.com\/opus\/(\d+)/.test(longLink): {
37 | const tMatch = longLink.match(/^https:\/\/t\.bilibili\.com\/(\d+)/)
38 | const opusMatch = longLink.match(/^https:\/\/www\.bilibili\.com\/opus\/(\d+)/)
39 | const dynamic_id = tMatch || opusMatch
40 | result = {
41 | type: 'bilibilidynamic',
42 | dynamic_id: dynamic_id[1],
43 | P: '哔哩哔哩'
44 | }
45 | break
46 | }
47 | case /live\.bilibili\.com/.test(longLink): {
48 | const match = longLink.match(/https?:\/\/live\.bilibili\.com\/(\d+)/)
49 | result = {
50 | type: '直播live',
51 | room_id: match[1],
52 | P: '哔哩哔哩'
53 | }
54 | break
55 | }
56 | default:
57 | logger.warn('无法获取作品ID')
58 | break
59 | }
60 |
61 | console.log(result)
62 | return result
63 | }
64 |
--------------------------------------------------------------------------------
/module/platform/bilibili/index.js:
--------------------------------------------------------------------------------
1 | import BiLiBiLi from './bilibili.js'
2 | import Bilidata from './getdata.js'
3 | import BiLogin from './login.js'
4 | import GetBilibiliID from './getid.js'
5 | import checkuser from './cookie.js'
6 | import bilicomments from './comments.js'
7 | import Bilibilipush from './push.js'
8 |
9 | export { BiLiBiLi, Bilidata, BiLogin, GetBilibiliID, bilicomments, checkuser, Bilibilipush }
10 |
--------------------------------------------------------------------------------
/module/platform/bilibili/login.js:
--------------------------------------------------------------------------------
1 | import { Config, Base, Sleep } from '../../utils/index.js'
2 | import Bilidata from './getdata.js'
3 | import QRCode from 'qrcode'
4 |
5 | export default class BiLogin extends Base {
6 | constructor (e = {}) {
7 | super()
8 | this.e = e
9 | }
10 |
11 | async Login () {
12 | /** 申请二维码 */
13 | const qrcodeurl = await new Bilidata('申请二维码').GetData()
14 | const qrimg = await QRCode.toDataURL(qrcodeurl.data.url)
15 | this.qrcode_key = qrcodeurl.data.qrcode_key
16 | await this.e.reply(
17 | '免责声明:\n您将通过扫码完成获取哔哩哔哩的ck用于请求B站API接口以获取数据。\n本Bot不会保存您的登录状态。\n我方仅提供视频解析及相关B站内容服务,若您的账号封禁、被盗等处罚与我方无关。\n害怕风险请勿扫码 ~',
18 | { recallMsg: 180 }
19 | )
20 | await this.e.reply([ segment.image(qrimg.split(';')[1].replace('base64,', 'base64://')), segment.at(this.e.user_id), '请扫码以完成获取' ], { recallMsg: 180 })
21 |
22 | /** 判断二维码状态 */
23 | // let Execution86038 = -1
24 | let executed86090 = false
25 | let completedCase0 = false
26 | for (let i = 0; i < 33; i++) {
27 | const qrcodestatusdata = await new Bilidata('判断二维码状态').GetData({ qrcode_key: this.qrcode_key })
28 | switch (qrcodestatusdata.data.data.code) {
29 | case 0:{
30 | // console.log(qrcodestatusdata.data.data.refresh_token)
31 | Config.modify('cookies', 'bilibili', formatCookies(qrcodestatusdata.headers['set-cookie']))
32 | // Config.bilibilirefresh_token = qrcodestatusdata.data.data.refresh_token
33 | this.e.reply('登录成功!相关信息已保存至cookies.yaml', true)
34 | completedCase0 = true
35 | break
36 | }
37 | case 86038:{
38 | i === 17 && this.e.reply('二维码已失效', true)
39 | break
40 | }
41 | case 86090:{
42 | if (!executed86090) {
43 | this.e.reply('二维码已扫码,未确认', true)
44 | executed86090 = true
45 | } else {
46 | executed86090 = true
47 | }
48 | break
49 | }
50 | default:
51 | break
52 | }
53 | if (completedCase0) break
54 | await Sleep(5000)
55 | }
56 | }
57 | }
58 |
59 | function formatCookies (cookies) {
60 | return cookies.map(cookie => {
61 | // 分割每个cookie字符串以获取名称和值
62 | const [ nameValue, ...attributes ] = cookie.split(';').map(part => part.trim())
63 | const [ name, value ] = nameValue.split('=')
64 |
65 | // 重新组合名称和值,忽略其他属性
66 | return `${name}=${value}`
67 | }).join('; ')
68 | }
69 |
--------------------------------------------------------------------------------
/module/platform/douyin/comments.js:
--------------------------------------------------------------------------------
1 | import DouyinData from './getdata.js'
2 | /**
3 | *
4 | * @param {*} data 完整的评论数据
5 | * @param {*} emojidata 处理过后的emoji列表
6 | * @returns obj
7 | */
8 | export default async function comments (data, emojidata) {
9 | const jsonArray = []
10 | if (data.comments === null) return []
11 |
12 | for (let i = 0; i < data.comments.length; i++) {
13 | const cid = data.comments[i].cid
14 | const aweme_id = data.comments[i].aweme_id
15 | const nickname = data.comments[i].user.nickname
16 | const userimageurl = data.comments[i].user.avatar_thumb.url_list[0]
17 | const text = data.comments[i].text
18 | const ip = data.comments[i].ip_label ? data.comments[i].ip_label : '未知'
19 | const time = data.comments[i].create_time
20 | const label_type = data.comments[i].label_type ? data.comments[i].label_type : -1
21 | const sticker = data.comments[i].sticker ? data.comments[i].sticker.animate_url.url_list[0] : null
22 | const digg_count = data.comments[i].digg_count
23 | const imageurl =
24 | data.comments[i].image_list &&
25 | data.comments[i].image_list[0] &&
26 | data.comments[i].image_list[0].origin_url &&
27 | data.comments[i].image_list[0].origin_url.url_list
28 | ? data.comments[i].image_list[0].origin_url.url_list[0]
29 | : null
30 | const status_label = data.comments[i].label_list ? data.comments[i].label_list[0].text : null
31 | const userintextlongid =
32 | data.comments[i].text_extra && data.comments[i].text_extra[0] && data.comments[i].text_extra[0].sec_uid
33 | ? data.comments[i].text_extra[0].sec_uid && data.comments[i].text_extra.map((extra) => extra.sec_uid)
34 | : null
35 | const search_text =
36 | data.comments[i].text_extra && data.comments[i].text_extra[0] && data.comments[i].text_extra[0].search_text
37 | ? data.comments[i].text_extra[0].search_text &&
38 | data.comments[i].text_extra.map((extra) => ({
39 | search_text: extra.search_text,
40 | search_query_id: extra.search_query_id
41 | }))
42 | : null
43 | const relativeTime = await getRelativeTimeFromTimestamp(time)
44 | const reply_comment_total = data.comments[i].reply_comment_total
45 | const commentObj = {
46 | id: i + 1,
47 | cid,
48 | aweme_id,
49 | nickname,
50 | userimageurl,
51 | text,
52 | digg_count,
53 | ip_label: ip,
54 | create_time: relativeTime,
55 | commentimage: imageurl,
56 | label_type,
57 | sticker,
58 | status_label,
59 | is_At_user_id: userintextlongid,
60 | search_text,
61 | reply_comment_total
62 | }
63 | jsonArray.push(commentObj)
64 | }
65 |
66 | jsonArray.sort((a, b) => b.digg_count - a.digg_count)
67 | const indexLabelTypeOne = jsonArray.findIndex((comment) => comment.label_type === 1)
68 |
69 | if (indexLabelTypeOne !== -1) {
70 | const commentTypeOne = jsonArray.splice(indexLabelTypeOne, 1)[0]
71 | jsonArray.unshift(commentTypeOne)
72 | }
73 |
74 |
75 | jsonArray.text = await br(jsonArray)
76 | jsonArray.text = await handling_at(jsonArray)
77 | jsonArray.text = await search_text(jsonArray)
78 |
79 |
80 | const CommentData = {
81 | jsonArray
82 | }
83 |
84 | for (let i = 0; i < jsonArray.length; i++) {
85 | if (jsonArray[i].digg_count > 10000) {
86 | jsonArray[i].digg_count = (jsonArray[i].digg_count / 10000).toFixed(1) + 'w'
87 | }
88 | }
89 |
90 | for (const item1 of CommentData.jsonArray) {
91 | // 遍历emojidata中的每个元素
92 | for (const item2 of emojidata) {
93 | // 如果jsonArray中的text包含在emojidata中的name中
94 | if (item1.text.includes(item2.name)) {
95 | // 检查是否存在中括号
96 | if (item1.text.includes('[') && item1.text.includes(']')) {
97 | item1.text = item1.text.replace(/\[[^\]]*\]/g, `
`).replace(/\\/g, '')
98 | } else {
99 | item1.text = `
`
100 | }
101 | item1.text += ' '
102 | }
103 | }
104 | }
105 | return CommentData
106 | }
107 |
108 | async function getRelativeTimeFromTimestamp (timestamp) {
109 | const now = Math.floor(Date.now() / 1000) // 当前时间的时间戳
110 | const differenceInSeconds = now - timestamp
111 |
112 | if (differenceInSeconds < 30) {
113 | return '刚刚'
114 | } else if (differenceInSeconds < 60) {
115 | return differenceInSeconds + '秒前'
116 | } else if (differenceInSeconds < 3600) {
117 | return Math.floor(differenceInSeconds / 60) + '分钟前'
118 | } else if (differenceInSeconds < 86400) {
119 | return Math.floor(differenceInSeconds / 3600) + '小时前'
120 | } else if (differenceInSeconds < 2592000) {
121 | return Math.floor(differenceInSeconds / 86400) + '天前'
122 | } else if (differenceInSeconds < 7776000) {
123 | // 三个月的秒数
124 | return Math.floor(differenceInSeconds / 2592000) + '个月前'
125 | } else {
126 | const date = new Date(timestamp * 1000) // 将时间戳转换为毫秒
127 | const year = date.getFullYear()
128 | const month = (date.getMonth() + 1).toString().padStart(2, '0')
129 | const day = date.getDate().toString().padStart(2, '0')
130 | return year + '-' + month + '-' + day
131 | }
132 | }
133 |
134 | async function handling_at (data) {
135 | for (const item of data) {
136 | if (item.is_At_user_id !== null && Array.isArray(item.is_At_user_id)) {
137 | for (const secUid of item.is_At_user_id) {
138 | const UserInfoData = await new DouyinData('UserInfoData').GetData({
139 | user_id: secUid
140 | })
141 | if (UserInfoData.user.sec_uid === secUid) {
142 | /** 这里评论只要生成了艾特,如果艾特的人改了昵称,评论也不会变,所以可能会出现有些艾特没有正确上颜色,因为接口没有提供历史昵称 */
143 | const regex = new RegExp(`@${UserInfoData.user.nickname?.replace(/[-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')}`, 'g')
144 | item.text = item.text.replace(regex, (match) => {
145 | return `${match}`
146 | })
147 | }
148 | }
149 | }
150 | }
151 | }
152 |
153 | async function search_text (data) {
154 | for (const item of data) {
155 | if (item.search_text !== null && Array.isArray(item.search_text)) {
156 | for (const search_text of item.search_text) {
157 | const SuggestWordsData = await new DouyinData('SuggestWords').GetData({
158 | query: search_text.search_text
159 | })
160 | if (
161 | SuggestWordsData.data &&
162 | SuggestWordsData.data[0] &&
163 | SuggestWordsData.data[0].params &&
164 | SuggestWordsData.data[0].params.query_id &&
165 | SuggestWordsData.data[0].params.query_id === search_text.search_query_id
166 | ) {
167 | const regex = new RegExp(`${search_text.search_text}`, 'g')
168 | item.text = item.text.replace(regex, (match) => {
169 | const themeClass = isdarktheme() ? 'dark-mode' : ''
170 | return `
171 | ${match}
172 |
173 | `
174 | })
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | async function br (data) {
182 | for (let i = 0; i < data.length; i++) {
183 | let text = data[i].text
184 |
185 | text = text.replace(/\n/g, '
')
186 | data[i].text = text
187 | }
188 | return data
189 | }
190 |
191 | /**
192 | * 是否启用深色模式
193 | * @returns boolean
194 | */
195 | function isdarktheme () {
196 | let light = false
197 | const date = new Date().getHours()
198 | if (date >= 6 && date < 18) {
199 | light = true
200 | }
201 | return light
202 | }
203 |
--------------------------------------------------------------------------------
/module/platform/douyin/emoji.js:
--------------------------------------------------------------------------------
1 | export default async function Emoji (data) {
2 | const ListArray = []
3 |
4 | for (let i = 0; i < data.emoji_list.length; i++) {
5 | const display_name = data.emoji_list[i].display_name
6 | const url = data.emoji_list[i].emoji_url.url_list[0]
7 |
8 | const Objject = {
9 | name: display_name,
10 | url
11 | }
12 | ListArray.push(Objject)
13 | }
14 | return ListArray
15 | }
16 |
--------------------------------------------------------------------------------
/module/platform/douyin/getdata.js:
--------------------------------------------------------------------------------
1 | import { getDouyinData } from '@ikenxuan/amagi'
2 | import Config from '../../utils/Config.js'
3 | import Base from '../../utils/Base.js'
4 |
5 | export default class DouyinData extends Base {
6 | constructor (type) {
7 | super()
8 | this.type = type
9 | this.headers.Referer = 'https://www.douyin.com/'
10 | this.headers.Cookie = Config.cookies.douyin
11 | }
12 |
13 | async GetData (data) {
14 | if (!this.allow) throw new Error('请使用 [#kkk设置抖音ck] 以设置抖音ck')
15 | switch (this.type) {
16 | case 'video':
17 | case 'note': {
18 | const VideoData = await getDouyinData('聚合解析', Config.cookies.douyin, { aweme_id: data.id })
19 | const CommentsData = Config.douyin.comments
20 | ? await getDouyinData('评论数据', Config.cookies.douyin, {
21 | aweme_id: data.id,
22 | number: Config.douyin.numcomments
23 | })
24 | : { data: null }
25 | if (VideoData !== '') VideoData.is_mp4 = data.is_mp4
26 | return { VideoData, CommentsData }
27 | }
28 |
29 | case 'LiveImage': {
30 | const LiveImageData = await getDouyinData('合辑作品数据', Config.cookies.douyin, { aweme_id: data.id })
31 | return { LiveImageData }
32 | }
33 | case 'Live':
34 | case 'UserInfoData': {
35 | const UserInfoData = await getDouyinData('用户主页数据', Config.cookies.douyin, { sec_uid: data.user_id })
36 | return UserInfoData
37 | }
38 | case 'Emoji': {
39 | const EmojiData = await getDouyinData('Emoji数据')
40 | return EmojiData
41 | }
42 | case 'UserVideosList': {
43 | const UserVideoListData = await getDouyinData('用户主页视频列表数据', Config.cookies.douyin, { sec_uid: data.user_id })
44 | return UserVideoListData
45 | }
46 | case 'SuggestWords': {
47 | const SuggestWordsData = await getDouyinData('热点词数据', Config.cookies.douyin, { query: data.query })
48 | return SuggestWordsData
49 | }
50 | case 'Search': {
51 | const SearchData = await getDouyinData('搜索数据', Config.cookies.douyin, { query: data.query })
52 | return SearchData
53 | }
54 | case 'Music': {
55 | const MusicData = await getDouyinData('音乐数据', Config.cookies.douyin, { music_id: data.music_id })
56 | return MusicData
57 | }
58 | default:
59 | break
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/module/platform/douyin/getid.js:
--------------------------------------------------------------------------------
1 | import Networks from '../../utils/Networks.js'
2 |
3 | /**
4 | * 返回作品id对象
5 | * @param {string} url 分享连接
6 | * @returns
7 | */
8 | export default async function GetDouyinID (url) {
9 | const longLink = await new Networks({ url }).getLongLink()
10 | let result
11 |
12 | switch (true) {
13 | case longLink === 'https://www.douyin.com/': {
14 | const newres = await new Networks({ url }).getLocation()
15 | const match = newres.match(/share\/slides\/(\d+)/)
16 | result = {
17 | type: 'LiveImage',
18 | id: match[1],
19 | is_mp4: true,
20 | P: '抖音'
21 | }
22 | break
23 | }
24 |
25 | case longLink.includes('webcast.amemv.com'): {
26 | const sec_uid = longLink.match(/sec_user_id=([^&]+)/)
27 | result = {
28 | type: 'Live',
29 | user_id: sec_uid[1],
30 | P: '抖音'
31 | }
32 | break
33 | }
34 |
35 | case /video\/(\d+)/.test(longLink): {
36 | const videoMatch = longLink.match(/video\/(\d+)/)
37 | result = {
38 | type: 'video',
39 | id: videoMatch[1],
40 | is_mp4: true,
41 | P: '抖音'
42 | }
43 | break
44 | }
45 | case /note\/(\d+)/.test(longLink): {
46 | const noteMatch = longLink.match(/note\/(\d+)/)
47 | result = {
48 | type: 'note',
49 | id: noteMatch[1],
50 | is_mp4: false,
51 | P: '抖音'
52 | }
53 | break
54 | }
55 | case /https:\/\/(?:www\.douyin\.com|www\.iesdouyin\.com)\/share\/user\/(\S+)/.test(longLink): {
56 | const userMatch = longLink.match(/user\/([a-zA-Z0-9_]+)\b/)
57 | result = {
58 | type: 'UserVideosList',
59 | user_id: userMatch[1],
60 | P: '抖音'
61 | }
62 | break
63 | }
64 | case /music\/(\d+)/.test(longLink): {
65 | const musicMatch = longLink.match(/music\/(\d+)/)
66 | result = {
67 | type: 'Music',
68 | music_id: musicMatch[1],
69 | P: '抖音'
70 | }
71 | break
72 | }
73 | default:
74 | logger.warn('无法获取作品ID')
75 | break
76 | }
77 |
78 | console.log(result)
79 | return result
80 | }
81 |
--------------------------------------------------------------------------------
/module/platform/douyin/index.js:
--------------------------------------------------------------------------------
1 | import DouYin from './douyin.js'
2 | import DouYinpush from './push.js'
3 | import DouyinData from './getdata.js'
4 | import GetDouyinID from './getid.js'
5 | import Emoji from './emoji.js'
6 | import comments from './comments.js'
7 | import { dylogin } from './login.js'
8 |
9 | export { DouYin, DouYinpush, DouyinData, GetDouyinID, Emoji, comments, dylogin }
10 |
--------------------------------------------------------------------------------
/module/platform/douyin/login.js:
--------------------------------------------------------------------------------
1 | import puppeteer from 'puppeteer'
2 | import fs from 'node:fs'
3 | import { Version, Config } from '../../utils/index.js'
4 | export const dylogin = async (e) => {
5 | const msg_id = []
6 | const message1 = await e.reply('免责声明:\n您将通过扫码完成获取抖音网页端的用户登录凭证(ck),该ck将用于请求抖音WEB API接口。\n本Bot不会保存您的登录状态。\n我方仅提供视频解析及相关抖音内容服务,若您的账号封禁、被盗等处罚与我方无关。\n害怕风险请勿扫码 ~')
7 | const browser = await puppeteer.launch({
8 | headless: false,
9 | args: [
10 | '--window-position=-10000,-10000', // 将窗口移到屏幕外
11 | '--start-minimized', // 启动时最小化
12 | '--mute-audio' // 静音
13 | ]
14 | })
15 | const pages = await browser.pages()
16 | // 获取第一个页面,如果没有,就创建一个新页面
17 | const page = pages[0] || (await browser.newPage())
18 | await page.goto('https://www.douyin.com')
19 |
20 | // 等待二维码容器出现
21 | await page.waitForSelector('.web-login-scan-code__content__qrcode-wrapper', { timeout: 10000 })
22 |
23 | // 等待 img 元素加载并变得可见
24 | const qrcodeImage = await page.waitForSelector('.web-login-scan-code__content__qrcode-wrapper img', { timeout: 10000 })
25 |
26 | // 使用 evaluate 获取 img 的 src 属性内容
27 | const qrCodeBase64 = await page.evaluate(img => img.getAttribute('src'), qrcodeImage)
28 |
29 | // 移除 base64 前缀
30 | const base64Data = qrCodeBase64 ? qrCodeBase64.replace(/^data:image\/\w+;base64,/, '') : ''
31 | const buffer = Buffer.from(base64Data, 'base64')
32 | fs.writeFileSync(`${Version.clientPath}/resources/kkkdownload/DouyinLoginQrcode.png`, buffer)
33 |
34 | const message2 = await e.reply([
35 | segment.image('base64://' + base64Data),
36 | '请在120秒内通过抖音APP扫码进行登录'
37 | ], true, { recallMsg: 10 })
38 | msg_id.push(message2.message_id, message1.message_id)
39 |
40 | try {
41 | // 监听页面的 response 事件,捕捉包含 Set-Cookie 的 302 重定向响应
42 | page.on('response', async (response) => {
43 | if (response.status() === 302 && response.url().includes('/passport/sso/login/callback')) {
44 | // 获取本地的 cookie
45 | const localCookies = await page.cookies()
46 | const cookieString = localCookies.map(cookie => {
47 | return `${cookie.name}=${cookie.value}`
48 | }).join('; ')
49 | Config.modify('cookies', 'douyin', cookieString)
50 | await e.reply('登录成功!用户登录凭证已保存至cookies.yaml', true)
51 | // 关闭浏览器
52 | await browser.close()
53 | }
54 | })
55 | } catch (err) {
56 | await browser.close()
57 | await e.reply('登录超时!二维码已失效!', true)
58 | logger.error(err)
59 | }
60 | return true
61 | }
62 |
--------------------------------------------------------------------------------
/module/platform/kuaishou/API.js:
--------------------------------------------------------------------------------
1 | class KuaishouAPI {
2 | 单个作品信息 (photoId) {
3 | return {
4 | type: 'visionVideoDetail',
5 | url: 'https://www.kuaishou.com/graphql',
6 | body: {
7 | operationName: 'visionVideoDetail',
8 | variables: {
9 | photoId,
10 | page: 'detail'
11 | },
12 | 'query': 'query visionVideoDetail($photoId: String, $type: String, $page: String, $webPageArea: String) {\n visionVideoDetail(photoId: $photoId, type: $type, page: $page, webPageArea: $webPageArea) {\n status\n type\n author {\n id\n name\n following\n headerUrl\n __typename\n }\n photo {\n id\n duration\n caption\n likeCount\n realLikeCount\n coverUrl\n photoUrl\n liked\n timestamp\n expTag\n llsid\n viewCount\n videoRatio\n stereoType\n musicBlocked\n manifest {\n mediaType\n businessType\n version\n adaptationSet {\n id\n duration\n representation {\n id\n defaultSelect\n backupUrl\n codecs\n url\n height\n width\n avgBitrate\n maxBitrate\n m3u8Slice\n qualityType\n qualityLabel\n frameRate\n featureP2sp\n hidden\n disableAdaptive\n __typename\n }\n __typename\n }\n __typename\n }\n manifestH265\n photoH265Url\n coronaCropManifest\n coronaCropManifestH265\n croppedPhotoH265Url\n croppedPhotoUrl\n videoResource\n __typename\n }\n tags {\n type\n name\n __typename\n }\n commentLimit {\n canAddComment\n __typename\n }\n llsid\n danmakuSwitch\n __typename\n }\n}\n'
13 | }
14 | }
15 | }
16 |
17 | 作品评论信息 (photoId) {
18 | return {
19 | type: 'commentListQuery',
20 | url: 'https://www.kuaishou.com/graphql',
21 | body: {
22 | operationName: 'commentListQuery',
23 | variables: {
24 | photoId,
25 | pcursor: ''
26 | },
27 | query: 'query commentListQuery($photoId: String, $pcursor: String) {\n visionCommentList(photoId: $photoId, pcursor: $pcursor) {\n commentCount\n pcursor\n rootComments {\n commentId\n authorId\n authorName\n content\n headurl\n timestamp\n likedCount\n realLikedCount\n liked\n status\n authorLiked\n subCommentCount\n subCommentsPcursor\n subComments {\n commentId\n authorId\n authorName\n content\n headurl\n timestamp\n likedCount\n realLikedCount\n liked\n status\n authorLiked\n replyToUserName\n replyTo\n __typename\n }\n __typename\n }\n __typename\n }\n}\n'
28 | }
29 | }
30 | }
31 |
32 | 表情 () {
33 | return {
34 | type: 'visionBaseEmoticons',
35 | url: 'https://www.kuaishou.com/graphql',
36 | body: {
37 | operationName: 'visionBaseEmoticons',
38 | variables: {},
39 | query: 'query visionBaseEmoticons {\n visionBaseEmoticons {\n iconUrls\n __typename\n }\n}\n'
40 | }
41 | }
42 | }
43 | }
44 | export default new KuaishouAPI()
45 |
--------------------------------------------------------------------------------
/module/platform/kuaishou/comments.js:
--------------------------------------------------------------------------------
1 | import Config from '../../utils/Config.js'
2 |
3 | /**
4 | *
5 | * @param {*} data 完整的评论数据
6 | * @param {*} emojidata 处理过后的emoji列表
7 | * @returns obj
8 | */
9 | export default async function comments (data, emojidata) {
10 | const jsonArray = []
11 | for (let i = 0; i < data.data.visionCommentList.rootComments.length; i++) {
12 | const cid = data.data.visionCommentList.rootComments[i].commentId
13 | const aweme_id = data.data.visionCommentList.rootComments[i].commentId
14 | const nickname = data.data.visionCommentList.rootComments[i].authorName
15 | const userimageurl = data.data.visionCommentList.rootComments[i].headurl
16 | const text = data.data.visionCommentList.rootComments[i].content
17 | const time = getRelativeTimeFromTimestamp(data.data.visionCommentList.rootComments[i].timestamp)
18 | const digg_count = data.data.visionCommentList.rootComments[i].likedCount
19 | const commentObj = {
20 | id: i + 1,
21 | cid,
22 | aweme_id,
23 | nickname,
24 | userimageurl,
25 | text,
26 | digg_count,
27 | create_time: time
28 | }
29 | jsonArray.push(commentObj)
30 | }
31 |
32 | // 按照点赞量降序
33 | jsonArray.sort((a, b) => b.digg_count - a.digg_count)
34 | // const indexLabelTypeOne = jsonArray.findIndex((comment) => comment.label_type === 1)
35 | // if (indexLabelTypeOne !== -1) {
36 | // const commentTypeOne = jsonArray.splice(indexLabelTypeOne, 1)[0]
37 | // jsonArray.unshift(commentTypeOne)
38 | // }
39 |
40 | jsonArray.text = br(jsonArray)
41 | jsonArray.text = await handling_at(jsonArray)
42 | // jsonArray.text = await search_text(jsonArray)
43 |
44 | for (let i = 0; i < jsonArray.length; i++) {
45 | if (jsonArray[i].digg_count > 10000) {
46 | jsonArray[i].digg_count = (jsonArray[i].digg_count / 10000).toFixed(1) + 'w'
47 | }
48 | }
49 |
50 | for (const item1 of jsonArray) {
51 | // 遍历emojidata中的每个元素
52 | for (const item2 of emojidata) {
53 | // 如果jsonArray中的text包含在emojidata中的name中
54 | if (item1.text.includes(item2.name)) {
55 | // 检查是否存在中括号
56 | if (item1.text.includes('[') && item1.text.includes(']')) {
57 | item1.text = item1.text.replace(/\[[^\]]*\]/g, `
`).replace(/\\/g, '')
58 | } else {
59 | item1.text = `
`
60 | }
61 | item1.text += ' '
62 | }
63 | }
64 | }
65 | // 从数组前方开始保留 Config.kuaishou.kuaishounumcomments 条评论,自动移除数组末尾的评论
66 | return jsonArray.slice(0, Math.min(jsonArray.length, Config.kuaishou.kuaishounumcomments))
67 | }
68 |
69 | function getRelativeTimeFromTimestamp (timestamp) {
70 | // 快手是毫秒(ms)
71 | const timestampInSeconds = Math.floor(timestamp / 1000)
72 | const now = Math.floor(Date.now() / 1000)
73 | const differenceInSeconds = now - timestampInSeconds
74 |
75 | if (differenceInSeconds < 30) {
76 | return '刚刚'
77 | } else if (differenceInSeconds < 60) {
78 | return differenceInSeconds + '秒前'
79 | } else if (differenceInSeconds < 3600) {
80 | return Math.floor(differenceInSeconds / 60) + '分钟前'
81 | } else if (differenceInSeconds < 86400) {
82 | return Math.floor(differenceInSeconds / 3600) + '小时前'
83 | } else if (differenceInSeconds < 2592000) {
84 | return Math.floor(differenceInSeconds / 86400) + '天前'
85 | } else if (differenceInSeconds < 7776000) {
86 | // 三个月的秒数
87 | return Math.floor(differenceInSeconds / 2592000) + '个月前'
88 | } else {
89 | const date = new Date(timestamp * 1000) // 将时间戳转换为毫秒
90 | const year = date.getFullYear()
91 | const month = (date.getMonth() + 1).toString().padStart(2, '0')
92 | const day = date.getDate().toString().padStart(2, '0')
93 | return year + '-' + month + '-' + day
94 | }
95 | }
96 |
97 | function br (data) {
98 | for (let i = 0; i < data.length; i++) {
99 | let text = data[i].text
100 |
101 | text = text.replace(/\n/g, '
')
102 | data[i].text = text
103 | }
104 | return data
105 | }
106 |
107 | async function handling_at (data) {
108 | for (let i = 0; i < data.length; i++) {
109 | let text = data[i].text
110 |
111 | // 匹配 @后面的字符,允许空格,直到 (\w+\)
112 | text = text.replace(/(@[\S\s]+?)\(\w+\)/g, (match, p1) => {
113 | // 将 @后面的名字替换为带有样式的 ,保留空格
114 | return `${p1.trim()}`
115 | })
116 |
117 | data[i].text = text
118 | }
119 | return data
120 | }
121 |
--------------------------------------------------------------------------------
/module/platform/kuaishou/getdata.js:
--------------------------------------------------------------------------------
1 | import { Base, Config, Networks } from '../../utils/index.js'
2 | import KuaishouAPI from './API.js'
3 |
4 | export default class KuaishouData extends Base {
5 | constructor (type) {
6 | super()
7 | this.type = type
8 | this.headers.Referer = 'https://www.kuaishou.com/'
9 | this.headers['Content-Type'] = 'application/json'
10 | this.headers.Host = 'www.kuaishou.com'
11 | this.headers.Origin = this.headers.Referer
12 | this.headers['X-Requested-With'] = 'mixiaba.com.Browser'
13 | /** 默认游客ck */
14 | this.headers.Cookie = Config.cookies.kuaishou || 'did=web_50424132d556424eb8fa8d27a612fda9; didv=1720860549000; kpf=PC_WEB; clientid=3; kpn=KUAISHOU_VISION'
15 | }
16 |
17 | /**
18 | *
19 | * @param {any} data param
20 | * @returns
21 | */
22 | async GetData (data) {
23 | switch (this.type) {
24 | case '单个作品信息': {
25 | this.obj = KuaishouAPI.单个作品信息(data.photoId)
26 | const VideoData = await this.GlobalGetData(
27 | {
28 | url: this.obj.url,
29 | method: 'POST',
30 | headers: this.headers,
31 | body: this.obj.body
32 | }
33 | )
34 |
35 | this.obj = KuaishouAPI.作品评论信息(data.photoId)
36 | const CommentData = await this.GlobalGetData(
37 | {
38 | url: this.obj.url,
39 | method: 'POST',
40 | headers: this.headers,
41 | body: this.obj.body
42 | }
43 | )
44 |
45 | this.obj = KuaishouAPI.表情()
46 | const EmojiData = await this.GlobalGetData(
47 | {
48 | url: this.obj.url,
49 | method: 'POST',
50 | headers: this.headers,
51 | body: this.obj.body
52 | }
53 | )
54 |
55 | return { VideoData, CommentData, EmojiData }
56 | }
57 |
58 | case '作品评论信息': {
59 | this.obj = KuaishouAPI.作品评论信息(data.photoId)
60 | const CommentData = await this.GlobalGetData(
61 | {
62 | url: this.obj.url,
63 | method: 'POST',
64 | headers: this.headers,
65 | body: this.obj.body
66 | }
67 | )
68 | return CommentData
69 | }
70 | default:
71 | break
72 | }
73 |
74 | }
75 |
76 | /**
77 | * @param {*} options opt
78 | * @returns
79 | */
80 | async GlobalGetData (options) {
81 | const result = await new Networks(options).getData()
82 | if (!result || result === '') {
83 | logger.error('获取响应数据失败!\n请求类型:' + this.type + '\n请求URL:' + options.url)
84 | }
85 | return result
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/module/platform/kuaishou/getid.js:
--------------------------------------------------------------------------------
1 | import Networks from '../../utils/Networks.js'
2 |
3 | /**
4 | * return aweme_id
5 | * @param {string} url 分享连接
6 | * @returns
7 | */
8 | export default async function GetKuaishouID (url) {
9 | const longLink = await new Networks({ url }).getLongLink()
10 | let result
11 |
12 | switch (true) {
13 | case /photoId=(.*)/.test(longLink): {
14 | const workid = longLink.match(/photoId=([^&]+)/)
15 | result = {
16 | type: '单个作品信息',
17 | id: workid[1],
18 | P: '快手'
19 | }
20 | break
21 | }
22 |
23 | default:
24 | logger.warn('无法获取作品ID')
25 | break
26 | }
27 |
28 | console.log(result)
29 | return result
30 | }
31 |
--------------------------------------------------------------------------------
/module/platform/kuaishou/index.js:
--------------------------------------------------------------------------------
1 | import KuaiShou from './kuaishou.js'
2 | import KuaishouAPI from './API.js'
3 | import KuaishouData from './getdata.js'
4 | import GetKuaishouID from './getid.js'
5 | import comments from './comments.js'
6 |
7 | export { KuaiShou, KuaishouData, KuaishouAPI, GetKuaishouID, comments }
--------------------------------------------------------------------------------
/module/platform/kuaishou/kuaishou.js:
--------------------------------------------------------------------------------
1 | import { Base, Config, Render, Networks } from '../../utils/index.js'
2 | import comments from './comments.js'
3 |
4 | export default class KuaiShou extends Base {
5 | constructor (e = {}, Iddata) {
6 | super()
7 | this.e = e
8 | }
9 |
10 | async Action (data) {
11 | if (!data.VideoData.data.visionVideoDetail.status === 1) {
12 | await this.e.reply('不支持解析的视频')
13 | return true
14 | }
15 | Config.kuaishou.kuaishoutip && await this.e.reply('检测到快手链接,开始解析')
16 | const video_url = data.VideoData.data.visionVideoDetail.photo.photoUrl
17 | const transformedData = Object.entries(data.EmojiData.data.visionBaseEmoticons.iconUrls).map(([ name, path ]) => {
18 | return { name, url: `https:${path}` }
19 | })
20 | const CommentsData = await comments(data.CommentData, transformedData)
21 | const videoheaders = await new Networks({ url: video_url, headers: this.headers }).getHeaders()
22 | const Size = videoheaders['content-length'] ? parseInt(videoheaders['content-length'], 10) : 0
23 | const videoSizeInMB = (Size / (1024 * 1024)).toFixed(2)
24 | const img = await Render.render(
25 | 'kuaishou/comment',
26 | {
27 | Type: '视频',
28 | viewCount: data.VideoData.data.visionVideoDetail.photo.viewCount,
29 | CommentsData,
30 | CommentLength: String(CommentsData?.length ? CommentsData.length : 0),
31 | VideoUrl: video_url,
32 | VideoSize: videoSizeInMB,
33 | likeCount: data.VideoData.data.visionVideoDetail.photo.likeCount
34 | }
35 | )
36 | await this.e.reply(img)
37 | await this.DownLoadVideo(video_url, Config.app.rmmp4 ? 'tmp_' + Date.now() : data.VideoData.data.visionVideoDetail.photo.caption)
38 | return true
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/module/utils/Common.js:
--------------------------------------------------------------------------------
1 | import Config from './Config.js'
2 | import Base from './Base.js'
3 | import fs from 'node:fs'
4 |
5 | /** 常用工具合集 */
6 | class Tools {
7 | /**
8 | * 获取回复消息的内容
9 | * @param {object} e 消息事件对象
10 | * @returns {Promise} 回复消息的文本内容
11 | */
12 | async getReplyMessage(e) {
13 | const botAdapter = await new Base(e).botadapter
14 | switch (botAdapter) {
15 | case 'ICQQ': {
16 | if (e.source) {
17 | const source = (await e.group.getChatHistory(e.source.seq, 1)).pop()
18 | for (const v of source.message) {
19 | if (v.type === 'text' || v.type === 'json') e.msg = v?.text || v?.data
20 | }
21 | }
22 | break
23 | }
24 | case 'LagrangeCore':
25 | case 'Lagrange.OneBot':
26 | case 'OneBotv11': {
27 | const source = e.message.find(msg => msg.type === 'reply')
28 | if (source) {
29 | const replyMessage = (await e.bot?.sendApi?.('get_msg', { message_id: source.id }))?.data
30 | if (replyMessage?.message) {
31 | for (const val of replyMessage.message) {
32 | if (val.type === 'text' || val.type === 'json') e.msg = val.data?.text || val.data?.data
33 | }
34 | }
35 | }
36 | break
37 | }
38 | }
39 | return e.msg
40 | }
41 |
42 | /**
43 | * 将中文数字转换为阿拉伯数字
44 | * @param {string} chineseNumber 中文数字字符串
45 | * @returns {number} 转换后的阿拉伯数字
46 | */
47 | chineseToArabic(chineseNumber) {
48 | const chineseToArabicMap = {
49 | 零: 0, 一: 1, 二: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9
50 | }
51 | const units = {
52 | 十: 10, 百: 100, 千: 1000, 万: 10000, 亿: 100000000
53 | }
54 | let result = 0
55 | let temp = 0
56 | let unit = 1
57 |
58 | for (let i = chineseNumber.length - 1; i >= 0; i--) {
59 | const char = chineseNumber[i]
60 |
61 | if (units[char] !== undefined) {
62 | unit = units[char]
63 | if (unit === 10000 || unit === 100000000) {
64 | result += temp * unit
65 | temp = 0
66 | }
67 | } else {
68 | const num = chineseToArabicMap[char]
69 | if (unit > 1) {
70 | temp += num * unit
71 | } else {
72 | temp += num
73 | }
74 | unit = 1
75 | }
76 | }
77 | return result + temp
78 | }
79 |
80 | /**
81 | * 格式化Cookie字符串
82 | * @param {string[]} cookies Cookie数组
83 | * @returns {string} 格式化后的Cookie字符串
84 | */
85 | formatCookies(cookies) {
86 | return cookies.map(cookie => {
87 | const [nameValue] = cookie.split(';').map(part => part.trim())
88 | const [name, value] = nameValue.split('=')
89 | return `${name}=${value}`
90 | }).join('; ')
91 | }
92 |
93 | /**
94 | * 计算视频比特率
95 | * @param {number} targetSizeMB 目标文件大小(MB)
96 | * @param {number} duration 视频时长(秒)
97 | * @returns {number} 计算得到的比特率(kbps)
98 | */
99 | calculateBitrate(targetSizeMB, duration) {
100 | const targetSizeBytes = targetSizeMB * 1024 * 1024
101 | return (targetSizeBytes * 8) / duration / 1024
102 | }
103 |
104 | /**
105 | * 获取视频文件大小
106 | * @param {string} filePath 文件路径
107 | * @returns {Promise} 文件大小(MB)
108 | */
109 | async getVideoFileSize(filePath) {
110 | try {
111 | const stats = await fs.promises.stat(filePath)
112 | const fileSizeInBytes = stats.size
113 | const fileSizeInMB = fileSizeInBytes / (1024 * 1024)
114 | return fileSizeInMB
115 | } catch (error) {
116 | console.error('获取文件大小时发生错误:', error)
117 | throw error
118 | }
119 | }
120 |
121 | /**
122 | * 将数字转换为带"万"单位的字符串
123 | * @param {number} count 需要转换的数字
124 | * @returns {string} 转换后的字符串,超过1万的数字会转换为"xx万"的格式
125 | * @example
126 | * count(12345) // 返回 "1.2万"
127 | * count(999) // 返回 "999"
128 | * count(undefined) // 返回 "无法获取"
129 | */
130 | count (count) {
131 | if (count > 10000) {
132 | return (count / 10000).toFixed(1) + '万'
133 | } else {
134 | return count?.toString() || '无法获取'
135 | }
136 | }
137 |
138 | /**
139 | * 删除文件
140 | * @param {string} path 文件路径
141 | * @param {boolean} force 是否强制删除
142 | * @returns {Promise} 删除是否成功
143 | */
144 | async removeFile(path, force = false) {
145 | path = path.replace(/\\/g, '/')
146 | if (Config.app.rmmp4) {
147 | try {
148 | await fs.promises.unlink(path)
149 | logger.mark('缓存文件: ', path + ' 删除成功!')
150 | return true
151 | } catch (err) {
152 | logger.error('缓存文件: ', path + ' 删除失败!', err)
153 | return false
154 | }
155 | } else if (force) {
156 | try {
157 | await fs.promises.unlink(path)
158 | logger.mark('缓存文件: ', path + ' 删除成功!')
159 | return true
160 | } catch (err) {
161 | logger.error('缓存文件: ', path + ' 删除失败!', err)
162 | return false
163 | }
164 | }
165 | return true
166 | }
167 |
168 | /**
169 | * 将时间戳转换为日期时间字符串
170 | * @param {number} timestamp Unix时间戳
171 | * @returns {string} 格式化的日期时间字符串 (YYYY-MM-DD HH:mm)
172 | */
173 | convertTimestampToDateTime(timestamp) {
174 | const date = new Date(timestamp * 1000)
175 | const year = date.getFullYear()
176 | const month = String(date.getMonth() + 1).padStart(2, '0')
177 | const day = String(date.getDate()).padStart(2, '0')
178 | const hours = String(date.getHours()).padStart(2, '0')
179 | const minutes = String(date.getMinutes()).padStart(2, '0')
180 | return `${year}-${month}-${day} ${hours}:${minutes}`
181 | }
182 |
183 | /**
184 | * 获取当前时间的格式化字符串
185 | * @returns {string} 格式化的当前时间字符串 (YYYY-MM-DD HH:mm:ss)
186 | */
187 | getCurrentTime() {
188 | const now = new Date()
189 | const year = now.getFullYear()
190 | const month = now.getMonth() + 1
191 | const day = now.getDate()
192 | const hour = now.getHours()
193 | const minute = now.getMinutes()
194 | const second = now.getSeconds()
195 |
196 | const formattedMonth = month < 10 ? '0' + month : '' + month
197 | const formattedDay = day < 10 ? '0' + day : '' + day
198 | const formattedHour = hour < 10 ? '0' + hour : '' + hour
199 | const formattedMinute = minute < 10 ? '0' + minute : '' + minute
200 | const formattedSecond = second < 10 ? '0' + second : '' + second
201 | return `${year}-${formattedMonth}-${formattedDay} ${formattedHour}:${formattedMinute}:${formattedSecond}`
202 | }
203 |
204 | /**
205 | * 根据配置和时间判断是否使用深色主题
206 | * @returns {boolean} 是否使用深色主题
207 | */
208 | useDarkTheme() {
209 | let dark = true
210 | const configTheme = Config.app.Theme
211 | if (configTheme === 0) {
212 | const date = new Date().getHours()
213 | if (date >= 6 && date < 18) {
214 | dark = false
215 | }
216 | } else if (configTheme === 1) {
217 | dark = false
218 | } else if (configTheme === 2) {
219 | dark = true
220 | }
221 | return dark
222 | }
223 |
224 | /**
225 | * 计算从指定时间戳到现在经过的时间
226 | * @param {number} timestamp 起始时间戳
227 | * @returns {string} 格式化的经过时间字符串
228 | */
229 | timeSince(timestamp) {
230 | const now = Date.now()
231 | const elapsed = now - timestamp
232 |
233 | const seconds = Math.floor(elapsed / 1000)
234 | const minutes = Math.floor(seconds / 60)
235 | const hours = Math.floor(minutes / 60)
236 |
237 | const remainingSeconds = seconds % 60
238 | const remainingMinutes = minutes % 60
239 |
240 | if (hours > 0) {
241 | return `${hours}小时${remainingMinutes}分钟${remainingSeconds}秒`
242 | } else if (minutes > 0) {
243 | return `${minutes}分钟${remainingSeconds}秒`
244 | } else {
245 | return `${seconds}秒`
246 | }
247 | }
248 | }
249 |
250 | export default new Tools()
251 |
--------------------------------------------------------------------------------
/module/utils/Config.js:
--------------------------------------------------------------------------------
1 | import Version from '../utils/Version.js'
2 | import YamlReader from './YamlReader.js'
3 | import chokidar from 'chokidar'
4 | import fs from 'node:fs'
5 | import _ from 'lodash'
6 | import YAML from 'yaml'
7 |
8 | class Config {
9 | constructor () {
10 | this.config = {}
11 | /** 监听文件 */
12 | this.watcher = { config: {}, defSet: {} }
13 | this.initCfg()
14 | this.transition()
15 | }
16 |
17 | /** 初始化配置 */
18 | initCfg () {
19 | let path
20 | if (Version.BotName === 'Karin') path = `${Version.pluginPath}/config/`
21 | path = `${Version.pluginPath}/config/config/`
22 | if (!fs.existsSync(path)) fs.mkdirSync(path)
23 | const pathDef = `${Version.pluginPath}/config/default_config/`
24 | const files = fs.readdirSync(pathDef).filter(file => file.endsWith('.yaml'))
25 | for (const file of files) {
26 | if (!fs.existsSync(`${path}${file}`)) {
27 | fs.copyFileSync(`${pathDef}${file}`, `${path}${file}`)
28 | } else {
29 | const config = YAML.parse(fs.readFileSync(`${path}${file}`, 'utf8'))
30 | const defConfig = YAML.parse(fs.readFileSync(`${pathDef}${file}`, 'utf8'))
31 | const { differences, result } = this.mergeObjectsWithPriority(config, defConfig)
32 | if (differences) {
33 | fs.copyFileSync(`${pathDef}${file}`, `${path}${file}`)
34 | for (const key in result) {
35 | this.modify(file.replace('.yaml', ''), key, result[key])
36 | }
37 | }
38 | }
39 | this.watch(`${path}${file}`, file.replace('.yaml', ''), 'config')
40 | }
41 | }
42 |
43 | /** json 转 yaml */
44 | transition () {
45 | if (!fs.existsSync(`${Version.pluginPath}/config/config.json`, 'utf8')) {
46 | return
47 | }
48 | const oldCfg = JSON.parse(fs.readFileSync(`${Version.pluginPath}/config/config.json`, 'utf8'))
49 | const configMap = {
50 | cookies: [ 'douyin', 'bilibili' ],
51 | app: [
52 | 'priority', 'filelimit', 'defaulttool', 'rmmp4',
53 | 'sendforwardmsg', 'usefilelimit', 'videotool'
54 | ],
55 | bilibili: [
56 | 'bilibilicommentsimg', 'bilibilinumcomments',
57 | 'bilibilipush', 'bilibilipushcron', 'bilibilipushGroup',
58 | 'bilibilipushlog', 'bilibilitip', 'bilibilitool'
59 | ],
60 | douyin: [ 'comments', 'commentsimg', 'douyinpush',
61 | 'douyinpushcron', 'douyinpushGroup', 'douyinpushlog',
62 | 'douyintip', 'douyintool', 'numcomments', 'sendHDrecord' ], // 省略douyin相关配置项
63 | pushlist: {
64 | douyin: oldCfg.douyinpushlist.map(item => ({
65 | ...item
66 | })),
67 | bilibili: oldCfg.bilibilipushlist.map(item => ({
68 | ...item
69 | }))
70 | }
71 | }
72 | for (const [ category, keys ] of Object.entries(configMap)) {
73 | switch (category) {
74 | case 'cookies':
75 | for (const key of keys) {
76 | this.modify(category, key, key === 'douyin' ? oldCfg['ck'] : oldCfg['bilibilick'])
77 | }
78 | break
79 | case 'pushlist':
80 | for (const [ subCategory, items ] of Object.entries(keys)) {
81 | this.modify(category, subCategory, items)
82 | }
83 | break
84 | default:
85 | for (const key of keys) {
86 | this.modify(category, key, oldCfg[key])
87 | }
88 | }
89 | }
90 | const newCfg = this.All()
91 | fs.unlinkSync(`${Version.pluginPath}/config/config.json`)
92 | return newCfg
93 | }
94 |
95 | /** 插件相关配置 */
96 | get app () {
97 | return this.getDefOrConfig('app')
98 | }
99 |
100 | /** ck相关配置 */
101 | get cookies () {
102 | return this.getDefOrConfig('cookies')
103 | }
104 |
105 | /** 抖音相关配置 */
106 | get douyin () {
107 | return this.getDefOrConfig('douyin')
108 | }
109 |
110 | /** B站相关配置 */
111 | get bilibili () {
112 | return this.getDefOrConfig('bilibili')
113 | }
114 |
115 | /** 推送列表 */
116 | get pushlist () {
117 | return this.getDefOrConfig('pushlist')
118 | }
119 |
120 | get kuaishou () {
121 | return this.getDefOrConfig('kuaishou')
122 | }
123 |
124 | All () {
125 | return {
126 | cookies: this.cookies,
127 | app: this.app,
128 | douyin: this.douyin,
129 | bilibili: this.bilibili,
130 | pushlist: this.pushlist,
131 | kuaishou: this.kuaishou
132 | }
133 | }
134 |
135 | /** 默认配置和用户配置 */
136 | getDefOrConfig (name) {
137 | const def = this.getdefSet(name)
138 | const config = this.getConfig(name)
139 | return { ...def, ...config }
140 | }
141 |
142 | /** 默认配置 */
143 | getdefSet (name) {
144 | return this.getYaml('default_config', name)
145 | }
146 |
147 | /** 用户配置 */
148 | getConfig (name) {
149 | return this.getYaml('config', name)
150 | }
151 |
152 | /**
153 | * 获取配置yaml
154 | * @param type 默认跑配置-defSet,用户配置-config
155 | * @param name 名称
156 | */
157 | getYaml (type, name) {
158 | const file = `${Version.pluginPath}/config/${type}/${name}.yaml`
159 | const key = `${type}.${name}`
160 |
161 | if (this.config[key]) return this.config[key]
162 |
163 | this.config[key] = YAML.parse(
164 | fs.readFileSync(file, 'utf8')
165 | )
166 |
167 | this.watch(file, name, type)
168 |
169 | return this.config[key]
170 | }
171 |
172 | /** 监听配置文件 */
173 | watch (file, name, type = 'default_config') {
174 | const key = `${type}.${name}`
175 | if (this.watcher[key]) return
176 |
177 | const watcher = chokidar.watch(file)
178 | watcher.on('change', async path => {
179 | delete this.config[key]
180 | logger.mark(`[${Version.pluginName}][修改配置文件][${type}][${name}]`)
181 | })
182 |
183 | this.watcher[key] = watcher
184 | }
185 |
186 | /**
187 | * 修改设置
188 | * @param {'cookies','app','douyin','bilibili','pushlist'} name 文件名
189 | * @param {String} key 修改的key值
190 | * @param {String|Number} value 修改的value值
191 | * @param {'config'|'default_config'} type 配置文件或默认
192 | */
193 | modify (name, key, value, type = 'config') {
194 | const path = `${Version.pluginPath}/config/${type}/${name}.yaml`
195 | new YamlReader(path).set(key, value)
196 | delete this.config[`${type}.${name}`]
197 | }
198 |
199 | mergeObjectsWithPriority (objA, objB) {
200 | let differences = false
201 |
202 | function customizer (objValue, srcValue, key, object, source, stack) {
203 | if (_.isArray(objValue) && _.isArray(srcValue)) {
204 | return objValue
205 | } else if (_.isPlainObject(objValue) && _.isPlainObject(srcValue)) {
206 | if (!_.isEqual(objValue, srcValue)) {
207 | return _.mergeWith({}, objValue, srcValue, customizer)
208 | }
209 | } else if (!_.isEqual(objValue, srcValue)) {
210 | differences = true
211 | return objValue !== undefined ? objValue : srcValue
212 | }
213 | return objValue !== undefined ? objValue : srcValue
214 | }
215 |
216 | const result = _.mergeWith({}, objA, objB, customizer)
217 |
218 | return {
219 | differences,
220 | result
221 | }
222 | }
223 | }
224 | export default new Config()
225 |
--------------------------------------------------------------------------------
/module/utils/FFmpeg.js:
--------------------------------------------------------------------------------
1 | import { exec } from 'child_process'
2 |
3 | class FFmpeg {
4 | /** 检查FFmpeg环境 */
5 | async checkEnv () {
6 | return new Promise((resolve, reject) => {
7 | exec('ffmpeg -version', (err) => {
8 | if (err) {
9 | logger.error('ffmepg未安装')
10 | resolve(false)
11 | }
12 | resolve(true)
13 | })
14 | })
15 | }
16 |
17 | /**
18 | * @param {number} 合成方式
19 | * @param {string} 文件1路径
20 | * @param {string} 文件2路径
21 | * @param {string} 合成后的路径
22 | * @param {any} suc 合成成功后的处理函数
23 | * @param {any} faith 合成失败后的处理函数
24 | */
25 | async VideoComposite (v = 1, path = '', path2 = '', resultPath = '', suc, faith = () => { }) {
26 | if (v === 1) {
27 | // 视频 + 音频
28 | exec(`ffmpeg -y -i ${path} -i ${path2} -c copy ${resultPath}`, async function (err) {
29 | if (err) {
30 | logger.error('视频合成失败\n', err)
31 | await faith()
32 | } else {
33 | logger.mark('视频合成成功')
34 | await suc()
35 | }
36 | })
37 | } else if (v === 2) {
38 | // 视频*3 + 音频
39 | exec(`ffmpeg -y -stream_loop 2 -i ${path} -i ${path2} -filter_complex "[0:v]setpts=N/FRAME_RATE/TB[v];[0:a][1:a]amix=inputs=2:duration=shortest:dropout_transition=3[aout]" -map "[v]" -map "[aout]" -c:v libx264 -c:a aac -b:a 192k -shortest ${resultPath}`, async function (err) {
40 | if (err) {
41 | logger.error('视频合成失败\n', err)
42 | await faith()
43 | } else {
44 | logger.mark('视频合成成功')
45 | await suc()
46 | }
47 | })
48 | }
49 | }
50 | }
51 |
52 | export default new FFmpeg()
53 |
--------------------------------------------------------------------------------
/module/utils/Pushlist.js:
--------------------------------------------------------------------------------
1 | import { getDouyinData, getBilibiliData } from '@ikenxuan/amagi'
2 | import { Common, Render, Config } from '../utils/index.js'
3 |
4 | /**
5 | *
6 | * @param {object} e 消息对象
7 | * @param {array} list 推送数组
8 | * @param {string} platform 平台
9 | * @returns {any} 推送消息
10 | */
11 | export default async function Pushlist (e, list, platform) {
12 | const renderOpt = []
13 | if (platform === 'douyin') {
14 | for (const item of list['douyin']) {
15 | const userInfo = await getDouyinData('用户主页数据', Config.cookies.douyin, { sec_uid: item.sec_uid, typeMode: 'strict' })
16 | renderOpt.push({
17 | avatar_img: userInfo.user.avatar_larger.url_list[0],
18 | username: userInfo.user.nickname,
19 | short_id: userInfo.user.unique_id === '' ? userInfo.user.short_id : userInfo.user.unique_id,
20 | fans: Common.count(userInfo.user.follower_count),
21 | total_favorited: Common.count(userInfo.user.total_favorited),
22 | following_count: Common.count(userInfo.user.following_count)
23 | })
24 | }
25 | } else {
26 | for (const item of list['bilibili']) {
27 | const userInfo = await getBilibiliData('用户主页数据', Config.cookies.bilibili, { host_mid: item.host_mid, typeMode: 'strict' })
28 | renderOpt.push({
29 | avatar_img: userInfo.data.card.face,
30 | username: userInfo.data.card.name,
31 | host_mid: userInfo.data.card.mid,
32 | fans: Common.count(userInfo.data.follower),
33 | total_favorited: Common.count(userInfo.data.like_num),
34 | following_count: Common.count(userInfo.data.card.attention)
35 | })
36 | }
37 | }
38 |
39 | if(renderOpt.length === 0) {
40 | if(platform === 'douyin') {
41 | await e.reply(`当前群:${e.group_name}(${e.group_id})\n没有设置任何抖音博主推送!\n可使用「#设置抖音推送 + 抖音号」进行设置`)
42 | } else {
43 | await e.reply(`当前群:${e.group_name}(${e.group_id})\n没有设置任何B站UP推送!\n可使用「#设置B站推送 + UP主UID」进行设置`)
44 | }
45 | return false
46 | }
47 |
48 | const img = await Render.render(
49 | platform === 'douyin' ? 'douyin/userlist' : 'bilibili/userlist',
50 | { renderOpt }
51 | )
52 | return img
53 | }
54 |
--------------------------------------------------------------------------------
/module/utils/Render.js:
--------------------------------------------------------------------------------
1 | import puppeteer from '../../../../lib/puppeteer/puppeteer.js'
2 | import Version from './Version.js'
3 | import Common from './Common.js'
4 | import Config from './Config.js'
5 | import { join } from 'node:path'
6 |
7 | function scale (pct = 1) {
8 | const scale = Math.min(2, Math.max(0.5, Number(Config.app.renderScale) / 100))
9 | pct = pct * scale
10 | return `style=transform:scale(${pct})`
11 | }
12 |
13 | async function gitstatus () {
14 | const status = await Version.checkCommitIdAndUpdateStatus()
15 | if (status.latest) {
16 | return `& Id${status.currentCommitId}`
17 | } else {
18 | return `& Id${status.currentCommitId} & 新版本${status.remoteCommitId}`
19 | }
20 | }
21 |
22 | const Render = {
23 | /**
24 | *
25 | * @param {string} path html模板路径
26 | * @param {*} params 模板参数
27 | * @param {*} cfg 渲染参数
28 | * @param {boolean} multiPage 是否分页截图,默认false
29 | * @returns
30 | */
31 | async render (path, params) {
32 | const basePaths = {
33 | douyin: 'douyin/html',
34 | bilibili: 'bilibili/html',
35 | admin: 'admin/html',
36 | kuaishou: 'kuaishou/html',
37 | help: 'help/html',
38 | version: 'version/html'
39 | }
40 | const platform = Object.keys(basePaths).find(key => path.startsWith(key)) || ''
41 | let newPath = path.substring(platform.length)
42 | // 如果 newPath 以斜杠开头,去掉这个斜杠
43 | if (newPath.startsWith('/')) {
44 | newPath = newPath.substring(1)
45 | }
46 | path = `${basePaths[platform]}/${newPath}`
47 | const data = {
48 | // 资源路径
49 | _res_path: join(Version.pluginPath, 'resources').replace(/\\/g, '/') + '/',
50 | // 布局模板路径
51 | _layout_path: join(Version.pluginPath, 'resources', 'template', 'extend').replace(/\\/g, '/') + '/',
52 | // 默认布局文件路径
53 | defaultLayout: join(Version.pluginPath, 'resources', 'template', 'extend', 'html', 'default.html').replace(/\\/g, '/'),
54 | sys: {
55 | scale: scale(params?.scale ?? 1)
56 | },
57 | copyright: `${Version.BotName}${Version.BotVersion} & ${Version.pluginName}${Version.version} ${await gitstatus()}`,
58 | pageGotoParams: {
59 | waitUntil: 'load'
60 | },
61 | useDarkTheme: Common.useDarkTheme(),
62 | tplFile: `${Version.pluginPath}/resources/template/${path}.html`,
63 | pluResPath: `${Version.pluginPath}/resources/`,
64 | saveId: path.split('/').pop(),
65 | imgType: 'jpeg',
66 | multiPage: true,
67 | multiPageHeight: 12000,
68 | ...params
69 | }
70 | return await puppeteer.screenshots(join(Version.pluginName, path), data)
71 | }
72 | }
73 |
74 | export default Render
75 |
--------------------------------------------------------------------------------
/module/utils/Version.js:
--------------------------------------------------------------------------------
1 | import { join, dirname, basename } from 'path'
2 | import { execSync } from 'child_process'
3 | import { fileURLToPath } from 'url'
4 | import simpleGit from 'simple-git'
5 | import lodash from 'lodash'
6 | import fs from 'fs'
7 |
8 | const __filename = fileURLToPath(import.meta.url)
9 |
10 | const __dirname = dirname(__filename)
11 |
12 | const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
13 |
14 | const getLine = function (line) {
15 | line = line.replace(/(^\s*\*|\r)/g, '')
16 | line = line.replace(/\s*`([^`]+`)/g, '$1')
17 | line = line.replace(/`\s*/g, '')
18 | line = line.replace(/\s*\*\*([^\\*]+\*\*)/g, '$1')
19 | line = line.replace(/\*\*\s*/g, '')
20 | line = line.replace(/ⁿᵉʷ/g, '')
21 | return line
22 | }
23 |
24 | const readLogFile = function (root, versionCount = 4) {
25 | const logPath = `${root}/CHANGELOG.md`
26 | const packagePath = `${root}/package.json`
27 | let logs = {}
28 | const changelogs = []
29 | let currentVersion
30 | const ver = JSON.parse(fs.readFileSync(packagePath))
31 |
32 | try {
33 | if (fs.existsSync(logPath)) {
34 | logs = fs.readFileSync(logPath, 'utf8') || ''
35 | logs = logs.split('\n')
36 |
37 | let temp = {}
38 | let lastLine = {}
39 | lodash.forEach(logs, (line) => {
40 | if (versionCount <= -1) {
41 | return false
42 | }
43 | const versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line)
44 | if (versionRet && versionRet[1]) {
45 | const v = versionRet[1].trim()
46 | if (!currentVersion) {
47 | currentVersion = v || ver.version
48 | } else {
49 | changelogs.push(temp)
50 | if (/0\s*$/.test(v) && versionCount > 0) {
51 | versionCount = 0
52 | } else {
53 | versionCount--
54 | }
55 | }
56 |
57 | temp = {
58 | version: v,
59 | logs: []
60 | }
61 | } else {
62 | if (!line.trim()) {
63 | return
64 | }
65 | if (/^\*/.test(line)) {
66 | lastLine = {
67 | title: getLine(line),
68 | logs: []
69 | }
70 | temp.logs.push(lastLine)
71 | } else if (/^\s{2,}\*/.test(line)) {
72 | lastLine.logs.push(getLine(line))
73 | }
74 | }
75 | })
76 |
77 | if (Object.keys(temp).length > 0) {
78 | changelogs.push(temp)
79 | }
80 | }
81 | } catch (e) {
82 | // do nth
83 | }
84 | return { changelogs, currentVersion }
85 | }
86 | const pluginPath = join(__dirname, '..', '..').replace(/\\/g, '/')
87 |
88 | const pluginName = basename(pluginPath)
89 |
90 | /**
91 | * @type {'Karin'|'Miao-Yunzai'|'Trss-Yunzai'|'yunzai'}
92 | */
93 | const BotName = (() => {
94 | if (/^karin/i.test(pluginName)) {
95 | return 'Karin'
96 | } else if (packageJson.name === 'yunzai-next') {
97 | return 'yunzai'
98 | } else if (Array.isArray(global.Bot?.uin)) {
99 | return 'TRSS-Yunzai'
100 | } else if (packageJson.dependencies.sequelize) {
101 | return 'Miao-Yunzai'
102 | } else {
103 | throw new Error('还有人玩Yunzai-Bot??')
104 | }
105 | })()
106 |
107 | const BotVersion = packageJson.version
108 |
109 | const { changelogs, currentVersion } = readLogFile(pluginPath)
110 |
111 | const clientPath = process.cwd()
112 |
113 | async function checkCommitIdAndUpdateStatus () {
114 | const git = simpleGit({ baseDir: pluginPath })
115 | let result = {
116 | currentCommitId: null,
117 | remoteCommitId: null,
118 | latest: false,
119 | error: null,
120 | commitLog: null
121 | }
122 |
123 | // Timeout Promise
124 | const timeoutPromise = new Promise((_, reject) =>
125 | setTimeout(() => reject(new Error('Operation timed out')), 5000)
126 | )
127 |
128 | // Main logic wrapped in a promise
129 | const mainLogic = (async () => {
130 | try {
131 | // Attempt to get the current commit ID (short version)
132 | const stdout = execSync(`git -C "${pluginPath}" rev-parse --short=7 HEAD`).toString().trim()
133 | result.currentCommitId = stdout
134 |
135 | // Perform git fetch
136 | await git.fetch()
137 |
138 | // Get the remote commit ID (short version)
139 | const remoteCommitId = (await git.revparse([ 'HEAD@{u}' ])).substring(0, 7)
140 | result.remoteCommitId = remoteCommitId
141 |
142 | // Compare local and remote commit IDs
143 | if (result.currentCommitId === result.remoteCommitId) {
144 | result.latest = true
145 | const log = await git.log({ from: result.currentCommitId, to: result.currentCommitId })
146 | if (log && log.all && log.all.length > 0) {
147 | result.commitLog = log.all[0].message
148 | }
149 | }
150 | } catch (error) {
151 | console.error(`Failed to check update status: ${error.message}`)
152 | result.error = 'Failed to check update status'
153 | }
154 |
155 | return result
156 | })()
157 |
158 | // Race the main logic against the timeout
159 | try {
160 | return await Promise.race([ mainLogic, timeoutPromise ])
161 | } catch (error) {
162 | console.error(error.message)
163 | result.error = error.message
164 | return result
165 | }
166 | }
167 |
168 | export default {
169 | /**
170 | * @type {string} 插件版本号
171 | */
172 | get version () {
173 | return JSON.parse(fs.readFileSync(`${pluginPath}/package.json`, 'utf8')).version
174 | },
175 |
176 | /**
177 | * @type {string} 插件更新日志
178 | */
179 | get changelogs () {
180 | return changelogs
181 | },
182 |
183 | /**
184 | * @type {string} 匹配更新日志函数
185 | */
186 | readLogFile,
187 |
188 | /**
189 | * @type {string} 插件名称
190 | */
191 | pluginName,
192 |
193 | /**
194 | * @type {string} 插件路径
195 | */
196 | pluginPath,
197 |
198 | /**
199 | * @type {'Karin'|'Miao-Yunzai'|'TRSS-Yunzai'|'yunzai'} Bot名称
200 | */
201 | BotName,
202 |
203 | /**
204 | * @type {string} Bot版本
205 | */
206 | BotVersion,
207 |
208 | /**
209 | * @type {string} 机器人程序/客户端路径
210 | */
211 | clientPath,
212 | checkCommitIdAndUpdateStatus
213 | }
214 |
--------------------------------------------------------------------------------
/module/utils/YamlReader.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import YAML from 'yaml'
3 |
4 | /**
5 | * YamlReader类提供了对YAML文件的动态读写功能
6 | */
7 | export default class YamlReader {
8 | /**
9 | * 创建一个YamlReader实例。
10 | * @param {string} filePath - 文件路径
11 | */
12 | constructor (filePath) {
13 | this.filePath = filePath
14 | this.document = this.parseDocument()
15 | }
16 |
17 | /**
18 | * 解析YAML文件并返回Document对象,保留注释。
19 | * @returns {Document} 包含YAML数据和注释的Document对象
20 | */
21 | parseDocument () {
22 | const fileContent = fs.readFileSync(this.filePath, 'utf8')
23 | return YAML.parseDocument(fileContent)
24 | }
25 |
26 | /**
27 | * 修改指定参数的值。
28 | * @param {string} key - 参数键名
29 | * @param {any} value - 新的参数值
30 | */
31 | set (key, value) {
32 | this.document.set(key, value)
33 | this.write()
34 | }
35 |
36 | /**
37 | * 从YAML文件中删除指定参数。
38 | * @param {string} key - 要删除的参数键名
39 | */
40 | rm (key) {
41 | this.document.delete(key)
42 | this.write()
43 | }
44 |
45 | /**
46 | * 将更新后的Document对象写入YAML文件中。
47 | */
48 | write () {
49 | fs.writeFileSync(this.filePath,
50 | this.document.toString({
51 | lineWidth: -1,
52 | noCompatMode: true,
53 | simpleKeys: true
54 | }), 'utf8')
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/module/utils/index.js:
--------------------------------------------------------------------------------
1 | import Base from './Base.js'
2 | import Version from './Version.js'
3 | import Render from './Render.js'
4 | import Config from './Config.js'
5 | import UploadRecord from './UploadRecord.js'
6 | import Networks from './Networks.js'
7 | import Pushlist from './Pushlist.js'
8 | import DB from '../db/index.js'
9 | import FFmpeg from './FFmpeg.js'
10 | import Common from './Common.js'
11 |
12 | export { Version, Render, Config, Base, UploadRecord, Networks, Pushlist, DB, Sleep, FFmpeg, Common }
13 |
14 | /**
15 | * 休眠函数
16 | * @param ms 毫秒
17 | */
18 | function Sleep (ms) {
19 | return new Promise((resolve) => setTimeout(resolve, ms))
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kkkkkk-10086",
3 | "version": "1.8.0",
4 | "main": "index.js",
5 | "type": "module",
6 | "repository": "https://github.com/ikenxuan/kkkkkk-10086.git",
7 | "author": "ikenxuan",
8 | "license": "GPL-3.0-only",
9 | "scripts": {
10 | "fix": "eslint apps/**/*.js --fix && eslint module/**/*.js --fix"
11 | },
12 | "dependencies": {
13 | "@ikenxuan/amagi": "^4.4.16",
14 | "@karinjs/md-html": "^1.2.1",
15 | "child_process": "^1.0.2",
16 | "node-fetch": "^3.3.2",
17 | "qrcode": "^1.5.4",
18 | "sequelize": "6.37.3",
19 | "sharp": "0.33.4",
20 | "simple-git": "^3.25.0",
21 | "sqlite3": "5.1.6"
22 | },
23 | "devDependencies": {
24 | "eslint": "^9.11.1",
25 | "globals": "^15.9.0",
26 | "neostandard": "^0.11.6"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/font/HarmonyOS_Sans_SC_Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/font/HarmonyOS_Sans_SC_Regular.woff2
--------------------------------------------------------------------------------
/resources/font/bilifont/bilifont.1.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/font/bilifont/bilifont.1.woff2
--------------------------------------------------------------------------------
/resources/font/bilifont/bilifont.2.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/font/bilifont/bilifont.2.woff2
--------------------------------------------------------------------------------
/resources/font/bilifont/font.css:
--------------------------------------------------------------------------------
1 | /** generated by https://github.com/voderl/font-slice */
2 | @font-face {
3 | font-family: bilifont;
4 | src: url("./bilifont.1.woff2") format("woff2");
5 | font-weight: 100;
6 | font-style: normal;
7 | font-display: swap;
8 | unicode-range: U+4e;
9 | }
10 |
11 | @font-face {
12 | font-family: bilifont;
13 | src: url("./bilifont.2.woff2") format("woff2");
14 | font-weight: 100;
15 | font-style: normal;
16 | font-display: swap;
17 | unicode-range: U+2e,U+30-39,U+43,U+44,U+4f;
18 | }
19 |
20 | @font-face {
21 | font-family: HarmonyOSHans-Regular;
22 | src: url("../HarmonyOS_Sans_SC_Regular.woff2") format("woff2");
23 | font-weight: 100;
24 | font-style: normal;
25 | font-display: swap;
26 | }
27 |
--------------------------------------------------------------------------------
/resources/image/admin/bg-sr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/admin/bg-sr.webp
--------------------------------------------------------------------------------
/resources/image/admin/default.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/admin/default.jpg
--------------------------------------------------------------------------------
/resources/image/bilibili/bilibili-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/bilibili/bilibili-dark.png
--------------------------------------------------------------------------------
/resources/image/bilibili/bilibili-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/bilibili/bilibili-light.png
--------------------------------------------------------------------------------
/resources/image/bilibili/bilibili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/bilibili/bilibili.png
--------------------------------------------------------------------------------
/resources/image/bilibili/id-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/bilibili/id-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/bilibili/like-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/bilibili/like-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/bilibili/直播中.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/bilibili/直播中.png
--------------------------------------------------------------------------------
/resources/image/douyin/douyin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/douyin/douyin.png
--------------------------------------------------------------------------------
/resources/image/douyin/dylogo-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/douyin/dylogo-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/image/douyin/search-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/douyin/search-light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/image/douyin/抖音-直播中.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/douyin/抖音-直播中.png
--------------------------------------------------------------------------------
/resources/image/kuaishou/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/kuaishou/logo.png
--------------------------------------------------------------------------------
/resources/image/pic1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikenxuan/kkkkkk-10086/0d7464f145ef2c7c66925c31fd97b9f4d13e12e8/resources/image/pic1.png
--------------------------------------------------------------------------------
/resources/template/admin/css/index.css:
--------------------------------------------------------------------------------
1 | .container {
2 | background: url('../../../image/admin/default.jpg') #606060 left top no-repeat;
3 | background-size: 520px auto;
4 | padding: 20px 15px 10px 15px;
5 | background-size: contain;
6 | font-size: 45px;
7 | color: #1e1f20;
8 | width: 1440px;
9 | }
10 |
11 | .head-box {
12 | border-radius: 15px;
13 | padding: 10px 20px;
14 | position: relative;
15 | color: #fff;
16 | margin-top: 30px;
17 | }
18 |
19 | .head-box .title {
20 | font-size: 130px;
21 | text-shadow: 6px 5px 13px #4a4a4a, 1px 1px 0px rgba(0, 0, 0, 0.9);
22 | font-weight: 700;
23 | }
24 |
25 | .head-box .title .label {
26 | display: inline-block;
27 | margin-left: 10px;
28 | }
29 |
30 | .head-box .genshin_logo {
31 | position: absolute;
32 | top: 1px;
33 | right: 15px;
34 | width: 97px;
35 | }
36 |
37 | .head-box .label {
38 | font-size: 50px;
39 | text-shadow: 0 0 3px #000,
40 | 1px 1px 3px rgba(0, 0, 0, 0.9);
41 | }
42 |
43 | .head-box .label span {
44 | color: #d3bc8e;
45 | padding: 0 2px;
46 | }
47 |
48 | .head-box {
49 | margin: 0 0 90px 0;
50 | }
51 |
52 | .cfg-box {
53 | border-radius: 15px;
54 | margin-top: 20px;
55 | margin-bottom: 20px;
56 | padding: 15px 20px;
57 | overflow: hidden;
58 | position: relative;
59 | -webkit-backdrop-filter: blur(10px);
60 | backdrop-filter: blur(100px);
61 | background: #d5deff1f;
62 | border-radius: 25px;
63 | box-shadow: 0px 8px 20px 0px rgba(51, 51, 52, 0.5), inset 3px 3px 30px 10px rgba(255, 255, 255, 0.8);
64 | }
65 |
66 | .cfg-group {
67 | color: #ffcfde;
68 | font-size: 65px;
69 | font-weight: 700;
70 | padding: 30px 30px;
71 | text-shadow: 2px 2px 5px #0000008f;
72 | }
73 |
74 | .cfg-li {
75 | min-height: 36px;
76 | position: relative;
77 | list-style: none;
78 | margin-bottom: 15px;
79 | background: rgb(203 196 190 / 0%);
80 | }
81 |
82 | .cfg-line {
83 | color: #303030;
84 | line-height: 69px;
85 | padding-left: 41px;
86 | font-weight: 700;
87 | border-radius: 5px 30px;
88 | box-shadow: 3px 3px 20px 0px rgb(0 0 0 / 50%);
89 | background-image: linear-gradient(90deg, #fceae7a1 40%, #fbf3f1b5 65%, #d5dfff9e);
90 | font-size: 37px;
91 | }
92 |
93 | .cfg-hint {
94 | font-size: 27px;
95 | font-weight: 400;
96 | margin-top: -4px;
97 | margin-bottom: -3px;
98 | }
99 |
100 | .cfg-status {
101 | position: absolute;
102 | top: 0;
103 | right: 0;
104 | height: 36px;
105 | width: 345px;
106 | text-align: center;
107 | /* line-height: 80px; */
108 | font-size: 37px;
109 | color: #2d5285;
110 | font-weight: 700;
111 | border-radius: 0 16px 16px 0;
112 | }
113 |
114 | .cfg-status.status-off {
115 | color: #b74545;
116 | }
117 |
118 | .cfg-desc {
119 | font-size: 29px;
120 | color: #454444;
121 | margin: 10px 0 14px 23px;
122 | }
123 |
124 | .badge {
125 | display: inline-block;
126 | vertical-align: top;
127 | height: 18px;
128 | padding: 0 6px;
129 | border-radius: 3px;
130 | background: #f4cd00;
131 | color: #fff;
132 | font-size: 14px;
133 | line-height: 18px;
134 | }
135 |
--------------------------------------------------------------------------------
/resources/template/bilibili/css/dynamic/DYNAMIC_TYPE_DRAW.css:
--------------------------------------------------------------------------------
1 | body.light-mode .container {
2 | position: relative;
3 | background-color: #f4f4f4;
4 | width: 1440px;
5 | height: auto;
6 | }
7 |
8 | body.dark-mode .container {
9 | position: relative;
10 | background-color: #1A1A1A;
11 | width: 1440px;
12 | height: auto;
13 | }
14 |
15 | .spacer1 {
16 | height: 60px;
17 | }
18 |
19 | .spacer2 {
20 | height: 82px;
21 | }
22 |
23 | .spacer3 {
24 | height: 50px;
25 | }
26 |
27 | .spacer4 {
28 | height: 60px;
29 | }
30 |
31 | .spacer5 {
32 | height: 10px;
33 | }
34 |
35 | .spacer6 {
36 | height: 100px;
37 | }
38 |
39 | .container {
40 | width: 100% !important;
41 | margin: 0 auto;
42 | }
43 |
44 | body.light-mode .bitop {
45 | color: #3e3e3eb8;
46 | font-size: 65px;
47 | display: -moz-box;
48 | display: -ms-flexbox;
49 | -moz-box-align: center;
50 | -ms-flex-align: center;
51 | align-items: center;
52 | }
53 |
54 | body.dark-mode .bitop {
55 | color: #d3d3d3;
56 | font-size: 65px;
57 | display: -moz-box;
58 | display: -ms-flexbox;
59 | -moz-box-align: center;
60 | -ms-flex-align: center;
61 | align-items: center;
62 | }
63 |
64 | .bitop img {
65 | width: 320px;
66 | height: auto;
67 | }
68 |
69 | .cover {
70 | display: flex;
71 | flex-direction: column;
72 | align-items: center;
73 | }
74 |
75 | body.light-mode .imgbox {
76 | display: flex;
77 | flex-direction: column;
78 | align-items: center;
79 | overflow: hidden;
80 | box-shadow: 0px 10px 20px 0px #7d7d7d80;
81 | border-radius: 25px;
82 | width: 90%;
83 | flex: 1;
84 | }
85 |
86 | body.dark-mode .imgbox {
87 | display: flex;
88 | flex-direction: column;
89 | align-items: center;
90 | overflow: hidden;
91 | box-shadow: 0px 10px 20px 0px #00000080;
92 | border-radius: 25px;
93 | width: 90%;
94 | flex: 1;
95 | }
96 |
97 |
98 | .imgbox_1 {
99 | border-radius: 25px;
100 | object-fit: contain;
101 | width: 100%;
102 | height: 100%;
103 | }
104 |
105 | .info {
106 | width: 100% !important;
107 | display: flex !important;
108 | flex-direction: column;
109 | padding: 0 85px !important;
110 | line-height: 1.5;
111 | }
112 |
113 | body.light-mode .info_text {
114 | font-size: 50px;
115 | align-items: center;
116 | letter-spacing: 1.5px;
117 | position: relative;
118 | word-wrap: break-word;
119 | }
120 |
121 | body.dark-mode .info_text {
122 | font-size: 50px;
123 | align-items: center;
124 | letter-spacing: 1.5px;
125 | position: relative;
126 | word-wrap: break-word;
127 | color: #e9e9e9;
128 | }
129 |
130 | body.light-mode .video_info_text {
131 | font-size: 80px;
132 | font-weight: 700;
133 | align-items: center;
134 | letter-spacing: 1.5px;
135 | position: relative;
136 | word-wrap: break-word;
137 | }
138 |
139 | body.dark-mode .video_info_text {
140 | font-size: 80px;
141 | font-weight: 700;
142 | align-items: center;
143 | letter-spacing: 1.5px;
144 | position: relative;
145 | word-wrap: break-word;
146 | color: #e7e7e7;
147 | }
148 |
149 | body.light-mode .intro {
150 | color: #555555;
151 | font-size: 60px;
152 | }
153 |
154 | body.dark-mode .intro {
155 | color: #ababab;
156 | font-size: 60px;
157 | }
158 |
159 | .work_status {
160 | color: #808080;
161 | letter-spacing: normal;
162 | font-size: 45px;
163 | font-weight: 300;
164 | }
165 |
166 | .dynamic_create_time {
167 | white-space: nowrap;
168 | color: #808080;
169 | font-size: 35px;
170 | font-weight: 400;
171 | }
172 |
173 | .under {
174 | display: flex;
175 | height: 100%;
176 | flex-direction: column;
177 | }
178 |
179 | .rectangular_box {
180 | height: auto;
181 | display: -webkit-box;
182 | display: -webkit-flex;
183 | display: -moz-box;
184 | display: -ms-flexbox;
185 | display: flex;
186 | -webkit-box-pack: justify;
187 | -webkit-justify-content: space-between;
188 | -moz-box-pack: justify;
189 | -ms-flex-pack: justify;
190 | justify-content: space-between;
191 | padding: 100px 0 45px 0;
192 | -webkit-box-align: center;
193 | -webkit-align-items: center;
194 | -moz-box-align: center;
195 | -ms-flex-align: center;
196 | align-items: center;
197 | }
198 |
199 | .user {
200 | display: flex;
201 | flex-direction: column;
202 | padding-left: 65px;
203 | }
204 |
205 | .userinfo {
206 | display: flex;
207 | align-items: center;
208 | padding: 0 0 0 100px;
209 | }
210 |
211 | body.light-mode .userinfo_text {
212 | display: flex;
213 | font-size: 35px;
214 | color: #2f2f2f;
215 | padding: 25px 0 0 0;
216 | flex-direction: column;
217 | align-items: flex-start;
218 | width: 100%;
219 | letter-spacing: 2.5px;
220 | }
221 |
222 | body.dark-mode .userinfo_text {
223 | display: flex;
224 | font-size: 35px;
225 | color: #cfcfcf;
226 | padding: 25px 0 0 0;
227 | flex-direction: column;
228 | align-items: flex-start;
229 | width: 100%;
230 | letter-spacing: 2.5px;
231 | }
232 |
233 | .name_and_followers {
234 | display: flex;
235 | font-size: 30px;
236 | flex-direction: column;
237 | }
238 |
239 | .bili_dyn_item_ornament {
240 | padding: 0 0 0 235px;
241 | }
242 |
243 | .name_and_followers_text {
244 | font-size: 20px;
245 | color: #2f2f2f;
246 | margin-left: 10px;
247 | text-align: right;
248 | margin-right: 10px;
249 | }
250 |
251 | .avatar {
252 | margin: 0px 20px 0 0;
253 | border-radius: 50%;
254 | height: auto;
255 | width: 127px;
256 | box-shadow: 0px 10px 20px 0px #4343434f;
257 | }
258 |
259 | .avatar-1 {
260 | width: 142px;
261 | /* height: 150px; */
262 | margin: 0 -115px 0 0;
263 | margin-top: 50px;
264 | transform: translateY(-19%) translateX(-109%) scale(1.6);
265 | }
266 |
267 | /* 当img元素为空时,设置元素透明度为0,图裂图标将被隐藏 */
268 | img[src=''],
269 | img:not([src]) {
270 | opacity: 0;
271 | }
272 |
273 | .username {
274 | font-weight: 700;
275 | font-size: 60px;
276 | /* margin: 0 0 0 0; */
277 | }
278 |
279 | .qrcode_box {
280 | display: flex !important;
281 | margin: 0 75px -50px 0 !important;
282 | align-items: center !important;
283 | flex-direction: column-reverse;
284 | }
285 |
286 | body.light-mode .qrcode_text {
287 | font-size: 45px;
288 | color: #2f2f2f;
289 | margin-left: 10px;
290 | text-align: right;
291 | margin: 20px 0 0 0;
292 | }
293 |
294 | body.dark-mode .qrcode_text {
295 | font-size: 45px;
296 | color: #d7d7d7;
297 | margin-left: 10px;
298 | text-align: right;
299 | margin: 20px 0 0 0;
300 | }
301 |
302 | body.light-mode #qrcode {
303 | border: 7px dashed #3a3a3a;
304 | padding: 10px;
305 | border-radius: 2%;
306 | }
307 |
308 | body.dark-mode #qrcode {
309 | border: 7px dashed #C3C3C3;
310 | padding: 10px;
311 | border-radius: 2%;
312 | }
313 |
314 | #qrcode img {
315 | width: 350px;
316 | height: auto;
317 | }
318 |
319 | body.light-mode .other_text {
320 | color: #575757;
321 | font-size: 70px;
322 | text-align: right;
323 | margin: 0 80px -45px 0;
324 | z-index: -1;
325 | }
326 |
327 | body.dark-mode .other_text {
328 | color: #c1c1c1;
329 | font-size: 70px;
330 | text-align: right;
331 | margin: 0 80px -45px 0;
332 | z-index: -1;
333 | }
--------------------------------------------------------------------------------
/resources/template/bilibili/css/dynamic/DYNAMIC_TYPE_LIVE_RCMD.css:
--------------------------------------------------------------------------------
1 | body.light-mode .container {
2 | transform: scale(1.4);
3 | transform-origin: 0 0;
4 | position: relative;
5 | background-color: #f4f4f4;
6 | width: fit-content;
7 | height: auto;
8 | }
9 |
10 | body.dark-mode .container {
11 | transform: scale(1.4);
12 | transform-origin: 0 0;
13 | position: relative;
14 | background-color: #1a1a1a;
15 | width: fit-content;
16 | height: auto;
17 | }
18 |
19 | .spacer1 {
20 | height: 20px;
21 | }
22 |
23 | .spacer2 {
24 | height: 70px;
25 | }
26 |
27 | .spacer3 {
28 | height: 10px;
29 | }
30 |
31 | .spacer4 {
32 | height: 20px;
33 | }
34 |
35 | .spacer5 {
36 | height: 25px;
37 | }
38 |
39 | .spacer6 {
40 | height: 100px;
41 | }
42 |
43 | .spacer7 {
44 | height: 120px;
45 | }
46 |
47 | .container {
48 | width: 100% !important;
49 | }
50 |
51 | .bitop {
52 | display: -webkit-box;
53 | display: -webkit-flex;
54 | display: -moz-box;
55 | display: -ms-flexbox;
56 | -webkit-box-align: center;
57 | -webkit-align-items: center;
58 | -moz-box-align: center;
59 | -ms-flex-align: center;
60 | align-items: center;
61 | padding: 0 0 50px 0;
62 | }
63 |
64 | .bitop svg {
65 | width: 400px;
66 | height: auto;
67 | }
68 |
69 | .cover {
70 | display: flex;
71 | flex-direction: column;
72 | align-items: center;
73 | }
74 |
75 | body.light-mode .imgbox {
76 | margin: 50px 0 0 0;
77 | display: flex;
78 | flex-direction: column;
79 | align-items: center;
80 | overflow: hidden;
81 | box-shadow: 0px 10px 20px 0px #7d7d7d80;
82 | border-radius: 25px;
83 | width: 95%;
84 | flex: 1;
85 | }
86 |
87 | body.dark-mode .imgbox {
88 | margin: 50px 0 0 0;
89 | display: flex;
90 | flex-direction: column;
91 | align-items: center;
92 | overflow: hidden;
93 | box-shadow: 0px 10px 20px 0px #00000080;
94 | border-radius: 25px;
95 | width: 95%;
96 | flex: 1;
97 | }
98 |
99 | .imgbox_1 {
100 | border-radius: 25px;
101 | object-fit: contain;
102 | width: 100%;
103 | height: 100%;
104 | }
105 |
106 | .info {
107 | display: flex !important;
108 | flex-direction: column;
109 | padding: 0 60px;
110 | }
111 |
112 | body.light-mode .info_title {
113 | font-weight: 700;
114 | font-size: 65px;
115 | align-items: center;
116 | letter-spacing: 1.5px;
117 | position: relative;
118 | word-wrap: break-word;
119 | }
120 |
121 | body.dark-mode .info_title {
122 | font-weight: 700;
123 | font-size: 65px;
124 | align-items: center;
125 | letter-spacing: 1.5px;
126 | position: relative;
127 | word-wrap: break-word;
128 | color: #e7e7e7;
129 | }
130 |
131 | body.light-mode .room_id {
132 | color: #3d3d3d;
133 | font-size: 45px;
134 | align-items: center;
135 | letter-spacing: 1.5px;
136 | position: relative;
137 | word-wrap: break-word;
138 | letter-spacing: normal;
139 | }
140 |
141 | body.dark-mode .room_id {
142 | font-size: 45px;
143 | align-items: center;
144 | letter-spacing: 1.5px;
145 | position: relative;
146 | word-wrap: break-word;
147 | color: #bbbbbb;
148 | letter-spacing: normal;
149 | }
150 |
151 | body.light-mode .room_create_time {
152 | color: #3d3d3d;
153 | letter-spacing: normal;
154 | font-size: 35px;
155 | }
156 |
157 | body.dark-mode .room_create_time {
158 | color: #bbbbbb;
159 | letter-spacing: normal;
160 | font-size: 35px;
161 | }
162 |
163 |
164 | .under {
165 | display: flex;
166 | width: auto;
167 | height: 100%;
168 | display: -webkit-box;
169 | display: -webkit-flex;
170 | display: -moz-box;
171 | display: -ms-flexbox;
172 | display: flex;
173 | -webkit-box-orient: vertical;
174 | -webkit-box-direction: normal;
175 | -webkit-flex-direction: column;
176 | -moz-box-orient: vertical;
177 | -moz-box-direction: normal;
178 | -ms-flex-direction: column;
179 | flex-direction: column;
180 | }
181 |
182 | body.light-mode .other_text {
183 | width: inherit;
184 | color: #454545;
185 | font-size: 70px;
186 | text-align: right;
187 | margin: 0 20px -45px 0;
188 | z-index: -1;
189 | }
190 |
191 | body.dark-mode .other_text {
192 | width: inherit;
193 | color: #bbbbbb;
194 | font-size: 70px;
195 | text-align: right;
196 | margin: 0 20px -45px 0;
197 | z-index: -1;
198 | }
199 |
200 | .rectangular_box {
201 | height: auto;
202 | display: -webkit-box;
203 | display: -webkit-flex;
204 | display: -moz-box;
205 | display: -ms-flexbox;
206 | display: flex;
207 | -webkit-box-pack: justify;
208 | -webkit-justify-content: space-between;
209 | -moz-box-pack: justify;
210 | -ms-flex-pack: justify;
211 | justify-content: space-between;
212 | padding: 70px 0 0 0;
213 | -webkit-box-align: center;
214 | -webkit-align-items: center;
215 | -moz-box-align: center;
216 | -ms-flex-align: center;
217 | align-items: center;
218 | }
219 |
220 | .user {
221 | display: flex;
222 | flex-direction: column;
223 | margin: 0 0 0 45px;
224 | }
225 |
226 | .userinfo {
227 | display: flex;
228 | justify-content: flex-start;
229 | align-items: flex-start;
230 | flex-direction: column;
231 | }
232 |
233 | .userinfo_text {
234 | display: flex;
235 | font-size: 2em;
236 | color: #2f2f2f;
237 | padding: 25px 0 0 150px;
238 | flex-direction: column;
239 | align-items: flex-start;
240 | width: 100%;
241 | letter-spacing: 2.5px;
242 | }
243 |
244 | .name_and_followers {
245 | display: flex;
246 | flex-direction: row;
247 | align-items: center;
248 | margin-bottom: 5px;
249 | }
250 |
251 | .livestat {
252 | width: 130px;
253 | height: auto;
254 | }
255 |
256 | body.dark-mode .fans {
257 | color: #4d4d4d;
258 | font-size: 35px;
259 | }
260 |
261 | body.dark-mode .fans {
262 | color: #dbdbdb;
263 | font-size: 35px;
264 | }
265 |
266 | .avatar {
267 | margin: 0 15px 0 0;
268 | border-radius: 50%;
269 | height: auto;
270 | width: 130px;
271 | }
272 |
273 | .avatar-1 {
274 | width: 220px;
275 | height: 220px;
276 | margin-right: -255px;
277 | margin-top: 50px;
278 | transform: translateY(-11%) translateX(-105%) scale(1);
279 | }
280 |
281 | /* 当img元素为空时,设置元素透明度为0,图裂图标将被隐藏 */
282 | img[src=''],
283 | img:not([src]) {
284 | opacity: 0;
285 | }
286 |
287 | .avatarinfo {
288 | display: flex;
289 | align-items: center;
290 | gap: 40px;
291 | }
292 |
293 | body.light-mode .username {
294 | font-size: 60px;
295 | color: #1a1a1a;
296 | }
297 |
298 | body.dark-mode .username {
299 | font-size: 60px;
300 | color: #e7e7e7;
301 | }
302 |
303 | body.light-mode .fans {
304 | font-size: 35px;
305 | color: #1a1a1a;
306 | }
307 |
308 | body.dark-mode .fans {
309 | font-size: 35px;
310 | color: #e7e7e7;
311 | }
312 |
313 | .qrcode_box {
314 | display: -webkit-box !important;
315 | display: -webkit-flex !important;
316 | display: -moz-box !important;
317 | display: -ms-flexbox !important;
318 | display: flex !important;
319 | margin: 30px 20px 0 0 !important;
320 | -webkit-box-align: center !important;
321 | -webkit-align-items: center !important;
322 | -moz-box-align: center !important;
323 | -ms-flex-align: center !important;
324 | align-items: center !important;
325 | -webkit-box-orient: vertical;
326 | -webkit-box-direction: reverse;
327 | -webkit-flex-direction: column-reverse;
328 | -moz-box-orient: vertical;
329 | -moz-box-direction: reverse;
330 | -ms-flex-direction: column-reverse;
331 | flex-direction: column-reverse;
332 | }
333 |
334 | body.light-mode .qrcode_text {
335 | font-size: 50px;
336 | color: #2f2f2f;
337 | margin-left: 10px;
338 | text-align: right;
339 | margin-right: 10px;
340 | }
341 |
342 | body.dark-mode .qrcode_text {
343 | font-size: 50px;
344 | color: #c3c3c3;
345 | margin-left: 10px;
346 | text-align: right;
347 | margin-right: 10px;
348 | }
349 |
350 | body.light-mode .t {
351 | letter-spacing: 10px;
352 | font-size: 50px;
353 | color: #5d5d5d;
354 | }
355 |
356 | body.dark-mode .t {
357 | letter-spacing: 10px;
358 | font-size: 50px;
359 | color: #bbbbbb;
360 | }
361 |
362 | body.light-mode #qrcode {
363 | border: 7px dashed #3a3a3a;
364 | padding: 10px;
365 | border-radius: 2%;
366 | }
367 |
368 | body.dark-mode #qrcode {
369 | border: 7px dashed #C3C3C3;
370 | padding: 10px;
371 | border-radius: 2%;
372 | }
373 |
374 | #qrcode img {
375 | width: 350px;
376 | /* height: 150px; */
377 | }
378 |
379 | .ftext {
380 | color: #808080;
381 | font-size: 15px;
382 | }
--------------------------------------------------------------------------------
/resources/template/bilibili/css/dynamic/base.css:
--------------------------------------------------------------------------------
1 | body.light-mode .container {
2 | position: relative;
3 | background-color: #f4f4f4;
4 | width: 1440px;
5 | height: auto;
6 | }
7 |
8 | body.dark-mode .container {
9 | position: relative;
10 | background-color: #1A1A1A;
11 | width: 1440px;
12 | height: auto;
13 | }
14 |
15 | .spacer1 {
16 | height: 60px;
17 | }
18 |
19 | .spacer2 {
20 | height: 82px;
21 | }
22 |
23 | .spacer3 {
24 | height: 50px;
25 | }
26 |
27 | .spacer4 {
28 | height: 60px;
29 | }
30 |
31 | .spacer5 {
32 | height: 10px;
33 | }
34 |
35 | .spacer6 {
36 | height: 100px;
37 | }
38 |
39 | .container {
40 | width: 100% !important;
41 | margin: 0 auto;
42 | }
43 |
44 | body.light-mode .bitop {
45 | color: #3e3e3eb8;
46 | font-size: 65px;
47 | display: -webkit-box;
48 | display: -webkit-flex;
49 | display: -moz-box;
50 | display: -ms-flexbox;
51 | display: flex;
52 | -webkit-box-align: center;
53 | -webkit-align-items: center;
54 | -moz-box-align: center;
55 | -ms-flex-align: center;
56 | align-items: center;
57 | padding: 0 0 0 80px;
58 | }
59 |
60 | body.dark-mode .bitop {
61 | color: #d3d3d3;
62 | font-size: 65px;
63 | display: -webkit-box;
64 | display: -webkit-flex;
65 | display: -moz-box;
66 | display: -ms-flexbox;
67 | display: flex;
68 | -webkit-box-align: center;
69 | -webkit-align-items: center;
70 | -moz-box-align: center;
71 | -ms-flex-align: center;
72 | align-items: center;
73 | padding: 0 0 0 50px;
74 | }
75 |
76 | .bitop img {
77 | width: 470px;
78 | height: auto;
79 | }
80 |
81 | .cover {
82 | display: flex;
83 | flex-direction: column;
84 | align-items: center;
85 | }
86 |
87 | .imgbox {
88 | display: flex;
89 | flex-direction: column;
90 | align-items: center;
91 | overflow: hidden;
92 | box-shadow: 0px 10px 20px 0px #4343434f;
93 | border-radius: 25px;
94 | width: 90%;
95 | flex: 1;
96 | }
97 |
98 | .imgbox_1 {
99 | border-radius: 25px;
100 | object-fit: contain;
101 | width: 100%;
102 | height: 100%;
103 | }
104 |
105 | .info {
106 | width: 100% !important;
107 | display: flex !important;
108 | flex-direction: column;
109 | padding: 0 85px !important;
110 | line-height: 1.5;
111 | }
112 |
113 | body.light-mode .info_text {
114 | font-size: 50px;
115 | align-items: center;
116 | letter-spacing: 1.5px;
117 | position: relative;
118 | word-wrap: break-word;
119 | }
120 |
121 | body.dark-mode .info_text {
122 | font-size: 50px;
123 | align-items: center;
124 | letter-spacing: 1.5px;
125 | position: relative;
126 | word-wrap: break-word;
127 | color: #e9e9e9;
128 | }
129 |
130 | body.light-mode .video_info_text {
131 | font-size: 80px;
132 | font-weight: 700;
133 | align-items: center;
134 | letter-spacing: 1.5px;
135 | position: relative;
136 | word-wrap: break-word;
137 | }
138 |
139 | body.dark-mode .video_info_text {
140 | font-size: 80px;
141 | font-weight: 700;
142 | align-items: center;
143 | letter-spacing: 1.5px;
144 | position: relative;
145 | word-wrap: break-word;
146 | color: #e7e7e7;
147 | }
148 |
149 | body.light-mode .intro {
150 | color: #555555;
151 | font-size: 60px;
152 | }
153 |
154 | body.dark-mode .intro {
155 | color: #ababab;
156 | font-size: 60px;
157 | }
158 |
159 | .work_status {
160 | color: #808080;
161 | letter-spacing: normal;
162 | font-size: 45px;
163 | font-weight: 300;
164 | }
165 |
166 | .dynamic_create_time {
167 | white-space: nowrap;
168 | color: #808080;
169 | font-size: 35px;
170 | font-weight: 300;
171 | }
172 |
173 | .under {
174 | display: flex;
175 | height: 100%;
176 | flex-direction: column;
177 | }
178 |
179 | .rectangular_box {
180 | height: auto;
181 | display: -webkit-box;
182 | display: -webkit-flex;
183 | display: -moz-box;
184 | display: -ms-flexbox;
185 | display: flex;
186 | -webkit-box-pack: justify;
187 | -webkit-justify-content: space-between;
188 | -moz-box-pack: justify;
189 | -ms-flex-pack: justify;
190 | justify-content: space-between;
191 | padding: 100px 0 45px 0;
192 | -webkit-box-align: center;
193 | -webkit-align-items: center;
194 | -moz-box-align: center;
195 | -ms-flex-align: center;
196 | align-items: center;
197 | }
198 |
199 | .user {
200 | display: flex;
201 | flex-direction: column;
202 | padding-left: 65px;
203 | }
204 |
205 | .userinfo {
206 | display: flex;
207 | align-items: center;
208 | }
209 |
210 | body.light-mode .userinfo_text {
211 | display: flex;
212 | font-size: 35px;
213 | color: #2f2f2fb8;
214 | padding: 25px 0 0 20px;
215 | flex-direction: column;
216 | align-items: flex-start;
217 | width: 100%;
218 | letter-spacing: 2.5px;
219 | }
220 |
221 | body.dark-mode .userinfo_text {
222 | display: flex;
223 | font-size: 35px;
224 | color: #cfcfcf;
225 | padding: 25px 0 0 20px;
226 | flex-direction: column;
227 | align-items: flex-start;
228 | width: 100%;
229 | letter-spacing: 2.5px;
230 | }
231 |
232 | .name_and_followers {
233 | display: flex;
234 | font-size: 30px;
235 | flex-direction: column;
236 | }
237 |
238 | .name_and_followers_text {
239 | font-size: 20px;
240 | color: #2f2f2fb8;
241 | margin-left: 10px;
242 | text-align: right;
243 | margin-right: 10px;
244 | }
245 |
246 | .avatar {
247 | margin: 0px 20px 0 0;
248 | border-radius: 50%;
249 | height: auto;
250 | width: 200px;
251 | box-shadow: 0px 10px 20px 0px #4343434f;
252 | }
253 |
254 | .avatar-1 {
255 | width: 220px;
256 | height: 220px;
257 | margin-right: -160px;
258 | margin-top: 50px;
259 | transform: translateY(-13%) translateX(-105%) scale(1.6);
260 | }
261 |
262 | /* 当img元素为空时,设置元素透明度为0,图裂图标将被隐藏 */
263 | img[src=''],
264 | img:not([src]) {
265 | opacity: 0;
266 | }
267 |
268 | .username {
269 | font-weight: 700;
270 | font-size: 80px;
271 | }
272 |
273 | .qrcode_box {
274 | display: flex !important;
275 | margin: 0 75px -50px 0 !important;
276 | align-items: center !important;
277 | flex-direction: column-reverse;
278 | }
279 |
280 | body.light-mode .qrcode_text {
281 | font-size: 45px;
282 | color: #2f2f2fb8;
283 | margin-left: 10px;
284 | text-align: right;
285 | margin: 20px 0 0 0;
286 | }
287 |
288 | body.dark-mode .qrcode_text {
289 | font-size: 45px;
290 | color: #d7d7d7;
291 | margin-left: 10px;
292 | text-align: right;
293 | margin: 20px 0 0 0;
294 | }
295 |
296 | body.light-mode #qrcode {
297 | border: 7px dashed #3a3a3a;
298 | padding: 10px;
299 | border-radius: 2%;
300 | }
301 |
302 | body.dark-mode #qrcode {
303 | border: 7px dashed #C3C3C3;
304 | padding: 10px;
305 | border-radius: 2%;
306 | }
307 |
308 | #qrcode img {
309 | width: 350px;
310 | height: auto;
311 | }
312 |
313 | body.light-mode .other_text {
314 | color: #575757;
315 | font-size: 70px;
316 | text-align: right;
317 | margin: 0 80px -45px 0;
318 | z-index: -1;
319 | }
320 |
321 | body.dark-mode .other_text {
322 | color: #c1c1c1;
323 | font-size: 70px;
324 | text-align: right;
325 | margin: 0 80px -45px 0;
326 | z-index: -1;
327 | }
--------------------------------------------------------------------------------
/resources/template/bilibili/css/userlist.css:
--------------------------------------------------------------------------------
1 | body.light-mode {
2 | --background-color: #fff;
3 | --text-color: #000;
4 | --shadow-color: rgba(0, 0, 0, 0.3);
5 | }
6 |
7 |
8 | body.dark-mode {
9 | --background-color: #4d4d4d;
10 | --text-color: #ededed;
11 | --shadow-color: rgb(6 6 6);
12 | }
13 |
14 | .user-list {
15 | display: flex;
16 | list-style-type: none;
17 | padding: 0;
18 | align-items: center;
19 | flex-direction: column-reverse;
20 | }
21 |
22 | .user-item {
23 | display: flex;
24 | width: 90%;
25 | align-items: center;
26 | background-color: var(--background-color);
27 | padding: 35px 45px;
28 | border-radius: 25px;
29 | box-shadow: 0 4px 20px 0px var(--shadow-color);
30 | }
31 |
32 | .user-avatar {
33 | width: 150px;
34 | height: 150px;
35 | border-radius: 50%;
36 | margin-right: 50px;
37 | }
38 |
39 | .user-info {
40 | flex-grow: 1;
41 | display: flex;
42 | justify-content: space-between;
43 | align-items: center;
44 | }
45 |
46 | .user-details {
47 | display: flex;
48 | flex-direction: column;
49 | }
50 |
51 | .user-name {
52 | color: var(--text-color);
53 | font-size: 55px;
54 | margin-bottom: 15px;
55 | }
56 |
57 | .user-stats {
58 | display: flex;
59 | gap: 30px;
60 | color: var(--text-color);
61 | font-size: 25px;
62 | }
63 |
64 | .pending-push {
65 | color: #ffffff;
66 | padding: 10px 20px;
67 | border-radius: 10px;
68 | font-size: 40px;
69 | }
70 |
71 | .pending-push.active {
72 | background-color: #00a1d6;
73 | }
74 |
75 | .pending-push.inactive {
76 | background-color: #dddddd;
77 | }
--------------------------------------------------------------------------------
/resources/template/bilibili/html/bangumi.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | text
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
{{title}}
21 |
22 |
23 | {{each bangumiData val}}
24 |
25 |
第 {{val.id}} 集
26 |
27 |
28 | 标题: {{val.long_title}}
29 |
30 | 🔒 播放要求: {{if val.badge === '会员'}}
31 | {{val.badge}}
32 | {{else}}
33 | {{val.badge}}
34 | {{/if}}
35 |
36 | 🔗 分享链接:
37 | {{val.short_link}}
38 |
39 |
40 | {{/each}}
41 |
42 |
43 |
44 | 输入
45 | 第?集
46 | 进行选集
47 |
48 | 温馨提示:
49 | 你有 60 秒的时间进行选择
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/resources/template/bilibili/html/dynamic/DYNAMIC_TYPE_AV.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
8 |

9 |
10 |
你感兴趣的视频都在哔哩哔哩
11 |
12 |
13 | {{each image_url val}}
14 |
15 |
16 |

17 |
18 |
19 |
20 | {{/each}}
21 |
22 |
{{@text}}
23 |
24 |
{{@desc}}
25 |
26 |
{{dianzan}}点赞 · {{pinglun}}评论 · {{share}}分享 · {{coin}}硬币 · {{view}}浏览
27 |
视频时长: {{duration_text}}
28 |
29 |
发布于{{create_time}}
30 |
31 |
32 | 哔哩哔哩{{dynamicTYPE}}
33 |
34 |
35 |
36 |
37 |
38 |

39 |
42 |
43 |
44 | UID: {{user_shortid}}
45 | 获赞: {{total_favorited}}
46 | 关注: {{following_count}}
47 | 粉丝: {{fans}}
48 |
49 |
50 |
54 |
55 |
56 |
65 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/bilibili/html/dynamic/DYNAMIC_TYPE_DRAW.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
8 |

9 |

10 |
11 |
{{@username}}
12 |
{{create_time}}
13 |
14 |
{{@decoration_card}}
15 |
16 |
17 |
21 | {{each image_url val}}
22 |
23 |
24 |

25 |
26 |
27 |
28 | {{/each}}
29 |
30 |
{{dianzan}}点赞 · {{pinglun}}评论 · {{share}}分享
31 |
图片生成时间: {{render_time}}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |

40 |
41 |
长按识别二维码即可查看全文
42 |
43 |
44 | UID: {{user_shortid}}
45 | 获赞: {{total_favorited}}
46 | 关注: {{following_count}}
47 | 粉丝: {{fans}}
48 |
49 |
50 |
51 |
{{dynamicTYPE}}
52 |
53 |
54 |
55 |
56 |
65 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/bilibili/html/dynamic/DYNAMIC_TYPE_FORWARD.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
8 |

9 |

10 |
11 |
{{@username}}
12 |
{{create_time}}
13 |
14 |
{{@decoration_card}}
15 |
16 |
17 |
21 |
22 | {{if original_content.DYNAMIC_TYPE_AV}}
23 |
24 |
25 |
26 |
27 |

28 |

29 |
30 |
{{@original_content.DYNAMIC_TYPE_AV.username}}
31 |
{{original_content.DYNAMIC_TYPE_AV.create_time}}
32 |
33 |
34 |
35 | {{@original_content.DYNAMIC_TYPE_AV.decoration_card}}
36 |
37 |
38 |
39 |
40 |
41 |

42 |
43 |
{{original_content.DYNAMIC_TYPE_AV.duration_text}}
45 | {{original_content.DYNAMIC_TYPE_AV.play}}观看
46 | {{original_content.DYNAMIC_TYPE_AV.danmaku}}弹幕
47 |
48 |
49 |
{{@original_content.DYNAMIC_TYPE_AV.title}}
50 |
51 |
52 | {{else if original_content.DYNAMIC_TYPE_DRAW}}
53 |
54 |
55 |
56 |
57 |

58 |

59 |
60 |
{{@original_content.DYNAMIC_TYPE_DRAW.username}}
61 |
{{original_content.DYNAMIC_TYPE_DRAW.create_time}}
62 |
63 |
64 |
65 | {{@original_content.DYNAMIC_TYPE_DRAW.decoration_card}}
66 |
67 |
68 |
69 |
70 |
{{@original_content.DYNAMIC_TYPE_DRAW.text}}
71 | {{if original_content.DYNAMIC_TYPE_DRAW.image_url.length === 1}}
72 |
73 |
74 |
75 |

76 |
77 |
78 |
79 | {{else}}
80 |
81 | {{each original_content.DYNAMIC_TYPE_DRAW.image_url}}
82 |
83 |

84 |
85 | {{/each}}
86 |
87 | {{/if}}
88 |
89 |
90 | {{else if original_content.DYNAMIC_TYPE_WORD}}
91 |
92 |
93 |
94 |
95 |

96 |

97 |
98 |
{{@original_content.DYNAMIC_TYPE_WORD.username}}
99 |
{{original_content.DYNAMIC_TYPE_WORD.create_time}}
100 |
101 |
102 |
103 | {{@original_content.DYNAMIC_TYPE_WORD.decoration_card}}
104 |
105 |
106 |
107 |
108 |
{{@original_content.DYNAMIC_TYPE_WORD.text}}
109 |
110 | {{else if original_content.DYNAMIC_TYPE_LIVE_RCMD}}
111 |
112 |
113 |
114 |
115 |

116 |

117 |
118 |
{{@original_content.DYNAMIC_TYPE_LIVE_RCMD.username}}
119 |
{{original_content.DYNAMIC_TYPE_LIVE_RCMD.create_time}}
120 |
121 |
122 |
123 | {{@original_content.DYNAMIC_TYPE_LIVE_RCMD.decoration_card}}
124 |
125 |
126 |
127 |
128 |
129 |

130 |
131 |
{{original_content.DYNAMIC_TYPE_LIVE_RCMD.area_name}}
133 | {{original_content.DYNAMIC_TYPE_LIVE_RCMD.text_large}}
134 | 在线: {{original_content.DYNAMIC_TYPE_LIVE_RCMD.online}}
135 |
136 |
137 |
{{@original_content.DYNAMIC_TYPE_LIVE_RCMD.title}}
138 |
139 | {{/if}}
140 |
141 |
142 |
143 |
{{dianzan}}点赞 · {{pinglun}}评论 · {{share}}分享
144 |
图片生成时间: {{render_time}}
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |

153 |
154 |
长按识别二维码即可查看全文
155 |
156 |
157 | UID: {{user_shortid}}
158 | 获赞: {{total_favorited}}
159 | 关注: {{following_count}}
160 | 粉丝: {{fans}}
161 |
162 |
163 |
164 |
{{dynamicTYPE}}
165 |
166 |
167 |
168 |
169 |
178 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/bilibili/html/dynamic/DYNAMIC_TYPE_LIVE_RCMD.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 | {{each image_url val}}
8 |
9 |
10 |

11 |
12 |
13 |
14 | {{/each}}
15 |
16 |
17 |
{{@text}}
18 |
19 |
{{liveinf}}
20 |
直播开始时间: {{create_time}}
21 |
22 |
23 |

24 |

25 |
26 |
27 |
{{@username}}
28 |
29 |

30 |
31 |
32 | {{fans}}粉丝
33 |
34 |
35 |
36 |
37 |
38 |
哔哩哔哩{{dynamicTYPE}}
39 |
40 |
41 |
42 |
43 |

44 |
45 |
46 | 你感兴趣的视频都在B站
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
66 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/bilibili/html/dynamic/DYNAMIC_TYPE_WORD.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
8 |

9 |
10 |
你感兴趣的视频都在哔哩哔哩
11 |
12 |
13 | {{each image_url val}}
14 |
15 |
16 |

17 |
18 |
19 |
20 | {{/each}}
21 |
22 |
{{@text}}
23 |
24 |
{{dianzan}}点赞 · {{pinglun}}评论 · {{share}}分享
25 |
26 |
发布于{{create_time}}
27 |
28 |
29 | 哔哩哔哩{{dynamicTYPE}}
30 |
31 |
32 |
33 |
34 |
35 |

36 |
39 |
40 |
41 | UID: {{user_shortid}}
42 | 获赞: {{total_favorited}}
43 | 关注: {{following_count}}
44 | 粉丝: {{fans}}
45 |
46 |
47 |
51 |
52 |
53 |
62 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/bilibili/html/userlist.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 | {{/block}} {{block 'main'}}
4 |
5 |
6 | {{each renderOpt val}}
7 | -
8 |
9 |
10 |
11 |
{{val.username}}
12 |
13 | UID: {{val.host_mid}}
14 | 粉丝: {{val.fans}}
15 | 获赞: {{val.total_favorited}}
16 | 关注: {{val.following_count}}
17 |
18 |
19 |
20 |
21 |
22 | {{/each}}
23 |
24 |
25 | {{/block}}
26 |
--------------------------------------------------------------------------------
/resources/template/douyin/css/live.css:
--------------------------------------------------------------------------------
1 | body.light-mode .container {
2 | transform: scale(1.4);
3 | transform-origin: 0 0;
4 | position: relative;
5 | background-color: #f4f4f4;
6 | width: fit-content;
7 | height: auto;
8 | }
9 |
10 | body.dark-mode .container {
11 | transform: scale(1.4);
12 | transform-origin: 0 0;
13 | position: relative;
14 | background-color: #111111;
15 | width: fit-content;
16 | height: auto;
17 | }
18 |
19 | .spacer1 {
20 | height: 20px;
21 | }
22 |
23 | .spacer2 {
24 | height: 70px;
25 | }
26 |
27 | .spacer3 {
28 | height: 10px;
29 | }
30 |
31 | .spacer4 {
32 | height: 20px;
33 | }
34 |
35 | .spacer5 {
36 | height: 25px;
37 | }
38 |
39 | .spacer6 {
40 | height: 80px;
41 | }
42 |
43 | .spacer7 {
44 | height: 120px;
45 | }
46 |
47 | .container {
48 | width: 100% !important;
49 | }
50 |
51 | body.dark-mode .logo {
52 | width: 130%;
53 | height: 245px;
54 | margin: 0 0 52px 0;
55 | background-size: cover;
56 | background-position: center;
57 | background-attachment: fixed;
58 | background-image: url(../../../image/douyin/dylogo-light.svg);
59 | }
60 |
61 | body.light-mode .logo {
62 | width: 107%;
63 | height: 200px;
64 | margin: 0 0 52px 0;
65 | background-size: cover;
66 | background-position: center;
67 | background-attachment: fixed;
68 | background-image: url(../../../image/douyin/dylogo-dark.svg);
69 | }
70 |
71 |
72 | .bitop svg {
73 | width: 250px;
74 | height: auto;
75 | }
76 |
77 | .cover {
78 | display: flex;
79 | flex-direction: column;
80 | align-items: center;
81 | }
82 |
83 | .imgbox {
84 | display: flex;
85 | flex-direction: column;
86 | align-items: center;
87 | overflow: hidden;
88 | box-shadow: 0px 10px 20px 0px #4343434f;
89 | border-radius: 25px;
90 | width: 95%;
91 | flex: 1;
92 | margin: 90px 0 0 0;
93 | }
94 |
95 | .imgbox_1 {
96 | border-radius: 25px;
97 | object-fit: contain;
98 | width: 100%;
99 | height: 100%;
100 | }
101 |
102 | .info {
103 | display: flex !important;
104 | flex-direction: column;
105 | padding: 0 80px;
106 | }
107 |
108 | body.light-mode .info_title {
109 | font-size: 65px;
110 | align-items: center;
111 | letter-spacing: 1.5px;
112 | position: relative;
113 | word-wrap: break-word;
114 | }
115 |
116 | body.dark-mode .info_title {
117 | font-size: 65px;
118 | align-items: center;
119 | letter-spacing: 1.5px;
120 | position: relative;
121 | word-wrap: break-word;
122 | color: #e7e7e7e7;
123 | }
124 |
125 | .info_text {
126 | font-size: 45px;
127 | align-items: center;
128 | letter-spacing: 1.5px;
129 | position: relative;
130 | word-wrap: break-word;
131 | }
132 |
133 | .under {
134 | display: flex;
135 | width: auto;
136 | height: 100%;
137 | display: -webkit-box;
138 | display: -webkit-flex;
139 | display: -moz-box;
140 | display: -ms-flexbox;
141 | display: flex;
142 | -webkit-box-orient: vertical;
143 | -webkit-box-direction: normal;
144 | -webkit-flex-direction: column;
145 | -moz-box-orient: vertical;
146 | -moz-box-direction: normal;
147 | -ms-flex-direction: column;
148 | flex-direction: column;
149 | }
150 |
151 | body.light-mode .other_text {
152 | width: inherit;
153 | color: #3e3e3e;
154 | font-size: 70px;
155 | text-align: right;
156 | margin: 0 20px -45px 0;
157 | z-index: -1;
158 | }
159 |
160 | body.dark-mode .other_text {
161 | width: inherit;
162 | color: #dddddd;
163 | font-size: 70px;
164 | text-align: right;
165 | margin: 0 20px -45px 0;
166 | z-index: -1;
167 | }
168 |
169 | .rectangular_box {
170 | height: auto;
171 | display: -webkit-box;
172 | display: -webkit-flex;
173 | display: -moz-box;
174 | display: -ms-flexbox;
175 | display: flex;
176 | -webkit-box-pack: justify;
177 | -webkit-justify-content: space-between;
178 | -moz-box-pack: justify;
179 | -ms-flex-pack: justify;
180 | justify-content: space-between;
181 | padding: 60px 0 0 0;
182 | -webkit-box-align: center;
183 | -webkit-align-items: center;
184 | -moz-box-align: center;
185 | -ms-flex-align: center;
186 | align-items: center;
187 | }
188 |
189 | .user {
190 | display: flex;
191 | flex-direction: column;
192 | margin: 0 0 0 45px;
193 | }
194 |
195 | .userinfo {
196 | display: flex;
197 | justify-content: flex-start;
198 | align-items: flex-start;
199 | flex-direction: column;
200 | }
201 |
202 | .userinfo_text {
203 | display: flex;
204 | font-size: 2em;
205 | color: #2f2f2f;
206 | padding: 25px 0 0 150px;
207 | flex-direction: column;
208 | align-items: flex-start;
209 | width: 100%;
210 | letter-spacing: 2.5px;
211 | }
212 |
213 | .name_and_followers {
214 | display: flex;
215 | flex-direction: row;
216 | align-items: center;
217 | margin-bottom: 5px;
218 | }
219 |
220 | .livestat {
221 | width: 170px;
222 | height: auto;
223 | }
224 |
225 | .avatar {
226 | margin: 0 15px 0 0;
227 | border-radius: 50%;
228 | height: auto;
229 | width: 130px;
230 | }
231 |
232 | .avatarinfo {
233 | display: flex;
234 | align-items: center;
235 | gap: 40px;
236 | }
237 |
238 | body.light-mode .extext {
239 | letter-spacing: 10px;
240 | color: #212121;
241 | }
242 |
243 | body.dark-mode .extext {
244 | letter-spacing: 10px;
245 | color: #dddddd;
246 | }
247 |
248 | body.light-mode .username {
249 | font-size: 60px;
250 | }
251 |
252 | body.dark-mode .username {
253 | font-size: 60px;
254 | color: #dddddd;
255 | }
256 |
257 | .qrcode_box {
258 | display: -webkit-box !important;
259 | display: -webkit-flex !important;
260 | display: -moz-box !important;
261 | display: -ms-flexbox !important;
262 | display: flex !important;
263 | margin: 30px 20px 0 0 !important;
264 | -webkit-box-align: center !important;
265 | -webkit-align-items: center !important;
266 | -moz-box-align: center !important;
267 | -ms-flex-align: center !important;
268 | align-items: center !important;
269 | -webkit-box-orient: vertical;
270 | -webkit-box-direction: reverse;
271 | -webkit-flex-direction: column-reverse;
272 | -moz-box-orient: vertical;
273 | -moz-box-direction: reverse;
274 | -ms-flex-direction: column-reverse;
275 | flex-direction: column-reverse;
276 | }
277 |
278 | body.light-mode .qrcode_text {
279 | font-size: 50px;
280 | color: #2f2f2ff9;
281 | margin-left: 10px;
282 | text-align: right;
283 | margin-right: 10px;
284 | }
285 |
286 | body.dark-mode .qrcode_text {
287 | font-size: 50px;
288 | color: #dbdbdb;
289 | margin-left: 10px;
290 | text-align: right;
291 | margin-right: 10px;
292 | }
293 |
294 | body.light-mode #qrcode {
295 | border: 7px dashed #3a3a3a;
296 | padding: 10px;
297 | border-radius: 2%;
298 | }
299 |
300 | body.dark-mode #qrcode {
301 | border: 7px dashed #C3C3C3;
302 | padding: 10px;
303 | border-radius: 2%;
304 | }
305 |
306 | #qrcode img {
307 | width: 350px;
308 | /* height: 150px; */
309 | }
310 |
311 | .ftext {
312 | color: #808080;
313 | font-size: 50px;
314 | }
--------------------------------------------------------------------------------
/resources/template/douyin/css/userlist.css:
--------------------------------------------------------------------------------
1 | body.light-mode {
2 | --background-color: #fff;
3 | --text-color: #000;
4 | --shadow-color: rgba(0, 0, 0, 0.3);
5 | }
6 |
7 |
8 | body.dark-mode {
9 | --background-color: #4d4d4d;
10 | --text-color: #ededed;
11 | --shadow-color: rgb(6 6 6);
12 | }
13 |
14 | .user-list {
15 | display: flex;
16 | list-style-type: none;
17 | padding: 0;
18 | align-items: center;
19 | flex-direction: column-reverse;
20 | }
21 |
22 | .user-item {
23 | display: flex;
24 | width: 90%;
25 | align-items: center;
26 | background-color: var(--background-color);
27 | padding: 35px 45px;
28 | border-radius: 25px;
29 | box-shadow: 0 4px 20px 0px var(--shadow-color);
30 | }
31 |
32 | .user-avatar {
33 | width: 150px;
34 | height: 150px;
35 | border-radius: 50%;
36 | margin-right: 50px;
37 | }
38 |
39 | .user-info {
40 | flex-grow: 1;
41 | display: flex;
42 | justify-content: space-between;
43 | align-items: center;
44 | }
45 |
46 | .user-details {
47 | display: flex;
48 | flex-direction: column;
49 | }
50 |
51 | .user-name {
52 | color: var(--text-color);
53 | font-size: 55px;
54 | margin-bottom: 15px;
55 | }
56 |
57 | .user-stats {
58 | display: flex;
59 | gap: 30px;
60 | color: var(--text-color);
61 | font-size: 25px;
62 | }
63 |
64 | .pending-push {
65 | color: #ffffff;
66 | padding: 10px 20px;
67 | border-radius: 10px;
68 | font-size: 40px;
69 | }
70 |
71 | .pending-push.active {
72 | background-color: #00a1d6;
73 | }
74 |
75 | .pending-push.inactive {
76 | background-color: #dddddd;
77 | }
--------------------------------------------------------------------------------
/resources/template/douyin/html/comment.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
8 |
9 |
10 |
作品类型:{{Type}}
11 |
评论数量:{{CommentLength}}条
12 | {{if Type == '视频'}}
13 |
视频大小:{{VideoSize}}MB
14 |
视频帧率:{{VideoFPS}}Hz
15 | {{else if Type == '图集' || Type == '合辑'}}
16 |
图片数量:{{ImageLength}}张
17 | {{/if}}
18 |
19 |
20 |
21 |
22 | {{if Type == '视频'}}
23 |
视频直链(永久)
24 | {{else if Type == '图集'}}
25 |
图集分享链接 共{{ImageLength}}张
26 | {{/if}}
27 |
28 |
29 |
30 |
81 |
90 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/douyin/html/dynamic.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
11 |
12 |
13 |
14 |

15 |
16 |
17 |
18 |
19 |
20 | {{@desc}}
21 |
22 |
23 |
{{dianzan}}点赞 · {{pinglun}}评论 · {{shouchang}}收藏 · {{share}}分享
24 |
25 |
发布于{{create_time}}
26 |
27 |
28 | 抖音作品推送
29 |
30 |
31 |
32 |
33 |
34 |
35 | {{username}}
36 |
37 |
38 |
39 | 抖音号: {{抖音号}}
40 | 获赞: {{获赞}}
41 | 关注: {{关注}}
42 | 粉丝: {{粉丝}}
43 |
44 |
45 |
49 |
50 |
51 |
60 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/douyin/html/live.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 | {{each image_url val}}
8 |
9 |
10 |

11 |
12 |
13 |
14 | {{/each}}
15 |
16 |
17 |
{{@text}}
18 |
19 |
{{liveinf}}
20 |
观看总人数{{总观看次数}} | 在线观众{{在线观众}}
21 |
22 |
23 |

24 |
25 |
26 |
{{@username}}
27 |
28 |

29 |
30 |
31 | {{fans}}粉丝
32 |
33 |
34 |
35 |
36 |
37 |
抖音{{dynamicTYPE}}
38 |
39 |
40 |
41 |
42 |
43 | 抖音 记录美好生活
44 |
45 |
46 |
47 |
51 |
52 |
53 |
54 |
63 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/douyin/html/musicinfo.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
11 |
12 |
13 |
14 |

15 |
16 |
17 |
18 |
19 |
20 | {{@desc}}
21 |
22 |
23 |
音乐ID: {{music_id}}
24 |
{{user_count}} 人使用过
25 |
26 |
图片生成于{{create_time}}
27 |
28 |
29 | 抖音音乐信息
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
{{username}}
38 |
39 |
40 |
41 | ID: {{user_shortid}}
42 | 获赞: {{total_favorited}}
43 | 关注: {{following_count}}
44 | 粉丝: {{fans}}
45 |
46 |
47 |
51 |
52 |
53 |
62 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/douyin/html/userlist.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 | {{/block}} {{block 'main'}}
4 |
5 |
6 | {{each renderOpt val index}}
7 | -
8 |
9 |
10 |
11 |
{{val.username}}
12 |
13 | 抖音号: {{val.short_id}}
14 | 粉丝: {{val.fans}}
15 | 获赞: {{val.total_favorited}}
16 | 关注: {{val.following_count}}
17 |
18 |
19 |
20 |
21 |
22 | {{/each}}
23 |
24 |
25 | {{/block}}
26 |
--------------------------------------------------------------------------------
/resources/template/extend/html/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
21 | kkkkkk-10086
22 | {{block 'css'}} {{/block}}
23 |
24 |
25 |
26 |
27 | {{block 'main'}}{{/block}}
28 |
{{@copyright}}
29 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/resources/template/help/css/index.css:
--------------------------------------------------------------------------------
1 | .menu-container {
2 | display: flex;
3 | justify-content: space-around;
4 | padding: 20px;
5 | }
6 |
7 | body.light-mode .menu-column {
8 | flex-basis: 88%;
9 | background-color: #f4f4f4;
10 | padding: 45px;
11 | border-radius: 70px 15px 70px 15px;
12 | box-shadow: 0 2px 20px 3px rgb(0 0 0 / 21%);
13 | }
14 |
15 | body.dark-mode .menu-column {
16 | flex-basis: 88%;
17 | background-color: #303030;
18 | padding: 45px;
19 | border-radius: 70px 15px 70px 15px;
20 | box-shadow: 0 2px 20px 3px rgb(0 0 0 / 21%);
21 | }
22 |
23 | body.light-mode .menu-column-second {
24 | background-color: #f4f4f4;
25 | padding: 60px;
26 | border-radius: 70px 15px 70px 15px;
27 | box-shadow: 0 2px 20px 3px rgb(0 0 0 / 21%);
28 | margin: 0 0 45px 0;
29 | }
30 |
31 | body.dark-mode .menu-column-second {
32 | background-color: #393939;
33 | padding: 60px;
34 | border-radius: 70px 15px 70px 15px;
35 | box-shadow: 0 2px 20px 3px rgb(0 0 0 / 21%);
36 | margin: 0 0 45px 0;
37 | }
38 |
39 | body.light-mode .menu-column h2 {
40 | color: #333;
41 | font-size: 50px;
42 | margin: 0 0 15px 0;
43 | }
44 |
45 | body.dark-mode .menu-column h2 {
46 | color: cornsilk;
47 | font-size: 50px;
48 | margin: 0 0 15px 0;
49 | }
50 |
51 | body.dark-mode .menu-column h3 {
52 | font-size: xx-large;
53 | color: cornsilk;
54 | margin: 0 0 25px 0;
55 | }
56 |
57 | body.light-mode .menu-column h3 {
58 | font-size: xx-large;
59 | color: #333;
60 | margin: 0 0 25px 0;
61 | }
62 |
63 | body.light-mode .menu-item {
64 | display: flex;
65 | font-size: 34px;
66 | padding: 20px;
67 | background-color: #e9e9e9;
68 | border-radius: 26px;
69 | cursor: pointer;
70 | justify-content: center;
71 | font-weight: 700;
72 | }
73 |
74 | body.dark-mode .menu-item {
75 | color: cornsilk;
76 | display: flex;
77 | font-size: 34px;
78 | padding: 20px;
79 | background-color: #1e1e1e;
80 | border-radius: 26px;
81 | cursor: pointer;
82 | justify-content: center;
83 | font-weight: 700;
84 | }
85 |
86 | .menu-item:hover {
87 | background-color: #d6d6d6;
88 | }
89 |
90 | body.light-mode .menu-description {
91 | display: flex;
92 | font-size: 27px;
93 | color: #666;
94 | margin: 10px 0 20px 15px;
95 | }
96 |
97 | body.dark-mode .menu-description {
98 | display: flex;
99 | font-size: 27px;
100 | color: #b4b4b4;
101 | margin: 10px 0 20px 15px;
102 | }
103 |
104 | .spacer {
105 | height: 100px;
106 | }
107 |
--------------------------------------------------------------------------------
/resources/template/help/html/index.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 | {{/block}} {{block 'main'}}
4 |
5 |
12 |
30 |
41 | {{/block}}
42 |
--------------------------------------------------------------------------------
/resources/template/kuaishou/html/comment.html:
--------------------------------------------------------------------------------
1 | {{extend defaultLayout}} {{block 'css'}}
2 |
3 |
4 | {{/block}} {{block 'main'}}
5 |
6 |
7 |
8 |

9 |
评论数量:{{CommentLength}}条
10 | {{if Type == '视频'}}
11 |
视频大小:{{VideoSize}}MB
12 |
点赞数量:{{likeCount}}
13 |
观看次数:{{viewCount}}
14 | {{else if Type == '图集'}}
15 |
图片数量:{{ImageLength}}张
16 | {{/if}}
17 |
18 |
19 |
20 | {{if Type == '视频'}}
21 |
视频直链(永久)
22 | {{else if Type == '图集'}}
23 |
图集分享链接 共{{ImageLength}}张
24 | {{/if}}
25 |
26 |
27 |
28 |
75 |
84 | {{/block}}
--------------------------------------------------------------------------------
/resources/template/videoView/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{filename}}
7 |
177 |
178 |
179 |
180 |
184 |
185 |
186 |
187 |
188 |
193 |
194 | 页面有效期剩余
195 |
196 | 10:00
197 |
198 |
199 |
200 |
201 |
202 |
240 |
241 |
242 |
--------------------------------------------------------------------------------