mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 21:37:11 +00:00
* 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. --------- Co-authored-by: Sean <1519059137@qq.com> Co-authored-by: ikechan8370 <geyinchibuaa@gmail.com>
309 lines
12 KiB
JavaScript
309 lines
12 KiB
JavaScript
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 fs from 'fs'
|
||
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
||
import fetch from 'node-fetch'
|
||
import { mkdirs } from '../utils/common.js'
|
||
import uploadRecord from '../utils/uploadRecord.js'
|
||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||
import Translate, { transMap } from '../utils/baiduTranslate.js'
|
||
import _ from 'lodash'
|
||
let useSilk = false
|
||
try {
|
||
await import('node-silk')
|
||
useSilk = true
|
||
} catch (e) {
|
||
useSilk = false
|
||
}
|
||
export class Entertainment extends plugin {
|
||
constructor (e) {
|
||
super({
|
||
name: 'ChatGPT-Plugin 娱乐小功能',
|
||
dsc: '让你的聊天更有趣!现已支持主动打招呼、表情合成、群聊词云统计与文本翻译小功能!',
|
||
event: 'message',
|
||
priority: 500,
|
||
rule: [
|
||
{
|
||
reg: '^#chatgpt打招呼(帮助)?',
|
||
fnc: 'sendMessage',
|
||
permission: 'master'
|
||
},
|
||
{
|
||
reg: '^#chatgpt(查看|设置|删除)打招呼',
|
||
fnc: 'handleSentMessage',
|
||
permission: 'master'
|
||
},
|
||
{
|
||
reg: `^(${emojiRegex()}){2}$`,
|
||
fnc: 'combineEmoj'
|
||
},
|
||
{
|
||
reg: '^#?(今日词云|群友在聊什么)$',
|
||
fnc: 'wordcloud'
|
||
},
|
||
{
|
||
reg: '^#((?:寄批踢)?翻.*|chatgpt翻译帮助)',
|
||
fnc: 'translate'
|
||
}
|
||
]
|
||
})
|
||
this.task = [
|
||
{
|
||
// 设置十分钟左右的浮动
|
||
cron: '0 ' + Math.ceil(Math.random() * 10) + ' 7-23/' + Config.helloInterval + ' * * ?',
|
||
// cron: '0 ' + '*/' + Config.helloInterval + ' * * * ?',
|
||
name: 'ChatGPT主动随机说话',
|
||
fnc: this.sendRandomMessage.bind(this)
|
||
}
|
||
]
|
||
}
|
||
|
||
async translate (e) {
|
||
if (e.msg.trim() === '#chatgpt翻译帮助') {
|
||
await this.reply('支持中、日、文(文言文)、英、俄、韩语言之间的文本翻译功能,"寄批踢"为可选前缀' +
|
||
'\n示例:1. #寄批踢翻英 你好' +
|
||
'\t2. #翻中 你好' +
|
||
'\t3. #寄批踢翻文 hello')
|
||
return
|
||
}
|
||
if (_.isEmpty(Config.baiduTranslateAppId) || _.isEmpty(Config.baiduTranslateSecret)) {
|
||
this.reply('请检查翻译配置是否正确。')
|
||
return
|
||
}
|
||
const regExp = /(#(?:寄批踢)?翻(.))(.*)/
|
||
const msg = e.msg.trim()
|
||
const match = msg.match(regExp)
|
||
let result = ''
|
||
if (!(match[2] in transMap)) {
|
||
e.reply('输入格式有误或暂不支持该语言,' +
|
||
'\n当前支持:中、日、文(文言文)、英、俄、韩。', e.isGroup
|
||
)
|
||
return
|
||
}
|
||
const PendingText = match[3]
|
||
try {
|
||
const translate = new Translate({
|
||
appid: Config.baiduTranslateAppId,
|
||
secret: Config.baiduTranslateSecret
|
||
})
|
||
result = await translate(PendingText, match[2])
|
||
} catch (err) {
|
||
logger.error(err)
|
||
result = err.message
|
||
}
|
||
await this.reply(result, e.isGroup)
|
||
}
|
||
|
||
async wordcloud (e) {
|
||
if (e.isGroup) {
|
||
let groupId = e.group_id
|
||
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
||
if (lock) {
|
||
await e.reply('别着急,上次统计还没完呢')
|
||
return true
|
||
}
|
||
await e.reply('在统计啦,请稍等...')
|
||
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
||
try {
|
||
await makeWordcloud(e, e.group_id)
|
||
} catch (err) {
|
||
logger.error(err)
|
||
await e.reply(err)
|
||
}
|
||
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
|
||
} else {
|
||
await e.reply('请在群里发送此命令')
|
||
}
|
||
}
|
||
|
||
async combineEmoj (e) {
|
||
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
|
||
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
|
||
if (left === right) {
|
||
return false
|
||
}
|
||
mkdirs('data/chatgpt/emoji')
|
||
logger.info('combine ' + e.msg)
|
||
let resultFileLoc = `data/chatgpt/emoji/${left}_${right}.jpg`
|
||
if (fs.existsSync(resultFileLoc)) {
|
||
let image = segment.image(fs.createReadStream(resultFileLoc))
|
||
image.asface = true
|
||
await e.reply(image, true)
|
||
return true
|
||
}
|
||
const _path = process.cwd()
|
||
const fullPath = fs.realpathSync(`${_path}/plugins/chatgpt-plugin/resources/emojiData.json`)
|
||
const data = fs.readFileSync(fullPath)
|
||
let emojDataJson = JSON.parse(data)
|
||
logger.mark(`合成emoji:${left} ${right}`)
|
||
let url
|
||
if (emojDataJson[right]) {
|
||
let find = emojDataJson[right].find(item => item.leftEmoji === left)
|
||
if (find) {
|
||
url = googleRequestUrl(find)
|
||
}
|
||
}
|
||
if (!url && emojDataJson[left]) {
|
||
let find = emojDataJson[left].find(item => item.leftEmoji === right)
|
||
if (find) {
|
||
url = googleRequestUrl(find)
|
||
}
|
||
}
|
||
if (!url) {
|
||
await e.reply('不支持合成', true)
|
||
return false
|
||
}
|
||
let response = await fetch(url)
|
||
const resultBlob = await response.blob()
|
||
const resultArrayBuffer = await resultBlob.arrayBuffer()
|
||
const resultBuffer = Buffer.from(resultArrayBuffer)
|
||
await fs.writeFileSync(resultFileLoc, resultBuffer)
|
||
let image = segment.image(fs.createReadStream(resultFileLoc))
|
||
image.asface = true
|
||
await e.reply(image, true)
|
||
return true
|
||
}
|
||
|
||
async sendMessage (e) {
|
||
if (e.msg.match(/^#chatgpt打招呼帮助/) !== null) {
|
||
await this.reply('设置主动打招呼的群聊名单,群号之间以,隔开,参数之间空格隔开\n' +
|
||
'#chatgpt打招呼+群号:立即在指定群聊发起打招呼' +
|
||
'#chatgpt查看打招呼\n' +
|
||
'#chatgpt删除打招呼:删除主动打招呼群聊,可指定若干个群号\n' +
|
||
'#chatgpt设置打招呼:可指定1-3个参数,依次是更新打招呼列表、打招呼间隔时间和触发概率、更新打招呼所有配置项')
|
||
return false
|
||
}
|
||
let groupId = e.msg.replace(/^#chatgpt打招呼/, '')
|
||
logger.info(groupId)
|
||
groupId = parseInt(groupId)
|
||
if (groupId && !Bot.getGroupList().get(groupId)) {
|
||
await e.reply('机器人不在这个群里!')
|
||
return
|
||
}
|
||
let message = await generateHello()
|
||
let sendable = message
|
||
logger.info(`打招呼给群聊${groupId}:` + message)
|
||
if (Config.defaultUseTTS) {
|
||
let audio = await generateAudio(message, Config.defaultTTSRole)
|
||
sendable = segment.record(audio)
|
||
}
|
||
if (!groupId) {
|
||
await e.reply(sendable)
|
||
} else {
|
||
await Bot.sendGroupMsg(groupId, sendable)
|
||
await e.reply('发送成功!')
|
||
}
|
||
}
|
||
|
||
async sendRandomMessage () {
|
||
if (Config.debug) {
|
||
logger.info('开始处理:ChatGPT随机打招呼。')
|
||
}
|
||
let toSend = Config.initiativeChatGroups || []
|
||
for (let i = 0; i < toSend.length; i++) {
|
||
if (!toSend[i]) {
|
||
continue
|
||
}
|
||
let groupId = parseInt(toSend[i])
|
||
if (Bot.getGroupList().get(groupId)) {
|
||
// 打招呼概率
|
||
if (Math.floor(Math.random() * 100) < Config.helloProbability) {
|
||
let message = await generateHello()
|
||
logger.info(`打招呼给群聊${groupId}:` + message)
|
||
if (Config.defaultUseTTS) {
|
||
let audio = await generateAudio(message, Config.defaultTTSRole)
|
||
if (useSilk) {
|
||
await Bot.sendGroupMsg(groupId, await uploadRecord(audio))
|
||
} else {
|
||
await Bot.sendGroupMsg(groupId, segment.record(audio))
|
||
}
|
||
} else {
|
||
await Bot.sendGroupMsg(groupId, message)
|
||
}
|
||
} else {
|
||
logger.info(`时机未到,这次就不打招呼给群聊${groupId}了`)
|
||
}
|
||
} else {
|
||
logger.warn('机器人不在要发送的群组里,忽略群。同时建议检查配置文件修改要打招呼的群号。' + groupId)
|
||
}
|
||
}
|
||
}
|
||
|
||
async handleSentMessage (e) {
|
||
const addReg = /^#chatgpt设置打招呼[::]?\s?(\S+)(?:\s+(\d+))?(?:\s+(\d+))?$/
|
||
const delReg = /^#chatgpt删除打招呼[::\s]?(\S+)/
|
||
const checkReg = /^#chatgpt查看打招呼$/
|
||
let replyMsg = ''
|
||
Config.initiativeChatGroups = Config.initiativeChatGroups.filter(group => group.trim() !== '')
|
||
if (e.msg.match(checkReg)) {
|
||
if (Config.initiativeChatGroups.length === 0) {
|
||
replyMsg = '当前没有需要打招呼的群聊'
|
||
} else {
|
||
replyMsg = `当前打招呼设置为:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
|
||
}
|
||
} else if (e.msg.match(delReg)) {
|
||
const groupsToDelete = e.msg.trim().match(delReg)[1].split(/[,,]\s?/).filter(group => group.trim() !== '')
|
||
let deletedGroups = []
|
||
|
||
for (const element of groupsToDelete) {
|
||
if (!/^[1-9]\d{8,9}$/.test(element)) {
|
||
await this.reply(`群号${element}不合法,请输入9-10位不以0开头的数字`, true)
|
||
return false
|
||
}
|
||
if (!Config.initiativeChatGroups.includes(element)) {
|
||
continue
|
||
}
|
||
Config.initiativeChatGroups.splice(Config.initiativeChatGroups.indexOf(element), 1)
|
||
deletedGroups.push(element)
|
||
}
|
||
Config.initiativeChatGroups = Config.initiativeChatGroups.filter(group => group.trim() !== '')
|
||
if (deletedGroups.length === 0) {
|
||
replyMsg = '没有可删除的群号,请输入正确的群号\n'
|
||
} else {
|
||
replyMsg = `已删除打招呼群号:${deletedGroups.join(', ')}\n`
|
||
}
|
||
replyMsg += `当前打招呼设置为:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
|
||
} else if (e.msg.match(addReg)) {
|
||
let paramArray = e.msg.match(addReg)
|
||
if (typeof paramArray[3] === 'undefined' && typeof paramArray[2] !== 'undefined') {
|
||
Config.helloInterval = Math.min(Math.max(parseInt(paramArray[1]), 1), 24)
|
||
Config.helloProbability = Math.min(Math.max(parseInt(paramArray[2]), 0), 100)
|
||
replyMsg = `已更新打招呼设置:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
|
||
} else {
|
||
const validGroups = []
|
||
const groups = paramArray ? paramArray[1].split(/[,,]\s?/) : []
|
||
for (const element of groups) {
|
||
if (!/^[1-9]\d{8,9}$/.test(element)) {
|
||
await this.reply(`群号${element}不合法,请输入9-10位不以0开头的数字`, true)
|
||
return false
|
||
}
|
||
if (Config.initiativeChatGroups.includes(element)) {
|
||
continue
|
||
}
|
||
validGroups.push(element)
|
||
}
|
||
if (validGroups.length === 0) {
|
||
await this.reply('没有可添加的群号,请输入新的群号')
|
||
return false
|
||
} else {
|
||
Config.initiativeChatGroups = Config.initiativeChatGroups
|
||
.filter(group => group.trim() !== '')
|
||
.concat(validGroups)
|
||
}
|
||
if (typeof paramArray[2] === 'undefined' && typeof paramArray[3] === 'undefined') {
|
||
replyMsg = `已更新打招呼设置:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
|
||
} else {
|
||
Config.helloInterval = Math.min(Math.max(parseInt(paramArray[2]), 1), 24)
|
||
Config.helloProbability = Math.min(Math.max(parseInt(paramArray[3]), 0), 100)
|
||
replyMsg = `已更新打招呼设置:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
|
||
}
|
||
}
|
||
} else {
|
||
replyMsg = '无效的打招呼设置,请输入正确的命令。\n可发送”#chatgpt打招呼帮助“获取打招呼指北。'
|
||
}
|
||
await this.reply(replyMsg)
|
||
return false
|
||
}
|
||
}
|