fix: merge tons of conflict

This commit is contained in:
ikechan8370 2023-07-01 15:55:07 +08:00
commit 7a8b30db14
50 changed files with 1791 additions and 828 deletions

View file

@ -7,10 +7,8 @@ import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
import { BingAIClient } from '@waylaidwanderer/chatgpt-api'
import SydneyAIClient from '../utils/SydneyAIClient.js'
import { PoeClient } from '../utils/poe/index.js'
import AzureTTS, { supportConfigurations } from '../utils/tts/microsoft-azure.js'
import AzureTTS from '../utils/tts/microsoft-azure.js'
import VoiceVoxTTS from '../utils/tts/voicevox.js'
import { translate } from '../utils/translate.js'
import fs from 'fs'
import {
render,
renderUrl,
@ -27,24 +25,21 @@ import {
getUserReplySetting,
getImageOcrText,
getImg,
processList,
getMaxModelTokens, formatDate
getMaxModelTokens, formatDate, generateAudio
} from '../utils/common.js'
import { ChatGPTPuppeteer } from '../utils/browser.js'
import { KeyvFile } from 'keyv-file'
import { OfficialChatGPTClient } from '../utils/message.js'
import fetch from 'node-fetch'
import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js'
import { convertSpeaker, generateAudio, speakers } from '../utils/tts.js'
import { convertSpeaker, speakers } from '../utils/tts.js'
import ChatGLMClient from '../utils/chatglm.js'
import { convertFaces } from '../utils/face.js'
import uploadRecord from '../utils/uploadRecord.js'
import { SlackClaudeClient } from '../utils/slack/slackClient.js'
import { getPromptByName } from '../utils/prompts.js'
import BingDrawClient from '../utils/BingDraw.js'
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
import { JinyanTool } from '../utils/tools/JinyanTool.js'
import { SendMusicTool } from '../utils/tools/SendMusicTool.js'
import { SendVideoTool } from '../utils/tools/SendBilibiliTool.js'
import { KickOutTool } from '../utils/tools/KickOutTool.js'
import { EditCardTool } from '../utils/tools/EditCardTool.js'
@ -58,12 +53,19 @@ import { SerpIkechan8370Tool } from '../utils/tools/SerpIkechan8370Tool.js'
import { SendPictureTool } from '../utils/tools/SendPictureTool.js'
import { SerpImageTool } from '../utils/tools/SearchImageTool.js'
import { ImageCaptionTool } from '../utils/tools/ImageCaptionTool.js'
import { TTSTool } from '../utils/tools/TTSTool.js'
import { SendAudioMessageTool } from '../utils/tools/SendAudioMessageTool.js'
import { ProcessPictureTool } from '../utils/tools/ProcessPictureTool.js'
import { APTool } from '../utils/tools/APTool.js'
import { QueryGenshinTool } from '../utils/tools/QueryGenshinTool.js'
import { HandleMessageMsgTool } from '../utils/tools/HandleMessageMsgTool.js'
import {QueryUserinfoTool} from "../utils/tools/QueryUserinfoTool.js";
import { QueryUserinfoTool } from '../utils/tools/QueryUserinfoTool.js'
import { EliMovieTool } from '../utils/tools/EliMovieTool.js'
import { EliMusicTool } from '../utils/tools/EliMusicTool.js'
import { SendMusicTool } from '../utils/tools/SendMusicTool.js'
import { SendDiceTool } from '../utils/tools/SendDiceTool.js'
import { SendAvatarTool } from '../utils/tools/SendAvatarTool.js'
import { SendMessageToSpecificGroupOrUserTool } from '../utils/tools/SendMessageToSpecificGroupOrUserTool.js'
try {
await import('emoji-strip')
} catch (err) {
@ -283,6 +285,8 @@ export class chatgpt extends plugin {
return
}
let ats = e.message.filter(m => m.type === 'at')
const isAtMode = Config.toggleMode === 'at'
if (isAtMode) ats = ats.filter(item => item.qq !== Bot.uin)
if (ats.length === 0) {
if (use === 'api3') {
await redis.del(`CHATGPT:QQ_CONVERSATION:${e.sender.user_id}`)
@ -783,16 +787,37 @@ export class chatgpt extends plugin {
return false
}
// 黑白名单过滤对话
let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist)
let [whitelist, blacklist] = [Config.whitelist, Config.blacklist]
let chatPermission = false // 对话许可
if (whitelist.join('').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
for (const item of whitelist) {
if (item.length > 11) {
const [group, qq] = item.split('^')
if (e.isGroup && group === e.group_id.toString() && qq === e.sender.user_id.toString()) {
chatPermission = true
break
}
} else if (item.startsWith('^') && item.slice(1) === e.sender.user_id.toString()) {
chatPermission = true
break
} else if (e.isGroup && !item.startsWith('^') && item === e.group_id.toString()) {
chatPermission = true
break
}
}
}
if (blacklist.join('').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
// 当前用户有对话许可则不再判断黑名单
if (!chatPermission) {
if (blacklist.join('').length > 0) {
for (const item of blacklist) {
if (e.isGroup && !item.startsWith('^') && item === e.group_id.toString()) return false
if (item.startsWith('^') && item.slice(1) === e.sender.user_id.toString()) return false
if (item.length > 11) {
const [group, qq] = item.split('^')
if (e.isGroup && group === e.group_id.toString() && qq === e.sender.user_id.toString()) return false
}
}
}
}
let userSetting = await getUserReplySetting(this.e)
@ -1149,82 +1174,12 @@ export class chatgpt extends plugin {
this.reply(`建议的回复:\n${chatMessage.suggestedResponses}`)
}
}
let wav
if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) {
if (Config.autoJapanese) {
try {
ttsResponse = await translate(ttsResponse, '日')
} catch (err) {
logger.error(err)
await this.reply(err.message + '\n将使用原始文本合成语音...')
}
}
try {
wav = await generateAudio(ttsResponse, speaker, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
} catch (err) {
logger.error(err)
await this.reply('合成语音发生错误~')
}
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
if (speaker !== '随机') {
let languagePrefix = AzureTTS.supportConfigurations.find(config => config.code === speaker).languageDetail.charAt(0)
languagePrefix = languagePrefix.startsWith('E') ? '英' : languagePrefix
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, {
speaker,
emotion,
emotionDegree
})
wav = await AzureTTS.generateAudio(ttsResponse, {
speaker
}, await ssml)
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
ttsResponse = (await translate(ttsResponse, '日')).replace('\n', '')
wav = await VoiceVoxTTS.generateAudio(ttsResponse, {
speaker
})
} else if (!Config.ttsSpace && !Config.azureTTSKey && !Config.voicevoxSpace) {
await this.reply('你没有配置转语音API哦')
}
try {
try {
let sendable = await uploadRecord(wav, Config.ttsMode)
if (sendable) {
await e.reply(sendable)
} else {
// 如果合成失败尝试使用ffmpeg合成
await e.reply(segment.record(wav))
}
} catch (err) {
logger.error(err)
await e.reply(segment.record(wav))
}
} catch (err) {
logger.error(err)
const sendable = await generateAudio(this.e, ttsResponse, emotion, emotionDegree)
if (sendable) {
await this.reply(sendable)
} else {
await this.reply('合成语音发生错误~')
}
if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
// 清理文件
try {
fs.unlinkSync(wav)
} catch (err) {
logger.warn(err)
}
}
} else if (userSetting.usePicture || (Config.autoUsePicture && response.length > Config.autoUsePictureThreshold)) {
// todo use next api of chatgpt to complete incomplete respoonse
try {
@ -1849,12 +1804,13 @@ export class chatgpt extends plugin {
chats.push(...chatHistory.reverse())
}
chats = chats.slice(0, Config.groupContextLength)
// 太多可能会干扰AI对自身qq号和用户qq的判断感觉gpt3.5也处理不了那么多信息
chats = chats > 50 ? 50 : chats
let mm = await e.group.getMemberMap()
chats.forEach(chat => {
let sender = mm.get(chat.sender.user_id)
chat.sender = sender
})
// console.log(chats)
opt.chats = chats
const namePlaceholder = '[name]'
const defaultBotName = 'ChatGPT'
@ -1872,8 +1828,7 @@ export class chatgpt extends plugin {
admin: 'group administrator'
}
if (chats) {
system += `There is the conversation history in the group, you must chat according to the conversation history context"
`
system += `There is the conversation history in the group, you must chat according to the conversation history context"`
system += chats
.map(chat => {
let sender = chat.sender || {}
@ -1891,6 +1846,7 @@ export class chatgpt extends plugin {
logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err)
}
}
// logger.info(system)
}
let opts = {
apiBaseUrl: Config.openAiBaseUrl,
@ -1949,45 +1905,58 @@ export class chatgpt extends plugin {
new WeatherTool(),
new SendPictureTool(),
new SendVideoTool(),
new SearchMusicTool(),
new SendMusicTool(),
new ImageCaptionTool(),
new SearchVideoTool(),
new SendAvatarTool(),
new SerpImageTool(),
new SearchMusicTool(),
new SendMusicTool(),
new SerpIkechan8370Tool(),
new SerpTool(),
new TTSTool(),
new SendAudioMessageTool(),
new ProcessPictureTool(),
new APTool(),
new QueryGenshinTool(),
new HandleMessageMsgTool(),
new QueryUserinfoTool()
new QueryUserinfoTool(),
new EliMusicTool(),
new EliMovieTool(),
new SendMessageToSpecificGroupOrUserTool(),
new SendDiceTool(),
new QueryGenshinTool()
]
// todo 3.0再重构tool的插拔和管理
let tools = [
// new SendAvatarTool(),
// new SendDiceTool(),
new SendAvatarTool(),
new SendDiceTool(),
new SendMessageToSpecificGroupOrUserTool(),
// new EditCardTool(),
new QueryStarRailTool(),
new QueryGenshinTool(),
new ProcessPictureTool(),
new WebsiteTool(),
// new JinyanTool(),
// new KickOutTool(),
new WeatherTool(),
new SendPictureTool(),
new TTSTool(),
new SendAudioMessageTool(),
new APTool(),
// new HandleMessageMsgTool(),
serpTool,
new QueryUserinfoTool()
]
try {
await import('../../avocado-plugin/apps/avocado.js')
tools.push(...[new EliMusicTool(), new EliMovieTool()])
} catch (err) {
tools.push(...[new SendMusicTool(), new SearchMusicTool()])
logger.mark(logger.green('【ChatGPT-Plugin】插件avocado-plugin未安装') + ',安装后可查看最近热映电影与体验可玩性更高的点歌工具。\n可前往 https://github.com/Qz-Sean/avocado-plugin 获取')
}
if (e.isGroup) {
let botInfo = await Bot.getGroupMemberInfo(e.group_id, Bot.uin, true)
if (botInfo.role !== 'member') {
// 管理员才给这些工具
tools.push(...[new EditCardTool(), new JinyanTool(), new KickOutTool(), new HandleMessageMsgTool()])
// 用于撤回和加精的id
if (e.source?.seq) {
let source = (await e.group.getChatHistory(e.source?.seq, 1)).pop()
option.systemMessage += `\nthe last message is replying to ${source.message_id}"\n`
@ -2025,8 +1994,7 @@ export class chatgpt extends plugin {
tools.push(new SerpImageTool())
tools.push(...[new SearchVideoTool(),
new SendVideoTool(),
new SearchMusicTool(),
new SendMusicTool()])
new EliMusicTool()])
}
let funcMap = {}
let fullFuncMap = {}
@ -2053,6 +2021,7 @@ export class chatgpt extends plugin {
while (msg.functionCall) {
let { name, arguments: args } = msg.functionCall
args = JSON.parse(args)
// 感觉换成targetGroupIdOrUserQQNumber这种表意比较清楚的变量名效果会好一丢丢
if (!args.groupId) {
args.groupId = e.group_id + '' || e.sender.user_id + ''
}
@ -2061,14 +2030,6 @@ export class chatgpt extends plugin {
} catch (err) {
args.groupId = e.group_id + '' || e.sender.user_id + ''
}
if (!args.qq) {
args.qq = e.sender.user_id + ''
}
try {
parseInt(args.qq)
} catch (err) {
args.qq = e.sender.user_id + ''
}
let functionResult = await fullFuncMap[name].exec(Object.assign({ isAdmin, sender }, args), e)
logger.mark(`function ${name} execution result: ${functionResult}`)
option.parentMessageId = msg.id

View file

@ -1,7 +1,7 @@
import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
import { generateHello } from '../utils/randomMessage.js'
import { generateAudio } from '../utils/tts.js'
import { generateVitsAudio } from '../utils/tts.js'
import fs from 'fs'
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
import fetch from 'node-fetch'
@ -304,7 +304,7 @@ ${translateLangLabels}
let sendable = message
logger.info(`打招呼给群聊${groupId}` + message)
if (Config.defaultUseTTS) {
let audio = await generateAudio(message, Config.defaultTTSRole)
let audio = await generateVitsAudio(message, Config.defaultTTSRole)
sendable = segment.record(audio)
}
if (!groupId) {
@ -362,7 +362,7 @@ ${translateLangLabels}
}
}
try {
audio = await generateAudio(message, defaultVitsTTSRole, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
audio = await generateVitsAudio(message, defaultVitsTTSRole, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
} catch (err) {
logger.error(err)
}

View file

@ -8,7 +8,7 @@ import {
getVitsRoleList,
getVoicevoxRoleList,
makeForwardMsg,
parseDuration, processList,
parseDuration,
renderUrl
} from '../utils/common.js'
import SydneyAIClient from '../utils/SydneyAIClient.js'
@ -20,10 +20,8 @@ import loader from '../../../lib/plugins/loader.js'
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
let isWhiteList = true
let isSetGroup = true
export class ChatgptManagement extends plugin {
constructor (e) {
constructor(e) {
super({
name: 'ChatGPT-Plugin 管理',
dsc: '插件的管理项配置,让你轻松掌控各个功能的开闭和管理。包含各种实用的配置选项,让你的聊天更加便捷和高效!',
@ -195,21 +193,6 @@ export class ChatgptManagement extends plugin {
fnc: 'enablePrivateChat',
permission: 'master'
},
{
reg: '^#chatgpt(设置|添加)对话[白黑]名单$',
fnc: 'setList',
permission: 'master'
},
{
reg: '^#chatgpt(查看)?对话[白黑]名单(帮助)?$',
fnc: 'checkList',
permission: 'master'
},
{
reg: '^#chatgpt(删除|移除)对话[白黑]名单$',
fnc: 'delList',
permission: 'master'
},
{
reg: '^#(设置|修改)管理密码',
fnc: 'setAdminPassword',
@ -268,7 +251,7 @@ export class ChatgptManagement extends plugin {
})
}
async viewUserSetting (e) {
async viewUserSetting(e) {
const userSetting = await getUserReplySetting(this.e)
const replyMsg = `${this.e.sender.user_id}的回复设置:
图片模式: ${userSetting.usePicture === true ? '开启' : '关闭'}
@ -281,7 +264,7 @@ ${userSetting.useTTS === true ? '当前语音模式为' + Config.ttsMode : ''}`
return true
}
async getTTSRoleList (e) {
async getTTSRoleList(e) {
const matchCommand = e.msg.match(/^#(chatgpt)?(vits|azure|vox)?语音(服务|角色列表)/)
if (matchCommand[3] === '服务') {
await this.reply(`当前支持vox、vits、azure语音服务可使用'#(vox|azure|vits)语音角色列表'查看支持的语音角色。
@ -332,7 +315,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
await this.reply(roleList)
}
async ttsSwitch (e) {
async ttsSwitch(e) {
let userReplySetting = await getUserReplySetting(this.e)
if (!userReplySetting.useTTS) {
let replyMsg
@ -359,11 +342,11 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return false
}
async commandHelp (e) {
async commandHelp(e) {
if (/^#(chatgpt)?指令表帮助$/.exec(e.msg.trim())) {
await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' +
'#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' +
'#chatgpt指令表搜索xxx: 查看包含对应关键词的指令')
'#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' +
'#chatgpt指令表搜索xxx: 查看包含对应关键词的指令')
return false
}
const categories = {
@ -375,7 +358,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
聊天记录: '聊天记录'
}
function getCategory (e, plugin) {
function getCategory(e, plugin) {
for (const key in categories) {
if (e.msg.includes(key) && plugin.name.includes(categories[key])) {
return '功能名称: '
@ -440,7 +423,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return true
}
async setList (e) {
async setList(e) {
this.setContext('saveList')
isWhiteList = e.msg.includes('白')
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
@ -448,7 +431,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return false
}
async saveList (e) {
async saveList(e) {
if (!this.e.msg) return
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
const regex = /^\^?[1-9]\d{5,9}$/
@ -485,7 +468,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
this.finish('saveList')
}
async checkList (e) {
async checkList(e) {
if (e.msg.includes('帮助')) {
await this.reply('默认设置为添加群号需要拉黑QQ号时在前面添加^(例如:^123456),可一次性混合输入多个配置号码,错误项会自动忽略。具体使用指令可通过 "#指令表搜索名单" 查看,白名单优先级高于黑名单。')
return true
@ -498,7 +481,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return false
}
async delList (e) {
async delList(e) {
isWhiteList = e.msg.includes('白')
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
let replyMsg = ''
@ -516,7 +499,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return false
}
async confirmDelList (e) {
async confirmDelList(e) {
if (!this.e.msg) return
const isAllDeleted = this.e.msg.trim() === '全部删除'
const regex = /^\^?[1-9]\d{5,9}$/
@ -560,13 +543,13 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
this.finish('confirmDelList')
}
async enablePrivateChat (e) {
async enablePrivateChat(e) {
Config.enablePrivateChat = !!e.msg.match(/(允许|打开|同意)/)
await this.reply('设置成功', e.isGroup)
return false
}
async enableGroupContext (e) {
async enableGroupContext(e) {
const reg = /(关闭|打开)/
const match = e.msg.match(reg)
if (match) {
@ -582,7 +565,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return false
}
async setDefaultReplySetting (e) {
async setDefaultReplySetting(e) {
const reg = /^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|((azure|vits|vox)?语音角色|角色语音|角色)(.*))|回复帮助)/
const matchCommand = e.msg.match(reg)
const settingType = matchCommand[2]
@ -715,31 +698,31 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
await this.reply(replyMsg, true)
}
async turnOnConfirm (e) {
async turnOnConfirm(e) {
await redis.set('CHATGPT:CONFIRM', 'on')
await this.reply('已开启消息确认', true)
return false
}
async turnOffConfirm (e) {
async turnOffConfirm(e) {
await redis.set('CHATGPT:CONFIRM', 'off')
await this.reply('已关闭消息确认', true)
return false
}
async setAccessToken (e) {
async setAccessToken(e) {
this.setContext('saveToken')
await this.reply('请发送ChatGPT AccessToken', true)
return false
}
async setPoeCookie () {
async setPoeCookie() {
this.setContext('savePoeToken')
await this.reply('请发送Poe Cookie', true)
return false
}
async savePoeToken (e) {
async savePoeToken(e) {
if (!this.e.msg) return
let token = this.e.msg
if (!token.startsWith('p-b=')) {
@ -752,13 +735,13 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
this.finish('savePoeToken')
}
async setBingAccessToken (e) {
async setBingAccessToken(e) {
this.setContext('saveBingToken')
await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true)
return false
}
async migrateBingAccessToken () {
async migrateBingAccessToken() {
let token = await redis.get('CHATGPT:BING_TOKEN')
if (token) {
token = token.split('|')
@ -782,27 +765,27 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
await this.reply('迁移完成', true)
}
async getBingAccessToken (e) {
async getBingAccessToken(e) {
let tokens = await redis.get('CHATGPT:BING_TOKENS')
if (tokens) tokens = JSON.parse(tokens)
else tokens = []
tokens = tokens.length > 0
? tokens.map((item, index) => (
`${index}】 Token${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}`
`${index}】 Token${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}`
)).join('\n')
: '无必应Token记录'
await this.reply(`${tokens}`, true)
return false
}
async delBingAccessToken (e) {
async delBingAccessToken(e) {
this.setContext('deleteBingToken')
let tokens = await redis.get('CHATGPT:BING_TOKENS')
if (tokens) tokens = JSON.parse(tokens)
else tokens = []
tokens = tokens.length > 0
? tokens.map((item, index) => (
`${index}】 Token${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}`
`${index}】 Token${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}`
)).join('\n')
: '无必应Token记录'
await this.reply(`请发送要删除的token编号\n${tokens}`, true)
@ -810,7 +793,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return false
}
async saveBingToken () {
async saveBingToken() {
if (!this.e.msg) return
let token = this.e.msg
if (token.length < 100) {
@ -867,7 +850,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
this.finish('saveBingToken')
}
async deleteBingToken () {
async deleteBingToken() {
if (!this.e.msg) return
let tokenId = this.e.msg
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
@ -888,7 +871,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async saveToken () {
async saveToken() {
if (!this.e.msg) return
let token = this.e.msg
if (!token.startsWith('ey') || token.length < 20) {
@ -901,12 +884,12 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
this.finish('saveToken')
}
async useBrowserBasedSolution (e) {
async useBrowserBasedSolution(e) {
await redis.set('CHATGPT:USE', 'browser')
await this.reply('已切换到基于浏览器的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误')
}
async useOpenAIAPIBasedSolution (e) {
async useOpenAIAPIBasedSolution(e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'api') {
await redis.set('CHATGPT:USE', 'api')
@ -916,12 +899,12 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useChatGLMSolution (e) {
async useChatGLMSolution(e) {
await redis.set('CHATGPT:USE', 'chatglm')
await this.reply('已切换到ChatGLM-6B解决方案如果已经对话过建议执行`#结束对话`避免引起404错误')
}
async useReversedAPIBasedSolution2 (e) {
async useReversedAPIBasedSolution2(e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'api3') {
await redis.set('CHATGPT:USE', 'api3')
@ -931,7 +914,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useBingSolution (e) {
async useBingSolution(e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'bing') {
await redis.set('CHATGPT:USE', 'bing')
@ -941,7 +924,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useClaudeBasedSolution (e) {
async useClaudeBasedSolution(e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'poe') {
await redis.set('CHATGPT:USE', 'poe')
@ -951,7 +934,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useSlackClaudeBasedSolution () {
async useSlackClaudeBasedSolution() {
let use = await redis.get('CHATGPT:USE')
if (use !== 'claude') {
await redis.set('CHATGPT:USE', 'claude')
@ -961,7 +944,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useXinghuoBasedSolution () {
async useXinghuoBasedSolution() {
let use = await redis.get('CHATGPT:USE')
if (use !== 'xh') {
await redis.set('CHATGPT:USE', 'xh')
@ -971,7 +954,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async changeBingTone (e) {
async changeBingTone(e) {
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
if (!tongStyle) {
return
@ -994,12 +977,12 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async bingOpenSuggestedResponses (e) {
async bingOpenSuggestedResponses(e) {
Config.enableSuggestedResponses = e.msg.indexOf('开启') > -1
await e.reply('操作成功')
}
async checkAuth (e) {
async checkAuth(e) {
if (!e.isMaster) {
e.reply(`只有主人才能命令ChatGPT哦~
(*/ω*)`)
@ -1008,11 +991,11 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
return true
}
async versionChatGPTPlugin (e) {
async versionChatGPTPlugin(e) {
await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/version`, { Viewport: { width: 800, height: 600 } })
}
async modeHelp () {
async modeHelp() {
let mode = await redis.get('CHATGPT:USE')
const modeMap = {
browser: '浏览器',
@ -1049,7 +1032,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
await this.reply(message)
}
async shutUp (e) {
async shutUp(e) {
let duration = e.msg.replace(/^#chatgpt(本群)?(群\d+)?(关闭|闭嘴|关机|休眠|下班)/, '')
let scope
let time = 3600000
@ -1101,7 +1084,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
}
}
async openMouth (e) {
async openMouth(e) {
const match = e.msg.match(/^#chatgpt群(\d+)/)
if (e.msg.indexOf('本群') > -1) {
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
@ -1156,7 +1139,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
}
}
async listShutUp () {
async listShutUp() {
let keys = await redis.keys('CHATGPT:SHUT_UP:*')
if (!keys || keys.length === 0) {
await this.reply('已经开启过全群响应啦', true)
@ -1173,13 +1156,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
}
}
async setAPIKey (e) {
async setAPIKey(e) {
this.setContext('saveAPIKey')
await this.reply('请发送OpenAI API Key.', true)
return false
}
async saveAPIKey () {
async saveAPIKey() {
if (!this.e.msg) return
let token = this.e.msg
if (!token.startsWith('sk-')) {
@ -1193,13 +1176,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('saveAPIKey')
}
async setXinghuoToken () {
async setXinghuoToken() {
this.setContext('saveXinghuoToken')
await this.reply('请发送星火的ssoSessionId', true)
return false
}
async saveXinghuoToken () {
async saveXinghuoToken() {
if (!this.e.msg) return
let token = this.e.msg
// todo
@ -1208,13 +1191,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('saveXinghuoToken')
}
async setAPIPromptPrefix (e) {
async setAPIPromptPrefix(e) {
this.setContext('saveAPIPromptPrefix')
await this.reply('请发送用于API模式的设定', true)
return false
}
async saveAPIPromptPrefix (e) {
async saveAPIPromptPrefix(e) {
if (!this.e.msg) return
if (this.e.msg === '取消') {
await this.reply('已取消设置API设定', true)
@ -1227,13 +1210,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('saveAPIPromptPrefix')
}
async setBingPromptPrefix (e) {
async setBingPromptPrefix(e) {
this.setContext('saveBingPromptPrefix')
await this.reply('请发送用于Bing Sydney模式的设定', true)
return false
}
async saveBingPromptPrefix (e) {
async saveBingPromptPrefix(e) {
if (!this.e.msg) return
if (this.e.msg === '取消') {
await this.reply('已取消设置Sydney设定', true)
@ -1245,7 +1228,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('saveBingPromptPrefix')
}
async switchDraw (e) {
async switchDraw(e) {
if (e.msg.indexOf('开启') > -1) {
if (Config.enableDraw) {
await this.reply('当前已经开启chatgpt画图功能', true)
@ -1263,15 +1246,15 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
}
}
async queryAPIPromptPrefix (e) {
async queryAPIPromptPrefix(e) {
await this.reply(Config.promptPrefixOverride, true)
}
async queryBingPromptPrefix (e) {
async queryBingPromptPrefix(e) {
await this.reply(Config.sydney, true)
}
async setAdminPassword (e) {
async setAdminPassword(e) {
if (e.isGroup || !e.isPrivate) {
await this.reply('请私聊发送命令', true)
return true
@ -1281,7 +1264,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
return false
}
async setUserPassword (e) {
async setUserPassword(e) {
if (e.isGroup || !e.isPrivate) {
await this.reply('请私聊发送命令', true)
return true
@ -1291,7 +1274,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
return false
}
async saveAdminPassword (e) {
async saveAdminPassword(e) {
if (!this.e.msg) return
const passwd = this.e.msg
await redis.set('CHATGPT:ADMIN_PASSWD', md5(passwd))
@ -1299,7 +1282,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('saveAdminPassword')
}
async saveUserPassword (e) {
async saveUserPassword(e) {
if (!this.e.msg) return
const passwd = this.e.msg
const dir = 'resources/ChatGPTCache/user'
@ -1335,7 +1318,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('saveUserPassword')
}
async adminPage (e) {
async adminPage(e) {
if (!Config.groupAdminPage && (e.isGroup || !e.isPrivate)) {
await this.reply('请私聊发送命令', true)
return true
@ -1344,7 +1327,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
await this.reply(`请登录${viewHost + 'admin/settings'}进行系统配置`, true)
}
async userPage (e) {
async userPage(e) {
if (!Config.groupAdminPage && (e.isGroup || !e.isPrivate)) {
await this.reply('请私聊发送命令', true)
return true
@ -1353,12 +1336,12 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
await this.reply(`请登录${viewHost + 'admin/dashboard'}进行系统配置`, true)
}
async setOpenAIPlatformToken (e) {
async setOpenAIPlatformToken(e) {
this.setContext('doSetOpenAIPlatformToken')
await e.reply('请发送refreshToken\n你可以在已登录的platform.openai.com后台界面打开调试窗口在终端中执行\nJSON.parse(localStorage.getItem(Object.keys(localStorage).filter(k => k.includes(\'auth0\'))[0])).body.refresh_token\n如果仍不能查看余额请退出登录重新获取刷新令牌')
}
async doSetOpenAIPlatformToken () {
async doSetOpenAIPlatformToken() {
let token = this.e.msg
if (!token) {
return false
@ -1368,7 +1351,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('doSetOpenAIPlatformToken')
}
async exportConfig (e) {
async exportConfig(e) {
if (e.isGroup || !e.isPrivate) {
await this.reply('请私聊发送命令', true)
return true
@ -1387,9 +1370,12 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
if (await redis.exists('CHATGPT:USE') != 0) {
redisConfig.useMode = await redis.get('CHATGPT:USE')
}
const filepath = path.join('plugins/chatgpt-plugin/resources', 'view.json')
const configView = JSON.parse(fs.readFileSync(filepath, 'utf8'))
const configJson = JSON.stringify({
chatConfig: Config,
redisConfig
redisConfig,
view: configView
})
console.log(configJson)
const buf = Buffer.from(configJson)
@ -1397,7 +1383,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
return true
}
async importConfig (e) {
async importConfig(e) {
if (e.isGroup || !e.isPrivate) {
await this.reply('请私聊发送命令', true)
return true
@ -1406,7 +1392,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
await e.reply('请发送配置文件')
}
async doImportConfig (e) {
async doImportConfig(e) {
const file = this.e.message.find(item => item.type === 'file')
if (file) {
const fileUrl = await this.e.friend.getFileUrl(file.fid)
@ -1470,7 +1456,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie
this.finish('doImportConfig')
}
async switchSmartMode (e) {
async switchSmartMode(e) {
if (e.msg.includes('开启')) {
if (Config.smartMode) {
await e.reply('已经开启了')

View file

@ -42,13 +42,17 @@ export function supportGuoba () {
{
field: 'whitelist',
label: '对话白名单',
bottomHelpMessage: '只有在白名单内的QQ号或群组才能使用本插件进行对话。如果需要添加QQ号请在号码前面加上^符号(例如:^123456多个号码之间请用英文逗号(,)隔开。白名单优先级高于黑名单。',
bottomHelpMessage: '默认设置为添加群号。优先级高于黑名单。\n' +
'注意需要添加QQ号时在前面添加^(例如:^123456),此全局添加白名单,即除白名单以外的所有人都不能使用插件对话。\n' +
'如果需要在某个群里独享moment即群聊中只有白名单上的qq号能用则使用群号^qq的格式(例如123456^123456)。\n' +
'白名单优先级:混合制 > qq > 群号。\n' +
'黑名单优先级: 群号 > qq > 混合制。',
component: 'Input'
},
{
field: 'blacklist',
label: '对话黑名单',
bottomHelpMessage: '名单内的群或QQ号将无法使用本插件进行对话。如果需要添加QQ号请在QQ号前面加上^符号(例如:^123456并用英文逗号,)将各个号码分隔开。',
bottomHelpMessage: '参考白名单设置规则。',
component: 'Input'
},
{
@ -834,7 +838,22 @@ export function supportGuoba () {
setConfigData (data, { Result }) {
for (let [keyPath, value] of Object.entries(data)) {
// 处理黑名单
if (keyPath === 'blacklist' || keyPath === 'whitelist' || keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,;\|]/) }
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,;\|]/) }
if (keyPath === 'blacklist' || keyPath === 'whitelist') {
// 6-10位数的群号或qq
const regex = /^\^?[1-9]\d{5,9}(\^[1-9]\d{5,9})?$/
const inputSet = new Set()
value = value.toString().split(/[,;|\s]/).reduce((acc, item) => {
item = item.trim()
if (!inputSet.has(item) && regex.test(item)) {
if (item.length <= 11 || (item.length <= 21 && item.length > 11 && !item.startsWith('^'))) {
inputSet.add(item)
acc.push(item)
}
}
return acc
}, [])
}
if (Config[keyPath] !== value) { Config[keyPath] = value }
}
// 正确储存azureRoleSelect结果

View file

@ -30,7 +30,8 @@
"random": "^4.1.0",
"undici": "^5.21.0",
"uuid": "^9.0.0",
"ws": "^8.13.0"
"ws": "^8.13.0",
},
"optionalDependencies": {
"@node-rs/jieba": "^1.6.2",

View file

@ -0,0 +1,867 @@
[
{
"id": "GeneralSettings",
"title": "通用设置",
"view": [
{
"type": "check",
"label": "图片识别OCR",
"data": "imgOcr"
},
{
"type": "check",
"label": "允许其他模式",
"data": "allowOtherMode"
},
{
"type": "check",
"label": "调试信息",
"data": "debug"
},
{
"type": "check",
"label": "是否允许私聊机器人",
"data": "enablePrivateChat"
},
{
"type": "check",
"label": "回复确认",
"model": "redisConfig",
"data": "turnConfirm"
},
{
"type": "check",
"label": "新版帮助",
"data": "newhelp"
},
{
"type": "number",
"label": "对话保留时长",
"placeholder": "每个人发起的对话保留时长",
"data": "conversationPreserveTime"
},
{
"type": "url",
"label": "代理服务器地址",
"placeholder": "数据通过代理服务器发送http或socks5代理",
"data": "proxy"
},
{
"type": "select",
"label": "对话模式",
"model": "redisConfig",
"data": "useMode",
"items": [
{
"label": "必应",
"value": "bing"
},
{
"label": "ChatGPT API",
"value": "api"
},
{
"label": "ChatGPT API3",
"value": "api3"
},
{
"label": "Slack Claude",
"value": "claude"
},
{
"label": "ChatGLM",
"value": "chatglm"
},
{
"label": "星火",
"value": "xh"
},
{
"label": "浏览器",
"value": "browser"
}
]
},
{
"type": "password",
"label": "高德APIKey",
"placeholder": "用于查询天气",
"data": "amapKey"
},
{
"type": "url",
"label": "Azure search key",
"placeholder": "https://www.microsoft.com/en-us/bing/apis/bing-web-search-api",
"data": "azSerpKey"
},
{
"type": "select",
"label": "搜索来源",
"data": "serpSource",
"items": [
{
"label": "Azure",
"value": "azure"
},
{
"label": "ikechan8370",
"value": "ikechan8370"
}
]
},
{
"type": "url",
"label": "额外工具url",
"placeholder": "测试期间提供一个公益接口,一段时间后撤掉",
"data": "extraUrl"
}
]
},
{
"id": "ChatSettings",
"title": "聊天设置",
"view": [
{
"type": "tabs",
"id": "ChatSetting",
"tabs": [
{
"title": "文本模式",
"icon": "mdi-format-text",
"tab": "text",
"view": [
{
"type": "check",
"label": "长文本自动转图片",
"data": "autoUsePicture"
},
{
"type": "check",
"label": "是否允许机器人真AT",
"data": "enableRobotAt"
},
{
"type": "number",
"label": "自动转图片阈值",
"placeholder": "自动转图片的字数阈值",
"data": "autoUsePictureThreshold"
}
]
},
{
"title": "图片模式[基础参数]",
"icon": "mdi-image",
"tab": "image_base",
"view": [
{
"type": "check",
"label": "全局图片模式",
"data": "defaultUsePicture"
},
{
"type": "check",
"label": "图片引用消息",
"data": "quoteReply"
},
{
"type": "check",
"label": "启用二维码",
"data": "showQRCode"
},
{
"type": "text",
"label": "BOT命名",
"placeholder": "强制修改Bot命名",
"data": "chatViewBotName"
},
{
"type": "url",
"label": "渲染服务器地址",
"placeholder": "可选择第三方渲染服务器",
"data": "viewHost"
},
{
"type": "number",
"label": "图片渲染宽度",
"placeholder": "图片渲染宽度",
"data": "chatViewWidth"
},
{
"type": "check",
"label": "云渲染",
"data": "cloudRender"
},
{
"type": "number",
"label": "云渲染DPR",
"placeholder": "设置云渲染画面缩放,数值愈大越清晰",
"data": "cloudDPR"
}
]
},
{
"title": "图片模式[Live2D]",
"icon": "mdi-image",
"tab": "image_live2d",
"view": [
{
"type": "check",
"label": "Live2D",
"data": "live2d"
},
{
"type": "text",
"label": "Live2D模型",
"placeholder": "使用的Live2D模式文件",
"data": "live2dModel"
},
{
"type": "number",
"label": "Live2D模型缩放",
"placeholder": "渲染live2d的模型大小",
"data": "live2dOption_scale"
},
{
"type": "number",
"label": "Live2D模型位置X",
"placeholder": "Live2d模型在区域的位置X轴微调",
"data": "live2dOption_positionX"
},
{
"type": "number",
"label": "Live2D模型位置Y",
"placeholder": "Live2d模型在区域的位置Y轴微调",
"data": "live2dOption_positionY"
},
{
"type": "number",
"label": "Live2D模型旋转",
"placeholder": "Live2d模型在区域的旋转角度",
"data": "live2dOption_rotation"
},
{
"type": "number",
"label": "Live2D模型透明度",
"placeholder": "Live2d模型的透明度",
"data": "live2dOption_alpha"
}
]
},
{
"title": "图片模式[旧渲染]",
"icon": "mdi-image",
"tab": "image_old",
"view": [
{
"type": "check",
"label": "旧版本渲染",
"data": "oldview"
},
{
"type": "check",
"label": "预制渲染服务器访问代码",
"data": "cacheEntry"
},
{
"type": "url",
"label": "渲染服务器地址",
"placeholder": "可选择第三方渲染服务器",
"data": "cacheUrl"
}
]
},
{
"title": "语音模式",
"icon": "mdi-microphone",
"tab": "voice",
"view": [
{
"type": "check",
"label": "全局语音模式",
"data": "defaultUseTTS"
},
{
"type": "check",
"label": "语音同时发送文字",
"data": "alsoSendText"
},
{
"type": "number",
"label": "语音转文字阈值",
"placeholder": "语音模式下,字数超过这个阈值就降级为文字",
"data": "ttsAutoFallbackThreshold"
},
{
"type": "text",
"label": "语音过滤正则表达式",
"placeholder": "语音模式下,配置此项以过滤不想被读出来的内容",
"data": "ttsRegex"
},
{
"type": "select",
"label": "语音模式源",
"data": "ttsMode",
"items": [
{
"label": "Vits",
"value": "vits-uma-genshin-honkai"
},
{
"label": "微软Azure",
"value": "azure"
},
{
"label": "VoiceVox",
"value": "VoiceVox"
}
]
},
{
"type": "select",
"label": "云转码模式",
"data": "cloudMode",
"items": [
{
"label": "文件",
"value": "file"
},
{
"label": "链接",
"value": "url"
}
]
}
]
},
{
"title": "语音模式[vits]",
"icon": "mdi-microphone",
"tab": "vits",
"view": [
{
"type": "url",
"label": "语音转换API地址",
"placeholder": "前往duplicate空间查看api地址",
"data": "ttsSpace"
},
{
"type": "url",
"label": "语音转换huggingface反代",
"data": "huggingFaceReverseProxy"
},
{
"type": "number",
"label": "控制情感变化程度",
"data": "noiseScale"
},
{
"type": "number",
"label": "控制音素发音长度",
"data": "noiseScaleW"
},
{
"type": "number",
"label": "控制整体语速",
"data": "lengthScale"
},
{
"type": "check",
"label": "日语输出",
"data": "autoJapanese"
}
]
},
{
"title": "语音模式[Azure]",
"icon": "mdi-microphone",
"tab": "azure",
"view": [
{
"type": "password",
"label": "语音服务密钥",
"placeholder": "Azure的语音服务密钥",
"data": "azureTTSKey"
},
{
"type": "text",
"label": "语音服务区域",
"placeholder": "Azure语音服务区域",
"data": "azureTTSRegion"
},
{
"type": "number",
"label": "Azure情绪多样化",
"data": "azureTTSEmotion"
},
{
"type": "number",
"label": "Azure情绪纠正",
"data": "enhanceAzureTTSEmotion"
}
]
},
{
"title": "语音模式[Voicevox]",
"icon": "mdi-microphone",
"tab": "voicevox",
"view": [
{
"type": "url",
"label": "语音转换API地址",
"placeholder": "voicevox语音转换API地址",
"data": "voicevoxSpace"
}
]
}
]
}
]
},
{
"id": "ModelSettings",
"title": "模式设置",
"view": [
{
"type": "tabs",
"id": "ChatSetting",
"tabs": [
{
"title": "API",
"tab": "api",
"view": [
{
"type": "check",
"label": "强制使用OpenAI反代",
"data": "openAiForceUseReverse"
},
{
"type": "check",
"label": "智能模式",
"data": "smartMode"
},
{
"type": "password",
"label": "OpenAI API Key",
"placeholder": "OpenAI的ApiKey用于访问OpenAI的API接口",
"data": "apiKey"
},
{
"type": "text",
"label": "OpenAI 模型",
"placeholder": "gpt-4, gpt-4-0314, gpt-4-32k, gpt-4-32k-0314, gpt-3.5-turbo, gpt-3.5-turbo-0301",
"data": "model"
},
{
"type": "text",
"label": "AI名字",
"placeholder": "AI认为的自己的名字当你问他你是谁是他会回答这里的名字",
"data": "assistantLabel"
},
{
"type": "number",
"label": "temperature",
"placeholder": "用于控制回复内容的多样性,数值越大回复越加随机、多元化,数值越小回复越加保守",
"data": "temperature"
},
{
"type": "url",
"label": "OpenAI API服务器地址",
"placeholder": "OpenAI的API服务器地址注意要带上/v1。",
"data": "openAiBaseUrl"
},
{
"type": "textarea",
"label": "AI风格",
"placeholder": "你可以在这里写入你希望AI回答的风格比如希望优先回答中文回答长一点等",
"data": "promptPrefixOverride"
}
]
},
{
"title": "API3",
"tab": "api3",
"view": [
{
"type": "url",
"label": "ChatGPT API反代服务器地址",
"placeholder": "ChatGPT的API反代服务器用于绕过Cloudflare访问ChatGPT API。",
"data": "api"
},
{
"type": "url",
"label": "apiBaseUrl地址",
"data": "apiBaseUrl"
},
{
"type": "password",
"label": "OpenAI refreshToken",
"placeholder": "OpenAI的refreshToken用于刷新Access Token",
"data": "OpenAiPlatformRefreshToken"
},
{
"type": "password",
"label": "OpenAI AccessToken",
"model": "redisConfig",
"data": "openAiPlatformAccessToken"
},
{
"type": "check",
"label": "强制使用ChatGPT反代",
"data": "apiForceUseReverse"
},
{
"type": "check",
"label": "使用GPT-4",
"data": "useGPT4"
}
]
},
{
"title": "浏览器",
"tab": "browser",
"view": [
{
"type": "check",
"label": "无头模式",
"data": "headless"
},
{
"type": "text",
"label": "用户名",
"placeholder": "OpenAI用户名",
"data": "username"
},
{
"type": "password",
"label": "密码",
"placeholder": "OpenAI密码",
"data": "password"
},
{
"type": "text",
"label": "Chrome路径",
"placeholder": "为空使用默认puppeteer的chromium也可以传递自己本机安装的Chrome可执行文件地址提高通过率。",
"data": "chromePath"
},
{
"type": "textarea",
"label": "浏览器UA",
"placeholder": "模拟浏览器UA无特殊需求保持默认即可",
"data": "UA"
},
{
"type": "password",
"label": "验证码平台Token",
"placeholder": "可注册2captcha实现跳过验证码",
"data": "2captchaToken"
}
]
},
{
"title": "必应",
"tab": "bing",
"view": [
{
"type": "select",
"label": "Bing模式",
"data": "toneStyle",
"items": [
{
"label": "均衡",
"value": "balanced"
},
{
"label": "创意",
"value": "creative"
},
{
"label": "精确",
"value": "precise"
},
{
"label": "Sydney(可能存在风险)",
"value": "Sydney"
},
{
"label": "自设定(可能存在风险)",
"value": "Custom"
}
]
},
{
"type": "check",
"label": "是否开启建议回复",
"data": "enableSuggestedResponses"
},
{
"type": "check",
"label": "是否允许机器人读取近期的群聊聊天记录",
"data": "enableGroupContext"
},
{
"type": "number",
"label": "允许机器人读取近期的最多群聊聊天记录条数",
"placeholder": "允许机器人读取近期的最多群聊聊天记录条数。太多可能会超。",
"data": "groupContextLength"
},
{
"type": "text",
"label": "机器人读取聊天记录时的后台prompt",
"data": "groupContextTip"
},
{
"type": "check",
"label": "加强主人认知",
"data": "enforceMaster"
},
{
"type": "check",
"label": "Bing抱歉是否不计入聊天记录",
"data": "sydneyApologyIgnored"
},
{
"type": "check",
"label": "情感显示",
"data": "sydneyMood"
},
{
"type": "textarea",
"label": "Custom的设定",
"placeholder": "仅自设定模式下有效。你可以自己改写设定让Sydney变成你希望的样子",
"data": "sydney"
},
{
"type": "textarea",
"label": "Bing的扩展资料",
"placeholder": "AI将会从你提供的扩展资料中学习到一些知识帮助它更好地回答你的问题",
"data": "sydneyContext"
},
{
"type": "textarea",
"label": "情感模式设定",
"placeholder": "情感显示开启的情况下AI将根据设定在正文中体现情感内容",
"data": "sydneyMoodTip"
},
{
"type": "url",
"label": "sydney反代",
"placeholder": "仅悉尼和自设定模式下有效,用于创建对话(默认不用于正式对话)",
"data": "sydneyReverseProxy"
},
{
"type": "check",
"label": "强制使用sydney反代",
"data": "sydneyReverseProxy"
},
{
"type": "check",
"label": "对话使用sydney反代",
"data": "sydneyForceUseReverse"
},
{
"type": "check",
"label": "允许生成图像等内容",
"data": "enableGenerateContents"
}
]
},
{
"title": "ChatGLM",
"tab": "chatglm",
"view": [
{
"type": "url",
"label": "ChatGLM API地址",
"data": "chatglmBaseUrl"
}
]
},
{
"title": "Slack Claude",
"tab": "claude",
"view": [
{
"type": "password",
"label": "Slack用户Token",
"placeholder": "slackUserToken在OAuth&Permissions页面获取",
"data": "slackUserToken"
},
{
"type": "password",
"label": "Slack Bot Token",
"placeholder": "slackBotUserToken在OAuth&Permissions页面获取",
"data": "slackBotUserToken"
},
{
"type": "text",
"label": "Slack成员id",
"placeholder": "在Slack中点击Claude头像查看详情其中的成员ID复制过来",
"data": "slackClaudeUserId"
},
{
"type": "password",
"label": "Slack签名密钥",
"placeholder": "Signing Secret。在Basic Information页面获取",
"data": "slackSigningSecret"
},
{
"type": "check",
"label": "Claude使用全局设定",
"data": "slackClaudeEnableGlobalPreset"
},
{
"type": "textarea",
"label": "Slack全局设定",
"placeholder": "若启用全局设定,每个人都会默认使用这里的设定",
"data": "slackClaudeGlobalPreset"
}
]
},
{
"title": "星火",
"tab": "xh",
"view": [
{
"type": "password",
"label": "星火Cookie",
"data": "xinghuoToken"
}
]
}
]
}
]
},
{
"id": "DrawSettings",
"title": "绘图设置",
"view": [
{
"type": "check",
"label": "绘图功能开关",
"data": "enableDraw"
},
{
"type": "number",
"label": "绘图CD",
"placeholder": "绘图指令的CD时间",
"data": "drawCD"
},
{
"type": "url",
"label": "emojiAPI地址",
"placeholder": "合成emoji的API地址",
"data": "emojiBaseURL"
}
]
},
{
"id": "GroupSettings",
"title": "群聊设置",
"view": [
{
"type": "textarea",
"label": "打招呼prompt",
"placeholder": "将会用这段文字询问ChatGPT由ChatGPT给出随机的打招呼文字",
"data": "helloPrompt"
},
{
"type": "number",
"label": "打招呼间隔(小时)",
"data": "helloInterval"
},
{
"type": "number",
"label": "打招呼的触发概率(%)",
"placeholder": "设置为100则每次经过间隔时间必定触发主动打招呼事件。",
"data": "helloProbability"
},
{
"type": "select",
"label": "触发方式",
"data": "toggleMode",
"items": [
{
"label": "at",
"value": "at"
},
{
"label": "#chat",
"value": "prefix"
}
]
}
]
},
{
"id": "TimeoutSettings",
"title": "服务超时配置",
"view": [
{
"type": "number",
"label": "默认超时时间",
"placeholder": "各个地方的默认超时时间",
"data": "defaultTimeoutMs"
},
{
"type": "number",
"label": "浏览器超时时间",
"placeholder": "浏览器默认超时,浏览器可能需要更高的超时时间",
"data": "chromeTimeoutMS"
},
{
"type": "number",
"label": "Sydney模式接受首条信息超时时间",
"placeholder": "超过该时间阈值未收到Bing的任何消息则断开本次连接并重试",
"data": "sydneyFirstMessageTimeout"
}
]
},
{
"id": "ReviewSettings",
"title": "违禁内容核查",
"view": [
{
"type": "textarea",
"label": "输出黑名单",
"placeholder": "检查输出结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开",
"data": "blockWords"
},
{
"type": "textarea",
"label": "输入黑名单",
"placeholder": "检查输入结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开",
"data": "promptBlockWords"
}
]
},
{
"id": "GroupSettings",
"title": "群聊设置",
"view": [
{
"type": "number",
"label": "系统Api服务端口",
"placeholder": "系统Api服务开启的端口号如需外网访问请将系统防火墙和服务器防火墙对应端口开放",
"data": "ServerSettings"
},
{
"type": "text",
"label": "系统服务访问域名",
"placeholder": "使用域名代替公网ip适用于有服务器和域名的朋友避免暴露ip使用",
"data": "serverHost"
},
{
"type": "url",
"label": "云服务API地址",
"placeholder": "目前支持node-silk语音转码、云图片渲染和页面生成缓存",
"data": "cloudTranscode"
},
{
"type": "check",
"label": "允许群获取后台地址",
"data": "groupAdminPage"
}
]
}
]

View file

@ -1,384 +0,0 @@
import fastify from 'fastify'
import fastifyCookie from '@fastify/cookie'
import cors from '@fastify/cors'
import fstatic from '@fastify/static'
import fs from 'fs'
import path from 'path'
import os from 'os'
import schedule from 'node-schedule'
import { Config } from '../utils/config.js'
import { randomString, getPublicIP, getUserData } from '../utils/common.js'
const __dirname = path.resolve()
const server = fastify({
logger: Config.debug
})
let usertoken = []
let Statistics = {
SystemAccess: {
count: 0,
oldCount: 0
},
CacheFile: {
count: 0,
oldCount: 0
},
WebAccess: {
count: 0,
oldCount: 0
},
SystemLoad: {
count: 0,
oldCount: 0
}
}
async function getLoad() {
// 获取当前操作系统平台
const platform = os.platform()
// 判断平台是Linux还是Windows
if (platform === 'linux') {
// 如果是Linux使用os.loadavg()方法获取负载平均值
const loadAvg = os.loadavg()
return loadAvg[0] * 100
} else if (platform === 'win32') {
// 如果是Windows不获取性能
return 0
} else {
return 0
}
}
async function setUserData(qq, data) {
const dir = 'resources/ChatGPTCache/user'
const filename = `${qq}.json`
const filepath = path.join(dir, filename)
fs.mkdirSync(dir, { recursive: true })
fs.writeFileSync(filepath, JSON.stringify(data))
}
export async function createServer() {
await server.register(cors, {
origin: '*'
})
await server.register(fstatic, {
root: path.join(__dirname, 'plugins/chatgpt-plugin/server/static/')
})
await server.register(fastifyCookie)
await server.get('/page/*', (request, reply) => {
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
await server.get('/help/*', (request, reply) => {
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
await server.get('/version', (request, reply) => {
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
await server.get('/auth/*', (request, reply) => {
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
await server.get('/admin*', (request, reply) => {
const token = request.cookies.token || 'unknown'
const user = usertoken.find(user => user.token === token)
if (!user) {
reply.redirect(301, '/auth/login')
}
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
await server.get('/admin/dashboard', (request, reply) => {
const token = request.cookies.token || 'unknown'
const user = usertoken.find(user => user.token === token)
if (!user) {
reply.redirect(301, '/auth/login')
}
if (user.autho === 'admin') {
reply.redirect(301, '/admin/settings')
}
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
await server.get('/admin/settings', (request, reply) => {
const token = request.cookies.token || 'unknown'
const user = usertoken.find(user => user.token === token)
if (!user || user.autho != 'admin') {
reply.redirect(301, '/admin/')
}
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
reply.type('text/html').send(stream)
})
// 登录
server.post('/login', async (request, reply) => {
const body = request.body || {}
if (body.qq && body.passwd) {
const token = randomString(32)
if (body.qq == Bot.uin && await redis.get('CHATGPT:ADMIN_PASSWD') == body.passwd) {
usertoken.push({ user: body.qq, token, autho: 'admin' })
reply.setCookie('token', token, { path: '/' })
reply.send({ login: true, autho: 'admin' })
} else {
const user = await getUserData(body.qq)
if (user.passwd != '' && user.passwd === body.passwd) {
usertoken.push({ user: body.qq, token, autho: 'user' })
reply.setCookie('token', token, { path: '/' })
reply.send({ login: true, autho: 'user' })
} else {
reply.send({ login: false, err: `用户名密码错误,如果忘记密码请私聊机器人输入 ${body.qq == Bot.uin ? '#修改管理密码' : '#修改用户密码'} 进行修改` })
}
}
} else {
reply.send({ login: false, err: '未输入用户名或密码' })
}
})
// 页面数据获取
server.post('/page', async (request, reply) => {
const body = request.body || {}
if (body.code) {
const dir = 'resources/ChatGPTCache/page'
const filename = body.code + '.json'
const filepath = path.join(dir, filename)
let data = fs.readFileSync(filepath, 'utf8')
reply.send(data)
}
})
// 帮助内容获取
server.post('/help', async (request, reply) => {
const body = request.body || {}
if (body.use) {
const dir = 'plugins/chatgpt-plugin/resources'
const filename = 'help.json'
const filepath = path.join(dir, filename)
let data = fs.readFileSync(filepath, 'utf8')
data = JSON.parse(data)
reply.send(data[body.use])
}
})
// 创建页面缓存内容
server.post('/cache', async (request, reply) => {
const body = request.body || {}
if (body.content) {
const dir = 'resources/ChatGPTCache/page'
const filename = body.entry + '.json'
const filepath = path.join(dir, filename)
const regexUrl = /\b((?:https?|ftp|file):\/\/[-a-zA-Z0-9+&@#\/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#\/%=~_|])/g
const ip = await getPublicIP()
let botName = ''
switch (body.model) {
case 'bing':
botName = 'Bing'
break
case 'api':
botName = 'ChatGPT'
break
case 'api3':
botName = 'ChatGPT'
break
case 'browser':
botName = 'ChatGPT'
break
case 'chatglm':
botName = 'ChatGLM'
break
case 'claude':
botName = 'Claude'
break
default:
botName = body.model
break
}
try {
fs.mkdirSync(dir, { recursive: true })
const data = {
user: body.content.senderName,
bot: Config.chatViewBotName || botName,
userImg: body.userImg || '',
botImg: body.botImg || '',
question: body.content.prompt,
message: body.content.content,
group: body.content.group,
herf: `http://${body.cacheHost || (ip + ':' + Config.serverPort || 3321)}/page/${body.entry}`,
quote: body.content.quote,
images: body.content.images || [],
suggest: body.content.suggest || [],
model: body.model,
mood: body.content.mood || 'blandness',
live2d: Config.live2d,
live2dModel: Config.live2dModel,
time: new Date()
}
fs.writeFileSync(filepath, JSON.stringify(data))
const user = await getUserData(body.qq)
user.chat.push({
user: data.user,
bot: data.bot,
group: data.group,
herf: data.herf,
model: data.model,
time: data.time
})
await setUserData(body.qq, user)
Statistics.CacheFile.count += 1
reply.send({ file: body.entry, cacheUrl: `http://${ip}:${Config.serverPort || 3321}/page/${body.entry}` })
} catch (err) {
server.log.error(`用户生成缓存${body.entry}时发生错误: ${err}`)
reply.send({ file: body.entry, cacheUrl: `http://${ip}:${Config.serverPort || 3321}/page/${body.entry}`, error: body.entry + '生成失败' })
}
}
})
// 获取系统状态
server.post('/system-statistics', async (request, reply) => {
Statistics.SystemLoad.count = await getLoad()
reply.send(Statistics)
})
// 获取用户数据
server.post('/userData', async (request, reply) => {
const token = request.cookies.token || 'unknown'
let user = usertoken.find(user => user.token === token)
if (!user) user = { user: '' }
const userData = await getUserData(user.user)
reply.send({
chat: userData.chat || [],
mode: userData.mode || '',
cast: userData.cast || {
api: '', //API设定
bing: '', //必应设定
bing_resource: '', //必应扩展资料
slack: '', //Slack设定
}
})
})
// 清除缓存数据
server.post('/cleanCache', async (request, reply) => {
const token = request.cookies.token || 'unknown'
let user = usertoken.find(user => user.token === token)
if (!user) user = { user: '' }
const userData = await getUserData(user.user)
const dir = 'resources/ChatGPTCache/page'
userData.chat.forEach(function (item, index) {
const filename = item.herf.substring(item.herf.lastIndexOf('/') + 1) + '.json'
const filepath = path.join(dir, filename)
fs.unlinkSync(filepath)
})
userData.chat = []
await setUserData(user.user, userData)
reply.send({ state: true })
})
// 获取系统参数
server.post('/sysconfig', async (request, reply) => {
const token = request.cookies.token || 'unknown'
const user = usertoken.find(user => user.token === token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
let redisConfig = {}
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
let bingTokens = await redis.get('CHATGPT:BING_TOKENS')
if (bingTokens) { bingTokens = JSON.parse(bingTokens) } else bingTokens = []
redisConfig.bingTokens = bingTokens
} else {
redisConfig.bingTokens = []
}
if (await redis.exists('CHATGPT:CONFIRM') != 0) {
redisConfig.turnConfirm = await redis.get('CHATGPT:CONFIRM') === 'on'
}
reply.send({
chatConfig: Config,
redisConfig
})
} else {
let userSetting = await redis.get(`CHATGPT:USER:${user.user}`)
if (!userSetting) {
userSetting = {
usePicture: Config.defaultUsePicture,
useTTS: Config.defaultUseTTS,
ttsRole: Config.defaultTTSRole
}
} else {
userSetting = JSON.parse(userSetting)
}
reply.send({
userSetting
})
}
})
// 设置系统参数
server.post('/saveconfig', async (request, reply) => {
const token = request.cookies.token || 'unknown'
const user = usertoken.find(user => user.token === token)
const body = request.body || {}
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const chatdata = body.chatConfig || {}
for (let [keyPath, value] of Object.entries(chatdata)) {
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,;\|]/) }
if (Config[keyPath] != value) { Config[keyPath] = value }
}
const redisConfig = body.redisConfig || {}
if (redisConfig.bingTokens != null) {
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(redisConfig.bingTokens))
}
if (redisConfig.turnConfirm != null) {
await redis.set('CHATGPT:CONFIRM', redisConfig.turnConfirm ? 'on' : 'off')
}
} else {
if (body.userSetting) {
await redis.set(`CHATGPT:USER:${user.user}`, JSON.stringify(body.userSetting))
}
if (body.userConfig) {
let temp_userData = await getUserData(user.user)
if (body.userConfig.mode) {
temp_userData.mode = body.userConfig.mode
}
if (body.userConfig.cast) {
temp_userData.cast = body.userConfig.cast
}
await setUserData(user.user, temp_userData)
}
}
})
server.addHook('onRequest', (request, reply, done) => {
if (request.method == 'POST') { Statistics.SystemAccess.count += 1 }
if (request.method == 'GET') { Statistics.WebAccess.count += 1 }
done()
})
// 定时任务
let rule = new schedule.RecurrenceRule()
rule.hour = 0
rule.minute = 0
let job_Statistics = schedule.scheduleJob(rule, function () {
Statistics.SystemAccess.oldCount = Statistics.SystemAccess.count
Statistics.CacheFile.oldCount = Statistics.CacheFile.count
Statistics.WebAccess.oldCount = Statistics.WebAccess.count
Statistics.SystemAccess.count = 0
Statistics.CacheFile.count = 0
Statistics.WebAccess.count = 0
})
let job_Statistics_SystemLoad = schedule.scheduleJob('0 * * * *', async function () {
Statistics.SystemLoad.count = await getLoad()
Statistics.SystemLoad.oldCount = Statistics.SystemLoad.count
})
server.listen({
port: Config.serverPort || 3321,
host: '::'
}, (error) => {
if (error) {
server.log.error(`服务启动失败: ${error}`)
} else {
server.log.info(`server listening on ${server.server.address().port}`)
}
})
}

View file

@ -8,13 +8,15 @@ import fs from 'fs'
import path from 'path'
import os from 'os'
import schedule from 'node-schedule'
import websocketclient from 'ws'
import { Config } from '../utils/config.js'
import { UserInfo, GetUser } from './modules/user_data.js'
import { getPublicIP, getUserData } from '../utils/common.js'
import { getPublicIP, getUserData, getMasterQQ, randomString } from '../utils/common.js'
import webRoute from './modules/web_route.js'
import webUser from './modules/user.js'
import SettingView from './modules/setting_view.js'
const __dirname = path.resolve()
const server = fastify({
@ -79,6 +81,92 @@ await server.register(websocket, {
await server.register(fastifyCookie)
await server.register(webRoute)
await server.register(webUser)
await server.register(SettingView)
// 无法访问端口的情况下创建与media的通讯
async function mediaLink() {
const ip = await getPublicIP()
const testServer = await fetch(`${Config.cloudTranscode}/check`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: `http://${ip}:${Config.serverPort || 3321}/`
})
})
if (testServer.ok) {
const checkCloudData = await testServer.json()
if (checkCloudData.state != 'error') {
console.log('本地服务无法访问开启media服务代理')
const serverurl = new URL(Config.cloudTranscode)
const ws = new websocketclient(`ws://${serverurl.hostname}${serverurl.port ? ':' + serverurl.port : ''}/ws`)
ws.on('open', () => {
ws.send(JSON.stringify({
command: 'register',
region: Bot.uin,
type: 'server',
}))
})
ws.on('message', async (message) => {
try {
const data = JSON.parse(message)
switch (data.command) {
case 'register':
if (data.state) {
let master = (await getMasterQQ())[0]
Bot.sendPrivateMsg(master, `当前chatgpt插件服务无法被外网访问已启用代理链接访问代码${data.token}`, false)
} else {
console.log('注册区域失败')
}
break
case 'login':
if (data.token) {
const user = UserInfo(data.token)
if (user) {
ws.login = true
ws.send(JSON.stringify({ command: data.command, state: true, region: Bot.uin, type: 'server' }))
} else {
ws.send(JSON.stringify({ command: data.command, state: false, error: '权限验证失败', region: Bot.uin, type: 'server' }))
}
}
break
case 'post_login':
if (data.qq && data.passwd) {
const token = randomString(32)
if (data.qq == Bot.uin && await redis.get('CHATGPT:ADMIN_PASSWD') == data.passwd) {
AddUser({ user: data.qq, token: token, autho: 'admin' })
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'admin', token: token, region: Bot.uin, type: 'server' }))
} else {
const user = await getUserData(data.qq)
if (user.passwd != '' && user.passwd === data.passwd) {
AddUser({ user: data.qq, token: token, autho: 'user' })
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'user', token: token, region: Bot.uin, type: 'server' }))
} else {
ws.send(JSON.stringify({ command: data.command, state: false, error: `用户名密码错误,如果忘记密码请私聊机器人输入 ${data.qq == Bot.uin ? '#修改管理密码' : '#修改用户密码'} 进行修改`, region: Bot.uin, type: 'server' }))
}
}
} else {
ws.send(JSON.stringify({ command: data.command, state: false, error: '未输入用户名或密码', region: Bot.uin, type: 'server' }))
}
break
}
} catch (error) {
console.log(error)
}
})
} else {
console.log('本地服务网络正常,无需开启通讯')
}
} else {
console.log('media服务器未响应')
}
}
// 未完工,暂不开启这个功能
// mediaLink()
export async function createServer() {
// 页面数据获取

View file

@ -0,0 +1,23 @@
import { UserInfo } from './user_data.js'
import fs from 'fs'
import path from 'path'
async function SettingView(fastify, options) {
// 获取配置视图
fastify.post('/settingView', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const filepath = path.join('plugins/chatgpt-plugin/resources/view', 'setting_view.json')
const configView = JSON.parse(fs.readFileSync(filepath, 'utf8'))
reply.send(configView)
} else {
reply.send({ err: '权限不足' })
}
return reply
})
}
export default SettingView

View file

@ -40,7 +40,8 @@ async function User(fastify, options) {
reply.send({
verify: true,
user: user.user,
autho: user.autho
autho: user.autho,
version: 10010,
})
return reply
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View file

@ -1,21 +0,0 @@
<!--
=========================================================
* Vue Notus - v1.1.0 based on Tailwind Starter Kit by Creative Tim
=========================================================
* Product Page: https://www.creative-tim.com/product/vue-notus
* Copyright 2021 Creative Tim (https://www.creative-tim.com)
* Licensed under MIT (https://github.com/creativetimofficial/vue-notus/blob/main/LICENSE.md)
* Tailwind Starter Kit Page: https://www.creative-tim.com/learning-lab/tailwind-starter-kit/presentation
* Coded by Creative Tim
=========================================================
* 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><title>ChatGPT-Plugin</title><script defer="defer" type="module" src="/js/chunk-vendors.f0ab5903.js"></script><script defer="defer" type="module" src="/js/app.5fabf316.js"></script><link href="/css/chunk-vendors.0ede84b4.css" rel="stylesheet"><link href="/css/app.db850df4.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.f38f83ce.js" nomodule></script><script defer="defer" src="/js/app-legacy.2b7469b6.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>

View file

@ -8,9 +8,11 @@ import buffer from 'buffer'
import yaml from 'yaml'
import puppeteer from '../../../lib/puppeteer/puppeteer.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'
import { convertSpeaker, generateVitsAudio, speakers as vitsRoleList } from './tts.js'
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from './tts/voicevox.js'
import AzureTTS, { supportConfigurations as azureRoleList } from './tts/microsoft-azure.js'
import { translate } from './translate.js'
import uploadRecord from './uploadRecord.js'
// export function markdownToText (markdown) {
// return remark()
// .use(stripMarkdown)
@ -702,11 +704,11 @@ export async function getUserData (user) {
}
export function getVoicevoxRoleList () {
return voxRoleList.map(item => item.name).join('')
return voxRoleList.map(item => item.name).join(',')
}
export function getAzureRoleList () {
return azureRoleList.map(item => item.name).join('、')
return azureRoleList.map(item => item.roleInfo + (item?.emotion ? '-> 支持:' + Object.keys(item.emotion).join('') + ' 情绪。' : '')).join('\n\n')
}
export async function getVitsRoleList (e) {
@ -778,18 +780,6 @@ export async function getImageOcrText (e) {
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]
}
export function getMaxModelTokens (model = 'gpt-3.5-turbo') {
if (model.startsWith('gpt-3.5-turbo')) {
@ -806,3 +796,137 @@ export function getMaxModelTokens (model = 'gpt-3.5-turbo') {
}
}
}
/**
* 生成当前语音模式下可发送的音频信息
* @param e - 上下文对象
* @param pendingText - 待处理文本
* @param speakingEmotion - AzureTTSMode中的发言人情绪
* @param emotionDegree - AzureTTSMode中的发言人情绪强度
* @returns {Promise<{file: string, type: string}|undefined|boolean>}
*/
export async function generateAudio (e, pendingText, speakingEmotion, emotionDegree = 1) {
if (!Config.ttsSpace && !Config.azureTTSKey && !Config.voicevoxSpace) return false
let wav
const speaker = getUserSpeaker(await getUserReplySetting(e))
try {
if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) {
if (Config.autoJapanese) {
try {
pendingText = await translate(pendingText, '日')
} catch (err) {
logger.warn(err.message + '\n将使用原始文本合成语音...')
return false
}
}
wav = await generateVitsAudio(pendingText, speaker, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
return await generateAzureAudio(pendingText, speaker, speakingEmotion, emotionDegree)
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
pendingText = (await translate(pendingText, '日')).replace('\n', '')
wav = await VoiceVoxTTS.generateAudio(pendingText, {
speaker
})
}
} catch (err) {
logger.error(err)
return false
}
let sendable
try {
try {
sendable = await uploadRecord(wav, Config.ttsMode)
if (sendable) {
await e.reply(sendable)
} else {
// 如果合成失败尝试使用ffmpeg合成
sendable = segment.record(wav)
}
} catch (err) {
logger.error(err)
sendable = segment.record(wav)
}
} catch (err) {
logger.error(err)
return false
}
if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
// 清理文件
try {
fs.unlinkSync(wav)
} catch (err) {
logger.warn(err)
}
}
return sendable
}
/**
* 生成可发送的AzureTTS音频
* @param pendingText - 待转换文本
* @param role - 发言人
* @param speakingEmotion - 发言人情绪
* @param emotionDegree - 发言人情绪强度
* @returns {Promise<{file: string, type: string}|boolean>}
*/
export async function generateAzureAudio (pendingText, role = '随机', speakingEmotion, emotionDegree = 1) {
if (!Config.azureTTSKey) return false
let speaker
try {
if (role !== '随机') {
// 判断传入的是不是code
if (azureRoleList.find(s => s.code === role.trim())) {
speaker = role
} else {
speaker = azureRoleList.find(s => s.roleInfo.includes(role.trim()))
if (!speaker) {
logger.warn('找不到名为' + role + '的发言人,将使用默认发言人 晓晓 发送音频.')
speaker = 'zh-CN-XiaoxiaoNeural'
} else {
speaker = speaker.code
}
}
let languagePrefix = azureRoleList.find(config => config.code === speaker).languageDetail.charAt(0)
languagePrefix = languagePrefix.startsWith('E') ? '英' : languagePrefix
pendingText = (await translate(pendingText, languagePrefix)).replace('\n', '')
} else {
let role, languagePrefix
role = azureRoleList[Math.floor(Math.random() * azureRoleList.length)]
speaker = role.code
languagePrefix = role.languageDetail.charAt(0).startsWith('E') ? '英' : role.languageDetail.charAt(0)
pendingText = (await translate(pendingText, languagePrefix)).replace('\n', '')
if (role?.emotion) {
const keys = Object.keys(role.emotion)
speakingEmotion = keys[Math.floor(Math.random() * keys.length)]
}
emotionDegree = 2
logger.info('using speaker: ' + speaker)
logger.info('using language: ' + languagePrefix)
logger.info('using emotion: ' + speakingEmotion)
}
let ssml = AzureTTS.generateSsml(pendingText, {
speaker,
emotion: speakingEmotion,
pendingText,
emotionDegree
})
return await uploadRecord(
await AzureTTS.generateAudio(pendingText, {
speaker
}, await ssml)
, Config.ttsMode
)
} catch (err) {
logger.error(err)
return false
}
}
export function getUserSpeaker (userSetting) {
if (Config.ttsMode === 'vits-uma-genshin-honkai') {
return convertSpeaker(userSetting.ttsRole || Config.defaultTTSRole)
} else if (Config.ttsMode === 'azure') {
return userSetting.ttsRoleAzure || Config.azureTTSSpeaker
} else if (Config.ttsMode === 'voicevox') {
return userSetting.ttsRoleVoiceVox || Config.voicevoxTTSSpeaker
}
}

View file

@ -1,4 +1,5 @@
import { AbstractTool } from './AbstractTool.js'
import cfg from '../../../../lib/config/config.js'
export class EditCardTool extends AbstractTool {
name = 'editCard'
@ -23,13 +24,21 @@ export class EditCardTool extends AbstractTool {
description = 'Useful when you want to edit someone\'s card in the group(群名片)'
func = async function (opts) {
func = async function (opts, e) {
let { qq, card, groupId } = opts
groupId = parseInt(groupId.trim())
qq = parseInt(qq.trim())
logger.info('edit card: ', groupId, qq)
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
groupId = isNaN(groupId) || !groupId ? e.group_id : parseInt(groupId.trim())
let group = await Bot.pickGroup(groupId)
let mm = await group.getMemberMap()
if (!mm.has(qq)) {
return `failed, the user ${qq} is not in group ${groupId}`
}
if (mm.get(Bot.uin).role === 'member') {
return `failed, you, not user, don't have permission to edit card in group ${groupId}`
}
logger.info('edit card: ', groupId, qq)
await group.setCard(qq, card)
return `the user ${qq}'s card has been changed into ${card}`
}
}
}

View file

@ -0,0 +1,45 @@
import { AbstractTool } from './AbstractTool.js'
export class EliMovieTool extends AbstractTool {
name = 'currentHotMovies'
parameters = {
properties: {
yesOrNo: {
type: 'string',
description: 'do you want to check?'
}
},
required: ['yesOrNo']
}
description = 'Useful when you want to check out the current hot movies'
func = async function (opts, e) {
let { yesOrNo } = opts
if (yesOrNo === 'no') {
return 'tell user why you don\'t want to check'
}
if (e.at === Bot.uin) {
e.at = null
}
e.atBot = false
let avocado
try {
// eslint-disable-next-line camelcase
let { AvocadoRuleALL } = await import('../../../avocado-plugin/apps/avocado.js')
avocado = new AvocadoRuleALL(e)
} catch (err1) {
return 'the user didn\'t install avocado-plugin. suggest him to install'
}
try {
// eslint-disable-next-line new-cap
e.senderFromChatGpt = e.sender.user_id
await avocado.avocadoMovie(e)
return 'notify the user that the movie has been sent to them and they can obtain more information by sending commands displayed in the picture. you dont need to search for additional information to reply! just simply inform them that you have completed your task!!!'
} catch (err) {
logger.warn(err)
return 'failed due to unknown error'
}
}
}

View file

@ -0,0 +1,82 @@
import { AbstractTool } from './AbstractTool.js'
export class EliMusicTool extends AbstractTool {
name = 'musicTool'
parameters = {
properties: {
keyword: {
type: 'string',
description: 'Not necessarily a songName, it can be some descriptive words, but does not include the singer\'s name! can be left blank!'
},
singer: {
type: 'string',
description: 'Singer name, multiple singers are separated by \',\'! can be left blank!'
},
isRandom: {
type: 'boolean',
description: 'Whether to randomly select songs, default is false'
},
isRandom2: {
type: 'boolean',
description: 'when isRandom is true and neither singer nor songName is specified!!!, this value is true; otherwise, it is false. The default value is false.'
},
isHot: {
type: 'boolean',
description: 'Whether it\'s related to \'hot\', consider filling in this item when there is no song name, can be left blank'
},
singerRegion: {
type: 'string',
description: 'Whether it\'s related to \'hot\', consider filling in this item when there is no song name, can be left blank'
},
isRelax: {
type: 'boolean',
description: 'Fill in when the user wants to sleep or rest, leave others blank when filling in this item, default is false'
}
},
required: ['keyword', 'singer', 'isRandom', 'singerRegion, isRelax', 'isRandom2']
}
description = 'It is very useful when you want to meet the music needs or of users or when users want to sleep or unwind., so you should use this tool as much as possible, regardless of whether I asked you before!'
func = async function (opts, e) {
let { keyword, singer, isRandom, isHot, singerRegion, isRelax, isRandom2 } = opts
let avocado
try {
let { AvocadoMusic } = await import('../../../avocado-plugin/apps/avocadoMusic.js')
avocado = new AvocadoMusic(e)
} catch (err) {
return 'the user didn\'t install avocado-plugin. suggest him to install'
}
try {
// 不听话的gpt
isRandom2 = !keyword && isRandom && !isRandom2 && !singer
if (isRandom2) {
try {
singer = await redis.get(`AVOCADO:MUSIC_${e.sender.user_id}_FAVSINGER`)
if (!singer) throw new Error('no favorite singer')
singer = JSON.parse(singer).singer
logger.warn(singer)
} catch (err) {
return 'the user didn\'t set a favorite singer. Suggest setting it through the command \'#设置歌手+歌手名称\'!'
}
e.msg = '#鳄梨酱#随机' + singer
} else if (isRelax) {
e.msg = '#鳄梨酱#随机放松'
} else if (singerRegion) {
e.msg = '#鳄梨酱#' + (isRandom ? '随机' : '') + (isHot ? '热门' : '') + singerRegion + '歌手'
} else {
e.msg = '#鳄梨酱#' + (isRandom ? '随机' : '') + (isHot ? '热门' : '') + (singer ? singer + (keyword ? ',' + keyword : '') : keyword)
}
e.senderFromChatGpt = e.sender.user_id
await avocado.pickMusic(e)
if (isRandom2) {
return 'tell the user that a random song by his favorite artist has been sent to him! you don\'t need to find other info!'
} else {
return 'tell user that the response of his request has been sent to the user! you don\'t need to find other info!'
}
} catch (e) {
return `music share failed: ${e}`
}
}
}

View file

@ -24,8 +24,9 @@ export class ImageCaptionTool extends AbstractTool {
description = 'useful when you want to know what is inside a photo, such as user\'s avatar or other pictures'
func = async function (opts) {
func = async function (opts, e) {
let { imgUrl, qq, question } = opts
if (isNaN(qq) || !qq) qq = e.sender.user_id
if (!imgUrl && qq) {
imgUrl = `https://q1.qlogo.cn/g?b=qq&s=160&nk=${qq}`
}

View file

@ -25,9 +25,20 @@ export class JinyanTool extends AbstractTool {
required: ['groupId', 'time']
}
func = async function (opts) {
func = async function (opts, e) {
let { qq, groupId, time = '600', sender, isAdmin, isPunish } = opts
groupId = isNaN(groupId) || !groupId ? e.group_id : parseInt(groupId.trim())
qq = qq !== 'all'
? isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
: 'all'
let group = await Bot.pickGroup(groupId)
let m = await group.getMemberMap()
if (!m.has(qq)) {
return `failed, the user ${qq} is not in group ${groupId}`
}
if (m.get(Bot.uin).role === 'member') {
return `failed, you, not user, don't have permission to edit card in group ${groupId}`
}
time = parseInt(time.trim())
if (time < 60 && time !== 0) {
time = 60
@ -36,17 +47,16 @@ export class JinyanTool extends AbstractTool {
time = 86400 * 30
}
if (isAdmin) {
if (qq.trim() === 'all') {
if (qq === 'all') {
return 'you cannot mute all because the master doesn\'t allow it'
} else {
qq = parseInt(qq.trim())
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
await group.muteMember(qq, time)
}
} else {
if (qq.trim() === 'all') {
if (qq === 'all') {
return 'the user is not admin, he can\'t mute all. the user should be punished'
} else if (qq == sender) {
qq = parseInt(qq.trim())
await group.muteMember(qq, time)
} else {
return 'the user is not admin, he can\'t let you mute other people.'

View file

@ -22,10 +22,10 @@ export class KickOutTool extends AbstractTool {
required: ['groupId']
}
func = async function (opts) {
func = async function (opts, e) {
let { qq, groupId, sender, isAdmin, isPunish } = opts
groupId = parseInt(groupId.trim())
qq = parseInt(qq.trim())
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
groupId = isNaN(groupId) || !groupId ? e.group_id : parseInt(groupId.trim())
if (!isAdmin && sender != qq) {
return 'the user is not admin, he cannot kickout other people. he should be punished'
}

View file

@ -9,7 +9,7 @@ export class ProcessPictureTool extends AbstractTool {
type: {
type: 'string',
enum: ['Image2Hed', 'Image2Scribble'],
description: 'how to process it. Image2Hed: useful when you want to detect the soft hed boundary of the image; Image2Scribble: useful when you want to generate a scribble of the image'
description: 'how to process it. Image2Hed: useful when you want to detect the soft hed boundary of the picture; Image2Scribble: useful when you want to generate a scribble of the picture'
},
qq: {
type: 'string',
@ -23,9 +23,9 @@ export class ProcessPictureTool extends AbstractTool {
required: ['type']
}
description = 'useful when you want to know what is inside a photo, such as user\'s avatar or other pictures'
description = 'useful when you want to process a picture or user\'s avatar.'
func = async function (opts) {
func = async function (opts, e) {
let { url, qq, type } = opts
if (qq) {
url = `https://q1.qlogo.cn/g?b=qq&s=160&nk=${qq}`
@ -57,7 +57,7 @@ export class ProcessPictureTool extends AbstractTool {
})
if (captionRes.status === 200) {
let result = await captionRes.text()
return `the processed image url is ${Config.extraUrl}${result}`
return `the processed image url is ${Config.extraUrl}${result}${qq ? ' and ' + url : ''}. you should send it with SendPictureTool.`
} else {
return 'error happened'
}

View file

@ -23,6 +23,7 @@ export class QueryGenshinTool extends AbstractTool {
func = async function (opts, e) {
let { qq, uid = '', character = '' } = opts
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
if (e.at === Bot.uin) {
e.at = null
}

View file

@ -23,6 +23,7 @@ export class QueryStarRailTool extends AbstractTool {
func = async function (opts, e) {
let { qq, uid, character } = opts
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
if (e.at === Bot.uin) {
e.at = null
}

View file

@ -2,7 +2,7 @@ import { AbstractTool } from './AbstractTool.js'
import { getMasterQQ } from '../common.js'
export class QueryUserinfoTool extends AbstractTool {
name = 'queryUser'
name = 'queryUserinfo'
parameters = {
properties: {
@ -16,9 +16,10 @@ export class QueryUserinfoTool extends AbstractTool {
func = async function (opts, e) {
let { qq } = opts
if (e.is_group && typeof e.group.getMemberMap === 'function') {
let mm = e.group.getMemberMap()
let user = mm.get(parseInt(qq) || e.sender.user_id)
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
if (e.isGroup && typeof e.group.getMemberMap === 'function') {
let mm = await e.group.getMemberMap()
let user = mm.get(qq) || e.sender.user_id
let master = (await getMasterQQ())[0]
let prefix = ''
if (qq != master) {

View file

@ -26,5 +26,5 @@ export class SerpImageTool extends AbstractTool {
return `the images search results are here in json format:\n${JSON.stringify(res)}. the murl field is real picture url. You should use sendPicture to send them`
}
description = 'Useful when you want to search images from the internet. '
description = 'Useful when you want to search images from the internet.'
}

View file

@ -8,7 +8,7 @@ export class SearchMusicTool extends AbstractTool {
properties: {
keyword: {
type: 'string',
description: '音乐的标题或关键词'
description: '音乐的标题或关键词, 可以是歌曲名或歌曲名+歌手名的组合'
}
},
required: ['keyword']

View file

@ -0,0 +1,122 @@
import { AbstractTool } from './AbstractTool.js'
import { generateVitsAudio } from '../tts.js'
import { Config } from '../config.js'
import { generateAudio, generateAzureAudio } from '../common.js'
import VoiceVoxTTS from '../tts/voicevox.js'
import uploadRecord from '../uploadRecord.js'
export class SendAudioMessageTool extends AbstractTool {
name = 'sendAudioMessage'
parameters = {
properties: {
pendingText: {
type: 'string',
description: 'Message to be sent and it will be turned into audio message'
},
ttsMode: {
type: 'number',
description: 'default is 1, which indicates that the text will be processed in the current ttsMode.' +
'2 is azureMode.' +
'3 or 4 corresponds to vitsMode or voxMode.'
},
vitsModeRole: {
type: 'string',
description: 'use whose voice',
enum: ['琴', '空',
'丽莎', '荧', '芭芭拉', '凯亚', '迪卢克', '雷泽', '安柏', '温迪',
'香菱', '北斗', '行秋', '魈', '凝光', '可莉', '钟离', '菲谢尔(皇女)',
'班尼特', '达达利亚(公子)', '诺艾尔(女仆)', '七七', '重云', '甘雨(椰羊)',
'阿贝多', '迪奥娜(猫猫)', '莫娜', '刻晴', '砂糖', '辛焱', '罗莎莉亚',
'胡桃', '枫原万叶(万叶)', '烟绯', '宵宫', '托马', '优菈', '雷电将军(雷神)',
'早柚', '珊瑚宫心海', '五郎', '九条裟罗', '荒泷一斗',
'埃洛伊', '申鹤', '八重神子', '神里绫人(绫人)', '夜兰', '久岐忍',
'鹿野苑平藏', '提纳里', '柯莱', '多莉', '云堇', '纳西妲(草神)', '深渊使徒',
'妮露', '赛诺']
},
azureModeRole: {
type: 'string',
description: 'can be \'随机\' or specified by the user. default is currentRole.'
},
voxModeRole: {
type: 'string',
description: 'can be random or currentRole or specified by the user. default is currentRole.'
},
speakingEmotion: {
type: 'string',
description: 'specified by the user. default is blank.'
},
speakingEmotionDegree: {
type: 'number',
description: 'specified by the user. default is blank.'
},
targetGroupIdOrQQNumber: {
type: 'string',
description: 'Fill in the target user\'s qq number or groupId when you need to send audio message to specific user or group, otherwise leave blank'
}
},
required: ['pendingText', 'ttsMode', 'targetGroupIdOrQQNumber']
}
description = 'This tool is used to send voice|audio messages, utilize it only if the user grants you permission to do so.'
func = async function (opts, e) {
if (!Config.ttsSpace && !Config.azureTTSKey && !Config.voicevoxSpace) {
return 'you don\'t have permission to send audio message due to a lack of a valid ttsKey'
}
let { pendingText, ttsMode, vitsModeRole, azureModeRole, voxModeRole, speakingEmotion, speakingEmotionDegree, targetGroupIdOrQQNumber } = opts
let sendable
ttsMode = isNaN(ttsMode) || !ttsMode ? 1 : ttsMode
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
try {
switch (ttsMode) {
case 1:
sendable = await generateAudio(e, pendingText, speakingEmotion)
break
case 2:
if (!Config.azureTTSKey) return 'audio generation failed, due to a lack of a azureTTSKey'
sendable = await generateAzureAudio(pendingText, azureModeRole, speakingEmotion, speakingEmotionDegree)
break
case 3:
if (!Config.ttsSpace) return 'audio generation failed, due to a lack of a ttsSpace'
sendable = await uploadRecord(
await generateVitsAudio(pendingText, vitsModeRole, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
, 'vits-uma-genshin-honkai'
)
break
case 4:
if (!Config.voicevoxSpace) return 'audio generation failed, due to a lack of a voicevoxSpace'
sendable = await uploadRecord(
await VoiceVoxTTS.generateAudio(pendingText, voxModeRole)
, 'voicevox'
)
break
default:
sendable = await generateAzureAudio(pendingText, azureModeRole, speakingEmotion, speakingEmotionDegree)
}
} catch (err) {
logger.error(err)
return `audio generation failed, error: ${JSON.stringify(err)}`
}
if (sendable) {
let groupList = await Bot.getGroupList()
try {
if (groupList.get(target)) {
let group = await Bot.pickGroup(target)
await group.sendMsg(sendable)
return 'audio has been sent to group' + target
} else {
let user = await Bot.pickFriend(target)
await user.sendMsg(sendable)
return 'audio has been sent to user' + target
}
} catch (err) {
return `failed to send audio, error: ${JSON.stringify(err)}`
}
} else {
return 'audio generation failed'
}
}
}

View file

@ -7,27 +7,34 @@ export class SendAvatarTool extends AbstractTool {
properties: {
qq: {
type: 'string',
description: '要发头像的人的qq号'
description: 'if you need to send avatar of a user, input his qq.If there are multiple qq, separate them with a space'
},
groupId: {
targetGroupIdOrQQNumber: {
type: 'string',
description: '群号或qq号发送目标'
description: 'Fill in the target user\'s qq number or groupId when you need to send avatar to specific user or group, otherwise leave blank'
}
},
required: ['qq', 'groupId']
required: ['qq', 'targetGroupIdOrQQNumber']
}
func = async function (opts) {
let { qq, groupId } = opts
let groupList = await Bot.getGroupList()
groupId = parseInt(groupId.trim())
console.log('sendAvatar', groupId, qq)
if (groupList.get(groupId)) {
let group = await Bot.pickGroup(groupId)
await group.sendMsg(segment.image('https://q1.qlogo.cn/g?b=qq&s=0&nk=' + qq))
func = async function (opts, e) {
let { qq, targetGroupIdOrQQNumber } = opts
const pictures = qq.split(/[,\s]/).filter(qq => !isNaN(qq.trim()) && qq.trim()).map(qq => segment.image('https://q1.qlogo.cn/g?b=qq&s=0&nk=' + parseInt(qq.trim())))
if (!pictures.length) {
return 'there is no valid qq'
}
return `the user ${qq}'s avatar has been sent to group ${groupId}`
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
let groupList = await Bot.getGroupList()
console.log('sendAvatar', target, pictures)
if (groupList.get(target)) {
let group = await Bot.pickGroup(target)
await group.sendMsg(pictures)
}
return `the ${pictures.length > 1 ? 'users: ' + qq + '\'s avatar' : 'avatar'} has been sent to group ${target}`
}
description = 'Useful when you want to send the user avatar picture to the group. The input to this tool should be the user\'s qq number and the target group number, and they should be concated with a space. 如果是在群聊中,优先选择群号发送。'
description = 'Useful when you want to send the user avatar to the group. Note that if you want to process user\'s avatar, it is advisable to utilize the ProcessPictureTool and input the qq of target user.'
}

View file

@ -12,21 +12,24 @@ export class SendVideoTool extends AbstractTool {
type: 'string',
description: '要发的视频的id'
},
groupId: {
targetGroupIdOrQQNumber: {
type: 'string',
description: '群号或qq号发送目标为空则发送到当前聊天'
description: 'Fill in the target user\'s qq number or groupId when you need to send video to specific user or group, otherwise leave blank'
}
},
required: ['id']
}
func = async function (opts) {
let { id, groupId } = opts
groupId = parseInt(groupId.trim())
func = async function (opts, e) {
let { id, targetGroupIdOrQQNumber } = opts
// 非法值则发送到当前群聊或私聊
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
let msg = []
try {
let { arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like, honor } = await getBilibili(id)
let group = await Bot.pickGroup(groupId)
let group = await Bot.pickGroup(target)
msg.push(title.replace(/(<([^>]+)>)/ig, '') + '\n')
msg.push(`UP主${author} 发布日期:${formatDate(new Date(pubdate * 1000))} 播放量:${play} 点赞:${like}\n`)
msg.push(arcurl + '\n')
@ -47,7 +50,7 @@ export class SendVideoTool extends AbstractTool {
await fs.writeFileSync(fileLoc, buffer)
await group.sendMsg(segment.video(fileLoc))
})
return `the video ${title.replace(/(<([^>]+)>)/ig, '')} was shared to ${groupId}. the video information: ${msg}`
return `the video ${title.replace(/(<([^>]+)>)/ig, '')} was shared to ${target}. the video information: ${msg}`
} catch (err) {
logger.error(err)
if (msg.length > 0) {
@ -58,7 +61,7 @@ export class SendVideoTool extends AbstractTool {
}
}
description = 'Useful when you want to share a video. You must use searchVideo to get search result and choose one video and get its id'
description = 'Useful when you are allowed to send a video. You must use searchVideo to get search result and choose one video and get its id'
}
export async function getBilibili (bvid) {
@ -103,7 +106,7 @@ export async function getBilibili (bvid) {
let pubdate = videoInfo.data.pubdate
let like = videoInfo.data.stat.like
let honor = videoInfo.data.honor_reply?.honor?.map(h => h.desc)?.join('、')
let downloadInfo = await fetch(`https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${cid}`, {headers})
let downloadInfo = await fetch(`https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${cid}`, { headers })
let videoUrl = (await downloadInfo.json()).data.durl[0].url
return {
arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like, honor
@ -133,4 +136,4 @@ function randomIndex () {
}
}
console.log('send bilibili')
// console.log('send bilibili')

View file

@ -9,26 +9,38 @@ export class SendDiceTool extends AbstractTool {
type: 'number',
description: '骰子的数量'
},
groupId: {
targetGroupIdOrQQNumber: {
type: 'string',
description: '群号或qq号发送目标'
description: 'Fill in the target qq number or groupId when you need to send Dice to specific user or group, otherwise leave blank'
}
},
required: ['num', 'groupId']
required: ['num', 'targetGroupIdOrQQNumber']
}
func = async function (opts) {
let { num, groupId } = opts
func = async function (opts, e) {
let { num, targetGroupIdOrQQNumber } = opts
// 非法值则发送到当前群聊或私聊
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
let groupList = await Bot.getGroupList()
if (groupList.get(groupId)) {
let group = await Bot.pickGroup(groupId, true)
await group.sendMsg(segment.dice(num))
num = isNaN(num) || !num ? 1 : num > 5 ? 5 : num
if (groupList.get(target)) {
let group = await Bot.pickGroup(target, true)
for (let i = 0; i < num; i++) {
await group.sendMsg(segment.dice())
}
} else {
let friend = await Bot.pickFriend(groupId)
await friend.sendMsg(segment.dice(num))
let friend = await Bot.pickFriend(target)
await friend.sendMsg(segment.dice())
}
if (num === 5) {
logger.warn(1)
return 'tell the user that in order to avoid spamming the chat, only five dice are sent this time, and warn him not to use this tool to spamming the chat, otherwise you will use JinyanTool to punish him'
} else {
return 'the dice has been sent'
}
return 'the dice has been sent'
}
description = 'If you want to roll dice, use this tool. If you know the group number, use the group number instead of the qq number first. The input should be the number of dice to be cast (1-6) and the target group number or qq numberand they should be concat with a space'
description = 'If you want to roll dice, use this tool. Be careful to check that the targetGroupIdOrQQNumber is correct. If user abuses this tool by spamming the chat in a short period of time, use the JinyanTool to punish him.'
}

View file

@ -0,0 +1,44 @@
import { AbstractTool } from './AbstractTool.js'
import { convertFaces } from '../face.js'
export class SendMessageToSpecificGroupOrUserTool extends AbstractTool {
name = 'sendMessageToSpecificGroupOrUser'
parameters = {
properties: {
msg: {
type: 'string',
description: 'Message text to be sent'
},
targetGroupIdOrQQNumber: {
type: 'string',
description: 'Fill in the target user\'s qq number or groupId when you need to send specific message to specific user or group, otherwise leave blank'
}
},
required: ['msg', 'msgType', 'targetGroupIdOrQQNumber']
}
func = async function (opt, e) {
let { msg, targetGroupIdOrQQNumber } = opt
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
let groupList = await Bot.getGroupList()
try {
if (groupList.get(target)) {
let group = await Bot.pickGroup(target)
await group.sendMsg(await convertFaces(msg, true, e))
return 'msg has been sent to group' + target
} else {
let user = await Bot.pickFriend(target)
await user.sendMsg(msg)
return 'msg has been sent to user' + target
}
} catch (err) {
return `failed to send msg, error: ${JSON.stringify(err)}`
}
}
description = 'Useful when you want to send a text message to specific user or group'
}

View file

@ -9,21 +9,25 @@ export class SendMusicTool extends AbstractTool {
type: 'string',
description: '音乐的id'
},
groupId: {
targetGroupIdOrQQNumber: {
type: 'string',
description: '群号或qq号发送目标为空则发送到当前聊天'
description: 'Fill in the target user_id or groupId when you need to send music to specific group or user, otherwise leave blank'
}
},
required: ['keyword']
}
func = async function (opts) {
let { id, groupId } = opts
groupId = parseInt(groupId.trim())
func = async function (opts, e) {
let { id, targetGroupIdOrQQNumber } = opts
// 非法值则发送到当前群聊
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.group_id
: parseInt(targetGroupIdOrQQNumber.trim())
try {
let group = await Bot.pickGroup(groupId)
let group = await Bot.pickGroup(target)
await group.shareMusic('163', id)
return `the music has been shared to ${groupId}`
return `the music has been shared to ${target}`
} catch (e) {
return `music share failed: ${e}`
}

View file

@ -5,46 +5,47 @@ export class SendPictureTool extends AbstractTool {
parameters = {
properties: {
picture: {
urlOfPicture: {
type: 'string',
description: 'the url of the pictures, split with space if more than one.'
description: 'the url of the pictures, not text, split with space if more than one. can be left blank.'
},
qq: {
targetGroupIdOrQQNumber: {
type: 'string',
description: 'if you want to send avatar of a user, input his qq number.'
},
groupId: {
type: 'string',
description: 'the group number or qq number, will send to current conversation if leave blank'
description: 'Fill in the target user\'s qq number or groupId when you need to send picture to specific user or group, otherwise leave blank'
}
},
required: ['picture']
required: ['urlOfPicture', 'targetGroupIdOrQQNumber']
}
func = async function (opt) {
let { picture, groupId, qq } = opt
if (qq) {
let avatar = `https://q1.qlogo.cn/g?b=qq&s=0&nk=${qq}`
picture += (' ' + avatar)
func = async function (opt, e) {
let { urlOfPicture, targetGroupIdOrQQNumber } = opt
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
// 处理错误url和picture留空的情况
const urlRegex = /(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:((?:(?:[a-z0-9\u00a1-\u4dff\u9fd0-\uffff][a-z0-9\u00a1-\u4dff\u9fd0-\uffff_-]{0,62})?[a-z0-9\u00a1-\u4dff\u9fd0-\uffff]\.)+(?:[a-z\u00a1-\u4dff\u9fd0-\uffff]{2,}\.?))(?::\d{2,5})?)(?:\/[\w\u00a1-\u4dff\u9fd0-\uffff$-_.+!*'(),%]+)*(?:\?(?:[\w\u00a1-\u4dff\u9fd0-\uffff$-_.+!*(),%:@&=]|(?:[\[\]])|(?:[\u00a1-\u4dff\u9fd0-\uffff]))*)?(?:#(?:[\w\u00a1-\u4dff\u9fd0-\uffff$-_.+!*'(),;:@&=]|(?:[\[\]]))*)?\/?/i
if (/https:\/\/example.com/.test(urlOfPicture) || !urlOfPicture || !urlRegex.test(urlOfPicture)) urlOfPicture = ''
if (!urlOfPicture) {
return 'Because there is no correct URL for the picture ,tell user the reason and ask user if he want to use SearchImageTool'
}
let pictures = picture.trim().split(' ')
let pictures = urlOfPicture.trim().split(' ')
logger.mark('pictures to send: ', pictures)
pictures = pictures.map(img => segment.image(img))
let groupList = await Bot.getGroupList()
groupId = parseInt(groupId)
try {
if (groupList.get(groupId)) {
let group = await Bot.pickGroup(groupId)
if (groupList.get(target)) {
let group = await Bot.pickGroup(target)
await group.sendMsg(pictures)
return `picture has been sent to group ${groupId}`
return 'picture has been sent to group' + target
} else {
let user = await Bot.pickFriend(groupId)
let user = await Bot.pickFriend(target)
await user.sendMsg(pictures)
return `picture has been sent to user ${groupId}`
return 'picture has been sent to user' + target
}
} catch (err) {
return `failed to send pictures, error: ${JSON.stringify(err)}`
}
}
description = 'Useful when you want to send one or more pictures. '
description = 'Useful when you want to send one or more pictures.'
}

View file

@ -1,4 +1,4 @@
import {AbstractTool} from "./AbstractTool.js";
import { AbstractTool } from './AbstractTool.js'
export class SendRPSTool extends AbstractTool {
name = 'sendRPS'
@ -8,20 +8,24 @@ export class SendRPSTool extends AbstractTool {
type: 'number',
description: '石头剪刀布的代号'
},
groupId: {
targetGroupIdOrQQNumber: {
type: 'string',
description: '群号或qq号发送目标'
description: 'Fill in the target user_id or groupId when you need to send RPS to specific group or user'
},
required: ['num', 'groupId']
required: ['num', 'targetGroupIdOrUserQQNumber']
}
func = async function (num, groupId) {
func = async function (num, targetGroupIdOrQQNumber, e) {
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? e.isGroup ? e.group_id : e.sender.user_id
: parseInt(targetGroupIdOrQQNumber.trim())
let groupList = await Bot.getGroupList()
if (groupList.get(groupId)) {
let group = await Bot.pickGroup(groupId, true)
if (groupList.get(target)) {
let group = await Bot.pickGroup(target, true)
await group.sendMsg(segment.rps(num))
} else {
let friend = await Bot.pickFriend(groupId)
let friend = await Bot.pickFriend(target)
await friend.sendMsg(segment.rps(num))
}
}

View file

@ -36,5 +36,5 @@ export class SerpTool extends AbstractTool {
return `the search results are here in json format:\n${JSON.stringify(res)}`
}
description = 'Useful when you want to search something from the internet. If you don\'t know much about the user\'s question, just search about it! If you want to know details of a result, you can use website tool'
description = 'Useful when you want to search something from the internet. If you don\'t know much about the user\'s question, just search about it! If you want to know details of a result, you can use website tool! use it as much as you can!'
}

View file

@ -1,57 +0,0 @@
import { AbstractTool } from './AbstractTool.js'
import { convertSpeaker, generateAudio } from '../tts.js'
import uploadRecord from '../uploadRecord.js'
import { Config } from '../config.js'
export class TTSTool extends AbstractTool {
name = 'tts'
parameters = {
properties: {
text: {
type: 'string',
description: 'the text will be turned into audio'
},
role: {
type: 'string',
description: 'use whose voice',
enum: ['琴', '空',
'丽莎', '荧', '芭芭拉', '凯亚', '迪卢克', '雷泽', '安柏', '温迪',
'香菱', '北斗', '行秋', '魈', '凝光', '可莉', '钟离', '菲谢尔(皇女)',
'班尼特', '达达利亚(公子)', '诺艾尔(女仆)', '七七', '重云', '甘雨(椰羊)',
'阿贝多', '迪奥娜(猫猫)', '莫娜', '刻晴', '砂糖', '辛焱', '罗莎莉亚',
'胡桃', '枫原万叶(万叶)', '烟绯', '宵宫', '托马', '优菈', '雷电将军(雷神)',
'早柚', '珊瑚宫心海', '五郎', '九条裟罗', '荒泷一斗',
'埃洛伊', '申鹤', '八重神子', '神里绫人(绫人)', '夜兰', '久岐忍',
'鹿野苑平藏', '提纳里', '柯莱', '多莉', '云堇', '纳西妲(草神)', '深渊使徒',
'妮露', '赛诺']
},
groupId: {
type: 'string',
description: 'groupId'
}
},
required: ['text', 'role', 'groupId']
}
description = 'Useful when you want to turn text into audio and send it'
func = async function (opts) {
let { text, role, groupId } = opts
groupId = parseInt(groupId.trim())
try {
let wav = await generateAudio(text, convertSpeaker(role), '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
let sendable = await uploadRecord(wav, Config.ttsMode)
if (sendable) {
let group = await Bot.pickGroup(groupId)
await group.sendMsg(sendable)
return 'audio has been sent successfully'
} else {
return 'audio generation failed'
}
} catch (err) {
logger.error(err)
return 'audio generation failed'
}
}
}

View file

@ -43,14 +43,21 @@ export class WebsiteTool extends AbstractTool {
if (origin) {
Config.headless = false
}
// text = text.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
// .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
// .replace(/<head\b[^<]*(?:(?!<\/head>)<[^<]*)*<\/head>/gi, '')
// .replace(/<!--[\s\S]*?-->/gi, '')
text = text.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '') // 移除<style>标签及其内容
.replace(/<[^>]+style\s*=\s*(["'])(?:(?!\1).)*\1[^>]*>/gi, '') // 移除带有style属性的标签
.replace(/<[^>]+>/g, '')
text = text.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<head\b[^<]*(?:(?!<\/head>)<[^<]*)*<\/head>/gi, '')
.replace(/<figure\b[^<]*(?:(?!<\/figure>)<[^<]*)*<\/figure>/gi, '')
.replace(/<path\b[^<]*(?:(?!<\/path>)<[^<]*)*<\/path>/gi, '')
.replace(/<video\b[^<]*(?:(?!<\/video>)<[^<]*)*<\/video>/gi, '')
.replace(/<audio\b[^<]*(?:(?!<\/audio>)<[^<]*)*<\/audio>/gi, '')
.replace(/<img[^>]*>/gi, '')
.replace(/<!--[\s\S]*?-->/gi, '') // 去除注释
.replace(/<(?!\/?(title|ul|li|td|tr|thead|tbody|blockquote|h[1-6]|H[1-6])[^>]*)\w+\s+[^>]*>/gi, '') // 去除常见语音标签外的含属性标签
.replace(/<(\w+)(\s[^>]*)?>/gi, '<$1>') // 进一步去除剩余标签的属性
.replace(/<\/(?!\/?(title|ul|li|td|tr|thead|tbody|blockquote|h[1-6]|H[1-6])[^>]*)[a-z][a-z0-9]*>/gi, '') // 去除常见语音标签外的含属性结束标签
.replace(/[\n\r]/gi, '') // 去除回车换行
.replace(/\s{2}/g, '') // 多个空格只保留一个空格
.replace('<!DOCTYPE html>', '') // 去除<!DOCTYPE>声明
let maxModelTokens = getMaxModelTokens(Config.model)
text = text.slice(0, Math.min(text.length, maxModelTokens - 1600))
let api = new ChatGPTAPI({
@ -74,7 +81,7 @@ export class WebsiteTool extends AbstractTool {
},
maxModelTokens
})
const htmlContentSummaryRes = await api.sendMessage(`这是一个网页html经过筛选的内容请你进一步去掉其中的标签、样式、script等无用信息并从中提取出其中的主体内容转换成自然语言告诉我不需要主观描述性的语言${text}`)
const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行${text}`)
let htmlContentSummary = htmlContentSummaryRes.text
return `this is the main content of website:\n ${htmlContentSummary}`
} catch (err) {

View file

@ -36,7 +36,18 @@ function randomNum (minNum, maxNum) {
return 0
}
}
export async function generateAudio (text, speaker = '随机', language = '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)', noiseScale = Config.noiseScale, noiseScaleW = Config.noiseScaleW, lengthScale = Config.lengthScale) {
/**
* 生成VitsTTSMode下的wav音频
* @param text
* @param speaker
* @param language
* @param noiseScale
* @param noiseScaleW
* @param lengthScale
* @returns {Promise<string>}
*/
export async function generateVitsAudio (text, speaker = '随机', language = '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)', noiseScale = Config.noiseScale, noiseScaleW = Config.noiseScaleW, lengthScale = Config.lengthScale) {
if (!speaker || speaker === '随机') {
logger.info('随机角色!这次哪个角色这么幸运会被选到呢……')
speaker = speakers[randomNum(0, speakers.length)]

View file

@ -9,7 +9,15 @@ try {
} catch (err) {
logger.warn('未安装microsoft-cognitiveservices-speech-sdk无法使用微软Azure语音源')
}
async function generateAudio (text, option = {}, ssml = '') {
/**
* 生成AzureTTSMode下的wav音频
* @param pendingText - 待处理文本
* @param option
* @param ssml
* @returns {Promise<string>}
*/
async function generateAudio (pendingText, option = {}, ssml = '') {
if (!sdk) {
throw new Error('未安装microsoft-cognitiveservices-speech-sdk无法使用微软Azure语音源')
}
@ -22,7 +30,7 @@ async function generateAudio (text, option = {}, ssml = '') {
let audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename)
let synthesizer
let speaker = option?.speaker || '随机'
let context = text
let context = pendingText
// 打招呼用
if (speaker === '随机') {
speaker = supportConfigurations[Math.floor(Math.random() * supportConfigurations.length)].code
@ -47,9 +55,9 @@ async function generateAudio (text, option = {}, ssml = '') {
return filename
}
async function speakTextAsync (synthesizer, text) {
async function speakTextAsync (synthesizer, pendingText) {
return new Promise((resolve, reject) => {
synthesizer.speakTextAsync(text, result => {
synthesizer.speakTextAsync(pendingText, result => {
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
logger.info('speakTextAsync: true')
resolve()
@ -82,7 +90,7 @@ async function speakSsmlAsync (synthesizer, ssml) {
})
})
}
async function generateSsml (text, option = {}) {
async function generateSsml (pendingText, option = {}) {
let speaker = option?.speaker || '随机'
let emotionDegree, role, emotion
// 打招呼用
@ -104,7 +112,7 @@ async function generateSsml (text, option = {}) {
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">
<voice name="${speaker}">
${expressAs}${text}${expressAs ? '</mstts:express-as>' : ''}
${expressAs}${pendingText}${expressAs ? '</mstts:express-as>' : ''}
</voice>
</speak>`
}

View file

@ -24,6 +24,12 @@ const newFetch = (url, options = {}) => {
return fetch(url, mergedOptions)
}
/**
* 生成voxTTSMode下的wav音频
* @param text
* @param options
* @returns {Promise<Buffer>}
*/
async function generateAudio (text, options = {}) {
let host = Config.voicevoxSpace
let speaker = options.speaker || '随机'