mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 13:57:10 +00:00
Merge branch 'v2' into v2
This commit is contained in:
commit
f1ca8e2d6c
12 changed files with 404 additions and 263 deletions
|
|
@ -230,7 +230,10 @@ pnpm i
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* 预设分群组和个人(已初步实现个人,在用户面板设置)
|
* V3重构
|
||||||
|
* 插件in插件
|
||||||
|
* langchain分支完善
|
||||||
|
* 游戏机制
|
||||||
|
|
||||||
## 其他
|
## 其他
|
||||||
|
|
||||||
|
|
|
||||||
96
apps/chat.js
96
apps/chat.js
|
|
@ -9,7 +9,9 @@ import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||||
import { PoeClient } from '../utils/poe/index.js'
|
import { PoeClient } from '../utils/poe/index.js'
|
||||||
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||||
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||||
|
import { translate } from '../utils/translate.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import { getImg, getImageOcrText } from './entertainment.js'
|
||||||
import {
|
import {
|
||||||
render, renderUrl,
|
render, renderUrl,
|
||||||
getMessageById,
|
getMessageById,
|
||||||
|
|
@ -33,9 +35,13 @@ import uploadRecord from '../utils/uploadRecord.js'
|
||||||
import { SlackClaudeClient } from '../utils/slack/slackClient.js'
|
import { SlackClaudeClient } from '../utils/slack/slackClient.js'
|
||||||
import { ChatgptManagement } from './management.js'
|
import { ChatgptManagement } from './management.js'
|
||||||
import { getPromptByName } from '../utils/prompts.js'
|
import { getPromptByName } from '../utils/prompts.js'
|
||||||
import Translate from '../utils/baiduTranslate.js'
|
import BingDrawClient from '../utils/BingDraw.js'
|
||||||
import emojiStrip from 'emoji-strip'
|
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||||
import XinghuoClient from "../utils/xinghuo/xinghuo.js";
|
try {
|
||||||
|
await import('emoji-strip')
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('【ChatGPT-Plugin】依赖emoji-strip未安装,会导致azure语音模式下朗读emoji的问题,建议执行pnpm install emoji-strip安装')
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await import('keyv')
|
await import('keyv')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -800,40 +806,15 @@ export class chatgpt extends plugin {
|
||||||
speaker = convertSpeaker(trySplit[0])
|
speaker = convertSpeaker(trySplit[0])
|
||||||
prompt = trySplit[1]
|
prompt = trySplit[1]
|
||||||
}
|
}
|
||||||
if (Config.imgOcr) {
|
const isImg = await getImg(e)
|
||||||
// 取消息中的图片、at的头像、回复的图片,放入e.img
|
if (Config.imgOcr && !!isImg) {
|
||||||
if (e.at && !e.source) {
|
let imgOcrText = await getImageOcrText(e)
|
||||||
e.img = [`https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.at}`]
|
if (imgOcrText) {
|
||||||
}
|
prompt = prompt + '"'
|
||||||
if (e.source) {
|
for (let imgOcrTextKey in imgOcrText) {
|
||||||
let reply
|
prompt += imgOcrText[imgOcrTextKey]
|
||||||
if (e.isGroup) {
|
|
||||||
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
|
||||||
} else {
|
|
||||||
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
|
||||||
}
|
}
|
||||||
if (reply) {
|
prompt = prompt + ' "'
|
||||||
for (let val of reply) {
|
|
||||||
if (val.type === 'image') {
|
|
||||||
e.img = [val.url]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e.img) {
|
|
||||||
try {
|
|
||||||
let imgOcrText = ''
|
|
||||||
for (let i in e.img) {
|
|
||||||
const imgorc = await Bot.imageOcr(e.img[i])
|
|
||||||
// if (imgorc.language === 'zh' || imgorc.language === 'en') {
|
|
||||||
for (let text of imgorc.wordslist) {
|
|
||||||
imgOcrText += `${text.words} \n`
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
prompt = imgOcrText + prompt
|
|
||||||
} catch (err) { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 检索是否有屏蔽词
|
// 检索是否有屏蔽词
|
||||||
|
|
@ -1015,6 +996,8 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let response = chatMessage?.text
|
let response = chatMessage?.text
|
||||||
|
// 过滤无法正常显示的emoji
|
||||||
|
if (use === 'claude') response = response.replace(/:[a-zA-Z_]+:/g, '')
|
||||||
let mood = 'blandness'
|
let mood = 'blandness'
|
||||||
if (!response) {
|
if (!response) {
|
||||||
await e.reply('没有任何回复', true)
|
await e.reply('没有任何回复', true)
|
||||||
|
|
@ -1139,7 +1122,14 @@ export class chatgpt extends plugin {
|
||||||
ttsRegex = ''
|
ttsRegex = ''
|
||||||
}
|
}
|
||||||
ttsResponse = response.replace(ttsRegex, '')
|
ttsResponse = response.replace(ttsRegex, '')
|
||||||
ttsResponse = emojiStrip(ttsResponse)
|
// 处理azure语音会读出emoji的问题
|
||||||
|
try {
|
||||||
|
let emojiStrip
|
||||||
|
emojiStrip = (await import('emoji-strip')).default
|
||||||
|
ttsResponse = emojiStrip(ttsResponse)
|
||||||
|
} catch (error) {
|
||||||
|
await this.reply('依赖emoji-strip未安装,请执行pnpm install emoji-strip安装依赖', true)
|
||||||
|
}
|
||||||
// 处理多行回复有时候只会读第一行和azure语音会读出一些标点符号的问题
|
// 处理多行回复有时候只会读第一行和azure语音会读出一些标点符号的问题
|
||||||
ttsResponse = ttsResponse.replace(/[-:_;*;\n]/g, ',')
|
ttsResponse = ttsResponse.replace(/[-:_;*;\n]/g, ',')
|
||||||
// 先把文字回复发出去,避免过久等待合成语音
|
// 先把文字回复发出去,避免过久等待合成语音
|
||||||
|
|
@ -1158,16 +1148,8 @@ export class chatgpt extends plugin {
|
||||||
if(ttsResponse.length <= Config.ttsAutoFallbackThreshold) {
|
if(ttsResponse.length <= Config.ttsAutoFallbackThreshold) {
|
||||||
let wav
|
let wav
|
||||||
if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) {
|
if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) {
|
||||||
if (Config.autoJapanese && (_.isEmpty(Config.baiduTranslateAppId) || _.isEmpty(Config.baiduTranslateSecret))) {
|
|
||||||
await this.reply('请检查翻译配置是否正确。')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (Config.autoJapanese) {
|
if (Config.autoJapanese) {
|
||||||
try {
|
try {
|
||||||
const translate = new Translate({
|
|
||||||
appid: Config.baiduTranslateAppId,
|
|
||||||
secret: Config.baiduTranslateSecret
|
|
||||||
})
|
|
||||||
ttsResponse = await translate(ttsResponse, '日')
|
ttsResponse = await translate(ttsResponse, '日')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
|
|
@ -1181,6 +1163,11 @@ export class chatgpt extends plugin {
|
||||||
await this.reply('合成语音发生错误~')
|
await this.reply('合成语音发生错误~')
|
||||||
}
|
}
|
||||||
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
||||||
|
const ttsRoleAzure = userReplySetting.ttsRoleAzure
|
||||||
|
const isEn = AzureTTS.supportConfigurations.find(config => config.code === ttsRoleAzure)?.language.includes('en')
|
||||||
|
if (isEn) {
|
||||||
|
ttsResponse = (await translate(ttsResponse, '英')).replace('\n', '')
|
||||||
|
}
|
||||||
let ssml = AzureTTS.generateSsml(ttsResponse, {
|
let ssml = AzureTTS.generateSsml(ttsResponse, {
|
||||||
speaker,
|
speaker,
|
||||||
emotion,
|
emotion,
|
||||||
|
|
@ -1641,6 +1628,25 @@ export class chatgpt extends plugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(response)
|
||||||
|
// 处理内容生成的图片
|
||||||
|
if (response.details.imageTag) {
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.mark(`开始生成内容:${response.details.imageTag}`)
|
||||||
|
}
|
||||||
|
let client = new BingDrawClient({
|
||||||
|
baseUrl: Config.sydneyReverseProxy,
|
||||||
|
userToken: bingToken
|
||||||
|
})
|
||||||
|
await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 })
|
||||||
|
try {
|
||||||
|
await client.getImages(response.details.imageTag, e)
|
||||||
|
} catch (err) {
|
||||||
|
await redis.del(`CHATGPT:DRAW:${e.sender.user_id}`)
|
||||||
|
await e.reply('绘图失败:' + err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果token曾经有异常,则清除异常
|
// 如果token曾经有异常,则清除异常
|
||||||
let Tokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
let Tokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
||||||
const TokenIndex = Tokens.findIndex(element => element.Token === abtrs.bingToken)
|
const TokenIndex = Tokens.findIndex(element => element.Token === abtrs.bingToken)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,10 @@ import { generateAudio } from '../utils/tts.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { mkdirs } from '../utils/common.js'
|
import { makeForwardMsg, mkdirs } from '../utils/common.js'
|
||||||
import uploadRecord from '../utils/uploadRecord.js'
|
import uploadRecord from '../utils/uploadRecord.js'
|
||||||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||||||
import Translate, { transMap } from '../utils/baiduTranslate.js'
|
import { translate, translateLangSupports } from '../utils/translate.js'
|
||||||
import _ from 'lodash'
|
|
||||||
let useSilk = false
|
let useSilk = false
|
||||||
try {
|
try {
|
||||||
await import('node-silk')
|
await import('node-silk')
|
||||||
|
|
@ -18,10 +17,10 @@ try {
|
||||||
useSilk = false
|
useSilk = false
|
||||||
}
|
}
|
||||||
export class Entertainment extends plugin {
|
export class Entertainment extends plugin {
|
||||||
constructor (e) {
|
constructor(e) {
|
||||||
super({
|
super({
|
||||||
name: 'ChatGPT-Plugin 娱乐小功能',
|
name: 'ChatGPT-Plugin 娱乐小功能',
|
||||||
dsc: '让你的聊天更有趣!现已支持主动打招呼、表情合成、群聊词云统计与文本翻译小功能!',
|
dsc: '让你的聊天更有趣!现已支持主动打招呼、表情合成、群聊词云统计、文本翻译与图片ocr小功能!',
|
||||||
event: 'message',
|
event: 'message',
|
||||||
priority: 500,
|
priority: 500,
|
||||||
rule: [
|
rule: [
|
||||||
|
|
@ -44,8 +43,12 @@ export class Entertainment extends plugin {
|
||||||
fnc: 'wordcloud'
|
fnc: 'wordcloud'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#((?:寄批踢)?翻.*|chatgpt翻译帮助)',
|
reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)',
|
||||||
fnc: 'translate'
|
fnc: 'translate'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#ocr',
|
||||||
|
fnc: 'ocr'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
@ -59,44 +62,112 @@ export class Entertainment extends plugin {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
async ocr (e) {
|
||||||
async translate (e) {
|
let replyMsg
|
||||||
if (e.msg.trim() === '#chatgpt翻译帮助') {
|
let imgOcrText = await getImageOcrText(e)
|
||||||
await this.reply('支持中、日、文(文言文)、英、俄、韩语言之间的文本翻译功能,"寄批踢"为可选前缀' +
|
if (!imgOcrText) {
|
||||||
'\n示例:1. #寄批踢翻英 你好' +
|
await this.reply('没有识别到文字', e.isGroup)
|
||||||
'\t2. #翻中 你好' +
|
return false
|
||||||
'\t3. #寄批踢翻文 hello')
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if (_.isEmpty(Config.baiduTranslateAppId) || _.isEmpty(Config.baiduTranslateSecret)) {
|
replyMsg = await makeForwardMsg(e, imgOcrText, 'OCR结果')
|
||||||
this.reply('请检查翻译配置是否正确。')
|
await this.reply(replyMsg, e.isGroup)
|
||||||
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 translate(e) {
|
||||||
async wordcloud (e) {
|
const translateLangLabels = translateLangSupports.map(item => item.label).join(',')
|
||||||
|
const translateLangLabelAbbrS = translateLangSupports.map(item => item.abbr).join(',')
|
||||||
|
if (e.msg.trim() === '#chatgpt翻译帮助') {
|
||||||
|
await this.reply(`支持以下语种的翻译:
|
||||||
|
${translateLangLabels}
|
||||||
|
在使用本工具时,请采用简写的方式描述目标语言。此外,可以引用消息或图片来进行翻译。
|
||||||
|
示例:
|
||||||
|
1. #gpt翻英 你好
|
||||||
|
2. #gpt翻中 你好
|
||||||
|
3. #gpt翻译 hello`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const regExp = /^#(寄批踢|gpt|GPT)?翻(.)([\s\S]*)/
|
||||||
|
const match = e.msg.trim().match(regExp)
|
||||||
|
let languageCode = match[2] === '译' ? 'auto' : match[2]
|
||||||
|
let pendingText = match[3]
|
||||||
|
const isImg = !!(await getImg(e))?.length
|
||||||
|
let result = []
|
||||||
|
let multiText = false
|
||||||
|
if (languageCode !== 'auto' && !translateLangLabelAbbrS.includes(languageCode)) {
|
||||||
|
e.reply(`输入格式有误或暂不支持该语言,\n当前支持${translateLangLabels}`, e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 引用回复
|
||||||
|
if (e.source) {
|
||||||
|
if (pendingText.length) {
|
||||||
|
await this.reply('引用模式下不需要添加翻译文本,已自动忽略输入文本...((*・∀・)ゞ→→”', e.isGroup)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isImg && pendingText) {
|
||||||
|
await this.reply('检测到图片输入,已自动忽略输入文本...((*・∀・)ゞ→→', e.isGroup)
|
||||||
|
}
|
||||||
|
if (!pendingText && !isImg) {
|
||||||
|
await this.reply('你让我翻译啥呢 ̄へ ̄!', e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isImg) {
|
||||||
|
let imgOcrText = await getImageOcrText(e)
|
||||||
|
multiText = Array.isArray(imgOcrText)
|
||||||
|
if (imgOcrText) {
|
||||||
|
pendingText = imgOcrText
|
||||||
|
} else {
|
||||||
|
await this.reply('没有识别到有效文字(・-・*)', e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.source) {
|
||||||
|
let previousMsg
|
||||||
|
if (e.isGroup) {
|
||||||
|
previousMsg = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
||||||
|
} else {
|
||||||
|
previousMsg = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
||||||
|
}
|
||||||
|
// logger.warn('previousMsg', previousMsg)
|
||||||
|
if (previousMsg.find(msg => msg.type === 'text')?.text) {
|
||||||
|
pendingText = previousMsg.find(msg => msg.type === 'text')?.text
|
||||||
|
} else {
|
||||||
|
await this.reply('这是什么怪东西!(⊙ˍ⊙)', e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (multiText) {
|
||||||
|
result = await Promise.all(pendingText.map(text => translate(text, languageCode)))
|
||||||
|
} else {
|
||||||
|
result = await translate(pendingText, languageCode)
|
||||||
|
}
|
||||||
|
// logger.warn(multiText, result)
|
||||||
|
} catch (err) {
|
||||||
|
await this.reply(err.message, e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const totalLength = Array.isArray(result)
|
||||||
|
? result.reduce((acc, cur) => acc + cur.length, 0)
|
||||||
|
: result.length
|
||||||
|
if (totalLength > 300 || multiText) {
|
||||||
|
// 多条翻译结果
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
result = await makeForwardMsg(e, result, '翻译结果')
|
||||||
|
} else {
|
||||||
|
result = ('译文:\n' + result.trim()).split()
|
||||||
|
result.unshift('原文:\n' + pendingText.trim())
|
||||||
|
result = await makeForwardMsg(e, result, '翻译结果')
|
||||||
|
}
|
||||||
|
await this.reply(result, e.isGroup)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 保持原格式输出
|
||||||
|
result = Array.isArray(result) ? result.join('\n') : result
|
||||||
|
await this.reply(result, e.isGroup)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
async wordcloud(e) {
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let groupId = e.group_id
|
let groupId = e.group_id
|
||||||
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
|
|
@ -105,7 +176,7 @@ export class Entertainment extends plugin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
await e.reply('在统计啦,请稍等...')
|
await e.reply('在统计啦,请稍等...')
|
||||||
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', {EX: 600})
|
||||||
try {
|
try {
|
||||||
await makeWordcloud(e, e.group_id)
|
await makeWordcloud(e, e.group_id)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -118,7 +189,7 @@ export class Entertainment extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async combineEmoj (e) {
|
async combineEmoj(e) {
|
||||||
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
|
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
|
||||||
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
|
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
|
||||||
if (left === right) {
|
if (left === right) {
|
||||||
|
|
@ -166,7 +237,7 @@ export class Entertainment extends plugin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage (e) {
|
async sendMessage(e) {
|
||||||
if (e.msg.match(/^#chatgpt打招呼帮助/) !== null) {
|
if (e.msg.match(/^#chatgpt打招呼帮助/) !== null) {
|
||||||
await this.reply('设置主动打招呼的群聊名单,群号之间以,隔开,参数之间空格隔开\n' +
|
await this.reply('设置主动打招呼的群聊名单,群号之间以,隔开,参数之间空格隔开\n' +
|
||||||
'#chatgpt打招呼+群号:立即在指定群聊发起打招呼' +
|
'#chatgpt打招呼+群号:立即在指定群聊发起打招呼' +
|
||||||
|
|
@ -197,7 +268,7 @@ export class Entertainment extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRandomMessage () {
|
async sendRandomMessage() {
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
logger.info('开始处理:ChatGPT随机打招呼。')
|
logger.info('开始处理:ChatGPT随机打招呼。')
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +302,7 @@ export class Entertainment extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSentMessage (e) {
|
async handleSentMessage(e) {
|
||||||
const addReg = /^#chatgpt设置打招呼[::]?\s?(\S+)(?:\s+(\d+))?(?:\s+(\d+))?$/
|
const addReg = /^#chatgpt设置打招呼[::]?\s?(\S+)(?:\s+(\d+))?(?:\s+(\d+))?$/
|
||||||
const delReg = /^#chatgpt删除打招呼[::\s]?(\S+)/
|
const delReg = /^#chatgpt删除打招呼[::\s]?(\S+)/
|
||||||
const checkReg = /^#chatgpt查看打招呼$/
|
const checkReg = /^#chatgpt查看打招呼$/
|
||||||
|
|
@ -307,3 +378,51 @@ export class Entertainment extends plugin {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function getImg (e) {
|
||||||
|
// 取消息中的图片、at的头像、回复的图片,放入e.img
|
||||||
|
if (e.at && !e.source) {
|
||||||
|
e.img = [`https://q1.qlogo.cn/g?b=qq&s=0&nk=${e.at}`]
|
||||||
|
}
|
||||||
|
if (e.source) {
|
||||||
|
let reply
|
||||||
|
if (e.isGroup) {
|
||||||
|
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
||||||
|
} else {
|
||||||
|
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
||||||
|
}
|
||||||
|
if (reply) {
|
||||||
|
let i = []
|
||||||
|
for (let val of reply) {
|
||||||
|
if (val.type === 'image') {
|
||||||
|
i.push(val.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.img = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.img
|
||||||
|
}
|
||||||
|
export async function getImageOcrText (e) {
|
||||||
|
const img = await getImg(e)
|
||||||
|
if (img) {
|
||||||
|
try {
|
||||||
|
let resultArr = []
|
||||||
|
let eachImgRes = ''
|
||||||
|
for (let i in img) {
|
||||||
|
const imgOCR = await Bot.imageOcr(img[i])
|
||||||
|
for (let text of imgOCR.wordslist) {
|
||||||
|
eachImgRes += (`${text?.words} \n`)
|
||||||
|
}
|
||||||
|
if (eachImgRes) resultArr.push(eachImgRes)
|
||||||
|
eachImgRes = ''
|
||||||
|
}
|
||||||
|
// logger.warn('resultArr', resultArr)
|
||||||
|
return resultArr
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
// logger.error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'userPage'
|
fnc: 'userPage'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助)?',
|
reg: '^#(chatgpt)?(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助|搜索(.+))?',
|
||||||
fnc: 'commandHelp'
|
fnc: 'commandHelp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -281,8 +281,21 @@ export class ChatgptManagement extends plugin {
|
||||||
}
|
}
|
||||||
await this.reply(roleList)
|
await this.reply(roleList)
|
||||||
}
|
}
|
||||||
|
|
||||||
async ttsSwitch (e) {
|
async ttsSwitch (e) {
|
||||||
|
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
||||||
|
userReplySetting = !userReplySetting
|
||||||
|
? getDefaultReplySetting()
|
||||||
|
: JSON.parse(userReplySetting)
|
||||||
|
if (!userReplySetting.useTTS) {
|
||||||
|
let replyMsg
|
||||||
|
if (userReplySetting.usePicture) {
|
||||||
|
replyMsg = `当前为${!userReplySetting.useTTS ? '图片模式' : ''},请先切换到语音模式吧~`
|
||||||
|
} else {
|
||||||
|
replyMsg = `当前为${!userReplySetting.useTTS ? '文本模式' : ''},请先切换到语音模式吧~`
|
||||||
|
}
|
||||||
|
await this.reply(replyMsg, e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
let regExp = /#语音切换(.*)/
|
let regExp = /#语音切换(.*)/
|
||||||
let ttsMode = e.msg.match(regExp)[1]
|
let ttsMode = e.msg.match(regExp)[1]
|
||||||
if (['vits', 'azure', 'voicevox'].includes(ttsMode)) {
|
if (['vits', 'azure', 'voicevox'].includes(ttsMode)) {
|
||||||
|
|
@ -300,9 +313,10 @@ export class ChatgptManagement extends plugin {
|
||||||
|
|
||||||
async commandHelp (e) {
|
async commandHelp (e) {
|
||||||
if (!this.e.isMaster) { return this.reply('你没有权限') }
|
if (!this.e.isMaster) { return this.reply('你没有权限') }
|
||||||
if (e.msg.trim() === '#chatgpt指令表帮助') {
|
if (/^#(chatgpt)?指令表帮助$/.exec(e.msg.trim())) {
|
||||||
await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' +
|
await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' +
|
||||||
'#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表')
|
'#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' +
|
||||||
|
'#chatgpt指令表搜索xxx: 查看包含对应关键词的指令')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const categories = {
|
const categories = {
|
||||||
|
|
@ -332,7 +346,33 @@ export class ChatgptManagement extends plugin {
|
||||||
commandSet.push({ name, dsc: plugin.dsc, rule })
|
commandSet.push({ name, dsc: plugin.dsc, rule })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (e.msg.includes('搜索')) {
|
||||||
|
let cmd = e.msg.trim().match(/^#(chatgpt)?(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助|搜索(.+))?/)[4]
|
||||||
|
if (!cmd) {
|
||||||
|
await this.reply('(⊙ˍ⊙)')
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
let searchResults = []
|
||||||
|
commandSet.forEach(plugin => {
|
||||||
|
plugin.rule.forEach(item => {
|
||||||
|
if (item.reg.toLowerCase().includes(cmd.toLowerCase())) {
|
||||||
|
searchResults.push(item.reg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (!searchResults.length) {
|
||||||
|
await this.reply('没有找到符合的结果,换个关键词吧!', e.isGroup)
|
||||||
|
return 0
|
||||||
|
} else if (searchResults.length <= 5) {
|
||||||
|
await this.reply(searchResults.join('\n'), e.isGroup)
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
let msg = await makeForwardMsg(e, searchResults, e.msg.slice(1).startsWith('chatgpt') ? e.msg.slice(8) : 'chatgpt' + e.msg.slice(1))
|
||||||
|
await this.reply(msg)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const generatePrompt = (plugin, command) => {
|
const generatePrompt = (plugin, command) => {
|
||||||
const category = getCategory(e, plugin)
|
const category = getCategory(e, plugin)
|
||||||
const commandsStr = command.length ? `正则指令:\n${command.join('\n')}\n` : '正则指令: 无\n'
|
const commandsStr = command.length ? `正则指令:\n${command.join('\n')}\n` : '正则指令: 无\n'
|
||||||
|
|
@ -348,7 +388,7 @@ export class ChatgptManagement extends plugin {
|
||||||
prompts.push(generatePrompt(plugin, commands))
|
prompts.push(generatePrompt(plugin, commands))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let msg = await makeForwardMsg(e, prompts, e.msg.slice(1))
|
let msg = await makeForwardMsg(e, prompts, e.msg.slice(1).startsWith('chatgpt') ? e.msg.slice(1) : ('chatgpt' + e.msg.slice(1)))
|
||||||
await this.reply(msg)
|
await this.reply(msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -513,7 +553,7 @@ export class ChatgptManagement extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDefaultReplySetting (e) {
|
async setDefaultReplySetting (e) {
|
||||||
const reg = /^#chatgpt(打开|关闭|设置)?全局((图片模式|语音模式|(语音角色|角色语音|角色).*)|回复帮助)/
|
const reg = /^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|(语音角色|角色语音|角色).*)|回复帮助)/
|
||||||
const matchCommand = e.msg.match(reg)
|
const matchCommand = e.msg.match(reg)
|
||||||
const settingType = matchCommand[2]
|
const settingType = matchCommand[2]
|
||||||
let replyMsg = ''
|
let replyMsg = ''
|
||||||
|
|
@ -533,6 +573,23 @@ export class ChatgptManagement extends plugin {
|
||||||
} else if (matchCommand[1] === '设置') {
|
} else if (matchCommand[1] === '设置') {
|
||||||
replyMsg = '请使用“#chatgpt打开全局图片模式”或“#chatgpt关闭全局图片模式”命令来设置回复模式'
|
replyMsg = '请使用“#chatgpt打开全局图片模式”或“#chatgpt关闭全局图片模式”命令来设置回复模式'
|
||||||
} break
|
} break
|
||||||
|
case '文本模式':
|
||||||
|
if (matchCommand[1] === '打开') {
|
||||||
|
Config.defaultUsePicture = false
|
||||||
|
Config.defaultUseTTS = false
|
||||||
|
replyMsg = 'ChatGPT将默认以文本回复'
|
||||||
|
} else if (matchCommand[1] === '关闭') {
|
||||||
|
if (Config.defaultUseTTS) {
|
||||||
|
replyMsg = 'ChatGPT将默认以语音回复'
|
||||||
|
} else if (Config.defaultUsePicture) {
|
||||||
|
replyMsg = 'ChatGPT将默认以图片回复'
|
||||||
|
} else {
|
||||||
|
Config.defaultUseTTS = true
|
||||||
|
replyMsg = 'ChatGPT将默认以语音回复'
|
||||||
|
}
|
||||||
|
} else if (matchCommand[1] === '设置') {
|
||||||
|
replyMsg = '请使用“#chatgpt打开全局文本模式”或“#chatgpt关闭全局文本模式”命令来设置回复模式'
|
||||||
|
} break
|
||||||
case '语音模式':
|
case '语音模式':
|
||||||
if (!Config.ttsSpace) {
|
if (!Config.ttsSpace) {
|
||||||
replyMsg = '您没有配置VITS API,请前往锅巴面板进行配置'
|
replyMsg = '您没有配置VITS API,请前往锅巴面板进行配置'
|
||||||
|
|
@ -553,7 +610,7 @@ export class ChatgptManagement extends plugin {
|
||||||
replyMsg = '请使用“#chatgpt打开全局语音模式”或“#chatgpt关闭全局语音模式”命令来设置回复模式'
|
replyMsg = '请使用“#chatgpt打开全局语音模式”或“#chatgpt关闭全局语音模式”命令来设置回复模式'
|
||||||
} break
|
} break
|
||||||
case '回复帮助':
|
case '回复帮助':
|
||||||
replyMsg = '可使用以下命令配置全局回复:\n#chatgpt(打开/关闭)全局(语音/图片)模式\n#chatgpt设置全局(语音角色|角色语音|角色)+角色名称(留空则为随机)'
|
replyMsg = '可使用以下命令配置全局回复:\n#chatgpt(打开/关闭)全局(语音/图片/文本)模式\n#chatgpt设置全局(语音角色|角色语音|角色)+角色名称(留空则为随机)'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (!Config.ttsSpace) {
|
if (!Config.ttsSpace) {
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,7 @@ export function supportGuoba () {
|
||||||
field: 'autoJapanese',
|
field: 'autoJapanese',
|
||||||
label: 'vits模式日语输出',
|
label: 'vits模式日语输出',
|
||||||
bottomHelpMessage: '使用vits语音时,将机器人的文字回复翻译成日文后获取语音。' +
|
bottomHelpMessage: '使用vits语音时,将机器人的文字回复翻译成日文后获取语音。' +
|
||||||
'需要填写下方的翻译配置,配置文档:http://api.fanyi.baidu.com/doc/21 ' +
|
'若想使用插件的翻译功能,发送"#chatgpt翻译帮助"查看使用方法,支持图片翻译,引用翻译...',
|
||||||
'填写配置后另外支持通过本插件使用文字翻译功能,发送"#chatgpt翻译帮助"查看使用方法。',
|
|
||||||
component: 'Switch'
|
component: 'Switch'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -581,16 +580,6 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住',
|
bottomHelpMessage: '可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住',
|
||||||
component: 'InputPassword'
|
component: 'InputPassword'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'baiduTranslateAppId',
|
|
||||||
label: '百度翻译应用ID',
|
|
||||||
component: 'Input'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'baiduTranslateSecret',
|
|
||||||
label: '百度翻译密钥',
|
|
||||||
component: 'Input'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'ttsSpace',
|
field: 'ttsSpace',
|
||||||
label: 'vits-uma-genshin-honkai语音转换API地址',
|
label: 'vits-uma-genshin-honkai语音转换API地址',
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,9 @@
|
||||||
"@slack/bolt": "^3.13.0",
|
"@slack/bolt": "^3.13.0",
|
||||||
"@waylaidwanderer/chatgpt-api": "^1.33.2",
|
"@waylaidwanderer/chatgpt-api": "^1.33.2",
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
"axios": "^1.3.6",
|
|
||||||
"chatgpt": "^5.1.1",
|
"chatgpt": "^5.1.1",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"emoji-strip": "^1.0.1",
|
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"eventsource-parser": "^1.0.0",
|
"eventsource-parser": "^1.0.0",
|
||||||
"fastify": "^4.13.0",
|
"fastify": "^4.13.0",
|
||||||
|
|
@ -21,8 +19,8 @@
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
"keyv": "^4.5.2",
|
"keyv": "^4.5.2",
|
||||||
"keyv-file": "^0.2.0",
|
"keyv-file": "^0.2.0",
|
||||||
"md5-node": "^1.0.1",
|
|
||||||
"microsoft-cognitiveservices-speech-sdk": "^1.27.0",
|
"microsoft-cognitiveservices-speech-sdk": "^1.27.0",
|
||||||
|
"emoji-strip": "^1.0.1",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
"openai": "^3.2.1",
|
"openai": "^3.2.1",
|
||||||
"random": "^4.1.0",
|
"random": "^4.1.0",
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,9 @@ export default class SydneyAIClient {
|
||||||
'responsible_ai_policy_235',
|
'responsible_ai_policy_235',
|
||||||
'enablemm',
|
'enablemm',
|
||||||
toneOption,
|
toneOption,
|
||||||
|
'clgalileo',
|
||||||
|
'gencontentv3',
|
||||||
|
'rai267',
|
||||||
'dtappid',
|
'dtappid',
|
||||||
'cricinfo',
|
'cricinfo',
|
||||||
'cricinfov2',
|
'cricinfov2',
|
||||||
|
|
@ -625,7 +628,11 @@ export default class SydneyAIClient {
|
||||||
adaptiveCards: adaptiveCardsSoFar,
|
adaptiveCards: adaptiveCardsSoFar,
|
||||||
text: replySoFar.join('')
|
text: replySoFar.join('')
|
||||||
}
|
}
|
||||||
message.text = messages.filter(m => m.author === 'bot').map(m => m.text).join('')
|
// 获取到图片内容
|
||||||
|
if (message.contentType === 'IMAGE') {
|
||||||
|
message.imageTag = messages.filter(m => m.contentType === 'IMAGE').map(m => m.text).join('')
|
||||||
|
}
|
||||||
|
message.text = messages.filter(m => m.author === 'bot' && m.contentType != 'IMAGE').map(m => m.text).join('')
|
||||||
if (!message) {
|
if (!message) {
|
||||||
reject('No message was generated.')
|
reject('No message was generated.')
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
import md5 from 'md5-node'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
// noinspection NonAsciiCharacters
|
|
||||||
export const transMap = { 中: 'zh', 日: 'jp', 文: 'wyw', 英: 'en', 俄: 'ru', 韩: 'kr' }
|
|
||||||
const errOr = {
|
|
||||||
52001: '请求超时,请重试。',
|
|
||||||
52002: '系统错误,请重试。',
|
|
||||||
52003: '未授权用户,请检查appid是否正确或者服务是否开通。',
|
|
||||||
54000: '必填参数为空,请检查是否少传参数。',
|
|
||||||
54001: '签名错误,请检查您的签名生成方法。',
|
|
||||||
54003: '访问频率受限,请降低您的调用频率,或进行身份认证后切换为高级版/尊享版。',
|
|
||||||
54004: '账户余额不足,请前往管理控制台为账户充值。',
|
|
||||||
54005: '长query请求频繁,请降低长query的发送频率,3s后再试。',
|
|
||||||
58000: '客户端IP非法,检查个人资料里填写的IP地址是否正确,可前往开发者信息-基本信息修改。',
|
|
||||||
58001: '译文语言方向不支持,检查译文语言是否在语言列表里。',
|
|
||||||
58002: '服务当前已关闭,请前往管理控制台开启服务。',
|
|
||||||
90107: '认证未通过或未生效,请前往我的认证查看认证进度。'
|
|
||||||
}
|
|
||||||
function Translate (config) {
|
|
||||||
this.requestNumber = 0 // 请求次数
|
|
||||||
this.config = {
|
|
||||||
showProgress: 1, // 是否显示进度
|
|
||||||
requestNumber: 1, // 最大请求次数
|
|
||||||
agreement: 'http', // 协议
|
|
||||||
...config
|
|
||||||
}
|
|
||||||
this.baiduApi = `${this.config.agreement}://api.fanyi.baidu.com/api/trans/vip/translate`
|
|
||||||
|
|
||||||
// 拼接url
|
|
||||||
this.createUrl = (domain, form) => {
|
|
||||||
let result = domain + '?'
|
|
||||||
for (let key in form) {
|
|
||||||
result += `${key}=${form[key]}&`
|
|
||||||
}
|
|
||||||
return result.slice(0, result.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.translate = async (value, ...params) => {
|
|
||||||
let result = ''
|
|
||||||
let from = 'auto'
|
|
||||||
let to = 'en'
|
|
||||||
|
|
||||||
if (params.length === 1) {
|
|
||||||
to = transMap[params[0]] || to
|
|
||||||
} else if (params.length === 2) {
|
|
||||||
from = transMap[params[0]] || from
|
|
||||||
to = transMap[params[1]] || to
|
|
||||||
}
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const res = await this.requestApi(value, { from, to })
|
|
||||||
result = res[0].dst
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value) || Object.prototype.toString.call(value) === '[object Object]') {
|
|
||||||
result = await this._createObjValue(value, { from, to })
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestApi = (value, params) => {
|
|
||||||
if (this.requestNumber >= this.config.requestNumber) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requestApi(value, params).then((res) => {
|
|
||||||
resolve(res)
|
|
||||||
})
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestNumber++
|
|
||||||
|
|
||||||
const { appid, secret } = this.config
|
|
||||||
const q = value
|
|
||||||
const salt = Math.random()
|
|
||||||
const sign = md5(`${appid}${q}${salt}${secret}`)
|
|
||||||
|
|
||||||
const fromData = {
|
|
||||||
q: encodeURIComponent(q),
|
|
||||||
sign,
|
|
||||||
appid,
|
|
||||||
salt,
|
|
||||||
from: params.from || 'auto',
|
|
||||||
to: params.to || 'en'
|
|
||||||
}
|
|
||||||
|
|
||||||
const fanyiApi = this.createUrl(this.baiduApi, fromData)
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.get(fanyiApi)
|
|
||||||
.then(({ data: res }) => {
|
|
||||||
if (!res.error_code) {
|
|
||||||
const resList = res.trans_result
|
|
||||||
resolve(resList)
|
|
||||||
} else {
|
|
||||||
const errCode = res.error_code
|
|
||||||
if (errOr[errCode]) {
|
|
||||||
reject(new Error('翻译出错了~' + errOr[errCode]))
|
|
||||||
} else {
|
|
||||||
reject(new Error('翻译出错了~' + errCode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requestNumber--
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 递归翻译数组或对象
|
|
||||||
this._createObjValue = async (value, parames) => {
|
|
||||||
let index = 0
|
|
||||||
const obj = Array.isArray(value) ? [] : {}
|
|
||||||
const strDatas = Array.isArray(value) ? value : Object.values(value)
|
|
||||||
const reqData = strDatas
|
|
||||||
.filter((item) => typeof item === 'string') // 过滤字符串
|
|
||||||
.join('\n')
|
|
||||||
const res = reqData ? await this.requestApi(reqData, parames) : []
|
|
||||||
for (let key in value) {
|
|
||||||
if (typeof value[key] === 'string') {
|
|
||||||
obj[key] = res[index].dst
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
Array.isArray(value[key]) ||
|
|
||||||
Object.prototype.toString.call(value[key]) === '[object Object]'
|
|
||||||
) {
|
|
||||||
obj[key] = await this.translate(value[key], parames) // 递归翻译
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.translate
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Translate
|
|
||||||
|
|
@ -120,8 +120,6 @@ const defaultConfig = {
|
||||||
azureTTSSpeaker: 'zh-CN-XiaochenNeural',
|
azureTTSSpeaker: 'zh-CN-XiaochenNeural',
|
||||||
voicevoxSpace: '',
|
voicevoxSpace: '',
|
||||||
voicevoxTTSSpeaker: '护士机器子T',
|
voicevoxTTSSpeaker: '护士机器子T',
|
||||||
baiduTranslateAppId: '',
|
|
||||||
baiduTranslateSecret: '',
|
|
||||||
azureTTSEmotion: false,
|
azureTTSEmotion: false,
|
||||||
enhanceAzureTTSEmotion: false,
|
enhanceAzureTTSEmotion: false,
|
||||||
autoJapanese: false,
|
autoJapanese: false,
|
||||||
|
|
|
||||||
|
|
@ -468,7 +468,7 @@ export async function convertFaces (msg, handleAt = false, e) {
|
||||||
handleAt = e?.isGroup && handleAt
|
handleAt = e?.isGroup && handleAt
|
||||||
let groupMembers
|
let groupMembers
|
||||||
let groupCardQQMap = {}
|
let groupCardQQMap = {}
|
||||||
if (handleAt) {
|
if (handleAt && typeof e.group.getMemberMap === 'function') {
|
||||||
groupMembers = await e.group.getMemberMap()
|
groupMembers = await e.group.getMemberMap()
|
||||||
for (let key of groupMembers.keys()) {
|
for (let key of groupMembers.keys()) {
|
||||||
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
||||||
|
|
|
||||||
97
utils/translate.js
Normal file
97
utils/translate.js
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
import md5 from 'md5'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
// 代码参考:https://github.com/yeyang52/yenai-plugin/blob/b50b11338adfa5a4ef93912eefd2f1f704e8b990/model/api/funApi.js#L25
|
||||||
|
export const translateLangSupports = [
|
||||||
|
{ code: 'ar', label: '阿拉伯语', abbr: '阿', alphabet: 'A' },
|
||||||
|
{ code: 'de', label: '德语', abbr: '德', alphabet: 'D' },
|
||||||
|
{ code: 'ru', label: '俄语', abbr: '俄', alphabet: 'E' },
|
||||||
|
{ code: 'fr', label: '法语', abbr: '法', alphabet: 'F' },
|
||||||
|
{ code: 'ko', label: '韩语', abbr: '韩', alphabet: 'H' },
|
||||||
|
{ code: 'nl', label: '荷兰语', abbr: '荷', alphabet: 'H' },
|
||||||
|
{ code: 'pt', label: '葡萄牙语', abbr: '葡', alphabet: 'P' },
|
||||||
|
{ code: 'ja', label: '日语', abbr: '日', alphabet: 'R' },
|
||||||
|
{ code: 'th', label: '泰语', abbr: '泰', alphabet: 'T' },
|
||||||
|
{ code: 'es', label: '西班牙语', abbr: '西', alphabet: 'X' },
|
||||||
|
{ code: 'en', label: '英语', abbr: '英', alphabet: 'Y' },
|
||||||
|
{ code: 'it', label: '意大利语', abbr: '意', alphabet: 'Y' },
|
||||||
|
{ code: 'vi', label: '越南语', abbr: '越', alphabet: 'Y' },
|
||||||
|
{ code: 'id', label: '印度尼西亚语', abbr: '印', alphabet: 'Y' },
|
||||||
|
{ code: 'zh-CHS', label: '中文', abbr: '中', alphabet: 'Z' }
|
||||||
|
]
|
||||||
|
const API_ERROR = '出了点小问题,待会再试试吧'
|
||||||
|
export async function translate (msg, to = 'auto') {
|
||||||
|
let from = 'auto'
|
||||||
|
if (to !== 'auto') to = translateLangSupports.find(item => item.abbr == to)?.code
|
||||||
|
if (!to) return `未找到翻译的语种,支持的语言为:\n${translateLangSupports.map(item => item.abbr).join(',')}\n`
|
||||||
|
// 翻译结果为空的提示
|
||||||
|
const RESULT_ERROR = '找不到翻译结果'
|
||||||
|
// API 请求错误提示
|
||||||
|
const API_ERROR = '翻译服务暂不可用,请稍后再试'
|
||||||
|
const qs = (obj) => {
|
||||||
|
let res = ''
|
||||||
|
for (const [k, v] of Object.entries(obj)) { res += `${k}=${encodeURIComponent(v)}&` }
|
||||||
|
return res.slice(0, res.length - 1)
|
||||||
|
}
|
||||||
|
const appVersion = '5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4750.0'
|
||||||
|
const payload = {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
bv: md5(appVersion),
|
||||||
|
client: 'fanyideskweb',
|
||||||
|
doctype: 'json',
|
||||||
|
version: '2.1',
|
||||||
|
keyfrom: 'fanyi.web',
|
||||||
|
action: 'FY_BY_DEFAULT',
|
||||||
|
smartresult: 'dict'
|
||||||
|
}
|
||||||
|
const headers = {
|
||||||
|
Host: 'fanyi.youdao.com',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102',
|
||||||
|
Referer: 'https://fanyi.youdao.com/',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
Cookie: 'OUTFOX_SEARCH_USER_ID_NCOO=133190305.98519628; OUTFOX_SEARCH_USER_ID="2081065877@10.169.0.102";'
|
||||||
|
}
|
||||||
|
const api = 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
|
||||||
|
const key = 'Ygy_4c=r#e#4EX^NUGUc5'
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Array.isArray(msg)) {
|
||||||
|
const results = []
|
||||||
|
for (let i = 0; i < msg.length; i++) {
|
||||||
|
const item = msg[i]
|
||||||
|
const lts = '' + new Date().getTime()
|
||||||
|
const salt = lts + parseInt(String(10 * Math.random()), 10)
|
||||||
|
const sign = md5(payload.client + item + salt + key)
|
||||||
|
const postData = qs(Object.assign({ i: item, lts, sign, salt }, payload))
|
||||||
|
let { errorCode, translateResult } = await fetch(api, {
|
||||||
|
method: 'POST',
|
||||||
|
body: postData,
|
||||||
|
headers
|
||||||
|
}).then(res => res.json()).catch(err => console.error(err))
|
||||||
|
if (errorCode !== 0) return API_ERROR
|
||||||
|
translateResult = _.flattenDeep(translateResult)?.map(item => item.tgt).join('\n')
|
||||||
|
if (!translateResult) results.push(RESULT_ERROR)
|
||||||
|
else results.push(translateResult)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
} else {
|
||||||
|
const i = msg // 翻译的内容
|
||||||
|
const lts = '' + new Date().getTime()
|
||||||
|
const salt = lts + parseInt(String(10 * Math.random()), 10)
|
||||||
|
const sign = md5(payload.client + i + salt + key)
|
||||||
|
const postData = qs(Object.assign({ i, lts, sign, salt }, payload))
|
||||||
|
let { errorCode, translateResult } = await fetch(api, {
|
||||||
|
method: 'POST',
|
||||||
|
body: postData,
|
||||||
|
headers
|
||||||
|
}).then(res => res.json()).catch(err => console.error(err))
|
||||||
|
if (errorCode !== 0) return API_ERROR
|
||||||
|
translateResult = _.flattenDeep(translateResult)?.map(item => item.tgt).join('\n')
|
||||||
|
if (!translateResult) return RESULT_ERROR
|
||||||
|
return translateResult
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return API_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -66,6 +66,10 @@ async function uploadRecord (recordUrl, ttsMode = 'vits-uma-genshin-honkai') {
|
||||||
recordUrl = tmpFile
|
recordUrl = tmpFile
|
||||||
}
|
}
|
||||||
if (recordType === 'file' || Config.cloudMode === 'file') {
|
if (recordType === 'file' || Config.cloudMode === 'file') {
|
||||||
|
if (!recordUrl) {
|
||||||
|
logger.error('云转码错误:recordUrl 异常')
|
||||||
|
return false
|
||||||
|
}
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
let buffer
|
let buffer
|
||||||
if (!recordUrl.startsWith('http')) {
|
if (!recordUrl.startsWith('http')) {
|
||||||
|
|
@ -87,9 +91,13 @@ async function uploadRecord (recordUrl, ttsMode = 'vits-uma-genshin-honkai') {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
let t = await resultres.text()
|
let t = await resultres.arrayBuffer()
|
||||||
try {
|
try {
|
||||||
result = JSON.parse(t)
|
result = {
|
||||||
|
buffer: {
|
||||||
|
data: t
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(t)
|
logger.error(t)
|
||||||
throw e
|
throw e
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue