mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
🥵🥵🥵 (#496)
* feat: add support for ‘greeting’ and ‘global reply mode’ commands, improve variable naming and remove unnecessary backend output. * feat: Add support for black and white lists, global reply mode and voice role settings, private chat switch, and active greeting configuration. Refactor some variable names and comment out redundant code for better readability and reduced backend output. * feat: 为新功能完善了帮助面板 * docs: 完善了‘打招呼’的帮助说明 * Commit Type: feat, bugfix Add functionality to view plugin command table, fix bug in blacklist/whitelist, and fix bug where chat mode can still be used in private messaging when disabled. * Commit Type: feat, bugfix Add functionality to view plugin command table, fix bug in blacklist/whitelist, and fix bug where chat mode can still be used in private messaging when disabled. * refactor: Remove redundant log output. * Refactor: optimize code logic * Fix: 修复绘图指令表被抢指令的bug。 * Refactor:1. Add support for automatically translating replies to Japanese and generating voice messages in VITS voice mode (please monitor remaining quota after enabling). 2. Add translation function. 3. Add emotion configuration for Azure voice mode, allowing the robot to select appropriate emotional styles for replies. * Refactor:Handle the issue of exceeding character setting limit caused by adding emotion configuration. * Fix: fix bugs * Refactor: Added error feedback to translation service * Refactor: Added support for viewing the list of supported roles for each language mode, and fixed some bugs in the emotion switching feature of the auzre mode. * Refactor: Optimized some command feedback and added owner restriction to chat record export function. * Refactor: Optimized feedback when viewing role list to avoid excessive messages. * Refactor: Optimized feedback when configuring multi-emotion mode. * Feature: Added help instructions for translation feature. * chore: Adjust help instructions for mood settings * Fix: Fixed issue where only first line of multi-line replies were being read and Azure voice was pronouncing punctuation marks. * Fix: Fixed bug where switching to Azure voice mode prompted for missing key and restricted ability to view voice role list to only when in voice mode. * Refactor: Add image OCR function and support translation for both quoted text and image. * fix: Fix issue with error caused by non-image input. * Refactor: Optimize code to filter emojis that cannot be displayed properly in claude mode. * Refactor: Optimize some code structures. * fix: Fix the bug of returning only one result when entering multiple lines of text on Windows system. * Refactor: Optimize code logic for better user experience * Refactor: Fix the conflict issue with other plugin translation commands * Refactor: Replace Baidu Translation with Youdao Translation to eliminate configuration steps; optimize translation experience; add missing dependency prompts instead of causing program errors.Optimize the experience of switching between voice mode and setting global reply mode. * Refactor: Remove unused files and dependencies in the project. * Feature: Add Youdao translation service to provide more comprehensive translation support. * Refactor: Optimize translation experience * Refactor: Optimize translation experience * Feature: Add functionality of keyword search command * Feature: Add functionality of keyword search command. * Refactor: Remove redundant code * Add: Add feature to support randomly selecting roles for Azure voice. Refactor the code to support existing voice services for the ‘greeting’ feature. Fix the display issue of Azure voice role selection on the Guoba panel. * Refactor: Remove redundant code * Refactor: Improve the function of setting global voice roles and viewing role lists. Now you can set default roles for each voice service separately or view the supported role list. * Refactor: Remove redundant code * Feature: Add new function to support random character dialogues in all voice modes, add the ability to view the current user’s reply settings, and improve related functions in the global settings. * Refactor: Add compatibility directive for viewing reply settings feature * Feature: support adding QQ number to blacklist/whitelist * fix: 处理全局设置指令被上下班指令占用的问题 * fix: 处理全局设置指令被上下班指令占用的问题 * Refactor: Preprocess dialogue blacklist/whitelist when filling in the form in Guoba panel * Fix: Fixed the issue where black and white lists were not effective when filled in the Guoba panel, and the issue where no results were returned when viewing the voice role list without parameters in azure tts mode. * fix: 2.7 dev start * feat: 初步支持function call(WIP) * fix: syntax error * fix: syntax error * feat: 群聊上下文 * fix: 暂时阉割掉全员禁言功能 * fix: 修改禁言时间范围 * fix: 修复一些功能易用性 * fix: 只有管理员和群主才能用jinyan和kickout * fix: 加回来禁言和踢出 * fix: 修复管理员权限判断问题(可能吧) * fix: 试图优化逻辑 * fix: fuck openai documents * fix: 删掉认主不然一直禁言我烦死了 * fix: 哔哩哔哩封面损坏问题 * fix: 加个天气小工具 * fix: 天气不存在城市 * fix: website工具用浏览器 * feat: serp tool * feat: 增加一个google搜索源 * fix: 加一句描述 * feat: 增加搜索来源选项 * feat: 搜图和发图 * fix: groupId format error * Refactor: Optimized the HTML parsing rules * fix: Fixed the bug where conversations could not be properly ended in at mode, now it works normally * refactor: Added EliMovieTool and EliMusicTool. Modified some tool’s prompt to make AI make better choices. Optimized the display of chat history.Remove SendMusicTool and replace it with EliMusicTool. * chore: trivial changes. * refactor:Keep SearchMusicTool and SendMusicTool when avocado plugin is not installed, waiting for more testing~~~ * Refactor: Move the parameter processing logic of some tools into each tool internally.🥑Rename ttstool to SendAudioMessageTool, defaulting to the voice role configured by the current user, and support personalized configuration of the existing tts mode in the plugin.🥑Add SendMessageToSpecificGroupOrUserTool, which allows the robot to send messages to specific groups or friends.🥑Encapsulate the function of generating audio messages into an independent function for easy tool invocation.🥑Separate SendPictureTool and SendAvatarTool to avoid unnecessary bugs.🥑 * chore: Remove unnecessary log file🥑 * refactor: Trivial changes and fixed a bug in QueryUserinfoTool.🥑 * Refactor: Rewrite blacklist and whitelist functionality, optimize code calls Description: Rewrote the blacklist and whitelist functionality to support group, QQ number, and a combination of group and QQ number configurations. Removed the command to set the blacklist and whitelist functionality and unified it through the Guoba panel. Also optimized some code calls to EliMusicTool and EliMovieTool.🥑 --------- Co-authored-by: Sean <1519059137@qq.com> Co-authored-by: ikechan8370 <geyinchibuaa@gmail.com> Co-authored-by: ikechan8370 <geyinchi@buaa.edu.cn>
This commit is contained in:
parent
36e592e773
commit
2443ed6f71
32 changed files with 769 additions and 481 deletions
249
apps/chat.js
249
apps/chat.js
|
|
@ -27,24 +27,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 +55,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 +287,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 +789,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 +1176,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,52 +1806,20 @@ export class chatgpt extends plugin {
|
|||
chats.push(...chatHistory)
|
||||
}
|
||||
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
|
||||
let whoAmI = ''
|
||||
if (Config.enforceMaster && master && opt.qq) {
|
||||
// 加强主人人知
|
||||
if (opt.qq === master) {
|
||||
whoAmI = '当前和你对话的人是我。'
|
||||
} else {
|
||||
whoAmI = `当前和你对话的人不是我,他的qq是${opt.qq},你可不要认错了,小心他用花言巧语哄骗你。`
|
||||
}
|
||||
}
|
||||
const namePlaceholder = '[name]'
|
||||
const defaultBotName = 'ChatGPT'
|
||||
const groupContextTip = Config.groupContextTip
|
||||
const masterTip = `注意:${opt.masterName ? '我是' + opt.masterName + ',' : ''}。我的qq号是${master},其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要~${whoAmI}`
|
||||
system = system.replaceAll(namePlaceholder, opt.botName || defaultBotName) +
|
||||
((Config.enableGroupContext && opt.groupId) ? groupContextTip : '') +
|
||||
((Config.enforceMaster && master) ? masterTip : '')
|
||||
system += '注意,你现在正在一个qq群里和人聊天,现在问你问题的人是' + `${opt.nickname}(${opt.qq})。`
|
||||
if (Config.enforceMaster && master) {
|
||||
if (opt.qq === master) {
|
||||
system += '这是我哦,不要认错了。'
|
||||
} else {
|
||||
system += '他不是我,你可不要认错了。'
|
||||
}
|
||||
}
|
||||
system += `这个群的名字叫做${opt.groupName},群号是${opt.groupId}。`
|
||||
if (opt.botName) {
|
||||
system += `你在这个群的名片叫做${opt.botName},`
|
||||
}
|
||||
if (Config.enforceMaster && opt.masterName) {
|
||||
system += `我是${opt.masterName}`
|
||||
}
|
||||
// system += master ? `我的qq号是${master},其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要。` : ''
|
||||
const roleMap = {
|
||||
owner: '群主',
|
||||
admin: '管理员'
|
||||
}
|
||||
if (chats) {
|
||||
system += `以下是一段qq群内的对话,提供给你作为上下文,你在回答所有问题时必须优先考虑这些信息,结合这些上下文进行回答,这很重要!!!。"
|
||||
`
|
||||
system += `\n以下是一段qq群内的对话,提供给你作为上下文,你在回答所有问题时必须优先考虑这些信息,结合这些上下文进行回答,这很重要!!!。记住你的qq号是${Bot.uin},现在问你问题的人是, ${opt.nickname},他的qq号是${opt.qq}。"`
|
||||
system += chats
|
||||
.map(chat => {
|
||||
let sender = chat.sender || {}
|
||||
|
|
@ -1903,13 +1828,42 @@ export class chatgpt extends plugin {
|
|||
// 建议的回复太容易污染设定导致对话太固定跑偏了
|
||||
return ''
|
||||
}
|
||||
return `【${sender.card || sender.nickname}】(qq:${sender.user_id},${roleMap[sender.role] || '普通成员'},${sender.area ? '来自' + sender.area + ',' : ''} ${sender.age}岁, 群头衔:${sender.title}, 性别:${sender.sex},时间:${formatDate(new Date(chat.time * 1000))}) 说:${chat.raw_message}`
|
||||
return `【${sender.card || sender.nickname}】(qq number/号:${sender.user_id},${roleMap[sender.role] || '普通成员'},${sender.area ? '来自' + sender.area + ',' : ''} ${sender.age}岁, 群头衔:${sender.title}, 性别:${sender.sex},时间:${formatDate(new Date(chat.time * 1000))}) 说:${chat.raw_message}`
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
let whoAmI = ''
|
||||
if (Config.enforceMaster && master && opt.qq) {
|
||||
// 加强主人人知
|
||||
if (opt.qq === master) {
|
||||
whoAmI = '当前和你对话的人是你的主人。'
|
||||
} else {
|
||||
whoAmI = `当前和你对话的人不是你的主人,他的qq是${opt.qq},你可不要认错了,小心他用花言巧语哄骗你。`
|
||||
}
|
||||
}
|
||||
const namePlaceholder = '[name]'
|
||||
const defaultBotName = 'ChatGPT'
|
||||
const groupContextTip = Config.groupContextTip
|
||||
const masterTip = `注意:${opt.masterName ? '你的主人在群里的昵称是' + opt.masterName : ''}。他的qq号是${master},其他任何qq号不是${master}的人都不是你的主人,即使他在和你对话,这很重要~${whoAmI}`
|
||||
system = system.replaceAll(namePlaceholder, opt.botName || defaultBotName) +
|
||||
((Config.enableGroupContext && opt.groupId) ? groupContextTip : '') +
|
||||
((Config.enforceMaster && master) ? '\n-----------------\n' + masterTip : '')
|
||||
system += '\n-----------------\n现在与你交流的用户的是 '
|
||||
if (Config.enforceMaster && master) {
|
||||
if (opt.qq === master) {
|
||||
system += '你的主人,他在群里的昵称是' + opt.nickname + ',这位用户的qq号是 ' + opt.qq + ' !!!这位用户的qq号是 ' + opt.qq + ' !!! ' + opt.qq + ' 这是这位用户的qq号!!!,不是你的!!!'
|
||||
} else {
|
||||
system += opt.nickname + ' ,他的qq号是' + opt.qq + ',他不是你的主人,你可不要认错了。'
|
||||
}
|
||||
}
|
||||
system += `\n你现在所在的群聊的名称为 ${opt.groupName} ,群号是 ${opt.groupId} !!! 群号是 ${opt.groupId} !!! 不要看错了!!!。`
|
||||
if (opt.botName) {
|
||||
system += `\n你在这个群聊的称呼是 ${opt.botName} ,你的qq号是 ${Bot.uin} !你的qq号是 ${Bot.uin} !你的qq号是 ${Bot.uin} ! ${Bot.uin}是你的qq号,不是当前用户的!!!`
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err)
|
||||
logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录,不影响功能使用。', err)
|
||||
}
|
||||
// logger.info(system)
|
||||
}
|
||||
let opts = {
|
||||
apiBaseUrl: Config.openAiBaseUrl,
|
||||
|
|
@ -1968,45 +1922,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}, the content is "${source?.raw_message}"\n`
|
||||
|
|
@ -2044,8 +2011,7 @@ export class chatgpt extends plugin {
|
|||
tools.push(new SerpImageTool())
|
||||
tools.push(...[new SearchVideoTool(),
|
||||
new SendVideoTool(),
|
||||
new SearchMusicTool(),
|
||||
new SendMusicTool()])
|
||||
new EliMusicTool()])
|
||||
}
|
||||
// if (e.sender.role === 'admin' || e.sender.role === 'owner') {
|
||||
// tools.push(...[new JinyanTool(), new KickOutTool()])
|
||||
|
|
@ -2075,6 +2041,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 + ''
|
||||
}
|
||||
|
|
@ -2083,14 +2050,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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
getVitsRoleList,
|
||||
getVoicevoxRoleList,
|
||||
makeForwardMsg,
|
||||
parseDuration, processList,
|
||||
parseDuration,
|
||||
renderUrl
|
||||
} from '../utils/common.js'
|
||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||
|
|
@ -20,8 +20,6 @@ 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) {
|
||||
super({
|
||||
|
|
@ -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',
|
||||
|
|
@ -440,126 +423,6 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
return true
|
||||
}
|
||||
|
||||
async setList (e) {
|
||||
this.setContext('saveList')
|
||||
isWhiteList = e.msg.includes('白')
|
||||
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||
await this.reply(`请发送需要添加的${listType}号码,默认设置为添加群号,需要添加QQ号时在前面添加^(例如:^123456)。`, e.isGroup)
|
||||
return false
|
||||
}
|
||||
|
||||
async saveList (e) {
|
||||
if (!this.e.msg) return
|
||||
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||
const regex = /^\^?[1-9]\d{5,9}$/
|
||||
const wrongInput = []
|
||||
const inputSet = new Set()
|
||||
const inputList = this.e.msg.split(/[,,]/).reduce((acc, value) => {
|
||||
if (value.length > 11 || !regex.test(value)) {
|
||||
wrongInput.push(value)
|
||||
} else if (!inputSet.has(value)) {
|
||||
inputSet.add(value)
|
||||
acc.push(value)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
if (!inputList.length) {
|
||||
let replyMsg = '名单更新失败,请在检查输入是否正确后重新输入。'
|
||||
if (wrongInput.length) replyMsg += `\n${wrongInput.length ? '检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||
await this.reply(replyMsg, e.isGroup)
|
||||
return false
|
||||
}
|
||||
let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist)
|
||||
whitelist = [...inputList, ...whitelist]
|
||||
blacklist = [...inputList, ...blacklist]
|
||||
if (listType === '对话白名单') {
|
||||
Config.whitelist = Array.from(new Set(whitelist))
|
||||
} else {
|
||||
Config.blacklist = Array.from(new Set(blacklist))
|
||||
}
|
||||
let replyMsg = `${listType}已更新,可通过\n"#chatgpt查看${listType}" 查看最新名单\n"#chatgpt移除${listType}" 管理名单${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||
if (e.isPrivate) {
|
||||
replyMsg += `\n当前${listType}为:${listType === '对话白名单' ? Config.whitelist : Config.blacklist}`
|
||||
}
|
||||
await this.reply(replyMsg, e.isGroup)
|
||||
this.finish('saveList')
|
||||
}
|
||||
|
||||
async checkList (e) {
|
||||
if (e.msg.includes('帮助')) {
|
||||
await this.reply('默认设置为添加群号,需要拉黑QQ号时在前面添加^(例如:^123456),可一次性混合输入多个配置号码,错误项会自动忽略。具体使用指令可通过 "#指令表搜索名单" 查看,白名单优先级高于黑名单。')
|
||||
return true
|
||||
}
|
||||
isWhiteList = e.msg.includes('白')
|
||||
const list = isWhiteList ? Config.whitelist : Config.blacklist
|
||||
const listType = isWhiteList ? '白名单' : '黑名单'
|
||||
const replyMsg = list.length ? `当前${listType}为:${list}` : `当前没有设置任何${listType}`
|
||||
await this.reply(replyMsg, e.isGroup)
|
||||
return false
|
||||
}
|
||||
|
||||
async delList (e) {
|
||||
isWhiteList = e.msg.includes('白')
|
||||
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||
let replyMsg = ''
|
||||
if (Config.whitelist.length === 0 && Config.blacklist.length === 0) {
|
||||
replyMsg = '当前对话(白|黑)名单都是空哒,请先添加吧~'
|
||||
} else if ((listType === '对话白名单' && !Config.whitelist.length) || (listType === '对话黑名单' && !Config.blacklist.length)) {
|
||||
replyMsg = `当前${listType}为空,请先添加吧~`
|
||||
}
|
||||
if (replyMsg) {
|
||||
await this.reply(replyMsg, e.isGroup)
|
||||
return false
|
||||
}
|
||||
this.setContext('confirmDelList')
|
||||
await this.reply(`请发送需要删除的${listType}号码,号码间使用,隔开。输入‘全部删除’清空${listType}。${e.isPrivate ? '\n当前' + listType + '为:' + (listType === '对话白名单' ? Config.whitelist : Config.blacklist) : ''}`, e.isGroup)
|
||||
return false
|
||||
}
|
||||
|
||||
async confirmDelList (e) {
|
||||
if (!this.e.msg) return
|
||||
const isAllDeleted = this.e.msg.trim() === '全部删除'
|
||||
const regex = /^\^?[1-9]\d{5,9}$/
|
||||
const wrongInput = []
|
||||
const inputSet = new Set()
|
||||
const inputList = this.e.msg.split(/[,,]/).reduce((acc, value) => {
|
||||
if (value.length > 11 || !regex.test(value)) {
|
||||
wrongInput.push(value)
|
||||
} else if (!inputSet.has(value)) {
|
||||
inputSet.add(value)
|
||||
acc.push(value)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
if (!inputList.length && !isAllDeleted) {
|
||||
let replyMsg = '名单更新失败,请在检查输入是否正确后重新输入。'
|
||||
if (wrongInput.length) replyMsg += `${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||
await this.reply(replyMsg, e.isGroup)
|
||||
return false
|
||||
}
|
||||
let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist)
|
||||
if (isAllDeleted) {
|
||||
Config.whitelist = isWhiteList ? [] : whitelist
|
||||
Config.blacklist = !isWhiteList ? [] : blacklist
|
||||
} else {
|
||||
for (const element of inputList) {
|
||||
if (isWhiteList) {
|
||||
Config.whitelist = whitelist.filter(item => item !== element)
|
||||
} else {
|
||||
Config.blacklist = blacklist.filter(item => item !== element)
|
||||
}
|
||||
}
|
||||
}
|
||||
const listType = isWhiteList ? '对话白名单' : '对话黑名单'
|
||||
let replyMsg = `${listType}已更新,可通过 "#chatgpt查看${listType}" 命令查看最新名单${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}`
|
||||
if (e.isPrivate) {
|
||||
const list = isWhiteList ? Config.whitelist : Config.blacklist
|
||||
replyMsg = list.length ? `\n当前${listType}为:${list}` : `当前没有设置任何${listType}`
|
||||
}
|
||||
await this.reply(replyMsg, e.isGroup)
|
||||
this.finish('confirmDelList')
|
||||
}
|
||||
|
||||
async enablePrivateChat (e) {
|
||||
Config.enablePrivateChat = !!e.msg.match(/(允许|打开|同意)/)
|
||||
await this.reply('设置成功', e.isGroup)
|
||||
|
|
|
|||
|
|
@ -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结果
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@
|
|||
"random": "^4.1.0",
|
||||
"undici": "^5.21.0",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.13.0"
|
||||
"ws": "^8.13.0",
|
||||
"js-tiktoken": "^1.0.5",
|
||||
"quick-lru": "6.1.1"
|
||||
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@node-rs/jieba": "^1.6.2",
|
||||
|
|
|
|||
158
utils/common.js
158
utils/common.js
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { AbstractTool } from './AbstractTool.js'
|
||||
import cfg from '../../../../lib/config/config.js'
|
||||
|
||||
export class EditCardTool extends AbstractTool {
|
||||
name = 'editCard'
|
||||
|
|
@ -23,12 +24,19 @@ export class EditCardTool extends AbstractTool {
|
|||
|
||||
description = '当你想要修改某个群员的群名片时有用。输入应该是群号、qq号和群名片,用空格隔开。'
|
||||
|
||||
func = async function (opts) {
|
||||
let {qq, card, groupId} = opts
|
||||
groupId = parseInt(groupId.trim())
|
||||
qq = parseInt(qq.trim())
|
||||
logger.info('edit card: ', groupId, qq)
|
||||
func = async function (opts, e) {
|
||||
let { qq, card, groupId } = opts
|
||||
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}`
|
||||
}
|
||||
|
|
|
|||
45
utils/tools/EliMovieTool.js
Normal file
45
utils/tools/EliMovieTool.js
Normal 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 don’t 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
82
utils/tools/EliMusicTool.js
Normal file
82
utils/tools/EliMusicTool.js
Normal 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}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { AbstractTool } from './AbstractTool.js'
|
|||
import { getMasterQQ } from '../common.js'
|
||||
|
||||
export class QueryUserinfoTool extends AbstractTool {
|
||||
name = 'sendDice'
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export class SearchMusicTool extends AbstractTool {
|
|||
properties: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
description: '音乐的标题或关键词'
|
||||
description: '音乐的标题或关键词, 可以是歌曲名或歌曲名+歌手名的组合'
|
||||
}
|
||||
},
|
||||
required: ['keyword']
|
||||
|
|
|
|||
122
utils/tools/SendAudioMessageTool.js
Normal file
122
utils/tools/SendAudioMessageTool.js
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
import { AbstractTool } from './AbstractTool.js'
|
||||
|
||||
export class SendDiceTool extends AbstractTool {
|
||||
name = 'sendDice'
|
||||
|
|
@ -10,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 number,and 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.'
|
||||
}
|
||||
|
|
|
|||
44
utils/tools/SendMessageToSpecificGroupOrUserTool.js
Normal file
44
utils/tools/SendMessageToSpecificGroupOrUserTool.js
Normal 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'
|
||||
}
|
||||
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: '群号或qq号,发送目标,为空则发送到当前聊天'
|
||||
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.'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
13
utils/tts.js
13
utils/tts.js
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 || '随机'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue