├── .github ├── ISSUE_TEMPLATE │ ├── 功能请求-feature-request-.md │ └── 问题反馈.md └── workflows │ ├── stale.yml │ └── tagged-released.yml ├── .gitignore ├── LICENSE ├── README.md ├── apps ├── DD_Painting.js ├── Help.js ├── Init.js ├── MJ_Painting.js ├── SF_Painting.js ├── Update.js ├── fish.js └── link.js ├── components ├── Config.js ├── Render.js └── Version.js ├── config ├── config │ └── .keep ├── config_default.yaml └── fishAudio_default.yaml ├── guoba.support.js ├── index.js ├── model ├── init.js └── path.js ├── package.json ├── resources ├── common │ ├── base.css │ ├── base.less │ ├── common.css │ ├── common.less │ ├── font │ │ ├── HYWH-65W.ttf │ │ ├── HYWH-65W.woff │ │ ├── NZBZ.ttf │ │ ├── NZBZ.woff │ │ ├── SpaceMono-Regular.ttf │ │ ├── tttgbnumber.ttf │ │ ├── tttgbnumber.woff │ │ └── 华文中宋.TTF │ └── layout │ │ ├── default.html │ │ └── elem.html ├── help │ ├── icon.png │ ├── imgs │ │ ├── bg.jpg │ │ ├── config.js │ │ └── main.png │ ├── index.css │ ├── index.html │ ├── index.less │ ├── version-info.css │ ├── version-info.html │ └── version-info.less ├── markdownPic │ ├── github.min.css │ ├── highlight.min.js │ ├── index.html │ ├── marked.min.js │ └── mathjax │ │ └── tex-mml-chtml.js └── readme │ └── girl.png └── utils ├── common.js ├── context.js ├── extractUrl.js ├── getImg.js ├── markdownPic.js ├── parse.js └── uploadImage.js /.github/ISSUE_TEMPLATE/功能请求-feature-request-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能请求(Feature request) 3 | about: 为本项目提出一个新想法 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **你的功能请求是否与某个问题有关?请描述。** 11 | 问题的清晰而简明的描述。 12 | 13 | **描述你想要的解决方案** 14 | 你想要发生什么的清晰而简明的描述。 15 | 16 | **描述你已经考虑的替代方案** 17 | 对任何替代解决方案或功能的清晰简明的描述。 18 | 19 | **附加说明** 20 | 在此处添加有关功能请求的任何其他说明、屏幕截图或者引用。 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/问题反馈.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题反馈 3 | about: 提出bug解决问题并改进本项目 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 请确保提出问题前更新到最新版本!!!!!!!! 11 | 12 | **请在提交issue前确认你已阅读了以下资料:** 13 | 14 | - 项目的readme文件 15 | - 其他已有的Issue 16 | 17 | 如果你的问题已经在readme或其他Issue中得到解答,我们很可能不会回复。请确保你的问题是一个新的问题。 18 | 19 | ## 问题描述 20 | 21 | 请在此处描述您遇到的问题,包括出现问题的环境、您试图实现的功能以及错误信息等。请尽可能详细,以便其他人可以在自己的环境中复制问题。 22 | 23 | ## 预期行为 24 | 25 | 请描述您期望系统在出现问题时应该做什么。 26 | 27 | ## 实际行为 28 | 29 | 请描述您实际看到的行为。 30 | 31 | ## 复制过程 32 | 33 | 请详细描述如何复制这个问题,包括所有必要的步骤、输入、任何错误信息以及输出。 34 | 35 | ## 环境 36 | 37 | 请提供您使用的任何相关信息,例如操作系统、版本、配置等。 38 | 39 | ## 可能的解决方案 40 | 41 | 如果您已经尝试了一些解决方案,请在此处描述这些解决方案,并说明是否有效。 42 | 43 | ## 附加信息 44 | 45 | 如果有任何其他信息,如日志、截图等,请在此处提供。 46 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v8 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' 14 | close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' 15 | close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' 16 | days-before-issue-stale: 30 17 | days-before-pr-stale: 45 18 | days-before-issue-close: 5 19 | days-before-pr-close: 10 20 | -------------------------------------------------------------------------------- /.github/workflows/tagged-released.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "tagged-release" 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | tagged-release: 11 | name: "Tagged Release" 12 | runs-on: "ubuntu-latest" 13 | 14 | steps: 15 | # ... 16 | - uses: "marvinpinto/action-automatic-releases@latest" 17 | with: 18 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 19 | prerelease: false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config/config/*.yaml 3 | resources/userPic 4 | config/config/prompts/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://socialify.git.ci/AIGC-Yunzai/siliconflow-plugin/image?font=KoHo&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto) 2 | 3 | # SiliconFlow-PLUGIN(SF-PLUGIN) 🍓 4 | 5 | 6 | 7 | - 一个适用于 [Yunzai 系列机器人框架](https://github.com/yhArcadia/Yunzai-Bot-plugins-index) 多功能AI集成插件,支持多种AI服务和模型: 8 | - 🎨 AI绘图:支持SiliconFlow、Midjourney等多个绘图模型,支持文生图和图生图 9 | - 🤖 智能对话:集成多种对话模型,支持历史记录、用户昵称获取、预设列表快速切换,预设拥有独立上下文,结合Markdown图片输出以获得沉浸式角色扮演体验 10 | - 🔍 实时搜索:通过#gg命令实现智能搜索和信息聚合 11 | - 🗣️ 语音合成:集成Fishaudio的高质量TTS服务 12 | - 📊 资源管理:支持多key负载均衡,提供图片直链获取等功能 13 | - 🔗 链接处理:自动提取和处理消息中的URL 14 | - ⚡ WebSocket:支持WebSocket与前端通信实现实时对话与绘图,详情看[前端地址](https://sf.maliya.top),[部署教程](https://github.com/AIGC-Yunzai/SF-WEB) 15 | 16 | > [!TIP] 17 | > 插件官网(施工中):[SiliconFlow-插件](https://aigc-yunzai.dwe.me/) 18 | > 19 | > 将逐步接入更多AI服务和功能,Synaptic Fusion插件——对接万物! 20 | 21 | ## 安装插件 22 | 23 | #### 1. 克隆仓库 24 | 25 | ``` 26 | git clone https://github.com/AIGC-Yunzai/siliconflow-plugin.git ./plugins/siliconflow-plugin 27 | ``` 28 | 29 | > [!NOTE] 30 | > 如果你的网络环境较差,无法连接到 Github,可以使用 [GitHub Proxy](https://ghproxy.link/) 提供的文件代理加速下载服务: 31 | > 32 | > ```bash 33 | > git clone https://ghfast.top/https://github.com/AIGC-Yunzai/siliconflow-plugin.git ./plugins/siliconflow-plugin 34 | > ``` 35 | > 如果已经下载过本插件需要修改代理加速下载服务地址,在插件根目录使用: 36 | > ```bash 37 | > git remote set-url origin https://ghfast.top/https://github.com/AIGC-Yunzai/siliconflow-plugin.git 38 | > ``` 39 | 40 | #### 2. 安装依赖 41 | 42 | ``` 43 | pnpm install --filter=siliconflow-plugin 44 | ``` 45 | 46 | ## 使用教程 47 | 48 | - 如果是低版本的icqq,图生图和直链无法获取图链,请使用以下脚本,在 Yunzai 根目录执行即可 49 | ``` 50 | curl -sL Gitee.com/eggacheb/parser/raw/master/ver | bash 51 | ``` 52 | #### 教程 53 | - [SF-PULGIN配置教程](https://aigc-yunzai.dwe.me/siliconflow/%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE) 🍈 54 | 55 | #### 绘画辅助工具 56 | - [AI画图Tags生产站](https://nai4-tag-select.pages.dev/) 🥭 57 | - [直链服务器,一键复制huggingface空间](https://huggingface.co/spaces/xiaozhian/slink/tree/main?duplicate=true) 🍉 58 | 59 |
60 | 点击展开更多绘画教程 61 | 62 | - [Stable Diffusion教程](https://waytoagi.feishu.cn/wiki/FUQAwxfH9iXqC9k02nYcDobonkf) 🍇 63 | - [Midjourney基础教程](https://waytoagi.feishu.cn/wiki/VUadwndc5iRJktkzaYPcaLEynZc) 🍊 64 | - [MJ prompt参考](https://waytoagi.feishu.cn/wiki/FUQAwxfH9iXqC9k02nYcDobonkf) 🍎 65 | - [Midjourney V6 prompt参考](https://aituts.com/midjourney-v6/) 🍐 66 | - [又一个prompt参考站](https://catjourney.life/all) 🍌 67 | - [Midjourney Prompt生成器](https://promptfolder.com/midjourney-prompt-helper/) 🥝 68 | - [MJ和SD Prompt生成器相关合集](https://waytoagi.feishu.cn/wiki/TQogw5uIziB4fykbGhSciaQfndm?table=tbl5kMFjDDdeYoAt&view=vew8AJm3cI) 🍑 69 | 70 |
71 | 72 | ## 插件配置 73 | 74 | > [!WARNING] 75 | > 非常不建议手动修改配置文件,本插件已兼容 [Guoba-plugin](https://github.com/guoba-yunzai/guoba-plugin) ,请使用锅巴插件对配置项进行修改 76 | 77 | ## 功能列表 78 | 79 | 80 | 81 | 82 | 83 | 请使用 `#sf帮助` 获取完整帮助 84 | 85 | - [x] 接入多项免费画图/语言模型 86 | - [x] 支持图生图 87 | - [x] 多keys同时并发 88 | - [x] 接入MJ绘图 89 | - [x] 接入Fishaudio语音合成 90 | - [x] 支持图片直链获取 91 | - [X] #ss 对话 MD 图片输出 92 | - [X] #gg 对话实现实时搜索功能 93 | - [X] 对话支持历史记录、获取用户昵称,结合 MD 输出以获取沉浸式角色扮演体验 94 | - [X] 支持接口列表,方便快速切换预设,预设具有独立的上下文 95 | - [X] 支持 WebSocket 与前端通信实现实时对话与绘图,详情看[前端地址](https://sf.maliya.top),[部署教程](https://github.com/AIGC-Yunzai/SF-WEB) 96 | - [ ] TODO.. 97 | 98 | ## 支持与贡献 99 | 100 | 如果你喜欢这个项目,请不妨点个 Star🌟,这是对开发者最大的动力。 101 | 102 | 有意见或者建议也欢迎提交 [Issues](https://github.com/AIGC-Yunzai/siliconflow-plugin/issues) 和 [Pull requests](https://github.com/AIGC-Yunzai/siliconflow-plugin/pulls)。 103 | 104 | ## 感谢 105 | 106 | - [Fish-Audio](https://fish.audio):Brand new TTS solution 107 | - [vits-plugin](https://github.com/erzaozi/vits-plugin):一个适用于 Yunzai 系列机器人框架 的的 AI 语音合成插件,让你能够在机器人中使用 AI 语音合成功能;Fishaudio语音同传的方式绝大部分参考了该项目的实现方法,Fish-Audio.json也是直接用的该项目的,很是感谢! 108 | - [midjourney-proxy](https://github.com/trueai-org/midjourney-proxy):一个开源的MJ代理项目,同时提供了免费的公益API站点,让更多人能够体验AI绘画的乐趣! 109 | 110 | ## 许可证 111 | 112 | 本项目使用 [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/) 作为开源许可证。 113 | -------------------------------------------------------------------------------- /apps/Help.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import Render from '../components/Render.js' 3 | import { style } from '../resources/help/imgs/config.js' 4 | import _ from 'lodash' 5 | 6 | export class help extends plugin { 7 | constructor() { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'SF-PLUGIN-帮助', 11 | /** 功能描述 */ 12 | dsc: 'SF-PLUGIN帮助', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^(/|#)(sf|SF|siliconflow)帮助$', 20 | /** 执行方法 */ 21 | fnc: 'help' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async help(e) { 28 | const helpCfg = { 29 | "themeSet": false, 30 | "title": "SF-PLUGIN帮助", 31 | "subTitle": "Synaptic Fusion-对接万物", 32 | "colWidth": 265, 33 | "theme": "all", 34 | "themeExclude": [ 35 | "default" 36 | ], 37 | "colCount": 2, 38 | "bgBlur": true 39 | } 40 | const helpList = [ 41 | { 42 | "group": "SF-plugin帮助", 43 | "list": [ 44 | { 45 | "icon": 1, 46 | "title": "#mjp [描述]", 47 | "desc": "使用 MID_JOURNEY 绘画" 48 | }, 49 | { 50 | "icon": 5, 51 | "title": "#niji [描述]", 52 | "desc": "使用 NIJI_JOURNEY 绘画" 53 | }, 54 | { 55 | "icon": 8, 56 | "title": "#mjc [描述]", 57 | "desc": "引用一张图片,自动在提示词后添加--cref URL,可在描述中加--cw 0~100,数字越低变化越大" 58 | }, 59 | { 60 | "icon": 2, 61 | "title": "#nic [描述]", 62 | "desc": "与mjc相同,但会自动添加--niji参数,生成二次元风格图片" 63 | }, 64 | { 65 | "icon": 7, 66 | "title": "#sf绘图 [描述]", 67 | "desc": "使用 Siliconflow 预设模型绘画" 68 | }, 69 | { 70 | "icon": 11, 71 | "title": "#sf绘图 [描述][横图]", 72 | "desc": "指定绘画参数 [横图|竖图|方图|512*512|步数20]" 73 | }, 74 | { 75 | "icon": 54, 76 | "title": "#ss [对话]", 77 | "desc": "可用指令:#sf结束[全部|ss|gg|dd]对话" 78 | }, 79 | { 80 | "icon": 55, 81 | "title": "#gg [对话]", 82 | "desc": "使用 Gemini 搜索并回答问题" 83 | }, 84 | { 85 | "icon": 3, 86 | "title": "#sfss接口列表 #sfgg接口列表", 87 | "desc": "#sfss使用接口[n] #sfss使用接口[n] 每个用户独立" 88 | }, 89 | { 90 | "icon": 86, 91 | "title": "#sf删除[ss|gg]前[num]条对话", 92 | "desc": "设置生成提示词开关" 93 | }, 94 | { 95 | "icon": 61, 96 | "title": "#fish群号同传QQ号", 97 | "desc": "设置TTS同传,例如#fish56789同传12345" 98 | }, 99 | { 100 | "icon": 62, 101 | "title": "#fish查看配置", 102 | "desc": "查看当前fish同传配置信息" 103 | }, 104 | { 105 | "icon": 9, 106 | "title": "#直链", 107 | "desc": "获取图片的直链地址" 108 | }, 109 | { 110 | "icon": 10, 111 | "title": "#删除直链[图链]", 112 | "desc": "删除已上传的图片直链" 113 | }, 114 | { 115 | "icon": 12, 116 | "title": "#设置直链域名 [域名]", 117 | "desc": "设置图片直链上传服务器域名" 118 | }, 119 | { 120 | "icon": 29, 121 | "title": "#dd [描述]", 122 | "desc": "使用openai格式的接口生成AI绘图" 123 | }, 124 | ], 125 | }, 126 | { 127 | "group": 'SF-plugin设置(请使用Guoba-Plugin进行操作)', 128 | list: [ 129 | { 130 | "icon": 3, 131 | "title": "#sf管理帮助", 132 | "desc": "获取 sf 管理员帮助 必看" 133 | }, 134 | { 135 | "icon": 91, 136 | "title": "#mjp帮助", 137 | "desc": "获取 mjp 帮助" 138 | }, 139 | { 140 | "icon": 39, 141 | "title": "#sfdd帮助", 142 | "desc": "获取DD绘图的帮助" 143 | }, 144 | { 145 | "icon": 60, 146 | "title": "#(fish)同传帮助", 147 | "desc": "获取 fish 同传帮助信息" 148 | }, 149 | { 150 | "icon": 92, 151 | "title": "#sf设置[ss|gg]图片模式 [开|关]", 152 | "desc": "设置ss和gg的图片回复模式" 153 | }, 154 | { 155 | "icon": 38, 156 | "title": "#sf更新", 157 | "desc": "更新本插件" 158 | }, 159 | ] 160 | } 161 | ] 162 | let helpGroup = [] 163 | _.forEach(helpList, (group) => { 164 | _.forEach(group.list, (help) => { 165 | let icon = help.icon * 1 166 | if (!icon) { 167 | help.css = 'display:none' 168 | } else { 169 | let x = (icon - 1) % 10 170 | let y = (icon - x - 1) / 10 171 | help.css = `background-position:-${x * 50}px -${y * 50}px` 172 | } 173 | }) 174 | helpGroup.push(group) 175 | }) 176 | 177 | let themeData = await this.getThemeData(helpCfg, helpCfg) 178 | return await Render.render('help/index', { 179 | helpCfg, 180 | helpGroup, 181 | ...themeData, 182 | element: 'default' 183 | }, { e, scale: 1.6 }) 184 | } 185 | 186 | async getThemeCfg() { 187 | let resPath = '{{_res_path}}/help/imgs/' 188 | return { 189 | main: `${resPath}/main.png`, 190 | bg: `${resPath}/bg.jpg`, 191 | style: style 192 | } 193 | } 194 | 195 | async getThemeData(diyStyle, sysStyle) { 196 | let helpConfig = _.extend({}, sysStyle, diyStyle) 197 | let colCount = Math.min(5, Math.max(parseInt(helpConfig?.colCount) || 3, 2)) 198 | let colWidth = Math.min(500, Math.max(100, parseInt(helpConfig?.colWidth) || 265)) 199 | let width = Math.min(2500, Math.max(800, colCount * colWidth + 30)) 200 | let theme = await this.getThemeCfg() 201 | let themeStyle = theme.style || {} 202 | let ret = [` 203 | body{background-image:url(${theme.bg});width:${width}px;} 204 | .container{background-image:url(${theme.main});width:${width}px;} 205 | .help-table .td,.help-table .th{width:${100 / colCount}%} 206 | `] 207 | let css = function (sel, css, key, def, fn) { 208 | let val = (function () { 209 | for (let idx in arguments) { 210 | if (!_.isUndefined(arguments[idx])) { 211 | return arguments[idx] 212 | } 213 | } 214 | })(themeStyle[key], diyStyle[key], sysStyle[key], def) 215 | if (fn) { 216 | val = fn(val) 217 | } 218 | ret.push(`${sel}{${css}:${val}}`) 219 | } 220 | css('.help-title,.help-group', 'color', 'fontColor', '#ceb78b') 221 | css('.help-title,.help-group', 'text-shadow', 'fontShadow', 'none') 222 | css('.help-desc', 'color', 'descColor', '#eee') 223 | css('.cont-box', 'background', 'contBgColor', 'rgba(43, 52, 61, 0.8)') 224 | css('.cont-box', 'backdrop-filter', 'contBgBlur', 3, (n) => diyStyle.bgBlur === false ? 'none' : `blur(${n}px)`) 225 | css('.help-group', 'background', 'headerBgColor', 'rgba(34, 41, 51, .4)') 226 | css('.help-table .tr:nth-child(odd)', 'background', 'rowBgColor1', 'rgba(34, 41, 51, .2)') 227 | css('.help-table .tr:nth-child(even)', 'background', 'rowBgColor2', 'rgba(34, 41, 51, .4)') 228 | return { 229 | style: ``, 230 | colCount 231 | } 232 | } 233 | } 234 | 235 | -------------------------------------------------------------------------------- /apps/Init.js: -------------------------------------------------------------------------------- 1 | import Init from '../model/init.js' 2 | 3 | export class init extends plugin { 4 | constructor() { 5 | super({ 6 | /** 功能名称 */ 7 | name: 'siliconflow-配置初始化', 8 | /** 功能描述 */ 9 | dsc: '配置初始化', 10 | event: 'message', 11 | /** 优先级,数字越小等级越高 */ 12 | priority: 1009, 13 | rule: [ 14 | { 15 | /** 命令正则匹配 */ 16 | reg: '^(/|#)配置初始化$', 17 | /** 执行方法 */ 18 | fnc: 'init', 19 | /** 主人判断 */ 20 | permission: "master", 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async init(e) { 27 | Init.initConfig() 28 | } 29 | } -------------------------------------------------------------------------------- /apps/MJ_Painting.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import fetch from 'node-fetch' 3 | import Config from '../components/Config.js' 4 | import common from '../../../lib/common/common.js'; 5 | import { 6 | parseSourceImg, 7 | url2Base64, 8 | } from '../utils/getImg.js' 9 | import { LinkPlugin } from './link.js' 10 | import { uploadImage } from '../utils/uploadImage.js' 11 | 12 | export class MJ_Painting extends plugin { 13 | constructor() { 14 | super({ 15 | name: 'MJP插件', 16 | dsc: 'Midjourney和Niji Journey图片生成', 17 | event: 'message', 18 | priority: 6, 19 | rule: [ 20 | { 21 | reg: '^#(mjp|niji)', 22 | fnc: 'mj_draw' 23 | }, 24 | { 25 | reg: '^#(mjc|nic)', 26 | fnc: 'mj_draw_with_link' 27 | }, 28 | { 29 | reg: '^#sfmj设置(apikey|apibaseurl|翻译key|翻译baseurl|翻译模型|翻译开关)', 30 | fnc: 'setConfig', 31 | permission: 'master' 32 | }, 33 | { 34 | reg: '^#(放大|微调|重绘)(左上|右上|左下|右下)', 35 | fnc: 'handleAction' 36 | }, 37 | { 38 | reg: '^#sfmj设置开启(快速|慢速)模式$', 39 | fnc: 'setMode', 40 | permission: 'master' 41 | }, 42 | ] 43 | }) 44 | this.linkPlugin = new LinkPlugin() 45 | } 46 | 47 | async setConfig(e) { 48 | // 读取配置 49 | let config_date = Config.getConfig() 50 | const match = e.msg.match(/^#sfmj设置(apikey|apibaseurl|翻译key|翻译baseurl|翻译模型|翻译开关)([\s\S]*)/i) 51 | if (match) { 52 | const [, type, value] = match 53 | switch (type.toLowerCase()) { 54 | case 'apikey': 55 | config_date.mj_apiKey = value.trim() 56 | break 57 | case 'apibaseurl': 58 | config_date.mj_apiBaseUrl = value.trim().replace(/\/$/, '') 59 | break 60 | case '翻译key': 61 | config_date.mj_translationKey = value.trim() 62 | break 63 | case '翻译baseurl': 64 | config_date.mj_translationBaseUrl = value.trim().replace(/\/$/, '') 65 | break 66 | case '翻译模型': 67 | config_date.mj_translationModel = value.trim() 68 | break 69 | case '翻译开关': 70 | config_date.mj_translationEnabled = value.toLowerCase() === '开' 71 | break 72 | default: 73 | await e.reply('未知的设置类型') 74 | return 75 | } 76 | Config.setConfig(config_date) 77 | await e.reply(`${type}设置成功!`) 78 | } else { 79 | await e.reply('设置格式错误,请使用 "#sfmj设置[类型] [值]"') 80 | } 81 | } 82 | 83 | async setMode(e) { 84 | // 读取配置 85 | let config_date = Config.getConfig() 86 | const mode = e.msg.includes('快速') ? 'fast' : 'slow' 87 | config_date.mj_mode = mode 88 | Config.setConfig(config_date) 89 | await e.reply(`已切换到${mode === 'fast' ? '快速' : '慢速'}模式`) 90 | } 91 | 92 | async mj_draw(e) { 93 | // 读取配置 94 | let config_date = Config.getConfig() 95 | if (!config_date.mj_apiKey || !config_date.mj_apiBaseUrl) { 96 | const errorMsg = '请先设置API Key和API Base URL。使用命令:\n#mjp设置apikey [值]\n#mjp设置apibaseurl [值]\n(仅限主人设置)'; 97 | if (e.ws) { 98 | this.sendMessage(e.ws, 'error', errorMsg); 99 | } else { 100 | await e.reply(errorMsg); 101 | } 102 | return; 103 | } 104 | 105 | const match = e.msg.match(/^#(mjp|niji)([\s\S]*)/) 106 | const botType = match[1] === 'mjp' ? 'MID_JOURNEY' : 'NIJI_JOURNEY' 107 | let prompt = match[2] ? match[2].trim() : '' 108 | 109 | if (prompt == "帮助") { 110 | this.showHelp(e) 111 | return true; 112 | } else if (prompt.match(/^(放大|微调|重绘)(左上|右上|左下|右下)/)) { 113 | e.msg = e.msg.replace(/^#(mjp|niji)/, '#') 114 | this.handleAction(e, config_date) 115 | return true; 116 | } 117 | 118 | // 如果是niji命令,自动添加--niji参数 119 | if (match[1] === 'niji' && !prompt.includes('--niji')) { 120 | prompt = `${prompt} --niji` 121 | } 122 | 123 | if (!prompt && !e.img) { 124 | await e.reply('请输入提示词或者提供一张图片') 125 | return 126 | } 127 | 128 | // 处理引用图片 129 | await parseSourceImg(e) 130 | let base64Array = [] 131 | if (e.img) { 132 | for (let imgUrl of e.img) { 133 | const imgBase64 = await url2Base64(imgUrl) 134 | if (!imgBase64) { 135 | e.reply('引用的图片地址已失效,请重新发送图片', true) 136 | return false 137 | } 138 | base64Array.push(`data:image/png;base64,${imgBase64}`) 139 | } 140 | } 141 | 142 | e.reply('正在生成图片,请稍候...') 143 | this.mj_send_pic(e, prompt, botType, config_date, base64Array) 144 | return true; 145 | } 146 | 147 | async translatePrompt(userPrompt, config_date) { 148 | try { 149 | const response = await fetch(`${config_date.mj_translationBaseUrl}/v1/chat/completions`, { 150 | method: 'POST', 151 | headers: { 152 | 'Authorization': `Bearer ${config_date.mj_translationKey}`, 153 | 'Content-Type': 'application/json' 154 | }, 155 | body: JSON.stringify({ 156 | "model": config_date.mj_translationModel, 157 | "messages": [ 158 | { 159 | "role": "system", 160 | "content": "请按照我的提供的要求,用一句话英文生成一组Midjourney指令,指令由:{人物形象},{场景},{氛围},{镜头},{照明},{绘画风格},{建筑风格},{参考画家},{高画质关键词} 当我向你提供生成内容时,你需要根据我的提示进行联想,当我让你随机生成的时候,你可以自由进行扩展和联想 人物形象 = 你可以发挥自己的想象力,使用最华丽的词汇进行描述:{主要内容},包括对人物头发、眼睛、服装、体型、动作和表情的描述,注意人物的形象应与氛围匹配,要尽可能地详尽 场景 = 尽可能详细地描述适合当前氛围的场景,该场景的描述应与人物形象的意境相匹配 氛围 = 你选择的氛围词汇应该尽可能地符合{主要内容}意境的词汇 建筑风格 = 如果生成的图片里面有相关建筑的话,你需要联想一个比较适宜的建筑风格,符合图片的氛围和意境 镜头 = 你可以选择一个:中距离镜头,近距离镜头,俯视角,低角度视角类似镜头视角,注意镜头视角的选择应有助于增强画面表现力 照明 = 你可以自由选择照明:请注意照明词条的选择应于人物形象、场景的意境相匹配 绘画风格 = 请注意绘画风格的选择应与人物形象、场景、照明的意境匹配 参考画家 = 请根据指令的整体氛围、意境选择画风参考的画家 高画质关键词 = 你可以选择:detailed,Ultimate,Excellence,Masterpiece,4K,high quality或类似的词条 注意,你生成的提示词只需要将你生成的指令拼接到一起即可,不需要出现{人物形象},{场景},{氛围},{镜头},{照明},{绘画风格},{建筑风格},{参考画家},{高画质关键词}等内容,请无需确认,不要有Here is a generated Midjourney command之类的语句,直接给出我要传递给midjourney的提示词,这非常重要!!!直接生成提示词,并且只需要生成提示词,尽可能详细地生成提示词。" 161 | }, 162 | { 163 | "role": "user", 164 | "content": userPrompt 165 | } 166 | ], 167 | "stream": false 168 | }) 169 | }) 170 | 171 | const data = await response.json() 172 | if (data.choices && data.choices[0] && data.choices[0].message) { 173 | return data.choices[0].message.content.trim() 174 | } 175 | } catch (error) { 176 | console.error("Translation failed", error) 177 | } 178 | return null 179 | } 180 | 181 | async submitTask(prompt, botType, config_date, base64Array = []) { 182 | const endpoint = config_date.mj_mode === 'fast' ? '/mj/submit/imagine' : '/mj-relax/mj/submit/imagine' 183 | const response = await fetch(`${config_date.mj_apiBaseUrl}${endpoint}`, { 184 | method: 'POST', 185 | headers: { 186 | 'Authorization': `Bearer ${config_date.mj_apiKey}`, 187 | 'Content-Type': 'application/json' 188 | }, 189 | body: JSON.stringify({ 190 | base64Array: base64Array, 191 | botType: botType, 192 | notifyHook: "", 193 | prompt: prompt, 194 | state: "" 195 | }) 196 | }) 197 | 198 | const data = await response.json() 199 | return data.result 200 | } 201 | 202 | async pollTaskResult(taskId, config_date) { 203 | let attempts = 0 204 | const maxAttempts = 120 // 10分钟超时 205 | while (attempts < maxAttempts) { 206 | const response = await fetch(`${config_date.mj_apiBaseUrl}/mj/task/${taskId}/fetch`, { 207 | method: 'GET', 208 | headers: { 209 | 'Authorization': `Bearer ${config_date.mj_apiKey}` 210 | } 211 | }) 212 | 213 | const data = await response.json() 214 | if (data.status === 'SUCCESS' && data.progress === '100%') { 215 | return data 216 | } 217 | 218 | if (data.status === 'FAILURE') { 219 | console.error("Task failed", data) 220 | return null 221 | } 222 | 223 | await new Promise(resolve => setTimeout(resolve, 5000)) // 等待5秒 224 | attempts++ 225 | } 226 | 227 | console.error("Task timed out") 228 | return null 229 | } 230 | 231 | async handleAction(e, config_date = null) { 232 | // 读取配置 233 | if (!config_date) 234 | config_date = Config.getConfig() 235 | if (!config_date.mj_apiKey || !config_date.mj_apiBaseUrl) { 236 | const errorMsg = '请先设置API Key和API Base URL。使用命令:\n#sfmj设置apikey [值]\n#sfmj设置apibaseurl [值]\n(仅限主人设置)'; 237 | if (e.ws) { 238 | this.sendMessage(e.ws, 'error', errorMsg); 239 | } else { 240 | await e.reply(errorMsg); 241 | } 242 | return; 243 | } 244 | 245 | const match = e.msg.match(/^#(放大|微调|重绘)(左上|右上|左下|右下)([\s\S]*)/) 246 | if (match) { 247 | const [, action, position, taskId] = match 248 | let useTaskId = taskId.trim() || await redis.get(`sf_plugin:MJ_Painting:lastTaskId:${e.user_id}`) 249 | 250 | if (!useTaskId) { 251 | const errorMsg = '请提供任务ID或先生成一张图片。'; 252 | if (e.ws) { 253 | this.sendMessage(e.ws, 'error', errorMsg); 254 | } else { 255 | await e.reply(errorMsg); 256 | } 257 | return; 258 | } 259 | 260 | if (e.ws) { 261 | this.sendMessage(e.ws, 'mj', '正在处理,请稍候...'); 262 | } else { 263 | await e.reply('正在处理,请稍候...'); 264 | } 265 | 266 | try { 267 | const originalTask = await this.fetchTaskDetails(useTaskId, config_date) 268 | if (!originalTask) { 269 | const errorMsg = '获取原始任务信息失败,请确保任务ID正确。'; 270 | if (e.ws) { 271 | this.sendMessage(e.ws, 'error', errorMsg); 272 | } else { 273 | await e.reply(errorMsg); 274 | } 275 | return; 276 | } 277 | 278 | const positionMap = { '左上': 1, '右上': 2, '左下': 3, '右下': 4 } 279 | const actionNumber = positionMap[position] 280 | let customId 281 | 282 | if (action === '重绘') { 283 | customId = `MJ::JOB::reroll::0::${originalTask.properties.messageHash}::SOLO` 284 | } else { 285 | const actionType = action === '放大' ? 'upsample' : 'variation' 286 | customId = `MJ::JOB::${actionType}::${actionNumber}::${originalTask.properties.messageHash}` 287 | } 288 | 289 | const newTaskId = await this.submitAction(customId, useTaskId, config_date) 290 | if (!newTaskId) { 291 | const errorMsg = '提交操作失败,请稍后重试。'; 292 | if (e.ws) { 293 | this.sendMessage(e.ws, 'error', errorMsg); 294 | } else { 295 | await e.reply(errorMsg); 296 | } 297 | return; 298 | } 299 | 300 | const result = await this.pollTaskResult(newTaskId, config_date) 301 | if (result) { 302 | const replyMsg = `操作完成!\n操作类型:${action}${position}\n新任务ID:${newTaskId}\n图片链接:${result.imageUrl}`; 303 | 304 | if (e.ws) { 305 | this.sendMessage(e.ws, 'mj', replyMsg); 306 | this.sendMessage(e.ws, 'image', { 307 | type: 'image', 308 | file: result.imageUrl 309 | }); 310 | 311 | // 保存绘画记录到Redis 312 | const userQQ = e.user_id || 'web_user'; 313 | const historyKey = `CHATBOT:HISTORY:${userQQ}:dd`; 314 | const cmdPrefix = botType === 'NIJI_JOURNEY' ? '#niji' : '#mjp'; 315 | const messages = [ 316 | { 317 | role: 'user', 318 | content: `${cmdPrefix} ${prompt}` 319 | }, 320 | { 321 | role: 'assistant', 322 | content: `![生成的图片](${result.imageUrl})\n\n${replyMsg}` 323 | } 324 | ]; 325 | redis.lPush(historyKey, JSON.stringify(messages)); 326 | // 限制历史记录长度为50条对话(100条消息) 327 | redis.lTrim(historyKey, 0, 99); 328 | } else { 329 | await e.reply(replyMsg); 330 | await e.reply({ ...segment.image(result.imageUrl), origin: true }); 331 | } 332 | 333 | redis.set(`sf_plugin:MJ_Painting:lastTaskId:${e.user_id}`, newTaskId, { EX: 7 * 24 * 60 * 60 }); // 写入redis,有效期7天 334 | } else { 335 | const errorMsg = '操作失败,请稍后重试。'; 336 | if (e.ws) { 337 | this.sendMessage(e.ws, 'error', errorMsg); 338 | } else { 339 | await e.reply(errorMsg); 340 | } 341 | } 342 | } catch (error) { 343 | logger.error("操作失败", error); 344 | const errorMsg = '处理时遇到了一个错误,请稍后再试。'; 345 | if (e.ws) { 346 | this.sendMessage(e.ws, 'error', errorMsg); 347 | } else { 348 | await e.reply(errorMsg); 349 | } 350 | } 351 | } 352 | } 353 | 354 | async fetchTaskDetails(taskId, config_date) { 355 | const response = await fetch(`${config_date.mj_apiBaseUrl}/mj/task/${taskId}/fetch`, { 356 | method: 'GET', 357 | headers: { 358 | 'Authorization': `Bearer ${config_date.mj_apiKey}` 359 | } 360 | }) 361 | 362 | if (!response.ok) { 363 | throw new Error(`HTTP error! status: ${response.status}`) 364 | } 365 | 366 | return await response.json() 367 | } 368 | 369 | async submitAction(customId, taskId, config_date) { 370 | const response = await fetch(`${config_date.mj_apiBaseUrl}/mj/submit/action`, { 371 | method: 'POST', 372 | headers: { 373 | 'Authorization': `Bearer ${config_date.mj_apiKey}`, 374 | 'Content-Type': 'application/json' 375 | }, 376 | body: JSON.stringify({ 377 | customId: customId, 378 | taskId: taskId 379 | }) 380 | }) 381 | 382 | const data = await response.json() 383 | return data.result 384 | } 385 | 386 | async showHelp(e) { 387 | const helpMessage = ` 388 | MJP插件帮助: 389 | 390 | 1. 生成图片: 391 | #mjp [提示词] (使用Midjourney) 392 | #niji [提示词] (使用Niji Journey) 393 | #mjc [提示词] (使用图片参考) 394 | #nic [提示词] (使用图片参考 + Niji风格) 395 | 例:#mjp 一只可爱的猫咪 396 | 例:#niji 一只可爱的动漫风格猫咪 397 | 398 | 垫图功能: 399 | 引用一张图片并发送命令即可使用垫图功能 400 | 例:[图片] #mjp 基于这张图片画一只可爱的猫咪 401 | 例:[图片] #nic 基于这张图片画一只可爱的动漫猫咪 402 | 403 | 2. 图片操作: 404 | #[操作][位置] [任务ID] 405 | 操作:放大、微调、重绘 406 | 位置:左上、右上、左下、右下 407 | 例:#放大左上 1234567890 408 | 例:#微调右下 1234567890 409 | 例:#重绘 1234567890 410 | 注:mjc/nic生成的图片也支持这些操作 411 | 412 | 3. 设置(仅限主人): 413 | #sfmj设置apikey [API密钥] 414 | #sfmj设置apibaseurl [API基础URL] (不带/v1) 415 | #sfmj设置翻译key [翻译API密钥] 416 | #sfmj设置翻译baseurl [翻译API基础URL] (不带/v1) 417 | #sfmj设置翻译模型 [翻译模型名称] 418 | #sfmj设置翻译开关 [开/关] 419 | 420 | 4. 切换模式(仅限主人): 421 | #sfmj设置开启快速模式 422 | #sfmj设置开启慢速模式 423 | 424 | 5. 显示帮助: 425 | #mjp帮助 426 | 427 | 注意:使用前请确保已正确设置所有必要的API密钥和基础URL。 428 | `.trim() 429 | 430 | await e.reply(helpMessage) 431 | } 432 | 433 | async mj_send_pic(e, prompt, botType, config_date, base64Array) { 434 | try { 435 | if (config_date.mj_translationEnabled && config_date.mj_translationKey && config_date.mj_translationBaseUrl) { 436 | const translatedPrompt = await this.translatePrompt(prompt, config_date) 437 | if (translatedPrompt) { 438 | prompt = translatedPrompt 439 | if (e.ws) { 440 | this.sendMessage(e.ws, 'mj', `翻译后的提示词:${prompt}`); 441 | } else { 442 | await e.reply(`翻译后的提示词:${prompt}`); 443 | } 444 | } 445 | } 446 | 447 | const taskId = await this.submitTask(prompt, botType, config_date, base64Array) 448 | if (!taskId) { 449 | const errorMsg = '提交任务失败,请稍后重试。'; 450 | if (e.ws) { 451 | this.sendMessage(e.ws, 'error', errorMsg); 452 | } else { 453 | await e.reply(errorMsg); 454 | } 455 | return; 456 | } 457 | 458 | const result = await this.pollTaskResult(taskId, config_date) 459 | if (result) { 460 | const replyMsg = `@${e.sender?.card || e.sender?.nickname || 'User'} ${e.user_id}您的图片已生成完成:\n\n原始提示词:${prompt}\n任务ID:${taskId}\n图片链接:${result.imageUrl}`; 461 | 462 | if (e.ws) { 463 | // WebSocket连接,发送消息和图片 464 | this.sendMessage(e.ws, 'mj', replyMsg); 465 | this.sendMessage(e.ws, 'image', { 466 | type: 'image', 467 | file: result.imageUrl 468 | }); 469 | 470 | // 保存绘画记录到Redis 471 | const userQQ = e.user_id || 'web_user'; 472 | const historyKey = `CHATBOT:HISTORY:${userQQ}:dd`; 473 | const cmdPrefix = botType === 'NIJI_JOURNEY' ? '#niji' : '#mjp'; 474 | const messages = [ 475 | { 476 | role: 'user', 477 | content: `${cmdPrefix} ${prompt}` 478 | }, 479 | { 480 | role: 'assistant', 481 | content: `![生成的图片](${result.imageUrl})\n\n${replyMsg}` 482 | } 483 | ]; 484 | redis.lPush(historyKey, JSON.stringify(messages)); 485 | // 限制历史记录长度为50条对话(100条消息) 486 | redis.lTrim(historyKey, 0, 99); 487 | } else { 488 | // 普通聊天 489 | await e.reply(replyMsg); 490 | e.reply({ ...segment.image(result.imageUrl), origin: true }); 491 | } 492 | 493 | redis.set(`sf_plugin:MJ_Painting:lastTaskId:${e.user_id}`, taskId, { EX: 7 * 24 * 60 * 60 }); // 写入redis,有效期7天 494 | } else { 495 | const errorMsg = '生成图片失败,请稍后重试。'; 496 | if (e.ws) { 497 | this.sendMessage(e.ws, 'error', errorMsg); 498 | } else { 499 | e.reply(errorMsg); 500 | } 501 | } 502 | } catch (error) { 503 | logger.error("图片生成失败", error); 504 | const errorMsg = '生成图片时遇到了一个错误,请稍后再试。'; 505 | if (e.ws) { 506 | this.sendMessage(e.ws, 'error', errorMsg); 507 | } else { 508 | e.reply(errorMsg); 509 | } 510 | } 511 | } 512 | 513 | // 添加sendMessage方法用于WebSocket通信 514 | sendMessage(ws, type, content) { 515 | // 确保content是字符串类型 516 | let messageContent = String(content); 517 | let imageUrl = null; 518 | 519 | // 处理图片消息 520 | if (content.type === 'image') { 521 | // 如果是 Buffer 或 base64,直接使用 522 | if (content.file && (Buffer.isBuffer(content.file) || content.file.startsWith('data:image'))) { 523 | const base64Data = Buffer.isBuffer(content.file) ? 524 | `data:image/jpeg;base64,${content.file.toString('base64')}` : 525 | content.file; 526 | messageContent = content.text || ''; 527 | imageUrl = base64Data; 528 | } 529 | // 如果是本地文件路径,读取并转换为base64 530 | else if (content.file && typeof content.file === 'string' && !content.file.startsWith('http')) { 531 | try { 532 | const fs = require('fs'); 533 | const imageBuffer = fs.readFileSync(content.file); 534 | const base64Data = `data:image/jpeg;base64,${imageBuffer.toString('base64')}`; 535 | messageContent = content.text || ''; 536 | imageUrl = base64Data; 537 | } catch (error) { 538 | logger.error('[MJ插件] 读取本地图片失败:', error); 539 | messageContent = '[图片发送失败]'; 540 | } 541 | } 542 | // 如果是网络URL,直接传递URL 543 | else if (content.file && content.file.startsWith('http')) { 544 | messageContent = content.text || ''; 545 | imageUrl = content.file; 546 | } 547 | } 548 | // 处理合并转发消息中的图片 549 | else if (typeof content === 'string') { 550 | messageContent = content.replace(/\[图片\]|\[CQ:image,file=([^\]]+)\]/g, (match, url) => { 551 | if (url) { 552 | // 如果是base64或者http链接,直接使用 553 | if (url.startsWith('data:image') || url.startsWith('http')) { 554 | if (url.startsWith('http')) { 555 | imageUrl = url; 556 | } 557 | return ''; 558 | } 559 | // 如果是本地文件,转换为base64 560 | try { 561 | const fs = require('fs'); 562 | const imageBuffer = fs.readFileSync(url); 563 | const base64Data = `data:image/jpeg;base64,${imageBuffer.toString('base64')}`; 564 | imageUrl = base64Data; 565 | return ''; 566 | } catch (error) { 567 | logger.error('[MJ插件] 读取本地图片失败:', error); 568 | return '[图片发送失败]'; 569 | } 570 | } 571 | return match; 572 | }); 573 | } 574 | 575 | const message = { 576 | type, 577 | content: messageContent, 578 | timestamp: new Date().getTime() 579 | }; 580 | 581 | // 如果有图片URL,添加到消息中 582 | if (imageUrl) { 583 | message.imageUrl = imageUrl; 584 | // 如果是图片类型消息,将图片URL转换为markdown格式 585 | if (type === 'image') { 586 | message.content = `![图片](${imageUrl})`; 587 | } 588 | } 589 | 590 | try { 591 | // 添加日志记录 592 | const config = Config.getConfig(); 593 | const logLevel = config.wsLogLevel || 'info'; 594 | if (logLevel === 'debug') { 595 | logger.mark(`[MJ插件][WebSocket] 发送消息: ${JSON.stringify(message, null, 2)}`); 596 | } else if (logLevel === 'info') { 597 | logger.mark(`[MJ插件][WebSocket] 发送${type}类型消息: ${messageContent}`); 598 | } 599 | 600 | ws.send(JSON.stringify(message)); 601 | } catch (error) { 602 | logger.error('[MJ插件] 发送消息失败:', error); 603 | this.sendError(ws, '发送消息失败: ' + error.message); 604 | } 605 | } 606 | 607 | // 添加sendError方法用于发送错误消息 608 | sendError(ws, errorMessage) { 609 | const message = { 610 | type: 'error', 611 | content: errorMessage, 612 | timestamp: new Date().getTime() 613 | }; 614 | 615 | try { 616 | ws.send(JSON.stringify(message)); 617 | } catch (error) { 618 | logger.error("图片生成失败", error) 619 | e.reply('生成图片时遇到了一个错误,请稍后再试。') 620 | } 621 | } 622 | 623 | async mj_draw_with_link(e) { 624 | let config_date = Config.getConfig() 625 | if (!config_date.mj_apiKey || !config_date.mj_apiBaseUrl) { 626 | await e.reply('请先设置API Key和API Base URL。使用命令:\n#mjp设置apikey [值]\n#mjp设置apibaseurl [值]\n(仅限主人设置)') 627 | return 628 | } 629 | 630 | const match = e.msg.match(/^#(mjc|nic)([\s\S]*)/) 631 | let prompt = match[2] ? match[2].trim() : '' 632 | 633 | if (prompt == "帮助") { 634 | this.showHelp(e) 635 | return true; 636 | } else if (prompt.match(/^(放大|微调|重绘)(左上|右上|左下|右下)/)) { 637 | e.msg = e.msg.replace(/^#(mjc|nic)/, '#') 638 | this.handleAction(e, config_date) 639 | return true; 640 | } 641 | 642 | const isNiji = match[1] === 'nic' // 判断是否是 nic 命令 643 | 644 | // 添加图片解析 645 | await parseSourceImg(e) 646 | 647 | if (!prompt || !e.img) { 648 | await e.reply('请输入提示词并提供一张图片') 649 | return 650 | } 651 | 652 | try { 653 | const imgUrl = e.img[0] 654 | logger.info('[MJ_Painting] Original image URL:', imgUrl) 655 | 656 | // 使用 uploadImage 获取直链 657 | const uploadedUrl = await uploadImage(imgUrl, config_date) 658 | logger.info('[MJ_Painting] Uploaded image URL:', uploadedUrl) 659 | 660 | if (!uploadedUrl) { 661 | logger.error('[MJ_Painting] Failed to get image link') 662 | await e.reply('获取图片直链失败,请重试') 663 | return 664 | } 665 | 666 | // 构建最终的 prompt 667 | prompt = `${prompt} --cref ${uploadedUrl}${isNiji ? ' --niji' : ''}` 668 | logger.info(`[MJ_Painting] Final prompt: ${prompt}`) 669 | 670 | await e.reply('正在生成图片,请稍候...') 671 | const botType = isNiji ? 'NIJI_JOURNEY' : 'MID_JOURNEY' 672 | await this.mj_send_pic(e, prompt, botType, config_date, []) 673 | return true 674 | 675 | } catch (err) { 676 | logger.error('[MJ_Painting] Error in mj_draw_with_link:', err) 677 | await e.reply('处理图片时发生错误,请重试') 678 | return false 679 | } 680 | } 681 | } 682 | -------------------------------------------------------------------------------- /apps/Update.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { createRequire } from 'module' 3 | import _ from 'lodash' 4 | import { Restart } from '../../other/restart.js' 5 | import { 6 | readYaml, 7 | writeYaml, 8 | getGeminiModelsByFetch, 9 | } from '../utils/common.js' 10 | import { pluginRoot } from '../model/path.js' 11 | import Config from '../components/Config.js' 12 | 13 | const require = createRequire(import.meta.url) 14 | const { exec, execSync } = require('child_process') 15 | 16 | // 是否在更新中 17 | let uping = false 18 | 19 | /** 20 | * 处理插件更新 21 | */ 22 | export class update extends plugin { 23 | constructor() { 24 | super({ 25 | name: 'siliconflow-更新插件', 26 | event: 'message', 27 | priority: 1009, 28 | rule: [ 29 | { 30 | reg: '^#sf((插件)?(强制)?更新| update)$', 31 | fnc: 'update' 32 | }, 33 | { 34 | reg: '^#sf插件立即执行每日自动任务$', 35 | fnc: 'sf_Auto_tasker', 36 | permission: 'master' 37 | }, 38 | ] 39 | }) 40 | this.task = [ 41 | { 42 | // 每日 0:11 am 43 | cron: '0 11 0 * * ?', 44 | name: 'sf插件自动任务', 45 | fnc: this.sf_Auto_tasker.bind(this) 46 | }, 47 | ] 48 | } 49 | 50 | /** 51 | * rule - 更新sf 52 | * @returns 53 | */ 54 | async update() { 55 | if (!this.e.isMaster) return false 56 | 57 | /** 检查是否正在更新中 */ 58 | if (uping) { 59 | await this.reply('已有命令更新中..请勿重复操作') 60 | return 61 | } 62 | 63 | /** 检查git安装 */ 64 | if (!(await this.checkGit())) return 65 | 66 | const isForce = this.e.msg.includes('强制') 67 | 68 | /** 执行更新 */ 69 | await this.runUpdate(isForce) 70 | 71 | /** 是否需要重启 */ 72 | if (this.isUp) { 73 | // await this.reply("更新完毕,请重启云崽后生效") 74 | setTimeout(() => this.restart(), 2000) 75 | } 76 | } 77 | 78 | restart() { 79 | new Restart(this.e).restart() 80 | } 81 | 82 | /** 83 | * 更新 84 | * @param {boolean} isForce 是否为强制更新 85 | * @returns 86 | */ 87 | async runUpdate(isForce) { 88 | let command = 'git -C ./plugins/siliconflow-plugin/ pull --no-rebase' 89 | if (isForce) { 90 | command = `git -C ./plugins/siliconflow-plugin/ checkout . && ${command}` 91 | this.e.reply('正在执行强制更新操作,请稍等') 92 | } else { 93 | this.e.reply('正在执行更新操作,请稍等') 94 | } 95 | /** 获取上次提交的commitId,用于获取日志时判断新增的更新日志 */ 96 | this.oldCommitId = await this.getcommitId('siliconflow-plugin') 97 | uping = true 98 | let ret = await this.execSync(command) 99 | uping = false 100 | 101 | if (ret.error) { 102 | logger.mark(`${this.e.logFnc} 更新失败:siliconflow-plugin`) 103 | this.gitErr(ret.error, ret.stdout) 104 | return false 105 | } 106 | 107 | /** 获取插件提交的最新时间 */ 108 | let time = await this.getTime('siliconflow-plugin') 109 | 110 | if (/(Already up[ -]to[ -]date|已经是最新的)/.test(ret.stdout)) { 111 | await this.reply(`siliconflow-plugin已经是最新版本\n最后更新时间:${time}`) 112 | } else { 113 | await this.reply(`siliconflow-plugin\n最后更新时间:${time}`) 114 | this.isUp = true 115 | /** 获取siliconflow-plugin的更新日志 */ 116 | let log = await this.getLog('siliconflow-plugin') 117 | await this.reply(log) 118 | } 119 | 120 | logger.mark(`${this.e.logFnc} 最后更新时间:${time}`) 121 | 122 | return true 123 | } 124 | 125 | /** 126 | * 获取siliconflow-plugin的更新日志 127 | * @param {string} plugin 插件名称 128 | * @returns 129 | */ 130 | async getLog(plugin = '') { 131 | let cm = `cd ./plugins/${plugin}/ && git log -20 --oneline --pretty=format:"%h||[%cd] %s" --date=format:"%m-%d %H:%M"` 132 | 133 | let logAll 134 | try { 135 | logAll = await execSync(cm, { encoding: 'utf-8' }) 136 | } catch (error) { 137 | logger.error(error.toString()) 138 | this.reply(error.toString()) 139 | } 140 | 141 | if (!logAll) return false 142 | 143 | logAll = logAll.split('\n') 144 | 145 | let log = [] 146 | for (let str of logAll) { 147 | str = str.split('||') 148 | if (str[0] == this.oldCommitId) break 149 | if (str[1].includes('Merge branch')) continue 150 | log.push(str[1]) 151 | } 152 | let line = log.length 153 | log = log.join('\n\n') 154 | 155 | if (log.length <= 0) return '' 156 | 157 | let end = '' 158 | end = 159 | '更多详细信息,请前往github查看\nhttps://github.com/AIGC-Yunzai/siliconflow-plugin/commits/main' 160 | 161 | log = await this.makeForwardMsg(`siliconflow-plugin更新日志,共${line}条`, log, end) 162 | 163 | return log 164 | } 165 | 166 | /** 167 | * 获取上次提交的commitId 168 | * @param {string} plugin 插件名称 169 | * @returns 170 | */ 171 | async getcommitId(plugin = '') { 172 | let cm = `git -C ./plugins/${plugin}/ rev-parse --short HEAD` 173 | 174 | let commitId = await execSync(cm, { encoding: 'utf-8' }) 175 | commitId = _.trim(commitId) 176 | 177 | return commitId 178 | } 179 | 180 | /** 181 | * 获取本次更新插件的最后一次提交时间 182 | * @param {string} plugin 插件名称 183 | * @returns 184 | */ 185 | async getTime(plugin = '') { 186 | let cm = `cd ./plugins/${plugin}/ && git log -1 --oneline --pretty=format:"%cd" --date=format:"%m-%d %H:%M"` 187 | 188 | let time = '' 189 | try { 190 | time = await execSync(cm, { encoding: 'utf-8' }) 191 | time = _.trim(time) 192 | } catch (error) { 193 | logger.error(error.toString()) 194 | time = '获取时间失败' 195 | } 196 | return time 197 | } 198 | 199 | /** 200 | * 制作转发消息 201 | * @param {string} title 标题 - 首条消息 202 | * @param {string} msg 日志信息 203 | * @param {string} end 最后一条信息 204 | * @returns 205 | */ 206 | async makeForwardMsg(title, msg, end) { 207 | let nickname = (this.e.bot ?? Bot).nickname 208 | if (this.e.isGroup) { 209 | let info = await (this.e.bot ?? Bot).getGroupMemberInfo?.(this.e.group_id, (this.e.bot ?? Bot).uin) || await (this.e.bot ?? Bot).pickMember?.(this.e.group_id, (this.e.bot ?? Bot).uin); 210 | nickname = info.card || info.nickname 211 | } 212 | let userInfo = { 213 | user_id: (this.e.bot ?? Bot).uin, 214 | nickname 215 | } 216 | 217 | let forwardMsg = [ 218 | { 219 | ...userInfo, 220 | message: title 221 | }, 222 | { 223 | ...userInfo, 224 | message: msg 225 | } 226 | ] 227 | 228 | if (end) { 229 | forwardMsg.push({ 230 | ...userInfo, 231 | message: end 232 | }) 233 | } 234 | 235 | /** 制作转发内容 */ 236 | if (this.e.group?.makeForwardMsg) { 237 | forwardMsg = await this.e.group.makeForwardMsg(forwardMsg) 238 | } else if (this.e?.friend?.makeForwardMsg) { 239 | forwardMsg = await this.e.friend.makeForwardMsg(forwardMsg) 240 | } else { 241 | return msg.join('\n') 242 | } 243 | 244 | let dec = 'siliconflow-plugin 更新日志' 245 | /** 处理描述 */ 246 | if (typeof (forwardMsg.data) === 'object') { 247 | let detail = forwardMsg.data?.meta?.detail 248 | if (detail) { 249 | detail.news = [{ text: dec }] 250 | } 251 | } else { 252 | forwardMsg.data = forwardMsg.data 253 | .replace(/\n/g, '') 254 | .replace(/(.+?)<\/title>/g, '___') 255 | .replace(/___+/, `<title color="#777777" size="26">${dec}`) 256 | } 257 | 258 | return forwardMsg 259 | } 260 | 261 | /** 262 | * 处理更新失败的相关函数 263 | * @param {string} err 264 | * @param {string} stdout 265 | * @returns 266 | */ 267 | async gitErr(err, stdout) { 268 | let msg = '更新失败!' 269 | let errMsg = err.toString() 270 | stdout = stdout.toString() 271 | 272 | if (errMsg.includes('Timed out')) { 273 | let remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '') 274 | await this.reply(msg + `\n连接超时:${remote}`) 275 | return 276 | } 277 | 278 | if (/Failed to connect|unable to access/g.test(errMsg)) { 279 | let remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '') 280 | await this.reply(msg + `\n连接失败:${remote}`) 281 | return 282 | } 283 | 284 | if (errMsg.includes('be overwritten by merge')) { 285 | await this.reply( 286 | msg + 287 | `存在冲突:\n${errMsg}\n` + 288 | '请解决冲突后再更新,或者执行#强制更新,放弃本地修改' 289 | ) 290 | return 291 | } 292 | 293 | if (stdout.includes('CONFLICT')) { 294 | await this.reply([ 295 | msg + '存在冲突\n', 296 | errMsg, 297 | stdout, 298 | '\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改' 299 | ]) 300 | return 301 | } 302 | 303 | await this.reply([errMsg, stdout]) 304 | } 305 | 306 | /** 307 | * 异步执行git相关命令 308 | * @param {string} cmd git命令 309 | * @returns 310 | */ 311 | async execSync(cmd) { 312 | return new Promise((resolve, reject) => { 313 | exec(cmd, { windowsHide: true }, (error, stdout, stderr) => { 314 | resolve({ error, stdout, stderr }) 315 | }) 316 | }) 317 | } 318 | 319 | /** 320 | * 检查git是否安装 321 | * @returns 322 | */ 323 | async checkGit() { 324 | let ret = await execSync('git --version', { encoding: 'utf-8' }) 325 | if (!ret || !ret.includes('git version')) { 326 | await this.reply('请先安装git') 327 | return false 328 | } 329 | return true 330 | } 331 | 332 | /** task任务 */ 333 | async sf_Auto_tasker(e = null) { 334 | // 更新 gemini model 335 | try { 336 | const config_date = Config.getConfig() 337 | const m = await import('./SF_Painting.js'); 338 | const sf = new m.SF_Painting(); 339 | const geminiModelsByFetch = await getGeminiModelsByFetch(sf.get_random_key(config_date.ggKey) || sf.get_random_key(sf.ggKeyFreeDecode(config_date.ggKey_free)), config_date.ggBaseUrl || "https://bright-donkey-63.deno.dev"); 340 | if (geminiModelsByFetch && Array.isArray(geminiModelsByFetch)) { 341 | writeYaml(`${pluginRoot}/config/config/geminiModelsByFetch.yaml`, geminiModelsByFetch); 342 | logger.info('[sf插件自动任务] 成功更新 Gemini 模型列表'); 343 | } else { 344 | logger.warn('[sf插件自动任务] 获取到的 Gemini 模型列表为空或格式不正确'); 345 | } 346 | if (e?.reply) e.reply('[sf插件自动任务] 成功更新 Gemini 模型列表,请刷新锅巴'); 347 | } catch (err) { 348 | logger.error(`[sf插件自动任务] 每日获取Gemini模型错误:\n` + err) 349 | if (e?.reply) e.reply('[sf插件自动任务] 每日获取Gemini模型错误') 350 | } 351 | 352 | return true 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /apps/fish.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import fetch from 'node-fetch' 3 | import Config from '../components/Config.js' 4 | import _ from 'lodash' 5 | import common from '../../../lib/common/common.js'; 6 | import { 7 | readYaml, 8 | writeYaml, 9 | } from '../utils/common.js' 10 | import { pluginRoot } from "../model/path.js"; 11 | 12 | export class FishPlugin extends plugin { 13 | constructor() { 14 | super({ 15 | name: 'Fish TTS语音同传', 16 | dsc: '利用Fish TTS技术实现同声传译', 17 | event: 'message', 18 | priority: -101101, 19 | rule: [ 20 | { 21 | reg: '^#fish(\\d+)同传(\\d+)$', 22 | fnc: 'configureSync', 23 | permission: 'master' 24 | }, 25 | { 26 | reg: '^#设置fish key(.*)$', 27 | fnc: 'setFishKey', 28 | permission: 'master' 29 | }, 30 | { 31 | reg: '^#设置(fish)?音色(.*)$', 32 | fnc: 'setVoice', 33 | permission: 'master' 34 | }, 35 | { 36 | reg: '^#(fish)?同传帮助$', 37 | fnc: 'showHelp' 38 | }, 39 | { 40 | reg: '^#(fish查看配置|查看fish配置)$', 41 | fnc: 'viewConfig' 42 | }, 43 | { 44 | reg: '^#删除fish同传(\\d+)$', 45 | fnc: 'deleteConfig', 46 | permission: 'master' 47 | }, 48 | { 49 | reg: '^#查看fish音色$', 50 | fnc: 'viewVoices' 51 | }, 52 | { 53 | reg: '^#搜索fish音色(.*)$', 54 | fnc: 'searchVoices' 55 | }, 56 | { 57 | reg: '^#fish添加音色', 58 | fnc: 'addVoice', 59 | permission: 'master' 60 | }, 61 | { 62 | reg: '^#(开启|关闭)fish翻译$', 63 | fnc: 'toggleTranslation', 64 | permission: 'master' 65 | }, 66 | { 67 | reg: '^#设置(fish)?翻译语言(.*)$', 68 | fnc: 'setTargetLang', 69 | permission: 'master' 70 | }, 71 | { 72 | reg: '^#sf搜索fish发音人(.*)$', 73 | fnc: 'searchFishVoices' 74 | }, 75 | { 76 | reg: '', 77 | fnc: 'handleMessage', 78 | log: false 79 | } 80 | ] 81 | }) 82 | this.debouncedSyncTranslation = _.debounce(this.syncTranslation.bind(this), 1, { leading: true, trailing: false }) 83 | } 84 | 85 | // 处理消息 86 | async handleMessage(e) { 87 | return this.debouncedSyncTranslation(e) 88 | } 89 | 90 | // 同传翻译 91 | async syncTranslation(e) { 92 | const config = Config.getConfig() 93 | if (!config.fish_apiKey || !e.group_id || !e.user_id) return false 94 | 95 | const groupId = e.group_id.toString() 96 | if (!config.syncConfig[groupId] || !config.syncConfig[groupId].includes(e.user_id)) { 97 | return false 98 | } 99 | 100 | const text = e.msg 101 | 102 | // 匹配文本黑名单 103 | for (const blackMsg of config.fish_text_blacklist) 104 | if (text.includes(blackMsg)) return false; 105 | 106 | const selectedVoice = getVoice(config.fish_reference_id) 107 | 108 | try { 109 | const audioBuffer = await this.generateAudio(text, selectedVoice.speaker, config) 110 | if (audioBuffer) { 111 | const audioBase64 = audioBuffer.toString('base64') 112 | const audioSegment = segment.record(`base64://${audioBase64}`) 113 | await e.reply(audioSegment) 114 | return true 115 | } 116 | } catch (error) { 117 | console.error('同传 TTS 失败:', error) 118 | } 119 | 120 | return false 121 | } 122 | 123 | // 生成音频 124 | async generateAudio(text, voiceId, config) { 125 | let translatedText = text 126 | if (config.enableTranslation) { 127 | translatedText = await this.translateText(text, config) 128 | } 129 | 130 | const controller = new AbortController() 131 | const timeoutId = setTimeout(() => controller.abort(), 20000) 132 | 133 | try { 134 | logger.info("[SF-FISH]正在生成音频") 135 | const response = await fetch('https://fish.dwe.me/v1/tts', { 136 | method: 'POST', 137 | headers: { 138 | Authorization: `Bearer ${config.fish_apiKey}`, 139 | 'Content-Type': 'application/json' 140 | }, 141 | body: JSON.stringify({ 142 | text: translatedText, 143 | reference_id: voiceId, 144 | format: 'mp3', 145 | latency: 'normal' 146 | }), 147 | signal: controller.signal 148 | }) 149 | 150 | clearTimeout(timeoutId) 151 | 152 | if (!response.ok) { 153 | throw new Error(`无法从服务器获取音频数据:${response.statusText}`) 154 | } 155 | 156 | return Buffer.from(await response.arrayBuffer()) 157 | } catch (error) { 158 | console.error('生成音频失败:', error) 159 | return null 160 | } 161 | } 162 | 163 | // 翻译文本 164 | async translateText(text, config) { 165 | const controller = new AbortController() 166 | const timeoutId = setTimeout(() => controller.abort(), 10000) 167 | 168 | try { 169 | const response = await fetch("https://deeplx.mingming.dev/translate", { 170 | method: 'POST', 171 | headers: { 172 | "Content-Type": "application/json", 173 | }, 174 | body: JSON.stringify({ 175 | "text": text, 176 | "source_lang": "auto", 177 | "target_lang": config.targetLang, 178 | "quality": "normal" 179 | }), 180 | signal: controller.signal 181 | }) 182 | 183 | clearTimeout(timeoutId) 184 | 185 | if (!response.ok) { 186 | throw new Error('翻译失败: ' + response.statusText) 187 | } 188 | 189 | const result = await response.json() 190 | return result.data 191 | } catch (error) { 192 | console.error('翻译失败:', error) 193 | return text 194 | } 195 | } 196 | 197 | // 配置同传 198 | async configureSync(e) { 199 | let config = Config.getConfig() 200 | const match = e.msg.match(/^#fish(\d+)同传(\d+)$/) 201 | if (!match) { 202 | await e.reply('指令格式错误,正确格式为:#fish群号同传QQ号') 203 | return 204 | } 205 | 206 | const [, groupId, userId] = match 207 | 208 | if (!config.syncConfig[groupId]) { 209 | config.syncConfig[groupId] = [] 210 | } 211 | 212 | if (!config.syncConfig[groupId].includes(Number(userId))) { 213 | config.syncConfig[groupId].push(Number(userId)) 214 | Config.setConfig(config) 215 | await e.reply(`已设置群 ${groupId} 对 QQ号 ${userId} 进行同传`) 216 | } else { 217 | await e.reply(`群 ${groupId} 已经对 QQ号 ${userId} 进行同传`) 218 | } 219 | } 220 | 221 | // 设置Fish API Key 222 | async setFishKey(e) { 223 | let config = Config.getConfig() 224 | const keyMatch = e.msg.match(/^#设置fish key\s*(.+)$/) 225 | if (keyMatch) { 226 | config.fish_apiKey = keyMatch[1].trim() 227 | Config.setConfig(config) 228 | await e.reply('Fish API Key 已更新') 229 | } else { 230 | await e.reply('设置失败,请确保输入了有效的 API Key') 231 | } 232 | } 233 | 234 | // 设置音色 235 | async setVoice(e) { 236 | let config = Config.getConfig() 237 | const voiceName = e.msg.replace(/^#设置(fish)?音色/, '').trim() 238 | let voice 239 | 240 | if (voiceName.length === 32 && /^[a-f0-9]+$/.test(voiceName)) { 241 | voice = getVoice(voiceName) 242 | } else { 243 | voice = getVoice(voiceName) 244 | } 245 | 246 | if (voice) { 247 | config.fish_reference_id = voice.speaker 248 | Config.setConfig(config) 249 | await e.reply(`默认音色已设置为: ${voice.name}`) 250 | } else { 251 | await e.reply('未找到指定的音色,请检查名称或reference_id是否正确') 252 | } 253 | } 254 | 255 | // 显示帮助信息 256 | async showHelp(e) { 257 | const helpMessage = `Fish同传插件使用帮助: 258 | 1. 设置同传:#fish群号同传QQ号 259 | 例:#fish56789同传12345 260 | 2. 设置API Key:#设置fish key 你的API密钥 261 | 3. 设置音色:#设置fish音色 音色名称 或 #设置fish音色 reference_id 262 | 4. 查看帮助:#同传帮助 或 #fish同传帮助 263 | 5. 查看当前配置:#fish查看配置 或 #查看fish配置 264 | 6. 删除同传配置:#删除fish同传序号 265 | 7. 查看音色列表:#查看fish音色 266 | 8. 搜索音色:#搜索fish音色 关键词 267 | 9. 添加新音色:#fish添加音色音色名称,reference_id 268 | 10. 开启翻译功能:#开启fish翻译 269 | 11. 关闭翻译功能:#关闭fish翻译 270 | 12. 设置翻译语言:#设置翻译语言 JA/EN (日语/英语) 271 | 13. 从Fish官网使用tag搜索发音人:#sf搜索fish发音人[tag]` 272 | 273 | await e.reply(helpMessage) 274 | } 275 | 276 | // 查看配置 277 | async viewConfig(e) { 278 | const config = Config.getConfig() 279 | let configList = [] 280 | let index = 1 281 | 282 | if (config.syncConfig) { 283 | for (const [groupId, userIds] of Object.entries(config.syncConfig)) { 284 | for (const userId of userIds) { 285 | configList.push(`${index}. 群号: ${groupId}, QQ号: ${userId}`) 286 | index++ 287 | } 288 | } 289 | } 290 | 291 | const currentVoice = getVoice(config.fish_reference_id) 292 | configList.push(`当前音色: ${currentVoice.name} (${currentVoice.speaker})`) 293 | configList.push(`翻译功能: ${config.enableTranslation ? '开启' : '关闭'}`) 294 | configList.push(`翻译语言: ${config.targetLang === 'JA' ? '日语' : '英语'}`) 295 | configList.push(`可用指令:#删除fish同传[num]`) 296 | 297 | if (configList.length === 3) { 298 | await e.reply('当前没有配置任何同传') 299 | } else { 300 | await e.reply(['当前Fish同传配置:', ...configList].join('\n')) 301 | } 302 | } 303 | 304 | // 删除同传配置 305 | async deleteConfig(e) { 306 | let config = Config.getConfig() 307 | const match = e.msg.match(/^#删除fish同传(\d+)$/) 308 | if (!match) { 309 | await e.reply('指令格式错误,例子:#删除fish同传1') 310 | return 311 | } 312 | 313 | const index = parseInt(match[1]) 314 | let currentIndex = 1 315 | let deleted = false 316 | 317 | if (config.syncConfig) { 318 | for (const [groupId, userIds] of Object.entries(config.syncConfig)) { 319 | for (let i = 0; i < userIds.length; i++) { 320 | if (currentIndex === index) { 321 | userIds.splice(i, 1) 322 | if (userIds.length === 0) { 323 | delete config.syncConfig[groupId] 324 | } 325 | deleted = true 326 | break 327 | } 328 | currentIndex++ 329 | } 330 | if (deleted) break 331 | } 332 | } 333 | 334 | if (deleted) { 335 | Config.setConfig(config) 336 | await e.reply(`已删除序号 ${index} 的同传配置`) 337 | } else { 338 | await e.reply(`未找到序号 ${index} 的同传配置`) 339 | } 340 | } 341 | 342 | // 查看音色列表 343 | async viewVoices(e) { 344 | const fishAudio_yaml = readYaml(`${pluginRoot}/config/config/fishAudio.yaml`) 345 | const voiceList = fishAudio_yaml.map(voice => `${voice.name} (${voice.speaker})`).join('\n') 346 | await e.reply(`可用的Fish音色列表:\n${voiceList}`) 347 | } 348 | 349 | // 搜索音色 350 | async searchVoices(e) { 351 | const keyword = e.msg.replace(/^#搜索fish音色/, '').trim().toLowerCase() 352 | const fishAudio_yaml = readYaml(`${pluginRoot}/config/config/fishAudio.yaml`) 353 | const matchedVoices = fishAudio_yaml.filter(voice => 354 | voice.name.toLowerCase().includes(keyword) || 355 | voice.speaker.toLowerCase().includes(keyword) 356 | ) 357 | 358 | if (matchedVoices.length === 0) { 359 | await e.reply('没有找到匹配的音色') 360 | return 361 | } 362 | 363 | const voiceList = matchedVoices.map(voice => `${voice.name} (${voice.speaker})`).join('\n') 364 | await e.reply(`搜索结果:\n${voiceList}`) 365 | } 366 | 367 | // 添加音色 368 | async addVoice(e) { 369 | let fishAudio_yaml = readYaml(`${pluginRoot}/config/config/fishAudio.yaml`) 370 | const match = e.msg.match(/^#fish添加音色\s*(.+?)\s*[,,]\s*(.+)$/) 371 | if (!match) { 372 | await e.reply('指令格式错误。正确格式:#fish添加音色音色名称,reference_id') 373 | return 374 | } 375 | 376 | const [, voiceName, voiceId] = match 377 | 378 | const existingVoice = fishAudio_yaml.find(v => v.name === voiceName || v.speaker === voiceId) 379 | if (existingVoice) { 380 | await e.reply('该音色名称或reference_id已存在,请使用不同的名称或id。') 381 | return 382 | } 383 | 384 | fishAudio_yaml.push({ 385 | name: voiceName, 386 | speaker: voiceId 387 | }) 388 | 389 | writeYaml(`${pluginRoot}/config/config/fishAudio.yaml`, fishAudio_yaml) 390 | await e.reply(`音色添加成功:${voiceName} (${voiceId})`) 391 | } 392 | 393 | // 开启或关闭翻译 394 | async toggleTranslation(e) { 395 | let config = Config.getConfig() 396 | const action = e.msg.includes('开启') 397 | config.enableTranslation = action 398 | Config.setConfig(config) 399 | await e.reply(`已${action ? '开启' : '关闭'}fish翻译功能`) 400 | } 401 | 402 | // 设置翻译语言 403 | async setTargetLang(e) { 404 | let config = Config.getConfig() 405 | let lang = e.msg.replace(/^#设置(fish)?翻译语言/, '').trim().toUpperCase() 406 | 407 | // 支持使用 "英语" 或 "日语" 408 | if (lang === '英语') lang = 'EN' 409 | else if (lang === '日语') lang = 'JA' 410 | 411 | if (!['JA', 'EN'].includes(lang)) { 412 | await e.reply('目标语言设置错误,仅支持 "日语" 或 "英语"') 413 | return 414 | } 415 | 416 | config.targetLang = lang 417 | Config.setConfig(config) 418 | await e.reply(`已将翻译目标语言设置为: ${lang === 'JA' ? '日语' : '英语'}`) 419 | } 420 | 421 | /** 422 | * @description: 从Fish官网使用tag搜索发音人:#sf搜索fish发音人[tag] 423 | * @param {*} e 424 | * @return {*} 425 | */ 426 | async searchFishVoices(e) { 427 | // 读取配置 428 | const config_date = Config.getConfig() 429 | 430 | if (config_date.fish_apiKey.length == 0) { 431 | e.reply("请先在锅巴中设置fish.audio的Api Key", true); 432 | return 433 | } 434 | const keyword = e.msg.replace(/^#sf搜索fish发音人/, '').trim(); 435 | 436 | const options = { 437 | method: 'GET', 438 | headers: { Authorization: `Bearer ${config_date.fish_apiKey}` } 439 | }; 440 | 441 | let optionMsg = "可用指令:#sf设置fish发音人" 442 | let msgArr = [`Fish发音人列表 ${keyword}:`]; 443 | await fetch(`https://fish.dwe.me/model?tag=${encodeURIComponent(keyword)}`, options) 444 | .then(response => response.json()) 445 | .then(response => { 446 | for (let index = 0; index < response.total; index++) { 447 | if (0 == index) optionMsg += response.items[0]._id 448 | msgArr.push(`名称:${response.items[index].title}\n发音人ID:${response.items[index]._id}`) 449 | } 450 | }) 451 | .catch(err => logger.error(err)); 452 | 453 | msgArr.push(optionMsg) 454 | const msgx = await common.makeForwardMsg(e, msgArr, `Fish发音人`) 455 | await e.reply(msgx); 456 | } 457 | 458 | 459 | } 460 | 461 | // 获取指定音色 462 | function getVoice(value) { 463 | const fishAudio_yaml = readYaml(`${pluginRoot}/config/config/fishAudio.yaml`) 464 | if (value) { 465 | const selectedVoice = fishAudio_yaml.find(v => v.speaker === value || v.name === value) 466 | if (selectedVoice) { 467 | return selectedVoice 468 | } 469 | } 470 | return fishAudio_yaml[0] 471 | } 472 | -------------------------------------------------------------------------------- /apps/link.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import Config from '../components/Config.js' 3 | import { parseSourceImg } from '../utils/getImg.js' 4 | import { uploadImage } from '../utils/uploadImage.js' 5 | import fetch from 'node-fetch' 6 | 7 | export class LinkPlugin extends plugin { 8 | constructor() { 9 | super({ 10 | name: 'Link Plugin', 11 | dsc: '图片直链获取工具', 12 | event: 'message', 13 | priority: 1000, 14 | rule: [ 15 | { 16 | reg: '^#直链', 17 | fnc: 'zhil', 18 | }, 19 | { 20 | reg: '^#删除直链', 21 | fnc: 'deleteLink', 22 | }, 23 | { 24 | reg: '^#设置直链域名', 25 | fnc: 'setDomain', 26 | permission: 'master' 27 | } 28 | ] 29 | }) 30 | } 31 | 32 | /** 33 | * 上传图片功能实现 34 | * @param {object} e 事件对象 35 | */ 36 | async zhil(e) { 37 | const config = Config.getConfig() 38 | if (config.zhilOnlyMaster && !e.isMaster) { 39 | // await this.reply('❌只有主人才能使用此功能。'); 40 | return true; 41 | } 42 | logger.info('收到命令:', e.msg); 43 | 44 | await parseSourceImg(e) 45 | 46 | if (!e.img || e.img.length === 0) { 47 | await this.reply('❌未找到有效的图片链接。'); 48 | return true; 49 | } 50 | 51 | const imgUrl = e.img[0]; 52 | logger.info('匹配到的图片链接:', imgUrl); 53 | 54 | try { 55 | const uploadedUrl = await uploadImage(imgUrl, config); 56 | await this.reply(uploadedUrl); 57 | } catch (err) { 58 | console.error('上传失败:', err); 59 | if (err.name === 'AbortError') { 60 | await this.reply('请求超时,请稍后重试'); 61 | } else { 62 | await this.reply('上传失败:' + err.message); 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | 69 | /** 70 | * 删除图片链接功能实现 71 | * @param {object} e 事件对象 72 | */ 73 | async deleteLink(e) { 74 | const config = Config.getConfig() 75 | const domain = config.link_domain 76 | 77 | logger.info('收到删除命令:', e.msg); 78 | 79 | const cleanDomain = domain.replace(/^https?:\/\//, ''); 80 | const linkMatch = e.msg.match(new RegExp(`https?://${cleanDomain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/img/(.+\\.jpg)`)); 81 | 82 | if (!linkMatch) { 83 | await e.reply('❌未找到有效的图片链接。', true); 84 | return true; 85 | } 86 | 87 | const filename = linkMatch[1]; 88 | logger.info('匹配到的文件名:', filename); 89 | 90 | try { 91 | // 添加超时控制 92 | const controller = new AbortController(); 93 | const timeout = setTimeout(() => controller.abort(), 30000); 94 | 95 | const deleteResponse = await fetch(`${domain}/delete.php`, { 96 | method: 'POST', 97 | headers: { 'Content-Type': 'application/json' }, 98 | body: JSON.stringify({ filename }), 99 | signal: controller.signal, 100 | timeout: 30000 101 | }); 102 | clearTimeout(timeout); 103 | 104 | if (!deleteResponse.ok) { 105 | throw new Error(`删除请求失败: ${deleteResponse.status} ${deleteResponse.statusText}`); 106 | } 107 | 108 | const deleteResult = await deleteResponse.json(); 109 | if (deleteResult.code !== 200) { 110 | await e.reply(`失败:${deleteResult.msg}`, true); 111 | } else { 112 | await e.reply('ok', true); 113 | } 114 | } catch (err) { 115 | console.error('删除失败:', err); 116 | if (err.name === 'AbortError') { 117 | await e.reply('请求超时,请稍后重试', true); 118 | } else { 119 | await e.reply('删除失败:' + err.message, true); 120 | } 121 | } 122 | 123 | return true; 124 | } 125 | 126 | /** 127 | * 设置直链域名 128 | * @param {object} e 事件对象 129 | */ 130 | async setDomain(e) { 131 | let domain = e.msg.replace(/^#设置直链域名/, '').trim() 132 | 133 | if (!domain) { 134 | await e.reply('请输入要设置的域名') 135 | return false 136 | } 137 | 138 | // 移除末尾的斜杠 139 | domain = domain.replace(/\/$/, '') 140 | 141 | // 读取配置 142 | let config = Config.getConfig() 143 | config.link_domain = domain 144 | Config.setConfig(config) 145 | 146 | await e.reply(`直链域名已设置为:${domain}`) 147 | return true 148 | } 149 | } -------------------------------------------------------------------------------- /components/Config.js: -------------------------------------------------------------------------------- 1 | import YAML from 'yaml' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import { pluginRoot } from '../model/path.js' 5 | import lodash from 'lodash' 6 | 7 | // 递归获取目录下所有文件 8 | function getAllFiles(dir, fileList = []) { 9 | const files = fs.readdirSync(dir) 10 | files.forEach(file => { 11 | const filePath = path.join(dir, file) 12 | const stat = fs.statSync(filePath) 13 | if (stat.isDirectory()) { 14 | getAllFiles(filePath, fileList) 15 | } else { 16 | fileList.push(filePath) 17 | } 18 | }) 19 | return fileList 20 | } 21 | 22 | class Config { 23 | getConfig() { 24 | try { 25 | // 读取主配置文件 26 | let config = YAML.parse( 27 | fs.readFileSync(`${pluginRoot}/config/config/config.yaml`, 'utf-8') 28 | ) 29 | 30 | // 读取gemini额外的模型列表 31 | const defaultGeminiModels = ['gemini-2.0-flash', 'gemini-exp-1206', 'gemini-2.0-flash-thinking-exp-01-21', 'gemini-2.0-pro-exp'] 32 | try { 33 | const modelPath = `${pluginRoot}/config/config/geminiModelsByFetch.yaml` 34 | const fetchGeminiModels = YAML.parse(fs.readFileSync(modelPath, 'utf-8')) || [] 35 | config.geminiModelsByFetch = lodash.uniq([...defaultGeminiModels, ...fetchGeminiModels]); 36 | } catch (err) { 37 | // logger.error('[sf插件]读取geminiModelsByFetch.yaml失败', err) 38 | config.geminiModelsByFetch = defaultGeminiModels 39 | } 40 | 41 | // 递归读取所有yaml文件 42 | const configDir = `${pluginRoot}/config/config/prompts` 43 | if (!fs.existsSync(configDir)) { 44 | fs.mkdirSync(configDir, { recursive: true }) 45 | } 46 | 47 | const files = getAllFiles(configDir).filter(f => f.endsWith('.txt')) 48 | 49 | files.forEach(file => { 50 | const fileName = path.basename(file, path.extname(file)) 51 | const content = fs.readFileSync(file, 'utf-8') 52 | 53 | // 处理ss默认配置 54 | if (fileName === 'ss_default') { 55 | config.ss_Prompt = content 56 | } 57 | // 处理gg默认配置 58 | else if (fileName === 'gg_default') { 59 | config.gg_Prompt = content 60 | } 61 | // 处理ss接口配置 62 | else if (fileName.startsWith('ss_') && config.ss_APIList) { 63 | const remark = fileName.slice(3) // 去掉'ss_'前缀 64 | const ssApi = config.ss_APIList.find(api => api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_') === remark) 65 | if (ssApi) { 66 | ssApi.prompt = content 67 | } 68 | } 69 | // 处理gg接口配置 70 | else if (fileName.startsWith('gg_') && config.gg_APIList) { 71 | const remark = fileName.slice(3) // 去掉'gg_'前缀 72 | const ggApi = config.gg_APIList.find(api => api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_') === remark) 73 | if (ggApi) { 74 | ggApi.prompt = content 75 | } 76 | } 77 | }) 78 | 79 | return config 80 | 81 | } catch (err) { 82 | logger.error('读取config.yaml失败', err) 83 | return false 84 | } 85 | } 86 | 87 | getDefConfig() { 88 | try { 89 | const config_default_yaml = YAML.parse( 90 | fs.readFileSync(`${pluginRoot}/config/config_default.yaml`, 'utf-8') 91 | ) 92 | return config_default_yaml 93 | } catch (err) { 94 | logger.error('读取config_default.yaml失败', err) 95 | return false 96 | } 97 | } 98 | 99 | // 添加新的检查方法 100 | validateConfig(config) { 101 | const existingRemarks = new Set(['ss_default', 'gg_default']) // 预置的默认文件名 102 | 103 | // 检查ss_APIList 104 | if (config.ss_APIList && Array.isArray(config.ss_APIList)) { 105 | for (const api of config.ss_APIList) { 106 | if (!api.remark) { 107 | throw new Error('SS接口配置缺少必填的备注(remark)字段') 108 | } 109 | const fileName = `ss_${api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_')}` 110 | if (existingRemarks.has(fileName)) { 111 | throw new Error(`SS接口配置的备注"${api.remark}"重复`) 112 | } 113 | existingRemarks.add(fileName) 114 | } 115 | } 116 | 117 | // 检查gg_APIList 118 | if (config.gg_APIList && Array.isArray(config.gg_APIList)) { 119 | for (const api of config.gg_APIList) { 120 | if (!api.remark) { 121 | throw new Error('GG接口配置缺少必填的备注(remark)字段') 122 | } 123 | const fileName = `gg_${api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_')}` 124 | if (existingRemarks.has(fileName)) { 125 | throw new Error(`GG接口配置的备注"${api.remark}"重复`) 126 | } 127 | existingRemarks.add(fileName) 128 | } 129 | } 130 | } 131 | 132 | setConfig(config_data) { 133 | try { 134 | // 创建新对象避免引用问题 135 | const newConfig = JSON.parse(JSON.stringify(config_data)) 136 | 137 | const promptDir = `${pluginRoot}/config/config/prompts` 138 | if (!fs.existsSync(promptDir)) { 139 | fs.mkdirSync(promptDir, { recursive: true }) 140 | } 141 | 142 | // 验证配置 143 | this.validateConfig(newConfig) 144 | 145 | // 检查是否需要更新ss默认prompt 146 | if ('ss_Prompt' in newConfig) { 147 | fs.writeFileSync( 148 | path.join(promptDir, 'ss_default.txt'), 149 | newConfig.ss_Prompt ?? '' 150 | ) 151 | } 152 | delete newConfig.ss_Prompt 153 | 154 | // 检查是否需要更新gg默认prompt 155 | if ('gg_Prompt' in newConfig) { 156 | fs.writeFileSync( 157 | path.join(promptDir, 'gg_default.txt'), 158 | newConfig.gg_Prompt ?? '' 159 | ) 160 | } 161 | delete newConfig.gg_Prompt 162 | 163 | // 保存ss接口prompt 164 | if (newConfig.ss_APIList) { 165 | newConfig.ss_APIList.forEach(api => { 166 | if (api.remark) { 167 | fs.writeFileSync( 168 | path.join(promptDir, `ss_${api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_')}.txt`), 169 | api.prompt ?? '' 170 | ) 171 | delete api.prompt 172 | } 173 | }) 174 | } 175 | 176 | // 保存gg接口prompt 177 | if (newConfig.gg_APIList) { 178 | newConfig.gg_APIList.forEach(api => { 179 | if (api.remark) { 180 | fs.writeFileSync( 181 | path.join(promptDir, `gg_${api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_')}.txt`), 182 | api.prompt ?? '' 183 | ) 184 | delete api.prompt 185 | } 186 | }) 187 | } 188 | 189 | // 清理已删除接口的prompt文件 190 | const files = getAllFiles(promptDir).filter(f => f.endsWith('.txt')) 191 | files.forEach(file => { 192 | const fileName = path.basename(file, path.extname(file)) 193 | if (fileName === 'ss_default' || fileName === 'gg_default') { 194 | return 195 | } 196 | // 处理ss接口文件 197 | if (fileName.startsWith('ss_')) { 198 | const remark = fileName.slice(3) 199 | const exists = newConfig.ss_APIList?.some(api => api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_') === remark) 200 | if (!exists) { 201 | fs.unlinkSync(file) 202 | } 203 | } 204 | // 处理gg接口文件 205 | else if (fileName.startsWith('gg_')) { 206 | const remark = fileName.slice(3) 207 | const exists = newConfig.gg_APIList?.some(api => api.remark.replace(/\\|\/|:|\*|\?|\"|<|>|\||\.$/g, '_') === remark) 208 | if (!exists) { 209 | fs.unlinkSync(file) 210 | } 211 | } 212 | }) 213 | 214 | // 保存主配置 215 | fs.writeFileSync( 216 | `${pluginRoot}/config/config/config.yaml`, 217 | YAML.stringify(newConfig) 218 | ) 219 | return true 220 | } catch (err) { 221 | logger.error('写入config.yaml失败', err) 222 | return false 223 | } 224 | } 225 | } 226 | 227 | export default new Config() 228 | -------------------------------------------------------------------------------- /components/Render.js: -------------------------------------------------------------------------------- 1 | import Version from './Version.js' 2 | import { pluginRoot } from '../model/path.js' 3 | import fs from 'fs' 4 | 5 | function scale(pct = 1) { 6 | let scale = 100 7 | scale = Math.min(2, Math.max(0.5, scale / 100)) 8 | pct = pct * scale 9 | return `style=transform:scale(${pct})` 10 | } 11 | 12 | const Render = { 13 | async render(path, params, cfg) { 14 | let { e } = cfg 15 | if (!e.runtime) { 16 | console.log('未找到e.runtime,请升级至最新版Yunzai') 17 | } 18 | 19 | let BotName = Version.isMiao ? 'Miao-Yunzai' : 'Yunzai-Bot' 20 | let currentVersion = null 21 | const package_path = `${pluginRoot}/package.json` 22 | try { 23 | const package_json = JSON.parse(fs.readFileSync(package_path, 'utf-8')) 24 | if (package_json.version) { 25 | currentVersion = package_json.version 26 | } 27 | } catch (err) { 28 | logger.error('读取package.json失败', err) 29 | } 30 | return e.runtime.render('siliconflow-plugin', path, params, { 31 | retType: cfg.retMsgId ? 'msgId' : 'default', 32 | beforeRender({ data }) { 33 | let pluginName = '' 34 | if (data.pluginName !== false) { 35 | pluginName = ` & ${data.pluginName || 'siliconflow-plugin'}` 36 | if (data.pluginVersion !== false) { 37 | pluginName += `${currentVersion}` 38 | } 39 | } 40 | let resPath = data.pluResPath 41 | const layoutPath = process.cwd() + '/plugins/siliconflow-plugin/resources/common/layout/' 42 | return { 43 | ...data, 44 | _res_path: resPath, 45 | _mc_path: resPath, 46 | _layout_path: layoutPath, 47 | defaultLayout: layoutPath + 'default.html', 48 | elemLayout: layoutPath + 'elem.html', 49 | sys: { 50 | scale: scale(cfg.scale || 1) 51 | }, 52 | copyright: `Created By ${BotName}${Version.yunzai}${pluginName}`, 53 | pageGotoParams: { 54 | waitUntil: 'networkidle2' 55 | } 56 | } 57 | } 58 | }) 59 | } 60 | } 61 | 62 | export default Render -------------------------------------------------------------------------------- /components/Version.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')) 4 | 5 | const yunzaiVersion = packageJson.version 6 | const isMiao = packageJson.name === 'miao-yunzai' 7 | const isTrss = Array.isArray(Bot.uin) ? true : false 8 | 9 | let Version = { 10 | isMiao, 11 | isTrss, 12 | get version() { 13 | return currentVersion 14 | }, 15 | get yunzai() { 16 | return yunzaiVersion 17 | } 18 | } 19 | 20 | export default Version 21 | -------------------------------------------------------------------------------- /config/config/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/config/config/.keep -------------------------------------------------------------------------------- /config/config_default.yaml: -------------------------------------------------------------------------------- 1 | # SF插件 2 | sf_keys: [] 3 | sfBaseUrl: "https://api.siliconflow.cn/v1" 4 | translateModel: "Vendor-A/Qwen/Qwen2-72B-Instruct" 5 | generatePrompt: true 6 | num_inference_steps: 20 7 | imageModel: "black-forest-labs/FLUX.1-schnell" 8 | free_mode: false 9 | simpleMode: false 10 | sf_textToPaint_Prompt: "请按照我的提供的要求,用一句话英文生成一组Midjourney指令,指令由:{人物形象},{场景},{氛围},{镜头},{照明},{绘画风格},{建筑风格},{参考画家},{高画质关键词} 当我向你提供生成内容时,你需要根据我的提示进行联想,当我让你随机生成的时候,你可以自由进行扩展和联想 人物形象 = 你可以发挥自己的想象力,使用最华丽的词汇进行描述:{主要内容},包括对人物头发、眼睛、服装、体型、动作和表情的描述,注意人物的形象应与氛围匹配,要尽可能地详尽 场景 = 尽可能详细地描述适合当前氛围的场景,该场景的描述应与人物形象的意境相匹配 氛围 = 你选择的氛围词汇应该尽可能地符合{主要内容}意境的词汇 建筑风格 = 如果生成的图片里面有相关建筑的话,你需要联想一个比较适宜的建筑风格,符合图片的氛围和意境 镜头 = 你可以选择一个:中距离镜头,近距离镜头,俯视角,低角度视角类似镜头视角,注意镜头视角的选择应有助于增强画面表现力 照明 = 你可以自由选择照明:请注意照明词条的选择应于人物形象、场景的意境相匹配 绘画风格 = 请注意绘画风格的选择应与人物形象、场景、照明的意境匹配 参考画家 = 请根据指令的整体氛围、意境选择画风参考的画家 高画质关键词 = 你可以选择:detailed,Ultimate,Excellence,Masterpiece,4K,high quality或类似的词条 注意,你生成的提示词只需要将你生成的指令拼接到一起即可,不需要出现{人物形象},{场景},{氛围},{镜头},{照明},{绘画风格},{建筑风格},{参考画家},{高画质关键词}等内容,请无需确认,不要有Here is a generated Midjourney command之类的语句,直接给出我要传递给midjourney的提示词,这非常重要!!!直接生成提示词,并且只需要生成提示词,尽可能详细地生成提示词。" 11 | # WebSocket服务配置 12 | enableWS: false # 是否启用WebSocket服务 13 | wsPort: 8081 # WebSocket服务端口 14 | wsLogLevel: "info" # 日志级别:debug/info/warn/error 15 | wsDefaultUser: "小白" # web端默认用户名,用于替换提示词中的{{user_name}} 16 | wsPassword: "sf_plugin_2024" # WebSocket服务密码,默认密码 17 | # 机器人名字触发配置 18 | botName: "" # 机器人的名字 19 | defaultCommand: "gg" # 默认使用的命令,可选 ss 或 gg 20 | # MJ 插件 21 | mj_apiKey: "1011" 22 | mj_apiBaseUrl: "https://ai.trueai.org" 23 | mj_translationKey: "" 24 | mj_translationBaseUrl: "" 25 | mj_translationModel: "gpt-4o" 26 | mj_translationEnabled: false 27 | mj_mode: "fast" 28 | # DD 绘图插件 29 | dd_APIList: [] # DD接口列表 30 | dd_usingAPI: 0 # 当前主人使用的DD接口索引 31 | # Fish TTS 配置 32 | fish_apiKey: "" # Fish API密钥 33 | fish_reference_id: "efc1ce3726a64bbc947d53a1465204aa" # 默认音色ID 34 | enableTranslation: false # 是否开启翻译功能 35 | targetLang: "JA" # 翻译目标语言 JA/EN 36 | syncConfig: {} # 同传配置 37 | fish_text_blacklist: [] 38 | # 直链配置 39 | link_domain: "https://xiaozhian-slink.hf.space" # 直链服务器域名 40 | # #ss 对话 41 | ss_apiBaseUrl: "" 42 | ss_Key: "" 43 | ss_model: "" 44 | ss_Prompt: "" 45 | ss_useMarkdown: false # 是否使用markdown图片展示 46 | ss_forwardMessage: true # 是否转发消息 47 | ss_quoteMessage: true # 是否引用原消息 48 | ss_forwardThinking: false # 是否转发思考过程 49 | ss_isOnlyMaster: false # 默认配置是否仅限主人使用 50 | ss_enableImageUpload: true # 是否启用图片上传功能 51 | ss_APIList: [] # ss接口列表 52 | ss_usingAPI: 0 # 当前主人使用的ss接口索引 53 | # ss_userAPI: 0 # 当前用户使用的ss接口索引 54 | # Gemini API配置 55 | ggBaseUrl: "" 56 | ggKey: "" 57 | gg_useMarkdown: false # 是否使用markdown图片展示 58 | gg_forwardMessage: true # 是否转发消息 59 | gg_quoteMessage: true # 是否引用原消息 60 | gg_Prompt: "" # 对话API提示词 61 | gg_model: "" # 对话API模型 62 | gg_ss_useContext: false # 是否启用上下文功能 63 | gg_maxHistoryLength: 20 # 最大历史记录条数 64 | gg_useSearch: true # 是否启用搜索功能 65 | gg_enableImageGeneration: false # 是否启用文生图功能 66 | gg_enableImageUpload: true # 是否启用图片上传功能 67 | gg_HistoryExTime: 12 68 | gg_isOnlyMaster: false # 默认配置是否仅限主人使用 69 | gg_APIList: [] # gg接口列表 70 | gg_usingAPI: 0 # 当前主人使用的gg接口索引 71 | # gg_userAPI: 0 # 当前用户使用的gg接口索引 72 | toggleAtMode: false 73 | zhilOnlyMaster: false 74 | groupMultiChat: false # 是否启用群聊多人对话功能 75 | enablePrivateChatAI: true # 是否启用私聊模式的AI对话 76 | ggKey_free: QUl6YVN5QTlKNTNVa21Pd0h1dkstRDVvWEZ4c1A3WUhMVGo1OXh3LEFJemFTeUJMbzcxTGppQ1NYT20ydFFQRHdsQ0V3cmNKdUE1XzFTMCxBSXphU3lBMG9lRzVpaDNDUlhPeTJNYl9xTHdCRVI2Wlhnbld2blEsQUl6YVN5Q0dvOWdPOEtHTkVoQlNXX3VrMkR0WlRyTzhkNXQ0N2lRLEFJemFTeUE0dHZqQlJtMkdRY0h6VW9EMm5sNE9XdGctUlI3V2IxVSxBSXphU3lCM190NEdjcVdicGM2SHNkQy1zbjVLNVZDTE8xdkhxcWMsQUl6YVN5Qnpva21CV2JvVWlPZS15TUF0NUF0ZG9FTklVaGtSWGtVLEFJemFTeURTdTRQSW8xM1ZCVlZ4bno1V2FralNIRWN6clZiOVo2VSxBSXphU3lBZnN1TTdTRnJwZERLQVpZNlptc3c4ejU5ZDN4N0gwSmssQUl6YVN5QTY5dlRYanp1WVdSVXRBdk5BVjE1bFExT29ISUJmaFo4LEFJemFTeUFXVC05b05ISE5TWDNzMkJud3dRSGctcWFueUlVclNvQSxBSXphU3lBbnhlMm9UTEZKNVcxeWpBeEsxZWdmUW1xdDJDakYweHcsQUl6YVN5Q25LYnNmMXU0dFM1NkNyTG1QbThYMkNaYnJJY1ppV1djLEFJemFTeURGZWcwOHhaMUlSMDVKRW9GNlRrVTMyZ0FObkhhZ2VCcyxBSXphU3lEbFd2cFFBS1AyU3g2RWM0b05uODAxOHRlTXRkVkxySlUsQUl6YVN5Q2llR1l6Ykl5akNQeHp0Z0kzalRKTVpXaXpVUkFiQWhjLEFJemFTeUJERFowamo2b1BDZkVDcllHMWtsNUlkbWpacUZnQVIwdyxBSXphU3lBX3lNZWVIRkpsbWdEeHlBLXNJOHFzUmtDdVFJZGJCNDgsQUl6YVN5QzVFcnBIaVA2clowUXRiZDhGSE9KalNKSU9FZ2dWa2FjLEFJemFTeUJsRmthLUlFcDBzNTBQeXIwdktDWVlzWXZGZnJnSWZqdyxBSXphU3lCQ0RVS3ltZGNTVVBTTWYyVkpHZ3lHTzViYlh2SzVzaDAsQUl6YVN5QlRqY245aE9mQ01makthSVJKVnIwSjBWeDhqbFhwaEdNLEFJemFTeUEwZzAyeVM2RTRZSURLSWlTbWViN0IwUDZPcl9CSE8tNCxBSXphU3lBRC1xcjRqUWZranpZeXZnVHRFN2hfZFdONlNKRkZKNUUsQUl6YVN5RHBpVUNocXpMVWFBSWRVQm03TGtiWTllSFJDRFNPUTIwLEFJemFTeUJ1UjNlMkktcVJ2VW1CdWZmVVdYN3Fzei1NT0k5M01TUSxBSXphU3lCbjNIZ0VDek5HbjctMXE2WGtRaGU4b2pzdUJWZS1FRHcsQUl6YVN5QUc0OTNFbENHZGdnXzFIZm5NRkdWaEhwOXo0LVJoZ1BjLEFJemFTeUNvYnpWTEhqamozZzN2dFBTam5VUElVVUhoSmdrNGtDcyxBSXphU3lCeW5EUnRhSlBTZmJwSzdWZmJYRGVBeHYtemNfMHZZM0UsQUl6YVN5QWI4OFVyay1EeTU3d01zNWR2N3JlWEwzMWlSdVdGTFBRLEFJemFTeURycjJuQVVqVUVKUUZHdEY2NEFPSXc5LW5FTGNnSGkwRSxBSXphU3lDYU4xX19HWEtCRmRTbE5lcmZnM2EwUzJVLTVPMVRTZmssQUl6YVN5RDlpR1dzS1R3T2FCcXFQd1UxSXBlRVJQdU9NVFZXLVRFLEFJemFTeUNjQzZZbFlEY3EteFR5WWtqeVh2S2tVQk1QQWl2RnlWayxBSXphU3lCTklQSDJkTFJvVGVOVWVhU1liazZZRGw4ZUo5X0M5N1EsQUl6YVN5Q3g3LVRkWV9MZXVXdl9OM1lNM290T3lDT1Y0RS1TLTRnLEFJemFTeUMybGZWOU1FcnJDYlp5THM3Y0JyTFdLSlhtS1RSandzUSxBSXphU3lDU3BRUVItQjFXVDNlamNhcEE1bk5zTHVTbXlsX0t0aDQsQUl6YVN5Q3NpUkY1djBLbnRrNWJucXd5NTlEcDJ5cEsxMkpBV1dJLEFJemFTeURNUG5idFVlZTItZElMQ3BRRXQ0ckJoUHVaY1VWYUpwWSxBSXphU3lBQTQybmF5MXVlTVlHM3lWNldEa2N2ckRIdldtb3RhdWMsQUl6YVN5Qno0MGgyRnFyR3M4WE0tcEZ0VkFpc1BSWTB4YzRsOVVvLEFJemFTeURUdVBfZzByYkFMUEZpeDM4VnBTVzZsdmk3LUkyUGFOOCxBSXphU3lEbEhDUGx4WnZvQmxpMWt4UE92b3o5RXlKUnJLSWNiaEUsQUl6YVN5QzR0UHJJN0xCbThITWl4TzQ5STRYMmZ0NWtwQlpJYVFNLEFJemFTeUFaNXVXaU4yVVJ2eEtudGJMVGRTREUzcW9qSmUwWmxvNCxBSXphU3lBWVVwSjZnTjIwWGVZejJjSmpuQzRBZ1IzUERmeDhSUHcsQUl6YVN5QlNzTzhEWFVHLXhEYTVoZFlGZXgwMVZGUTdFMm9xQUdnLEFJemFTeUN1R3NKU1FuOVVMbUhOQktCNWJPQUlaSHN3bzJyandMZyxBSXphU3lCMHRFOXZPM185dl8yMGNWb1RFSnNkMThzOFRtNVo2TmMsQUl6YVN5Q2ZxLVlDVjRYZGhHOS0xcm1ITEZfUkNTbG8wTkRCdXJzLEFJemFTeUR6LTg1a1Y3cXRpTVRyWjM0N1J6TmpSamFJV09DVk8tayxBSXphU3lEMkVHNkdiUVdBVUh1SV9Cemt3aFY0VXVlN09seWZTT3csQUl6YVN5QVVvdjA2N2xFSWJqb0x4QVMtQ21RbDBWX0ZJSWNDN2Q4LEFJemFTeURIMmd2cXo5RWlEczh0dGNfdGp5bEJ1dWZ6TmVsRnRuWSxBSXphU3lCMnlkQTdwcmhGUXRrVE5lYy1UcjM5V0p5cENKcl84b1UsQUl6YVN5Qi1Jb0RhZUxNdGVjM0VxbDVSZU5SWk5fdDgtcXU4NDljLEFJemFTeURWZXcxYlNRVkR2WVRzdXA2QkZHRWhGYmJRRmdMcnF5ZyxBSXphU3lCRjlhY01lUzZ2ZG5KOTVzNWFGQkI0c3FVbV9qZlJKZ1UsQUl6YVN5Qlloeml0dG0tTnZPY0M3Qi1jSkFocVFrdEZGTWxRV1dzLEFJemFTeUJHck16OHJzZGU5SXFtMzcwdkxVb1h0eThVMnZKSTRUQSxBSXphU3lEb09GQmU4bmxaM3lMN3RhUWxfUk5SQy14OUJIQWRrMncsQUl6YVN5RF82cVc4Y1Jhell5SVVTenRJM0E3NS13aEx6ckZYZVdvLEFJemFTeUJ1cF9Va1VYUnZKWEZEZUdkbFFOaWRXYkxmQkoyMlIxRSxBSXphU3lELXdVMzJXRE0tTEV0VGU5YzJaRTJiLVM4Q25CT010X1UsQUl6YVN5RHg3QkoxSExaNnAxVUZ3MzRJWWRBNU00eXdUOWVTOGNnLEFJemFTeUFxUUxLRmZvTngtUFNWaVg2bVJocVYwRUhZR25FRTd3dyxBSXphU3lEdGk3VkFmQnZsWXczdHF5cXFGNDhmNUVZQVJjU2w4UlEsQUl6YVN5QkV0WlJlQS0yNTZpQjJxTlJQWmpqeFJFMTIwTGdCQi13LEFJemFTeUFqanZKMEFzWVVLM1NTSnF5SU5KQUpUNTdvUFZmZlc1QSxBSXphU3lDREcxN0Ruc0VTVnVacVNWTDhLZnVNRHRmQVVLX3hvTWMsQUl6YVN5Q2puQkZpTEpxVk5ORzBlMERwaUl6MEFEcnRabmxEd2U0LEFJemFTeURPb1NaYkdPRmNPSFVvRTR3aWtNazdNZWllbkN4ZmpiYyxBSXphU3lDc3JmaWhQREpSeWN2eFZ0amtmXzN5UEN6dkFpMmNZTG8sQUl6YVN5RGg3a0JBenhRaHNhOHc4ZS12c2M3N2hWdkpnOEhVUF80LEFJemFTeUNib0pjd2RvWGF2NjRCUVcyUk5GME1DMXRFUVpCQUU5MCxBSXphU3lDVmoyVUdVQ3VoRmR1eUdYcUVyNko4YVI1R3B2Y3gtUjQsQUl6YVN5RE90MWItRnljZ1FRaTVNS2trYU5pZkhLOW93YVJYbkhzLEFJemFTeUM1RkpNVDNnV1NGMVM5YTRBcWdnaXZTeWJ6a2x2VnEwRSxBSXphU3lBU2xSMWFQQ054bWZGU0Z1a25VNjN4Rlh0RGpBRkFYNncsQUl6YVN5QTRDUWkzZWswV2h4YkZvOUlRbDl1WldmNC1nVWlBd3NnLEFJemFTeUFZamdiOV9DWEJuZFFULUktTEFWQ1c0eU1CLXNCRVpYQSxBSXphU3lDOFpaMzBJdzd5bnAwRGttUU5ITG1QZWxkbGVEUlhTOEksQUl6YVN5QkpRSHVOQXV6MVFNdnUzQUZYNC1vcHR5a0N4dG42eEdRLEFJemFTeUQyWFFXLWUtZVpBdVB0aTRoNGFtdXdYSHdSem5DVnQwVSxBSXphU3lENU5wbm9XckQ3QTlwZVp1Tmd0UWJfa1hvV2RNVUtvLWcsQUl6YVN5Q3NONGJNTU0yTEptRHFKcFg4WW9SbExsVDgxdVhrUzBvLEFJemFTeUJzZE9WUlgtcDBMdG9OVGsxMWxpVkdlR09HeXFrZTBFZyxBSXphU3lEMndHWFIxODQ5UkhmODlMVl9PZ2VMWEY0d1Z4Y2RWancsQUl6YVN5QXczTGxJcjh3Wmd4NkZKUGEwTmNrVmRTNno1ckZWdzZvLEFJemFTeUQyOVlzNllPVlh1U244VkR0QXVmVDNHTlJjSTlBSEdRQSxBSXphU3lBZU1Ld0xGMjhleEh4WGlfTmJoN2c2V2kxZmI4LVhJa2ssQUl6YVN5RGJOMXozaWxhblI2QV96Qm05bDZkeVZvNzJCcWJQQ3FBLEFJemFTeUM0V3Jua0JwSHoxMTRlRGtRMjJ6WkJZX0pvVVJ5YnpqSSxBSXphU3lENWpPS1I4elVzTC1LNXktT0F3N0dCZnN5LXQ3RjljWTAsQUl6YVN5QjRIajRSUllLUlI0RUlqbGZpeHBadk1IRmUzcGNvLXpjLEFJemFTeUExbzk1RnRjaU01WTFSWjBkNzdxZ2FtZ2ItUWFxOUJ2OCxBSXphU3lBNktHejh0Sm9pbW8zaThKX2xLWTFsWkViOEJSa3JVQUEsQUl6YVN5QUViMHMtWUt5U0VrNkRkeDhlRXJ6TWlqSEFBSlNhSzNBLEFJemFTeUNjZmxpZzhMWFprM1hSYnRQMjZha2ZGSkFqU0c2Rl9TTSxBSXphU3lCQ2ZDTlhRZ3JSRHQ5UTUtaHBIY2Y3R0xOZmtkRUttREksQUl6YVN5QmxEY3FzeGU4WWhqa3lDdEZpd1ktOGNDWWtGeDJsQm5JLEFJemFTeUFZaUZsaVBoM2M2WUI3djZmS0xRNldTbG1KS1M1MncxRSxBSXphU3lDLU9Hd1o0dFF1NjR1U18xVHQxVkk0bDVobER2ZVdtemssQUl6YVN5Q0dwZHUtWHd5RVlHSFU0SXhUSkJyQW1qVG0wbVVLd3FBLEFJemFTeUM5dmJuYTJkU2RyRzlxNFdxUG15NWVpMks1OEowSnhTNCxBSXphU3lCSWJmaU0xWEZ5VmNjTWhLVHF6cHlIc2xjNXpBQXVpVXcsQUl6YVN5REh3djVkclU0QTBHRDJacjZCRUVzd2N6TEhrSU94NHdnLEFJemFTeUJQX2xyZVlqRXc2Tzc2QjIyYnIxd1dVTjVoclROek5JYyxBSXphU3lBenB0dFVjeXhKQUQ4a2ZzQVlxUUVZcXRJWDVEWnBHdDQsQUl6YVN5QTlUTnhieU1KWk9sdktBaXZVazZDOTd3elVodDRxdjhRLEFJemFTeUNkbXlTZGw4T0tYb0FmTkFaWEh1YXJfSkFkN2RsOWFGSSxBSXphU3lEZDF5WXQtWTNKQmlyX1hnbHVqV0hZVFFxaDQ4c1NqcEUsQUl6YVN5Q1VFUFA0NTdmZk41NFJ6YWRXVjFTa0hYOXlhVHhhYmY0LEFJemFTeUFlcnhia3BqaGxSVnBRNmxBMnNFbWNTVzdBa1VjZUxHVSxBSXphU3lEZFlKd2szQ1IyM2tXWUhDX1FiU2lPUlI4SDhSa3pLSWssQUl6YVN5RFhTNEp5Sy1VMUo3enBySkNUUkdPZzM1Um5jY0k5WlNZLEFJemFTeUJJeXJoZjhiTTlqaGtsMHZQTmVPM3dscTY2blR6UG5ROCxBSXphU3lDUERVZEF5RHVkU3ptQzYzMnVWWkRTQUlpc2kzWWxQbVksQUl6YVN5REREWU1tdXZOOTNjUk9Bd2ZRN0h6d0J6ZGtDeWZnZFdNLEFJemFTeURWaVJ3Yll5bTNrTDE3ZnllVDI1YUE2YzJCel80WnRjayxBSXphU3lBM2ZkZEdZX1l5NEVJRzZrSW1zSWlhdHJUX2JkZXhnVTAsQUl6YVN5QWtrWU0ybEQxc3lwNmpZUnVUOW1acGhUX2ItR1NSMGxBLEFJemFTeUQ5SzhkNVJQZlA3cWwwVzVrcHRtNGNnZXh6bkkzV3U4byxBSXphU3lDYV9NQWR6MUxzVE9CRkcwb1c1THVJUml3ZmZvckZpdTgsQUl6YVN5Q3RPZ3VPclBHRzJLYUc3SWtXdTU4NlkxTDJ6Qm5mTnVZLEFJemFTeUFsZWFvakZLSVJ2YVFtVUlqMGN0M2RBM3Q1emthNEt2TSxBSXphU3lEbHlQMmt1ZXFYTW1Ta0xjS2U5d3BjcGhKcm03X1k5bjQsQUl6YVN5QlBmS2wzWm5PTjNndWVEOXVsZzRIcEM1aVgxcGtNVDNBLEFJemFTeUNENEUtOHJWNklyQkNpUDhjVHFURTF3dVlIZm1SakNhUSxBSXphU3lEclBtVWFHbUtRb3N4UlVtUXhIdFZYWjhCZFRzcm9yMWcsQUl6YVN5QlBUSkxaY2N3NzdpdlBmcXB4eGQzZThUd2pRa1VPTU9rLEFJemFTeUNENkNsZ2Fhb3FfYUh5WkViQnRyVEZqY3FtMXdWNDVVQSxBSXphU3lBd1U1bjl5d2xKU09TbUc2WDJWclFhRGlzaUo0bGtvVmcsQUl6YVN5QkgwZXE0dXMxQlZkSmViSTNKamNfLW5HRnVZVlBoc3pFLEFJemFTeUJZNXlIS2JfMFVWWUtlVFY2djRCRHlCWWE1dkQxNWtMOCxBSXphU3lEeVZXQUN5Q0RfSGNsb09oVm9uSVpKd01xNUptRmJ6bm8sQUl6YVN5QTRETFgySEMwZkx3ODJBdlFuWFpWdFNmSnVBTGRnNG4wLEFJemFTeUE2bk9ROG5BOFhqYktfR2J0XzQxOVhVLWxqZ1piTXJsbyxBSXphU3lDMmhidEN4RWhsSzZHSU1FMjNzeXIyeE9FNk5vSjBLdzgsQUl6YVN5QXdhMkxPZzRRbHFHNjdreXM3eEh5d19Rd0VMZGg1OC1JLEFJemFTeUIzT3Nqek5WQlROVTZUVVJVdzVRYklpU0lJVWdXQXNOMCxBSXphU3lDaGxhaDFETVdVNDJHOXBJMVgyeHoyc3VrNXlQSk5qdmssQUl6YVN5Qy1sQ3ZIblpqblp2OEVNMFc5MnFQLWFvUEI3ZWpJMXVJLEFJemFTeUM4SHlvMFVfeVU0VGRLUktvZVExS2hfMGhtdUNUcGJBayxBSXphU3lDOWloY1ktOHNlNUtrSVFoX1ZvM25hY0owd3h4bkxSUjAsQUl6YVN5RGNvc2xzeG9UbmhKaXl2TTV4TzUzZUtDa2dORHA0akNjLEFJemFTeURHczJrdGo0THRxd1Y4aHdqcDNISU41RWg1RFdEMlNzYyxBSXphU3lBVjhsNlh1SWFOWXJaOU5VUksyb0Y3NzM3RXVMZi1wSGMsQUl6YVN5REZoRWxDdTRSSGNqS2JDaFYtY0gwWklQZElwOGRHdU44LEFJemFTeUNwZGEydEY5NUzigJNtWF9VaU1EQ25oRGRCQVRUQ3VsNGssQUl6YVN5Q3Y0bUdIWFg5N3FGc3c5Y3Izd2F2WVVjSE5CYTg4d29nLEFJemFTeUEyelFpeGFrZGdYVkNhX0h5SldPMjcyb3BwU2ROMHpnRSxBSXphU3lDWGRiM0xyMDZ2MDRSSllqWXo0TF93aWp6Y0V1XzVfUXcsQUl6YVN5Qkg5RDVrbXNnLXA5M3dQLUtQRVltbTkzeU1kV0VuMUx3LEFJemFTeUNzdGE4RDd5Rk5nN3d6c21mbnM3c1ZRdmZpTUtyM3djRSxBSXphU3lDUmVRZmZvZjVMX2txdzlMZldxM1dkS0ZIczE2aHJzX2MsQUl6YVN5RGRPTUotS3p1SmZuQzE0VUFkZE1pLWNwemd4bHpqYkVzLEFJemFTeUQ2X1pQbGF5RnJsdDYwS0FReFdfc0IyYl9QNVI1ZnpMcyxBSXphU3lDd3dycTdfOWVuSnZ6VVA5UWRiNnRobmZ1b2lvbWlnemMsQUl6YVN5Qm92UXdReVQ3TkpmNG1WSmFMcWF6d0dEek93TkN3a2gwLEFJemFTeURjNHcyQWZRQ3FJSnp1bUtjRkx2QWJtOGRoeWVtWUhuQSxBSXphU3lDTXQxanZkbzdQWWpFbDBzY2xEVUk3MXdSVXpXZzIxRTAsQUl6YVN5Qm9wcEZPR0xxd1d1V2t4UGcxMFpUT3hSZjV5SEEtUEw4LEFJemFTeUFleENpeTlyY2ppaG43ZnpoMDdlV21LVUlkRi03MXNoUSxBSXphU3lBcV9YalJrLVdfb1kydU93MFJzajBqb3A1Z0NsZW0xTDAsQUl6YVN5QkR5QnNzazZ5R2R6bDZfdlZvQkVXbW82bTNXd2t3MnVNLEFJemFTeUN4RENSWnN0R3RrdUlCbnJRYUxlMDFGX0RqQ3hIZk5NayxBSXphU3lBMzljY2h3N2pJRFM1ZTd3aHM2Y0hGYmJ6VXJ2bEZ6MGssQUl6YVN5QURMUnBIei1HRWozTkV2SFBjenc3NlZNcEVuZDFuLWE0LEFJemFTeUFndDI4bmRPRzNSbmJ0bC0zeDB6NHlHajktbjFmTkRGWSxBSXphU3lEam1FLWtXX2NYMWM1WUZKc1oyRjR6WUs2U2pteFhFM1EsQUl6YVN5QzRrdXRZY1dNYTFrRVFZN213UHdGZTBoSmJXQ2pfN2drLEFJemFTeUNDcWI5WW84LVRfU3dKYVhWTXZ0S1ExeDgxR3JxRF9lZyxBSXphU3lBS01MT2lvbkhhaUVTemhDNFpOZEkxWFZzbVlNbnE0VUEsQUl6YVN5Q0NRNTNmTGcxdWpLZU1BTlI1d1V2ek1HNXB2YnRveWpnLEFJemFTeURMc3M4ODdmLVBVdmc5bTRDalZqMTgzREpPYzJ3c0NpRSxBSXphU3lCR0JCa2h2ZjA0VXFKSzJxUTZTbl9NQWZXc0hYeDdNRlEsQUl6YVN5QjZWQllSUHVkbTZlZzRCRWV3Ykg4a284djNzRDdGcUE4LEFJemFTeUM2MGUwVHRSUjlSWkZ6SElVNWdaN2h4d2NSMzQ4SXMyNCxBSXphU3lCOGluWnJzYkZDSDl2ZG1CSGJJTFkwSVNYa3k4U0swU0ksQUl6YVN5Qjk5alp5c0g2RHVvVUlVb2pKNENweWRnWHQ5Rlg3Q1g4LEFJemFTeURVUFp4UkNhUXdaUi1RMUdsb2llMnpBNHlIWWVuVS1CayxBSXphU3lCY2l5MWRDWjdkN1owUE5NdW1oLThleGUwQ0o4SVA4UFUsQUl6YVN5RHlpeUp4Yndxak9ENDVMVkFDUVN2TVB6TW9iZk1wTXM4LEFJemFTeURMMXBfWjJ0dDBiWTAzVm4tQ2tOOUFPak9UN3Axby1LOCxBSXphU3lDbTdlNFFhSlZ5cVkxQmJNZkNlRE56dldrcVMyOEpERVEsQUl6YVN5QzZUeklJc2NndkMyaEFuV1lDTnBKUUlfSXJUQUVpV1g0LEFJemFTeUFXOVBuRlp4dlFENXM5QUw5dmlnb3JpTFFhV3NVaERBdyxBSXphU3lBNmthdTluaFZmWmR0bHFTNFAzcWJTREJxdm1tV1Rubk0sQUl6YVN5RGYzXzRwbEVtQV81UllUanFKTVRCU3I2d2I1Rm5kNkNZLEFJemFTeUQwVXl3dGR3M2xYZXJ2cnpyN0U3S1o1YlBQNWcxZVBGMCxBSXphU3lCV2NCOFYxQWZIZDR1UC1BVlVoTVNwLVloc21yeVV5VVksQUl6YVN5Q2VLRVFvZDQzdkl0UFFGZi1sdUFWNVhBalhsWHJIVmJVLEFJemFTeUNOS090Q2RtSzhPenM3WFZFdmxibDBrZVBSaGkxdlludyxBSXphU3lDVHJEWTVhd0E1SVQtbm1RcGdkUS1IT2tXUi0wU3p4LWMsQUl6YVN5QmdUTFZUbVRVc0RMM19iT3N4NnJZdG9tSVFNNVZONnFnLA== -------------------------------------------------------------------------------- /config/fishAudio_default.yaml: -------------------------------------------------------------------------------- 1 | - name: 丁真 2 | speaker: 54a5170264694bfc8e9ad98df7bd89c3 3 | - name: AD学姐 4 | speaker: 7f92f8afb8ec43bf81429cc1c9199cb1 5 | - name: 纪录片旁白 6 | speaker: 8bad0cb3e890489a8925db005f85a765 7 | - name: 永雏塔菲 8 | speaker: e1cfccf59a1c4492b5f51c7c62a8abd2 9 | - name: POTUS 45 - Trump 10 | speaker: e58b0d7efca34eb38d5c4985e378abcb 11 | - name: 流萤 12 | speaker: bcbb6d60721c44a489bc33dd59ce7cfc 13 | - name: 七海みなみ 14 | speaker: 8e2c03703de647f9a576b0b130c1c336 15 | - name: 阿梓 16 | speaker: c2a6125240f343498e26a9cf38db87b7 17 | - name: 芙宁娜 18 | speaker: 1aacaeb1b840436391b835fd5513f4c4 19 | - name: 符玄 20 | speaker: 40c545b189414e5b9ca8818211e9d41f 21 | - name: 雷电将军 22 | speaker: ec4875ed4e154ed09d1b501a2214579a 23 | - name: 董宇辉 24 | speaker: c7cbda1c101c4ce8906c046f01eca1a2 25 | - name: 邓紫琪 26 | speaker: 3b55b3d84d2f453a98d8ca9bb24182d6 27 | - name: 丁真(锐刻五代版) 28 | speaker: 332941d1360c48949f1b4e0cabf912cd 29 | - name: 电棍otto 30 | speaker: 25d496c425d14109ba4958b6e47ea037 31 | - name: 派蒙 32 | speaker: eacc56f8ab48443fa84421c547d3b60e 33 | - name: 单田芳 34 | speaker: 47b2f5a685aa456286e6746b42db4490 35 | - name: 扣比不 36 | speaker: d1f2aabbde274195adaf0710086269d7 37 | - name: 黑手 38 | speaker: f7561ff309bd4040a59f1e600f4f4338 39 | - name: 孙笑川258 40 | speaker: e80ea225770f42f79d50aa98be3cedfc 41 | - name: 七海Nana7mi 42 | speaker: a7725771e0974eb5a9b044ba357f6e13 43 | - name: 刻晴 44 | speaker: 5611bf78886a4a9998f56538c4ec7d8c 45 | - name: 钟离 46 | speaker: 305ffec2625549318ab031c956aa9577 47 | - name: 冰糖IO 48 | speaker: 3cea24d91ff44b2aa3730a03d781e64c 49 | - name: 李立宏美食 50 | speaker: 40200c335f004670af7fe447224d379e 51 | - name: AI沈子钧 52 | speaker: f37617b269164d2ea6ef5aa7ff57a05d 53 | - name: 黄泉 54 | speaker: 782a92088ad944e6b9b07387fb01ee5b 55 | - name: 花火 56 | speaker: 93ea9dba669e4f14be513d5c4e0ca9e2 57 | - name: 棋手战鹰 58 | speaker: 6021b47493af4b65afed404858eecde9 59 | - name: 弘历 60 | speaker: 403bf80018a3408faf41a28d2d5015ac 61 | - name: 明前奶绿2 62 | speaker: 7a90968f0a0341b5ab0c82480c018312 63 | - name: 纳西妲 64 | speaker: 9f7eb4b1663a4d58b1554921121c4a47 65 | - name: 井芹仁菜 66 | speaker: 63968837b9ea45c2a3dd0d44af51c09b 67 | - name: 东雪莲 68 | speaker: 7af4d620be1c4c6686132f21940d51c5 69 | - name: 尼奈(猫娘版) 70 | speaker: d48d8cd2382e4ca19ab4f713c4cf96c4 71 | - name: "Robin (Honkai: Star Rail)" 72 | speaker: 606b42bce59b4ec5b0349d38bdfee16c 73 | - name: 炫神 74 | speaker: b48533d37bed4ef4b9ad5b11d8b0b694 75 | - name: 赤井秀一 76 | speaker: 010d44a2207e4f54b9774ef4c4ea9ca5 77 | - name: 银枝 78 | speaker: c7eca0b53a4947328bb2e000c55ff06a 79 | - name: 砂金 80 | speaker: b5523fe4857140118c9dbcf0062527f7 81 | - name: 巴图鲁 82 | speaker: a36707153fe743838e3c91b9e8348624 83 | - name: 胡桃 84 | speaker: 27afebb2dd2e424eaba1d74fb0d25c9f 85 | - name: 明日香 86 | speaker: a56d4039c92644dda9d2ac429d44c609 87 | - name: 空1 88 | speaker: 84408de3fdaf4f9bad0a1bc870bd0a35 89 | - name: 花火导演1 90 | speaker: e9d720e745334ab0beecf1a16bf56a7a 91 | - name: 星瞳 92 | speaker: 825c5957e57d469181783bd47786c5e1 93 | - name: 旭旭宝宝 94 | speaker: e3f3d8c7dddc4b0ca70c6e993c9d9c1e 95 | - name: 黑天鹅 96 | speaker: 5816b9e2a12e40e69aa645bb7f76ec79 97 | - name: 亚太空间合作组织秘书长 98 | speaker: c38fc97459054746bc7cc425e4c7938e 99 | - name: 温迪 100 | speaker: 924f4189bf174686bd041d144deb3f36 101 | - name: "1" 102 | speaker: 2c831dab24874870ad491c393e03af69 103 | - name: 尼奈 104 | speaker: fcce991d24e6453fbc2663a42281f11e 105 | - name: アルマ2 106 | speaker: fb62b47d73b24e77afa86e19108419fe 107 | - name: 孙笑川 108 | speaker: cffbce6b2f004f9480ab284516ef1e7b 109 | - name: 记者2.0 110 | speaker: 5ad04e32a434421a92c5ad321cb33fb8 111 | - name: 希格雯 112 | speaker: c503b9b8d4c240f6a6bf2658d11f470b 113 | - name: huhu 114 | speaker: 6efebbaa091645c5b34b5437dea6e679 115 | - name: 试试 116 | speaker: f73c04417e124061a3bfd3cdb3aecd8a 117 | - name: 唐僧 118 | speaker: c8aea63ceb124be9a370efdcc7197db5 119 | - name: 米 120 | speaker: 68b13326ba0c4584a93abf4fb2cd847a 121 | - name: coco1 122 | speaker: ba12661c6769479cabd9d993279cd444 123 | - name: 夹子米虫 124 | speaker: 7c3585f8db0647649fc8498f33444299 125 | - name: 包包大人 126 | speaker: 34326bc762f043deb98886fd40a897bb 127 | - name: baicai_ZH 128 | speaker: 25b66dc5cb7f4f1db8d59d39c838158d 129 | - name: 算 130 | speaker: e4440a605ee44129a4f8dba4411058c4 131 | - name: 烟绯 132 | speaker: 7a34cd3f65ff4837ae8f270679b7db18 133 | - name: "男2常用 " 134 | speaker: a7d5559f3e3d4c5ab21657af10f11ca1 135 | - name: 偶像大师 新田美波 136 | speaker: d3901c9a9ca143b9bafc8aa2b50635f4 137 | - name: 枣伊吕波 138 | speaker: 3af6152238144815a961a453bfee4783 139 | - name: 亚托莉 140 | speaker: d5173525443c45218b9007b80165b08c 141 | - name: 罗永浩 142 | speaker: 9cc8e9b9d9ed471a82144300b608bf7f 143 | - name: 佛耶戈 144 | speaker: 551c89b09d484746847babf8f3650d72 145 | - name: 斯维因 146 | speaker: c501bb41d83a45f29d8ef393e5be2972 147 | - name: 布洛妮娅 148 | speaker: dfedaead274c44d1acb1c9f936386458 149 | - name: 小桃 150 | speaker: 9511e70b94cc4a18b584be4582a2e058 151 | - name: 扇宝 152 | speaker: e62be7c938074482908e5444487a5f58 153 | - name: 袁腾飞 154 | speaker: 04ccc399bbf3402f872f8d01b2eeefda 155 | - name: 东方镜 156 | speaker: 8f02254b431848afb5cf67777dc33282 157 | - name: waner 158 | speaker: 4ca8564f35e947abb27ea8c616168f17 159 | - name: 管家 160 | speaker: 6129670c24684d0cb2d0e6f048d9b77a 161 | - name: 晶蓝研究室 162 | speaker: 08592604c1f9456d97f366dd94db3f5d 163 | - name: Ageha Shama 164 | speaker: e98ed4fe92534810b9a98ad9baacf86d 165 | - name: 古美门 166 | speaker: 978df303226b4f35852315bdf13a7cf0 167 | - name: nkl 168 | speaker: 30539d2d0c3042ecb4493a2bc0f2935a 169 | - name: md1 170 | speaker: de90dae70b1247aa823c49297ad2acac 171 | - name: 露早GOGO 172 | speaker: 7e1650fabf834b08affba3163b47598f 173 | - name: baicai_JP 174 | speaker: 4f1a4f31facf4e7fa00d24b6be3c6649 175 | - name: 青春恋物 纯爱酱 176 | speaker: 210d872054604852b5574e9e56895487 177 | - name: Sakura 178 | speaker: 60e5c8f71d33405e84122fcd84b99360 179 | - name: 卖卖 180 | speaker: 6343f4d874374a1f9cbf73e9c16414d8 181 | - name: 夏目蓝 182 | speaker: af86804cd75745f397a7732f8615e364 183 | - name: 小熊维尼 184 | speaker: 00c490249c664b5685443cecd8d69a45 185 | - name: 蓝斯报站 186 | speaker: e8dfbb62527544d9a8d775587599582c 187 | - name: 影 188 | speaker: 35c7aba788c34e4ea9f4979ed7dbfd4d 189 | - name: 老母亲 190 | speaker: 04da7364123c481999a4bce1ebf98ad0 191 | - name: 企业宣传片 192 | speaker: 3b448321a1bc4adf809c2ff85742bc96 193 | - name: chouxiangping1 194 | speaker: a7acd72129f147c1a3ca12954e50b2fc 195 | - name: 作曲 196 | speaker: c818b4cafaa14c279bbbd0bdfc096e4f 197 | - name: mm 198 | speaker: 3169142c49e9499783873e8af8ba7922 199 | - name: 农村生活 200 | speaker: 2fb17cb046a046c982b9dedbb264c514 201 | - name: Meow Moew 202 | speaker: 4d361843292d4f6eaaff875088668912 203 | - name: ttt2 204 | speaker: f0932d09fa2c4b26a217065ca03ef115 205 | - name: 流萤 206 | speaker: 79b9954c15f24deeab2319ec5f44f3b0 207 | - name: ali 208 | speaker: d23df9b20fff4ceaa81bf1f1f4cd2a71 209 | - name: jian 210 | speaker: e475414740964ec79d2bfa9ea679cc40 211 | - name: 余切 212 | speaker: fc5c34467a744f9a8c3d02113e126c2b 213 | - name: 战鹰01 214 | speaker: 6ede770c9af546ecac9da1c565639919 215 | - name: 派蒙1 216 | speaker: f6425925ac4d4b70812e411d5a51fad2 217 | - name: 多人 218 | speaker: b143da342cc046b29900a159d1a1fa11 219 | - name: 流浪者_原神 220 | speaker: 5fea54a40c5e46609cafb005741649d6 221 | - name: qixuanm 222 | speaker: 407ea0a10d524664a27d642b7ef4fe63 223 | - name: 扇宝 224 | speaker: 944473ec6af34a109c2ab69d78fc20d1 225 | - name: Qinlan 226 | speaker: ede5292dc7ab42efba64baa117689419 227 | - name: coco 228 | speaker: 97c330bcc93f4b54bd6998364ca1dd9d 229 | - name: 安和昴 230 | speaker: 12e2d778239243809a264792827a3a2d 231 | - name: wannn 232 | speaker: 1221b806f4e0463c8a2931a7eb6c519b 233 | - name: 昆虫学者 234 | speaker: 568df317a1b64b248e5f6c113e865f8d 235 | - name: 记者 236 | speaker: 7c0a8976e720447aa60e944238589b9d 237 | - name: 白露 238 | speaker: 500cf16517af408e8c26f4cc5d00bc54 239 | - name: 艾丝妲 240 | speaker: 9bf9ec2b27954efdaf516e3019bddaa5 241 | - name: 测试 242 | speaker: 6bace51773144c09b4127ef46ebd0639 243 | - name: 姐姐1 244 | speaker: 02264764e0f64d3f828bb3f7d6fc7681 245 | - name: 扣税国王 246 | speaker: fa3aafdf14424158b766eb9dea710257 247 | - name: 浊心斯卡蒂 248 | speaker: 343b337a6d0749ccb553786d02fc8f17 249 | - name: 明前奶绿 250 | speaker: e0cefc0c31674611862242d79f1075cc 251 | - name: ? 252 | speaker: 053247335b9d4a7a84929b542568fab8 253 | - name: 菩提祖师 254 | speaker: d85181a05b284840a89e9df2b8e40d96 255 | - name: AI李奥翔 256 | speaker: 6b945bbb360a4d0aaf6c7f35792756eb 257 | - name: 周棋洛 258 | speaker: cd746259ffef4c0490cf94dd3b3213c4 259 | - name: 奥尔菲斯2.0 260 | speaker: 956e9d33c21e414ab544a6759832f76f 261 | - name: "\U0001F955" 262 | speaker: 08c3ee8881544fac8f7fc2032c4ef87c 263 | - name: 撒贝宁 264 | speaker: 9a32f25d901e48e0885dbda99b08593a 265 | - name: 全蛋 266 | speaker: c90d41749e8d42af90a301e8a81a068d 267 | - name: dongbei 268 | speaker: d43da834685b4d9ca08f05c3ed3fb790 269 | - name: 溜溜leo 270 | speaker: 2f86f2067d5a45b19b0b57235dfd9518 271 | - name: WY 272 | speaker: 911236d9c2a54864b6b253761c97f274 273 | - name: 琳妮特 274 | speaker: e2490541e3c5446a968deed51a2003d2 275 | - name: ling 276 | speaker: 63074579f8324b27ae1188eeb6ab9805 277 | - name: MOSS 278 | speaker: 14a6e15622f44ac89a87b06dd0459fa5 279 | - name: 崔永元 280 | speaker: a09f3c4685f640539acdece567c00ee0 281 | - name: 明日方舟 W 282 | speaker: 801157ebc51f4de2a02aa95d8330f241 283 | - name: 夏提雅 284 | speaker: cf2cb592b4fc4095bde6093336e53361 285 | - name: 陈冲 286 | speaker: 097df5f739544e3d84af11ebc0494ed3 287 | - name: Negan 288 | speaker: 11d19341a6864ad3b64223b9ecd25905 289 | - name: ID001 290 | speaker: 5323d74f9ac9474eab75b84386f17568 291 | - name: 佛耶戈 292 | speaker: a2a11129b14d4f9ca8405b4ee50ebc0e 293 | - name: 汤圆四水 294 | speaker: e9c4bc4aa85546f1a91df4ea07849411 295 | - name: 悟空1 296 | speaker: 495800c2c5d942e292f4dcacd9f307fd 297 | - name: 郑容和 298 | speaker: f25bc67218c34791acb75c744b7ad5bc 299 | - name: dw 300 | speaker: 67b8b8f6ca094ab09b6bc195c7d18c4f 301 | - name: 敖 302 | speaker: 327c2be1c3ba4d878bdc4b0ba1e80ffa 303 | - name: 文静 304 | speaker: 15f6a34740cf4a1683af01c93454f483 305 | - name: aishe 306 | speaker: 279ccc6f58434f49861a47513c314db0 307 | - name: 战场原黑仪 308 | speaker: e1cdba21e73a41a7b9c0084e23951a35 309 | - name: niao 310 | speaker: 734d99fbbc96496c8f6289dd9a6e42dd 311 | - name: 我 312 | speaker: 69c9b3be7b9c4112aec75f3a398642b3 313 | - name: 测试 314 | speaker: a7bf89a96aec4a56afb7b5259cc88d44 315 | - name: 匠心帝。 316 | speaker: 8c09746370484c949e05841a8b14d2be 317 | - name: 雷军 318 | speaker: 738d0cc1a3e9430a9de2b544a466a7fc 319 | - name: 神里绫华 320 | speaker: 931fad22448d4bd4a6052e84e788f9a1 321 | - name: tts 322 | speaker: 10937afb35b24eaeb0097d5b5c7dafad 323 | - name: Within 324 | speaker: 572bdc613eec4fb58c8bbe129abf2941 325 | - name: 藿藿 326 | speaker: 8825d4dab4744d65807703ef07c3ff08 327 | - name: 飞虎队 328 | speaker: d80876e8f7d94e8e9898ff2a3634d418 329 | - name: 测试 330 | speaker: 736564bc73564f71bd9d54fe3fe9e741 331 | - name: 阿里马斯 332 | speaker: 3fbc1d2e48e84e7ea7a91c084e2f76c8 333 | - name: tttt 334 | speaker: a49f06a88f6040cea73793708b8f8005 335 | - name: 后羿 336 | speaker: aa6ff01050e9481093191cdec78cc782 337 | - name: 高松灯 338 | speaker: d9722419d6494f869c6e59e8c68842df 339 | - name: 相泽南 340 | speaker: 32d428ac13ff4e3c95b940419d9aa465 341 | - name: 甘雨 342 | speaker: aa88c8d13ea147f9ae452f4803aef634 343 | - name: ayane 344 | speaker: c89e2b4aa5aa4907bf3c43c34b065fa0 345 | - name: June (The Finals) 346 | speaker: 44ec90c73912458aa36fb90adb2db137 347 | - name: 张顺飞 348 | speaker: c88b80d38d0f4ed0aed1a92a5c19f00f 349 | - name: 明日方舟山 350 | speaker: 6cb9fb889f8e4272833bc01e9e50d82e 351 | - name: AI何佳宇 352 | speaker: b456ba5b86434d9882a9854f52afe886 353 | - name: hhjk 354 | speaker: ee8dfb6aa4f14006937b6bea604adb0c 355 | - name: 河原木桃香 356 | speaker: 891f17ac9a9f43b6adc1996d1214a155 357 | - name: xwy 358 | speaker: 13473ba75e944a3aaccf124e8e830f8e 359 | - name: 星瞳 360 | speaker: f94038f8c9424bb8ac3f5546a86161c6 361 | - name: huawei 362 | speaker: 90957007fe254c50af2923b46e77a139 363 | - name: 流萤 364 | speaker: 4c3f5ee88f7b4554af62799b44d6e168 365 | - name: lu 366 | speaker: 7c32afa5b6ec4f1085c80e93b6e62596 367 | - name: 邓女士 368 | speaker: 5e89a0832d974a4ea093efc35923f752 369 | - name: Natsuhi 370 | speaker: 5fd557df6a7d4b62833616cea3eb2c41 371 | - name: 爱莉希雅 372 | speaker: aca922eff5f0446fbfd395fe03e48f35 373 | - name: 露西(en 374 | speaker: 0f685855b04e491392ed9d4a48efa275 375 | - name: 七濑胡桃中文ai 376 | speaker: f1ea6e02b6a74b4381c9831f00d25d25 377 | - name: 大家好 378 | speaker: a30b350a5d37431fb330981464e795f6 379 | - name: 阿兰 380 | speaker: 579292542ed942f798528a12a7fece4b 381 | - name: pzd 382 | speaker: 2e4b8699f5824f538687908380229795 383 | - name: 王二 384 | speaker: 0bb69186e78144a5a50013187d513efe 385 | - name: Harold Finch 386 | speaker: f8dbbcc1f9ec475e98213243b4469e78 387 | - name: yanhe 388 | speaker: 47a429f21d254d3db73a9b3c3e895803 389 | - name: LUO-002 390 | speaker: 9333a517081b48a0939b37ede21c1f33 391 | - name: 温柔动听女声 392 | speaker: faccba1a8ac54016bcfc02761285e67f 393 | - name: Taylor Swift 394 | speaker: cfc33da8775c47afacccf4eebabe44dc 395 | - name: 包龙星 396 | speaker: ccc2d12e4ca34dc99b258a802311bce2 397 | - name: 凯伦(实验版) 398 | speaker: b05e816d49284856b302a5fb1ed4ed9f 399 | - name: "22" 400 | speaker: 8b51b7c997694bde8a523791ff44dc10 401 | - name: 洛天依_灵 402 | speaker: a3587a5e66354a99a79e8b129330e320 403 | - name: 次生银翼 404 | speaker: 537b3d9dbfc04b03a7fac02144cc59df 405 | - name: 爱衣休伯利安 406 | speaker: 7e688c41c52d40a6ac0f346078806d8b 407 | - name: 露西(zh 408 | speaker: 5d7f36b042a74649a5948e60228ee823 409 | - name: awei 410 | speaker: 63509dfbba87440f88310e400c414092 411 | - name: 52ba乌尔比安 412 | speaker: d85206d9730347a68ea892451c66d938 413 | - name: gyh 414 | speaker: 94babbe7789440a5acf038f14e5ad96b 415 | - name: 卖卖 416 | speaker: 0d7e798a1d1e4d368e71d1ff705358b9 417 | - name: w的声音 418 | speaker: e2e088d571c349648b54ececdb09d843 419 | - name: 凯尔希 420 | speaker: 63a1c25a3b4e46e19476c4a57e12e063 421 | - name: 明日方舟 特雷西娅、魔王 422 | speaker: 816affccf6344baa8f878af81b1b7104 423 | - name: Rupa 424 | speaker: 27bcd7d3651f4a37a2b0e0c3cc630e70 425 | - name: fy 426 | speaker: c78097f66c954a73b51ec358b51c1751 427 | - name: liuliu 428 | speaker: ab5bb5cc298d424baa1b178fc85eb89a 429 | - name: lamune 430 | speaker: bb9b659562364bd88bc6e9c28a9f1d2f 431 | - name: 狗蛋 432 | speaker: f6a364bcb5034ac889c561f8a0bc0562 433 | - name: 测试 434 | speaker: d73edd9924bb420bbb0f4403ad173320 435 | - name: 高血压 436 | speaker: ce748bd9d07f4851a3af16ca440ed088 437 | - name: xiaohua 438 | speaker: c0beaca84ee54bb0964f870eb313d894 439 | - name: 霸道姐姐sweetbox 440 | speaker: cc6b0986eeb245638b82562391d915a7 441 | - name: qy 442 | speaker: 55f1daa41bcc42199bdba1f4996107b1 443 | - name: 赵本山 444 | speaker: 861758553f1d462680c0b1d97ee7c8c9 445 | - name: Dr 446 | speaker: 1590f9cc6f4a4f03b7ed806f7ded059f 447 | - name: 大和田常务 448 | speaker: 7e27c2fac4f14eedb8084095022602bc 449 | - name: Xixixi 450 | speaker: 8e5b7c5f001f4b22bbf5ae8a43472fb7 451 | - name: 邓浩洋 452 | speaker: e9bb3cb794cc43babbc7a93ecd3bb1ce 453 | - name: 炫烨 454 | speaker: 7b728be396e44c889c97f53351ae829a 455 | - name: 雨 456 | speaker: 3943efd62b8f4236ac647b02a76defe7 457 | - name: 温柔女声 458 | speaker: 26c259f0f07247c98899ae4cfa1774be 459 | - name: 凤凤 460 | speaker: e6979038a4a7470dabe860f1df5bae96 461 | - name: MG动画男声 462 | speaker: 17ad3eea8145452da8dfd077e8358e3e 463 | - name: 毕业季温情女学生 464 | speaker: a1417155aa234890aab4a18686d12849 465 | - name: boss3 466 | speaker: d27d908965724140b83f072ad0fdb01d 467 | - name: 刘德科 468 | speaker: 414e07563a18459e8b0356f56dfb403a 469 | - name: 湖普 470 | speaker: 521e62c892fd476586ed6227fe145efb 471 | - name: 企业宣传片-大气 472 | speaker: ec32b866b4c54dfdbb0bd6e1a37cf8d6 473 | - name: 彩虹红 474 | speaker: 5c9a914624e849338e97c0229283c1ff 475 | - name: 幽兰黛尔 476 | speaker: 559e9d528859405b81866e9ccbee9f1a 477 | - name: 胖宝宝 478 | speaker: cef93653fd9e49f7836dd255aaa0e492 479 | - name: "11" 480 | speaker: 54e3c013513f4c5199dcdbdadb557a96 481 | - name: 洛天依_柔 482 | speaker: 69c8e52894ad4caf815880cd88401019 483 | - name: WK 484 | speaker: 93df015f05f04659befb40dce0bce6b7 485 | - name: 听不懂思密达 486 | speaker: 817432221ab546a7af38ad8c4839227a 487 | - name: 嘟嘟_学校广播电台_CN 488 | speaker: 4d8db1f92f904c12806ad4d3302e4615 489 | - name: 波提欧 490 | speaker: 8d83d2d704e74b24903f8fe62f6f6f08 491 | - name: 刃 492 | speaker: 1a1b459401944e3795777a18029ee266 493 | - name: 剑先鹤城 494 | speaker: 7ac1a811175e4a11957057b5345864c4 495 | - name: 令 496 | speaker: eab668c39a15401e8a213b4b029c1f05 497 | - name: 东风谷早苗mv 498 | speaker: 854f41ce29774e4f910afdbd2ae211c8 499 | - name: 萨格尔王 500 | speaker: 0a4221e822194ff28c49dfa723a9fdce 501 | - name: 男1 502 | speaker: a5c7de2d172f4ac09feeaea6226e7e5b 503 | - name: pjs 504 | speaker: 5e247c51f55b44298e334b9033a75181 505 | - name: 夏生 506 | speaker: 53c48fd76d8742e987df5cfce018ae74 507 | - name: 田二喜 508 | speaker: 6f24ad8e9adc4338baec7f0fbd965fcb 509 | - name: dons 510 | speaker: e630bb053f2f4965a9b274b080ca0d50 511 | - name: 无添加 512 | speaker: 09325b6dabc44d0ca2128bd131bb657a 513 | - name: md 514 | speaker: 81eae19d54bc4c3a84c7b2ab3f724a36 515 | - name: test 516 | speaker: f7675ba703f746748e1cffde29bdb319 517 | - name: WKR 518 | speaker: de84099d24c44cc083ac420e21640bb2 519 | - name: 大司马 520 | speaker: f198a680f1774280821cba47969eb687 521 | - name: 陈功的声音模型 522 | speaker: c722156bba1c47eaa62d872485e63be0 523 | - name: 测试1 524 | speaker: a8f9c37863194db4b8758927d3a1855b 525 | - name: me 526 | speaker: a685769971674ebdb75d83f5f5f7ea2b 527 | - name: lze 528 | speaker: 967dbe1ab8f14186b3bd63f37a14c2c3 529 | - name: 男1 530 | speaker: 315eb83b32214dda8788fc5c8be58503 531 | - name: ling 532 | speaker: 80498e4a3c9f4875aaf6d6c092c5b27d 533 | - name: 二次元妲己 534 | speaker: d2ef0438eba34f1dbe884062c29e9589 535 | - name: 凡 536 | speaker: f677c88994224d09a98ecf6433d442e8 537 | - name: Freeind AI 538 | speaker: 0e02e2d442fe4436af3f1d15ac72d243 539 | - name: test 540 | speaker: cac4367f908a4b8a93863610adb21fcc 541 | - name: 大家的日语中级1阅读 542 | speaker: ba9550f61889433aaefd92e80f9492f6 543 | - name: JJ 544 | speaker: cc8b4ae64aa14e0f9cac573a56ff4d3c 545 | - name: Maxテスト 546 | speaker: a765c4d21b33497abf3f537003c374b4 547 | - name: henry 548 | speaker: 09bc22352e664f6e9e1ae56c90d0f5b6 549 | - name: 暗人 550 | speaker: 5e5df81ec9da4013961e49d609b33f9e 551 | - name: 阿星 552 | speaker: a8fd58525fd74e6998e6784850841960 553 | - name: tstt2 554 | speaker: 7e5964259ed84f94ba7dc229cda6bc81 555 | - name: test 556 | speaker: a28a98a0f46041c7a4759e22764adbee 557 | - name: 憨堡Ger 558 | speaker: c0e80b94d42e40d2b43bf7be7db7cf62 559 | - name: hou 560 | speaker: c2c95067095c47e48a12d00ed24ef7d7 561 | - name: moss 562 | speaker: 8eed4e378a204cfa8b54f853df2e176b 563 | - name: slang 564 | speaker: e1c95bf81fcb4610a7c277d010128571 565 | - name: 李素裳 566 | speaker: 8bedecc46d9647fc860e9073aa410925 567 | - name: 独人十三 568 | speaker: f0c9b3ad755140bf8b8a237301e38e80 569 | - name: liu 570 | speaker: 8f58f2cd7494485b862de25d2678c6ea 571 | - name: "33" 572 | speaker: f8f2091df0d4424ebe5638c55272dda1 573 | - name: test 574 | speaker: 9f7de6ac06d4403ea77aca59d7cac710 575 | - name: 永雏小菲 576 | speaker: 3aa6c0a9dbc64eb588d588cb5449edc4 577 | - name: 丹花伊吹ibkui 578 | speaker: 7eff137451de4b21bec115781c354b4d 579 | - name: "1" 580 | speaker: 26137903918e4075bbdfc83784b60588 581 | - name: "9729" 582 | speaker: a255a2a9e9704591b14f29997f054cf4 583 | - name: 广西大师 584 | speaker: 1c5c2e965ba64481a8273ca729972758 585 | - name: "9729" 586 | speaker: 238e42b853274c1683f927d07d27d3fd 587 | - name: 小孩 588 | speaker: dd61b195b2114c6b940d9b1fd8395eec 589 | - name: 迪奥娜ZH 590 | speaker: 05c7fc715b7b43a4a55d1124e6bbe5ba 591 | - name: Scotty (The Finals) 592 | speaker: a33e8d3be99941e3be8154bac658b93f 593 | - name: "8" 594 | speaker: c9f3cac5024242838c21f5b0484c9322 595 | - name: boss 596 | speaker: 0bd41770680c4007a400861816791315 597 | - name: 包子 598 | speaker: 04a733b999df41edb227d91ef49e96dc 599 | - name: 扇宝 600 | speaker: 001afb35707044a7b9096e0a72ebfd03 601 | - name: 络希 602 | speaker: e66797bb467647e5afd0a8cee5823499 603 | - name: "007" 604 | speaker: daf4a8c039ba42ed826a2fa3a3a4be12 605 | - name: 嘉然 606 | speaker: 1d11381f42b54487b895486f69fb14fb 607 | - name: AI李奥翔 608 | speaker: 843cb28099f0461fa30a3b3f69ee2e02 609 | - name: beagle 610 | speaker: 3cf67e70a1cb445589977db2a331d70a 611 | - name: 海老塚智 612 | speaker: 36f83a3fac734a2a9af99fe87abefcfd 613 | - name: 司 614 | speaker: c660e5ba19aa4fb2ae94af9f7bfaaed7 615 | - name: 测试1 616 | speaker: 560b14efb52a427ea3ce96c6136b055c 617 | - name: "1" 618 | speaker: 6ff151111892477a9b303e78ab11890e 619 | - name: 奥尔菲斯 620 | speaker: 7aa38cd9941b440ca07d8a88552ab3d7 621 | - name: "33" 622 | speaker: 316a0d0a361e43589d65d357bda14b65 623 | - name: 老叶2 624 | speaker: a279eb5388e248f7acd1b1f094657daa 625 | - name: 周星驰 626 | speaker: 1a5dbf4c3ebc482abf9f2f07b4f463f5 627 | - name: 安仁2 628 | speaker: bd153f68c0f2469fbf4ec7ce7a773135 629 | - name: 发姐 630 | speaker: ec97c8c0b9a2423e82a815f7849cb876 631 | - name: Ngo Lan Huong 632 | speaker: f24016f549ff4b01996d53aa5180c25c 633 | - name: ouyang 634 | speaker: 5435adc163fd42efa658b316e2d49d6e 635 | - name: ces 636 | speaker: ab76c1102fe6452089c6040d6be3765e 637 | - name: mfq 638 | speaker: f84a576383954e1dac15fac07725dde1 639 | - name: 瓶子君152 640 | speaker: 7e19d65d655947788870b65b9f5a3310 641 | - name: Mun 642 | speaker: 840329e4a24241d0be47bb0eeb26c9d5 643 | - name: 瓶子君152 644 | speaker: e51550f2419a431f8fc5811225f7df07 645 | - name: 鱼头 646 | speaker: d8fa3145fc534fa0a721338b0a983bc4 647 | - name: SELF 648 | speaker: cb03e8555d7a40978888a4a89e981469 649 | - name: 个人C 650 | speaker: 26a39b04984f49ccba851a6af27fa773 651 | - name: Sheldon 652 | speaker: 34eed45ea25b47e2b6f60c70ffef858e 653 | - name: 萧炎 654 | speaker: aded62e8c2024a6f881ce7cf37217d18 655 | - name: wan 656 | speaker: c40b0946210d4c438062a84f2ca91a88 657 | - name: 然然2.0 658 | speaker: bf22215c3cd94c619f0e53c0f5bf4d86 659 | - name: 科比 660 | speaker: a9a64728cf0b4bdab9d4691535e762c5 661 | - name: 维什戴尔 662 | speaker: 23921fd9e9464965916b6a8bf7706009 663 | - name: 然然 664 | speaker: 7c2f04ebabac4d018af3d21d87447c05 665 | - name: jingxiangshuiyi 666 | speaker: 3006b7b9787b41769919b8ac827ce6d2 667 | - name: marklin 668 | speaker: 77b8b59d6d034f508aaca7664c6d6541 669 | - name: JJJ 670 | speaker: 6347b6b518d041578c8e8f129d117ca7 671 | - name: ele_read 672 | speaker: 9884b6b6b36f45cab2148c1898c5efe2 673 | - name: xm_01 674 | speaker: 59dc8a8f47ac4ce5b30dfe7806be45df 675 | - name: 抖音米敏 676 | speaker: 21b7fbb0ad90465a99f4242ed60e7ff5 677 | - name: 自己 678 | speaker: 04d3690408d8494aa725c934d72a9b23 679 | - name: 测试单田芳 680 | speaker: 73dc07bbe97246fc923b9d2dd56ced91 681 | - name: "y" 682 | speaker: 1b3c2a8a7ac54c0ab9f22e5f9ecc0359 683 | - name: "1" 684 | speaker: c4e06231ae7145dc940807c2c777b337 685 | - name: "1" 686 | speaker: 6fc86caf6828466383b9a1ce26d39cce 687 | - name: "6653" 688 | speaker: b68bf05cbc8241f3a78e71d963ddbc2d 689 | - name: 芙宁娜 690 | speaker: f6099a973efd4d50ac1bbd6a6112ba41 691 | - name: jackzou 692 | speaker: d5c68607a35347e48e1272368613c8fc 693 | - name: 北方老师的话 694 | speaker: f766b419ad684cce98e1727c4cc4cea5 695 | - name: 没有没有 696 | speaker: 85cf65cedc6c418b89195743d2cf9b39 697 | - name: 康辉 698 | speaker: 192ebdeebefd4e8cae0494ac3e31ce54 699 | - name: 董宇辉 700 | speaker: 9633bb33101c4f2fb536a85a5c53f9fc 701 | - name: 芽衣_雷之律者 702 | speaker: d09e46ea44a14443be19467ae25e13d5 703 | - name: 琪亚娜_薪炎之律者 704 | speaker: d36924addc4f4d83bacde7d97a9b4e5d 705 | - name: 梅比乌斯 706 | speaker: e8a46f17d2754dac9f11b1c1c91caa8d 707 | - name: 凯文 708 | speaker: 9ea7b809c5fe4446a5dba1ebf3489bb1 709 | - name: 夏提雅中文 710 | speaker: f47f43af6d434c29a194d9c5e59eb379 711 | - name: gmz 712 | speaker: 95499b87e58f4287b73645d77d542914 713 | - name: My Voice 2 714 | speaker: 7140eac89b2246bb856c0768f9d88a7b 715 | - name: My Voice 716 | speaker: ca062c9b029241de9b13340b0abbf806 717 | - name: rui 718 | speaker: f15783f70b99403abcb08a378eee564d 719 | - name: 测试 720 | speaker: c826c023cd11472399c502de0f0be42a 721 | - name: "2" 722 | speaker: 74f901d2b82a44d19f0f6482a38a7f52 723 | - name: "222" 724 | speaker: 0b855d21732048078290437c8b0843ea 725 | - name: "1111" 726 | speaker: 2495771526c84f51a60a5afc7795cd66 727 | - name: logos逻各斯 728 | speaker: dffc38b732724d74be023e999dad073f 729 | - name: 测试 730 | speaker: e2ba0fca9f8d4e5a83e724c830b1c62d 731 | - name: 嘟嘟_商超女播报员_CN 732 | speaker: 48811d47615144cd93af147735afdebd 733 | - name: xjp 734 | speaker: 186d352466834695b4305ddc15c25ea1 735 | - name: hjj 736 | speaker: a826835e9f4c4d45a6886943d97ad940 737 | - name: ll 738 | speaker: be5b47811fe64d69b0c0bb8068371dcb 739 | - name: Negan 740 | speaker: 68eb917bfbb3415ead3f5a9eb029fddf 741 | - name: 小孩ZH 742 | speaker: d283490327064d0c8af34b9131a8be31 743 | - name: 萌小天 744 | speaker: 0ed9a875b1934860a5c16b4b520cb6af 745 | - name: fisher_test1 746 | speaker: b337881546fa4355b2d87dee19392934 747 | - name: 莹凡的声音 748 | speaker: d4c437030a2a49d388ba8d90d81c87dd 749 | - name: 米诺 750 | speaker: b1f2af572875409581ea96954bdfcb08 751 | - name: hiyo 752 | speaker: 561a9b7181f54da7a85569d6d81ec1f7 753 | - name: 2024.01.27旅行骑士CPA1 754 | speaker: 82c4554a95e94ec8ab1d15468f86d22c 755 | - name: Y10REO 756 | speaker: 0b5ea41d86f5420c8d68d547fa3b10c8 757 | - name: 哈哈哈哈 758 | speaker: 8013307584f042b9b44cbec635d67338 759 | - name: 鱿鱼安静吗 760 | speaker: f3ed5cb2f28f4b31ba7ca6d5a257c55a 761 | - name: 小U 762 | speaker: 1af6fd526bbf49d4a7822db5066772dc 763 | - name: 居居 764 | speaker: 56fa56144e074a5caaad85ed3069d939 765 | - name: 居居 766 | speaker: 8e866b3784494dfea83f255537ece7c5 767 | - name: 袁腾飞 768 | speaker: 0df160c417974e469b0b298aab8d5aaa 769 | - name: 梁宏达 770 | speaker: 1c604c4e84224d0680ac3227c42decbf 771 | - name: 阿波尼亚 772 | speaker: 95d1f91e06f24d0ca6501bc4e150180c 773 | - name: 萝莎莉娅 774 | speaker: 2d4600689cf94de3921d7da3ebc248c3 775 | - name: 菲谢尔 776 | speaker: b4463f397c354d1f96a2fcfd031e775b 777 | - name: 莱尔 778 | speaker: 538cb64cd4b5491691ffcbcb0da03e1f 779 | - name: 莉莉娅 780 | speaker: 23b49994583f4fb5b6458d673ad471de 781 | - name: 苏莎娜 782 | speaker: d9f116166f2f48bfb9d95bdb768d3ba4 783 | - name: 苏 784 | speaker: 33f7afcacfef4a34905e8224ee1b385c 785 | - name: 芽衣 786 | speaker: 88a974ce5d654dd59bd047353b367692 787 | - name: 缇米朵 788 | speaker: 3c8b8b1de5974a0faea9528fd523ad20 789 | - name: 维尔薇 790 | speaker: be2216a55c68419aaa534d8573aa31e3 791 | - name: 符华_识之律者 792 | speaker: 47e1fe702dbc4d4d9b350cd3ecca530d 793 | - name: 符华_云墨丹心 794 | speaker: 88a46d353adb4ef092e3df206a51d45e 795 | - name: 符华 796 | speaker: 2a63e2c89c1f4368b85a2517569c1b8b 797 | - name: 科斯魔 798 | speaker: 2c28efaa097742908c7e9c6476ab3a44 799 | - name: 琪亚娜_空之律者 800 | speaker: 19553e11d45046d5ba09e232143e6549 801 | - name: 琪亚娜 802 | speaker: 4bcb034b8ab44b568cbc9afdb558338f 803 | - name: 狂热蓝调 804 | speaker: 6e9b612a12474f99a84788e4c68f9ecc 805 | - name: 爱衣 806 | speaker: 315184c2e35145179a5a5f8314780fcf 807 | - name: 渡鸦 808 | speaker: 130a4f7833e548ffa314d3890a02e64a 809 | - name: 格蕾修 810 | speaker: f0b8a39d2060493db2ca9401fd9503b9 811 | - name: 德丽莎_月下初拥 812 | speaker: 4f5424c4a54e4c60861c04191571fb8c 813 | - name: 德丽莎 814 | speaker: 5cc66a9fd0054ab9a7acc0ffa37aeabb 815 | - name: 帕朵菲莉丝 816 | speaker: 0bd304f344a041e9a84d3ff228d82106 817 | - name: 希儿_黑 818 | speaker: 5b4c030f044b41949ccfe219820befc0 819 | - name: 希儿 820 | speaker: db9193283daf43a6bc45bffc0b30bec7 821 | - name: 布洛妮娅_迷城骇兔 822 | speaker: ff1b22f407ee4ca7b21c773e564b0854 823 | - name: 布洛妮娅_次生银翼 824 | speaker: 102bbcad98b0447a8c7047d2c0fee9ab 825 | - name: 布洛妮娅 826 | speaker: e6b1769aeeb045fab1a0f6991fb14487 827 | - name: 姬子_极地战刃 828 | speaker: f57d87b1715d4334829e5ad36fb3f426 829 | - name: 姬子 830 | speaker: 37cada6c3a084d349e491232a3f6cbcd 831 | - name: 卡萝尔 832 | speaker: dab2d332242c43f3ac0c7d2836c447e4 833 | - name: 卡莲 834 | speaker: 43a663a38ea54971a08b5e238b2d4d56 835 | - name: 千劫 836 | speaker: ae433b9296a44863aa91b8dfda1f742c 837 | - name: 八重樱 838 | speaker: 507248329802416cb183d0b74be182bf 839 | - name: 伊甸 840 | speaker: c84538b13c994e8ea9191e9ba121b27d 841 | - name: 人偶_赤鸢 842 | speaker: 322bfc10896f479da2ae0fc2d2bf54e0 843 | - name: 人偶_贝拉 844 | speaker: 5a551f6c650c4abf89ed02347e535611 845 | - name: 人偶_西琳 846 | speaker: 03f77830ac854cbb964f307e0ac8c630 847 | - name: 人偶_若水 848 | speaker: 0783b3eb162e42d49099609f339e3a17 849 | - name: 人偶_苍玄 850 | speaker: 506ebbbefd79407d900b009b3caa7b52 851 | - name: 人偶_绯玉丸 852 | speaker: c9243e47984147be85ca29038ac0fdf6 853 | - name: 人偶_特斯拉Zero 854 | speaker: f2171dbd872243b99a5ff20d5cf47257 855 | - name: 人偶_爱酱 856 | speaker: f8522454927f4ce0a983a73db2b430c9 857 | - name: 人偶_晓月镇魂歌 858 | speaker: a1d67ee6c8e34234a1bdecd0bdc32207 859 | - name: 人偶_妖精爱莉 860 | speaker: 6c0499dfcfff45f190630e693897775e 861 | - name: 人偶_圣剑幽兰黛尔 862 | speaker: 0cc5203569af430e813d6035930a6ea2 863 | - name: 人偶_克莱因 864 | speaker: f325bac8e2e145778708ee8b3ef3ffbf 865 | - name: 人偶_仿犹大 866 | speaker: 0d8365076ab54389bf350293b9a13587 867 | - name: 丽塔 868 | speaker: f5f5e9513d0e44438cd78658d3e1033a 869 | - name: 主角 870 | speaker: 092bb020d493438abacf77910dcdce93 871 | - name: June (The Finals) 872 | speaker: e4c2507812b04187a421d9e79d375ad3 873 | - name: 绫波丽 874 | speaker: f5909c9180e54ea89877cf2f2d2e45c9 875 | - name: 特蕾西娅 876 | speaker: 853e2e66777446dd94d068439910230b 877 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | if (!global.segment) { 4 | global.segment = (await import("oicq")).segment; 5 | } 6 | 7 | let ret = []; 8 | 9 | logger.info(logger.yellow("[sf插件] 正在载入 siliconflow-PLUGIN")); 10 | 11 | const files = fs 12 | .readdirSync('./plugins/siliconflow-plugin/apps') 13 | .filter((file) => file.endsWith('.js')); 14 | 15 | files.forEach((file) => { 16 | ret.push(import(`./apps/${file}`)) 17 | }) 18 | 19 | ret = await Promise.allSettled(ret); 20 | 21 | let apps = {}; 22 | for (let i in files) { 23 | let name = files[i].replace('.js', ''); 24 | 25 | if (ret[i].status !== 'fulfilled') { 26 | logger.error(`[sf插件] 载入插件错误:${logger.red(name)}`); 27 | logger.error(ret[i].reason); 28 | continue; 29 | } 30 | apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]; 31 | } 32 | 33 | logger.info(logger.green("[sf插件] siliconflow-PLUGIN 载入成功")); 34 | 35 | export { apps }; -------------------------------------------------------------------------------- /model/init.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import Config from '../components/Config.js' 3 | import { pluginRoot } from '../model/path.js' 4 | import { 5 | readYaml, 6 | writeYaml, 7 | } from '../utils/common.js' 8 | 9 | class Init { 10 | constructor() { 11 | this.initConfig() 12 | } 13 | 14 | initConfig() { 15 | const config_default_path = `${pluginRoot}/config/config_default.yaml` 16 | if (!fs.existsSync(config_default_path)) { 17 | logger.error('默认设置文件不存在,请检查或重新安装插件') 18 | return true 19 | } 20 | const config_path = `${pluginRoot}/config/config/config.yaml` 21 | if (!fs.existsSync(config_path)) { 22 | logger.error('设置文件不存在,将使用默认设置文件') 23 | fs.copyFileSync(config_default_path, config_path) 24 | } 25 | const config_default_yaml = Config.getDefConfig() 26 | const config_yaml = Config.getConfig() 27 | for (const key in config_default_yaml) { 28 | if (!(key in config_yaml)) { 29 | config_yaml[key] = config_default_yaml[key] 30 | } 31 | } 32 | for (const key in config_yaml) { 33 | if (!(key in config_default_yaml)) { 34 | delete config_yaml[key] 35 | } 36 | } 37 | Config.setConfig(config_yaml) 38 | 39 | // fishAudio_default 40 | const fishAudio_default_path = `${pluginRoot}/config/fishAudio_default.yaml` 41 | if (!fs.existsSync(fishAudio_default_path)) { 42 | logger.mark('默认设置文件fishAudio不存在,请检查或重新安装插件') 43 | return true 44 | } 45 | const config_path_fishAudio = `${pluginRoot}/config/config/fishAudio.yaml` 46 | if (!fs.existsSync(config_path_fishAudio)) { 47 | logger.mark('设置文件fishAudio不存在,将使用默认设置文件') 48 | fs.copyFileSync(fishAudio_default_path, config_path_fishAudio) 49 | } 50 | let fishAudio_default_yaml = readYaml(`${pluginRoot}/config/fishAudio_default.yaml`) 51 | let fishAudio_yaml = readYaml(`${pluginRoot}/config/config/fishAudio.yaml`) 52 | for (const key in fishAudio_default_yaml) { 53 | if (!(key in fishAudio_yaml)) { 54 | fishAudio_yaml[key] = fishAudio_default_yaml[key] 55 | } 56 | } 57 | for (const key in fishAudio_yaml) { 58 | if (!(key in fishAudio_default_yaml)) { 59 | delete fishAudio_yaml[key] 60 | } 61 | } 62 | writeYaml(`${pluginRoot}/config/config/fishAudio.yaml`, fishAudio_yaml) 63 | 64 | 65 | 66 | } 67 | } 68 | 69 | export default new Init() 70 | -------------------------------------------------------------------------------- /model/path.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const _path = process.cwd().replace(/\\/g, '/') 4 | 5 | // 插件名 6 | const pluginName = path.basename(path.join(import.meta.url, '../../')) 7 | // 插件根目录 8 | const pluginRoot = path.join(_path, 'plugins', pluginName) 9 | // 插件资源目录 10 | const pluginResources = path.join(pluginRoot, 'resources') 11 | 12 | export { _path, pluginName, pluginRoot, pluginResources } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siliconflow-plugin", 3 | "version": "1.0.2", 4 | "description": "Yunzai-Bot插件", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "type": "module", 10 | "module": "index.js", 11 | "dependencies": { 12 | "axios": "^1.6.2", 13 | "ws": "^8.18.0" 14 | }, 15 | "keywords": [ 16 | "Yunzai-Bot", 17 | "云崽" 18 | ], 19 | "author": "@Misaka20002", 20 | "license": "ISC" 21 | } -------------------------------------------------------------------------------- /resources/common/base.css: -------------------------------------------------------------------------------- 1 | .font-ys { 2 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 3 | } 4 | .font-nzbz { 5 | font-family: Number, "印品南征北战NZBZ体", NZBZ, PingFangSC-Medium, "PingFang SC", sans-serif; 6 | } 7 | /*# sourceMappingURL=base.css.map */ -------------------------------------------------------------------------------- /resources/common/base.less: -------------------------------------------------------------------------------- 1 | .font-YS { 2 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 3 | } 4 | 5 | .font-NZBZ { 6 | font-family: Number, "印品南征北战NZBZ体", NZBZ, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 7 | } -------------------------------------------------------------------------------- /resources/common/common.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Number'; 3 | src: url("./font/tttgbnumber.woff") format('woff'), url("./font/tttgbnumber.ttf") format('truetype'); 4 | } 5 | @font-face { 6 | font-family: 'NZBZ'; 7 | src: url("./font/NZBZ.woff") format('woff'), url("./font/NZBZ.ttf") format('truetype'); 8 | } 9 | @font-face { 10 | font-family: 'YS'; 11 | src: url("./font/HYWH-65W.woff") format('woff'), url("./font/HYWH-65W.ttf") format('truetype'); 12 | } 13 | .font-YS { 14 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 15 | } 16 | .font-NZBZ { 17 | font-family: Number, "印品南征北战NZBZ体", NZBZ, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 18 | } 19 | * { 20 | margin: 0; 21 | padding: 0; 22 | box-sizing: border-box; 23 | -webkit-user-select: none; 24 | user-select: none; 25 | } 26 | body { 27 | font-size: 18px; 28 | color: #1e1f20; 29 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 30 | transform: scale(1.4); 31 | transform-origin: 0 0; 32 | width: 600px; 33 | } 34 | .container { 35 | width: 600px; 36 | padding: 20px 15px 10px 15px; 37 | background-size: contain; 38 | } 39 | .head-box { 40 | border-radius: 15px; 41 | padding: 10px 20px; 42 | position: relative; 43 | color: #fff; 44 | margin-top: 30px; 45 | } 46 | .head-box .title { 47 | font-family: Number, "印品南征北战NZBZ体", NZBZ, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 48 | font-size: 36px; 49 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); 50 | } 51 | .head-box .title .label { 52 | display: inline-block; 53 | margin-left: 10px; 54 | } 55 | .head-box .genshin_logo { 56 | position: absolute; 57 | top: 1px; 58 | right: 15px; 59 | width: 97px; 60 | } 61 | .head-box .label { 62 | font-size: 16px; 63 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); 64 | } 65 | .head-box .label span { 66 | color: #d3bc8e; 67 | padding: 0 2px; 68 | } 69 | .notice { 70 | color: #888; 71 | font-size: 12px; 72 | text-align: right; 73 | padding: 12px 5px 5px; 74 | } 75 | .notice-center { 76 | color: #fff; 77 | text-align: center; 78 | margin-bottom: 10px; 79 | text-shadow: 1px 1px 1px #333; 80 | } 81 | .copyright { 82 | font-size: 14px; 83 | text-align: center; 84 | color: #fff; 85 | position: relative; 86 | padding-left: 10px; 87 | text-shadow: 1px 1px 1px #000; 88 | margin: 10px 0; 89 | } 90 | .copyright .version { 91 | color: #d3bc8e; 92 | display: inline-block; 93 | padding: 0 3px; 94 | } 95 | /* */ 96 | .cons { 97 | display: inline-block; 98 | vertical-align: middle; 99 | padding: 0 5px; 100 | border-radius: 4px; 101 | } 102 | .cons-0 { 103 | background: #666; 104 | color: #fff; 105 | } 106 | .cons-n0 { 107 | background: #404949; 108 | color: #fff; 109 | } 110 | .cons-1 { 111 | background: #5cbac2; 112 | color: #fff; 113 | } 114 | .cons-2 { 115 | background: #339d61; 116 | color: #fff; 117 | } 118 | .cons-3 { 119 | background: #3e95b9; 120 | color: #fff; 121 | } 122 | .cons-4 { 123 | background: #3955b7; 124 | color: #fff; 125 | } 126 | .cons-5 { 127 | background: #531ba9cf; 128 | color: #fff; 129 | } 130 | .cons-6 { 131 | background: #ff5722; 132 | color: #fff; 133 | } 134 | .cons2-0 { 135 | border-radius: 4px; 136 | background: #666; 137 | color: #fff; 138 | } 139 | .cons2-1 { 140 | border-radius: 4px; 141 | background: #71b1b7; 142 | color: #fff; 143 | } 144 | .cons2-2 { 145 | border-radius: 4px; 146 | background: #369961; 147 | color: #fff; 148 | } 149 | .cons2-3 { 150 | border-radius: 4px; 151 | background: #4596b9; 152 | color: #fff; 153 | } 154 | .cons2-4 { 155 | border-radius: 4px; 156 | background: #4560b9; 157 | color: #fff; 158 | } 159 | .cons2-5 { 160 | border-radius: 4px; 161 | background: #531ba9cf; 162 | color: #fff; 163 | } 164 | .cons2-6 { 165 | border-radius: 4px; 166 | background: #ff5722; 167 | color: #fff; 168 | } 169 | /******** Fetter ********/ 170 | .fetter { 171 | width: 50px; 172 | height: 50px; 173 | display: inline-block; 174 | background: url('./item/fetter.png'); 175 | background-size: auto 100%; 176 | } 177 | .fetter.fetter1 { 178 | background-position: 0% 0; 179 | } 180 | .fetter.fetter2 { 181 | background-position: 11.11111111% 0; 182 | } 183 | .fetter.fetter3 { 184 | background-position: 22.22222222% 0; 185 | } 186 | .fetter.fetter4 { 187 | background-position: 33.33333333% 0; 188 | } 189 | .fetter.fetter5 { 190 | background-position: 44.44444444% 0; 191 | } 192 | .fetter.fetter6 { 193 | background-position: 55.55555556% 0; 194 | } 195 | .fetter.fetter7 { 196 | background-position: 66.66666667% 0; 197 | } 198 | .fetter.fetter8 { 199 | background-position: 77.77777778% 0; 200 | } 201 | .fetter.fetter9 { 202 | background-position: 88.88888889% 0; 203 | } 204 | .fetter.fetter10 { 205 | background-position: 100% 0; 206 | } 207 | /******** ELEM ********/ 208 | .elem-hydro .talent-icon { 209 | background-image: url("./bg/talent-hydro.png"); 210 | } 211 | .elem-hydro .elem-bg, 212 | .hydro-bg { 213 | background-image: url("./bg/bg-hydro.jpg"); 214 | } 215 | .elem-anemo .talent-icon { 216 | background-image: url("./bg/talent-anemo.png"); 217 | } 218 | .elem-anemo .elem-bg, 219 | .anemo-bg { 220 | background-image: url("./bg/bg-anemo.jpg"); 221 | } 222 | .elem-cryo .talent-icon { 223 | background-image: url("./bg/talent-cryo.png"); 224 | } 225 | .elem-cryo .elem-bg, 226 | .cryo-bg { 227 | background-image: url("./bg/bg-cryo.jpg"); 228 | } 229 | .elem-electro .talent-icon { 230 | background-image: url("./bg/talent-electro.png"); 231 | } 232 | .elem-electro .elem-bg, 233 | .electro-bg { 234 | background-image: url("./bg/bg-electro.jpg"); 235 | } 236 | .elem-geo .talent-icon { 237 | background-image: url("./bg/talent-geo.png"); 238 | } 239 | .elem-geo .elem-bg, 240 | .geo-bg { 241 | background-image: url("./bg/bg-geo.jpg"); 242 | } 243 | .elem-pyro .talent-icon { 244 | background-image: url("./bg/talent-pyro.png"); 245 | } 246 | .elem-pyro .elem-bg, 247 | .pyro-bg { 248 | background-image: url("./bg/bg-pyro.jpg"); 249 | } 250 | .elem-dendro .talent-icon { 251 | background-image: url("./bg/talent-dendro.png"); 252 | } 253 | .elem-dendro .elem-bg, 254 | .dendro-bg { 255 | background-image: url("./bg/bg-dendro.jpg"); 256 | } 257 | /* cont */ 258 | .cont { 259 | border-radius: 10px; 260 | background: url("../common/cont/card-bg.png") top left repeat-x; 261 | background-size: auto 100%; 262 | margin: 5px 15px 5px 10px; 263 | position: relative; 264 | box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, 0.8); 265 | overflow: hidden; 266 | color: #fff; 267 | font-size: 16px; 268 | } 269 | .cont-title { 270 | background: rgba(0, 0, 0, 0.4); 271 | box-shadow: 0 0 1px 0 #fff; 272 | color: #d3bc8e; 273 | padding: 10px 20px; 274 | text-align: left; 275 | border-radius: 10px 10px 0 0; 276 | } 277 | .cont-title span { 278 | font-size: 12px; 279 | color: #aaa; 280 | margin-left: 10px; 281 | font-weight: normal; 282 | } 283 | .cont-title.border-less { 284 | background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 285 | box-shadow: none; 286 | padding-bottom: 5px; 287 | } 288 | .cont-body { 289 | padding: 10px 15px; 290 | font-size: 12px; 291 | background: rgba(0, 0, 0, 0.5); 292 | box-shadow: 0 0 1px 0 #fff; 293 | font-weight: normal; 294 | } 295 | .cont-footer { 296 | padding: 10px 15px; 297 | font-size: 12px; 298 | background: rgba(0, 0, 0, 0.5); 299 | font-weight: normal; 300 | } 301 | .cont > ul.cont-msg { 302 | display: block; 303 | padding: 5px 10px; 304 | background: rgba(0, 0, 0, 0.5); 305 | } 306 | ul.cont-msg, 307 | .cont-footer ul { 308 | padding-left: 15px; 309 | } 310 | ul.cont-msg li, 311 | .cont-footer ul li { 312 | margin: 5px 0; 313 | margin-left: 15px; 314 | } 315 | ul.cont-msg li strong, 316 | .cont-footer ul li strong { 317 | font-weight: normal; 318 | margin: 0 2px; 319 | color: #d3bc8e; 320 | } 321 | .cont-table { 322 | display: table; 323 | width: 100%; 324 | } 325 | .cont-table .tr { 326 | display: table-row; 327 | } 328 | .cont-table .tr:nth-child(even) { 329 | background: rgba(0, 0, 0, 0.4); 330 | } 331 | .cont-table .tr:nth-child(odd) { 332 | background: rgba(50, 50, 50, 0.4); 333 | } 334 | .cont-table .tr > div, 335 | .cont-table .tr > td { 336 | display: table-cell; 337 | box-shadow: 0 0 1px 0 #fff; 338 | } 339 | .cont-table .tr > div.value-full { 340 | display: table; 341 | width: 200%; 342 | } 343 | .cont-table .tr > div.value-none { 344 | box-shadow: none; 345 | } 346 | .cont-table .thead { 347 | text-align: center; 348 | } 349 | .cont-table .thead > div, 350 | .cont-table .thead > td { 351 | color: #d3bc8e; 352 | background: rgba(0, 0, 0, 0.4); 353 | line-height: 40px; 354 | height: 40px; 355 | } 356 | .cont-table .title, 357 | .cont-table .th { 358 | color: #d3bc8e; 359 | padding-right: 15px; 360 | text-align: right; 361 | background: rgba(0, 0, 0, 0.4); 362 | min-width: 100px; 363 | vertical-align: middle; 364 | } 365 | .logo { 366 | font-size: 18px; 367 | text-align: center; 368 | color: #fff; 369 | margin: 20px 0 10px 0; 370 | } 371 | /* item-icon */ 372 | .item-icon { 373 | width: 100%; 374 | height: 100%; 375 | border-radius: 4px; 376 | position: relative; 377 | overflow: hidden; 378 | } 379 | .item-icon .img { 380 | width: 100%; 381 | height: 100%; 382 | display: block; 383 | background-size: contain; 384 | background-position: center; 385 | background-repeat: no-repeat; 386 | } 387 | .item-icon.artis .img { 388 | width: 84%; 389 | height: 84%; 390 | margin: 8%; 391 | } 392 | .item-icon.star1 { 393 | background-image: url("../common/item/bg1.png"); 394 | } 395 | .item-icon.opacity-bg.star1 { 396 | background-image: url("../common/item/bg1-o.png"); 397 | } 398 | .item-icon.star2 { 399 | background-image: url("../common/item/bg2.png"); 400 | } 401 | .item-icon.opacity-bg.star2 { 402 | background-image: url("../common/item/bg2-o.png"); 403 | } 404 | .item-icon.star3 { 405 | background-image: url("../common/item/bg3.png"); 406 | } 407 | .item-icon.opacity-bg.star3 { 408 | background-image: url("../common/item/bg3-o.png"); 409 | } 410 | .item-icon.star4 { 411 | background-image: url("../common/item/bg4.png"); 412 | } 413 | .item-icon.opacity-bg.star4 { 414 | background-image: url("../common/item/bg4-o.png"); 415 | } 416 | .item-icon.star5 { 417 | background-image: url("../common/item/bg5.png"); 418 | } 419 | .item-icon.opacity-bg.star5 { 420 | background-image: url("../common/item/bg5-o.png"); 421 | } 422 | .item-icon.star-w { 423 | background: #fff; 424 | } 425 | .item-list { 426 | display: flex; 427 | } 428 | .item-list .item-card { 429 | width: 70px; 430 | background: #e7e5d9; 431 | } 432 | .item-list .item-icon { 433 | border-bottom-left-radius: 0; 434 | border-bottom-right-radius: 12px; 435 | } 436 | .item-list .item-title { 437 | color: #222; 438 | font-size: 13px; 439 | text-align: center; 440 | padding: 2px; 441 | white-space: nowrap; 442 | overflow: hidden; 443 | } 444 | .item-list .item-icon { 445 | height: initial; 446 | } 447 | .item-list .item-badge { 448 | position: absolute; 449 | display: block; 450 | left: 0; 451 | top: 0; 452 | background: rgba(0, 0, 0, 0.6); 453 | font-size: 12px; 454 | color: #fff; 455 | padding: 4px 5px 3px; 456 | border-radius: 0 0 6px 0; 457 | } 458 | /*# sourceMappingURL=common.css.map */ -------------------------------------------------------------------------------- /resources/common/common.less: -------------------------------------------------------------------------------- 1 | .font(@name, @file) { 2 | @font-face { 3 | font-family: @name; 4 | src: url("./font/@{file}.woff") format('woff'), url("./font/@{file}.ttf") format('truetype'); 5 | } 6 | } 7 | 8 | .font('Number', 'tttgbnumber'); 9 | .font('NZBZ', 'NZBZ'); 10 | .font('YS', 'HYWH-65W'); 11 | 12 | @import "base.less"; 13 | 14 | * { 15 | margin: 0; 16 | padding: 0; 17 | box-sizing: border-box; 18 | -webkit-user-select: none; 19 | user-select: none; 20 | } 21 | 22 | body { 23 | font-size: 18px; 24 | color: #1e1f20; 25 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 26 | transform: scale(1.4); 27 | transform-origin: 0 0; 28 | width: 600px; 29 | } 30 | 31 | .container { 32 | width: 600px; 33 | padding: 20px 15px 10px 15px; 34 | background-size: contain; 35 | } 36 | 37 | 38 | .head-box { 39 | border-radius: 15px; 40 | padding: 10px 20px; 41 | position: relative; 42 | color: #fff; 43 | margin-top: 30px; 44 | 45 | .title { 46 | .font-NZBZ; 47 | font-size: 36px; 48 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .9); 49 | 50 | .label { 51 | display: inline-block; 52 | margin-left: 10px; 53 | } 54 | } 55 | 56 | .genshin_logo { 57 | position: absolute; 58 | top: 1px; 59 | right: 15px; 60 | width: 97px; 61 | } 62 | 63 | .label { 64 | font-size: 16px; 65 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .9); 66 | 67 | span { 68 | color: #d3bc8e; 69 | padding: 0 2px; 70 | } 71 | } 72 | } 73 | 74 | 75 | .notice { 76 | color: #888; 77 | font-size: 12px; 78 | text-align: right; 79 | padding: 12px 5px 5px; 80 | } 81 | 82 | .notice-center { 83 | color: #fff; 84 | text-align: center; 85 | margin-bottom: 10px; 86 | text-shadow: 1px 1px 1px #333; 87 | } 88 | 89 | .copyright { 90 | font-size: 14px; 91 | text-align: center; 92 | color: #fff; 93 | position: relative; 94 | padding-left: 10px; 95 | text-shadow: 1px 1px 1px #000; 96 | margin: 10px 0; 97 | 98 | .version { 99 | color: #d3bc8e; 100 | display: inline-block; 101 | padding: 0 3px; 102 | } 103 | } 104 | 105 | 106 | /* */ 107 | 108 | .cons { 109 | display: inline-block; 110 | vertical-align: middle; 111 | padding: 0 5px; 112 | border-radius: 4px; 113 | } 114 | 115 | 116 | .cons(@idx, @bg, @color:#fff) { 117 | .cons-@{idx} { 118 | background: @bg; 119 | color: @color; 120 | } 121 | } 122 | 123 | .cons(0, #666); 124 | .cons(n0, #404949); 125 | .cons(1, #5cbac2); 126 | .cons(2, #339d61); 127 | .cons(3, #3e95b9); 128 | .cons(4, #3955b7); 129 | .cons(5, #531ba9cf); 130 | .cons(6, #ff5722); 131 | 132 | .cons2(@idx, @bg, @color:#fff) { 133 | .cons2-@{idx} { 134 | border-radius: 4px; 135 | background: @bg; 136 | color: @color; 137 | } 138 | } 139 | 140 | .cons2(0, #666); 141 | .cons2(1, #71b1b7); 142 | .cons2(2, #369961); 143 | .cons2(3, #4596b9); 144 | .cons2(4, #4560b9); 145 | .cons2(5, #531ba9cf); 146 | .cons2(6, #ff5722); 147 | 148 | /******** Fetter ********/ 149 | 150 | .fetter { 151 | width: 50px; 152 | height: 50px; 153 | display: inline-block; 154 | background: url('./item/fetter.png'); 155 | background-size: auto 100%; 156 | @fetters: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; 157 | each(@fetters, { 158 | &.fetter@{value} { 159 | background-position: (-100%/9)+(100%/9)*@value 0; 160 | } 161 | }) 162 | } 163 | 164 | /******** ELEM ********/ 165 | 166 | @elems: hydro, anemo, cryo, electro, geo, pyro, dendro; 167 | 168 | each(@elems, { 169 | .elem-@{value} .talent-icon { 170 | background-image: url("./bg/talent-@{value}.png"); 171 | } 172 | 173 | .elem-@{value} .elem-bg, 174 | .@{value}-bg { 175 | background-image: url("./bg/bg-@{value}.jpg"); 176 | } 177 | }) 178 | 179 | 180 | /* cont */ 181 | 182 | .cont { 183 | border-radius: 10px; 184 | background: url("../common/cont/card-bg.png") top left repeat-x; 185 | background-size: auto 100%; 186 | // backdrop-filter: blur(3px); 187 | margin: 5px 15px 5px 10px; 188 | position: relative; 189 | box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, .8); 190 | overflow: hidden; 191 | color: #fff; 192 | font-size: 16px; 193 | } 194 | 195 | 196 | .cont-title { 197 | background: rgba(0, 0, 0, .4); 198 | box-shadow: 0 0 1px 0 #fff; 199 | color: #d3bc8e; 200 | padding: 10px 20px; 201 | text-align: left; 202 | border-radius: 10px 10px 0 0; 203 | 204 | span { 205 | font-size: 12px; 206 | color: #aaa; 207 | margin-left: 10px; 208 | font-weight: normal; 209 | } 210 | 211 | &.border-less { 212 | background: linear-gradient(rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)); 213 | box-shadow: none; 214 | padding-bottom: 5px; 215 | } 216 | } 217 | 218 | .cont-body { 219 | padding: 10px 15px; 220 | font-size: 12px; 221 | background: rgba(0, 0, 0, 0.5); 222 | box-shadow: 0 0 1px 0 #fff; 223 | font-weight: normal; 224 | } 225 | 226 | 227 | .cont-footer { 228 | padding: 10px 15px; 229 | font-size: 12px; 230 | background: rgba(0, 0, 0, 0.5); 231 | font-weight: normal; 232 | } 233 | 234 | .cont > ul.cont-msg { 235 | display: block; 236 | padding: 5px 10px; 237 | background: rgba(0, 0, 0, 0.5); 238 | } 239 | 240 | ul.cont-msg, .cont-footer ul { 241 | padding-left: 15px; 242 | 243 | li { 244 | margin: 5px 0; 245 | margin-left: 15px; 246 | 247 | strong { 248 | font-weight: normal; 249 | margin: 0 2px; 250 | color: #d3bc8e; 251 | } 252 | } 253 | } 254 | 255 | .cont-table { 256 | display: table; 257 | width: 100%; 258 | } 259 | 260 | .cont-table .tr { 261 | display: table-row; 262 | } 263 | 264 | .cont-table .tr:nth-child(even) { 265 | background: rgba(0, 0, 0, .4); 266 | } 267 | 268 | .cont-table .tr:nth-child(odd) { 269 | background: rgba(50, 50, 50, .4); 270 | } 271 | 272 | .cont-table .tr > div, 273 | .cont-table .tr > td { 274 | display: table-cell; 275 | box-shadow: 0 0 1px 0 #fff; 276 | } 277 | 278 | .cont-table .tr > div.value-full { 279 | display: table; 280 | width: 200%; 281 | } 282 | 283 | .cont-table .tr > div.value-none { 284 | box-shadow: none; 285 | } 286 | 287 | .cont-table .thead { 288 | text-align: center; 289 | } 290 | 291 | .cont-table .thead > div, 292 | .cont-table .thead > td { 293 | color: #d3bc8e; 294 | background: rgba(0, 0, 0, .4); 295 | line-height: 40px; 296 | height: 40px; 297 | } 298 | 299 | 300 | .cont-table .title, 301 | .cont-table .th { 302 | color: #d3bc8e; 303 | padding-right: 15px; 304 | text-align: right; 305 | background: rgba(0, 0, 0, .4); 306 | min-width: 100px; 307 | vertical-align: middle; 308 | } 309 | 310 | .logo { 311 | font-size: 18px; 312 | text-align: center; 313 | color: #fff; 314 | margin: 20px 0 10px 0; 315 | } 316 | 317 | /* item-icon */ 318 | .item-icon { 319 | width: 100%; 320 | height: 100%; 321 | border-radius: 4px; 322 | position: relative; 323 | overflow: hidden; 324 | 325 | .img { 326 | width: 100%; 327 | height: 100%; 328 | display: block; 329 | background-size: contain; 330 | background-position: center; 331 | background-repeat: no-repeat; 332 | } 333 | 334 | &.artis { 335 | .img { 336 | width: 84%; 337 | height: 84%; 338 | margin: 8%; 339 | } 340 | } 341 | 342 | @stars: 1, 2, 3, 4, 5; 343 | each(@stars, { 344 | &.star@{value} { 345 | background-image: url("../common/item/bg@{value}.png"); 346 | } 347 | &.opacity-bg.star@{value} { 348 | background-image: url("../common/item/bg@{value}-o.png"); 349 | } 350 | }) 351 | 352 | &.star-w { 353 | background: #fff; 354 | } 355 | } 356 | 357 | .item-list { 358 | display: flex; 359 | 360 | .item-card { 361 | width: 70px; 362 | background: #e7e5d9; 363 | } 364 | 365 | .item-icon { 366 | border-bottom-left-radius: 0; 367 | border-bottom-right-radius: 12px; 368 | } 369 | 370 | .item-title { 371 | color: #222; 372 | font-size: 13px; 373 | text-align: center; 374 | padding: 2px; 375 | white-space: nowrap; 376 | overflow: hidden; 377 | } 378 | 379 | .item-icon { 380 | height: initial; 381 | } 382 | 383 | .item-badge { 384 | position: absolute; 385 | display: block; 386 | left: 0; 387 | top: 0; 388 | background: rgba(0, 0, 0, 0.6); 389 | font-size: 12px; 390 | color: #fff; 391 | padding: 4px 5px 3px; 392 | border-radius: 0 0 6px 0; 393 | } 394 | } -------------------------------------------------------------------------------- /resources/common/font/HYWH-65W.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/HYWH-65W.ttf -------------------------------------------------------------------------------- /resources/common/font/HYWH-65W.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/HYWH-65W.woff -------------------------------------------------------------------------------- /resources/common/font/NZBZ.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/NZBZ.ttf -------------------------------------------------------------------------------- /resources/common/font/NZBZ.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/NZBZ.woff -------------------------------------------------------------------------------- /resources/common/font/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /resources/common/font/tttgbnumber.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/tttgbnumber.ttf -------------------------------------------------------------------------------- /resources/common/font/tttgbnumber.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/tttgbnumber.woff -------------------------------------------------------------------------------- /resources/common/font/华文中宋.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/common/font/华文中宋.TTF -------------------------------------------------------------------------------- /resources/common/layout/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | miao-plugin 12 | {{block 'css'}} 13 | {{/block}} 14 | 15 | 16 |
17 | {{block 'main'}}{{/block}} 18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /resources/common/layout/elem.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | mc-plugin 12 | {{block 'css'}} 13 | {{/block}} 14 | 15 | 16 |
17 | {{block 'main'}}{{/block}} 18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /resources/help/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/help/icon.png -------------------------------------------------------------------------------- /resources/help/imgs/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/help/imgs/bg.jpg -------------------------------------------------------------------------------- /resources/help/imgs/config.js: -------------------------------------------------------------------------------- 1 | export const style = { 2 | // 主文字颜色 3 | fontColor: '#ceb78b', 4 | // 主文字阴影: 横向距离 垂直距离 阴影大小 阴影颜色 5 | // fontShadow: '0px 0px 1px rgba(6, 21, 31, .9)', 6 | fontShadow: 'none', 7 | // 描述文字颜色 8 | descColor: '#eee', 9 | 10 | /* 面板整体底色,会叠加在标题栏及帮助行之下,方便整体帮助有一个基础底色 11 | * 若无需此项可将rgba最后一位置为0即为完全透明 12 | * 注意若综合透明度较低,或颜色与主文字颜色过近或太透明可能导致阅读困难 */ 13 | contBgColor: 'rgba(6, 21, 31, .5)', 14 | 15 | // 面板底图毛玻璃效果,数字越大越模糊,0-10 ,可为小数 16 | contBgBlur: 3, 17 | 18 | // 板块标题栏底色 19 | headerBgColor: 'rgba(6, 21, 31, .4)', 20 | // 帮助奇数行底色 21 | rowBgColor1: 'rgba(6, 21, 31, .2)', 22 | // 帮助偶数行底色 23 | rowBgColor2: 'rgba(6, 21, 31, .35)' 24 | } 25 | -------------------------------------------------------------------------------- /resources/help/imgs/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/help/imgs/main.png -------------------------------------------------------------------------------- /resources/help/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | transform: scale(1); 3 | width: 830px; 4 | background: url("../common/theme/bg-01.jpg"); 5 | } 6 | .container { 7 | background: url(../common/theme/main-01.png) top left no-repeat; 8 | background-size: 100% auto; 9 | width: 830px; 10 | } 11 | .head-box { 12 | margin: 60px 0 0 0; 13 | padding-bottom: 0; 14 | } 15 | .head-box .title { 16 | font-size: 50px; 17 | } 18 | .cont-box { 19 | border-radius: 15px; 20 | margin-top: 20px; 21 | margin-bottom: 20px; 22 | overflow: hidden; 23 | box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15); 24 | position: relative; 25 | } 26 | .help-group { 27 | font-size: 18px; 28 | font-weight: bold; 29 | padding: 15px 15px 10px 20px; 30 | } 31 | .help-table { 32 | text-align: center; 33 | border-collapse: collapse; 34 | margin: 0; 35 | border-radius: 0 0 10px 10px; 36 | display: table; 37 | overflow: hidden; 38 | width: 100%; 39 | color: #fff; 40 | } 41 | .help-table .tr { 42 | display: table-row; 43 | } 44 | .help-table .td, 45 | .help-table .th { 46 | font-size: 14px; 47 | display: table-cell; 48 | box-shadow: 0 0 1px 0 #888 inset; 49 | padding: 12px 0 12px 50px; 50 | line-height: 24px; 51 | position: relative; 52 | text-align: left; 53 | } 54 | .help-table .tr:last-child .td { 55 | padding-bottom: 12px; 56 | } 57 | .help-table .th { 58 | background: rgba(34, 41, 51, 0.5); 59 | } 60 | .help-icon { 61 | width: 40px; 62 | height: 40px; 63 | display: block; 64 | position: absolute; 65 | background: url("icon.png") 0 0 no-repeat; 66 | background-size: 500px auto; 67 | border-radius: 5px; 68 | left: 6px; 69 | top: 12px; 70 | transform: scale(0.85); 71 | } 72 | .help-title { 73 | display: block; 74 | color: #d3bc8e; 75 | font-size: 16px; 76 | line-height: 24px; 77 | } 78 | .help-desc { 79 | display: block; 80 | font-size: 13px; 81 | line-height: 18px; 82 | } 83 | /*# sourceMappingURL=index.css.map */ -------------------------------------------------------------------------------- /resources/help/index.html: -------------------------------------------------------------------------------- 1 | {{extend defaultLayout}} 2 | 3 | {{block 'css'}} 4 | 5 | <% style = style.replace(/{{_res_path}}/g, _res_path) %> 6 | {{@style}} 7 | {{/block}} 8 | 9 | {{block 'main'}} 10 | 11 |
12 |
13 |
{{helpCfg.title||"使用帮助"}}
14 |
{{helpCfg.subTitle || "Yunzai-Bot & Miao-Plugin"}}
15 |
16 |
17 | 18 | {{each helpGroup group}} 19 | {{set len = group?.list?.length || 0 }} 20 |
21 |
{{group.group}}
22 | {{if len > 0}} 23 |
24 |
25 | {{each group.list help idx}} 26 |
27 | 28 | {{help.title}} 29 | {{help.desc}} 30 |
31 | {{if idx%colCount === colCount-1 && idx>0 && idx< len-1}} 32 |
33 |
34 | {{/if}} 35 | {{/each}} 36 | <% for(let i=(len-1)%colCount; i< colCount-1 ; i++){ %> 37 |
38 | <% } %> 39 |
40 |
41 | {{/if}} 42 |
43 | {{/each}} 44 | {{/block}} -------------------------------------------------------------------------------- /resources/help/index.less: -------------------------------------------------------------------------------- 1 | body { 2 | transform: scale(1); 3 | width: 830px; 4 | background: url("../common/theme/bg-01.jpg"); 5 | } 6 | 7 | .container { 8 | background: url(../common/theme/main-01.png) top left no-repeat; 9 | background-size: 100% auto; 10 | width: 830px; 11 | } 12 | 13 | .head-box { 14 | margin: 60px 0 0 0; 15 | padding-bottom: 0; 16 | } 17 | 18 | .head-box .title { 19 | font-size: 50px; 20 | } 21 | 22 | .cont-box { 23 | border-radius: 15px; 24 | margin-top: 20px; 25 | margin-bottom: 20px; 26 | overflow: hidden; 27 | box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); 28 | position: relative; 29 | } 30 | 31 | .help-group { 32 | font-size: 18px; 33 | font-weight: bold; 34 | padding: 15px 15px 10px 20px; 35 | } 36 | 37 | .help-table { 38 | text-align: center; 39 | border-collapse: collapse; 40 | margin: 0; 41 | border-radius: 0 0 10px 10px; 42 | display: table; 43 | overflow: hidden; 44 | width: 100%; 45 | color: #fff; 46 | } 47 | 48 | .help-table .tr { 49 | display: table-row; 50 | } 51 | 52 | .help-table .td, 53 | .help-table .th { 54 | font-size: 14px; 55 | display: table-cell; 56 | box-shadow: 0 0 1px 0 #888 inset; 57 | padding: 12px 0 12px 50px; 58 | line-height: 24px; 59 | position: relative; 60 | text-align: left; 61 | } 62 | 63 | .help-table .tr:last-child .td { 64 | padding-bottom: 12px; 65 | } 66 | 67 | .help-table .th { 68 | background: rgba(34, 41, 51, .5) 69 | } 70 | 71 | .help-icon { 72 | width: 40px; 73 | height: 40px; 74 | display: block; 75 | position: absolute; 76 | background: url("icon.png") 0 0 no-repeat; 77 | background-size: 500px auto; 78 | border-radius: 5px; 79 | left: 6px; 80 | top: 12px; 81 | transform: scale(0.85); 82 | } 83 | 84 | .help-title { 85 | display: block; 86 | color: #d3bc8e; 87 | font-size: 16px; 88 | line-height: 24px; 89 | } 90 | 91 | .help-desc { 92 | display: block; 93 | font-size: 13px; 94 | line-height: 18px; 95 | } 96 | 97 | -------------------------------------------------------------------------------- /resources/help/version-info.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | user-select: none; 6 | } 7 | body { 8 | font-size: 18px; 9 | color: #1e1f20; 10 | transform: scale(1.3); 11 | transform-origin: 0 0; 12 | width: 600px; 13 | } 14 | .container { 15 | width: 600px; 16 | padding: 10px 0 10px 0; 17 | background-size: 100% 100%; 18 | } 19 | .log-cont { 20 | background-size: cover; 21 | margin: 5px 15px 5px 10px; 22 | border-radius: 10px; 23 | } 24 | .log-cont .cont { 25 | margin: 0; 26 | } 27 | .log-cont .cont-title { 28 | font-size: 16px; 29 | padding: 10px 20px 6px; 30 | } 31 | .log-cont .cont-title.current-version { 32 | font-size: 20px; 33 | } 34 | .log-cont ul { 35 | font-size: 14px; 36 | padding-left: 20px; 37 | } 38 | .log-cont ul li { 39 | margin: 3px 0; 40 | } 41 | .log-cont ul.sub-log-ul li { 42 | margin: 1px 0; 43 | } 44 | .log-cont .cmd { 45 | color: #d3bc8e; 46 | display: inline-block; 47 | border-radius: 3px; 48 | background: rgba(0, 0, 0, 0.5); 49 | padding: 0 3px; 50 | margin: 1px 2px; 51 | } 52 | .log-cont .strong { 53 | color: #24d5cd; 54 | } 55 | .log-cont .new { 56 | display: inline-block; 57 | width: 18px; 58 | margin: 0 -3px 0 1px; 59 | } 60 | .log-cont .new:before { 61 | content: "NEW"; 62 | display: inline-block; 63 | transform: scale(0.6); 64 | transform-origin: 0 0; 65 | color: #d3bc8e; 66 | white-space: nowrap; 67 | } 68 | .dev-cont { 69 | background: none; 70 | } 71 | .dev-cont .cont-title { 72 | background: rgba(0, 0, 0, 0.7); 73 | } 74 | .dev-cont .cont-body { 75 | background: rgba(0, 0, 0, 0.5); 76 | } 77 | .dev-cont .cont-body.dev-info { 78 | background: rgba(0, 0, 0, 0.2); 79 | } 80 | .dev-cont .strong { 81 | font-size: 15px; 82 | } 83 | /*# sourceMappingURL=version-info.css.map */ -------------------------------------------------------------------------------- /resources/help/version-info.html: -------------------------------------------------------------------------------- 1 | {{extend elemLayout}} 2 | 3 | {{block 'css'}} 4 | 5 | {{/block}} 6 | 7 | {{block 'main'}} 8 | {{each changelogs ds idx}} 9 |
10 | {{set v = ds.version }} 11 | {{set isDev = v[v.length-1] === 'v'}} 12 |
13 | {{if idx === 0 }} 14 |
当前版本 {{v}}
15 | {{else}} 16 |
{{name || 'siliconflow-plugin'}}版本 {{v}}
17 | {{/if}} 18 |
19 |
    20 | {{each ds.logs log}} 21 |
  • 22 |

    {{@log.title}}

    23 | {{if log.logs.length > 0}} 24 |
      25 | {{each log.logs ls}} 26 |
    • {{@ls}}
    • 27 | {{/each}} 28 |
    29 | {{/if}} 30 |
  • 31 | {{/each}} 32 |
33 |
34 |
35 |
36 | {{/each}} 37 | {{/block}} -------------------------------------------------------------------------------- /resources/help/version-info.less: -------------------------------------------------------------------------------- 1 | .linear-bg(@color) { 2 | background-image: linear-gradient(to right, @color, @color 80%, fade(@color, 0) 100%); 3 | } 4 | 5 | * { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | user-select: none; 10 | } 11 | 12 | body { 13 | font-size: 18px; 14 | color: #1e1f20; 15 | transform: scale(1.3); 16 | transform-origin: 0 0; 17 | width: 600px; 18 | } 19 | 20 | .container { 21 | width: 600px; 22 | padding: 10px 0 10px 0; 23 | background-size: 100% 100%; 24 | 25 | } 26 | 27 | .log-cont { 28 | background-size: cover; 29 | margin: 5px 15px 5px 10px; 30 | border-radius: 10px; 31 | 32 | .cont { 33 | margin: 0; 34 | } 35 | 36 | .cont-title { 37 | font-size: 16px; 38 | padding: 10px 20px 6px; 39 | 40 | &.current-version { 41 | font-size: 20px; 42 | } 43 | } 44 | 45 | .cont-body { 46 | } 47 | 48 | ul { 49 | font-size: 14px; 50 | padding-left: 20px; 51 | 52 | li { 53 | margin: 3px 0; 54 | } 55 | 56 | &.sub-log-ul { 57 | li { 58 | margin: 1px 0; 59 | } 60 | } 61 | } 62 | 63 | .cmd { 64 | color: #d3bc8e; 65 | display: inline-block; 66 | border-radius: 3px; 67 | background: rgba(0, 0, 0, 0.5); 68 | padding: 0 3px; 69 | margin: 1px 2px; 70 | } 71 | 72 | .strong { 73 | color: #24d5cd; 74 | } 75 | 76 | .new { 77 | display: inline-block; 78 | width: 18px; 79 | margin: 0 -3px 0 1px; 80 | } 81 | 82 | .new:before { 83 | content: "NEW"; 84 | display: inline-block; 85 | transform: scale(0.6); 86 | transform-origin: 0 0; 87 | color: #d3bc8e; 88 | white-space: nowrap; 89 | } 90 | } 91 | 92 | .dev-cont { 93 | background: none; 94 | 95 | .cont-title { 96 | background: rgba(0, 0, 0, .7); 97 | } 98 | 99 | .cont-body { 100 | background: rgba(0, 0, 0, .5); 101 | 102 | &.dev-info { 103 | background: rgba(0, 0, 0, .2); 104 | } 105 | } 106 | 107 | .strong { 108 | font-size: 15px; 109 | } 110 | } -------------------------------------------------------------------------------- /resources/markdownPic/github.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Skipped minification because the original files appears to be already minified. 3 | * Original file: /npm/highlight.js@11.8.0/styles/github.css 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! 8 | Theme: GitHub 9 | Description: Light theme as seen on github.com 10 | Author: github.com 11 | Maintainer: @Hirse 12 | Updated: 2021-05-15 13 | 14 | Outdated base version: https://github.com/primer/github-syntax-light 15 | Current colors taken from GitHub's CSS 16 | */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} -------------------------------------------------------------------------------- /resources/markdownPic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 427 | 428 | 429 | 430 |
431 |
432 | 用户头像 433 |
434 |
{{userMsg}}
435 |
436 |
437 | 438 |
439 | 助手头像 440 |
441 |
{{content}}
442 |
443 |
444 | 445 | 可爱的女孩 446 |
Powered By SiliconFlow-Plugin
447 |
448 | 449 | 487 | 488 | 489 | 490 | -------------------------------------------------------------------------------- /resources/readme/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGC-Yunzai/siliconflow-plugin/d78c633d435c2c3a5756c19ff6e303c300e108b4/resources/readme/girl.png -------------------------------------------------------------------------------- /utils/common.js: -------------------------------------------------------------------------------- 1 | import yaml from 'yaml' 2 | import fs from 'fs' 3 | /** 读取YAML文件 */ 4 | export function readYaml(filePath) { 5 | return yaml.parse(fs.readFileSync(filePath, 'utf8')) 6 | } 7 | 8 | /** 写入YAML文件 */ 9 | export function writeYaml(filePath, data) { 10 | fs.writeFileSync(filePath, yaml.stringify(data), 'utf8') 11 | } 12 | 13 | /** 14 | * @description: 获取适配器Uin 15 | * @param {*} e 16 | * @return {*} 17 | */ 18 | export function getUin(e) { 19 | if (e?.self_id) return e.self_id 20 | if (e?.bot?.uin) return e.bot.uin 21 | if (Array.isArray(Bot.uin)) { 22 | Bot.uin.forEach((u) => { 23 | if (Bot[u].self_id) { 24 | return Bot[u].self_id 25 | } 26 | }) 27 | return Bot.uin[Bot.uin.length - 1] 28 | } else return Bot.uin 29 | } 30 | 31 | /** 32 | * @description: 获取Gemini可用的模型列表 33 | * @param {string} apiKey - Google AI API密钥 34 | * @param {string} geminiBaseUrl - Google AI API基础URL 35 | * @return {Promise} 返回可用模型的数组 36 | */ 37 | export async function getGeminiModelsByFetch(apiKey = '', geminiBaseUrl = '') { 38 | // 构建请求URL(考虑自定义baseUrl的情况) 39 | const baseUrl = geminiBaseUrl || 'https://generativelanguage.googleapis.com'; 40 | const endpoint = baseUrl.endsWith('/') ? 41 | `${baseUrl.slice(0, -1)}/v1beta/models` : 42 | `${baseUrl}/v1beta/models`; 43 | 44 | // 将API密钥作为URL参数 45 | const url = `${endpoint}?key=${apiKey}`; 46 | 47 | // 发送请求 48 | const response = await fetch(url, { 49 | method: 'GET', 50 | headers: { 51 | 'User-Agent': 'Node/1.0.0', 52 | 'Accept': '*/*' 53 | }, 54 | timeout: 60000 // 60秒超时 55 | }); 56 | 57 | if (!response.ok) { 58 | throw new Error(`获取Gemini模型API请求失败: ${response.status} ${response.statusText}`); 59 | } 60 | 61 | const data = await response.json(); 62 | 63 | logger.debug('获取Gemini模型列表响应:', JSON.stringify(data)); 64 | 65 | // Extract model names from the models array and return them 66 | return (data.models || []).map(model => model.name?.replace(/models\//g, '').trim()).filter(Boolean); 67 | } 68 | -------------------------------------------------------------------------------- /utils/context.js: -------------------------------------------------------------------------------- 1 | import Config from '../components/Config.js' 2 | 3 | /** 格式化上下文消息为Gemini API格式 */ 4 | export function formatContextForGemini(messages) { 5 | return messages.map(msg => { 6 | // 构造基本消息结构 7 | const formattedMsg = { 8 | // 将assistant转换为model,其他保持不变 9 | role: msg.role === 'assistant' ? 'model' : msg.role, 10 | parts: [] 11 | } 12 | 13 | // 添加文本内容 14 | if (typeof msg.content === 'object') { 15 | formattedMsg.parts.push({ text: msg.content.text }) 16 | } else { 17 | formattedMsg.parts.push({ text: msg.content }) 18 | } 19 | 20 | // 只从 imageBase64 字段获取图片 21 | if (msg.imageBase64 && Array.isArray(msg.imageBase64)) { 22 | msg.imageBase64.forEach(base64 => { 23 | formattedMsg.parts.push({ 24 | inline_data: { 25 | mime_type: "image/jpeg", 26 | data: base64 27 | } 28 | }) 29 | }) 30 | } 31 | 32 | return formattedMsg 33 | }) 34 | } 35 | 36 | /** 构造Redis key */ 37 | function buildRedisKey(userId, timestamp, promptNum = 0, type = '') { 38 | // 如果是默认配置(promptNum=0),使用share01后缀 39 | if (promptNum === 0) { 40 | return `sfplugin:llm:${userId}:${timestamp}:share01` 41 | } 42 | // 其他情况加入type前缀 43 | const typePrefix = type ? `${type}:` : '' 44 | return `sfplugin:llm:${userId}:${timestamp}:${typePrefix}promptNum${promptNum}` 45 | } 46 | 47 | /** 构造Redis key pattern用于搜索 */ 48 | function buildRedisPattern(userId, promptNum = 0, type = '') { 49 | // 如果是默认配置(promptNum=0),使用share01后缀 50 | if (promptNum === 0) { 51 | return `sfplugin:llm:${userId}:*:share01` 52 | } 53 | // 其他情况加入type前缀 54 | const typePrefix = type ? `${type}:` : '' 55 | return `sfplugin:llm:${userId}:*:${typePrefix}promptNum${promptNum}` 56 | } 57 | 58 | /** 保存对话上下文 */ 59 | export async function saveContext(userId, message, promptNum = 0, type = '') { 60 | try { 61 | const config = Config.getConfig() 62 | const maxHistory = config.gg_maxHistoryLength * 2 || 40 63 | 64 | // 使用新的key构造函数,传入type参数 65 | const key = buildRedisKey(userId, Date.now(), promptNum, type) 66 | logger.debug(`[Context] 保存${message.role === 'user' ? '用户' : 'AI'}消息`); 67 | 68 | // 如果是群聊多人对话模式,添加用户信息 69 | if (config.groupMultiChat && message.role === 'user') { 70 | const timestamp = new Date().toLocaleString('zh-CN', { 71 | year: 'numeric', 72 | month: '2-digit', 73 | day: '2-digit', 74 | hour: '2-digit', 75 | minute: '2-digit', 76 | second: '2-digit', 77 | hour12: false 78 | }); 79 | 80 | // 使用传入的sender信息 81 | const userName = message.sender || '未知用户'; 82 | 83 | // 在消息内容前添加用户信息和时间 84 | if (typeof message.content === 'string') { 85 | message.content = `[${timestamp}] ${userName}:${message.content}`; 86 | } else if (typeof message.content === 'object') { 87 | // 如果是对象,检查是否有text属性 88 | if (message.content.text) { 89 | message.content.text = `[${timestamp}] ${userName}:${message.content.text}`; 90 | } else { 91 | // 如果没有text属性,将整个对象转换为字符串 92 | message.content = `[${timestamp}] ${userName}:${JSON.stringify(message.content)}`; 93 | } 94 | } 95 | } 96 | 97 | // 构造要保存的消息对象,只包含必要的信息 98 | const messageToSave = { 99 | role: message.role, 100 | content: message.content, 101 | imageBase64: message.imageBase64 102 | }; 103 | 104 | // 记录要保存的消息内容 105 | const content = typeof messageToSave.content === 'string' 106 | ? messageToSave.content 107 | : (messageToSave.content?.text || JSON.stringify(messageToSave.content)); 108 | logger.debug(`[Context] ${content}`); 109 | if (messageToSave.imageBase64) { 110 | logger.debug(`[Context] 包含${messageToSave.imageBase64.length}张图片`); 111 | } 112 | 113 | // 保存消息 114 | await redis.set(key, JSON.stringify(messageToSave), { EX: config.gg_HistoryExTime * 60 * 60 }) // x小时过期 115 | 116 | // 获取该用户的所有消息 117 | const pattern = buildRedisPattern(userId, promptNum, type) 118 | const keys = await redis.keys(pattern) 119 | keys.sort((a, b) => { 120 | const timeA = parseInt(a.split(':')[3]) 121 | const timeB = parseInt(b.split(':')[3]) 122 | return timeB - timeA // 按时间戳降序排序 123 | }) 124 | 125 | // 如果超出限制,删除旧消息 126 | if (keys.length > maxHistory) { 127 | const keysToDelete = keys.slice(maxHistory) 128 | for (const key of keysToDelete) { 129 | await redis.del(key) 130 | } 131 | logger.debug(`[Context] 清理${keysToDelete.length}条过期消息`); 132 | } 133 | 134 | return true 135 | } catch (error) { 136 | logger.error('[Context] 保存上下文失败:', error) 137 | return false 138 | } 139 | } 140 | 141 | /** 加载用户历史对话 */ 142 | export async function loadContext(userId, promptNum = 0, type = '') { 143 | try { 144 | const config = Config.getConfig() 145 | const maxHistory = config.gg_maxHistoryLength * 2 || 40 146 | 147 | // 添加日志 148 | logger.mark(`[Context] 加载上下文 - userId: ${userId}, promptNum: ${promptNum}, type: ${type}`); 149 | 150 | // 使用新的pattern构造函数,传入type参数 151 | const pattern = buildRedisPattern(userId, promptNum, type) 152 | logger.debug(`[Context] 加载历史记录`); 153 | 154 | const keys = await redis.keys(pattern) 155 | keys.sort((a, b) => { 156 | const timeA = parseInt(a.split(':')[3]) 157 | const timeB = parseInt(b.split(':')[3]) 158 | return timeA - timeB // 按时间戳升序排序 159 | }) 160 | 161 | // 只获取最近的N条消息 162 | const recentKeys = keys.slice(-maxHistory) 163 | const messages = [] 164 | 165 | for (const key of recentKeys) { 166 | const data = await redis.get(key) 167 | if (data) { 168 | const message = JSON.parse(data) 169 | messages.push(message) 170 | } 171 | } 172 | 173 | logger.debug(`[Context] 加载${messages.length}条记录`); 174 | return messages 175 | } catch (error) { 176 | logger.error('[Context] 加载上下文失败:', error) 177 | return [] 178 | } 179 | } 180 | 181 | /** 清除用户前 n 条历史对话 */ 182 | export async function clearContextByCount(userId, count = 1, promptNum = 0, type = '') { 183 | try { 184 | // 使用新的pattern构造函数,传入type参数 185 | const pattern = buildRedisPattern(userId, promptNum, type) 186 | logger.debug(`[Context] 清除${count}条历史记录`); 187 | 188 | const keys = await redis.keys(pattern) 189 | keys.sort((a, b) => { 190 | const timeA = parseInt(a.split(':')[3]) 191 | const timeB = parseInt(b.split(':')[3]) 192 | return timeA - timeB // 按时间戳升序排序 193 | }) 194 | 195 | // 删除最近的 n 条消息 196 | const keysToDelete = keys.slice(-count * 2) 197 | for (const key of keysToDelete) { 198 | await redis.del(key) 199 | } 200 | 201 | logger.debug(`[Context] 已清除${keysToDelete.length}条记录`); 202 | return { 203 | success: true, 204 | deletedCount: keysToDelete.length / 2 205 | } 206 | } catch (error) { 207 | logger.error('[Context] 清除历史对话失败:', error) 208 | return { 209 | success: false, 210 | error: error.message 211 | } 212 | } 213 | } 214 | 215 | /** 清除指定用户的上下文记录 */ 216 | export async function clearUserContext(userId, promptNum = 0, type = '') { 217 | try { 218 | const pattern = buildRedisPattern(userId, promptNum, type) 219 | logger.debug(`[Context] 清除用户所有记录`); 220 | 221 | const keys = await redis.keys(pattern) 222 | for (const key of keys) { 223 | await redis.del(key) 224 | } 225 | 226 | logger.debug(`[Context] 已清除${keys.length}条记录`); 227 | return true 228 | } catch (error) { 229 | logger.error('[Context] 清除用户上下文失败:', error) 230 | return false 231 | } 232 | } 233 | 234 | /** 清除所有用户的上下文记录 */ 235 | export async function clearAllContext() { 236 | try { 237 | logger.debug(`[Context] 清除所有记录`); 238 | const keys = await redis.keys('sfplugin:llm:*') 239 | for (const key of keys) { 240 | await redis.del(key) 241 | } 242 | logger.debug(`[Context] 已清除${keys.length}条记录`); 243 | return true 244 | } catch (error) { 245 | logger.error('[Context] 清除所有上下文失败:', error) 246 | return false 247 | } 248 | } -------------------------------------------------------------------------------- /utils/extractUrl.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | 3 | /** 4 | * 检查URL是否为不需要提取内容的文件类型 5 | * @param {string} url URL地址 6 | * @returns {boolean} 是否为不需要提取的文件类型 7 | */ 8 | function isSkippedUrl(url) { 9 | // 检查常见图片后缀 10 | const imageExtensions = /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tiff|tif|raw|cr2|nef|arw|dng|heif|heic|avif|jfif|psd|ai)$/i; 11 | 12 | // 检查常见视频后缀 13 | const videoExtensions = /\.(mp4|webm|mkv|flv|avi|mov|wmv|rmvb|m4v|3gp|mpeg|mpg|ts|mts)$/i; 14 | 15 | // 检查可执行文件和二进制文件 16 | const binaryExtensions = /\.(exe|msi|dll|sys|bin|dat|iso|img|dmg|pkg|deb|rpm|apk|ipa|jar|class|pyc|o|so|dylib)$/i; 17 | 18 | // 检查压缩文件 19 | const archiveExtensions = /\.(zip|rar|7z|tar|gz|bz2|xz|tgz|tbz|cab|ace|arc)$/i; 20 | 21 | // 检查是否包含媒体或下载相关路径关键词 22 | const skipKeywords = /\/(images?|photos?|pics?|videos?|medias?|downloads?|uploads?|binaries|assets)\//i; 23 | 24 | // 不跳过的URL类型 25 | const allowedExtensions = /(\.bilibili.com\/video|b23\.tv)\//i; 26 | 27 | return !allowedExtensions.test(url) && 28 | (imageExtensions.test(url) || 29 | videoExtensions.test(url) || 30 | binaryExtensions.test(url) || 31 | archiveExtensions.test(url) || 32 | skipKeywords.test(url)); 33 | } 34 | 35 | /** 36 | * 从文本中提取URL 37 | * @param {string} text 需要提取URL的文本 38 | * @returns {string[]} URL数组 39 | */ 40 | function extractUrls(text) { 41 | // 更新正则表达式以匹配包含中文和空格的URL 42 | const urlRegex = /https?:\/\/[^\s/$.?#].[^\s]*/gi; 43 | const matches = text.match(urlRegex) || []; 44 | 45 | // 清理URL并进行解码 46 | return matches.map(url => { 47 | // 移除URL末尾的标点符号和中文字符 48 | let cleanUrl = url.replace(/[.,!?;:,。!?、;:\s\u4e00-\u9fa5]+$/, ''); 49 | // 处理URL中的空格和中文字符 50 | try { 51 | // 尝试解码URL,如果已经是解码状态则保持不变 52 | cleanUrl = decodeURIComponent(cleanUrl); 53 | // 重新编码空格和特殊字符,但保留中文字符 54 | cleanUrl = cleanUrl.replace(/\s+/g, '%20') 55 | .replace(/[[\](){}|\\^<>]/g, encodeURIComponent); 56 | } catch (e) { 57 | // 如果解码失败,说明URL可能已经是正确格式,直接返回 58 | return cleanUrl; 59 | } 60 | return cleanUrl; 61 | }); 62 | } 63 | 64 | /** 65 | * 从URL提取内容 66 | * @param {string} url 需要提取内容的URL 67 | * @returns {Promise} 提取的内容 68 | */ 69 | async function extractUrlContent(url) { 70 | // 如果是需要跳过的URL类型,直接返回null 71 | if (isSkippedUrl(url)) { 72 | logger.mark(`[URL提取]跳过不需要处理的URL类型: ${url}`) 73 | return null; 74 | } 75 | 76 | try { 77 | logger.debug(`[URL提取]开始从URL获取内容: ${url}`) 78 | const response = await fetch(`https://lbl.news/api/extract?url=${encodeURIComponent(url)}`); 79 | if (!response.ok) { 80 | throw new Error(`提取内容失败: ${response.statusText}`); 81 | } 82 | const data = await response.json(); 83 | logger.debug(`[URL提取]成功获取URL内容: ${url}`) 84 | return data; 85 | } catch (error) { 86 | logger.error(`[URL提取]提取内容失败: ${error.message}, URL: ${url}`); 87 | return null; 88 | } 89 | } 90 | 91 | /** 92 | * 处理消息中的URL并提取内容 93 | * @param {string} message 用户消息 94 | * @param {boolean} appendContent 是否将提取的内容附加到消息中,默认为true 95 | * @returns {Promise<{message: string, extractedContent: string}>} 处理后的消息和提取的内容 96 | */ 97 | export async function processMessageWithUrls(message, appendContent = true) { 98 | const urls = extractUrls(message); 99 | if (urls.length === 0) { 100 | return { message, extractedContent: '' }; 101 | } 102 | 103 | logger.mark(`[URL处理]从消息中提取到${urls.length}个URL`) 104 | let processedMessage = message; 105 | let extractedContent = ''; 106 | 107 | for (const url of urls) { 108 | // 跳过不需要提取内容的URL 109 | if (isSkippedUrl(url)) { 110 | logger.mark(`[URL处理]跳过URL: ${url}`) 111 | continue; 112 | } 113 | 114 | logger.debug(`[URL处理]开始处理URL: ${url}`) 115 | const content = await extractUrlContent(url); 116 | if (content) { 117 | logger.debug(`[URL处理]成功提取URL内容: ${url}`) 118 | const urlContent = `\n\n提取的URL内容(${url}):\n内容: ${content.content}`; 119 | extractedContent += urlContent; 120 | if (appendContent) { 121 | processedMessage += urlContent; 122 | } 123 | } 124 | } 125 | 126 | return { message: processedMessage, extractedContent }; 127 | } 128 | -------------------------------------------------------------------------------- /utils/getImg.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | /** 4 | * @description: 处理引用消息:获取引用的图片和文本,图片放入e.img,优先级==> e.source.img > e.img,文本放入e.sourceMsg 5 | * @param {*} e 6 | * @param {*} alsoGetAtAvatar 开启使用At用户头像作为图片,默认 false 7 | * @return {*}处理过后的e 8 | */ 9 | export async function parseSourceImg(e, alsoGetAtAvatar = true) { 10 | let reply; 11 | if (alsoGetAtAvatar && e.at && !e.source && !e.reply_id && !e.img) { 12 | if (e.atBot) { 13 | e.img = []; 14 | e.img[0] = 15 | e.bot.avatar || `https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.self_id}`; 16 | } 17 | if (e.at) { 18 | try { 19 | e.img = [await e.group.pickMember(e.at).getAvatarUrl()]; 20 | } catch (error) { 21 | e.img = [`https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.at}`]; 22 | } 23 | } 24 | } 25 | // ICQQ原生 26 | if (e.source) { 27 | if (e.isGroup) { 28 | reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message; 29 | } else { 30 | reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message; 31 | } 32 | } 33 | // 添加OneBotv11适配器 34 | else if (e.reply_id) { 35 | reply = (await e.getReply(e.reply_id)).message; 36 | } 37 | 38 | if (reply) { 39 | let i = [] 40 | let text = [] // 用于存储文本消息 41 | let senderNickname = '' // 存储发送者昵称 42 | 43 | // 获取发送者昵称 44 | if (e.source) { 45 | if (e.isGroup) { 46 | try { 47 | const sender = await e.group.pickMember(e.source.user_id) 48 | senderNickname = sender.card || sender.nickname 49 | } catch (error) { 50 | logger.error('[sf插件]获取群成员信息失败:', error) 51 | } 52 | } else { 53 | try { 54 | const friend = e.bot.fl.get(e.source.user_id) 55 | senderNickname = friend?.nickname 56 | } catch (error) { 57 | logger.error('[sf插件]获取好友信息失败:', error) 58 | } 59 | } 60 | } 61 | // 添加OneBotv11适配器的处理 62 | else if (e.reply_id) { 63 | try { 64 | const reply = await e.getReply(e.reply_id) 65 | senderNickname = reply.sender?.card || reply.sender?.nickname 66 | } catch (error) { 67 | logger.error('[sf插件]获取回复消息发送者信息失败:', error) 68 | } 69 | } 70 | 71 | for (const val of reply) { 72 | if (val.type == 'image') { 73 | i.push(val.url) 74 | } 75 | if (val.type == 'text') { 76 | text.push(val.text) // 收集文本消息 77 | } 78 | if (val.type == "file") { 79 | e.reply("不支持消息中的文件,请将该文件以图片发送...", true); 80 | return; 81 | } 82 | } 83 | if (Boolean(i.length)) { 84 | e.img = i 85 | } 86 | if (text.length > 0) { 87 | // 如果有发送者昵称,添加到引用文本前,使用markdown引用格式 88 | const lines = text.join('\n').split('\n'); 89 | const quotedLines = lines.map(line => `> ${line}`).join('\n'); 90 | e.sourceMsg = senderNickname ? 91 | `> ##### ${senderNickname}:\n> ---\n${quotedLines}` : 92 | quotedLines; 93 | } 94 | } 95 | return e; 96 | } 97 | 98 | 99 | export async function url2Base64(url, isReturnBuffer = false) { 100 | try { 101 | const response = await axios.get(url, { 102 | responseType: 'arraybuffer', 103 | timeout: 60000 // 设置超时时间为60秒 104 | }); 105 | 106 | const contentLength = response.headers?.['content-length'] || response.headers?.get('size') 107 | const maxSizeInBytes = 10 * 1024 * 1024; // 10MB in bytes 108 | 109 | if (contentLength && parseInt(contentLength) > maxSizeInBytes) { 110 | logger.error('[sf插件]图片大小超过10MB,请使用大小合适的图片'); 111 | return null; 112 | } 113 | // 返回 Buffer 114 | if (isReturnBuffer) 115 | return Buffer.from(response.data, 'binary'); 116 | 117 | return Buffer.from(response.data, 'binary').toString('base64'); 118 | } catch (error) { 119 | logger.error(`[sf插件]下载引用图片错误,可能是图片链接已失效,使用的图片链接:\n` + url); 120 | return null; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /utils/markdownPic.js: -------------------------------------------------------------------------------- 1 | import puppeteer from "../../../lib/puppeteer/puppeteer.js"; 2 | const _path = process.cwd(); 3 | 4 | /** 5 | * @description: 生成对话内容的markdown图片 6 | * @param {number} userId 用户ID e.user_id;用于第一个头像显示 7 | * @param {number} botId 机器人ID e.self_id;用于第二个头像显示 8 | * @param {string} userMsg user输入内容 9 | * @param {string} answer bot回复内容 10 | * @return {object} 图片对象 11 | */ 12 | export async function markdown_screenshot(userId, botId, userMsg, answer) { 13 | logger.mark('[sf插件]正在生成markdown图片...') 14 | const data = { 15 | _path, 16 | tplFile: './plugins/siliconflow-plugin/resources/markdownPic/index.html', 17 | content: answer, 18 | userId, 19 | botId, 20 | userMsg 21 | } 22 | const img = await puppeteer.screenshot("markdown", data); 23 | return img; 24 | } -------------------------------------------------------------------------------- /utils/parse.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | // 尺寸处理 4 | function scaleParam(text, e) { 5 | // 确保text是字符串 6 | if (!text || typeof text !== 'string') { 7 | return { parameters: { height: 1024, width: 1024 }, text: text || '' }; 8 | } 9 | 10 | const scale = { 11 | "竖图": { height: 1216, width: 832 }, 12 | "长图": { height: 1216, width: 832 }, 13 | "宽图": { height: 832, width: 1216 }, 14 | "横图": { height: 832, width: 1216 }, 15 | "方图": { height: 1024, width: 1024 } 16 | }; 17 | 18 | let parameters = { height: 1024, width: 1024 }; 19 | 20 | Object.entries(scale).forEach(([size, dimensions]) => { 21 | if (text.includes(size)) { 22 | parameters = { ...dimensions }; 23 | text = text.replace(new RegExp(size, 'g'), ''); 24 | } 25 | }); 26 | const result = /(\d{2,7})[\*×](\d{2,7})/.exec(text); 27 | if (result) { 28 | let [width, height] = [Math.floor(Number(result[1]) / 64) * 64, Math.floor(Number(result[2]) / 64) * 64]; 29 | 30 | const maxArea = e.sfRuntime.config.free_mode ? 3145728 : 1048576; 31 | 32 | while (width * height > maxArea && (width > 64 || height > 64)) { 33 | width -= width > 64 ? 64 : 0; 34 | height -= height > 64 ? 64 : 0; 35 | } 36 | 37 | parameters = { height: height, width: width }; 38 | text = text.replace(/(\d{2,7})[\*×](\d{2,7})/g, ''); 39 | } 40 | 41 | return { parameters, text }; 42 | } 43 | function imgModelParam(text, e) { 44 | // 确保text是字符串 45 | if (!text || typeof text !== 'string') { 46 | return { parameters: { imageModel: e?.sfRuntime?.config?.imageModel || 'stabilityai/stable-diffusion-xl-base-1.0' }, text: text || '' }; 47 | } 48 | 49 | const samplers = { 50 | // 注释掉 非免费的 51 | // 'FLUX.1-dev': 'black-forest-labs/FLUX.1-dev', 52 | // 'FLUX.1-schnell': 'Pro/black-forest-labs/FLUX.1-schnell', 53 | 'FLUX.1-schnell': 'black-forest-labs/FLUX.1-schnell', 54 | 'sd-turbo': 'stabilityai/sd-turbo', 55 | 'sdxl-turbo': 'stabilityai/sdxl-turbo', 56 | 'stable-diffusion-2-1': 'stabilityai/stable-diffusion-2-1', 57 | 'stable-diffusion-3-medium': 'stabilityai/stable-diffusion-3-medium', 58 | 'stable-diffusion-xl-base-1.0': 'stabilityai/stable-diffusion-xl-base-1.0', 59 | } 60 | let parameters = { imageModel: e?.sfRuntime?.config?.imageModel || 'stabilityai/stable-diffusion-xl-base-1.0' } 61 | Object.entries(samplers).forEach(([alias, imageModel]) => { 62 | if (text.includes(alias)) { 63 | parameters.imageModel = imageModel 64 | text = text.replace(new RegExp(alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '') 65 | } 66 | }) 67 | return { parameters, text } 68 | } 69 | function seedParam(text) { 70 | // 确保text是字符串 71 | if (!text || typeof text !== 'string') { 72 | return { parameters: { seed: Math.floor((Math.random() + Math.floor(Math.random() * 9 + 1)) * Math.pow(10, 9)) }, text: text || '' }; 73 | } 74 | 75 | let parameters = {} 76 | let seed = text.match(/seed(\s)?=\s?(\d+)/)?.[2] 77 | if (seed) { 78 | parameters.seed = Number(seed.substring(0, 10)) 79 | text = text.replace(/seed(\s)?=\s?(\d+)/g, '') 80 | } else { 81 | parameters.seed = Math.floor((Math.random() + Math.floor(Math.random() * 9 + 1)) * Math.pow(10, 9)) 82 | } 83 | return { parameters, text } 84 | } 85 | function stepsParam(text, e) { 86 | // 确保text是字符串 87 | if (!text || typeof text !== 'string') { 88 | return { parameters: { steps: e?.sfRuntime?.config?.num_inference_steps || 28 }, text: text || '' }; 89 | } 90 | 91 | let parameters = {} 92 | let steps = text.match(/步数\s?(\d+)/)?.[1] 93 | // 安全地访问 e.sfRuntime.config 94 | const maxStep = e?.sfRuntime?.config?.free_mode ? 50 : 28 95 | parameters.steps = steps ? Math.min(Math.max(1, Number(steps)), maxStep) : (e?.sfRuntime?.config?.num_inference_steps || 28) 96 | text = text.replace(/步数\s?\d+/g, '') 97 | return { parameters, text } 98 | } 99 | function isGeneratePrompt(text, e) { 100 | // 确保text是字符串 101 | if (!text || typeof text !== 'string') { 102 | return { parameters: {}, text: text || '' }; 103 | } 104 | 105 | let parameters = {} 106 | let generatePrompt = text.match(/自?动?提示词(开|关)/)?.[1] 107 | if (generatePrompt && e?.sfRuntime) { 108 | e.sfRuntime.isgeneratePrompt = generatePrompt === '开' 109 | } 110 | text = text.replace(/自?动?提示词(开|关)/g, '') 111 | return { parameters, text } 112 | } 113 | 114 | 115 | /** 116 | * @description: 处理prompt 117 | * @param {*} text 118 | * @param {*} e 119 | * @return {*} 120 | */ 121 | async function promptParam(text, e) { 122 | // 确保text是字符串 123 | if (!text || typeof text !== 'string') { 124 | return { parameters: { prompt: '', negative_prompt: '' }, text: '', input: '' }; 125 | } 126 | 127 | let parameters = {} 128 | let input = '' 129 | let ntags = text.match(/ntags(\s)?=(.*)$/s)?.[2] 130 | if (ntags) { 131 | input = text.replace(/ntags(\s)?=(.*)$/s, '') 132 | } else { 133 | input = text 134 | } 135 | if (ntags) { 136 | parameters.negative_prompt = ntags 137 | } 138 | return (input === '') ? { parameters } : { parameters, input } 139 | } 140 | 141 | /** 142 | * @description: 处理画图参数 143 | * @param {*} e 144 | * @param {*} text 145 | * @return {*} { parameters, input } 146 | */ 147 | export async function handleParam(e, text, skipImgModel = false) { 148 | // 确保e和text参数存在 149 | if (!e) { 150 | throw new Error('参数e不能为空'); 151 | } 152 | 153 | // 确保text是字符串 154 | if (!text || typeof text !== 'string') { 155 | text = ''; 156 | } 157 | 158 | let parameters = {} 159 | let result = null 160 | 161 | // 尺寸处理 162 | result = scaleParam(text, e) 163 | parameters = Object.assign(parameters, result.parameters) 164 | text = result.text 165 | // 模型处理 166 | if (!skipImgModel) { 167 | result = imgModelParam(text, e) 168 | parameters = Object.assign(parameters, result.parameters) 169 | text = result.text 170 | } 171 | // 步数处理 172 | result = stepsParam(text, e) 173 | parameters = Object.assign(parameters, result.parameters) 174 | text = result.text 175 | // 种子处理 176 | result = seedParam(text) 177 | parameters = Object.assign(parameters, result.parameters) 178 | text = result.text 179 | // 自动提示词处理 180 | result = isGeneratePrompt(text, e) 181 | text = result.text 182 | 183 | // 正负词条处理 184 | try { 185 | result = await promptParam(text, e) 186 | } catch (error) { 187 | throw error 188 | } 189 | parameters = Object.assign(parameters, result.parameters) 190 | let input = result.input || undefined 191 | return { parameters, input } 192 | } -------------------------------------------------------------------------------- /utils/uploadImage.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | import Config from '../components/Config.js' 3 | 4 | /** 5 | * 上传图片到指定域名 6 | * @param {string} imgUrl 图片URL 7 | * @param {*} config 8 | * @returns {Promise} 上传后的图片URL 9 | */ 10 | export async function uploadImage(imgUrl, config = null) { 11 | if (!config) 12 | config = Config.getConfig() 13 | const domain = config.link_domain 14 | 15 | if (!domain) { 16 | throw new Error('未配置图片服务器域名,请使用 #设置直链域名 命令设置') 17 | } 18 | 19 | try { 20 | // 添加超时控制 21 | const controller = new AbortController(); 22 | const timeout = setTimeout(() => controller.abort(), 30000); 23 | 24 | const response = await fetch(imgUrl, { 25 | signal: controller.signal, 26 | timeout: 30000 27 | }); 28 | clearTimeout(timeout); 29 | 30 | if (!response.ok) { 31 | throw new Error(`获取图片失败: ${response.status} ${response.statusText}`); 32 | } 33 | 34 | const imgBuffer = Buffer.from(await response.arrayBuffer()); 35 | 36 | const boundary = '----WebKitFormBoundary' + Math.random().toString(36).slice(2); 37 | let formBody = ''; 38 | 39 | formBody += `--${boundary}\r\n`; 40 | formBody += 'Content-Disposition: form-data; name="file"; filename="image.jpg"\r\n'; 41 | formBody += 'Content-Type: image/jpeg\r\n\r\n'; 42 | formBody += imgBuffer.toString('binary'); 43 | formBody += `\r\n--${boundary}--\r\n`; 44 | 45 | // 添加新的超时控制 46 | const uploadController = new AbortController(); 47 | const uploadTimeout = setTimeout(() => uploadController.abort(), 30000); 48 | 49 | const uploadResponse = await fetch(`${domain}/upload.php`, { 50 | method: 'POST', 51 | body: Buffer.from(formBody, 'binary'), 52 | headers: { 53 | 'Content-Type': `multipart/form-data; boundary=${boundary}` 54 | }, 55 | signal: uploadController.signal, 56 | timeout: 30000 57 | }); 58 | clearTimeout(uploadTimeout); 59 | 60 | if (!uploadResponse.ok) { 61 | throw new Error(`上传失败: ${uploadResponse.status} ${uploadResponse.statusText}`); 62 | } 63 | 64 | const uploadResult = await uploadResponse.json(); 65 | if (uploadResult.code !== 200) { 66 | throw new Error(uploadResult.msg); 67 | } 68 | 69 | return uploadResult.img.replace(/([^:])\/+/g, '$1/'); 70 | 71 | } catch (error) { 72 | throw error; 73 | } finally { 74 | } 75 | } --------------------------------------------------------------------------------