UNPKG

koishi-plugin-beta-dxl-bert-vits

Version:

或许这是better-dxl-bert-vits呢(内置了gsvits和魔塔社区的语音合成api,同时支持自行添加bert-vits2后端api地址到/data/bert-vits/speakers.json中)

634 lines (616 loc) 22.1 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, inject: () => inject, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi3 = require("koishi"); // src/constants.ts var import_path = __toESM(require("path")); var import_fs = __toESM(require("fs")); var APISpeakerList = function() { const dir = import_path.default.resolve("data/bert-vits"); const file = import_path.default.resolve(dir, "speakers.json"); if (!import_fs.default.existsSync(file)) { const defaultPath = import_path.default.join(__dirname, "../resources/speakers.json"); try { import_fs.default.mkdirSync(dir); } catch (e) { } import_fs.default.copyFileSync(defaultPath, file); } return JSON.parse(import_fs.default.readFileSync(file, "utf-8")); }(); var baseSpeakId = 114514; var SpeakerKeyIdMap = APISpeakerList.flatMap((apiSpeaker) => { const entries = Object.entries(apiSpeaker.speakers); const result = []; for (const [key, value] of entries) { if (value.language) { result.push(`${key}_${value.language}`); continue; } if (value.languages) { value.languages.forEach((l) => { result.push(`${key}_${l}`); }); } } return result; }).sort((a, b) => a < b ? 1 : -1).map((k, index) => [k, baseSpeakId++]).reduce( (acc, [k, v]) => { acc[v] = k; return acc; }, {} ); var SpeakerKeyMap = Object.fromEntries( Object.entries(SpeakerKeyIdMap).map(([k, v]) => [v, k]) ); // src/service.ts var import_koishi = require("koishi"); var import_vits = __toESM(require("@initencounter/vits")); var betavits = class { constructor(ctx, config) { this.ctx = ctx; this.config = config; this.logger = ctx.logger("bert-vits"); } static { __name(this, "betavits"); } logger; _cacheSpeakers = {}; async say(input, options) { const option = fallback(options, this.config); const [api, speaker, lang] = this.findSpeaker(option.speaker); option.speaker = speaker.speaker ?? option.speaker; option.language = lang ?? option.language ?? "ZH"; input = await this.checkLanguage(input, option); const payload = this._generatePlayLoad(input, option); try { return await this._request(payload, api, speaker); } catch (error) { this.logger.error("ERROR:", error); throw error; } } async checkLanguage(input, options) { try { const franc = await importFranc(); const sourceLanguage = francLanguageMapping[franc.franc(input)] ?? options.language; if (sourceLanguage === options.language || !this.config.autoTranslate) { return input; } return this.ctx.translator.translate({ input, source: sourceLanguage.toLocaleLowerCase(), target: options.language.toLocaleLowerCase() }); } catch (error) { return input; } } findSpeaker(rawSpeaker) { if (this._cacheSpeakers[rawSpeaker]) { return this._cacheSpeakers[rawSpeaker]; } let [speaker, lang] = rawSpeaker.split("_"); if (!lang) { lang = "ZH"; } for (const apiSpeakers of APISpeakerList) { for (const [currentSpeaker, value] of Object.entries( apiSpeakers.speakers )) { if (speaker === currentSpeaker) { const result = [ apiSpeakers.api, value, lang ]; this._cacheSpeakers[speaker] = result; return result; } } } } _generatePlayLoad(input, options) { const { sdp_ratio, language, speaker, noise, noisew, length, prompt, weight } = options; return { data: [ input, speaker, sdp_ratio, noise, noisew, length, language, null, prompt, "Text prompt", "", weight ], event_data: null, fn_index: 0, dataType: [ "textbox", "dropdown", "slider", "slider", "slider", "slider", "dropdown", "audio", "textbox", "radio", "textbox", "slider" ], session_hash: "kg71r7fv3e8" }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any async _request(payload, api, speaker) { let requestUrl = `${api.base.replace("{version}", speaker.version)}${api.endpoint}`; const urlParams = new URLSearchParams(); if (api.params) { Object.entries(api.params).forEach(([key, value]) => { urlParams.append( key, value.replace("{version}", speaker.version).replace("{date}", String(Date.now())) ); }); requestUrl = `${requestUrl}?${urlParams}`; } if (api.is_gradio) { delete payload.session_hash; } let audioUrl; const res = await this.ctx.http.post(requestUrl, payload); if (res.event_id) { const gradioUrl = `${api.base.replace( "{version}", speaker.version )}${api.endpoint}/${res.event_id}`; const gradioEvents = await this.ctx.http.get(gradioUrl, { responseType: "text", headers: { "Content-Type": "text/event-stream" } }); const dataSource = gradioEvents.split("\n"); for (const data of dataSource) { if (data.startsWith("data:")) { const dataJson = JSON.parse(data.substring(5)); if (dataJson[0] === "Success") { audioUrl = dataJson[1].url; } else { this.logger.error("ERROR(gradio):", dataJson); } } } } else { audioUrl = `${api.base.replace( "{version}", speaker.version )}/file=${res.data[1].name}`; } const buffer = await this.ctx.http(audioUrl, { responseType: "arraybuffer" }); return import_koishi.h.audio(buffer.data, "wav"); } }; var betavitsService = class extends import_vits.default { constructor(ctx, impl) { super(ctx); this.impl = impl; } static { __name(this, "betavitsService"); } say(options) { return this.impl.say(options.input, { speaker: SpeakerKeyIdMap[options.speaker_id] }); } }; function fallback(options, defaultValues) { if (!options) { return defaultValues; } const result = Object.assign({}, defaultValues); for (const key in options) { if (options[key] && options[key] !== "") { result[key] = options[key]; } } return result; } __name(fallback, "fallback"); async function importFranc() { try { return await import("franc-min"); } catch (e) { throw new Error( "Please install franc-min as a dependency with, e.g. `npm install -S franc-min`" ); } } __name(importFranc, "importFranc"); var francLanguageMapping = { jpn: "jp", zho: "zh", eng: "en", spa: "es", fra: "fr", deu: "de", ita: "it", por: "pt", rus: "ru", kor: "ko", ara: "ar", heb: "he", hin: "hi", tur: "tr", vie: "vi", cmn: "zh", ell: "el" }; // src/config.ts var import_koishi2 = require("koishi"); var usage = ` <h2>🌈 使用</h2> <ul> <li>建议自行添加别名。</li> </ul> --- <h2>🌼 指令</h2> <h3>betavits</h3> <p>显示语音合成使用帮助。</p> <pre><code>betavits</code></pre> <h3>betavits -s 东雪莲|塔菲|坏女人星瞳...</h3> <p>将输入的文本转换为东雪莲|塔菲|坏女人星瞳...的语音。</p> <pre><code>betavits -s 东雪莲|塔菲|坏女人星瞳... 你好</code></pre> --- <h2>兼容原始 vits 指令</h2> <p>下表为每个讲者对应的 speaker_id,如果某个使用了 vits 插件的插件需要这个数字的 speaker_id,你可以根据下表来获取实际的 id。</p> --- 目前可以直接使用的speaker列表 <code>下方列表并不是实时生成的,如有误差,还请谅解。</code> <details> <summary>点击展开/折叠 [讲者--speaker_id] 列表</summary> <table> <thead> <tr> <th>讲者</th> <th>speaker_id</th> </tr> </thead> <tbody> <tr><td>黑桃影-Echo_ZH</td><td>5</td></tr> <tr><td>黑桃影-Echo_MIX</td><td>6</td></tr> <tr><td>黑桃影-Echo_JP</td><td>7</td></tr> <tr><td>黑桃影-Echo_EN</td><td>8</td></tr> <tr><td>黑桃影-Echo_AUTO</td><td>9</td></tr> <tr><td>鹿鸣-yoyo_ZH</td><td>10</td></tr> <tr><td>鹿鸣-yoyo_MIX</td><td>11</td></tr> <tr><td>鹿鸣-yoyo_JP</td><td>12</td></tr> <tr><td>鹿鸣-yoyo_EN</td><td>13</td></tr> <tr><td>鹿鸣-yoyo_AUTO</td><td>14</td></tr> <tr><td>鹿鸣-Lumi_ZH</td><td>15</td></tr> <tr><td>鹿鸣-Lumi_MIX</td><td>16</td></tr> <tr><td>鹿鸣-Lumi_JP</td><td>17</td></tr> <tr><td>鹿鸣-Lumi_EN</td><td>18</td></tr> <tr><td>鹿鸣-Lumi_AUTO</td><td>19</td></tr> <tr><td>露早_ZH</td><td>20</td></tr> <tr><td>露早_MIX</td><td>21</td></tr> <tr><td>露早_JP</td><td>22</td></tr> <tr><td>露早_EN</td><td>23</td></tr> <tr><td>露早_AUTO</td><td>24</td></tr> <tr><td>陈泽_ZH</td><td>35</td></tr> <tr><td>陈泽_MIX</td><td>36</td></tr> <tr><td>陈泽_JP</td><td>37</td></tr> <tr><td>陈泽_EN</td><td>38</td></tr> <tr><td>陈泽_AUTO</td><td>39</td></tr> <tr><td>贝拉_ZH</td><td>45</td></tr> <tr><td>贝拉_MIX</td><td>46</td></tr> <tr><td>贝拉_JP</td><td>47</td></tr> <tr><td>贝拉_EN</td><td>48</td></tr> <tr><td>贝拉_AUTO</td><td>49</td></tr> <tr><td>艾许_ZH</td><td>50</td></tr> <tr><td>艾许_MIX</td><td>51</td></tr> <tr><td>艾许_JP</td><td>52</td></tr> <tr><td>艾许_EN</td><td>53</td></tr> <tr><td>艾许_AUTO</td><td>54</td></tr> <tr><td>罗芭_ZH</td><td>55</td></tr> <tr><td>罗芭_MIX</td><td>56</td></tr> <tr><td>罗芭_JP</td><td>57</td></tr> <tr><td>罗芭_EN</td><td>58</td></tr> <tr><td>罗芭_AUTO</td><td>59</td></tr> <tr><td>皮特174_ZH</td><td>70</td></tr> <tr><td>皮特174_MIX</td><td>71</td></tr> <tr><td>皮特174_JP</td><td>72</td></tr> <tr><td>皮特174_EN</td><td>73</td></tr> <tr><td>皮特174_AUTO</td><td>74</td></tr> <tr><td>疯玛吉_ZH</td><td>75</td></tr> <tr><td>疯玛吉_MIX</td><td>76</td></tr> <tr><td>疯玛吉_JP</td><td>77</td></tr> <tr><td>疯玛吉_EN</td><td>78</td></tr> <tr><td>疯玛吉_AUTO</td><td>79</td></tr> <tr><td>瓦尔基里_ZH</td><td>90</td></tr> <tr><td>瓦尔基里_MIX</td><td>91</td></tr> <tr><td>瓦尔基里_JP</td><td>92</td></tr> <tr><td>瓦尔基里_EN</td><td>93</td></tr> <tr><td>瓦尔基里_AUTO</td><td>94</td></tr> <tr><td>珈乐2_ZH</td><td>95</td></tr> <tr><td>珈乐2_MIX</td><td>96</td></tr> <tr><td>珈乐2_JP</td><td>97</td></tr> <tr><td>珈乐2_EN</td><td>98</td></tr> <tr><td>珈乐2_AUTO</td><td>99</td></tr> <tr><td>沃特森_ZH</td><td>110</td></tr> <tr><td>沃特森_MIX</td><td>111</td></tr> <tr><td>沃特森_JP</td><td>112</td></tr> <tr><td>沃特森_EN</td><td>113</td></tr> <tr><td>沃特森_AUTO</td><td>114</td></tr> <tr><td>永雏小菲_ZH</td><td>115</td></tr> <tr><td>永雏小菲_MIX</td><td>116</td></tr> <tr><td>永雏小菲_JP</td><td>117</td></tr> <tr><td>永雏小菲_EN</td><td>118</td></tr> <tr><td>永雏小菲_AUTO</td><td>119</td></tr> <tr><td>永雏塔菲2.3_ZH</td><td>125</td></tr> <tr><td>永雏塔菲2.3_MIX</td><td>126</td></tr> <tr><td>永雏塔菲2.3_JP</td><td>127</td></tr> <tr><td>永雏塔菲2.3_EN</td><td>128</td></tr> <tr><td>永雏塔菲2.3_AUTO</td><td>129</td></tr> <tr><td>永雏塔菲1.2_ZH</td><td>130</td></tr> <tr><td>永雏塔菲1.2_MIX</td><td>131</td></tr> <tr><td>永雏塔菲1.2_JP</td><td>132</td></tr> <tr><td>永雏塔菲1.2_EN</td><td>133</td></tr> <tr><td>永雏塔菲1.2_AUTO</td><td>134</td></tr> <tr><td>星瞳2_ZH</td><td>155</td></tr> <tr><td>星瞳2_MIX</td><td>156</td></tr> <tr><td>星瞳2_JP</td><td>157</td></tr> <tr><td>星瞳2_EN</td><td>158</td></tr> <tr><td>星瞳2_AUTO</td><td>159</td></tr> <tr><td>星瞳2.3_ZH</td><td>160</td></tr> <tr><td>星瞳2.3_MIX</td><td>161</td></tr> <tr><td>星瞳2.3_JP</td><td>162</td></tr> <tr><td>星瞳2.3_EN</td><td>163</td></tr> <tr><td>星瞳2.3_AUTO</td><td>164</td></tr> <tr><td>文静_ZH</td><td>165</td></tr> <tr><td>文静_MIX</td><td>166</td></tr> <tr><td>文静_JP</td><td>167</td></tr> <tr><td>文静_EN</td><td>168</td></tr> <tr><td>文静_AUTO</td><td>169</td></tr> <tr><td>播音员_ZH</td><td>170</td></tr> <tr><td>播音员_MIX</td><td>171</td></tr> <tr><td>播音员_JP</td><td>172</td></tr> <tr><td>播音员_EN</td><td>173</td></tr> <tr><td>播音员_AUTO</td><td>174</td></tr> <tr><td>探路者2.0_ZH</td><td>180</td></tr> <tr><td>探路者2.0_MIX</td><td>181</td></tr> <tr><td>探路者2.0_JP</td><td>182</td></tr> <tr><td>探路者2.0_EN</td><td>183</td></tr> <tr><td>探路者2.0_AUTO</td><td>184</td></tr> <tr><td>扇宝_ZH</td><td>185</td></tr> <tr><td>扇宝_MIX</td><td>186</td></tr> <tr><td>扇宝_JP</td><td>187</td></tr> <tr><td>扇宝_EN</td><td>188</td></tr> <tr><td>扇宝_AUTO</td><td>189</td></tr> <tr><td>懒羊羊_ZH</td><td>190</td></tr> <tr><td>懒羊羊_MIX</td><td>191</td></tr> <tr><td>懒羊羊_JP</td><td>192</td></tr> <tr><td>懒羊羊_EN</td><td>193</td></tr> <tr><td>懒羊羊_AUTO</td><td>194</td></tr> <tr><td>恬豆2_ZH</td><td>200</td></tr> <tr><td>恬豆2_MIX</td><td>201</td></tr> <tr><td>恬豆2_JP</td><td>202</td></tr> <tr><td>恬豆2_EN</td><td>203</td></tr> <tr><td>恬豆2_AUTO</td><td>204</td></tr> <tr><td>恬豆2.3_ZH</td><td>205</td></tr> <tr><td>恬豆2.3_MIX</td><td>206</td></tr> <tr><td>恬豆2.3_JP</td><td>207</td></tr> <tr><td>恬豆2.3_EN</td><td>208</td></tr> <tr><td>恬豆2.3_AUTO</td><td>209</td></tr> <tr><td>弹道_ZH</td><td>210</td></tr> <tr><td>弹道_MIX</td><td>211</td></tr> <tr><td>弹道_JP</td><td>212</td></tr> <tr><td>弹道_EN</td><td>213</td></tr> <tr><td>弹道_AUTO</td><td>214</td></tr> <tr><td>寻血猎犬_ZH</td><td>235</td></tr> <tr><td>寻血猎犬_MIX</td><td>236</td></tr> <tr><td>寻血猎犬_JP</td><td>237</td></tr> <tr><td>寻血猎犬_EN</td><td>238</td></tr> <tr><td>寻血猎犬_AUTO</td><td>239</td></tr> <tr><td>密客_ZH</td><td>240</td></tr> <tr><td>密客_MIX</td><td>241</td></tr> <tr><td>密客_JP</td><td>242</td></tr> <tr><td>密客_EN</td><td>243</td></tr> <tr><td>密客_AUTO</td><td>244</td></tr> <tr><td>向晚_ZH</td><td>280</td></tr> <tr><td>向晚_MIX</td><td>281</td></tr> <tr><td>向晚_JP</td><td>282</td></tr> <tr><td>向晚_EN</td><td>283</td></tr> <tr><td>向晚_AUTO</td><td>284</td></tr> <tr><td>卖卖_ZH</td><td>285</td></tr> <tr><td>卖卖_MIX</td><td>286</td></tr> <tr><td>卖卖_JP</td><td>287</td></tr> <tr><td>卖卖_EN</td><td>288</td></tr> <tr><td>卖卖_AUTO</td><td>289</td></tr> <tr><td>动力小子_ZH</td><td>290</td></tr> <tr><td>动力小子_MIX</td><td>291</td></tr> <tr><td>动力小子_JP</td><td>292</td></tr> <tr><td>动力小子_EN</td><td>293</td></tr> <tr><td>动力小子_AUTO</td><td>294</td></tr> <tr><td>剑魔_ZH</td><td>300</td></tr> <tr><td>剑魔_MIX</td><td>301</td></tr> <tr><td>剑魔_JP</td><td>302</td></tr> <tr><td>剑魔_EN</td><td>303</td></tr> <tr><td>剑魔_AUTO</td><td>304</td></tr> <tr><td>亡灵_ZH</td><td>315</td></tr> <tr><td>亡灵_MIX</td><td>316</td></tr> <tr><td>亡灵_JP</td><td>317</td></tr> <tr><td>亡灵_EN</td><td>318</td></tr> <tr><td>亡灵_AUTO</td><td>319</td></tr> <tr><td>乃琳_ZH</td><td>320</td></tr> <tr><td>乃琳_MIX</td><td>321</td></tr> <tr><td>乃琳_JP</td><td>322</td></tr> <tr><td>乃琳_EN</td><td>323</td></tr> <tr><td>乃琳_AUTO</td><td>324</td></tr> <tr><td>万蒂奇_ZH</td><td>340</td></tr> <tr><td>万蒂奇_MIX</td><td>341</td></tr> <tr><td>万蒂奇_JP</td><td>342</td></tr> <tr><td>万蒂奇_EN</td><td>343</td></tr> <tr><td>万蒂奇_AUTO</td><td>344</td></tr> <tr><td>七海2_ZH</td><td>345</td></tr> <tr><td>七海2_MIX</td><td>346</td></tr> <tr><td>七海2_JP</td><td>347</td></tr> <tr><td>七海2_EN</td><td>348</td></tr> <tr><td>七海2_AUTO</td><td>349</td></tr> <tr><td>丁真-dingzhen_ZH</td><td>355</td></tr> <tr><td>丁真-dingzhen_MIX</td><td>356</td></tr> <tr><td>丁真-dingzhen_JP</td><td>357</td></tr> <tr><td>丁真-dingzhen_EN</td><td>358</td></tr> <tr><td>丁真-dingzhen_AUTO</td><td>359</td></tr> <tr><td>丁真-DZhen_ZH</td><td>360</td></tr> <tr><td>丁真-DZhen_MIX</td><td>361</td></tr> <tr><td>丁真-DZhen_JP</td><td>362</td></tr> <tr><td>丁真-DZhen_EN</td><td>363</td></tr> <tr><td>丁真-DZhen_AUTO</td><td>364</td></tr> </tbody> </table> </details> --- `; var Config = import_koishi2.Schema.intersect([ import_koishi2.Schema.object({ speaker: import_koishi2.Schema.union(Object.values(SpeakerKeyIdMap)).description( "全局默认讲者`有一些可能失效了`<br>可以输入speaker_id匹配查找" ).default("向晚_ZH"), sdp_ratio: import_koishi2.Schema.number().min(0).max(1).step(0.1).role("slider").description("SDP/DP混合比").default(0.5), noise: import_koishi2.Schema.number().min(0.1).max(2).step(0.1).role("slider").description("感情").default(0.6), noisew: import_koishi2.Schema.number().min(0.1).max(2).step(0.1).role("slider").description("音素长度").default(0.9), length: import_koishi2.Schema.number().min(0.1).max(2).step(0.1).role("slider").description("语速").default(1), prompt: import_koishi2.Schema.string().description("用文字描述生成风格。注意只能使用英文且首字母大写单词").default("Happy"), weight: import_koishi2.Schema.number().min(0).max(1).step(0).role("slider").description("主文本和辅助文本的混合比率").default(0.7) }).description("基础设置"), import_koishi2.Schema.object({ groupListmapping: import_koishi2.Schema.array( import_koishi2.Schema.object({ groupList: import_koishi2.Schema.string().description("群组ID(不要多空格哦)").pattern(/^\S+$/), defaultspeaker: import_koishi2.Schema.union(Object.values(SpeakerKeyIdMap)).description("默认讲者 (有一些可能失效了)").default("向晚_ZH") }) ).role("table").description("分群配置默认讲者").default([ { groupList: "114514", defaultspeaker: "永雏塔菲2.3_AUTO" } ]), autoTranslate: import_koishi2.Schema.boolean().default(false).description("自动翻译到目标语言(需要翻译服务,并且确保已安装可选依赖`franc-min`)") }).description("进阶设置"), import_koishi2.Schema.object({ loggerinfo: import_koishi2.Schema.boolean().default(false).description("日志调试模式`日常使用无需开启`") }).description("调试设置") ]); // src/index.ts var logger = new import_koishi3.Logger("beta-dxl-bert-vits"); function apply(ctx, config) { const vits = new betavits(ctx, config); ctx.plugin(betavitsService, vits); ctx.command("betavits <text:text>", "AIbetavits语音合成帮助").option("speaker", "-s [speaker:string] 语音合成的讲者", { fallback: config.speaker }).option("sdp_ratio", "-sr [sdp_ratio:nubmer] 语音合成的SDP/DP混合比", { fallback: config.sdp_ratio }).option("noise", "-n [noise:number] 语音合成的感情强度", { fallback: config.noise }).option("noisew", "-nw [noisew:number] 语音合成的音素长度", { fallback: config.noisew }).option("length", "-l [length:number] 语音合成语速", { fallback: config.length }).option("prompt", "-p [prompt:string] 辅助语音合成的情感文本", { fallback: config.prompt }).option("weight", "-w [weight:number] 主文本和辅助文本的混合比率", { fallback: config.weight }).action(async ({ session, options }, text) => { if (!text) { await session.execute("betavits -h"); return null; } const groupConfig = config.groupListmapping.find( (group) => group.groupList === session.channelId ); const finalSpeaker = options.speaker ?? (groupConfig ? groupConfig.defaultspeaker : config.speaker); if (config.loggerinfo) { logger.info(`当前频道: ${session.channelId}`); if (groupConfig) { logger.info( `找到的群组配置: ${JSON.stringify(groupConfig)}` ); } else { logger.info("未找到当前频道的群组配置。"); } logger.info(`最终确定的讲者: ${finalSpeaker}`); } const version = SpeakerKeyMap[finalSpeaker] ?? SpeakerKeyMap[finalSpeaker + "_ZH"]; if (!version) { return `找不到这个 ${finalSpeaker} 讲者,请检查你的输入。`; } return await vits.say( text, Object.assign(options, { speaker: finalSpeaker }) ); }); } __name(apply, "apply"); var inject = { optional: ["translator"] }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, inject, usage });