├── .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 | 
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: `\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: `\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 = ``;
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(/___+/, `${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 |
{{@copyright || sys?.copyright}}
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 |
{{@copyright || sys?.copyright}}
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 |
436 |
437 |
438 |
439 |

440 |
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