├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ ├── issue_close.yml │ ├── issue_geetings.yml │ ├── issue_similarity.yml │ ├── issue_welcome.yml │ └── release-please.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── apps ├── admin.js ├── help.js ├── tools.js └── update.js ├── config └── default_config │ ├── app.yaml │ ├── bilibili.yaml │ ├── cookies.yaml │ ├── douyin.yaml │ ├── kuaishou.yaml │ └── pushlist.yaml ├── eslint.config.js ├── guoba.support.js ├── index.js ├── module ├── db │ ├── base.js │ └── index.js ├── platform │ ├── bilibili │ │ ├── bilibili.js │ │ ├── comments.js │ │ ├── cookie.js │ │ ├── getdata.js │ │ ├── getid.js │ │ ├── index.js │ │ ├── login.js │ │ └── push.js │ ├── douyin │ │ ├── comments.js │ │ ├── douyin.js │ │ ├── emoji.js │ │ ├── getdata.js │ │ ├── getid.js │ │ ├── index.js │ │ ├── login.js │ │ └── push.js │ └── kuaishou │ │ ├── API.js │ │ ├── comments.js │ │ ├── getdata.js │ │ ├── getid.js │ │ ├── index.js │ │ └── kuaishou.js └── utils │ ├── Base.js │ ├── Common.js │ ├── Config.js │ ├── FFmpeg.js │ ├── Networks.js │ ├── Pushlist.js │ ├── Render.js │ ├── UploadRecord.js │ ├── Version.js │ ├── YamlReader.js │ └── index.js ├── package.json └── resources ├── font ├── HarmonyOS_Sans_SC_Regular.woff2 └── bilifont │ ├── bilifont.1.woff2 │ ├── bilifont.2.woff2 │ └── font.css ├── image ├── admin │ ├── bg-sr.webp │ └── default.jpg ├── bilibili │ ├── bilibili-dark.png │ ├── bilibili-light.png │ ├── bilibili.png │ ├── id-dark.svg │ ├── id-light.svg │ ├── like-dark.svg │ ├── like-light.svg │ └── 直播中.png ├── douyin │ ├── douyin.png │ ├── dylogo-dark.svg │ ├── dylogo-light.svg │ ├── search-dark.svg │ ├── search-light.svg │ └── 抖音-直播中.png ├── kuaishou │ └── logo.png └── pic1.png └── template ├── admin ├── css │ └── index.css └── html │ └── index.html ├── bilibili ├── css │ ├── bangumi.css │ ├── comment.css │ ├── dynamic │ │ ├── DYNAMIC_TYPE_DRAW.css │ │ ├── DYNAMIC_TYPE_FORWARD.css │ │ ├── DYNAMIC_TYPE_LIVE_RCMD.css │ │ └── base.css │ └── userlist.css └── html │ ├── bangumi.html │ ├── comment.html │ ├── dynamic │ ├── DYNAMIC_TYPE_AV.html │ ├── DYNAMIC_TYPE_DRAW.html │ ├── DYNAMIC_TYPE_FORWARD.html │ ├── DYNAMIC_TYPE_LIVE_RCMD.html │ └── DYNAMIC_TYPE_WORD.html │ └── userlist.html ├── douyin ├── css │ ├── base.css │ ├── commnet.css │ ├── live.css │ └── userlist.css └── html │ ├── comment.html │ ├── dynamic.html │ ├── live.html │ ├── musicinfo.html │ └── userlist.html ├── extend ├── css │ └── common.css ├── html │ └── default.html └── js │ └── qrcode.min.js ├── help ├── css │ └── index.css └── html │ └── index.html ├── kuaishou ├── css │ └── commnet.css └── html │ └── comment.html └── videoView └── index.html /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 错误报告 2 | description: 在使用 kkkkkk-10086 的过程中遇到了错误 3 | title: '[Bug?]: ' 4 | labels: [ "bug" ] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: 感谢您花时间填写此错误报告,请**务必确认您的issue不是重复的**,填写前请先阅读 [**我应该提供哪些信息?**](https://ikenxuan.github.io/kkkkkk-10086/docs/intro/problems#%E6%88%91%E5%BA%94%E8%AF%A5%E6%8F%90%E4%BE%9B%E5%93%AA%E4%BA%9B%E4%BF%A1%E6%81%AF) 10 | 11 | - type: checkboxes 12 | attributes: 13 | label: 请确保以下事项 14 | description: 您必须勾选以下所有内容,否则您的issue可能会被直接关闭。或者您可以去[讨论区](https://github.com/ikenxuan/kkkkkk-10086/discussions) 15 | options: 16 | - label: 我已经阅读了[文档](https://ikenxuan.github.io/kkkkkk-10086/)。 17 | - label: 我确定没有重复的issue或讨论。 18 | - label: 我确定是`kkkkkk-10086`的问题,而不是其他原因(例如网络,`依赖`或`操作`)。 19 | - label: 我确定这个问题在最新版本中没有被修复。 20 | 21 | - type: input 22 | id: version 23 | attributes: 24 | label: 版本 25 | description: 您使用的是哪个版本/commit id的源码?请不要使用`latest`或`master`作为答案。 26 | placeholder: v0.xx.xx 或者 commit id 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: bug-description 31 | attributes: 32 | label: 问题描述 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: config 37 | attributes: 38 | label: 配置 39 | description: 请提供您的`kkkkkk-10086`应用的配置文件(隐藏隐私字段) 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: Operation-procedure 44 | attributes: 45 | label: 操作步骤 46 | description: 大意为聊天记录截图 47 | validations: 48 | required: true 49 | - type: textarea 50 | id: logs 51 | attributes: 52 | label: 日志 53 | description: 请复制粘贴错误日志,或者截图。务必完整 54 | validations: 55 | required: true 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 新功能提议 2 | description: 希望拥有新的功能 3 | title: '[Feature Request]: ' 4 | labels: ["enhancement"] 5 | 6 | body: 7 | - type: checkboxes 8 | attributes: 9 | label: 请确保以下事项 10 | description: 您可以选择多个,甚至全部选择。 11 | options: 12 | - label: 我已经阅读了[文档](https://ikenxuan.github.io/kkkkkk-10086/)。 13 | - label: 我确定没有重复的issue或讨论。 14 | - label: 我确定这个功能没有实现。 15 | - label: 我相信这是一个合理和普遍的要求。 16 | - type: textarea 17 | id: feature-description 18 | attributes: 19 | label: 需求描述 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: suggested-solution 24 | attributes: 25 | label: 实现思路 26 | description: 实现此需求的解决思路。 27 | - type: textarea 28 | id: additional-context 29 | attributes: 30 | label: 附件 31 | description: 相关的任何其他上下文或截图,或者你觉得有帮助的信息 -------------------------------------------------------------------------------- /.github/workflows/issue_close.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 60 16 | days-before-issue-close: 30 17 | stale-issue-label: "stale" 18 | stale-issue-message: "📅 你好 @${{ github.event.issue.user.login }},这个问题已经过期了,因为它已经开放了30天,没有任何活动。" 19 | close-issue-message: "🚫 你好 @${{ github.event.issue.user.login }},此问题已关闭,因为它已被标记为过期后14天处于非活动状态。。" 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/issue_geetings.yml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: [labeled] 4 | 5 | jobs: 6 | create-comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Create comment for enhancement 10 | if: github.event.label.name == 'enhancement' 11 | uses: actions-cool/issues-helper@v3 12 | with: 13 | actions: 'create-comment' 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | body: | 16 | 你好 @${{ github.event.issue.user.login }},我们已经记录了你的新功能提议。如果你有任何具体的实现想法或设计草图,欢迎随时分享给我们。 17 | emoji: 'eyes' 18 | 19 | - name: Create comment for bug 20 | if: github.event.label.name == 'bug' 21 | uses: actions-cool/issues-helper@v3 22 | with: 23 | actions: 'create-comment' 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | body: | 26 | 你好 @${{ github.event.issue.user.login }},看来我们的代码不小心打了个盹儿。别担心,我们已经唤醒了开发团队,他们正快马加鞭地赶来修复!🔨🐞 27 | emoji: 'eyes' -------------------------------------------------------------------------------- /.github/workflows/issue_similarity.yml: -------------------------------------------------------------------------------- 1 | # 问题相似性分析 2 | name: Issues Similarity Analysis 3 | 4 | on: 5 | issues: 6 | types: [opened, edited] 7 | 8 | jobs: 9 | similarity-analysis: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: analysis 13 | uses: actions-cool/issues-similarity-analysis@v1 14 | with: 15 | filter-threshold: 0.5 16 | comment-title: '### 似乎有相似的问题' 17 | comment-body: '${index}. ${similarity} #${number}' 18 | show-footer: false 19 | show-mentioned: true 20 | since-days: 730 -------------------------------------------------------------------------------- /.github/workflows/issue_welcome.yml: -------------------------------------------------------------------------------- 1 | name: Issue Welcome 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | issue-welcome: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: welcome 12 | uses: actions-cool/issues-helper@v3 13 | with: 14 | actions: 'welcome' 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | body: 你好 @${{ github.event.issue.user.login }},我们注意到你是一次创建问题,感谢你的加入!我们非常期待你的想法和贡献。🌟 17 | issue-emoji: 'eyes' -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | # 触发条件:当代码被推送到仓库时 2 | on: 3 | push: 4 | # 触发的分支:仅当推送到 master 分支时触发 5 | branches: 6 | - master 7 | # 涉及以下路径文件的更改不会触发工作流 8 | paths-ignore: 9 | - LICENSE 10 | - README.md 11 | 12 | # 工作流名称 13 | name: release-please 14 | 15 | # 定义工作流中的各个任务 16 | jobs: 17 | # 任务名称:release-please 18 | release-please: 19 | # 运行环境:Ubuntu 最新版本 20 | runs-on: ubuntu-latest 21 | 22 | # 定义任务中的各个步骤 23 | steps: 24 | # 步骤:使用 Google 的 release-please-action 动作 25 | - name: Create Release 26 | uses: google-github-actions/release-please-action@v3 27 | # 为这个步骤指定一个标识符,方便后续引用 28 | id: release-please 29 | with: 30 | # 指定发布类型为 Node.js 项目 31 | release-type: node 32 | # 指定要发布的包名 33 | package-name: 'kkkkkk-10086' 34 | # 指定默认分支名称 35 | default-branch: master 36 | 37 | - name: Checkout master branch 38 | uses: actions/checkout@v4 39 | with: 40 | ref: master 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vitepress/* 2 | node_modules/* 3 | config/config/* 4 | data/* 5 | yarn.lock/* 6 | package-lock.json 7 | !.gitignore 8 | jsconfig.json 9 | lib/* 10 | resources/template/version/html/index.html 11 | resources/html/douyin/user_worklist.html 12 | amagi/node_modules/* 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 已跑路!已跑路!已跑路! 2 | 3 | > [!IMPORTANT] 4 | > 5 | > ### 该插件仅为小范围使用,暂无上架插件库意图
6 | > 7 | > 主开发已跑路到 [Karin](https://github.com/KarinJS/Karin) 了,使用开发效率更高和可维护性更健壮的强类型语言 TypeScript 重写插件逻(JavaScript 没类型和注释我写不下去了)。新仓库:https://github.com/ikenxuan/karin-plugin-kkk
8 | > 云崽版(该仓库)可能将由社区开发者维护。 9 | 10 | Q: 什么时候回归? 11 | A: 等云崽有完整的类型声明、代码注释和文档吧。(Yunzai Next?难评。) 12 | 13 | 🦄 _**kkkkkk-10086 是一个 [Miao-Yunzai](https://github.com/yoimiya-kokomi/Miao-Yunzai) & [TRSS-Yunzai](https://github.com/TimeRainStarSky/Yunzai) 的自用辅助插件,提供对 Bot 的视频解析功能,更多信息请移步[文档](https://ikenxuan.github.io/kkkkkk-10086)**_ 14 | 15 | ![Nodejs](https://img.shields.io/badge/-Node.js-3C873A?style=flat&logo=Node.js&logoColor=white) 16 | ![JavaScript](https://img.shields.io/badge/-JavaScript-eed718?style=flat&logo=javascript&logoColor=ffffff) 17 | [![GitHub stars](https://img.shields.io/github/stars/ikenxuan/kkkkkk-10086)](https://github.com/ikenxuan/kkkkkk-10086/stargazers) 18 | [![GitHub forks](https://img.shields.io/github/forks/ikenxuan/kkkkkk-10086)](https://github.com/ikenxuan/kkkkkk-10086/network) 19 | 20 |
21 | 22 | [![GitHub License](https://img.shields.io/github/license/ikenxuan/kkkkkk-10086)](https://github.com/ikenxuan/kkkkkk-10086/blob/master/LICENSE) 23 | [![GitHub Release](https://img.shields.io/github/v/release/ikenxuan/kkkkkk-10086)](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://contributors-img.web.app/image?repo=ikenxuan/kkkkkk-10086)](https://github.com/ikenxuan/kkkkkk-10086/graphs/contributors) 34 | 35 | ![Alt](https://repobeats.axiom.co/api/embed/3396f5ddc7a64da4b9089a4193c2cb3ba40588f7.svg 'Repobeats analytics image') 36 | 37 | [![Star History Chart](https://api.star-history.com/svg?repos=ikenxuan/kkkkkk-10086&type=Date)](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 | 3 | 6 | -------------------------------------------------------------------------------- /resources/image/bilibili/like-light.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/image/douyin/dylogo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /resources/image/douyin/search-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/image/douyin/search-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | bilibili 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 |
40 |
{{@username}}
41 |
42 |
43 |
44 | UID: {{user_shortid}} 45 | 获赞: {{total_favorited}} 46 | 关注: {{following_count}} 47 | 粉丝: {{fans}} 48 |
49 |
50 |
51 |
动态分享链接
52 |
53 |
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 |
18 |
{{@text}}
19 |
20 |
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 |
18 |
{{@text}}
19 |
20 |
21 |
22 | {{if original_content.DYNAMIC_TYPE_AV}} 23 |
24 |
25 | 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 | 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 | 106 |
107 |
108 |
{{@original_content.DYNAMIC_TYPE_WORD.text}}
109 |
110 | {{else if original_content.DYNAMIC_TYPE_LIVE_RCMD}} 111 |
112 |
113 | 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 |
51 |
动态分享链接
52 |
53 |
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 | bilibili 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 |
37 |
{{@username}}
38 |
39 |
40 |
41 | UID: {{user_shortid}} 42 | 获赞: {{total_favorited}} 43 | 关注: {{following_count}} 44 | 粉丝: {{fans}} 45 |
46 |
47 |
48 |
动态分享链接
49 |
50 |
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 | 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 |
31 | {{each CommentsData.jsonArray val}} 32 | 33 |
34 | 35 |
36 | 43 |
{{@val.text}}
44 | {{if (val.commentimage || val.sticker) != null}} 45 |
46 | 47 |
48 | {{/if}} 49 | 77 |
78 |
79 | {{/each}} 80 |
81 | 90 | {{/block}} -------------------------------------------------------------------------------- /resources/template/douyin/html/dynamic.html: -------------------------------------------------------------------------------- 1 | {{extend defaultLayout}} {{block 'css'}} 2 | 3 | 4 | {{/block}} {{block 'main'}} 5 | 6 |
7 |
8 | 9 |     记录美好生活 10 |
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 |
46 |
作品直链:永久有效
47 |
48 |
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 |
48 |
直播分享链接
49 |
50 |
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 |
8 | 9 |     抖音 记录美好生活 10 |
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 |
48 |
文件直链:永久有效
49 |
50 |
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 | 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 | 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 | Logo 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 |
29 | {{each CommentsData val}} 30 | 31 |
32 | 33 |
34 | 37 |
{{@val.text}}
38 | {{if (val.commentimage || val.sticker) != null}} 39 |
40 | 41 |
42 | {{/if}} 43 | 71 |
72 |
73 | {{/each}} 74 |
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 |
189 | 190 | 191 | 192 |
193 |
194 | 页面有效期剩余 195 | 196 | 10:00 197 | 198 |
199 |
200 |
201 | 202 | 240 | 241 | 242 | --------------------------------------------------------------------------------