mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 13:57:10 +00:00
Merge branch 'v2' into v2
This commit is contained in:
commit
be91e25415
26 changed files with 1029 additions and 632 deletions
23
README.md
23
README.md
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
### 推荐的相关文档和参考资料
|
### 推荐的相关文档和参考资料
|
||||||
本README
|
本README
|
||||||
|
[手册](https://chatgptplugin.ikechan8370.com/)
|
||||||
[文档1(建设中)](https://chatgpt-docs.err0r.top/)
|
[文档1(建设中)](https://chatgpt-docs.err0r.top/)
|
||||||
[插件常见问题(鹤望兰版)](https://www.wolai.com/4FCxxWAdjbrHF29MCJmAQK)
|
[插件常见问题(鹤望兰版)](https://www.wolai.com/4FCxxWAdjbrHF29MCJmAQK)
|
||||||
[Yunzai常见问题(LUCK小运版)](https://www.wolai.com/oA43vuW71aBnv7UsEysn4T)
|
[Yunzai常见问题(LUCK小运版)](https://www.wolai.com/oA43vuW71aBnv7UsEysn4T)
|
||||||
|
|
@ -25,11 +26,11 @@
|
||||||
|
|
||||||
## 特点
|
## 特点
|
||||||
|
|
||||||
* 支持单人连续对话Conversation,群聊中支持加入其他人的对话
|
* 支持单人连续对话Conversation
|
||||||
* API模式下,使用 gpt-3.5-turbo API,ChatGPT官网同款模型,仅需OpenAI Api Key,开箱即用。**注意收费**
|
* API模式下,使用 gpt-3.5-turbo 或 gpt-4 API,仅需OpenAI Api Key,开箱即用。**注意收费**
|
||||||
* 支持问答图片截图和聊天记录导出
|
* 支持问答图片截图和聊天记录导出
|
||||||
* 支持AI性格调教,强烈推荐Bing自定义模式
|
* 支持AI性格调教,角色扮演强烈推荐Bing自定义模式
|
||||||
* 支持对接vits回答直接转语音
|
* 支持对接vits和Azure等回答直接转语音
|
||||||
* API3模式下,绕过Cloudflare防护直接访问ChatGPT的SSE API,与官方体验一致,且保留对话记录,在官网可查。免费。
|
* API3模式下,绕过Cloudflare防护直接访问ChatGPT的SSE API,与官方体验一致,且保留对话记录,在官网可查。免费。
|
||||||
* (已不再维护)提供基于浏览器的解决方案作为备选,API3不可用的情况下或担心账户安全的用户可以选择使用浏览器模式。
|
* (已不再维护)提供基于浏览器的解决方案作为备选,API3不可用的情况下或担心账户安全的用户可以选择使用浏览器模式。
|
||||||
* 支持新[必应](https://www.bing.com/new)(token负载均衡,限流降级)
|
* 支持新[必应](https://www.bing.com/new)(token负载均衡,限流降级)
|
||||||
|
|
@ -37,6 +38,7 @@
|
||||||
* 支持[ChatGLM](https://github.com/THUDM/ChatGLM-6B)模型。基于[自建API](https://github.com/ikechan8370/SimpleChatGLM6BAPI)
|
* 支持[ChatGLM](https://github.com/THUDM/ChatGLM-6B)模型。基于[自建API](https://github.com/ikechan8370/SimpleChatGLM6BAPI)
|
||||||
* 2023-04-15 支持[Claude by Slack](https://www.anthropic.com/claude-in-slack )和Poe(WIP)。Claude配置参考[这里](https://ikechan8370.com/archives/chatgpt-plugin-for-yunzaipei-zhi-slack-claude)
|
* 2023-04-15 支持[Claude by Slack](https://www.anthropic.com/claude-in-slack )和Poe(WIP)。Claude配置参考[这里](https://ikechan8370.com/archives/chatgpt-plugin-for-yunzaipei-zhi-slack-claude)
|
||||||
* 2023-05-12 支持星火大模型
|
* 2023-05-12 支持星火大模型
|
||||||
|
* 2023-05-29 支持gpt-4 API.必应无需cookie即可对话(Sydney和自定义模式)
|
||||||
|
|
||||||
### 如果觉得这个插件有趣或者对你有帮助,请点一个star吧!
|
### 如果觉得这个插件有趣或者对你有帮助,请点一个star吧!
|
||||||
|
|
||||||
|
|
@ -124,7 +126,7 @@ pnpm i
|
||||||
| 名称 | 含义 | 解释 | 设置方式 |
|
| 名称 | 含义 | 解释 | 设置方式 |
|
||||||
| :-----------------: | :------------------: | :----------------------------------------------------------: |:--------------------------------------------------------:|
|
| :-----------------: | :------------------: | :----------------------------------------------------------: |:--------------------------------------------------------:|
|
||||||
| ChatGPT AccessToken | ChatGPT登录后的Token | 具体解释见下方 | \#chatgpt设置token |
|
| ChatGPT AccessToken | ChatGPT登录后的Token | 具体解释见下方 | \#chatgpt设置token |
|
||||||
| 必应token | 必应登录后的Token | 必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing 登录Cookie方可使用 | \#chatgpt设置必应token/\#chatgpt删除必应token/\#chatgpt查看必应token |
|
| 必应token | 必应登录后的Token | 必应(Bing)将调用微软必应AI接口进行对话。不填写token对话上限为5句,填写后为20句。无论填写与否插件都会无限续杯。 | \#chatgpt设置必应token/\#chatgpt删除必应token/\#chatgpt查看必应token |
|
||||||
|
|
||||||
|
|
||||||
> #### 我没有注册openai账号?如何获取
|
> #### 我没有注册openai账号?如何获取
|
||||||
|
|
@ -142,12 +144,14 @@ pnpm i
|
||||||
> - **登录后**访问https://chat.openai.com/api/auth/session
|
> - **登录后**访问https://chat.openai.com/api/auth/session
|
||||||
> - 您会获得类似如下一串json字符串`{"user":{"id":"AAA","name":"BBB","email":"CCC","image":"DDD","picture":"EEE","groups":[]},"expires":"FFF","accessToken":"XXX"}`
|
> - 您会获得类似如下一串json字符串`{"user":{"id":"AAA","name":"BBB","email":"CCC","image":"DDD","picture":"EEE","groups":[]},"expires":"FFF","accessToken":"XXX"}`
|
||||||
> - 其中的XXX即为`ChatGPT AccessToken`
|
> - 其中的XXX即为`ChatGPT AccessToken`
|
||||||
|
> - 如果是空的{},说明没有登录,要登录chatgpt而不是openai。
|
||||||
>
|
>
|
||||||
> #### ChatGPT AccessToken 设置了有什么用?我为什么用不了API模式
|
> #### ChatGPT AccessToken 设置了有什么用?我为什么用不了API模式
|
||||||
>
|
>
|
||||||
> - 部分API需要在和机器人的聊天里输入`#chatgpt设置token`才可以使用
|
> - 部分API需要在和机器人的聊天里输入`#chatgpt设置token`才可以使用
|
||||||
>
|
>
|
||||||
> #### 我有新必应的测试资格了,如何获取必应Token?
|
> #### 我有新必应的测试资格了,如何获取必应Token?
|
||||||
|
> 2023/05/29 无需登录也可以使用了,要求不高可以不填
|
||||||
>
|
>
|
||||||
> 1. JS一键获取
|
> 1. JS一键获取
|
||||||
>
|
>
|
||||||
|
|
@ -326,3 +330,12 @@ https://afdian.net/a/ikechan8370
|
||||||
|
|
||||||
[](https://star-history.com/#ikechan8370/chatgpt-plugin&Date)
|
[](https://star-history.com/#ikechan8370/chatgpt-plugin&Date)
|
||||||
|
|
||||||
|
## 工具支持
|
||||||
|
<a href="https://jb.gg/OpenSourceSupport" >
|
||||||
|
<img style="width: 300px" src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
JetBrains for Open Source development license
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
209
apps/chat.js
209
apps/chat.js
|
|
@ -7,11 +7,10 @@ import { ChatGPTAPI } from 'chatgpt'
|
||||||
import { BingAIClient } from '@waylaidwanderer/chatgpt-api'
|
import { BingAIClient } from '@waylaidwanderer/chatgpt-api'
|
||||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||||
import { PoeClient } from '../utils/poe/index.js'
|
import { PoeClient } from '../utils/poe/index.js'
|
||||||
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
import AzureTTS, { supportConfigurations } from '../utils/tts/microsoft-azure.js'
|
||||||
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||||
import { translate } from '../utils/translate.js'
|
import { translate } from '../utils/translate.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { getImg, getImageOcrText } from './entertainment.js'
|
|
||||||
import {
|
import {
|
||||||
render, renderUrl,
|
render, renderUrl,
|
||||||
getMessageById,
|
getMessageById,
|
||||||
|
|
@ -21,7 +20,7 @@ import {
|
||||||
completeJSON,
|
completeJSON,
|
||||||
isImage,
|
isImage,
|
||||||
getUserData,
|
getUserData,
|
||||||
getDefaultReplySetting, isCN, getMasterQQ
|
getDefaultReplySetting, isCN, getMasterQQ, getUserReplySetting, getImageOcrText, getImg, processList
|
||||||
} from '../utils/common.js'
|
} from '../utils/common.js'
|
||||||
import { ChatGPTPuppeteer } from '../utils/browser.js'
|
import { ChatGPTPuppeteer } from '../utils/browser.js'
|
||||||
import { KeyvFile } from 'keyv-file'
|
import { KeyvFile } from 'keyv-file'
|
||||||
|
|
@ -544,12 +543,7 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async switch2Text (e) {
|
async switch2Text (e) {
|
||||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
let userSetting = await getUserReplySetting(this.e)
|
||||||
if (!userSetting) {
|
|
||||||
userSetting = getDefaultReplySetting()
|
|
||||||
} else {
|
|
||||||
userSetting = JSON.parse(userSetting)
|
|
||||||
}
|
|
||||||
userSetting.usePicture = false
|
userSetting.usePicture = false
|
||||||
userSetting.useTTS = false
|
userSetting.useTTS = false
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
|
|
@ -577,12 +571,7 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
let userSetting = await getUserReplySetting(this.e)
|
||||||
if (!userSetting) {
|
|
||||||
userSetting = getDefaultReplySetting()
|
|
||||||
} else {
|
|
||||||
userSetting = JSON.parse(userSetting)
|
|
||||||
}
|
|
||||||
userSetting.useTTS = true
|
userSetting.useTTS = true
|
||||||
userSetting.usePicture = false
|
userSetting.usePicture = false
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
|
|
@ -629,37 +618,35 @@ export class chatgpt extends plugin {
|
||||||
let speaker = e.msg.replace(regex, '').trim() || '随机'
|
let speaker = e.msg.replace(regex, '').trim() || '随机'
|
||||||
switch (Config.ttsMode) {
|
switch (Config.ttsMode) {
|
||||||
case 'vits-uma-genshin-honkai': {
|
case 'vits-uma-genshin-honkai': {
|
||||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
let userSetting = await getUserReplySetting(this.e)
|
||||||
if (!userSetting) {
|
|
||||||
userSetting = getDefaultReplySetting()
|
|
||||||
} else {
|
|
||||||
userSetting = JSON.parse(userSetting)
|
|
||||||
}
|
|
||||||
userSetting.ttsRole = convertSpeaker(speaker)
|
userSetting.ttsRole = convertSpeaker(speaker)
|
||||||
if (speakers.indexOf(userSetting.ttsRole) >= 0) {
|
if (speakers.indexOf(userSetting.ttsRole) >= 0) {
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
await this.reply(`您的默认语音角色已被设置为”${userSetting.ttsRole}“`)
|
await this.reply(`当前语音模式为${Config.ttsMode},您的默认语音角色已被设置为 "${userSetting.ttsRole}" `)
|
||||||
|
} else if (speaker === '随机') {
|
||||||
|
userSetting.ttsRole = '随机'
|
||||||
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
|
await this.reply(`当前语音模式为${Config.ttsMode},您的默认语音角色已被设置为 "随机" `)
|
||||||
} else {
|
} else {
|
||||||
await this.reply(`抱歉,"${userSetting.ttsRole}"我还不认识呢`)
|
await this.reply(`抱歉,"${userSetting.ttsRole}"我还不认识呢`)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'azure': {
|
case 'azure': {
|
||||||
|
let userSetting = await getUserReplySetting(this.e)
|
||||||
let chosen = AzureTTS.supportConfigurations.filter(s => s.name === speaker)
|
let chosen = AzureTTS.supportConfigurations.filter(s => s.name === speaker)
|
||||||
if (chosen.length === 0) {
|
if (speaker === '随机') {
|
||||||
|
userSetting.ttsRoleAzure = '随机'
|
||||||
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
|
await this.reply(`当前语音模式为${Config.ttsMode},您的默认语音角色已被设置为 "随机" `)
|
||||||
|
} else if (chosen.length === 0) {
|
||||||
await this.reply(`抱歉,没有"${speaker}"这个角色,目前azure模式下支持的角色有${AzureTTS.supportConfigurations.map(item => item.name).join('、')}`)
|
await this.reply(`抱歉,没有"${speaker}"这个角色,目前azure模式下支持的角色有${AzureTTS.supportConfigurations.map(item => item.name).join('、')}`)
|
||||||
} else {
|
} else {
|
||||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
|
||||||
if (!userSetting) {
|
|
||||||
userSetting = getDefaultReplySetting()
|
|
||||||
} else {
|
|
||||||
userSetting = JSON.parse(userSetting)
|
|
||||||
}
|
|
||||||
userSetting.ttsRoleAzure = chosen[0].code
|
userSetting.ttsRoleAzure = chosen[0].code
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
// Config.azureTTSSpeaker = chosen[0].code
|
// Config.azureTTSSpeaker = chosen[0].code
|
||||||
const supportEmotion = AzureTTS.supportConfigurations.find(config => config.name === speaker)?.emotion
|
const supportEmotion = AzureTTS.supportConfigurations.find(config => config.name === speaker)?.emotion
|
||||||
await this.reply(`您的默认语音角色已被设置为 ${speaker}-${chosen[0].gender}-${chosen[0].languageDetail} ${supportEmotion && Config.azureTTSEmotion ? ',此角色支持多情绪配置,建议重新使用设定并结束对话以获得最佳体验!' : ''}`)
|
await this.reply(`当前语音模式为${Config.ttsMode},您的默认语音角色已被设置为 ${speaker}-${chosen[0].gender}-${chosen[0].languageDetail} ${supportEmotion && Config.azureTTSEmotion ? ',此角色支持多情绪配置,建议重新使用设定并结束对话以获得最佳体验!' : ''}`)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -671,6 +658,13 @@ export class chatgpt extends plugin {
|
||||||
speaker = match[1]
|
speaker = match[1]
|
||||||
style = match[2]
|
style = match[2]
|
||||||
}
|
}
|
||||||
|
let userSetting = await getUserReplySetting(e)
|
||||||
|
if (speaker === '随机') {
|
||||||
|
userSetting.ttsRoleVoiceVox = '随机'
|
||||||
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
|
await this.reply(`当前语音模式为${Config.ttsMode},您的默认语音角色已被设置为 "随机" `)
|
||||||
|
break
|
||||||
|
}
|
||||||
let chosen = VoiceVoxTTS.supportConfigurations.filter(s => s.name === speaker)
|
let chosen = VoiceVoxTTS.supportConfigurations.filter(s => s.name === speaker)
|
||||||
if (chosen.length === 0) {
|
if (chosen.length === 0) {
|
||||||
await this.reply(`抱歉,没有"${speaker}"这个角色,目前voicevox模式下支持的角色有${VoiceVoxTTS.supportConfigurations.map(item => item.name).join('、')}`)
|
await this.reply(`抱歉,没有"${speaker}"这个角色,目前voicevox模式下支持的角色有${VoiceVoxTTS.supportConfigurations.map(item => item.name).join('、')}`)
|
||||||
|
|
@ -680,15 +674,9 @@ export class chatgpt extends plugin {
|
||||||
await this.reply(`抱歉,"${speaker}"这个角色没有"${style}"这个风格,目前支持的风格有${chosen[0].styles.map(item => item.name).join('、')}`)
|
await this.reply(`抱歉,"${speaker}"这个角色没有"${style}"这个风格,目前支持的风格有${chosen[0].styles.map(item => item.name).join('、')}`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
|
||||||
if (!userSetting) {
|
|
||||||
userSetting = getDefaultReplySetting()
|
|
||||||
} else {
|
|
||||||
userSetting = JSON.parse(userSetting)
|
|
||||||
}
|
|
||||||
userSetting.ttsRoleVoiceVox = chosen[0].name + (style ? `-${style}` : '')
|
userSetting.ttsRoleVoiceVox = chosen[0].name + (style ? `-${style}` : '')
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
await this.reply(`您的默认语音角色已被设置为”${userSetting.ttsRoleVoiceVox}“`)
|
await this.reply(`当前语音模式为${Config.ttsMode},您的默认语音角色已被设置为 "${userSetting.ttsRoleVoiceVox}" `)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -698,23 +686,6 @@ export class chatgpt extends plugin {
|
||||||
* #chatgpt
|
* #chatgpt
|
||||||
*/
|
*/
|
||||||
async chatgpt (e) {
|
async chatgpt (e) {
|
||||||
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
|
||||||
// await this.reply('ChatGpt私聊通道已关闭。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (e.isGroup) {
|
|
||||||
let cm = new ChatgptManagement()
|
|
||||||
let [groupWhitelist, groupBlacklist] = await cm.processList(Config.groupWhitelist, Config.groupBlacklist)
|
|
||||||
// logger.info('groupWhitelist:', Config.groupWhitelist, 'groupBlacklist', Config.groupBlacklist)
|
|
||||||
const whitelist = groupWhitelist.filter(group => group.trim())
|
|
||||||
if (whitelist.length > 0 && !whitelist.includes(e.group_id.toString())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const blacklist = groupBlacklist.filter(group => group.trim())
|
|
||||||
if (blacklist.length > 0 && blacklist.includes(e.group_id.toString())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let prompt
|
let prompt
|
||||||
if (this.toggleMode === 'at') {
|
if (this.toggleMode === 'at') {
|
||||||
if (!e.raw_message || e.msg?.startsWith('#')) {
|
if (!e.raw_message || e.msg?.startsWith('#')) {
|
||||||
|
|
@ -781,15 +752,24 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async abstractChat (e, prompt, use) {
|
async abstractChat (e, prompt, use) {
|
||||||
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
// 关闭私聊通道后不回复
|
||||||
if (userSetting) {
|
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
||||||
userSetting = JSON.parse(userSetting)
|
return false
|
||||||
if (Object.keys(userSetting).indexOf('useTTS') < 0) {
|
|
||||||
userSetting.useTTS = Config.defaultUseTTS
|
|
||||||
}
|
}
|
||||||
} else {
|
// 黑白名单过滤对话
|
||||||
userSetting = getDefaultReplySetting()
|
let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist)
|
||||||
|
if (whitelist.length > 0) {
|
||||||
|
if (e.isGroup && !whitelist.includes(e.group_id.toString())) return false
|
||||||
|
const list = whitelist.filter(elem => elem.startsWith('^')).map(elem => elem.slice(1))
|
||||||
|
if (!list.includes(e.sender.user_id.toString())) return false
|
||||||
}
|
}
|
||||||
|
if (blacklist.length > 0) {
|
||||||
|
if (e.isGroup && blacklist.includes(e.group_id.toString())) return false
|
||||||
|
const list = blacklist.filter(elem => elem.startsWith('^')).map(elem => elem.slice(1))
|
||||||
|
if (list.includes(e.sender.user_id.toString())) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let userSetting = await getUserReplySetting(this.e)
|
||||||
let useTTS = !!userSetting.useTTS
|
let useTTS = !!userSetting.useTTS
|
||||||
let speaker
|
let speaker
|
||||||
if (Config.ttsMode === 'vits-uma-genshin-honkai') {
|
if (Config.ttsMode === 'vits-uma-genshin-honkai') {
|
||||||
|
|
@ -869,10 +849,7 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const emotionFlag = await redis.get(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
const emotionFlag = await redis.get(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||||
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
let userReplySetting = await getUserReplySetting(this.e)
|
||||||
userReplySetting = !userReplySetting
|
|
||||||
? getDefaultReplySetting()
|
|
||||||
: JSON.parse(userReplySetting)
|
|
||||||
// 图片模式就不管了,降低抱歉概率
|
// 图片模式就不管了,降低抱歉概率
|
||||||
if (Config.ttsMode === 'azure' && Config.enhanceAzureTTSEmotion && userReplySetting.useTTS === true && await AzureTTS.getEmotionPrompt(e)) {
|
if (Config.ttsMode === 'azure' && Config.enhanceAzureTTSEmotion && userReplySetting.useTTS === true && await AzureTTS.getEmotionPrompt(e)) {
|
||||||
switch (emotionFlag) {
|
switch (emotionFlag) {
|
||||||
|
|
@ -1163,10 +1140,23 @@ export class chatgpt extends plugin {
|
||||||
await this.reply('合成语音发生错误~')
|
await this.reply('合成语音发生错误~')
|
||||||
}
|
}
|
||||||
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
||||||
const ttsRoleAzure = userReplySetting.ttsRoleAzure
|
if (speaker !== '随机') {
|
||||||
const isEn = AzureTTS.supportConfigurations.find(config => config.code === ttsRoleAzure)?.language.includes('en')
|
let languagePrefix = AzureTTS.supportConfigurations.find(config => config.code === speaker).languageDetail.charAt(0)
|
||||||
if (isEn) {
|
languagePrefix = languagePrefix.startsWith('E') ? '英' : languagePrefix
|
||||||
ttsResponse = (await translate(ttsResponse, '英')).replace('\n', '')
|
ttsResponse = (await translate(ttsResponse, languagePrefix)).replace('\n', '')
|
||||||
|
} else {
|
||||||
|
let role, languagePrefix
|
||||||
|
role = AzureTTS.supportConfigurations[Math.floor(Math.random() * supportConfigurations.length)]
|
||||||
|
speaker = role.code
|
||||||
|
languagePrefix = role.languageDetail.charAt(0).startsWith('E') ? '英' : role.languageDetail.charAt(0)
|
||||||
|
ttsResponse = (await translate(ttsResponse, languagePrefix)).replace('\n', '')
|
||||||
|
if (role?.emotion) {
|
||||||
|
const keys = Object.keys(role.emotion)
|
||||||
|
emotion = keys[Math.floor(Math.random() * keys.length)]
|
||||||
|
}
|
||||||
|
logger.info('using speaker: ' + speaker)
|
||||||
|
logger.info('using language: ' + languagePrefix)
|
||||||
|
logger.info('using emotion: ' + emotion)
|
||||||
}
|
}
|
||||||
let ssml = AzureTTS.generateSsml(ttsResponse, {
|
let ssml = AzureTTS.generateSsml(ttsResponse, {
|
||||||
speaker,
|
speaker,
|
||||||
|
|
@ -1177,6 +1167,7 @@ export class chatgpt extends plugin {
|
||||||
speaker
|
speaker
|
||||||
}, await ssml)
|
}, await ssml)
|
||||||
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
|
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
|
||||||
|
ttsResponse = (await translate(ttsResponse, '日')).replace('\n', '')
|
||||||
wav = await VoiceVoxTTS.generateAudio(ttsResponse, {
|
wav = await VoiceVoxTTS.generateAudio(ttsResponse, {
|
||||||
speaker
|
speaker
|
||||||
})
|
})
|
||||||
|
|
@ -1256,10 +1247,6 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async chatgpt1 (e) {
|
async chatgpt1 (e) {
|
||||||
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
|
||||||
await this.reply('ChatGpt私聊通道已关闭。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!Config.allowOtherMode) {
|
if (!Config.allowOtherMode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1279,10 +1266,6 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async chatgpt3 (e) {
|
async chatgpt3 (e) {
|
||||||
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
|
||||||
await this.reply('ChatGpt私聊通道已关闭。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!Config.allowOtherMode) {
|
if (!Config.allowOtherMode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1321,10 +1304,6 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async bing (e) {
|
async bing (e) {
|
||||||
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
|
||||||
await this.reply('ChatGpt私聊通道已关闭。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!Config.allowOtherMode) {
|
if (!Config.allowOtherMode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1344,10 +1323,6 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async claude (e) {
|
async claude (e) {
|
||||||
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
|
||||||
// await this.reply('ChatGpt私聊通道已关闭。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!Config.allowOtherMode) {
|
if (!Config.allowOtherMode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1365,11 +1340,8 @@ export class chatgpt extends plugin {
|
||||||
await this.abstractChat(e, prompt, 'claude')
|
await this.abstractChat(e, prompt, 'claude')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async xh (e) {
|
async xh (e) {
|
||||||
if (!e.isMaster && e.isPrivate && !Config.enablePrivateChat) {
|
|
||||||
// await this.reply('ChatGpt私聊通道已关闭。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!Config.allowOtherMode) {
|
if (!Config.allowOtherMode) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1650,7 +1622,7 @@ export class chatgpt extends plugin {
|
||||||
// 如果token曾经有异常,则清除异常
|
// 如果token曾经有异常,则清除异常
|
||||||
let Tokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
let Tokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
||||||
const TokenIndex = Tokens.findIndex(element => element.Token === abtrs.bingToken)
|
const TokenIndex = Tokens.findIndex(element => element.Token === abtrs.bingToken)
|
||||||
if (Tokens[TokenIndex].exception) {
|
if (TokenIndex > 0 && Tokens[TokenIndex].exception) {
|
||||||
delete Tokens[TokenIndex].exception
|
delete Tokens[TokenIndex].exception
|
||||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(Tokens))
|
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(Tokens))
|
||||||
}
|
}
|
||||||
|
|
@ -1672,21 +1644,22 @@ export class chatgpt extends plugin {
|
||||||
// 不减次数
|
// 不减次数
|
||||||
} else if (message && typeof message === 'string' && message.indexOf('UnauthorizedRequest') > -1) {
|
} else if (message && typeof message === 'string' && message.indexOf('UnauthorizedRequest') > -1) {
|
||||||
// token过期了
|
// token过期了
|
||||||
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
// let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
||||||
const badBingToken = bingTokens.findIndex(element => element.Token === bingToken)
|
// const badBingToken = bingTokens.findIndex(element => element.Token === bingToken)
|
||||||
// 可能是微软抽风,给三次机会
|
// // 可能是微软抽风,给三次机会
|
||||||
if (bingTokens[badBingToken].exception) {
|
// if (bingTokens[badBingToken].exception) {
|
||||||
if (bingTokens[badBingToken].exception <= 3) {
|
// if (bingTokens[badBingToken].exception <= 3) {
|
||||||
bingTokens[badBingToken].exception += 1
|
// bingTokens[badBingToken].exception += 1
|
||||||
} else {
|
// } else {
|
||||||
bingTokens[badBingToken].exception = 0
|
// bingTokens[badBingToken].exception = 0
|
||||||
bingTokens[badBingToken].State = '过期'
|
// bingTokens[badBingToken].State = '过期'
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
bingTokens[badBingToken].exception = 1
|
// bingTokens[badBingToken].exception = 1
|
||||||
}
|
// }
|
||||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
// await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
||||||
logger.warn(`token${bingToken}已过期`)
|
logger.warn(`token${bingToken}疑似不存在或已过期,再试试`)
|
||||||
|
retry = retry - 0.1
|
||||||
} else {
|
} else {
|
||||||
retry--
|
retry--
|
||||||
errorMessage = message === 'Timed out waiting for response. Try enabling debug mode to see more information.' ? (reply ? `${reply}\n不行了,我的大脑过载了,处理不过来了!` : '必应的小脑瓜不好使了,不知道怎么回答!') : message
|
errorMessage = message === 'Timed out waiting for response. Try enabling debug mode to see more information.' ? (reply ? `${reply}\n不行了,我的大脑过载了,处理不过来了!` : '必应的小脑瓜不好使了,不知道怎么回答!') : message
|
||||||
|
|
@ -1778,9 +1751,11 @@ export class chatgpt extends plugin {
|
||||||
if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
||||||
// 先发送设定
|
// 先发送设定
|
||||||
let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
|
let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
|
||||||
|
let emotion = await AzureTTS.getEmotionPrompt(e)
|
||||||
|
if (emotion) {
|
||||||
|
prompt = prompt + '\n' + emotion
|
||||||
|
}
|
||||||
await client.sendMessage(prompt, e)
|
await client.sendMessage(prompt, e)
|
||||||
// 处理可能由情绪参数导致的设定超限问题
|
|
||||||
await client.sendMessage(await AzureTTS.getEmotionPrompt(e), e)
|
|
||||||
logger.info('claudeFirst:', prompt)
|
logger.info('claudeFirst:', prompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1829,10 +1804,10 @@ export class chatgpt extends plugin {
|
||||||
timeoutMs: 120000
|
timeoutMs: 120000
|
||||||
// systemMessage: promptPrefix
|
// systemMessage: promptPrefix
|
||||||
}
|
}
|
||||||
if (Math.floor(Math.random() * 100) < 5) {
|
// if (Math.floor(Math.random() * 100) < 5) {
|
||||||
// 小概率再次发送系统消息
|
// // 小概率再次发送系统消息
|
||||||
option.systemMessage = promptPrefix
|
// option.systemMessage = promptPrefix
|
||||||
}
|
// }
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
option = Object.assign(option, conversation)
|
option = Object.assign(option, conversation)
|
||||||
}
|
}
|
||||||
|
|
@ -2098,7 +2073,11 @@ export class chatgpt extends plugin {
|
||||||
async function getAvailableBingToken (conversation, throttled = []) {
|
async function getAvailableBingToken (conversation, throttled = []) {
|
||||||
let allThrottled = false
|
let allThrottled = false
|
||||||
if (!await redis.get('CHATGPT:BING_TOKENS')) {
|
if (!await redis.get('CHATGPT:BING_TOKENS')) {
|
||||||
throw new Error('未绑定Bing Cookie,请使用#chatgpt设置必应token命令绑定Bing Cookie')
|
return {
|
||||||
|
bingToken: null,
|
||||||
|
allThrottled
|
||||||
|
}
|
||||||
|
// throw new Error('未绑定Bing Cookie,请使用#chatgpt设置必应token命令绑定Bing Cookie')
|
||||||
}
|
}
|
||||||
|
|
||||||
let bingToken = ''
|
let bingToken = ''
|
||||||
|
|
@ -2128,7 +2107,11 @@ async function getAvailableBingToken (conversation, throttled = []) {
|
||||||
})
|
})
|
||||||
bingToken = minElement.Token
|
bingToken = minElement.Token
|
||||||
} else {
|
} else {
|
||||||
throw new Error('全部Token均已失效,暂时无法使用')
|
// throw new Error('全部Token均已失效,暂时无法使用')
|
||||||
|
return {
|
||||||
|
bingToken: null,
|
||||||
|
allThrottled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Config.toneStyle != 'Sydney' && Config.toneStyle != 'Custom') {
|
if (Config.toneStyle != 'Sydney' && Config.toneStyle != 'Custom') {
|
||||||
// bing 下,需要保证同一对话使用同一账号的token
|
// bing 下,需要保证同一对话使用同一账号的token
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ export class dalle extends plugin {
|
||||||
this.reply('请提供绘图prompt')
|
this.reply('请提供绘图prompt')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
this.reply('在画了,请稍等……')
|
||||||
let bingToken = ''
|
let bingToken = ''
|
||||||
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
|
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
|
||||||
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ import { generateAudio } from '../utils/tts.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { makeForwardMsg, mkdirs } from '../utils/common.js'
|
import { getImageOcrText, getImg, makeForwardMsg, mkdirs } from '../utils/common.js'
|
||||||
import uploadRecord from '../utils/uploadRecord.js'
|
import uploadRecord from '../utils/uploadRecord.js'
|
||||||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||||||
import { translate, translateLangSupports } from '../utils/translate.js'
|
import { translate, translateLangSupports } from '../utils/translate.js'
|
||||||
|
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||||
|
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||||
|
|
||||||
let useSilk = false
|
let useSilk = false
|
||||||
try {
|
try {
|
||||||
await import('node-silk')
|
await import('node-silk')
|
||||||
|
|
@ -17,7 +20,7 @@ try {
|
||||||
useSilk = false
|
useSilk = false
|
||||||
}
|
}
|
||||||
export class Entertainment extends plugin {
|
export class Entertainment extends plugin {
|
||||||
constructor(e) {
|
constructor (e) {
|
||||||
super({
|
super({
|
||||||
name: 'ChatGPT-Plugin 娱乐小功能',
|
name: 'ChatGPT-Plugin 娱乐小功能',
|
||||||
dsc: '让你的聊天更有趣!现已支持主动打招呼、表情合成、群聊词云统计、文本翻译与图片ocr小功能!',
|
dsc: '让你的聊天更有趣!现已支持主动打招呼、表情合成、群聊词云统计、文本翻译与图片ocr小功能!',
|
||||||
|
|
@ -42,6 +45,10 @@ export class Entertainment extends plugin {
|
||||||
reg: '^#?(今日词云|群友在聊什么)$',
|
reg: '^#?(今日词云|群友在聊什么)$',
|
||||||
fnc: 'wordcloud'
|
fnc: 'wordcloud'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#(|最新)词云(\\d{1,2}h{0,1}|)$',
|
||||||
|
fnc: 'wordcloud_latest'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)',
|
reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)',
|
||||||
fnc: 'translate'
|
fnc: 'translate'
|
||||||
|
|
@ -56,12 +63,13 @@ export class Entertainment extends plugin {
|
||||||
{
|
{
|
||||||
// 设置十分钟左右的浮动
|
// 设置十分钟左右的浮动
|
||||||
cron: '0 ' + Math.ceil(Math.random() * 10) + ' 7-23/' + Config.helloInterval + ' * * ?',
|
cron: '0 ' + Math.ceil(Math.random() * 10) + ' 7-23/' + Config.helloInterval + ' * * ?',
|
||||||
// cron: '0 ' + '*/' + Config.helloInterval + ' * * * ?',
|
// cron: '*/2 * * * *',
|
||||||
name: 'ChatGPT主动随机说话',
|
name: 'ChatGPT主动随机说话',
|
||||||
fnc: this.sendRandomMessage.bind(this)
|
fnc: this.sendRandomMessage.bind(this)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async ocr (e) {
|
async ocr (e) {
|
||||||
let replyMsg
|
let replyMsg
|
||||||
let imgOcrText = await getImageOcrText(e)
|
let imgOcrText = await getImageOcrText(e)
|
||||||
|
|
@ -72,7 +80,8 @@ export class Entertainment extends plugin {
|
||||||
replyMsg = await makeForwardMsg(e, imgOcrText, 'OCR结果')
|
replyMsg = await makeForwardMsg(e, imgOcrText, 'OCR结果')
|
||||||
await this.reply(replyMsg, e.isGroup)
|
await this.reply(replyMsg, e.isGroup)
|
||||||
}
|
}
|
||||||
async translate(e) {
|
|
||||||
|
async translate (e) {
|
||||||
const translateLangLabels = translateLangSupports.map(item => item.label).join(',')
|
const translateLangLabels = translateLangSupports.map(item => item.label).join(',')
|
||||||
const translateLangLabelAbbrS = translateLangSupports.map(item => item.abbr).join(',')
|
const translateLangLabelAbbrS = translateLangSupports.map(item => item.abbr).join(',')
|
||||||
if (e.msg.trim() === '#chatgpt翻译帮助') {
|
if (e.msg.trim() === '#chatgpt翻译帮助') {
|
||||||
|
|
@ -167,7 +176,8 @@ ${translateLangLabels}
|
||||||
await this.reply(result, e.isGroup)
|
await this.reply(result, e.isGroup)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
async wordcloud(e) {
|
|
||||||
|
async wordcloud (e) {
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let groupId = e.group_id
|
let groupId = e.group_id
|
||||||
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
|
|
@ -176,7 +186,7 @@ ${translateLangLabels}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
await e.reply('在统计啦,请稍等...')
|
await e.reply('在统计啦,请稍等...')
|
||||||
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', {EX: 600})
|
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
||||||
try {
|
try {
|
||||||
await makeWordcloud(e, e.group_id)
|
await makeWordcloud(e, e.group_id)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -188,8 +198,39 @@ ${translateLangLabels}
|
||||||
await e.reply('请在群里发送此命令')
|
await e.reply('请在群里发送此命令')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async wordcloud_latest(e) {
|
||||||
|
if (e.isGroup) {
|
||||||
|
let groupId = e.group_id
|
||||||
|
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
|
if (lock) {
|
||||||
|
await e.reply('别着急,上次统计还没完呢')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
async combineEmoj(e) {
|
const regExp = /词云(\d{0,2})(|h)/
|
||||||
|
const match = e.msg.trim().match(regExp)
|
||||||
|
const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h
|
||||||
|
|
||||||
|
if(duration > 24) {
|
||||||
|
await e.reply('最多只能统计24小时内的记录哦')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
await e.reply('在统计啦,请稍等...')
|
||||||
|
|
||||||
|
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', {EX: 600})
|
||||||
|
try {
|
||||||
|
await makeWordcloud(e, e.group_id, duration)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
await e.reply(err)
|
||||||
|
}
|
||||||
|
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
|
} else {
|
||||||
|
await e.reply('请在群里发送此命令')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async combineEmoj (e) {
|
||||||
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
|
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
|
||||||
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
|
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
|
||||||
if (left === right) {
|
if (left === right) {
|
||||||
|
|
@ -237,7 +278,7 @@ ${translateLangLabels}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage(e) {
|
async sendMessage (e) {
|
||||||
if (e.msg.match(/^#chatgpt打招呼帮助/) !== null) {
|
if (e.msg.match(/^#chatgpt打招呼帮助/) !== null) {
|
||||||
await this.reply('设置主动打招呼的群聊名单,群号之间以,隔开,参数之间空格隔开\n' +
|
await this.reply('设置主动打招呼的群聊名单,群号之间以,隔开,参数之间空格隔开\n' +
|
||||||
'#chatgpt打招呼+群号:立即在指定群聊发起打招呼' +
|
'#chatgpt打招呼+群号:立即在指定群聊发起打招呼' +
|
||||||
|
|
@ -268,23 +309,71 @@ ${translateLangLabels}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRandomMessage() {
|
async sendRandomMessage () {
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
logger.info('开始处理:ChatGPT随机打招呼。')
|
logger.info('开始处理:ChatGPT随机打招呼。')
|
||||||
}
|
}
|
||||||
let toSend = Config.initiativeChatGroups || []
|
let toSend = Config.initiativeChatGroups || []
|
||||||
for (let i = 0; i < toSend.length; i++) {
|
for (const element of toSend) {
|
||||||
if (!toSend[i]) {
|
if (!element) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let groupId = parseInt(toSend[i])
|
let groupId = parseInt(element)
|
||||||
if (Bot.getGroupList().get(groupId)) {
|
if (Bot.getGroupList().get(groupId)) {
|
||||||
// 打招呼概率
|
// 打招呼概率
|
||||||
if (Math.floor(Math.random() * 100) < Config.helloProbability) {
|
if (Math.floor(Math.random() * 100) < Config.helloProbability) {
|
||||||
let message = await generateHello()
|
let message = await generateHello()
|
||||||
logger.info(`打招呼给群聊${groupId}:` + message)
|
logger.info(`打招呼给群聊${groupId}:` + message)
|
||||||
if (Config.defaultUseTTS) {
|
if (Config.defaultUseTTS) {
|
||||||
let audio = await generateAudio(message, Config.defaultTTSRole)
|
let audio
|
||||||
|
const [defaultVitsTTSRole, defaultAzureTTSRole, defaultVoxTTSRole] = [Config.defaultTTSRole, Config.azureTTSSpeaker, Config.voicevoxTTSSpeaker]
|
||||||
|
let ttsSupportKinds = []
|
||||||
|
if (Config.azureTTSKey) ttsSupportKinds.push(1)
|
||||||
|
if (Config.ttsSpace) ttsSupportKinds.push(2)
|
||||||
|
if (Config.voicevoxSpace) ttsSupportKinds.push(3)
|
||||||
|
if (!ttsSupportKinds.length) {
|
||||||
|
logger.warn('没有配置任何语音服务!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const randomIndex = Math.floor(Math.random() * ttsSupportKinds.length)
|
||||||
|
switch (ttsSupportKinds[randomIndex]) {
|
||||||
|
case 1 : {
|
||||||
|
const isEn = AzureTTS.supportConfigurations.find(config => config.code === defaultAzureTTSRole)?.language.includes('en')
|
||||||
|
if (isEn) {
|
||||||
|
message = (await translate(message, '英')).replace('\n', '')
|
||||||
|
}
|
||||||
|
audio = await AzureTTS.generateAudio(message, {
|
||||||
|
defaultAzureTTSRole
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 2 : {
|
||||||
|
if (Config.autoJapanese) {
|
||||||
|
try {
|
||||||
|
message = await translate(message, '日')
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
audio = await generateAudio(message, defaultVitsTTSRole, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 3 : {
|
||||||
|
message = (await translate(message, '日')).replace('\n', '')
|
||||||
|
try {
|
||||||
|
audio = await VoiceVoxTTS.generateAudio(message, {
|
||||||
|
speaker: defaultVoxTTSRole
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
if (useSilk) {
|
if (useSilk) {
|
||||||
await Bot.sendGroupMsg(groupId, await uploadRecord(audio))
|
await Bot.sendGroupMsg(groupId, await uploadRecord(audio))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -302,7 +391,7 @@ ${translateLangLabels}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSentMessage(e) {
|
async handleSentMessage (e) {
|
||||||
const addReg = /^#chatgpt设置打招呼[::]?\s?(\S+)(?:\s+(\d+))?(?:\s+(\d+))?$/
|
const addReg = /^#chatgpt设置打招呼[::]?\s?(\S+)(?:\s+(\d+))?(?:\s+(\d+))?$/
|
||||||
const delReg = /^#chatgpt删除打招呼[::\s]?(\S+)/
|
const delReg = /^#chatgpt删除打招呼[::\s]?(\S+)/
|
||||||
const checkReg = /^#chatgpt查看打招呼$/
|
const checkReg = /^#chatgpt查看打招呼$/
|
||||||
|
|
@ -378,51 +467,3 @@ ${translateLangLabels}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getImg (e) {
|
|
||||||
// 取消息中的图片、at的头像、回复的图片,放入e.img
|
|
||||||
if (e.at && !e.source) {
|
|
||||||
e.img = [`https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.at}`]
|
|
||||||
}
|
|
||||||
if (e.source) {
|
|
||||||
let reply
|
|
||||||
if (e.isGroup) {
|
|
||||||
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
|
||||||
} else {
|
|
||||||
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
|
||||||
}
|
|
||||||
if (reply) {
|
|
||||||
let i = []
|
|
||||||
for (let val of reply) {
|
|
||||||
if (val.type === 'image') {
|
|
||||||
i.push(val.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.img = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e.img
|
|
||||||
}
|
|
||||||
export async function getImageOcrText (e) {
|
|
||||||
const img = await getImg(e)
|
|
||||||
if (img) {
|
|
||||||
try {
|
|
||||||
let resultArr = []
|
|
||||||
let eachImgRes = ''
|
|
||||||
for (let i in img) {
|
|
||||||
const imgOCR = await Bot.imageOcr(img[i])
|
|
||||||
for (let text of imgOCR.wordslist) {
|
|
||||||
eachImgRes += (`${text?.words} \n`)
|
|
||||||
}
|
|
||||||
if (eachImgRes) resultArr.push(eachImgRes)
|
|
||||||
eachImgRes = ''
|
|
||||||
}
|
|
||||||
// logger.warn('resultArr', resultArr)
|
|
||||||
return resultArr
|
|
||||||
} catch (err) {
|
|
||||||
return false
|
|
||||||
// logger.error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -331,7 +331,11 @@ export class help extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async help (e) {
|
async help (e) {
|
||||||
if (!Config.oldview) { await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/help/`, { Viewport: { width: 800, height: 600 } }) } else { await render(e, 'chatgpt-plugin', 'help/index', { helpData, version }) }
|
if (Config.newhelp && !Config.oldview) {
|
||||||
|
await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/help/`, { Viewport: { width: 800, height: 600 } })
|
||||||
|
} else {
|
||||||
|
await render(e, 'chatgpt-plugin', 'help/index', { helpData, version })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async newHelp (e) {
|
async newHelp (e) {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,14 @@ import { exec } from 'child_process'
|
||||||
import {
|
import {
|
||||||
checkPnpm,
|
checkPnpm,
|
||||||
formatDuration,
|
formatDuration,
|
||||||
parseDuration,
|
getAzureRoleList,
|
||||||
getPublicIP,
|
getPublicIP,
|
||||||
renderUrl,
|
getUserReplySetting,
|
||||||
|
getVitsRoleList,
|
||||||
|
getVoicevoxRoleList,
|
||||||
makeForwardMsg,
|
makeForwardMsg,
|
||||||
getDefaultReplySetting
|
parseDuration, processList,
|
||||||
|
renderUrl
|
||||||
} from '../utils/common.js'
|
} from '../utils/common.js'
|
||||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||||
import { convertSpeaker, speakers as vitsRoleList } from '../utils/tts.js'
|
import { convertSpeaker, speakers as vitsRoleList } from '../utils/tts.js'
|
||||||
|
|
@ -16,9 +19,11 @@ import md5 from 'md5'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import loader from '../../../lib/plugins/loader.js'
|
import loader from '../../../lib/plugins/loader.js'
|
||||||
import { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
|
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
|
||||||
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
|
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
|
||||||
|
|
||||||
let isWhiteList = true
|
let isWhiteList = true
|
||||||
|
let isSetGroup = true
|
||||||
export class ChatgptManagement extends plugin {
|
export class ChatgptManagement extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
super({
|
super({
|
||||||
|
|
@ -130,17 +135,17 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'versionChatGPTPlugin'
|
fnc: 'versionChatGPTPlugin'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(本群)?(群\\d+)?闭嘴',
|
reg: '^#chatgpt(本群)?(群\\d+)?(关闭|闭嘴|关机|休眠|下班)',
|
||||||
fnc: 'shutUp',
|
fnc: 'shutUp',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(本群)?(群\\d+)?(张嘴|开口|说话|上班)',
|
reg: '^#chatgpt(本群)?(群\\d+)?(开启|启动|激活|张嘴|开口|说话|上班)',
|
||||||
fnc: 'openMouth',
|
fnc: 'openMouth',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt查看闭嘴',
|
reg: '^#chatgpt查看?(关闭|闭嘴|关机|休眠|下班|休眠)列表',
|
||||||
fnc: 'listShutUp',
|
fnc: 'listShutUp',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
|
@ -180,7 +185,7 @@ export class ChatgptManagement extends plugin {
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(打开|关闭|设置)?全局((图片模式|语音模式|(语音角色|角色语音|角色).*)|回复帮助)$',
|
reg: '^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|((azure|vits|vox)?语音角色|角色语音|角色).*)|回复帮助)$',
|
||||||
fnc: 'setDefaultReplySetting',
|
fnc: 'setDefaultReplySetting',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
|
@ -202,18 +207,18 @@ export class ChatgptManagement extends plugin {
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(设置|添加)群聊[白黑]名单$',
|
reg: '^#chatgpt(设置|添加)对话[白黑]名单$',
|
||||||
fnc: 'setList',
|
fnc: 'setList',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt查看群聊[白黑]名单$',
|
reg: '^#chatgpt(查看)?对话[白黑]名单(帮助)?$',
|
||||||
fnc: 'checkGroupList',
|
fnc: 'checkList',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(删除|移除)群聊[白黑]名单$',
|
reg: '^#chatgpt(删除|移除)对话[白黑]名单$',
|
||||||
fnc: 'delGroupList',
|
fnc: 'delList',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -244,48 +249,89 @@ export class ChatgptManagement extends plugin {
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt角色列表$',
|
reg: '^#(chatgpt)?(vits|azure|vox)?语音(角色列表|服务)$',
|
||||||
fnc: 'getTTSRoleList'
|
fnc: 'getTTSRoleList'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt设置后台(刷新|refresh)(t|T)oken$',
|
reg: '^#chatgpt设置后台(刷新|refresh)(t|T)oken$',
|
||||||
fnc: 'setOpenAIPlatformToken'
|
fnc: 'setOpenAIPlatformToken'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#(chatgpt)?查看回复设置$',
|
||||||
|
fnc: 'viewUserSetting'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async viewUserSetting (e) {
|
||||||
|
const userSetting = await getUserReplySetting(this.e)
|
||||||
|
const replyMsg = `${this.e.sender.user_id}的回复设置:
|
||||||
|
图片模式: ${userSetting.usePicture === true ? '开启' : '关闭'}
|
||||||
|
语音模式: ${userSetting.useTTS === true ? '开启' : '关闭'}
|
||||||
|
Vits语音角色: ${userSetting.ttsRole}
|
||||||
|
Azure语音角色: ${userSetting.ttsRoleAzure}
|
||||||
|
VoiceVox语音角色: ${userSetting.ttsRoleVoiceVox}
|
||||||
|
${userSetting.useTTS === true ? '当前语音模式为' + Config.ttsMode : ''}`
|
||||||
|
await this.reply(replyMsg.replace(/\n\s*$/, ''), e.isGroup)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
async getTTSRoleList (e) {
|
async getTTSRoleList (e) {
|
||||||
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
const matchCommand = e.msg.match(/^#(chatgpt)?(vits|azure|vox)?语音(服务|角色列表)/)
|
||||||
userReplySetting = !userReplySetting
|
if (matchCommand[3] === '服务') {
|
||||||
? getDefaultReplySetting()
|
await this.reply(`当前支持vox、vits、azure语音服务,可使用'#(vox|azure|vits)语音角色列表'查看支持的语音角色。
|
||||||
: JSON.parse(userReplySetting)
|
|
||||||
if (!userReplySetting.useTTS) return
|
vits语音:主要有赛马娘,原神中文,原神日语,崩坏 3 的音色、结果有随机性,语调可能很奇怪。
|
||||||
|
|
||||||
|
vox语音:Voicevox 是一款由日本 DeNA 开发的语音合成软件,它可以将文本转换为自然流畅的语音。Voicevox 支持多种语言和声音,可以用于制作各种语音内容,如动画、游戏、广告等。Voicevox 还提供了丰富的调整选项,可以调整声音的音调、速度、音量等参数,以满足不同需求。除了桌面版软件外,Voicevox 还提供了 Web 版本和 API 接口,方便开发者在各种平台上使用。
|
||||||
|
|
||||||
|
azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,它可以帮助开发者将语音转换为文本、将文本转换为语音、实现自然语言理解和对话等功能。Azure 语音支持多种语言和声音,可以用于构建各种语音应用程序,如智能客服、语音助手、自动化电话系统等。Azure 语音还提供了丰富的 API 和 SDK,方便开发者在各种平台上集成使用。
|
||||||
|
`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let userReplySetting = await getUserReplySetting(this.e)
|
||||||
|
if (!userReplySetting.useTTS && matchCommand[2] === undefined) {
|
||||||
|
await this.reply('当前不是语音模式,如果想查看不同语音模式下支持的角色列表,可使用"#(vox|azure|vits)语音角色列表"查看')
|
||||||
|
return false
|
||||||
|
}
|
||||||
let ttsMode = Config.ttsMode
|
let ttsMode = Config.ttsMode
|
||||||
let roleList = []
|
let roleList = []
|
||||||
if (ttsMode === 'vits-uma-genshin-honkai') {
|
if (matchCommand[2] === 'vits') {
|
||||||
const [firstHalf, secondHalf] = [vitsRoleList.slice(0, Math.floor(vitsRoleList.length / 2)).join('、'), vitsRoleList.slice(Math.floor(vitsRoleList.length / 2)).join('、')]
|
roleList = getVitsRoleList(this.e)
|
||||||
const [chunk1, chunk2] = [firstHalf.match(/[^、]+(?:、[^、]+){0,30}/g), secondHalf.match(/[^、]+(?:、[^、]+){0,30}/g)]
|
} else if (matchCommand[2] === 'vox') {
|
||||||
const list = [await makeForwardMsg(e, chunk1, `${Config.ttsMode}角色列表1`), await makeForwardMsg(e, chunk2, `${Config.ttsMode}角色列表2`)]
|
roleList = getVoicevoxRoleList()
|
||||||
roleList = await makeForwardMsg(e, list, `${Config.ttsMode}角色列表`)
|
} else if (matchCommand[2] === 'azure') {
|
||||||
await this.reply(roleList)
|
roleList = getAzureRoleList()
|
||||||
return
|
} else if (matchCommand[2] === undefined) {
|
||||||
} else if (ttsMode === 'voicevox') {
|
switch (ttsMode) {
|
||||||
roleList = voxRoleList.map(item => item.name).join('、')
|
case 'vits-uma-genshin-honkai':
|
||||||
} else if (ttsMode === 'azure') {
|
roleList = getVitsRoleList(this.e)
|
||||||
roleList = azureRoleList.map(item => item.name).join('、')
|
break
|
||||||
|
case 'voicevox':
|
||||||
|
roleList = getVoicevoxRoleList()
|
||||||
|
break
|
||||||
|
case 'azure':
|
||||||
|
if (matchCommand[2] === 'azure') {
|
||||||
|
roleList = getAzureRoleList()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.reply('设置错误,请使用"#chatgpt语音服务"查看支持的语音配置')
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if (roleList.length > 300) {
|
if (roleList.length > 300) {
|
||||||
let chunks = roleList.match(/[^、]+(?:、[^、]+){0,30}/g)
|
let chunks = roleList.match(/[^、]+(?:、[^、]+){0,30}/g)
|
||||||
roleList = await makeForwardMsg(e, chunks, `${Config.ttsMode}角色列表`)
|
roleList = await makeForwardMsg(e, chunks, `${Config.ttsMode}语音角色列表`)
|
||||||
}
|
}
|
||||||
await this.reply(roleList)
|
await this.reply(roleList)
|
||||||
}
|
}
|
||||||
|
|
||||||
async ttsSwitch (e) {
|
async ttsSwitch (e) {
|
||||||
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
let userReplySetting = await getUserReplySetting(this.e)
|
||||||
userReplySetting = !userReplySetting
|
|
||||||
? getDefaultReplySetting()
|
|
||||||
: JSON.parse(userReplySetting)
|
|
||||||
if (!userReplySetting.useTTS) {
|
if (!userReplySetting.useTTS) {
|
||||||
let replyMsg
|
let replyMsg
|
||||||
if (userReplySetting.usePicture) {
|
if (userReplySetting.usePicture) {
|
||||||
|
|
@ -312,7 +358,6 @@ export class ChatgptManagement extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async commandHelp (e) {
|
async commandHelp (e) {
|
||||||
if (!this.e.isMaster) { return this.reply('你没有权限') }
|
|
||||||
if (/^#(chatgpt)?指令表帮助$/.exec(e.msg.trim())) {
|
if (/^#(chatgpt)?指令表帮助$/.exec(e.msg.trim())) {
|
||||||
await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' +
|
await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' +
|
||||||
'#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' +
|
'#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' +
|
||||||
|
|
@ -346,8 +391,8 @@ export class ChatgptManagement extends plugin {
|
||||||
commandSet.push({ name, dsc: plugin.dsc, rule })
|
commandSet.push({ name, dsc: plugin.dsc, rule })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.msg.includes('搜索')) {
|
if (/^#(chatgpt)?指令表搜索(.+)/.test(e.msg.trim())) {
|
||||||
let cmd = e.msg.trim().match(/^#(chatgpt)?(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助|搜索(.+))?/)[4]
|
let cmd = e.msg.trim().match(/#(chatgpt)?指令表搜索(.+)/)[2]
|
||||||
if (!cmd) {
|
if (!cmd) {
|
||||||
await this.reply('(⊙ˍ⊙)')
|
await this.reply('(⊙ˍ⊙)')
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -393,134 +438,124 @@ export class ChatgptManagement extends plugin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 对原始黑白名单进行去重和去除无效群号处理
|
|
||||||
* @param whitelist
|
|
||||||
* @param blacklist
|
|
||||||
* @returns {Promise<any[][]>}
|
|
||||||
*/
|
|
||||||
async processList (whitelist, blacklist) {
|
|
||||||
let groupWhitelist = Array.isArray(whitelist)
|
|
||||||
? whitelist
|
|
||||||
: String(whitelist).split(/[,,]/)
|
|
||||||
let groupBlacklist = !Array.isArray(blacklist)
|
|
||||||
? blacklist
|
|
||||||
: String(blacklist).split(/[,,]/)
|
|
||||||
groupWhitelist = Array.from(new Set(groupWhitelist)).filter(value => /^[1-9]\d{8,9}$/.test(value))
|
|
||||||
groupBlacklist = Array.from(new Set(groupBlacklist)).filter(value => /^[1-9]\d{8,9}$/.test(value))
|
|
||||||
return [groupWhitelist, groupBlacklist]
|
|
||||||
}
|
|
||||||
|
|
||||||
async setList (e) {
|
async setList (e) {
|
||||||
this.setContext('saveList')
|
this.setContext('saveList')
|
||||||
isWhiteList = e.msg.includes('白')
|
isWhiteList = e.msg.includes('白')
|
||||||
const listType = isWhiteList ? '白名单' : '黑名单'
|
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||||
await this.reply(`请发送需要设置的群聊${listType},群号间使用,隔开`, e.isGroup)
|
await this.reply(`请发送需要添加的${listType}号码,默认设置为添加群号,需要添加QQ号时在前面添加^(例如:^123456)。`, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveList (e) {
|
async saveList (e) {
|
||||||
if (!this.e.msg) return
|
if (!this.e.msg) return
|
||||||
const listType = isWhiteList ? '白名单' : '黑名单'
|
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||||
const inputMatch = this.e.msg.match(/\d+/g)
|
const regex = /^\^?[1-9]\d{5,9}$/
|
||||||
let [groupWhitelist, groupBlacklist] = await this.processList(Config.groupWhitelist, Config.groupBlacklist)
|
const wrongInput = []
|
||||||
let inputList = Array.isArray(inputMatch) ? this.e.msg.match(/\d+/g).filter(value => /^[1-9]\d{8,9}$/.test(value)) : []
|
const inputSet = new Set()
|
||||||
|
const inputList = this.e.msg.split(/[,,]/).reduce((acc, value) => {
|
||||||
|
if (value.length > 11 || !regex.test(value)) {
|
||||||
|
wrongInput.push(value)
|
||||||
|
} else if (!inputSet.has(value)) {
|
||||||
|
inputSet.add(value)
|
||||||
|
acc.push(value)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
if (!inputList.length) {
|
if (!inputList.length) {
|
||||||
await this.reply('无效输入,请在检查群号是否正确后重新输入', e.isGroup)
|
let replyMsg = '名单更新失败,请在检查输入是否正确后重新输入。'
|
||||||
|
if (wrongInput.length) replyMsg += `\n${wrongInput.length ? '检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||||
|
await this.reply(replyMsg, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
inputList = Array.from(new Set(inputList))
|
let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist)
|
||||||
let whitelist = []
|
whitelist = [...inputList, ...whitelist]
|
||||||
let blacklist = []
|
blacklist = [...inputList, ...blacklist]
|
||||||
for (const element of inputList) {
|
if (listType === '对话白名单') {
|
||||||
if (listType === '白名单') {
|
Config.whitelist = Array.from(new Set(whitelist))
|
||||||
groupWhitelist = groupWhitelist.filter(item => item !== element)
|
|
||||||
whitelist.push(element)
|
|
||||||
} else {
|
} else {
|
||||||
groupBlacklist = groupBlacklist.filter(item => item !== element)
|
Config.blacklist = Array.from(new Set(blacklist))
|
||||||
blacklist.push(element)
|
|
||||||
}
|
}
|
||||||
}
|
let replyMsg = `${listType}已更新,可通过\n"#chatgpt查看${listType}" 查看最新名单\n"#chatgpt移除${listType}" 管理名单${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||||
if (!(whitelist.length || blacklist.length)) {
|
|
||||||
await this.reply('无效输入,请在检查群号是否正确或重复添加后重新输入', e.isGroup)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
if (listType === '白名单') {
|
|
||||||
Config.groupWhitelist = groupWhitelist
|
|
||||||
.filter(group => group !== '')
|
|
||||||
.concat(whitelist)
|
|
||||||
} else {
|
|
||||||
Config.groupBlacklist = groupBlacklist
|
|
||||||
.filter(group => group !== '')
|
|
||||||
.concat(blacklist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let replyMsg = `群聊${listType}已更新,可通过\n'#chatgpt查看群聊${listType}'查看最新名单\n'#chatgpt移除群聊${listType}'管理名单`
|
|
||||||
if (e.isPrivate) {
|
if (e.isPrivate) {
|
||||||
replyMsg += `\n当前群聊${listType}为:${listType === '白名单' ? Config.groupWhitelist : Config.groupBlacklist}`
|
replyMsg += `\n当前${listType}为:${listType === '对话白名单' ? Config.whitelist : Config.blacklist}`
|
||||||
}
|
}
|
||||||
await this.reply(replyMsg, e.isGroup)
|
await this.reply(replyMsg, e.isGroup)
|
||||||
this.finish('saveList')
|
this.finish('saveList')
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkGroupList (e) {
|
async checkList (e) {
|
||||||
|
if (e.msg.includes('帮助')) {
|
||||||
|
await this.reply('默认设置为添加群号,需要拉黑QQ号时在前面添加^(例如:^123456),可一次性混合输入多个配置号码,错误项会自动忽略。具体使用指令可通过 "#指令表搜索名单" 查看,白名单优先级高于黑名单。')
|
||||||
|
return true
|
||||||
|
}
|
||||||
isWhiteList = e.msg.includes('白')
|
isWhiteList = e.msg.includes('白')
|
||||||
const list = isWhiteList ? Config.groupWhitelist : Config.groupBlacklist
|
const list = isWhiteList ? Config.whitelist : Config.blacklist
|
||||||
const listType = isWhiteList ? '白名单' : '黑名单'
|
const listType = isWhiteList ? '白名单' : '黑名单'
|
||||||
const replyMsg = list.length ? `当前群聊${listType}为:${list}` : `当前没有设置任何群聊${listType}`
|
const replyMsg = list.length ? `当前${listType}为:${list}` : `当前没有设置任何${listType}`
|
||||||
await this.reply(replyMsg, e.isGroup)
|
await this.reply(replyMsg, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async delGroupList (e) {
|
async delList (e) {
|
||||||
isWhiteList = e.msg.includes('白')
|
isWhiteList = e.msg.includes('白')
|
||||||
const listType = isWhiteList ? '白名单' : '黑名单'
|
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||||
let replyMsg = ''
|
let replyMsg = ''
|
||||||
if (Config.groupWhitelist.length === 0 && Config.groupBlacklist.length === 0) {
|
if (Config.whitelist.length === 0 && Config.blacklist.length === 0) {
|
||||||
replyMsg = `当前群聊(白|黑)名单为空,请先添加${listType}吧~`
|
replyMsg = '当前对话(白|黑)名单都是空哒,请先添加吧~'
|
||||||
} else if ((listType === '白名单' && !Config.groupWhitelist.length) || (listType === '黑名单' && !Config.groupBlacklist.length)) {
|
} else if ((listType === '对话白名单' && !Config.whitelist.length) || (listType === '对话黑名单' && !Config.blacklist.length)) {
|
||||||
replyMsg = `当前群聊${listType}为空,请先添加吧~`
|
replyMsg = `当前${listType}为空,请先添加吧~`
|
||||||
}
|
}
|
||||||
if (replyMsg) {
|
if (replyMsg) {
|
||||||
await this.reply(replyMsg, e.isGroup)
|
await this.reply(replyMsg, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
this.setContext('confirmDelGroup')
|
this.setContext('confirmDelList')
|
||||||
await this.reply(`请发送需要删除的群聊${listType},群号间使用,隔开。输入‘全部删除’清空${listType}。`, e.isGroup)
|
await this.reply(`请发送需要删除的${listType}号码,号码间使用,隔开。输入‘全部删除’清空${listType}。${e.isPrivate ? '\n当前' + listType + '为:' + (listType === '对话白名单' ? Config.whitelist : Config.blacklist) : ''}`, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmDelGroup (e) {
|
async confirmDelList (e) {
|
||||||
if (!this.e.msg) return
|
if (!this.e.msg) return
|
||||||
const isAllDeleted = this.e.msg.trim() === '全部删除'
|
const isAllDeleted = this.e.msg.trim() === '全部删除'
|
||||||
const groupNumRegex = /^[1-9]\d{8,9}$/
|
const regex = /^\^?[1-9]\d{5,9}$/
|
||||||
const inputMatch = this.e.msg.match(/\d+/g)
|
const wrongInput = []
|
||||||
const validGroups = Array.isArray(inputMatch) ? inputMatch.filter(groupNum => groupNumRegex.test(groupNum)) : []
|
const inputSet = new Set()
|
||||||
let [groupWhitelist, groupBlacklist] = await this.processList(Config.groupWhitelist, Config.groupBlacklist)
|
const inputList = this.e.msg.split(/[,,]/).reduce((acc, value) => {
|
||||||
if (isAllDeleted) {
|
if (value.length > 11 || !regex.test(value)) {
|
||||||
Config.groupWhitelist = isWhiteList ? [] : groupWhitelist
|
wrongInput.push(value)
|
||||||
Config.groupBlacklist = !isWhiteList ? [] : groupBlacklist
|
} else if (!inputSet.has(value)) {
|
||||||
} else {
|
inputSet.add(value)
|
||||||
if (!validGroups.length) {
|
acc.push(value)
|
||||||
await this.reply('无效输入,请在检查群号是否正确后重新输入', e.isGroup)
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
if (!inputList.length && !isAllDeleted) {
|
||||||
|
let replyMsg = '名单更新失败,请在检查输入是否正确后重新输入。'
|
||||||
|
if (wrongInput.length) replyMsg += `${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||||
|
await this.reply(replyMsg, e.isGroup)
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist)
|
||||||
|
if (isAllDeleted) {
|
||||||
|
Config.whitelist = isWhiteList ? [] : whitelist
|
||||||
|
Config.blacklist = !isWhiteList ? [] : blacklist
|
||||||
} else {
|
} else {
|
||||||
for (const element of validGroups) {
|
for (const element of inputList) {
|
||||||
if (isWhiteList) {
|
if (isWhiteList) {
|
||||||
Config.groupWhitelist = groupWhitelist.filter(item => item !== element)
|
Config.whitelist = whitelist.filter(item => item !== element)
|
||||||
} else {
|
} else {
|
||||||
Config.groupBlacklist = groupBlacklist.filter(item => item !== element)
|
Config.blacklist = blacklist.filter(item => item !== element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||||
const listType = isWhiteList ? '白名单' : '黑名单'
|
let replyMsg = `${listType}已更新,可通过 "#chatgpt查看${listType}" 命令查看最新名单${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||||
let replyMsg = `群聊${listType}已更新,可通过'#chatgpt查看群聊${listType}'命令查看最新名单`
|
|
||||||
if (e.isPrivate) {
|
if (e.isPrivate) {
|
||||||
replyMsg += `\n当前群聊${listType}为:${listType === '白名单' ? Config.groupWhitelist : Config.groupBlacklist}`
|
const list = isWhiteList ? Config.whitelist : Config.blacklist
|
||||||
|
replyMsg = list.length ? `\n当前${listType}为:${list}` : `当前没有设置任何${listType}`
|
||||||
}
|
}
|
||||||
await this.reply(replyMsg, e.isGroup)
|
await this.reply(replyMsg, e.isGroup)
|
||||||
this.finish('confirmDelGroup')
|
this.finish('confirmDelList')
|
||||||
}
|
}
|
||||||
|
|
||||||
async enablePrivateChat (e) {
|
async enablePrivateChat (e) {
|
||||||
|
|
@ -553,10 +588,14 @@ export class ChatgptManagement extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDefaultReplySetting (e) {
|
async setDefaultReplySetting (e) {
|
||||||
const reg = /^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|(语音角色|角色语音|角色).*)|回复帮助)/
|
const reg = /^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|((azure|vits|vox)?语音角色|角色语音|角色)(.*))|回复帮助)/
|
||||||
const matchCommand = e.msg.match(reg)
|
const matchCommand = e.msg.match(reg)
|
||||||
const settingType = matchCommand[2]
|
const settingType = matchCommand[2]
|
||||||
let replyMsg = ''
|
let replyMsg = ''
|
||||||
|
let ttsSupportKinds = []
|
||||||
|
if (Config.azureTTSKey) ttsSupportKinds.push(1)
|
||||||
|
if (Config.ttsSpace) ttsSupportKinds.push(2)
|
||||||
|
if (Config.voicevoxSpace) ttsSupportKinds.push(3)
|
||||||
switch (settingType) {
|
switch (settingType) {
|
||||||
case '图片模式':
|
case '图片模式':
|
||||||
if (matchCommand[1] === '打开') {
|
if (matchCommand[1] === '打开') {
|
||||||
|
|
@ -591,8 +630,8 @@ export class ChatgptManagement extends plugin {
|
||||||
replyMsg = '请使用“#chatgpt打开全局文本模式”或“#chatgpt关闭全局文本模式”命令来设置回复模式'
|
replyMsg = '请使用“#chatgpt打开全局文本模式”或“#chatgpt关闭全局文本模式”命令来设置回复模式'
|
||||||
} break
|
} break
|
||||||
case '语音模式':
|
case '语音模式':
|
||||||
if (!Config.ttsSpace) {
|
if (!ttsSupportKinds.length) {
|
||||||
replyMsg = '您没有配置VITS API,请前往锅巴面板进行配置'
|
replyMsg = '您没有配置任何语音服务,请前往锅巴面板进行配置'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (matchCommand[1] === '打开') {
|
if (matchCommand[1] === '打开') {
|
||||||
|
|
@ -610,25 +649,68 @@ export class ChatgptManagement extends plugin {
|
||||||
replyMsg = '请使用“#chatgpt打开全局语音模式”或“#chatgpt关闭全局语音模式”命令来设置回复模式'
|
replyMsg = '请使用“#chatgpt打开全局语音模式”或“#chatgpt关闭全局语音模式”命令来设置回复模式'
|
||||||
} break
|
} break
|
||||||
case '回复帮助':
|
case '回复帮助':
|
||||||
replyMsg = '可使用以下命令配置全局回复:\n#chatgpt(打开/关闭)全局(语音/图片/文本)模式\n#chatgpt设置全局(语音角色|角色语音|角色)+角色名称(留空则为随机)'
|
replyMsg = '可使用以下命令配置全局回复:\n#chatgpt(打开/关闭)全局(语音/图片/文本)模式\n#chatgpt设置全局(vox|azure|vits)语音角色+角色名称(留空则为随机)\n'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (!Config.ttsSpace) {
|
if (!ttsSupportKinds) {
|
||||||
replyMsg = '您没有配置VITS API,请前往锅巴面板进行配置'
|
replyMsg = '您没有配置任何语音服务,请前往锅巴面板进行配置'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (settingType.match(/(语音角色|角色语音|角色)/)) {
|
if (settingType.match(/(语音角色|角色语音|角色)/)) {
|
||||||
const speaker = matchCommand[2].replace(/(语音角色|角色语音|角色)/, '').trim() || ''
|
const voiceKind = matchCommand[5]
|
||||||
if (!speaker.length) {
|
let speaker = matchCommand[6] || ''
|
||||||
replyMsg = 'ChatGpt将随机挑选角色回复'
|
if (voiceKind === undefined) {
|
||||||
Config.defaultTTSRole = ''
|
await this.reply('请选择需要设置的语音类型。使用"#chatgpt语音服务"查看支持的语音类型')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!speaker.length || speaker === '随机') {
|
||||||
|
replyMsg = `设置成功,ChatGpt将在${voiceKind}语音模式下随机挑选角色进行回复`
|
||||||
|
if (voiceKind === 'vits') Config.defaultTTSRole = '随机'
|
||||||
|
if (voiceKind === 'azure') Config.azureTTSSpeaker = '随机'
|
||||||
|
if (voiceKind === 'vox') Config.voicevoxTTSSpeaker = '随机'
|
||||||
} else {
|
} else {
|
||||||
|
if (ttsSupportKinds.includes(1) && voiceKind === 'azure') {
|
||||||
|
if (getAzureRoleList().includes(speaker)) {
|
||||||
|
Config.defaultUseTTS = azureRoleList.filter(s => s.name === speaker)[0].code
|
||||||
|
replyMsg = `ChatGPT默认语音角色已被设置为“${speaker}”`
|
||||||
|
} else {
|
||||||
|
await this.reply(`抱歉,没有"${speaker}"这个角色,目前azure模式下支持的角色有${azureRoleList.map(item => item.name).join('、')}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (ttsSupportKinds.includes(2) && voiceKind === 'vits') {
|
||||||
const ttsRole = convertSpeaker(speaker)
|
const ttsRole = convertSpeaker(speaker)
|
||||||
if (vitsRoleList.includes(ttsRole)) {
|
if (vitsRoleList.includes(ttsRole)) {
|
||||||
Config.defaultTTSRole = ttsRole
|
Config.defaultTTSRole = ttsRole
|
||||||
replyMsg = `ChatGPT默认语音角色已被设置为“${ttsRole}”`
|
replyMsg = `ChatGPT默认语音角色已被设置为“${ttsRole}”`
|
||||||
} else {
|
} else {
|
||||||
replyMsg = `抱歉,我还不认识“${ttsRole}”这个语音角色`
|
replyMsg = `抱歉,我还不认识“${ttsRole}”这个语音角色,可使用'#vits角色列表'查看可配置的角色`
|
||||||
|
}
|
||||||
|
} else if (ttsSupportKinds.includes(3) && voiceKind === 'vox') {
|
||||||
|
if (getVoicevoxRoleList().includes(speaker)) {
|
||||||
|
let regex = /^(.*?)-(.*)$/
|
||||||
|
let match = regex.exec(speaker)
|
||||||
|
let style = null
|
||||||
|
if (match) {
|
||||||
|
speaker = match[1]
|
||||||
|
style = match[2]
|
||||||
|
}
|
||||||
|
let chosen = VoiceVoxTTS.supportConfigurations.filter(s => s.name === speaker)
|
||||||
|
if (chosen.length === 0) {
|
||||||
|
await this.reply(`抱歉,没有"${speaker}"这个角色,目前voicevox模式下支持的角色有${VoiceVoxTTS.supportConfigurations.map(item => item.name).join('、')}`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (style && !chosen[0].styles.find(item => item.name === style)) {
|
||||||
|
await this.reply(`抱歉,"${speaker}"这个角色没有"${style}"这个风格,目前支持的风格有${chosen[0].styles.map(item => item.name).join('、')}`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Config.ttsRoleVoiceVox = chosen[0].name + (style ? `-${style}` : '')
|
||||||
|
replyMsg = `ChatGPT默认语音角色已被设置为“${speaker}”`
|
||||||
|
} else {
|
||||||
|
await this.reply(`抱歉,没有"${speaker}"这个角色,目前voicevox模式下支持的角色有${voxRoleList.map(item => item.name).join('、')}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replyMsg = `${voiceKind}语音角色设置错误,请检查语音配置~`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1004,32 +1086,32 @@ export class ChatgptManagement extends plugin {
|
||||||
poe: 'Poe'
|
poe: 'Poe'
|
||||||
}
|
}
|
||||||
let modeText = modeMap[mode || 'api']
|
let modeText = modeMap[mode || 'api']
|
||||||
let message = ` API模式和浏览器模式如何选择?
|
let message = `API模式和浏览器模式如何选择?
|
||||||
|
|
||||||
// eslint-disable-next-line no-irregular-whitespace
|
API模式会调用 OpenAI 官方提供的 gpt-3.5-turbo API,只需要提供 API Key。一般情况下,该种方式响应速度更快,不会像 chatGPT 官网一样总出现不可用的现象,但要注意 gpt-3.5-turbo 的 API 调用是收费的,新用户有 $5 的试用金可用于支付,价格为 $0.0020/1K tokens。(问题和回答加起来算 token)
|
||||||
API模式会调用OpenAI官方提供的gpt-3.5-turbo API,只需要提供API Key。一般情况下,该种方式响应速度更快,不会像chatGPT官网一样总出现不可用的现象,但注意gpt-3.5-turbo的API调用是收费的,新用户有18美元试用金可用于支付,价格为$0.0020/ 1K tokens.(问题和回答加起来算token)
|
|
||||||
|
|
||||||
API3模式会调用官网反代API,他会帮你绕过CF防护,需要提供ChatGPT的Token。效果与官网和浏览器一致。设置token指令:#chatgpt设置token。
|
API3 模式会调用官网反代 API,它会帮你绕过 CF 防护,需要提供 ChatGPT 的 Token。效果与官网和浏览器一致。设置 Token 指令:#chatgpt设置token。
|
||||||
|
|
||||||
浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站,使得获得和官方以及API2模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问ChatGPT的IP地址),且响应速度不如API,而且高峰期容易无法使用。
|
浏览器模式通过在本地启动 Chrome 等浏览器模拟用户访问 ChatGPT 网站,使得获得和官方以及 API2 模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问 ChatGPT 的 IP 地址),且响应速度不如 API,而且高峰期容易无法使用。
|
||||||
|
|
||||||
必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing 登录Cookie方可使用。#chatgpt设置必应token
|
必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的 Bing 登录 Cookie 方可使用。#chatgpt设置必应 Token。
|
||||||
|
|
||||||
自建ChatGLM模式会调用自建的ChatGLM-6B服务器API进行对话,需要自建。参考https://github.com/ikechan8370/SimpleChatGLM6BAPI
|
自建 ChatGLM 模式会调用自建的 ChatGLM-6B 服务器 API 进行对话,需要自建。参考 https://github.com/ikechan8370/SimpleChatGLM6BAPI。
|
||||||
|
|
||||||
Claude模式会调用Slack中的Claude机器人进行对话,与其他模式不同的是全局共享一个对话。配置参考https://ikechan8370.com/archives/chatgpt-plugin-for-yunzaipei-zhi-slack-claude
|
Claude 模式会调用 Slack 中的 Claude 机器人进行对话,与其他模式不同的是全局共享一个对话。配置参考 https://ikechan8370.com/archives/chatgpt-plugin-for-yunzaipei-zhi-slack-claude。
|
||||||
|
|
||||||
Poe模式会调用Poe中的Claude-instant进行对话。需要提供cookie:#chatgpt设置PoeToken
|
Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie:#chatgpt设置 Poe Token。
|
||||||
|
|
||||||
您可以使用‘#chatgpt切换浏览器/API/API3/Bing/ChatGLM/Claude/Poe’来切换到指定模式。
|
星火 模式会调用科大讯飞推出的新一代认知智能大模型 '星火认知大模型' 进行对话。需要提供Cookie:#chatgpt设置星火token。
|
||||||
|
|
||||||
当前为${modeText}模式。
|
您可以使用 "#chatgpt切换浏览器/API/API3/Bing/ChatGLM/Claude/Poe/星火" 来切换到指定模式。
|
||||||
`
|
|
||||||
|
当前为 ${modeText} 模式。`
|
||||||
await this.reply(message)
|
await this.reply(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
async shutUp (e) {
|
async shutUp (e) {
|
||||||
let duration = e.msg.replace(/^#chatgpt(本群)?(群\d+)?闭嘴/, '')
|
let duration = e.msg.replace(/^#chatgpt(本群)?(群\d+)?(关闭|闭嘴|关机|休眠|下班)/, '')
|
||||||
let scope
|
let scope
|
||||||
let time = 3600000
|
let time = 3600000
|
||||||
if (duration === '永久') {
|
if (duration === '永久') {
|
||||||
|
|
@ -1037,20 +1119,20 @@ export class ChatgptManagement extends plugin {
|
||||||
} else if (duration) {
|
} else if (duration) {
|
||||||
time = parseDuration(duration)
|
time = parseDuration(duration)
|
||||||
}
|
}
|
||||||
const match = e.msg.match(/#chatgpt群(\d+)闭嘴(.*)/)
|
const match = e.msg.match(/#chatgpt群(\d+)?(关闭|闭嘴|关机|休眠|下班)(.*)/)
|
||||||
if (e.msg.indexOf('本群') > -1) {
|
if (e.msg.indexOf('本群') > -1) {
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
scope = e.group.group_id
|
scope = e.group.group_id
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
||||||
await e.reply(`好的,从现在开始我会在当前群聊闭嘴${formatDuration(time)}`)
|
await e.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`)
|
||||||
} else {
|
} else {
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
||||||
await e.reply(`好的,从现在开始我会在当前群聊闭嘴${formatDuration(time)}`)
|
await e.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('本群是指?你也没在群聊里让我闭嘴啊?')
|
await e.reply('主人,这里好像不是群哦')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (match) {
|
} else if (match) {
|
||||||
|
|
@ -1059,23 +1141,23 @@ export class ChatgptManagement extends plugin {
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
||||||
await e.reply(`好的,从现在开始我会在群聊${groupId}闭嘴${formatDuration(time)}`)
|
await e.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`)
|
||||||
} else {
|
} else {
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
||||||
await e.reply(`好的,从现在开始我会在群聊${groupId}闭嘴${formatDuration(time)}`)
|
await e.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('这是什么群?')
|
await e.reply('主人还没告诉我群号呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
||||||
await redis.del('CHATGPT:SHUT_UP:ALL')
|
await redis.del('CHATGPT:SHUT_UP:ALL')
|
||||||
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
||||||
await e.reply(`好的,我会再闭嘴${formatDuration(time)}`)
|
await e.reply(`好的,我会延长休眠时间${formatDuration(time)}`)
|
||||||
} else {
|
} else {
|
||||||
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
||||||
await e.reply(`好的,我会闭嘴${formatDuration(time)}`)
|
await e.reply(`好的,我会延长休眠时间${formatDuration(time)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1084,36 +1166,36 @@ export class ChatgptManagement extends plugin {
|
||||||
const match = e.msg.match(/^#chatgpt群(\d+)/)
|
const match = e.msg.match(/^#chatgpt群(\d+)/)
|
||||||
if (e.msg.indexOf('本群') > -1) {
|
if (e.msg.indexOf('本群') > -1) {
|
||||||
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
||||||
await e.reply('主人,我现在全局闭嘴呢,你让我在这个群张嘴咱也不敢张啊')
|
await e.reply('当前为休眠模式,没办法做出回应呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let scope = e.group.group_id
|
let scope = e.group.group_id
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
||||||
await e.reply('好的主人,我终于又可以在本群说话了')
|
await e.reply('好的主人,我又可以和大家聊天啦')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('啊?我也没闭嘴啊?')
|
await e.reply('主人,我已经启动过了哦')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('本群是指?你也没在群聊里让我张嘴啊?')
|
await e.reply('主人,这里好像不是群哦')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (match) {
|
} else if (match) {
|
||||||
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
||||||
await e.reply('主人,我现在全局闭嘴呢,你让我在那个群张嘴咱也不敢张啊')
|
await e.reply('当前为休眠模式,没办法做出回应呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const groupId = parseInt(match[1], 10)
|
const groupId = parseInt(match[1], 10)
|
||||||
if (Bot.getGroupList().get(groupId)) {
|
if (Bot.getGroupList().get(groupId)) {
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
||||||
await e.reply(`好的主人,我终于又可以在群${groupId}说话了`)
|
await e.reply(`好的主人,我终于又可以在群${groupId}和大家聊天了`)
|
||||||
} else {
|
} else {
|
||||||
await e.reply(`啊?我也没在群${groupId}闭嘴啊?`)
|
await e.reply(`主人,我在群${groupId}中已经是启动状态了哦`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('这是什么群?')
|
await e.reply('主人还没告诉我群号呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1123,14 +1205,14 @@ export class ChatgptManagement extends plugin {
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
await redis.del(keys[i])
|
await redis.del(keys[i])
|
||||||
}
|
}
|
||||||
await e.reply('好的,我会结束所有闭嘴')
|
await e.reply('好的,我会开启所有群聊响应')
|
||||||
} else if (keys || keys.length > 0) {
|
} else if (keys || keys.length > 0) {
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
await redis.del(keys[i])
|
await redis.del(keys[i])
|
||||||
}
|
}
|
||||||
await e.reply('好的,我会结束所有闭嘴?')
|
await e.reply('已经开启过全群响应啦')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('我没有在任何地方闭嘴啊?')
|
await e.reply('我没有在任何群休眠哦')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1138,7 +1220,7 @@ export class ChatgptManagement extends plugin {
|
||||||
async listShutUp () {
|
async listShutUp () {
|
||||||
let keys = await redis.keys('CHATGPT:SHUT_UP:*')
|
let keys = await redis.keys('CHATGPT:SHUT_UP:*')
|
||||||
if (!keys || keys.length === 0) {
|
if (!keys || keys.length === 0) {
|
||||||
await this.reply('我没有在任何群闭嘴', true)
|
await this.reply('已经开启过全群响应啦', true)
|
||||||
} else {
|
} else {
|
||||||
let list = []
|
let list = []
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@
|
||||||
"sydneyApologyIgnored": true,
|
"sydneyApologyIgnored": true,
|
||||||
"enforceMaster": false,
|
"enforceMaster": false,
|
||||||
"enablePrivateChat": false,
|
"enablePrivateChat": false,
|
||||||
"groupWhitelist": [],
|
"whitelist": [],
|
||||||
"groupBlacklist": [],
|
"blacklist": [],
|
||||||
"ttsRegex": "/匹配规则/匹配模式",
|
"ttsRegex": "/匹配规则/匹配模式",
|
||||||
"baiduTranslateAppId": "",
|
"baiduTranslateAppId": "",
|
||||||
"baiduTranslateSecret": "",
|
"baiduTranslateSecret": "",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Config } from './utils/config.js'
|
import { Config } from './utils/config.js'
|
||||||
import { speakers } from './utils/tts.js'
|
import { speakers } from './utils/tts.js'
|
||||||
import AzureTTS from './utils/tts/microsoft-azure.js'
|
import { supportConfigurations as azureRoleList } from './utils/tts/microsoft-azure.js'
|
||||||
import VoiceVoxTTS from './utils/tts/voicevox.js'
|
import { supportConfigurations as voxRoleList } from './utils/tts/voicevox.js'
|
||||||
// 支持锅巴
|
// 支持锅巴
|
||||||
export function supportGuoba () {
|
export function supportGuoba () {
|
||||||
return {
|
return {
|
||||||
|
|
@ -40,15 +40,15 @@ export function supportGuoba () {
|
||||||
component: 'InputTextArea'
|
component: 'InputTextArea'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'groupWhitelist',
|
field: 'whitelist',
|
||||||
label: '群聊白名单',
|
label: '对话白名单',
|
||||||
bottomHelpMessage: '设置后只有白名单内的群可以使用本插件。用英文逗号隔开',
|
bottomHelpMessage: '只有在白名单内的QQ号或群组才能使用本插件进行对话。如果需要添加QQ号,请在号码前面加上^符号(例如:^123456),多个号码之间请用英文逗号(,)隔开。白名单优先级高于黑名单。',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'groupBlacklist',
|
field: 'blacklist',
|
||||||
label: '群聊黑名单',
|
label: '对话黑名单',
|
||||||
bottomHelpMessage: '设置后名单内的群禁止使用本插件。用英文逗号隔开',
|
bottomHelpMessage: '名单内的群或QQ号将无法使用本插件进行对话。如果需要添加QQ号,请在QQ号前面加上^符号(例如:^123456),并用英文逗号(,)将各个号码分隔开。',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -102,7 +102,10 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: speakers.concat('随机').map(s => { return { label: s, value: s } })
|
options: [{
|
||||||
|
label: '随机',
|
||||||
|
value: '随机'
|
||||||
|
}].concat(speakers.map(s => { return { label: s, value: s } }))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -111,12 +114,16 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定',
|
bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: AzureTTS.supportConfigurations.map(item => {
|
options: [{
|
||||||
return {
|
label: '随机',
|
||||||
label: `${item.name}-${item.gender}-${item.languageDetail}`,
|
value: '随机'
|
||||||
value: item.code
|
},
|
||||||
}
|
...azureRoleList.flatMap(item => [
|
||||||
})
|
item.roleInfo
|
||||||
|
]).map(s => ({
|
||||||
|
label: s,
|
||||||
|
value: s
|
||||||
|
}))]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -125,11 +132,17 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: VoiceVoxTTS.supportConfigurations.map(item => {
|
options: [{
|
||||||
return item.styles.map(style => {
|
label: '随机',
|
||||||
return `${item.name}-${style.name}`
|
value: '随机'
|
||||||
}).concat(item.name)
|
},
|
||||||
}).flat().concat('随机').map(s => { return { label: s, value: s } })
|
...voxRoleList.flatMap(item => [
|
||||||
|
...item.styles.map(style => `${item.name}-${style.name}`),
|
||||||
|
item.name
|
||||||
|
]).map(s => ({
|
||||||
|
label: s,
|
||||||
|
value: s
|
||||||
|
}))]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -307,6 +320,12 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: 'OpenAI的ApiKey,用于访问OpenAI的API接口',
|
bottomHelpMessage: 'OpenAI的ApiKey,用于访问OpenAI的API接口',
|
||||||
component: 'InputPassword'
|
component: 'InputPassword'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'model',
|
||||||
|
label: 'OpenAI 模型',
|
||||||
|
bottomHelpMessage: 'gpt-4, gpt-4-0314, gpt-4-32k, gpt-4-32k-0314, gpt-3.5-turbo, gpt-3.5-turbo-0301。默认为gpt-3.5-turbo,gpt-4需账户支持',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'openAiBaseUrl',
|
field: 'openAiBaseUrl',
|
||||||
label: 'OpenAI API服务器地址',
|
label: 'OpenAI API服务器地址',
|
||||||
|
|
@ -383,6 +402,12 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '加强主人认知。希望机器人认清主人,避免NTR可开启。开启后可能会与自设定的内容有部分冲突。sydney模式可以放心开启',
|
bottomHelpMessage: '加强主人认知。希望机器人认清主人,避免NTR可开启。开启后可能会与自设定的内容有部分冲突。sydney模式可以放心开启',
|
||||||
component: 'Switch'
|
component: 'Switch'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'enableGenerateContents',
|
||||||
|
label: '允许生成图像等内容',
|
||||||
|
bottomHelpMessage: '开启后类似网页版能够发图。但是此选项会占用大量token,自设定等模式下容易爆token',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// field: 'cognitiveReinforcementTip',
|
// field: 'cognitiveReinforcementTip',
|
||||||
// label: '加强主人认知的后台prompt',
|
// label: '加强主人认知的后台prompt',
|
||||||
|
|
@ -780,7 +805,19 @@ export function supportGuoba () {
|
||||||
for (let [keyPath, value] of Object.entries(data)) {
|
for (let [keyPath, value] of Object.entries(data)) {
|
||||||
// 处理黑名单
|
// 处理黑名单
|
||||||
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,,;;\|]/) }
|
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,,;;\|]/) }
|
||||||
if (Config[keyPath] != value) { Config[keyPath] = value }
|
if (Config[keyPath] !== value) { Config[keyPath] = value }
|
||||||
|
}
|
||||||
|
// 正确储存azureRoleSelect结果
|
||||||
|
const azureSpeaker = azureRoleList.find(config => {
|
||||||
|
let i = config.roleInfo || config.code
|
||||||
|
if (i === data.azureTTSSpeaker) {
|
||||||
|
return config
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (typeof azureSpeaker === 'object' && azureSpeaker !== null) {
|
||||||
|
Config.azureTTSSpeaker = azureSpeaker.code
|
||||||
}
|
}
|
||||||
return Result.ok({}, '保存成功~')
|
return Result.ok({}, '保存成功~')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,9 @@ export async function createServer() {
|
||||||
if (await redis.exists('CHATGPT:CONFIRM') != 0) {
|
if (await redis.exists('CHATGPT:CONFIRM') != 0) {
|
||||||
redisConfig.turnConfirm = await redis.get('CHATGPT:CONFIRM') === 'on'
|
redisConfig.turnConfirm = await redis.get('CHATGPT:CONFIRM') === 'on'
|
||||||
}
|
}
|
||||||
|
if (await redis.exists('CHATGPT:USE') != 0) {
|
||||||
|
redisConfig.useMode = await redis.get('CHATGPT:USE')
|
||||||
|
}
|
||||||
reply.send({
|
reply.send({
|
||||||
chatConfig: Config,
|
chatConfig: Config,
|
||||||
redisConfig
|
redisConfig
|
||||||
|
|
@ -363,6 +366,9 @@ export async function createServer() {
|
||||||
if (redisConfig.turnConfirm != null) {
|
if (redisConfig.turnConfirm != null) {
|
||||||
await redis.set('CHATGPT:CONFIRM', redisConfig.turnConfirm ? 'on' : 'off')
|
await redis.set('CHATGPT:CONFIRM', redisConfig.turnConfirm ? 'on' : 'off')
|
||||||
}
|
}
|
||||||
|
if (redisConfig.useMode != null) {
|
||||||
|
await redis.set('CHATGPT:USE', redisConfig.useMode)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (body.userSetting) {
|
if (body.userSetting) {
|
||||||
await redis.set(`CHATGPT:USER:${user.user}`, JSON.stringify(body.userSetting))
|
await redis.set(`CHATGPT:USER:${user.user}`, JSON.stringify(body.userSetting))
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,4 @@
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="76x76" href="/apple-icon.png"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"/><script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.6.3/mermaid.min.js"></script><script src="/live2d/live2dcubismcore.min.js"></script><title>ChatGPT-Plugin</title><script defer="defer" type="module" src="/js/chunk-vendors.f436bd7e.js"></script><script defer="defer" type="module" src="/js/app.06ce534d.js"></script><link href="/css/chunk-vendors.0ede84b4.css" rel="stylesheet"><link href="/css/app.4dc5e420.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.70bbbaed.js" nomodule></script><script defer="defer" src="/js/app-legacy.de234224.js" nomodule></script></head><body class="text-blueGray-700 antialiased"><noscript><strong>We're sorry but vue-notus doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="76x76" href="/apple-icon.png"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"/><script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.6.3/mermaid.min.js"></script><script src="/live2d/live2dcubismcore.min.js"></script><title>ChatGPT-Plugin</title><script defer="defer" type="module" src="/js/chunk-vendors.f436bd7e.js"></script><script defer="defer" type="module" src="/js/app.84a0dda5.js"></script><link href="/css/chunk-vendors.0ede84b4.css" rel="stylesheet"><link href="/css/app.4dc5e420.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.70bbbaed.js" nomodule></script><script defer="defer" src="/js/app-legacy.b9741f05.js" nomodule></script></head><body class="text-blueGray-700 antialiased"><noscript><strong>We're sorry but vue-notus doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||||
21
server/static/js/app-legacy.b9741f05.js
Normal file
21
server/static/js/app-legacy.b9741f05.js
Normal file
File diff suppressed because one or more lines are too long
1
server/static/js/app-legacy.b9741f05.js.map
Normal file
1
server/static/js/app-legacy.b9741f05.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21
server/static/js/app.84a0dda5.js
Normal file
21
server/static/js/app.84a0dda5.js
Normal file
File diff suppressed because one or more lines are too long
1
server/static/js/app.84a0dda5.js.map
Normal file
1
server/static/js/app.84a0dda5.js.map
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
||||||
import fetch from 'node-fetch'
|
import fetch, { FormData } from 'node-fetch'
|
||||||
import { makeForwardMsg } from './common.js'
|
import { makeForwardMsg } from './common.js'
|
||||||
import { Config } from './config.js'
|
import { Config } from './config.js'
|
||||||
|
|
||||||
|
|
@ -22,8 +22,8 @@ export default class BingDrawClient {
|
||||||
async getImages (prompt, e) {
|
async getImages (prompt, e) {
|
||||||
let urlEncodedPrompt = encodeURIComponent(prompt)
|
let urlEncodedPrompt = encodeURIComponent(prompt)
|
||||||
let url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=4&FORM=GENCRE`
|
let url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=4&FORM=GENCRE`
|
||||||
let d = Math.ceil(Math.random() * 255)
|
// let d = Math.ceil(Math.random() * 255)
|
||||||
let randomIp = '141.11.138.' + d
|
// let randomIp = '141.11.138.' + d
|
||||||
let headers = {
|
let headers = {
|
||||||
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||||
'accept-language': 'en-US,en;q=0.9',
|
'accept-language': 'en-US,en;q=0.9',
|
||||||
|
|
@ -31,31 +31,58 @@ export default class BingDrawClient {
|
||||||
'content-type': 'application/x-www-form-urlencoded',
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
referrer: 'https://www.bing.com/images/create/',
|
referrer: 'https://www.bing.com/images/create/',
|
||||||
origin: 'https://www.bing.com',
|
origin: 'https://www.bing.com',
|
||||||
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',
|
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50',
|
||||||
cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||||
'x-forwarded-for': randomIp
|
// 'x-forwarded-for': randomIp,
|
||||||
|
Dnt: '1',
|
||||||
|
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
||||||
|
'sec-ch-ua-arch': '"x86"',
|
||||||
|
'sec-ch-ua-bitness': '"64"',
|
||||||
|
'sec-ch-ua-full-version': '"113.0.5672.126"',
|
||||||
|
'sec-ch-ua-full-version-list': '"Google Chrome";v="113.0.5672.126", "Chromium";v="113.0.5672.126", "Not-A.Brand";v="24.0.0.0"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-model': '',
|
||||||
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
|
'sec-ch-ua-platform-version': '"13.1.0"',
|
||||||
|
'sec-fetch-dest': 'document',
|
||||||
|
'sec-fetch-mode': 'navigate',
|
||||||
|
'sec-fetch-site': 'same-origin',
|
||||||
|
'sec-fetch-user': '?1',
|
||||||
|
'Referrer-Policy': 'origin-when-cross-origin',
|
||||||
|
'x-edge-shopping-flag': '1'
|
||||||
}
|
}
|
||||||
// headers['x-forwarded-for'] = '141.11.138.30'
|
// headers['x-forwarded-for'] = '141.11.138.30'
|
||||||
|
let body = new FormData()
|
||||||
|
body.append('q', prompt)
|
||||||
|
body.append('qs', 'ds')
|
||||||
let fetchOptions = {
|
let fetchOptions = {
|
||||||
method: 'POST',
|
headers
|
||||||
headers,
|
|
||||||
redirect: 'manual'
|
|
||||||
}
|
}
|
||||||
if (Config.proxy) {
|
if (Config.proxy) {
|
||||||
fetchOptions.agent = proxy(Config.proxy)
|
fetchOptions.agent = proxy(Config.proxy)
|
||||||
}
|
}
|
||||||
let response = await fetch(url, fetchOptions)
|
let success = false
|
||||||
|
let retry = 5
|
||||||
|
let response
|
||||||
|
while (!success && retry >= 0) {
|
||||||
|
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST' }))
|
||||||
let res = await response.text()
|
let res = await response.text()
|
||||||
if (res.toLowerCase().indexOf('this prompt has been blocked') > -1) {
|
if (res.toLowerCase().indexOf('this prompt has been blocked') > -1) {
|
||||||
throw new Error('Your prompt has been blocked by Bing. Try to change any bad words and try again.')
|
throw new Error('Your prompt has been blocked by Bing. Try to change any bad words and try again.')
|
||||||
}
|
}
|
||||||
if (response.status !== 302) {
|
if (response.status !== 302) {
|
||||||
url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=3&FORM=GENCRE`
|
url = `${this.opts.baseUrl}/images/create?q=${urlEncodedPrompt}&rt=3&FORM=GENCRE`
|
||||||
let response3 = await fetch(url, fetchOptions)
|
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST' }))
|
||||||
if (response3.status !== 302) {
|
|
||||||
throw new Error('绘图失败,请检查Bing token和代理/反代配置')
|
|
||||||
}
|
}
|
||||||
response = response3
|
if (response.status === 302) {
|
||||||
|
success = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
retry--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
throw new Error('绘图失败,请检查Bing token和代理/反代配置')
|
||||||
}
|
}
|
||||||
let redirectUrl = response.headers.get('Location').replace('&nfy=1', '')
|
let redirectUrl = response.headers.get('Location').replace('&nfy=1', '')
|
||||||
let requestId = redirectUrl.split('id=')[1]
|
let requestId = redirectUrl.split('id=')[1]
|
||||||
|
|
@ -66,26 +93,23 @@ export default class BingDrawClient {
|
||||||
let pollingUrl = `${this.opts.baseUrl}/images/create/async/results/${requestId}?q=${urlEncodedPrompt}`
|
let pollingUrl = `${this.opts.baseUrl}/images/create/async/results/${requestId}?q=${urlEncodedPrompt}`
|
||||||
logger.info({ pollingUrl })
|
logger.info({ pollingUrl })
|
||||||
logger.info('waiting for bing draw results...')
|
logger.info('waiting for bing draw results...')
|
||||||
let timeoutTimes = 50
|
let timeoutTimes = 30
|
||||||
let found = false
|
let found = false
|
||||||
let timer = setInterval(async () => {
|
let timer = setInterval(async () => {
|
||||||
if (found) {
|
if (found) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let r = await fetch(pollingUrl, {
|
let r = await fetch(pollingUrl, fetchOptions)
|
||||||
headers
|
|
||||||
})
|
|
||||||
let rText = await r.text()
|
let rText = await r.text()
|
||||||
if (rText) {
|
if (r.status === 200 && rText) {
|
||||||
// logger.info(rText)
|
// logger.info(rText)
|
||||||
logger.info('got bing draw results!')
|
logger.info('got bing draw results!')
|
||||||
found = true
|
found = true
|
||||||
let regex = /src="([^"]+)"/g
|
let regex = /src="([^"]+)"/g
|
||||||
let imageLinks = rText.match(regex)
|
let imageLinks = rText.match(regex)
|
||||||
if (!imageLinks || imageLinks.length === 0) {
|
if (!imageLinks || imageLinks.length === 0) {
|
||||||
await e.reply('绘图失败:no images', true)
|
// 很可能是微软内部error,重试即可
|
||||||
logger.error(rText)
|
return
|
||||||
throw new Error('no images')
|
|
||||||
}
|
}
|
||||||
imageLinks = imageLinks.map(link => link.split('?w=')[0]).map(link => link.replace('src="', ''))
|
imageLinks = imageLinks.map(link => link.split('?w=')[0]).map(link => link.replace('src="', ''))
|
||||||
imageLinks = [...new Set(imageLinks)]
|
imageLinks = [...new Set(imageLinks)]
|
||||||
|
|
@ -108,11 +132,12 @@ export default class BingDrawClient {
|
||||||
if (timeoutTimes === 0) {
|
if (timeoutTimes === 0) {
|
||||||
await e.reply('绘图超时', true)
|
await e.reply('绘图超时', true)
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
} else {
|
} else {
|
||||||
logger.info('still waiting for bing draw results... times left: ' + timeoutTimes)
|
logger.info('still waiting for bing draw results... times left: ' + timeoutTimes)
|
||||||
timeoutTimes--
|
timeoutTimes--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 1500)
|
}, 2000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import fetch, {
|
||||||
Response
|
Response
|
||||||
} from 'node-fetch'
|
} from 'node-fetch'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
import WebSocket from 'ws'
|
||||||
import HttpsProxyAgent from 'https-proxy-agent'
|
import HttpsProxyAgent from 'https-proxy-agent'
|
||||||
import { Config, pureSydneyInstruction } from './config.js'
|
import { Config, pureSydneyInstruction } from './config.js'
|
||||||
import { formatDate, getMasterQQ, isCN, getUserData } from './common.js'
|
import { formatDate, getMasterQQ, isCN, getUserData } from './common.js'
|
||||||
|
|
@ -29,15 +29,16 @@ if (Config.proxy) {
|
||||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getWebSocket () {
|
|
||||||
let WebSocket
|
// async function getWebSocket () {
|
||||||
try {
|
// let WebSocket
|
||||||
WebSocket = (await import('ws')).default
|
// try {
|
||||||
} catch (error) {
|
// WebSocket = (await import('ws')).default
|
||||||
throw new Error('ws依赖未安装,请使用pnpm install ws安装')
|
// } catch (error) {
|
||||||
}
|
// throw new Error('ws依赖未安装,请使用pnpm install ws安装')
|
||||||
return WebSocket
|
// }
|
||||||
}
|
// return WebSocket
|
||||||
|
// }
|
||||||
async function getKeyv () {
|
async function getKeyv () {
|
||||||
let Keyv
|
let Keyv
|
||||||
try {
|
try {
|
||||||
|
|
@ -58,7 +59,7 @@ export default class SydneyAIClient {
|
||||||
constructor (opts) {
|
constructor (opts) {
|
||||||
this.opts = {
|
this.opts = {
|
||||||
...opts,
|
...opts,
|
||||||
host: opts.host || Config.sydneyReverseProxy || 'https://www.bing.com'
|
host: opts.host || Config.sydneyReverseProxy || 'https://edgeservices.bing.com/edgesvc'
|
||||||
}
|
}
|
||||||
// if (opts.proxy && !Config.sydneyForceUseReverse) {
|
// if (opts.proxy && !Config.sydneyForceUseReverse) {
|
||||||
// this.opts.host = 'https://www.bing.com'
|
// this.opts.host = 'https://www.bing.com'
|
||||||
|
|
@ -80,42 +81,46 @@ export default class SydneyAIClient {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/json',
|
accept: 'application/json',
|
||||||
'accept-language': 'en-US,en;q=0.9',
|
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
'sec-ch-ua': '"Chromium";v="112", "Microsoft Edge";v="112", "Not:A-Brand";v="99"',
|
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
||||||
'sec-ch-ua-arch': '"x86"',
|
// 'sec-ch-ua-arch': '"x86"',
|
||||||
'sec-ch-ua-bitness': '"64"',
|
// 'sec-ch-ua-bitness': '"64"',
|
||||||
'sec-ch-ua-full-version': '"112.0.1722.7"',
|
// 'sec-ch-ua-full-version': '"112.0.1722.7"',
|
||||||
'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
// 'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
||||||
'sec-ch-ua-mobile': '?0',
|
'sec-ch-ua-mobile': '?0',
|
||||||
'sec-ch-ua-model': '',
|
// 'sec-ch-ua-model': '',
|
||||||
'sec-ch-ua-platform': '"Windows"',
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
'sec-ch-ua-platform-version': '"15.0.0"',
|
// 'sec-ch-ua-platform-version': '"15.0.0"',
|
||||||
'sec-fetch-dest': 'empty',
|
'sec-fetch-dest': 'empty',
|
||||||
'sec-fetch-mode': 'cors',
|
'sec-fetch-mode': 'cors',
|
||||||
'sec-fetch-site': 'same-origin',
|
'sec-fetch-site': 'same-origin',
|
||||||
'x-ms-client-request-id': crypto.randomUUID(),
|
'x-ms-client-request-id': crypto.randomUUID(),
|
||||||
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
|
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/macOS',
|
||||||
cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
// cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||||
Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx',
|
Referer: 'https://edgeservices.bing.com/edgesvc/chat?udsframed=1&form=SHORUN&clientscopes=chat,noheader,channelstable,',
|
||||||
'Referrer-Policy': 'origin-when-cross-origin',
|
'Referrer-Policy': 'origin-when-cross-origin',
|
||||||
// Workaround for request being blocked due to geolocation
|
// Workaround for request being blocked due to geolocation
|
||||||
'x-forwarded-for': '1.1.1.1'
|
'x-forwarded-for': '1.1.1.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.opts.cookies || this.opts.userToken) {
|
||||||
|
// 疑似无需token了
|
||||||
|
fetchOptions.headers.cookie = this.opts.cookies || `_U=${this.opts.userToken}`
|
||||||
|
}
|
||||||
if (this.opts.proxy) {
|
if (this.opts.proxy) {
|
||||||
fetchOptions.agent = proxy(Config.proxy)
|
fetchOptions.agent = proxy(Config.proxy)
|
||||||
}
|
}
|
||||||
let accessible = !(await isCN()) || this.opts.proxy
|
let accessible = !(await isCN()) || this.opts.proxy
|
||||||
if (accessible && !Config.sydneyForceUseReverse) {
|
if (accessible && !Config.sydneyForceUseReverse) {
|
||||||
// 本身能访问bing.com,那就不用反代啦,重置host
|
// 本身能访问bing.com,那就不用反代啦,重置host
|
||||||
logger.info('change hosts to https://www.bing.com')
|
logger.info('change hosts to https://edgeservices.bing.com')
|
||||||
this.opts.host = 'https://www.bing.com'
|
this.opts.host = 'https://edgeservices.bing.com/edgesvc'
|
||||||
}
|
}
|
||||||
logger.mark('使用host:' + this.opts.host)
|
logger.mark('使用host:' + this.opts.host)
|
||||||
let response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
|
let response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
|
||||||
let text = await response.text()
|
let text = await response.text()
|
||||||
let retry = 30
|
let retry = 10
|
||||||
while (retry >= 0 && response.status === 200 && !text) {
|
while (retry >= 0 && response.status === 200 && !text) {
|
||||||
await delay(400)
|
await delay(400)
|
||||||
response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
|
response = await fetch(`${this.opts.host}/turing/conversation/create`, fetchOptions)
|
||||||
|
|
@ -138,7 +143,7 @@ export default class SydneyAIClient {
|
||||||
|
|
||||||
async createWebSocketConnection () {
|
async createWebSocketConnection () {
|
||||||
await this.initCache()
|
await this.initCache()
|
||||||
let WebSocket = await getWebSocket()
|
// let WebSocket = await getWebSocket()
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let agent
|
let agent
|
||||||
let sydneyHost = 'wss://sydney.bing.com'
|
let sydneyHost = 'wss://sydney.bing.com'
|
||||||
|
|
@ -149,7 +154,7 @@ export default class SydneyAIClient {
|
||||||
sydneyHost = Config.sydneyReverseProxy.replace('https://', 'wss://').replace('http://', 'ws://')
|
sydneyHost = Config.sydneyReverseProxy.replace('https://', 'wss://').replace('http://', 'ws://')
|
||||||
}
|
}
|
||||||
logger.mark(`use sydney websocket host: ${sydneyHost}`)
|
logger.mark(`use sydney websocket host: ${sydneyHost}`)
|
||||||
let ws = new WebSocket(sydneyHost + '/sydney/ChatHub', { agent })
|
let ws = new WebSocket(sydneyHost + '/sydney/ChatHub', undefined, { agent, origin: 'https://edgeservices.bing.com' })
|
||||||
ws.on('error', (err) => {
|
ws.on('error', (err) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
reject(err)
|
reject(err)
|
||||||
|
|
@ -304,7 +309,8 @@ export default class SydneyAIClient {
|
||||||
const text = (pureSydney ? pureSydneyInstruction : (useCast?.bing || Config.sydney)).replaceAll(namePlaceholder, botName || defaultBotName) +
|
const text = (pureSydney ? pureSydneyInstruction : (useCast?.bing || Config.sydney)).replaceAll(namePlaceholder, botName || defaultBotName) +
|
||||||
((Config.enableGroupContext && groupId) ? groupContextTip : '') +
|
((Config.enableGroupContext && groupId) ? groupContextTip : '') +
|
||||||
((Config.enforceMaster && master) ? masterTip : '') +
|
((Config.enforceMaster && master) ? masterTip : '') +
|
||||||
(Config.sydneyMood ? moodTip : '')
|
(Config.sydneyMood ? moodTip : '') +
|
||||||
|
(Config.sydneySystemCode ? '' : '')
|
||||||
// logger.info(text)
|
// logger.info(text)
|
||||||
if (pureSydney) {
|
if (pureSydney) {
|
||||||
previousMessages = invocationId === 0
|
previousMessages = invocationId === 0
|
||||||
|
|
@ -347,25 +353,31 @@ export default class SydneyAIClient {
|
||||||
logger.mark('sydney websocket constructed successful')
|
logger.mark('sydney websocket constructed successful')
|
||||||
}
|
}
|
||||||
const toneOption = 'h3imaginative'
|
const toneOption = 'h3imaginative'
|
||||||
const obj = {
|
let optionsSets = [
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
source: 'cib',
|
|
||||||
optionsSets: [
|
|
||||||
'nlu_direct_response_filter',
|
'nlu_direct_response_filter',
|
||||||
'deepleo',
|
'deepleo',
|
||||||
'disable_emoji_spoken_text',
|
'disable_emoji_spoken_text',
|
||||||
'responsible_ai_policy_235',
|
'responsible_ai_policy_235',
|
||||||
'enablemm',
|
'enablemm',
|
||||||
toneOption,
|
toneOption,
|
||||||
'clgalileo',
|
'dagslnv1',
|
||||||
'gencontentv3',
|
'sportsansgnd',
|
||||||
'rai267',
|
'dl_edge_desc',
|
||||||
'dtappid',
|
'noknowimg',
|
||||||
'cricinfo',
|
// 'dtappid',
|
||||||
'cricinfov2',
|
// 'cricinfo',
|
||||||
'dv3sugg'
|
// 'cricinfov2',
|
||||||
],
|
'dv3sugg',
|
||||||
|
'gencontentv3'
|
||||||
|
]
|
||||||
|
if (Config.enableGenerateContents) {
|
||||||
|
optionsSets.push(...['gencontentv3'])
|
||||||
|
}
|
||||||
|
const obj = {
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
source: 'cib',
|
||||||
|
optionsSets,
|
||||||
sliceIds: [
|
sliceIds: [
|
||||||
'222dtappid',
|
'222dtappid',
|
||||||
'225cricinfo',
|
'225cricinfo',
|
||||||
|
|
|
||||||
112
utils/common.js
112
utils/common.js
|
|
@ -8,6 +8,9 @@ import buffer from 'buffer'
|
||||||
import yaml from 'yaml'
|
import yaml from 'yaml'
|
||||||
import puppeteer from '../../../lib/puppeteer/puppeteer.js'
|
import puppeteer from '../../../lib/puppeteer/puppeteer.js'
|
||||||
import { Config } from './config.js'
|
import { Config } from './config.js'
|
||||||
|
import { speakers as vitsRoleList } from './tts.js'
|
||||||
|
import { supportConfigurations as voxRoleList } from './tts/voicevox.js'
|
||||||
|
import { supportConfigurations as azureRoleList } from './tts/microsoft-azure.js'
|
||||||
// export function markdownToText (markdown) {
|
// export function markdownToText (markdown) {
|
||||||
// return remark()
|
// return remark()
|
||||||
// .use(stripMarkdown)
|
// .use(stripMarkdown)
|
||||||
|
|
@ -19,7 +22,7 @@ let _puppeteer
|
||||||
try {
|
try {
|
||||||
const Puppeteer = (await import('../../../renderers/puppeteer/lib/puppeteer.js')).default
|
const Puppeteer = (await import('../../../renderers/puppeteer/lib/puppeteer.js')).default
|
||||||
let puppeteerCfg = {}
|
let puppeteerCfg = {}
|
||||||
let configFile = `./renderers/puppeteer/config.yaml`
|
let configFile = './renderers/puppeteer/config.yaml'
|
||||||
if (fs.existsSync(configFile)) {
|
if (fs.existsSync(configFile)) {
|
||||||
try {
|
try {
|
||||||
puppeteerCfg = yaml.parse(fs.readFileSync(configFile, 'utf8'))
|
puppeteerCfg = yaml.parse(fs.readFileSync(configFile, 'utf8'))
|
||||||
|
|
@ -335,7 +338,7 @@ export async function renderUrl (e, url, renderCfg = {}) {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
url: url,
|
url,
|
||||||
option: {
|
option: {
|
||||||
width: renderCfg.Viewport.width || 1280,
|
width: renderCfg.Viewport.width || 1280,
|
||||||
height: renderCfg.Viewport.height || 720,
|
height: renderCfg.Viewport.height || 720,
|
||||||
|
|
@ -350,7 +353,7 @@ export async function renderUrl (e, url, renderCfg = {}) {
|
||||||
})
|
})
|
||||||
if (resultres.ok) {
|
if (resultres.ok) {
|
||||||
const buff = Buffer.from(await resultres.arrayBuffer())
|
const buff = Buffer.from(await resultres.arrayBuffer())
|
||||||
if(buff) {
|
if (buff) {
|
||||||
const base64 = segment.image(buff)
|
const base64 = segment.image(buff)
|
||||||
if (renderCfg.retType === 'base64') {
|
if (renderCfg.retType === 'base64') {
|
||||||
return base64
|
return base64
|
||||||
|
|
@ -401,7 +404,8 @@ export function getDefaultReplySetting () {
|
||||||
usePicture: Config.defaultUsePicture,
|
usePicture: Config.defaultUsePicture,
|
||||||
useTTS: Config.defaultUseTTS,
|
useTTS: Config.defaultUseTTS,
|
||||||
ttsRole: Config.defaultTTSRole,
|
ttsRole: Config.defaultTTSRole,
|
||||||
ttsRoleAzure: Config.azureTTSSpeaker
|
ttsRoleAzure: Config.azureTTSSpeaker,
|
||||||
|
ttsRoleVoiceVox: Config.voicevoxTTSSpeaker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -679,16 +683,106 @@ export async function getUserData (user) {
|
||||||
return JSON.parse(data)
|
return JSON.parse(data)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
user: user,
|
user,
|
||||||
passwd: '',
|
passwd: '',
|
||||||
chat: [],
|
chat: [],
|
||||||
mode: '',
|
mode: '',
|
||||||
cast: {
|
cast: {
|
||||||
api: '', //API设定
|
api: '', // API设定
|
||||||
bing: '', //必应设定
|
bing: '', // 必应设定
|
||||||
bing_resource: '', //必应扩展资料
|
bing_resource: '', // 必应扩展资料
|
||||||
slack: '', //Slack设定
|
slack: '' // Slack设定
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVoicevoxRoleList () {
|
||||||
|
return voxRoleList.map(item => item.name).join('、')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAzureRoleList () {
|
||||||
|
return azureRoleList.map(item => item.name).join('、')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVitsRoleList (e) {
|
||||||
|
const [firstHalf, secondHalf] = [vitsRoleList.slice(0, Math.floor(vitsRoleList.length / 2)).join('、'), vitsRoleList.slice(Math.floor(vitsRoleList.length / 2)).join('、')]
|
||||||
|
const [chunk1, chunk2] = [firstHalf.match(/[^、]+(?:、[^、]+){0,30}/g), secondHalf.match(/[^、]+(?:、[^、]+){0,30}/g)]
|
||||||
|
const list = [await makeForwardMsg(e, chunk1, 'vits角色列表1'), await makeForwardMsg(e, chunk2, 'vits角色列表2')]
|
||||||
|
return await makeForwardMsg(e, list, 'vits角色列表')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserReplySetting (e) {
|
||||||
|
let userSetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
||||||
|
if (userSetting) {
|
||||||
|
userSetting = JSON.parse(userSetting)
|
||||||
|
if (Object.keys(userSetting).indexOf('useTTS') < 0) {
|
||||||
|
userSetting.useTTS = Config.defaultUseTTS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userSetting = getDefaultReplySetting()
|
||||||
|
}
|
||||||
|
return userSetting
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getImg (e) {
|
||||||
|
// 取消息中的图片、at的头像、回复的图片,放入e.img
|
||||||
|
if (e.at && !e.source) {
|
||||||
|
e.img = [`https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.at}`]
|
||||||
|
}
|
||||||
|
if (e.source) {
|
||||||
|
let reply
|
||||||
|
if (e.isGroup) {
|
||||||
|
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
||||||
|
} else {
|
||||||
|
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
||||||
|
}
|
||||||
|
if (reply) {
|
||||||
|
let i = []
|
||||||
|
for (let val of reply) {
|
||||||
|
if (val.type === 'image') {
|
||||||
|
i.push(val.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.img = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.img
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getImageOcrText (e) {
|
||||||
|
const img = await getImg(e)
|
||||||
|
if (img) {
|
||||||
|
try {
|
||||||
|
let resultArr = []
|
||||||
|
let eachImgRes = ''
|
||||||
|
for (let i in img) {
|
||||||
|
const imgOCR = await Bot.imageOcr(img[i])
|
||||||
|
for (let text of imgOCR.wordslist) {
|
||||||
|
eachImgRes += (`${text?.words} \n`)
|
||||||
|
}
|
||||||
|
if (eachImgRes) resultArr.push(eachImgRes)
|
||||||
|
eachImgRes = ''
|
||||||
|
}
|
||||||
|
// logger.warn('resultArr', resultArr)
|
||||||
|
return resultArr
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
// logger.error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 对原始黑白名单进行去重和去除无效群号处理,并处理通过锅巴面板添加错误配置时可能导致的问题
|
||||||
|
export function processList (whitelist, blacklist) {
|
||||||
|
whitelist = Array.isArray(whitelist)
|
||||||
|
? whitelist
|
||||||
|
: String(whitelist).split(/[,,]/)
|
||||||
|
blacklist = !Array.isArray(blacklist)
|
||||||
|
? blacklist
|
||||||
|
: String(blacklist).split(/[,,]/)
|
||||||
|
whitelist = Array.from(new Set(whitelist)).filter(value => /^\^?[1-9]\d{5,9}$/.test(value))
|
||||||
|
blacklist = Array.from(new Set(blacklist)).filter(value => /^\^?[1-9]\d{5,9}$/.test(value))
|
||||||
|
return [whitelist, blacklist]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ const defaultConfig = {
|
||||||
sydneyApologyIgnored: true,
|
sydneyApologyIgnored: true,
|
||||||
enforceMaster: false,
|
enforceMaster: false,
|
||||||
oldview: false,
|
oldview: false,
|
||||||
|
newhelp: false,
|
||||||
serverPort: 3321,
|
serverPort: 3321,
|
||||||
serverHost: '',
|
serverHost: '',
|
||||||
viewHost: '',
|
viewHost: '',
|
||||||
|
|
@ -98,8 +99,8 @@ const defaultConfig = {
|
||||||
live2dOption_rotation: 0,
|
live2dOption_rotation: 0,
|
||||||
groupAdminPage: false,
|
groupAdminPage: false,
|
||||||
enablePrivateChat: false,
|
enablePrivateChat: false,
|
||||||
groupWhitelist: [],
|
whitelist: [],
|
||||||
groupBlacklist: [],
|
blacklist: [],
|
||||||
ttsRegex: '/匹配规则/匹配模式',
|
ttsRegex: '/匹配规则/匹配模式',
|
||||||
slackUserToken: '',
|
slackUserToken: '',
|
||||||
slackBotUserToken: '',
|
slackBotUserToken: '',
|
||||||
|
|
@ -123,7 +124,8 @@ const defaultConfig = {
|
||||||
azureTTSEmotion: false,
|
azureTTSEmotion: false,
|
||||||
enhanceAzureTTSEmotion: false,
|
enhanceAzureTTSEmotion: false,
|
||||||
autoJapanese: false,
|
autoJapanese: false,
|
||||||
version: 'v2.6.0'
|
enableGenerateContents: false,
|
||||||
|
version: 'v2.6.2'
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
let config = {}
|
let config = {}
|
||||||
|
|
|
||||||
|
|
@ -468,12 +468,18 @@ export async function convertFaces (msg, handleAt = false, e) {
|
||||||
handleAt = e?.isGroup && handleAt
|
handleAt = e?.isGroup && handleAt
|
||||||
let groupMembers
|
let groupMembers
|
||||||
let groupCardQQMap = {}
|
let groupCardQQMap = {}
|
||||||
if (handleAt && typeof e.group.getMemberMap === 'function') {
|
if (handleAt) {
|
||||||
|
try {
|
||||||
groupMembers = await e.group.getMemberMap()
|
groupMembers = await e.group.getMemberMap()
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to get group members: ${err}`)
|
||||||
|
}
|
||||||
|
if (groupMembers) {
|
||||||
for (let key of groupMembers.keys()) {
|
for (let key of groupMembers.keys()) {
|
||||||
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let tmpMsg = ''
|
let tmpMsg = ''
|
||||||
let tmpFace = ''
|
let tmpFace = ''
|
||||||
let tmpAt = ''
|
let tmpAt = ''
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { getDefaultReplySetting, mkdirs } from '../common.js'
|
import { getDefaultReplySetting, mkdirs } from '../common.js'
|
||||||
import { Config } from '../config.js'
|
import { Config } from '../config.js'
|
||||||
|
import { translate } from '../translate.js'
|
||||||
|
|
||||||
let sdk
|
let sdk
|
||||||
try {
|
try {
|
||||||
|
|
@ -20,20 +21,29 @@ async function generateAudio (text, option = {}, ssml = '') {
|
||||||
let filename = `${_path}/data/chatgpt/tts/azure/${crypto.randomUUID()}.wav`
|
let filename = `${_path}/data/chatgpt/tts/azure/${crypto.randomUUID()}.wav`
|
||||||
let audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename)
|
let audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename)
|
||||||
let synthesizer
|
let synthesizer
|
||||||
|
let speaker = option?.speaker || '随机'
|
||||||
|
let context = text
|
||||||
|
// 打招呼用
|
||||||
|
if (speaker === '随机') {
|
||||||
|
speaker = supportConfigurations[Math.floor(Math.random() * supportConfigurations.length)].code
|
||||||
|
let languagePrefix = supportConfigurations.find(config => config.code === speaker).languageDetail.charAt(0)
|
||||||
|
languagePrefix = languagePrefix.startsWith('E') ? '英' : languagePrefix
|
||||||
|
context = (await translate(context, languagePrefix)).replace('\n', '')
|
||||||
|
}
|
||||||
if (ssml) {
|
if (ssml) {
|
||||||
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
||||||
await speakSsmlAsync(synthesizer, ssml)
|
await speakSsmlAsync(synthesizer, ssml)
|
||||||
} else {
|
} else { // 打招呼用
|
||||||
speechConfig.speechSynthesisLanguage = option?.language || 'zh-CN'
|
speechConfig.speechSynthesisLanguage = option?.language || supportConfigurations.find(config => config.code === speaker).language
|
||||||
logger.info('using speaker: ' + option?.speaker || 'zh-CN-YunyeNeural')
|
speechConfig.speechSynthesisVoiceName = speaker
|
||||||
speechConfig.speechSynthesisVoiceName = option?.speaker || 'zh-CN-YunyeNeural'
|
logger.info('using speaker: ' + speaker)
|
||||||
|
logger.info('using language: ' + speechConfig.speechSynthesisLanguage)
|
||||||
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
||||||
await speakTextAsync(synthesizer, text)
|
await speakTextAsync(synthesizer, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('synthesis finished.')
|
console.log('synthesis finished.')
|
||||||
synthesizer.close()
|
synthesizer.close()
|
||||||
synthesizer = undefined
|
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,11 +83,27 @@ async function speakSsmlAsync (synthesizer, ssml) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async function generateSsml (text, option = {}) {
|
async function generateSsml (text, option = {}) {
|
||||||
const voiceName = option.speaker || 'zh-CN-YunyeNeural'
|
let speaker = option?.speaker || '随机'
|
||||||
const expressAs = option.emotion ? `<mstts:express-as style="${option.emotion}" styledegree="${option.emotionDegree || 1}">` : ''
|
let emotionDegree, role, emotion
|
||||||
|
// 打招呼用
|
||||||
|
if (speaker === '随机') {
|
||||||
|
role = supportConfigurations[Math.floor(Math.random() * supportConfigurations.length)]
|
||||||
|
speaker = role.code
|
||||||
|
if (role?.emotion) {
|
||||||
|
const keys = Object.keys(role.emotion)
|
||||||
|
emotion = keys[Math.floor(Math.random() * keys.length)]
|
||||||
|
}
|
||||||
|
logger.info('using speaker: ' + speaker)
|
||||||
|
logger.info('using emotion: ' + emotion)
|
||||||
|
emotionDegree = 2
|
||||||
|
} else {
|
||||||
|
emotion = option.emotion
|
||||||
|
emotionDegree = option.emotionDegree
|
||||||
|
}
|
||||||
|
const expressAs = emotion !== undefined ? `<mstts:express-as style="${emotion}" styledegree="${emotionDegree || 1}">` : ''
|
||||||
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
|
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
|
||||||
xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">
|
xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">
|
||||||
<voice name="${voiceName}">
|
<voice name="${speaker}">
|
||||||
${expressAs}${text}${expressAs ? '</mstts:express-as>' : ''}
|
${expressAs}${text}${expressAs ? '</mstts:express-as>' : ''}
|
||||||
</voice>
|
</voice>
|
||||||
</speak>`
|
</speak>`
|
||||||
|
|
@ -91,7 +117,7 @@ async function getEmotionPrompt (e) {
|
||||||
let emotionPrompt = ''
|
let emotionPrompt = ''
|
||||||
let ttsRoleAzure = userReplySetting.ttsRoleAzure
|
let ttsRoleAzure = userReplySetting.ttsRoleAzure
|
||||||
const configuration = Config.ttsMode === 'azure' ? supportConfigurations.find(config => config.code === ttsRoleAzure) : ''
|
const configuration = Config.ttsMode === 'azure' ? supportConfigurations.find(config => config.code === ttsRoleAzure) : ''
|
||||||
if (configuration !== '' && configuration.emotion) {
|
if (configuration !== '' && configuration?.emotion) {
|
||||||
// 0-1 感觉没啥区别,说实话只有1和2听得出差别。。
|
// 0-1 感觉没啥区别,说实话只有1和2听得出差别。。
|
||||||
emotionPrompt = `\n在回复的最开始使用[]在其中表示你这次回复的情绪风格和程度(1-2),最小单位0.1
|
emotionPrompt = `\n在回复的最开始使用[]在其中表示你这次回复的情绪风格和程度(1-2),最小单位0.1
|
||||||
\n例如:['angry',2]表示你极度愤怒
|
\n例如:['angry',2]表示你极度愤怒
|
||||||
|
|
@ -110,28 +136,32 @@ export const supportConfigurations = [
|
||||||
name: '晓北',
|
name: '晓北',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(东北官话,简体)',
|
languageDetail: '中文(东北官话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '晓北-女-中文(东北官话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-henan-YundengNeural',
|
code: 'zh-CN-henan-YundengNeural',
|
||||||
name: '云登',
|
name: '云登',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(中原官话河南,简体)',
|
languageDetail: '中文(中原官话河南,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '云登-男-中文(中原官话河南,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-shaanxi-XiaoniNeural',
|
code: 'zh-CN-shaanxi-XiaoniNeural',
|
||||||
name: '晓妮',
|
name: '晓妮',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(中原官话陕西,简体)',
|
languageDetail: '中文(中原官话陕西,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '晓妮-女-中文(中原官话陕西,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-henan-YundengNeural',
|
code: 'zh-CN-henan-YundengNeural',
|
||||||
name: '云翔',
|
name: '云翔',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(冀鲁官话,简体)',
|
languageDetail: '中文(冀鲁官话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '云翔-男-中文(冀鲁官话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoxiaoNeural',
|
code: 'zh-CN-XiaoxiaoNeural',
|
||||||
|
|
@ -157,7 +187,8 @@ export const supportConfigurations = [
|
||||||
'poetry-reading': '读诗时带情感和节奏的语气',
|
'poetry-reading': '读诗时带情感和节奏的语气',
|
||||||
sad: '表达悲伤语气',
|
sad: '表达悲伤语气',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓晓-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunxiNeural',
|
code: 'zh-CN-YunxiNeural',
|
||||||
|
|
@ -178,7 +209,8 @@ export const supportConfigurations = [
|
||||||
newscast: '用于新闻播报,表现出庄重、严谨的语气',
|
newscast: '用于新闻播报,表现出庄重、严谨的语气',
|
||||||
sad: '表达悲伤、失落的语气',
|
sad: '表达悲伤、失落的语气',
|
||||||
serious: '表现出认真、严肃的语气'
|
serious: '表现出认真、严肃的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云希-男-中文 (普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunyangNeural',
|
code: 'zh-CN-YunyangNeural',
|
||||||
|
|
@ -190,7 +222,8 @@ export const supportConfigurations = [
|
||||||
customerservice: '以亲切友好的语气为客户提供支持',
|
customerservice: '以亲切友好的语气为客户提供支持',
|
||||||
'narration-professional': '以专业、稳重的语气讲述',
|
'narration-professional': '以专业、稳重的语气讲述',
|
||||||
'newscast-casual': '以轻松自然的语气播报新闻'
|
'newscast-casual': '以轻松自然的语气播报新闻'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云扬-男-中文 (普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunyeNeural',
|
code: 'zh-CN-YunyeNeural',
|
||||||
|
|
@ -207,7 +240,8 @@ export const supportConfigurations = [
|
||||||
fearful: '表达害怕和不安的语气',
|
fearful: '表达害怕和不安的语气',
|
||||||
sad: '表达悲伤和失落的语气',
|
sad: '表达悲伤和失落的语气',
|
||||||
serious: '以认真和严肃的态度说话'
|
serious: '以认真和严肃的态度说话'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云野-男-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoshuangNeural',
|
code: 'zh-CN-XiaoshuangNeural',
|
||||||
|
|
@ -215,37 +249,40 @@ export const supportConfigurations = [
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女',
|
gender: '女',
|
||||||
emotion: {
|
emotion: { chat: '表达轻松随意的语气' },
|
||||||
chat: '表达轻松随意的语气'
|
roleInfo: '晓双-女-中文(普通话,简体)'
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoyouNeural',
|
code: 'zh-CN-XiaoyouNeural',
|
||||||
name: '晓悠',
|
name: '晓悠',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '晓悠-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoqiuNeural',
|
code: 'zh-CN-XiaoqiuNeural',
|
||||||
name: '晓秋',
|
name: '晓秋',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '晓秋-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaochenNeural',
|
code: 'zh-CN-XiaochenNeural',
|
||||||
name: '晓辰',
|
name: '晓辰',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '晓辰-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoyanNeural',
|
code: 'zh-CN-XiaoyanNeural',
|
||||||
name: '晓颜',
|
name: '晓颜',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '晓颜-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaomoNeural',
|
code: 'zh-CN-XiaomoNeural',
|
||||||
|
|
@ -266,7 +303,8 @@ export const supportConfigurations = [
|
||||||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
sad: '表达悲伤语气',
|
sad: '表达悲伤语气',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓墨-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoxuanNeural',
|
code: 'zh-CN-XiaoxuanNeural',
|
||||||
|
|
@ -283,7 +321,8 @@ export const supportConfigurations = [
|
||||||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓萱-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaohanNeural',
|
code: 'zh-CN-XiaohanNeural',
|
||||||
|
|
@ -302,7 +341,8 @@ export const supportConfigurations = [
|
||||||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
sad: '表达悲伤语气',
|
sad: '表达悲伤语气',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓涵-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoruiNeural',
|
code: 'zh-CN-XiaoruiNeural',
|
||||||
|
|
@ -315,7 +355,8 @@ export const supportConfigurations = [
|
||||||
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
sad: '表达悲伤语气'
|
sad: '表达悲伤语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓睿-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaomengNeural',
|
code: 'zh-CN-XiaomengNeural',
|
||||||
|
|
@ -323,9 +364,8 @@ export const supportConfigurations = [
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女',
|
gender: '女',
|
||||||
emotion: {
|
emotion: { chat: '表达轻松随意的语气' },
|
||||||
chat: '表达轻松随意的语气'
|
roleInfo: '晓梦-女-中文(普通话,简体)'
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoyiNeural',
|
code: 'zh-CN-XiaoyiNeural',
|
||||||
|
|
@ -340,7 +380,8 @@ export const supportConfigurations = [
|
||||||
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
sad: '表达悲伤语气',
|
sad: '表达悲伤语气',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓伊-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaozhenNeural',
|
code: 'zh-CN-XiaozhenNeural',
|
||||||
|
|
@ -355,7 +396,8 @@ export const supportConfigurations = [
|
||||||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
sad: '表达悲伤语气',
|
sad: '表达悲伤语气',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '晓甄-女-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunfengNeural',
|
code: 'zh-CN-YunfengNeural',
|
||||||
|
|
@ -371,14 +413,16 @@ export const supportConfigurations = [
|
||||||
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
sad: '表达悲伤语气',
|
sad: '表达悲伤语气',
|
||||||
serious: '严肃、命令的语气'
|
serious: '严肃、命令的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云枫-男-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunhaoNeural',
|
code: 'zh-CN-YunhaoNeural',
|
||||||
name: '云皓',
|
name: '云皓',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '云皓-男-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunjianNeural',
|
code: 'zh-CN-YunjianNeural',
|
||||||
|
|
@ -390,7 +434,8 @@ export const supportConfigurations = [
|
||||||
'narration-relaxed': '以轻松、自然的语气进行叙述',
|
'narration-relaxed': '以轻松、自然的语气进行叙述',
|
||||||
'sports-commentary': '在解说体育比赛时,使用专业而自信的语气',
|
'sports-commentary': '在解说体育比赛时,使用专业而自信的语气',
|
||||||
'sports-commentary-excited': '在解说激动人心的体育比赛时,使用兴奋和激动的语气'
|
'sports-commentary-excited': '在解说激动人心的体育比赛时,使用兴奋和激动的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云健-男-中文(普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunxiaNeural',
|
code: 'zh-CN-YunxiaNeural',
|
||||||
|
|
@ -404,7 +449,8 @@ export const supportConfigurations = [
|
||||||
cheerful: '表达积极愉快的语气',
|
cheerful: '表达积极愉快的语气',
|
||||||
fearful: '表达害怕、紧张的语气',
|
fearful: '表达害怕、紧张的语气',
|
||||||
sad: '表达悲伤和失落的语气'
|
sad: '表达悲伤和失落的语气'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云夏-男-中文 (普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunzeNeural',
|
code: 'zh-CN-YunzeNeural',
|
||||||
|
|
@ -422,105 +468,120 @@ export const supportConfigurations = [
|
||||||
fearful: '表达害怕、不安的情绪',
|
fearful: '表达害怕、不安的情绪',
|
||||||
sad: '用悲伤的语气表达悲伤和失落',
|
sad: '用悲伤的语气表达悲伤和失落',
|
||||||
serious: '以严肃的语气和态度表现出对事情的重视和认真对待'
|
serious: '以严肃的语气和态度表现出对事情的重视和认真对待'
|
||||||
}
|
},
|
||||||
|
roleInfo: '云泽-男-中文 (普通话,简体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-HK-HiuGaaiNeural',
|
code: 'zh-HK-HiuGaaiNeural',
|
||||||
name: '曉佳',
|
name: '曉佳',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(粤语,繁体)',
|
languageDetail: '中文(粤语,繁体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '曉佳-女-中文(粤语,繁体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-HK-HiuMaanNeural',
|
code: 'zh-HK-HiuMaanNeural',
|
||||||
name: '曉曼',
|
name: '曉曼',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(粤语,繁体)',
|
languageDetail: '中文(粤语,繁体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '曉曼-女-中文(粤语,繁体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-HK-WanLungNeural',
|
code: 'zh-HK-WanLungNeural',
|
||||||
name: '雲龍',
|
name: '雲龍',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(粤语,繁体)',
|
languageDetail: '中文(粤语,繁体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '雲龍-男-中文(粤语,繁体)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-AbbiNeural',
|
code: 'en-GB-AbbiNeural',
|
||||||
name: 'Abbi',
|
name: 'Abbi',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Abbi-女-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-AlfieNeural',
|
code: 'en-GB-AlfieNeural',
|
||||||
name: 'Alfie',
|
name: 'Alfie',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Alfie-男-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-BellaNeural',
|
code: 'en-GB-BellaNeural',
|
||||||
name: 'Bella',
|
name: 'Bella',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Bella-女-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-ElliotNeural',
|
code: 'en-GB-ElliotNeural',
|
||||||
name: 'Elliot',
|
name: 'Elliot',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Elliot-男-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-EthanNeural',
|
code: 'en-GB-EthanNeural',
|
||||||
name: 'Ethan',
|
name: 'Ethan',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Ethan-男-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-HollieNeural',
|
code: 'en-GB-HollieNeural',
|
||||||
name: 'Hollie',
|
name: 'Hollie',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Hollie-女-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-LibbyNeural',
|
code: 'en-GB-LibbyNeural',
|
||||||
name: 'Libby',
|
name: 'Libby',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Libby-女-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-MaisieNeural',
|
code: 'en-GB-MaisieNeural',
|
||||||
name: 'Maisie',
|
name: 'Maisie',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Maisie-女-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-NoahNeural',
|
code: 'en-GB-NoahNeural',
|
||||||
name: 'Noah',
|
name: 'Noah',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Noah-男-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-OliverNeural',
|
code: 'en-GB-OliverNeural',
|
||||||
name: 'Oliver',
|
name: 'Oliver',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Oliver-男-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-OliviaNeural',
|
code: 'en-GB-OliviaNeural',
|
||||||
name: 'Olivia',
|
name: 'Olivia',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Olivia-女-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-RyanNeural',
|
code: 'en-GB-RyanNeural',
|
||||||
|
|
@ -528,11 +589,8 @@ export const supportConfigurations = [
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male',
|
gender: 'male',
|
||||||
emotion: {
|
emotion: { chat: '表达轻松随意的语气', cheerful: '表达积极愉快的语气' },
|
||||||
chat: '表达轻松随意的语气',
|
roleInfo: 'Ryan-男-英语(英国)'
|
||||||
cheerful: '表达积极愉快的语气'
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-SoniaNeural',
|
code: 'en-GB-SoniaNeural',
|
||||||
|
|
@ -540,46 +598,48 @@ export const supportConfigurations = [
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'female',
|
gender: 'female',
|
||||||
emotion: {
|
emotion: { cheerful: '表达积极愉快的语气', sad: '表达悲伤语气' },
|
||||||
cheerful: '表达积极愉快的语气',
|
roleInfo: 'Sonia-女-英语(英国)'
|
||||||
sad: '表达悲伤语气'
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-GB-ThomasNeural',
|
code: 'en-GB-ThomasNeural',
|
||||||
name: 'Thomas',
|
name: 'Thomas',
|
||||||
language: 'en-GB',
|
language: 'en-GB',
|
||||||
languageDetail: '英语(英国)',
|
languageDetail: '英语(英国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Thomas-男-英语(英国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-AoiNeural',
|
code: 'ja-JP-AoiNeural',
|
||||||
name: '葵',
|
name: '葵',
|
||||||
language: 'ja-JP',
|
language: 'ja-JP',
|
||||||
languageDetail: '日语(日本)',
|
languageDetail: '日语(日本)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '葵-女-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-DaichiNeural',
|
code: 'ja-JP-DaichiNeural',
|
||||||
name: '大地',
|
name: '大地',
|
||||||
language: 'ja-JP',
|
language: 'ja-JP',
|
||||||
languageDetail: '日语(日本)',
|
languageDetail: '日语(日本)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '大地-男-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-KeitaNeural',
|
code: 'ja-JP-KeitaNeural',
|
||||||
name: '慶太',
|
name: '慶太',
|
||||||
language: 'ja-JP',
|
language: 'ja-JP',
|
||||||
languageDetail: '日语(日本)',
|
languageDetail: '日语(日本)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '慶太-男-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-MayuNeural',
|
code: 'ja-JP-MayuNeural',
|
||||||
name: '真由',
|
name: '真由',
|
||||||
language: 'ja-JP',
|
language: 'ja-JP',
|
||||||
languageDetail: '日语(日本)',
|
languageDetail: '日语(日本)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '真由-女-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-NanamiNeural',
|
code: 'ja-JP-NanamiNeural',
|
||||||
|
|
@ -591,49 +651,56 @@ export const supportConfigurations = [
|
||||||
chat: '表达轻松随意的语气',
|
chat: '表达轻松随意的语气',
|
||||||
cheerful: '表达积极愉快的语气',
|
cheerful: '表达积极愉快的语气',
|
||||||
customerservice: '以友好热情的语气为客户提供支持'
|
customerservice: '以友好热情的语气为客户提供支持'
|
||||||
}
|
},
|
||||||
|
roleInfo: '七海-女-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-NaokiNeural',
|
code: 'ja-JP-NaokiNeural',
|
||||||
name: '直樹',
|
name: '直樹',
|
||||||
language: 'ja-JP',
|
language: 'ja-JP',
|
||||||
languageDetail: '日语(日本)',
|
languageDetail: '日语(日本)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: '直樹-男-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'ja-JP-ShioriNeural',
|
code: 'ja-JP-ShioriNeural',
|
||||||
name: '栞',
|
name: '栞',
|
||||||
language: 'ja-JP',
|
language: 'ja-JP',
|
||||||
languageDetail: '日语(日本)',
|
languageDetail: '日语(日本)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: '栞-女-日语(日本)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-AIGenerate1Neural1',
|
code: 'en-US-AIGenerate1Neural1',
|
||||||
name: 'AI Generate 1',
|
name: 'AI Generate 1',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: 'AI Generate 1-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-AIGenerate2Neural1',
|
code: 'en-US-AIGenerate2Neural1',
|
||||||
name: 'AI Generate 2',
|
name: 'AI Generate 2',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: 'AI Generate 2-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-AmberNeural',
|
code: 'en-US-AmberNeural',
|
||||||
name: 'Amber',
|
name: 'Amber',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: 'Amber-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-AnaNeural',
|
code: 'en-US-AnaNeural',
|
||||||
name: 'Ana',
|
name: 'Ana',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '女性、儿童'
|
gender: '女性、儿童',
|
||||||
|
roleInfo: 'Ana-女性、儿童-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-AriaNeural',
|
code: 'en-US-AriaNeural',
|
||||||
|
|
@ -658,35 +725,40 @@ export const supportConfigurations = [
|
||||||
'narration-professional': '以专业、客观的语气朗读内容',
|
'narration-professional': '以专业、客观的语气朗读内容',
|
||||||
'newscast-casual': '以通用、随意的语气发布一般新闻',
|
'newscast-casual': '以通用、随意的语气发布一般新闻',
|
||||||
'newscast-formal': '以正式、自信和权威的语气发布新闻'
|
'newscast-formal': '以正式、自信和权威的语气发布新闻'
|
||||||
}
|
},
|
||||||
|
roleInfo: 'Aria-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-AshleyNeural',
|
code: 'en-US-AshleyNeural',
|
||||||
name: 'Ashley',
|
name: 'Ashley',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: 'Ashley-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-BrandonNeural',
|
code: 'en-US-BrandonNeural',
|
||||||
name: 'Brandon',
|
name: 'Brandon',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: 'Brandon-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-ChristopherNeural',
|
code: 'en-US-ChristopherNeural',
|
||||||
name: 'Christopher',
|
name: 'Christopher',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: 'Christopher-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-CoraNeural',
|
code: 'en-US-CoraNeural',
|
||||||
name: 'Cora',
|
name: 'Cora',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: 'Cora-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-DavisNeural',
|
code: 'en-US-DavisNeural',
|
||||||
|
|
@ -705,21 +777,24 @@ export const supportConfigurations = [
|
||||||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
}
|
},
|
||||||
|
roleInfo: 'Davis-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-ElizabethNeural',
|
code: 'en-US-ElizabethNeural',
|
||||||
name: 'Elizabeth',
|
name: 'Elizabeth',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
roleInfo: 'Elizabeth-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-EricNeural',
|
code: 'en-US-EricNeural',
|
||||||
name: 'Eric',
|
name: 'Eric',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: 'Eric-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-GuyNeural',
|
code: 'en-US-GuyNeural',
|
||||||
|
|
@ -739,15 +814,16 @@ export const supportConfigurations = [
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔',
|
whispering: '说话非常柔和,发出的声音小且温柔',
|
||||||
newscast: '以正式专业的语气叙述新闻'
|
newscast: '以正式专业的语气叙述新闻'
|
||||||
|
},
|
||||||
}
|
roleInfo: 'Guy-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-JacobNeural',
|
code: 'en-US-JacobNeural',
|
||||||
name: 'Jacob',
|
name: 'Jacob',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: 'English (United States)',
|
languageDetail: 'English (United States)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
roleInfo: 'Jacob-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-JaneNeural',
|
code: 'en-US-JaneNeural',
|
||||||
|
|
@ -766,7 +842,8 @@ export const supportConfigurations = [
|
||||||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
}
|
},
|
||||||
|
roleInfo: 'Jane-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-JasonNeural',
|
code: 'en-US-JasonNeural',
|
||||||
|
|
@ -785,14 +862,8 @@ export const supportConfigurations = [
|
||||||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
roleInfo: 'Jason-男-英语(美国)'
|
||||||
code: 'en-US-JennyMultilingualNeural3',
|
|
||||||
name: 'Jenny',
|
|
||||||
language: 'en-US',
|
|
||||||
languageDetail: '英语(美国)',
|
|
||||||
gender: 'female'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-JennyNeural',
|
code: 'en-US-JennyNeural',
|
||||||
|
|
@ -815,22 +886,24 @@ export const supportConfigurations = [
|
||||||
chat: '表达轻松随意的语气',
|
chat: '表达轻松随意的语气',
|
||||||
customerservice: '以友好热情的语气为客户提供支持',
|
customerservice: '以友好热情的语气为客户提供支持',
|
||||||
newscast: '以正式专业的语气叙述新闻'
|
newscast: '以正式专业的语气叙述新闻'
|
||||||
|
},
|
||||||
}
|
roleInfo: 'Jenny-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-MichelleNeural',
|
code: 'en-US-MichelleNeural',
|
||||||
name: 'Michelle',
|
name: 'Michelle',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: '英语(美国)',
|
languageDetail: '英语(美国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Michelle-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-MonicaNeural',
|
code: 'en-US-MonicaNeural',
|
||||||
name: 'Monica',
|
name: 'Monica',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: '英语(美国)',
|
languageDetail: '英语(美国)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Monica-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-NancyNeural',
|
code: 'en-US-NancyNeural',
|
||||||
|
|
@ -849,14 +922,16 @@ export const supportConfigurations = [
|
||||||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
}
|
},
|
||||||
|
roleInfo: 'Nancy-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-RogerNeural',
|
code: 'en-US-RogerNeural',
|
||||||
name: 'Roger',
|
name: 'Roger',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: '英语(美国)',
|
languageDetail: '英语(美国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Roger-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-SaraNeural',
|
code: 'en-US-SaraNeural',
|
||||||
|
|
@ -875,15 +950,16 @@ export const supportConfigurations = [
|
||||||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
},
|
||||||
}
|
roleInfo: 'Sara-女-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-SteffanNeural',
|
code: 'en-US-SteffanNeural',
|
||||||
name: 'Steffan',
|
name: 'Steffan',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
languageDetail: '英语(美国)',
|
languageDetail: '英语(美国)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Steffan-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-US-TonyNeural',
|
code: 'en-US-TonyNeural',
|
||||||
|
|
@ -902,21 +978,24 @@ export const supportConfigurations = [
|
||||||
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
unfriendly: '表达一种冷淡无情的语气',
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
whispering: '说话非常柔和,发出的声音小且温柔'
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
}
|
},
|
||||||
|
roleInfo: 'Tony-男-英语(美国)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-IN-NeerjaNeural',
|
code: 'en-IN-NeerjaNeural',
|
||||||
name: 'Neerja',
|
name: 'Neerja',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
languageDetail: '英语(印度)',
|
languageDetail: '英语(印度)',
|
||||||
gender: 'female'
|
gender: 'female',
|
||||||
|
roleInfo: 'Neerja-女-英语(印度)'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'en-IN-PrabhatNeural',
|
code: 'en-IN-PrabhatNeural',
|
||||||
name: 'Prabhat',
|
name: 'Prabhat',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
languageDetail: '英语(印度)',
|
languageDetail: '英语(印度)',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
|
roleInfo: 'Prabhat-男-英语(印度)'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tokenizer {
|
export class Tokenizer {
|
||||||
async getTodayHistory (groupId, date = new Date()) {
|
async getHistory (groupId, date = new Date(), duration = 0) {
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
throw new Error('no valid group id')
|
throw new Error('no valid group id')
|
||||||
}
|
}
|
||||||
|
|
@ -29,11 +29,22 @@ export class Tokenizer {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
// Get the current timestamp
|
||||||
|
let currentTime = date.getTime()
|
||||||
|
|
||||||
// Step 2: Set the hours, minutes, seconds, and milliseconds to 0
|
// Step 2: Set the hours, minutes, seconds, and milliseconds to 0
|
||||||
date.setHours(0, 0, 0, 0)
|
date.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
// Step 3: Calculate the timestamp representing the start of the specified date
|
// Step 3: Calculate the timestamp representing the start of the specified date
|
||||||
const startOfSpecifiedDate = date.getTime()
|
// duration represents the number of hours to go back
|
||||||
|
// if duration is 0, keeping the original date (start of today)
|
||||||
|
let startOfSpecifiedDate = date.getTime()
|
||||||
|
// if duration > 0, go back to the specified number of hours
|
||||||
|
if (duration > 0) {
|
||||||
|
// duration should be in range [0, 24]
|
||||||
|
duration = Math.min(duration, 24)
|
||||||
|
startOfSpecifiedDate = currentTime - (duration * 60 * 60 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
// Step 4: Get the end of the specified date by adding 24 hours (in milliseconds)
|
// Step 4: Get the end of the specified date by adding 24 hours (in milliseconds)
|
||||||
const endOfSpecifiedDate = startOfSpecifiedDate + (24 * 60 * 60 * 1000)
|
const endOfSpecifiedDate = startOfSpecifiedDate + (24 * 60 * 60 * 1000)
|
||||||
|
|
@ -56,12 +67,14 @@ export class Tokenizer {
|
||||||
return chats
|
return chats
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTodayKeywordTopK (groupId, topK = 100) {
|
async getKeywordTopK (groupId, topK = 100, duration = 0) {
|
||||||
if (!nodejieba) {
|
if (!nodejieba) {
|
||||||
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
||||||
}
|
}
|
||||||
let chats = await this.getTodayHistory(groupId)
|
// duration represents the number of hours to go back, should in range [0, 24]
|
||||||
logger.mark(`聊天记录拉去完成,获取到今日内${chats.length}条聊天记录,准备分词中`)
|
let chats = await this.getHistory(groupId, new Date(), duration)
|
||||||
|
let duration_str = duration > 0 ? `${duration}小时` : '今日'
|
||||||
|
logger.mark(`聊天记录拉取完成,获取到${duration_str}内${chats.length}条聊天记录,准备分词中`)
|
||||||
|
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
let stopWordsPath = `${_path}/plugins/chatgpt-plugin/utils/wordcloud/cn_stopwords.txt`
|
let stopWordsPath = `${_path}/plugins/chatgpt-plugin/utils/wordcloud/cn_stopwords.txt`
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Tokenizer } from './tokenizer.js'
|
import { Tokenizer } from './tokenizer.js'
|
||||||
import { render } from '../common.js'
|
import { render } from '../common.js'
|
||||||
|
|
||||||
export async function makeWordcloud (e, groupId) {
|
export async function makeWordcloud (e, groupId, duration = 0) {
|
||||||
let tokenizer = new Tokenizer()
|
let tokenizer = new Tokenizer()
|
||||||
let topK = await tokenizer.getTodayKeywordTopK(groupId, 100)
|
let topK = await tokenizer.getKeywordTopK(groupId, 100, duration)
|
||||||
let list = JSON.stringify(topK)
|
let list = JSON.stringify(topK)
|
||||||
// let list = topK
|
// let list = topK
|
||||||
console.log(list)
|
console.log(list)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue