mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
fix: merge tons of conflict
This commit is contained in:
commit
7a8b30db14
50 changed files with 1791 additions and 828 deletions
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,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}`
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = '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) {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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 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: '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.'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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