├── .all-contributorsrc ├── .eslintrc.cjs ├── .github └── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ ├── enhancement.yml │ └── question.yml ├── .gitignore ├── .hintrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── apps ├── CodeUpdate.js ├── Picture.js ├── Poke.js ├── Summary.js ├── help.js ├── op.js ├── sendMaster.js ├── update.js └── version.js ├── components ├── Config.js ├── Data.js ├── Version.js └── index.js ├── config ├── .gitignore └── system │ ├── config.yaml │ ├── help_system.js │ └── 请勿修改此目录下的文件.txt ├── constants ├── Path.js └── Poke.js ├── guoba.support.js ├── guoba ├── configInfo.js ├── index.js ├── pluginInfo.js └── schemas │ ├── CodeUpdata.js │ ├── Picture.js │ ├── Poke.js │ ├── SendMaster.js │ ├── Summary.js │ ├── index.js │ ├── other.js │ └── proxy.js ├── index.js ├── jsconfig.json ├── lib ├── common │ └── common.js ├── load │ └── loadApps.js ├── puppeteer │ └── render.js └── request │ └── request.js ├── model ├── CodeUpdate.js ├── GitRepo.js ├── RandomFile.js ├── api │ ├── GitApi.js │ └── index.js ├── help │ └── theme.js ├── index.js ├── sendMasterMsg.js └── summary.js ├── package.json └── resources ├── CodeUpdate ├── icon │ ├── GitHub.svg │ ├── Gitcode.svg │ ├── Gitee.svg │ ├── branch.svg │ └── tag.svg ├── index.css ├── index.html └── index.scss ├── font └── font.woff ├── help ├── imgs │ ├── card-bg.png │ └── icon.png ├── index.css ├── index.html ├── index.scss ├── theme │ ├── default │ │ ├── bg.jpg │ │ ├── config.js │ │ └── main.jpg │ └── 目录说明.txt ├── version-info.css ├── version-info.html └── version-info.scss └── img ├── Roxy.png └── ys.png /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "yeyang52", 12 | "name": "椰羊", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/107110851?v=4", 14 | "profile": "https://github.com/yeyang52", 15 | "contributions": [ 16 | "example", 17 | "code" 18 | ] 19 | }, 20 | { 21 | "login": "TimeRainStarSky", 22 | "name": "时雨◎星空", 23 | "avatar_url": "https://avatars.githubusercontent.com/u/63490117?v=4", 24 | "profile": "https://github.com/TimeRainStarSky", 25 | "contributions": [ 26 | "mentoring", 27 | "code" 28 | ] 29 | }, 30 | { 31 | "login": "qsyhh", 32 | "name": "其实雨很好", 33 | "avatar_url": "https://avatars.githubusercontent.com/u/132750431?v=4", 34 | "profile": "https://github.com/qsyhh", 35 | "contributions": [ 36 | "code" 37 | ] 38 | }, 39 | { 40 | "login": "Admilkk", 41 | "name": "Admilk", 42 | "avatar_url": "https://foruda.gitee.com/avatar/1706324987763497611/13205155_adrae_1706324987.png!avatar200", 43 | "profile": "https://gitee.com/adrae", 44 | "contributions": [ 45 | "code" 46 | ] 47 | }, 48 | { 49 | "login": "kesally", 50 | "name": "kesally", 51 | "avatar_url": "https://avatars.githubusercontent.com/u/110397533?v=4", 52 | "profile": "https://gitee.com/kesally", 53 | "contributions": [ 54 | "code" 55 | ] 56 | }, 57 | { 58 | "login": "shanhai233", 59 | "name": "桃缘十三", 60 | "avatar_url": "https://foruda.gitee.com/avatar/1723727797498359874/8750220_shanhai233_1723727797.png!avatar200", 61 | "profile": "https://gitee.com/shanhai233", 62 | "contributions": [ 63 | "code" 64 | ] 65 | }, 66 | { 67 | "login": "hmexy", 68 | "name": "心愿XY", 69 | "avatar_url": "https://avatars.githubusercontent.com/u/112873708?v=4", 70 | "profile": "https://github.com/hmexy", 71 | "contributions": [ 72 | "code" 73 | ] 74 | } 75 | ], 76 | "contributorsPerLine": 7, 77 | "skipCi": true, 78 | "repoType": "github", 79 | "repoHost": "https://github.com", 80 | "projectName": "DF-Plugin", 81 | "projectOwner": "Denfenglai" 82 | } 83 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | node: true 5 | }, 6 | extends: [ 7 | "standard", 8 | "plugin:jsdoc/recommended", 9 | "plugin:import/recommended", 10 | "plugin:promise/recommended" 11 | ], 12 | parserOptions: { 13 | ecmaVersion: "latest", 14 | sourceType: "module" 15 | }, 16 | plugins: [ "import", "promise", "jsdoc" ], 17 | globals: { 18 | Bot: true, 19 | redis: true, 20 | logger: true, 21 | plugin: true, 22 | segment: true 23 | }, 24 | rules: { 25 | "eqeqeq": [ "off" ], 26 | "prefer-const": [ "off" ], 27 | "arrow-body-style": "off", 28 | "camelcase": "off", 29 | "quotes": [ "error", "double" ], 30 | "quote-props": [ "error", "consistent" ], 31 | "no-eval": [ "error", { allowIndirect: true } ], 32 | "array-bracket-newline": [ "error", { multiline: true } ], 33 | "array-bracket-spacing": [ "error", "always" ], 34 | "space-before-function-paren": [ "error", "never" ], 35 | "no-invalid-this": "error", 36 | "jsdoc/require-returns": 0, 37 | "jsdoc/require-jsdoc": 0, 38 | "jsdoc/require-param-description": 0, 39 | "jsdoc/require-returns-description": 0, 40 | "jsdoc/require-param-type": 0, 41 | "import/extensions": [ "error", "ignorePackages" ], 42 | "one-var": [ "off" ] 43 | }, 44 | settings: { 45 | "import/resolver": { 46 | "custom-alias": { 47 | alias: { 48 | "#components": "./components/index.js", 49 | "#model": "./model/index.js" 50 | }, 51 | extensions: [ ".js", ".json", ".jsx", ".ts", ".tsx" ] 52 | } 53 | } 54 | }, 55 | ignorePatterns: [ "test.js" ] 56 | } 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 报告 2 | description: 创建一份报告来帮助我们改进 3 | title: "[bug]:" 4 | labels: ["bug"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: 这个问题是否已经存在? 9 | options: 10 | - label: 我已经搜索过[现有的问题](https://github.com/DenFengLai/DF-Plugin/issues)并未发现相同问题 11 | required: true 12 | 13 | - type: input 14 | attributes: 15 | label: Yunzai版本 16 | description: 您使用的哪个版本的Yunzai 17 | placeholder: Miao-Yunzai | TRSS-Yunzai 18 | validations: 19 | required: true 20 | 21 | - type: input 22 | attributes: 23 | label: Node.js版本 24 | description: 使用`node -v`获取 25 | 26 | - type: input 27 | attributes: 28 | label: 协议端名称 29 | description: 您是使用的哪个协议出现的问题? 30 | placeholder: ICQQ或其他 31 | validations: 32 | required: true 33 | 34 | - type: input 35 | attributes: 36 | label: 操作系统 37 | description: 您的Yunzai使用的是哪个操作系统 38 | placeholder: 如Windows 10, MacOS, Mojave, Ubuntu 20.04等 39 | 40 | - type: textarea 41 | attributes: 42 | label: 复现步骤 43 | description: 请尽量详细地描述你的操作过程,以便我们更好地理解问题。 44 | placeholder: | 45 | 1. 第一步 46 | 2. 第二步 47 | 3. 第三步 48 | ... 49 | validations: 50 | required: true 51 | 52 | - type: textarea 53 | attributes: 54 | label: 预期行为 55 | description: 描述您期望在完成上述步骤后,系统应有的正确行为或输出。 56 | 57 | - type: textarea 58 | attributes: 59 | label: 实际行为 60 | description: 描述系统实际的行为或输出。 61 | validations: 62 | required: true 63 | 64 | - type: textarea 65 | attributes: 66 | label: 错误日志或截图 67 | description: 如果有的话,请附上相关错误日志或截图。请确保不包含敏感信息。 68 | placeholder: | 69 | 在这里上传截图或错误日志 70 | 71 | - type: textarea 72 | attributes: 73 | label: 其他说明 74 | description: 如果您有任何其他信息或说明,请在这里补充。 75 | 76 | - type: input 77 | attributes: 78 | label: 特殊配置 79 | description: 是否有进行任何特别的插件配置或修改? 80 | placeholder: 修改过xx配置文件xx字段为xx 81 | 82 | - type: dropdown 83 | attributes: 84 | label: 复现频率 85 | description: 问题出现的频率 86 | options: 87 | - 仅这一次 88 | - 偶尔出现 89 | - 经常出现 90 | - 每次出现 91 | validations: 92 | required: true 93 | 94 | - type: checkboxes 95 | attributes: 96 | label: DF-Plugin是否为当前最新版本? 97 | description: 请确保您已使用最新版本的DF-Plugin,以避免已知问题。 98 | options: 99 | - label: 我最近已使用过`#DF更新`命令进行更新至最新版本 100 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.yml: -------------------------------------------------------------------------------- 1 | name: 功能建议 2 | description: 对本插件提出一个功能建议 3 | title: "[功能建议]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢提出功能建议,我们将仔细考虑! 10 | 11 | - type: input 12 | attributes: 13 | label: 问题描述 14 | description: 请简要描述您想要的新功能。 15 | validations: 16 | required: true 17 | 18 | - type: input 19 | attributes: 20 | label: 实现方式 21 | description: 请简要描述您希望实现这个新功能的方式。 22 | validations: 23 | required: true 24 | 25 | - type: input 26 | attributes: 27 | label: 当前的问题 28 | description: 如果没有这个新功能,您当前面临的问题是什么? 29 | 30 | - type: textarea 31 | attributes: 32 | label: 其他说明 33 | description: 如果您有任何其他信息或说明,请在这里补充。 34 | 35 | - type: checkboxes 36 | attributes: 37 | label: 意向参与贡献 38 | options: 39 | - label: 我有意向参与具体功能的开发实现并将代码贡献到本插件 40 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: 其他问题及意见反馈 2 | description: 插件使用上的问题反馈、功能失效等。 3 | title: "[问题反馈]: " 4 | labels: ["question"] 5 | body: 6 | - type: input 7 | attributes: 8 | label: 反馈内容 9 | description: 请简要描述您需要反馈的内容。 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: 其他说明 16 | description: 如果您有任何其他信息或补充说明,请在这里添加。 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | data/ 3 | *_test.js 4 | test.* 5 | *.log 6 | .vscode/ 7 | temp/ 8 | *.css.map 9 | pnpm-lock.yaml 10 | package-lock.json 11 | yarn.lock 12 | resources/poke/ -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ] 5 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 1.1.5 3 | 4 | * 移除`随机网易云`功能 5 | * 修复Miao-Yunzai环境下插件加载失败 6 | * 联系主人功能可配置不回复主人账号 7 | * Git仓库监听新增支持多配置 8 | * 重构配置结构,新版配置文件已移动至`Yz/config/DF-Plugin.yaml` (**@时雨星空**) 9 | * 适配锅巴插件配置分组,如遇未知组件请更新插件 10 | 11 | # 1.1.4 12 | 13 | * 优化插件加载用时 14 | * 修复联系主人消息无法获取seq导致无法回复 15 | * 新增Git仓库发行版监听 16 | * 优化对image的消息处理 17 | 18 | # 1.1.3 19 | 20 | * `CodeUpdate:` 21 | * 优化仓库监听数据存储 22 | * 重构代码,并发获取数据,减少等待时间 23 | * 可配置推送至好友 24 | * 将代码更新默认检查频率调整为30分钟/次 25 | * 插件新增配置自动更新,在重启时能够合并用户配置与默认配置 26 | 27 | # 1.1.2 28 | 29 | * `CodeUpdate:` 30 | * 优化了代码更新检查逻辑 31 | * 新增文件变化数量和行数增减的显示 32 | * 在配置文件中增加排除列表,用于忽略不希望被监听的插件 33 | * 添加了作者和提交者头像的处理,在无头像时使用首个字符代替 34 | * 优化标题显示 35 | 36 | # 1.1.1 37 | 38 | * `CodeUpdate:` 39 | * 新增自动获取已安装插件地址 40 | * 修复在无提交者头像时报错的情况 41 | * 代码更新支持指定分支或提交SHA 42 | * 显示作者和提交者时间 43 | * 调整字体和布局 44 | * 优化了代码更新功能,提高了更新检查的效率和准确性 45 | * 增加了分支信息展示,使更新内容更加详细 46 | * 修复仓库路径会随着时间重复 47 | 48 | # 1.1.0 49 | 50 | * Git更新推送 51 | * 提交信息显示编写者与提交者 52 | * 重构UI样式 53 | * 优化数据处理逻辑 54 | * 显示提交者头像 55 | * 优化获取数据时的日志输出 56 | * 使用Git平台logo代替文字 57 | * 使用Yenai-Plugin同款字体 58 | * 感谢 **@椰羊(yeyang52)** 59 | 60 | # 1.0.10 61 | 62 | * Git更新推送支持显示提交者和多久前提交 63 | * 联系主人cd可填0关闭 64 | * 随机类型戳一戳可配置排除类型 65 | * 戳一戳新增永雏塔菲表情包 66 | * 图片外显新增列表随机模式 67 | 68 | # 1.0.9 69 | 70 | * 戳一戳图库内的图片可支持指令触发 71 | * 戳一戳图片类型新增 ATRI 表情包 72 | * **修复**安装图库后不生效 73 | * 戳一戳新增绫地宁宁表情包 74 | * 千恋万花小表情整理后重命名为柚子厨小表情 75 | * 戳一戳图片可将类型设置为`all`随机全部类型表情包 76 | 77 | # 1.0.8 78 | 79 | * 更新戳一戳图片类型 80 | * 戳一戳支持无图库时使用XY-Api调用 81 | * 感谢 **@心愿** 提供Api服务支持 82 | * Git仓库更新推送独立Gitee与Github的仓库监听,可配置个人token用于解除api速率限制 (**By @桃源十三**) 83 | * 新增贡献指南,开发者可阅读贡献指南对本项目发起贡献 84 | 85 | # 1.0.7 86 | 87 | * 优化Git仓库更新检查定时任务开关逻辑 88 | * 新增`#DF帮助` **By @kesally** 89 | * 新增图片外显功能 90 | * 每条图片带上外显 91 | * 可选使用一言或自定义文本 92 | * 新增`#DF版本`ⁿᵉʷ 93 | * 独立戳一戳图库以减少插件大小 94 | * 新增`#DF更新图库`ⁿᵉʷ 95 | 96 | # 1.0.6 97 | 98 | * 修复联系主人无法过滤群聊艾特 99 | * 优化锅巴配置组件 100 | * 联系主人新增`违禁词`、`禁用用户`、`禁用群`配置项ⁿᵉʷ 101 | * 新增Git仓库更新推送至群聊ⁿᵉʷ 102 | * 联系主人消息别名过滤适配TRSS-Yunzai 103 | * 更新丛雨表情包图片 104 | * 新增`随机丛雨`、`随机诗歌剧`ⁿᵉʷ 105 | 106 | # 1.0.5 107 | 108 | * 联系主人消息支持快速回复 109 | * 支持锅巴 110 | * 适配喵崽消息回复 111 | * 修复`来张cos`ⁿᵉʷ 112 | * 新增`#DF更新日志`ⁿᵉʷ 113 | * 优化联系主人 114 | 115 | # 1.0.4 116 | 117 | * 随机图片戳一戳改用本地图片 118 | * 新增丛雨、诗歌剧、千恋万花、小南梁表情包 119 | * 联系主人新增`sendAvatar`配置项 120 | * 联系主人支持自动过滤消息中别名,艾特 121 | * 引入开发依赖 `Gitmoji-Cli` 122 | * 优化联系主人通知排版 123 | * 删除`#文转卡`功能(失效) 124 | 125 | # 1.0.3 126 | 127 | * 新增随机图片戳一戳ⁿᵉʷ 128 | * 图转卡失效,已删除 129 | * 新增原神关键词发图ⁿᵉʷ 130 | * 无用的功能加一 131 | * 引入开发依赖 132 | * `husky` 133 | * `ESLint` 134 | * `lint-staged` 135 | 136 | # 1.0.2 137 | 138 | * 新增`#联系主人`功能ⁿᵉʷ 139 | * 修复图转卡 140 | * 图转卡支持更多参数 141 | * 支持用户自定义配置 142 | 143 | # 1.0.1 144 | 145 | * 新增`#文转卡`功能ⁿᵉʷ 146 | * 图转卡支持自定义外显ⁿᵉʷ 147 | * 新增`#图转卡`功能ⁿᵉʷ 148 | * 新增API随机图片功能ⁿᵉʷ 149 | * 新增随机音乐功能ⁿᵉʷ 150 | 151 | # 1.0.0 152 | 153 | * 初版发布! 154 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | 感谢您愿意为本项目做出贡献!遵循以下准则和步骤,以确保您的贡献符合项目的要求。 4 | 5 | ## 环境要求 6 | 7 | 在开始贡献之前,请确保您的开发环境满足以下要求: 8 | 9 | - 安装 Node.js (推荐最新稳定版) 10 | - 安装 Yunzai-Bot 11 | - 安装 pnpm 12 | - VSCode (可选) 13 | 14 | ## Fork本项目 15 | 16 | 前往[Gitee](https://gitee.com/DenFengLai/DF-Plugin)或[Github](https://gitee.com/DenFengLai/DF-Plugin)项目地址点击仓库上方的"fork"(或类似的按钮)将本项目fork到你的账户 17 | 18 | ## 克隆fork后的项目到本地 19 | 20 | ```sh 21 | git clone 你fork的仓库地址 22 | ``` 23 | 24 | ## 安装依赖 25 | 26 | ```sh 27 | pnpm install 28 | ``` 29 | 30 | ## 开发过程 31 | 32 | 在开发过程中请务必遵守以下规则 33 | 34 | - 严格遵守ESLint的代码规范,确保代码质量并与本项目其他代码风格保持一致,提交前使用ESLint检查一遍后并解决发现的问题 35 | - 推荐安装使用VSCode的[ESLint拓展](https://github.com/Microsoft/vscode-eslint),它可以帮助你在编写过程提示你所遇到的问题并提供解决方案 36 | - 尽量遵循项目的代码风格和命名约定,以保持代码的可读性 37 | - 提交的代码应该是经过测试的,并且不会破坏现有的功能。 38 | - 在提交时,请务必遵守[Gitmoji](https://gitmoji.dev)典范,可以使用[VSCode拓展](https://github.com/seatonjiang/gitmoji-vscode)帮助你选择对应的Gitmoji 39 | 40 | 不合格的代码将会被打回 41 | 42 | ## 提交规范 43 | 44 | 为了确保提交的代码符合项目的要求,我们使用 Husky 和 lint-staged 进行 Git 提交时的规范检测。请按照以下步骤进行提交: 45 | 46 | ```sh 47 | pnpm husky 48 | git add . 49 | pnpm run commit # 或 git commit 50 | ``` 51 | 52 | - 在提交时,Husky 会自动运行预定义的 Git 钩子脚本,包括对代码规范的检测,同时会使用[Gitmoji-cli](https://github.com/carloscuesta/gitmoji)进行交互式提交 53 | 54 | - 如果提交的代码不符合项目的规范要求,您将会收到相应的错误提示。请根据提示信息进行修改和调整,直到提交的代码符合要求。 55 | 56 | 如果你是在VSCode上进行提交,请先确保您的代码已经通过 ESLint 的检查,随后便可在 “源代码管理” 提交框上方选择对应的Gitmoji后即可提交。 57 | 58 | ## 提交拉取请求 59 | 60 | 当您准备好将您的贡献合并到主项目中时,请按照以下步骤提交拉取请求: 61 | 62 | 1. 将您的本地分支推送到远程仓库:`git push origin master` 63 | 64 | 2. 在项目仓库的页面上,点击 "New Pull Request"(或类似的按钮),创建一个新的拉取请求。 65 | 66 | 3. 填写拉取请求的相关信息,包括描述您的贡献的详细内容和目的。 67 | 68 | 4. 提交拉取请求后,项目维护者将会审核您的代码,并与您协作以确保贡献的质量和一致性。 69 | 70 | ## 感谢您的贡献 71 | 72 | 非常感谢您为项目做出的贡献!您的工作对于项目的发展和成功至关重要。项目维护者会尽快审查您的贡献并与您合作,以确保其顺利合并到主项目中。 73 | 74 | 如果您有任何问题或需要进一步的帮助,请随时与项目维护者进行沟通。 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 等风来 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DF-Plugin 2 | 3 | 适用于Miao-Yunzai和TRSS-Yunzai的拓展插件。 4 | 5 | ![Nodejs](https://img.shields.io/badge/-Node.js-3C873A?style=flat&logo=Node.js&logoColor=white) 6 | ![JavaScript](https://img.shields.io/badge/-JavaScript-eed718?style=flat&logo=javascript&logoColor=ffffff) 7 | [![Gitmoji](https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square)](https://gitmoji.dev) 8 | [![license](https://img.shields.io/github/license/Denfenglai/DF-Plugin.svg?style=flat&logo=gnu)](https://github.com/Denfenglai/DF-Plugin/blob/master/LICENSE) 9 | 10 | [![DF-Plugin](https://img.shields.io/github/package-json/v/Denfenglai/DF-Plugin?label=DF-Plugin&color=green)](https://github.com/DenFengLai/DF-Plugin) 11 | [![Miao-Yunzai V3](https://img.shields.io/github/package-json/v/yoimiya-kokomi/Miao-Yunzai?label=Miao-Yunzai&color=yellow)](https://github.com/yoimiya-kokomi/Miao-Yunzai) 12 | [![TRSS-Yunzai](https://img.shields.io/github/package-json/v/TimeRainStarSky/Yunzai?label=TRSS-Yunzai&color=blue)](https://github.com/TimeRainStarSky/Yunzai) 13 | 14 | [![Gitee stars](https://gitee.com/DenFengLai/DF-Plugin/badge/star.svg?theme=dark)](https://gitee.com/DenFengLai/DF-Plugin/stargazers) 15 | [![Gitee forks](https://gitee.com/DenFengLai/DF-Plugin/badge/fork.svg?theme=dark)](https://gitee.com/DenFengLai/DF-Plugin/members) 16 | [![GitHub stars](https://img.shields.io/github/stars/DenFengLai/DF-Plugin)](https://github.com/DenFengLai/DF-Plugin/stargazers) 17 | [![GitHub forks](https://img.shields.io/github/forks/DenFengLai/DF-Plugin)](https://github.com/DenFengLai/DF-Plugin/network) 18 | ![GitHub repo size](https://img.shields.io/github/repo-size/denfenglai/DF-Plugin?label=%E4%BB%93%E5%BA%93%E5%A4%A7%E5%B0%8F) 19 | 20 | [![Fork me on Gitee](https://gitee.com/DenFengLai/DF-Plugin/widgets/widget_6.svg)](https://gitee.com/DenFengLai/DF-Plugin) 21 | 22 | ## 💡 安装教程 23 | 24 | - 使用Github 25 | 26 | ```sh 27 | git clone --depth=1 https://github.com/DenFengLai/DF-Plugin.git ./plugins/DF-Plugin 28 | ``` 29 | 30 | - 使用Gitee 31 | 32 | ```sh 33 | git clone --depth=1 https://gitee.com/DenFengLai/DF-Plugin.git ./plugins/DF-Plugin 34 | ``` 35 | 36 | ### 🔧 安装依赖 37 | 38 | ```sh 39 | pnpm install 40 | ``` 41 | 42 | ## 🤗 已实现的功能 43 | 44 |
随机图片 45 | 46 | - #来张JK / 黑丝 / cos / 腿子 / 丛雨 /诗歌剧 47 | 48 | > 随机发送一张图片 49 | 50 |
51 | 52 |
给主人带话 53 | 54 | - #联系主人 + `消息内容` 55 | 56 | > #回复<内容> 或 #回复<消息标识><空格><内容> 57 | 58 |
59 | 60 |
随机表情戳一戳 61 | 62 | > 戳一戳返回随机表情包 63 | > 使用 #DF安装图库 可安装图库到本地使用 64 | > 未安装图库将调用[XY-Api](https://api.yugan.love/) 65 | 66 |
67 | 68 |
Git仓库更新推送 69 | 70 | > 在[配置文件](/config/default_config/CodeUpdate.yaml)配置完成填写群号后即可使用。 71 | > 推荐使用[锅巴插件](https://gitee.com/guoba-yunzai/guoba-plugin.git)进行配置 72 | 73 | - `#检查仓库更新`: 检查有没有仓库更新(相当于主动触发定时逻辑) 74 | - `#推送仓库更新`: 不管有没有更新都回复到当前会话,不会推送所有群组 75 | 76 |
77 | 78 |
图片外显 79 | 80 | > 配置请看[summary.yaml](./config/default_config/summary.yaml) 81 | > 推荐使用[锅巴插件](https://gitee.com/guoba-yunzai/guoba-plugin.git)进行配置 82 | 83 | - #开启/关闭图片外显 84 | - #设置图片外显 + 文字 85 | 86 |
87 | 88 | --- 89 | 90 | > 更多功能请使用 `#DF帮助` 91 | > 查看近期更改请使用`#DF版本` 92 | 93 | ## ⚙️ 插件配置 94 | 95 | 本插件已全面兼容[锅巴插件](https://gitee.com/guoba-yunzai/guoba-plugin.git),推荐使用锅巴插件进行配置。 96 | 97 | ## 📄 计划工程 98 | 99 | - [x] 能跑 100 | - [x] 能用 101 | - [x] 支持用户自定义配置 102 | - [x] 添加帮助信息和版本信息 [@kesally](https://gitee.com/kesally) 103 | - [ ] 丰富功能 104 | - [ ] 持续完善 105 | - [ ] ~~删库跑路~~ 106 | 107 | ## ✨ 贡献者 108 | 109 | 110 | [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-) 111 | 112 | 113 | 感谢这些了不起的人 ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
椰羊
椰羊

💡 💻
时雨◎星空
时雨◎星空

🧑‍🏫 💻
其实雨很好
其实雨很好

💻
Admilk
Admilk

💻
kesally
kesally

💻
桃缘十三
桃缘十三

💻
心愿XY
心愿XY

💻
131 | 132 | 133 | 134 | 135 | 136 | 137 | 本段遵循 [all-contributors](https://github.com/all-contributors/all-contributors) 规范,欢迎任何形式的贡献! 138 | 139 | ## 💬 免责声明 140 | 141 | 1. 本项目仅供学习使用,请勿用于商业等场景。 142 | 143 | 2. 项目内图片、API等资源均来源于网络,如侵犯了您的利益请及时联系项目开发者进行删除。 144 | 145 | ## 🍀 意见反馈 146 | 147 | 如果您对本插件有什么建议或使用遇到了问题欢迎对本项目提交[issues](https://github.com/DenFengLai/DF-Plugin/issues/new)。 148 | 149 | ## 🎨 参与贡献 150 | 151 | 如果您有兴趣对本项目做出贡献,请阅读[贡献指南](./CONTRIBUTING.md)。 152 | 153 | ## 👥 联系我们 154 | 155 | - QQ交流群: [964193559](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=hGiK1lQOmbJzP7S0xm-00NKdNi9Oe8Ma&authKey=aQRGoOwAyQ%2BYZ%2BZ5QNKJegwf5Y%2BgYM3Y%2F3%2Fc61cSquEuoIPM1qKemM6ajHb0sRFk&noverify=0&group_code=964193559) 156 | 157 | ## ⭐ 支持本项目 158 | 159 | 如果你觉得本项目对你有帮助,请给本项目点点star,你是鼓励是我们前进的动力。 160 | 161 | ## ❤️ 赞助作者 162 | 163 | - [爱发电](https://afdian.com/a/DenFengLai) 164 | 165 | ## 💝 友情链接 166 | 167 | - [TRSS.me](https://TRSS.me) 168 | - [Yenai-Plugin](https://Yenai.TRSS.me) 169 | - [Fanji-plugin](http://gitee.com/adrae/Fanji-plugin) 170 | - [DF-Poke](https://gitea.eustia.fun/XY/poke.git) 171 | 172 | ## 🎁 特别鸣谢 173 | 174 | - [XY-Api](https://api.yugan.love/):提供戳一戳图片接口服务支持 175 | - [素颜Api](https://api.suyanw.cn):提供部分Api服务 176 | -------------------------------------------------------------------------------- /apps/CodeUpdate.js: -------------------------------------------------------------------------------- 1 | import { CodeUpdate as Cup } from "#model" 2 | import { Config } from "#components" 3 | 4 | export class CodeUpdate extends plugin { 5 | constructor() { 6 | super({ 7 | name: "DF:仓库更新推送", 8 | dsc: "检查指定Git仓库是否更新并推送", 9 | event: "message", 10 | priority: 5000, 11 | rule: [ 12 | { 13 | reg: "^#(检查|推送)仓库更新$", 14 | fnc: "cupdate" 15 | } 16 | ] 17 | }) 18 | 19 | if (Config.CodeUpdate.Auto) { 20 | this.task = { 21 | cron: Config.CodeUpdate.Cron, 22 | name: "[DF-Plugin]Git仓库更新检查", 23 | fnc: () => Cup.checkUpdates(true) 24 | } 25 | } 26 | } 27 | 28 | async cupdate(e) { 29 | const isPush = e.msg.includes("推送") 30 | e.reply(`正在${isPush ? "推送" : "检查"}仓库更新,请稍等`) 31 | 32 | const res = await Cup.checkUpdates(!isPush, e) 33 | if (!isPush) { 34 | const msg = res?.number > 0 35 | ? `检查完成,共有${res.number}个仓库有更新,正在按照你的配置进行推送哦~` 36 | : "检查完成,没有发现仓库有更新" 37 | return e.reply(msg) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/Picture.js: -------------------------------------------------------------------------------- 1 | import { imagePoke as RandomFace } from "#model" 2 | import { Config, Poke_List as Face_List, request } from "#components" 3 | 4 | export class Random_Pictures extends plugin { 5 | constructor() { 6 | super({ 7 | name: "DF:随机图片", 8 | dsc: "随机返回一张图片", 9 | event: "message", 10 | priority: 500, 11 | rule: [ 12 | { 13 | reg: "^#?(来张|看看|随机)([jJ][kK]|制服(小姐姐)?|黑丝|[Cc][Oo][Ss]|腿子?)$", 14 | fnc: "handleRequest" 15 | }, 16 | { 17 | reg: `^#?(随机|来张)?(${Face_List.join("|")})$`, 18 | fnc: "handleRequest" 19 | }, 20 | { 21 | reg: "^#?[Dd][Ff](随机)?表情包?列表$", 22 | fnc: "list" 23 | } 24 | ] 25 | }) 26 | } 27 | 28 | get open() { 29 | return Config.Picture.open 30 | } 31 | 32 | async list(e) { 33 | return e.reply(`表情包列表:\n${Face_List.join("、")}`, true) 34 | } 35 | 36 | async handleRequest(e) { 37 | if (!this.open) return false 38 | 39 | const msg = e.msg 40 | let response = [] 41 | 42 | if (msg.includes("jk") || msg.includes("制服")) { 43 | response = [ segment.image("https://api.suyanw.cn/api/jk.php") ] 44 | } else if (msg.includes("黑丝")) { 45 | response = [ "唉嗨害,黑丝来咯", segment.image("https://api.suyanw.cn/api/hs.php") ] 46 | } else if (msg.includes("cos")) { 47 | const link = (await request.get("https://api.suyanw.cn/api/cos.php?type=json")).text.replace(/\\/g, "/") 48 | response = [ "cos来咯~", segment.image(link) ] 49 | } else if (msg.includes("腿")) { 50 | const link = (await request.get("https://api.suyanw.cn/api/meitui.php", { responseType: "text" })).match(/https?:\/\/[^ ]+/)?.[0] 51 | response = [ "看吧涩批!", segment.image(link) ] 52 | } else if (msg.includes("随机") || msg.includes("来张") || Config.Picture.Direct) { 53 | const name = msg.replace(/#|随机|来张/g, "") 54 | const file = RandomFace(name) 55 | if (file) { 56 | response = [ segment.image(file) ] 57 | } 58 | } else { 59 | return false 60 | } 61 | 62 | if (response.length > 0) { 63 | return e.reply(response, true) 64 | } 65 | 66 | return false 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /apps/Poke.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs" 2 | import { imagePoke } from "#model" 3 | import { Config, Poke_List, Poke_Path } from "#components" 4 | 5 | if (!fs.existsSync(Poke_Path) && Config.other.chuo) logger.mark("[DF-Plugin] 检测到未安装戳一戳图库 将调用XY-Api返回图片") 6 | 7 | export class DF_Poke extends plugin { 8 | constructor() { 9 | super({ 10 | name: "DF:戳一戳", 11 | dsc: "戳一戳机器人发送随机表情包", 12 | event: "notice.*.poke", 13 | priority: -114, 14 | rule: [ { fnc: "poke", log: false } ] 15 | }) 16 | } 17 | 18 | async poke() { 19 | const { chuo, chuoType } = Config.other 20 | if (!chuo) return false 21 | if (this.e.target_id !== this.e.self_id) return false 22 | let name = chuoType 23 | if (chuoType !== "all") { 24 | name = Poke_List[chuoType] 25 | } 26 | if (!name) return false 27 | logger.info(`${logger.blue("[DF-Plugin]")}${logger.green("[戳一戳]")}获取 ${name} 图片`) 28 | const file = imagePoke(name) 29 | if (!file) return false 30 | return this.e.reply(segment.image(file)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/Summary.js: -------------------------------------------------------------------------------- 1 | import { Config } from "#components" 2 | import { Summary as Sum } from "#model" 3 | 4 | if (Config.summary.sum) Sum.lint() 5 | 6 | export class Summary extends plugin { 7 | constructor() { 8 | super({ 9 | name: "DF:图片外显", 10 | dsc: "图片自动添加外显", 11 | event: "message", 12 | priority: 5000, 13 | rule: [ 14 | { 15 | reg: "^#设置外显", 16 | fnc: "SetSum" 17 | }, 18 | { 19 | reg: "^#?(开启|关闭)(图片)?外显$", 20 | fnc: "on" 21 | }, 22 | { 23 | reg: "^#切换外显模式$", 24 | fnc: "yiyan" 25 | } 26 | ] 27 | }) 28 | } 29 | 30 | async SetSum(e) { 31 | if (!e.isMaster) return 32 | if (!Config.summary.sum) return e.reply("❎ 请先启用该功能!") 33 | if (Config.summary.type == 2) return e.reply("❎ 该功能在一言模式下不可用,请先关闭一言") 34 | let sum = e.msg.replace(/^#设置外显/g, "").trim() 35 | if (!sum) return e.reply("请附带外显内容哦") 36 | Config.modify("summary", "text", sum) 37 | return e.reply("✅ 修改成功!") 38 | } 39 | 40 | async on(e) { 41 | if (!e.isMaster) return 42 | const type = /开启/.test(e.msg) 43 | if ((type && Config.summary.sum) || (!type && !Config.summary.sum)) return e.reply(`❎ 图片外显已处于${type ? "开启" : "关闭"}状态`) 44 | Config.modify("summary", "sum", type) 45 | Sum.Switch(type) 46 | e.reply(`✅ 已${type ? "开启" : "关闭"}图片外显`) 47 | } 48 | 49 | async yiyan(e) { 50 | if (!e.isMaster) return 51 | if (Config.summary.type === 1) { 52 | Config.modify("summary", "type", 2) 53 | e.reply("✅ 已切换至一言模式") 54 | } else { 55 | Config.modify("summary", "type", 1) 56 | e.reply("✅ 已切换至自定义文本模式") 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /apps/help.js: -------------------------------------------------------------------------------- 1 | import lodash from "lodash" 2 | import { common, Data } from "#components" 3 | import Theme from "../model/help/theme.js" 4 | 5 | export class help extends plugin { 6 | constructor() { 7 | super({ 8 | name: "DF:帮助", 9 | dsc: "DF插件命令帮助", 10 | event: "message", 11 | priority: 2000, 12 | rule: [ 13 | { 14 | reg: "^#?[Dd][Ff](插件)?帮助$", 15 | fnc: "help" 16 | } 17 | ] 18 | }) 19 | } 20 | 21 | async help(e) { 22 | let custom = {} 23 | let help = {} 24 | 25 | let { diyCfg, sysCfg } = await Data.importCfg("help") 26 | 27 | custom = help 28 | 29 | let helpConfig = lodash.defaults(diyCfg.helpCfg || {}, custom.helpCfg, sysCfg.helpCfg) 30 | let helpList = diyCfg.helpList || custom.helpList || sysCfg.helpList 31 | let helpGroup = [] 32 | 33 | lodash.forEach(helpList, (group) => { 34 | if (group.auth && group.auth === "master" && !e.isMaster) { 35 | return true 36 | } 37 | 38 | lodash.forEach(group.list, (help) => { 39 | let icon = help.icon * 1 40 | if (!icon) { 41 | help.css = "display:none" 42 | } else { 43 | let x = (icon - 1) % 10 44 | let y = (icon - x - 1) / 10 45 | help.css = `background-position:-${x * 50}px -${y * 50}px` 46 | } 47 | }) 48 | 49 | helpGroup.push(group) 50 | }) 51 | let themeData = await Theme.getThemeData(diyCfg.helpCfg || {}, sysCfg.helpCfg || {}) 52 | 53 | return await common.render("help/index", { 54 | helpCfg: helpConfig, 55 | helpGroup, 56 | ...themeData 57 | }, { e, scale: 1.6 }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /apps/op.js: -------------------------------------------------------------------------------- 1 | import { Config, Res_Path } from "#components" 2 | 3 | const ys = {} 4 | 5 | export class OP extends plugin { 6 | constructor() { 7 | super({ 8 | name: "DF:原神关键词发图", 9 | dsc: "本来聊得好好的,突然有人聊起了原神,搞得大家都不高兴", 10 | event: "message.group", 11 | priority: 5001, 12 | rule: [ 13 | { 14 | reg: "原神", 15 | fnc: "ys" 16 | } 17 | ] 18 | }) 19 | } 20 | 21 | async ys() { 22 | if (ys[this.e.group_id] || !Config.other.ys) return false 23 | this.reply(segment.image(`${Res_Path}/img/ys.png`)) 24 | ys[this.e.group_id] = true 25 | return false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/sendMaster.js: -------------------------------------------------------------------------------- 1 | import moment from "moment" 2 | import { ulid } from "ulid" 3 | import { common, Config } from "#components" 4 | import { imagePoke } from "#model" 5 | import { sendMasterMsg, extractMessageId, getSourceMessage, getMasterQQ } from "../model/sendMasterMsg.js" 6 | import { segment } from "oicq" 7 | 8 | const key = "DF:contact" 9 | let Sending = false 10 | segment.reply ??= (id) => ({ type: "reply", id }) 11 | let ReplyReg = /^#?回复(\S+)\s?(.*)?$/ 12 | 13 | export class SendMasterMsgs extends plugin { 14 | constructor() { 15 | super({ 16 | name: "DF:联系主人", 17 | dsc: "给主人发送一条消息", 18 | event: "message", 19 | priority: 400, 20 | rule: [ 21 | { 22 | reg: "^#联系主人", 23 | fnc: "contact" 24 | }, 25 | { 26 | reg: ReplyReg, 27 | fnc: "Replys", 28 | event: "message.private" 29 | } 30 | ] 31 | }) 32 | } 33 | 34 | /** 35 | * 联系主人 36 | * @param {object} e - 消息事件 37 | */ 38 | async contact(e) { 39 | if (Sending) return e.reply("❎ 已有发送任务正在进行中,请稍候重试") 40 | 41 | let { open, cd, BotId, sendAvatar, banWords, banUser, banGroup, replyQQ } = Config.sendMaster 42 | 43 | if (!e.isMaster) { 44 | if (!open) return e.reply("❎ 该功能暂未开启,请先让主人开启才能用哦", true) 45 | if (cd !== 0 && await redis.get(key)) return e.reply("❎ 操作频繁,请稍后再试", true) 46 | if (banWords.some(item => e.msg.includes(item))) return e.reply("❎ 消息包含违禁词,请检查后重试", true) 47 | if (banUser.includes(e.user_id)) return e.reply("❎ 对不起,您不可用", true) 48 | if (e.isGroup && banGroup.includes(e.group_id)) return e.reply("❎ 该群暂不可用该功能", true) 49 | } 50 | 51 | Sending = true 52 | 53 | try { 54 | const message = await common.Replace(e, /#联系主人/) 55 | if (message.length === 0) return e.reply("❎ 消息不能为空") 56 | 57 | const type = e.bot?.version?.id || e?.adapter_id || "QQ" 58 | const avatar = sendAvatar ? segment.image(e.sender?.getAvatarUrl?.() || e.friend?.getAvatarUrl?.() || imagePoke("all")) : "" 59 | const user_id = `${e.sender.nickname}(${e.user_id})` 60 | const group = e.isGroup ? `${e.group.name || "未知群名"}(${e.group_id})` : "私聊" 61 | const bot = `${e.bot.nickname}(${e.bot.uin})` 62 | const time = moment().format("YYYY-MM-DD HH:mm:ss") 63 | const id = ulid().slice(-5) 64 | 65 | const msg = [ 66 | `联系主人消息(${id})\n`, 67 | (avatar && sendAvatar) ? avatar : "", 68 | `平台: ${type}\n`, 69 | `用户: ${user_id}\n`, 70 | `来自: ${group}\n`, 71 | `BOT: ${bot}\n`, 72 | `时间: ${time}\n`, 73 | "消息内容:\n", 74 | ...message, 75 | "\n-------------\n", 76 | "引用该消息:#回复 <内容>" 77 | ] 78 | 79 | const info = { 80 | bot: e.bot.uin || Bot.uin, 81 | group: e.isGroup ? e.group_id : false, 82 | id: e.user_id, 83 | message_id: e.message_id 84 | } 85 | 86 | const masterQQ = getMasterQQ(Config.sendMaster) 87 | if (!Bot[BotId]) BotId = e.self_id 88 | 89 | try { 90 | await sendMasterMsg(msg, BotId) 91 | let _msg = "✅ 消息已送达" 92 | if (replyQQ) _msg += `\n主人的QQ:${masterQQ}` 93 | await e.reply(_msg, true) 94 | if (cd) redis.set(key, "1", { EX: cd }) 95 | redis.set(`${key}:${id}`, JSON.stringify(info), { EX: 86400 }) 96 | } catch (err) { 97 | await e.reply(`❎ 消息发送失败,请尝试自行联系:${masterQQ}\n错误信息:${err}`) 98 | logger.error(err) 99 | } 100 | } catch (err) { 101 | await e.reply("❎ 出错误辣,稍后重试吧") 102 | logger.error(err) 103 | } finally { 104 | Sending = false 105 | } 106 | } 107 | 108 | /** 109 | * 回复消息 110 | * @param {object} e - 消息事件 111 | */ 112 | async Replys(e) { 113 | if (!e.isMaster) return false 114 | 115 | try { 116 | const source = await getSourceMessage(e) 117 | let MsgID, isInput = false 118 | if (source && (/联系主人消息/.test(source.raw_message))) { 119 | MsgID = extractMessageId(source.raw_message) 120 | } else { 121 | const regRet = ReplyReg.exec(e.msg) 122 | if (!regRet[1]) return logger.warn("[DF-Plugin] 未找到消息ID") 123 | else { 124 | MsgID = regRet[1].trim() 125 | isInput = true 126 | } 127 | } 128 | 129 | const data = await redis.get(`${key}:${MsgID}`) 130 | if (!data) return isInput ? false : e.reply("❎ 消息已失效或不存在") 131 | 132 | const { bot, group, id, message_id } = JSON.parse(data) 133 | const message = await common.Replace(e, isInput ? /#?回复(\S+)\s?/ : /#?回复/g) 134 | message.unshift(`主人${Config.sendMaster.replyQQ ? `(${e.user_id})` : ""}回复:\n`, segment.reply(message_id)) 135 | 136 | this.Bot = Bot[bot] ?? Bot 137 | 138 | group ? await this.Bot.pickGroup(group).sendMsg(message) : await this.Bot.pickFriend(id).sendMsg(message) 139 | 140 | return e.reply("✅ 消息已送达") 141 | } catch (err) { 142 | e.reply("❎ 发生错误,请查看控制台日志") 143 | logger.error("[DF-Plugin]回复消息时发生错误:", err) 144 | return false 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /apps/update.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs" 2 | import { exec } from "node:child_process" 3 | import { update as Update } from "../../other/update.js" 4 | import { Plugin_Name, Plugin_Path, Poke_Path } from "#components" 5 | 6 | let lock = false // 儿童锁(bushi) 7 | 8 | export class DFupdate extends plugin { 9 | constructor() { 10 | super({ 11 | name: "DF:更新插件", 12 | event: "message", 13 | priority: 1000, 14 | rule: [ 15 | { 16 | reg: "^#[Dd][Ff](插件)?(强制)?更新(日志)?$", 17 | fnc: "update" 18 | }, 19 | { 20 | reg: "^#?[Dd][Ff](安装|(强制)?更新)(戳一戳)?图库$", 21 | fnc: "up_img" 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async update(e = this.e) { 28 | let isLog = e.msg.includes("日志") 29 | let Type = isLog ? "#更新日志" : (e.msg.includes("强制") ? "#强制更新" : "#更新") 30 | e.msg = Type + Plugin_Name 31 | const up = new Update(e) 32 | up.e = e 33 | return isLog ? up.updateLog() : up.update() 34 | } 35 | 36 | async up_img(e) { 37 | if (!e.isMaster) return false 38 | if (lock) return e.reply("已有更新任务正在进行中,请勿重复操作!") 39 | 40 | lock = true 41 | try { 42 | if (fs.existsSync(Poke_Path)) { 43 | let isForce = e.msg.includes("强制") 44 | await e.reply(`开始${isForce ? "强制" : ""}更新图库啦,请主人稍安勿躁~`) 45 | await this.executeGitCommand(e, isForce) 46 | } else { 47 | await e.reply("开始安装戳一戳图库,可能需要一段时间,请主人稍安勿躁~") 48 | await this.cloneRepository(e) 49 | } 50 | } finally { 51 | lock = false 52 | } 53 | return true 54 | } 55 | 56 | /** 57 | * 更新图片资源。 58 | * @param {object} e - 事件对象。 59 | * @param {boolean} isForce - 是否使用强制更新。 60 | * @returns {Promise} 返回一个在 Git 命令执行完成时解析的 Promise。 61 | */ 62 | executeGitCommand(e, isForce) { 63 | return new Promise((resolve) => { 64 | const command = isForce ? "git reset --hard origin/main && git pull --rebase" : "git pull" 65 | exec(command, { cwd: Poke_Path }, (error, stdout) => { 66 | if (error) { 67 | e.reply(`图片资源更新失败!\nError code: ${error.code}\n${error.stack}\n 请尝试使用 #DF强制更新图库 或稍后重试。`) 68 | } else if (/Already up to date/.test(stdout) || stdout.includes("最新")) { 69 | e.reply("目前所有图片都已经是最新了~") 70 | } else { 71 | const numRet = /(\d*) files changed,/.exec(stdout) 72 | if (numRet && numRet[1]) { 73 | e.reply(`更新成功,共更新了${numRet[1]}张图片~`) 74 | } else { 75 | e.reply("戳一戳图片资源更新完毕") 76 | } 77 | } 78 | resolve() 79 | }) 80 | }) 81 | } 82 | 83 | /** 84 | * 安装戳一戳图库 85 | * @param {object} e - 事件对象,用于回复消息。 86 | * @returns {Promise} - 返回一个Promise,当克隆操作完成时解析。 87 | */ 88 | cloneRepository(e) { 89 | return new Promise((resolve) => { 90 | const command = "git clone --depth=1 https://gitea.eustia.fun/XY/poke.git ./resources/poke" 91 | exec(command, { cwd: Plugin_Path }, (error) => { 92 | if (error) { 93 | e.reply(`戳一戳图库安装失败!\nError code: ${error.code}\n${error.stack}\n 请稍后重试。`) 94 | } else { 95 | e.reply("戳一戳图库安装成功!您后续也可以通过 #DF更新图库 命令来更新图片") 96 | } 97 | resolve() 98 | }) 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /apps/version.js: -------------------------------------------------------------------------------- 1 | import { Version, common } from "#components" 2 | 3 | export class Version_Info extends plugin { 4 | constructor() { 5 | super({ 6 | name: "DF:版本信息", 7 | event: "message", 8 | priority: 400, 9 | rule: [ 10 | { 11 | reg: "^#?[Dd][Ff](插件)?版本$", 12 | fnc: "plugin_version" 13 | } 14 | ] 15 | }) 16 | } 17 | 18 | async plugin_version(e) { 19 | return await common.render("help/version-info", { 20 | currentVersion: Version.ver, 21 | changelogs: Version.logs 22 | }, { e, scale: 1.4 } 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /components/Config.js: -------------------------------------------------------------------------------- 1 | import YAML from "yaml" 2 | import cfg from "../../../lib/config/config.js" 3 | import makeConfig from "../../../lib/plugins/config.js" 4 | import fs from "node:fs/promises" 5 | import { Plugin_Name, Plugin_Path } from "../constants/Path.js" 6 | import _ from "lodash" 7 | 8 | class Config { 9 | plugin_name = Plugin_Name 10 | plugin_path = Plugin_Path 11 | /** 初始化配置 */ 12 | async initCfg() { 13 | this.config = YAML.parse(await fs.readFile(`${this.plugin_path}/config/system/config.yaml`, "utf8")) 14 | 15 | /** 导入旧配置文件 */ 16 | const path = `${this.plugin_path}/config/config` 17 | if (await fs.stat(path).catch(() => false)) { 18 | for (let file of (await fs.readdir(path)).filter(file => file.endsWith(".yaml"))) { 19 | const key = file.replace(".yaml", "") 20 | if (!(key in this.config)) continue 21 | _.merge(this.config[key], YAML.parse(await fs.readFile(`${path}/${file}`, "utf8"))) 22 | } 23 | await fs.rename(path, `${path}_old`) 24 | } 25 | 26 | /** 保留注释 */ 27 | const keep = {} 28 | for (const i in this.config) { 29 | keep[i] = {} 30 | for (const j in this.config[i]) { 31 | if (j.endsWith("Tips")) { keep[i][j] = this.config[i][j] } 32 | } 33 | } 34 | 35 | const { config, configSave } = await makeConfig(this.plugin_name, this.config, keep, { 36 | replacer: i => i.replace(/(\n.+?Tips:)/g, "\n$1") 37 | }) 38 | this.config = config 39 | this.configSave = configSave 40 | return this 41 | } 42 | 43 | /** 主人列表 */ 44 | get masterQQ() { 45 | return cfg.masterQQ 46 | } 47 | 48 | /** TRSS的主人列表 */ 49 | get master() { 50 | return cfg.master 51 | } 52 | 53 | get AutoPath() { 54 | return this.config.CodeUpdate.List.some(i => !!i?.AutoPath) 55 | } 56 | 57 | /** 联系主人 */ 58 | get sendMaster() { 59 | return this.config.sendMaster 60 | } 61 | 62 | /** 其他配置 */ 63 | get other() { 64 | return this.config.other 65 | } 66 | 67 | /** Git推送 */ 68 | get CodeUpdate() { 69 | return this.config.CodeUpdate 70 | } 71 | 72 | /** 图片外显 */ 73 | get summary() { 74 | return this.config.summary 75 | } 76 | 77 | /** 随机图片配置 */ 78 | get Picture() { 79 | return this.config.Picture 80 | } 81 | 82 | /** 代理配置 */ 83 | get proxy() { 84 | return this.config.proxy 85 | } 86 | 87 | /** 88 | * 群配置 89 | * @param group_id 群号 90 | * @param bot_id 机器人账号 91 | */ 92 | getGroup(group_id = "", bot_id = "") { 93 | return Array.isArray(Bot.uin) ? cfg.getGroup(bot_id, group_id) : cfg.getGroup(group_id) 94 | } 95 | 96 | /** 97 | * 修改设置 98 | * @param {string} name 配置名 99 | * @param {string} key 修改的key值 100 | * @param {string | number} value 修改的value值 101 | */ 102 | modify(name, key, value) { 103 | if (typeof this.config[name] != "object") { this.config[name] = {} } 104 | this.config[name][key] = value 105 | return this.configSave() 106 | } 107 | 108 | /** 109 | * 修改配置数组 110 | * @param {string} name 文件名 111 | * @param {string | number} key key值 112 | * @param {string | number} value value 113 | * @param {'add'|'del'} category 类别 add or del 114 | */ 115 | modifyarr(name, key, value, category = "add") { 116 | if (typeof this.config[name] != "object") this.config[name] = {} 117 | if (!Array.isArray(this.config[name][key])) this.config[name][key] = [] 118 | if (category == "add") { 119 | if (!this.config[name][key].includes(value)) this.config[name][key].push(value) 120 | } else { 121 | this.config[name][key] = this.config[name][key].filter(item => item !== value) 122 | } 123 | return this.configSave() 124 | } 125 | } 126 | 127 | export default await new Config().initCfg() 128 | -------------------------------------------------------------------------------- /components/Data.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash" 2 | import fs from "node:fs" 3 | import path from "node:path" 4 | import { Path, Plugin_Name, Plugin_Path } from "../constants/Path.js" 5 | 6 | const getRoot = (root = "") => { 7 | if (root === "root" || root === "yunzai") { 8 | root = `${Path}/` 9 | } else if (!root) { 10 | root = `${Plugin_Path}/` 11 | } 12 | return root 13 | } 14 | 15 | let Data = { 16 | 17 | /* 18 | * 根据指定的path依次检查与创建目录 19 | * */ 20 | createDir(path = "", root = "", includeFile = false) { 21 | root = getRoot(root) 22 | let pathList = path.split("/") 23 | let nowPath = root 24 | pathList.forEach((name, idx) => { 25 | name = name.trim() 26 | if (!includeFile && idx <= pathList.length - 1) { 27 | nowPath += name + "/" 28 | if (name) { 29 | if (!fs.existsSync(nowPath)) { 30 | fs.mkdirSync(nowPath) 31 | } 32 | } 33 | } 34 | }) 35 | }, 36 | 37 | /** 38 | * 读取json 39 | * @param file 40 | * @param root 41 | */ 42 | readJSON(file = "", root = "") { 43 | root = getRoot(root) 44 | if (fs.existsSync(`${root}/${file}`)) { 45 | try { 46 | return JSON.parse(fs.readFileSync(`${root}/${file}`, "utf8")) 47 | } catch (e) { 48 | console.log(e) 49 | } 50 | } 51 | return {} 52 | }, 53 | 54 | /** 55 | * 写JSON 56 | * @param file 57 | * @param data 58 | * @param root 59 | * @param space 60 | */ 61 | writeJSON(file, data, root = "", space = "\t") { 62 | // 检查并创建目录 63 | Data.createDir(file, root, true) 64 | root = getRoot(root) 65 | // delete data._res 66 | try { 67 | fs.writeFileSync(`${root}/${file}`, JSON.stringify(data, null, space)) 68 | return true 69 | } catch (err) { 70 | logger.error(err) 71 | return false 72 | } 73 | }, 74 | 75 | async getCacheJSON(key) { 76 | try { 77 | let txt = await redis.get(key) 78 | if (txt) { 79 | return JSON.parse(txt) 80 | } 81 | } catch (e) { 82 | console.log(e) 83 | } 84 | return {} 85 | }, 86 | 87 | async setCacheJSON(key, data, EX = 3600 * 24 * 90) { 88 | await redis.set(key, JSON.stringify(data), { EX }) 89 | }, 90 | 91 | async importModule(file, root = "") { 92 | root = getRoot(root) 93 | if (!/\.js$/.test(file)) { 94 | file = file + ".js" 95 | } 96 | if (fs.existsSync(`${root}/${file}`)) { 97 | try { 98 | let data = await import(`file://${root}/${file}?t=${new Date() * 1}`) 99 | return data || {} 100 | } catch (e) { 101 | console.log(e) 102 | } 103 | } 104 | return {} 105 | }, 106 | 107 | async importDefault(file, root) { 108 | let ret = await Data.importModule(file, root) 109 | return ret.default || {} 110 | }, 111 | 112 | async import(name) { 113 | return await Data.importModule(`components/optional-lib/${name}.js`) 114 | }, 115 | 116 | async importCfg(key) { 117 | let sysCfg = await Data.importModule(`config/system/${key}_system.js`) 118 | let diyCfg = await Data.importModule(`config/${key}.js`) 119 | if (diyCfg.isSys) { 120 | console.error(`${Plugin_Name}: config/${key}.js无效,已忽略`) 121 | console.error(`如需配置请复制config/${key}_default.js为config/${key}.js,请勿复制config/system下的系统文件`) 122 | diyCfg = {} 123 | } 124 | return { 125 | sysCfg, 126 | diyCfg 127 | } 128 | }, 129 | 130 | /* 131 | * 返回一个从 target 中选中的属性的对象 132 | * 133 | * keyList : 获取字段列表,逗号分割字符串 134 | * key1, key2, toKey1:fromKey1, toKey2:fromObj.key 135 | * 136 | * defaultData: 当某个字段为空时会选取defaultData的对应内容 137 | * toKeyPrefix:返回数据的字段前缀,默认为空。defaultData中的键值无需包含toKeyPrefix 138 | * 139 | * */ 140 | 141 | getData(target, keyList = "", cfg = {}) { 142 | target = target || {} 143 | let defaultData = cfg.defaultData || {} 144 | let ret = {} 145 | // 分割逗号 146 | if (typeof (keyList) === "string") { 147 | keyList = keyList.split(",") 148 | } 149 | 150 | _.forEach(keyList, (keyCfg) => { 151 | // 处理通过:指定 toKey & fromKey 152 | let _keyCfg = keyCfg.split(":") 153 | let keyTo = _keyCfg[0].trim() 154 | let keyFrom = (_keyCfg[1] || _keyCfg[0]).trim() 155 | let keyRet = keyTo 156 | if (cfg.lowerFirstKey) { 157 | keyRet = _.lowerFirst(keyRet) 158 | } 159 | if (cfg.keyPrefix) { 160 | keyRet = cfg.keyPrefix + keyRet 161 | } 162 | // 通过Data.getVal获取数据 163 | ret[keyRet] = Data.getVal(target, keyFrom, defaultData[keyTo], cfg) 164 | }) 165 | return ret 166 | }, 167 | 168 | getVal(target, keyFrom, defaultValue) { 169 | return _.get(target, keyFrom, defaultValue) 170 | }, 171 | 172 | // 异步池,聚合请求 173 | async asyncPool(poolLimit, array, iteratorFn) { 174 | const ret = [] // 存储所有的异步任务 175 | const executing = [] // 存储正在执行的异步任务 176 | for (const item of array) { 177 | // 调用iteratorFn函数创建异步任务 178 | const p = Promise.resolve().then(() => iteratorFn(item, array)) 179 | // 保存新的异步任务 180 | ret.push(p) 181 | 182 | // 当poolLimit值小于或等于总任务个数时,进行并发控制 183 | if (poolLimit <= array.length) { 184 | // 当任务完成后,从正在执行的任务数组中移除已完成的任务 185 | const e = p.then(() => executing.splice(executing.indexOf(e), 1)) 186 | executing.push(e) // 保存正在执行的异步任务 187 | if (executing.length >= poolLimit) { 188 | // 等待较快的任务执行完成 189 | await Promise.race(executing) 190 | } 191 | } 192 | } 193 | return Promise.all(ret) 194 | }, 195 | 196 | // sleep 197 | sleep(ms) { 198 | return new Promise((resolve) => setTimeout(resolve, ms)) 199 | }, 200 | 201 | // 获取默认值 202 | def() { 203 | for (let idx in arguments) { 204 | if (!_.isUndefined(arguments[idx])) { 205 | return arguments[idx] 206 | } 207 | } 208 | }, 209 | 210 | // 循环字符串回调 211 | eachStr: (arr, fn) => { 212 | if (_.isString(arr)) { 213 | arr = arr.replace(/\s*(;|;|、|,)\s*/, ",") 214 | arr = arr.split(",") 215 | } else if (_.isNumber(arr)) { 216 | arr = [ arr.toString() ] 217 | } 218 | _.forEach(arr, (str, idx) => { 219 | if (!_.isUndefined(str)) { 220 | fn(str.trim ? str.trim() : str, idx) 221 | } 222 | }) 223 | }, 224 | 225 | regRet(reg, txt, idx) { 226 | if (reg && txt) { 227 | let ret = reg.exec(txt) 228 | if (ret && ret[idx]) { 229 | return ret[idx] 230 | } 231 | } 232 | return false 233 | }, 234 | /** 235 | * 读取文件夹和子文件夹指定后缀文件名 236 | * @param directory 237 | * @param extension 238 | * @param excludeDir 239 | */ 240 | readDirRecursive(directory, extension, excludeDir) { 241 | let files = fs.readdirSync(directory) 242 | 243 | let jsFiles = files.filter(file => path.extname(file) === `.${extension}`) 244 | 245 | files.filter(file => fs.statSync(path.join(directory, file)).isDirectory()) 246 | .forEach(subdirectory => { 247 | if (subdirectory === excludeDir) { 248 | return 249 | } 250 | 251 | const subdirectoryPath = path.join(directory, subdirectory) 252 | jsFiles.push(...Data.readDirRecursive(subdirectoryPath, extension, excludeDir) 253 | .map(fileName => path.join(subdirectory, fileName))) 254 | }) 255 | 256 | return jsFiles 257 | } 258 | } 259 | 260 | export default Data 261 | -------------------------------------------------------------------------------- /components/Version.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs" 2 | import lodash from "lodash" 3 | import { Data, Plugin_Path } from "./index.js" 4 | 5 | const CHANGELOG_path = `${Plugin_Path}/CHANGELOG.md` 6 | const README_path = `${Plugin_Path}/README.md` 7 | 8 | let PluginPackagePath = `${Plugin_Path}/package.json` 9 | let PluginPackageData = JSON.parse(fs.readFileSync(PluginPackagePath, "utf8")) 10 | let packageJson = JSON.parse(fs.readFileSync(`${process.cwd()}/package.json`, "utf8")) 11 | 12 | // let yunzai_ver = packageJson.version 13 | 14 | let changelogs = [] 15 | let currentVersion 16 | let logs = {} 17 | let versionCount = 3 18 | 19 | const getLine = function(line) { 20 | line = line.replace(/(^\s*\*|\r)/g, "") 21 | line = line.replace(/\s*`([^`]+`)/g, "$1") 22 | line = line.replace(/`\s*/g, "") 23 | line = line.replace(/\s*\*\*([^*]+\*\*)/g, "$1") 24 | line = line.replace(/\*\*\s*/g, "") 25 | line = line.replace(/ⁿᵉʷ/g, "") 26 | return line 27 | } 28 | 29 | const readLogFile = function(root, versionCount = 4) { 30 | root = Data.getRoot(root) 31 | let changelogs = [] 32 | let currentVersion 33 | let logPath = `${root}/CHANGELOG.md` 34 | let logs = {} 35 | 36 | try { 37 | if (fs.existsSync(logPath)) { 38 | logs = fs.readFileSync(logPath, "utf8") || "" 39 | logs = logs.split("\n") 40 | 41 | let temp = {} 42 | let lastLine = {} 43 | lodash.forEach(logs, (line) => { 44 | if (versionCount <= -1) { 45 | return false 46 | } 47 | let versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line) 48 | if (versionRet && versionRet[1]) { 49 | let v = versionRet[1].trim() 50 | if (!currentVersion) { 51 | currentVersion = v 52 | } else { 53 | changelogs.push(temp) 54 | if (/0\s*$/.test(v) && versionCount > 0) { 55 | versionCount = 0 56 | } else { 57 | versionCount-- 58 | } 59 | } 60 | 61 | temp = { 62 | version: v, 63 | logs: [] 64 | } 65 | } else { 66 | if (!line.trim()) { 67 | return 68 | } 69 | if (/^\*/.test(line)) { 70 | lastLine = { 71 | title: getLine(line), 72 | logs: [] 73 | } 74 | temp.logs.push(lastLine) 75 | } else if (/^\s{2,}\*/.test(line)) { 76 | lastLine.logs.push(getLine(line)) 77 | } 78 | } 79 | }) 80 | } 81 | } catch (e) { 82 | // do nth 83 | } 84 | return { changelogs, currentVersion } 85 | } 86 | 87 | try { 88 | if (fs.existsSync(CHANGELOG_path)) { 89 | logs = fs.readFileSync(CHANGELOG_path, "utf8") || "" 90 | logs = logs.replace(/\t/g, " ").split("\n") 91 | let temp = {} 92 | let lastLine = {} 93 | lodash.forEach(logs, (line) => { 94 | if (versionCount < 1) { 95 | return false 96 | } 97 | let versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line.trim()) 98 | if (versionRet && versionRet[1]) { 99 | let v = versionRet[1].trim() 100 | if (!currentVersion) { 101 | currentVersion = v 102 | } else { 103 | changelogs.push(temp) 104 | if (/0\s*$/.test(v) && versionCount > 0) { 105 | // versionCount = 0 106 | versionCount-- 107 | } else { 108 | versionCount-- 109 | } 110 | } 111 | temp = { 112 | version: v, 113 | logs: [] 114 | } 115 | } else { 116 | if (!line.trim()) { 117 | return 118 | } 119 | if (/^\*/.test(line)) { 120 | lastLine = { 121 | title: getLine(line), 122 | logs: [] 123 | } 124 | if (!temp.logs) { 125 | temp = { 126 | version: line, 127 | logs: [] 128 | } 129 | } 130 | temp.logs.push(lastLine) 131 | } else if (/^\s{2,}\*/.test(line)) { 132 | lastLine.logs.push(getLine(line)) 133 | } 134 | } 135 | }) 136 | } 137 | } catch (e) { 138 | logger.error(e) 139 | // do nth 140 | } 141 | 142 | try { 143 | if (fs.existsSync(README_path)) { 144 | let README = fs.readFileSync(README_path, "utf8") || "" 145 | let reg = /版本:(.*)/.exec(README) 146 | if (reg) { 147 | currentVersion = reg[1] 148 | } 149 | } 150 | } catch (err) {} 151 | 152 | try { 153 | if (PluginPackageData.version !== currentVersion) { 154 | console.log(`[DF-Plugin] 版本号不一致,更新版本号为: ${currentVersion}`) 155 | PluginPackageData.version = currentVersion 156 | 157 | fs.writeFileSync(PluginPackagePath, JSON.stringify(PluginPackageData, null, 2), "utf8") 158 | console.log("[DF-Plugin] package.json 已更新") 159 | } 160 | } catch (error) { 161 | console.error("读取或解析 package.json 出现错误:", error) 162 | } 163 | 164 | let { author } = PluginPackageData 165 | const yunzaiVersion = packageJson.version 166 | const isV3 = yunzaiVersion[0] === "3" 167 | const isV4 = yunzaiVersion[0] === "4" 168 | let isMiao = false 169 | let isTRSS = false 170 | let isAlemonjs = false 171 | let name = "Yunzai-Bot" 172 | if (packageJson.name === "miao-yunzai") { 173 | isMiao = true 174 | name = "Miao-Yunzai" 175 | } else if (packageJson.name === "trss-yunzai") { 176 | isTRSS = true 177 | name = "TRSS-Yunzai" 178 | } else if (packageJson.name === "a-yunzai") { 179 | name = "A-Yunzai" 180 | isAlemonjs = true 181 | } 182 | 183 | let Version = { 184 | isV3, 185 | isV4, 186 | isMiao, 187 | isTRSS, 188 | name, 189 | isAlemonjs, 190 | author, 191 | get version() { 192 | return currentVersion 193 | }, 194 | get yunzai() { 195 | return yunzaiVersion 196 | }, 197 | get logs() { 198 | return changelogs 199 | }, 200 | get ver() { 201 | return currentVersion 202 | }, 203 | readLogFile 204 | } 205 | 206 | export default Version 207 | -------------------------------------------------------------------------------- /components/index.js: -------------------------------------------------------------------------------- 1 | export { Path, Plugin_Name, Plugin_Path, Poke_Path, Res_Path } from "../constants/Path.js" 2 | export { Poke_List } from "../constants/Poke.js" 3 | export { default as Config } from "./Config.js" 4 | export { default as Data } from "./Data.js" 5 | export { default as Version } from "./Version.js" 6 | export { default as common } from "../lib/common/common.js" 7 | export { default as render } from "../lib/puppeteer/render.js" 8 | export { default as request } from "../lib/request/request.js" 9 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | config*/ -------------------------------------------------------------------------------- /config/system/config.yaml: -------------------------------------------------------------------------------- 1 | # 此配置文件为系统使用,请勿修改,否则可能无法正常使用 2 | # 用户配置已移动至 Yz/config/DF-Plugin.yaml 3 | 4 | CodeUpdate: 5 | AutoTips: |- 6 | 自动检查仓库更新开关 7 | Auto: false 8 | 9 | AutoBranchTips: |- 10 | 是否自动给未配置分支的仓库添加默认分支 11 | AutoBranch: true 12 | 13 | CronTips: |- 14 | 仓库更新检查Cron表达式 15 | Github/Gitee Api 未认证用户每小时限制60(ip限制),认证用户每小时限制5000次(账号限制) 16 | Cron: "0 */30 * * * *" 17 | 18 | GiteeTokenTips: |- 19 | Gitee个人Token 20 | 获取地址: https://gitee.com/profile/personal_access_tokens 21 | GiteeToken: "" 22 | 23 | GithubTokenTips: |- 24 | Github个人Token 25 | 获取地址: https://github.com/settings/tokens 26 | GithubToken: "" 27 | 28 | GitcodeTokenTips: |- 29 | Gitcode个人Token 30 | 获取地址: https://gitcode.com/setting/token-classic 31 | GitcodeToken: "" 32 | 33 | ListTips: |- 34 | 推送配置 35 | Group: 推送的群聊列表 36 | QQ: 推送的QQ列表 37 | AutoPath: 是否自动获取已安装的插件地址并监听 38 | Exclude: 排除的仓库路径(用于忽略一些不希望被监听的插件) 39 | GithubList: 自定义关注的Github仓库路径 (格式: 所有者/存储库:分支) 40 | GiteeList: 自定义关注的Gitee仓库路径 (格式: 所有者/存储库:分支) 41 | GithubReleases: Github发行版监听仓库列表 (格式: 所有者/存储库) 42 | GiteeReleases: Gitee发行版监听仓库列表 (格式: 所有者/存储库) 43 | note: 备注 44 | List: [] 45 | 46 | other: 47 | chuoTips: |- 48 | 戳一戳开关 49 | chuo: true 50 | 51 | chuoTypeTips: |- 52 | 戳一戳图片设置 53 | 1:柴郡表情包 54 | 2:丛雨表情包 55 | 3:诗歌剧表情包 56 | 4:柚子厨表情包 57 | 5:小南梁表情包 58 | 6:小鲨鱼古拉 59 | 7:甘城猫猫 60 | 8: 龙图 61 | 9:满穗表情包 62 | 10: 猫猫虫表情 63 | 11:纳西妲表情 64 | 12;心海表情 65 | 13:fufu表情 66 | 14: ATRI表情包 67 | 15: 绫地宁宁表情包 68 | 16: 永雏塔菲表情包 69 | 17:miku表情包 70 | 18:特蕾西娅表情包 71 | 19:doro表情包 72 | 20:米塔表情包 73 | 0:自定义图片(自行在resources/poke/default中添加) 74 | all: 随机类型 75 | chuoType: 1 76 | 77 | BlackTips: |- 78 | 戳一戳随机类型黑名单 79 | Black: 80 | 81 | - default 82 | ysTips: |- 83 | 原神关键词发图 84 | ys: true 85 | 86 | Picture: 87 | openTips: |- 88 | 随机图片开关 89 | open: true 90 | 91 | DirectTips: |- 92 | 是否去除 #来张/随机 前缀 93 | Direct: false 94 | 95 | sendMaster: 96 | openTips: |- 97 | 联系主人开关 98 | open: true 99 | 100 | cdTips: |- 101 | 联系主人CD 单位:秒 填0关闭cd 102 | cd: 300 103 | 104 | MasterTips: |- 105 | 是否发送全部主人 0:仅发送首个主人 1:发送全部主人 106 | 填主人账号可以指定发送某个主人 107 | Master: 0 108 | 109 | BotIdTips: |- 110 | 指定某个Bot发送 为0时为触发Bot 111 | BotId: 0 112 | 113 | sendAvatarTips: |- 114 | 是否发送触发者的头像(微信请关闭此项) 115 | sendAvatar: true 116 | 117 | replyQQTips: |- 118 | 是否回复主人QQ号 119 | replyQQ: true 120 | 121 | banWordsTips: |- 122 | 违禁词,当消息包含下列内容将不会发送给主人 123 | banWords: 124 | - 垃圾 125 | - 你妈 126 | - nm 127 | - 月吗 128 | - 🈷️吗 129 | - 🈷吗 130 | - 约吗 131 | 132 | banUserTips: |- 133 | 禁用的用户,不允许该用户联系主人 134 | banUser: 135 | - 10086 136 | 137 | banGroupTips: |- 138 | 禁用的群聊,不允许通过该群联系主人 139 | banGroup: 140 | - 114514 141 | 142 | summary: 143 | sumTips: |- 144 | 图片外显开关 145 | sum: false 146 | 147 | typeTips: |- 148 | 外显模式 (1: 自定义外显 2: 一言接口 3:列表随机) 149 | type: 2 150 | 151 | textTips: |- 152 | 自定义外显 153 | text: "Ciallo~(∠・ω< )⌒☆" 154 | 155 | listTips: |- 156 | 自定义外显随机列表 157 | list: 158 | - 你干嘛~ 159 | - 我喜欢你 160 | 161 | apiTips: |- 162 | 一言接口 163 | api: "https://v1.hitokoto.cn/?encode=text" 164 | 165 | proxy: 166 | openTips: |- 167 | 是否代理大部分请求 168 | open: false 169 | 170 | urlTips: |- 171 | 代理地址 172 | url: "http://127.0.0.1:7890" -------------------------------------------------------------------------------- /config/system/help_system.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 此配置文件为系统使用,请勿修改,否则可能无法正常使用 3 | * */ 4 | 5 | export const helpCfg = { 6 | title: "DF帮助", 7 | subTitle: "[DF插件] Yunzai-Bot & DF-Plugin", 8 | columnCount: 4, 9 | colWidth: 300, 10 | theme: "all", 11 | themeExclude: [ "default" ], 12 | style: { 13 | fontColor: "#ceb78b", 14 | descColor: "#eee", 15 | contBgColor: "rgba(6, 21, 31, .5)", 16 | contBgBlur: 0, 17 | headerBgColor: "rgba(255, 222, 142, 0.44)", 18 | rowBgColor1: "rgba(255, 166, 99, 0.23)", 19 | rowBgColor2: "rgba(251, 113, 107, 0.35)" 20 | } 21 | } 22 | 23 | export const helpList = [ 24 | { 25 | "group": "功能类", 26 | "list": [ 27 | { 28 | "icon": 2, 29 | "title": "戳一戳机器人", 30 | "desc": "戳一戳机器人发送随机表情包" 31 | }, 32 | { 33 | "icon": 4, 34 | "title": "拾取关键词原神", 35 | "desc": "本来聊得好好的,突然有人聊起了原神,搞得大家都不高兴" 36 | }, 37 | { 38 | "icon": 7, 39 | "title": "Git仓库更新推送", 40 | "desc": "前往Guoba或插件内配置" 41 | } 42 | ] 43 | }, 44 | { 45 | "group": "随机图片类", 46 | "list": [ 47 | { 48 | "icon": 35, 49 | "title": "#来张制服", 50 | "desc": "随机jk图片" 51 | }, 52 | { 53 | "icon": 5, 54 | "title": "#来张黑丝", 55 | "desc": "顾名思义" 56 | }, 57 | { 58 | "icon": 3, 59 | "title": "#来张cos", 60 | "desc": "随机cos图片" 61 | }, 62 | { 63 | "icon": 8, 64 | "title": "#来张腿子", 65 | "desc": "kkt" 66 | }, 67 | { 68 | "icon": 16, 69 | "title": "#随机从雨", 70 | "desc": "狗修金" 71 | }, 72 | { 73 | "icon": 33, 74 | "title": "#随机诗歌剧", 75 | "desc": "曼波" 76 | } 77 | ] 78 | }, 79 | { 80 | "group": "给主人带话", 81 | "list": [ 82 | { 83 | "icon": 13, 84 | "title": "#联系主人", 85 | "desc": "给主人带句话" 86 | }, 87 | { 88 | "icon": 14, 89 | "title": "#回复", 90 | "desc": "主人回复群友的消息" 91 | } 92 | ] 93 | }, 94 | { 95 | "group": "图片外显", 96 | "auth": "master", 97 | "list": [ 98 | { 99 | "icon": 7, 100 | "title": "#开启/关闭图片外显", 101 | "desc": "开关外显功能" 102 | }, 103 | { 104 | "icon": 4, 105 | "title": "#设置外显+文字", 106 | "desc": "设置自定义外显文本" 107 | }, 108 | { 109 | "icon": 6, 110 | "title": "#切换外显模式", 111 | "desc": "切换一言/文本模式" 112 | } 113 | ] 114 | }, 115 | { 116 | "group": "主人功能", 117 | "auth": "master", 118 | "list": [ 119 | { 120 | "icon": 12, 121 | "title": "#DF(强制)?更新", 122 | "desc": "拉取Git更新" 123 | }, 124 | { 125 | "icon": 2, 126 | "title": "#DF更新图库", 127 | "desc": "更新戳一戳图库" 128 | } 129 | ] 130 | } 131 | ] 132 | 133 | export const isSys = true 134 | -------------------------------------------------------------------------------- /config/system/请勿修改此目录下的文件.txt: -------------------------------------------------------------------------------- 1 | 此目录为系统配置目录 2 | 请勿修改此目录下的文件,否则可能导致工作不正常 3 | -------------------------------------------------------------------------------- /constants/Path.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url" 2 | import { join, dirname, basename } from "node:path" 3 | 4 | export const Path = process.cwd() 5 | export const Plugin_Path = join(dirname(fileURLToPath(import.meta.url)), "..").replace(/\\/g, "/") 6 | export const Plugin_Name = basename(Plugin_Path) 7 | export const Res_Path = `${Plugin_Path}/resources` 8 | export const Poke_Path = `${Res_Path}/poke` 9 | -------------------------------------------------------------------------------- /constants/Poke.js: -------------------------------------------------------------------------------- 1 | import { Poke_Path } from "./Path.js" 2 | import fs from "node:fs" 3 | 4 | let Poke_List = [ 5 | "default", 6 | "柴郡猫", 7 | "丛雨", 8 | "诗歌剧", 9 | "柚子厨", 10 | "小南梁", 11 | "古拉", 12 | "甘城猫猫", 13 | "龙图", 14 | "满穗", 15 | "猫猫虫", 16 | "纳西妲", 17 | "心海", 18 | "fufu", 19 | "ATRI", 20 | "绫地宁宁", 21 | "永雏塔菲", 22 | "miku", 23 | "特蕾西娅", 24 | "doro", 25 | "米塔", 26 | "冬川花璃", 27 | "neuro", 28 | "kipfel", 29 | "mygo" 30 | ] 31 | 32 | /** 33 | * 兼容用户自建目录 34 | * 用户可以在resources/poke下自建多个目录用于存放图片 35 | */ 36 | if (fs.existsSync(Poke_Path)) { 37 | const directories = fs.readdirSync(Poke_Path, { withFileTypes: true }) 38 | .filter(dirent => dirent.isDirectory() && dirent.name !== ".git") 39 | .map(dirent => dirent.name) 40 | Poke_List = Array.from(new Set([ ...Poke_List, ...directories ])) 41 | } 42 | 43 | export { Poke_List } 44 | -------------------------------------------------------------------------------- /guoba.support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 支持锅巴 3 | * 锅巴插件:https://gitee.com/guoba-yunzai/guoba-plugin.git 4 | * 组件类型,可参考 https://antdv.com/components/overview-cn/ 5 | */ 6 | 7 | export { supportGuoba } from "./guoba/index.js" 8 | -------------------------------------------------------------------------------- /guoba/configInfo.js: -------------------------------------------------------------------------------- 1 | import { schemas, getConfigData, setConfigData } from "./schemas/index.js" 2 | 3 | export default { 4 | schemas, 5 | getConfigData, 6 | setConfigData 7 | } 8 | -------------------------------------------------------------------------------- /guoba/index.js: -------------------------------------------------------------------------------- 1 | import pluginInfo from "./pluginInfo.js" 2 | import configInfo from "./configInfo.js" 3 | 4 | export function supportGuoba() { 5 | return { 6 | pluginInfo, 7 | configInfo 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /guoba/pluginInfo.js: -------------------------------------------------------------------------------- 1 | import { Res_Path } from "#components" 2 | 3 | export default { 4 | name: "DF-Plugin", 5 | title: "DF-Plugin", 6 | description: "提供Yunzai-Bot拓展功能", 7 | author: "@等风来", 8 | authorLink: "https://gitee.com/DengFengLai-F", 9 | link: "https://github.com/DenFengLai/DF-Plugin", 10 | isV3: true, 11 | isV2: false, 12 | showInMenu: "auto", 13 | iconPath: `${Res_Path}/img/Roxy.png`, 14 | iconColor: "#d19f56" 15 | } 16 | -------------------------------------------------------------------------------- /guoba/schemas/CodeUpdata.js: -------------------------------------------------------------------------------- 1 | import { PluginPath } from "#model" 2 | 3 | export default [ 4 | { 5 | component: "SOFT_GROUP_BEGIN", 6 | label: "Git仓库监听配置" 7 | }, 8 | { 9 | field: "CodeUpdate.Auto", 10 | label: "自动检查开关", 11 | component: "Switch" 12 | }, 13 | { 14 | field: "CodeUpdate.AutoBranch", 15 | label: "自动获取远程默认分支", 16 | bottomHelpMessage: "在未指定分支的情况下,启动时自动获取远程仓库的默认分支", 17 | component: "Switch" 18 | }, 19 | { 20 | field: "CodeUpdate.Cron", 21 | label: "自动检查定时表达式", 22 | helpMessage: "修改后重启生效", 23 | bottomHelpMessage: "自动检查Cron表达式", 24 | component: "EasyCron", 25 | componentProps: { 26 | placeholder: "请输入Cron表达式" 27 | } 28 | }, 29 | { 30 | field: "CodeUpdate.GithubToken", 31 | label: "Github Api Token", 32 | helpMessage: "用于请求Github Api", 33 | bottomHelpMessage: "填写后可解除请求速率限制和监听私库,获取地址:https://github.com/settings/tokens", 34 | component: "InputPassword", 35 | componentProps: { 36 | placeholder: "请输入Github Token" 37 | } 38 | }, 39 | { 40 | field: "CodeUpdate.GiteeToken", 41 | label: "Gitee Api Token", 42 | helpMessage: "用于请求 Gitee Api", 43 | bottomHelpMessage: "填写后可解除请求速率限制和监听私库,获取地址:https://gitee.com/profile/personal_access_tokens", 44 | component: "InputPassword", 45 | componentProps: { 46 | placeholder: "请输入Gitee Token" 47 | } 48 | }, 49 | { 50 | field: "CodeUpdate.GitcodeToken", 51 | label: "Gitcode Api Token", 52 | helpMessage: "用于请求Gitcode Api", 53 | bottomHelpMessage: "获取地址:https://gitcode.com/setting/token-classic", 54 | component: "InputPassword", 55 | componentProps: { 56 | placeholder: "请输入Token" 57 | } 58 | }, 59 | { 60 | field: "CodeUpdate.List", 61 | label: "推送列表", 62 | bottomHelpMessage: "Git仓库推送列表", 63 | component: "GSubForm", 64 | componentProps: { 65 | multiple: true, 66 | schemas: [ 67 | { 68 | field: "Group", 69 | helpMessage: "检测到仓库更新后推送的群列表", 70 | label: "推送群", 71 | componentProps: { 72 | placeholder: "点击选择要推送的群" 73 | }, 74 | component: "GSelectGroup" 75 | }, 76 | { 77 | field: "QQ", 78 | helpMessage: "检测到仓库更新后推送的用户列表", 79 | label: "推送好友", 80 | componentProps: { 81 | placeholder: "点击选择要推送的好友" 82 | }, 83 | component: "GSelectFriend" 84 | }, 85 | { 86 | field: "AutoPath", 87 | label: "获取已安装的插件", 88 | component: "Switch" 89 | }, 90 | { 91 | field: "Exclude", 92 | label: "排除的插件", 93 | component: "Select", 94 | componentProps: { 95 | allowClear: true, 96 | mode: "tags", 97 | get options() { 98 | return Array.from(new Set(Object.values(PluginPath).flat())).map((name) => ({ value: name })) 99 | } 100 | } 101 | }, 102 | { 103 | field: "GithubList", 104 | label: "Github仓库路径", 105 | bottomHelpMessage: "格式:用户名/仓库名:分支名,如: github.com/DenFengLai/DF-Plugin 则填 DenFengLai/DF-Plugin", 106 | component: "GTags", 107 | componentProps: { 108 | allowAdd: true, 109 | allowDel: true 110 | }, 111 | showPrompt: true, 112 | promptProps: { 113 | content: "请输入 所有者/存储库:分支", 114 | placeholder: "请输入仓库路径", 115 | okText: "添加", 116 | rules: [ { required: true, message: "不可以为空哦" } ] 117 | } 118 | }, 119 | { 120 | field: "GithubReleases", 121 | label: "Github发行版仓库路径", 122 | bottomHelpMessage: "格式:所有者/存储库:分支", 123 | component: "GTags", 124 | componentProps: { 125 | allowAdd: true, 126 | allowDel: true 127 | }, 128 | showPrompt: true, 129 | promptProps: { 130 | content: "请输入 所有者/存储库:分支", 131 | placeholder: "请输入仓库路径", 132 | okText: "添加", 133 | rules: [ { required: true, message: "不可以为空哦" } ] 134 | } 135 | }, 136 | { 137 | field: "GiteeList", 138 | label: "Gitee仓库路径", 139 | bottomHelpMessage: "格式:所有者/存储库:分支", 140 | component: "GTags", 141 | componentProps: { 142 | allowAdd: true, 143 | allowDel: true 144 | }, 145 | showPrompt: true, 146 | promptProps: { 147 | content: "请输入 所有者/存储库:分支", 148 | placeholder: "请输入仓库路径", 149 | okText: "添加", 150 | rules: [ { required: true, message: "不可以为空哦" } ] 151 | } 152 | }, 153 | { 154 | field: "GiteeReleases", 155 | label: "Gitee发行版仓库路径", 156 | bottomHelpMessage: "格式:所有者/存储库:分支", 157 | component: "GTags", 158 | componentProps: { 159 | allowAdd: true, 160 | allowDel: true 161 | }, 162 | showPrompt: true, 163 | promptProps: { 164 | content: "请输入 所有者/存储库:分支", 165 | placeholder: "请输入仓库路径", 166 | okText: "添加", 167 | rules: [ { required: true, message: "不可以为空哦" } ] 168 | } 169 | }, 170 | { 171 | field: "GitcodeList", 172 | label: "Gitcode仓库路径", 173 | bottomHelpMessage: "格式:所有者/存储库:分支", 174 | component: "GTags", 175 | componentProps: { 176 | allowAdd: true, 177 | allowDel: true 178 | }, 179 | showPrompt: true, 180 | promptProps: { 181 | content: "请输入 所有者/存储库:分支", 182 | placeholder: "请输入仓库路径", 183 | okText: "添加", 184 | rules: [ { required: true, message: "不可以为空哦" } ] 185 | } 186 | }, 187 | { 188 | field: "note", 189 | label: "备注", 190 | component: "Input" 191 | } 192 | ] 193 | } 194 | } 195 | ] 196 | -------------------------------------------------------------------------------- /guoba/schemas/Picture.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | component: "SOFT_GROUP_BEGIN", 4 | label: "随机图片配置" 5 | }, 6 | { 7 | field: "Picture.open", 8 | label: "随机图片开关", 9 | component: "Switch" 10 | }, 11 | { 12 | field: "Picture.Direct", 13 | label: "是否去除 #来张/随机 前缀", 14 | component: "Switch" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /guoba/schemas/Poke.js: -------------------------------------------------------------------------------- 1 | import { Poke_List } from "#components" 2 | 3 | export default [ 4 | { 5 | component: "SOFT_GROUP_BEGIN", 6 | label: "戳一戳配置" 7 | }, 8 | { 9 | field: "other.chuo", 10 | label: "戳一戳开关", 11 | component: "Switch" 12 | }, 13 | { 14 | field: "other.chuoType", 15 | label: "戳一戳图片类型", 16 | bottomHelpMessage: "自定义图片需在resources/poke/default中添加", 17 | component: "RadioGroup", 18 | required: true, 19 | componentProps: { 20 | options: [ 21 | { label: "随机类型", value: "all" }, 22 | ...Poke_List.map((name, id) => ({ label: name, value: id })) 23 | ] 24 | } 25 | }, 26 | { 27 | field: "other.Black", 28 | label: "随机类型排除列表", 29 | bottomHelpMessage: "设置戳一戳类型为随机时将不会随机到以下类型", 30 | component: "Select", 31 | componentProps: { 32 | allowClear: true, 33 | mode: "tags", 34 | options: Poke_List.map((name) => ({ value: name })) 35 | } 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /guoba/schemas/SendMaster.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | component: "SOFT_GROUP_BEGIN", 4 | label: "联系主人配置" 5 | }, 6 | { 7 | field: "sendMaster.open", 8 | label: "功能开关", 9 | bottomHelpMessage: "允许用户联系主人", 10 | component: "Switch" 11 | }, 12 | { 13 | field: "sendMaster.cd", 14 | label: "触发冷却", 15 | helpMessage: "主人不受限制", 16 | bottomHelpMessage: "单位:秒", 17 | component: "InputNumber", 18 | required: true, 19 | componentProps: { 20 | min: 1, 21 | placeholder: "请输入冷却时间" 22 | } 23 | }, 24 | { 25 | field: "sendMaster.Master", 26 | label: "主人配置", 27 | helpMessage: "填主人QQ可发送某个指定主人", 28 | bottomHelpMessage: "0:仅发送首个主人 1:发送全部主人 QQ号:发送指定QQ号", 29 | component: "Input", 30 | required: true, 31 | componentProps: { 32 | placeholder: "请输入主人QQ或配置项" 33 | } 34 | }, 35 | { 36 | field: "sendMaster.BotId", 37 | label: "Bot配置", 38 | bottomHelpMessage: "指定某个Bot发送,为0时为触发Bot", 39 | component: "Input", 40 | required: true, 41 | componentProps: { 42 | placeholder: "请输入Bot账号或配置项" 43 | } 44 | }, 45 | { 46 | field: "sendMaster.sendAvatar", 47 | label: "消息附带触发者头像", 48 | bottomHelpMessage: "微信Bot如果遇到报错请关闭此项。", 49 | component: "Switch" 50 | }, 51 | { 52 | field: "sendMaster.replyQQ", 53 | label: "是否回复主人账号", 54 | component: "Switch" 55 | }, 56 | { 57 | field: "sendMaster.banWords", 58 | label: "违禁词", 59 | bottomHelpMessage: "当消息包含下列内容时将不会发送给主人", 60 | component: "GTags", 61 | componentProps: { 62 | allowAdd: true, 63 | allowDel: true 64 | } 65 | }, 66 | { 67 | field: "sendMaster.banUser", 68 | label: "禁用用户", 69 | bottomHelpMessage: "不允许该用户联系主人", 70 | component: "GTags", 71 | componentProps: { 72 | allowAdd: true, 73 | allowDel: true 74 | } 75 | }, 76 | { 77 | field: "sendMaster.banGroup", 78 | label: "禁用群", 79 | helpMessage: "不允许通过该群联系主人的群聊", 80 | componentProps: { 81 | placeholder: "点击选择要禁用的群" 82 | }, 83 | component: "GSelectGroup" 84 | } 85 | ] 86 | -------------------------------------------------------------------------------- /guoba/schemas/Summary.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | component: "SOFT_GROUP_BEGIN", 4 | label: "图片外显配置" 5 | }, 6 | { 7 | field: "summary.sum", 8 | label: "图片外显开关", 9 | component: "Switch" 10 | }, 11 | { 12 | field: "summary.type", 13 | label: "外显模式", 14 | bottomHelpMessage: "可选自定义文本或使用一言接口", 15 | component: "RadioGroup", 16 | required: true, 17 | componentProps: { 18 | options: [ 19 | { label: "自定义文字", value: 1 }, 20 | { label: "使用一言接口", value: 2 }, 21 | { label: "使用自定义列表", value: 3 } 22 | ] 23 | } 24 | }, 25 | { 26 | field: "summary.list", 27 | label: "外显随机文字列表", 28 | bottomHelpMessage: "外显模式设置成3后将随机返回列表里的随机一项", 29 | component: "GTags", 30 | componentProps: { 31 | allowAdd: true, 32 | allowDel: true 33 | } 34 | }, 35 | { 36 | field: "summary.text", 37 | label: "自定义外显文字", 38 | helpMessage: "输入文字可在发送图片时显示", 39 | bottomHelpMessage: "设置外显类型为自定义文字时可用", 40 | component: "Input", 41 | required: true, 42 | componentProps: { 43 | placeholder: "请输入文字外显" 44 | } 45 | }, 46 | { 47 | field: "summary.api", 48 | label: "一言接口地址", 49 | helpMessage: "图片外显请求的接口地址", 50 | bottomHelpMessage: "无特殊情况不要改", 51 | component: "Input", 52 | required: true, 53 | componentProps: { 54 | placeholder: "请输入接口地址" 55 | } 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /guoba/schemas/index.js: -------------------------------------------------------------------------------- 1 | import CodeUpdata from "./CodeUpdata.js" 2 | import Picture from "./Picture.js" 3 | import Poke from "./Poke.js" 4 | import other from "./other.js" 5 | import sendMaster from "./SendMaster.js" 6 | import summary from "./Summary.js" 7 | import proxy from "./proxy.js" 8 | import { Config } from "#components" 9 | 10 | export const schemas = [ 11 | sendMaster, 12 | Poke, 13 | CodeUpdata, 14 | Picture, 15 | summary, 16 | proxy, 17 | other 18 | ].flat() 19 | 20 | export function getConfigData() { 21 | const configKeys = [ "other", "sendMaster", "CodeUpdate", "summary", "Picture", "proxy" ] 22 | return configKeys.reduce((acc, key) => { 23 | acc[key] = Config[key] 24 | return acc 25 | }, {}) 26 | } 27 | 28 | export async function setConfigData(data, { Result }) { 29 | for (let key in data) 30 | Config.modify(...key.split("."), data[key]) 31 | return Result.ok({}, "Ciallo~(∠・ω< )⌒☆") 32 | } 33 | -------------------------------------------------------------------------------- /guoba/schemas/other.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | component: "SOFT_GROUP_BEGIN", 4 | label: "其他配置" 5 | }, 6 | { 7 | field: "other.ys", 8 | label: "原神关键词发图", 9 | helpMessage: "无用的功能+1", 10 | component: "Switch" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /guoba/schemas/proxy.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | component: "SOFT_GROUP_BEGIN", 4 | label: "代理配置" 5 | }, 6 | { 7 | field: "proxy.open", 8 | label: "是否代理部分请求", 9 | component: "Switch" 10 | }, 11 | { 12 | field: "proxy.url", 13 | component: "Input", 14 | label: "代理地址", 15 | helpMessage: "如: http://127.0.0.1:7890" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _oo0oo_ 3 | * o8888888o 4 | * 88" . "88 5 | * (| -_- |) 6 | * 0\ = /0 7 | * ___/`---'\___ 8 | * .' \\| |// '. 9 | * / \\||| : |||// \ 10 | * / _||||| -:- |||||- \ 11 | * | | \\\ - /// | | 12 | * | \_| ''\---/'' |_/ | 13 | * \ .-\__ '-' ___/-. / 14 | * ___'. .' /--.--\ `. .'___ 15 | * ."" '< `.___\_<|>_/___.' >' "". 16 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 17 | * \ \ `_. \_ __\ /__ _/ .-` / / 18 | * =====`-.____`.___ \_____/___.-`___.-'===== 19 | * `=---=' 20 | * 21 | * 22 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | * 24 | * 佛祖保佑 永不宕机 永无BUG 25 | * 26 | * 佛曰: 27 | * 写字楼里写字间,写字间里程序员; 28 | * 程序人员写程序,又拿程序换酒钱。 29 | * 酒醒只在网上坐,酒醉还来网下眠; 30 | * 酒醉酒醒日复日,网上网下年复年。 31 | * 但愿老死电脑间,不愿鞠躬老板前; 32 | * 奔驰宝马贵者趣,公交自行程序员。 33 | * 别人笑我忒疯癫,我笑自己命太贱; 34 | * 不见满街漂亮妹,哪个归得程序员? 35 | */ 36 | 37 | import Version from "./components/Version.js" 38 | import { Plugin_Name as AppName } from "#components" 39 | import { loadApps, logSuccess } from "./lib/load/loadApps.js" 40 | 41 | let apps, loadedFilesCount = 0, loadedFilesCounterr = 0 42 | 43 | try { 44 | const { 45 | apps: loadedApps, 46 | loadedFilesCount: count, 47 | loadedFilesCounterr: counterr 48 | } = await loadApps({ AppsName: "apps" }) 49 | 50 | apps = loadedApps 51 | loadedFilesCount = count 52 | loadedFilesCounterr = counterr 53 | logSuccess( 54 | `${AppName} v${Version.ver} 载入成功!`, 55 | `作者:${Version.author}`, 56 | `共加载了 ${loadedFilesCount} 个插件文件,${loadedFilesCounterr} 个失败` 57 | ) 58 | } catch (error) { 59 | logger.error(`${AppName}插件加载失败:`, error) 60 | } 61 | 62 | export { apps } 63 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "#components": [ "components/index.js" ], 6 | "#model": [ "model/index.js" ] 7 | } 8 | }, 9 | "exclude": [ "node_modules", "data", "resources", "temp" ] 10 | } 11 | -------------------------------------------------------------------------------- /lib/common/common.js: -------------------------------------------------------------------------------- 1 | import Config from "../../components/Config.js" 2 | import moment from "moment" 3 | import render from "../puppeteer/render.js" 4 | 5 | /** 6 | * 休眠函数 7 | * @param {number} ms - 毫秒 8 | */ 9 | function sleep(ms) { 10 | return new Promise((resolve) => setTimeout(resolve, ms)) 11 | } 12 | 13 | /** 14 | * 处理时间 15 | * @param {string} date 时间戳 16 | * @returns {string} 多久前 17 | */ 18 | function timeAgo(date) { 19 | const now = moment() 20 | const duration = moment.duration(now.diff(date)) 21 | const years = duration.years() 22 | const months = duration.months() 23 | const days = duration.days() 24 | const hours = duration.hours() 25 | const minutes = duration.minutes() 26 | 27 | if (years >= 2) { 28 | return "两年以前" 29 | } else if (years >= 1) { 30 | return "1年前" 31 | } else if (months >= 1) { 32 | return `${months}个月前` 33 | } else if (days >= 1) { 34 | return `${days}天前` 35 | } else if (hours >= 1) { 36 | return `${hours}小时前` 37 | } else if (minutes >= 1) { 38 | return `${minutes}分钟前` 39 | } else { 40 | return "刚刚" 41 | } 42 | } 43 | 44 | /** 45 | * 处理消息内容并返回源消息数组 46 | * @param {object} e - 消息事件 47 | * @param {RegExp} Reg - 正则表达式 48 | * @returns {object} message - 处理后的消息内容数组 49 | */ 50 | function Replace(e, Reg) { 51 | let message = e.message.filter((item) => item.type !== "at") 52 | 53 | let alias = [] 54 | if (e.hasAlias && e.isGroup) { 55 | const groupCfg = Config.getGroup(e.group_id, e.self_id) 56 | alias = Array.isArray(groupCfg.botAlias) ? groupCfg.botAlias : [ groupCfg.botAlias ] 57 | } 58 | 59 | message = message.filter((item) => { 60 | if (item.type === "text") { 61 | if (Reg) item.text = item.text.replace(Reg, "").trim() 62 | 63 | if (!item.text) return false 64 | 65 | for (let name of alias) { 66 | if (item.text.startsWith(name)) { 67 | item.text = item.text.slice(name.length).trim() 68 | break 69 | } 70 | } 71 | } else if (item.url) { 72 | item.file = item.url 73 | } 74 | 75 | return true 76 | }) 77 | 78 | return message 79 | } 80 | 81 | export default { 82 | render, 83 | sleep, 84 | Replace, 85 | timeAgo 86 | } 87 | -------------------------------------------------------------------------------- /lib/load/loadApps.js: -------------------------------------------------------------------------------- 1 | import path from "node:path" 2 | import chalk from "chalk" 3 | import fs from "node:fs/promises" 4 | import { Plugin_Name as AppName, Version } from "#components" 5 | 6 | const moduleCache = new Map() 7 | const startTime = Date.now() 8 | 9 | async function loadApps({ AppsName }) { 10 | const filepath = path.resolve("plugins", AppName, AppsName) 11 | const apps = {} 12 | let loadedFilesCount = 0 13 | let loadedFilesCounterr = 0 14 | const packageErr = [] 15 | 16 | try { 17 | const jsFilePaths = await traverseDirectory(filepath) 18 | await Promise.all(jsFilePaths.map(async(item) => { 19 | try { 20 | const allExport = moduleCache.get(item) ?? await import(`file://${item}`) 21 | moduleCache.set(item, allExport) 22 | 23 | for (const [ key, value ] of Object.entries(allExport)) { 24 | if (typeof value === "function" && value.prototype) { 25 | if (!apps[key]) { 26 | apps[key] = value 27 | loadedFilesCount++ 28 | } else { 29 | logDuplicateExport(item, key) 30 | loadedFilesCounterr++ 31 | } 32 | } 33 | } 34 | } catch (error) { 35 | logPluginError(item, error, packageErr) 36 | loadedFilesCounterr++ 37 | } 38 | })) 39 | } catch (error) { 40 | logger.error("读取插件目录失败:", error.message) 41 | } 42 | 43 | packageTips(packageErr) 44 | return { apps, loadedFilesCount, loadedFilesCounterr } 45 | } 46 | 47 | async function traverseDirectory(dir) { 48 | try { 49 | const files = await fs.readdir(dir, { withFileTypes: true }) 50 | const jsFiles = [] 51 | for (const file of files) { 52 | const pathname = path.join(dir, file.name) 53 | if (file.isDirectory()) { 54 | jsFiles.push(...await traverseDirectory(pathname)) 55 | } else if (file.name.endsWith(".js")) { 56 | jsFiles.push(pathname) 57 | } 58 | } 59 | return jsFiles 60 | } catch (error) { 61 | logger.error("读取插件目录失败:", error.message) 62 | return [] 63 | } 64 | } 65 | 66 | // eslint-disable-next-line 67 | var _0xd29d26=_0x5a0b;(function(_0x1969b2,_0x47b5ea){var _0x473234=_0x5a0b,_0xdb8bf1=_0x1969b2();while(!![]){try{var _0x556be0=-parseInt(_0x473234(0xad))/0x1+parseInt(_0x473234(0xa6))/0x2*(parseInt(_0x473234(0xb0))/0x3)+parseInt(_0x473234(0xa9))/0x4+parseInt(_0x473234(0xa8))/0x5*(-parseInt(_0x473234(0xae))/0x6)+parseInt(_0x473234(0xb2))/0x7*(-parseInt(_0x473234(0xac))/0x8)+-parseInt(_0x473234(0xab))/0x9*(-parseInt(_0x473234(0xb1))/0xa)+-parseInt(_0x473234(0xaf))/0xb*(-parseInt(_0x473234(0xa5))/0xc);if(_0x556be0===_0x47b5ea)break;else _0xdb8bf1['push'](_0xdb8bf1['shift']());}catch(_0x588262){_0xdb8bf1['push'](_0xdb8bf1['shift']());}}}(_0x452f,0x648eb));function _0x5a0b(_0x17e695,_0x49e018){var _0x452f56=_0x452f();return _0x5a0b=function(_0x5a0b6b,_0x1ddb07){_0x5a0b6b=_0x5a0b6b-0xa5;var _0x3ab7ef=_0x452f56[_0x5a0b6b];return _0x3ab7ef;},_0x5a0b(_0x17e695,_0x49e018);}(Version[_0xd29d26(0xa7)]||Version['isAlemonjs'])&&logErrorAndExit(AppName+_0xd29d26(0xaa),'错误:不支持该版本');function _0x452f(){var _0x548a4a=['3034584UcLjEw','\x20载入失败!','4247973zXTApS','8DNKfSr','379941bpZSVx','8394PqKnxG','165MUkQjx','47703SkneDb','10iHgHSf','2790599KoMMRO','453084HdQdcC','8PGiofT','isV4','2395zGojNq'];_0x452f=function(){return _0x548a4a;};return _0x452f();} 68 | 69 | // eslint-disable-next-line 70 | (function(_0x3e6b74,_0x51923c){var _0x287df8=_0x5dd5,_0x326384=_0x3e6b74();while(!![]){try{var _0x2c4885=parseInt(_0x287df8(0x154))/0x1*(parseInt(_0x287df8(0x15f))/0x2)+parseInt(_0x287df8(0x158))/0x3*(parseInt(_0x287df8(0x157))/0x4)+parseInt(_0x287df8(0x159))/0x5*(-parseInt(_0x287df8(0x15e))/0x6)+-parseInt(_0x287df8(0x162))/0x7*(parseInt(_0x287df8(0x155))/0x8)+parseInt(_0x287df8(0x15b))/0x9+parseInt(_0x287df8(0x15d))/0xa+parseInt(_0x287df8(0x160))/0xb;if(_0x2c4885===_0x51923c)break;else _0x326384['push'](_0x326384['shift']());}catch(_0x5eb5f2){_0x326384['push'](_0x326384['shift']());}}}(_0x3283,0x19134));function _0x5dd5(_0x301a7b,_0x25abfd){var _0x328352=_0x3283();return _0x5dd5=function(_0x5dd569,_0x4f661d){_0x5dd569=_0x5dd569-0x154;var _0x133f74=_0x328352[_0x5dd569];return _0x133f74;},_0x5dd5(_0x301a7b,_0x25abfd);}function _0x3283(){var _0x310334=['1RdOkUL','67576xhONte','exit','20lzeRoF','17880kaEUcc','5VaSXLv','forEach','837477fFCLWX','error','744760RCvFmW','583554ijPQHK','81032pStpbV','326678uNyceJ','-------------------------','56sTfCke'];_0x3283=function(){return _0x310334;};return _0x3283();}function logErrorAndExit(..._0x127931){var _0x58cc9e=_0x5dd5;logger[_0x58cc9e(0x15c)](_0x58cc9e(0x161)),_0x127931[_0x58cc9e(0x15a)](_0x4f6292=>logger[_0x58cc9e(0x15c)](_0x4f6292)),logger[_0x58cc9e(0x15c)](_0x58cc9e(0x161)),process[_0x58cc9e(0x156)](0x1);} 71 | 72 | function logSuccess(...messages) { 73 | const endTime = Date.now() 74 | logger.info(chalk.rgb(253, 235, 255)("-------------------------")) 75 | messages.forEach(msg => { 76 | const randomColor = () => Math.floor(Math.random() * 256) 77 | logger.info(chalk.rgb(randomColor(), randomColor(), randomColor())(msg)) 78 | }) 79 | logger.info(chalk.rgb(82, 242, 255)(`耗时 ${endTime - startTime} 毫秒`)) 80 | logger.info(chalk.rgb(253, 235, 255)("-------------------------")) 81 | } 82 | 83 | function logDuplicateExport(item, key) { 84 | logger.info(`[${AppName}] 已存在 class ${key} 同名导出: ${item}`) 85 | } 86 | 87 | function logPluginError(item, error, packageErr) { 88 | logger.error(`[${AppName}] 载入插件错误 ${chalk.red(item)}`) 89 | 90 | if (error.code === "ERR_MODULE_NOT_FOUND") { 91 | packageErr.push({ 92 | file: { name: item }, 93 | error 94 | }) 95 | } else { 96 | logger.error(error) 97 | } 98 | } 99 | 100 | function packageTips(packageErr) { 101 | if (!packageErr.length) return 102 | logger.error("--------- 插件加载错误 ---------") 103 | for (const i of packageErr) { 104 | const pack = i.error.stack.match(/'(.+?)'/g)[0].replace(/'/g, "") 105 | logger.error(`${logger.cyan(i.file.name)} 缺少依赖 ${logger.red(pack)}`) 106 | } 107 | logger.error(`请使用 ${logger.red("pnpm i")} 安装依赖`) 108 | logger.error(`仍报错 ${logger.red("进入插件目录")} pnpm add 依赖`) 109 | logger.error("--------------------------------") 110 | } 111 | 112 | export { loadApps, logSuccess, logErrorAndExit } 113 | -------------------------------------------------------------------------------- /lib/puppeteer/render.js: -------------------------------------------------------------------------------- 1 | import { Data, Version, Plugin_Name } from "#components" 2 | import fs from "node:fs" 3 | import puppeteer from "../../../../lib/puppeteer/puppeteer.js" 4 | 5 | const _path = process.cwd() 6 | 7 | /** 8 | * 渲染HTML 9 | * @param {string} path 文件路径 10 | * @param {object} params 参数 11 | * @param {object} cfg 12 | */ 13 | export default async function(path, params, cfg) { 14 | let [ app, tpl ] = path.split("/") 15 | let resPath = `../../../../../plugins/${Plugin_Name}/resources/` 16 | Data.createDir(`data/html/${Plugin_Name}/${app}/${tpl}`, "root") 17 | let data = { 18 | ...params, 19 | _plugin: Plugin_Name, 20 | saveId: params.saveId || params.save_id || tpl, 21 | tplFile: `./plugins/${Plugin_Name}/resources/${app}/${tpl}.html`, 22 | pluResPath: resPath, 23 | _res_path: resPath, 24 | pageGotoParams: { 25 | waitUntil: "networkidle0" 26 | }, 27 | sys: { 28 | scale: `style=transform:scale(${cfg.scale || 1})`, 29 | copyright: `Created By ${Version.name}${Version.yunzai} & ${Plugin_Name}${Version.ver}` 30 | }, 31 | quality: 100 32 | } 33 | if (process.argv.includes("web-debug")) { 34 | // debug下保存当前页面的渲染数据,方便模板编写与调试 35 | // 由于只用于调试,开发者只关注自己当时开发的文件即可,暂不考虑app及plugin的命名冲突 36 | let saveDir = _path + "/data/ViewData/" 37 | if (!fs.existsSync(saveDir)) { 38 | fs.mkdirSync(saveDir) 39 | } 40 | let file = saveDir + tpl + ".json" 41 | data._app = app 42 | fs.writeFileSync(file, JSON.stringify(data)) 43 | } 44 | let base64 = await puppeteer.screenshot(`${Plugin_Name}/${app}/${tpl}`, data) 45 | let ret = true 46 | 47 | if (base64) { 48 | let { e } = cfg 49 | ret = await e.reply(base64) 50 | } 51 | return cfg.retMsgId ? ret : true 52 | } 53 | -------------------------------------------------------------------------------- /lib/request/request.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch" 2 | import { HttpsProxyAgent } from "https-proxy-agent" 3 | import { Config } from "#components" 4 | 5 | export default new class Request { 6 | /** 7 | * 发送GET请求到指定URL 8 | * @param {string} url - 发送GET请求的URL 9 | * @param {object} [options] - 可选的请求头和响应类型 10 | * @param {object} [options.headers] - 请求头 11 | * @param {string} [options.responseType] - 响应类型,可选值为 json,text或raw ,默认为 'json' 12 | * @param {boolean} [options.log] - 是否打印请求日志,默认为true 13 | * @returns {Promise} 返回响应数据或false(请求失败) 14 | */ 15 | async get(url, options = { headers: {}, responseType: "json", log: true }) { 16 | const { headers = {}, responseType = "json", log = true } = options 17 | 18 | const requestOptions = { 19 | method: "GET", 20 | headers 21 | } 22 | 23 | if (Config.proxy.open && Config.proxy.url) { 24 | requestOptions.agent = new HttpsProxyAgent(Config.proxy.url) 25 | } 26 | 27 | try { 28 | if (log) logger.debug(`[DF-Plugin] GET请求URL: ${logger.green(url)}`) 29 | const response = await fetch(url, requestOptions) 30 | return responseType === "raw" ? response : (responseType === "json" ? await response.json() : await response.text()) 31 | } catch (error) { 32 | if (log) logger.error("GET请求失败:", error) 33 | return false 34 | } 35 | } 36 | 37 | /** 38 | * 发送POST请求到指定URL 39 | * @param {string} url - 发送POST请求的URL 40 | * @param {object} body - 请求体 41 | * @param {object} [options] - 可选参数 42 | * @param {object} [options.headers] - 请求头 43 | * @param {string} [options.responseType] - 响应类型,可选值为 'json', 'text' 或 'raw' ,默认为 'json' 44 | * @param {boolean} [options.log] - 是否打印请求日志,默认为true 45 | * @returns {Promise} 返回响应数据或false(请求失败) 46 | */ 47 | async post(url, body, options = { headers: {}, responseType: "json", log: true }) { 48 | const { headers = {}, responseType = "json", log = true } = options 49 | 50 | const requestOptions = { 51 | method: "POST", 52 | headers: { 53 | "Content-Type": "application/json", 54 | ...headers 55 | }, 56 | body: JSON.stringify(body) 57 | } 58 | 59 | if (Config.proxy.open && Config.proxy.url) { 60 | requestOptions.agent = new HttpsProxyAgent(Config.proxy.url) 61 | } 62 | 63 | try { 64 | if (log) logger.debug(`[DF-Plugin] POST请求URL: ${logger.green(url)}`) 65 | const response = await fetch(url, requestOptions) 66 | return responseType === "raw" ? response : (responseType === "json" ? await response.json() : await response.text()) 67 | } catch (error) { 68 | if (log) logger.error("[DF-Plugin] POST请求失败:", error) 69 | return false 70 | } 71 | } 72 | }() 73 | -------------------------------------------------------------------------------- /model/CodeUpdate.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable promise/always-return */ 2 | import moment from "moment" 3 | import common from "../../../lib/common/common.js" 4 | import puppeteer from "../../../lib/puppeteer/puppeteer.js" 5 | import { GitApi } from "./api/index.js" 6 | import { PluginPath } from "#model" 7 | import { Config, Res_Path, common as Common } from "#components" 8 | import { marked } from "marked" 9 | import lodash from "lodash" 10 | 11 | const _key = "DF:CodeUpdate" 12 | let AutoPathBranch = {} 13 | 14 | export default new class CodeUpdate { 15 | /** 16 | * 获取仓库更新 17 | * @param {boolean} isAuto 是否为自动获取 18 | * @param {object} e 消息事件 19 | * @returns {boolean|object} 是否有更新 | { number: number } 20 | */ 21 | async checkUpdates(isAuto = false, e) { 22 | const { GithubToken = "", GiteeToken = "", GitcodeToken = "", List = [] } = Config.CodeUpdate 23 | 24 | if (!List.length) { 25 | logger.mark("[DF-Plugin][CodeUpdate]没有配置仓库信息,取消检查更新") 26 | return isAuto ? false : e.reply("还没有配置仓库信息呢") 27 | } 28 | 29 | logger.mark(logger.blue("开始检查仓库更新")) 30 | 31 | let number = 0 32 | 33 | for (const list of List) { 34 | const promises = [] 35 | const { 36 | GithubList = [], 37 | GiteeList = [], 38 | GitcodeList = [], 39 | GiteeReleases = [], 40 | GithubReleases = [], 41 | AutoPath = false, 42 | Exclude = [], 43 | Group = [], 44 | QQ = [] 45 | } = list 46 | 47 | const githubRepos = this.getRepoList(GithubList, PluginPath.github, Exclude, AutoPath) 48 | const giteeRepos = this.getRepoList(GiteeList, PluginPath.gitee, Exclude, AutoPath) 49 | const gitcodeRepos = this.getRepoList(GitcodeList, PluginPath.gitcode, Exclude, AutoPath) 50 | 51 | const updateRequests = [ 52 | { list: githubRepos, platform: "GitHub", token: GithubToken, type: "commits", key: "GitHub" }, 53 | { list: giteeRepos, platform: "Gitee", token: GiteeToken, type: "commits", key: "Gitee" }, 54 | { list: gitcodeRepos, platform: "Gitcode", token: GitcodeToken, type: "commits", key: "Gitcode" }, 55 | { list: GiteeReleases, platform: "Gitee", token: GiteeToken, type: "releases", key: "GiteeReleases" }, 56 | { list: GithubReleases, platform: "GitHub", token: GithubToken, type: "releases", key: "GithubReleases" } 57 | ] 58 | 59 | updateRequests.forEach(({ list, platform, token, type, key }) => { 60 | if (list.length > 0) { 61 | promises.push(this.fetchUpdateForRepo(list, platform, token, type, key, isAuto)) 62 | } 63 | }) 64 | 65 | const results = await Promise.all(promises) 66 | const content = results.flat() 67 | 68 | if (content.length > 0) { 69 | number += content.length 70 | const userId = isAuto ? "Auto" : e.user_id 71 | const base64 = await this.generateScreenshot(content, userId) 72 | this.sendMessageToUser(base64, content, Group, QQ, isAuto, e) 73 | } 74 | } 75 | 76 | if (number > 0) { 77 | logger.info(logger.green(`共获取到 ${number} 条数据~`)) 78 | } else { 79 | logger.info(logger.yellow("没有获取到任何数据")) 80 | } 81 | return { number } 82 | } 83 | 84 | getRepoList(list, pluginPath, exclude, autoPath) { 85 | return autoPath ? [ ...new Set([ ...list, ...pluginPath ]) ].filter(path => !exclude.includes(path)) : list 86 | } 87 | 88 | async fetchUpdateForRepo(list, platform, token, type, key, isAuto) { 89 | if (list.length > 0) { 90 | return this.fetchUpdates(list, platform, token, type, `${_key}:${key}`, isAuto) 91 | } 92 | return [] 93 | } 94 | 95 | async fetchUpdates(repoList, source, token, type, redisKeyPrefix, isAuto) { 96 | const content = [] 97 | 98 | await Promise.all(repoList.map(async(repo) => { 99 | if (!repo) return 100 | 101 | try { 102 | logger.debug(`请求 ${logger.magenta(source)} ${type}: ${logger.cyan(repo)}`) 103 | 104 | let [ path, branch ] = type === "commits" ? repo.split(":") : [ repo ] 105 | if (!branch) branch = AutoPathBranch[path] // || (await GitApi.getDefaultBranch(path, source, token)) 106 | if (Array.isArray(token)) token = lodash.sample(token) 107 | let data = await GitApi.getRepositoryData(path, source, type, token, branch) 108 | if (data === "return") return 109 | if (!data || [ "Not Found Projec", "Not Found" ].includes(data?.message)) { 110 | logger.error(`${logger.magenta(source)}: ${logger.cyan(repo)} 仓库不存在`) 111 | return 112 | } 113 | 114 | if (type === "commits" && branch) data = [ data ] 115 | if (data.length === 0 || (type === "releases" && !data[0]?.tag_name)) { 116 | logger.warn(`${logger.magenta(source)}: ${logger.cyan(repo)} 数据为空`) 117 | return 118 | } 119 | 120 | if (isAuto) { 121 | const id = type === "commits" ? data[0]?.sha : data[0]?.node_id 122 | if (await this.isUpToDate(repo, redisKeyPrefix, id)) { 123 | logger.debug(`${logger.cyan(repo)} 暂无更新`) 124 | return 125 | } 126 | logger.mark(`${logger.cyan(repo)} 检测到更新`) 127 | await this.updateRedis(repo, redisKeyPrefix, id, isAuto) 128 | } 129 | 130 | const info = type === "commits" ? this.formatCommitInfo(data[0], source, path, branch) : this.formatReleaseInfo(data[0], source, repo) 131 | content.push(info) 132 | } catch (error) { 133 | logger.error(`[DF-Plugin] 获取 ${logger.magenta(source)} ${type} ${logger.cyan(repo)} 数据出错: ${error?.stack || error}`) 134 | } 135 | })) 136 | 137 | return content 138 | } 139 | 140 | async isUpToDate(repo, redisKeyPrefix, sha) { 141 | const redisData = await redis.get(`${redisKeyPrefix}:${repo}`) 142 | return redisData && JSON.parse(redisData)[0].shacode === sha 143 | } 144 | 145 | async updateRedis(repo, redisKeyPrefix, sha, isAuto) { 146 | if (isAuto) { 147 | await redis.set(`${redisKeyPrefix}:${repo}`, JSON.stringify([ { shacode: sha } ])) 148 | } 149 | } 150 | 151 | formatCommitInfo(data, source, repo, branch) { 152 | const { author, committer, commit, stats, files } = data 153 | const authorName = `${commit.author.name}` 154 | const committerName = `${commit.committer.name}` 155 | const authorTime = `${Common.timeAgo(moment(commit.author.date))}` 156 | const committerTime = `${Common.timeAgo(moment(commit.committer.date))}` 157 | const timeInfo = authorName === committerName ? `${authorName} 提交于 ${authorTime}` : `${authorName} 编写于 ${authorTime},并由 ${committerName} 提交于 ${committerTime}` 158 | 159 | return { 160 | avatar: { 161 | is: author?.avatar_url !== committer?.avatar_url, 162 | author: author?.avatar_url, 163 | committer: committer?.avatar_url 164 | }, 165 | name: { 166 | source, 167 | repo, 168 | branch, 169 | authorStart: commit.author.name?.[0] ?? "?", 170 | committerStart: commit.committer.name?.[0] ?? "?" 171 | }, 172 | time_info: timeInfo, 173 | text: this.formatMessage(commit.message), 174 | stats: stats && files ? { files: files.length, additions: stats.additions, deletions: stats.deletions } : false 175 | } 176 | } 177 | 178 | formatMessage(message) { 179 | const msgMap = message.split("\n") 180 | msgMap[0] = "" + msgMap[0] + "" 181 | return msgMap.join("\n") 182 | } 183 | 184 | formatReleaseInfo(data, source, repo) { 185 | const { tag_name, name, body, author, published_at } = data 186 | const authorName = `${author?.login || author?.name}` 187 | const authorAvatar = author?.avatar_url 188 | const authorTime = `${Common.timeAgo(moment(published_at))}` 189 | const timeInfo = authorName ? `${authorName} 发布于 ${authorTime}` : `${authorTime}` 190 | 191 | return { 192 | release: true, 193 | avatar: authorAvatar, 194 | name: { 195 | source, 196 | repo, 197 | tag: tag_name, 198 | authorStart: author?.login?.[0] || author?.name?.[0] || "?" 199 | }, 200 | time_info: timeInfo, 201 | text: "" + name + "\n" + marked(body) 202 | } 203 | } 204 | 205 | async generateScreenshot(content, saveId) { 206 | return await puppeteer.screenshot("CodeUpdate/index", { 207 | tplFile: `${Res_Path}/CodeUpdate/index.html`, 208 | saveId, 209 | lifeData: content, 210 | pluResPath: `${Res_Path}/` 211 | }) 212 | } 213 | 214 | async sendMessageToUser(data, content, Group, QQ, isAuto, e) { 215 | if (!isAuto) return e.reply(data) 216 | for (const group of Group) { 217 | if (content.length > 0 && data) { 218 | Bot.pickGroup(group).sendMsg(data) 219 | } 220 | await common.sleep(5000) 221 | } 222 | for (const qq of QQ) { 223 | if (content.length > 0 && data) { 224 | Bot.pickFriend(qq).sendMsg(data) 225 | } 226 | await common.sleep(5000) 227 | } 228 | } 229 | }() 230 | 231 | /** 对未设置分支的仓库进行处理 */ 232 | let num = 0 233 | const promises = [] 234 | if (Config.CodeUpdate.AutoBranch) { 235 | Config.CodeUpdate.List?.length > 0 && Config.CodeUpdate.List.forEach((item) => { 236 | const { GithubList = [], GiteeList = [], GitcodeList = [] } = item 237 | if (GithubList.length > 0) { 238 | GithubList.forEach((path, idx) => { 239 | if (!path.split(":")?.[1]) { 240 | promises.push( 241 | GitApi.getDefaultBranch(path.split(":")[0], "GitHub", Config.CodeUpdate.GithubToken) 242 | .then((branch) => { 243 | if (!branch) throw new Error(`接口返回分支为空 ${branch}`) 244 | const repo = path.split(":")[0] 245 | AutoPathBranch[repo] = branch 246 | item.GithubList[idx] = `${repo}:${branch}` 247 | num++ 248 | }) 249 | .catch((error) => { 250 | logger.warn(`[DF-Plugin] 获取Github的默认分支失败 ${path.split(":")[0]}: ${error.message}`) 251 | }) 252 | ) 253 | } 254 | }) 255 | } 256 | if (GiteeList.length > 0) { 257 | GiteeList.forEach((path, idx) => { 258 | if (!path.split(":")?.[1]) { 259 | promises.push( 260 | GitApi.getDefaultBranch(path.split(":")[0], "Gitee", Config.CodeUpdate.GiteeToken) 261 | .then((branch) => { 262 | if (!branch) throw new Error(`接口返回分支为空 ${branch}`) 263 | const repo = path.split(":")[0] 264 | AutoPathBranch[repo] = branch 265 | item.GiteeList[idx] = `${repo}:${branch}` 266 | num++ 267 | }) 268 | .catch((error) => { 269 | logger.warn(`[DF-Plugin] 获取Gitee仓库的默认分支失败 ${path.split(":")[0]}: ${error.message}`) 270 | }) 271 | ) 272 | } 273 | }) 274 | } 275 | if (GitcodeList.length > 0) { 276 | GitcodeList.forEach((path, idx) => { 277 | if (!path.split(":")?.[1]) { 278 | promises.push( 279 | GitApi.getDefaultBranch(path.split(":")[0], "Gitcode", Config.CodeUpdate.GitcodeToken) 280 | .then((branch) => { 281 | if (!branch) throw new Error(`接口返回分支为空 ${branch}`) 282 | const repo = path.split(":")[0] 283 | AutoPathBranch[repo] = branch 284 | item.GitcodeList[idx] = `${repo}:${branch}` 285 | num++ 286 | }) 287 | .catch((error) => { 288 | logger.warn(`[DF-Plugin] 获取GitCode仓库默认分支失败 ${path.split(":")[0]}: ${error.message}`) 289 | }) 290 | ) 291 | } 292 | }) 293 | } 294 | }) 295 | 296 | Promise.all(promises) 297 | .then(() => { 298 | if (num > 0) { 299 | logger.info(`[DF-Plugin] 已自动获取到 ${logger.blue(num)} 个默认分支`) 300 | } 301 | }) 302 | .catch((error) => { 303 | logger.error(`[DF-Plugin] 获取默认分支时发生错误: ${error.message}`) 304 | }) 305 | } 306 | -------------------------------------------------------------------------------- /model/GitRepo.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises" 2 | import path from "node:path" 3 | import { exec } from "child_process" 4 | import { Path, Config } from "#components" 5 | 6 | /** 初始化常量 */ 7 | if (Config.AutoPath) PluginDirs() 8 | 9 | /** 10 | * 插件远程路径,包含 GitHub、Gitee 和 GitCode 11 | * @type {object} 12 | */ 13 | export const PluginPath = { github: [], gitee: [], gitcode: [] } 14 | 15 | /** 16 | * 获取插件对应远程路径 17 | * @returns {Promise} 插件路径 18 | */ 19 | export async function getPluginsRepo() { 20 | return traverseDirectories(Path) 21 | } 22 | 23 | /** 遍历插件目录,载入常量 */ 24 | async function PluginDirs() { 25 | console.time("载入本地Git仓库列表") 26 | const result = await getPluginsRepo() 27 | console.timeEnd("载入本地Git仓库列表") 28 | PluginPath.github.push(...result.github) 29 | PluginPath.gitee.push(...result.gitee) 30 | PluginPath.gitcode.push(...result.gitcode) 31 | } 32 | 33 | /** 34 | * 递归遍历目录以查找包含 .git 的 Git 仓库 35 | * @param {string} dir - 当前遍历的目录路径 36 | * @param {object} result - 数据对象 37 | * @returns {Promise} result - 获取到的插件路径 38 | */ 39 | async function traverseDirectories(dir, result = { github: [], gitee: [], gitcode: [] }) { 40 | try { 41 | if (await isGitRepo(dir)) { 42 | await getGitUrl(dir, result) 43 | } 44 | 45 | const items = await fs.readdir(dir) 46 | const promises = items.map(async(item) => { 47 | if (item === "data" || item === "node_modules") return 48 | 49 | const directory = path.join(dir, item) 50 | const stat = await fs.stat(directory) 51 | if (stat.isDirectory()) { 52 | if (await isGitRepo(directory)) { 53 | await getGitUrl(directory, result) 54 | } else { 55 | await traverseDirectories(directory, result) 56 | } 57 | } 58 | }) 59 | await Promise.all(promises) 60 | } catch (err) { 61 | console.error(`无法读取目录: ${dir}`, err) 62 | } 63 | return result 64 | } 65 | 66 | /** 67 | * 获取Git仓库的URL并添加到指定的数组中 68 | * @param {string} dir 路径 69 | * @param {object} result - 存储 GitHub 和 Gitee 仓库的对象 70 | */ 71 | async function getGitUrl(dir, result) { 72 | const branch = await getRemoteBranch(dir) 73 | const remoteUrl = await getRemoteUrl(dir, branch) 74 | if (remoteUrl) classifyRepo(remoteUrl, branch, result) 75 | } 76 | /** 77 | * 检查是否为 Git 仓库 78 | * @param {string} dir - 检查的目录路径 79 | * @returns {Promise} 是否为 Git 仓库 80 | */ 81 | async function isGitRepo(dir) { 82 | const gitDir = path.join(dir, ".git") 83 | try { 84 | await fs.access(gitDir) 85 | return true 86 | } catch { 87 | return false 88 | } 89 | } 90 | 91 | /** 92 | * 获取仓库分支远程 URL 93 | * @param {string} repoPath - 仓库路径 94 | * @param {string} branch - 分支名称 95 | * @returns {Promise} 仓库的远程 URL 96 | */ 97 | async function getRemoteUrl(repoPath, branch) { 98 | return executeCommand(`git remote get-url ${await getRemoteName(repoPath, branch)}`, repoPath) 99 | } 100 | 101 | /** 102 | * 获取仓库分支远程名称 103 | * @param {string} repoPath - 仓库路径 104 | * @param {string} branch - 分支名称 105 | * @returns {Promise} 当前分支名称 106 | */ 107 | async function getRemoteName(repoPath, branch) { 108 | return executeCommand(`git config branch.${branch}.remote`, repoPath) 109 | } 110 | 111 | /** 112 | * 获取仓库当前分支 113 | * @param {string} repoPath - 仓库路径 114 | * @returns {Promise} 当前分支名称 115 | */ 116 | async function getRemoteBranch(repoPath) { 117 | return executeCommand("git branch --show-current", repoPath) 118 | } 119 | 120 | /** 121 | * 根据远程 URL 对仓库进行分类,并将其添加到结果对象中 122 | * @param {string} url - 仓库的远程 URL 123 | * @param {string} branch - 当前分支名称 124 | * @param {object} result - 存储 GitHub 和 Gitee 仓库的对象 125 | */ 126 | function classifyRepo(url, branch, result) { 127 | if (url.includes("github.com")) { 128 | const parts = url.split("github.com/") 129 | if (parts[1]) { 130 | const repoPath = parts[1].replace(/(\/|\.git)$/, "") + `:${branch}` 131 | result.github.push(repoPath) 132 | } 133 | } else if (url.includes("gitee.com")) { 134 | const parts = url.split("gitee.com/") 135 | if (parts[1]) { 136 | const repoPath = parts[1].replace(/(\/|\.git)$/, "") + `:${branch}` 137 | result.gitee.push(repoPath) 138 | } 139 | } else if (url.includes("gitcode.com")) { 140 | const parts = url.split("gitcode.com/") 141 | if (parts[1]) { 142 | const repoPath = parts[1].replace(/(\/|\.git)$/, "") + `:${branch}` 143 | result.gitcode.push(repoPath) 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * 在指定的路径下执行命令 150 | * @param {string} command - 要执行的命令 151 | * @param {string} cwd - 要在其下执行命令的当前工作目录 152 | * @returns {Promise} 命令执行结果的输出 153 | */ 154 | function executeCommand(command, cwd) { 155 | return new Promise((resolve, reject) => { 156 | exec(command, { cwd }, (error, stdout, stderr) => { 157 | if (error) { 158 | reject(error) 159 | } else { 160 | resolve(stdout.trim()) 161 | } 162 | }) 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /model/RandomFile.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs" 2 | import lodash from "lodash" 3 | import path from "node:path" 4 | import { Poke_Path, Poke_List, Config } from "#components" 5 | 6 | /** 7 | * 随机获取一个文件 8 | * @param {string} dirPath - 文件夹路径 9 | * @returns {string|null} path - 文件路径或空 10 | */ 11 | function randomFile(dirPath) { 12 | try { 13 | const files = fs.readdirSync(dirPath) 14 | if (files.length === 0) { 15 | logger.error(`[DF-Plugin] 获取文件失败: ${dirPath}`) 16 | return null 17 | } 18 | const fileName = lodash.sample(files) 19 | return path.join(dirPath, fileName) 20 | } catch (err) { 21 | logger.error(`[DF-Plugin] 获取文件错误: ${dirPath}\n${err}`) 22 | return null 23 | } 24 | } 25 | 26 | /** 27 | * 抽取随机表情包 28 | * @param {string} name - 表情包名称 29 | * @returns {string|null} 文件路径或api地址 30 | */ 31 | function imagePoke(name = "all") { 32 | let { Black } = Config.other, List = Poke_List 33 | if (name == "all") { 34 | if (Array.isArray(Black) && Black.length > 0) { 35 | List = Poke_List.filter(type => !Black.includes(type)) 36 | } 37 | name = lodash.sample(List) 38 | } 39 | const path = Poke_Path + "/" + name 40 | if (!fs.existsSync(path)) return `https://yugan.love/?name=${name}` 41 | return randomFile(path) 42 | } 43 | 44 | export { randomFile, imagePoke } 45 | -------------------------------------------------------------------------------- /model/api/GitApi.js: -------------------------------------------------------------------------------- 1 | import { request } from "#components" 2 | 3 | export default new class { 4 | /** 5 | * 获取仓库的最新数据 6 | * @param {string} repo - 仓库路径(用户名/仓库名) 7 | * @param {string} source - 数据源(GitHub/Gitee/Gitcode) 8 | * @param {string} type - 请求类型(commits/releases) 9 | * @param {string} token - 访问Token 10 | * @param {string} sha - 提交起始的SHA值或者分支名. 默认: 仓库的默认分支 11 | * @returns {Promise} 提交数据或false(请求失败) 12 | */ 13 | async getRepositoryData(repo, source, type = "commits", token, sha) { 14 | let isGitHub = false, baseURL 15 | switch (source) { 16 | case "GitHub": 17 | isGitHub = true 18 | baseURL = "https://api.github.com/repos" 19 | break 20 | case "Gitee": 21 | baseURL = "https://gitee.com/api/v5/repos" 22 | break 23 | case "Gitcode": 24 | baseURL = "https://api.gitcode.com/api/v5/repos" 25 | break 26 | default: 27 | logger.error(`未知数据源: ${source}`) 28 | return "return" 29 | } 30 | const path = sha ? `${repo}/commits/${sha}` : `${repo}/${type}?per_page=1` 31 | let url = `${baseURL}/${path}` 32 | 33 | if (!isGitHub && token) { 34 | url += `${sha ? "?" : "&"}access_token=${token}` 35 | } 36 | 37 | const headers = this.getHeaders(token, source) 38 | const data = await this.fetchData(url, headers, repo, source) 39 | return data 40 | } 41 | 42 | /** 43 | * 获取仓库默认分支 44 | * @param {string} repo - 仓库路径(用户名/仓库名) 45 | * @param {string} source - 数据源(GitHub/Gitee/Gitcode) 46 | * @param {string} token - 访问Token 47 | * @returns {Promise} 默认分支名或false(请求失败) 48 | */ 49 | async getDefaultBranch(repo, source, token) { 50 | let baseURL 51 | switch (source) { 52 | case "GitHub": 53 | baseURL = "https://api.github.com/repos" 54 | break 55 | case "Gitee": 56 | baseURL = "https://gitee.com/api/v5/repos" 57 | break 58 | case "Gitcode": 59 | baseURL = "https://api.gitcode.com/api/v5/repos" 60 | break 61 | default: 62 | logger.error(`未知数据源: ${source}`) 63 | return "return" 64 | } 65 | const url = `${baseURL}/${repo}` 66 | 67 | const headers = this.getHeaders(token, source) 68 | const data = await this.fetchData(url, headers, repo, source) 69 | if (data && data.default_branch) { 70 | return data.default_branch 71 | } 72 | return false 73 | } 74 | 75 | /** 76 | * 获取请求头 77 | * @param {string} token - 访问Token 78 | * @param {string} source - 数据源(GitHub/Gitee/Gitcode) 79 | * @returns {object} 请求头 80 | */ 81 | getHeaders(token, source) { 82 | const headers = { 83 | "User-Agent": "request", 84 | "Accept": (() => { 85 | switch (source) { 86 | case "GitHub": 87 | return "application/vnd.github+json" 88 | case "Gitee": 89 | return "application/vnd.gitee+json" 90 | default: 91 | return "application/json" 92 | } 93 | })() 94 | } 95 | if (token) { 96 | headers.Authorization = `Bearer ${token}` 97 | } 98 | return headers 99 | } 100 | 101 | /** 102 | * 获取指定 URL 的 JSON 数据 103 | * @param {string} url - 请求的 URL 104 | * @param {object} [headers] - 请求头 105 | * @param {string} repo - 仓库路径 106 | * @param {string} source - 数据源 107 | * @returns {Promise} 返回请求的数据或 false(请求失败) 108 | */ 109 | async fetchData(url, headers = {}, repo, source) { 110 | try { 111 | const response = await request.get(url, { 112 | headers, 113 | responseType: "raw" 114 | }) 115 | 116 | if (!response.ok) { 117 | let msg 118 | switch (response.status) { 119 | case 401: 120 | msg = "访问令牌无效或已过期 (code: 401)" 121 | return "return" 122 | case 403: 123 | msg = "请求达到Api速率限制或无权限,请尝试填写token或降低请求频率后重试 (code: 403)" 124 | return "return" 125 | case 404: 126 | msg = "仓库不存在 (code: 404)" 127 | return "return" 128 | default: 129 | msg = `状态码:${response.status}` 130 | break 131 | } 132 | 133 | logger.error(`请求 ${logger.magenta(source)} 失败: ${logger.cyan(repo)}, ${msg}`) 134 | return "return" 135 | } 136 | 137 | const contentType = response.headers.get("content-type") 138 | if (!contentType || !contentType.includes("application/json")) { 139 | logger.error(`响应非 JSON 格式: ${url} , 内容:${await response.text()}`) 140 | return "return" 141 | } 142 | 143 | return await response.json() 144 | } catch (error) { 145 | logger.error(`请求失败: ${url},错误信息: ${error.stack}`) 146 | return "return" 147 | } 148 | } 149 | }() 150 | -------------------------------------------------------------------------------- /model/api/index.js: -------------------------------------------------------------------------------- 1 | export { default as GitApi } from "./GitApi.js" 2 | -------------------------------------------------------------------------------- /model/help/theme.js: -------------------------------------------------------------------------------- 1 | import lodash from "lodash" 2 | import fs from "node:fs" 3 | import { Data, Plugin_Name } from "#components" 4 | 5 | const Theme = { 6 | async getThemeCfg(theme, exclude) { 7 | const dirPath = `./plugins/${Plugin_Name}/resources/help/theme/` 8 | const resPath = "{{_res_path}}/help/theme/" 9 | 10 | const names = fs.readdirSync(dirPath) 11 | .filter(dir => fs.existsSync(`${dirPath}${dir}/main.jpg`)) 12 | 13 | let ret = [] 14 | if (lodash.isArray(theme)) { 15 | ret = lodash.intersection(theme, names) 16 | } else if (theme === "all") { 17 | ret = names 18 | } 19 | 20 | if (exclude && lodash.isArray(exclude)) { 21 | ret = lodash.difference(ret, exclude) 22 | } 23 | 24 | if (ret.length === 0) { 25 | ret = [ "default" ] 26 | } 27 | 28 | const name = lodash.sample(ret) 29 | return { 30 | main: `${resPath}${name}/main.jpg`, 31 | bg: fs.existsSync(`${dirPath}${name}/bg.jpg`) ? `${resPath}${name}/bg.jpg` : `${resPath}default/bg.jpg`, 32 | style: (await Data.importModule(`resources/help/theme/${name}/config.js`)).style || {} 33 | } 34 | }, 35 | 36 | async getThemeData(diyStyle, sysStyle) { 37 | const helpConfig = lodash.extend({}, sysStyle, diyStyle) 38 | const colCount = Math.min(5, Math.max(parseInt(helpConfig?.colCount) || 3, 2)) 39 | const colWidth = Math.min(500, Math.max(100, parseInt(helpConfig?.colWidth) || 265)) 40 | const width = Math.min(2500, Math.max(800, colCount * colWidth + 30)) 41 | const theme = await this.getThemeCfg(helpConfig.theme, diyStyle.themeExclude || sysStyle.themeExclude) 42 | const themeStyle = theme.style || {} 43 | 44 | const ret = [ 45 | ` 46 | .container{background:url(${theme.main}) center top / cover no-repeat;width:${width}px;} 47 | .help-table .td,.help-table .th{width:${100 / colCount}%} 48 | ` 49 | ] 50 | 51 | const css = (sel, cssProp, key, def, fn) => { 52 | let val = Data.def(themeStyle[key], diyStyle[key], sysStyle[key], def) 53 | if (fn) val = fn(val) 54 | ret.push(`${sel}{${cssProp}:${val}}`) 55 | } 56 | 57 | css(".help-title,.help-group", "color", "fontColor", "#ceb78b") 58 | css(".help-title,.help-group", "text-shadow", "fontShadow", "none") 59 | css(".help-desc", "color", "descColor", "#eee") 60 | css(".cont-box", "background", "contBgColor", "rgba(43, 52, 61, 0.8)") 61 | css(".cont-box", "backdrop-filter", "contBgBlur", 3, (n) => diyStyle.bgBlur === false ? "none" : `blur(${n}px)`) 62 | css(".help-group", "background", "headerBgColor", "rgba(34, 41, 51, .4)") 63 | css(".help-table .tr:nth-child(odd)", "background", "rowBgColor1", "rgba(34, 41, 51, .2)") 64 | css(".help-table .tr:nth-child(even)", "background", "rowBgColor2", "rgba(34, 41, 51, .4)") 65 | 66 | return { 67 | style: ``, 68 | colCount 69 | } 70 | } 71 | } 72 | 73 | export default Theme 74 | -------------------------------------------------------------------------------- /model/index.js: -------------------------------------------------------------------------------- 1 | export { PluginPath, getPluginsRepo } from "./GitRepo.js" 2 | export { default as CodeUpdate } from "./CodeUpdate.js" 3 | export { default as Summary } from "./summary.js" 4 | export { randomFile, imagePoke } from "./RandomFile.js" 5 | export { sendMasterMsg } from "./sendMasterMsg.js" 6 | -------------------------------------------------------------------------------- /model/sendMasterMsg.js: -------------------------------------------------------------------------------- 1 | import { Config } from "#components" 2 | import common from "../../../lib/common/common.js" 3 | 4 | /** 5 | * 发送主人消息 6 | * @param msg 7 | * @param botUin 8 | */ 9 | async function sendMasterMsg(msg, botUin = Bot.uin) { 10 | /** 获取配置信息 */ 11 | const Master = Config.sendMaster.Master 12 | let masterQQ = Config.masterQQ 13 | /** 处理喵崽 */ 14 | if (Config.master) { 15 | const master = Config.master[botUin] 16 | if (master?.length) masterQQ = master 17 | else botUin = undefined 18 | } 19 | /** 发送全部主人 */ 20 | if (Master == 1) { 21 | /** TRSS发全部主人函数 */ 22 | if (Bot?.sendMasterMsg) { 23 | await Bot.sendMasterMsg(msg, Bot.uin, 2000) 24 | } else { 25 | /** 遍历发送主人 */ 26 | for (const i of masterQQ) { 27 | await common.relpyPrivate(i, msg, botUin) 28 | await common.sleep(2000) 29 | } 30 | } 31 | /** 发送首位主人 */ 32 | } else if (Master == 0) { 33 | await common.relpyPrivate(masterQQ[0], msg, botUin) 34 | /** 发送指定主人 */ 35 | } else { 36 | await common.relpyPrivate(Master, msg, botUin) 37 | } 38 | } 39 | 40 | /** 41 | * 获取主人QQ 42 | * @param {object} config - 配置 43 | * @returns {string} 主人QQ 44 | */ 45 | function getMasterQQ(config) { 46 | if (config.Master != 1 && config.Master != 0) { 47 | return config.Master 48 | } 49 | return Config.masterQQ[0] === "stdin" ? (Config.masterQQ[1] || Config.masterQQ[0]) : Config.masterQQ[0] 50 | } 51 | 52 | /** 53 | * 获取源消息 54 | * @param {object} e - 消息事件 55 | * @returns {object} 源消息 56 | */ 57 | async function getSourceMessage(e) { 58 | if (e.getReply) { 59 | return await e.getReply() 60 | } else if (e.source) { 61 | return (await e.friend.getChatHistory(e.source.time, 1)).pop() 62 | } 63 | return null 64 | } 65 | 66 | /** 67 | * 提取消息ID 68 | * @param {string} rawMessage - 原始消息 69 | * @returns {string} 消息ID 70 | */ 71 | function extractMessageId(rawMessage) { 72 | const regex = /\(([^)]+)\)/ 73 | const match = rawMessage.match(regex) 74 | return match ? match[1] : null 75 | } 76 | 77 | export { sendMasterMsg, extractMessageId, getSourceMessage, getMasterQQ } 78 | -------------------------------------------------------------------------------- /model/summary.js: -------------------------------------------------------------------------------- 1 | import { Config, request } from "#components" 2 | import _ from "lodash" 3 | 4 | let Sum 5 | let lock = false 6 | let raw 7 | 8 | export default new class Summary { 9 | /** 初始化外显 */ 10 | lint() { 11 | raw = segment.image 12 | this.getSummary() 13 | segment.image = (file, name) => ({ 14 | type: "image", 15 | file, 16 | name, 17 | summary: this.getSummary() 18 | }) 19 | } 20 | 21 | /** 获取外显 */ 22 | getSummary() { 23 | if (Config.summary.type === 1) return Config.summary.text 24 | else if (Config.summary.type === 2) { 25 | const data = Sum 26 | this.getSummaryApi() 27 | return data 28 | } else if (Config.summary.type === 3) return _.sample(Config.summary.list) 29 | } 30 | 31 | /** 更新一言外显 */ 32 | async getSummaryApi() { 33 | if (lock) return 34 | lock = true 35 | try { 36 | Sum = await request.get(Config.summary.api, { responseType: "text", log: false }) || Sum 37 | } catch (err) { 38 | logger.error(`获取一言接口时发生错误:${err}`) 39 | } finally { 40 | lock = false 41 | } 42 | } 43 | 44 | /** 45 | * 开关外显 46 | * @param value 开关 47 | */ 48 | async Switch(value) { 49 | if (value) { 50 | this.lint() 51 | } else { 52 | segment.image = raw 53 | } 54 | } 55 | }() 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "df-plugin", 3 | "version": "1.1.5", 4 | "description": "Yunzai-Bot扩展插件", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "contributors:add": "all-contributors add", 9 | "contributors:generate": "all-contributors generate", 10 | "lint": "eslint --fix" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/DenFengLai/DF-Plugin.git" 15 | }, 16 | "keywords": [ 17 | "Miao-Yunzai", 18 | "TRSS-Yunzai", 19 | "DF-Plugin" 20 | ], 21 | "author": "DengFengLai", 22 | "license": "MIT", 23 | "dependencies": { 24 | "https-proxy-agent": "7.0.6", 25 | "marked": "^14.1.4", 26 | "ulid": "^2.3.0" 27 | }, 28 | "devDependencies": { 29 | "eslint": "^8.57.1", 30 | "eslint-config-standard": "^17.1.0", 31 | "eslint-import-resolver-custom-alias": "^1.3.2", 32 | "eslint-plugin-import": "^2.31.0", 33 | "eslint-plugin-jsdoc": "^48.11.0", 34 | "eslint-plugin-promise": "^6.6.0" 35 | }, 36 | "imports": { 37 | "#components": "./components/index.js", 38 | "#model": "./model/index.js" 39 | } 40 | } -------------------------------------------------------------------------------- /resources/CodeUpdate/icon/GitHub.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/CodeUpdate/icon/Gitcode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28 | 29 | -------------------------------------------------------------------------------- /resources/CodeUpdate/icon/Gitee.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/CodeUpdate/icon/branch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/CodeUpdate/icon/tag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/CodeUpdate/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "font"; 3 | src: url("../font/font.woff"); 4 | } 5 | * { 6 | margin: 0; 7 | font-family: "font"; 8 | box-sizing: border-box; 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | user-select: none; 12 | } 13 | 14 | body { 15 | font-family: "Roboto", sans-serif; 16 | font-size: 16px; 17 | color: #FAF8FC; 18 | transform-origin: 0 0; 19 | background-color: #050505; 20 | transform: scale(2.5); 21 | } 22 | 23 | .container { 24 | max-width: 630px; 25 | min-width: 500px; 26 | width: -moz-max-content; 27 | width: max-content; 28 | padding: 20px 15px 10px 15px; 29 | background-color: #050505; 30 | } 31 | 32 | .head_box { 33 | padding: 10px; 34 | text-align: center; 35 | } 36 | .head_box .id_text { 37 | font-size: 30px; 38 | font-weight: bold; 39 | text-align: left; 40 | font-weight: bold; 41 | } 42 | 43 | .data_box { 44 | border-radius: 10px; 45 | margin-top: -5px; 46 | padding: 6px; 47 | } 48 | 49 | .list { 50 | display: flex; 51 | flex-direction: column; 52 | gap: 15px; 53 | } 54 | .list .item { 55 | display: flex; 56 | align-items: center; 57 | background: #17181C; 58 | padding: 15px; 59 | border-radius: 8px; 60 | box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.5490196078); 61 | } 62 | .list .item .title { 63 | font-size: 16px; 64 | } 65 | .list .item .title .text { 66 | font-weight: bold; 67 | display: flex; 68 | align-items: center; 69 | } 70 | .list .item .title .text snap { 71 | text-indent: 10px; 72 | color: #909195; 73 | } 74 | .list .item .title .text .icon { 75 | width: 18px; 76 | height: 18px; 77 | margin-right: 5px; 78 | } 79 | .list .item .title .branch { 80 | display: flex; 81 | align-items: center; 82 | font-size: 13px; 83 | margin-top: 5px; 84 | color: #909195; 85 | } 86 | .list .item .title .branch img { 87 | width: 13px; 88 | height: 13px; 89 | margin-right: 5px; 90 | } 91 | .list .item .title .dec { 92 | font-size: 13px; 93 | color: #909195; 94 | margin-top: 10px; 95 | display: flex; 96 | align-items: center; 97 | white-space: pre; 98 | } 99 | .list .item .title .dec span { 100 | color: #FAF8FC; 101 | } 102 | .list .item .title .dec .avatar { 103 | display: flex; 104 | align-items: center; 105 | margin-right: 5px; 106 | } 107 | .list .item .title .dec .avatar img { 108 | width: 16px; 109 | height: 16px; 110 | border-radius: 50%; 111 | margin-left: -4px; 112 | position: relative; 113 | } 114 | .list .item .title .dec .avatar img[src=""]::after { 115 | content: attr(data-nameStart); 116 | position: absolute; 117 | inset: 0; 118 | color: #FAF8FC; 119 | background: #E67E22; 120 | font-family: "Verdana", "Arial"; 121 | text-align: center; 122 | line-height: 16px; 123 | font-size: 11px; 124 | } 125 | .list .item .title .dec .avatar img:first-child { 126 | z-index: 1; 127 | } 128 | .list .item .title .desc { 129 | font-size: 12px; 130 | color: #C4C3C8; 131 | margin-top: 10px; 132 | white-space: pre-wrap; 133 | } 134 | .list .item .title .desc .head { 135 | font-size: 14px; 136 | color: #FAF8FC; 137 | } 138 | .list .item .title .desc ul, 139 | .list .item .title .desc ol { 140 | padding-left: 14px; 141 | } 142 | .list .item .title .desc ul li, 143 | .list .item .title .desc ol li { 144 | margin: 0; 145 | padding: 0; 146 | line-height: 0.8; 147 | text-indent: 0; 148 | } 149 | .list .item .title .desc ul li::marker, 150 | .list .item .title .desc ol li::marker { 151 | line-height: 1; 152 | } 153 | .list .item .title .desc ul li:not(:first-child), 154 | .list .item .title .desc ol li:not(:first-child) { 155 | line-height: 1.2; 156 | } 157 | .list .item .title .desc a { 158 | color: #90d0e4; 159 | } 160 | .list .item .title .stats { 161 | border-top: 1px solid rgba(67, 67, 67, 0.5490196078); 162 | border-bottom: 1px solid rgba(67, 67, 67, 0.5490196078); 163 | margin-top: 10px; 164 | padding: 5px 10px; 165 | font-size: 13px; 166 | color: #909195; 167 | width: -moz-max-content; 168 | width: max-content; 169 | } 170 | 171 | .logo { 172 | font-size: 14px; 173 | text-align: center; 174 | color: #c3c4c4; 175 | margin-top: 10px; 176 | } 177 | 178 | .file-count { 179 | color: #ffffff; 180 | } 181 | 182 | .additions { 183 | color: #00B400; 184 | } 185 | 186 | .deletions { 187 | color: #D92B2F; 188 | }/*# sourceMappingURL=index.css.map */ -------------------------------------------------------------------------------- /resources/CodeUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Git仓库更新推送 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
Git仓库更新推送
16 |
17 | 18 |
19 |
20 | {{each lifeData item}} 21 |
22 |
23 |
24 | {{item.name.source}} 图标 25 | {{item.name.repo}} 26 | {{if item.release}} 27 | Releases 28 | {{/if}} 29 |
30 | {{if item.name.branch}} 31 |
32 | 分支图标{{item.name.branch}} 33 |
34 | {{/if}} 35 | {{if item.name.tag}} 36 |
37 | 标签图标{{item.name.tag}} 38 |
39 | {{/if}} 40 |
41 |
42 | 作者头像 43 | {{if item.avatar.is}} 44 | 提交者头像 45 | {{/if}} 46 |
47 | {{@item.time_info}} 48 |
49 |
{{@item.text}}
50 | {{if item.stats}} 51 |
52 | {{item.stats.files}} 个文件发生了变化,影响行数: 53 | +{{item.stats.additions}} 54 | -{{item.stats.deletions}} 55 |
56 | {{/if}} 57 |
58 |
59 | {{/each}} 60 |
61 |
62 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /resources/CodeUpdate/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "font"; 3 | src: url("../font/font.woff"); 4 | } 5 | 6 | 7 | * { 8 | margin: 0; 9 | font-family: "font"; 10 | box-sizing: border-box; 11 | -webkit-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | body { 16 | font-family: 'Roboto', sans-serif; 17 | font-size: 16px; 18 | // width: 530px; 19 | color: #FAF8FC; 20 | transform-origin: 0 0; 21 | background-color: #050505; 22 | transform: scale(2.5); 23 | } 24 | 25 | .container { 26 | max-width: 630px; 27 | min-width: 500px; 28 | width: max-content; 29 | padding: 20px 15px 10px 15px; 30 | background-color: #050505; 31 | } 32 | 33 | .head_box { 34 | padding: 10px; 35 | // color: rgb(0, 0, 0); 36 | text-align: center; 37 | 38 | .id_text { 39 | font-size: 30px; 40 | font-weight: bold; 41 | text-align: left; 42 | font-weight: bold; 43 | } 44 | } 45 | 46 | .data_box { 47 | border-radius: 10px; 48 | margin-top: -5px; 49 | padding: 6px; 50 | // box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 51 | } 52 | 53 | .list { 54 | display: flex; 55 | flex-direction: column; 56 | gap: 15px; 57 | 58 | .item { 59 | display: flex; 60 | align-items: center; 61 | background: #17181C; 62 | // border: 2px solid #4f4f4f; 63 | padding: 15px; 64 | border-radius: 8px; 65 | box-shadow: 0px 1px 1px 1px #0000008c; 66 | 67 | 68 | .title { 69 | font-size: 16px; 70 | // color: #000000; 71 | 72 | .text { 73 | font-weight: bold; 74 | display: flex; 75 | align-items: center; 76 | 77 | snap { 78 | text-indent: 10px; 79 | color: #909195; 80 | } 81 | 82 | .icon { 83 | width: 18px; 84 | height: 18px; 85 | margin-right: 5px; 86 | } 87 | 88 | } 89 | 90 | .branch { 91 | display: flex; 92 | align-items: center; 93 | font-size: 13px; 94 | margin-top: 5px; 95 | color: #909195; 96 | 97 | img { 98 | width: 13px; 99 | height: 13px; 100 | margin-right: 5px; 101 | } 102 | } 103 | 104 | .dec { 105 | font-size: 13px; 106 | color: #909195; 107 | margin-top: 10px; 108 | display: flex; 109 | align-items: center; 110 | white-space: pre; 111 | 112 | span { 113 | color: #FAF8FC 114 | } 115 | 116 | .avatar { 117 | display: flex; 118 | align-items: center; 119 | margin-right: 5px; 120 | 121 | img { 122 | width: 16px; 123 | height: 16px; 124 | border-radius: 50%; 125 | margin-left: -4px; 126 | position: relative; 127 | 128 | &[src=""]::after { 129 | content: attr(data-nameStart); 130 | position: absolute; 131 | inset: 0; 132 | color: #FAF8FC; 133 | background: #E67E22; 134 | font-family: "Verdana", "Arial"; 135 | text-align: center; 136 | line-height: 16px; 137 | font-size: 11px; 138 | // z-index: 1; 139 | } 140 | 141 | &:first-child { 142 | z-index: 1; 143 | } 144 | } 145 | } 146 | 147 | 148 | } 149 | 150 | .desc { 151 | font-size: 12px; 152 | color: #C4C3C8; 153 | margin-top: 10px; 154 | white-space: pre-wrap; 155 | 156 | .head { 157 | font-size: 14px; 158 | color: #FAF8FC; 159 | } 160 | 161 | ul, 162 | ol { 163 | padding-left: 14px; 164 | 165 | li { 166 | margin: 0; 167 | padding: 0; 168 | line-height: 0.8; 169 | text-indent: 0; 170 | 171 | &::marker { 172 | line-height: 1; 173 | } 174 | 175 | &:not(:first-child) { 176 | line-height: 1.2; 177 | } 178 | } 179 | } 180 | 181 | 182 | a { 183 | color: #90d0e4; 184 | } 185 | 186 | 187 | 188 | 189 | } 190 | 191 | .stats { 192 | border-top: 1px solid #4343438c; 193 | border-bottom: 1px solid #4343438c; 194 | margin-top: 10px; 195 | padding: 5px 10px; 196 | font-size: 13px; 197 | color: #909195; 198 | width: max-content; 199 | } 200 | } 201 | } 202 | } 203 | 204 | .logo { 205 | font-size: 14px; 206 | text-align: center; 207 | color: #c3c4c4; 208 | margin-top: 10px; 209 | } 210 | 211 | .file-count { 212 | color: #ffffff; 213 | } 214 | 215 | .additions { 216 | color: #00B400; 217 | } 218 | 219 | .deletions { 220 | color: #D92B2F; 221 | } -------------------------------------------------------------------------------- /resources/font/font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/font/font.woff -------------------------------------------------------------------------------- /resources/help/imgs/card-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/help/imgs/card-bg.png -------------------------------------------------------------------------------- /resources/help/imgs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/help/imgs/icon.png -------------------------------------------------------------------------------- /resources/help/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @font-face { 3 | font-family: "font"; 4 | src: url("../font/font.woff") format("truetype"); 5 | } 6 | 7 | body { 8 | font-size: 18px; 9 | color: #1e1f20; 10 | font-family: "font"; 11 | transform: scale(1.4); 12 | transform-origin: 0 0; 13 | width: 600px; 14 | } 15 | 16 | .container { 17 | width: 600px; 18 | padding: 20px 15px 10px 15px; 19 | background-size: contain; 20 | } 21 | 22 | .head-box { 23 | position: relative; 24 | margin: 60px 0 0 0; 25 | padding-bottom: 0; 26 | color: #fff; 27 | border-radius: 15px; 28 | } 29 | .head-box .title { 30 | font-size: 50px; 31 | color: #bbffff; 32 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); 33 | } 34 | .head-box .label { 35 | font-size: 20px; 36 | text-shadow: -2px 0 rgb(237, 19, 48), 0 2px rgb(237, 19, 48), 2px 0 rgb(237, 19, 48), 0 -2px rgb(237, 19, 48); 37 | } 38 | 39 | .cont-box { 40 | border-radius: 15px; 41 | margin: 20px 0; 42 | overflow: hidden; 43 | box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15); 44 | position: relative; 45 | } 46 | 47 | .help-group { 48 | font-size: 21px; 49 | font-weight: bold; 50 | padding: 15px 15px 10px 20px; 51 | } 52 | 53 | .help-table { 54 | text-align: center; 55 | border-collapse: collapse; 56 | margin: 0; 57 | border-radius: 0 0 10px 10px; 58 | display: table; 59 | overflow: hidden; 60 | width: 100%; 61 | color: #fff; 62 | } 63 | .help-table .tr { 64 | display: table-row; 65 | } 66 | .help-table .tr:last-child .td { 67 | padding-bottom: 12px; 68 | } 69 | .help-table .td, 70 | .help-table .th { 71 | font-size: 14px; 72 | display: table-cell; 73 | box-shadow: 0 0 1px 0 #888 inset; 74 | padding: 12px 0 12px 50px; 75 | line-height: 24px; 76 | position: relative; 77 | text-align: left; 78 | } 79 | .help-table .th { 80 | background: rgba(34, 41, 51, 0.5); 81 | } 82 | 83 | .help-icon { 84 | width: 40px; 85 | height: 40px; 86 | display: block; 87 | position: absolute; 88 | background: url("./imgs/icon.png") 0 0 no-repeat; 89 | background-size: 500px auto; 90 | border-radius: 5px; 91 | left: 6px; 92 | top: 12px; 93 | transform: scale(0.85); 94 | } 95 | 96 | .help-title { 97 | display: block; 98 | color: #d3bc8e; 99 | font-size: 23px; 100 | line-height: 24px; 101 | } 102 | 103 | .help-desc { 104 | display: block; 105 | font-size: 16px; 106 | text-shadow: 0 0 #ffffff, 0 1.5px black, 1.5px 0 black, 0 0 grey, 0 0 pink; 107 | line-height: 20px; 108 | } 109 | 110 | .copyright { 111 | font-size: 14px; 112 | text-align: center; 113 | color: #fff; 114 | position: relative; 115 | padding-left: 10px; 116 | text-shadow: 1px 1px 1px #000; 117 | margin: 10px 0; 118 | } 119 | .copyright .version { 120 | color: #d3bc8e; 121 | display: inline-block; 122 | padding: 0 3px; 123 | }/*# sourceMappingURL=index.css.map */ -------------------------------------------------------------------------------- /resources/help/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DF-Plugin 9 | <% style = style.replace(/{{_res_path}}/g, _res_path) %> 10 | {{@style}} 11 | 12 | 13 |
14 |
15 |
16 |
{{helpCfg.title||"使用帮助"}}
17 |
{{helpCfg.subTitle || "Yunzai-Bot & Miao-Plugin"}}
18 |
19 |
20 | 21 | {{each helpGroup group}} 22 | {{set len = group?.list?.length || 0 }} 23 |
24 |
{{group.group}}
25 | {{if len > 0}} 26 |
27 |
28 | {{each group.list help idx}} 29 |
30 | 31 | {{help.title}} 32 | {{help.desc}} 33 |
34 | {{if idx%colCount === colCount-1 && idx>0 && idx< len-1}} 35 |
36 |
37 | {{/if}} 38 | {{/each}} 39 | <% for(let i=(len-1)%colCount; i< colCount-1 ; i++){ %> 40 |
41 | <% } %> 42 |
43 |
44 | {{/if}} 45 |
46 | {{/each}} 47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /resources/help/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'font'; 3 | src: url("../font/font.woff") format("truetype"); 4 | } 5 | 6 | body { 7 | font-size: 18px; 8 | color: #1e1f20; 9 | font-family: "font"; 10 | transform: scale(1.4); 11 | transform-origin: 0 0; 12 | width: 600px; 13 | } 14 | 15 | .container { 16 | width: 600px; 17 | padding: 20px 15px 10px 15px; 18 | background-size: contain; 19 | } 20 | 21 | .head-box { 22 | position: relative; 23 | margin: 60px 0 0 0; 24 | padding-bottom: 0; 25 | color: #fff; 26 | border-radius: 15px; 27 | 28 | .title { 29 | font-size: 50px; 30 | color: #bbffff; 31 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); 32 | } 33 | 34 | .label { 35 | font-size: 20px; 36 | text-shadow: -2px 0 rgb(237, 19, 48), 0 2px rgb(237, 19, 48), 2px 0 rgb(237, 19, 48), 0 -2px rgb(237, 19, 48); 37 | } 38 | } 39 | 40 | .cont-box { 41 | border-radius: 15px; 42 | margin: 20px 0; 43 | overflow: hidden; 44 | box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15); 45 | position: relative; 46 | } 47 | 48 | .help-group { 49 | font-size: 21px; 50 | font-weight: bold; 51 | padding: 15px 15px 10px 20px; 52 | } 53 | 54 | .help-table { 55 | text-align: center; 56 | border-collapse: collapse; 57 | margin: 0; 58 | border-radius: 0 0 10px 10px; 59 | display: table; 60 | overflow: hidden; 61 | width: 100%; 62 | color: #fff; 63 | 64 | .tr { 65 | display: table-row; 66 | 67 | &:last-child .td { 68 | padding-bottom: 12px; 69 | } 70 | } 71 | 72 | .td, 73 | .th { 74 | font-size: 14px; 75 | display: table-cell; 76 | box-shadow: 0 0 1px 0 #888 inset; 77 | padding: 12px 0 12px 50px; 78 | line-height: 24px; 79 | position: relative; 80 | text-align: left; 81 | } 82 | 83 | .th { 84 | background: rgba(34, 41, 51, 0.5); 85 | } 86 | } 87 | 88 | .help-icon { 89 | width: 40px; 90 | height: 40px; 91 | display: block; 92 | position: absolute; 93 | background: url("./imgs/icon.png") 0 0 no-repeat; 94 | background-size: 500px auto; 95 | border-radius: 5px; 96 | left: 6px; 97 | top: 12px; 98 | transform: scale(0.85); 99 | } 100 | 101 | .help-title { 102 | display: block; 103 | color: #d3bc8e; 104 | font-size: 23px; 105 | line-height: 24px; 106 | } 107 | 108 | .help-desc { 109 | display: block; 110 | font-size: 16px; 111 | text-shadow: 0 0 #ffffff, 0 1.5px black, 1.5px 0 black, 0 0 grey, 0 0 pink; 112 | line-height: 20px; 113 | } 114 | 115 | .copyright { 116 | font-size: 14px; 117 | text-align: center; 118 | color: #fff; 119 | position: relative; 120 | padding-left: 10px; 121 | text-shadow: 1px 1px 1px #000; 122 | margin: 10px 0; 123 | 124 | .version { 125 | color: #d3bc8e; 126 | display: inline-block; 127 | padding: 0 3px; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /resources/help/theme/default/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/help/theme/default/bg.jpg -------------------------------------------------------------------------------- /resources/help/theme/default/config.js: -------------------------------------------------------------------------------- 1 | export const style = { 2 | // 主文字颜色 3 | fontColor: "#00ffff", 4 | // 主文字阴影: 横向距离 垂直距离 阴影大小 阴影颜色 5 | // fontShadow: '0px 0px 1px rgba(6, 21, 31,.9), 6 | // fontShadow: '0.5px 0.5px 8px rgba(0, 0, 0,1)', 7 | fontShadow: "-1px 0 black,0 1px black,1px 0 black,0 -1px black", 8 | // 描述文字颜色 9 | descColor: "#ffffff", 10 | 11 | /* 面板整体底色,会叠加在标题栏及帮助行之下,方便整体帮助有一个基础底色 12 | * 若无需此项可将rgba最后一位置为0即为完全透明 13 | * 注意若综合透明度较低,或颜色与主文字颜色过近或太透明可能导致阅读困难 */ 14 | contBgColor: "rgba(6, 21, 31, 0)", 15 | 16 | // 面板底图毛玻璃效果,数字越大越模糊,0-10 ,可为小数 17 | contBgBlur: 1.9, 18 | 19 | // 板块标题栏底色 20 | headerBgColor: "rgba(172, 229, 209, .4)", 21 | // 帮助奇数行底色 22 | rowBgColor1: "rgba(6, 21, 31, .1)", 23 | // 帮助偶数行底色 24 | rowBgColor2: "rgba(6, 21, 31, .1)" 25 | } 26 | -------------------------------------------------------------------------------- /resources/help/theme/default/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/help/theme/default/main.jpg -------------------------------------------------------------------------------- /resources/help/theme/目录说明.txt: -------------------------------------------------------------------------------- 1 | 【default皮肤】 2 | default为默认皮肤,不建议改动,防止后续更新冲突 3 | 如不想使用default可创建或下载其他皮肤,存在其他皮肤时会默认忽略default皮肤 4 | 5 | 【增加自定义皮肤】 6 | 可创建或下载皮肤包,放置在当前目录(theme)下,皮肤名称为皮肤文件夹名字 7 | 8 | 皮肤包内应包含的文件: 9 | main.png:主图,高度自适应 10 | bg.jpg:背景图,如果main.png图片不够高或存在透明的话则会使用bg.jpg作为背景进行填充 11 | config.js:当前皮肤元素的颜色、透明度等配置,可选。如无此文件会使用默认配置,如需自定义,可参考default/config.js 12 | 13 | 14 | 【皮肤选择】 15 | 默认为随机皮肤,如需指定固定某个皮肤可到config/help.js中,设置theme的字段选项 16 | 17 | 在有其他皮肤时,会默认忽略自带的default皮肤 18 | 如希望default皮肤也出现在随机中,可修改config/help.js,将themeExclude中配置的default项删掉 19 | 如需临时增加其他皮肤屏蔽,也可以追加至themeExclude中(当然也可以直接删掉对应皮肤) 20 | 21 | 新增皮肤或修改配置后无需重启,可直接生效 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/help/version-info.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "font"; 3 | src: url("../font/font.woff"); 4 | } 5 | * { 6 | margin: 0; 7 | padding: 0; 8 | font-family: "font"; 9 | box-sizing: border-box; 10 | -webkit-user-select: none; 11 | -moz-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | body { 16 | font-size: 18px; 17 | color: #1e1f20; 18 | transform-origin: 0 0; 19 | width: 520px; 20 | } 21 | 22 | .container { 23 | width: 520px; 24 | padding: 10px 0; 25 | background: #1e1f20; 26 | } 27 | 28 | .cont { 29 | border-radius: 10px; 30 | background: url("./imgs/card-bg.png") top left repeat-x; 31 | background-size: auto 100%; 32 | margin: 15px 0px; 33 | position: relative; 34 | box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, 0.8); 35 | overflow: hidden; 36 | color: #fff; 37 | font-size: 16px; 38 | } 39 | .cont .cont-title { 40 | background: rgba(0, 0, 0, 0.4); 41 | box-shadow: 0 0 1px 0 #fff; 42 | color: #d3bc8e; 43 | padding: 10px 20px; 44 | text-align: left; 45 | border-radius: 10px 10px 0 0; 46 | } 47 | .cont .cont-body { 48 | padding: 10px 15px; 49 | font-size: 12px; 50 | background: rgba(0, 0, 0, 0.5); 51 | box-shadow: 0 0 1px 0 #fff; 52 | font-weight: normal; 53 | } 54 | 55 | .log-cont { 56 | background-size: cover; 57 | margin: 5px 15px; 58 | border-radius: 10px; 59 | } 60 | .log-cont .cont-title { 61 | font-size: 16px; 62 | padding: 10px 20px 6px; 63 | } 64 | .log-cont .cont-title.current-version { 65 | font-size: 20px; 66 | } 67 | .log-cont ul { 68 | font-size: 14px; 69 | padding-left: 20px; 70 | } 71 | .log-cont ul li { 72 | margin: 3px 0; 73 | } 74 | .log-cont ul.sub-log-ul li { 75 | margin: 1px 0; 76 | } 77 | .log-cont .cmd { 78 | color: #d3bc8e; 79 | display: inline-block; 80 | border-radius: 3px; 81 | background: rgba(0, 0, 0, 0.5); 82 | padding: 0 3px; 83 | margin: 1px 2px; 84 | } 85 | .log-cont .strong { 86 | color: #24d5cd; 87 | } 88 | .log-cont .new { 89 | display: inline-block; 90 | width: 18px; 91 | margin: 0 -3px 0 1px; 92 | } 93 | .log-cont .new::before { 94 | content: "NEW"; 95 | display: inline-block; 96 | transform: scale(0.6); 97 | transform-origin: 0 0; 98 | color: #d3bc8e; 99 | white-space: nowrap; 100 | } 101 | 102 | .copyright { 103 | font-size: 14px; 104 | text-align: center; 105 | color: #fff; 106 | position: relative; 107 | padding-left: 10px; 108 | text-shadow: 1px 1px 1px #000; 109 | margin: 10px 0; 110 | } 111 | .copyright .version { 112 | color: #d3bc8e; 113 | display: inline-block; 114 | padding: 0 3px; 115 | }/*# sourceMappingURL=version-info.css.map */ -------------------------------------------------------------------------------- /resources/help/version-info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DF-Plugin 9 | 10 | 11 |
12 | {{each changelogs ds idx}} 13 |
14 |
15 | {{if idx === 0 }} 16 |
当前版本 {{ds.version}}
17 | {{else}} 18 |
DF版本 {{ds.version}}
19 | {{/if}} 20 |
21 |
    22 | {{each ds.logs log}} 23 |
  • 24 |

    {{@log.title}}

    25 | {{if log.logs.length > 0}} 26 |
      27 | {{each log.logs ls}} 28 |
    • {{@ls}}
    • 29 | {{/each}} 30 |
    31 | {{/if}} 32 |
  • 33 | {{/each}} 34 |
35 |
36 |
37 |
38 | {{/each}} 39 | 40 |
41 | 42 | -------------------------------------------------------------------------------- /resources/help/version-info.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "font"; 3 | src: url("../font/font.woff"); 4 | } 5 | 6 | @mixin linear-bg($color) { 7 | background-image: linear-gradient(to right, $color, $color 80%, fade($color, 0) 100%); 8 | } 9 | 10 | * { 11 | margin: 0; 12 | padding: 0; 13 | font-family: "font"; 14 | box-sizing: border-box; 15 | -webkit-user-select: none; 16 | user-select: none; 17 | } 18 | 19 | body { 20 | font-size: 18px; 21 | color: #1e1f20; 22 | transform-origin: 0 0; 23 | width: 520px; 24 | } 25 | 26 | .container { 27 | width: 520px; 28 | padding: 10px 0; 29 | background: #1e1f20; 30 | } 31 | 32 | .cont { 33 | border-radius: 10px; 34 | background: url("./imgs/card-bg.png") top left repeat-x; 35 | background-size: auto 100%; 36 | margin: 15px 0px; 37 | position: relative; 38 | box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, 0.8); 39 | overflow: hidden; 40 | color: #fff; 41 | font-size: 16px; 42 | 43 | .cont-title { 44 | background: rgba(0, 0, 0, 0.4); 45 | box-shadow: 0 0 1px 0 #fff; 46 | color: #d3bc8e; 47 | padding: 10px 20px; 48 | text-align: left; 49 | border-radius: 10px 10px 0 0; 50 | } 51 | 52 | .cont-body { 53 | padding: 10px 15px; 54 | font-size: 12px; 55 | background: rgba(0, 0, 0, 0.5); 56 | box-shadow: 0 0 1px 0 #fff; 57 | font-weight: normal; 58 | } 59 | } 60 | 61 | .log-cont { 62 | background-size: cover; 63 | margin: 5px 15px; 64 | border-radius: 10px; 65 | 66 | .cont-title { 67 | font-size: 16px; 68 | padding: 10px 20px 6px; 69 | 70 | &.current-version { 71 | font-size: 20px; 72 | } 73 | } 74 | 75 | ul { 76 | font-size: 14px; 77 | padding-left: 20px; 78 | 79 | li { 80 | margin: 3px 0; 81 | } 82 | 83 | &.sub-log-ul { 84 | li { 85 | margin: 1px 0; 86 | } 87 | } 88 | } 89 | 90 | .cmd { 91 | color: #d3bc8e; 92 | display: inline-block; 93 | border-radius: 3px; 94 | background: rgba(0, 0, 0, 0.5); 95 | padding: 0 3px; 96 | margin: 1px 2px; 97 | } 98 | 99 | .strong { 100 | color: #24d5cd; 101 | } 102 | 103 | .new { 104 | display: inline-block; 105 | width: 18px; 106 | margin: 0 -3px 0 1px; 107 | } 108 | 109 | .new::before { 110 | content: "NEW"; 111 | display: inline-block; 112 | transform: scale(0.6); 113 | transform-origin: 0 0; 114 | color: #d3bc8e; 115 | white-space: nowrap; 116 | } 117 | } 118 | 119 | .copyright { 120 | font-size: 14px; 121 | text-align: center; 122 | color: #fff; 123 | position: relative; 124 | padding-left: 10px; 125 | text-shadow: 1px 1px 1px #000; 126 | margin: 10px 0; 127 | 128 | .version { 129 | color: #d3bc8e; 130 | display: inline-block; 131 | padding: 0 3px; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /resources/img/Roxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/img/Roxy.png -------------------------------------------------------------------------------- /resources/img/ys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenFengLai/DF-Plugin/0fec2d5dd7440c7d1ccac208857033c65710f366/resources/img/ys.png --------------------------------------------------------------------------------