mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 05:47:11 +00:00
Merge branch 'v2' into v2
This commit is contained in:
commit
fe4288a3d1
17 changed files with 761 additions and 90 deletions
|
|
@ -907,8 +907,8 @@ export class chatgpt extends plugin {
|
||||||
if (e.user_id == getUin(e)) return false
|
if (e.user_id == getUin(e)) return false
|
||||||
prompt = msg.trim()
|
prompt = msg.trim()
|
||||||
try {
|
try {
|
||||||
if (e.isGroup && typeof this.e.group.getMemberMap === 'function') {
|
if (e.isGroup) {
|
||||||
let mm = await this.e.group.getMemberMap()
|
let mm = this.e.bot.gml
|
||||||
let me = mm.get(getUin(e)) || {}
|
let me = mm.get(getUin(e)) || {}
|
||||||
let card = me.card
|
let card = me.card
|
||||||
let nickname = me.nickname
|
let nickname = me.nickname
|
||||||
|
|
@ -1595,7 +1595,7 @@ export class chatgpt extends plugin {
|
||||||
bingAIClient.opts.userToken = bingToken
|
bingAIClient.opts.userToken = bingToken
|
||||||
bingAIClient.opts.cookies = cookies
|
bingAIClient.opts.cookies = cookies
|
||||||
// opt.messageType = allThrottled ? 'Chat' : 'SearchQuery'
|
// opt.messageType = allThrottled ? 'Chat' : 'SearchQuery'
|
||||||
if (Config.enableGroupContext && e.isGroup && typeof e.group.getMemberMap === 'function') {
|
if (Config.enableGroupContext && e.isGroup) {
|
||||||
try {
|
try {
|
||||||
opt.groupId = e.group_id
|
opt.groupId = e.group_id
|
||||||
opt.qq = e.sender.user_id
|
opt.qq = e.sender.user_id
|
||||||
|
|
@ -1931,7 +1931,8 @@ export class chatgpt extends plugin {
|
||||||
let response = await client.sendMessage(prompt, {
|
let response = await client.sendMessage(prompt, {
|
||||||
e,
|
e,
|
||||||
chatId: conversation?.conversationId,
|
chatId: conversation?.conversationId,
|
||||||
image: image ? image[0] : undefined
|
image: image ? image[0] : undefined,
|
||||||
|
system: Config.xhPrompt
|
||||||
})
|
})
|
||||||
return response
|
return response
|
||||||
} else if (use === 'azure') {
|
} else if (use === 'azure') {
|
||||||
|
|
|
||||||
|
|
@ -324,10 +324,14 @@ export class dalle extends plugin {
|
||||||
const index = bingTokens.findIndex(element => element.Token === bingToken)
|
const index = bingTokens.findIndex(element => element.Token === bingToken)
|
||||||
bingTokens[index].Usage += 1
|
bingTokens[index].Usage += 1
|
||||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
||||||
|
let cookie
|
||||||
|
if (bingToken.includes('=')) {
|
||||||
|
cookie = bingToken
|
||||||
|
}
|
||||||
let client = new BingDrawClient({
|
let client = new BingDrawClient({
|
||||||
baseUrl: Config.sydneyReverseProxy,
|
baseUrl: Config.sydneyReverseProxy,
|
||||||
userToken: bingToken
|
userToken: bingToken,
|
||||||
|
cookies: cookie
|
||||||
})
|
})
|
||||||
await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 })
|
await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 })
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,13 @@ export class Entertainment extends plugin {
|
||||||
fnc: 'wordcloud_new'
|
fnc: 'wordcloud_new'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)',
|
reg: '^#((寄批踢|gpt|GPT)?翻[sS]*|chatgpt翻译帮助)',
|
||||||
fnc: 'translate'
|
fnc: 'translate'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#(chatgpt)?(设置|修改)翻译来源(openai|gemini|星火|通义千问|xh|qwen)$',
|
||||||
|
fnc: 'translateSource'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#ocr',
|
reg: '^#ocr',
|
||||||
fnc: 'ocr'
|
fnc: 'ocr'
|
||||||
|
|
@ -166,10 +170,10 @@ ${translateLangLabels}
|
||||||
await this.reply(err.message, e.isGroup)
|
await this.reply(err.message, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const totalLength = Array.isArray(result)
|
// const totalLength = Array.isArray(result)
|
||||||
? result.reduce((acc, cur) => acc + cur.length, 0)
|
// ? result.reduce((acc, cur) => acc + cur.length, 0)
|
||||||
: result.length
|
// : result.length
|
||||||
if (totalLength > 300 || multiText) {
|
if (multiText) {
|
||||||
// 多条翻译结果
|
// 多条翻译结果
|
||||||
if (Array.isArray(result)) {
|
if (Array.isArray(result)) {
|
||||||
result = await makeForwardMsg(e, result, '翻译结果')
|
result = await makeForwardMsg(e, result, '翻译结果')
|
||||||
|
|
@ -187,6 +191,26 @@ ${translateLangLabels}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
translateSource (e) {
|
||||||
|
let command = e.msg
|
||||||
|
if (command.includes('openai')) {
|
||||||
|
Config.translateSource = 'openai'
|
||||||
|
} else if (command.includes('gemini')) {
|
||||||
|
Config.translateSource = 'gemini'
|
||||||
|
} else if (command.includes('星火')) {
|
||||||
|
Config.translateSource = 'xh'
|
||||||
|
} else if (command.includes('通义千问')) {
|
||||||
|
Config.translateSource = 'qwen'
|
||||||
|
} else if (command.includes('xh')) {
|
||||||
|
Config.translateSource = 'xh'
|
||||||
|
} else if (command.includes('qwen')) {
|
||||||
|
Config.translateSource = 'qwen'
|
||||||
|
} else {
|
||||||
|
e.reply('暂不支持该翻译源')
|
||||||
|
}
|
||||||
|
e.reply('√成功设置翻译源为' + Config.translateSource)
|
||||||
|
}
|
||||||
|
|
||||||
async wordcloud (e) {
|
async wordcloud (e) {
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let groupId = e.group_id
|
let groupId = e.group_id
|
||||||
|
|
|
||||||
|
|
@ -22,22 +22,7 @@ import loader from '../../../lib/plugins/loader.js'
|
||||||
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
|
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
|
||||||
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
|
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { getProxy } from '../utils/proxy.js'
|
import { newFetch } from '../utils/proxy.js'
|
||||||
|
|
||||||
let proxy = getProxy()
|
|
||||||
const newFetch = (url, options = {}) => {
|
|
||||||
const defaultOptions = Config.proxy
|
|
||||||
? {
|
|
||||||
agent: proxy(Config.proxy)
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
const mergedOptions = {
|
|
||||||
...defaultOptions,
|
|
||||||
...options
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url, mergedOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChatgptManagement extends plugin {
|
export class ChatgptManagement extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
|
|
@ -87,11 +72,11 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'migrateBingAccessToken',
|
fnc: 'migrateBingAccessToken',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
reg: '^#chatgpt切换浏览器$',
|
// reg: '^#chatgpt切换浏览器$',
|
||||||
fnc: 'useBrowserBasedSolution',
|
// fnc: 'useBrowserBasedSolution',
|
||||||
permission: 'master'
|
// permission: 'master'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt切换API$',
|
reg: '^#chatgpt切换API$',
|
||||||
fnc: 'useOpenAIAPIBasedSolution',
|
fnc: 'useOpenAIAPIBasedSolution',
|
||||||
|
|
@ -243,7 +228,7 @@ export class ChatgptManagement extends plugin {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
/** 命令正则匹配 */
|
/** 命令正则匹配 */
|
||||||
reg: '^#(关闭|打开)群聊上下文$',
|
reg: '^#(chatgpt)?(关闭|打开)群聊上下文$',
|
||||||
/** 执行方法 */
|
/** 执行方法 */
|
||||||
fnc: 'enableGroupContext',
|
fnc: 'enableGroupContext',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
|
|
@ -254,16 +239,16 @@ export class ChatgptManagement extends plugin {
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#(设置|修改)管理密码',
|
reg: '^#(chatgpt)?(设置|修改)管理密码',
|
||||||
fnc: 'setAdminPassword',
|
fnc: 'setAdminPassword',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#(设置|修改)用户密码',
|
reg: '^#(chatgpt)?(设置|修改)用户密码',
|
||||||
fnc: 'setUserPassword'
|
fnc: 'setUserPassword'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#工具箱',
|
reg: '^#(chatgpt)?工具箱',
|
||||||
fnc: 'toolsPage',
|
fnc: 'toolsPage',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
|
@ -281,7 +266,7 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'commandHelp'
|
fnc: 'commandHelp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#语音切换.*',
|
reg: '^#(chatgpt)?语音切换.*',
|
||||||
fnc: 'ttsSwitch',
|
fnc: 'ttsSwitch',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
|
@ -910,7 +895,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
let use = await redis.get('CHATGPT:USE')
|
let use = await redis.get('CHATGPT:USE')
|
||||||
if (use !== 'bing') {
|
if (use !== 'bing') {
|
||||||
await redis.set('CHATGPT:USE', 'bing')
|
await redis.set('CHATGPT:USE', 'bing')
|
||||||
await this.reply('已切换到基于微软新必应的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
|
await this.reply('已切换到基于微软Copilot(必应)的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
|
||||||
} else {
|
} else {
|
||||||
await this.reply('当前已经是必应Bing模式了')
|
await this.reply('当前已经是必应Bing模式了')
|
||||||
}
|
}
|
||||||
|
|
@ -1577,7 +1562,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
const chatdata = data.chatConfig || {}
|
const chatdata = data.chatConfig || {}
|
||||||
for (let [keyPath, value] of Object.entries(chatdata)) {
|
for (let [keyPath, value] of Object.entries(chatdata)) {
|
||||||
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,,;;\|]/) }
|
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,,;;|]/) }
|
||||||
if (Config[keyPath] != value) {
|
if (Config[keyPath] != value) {
|
||||||
changeConfig.push({
|
changeConfig.push({
|
||||||
item: keyPath,
|
item: keyPath,
|
||||||
|
|
|
||||||
108
apps/vocal.js
Normal file
108
apps/vocal.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
|
import { SunoClient } from '../client/SunoClient.js'
|
||||||
|
import { Config } from '../utils/config.js'
|
||||||
|
import { downloadFile, maskEmail } from '../utils/common.js'
|
||||||
|
import common from '../../../lib/common/common.js'
|
||||||
|
import lodash from 'lodash'
|
||||||
|
|
||||||
|
export class Vocal extends plugin {
|
||||||
|
constructor (e) {
|
||||||
|
super({
|
||||||
|
name: 'ChatGPT-Plugin 音乐合成',
|
||||||
|
dsc: '基于Suno等AI的饮月生成!',
|
||||||
|
event: 'message',
|
||||||
|
priority: 500,
|
||||||
|
rule: [
|
||||||
|
{
|
||||||
|
reg: '^#((创作)?歌曲|suno|Suno)',
|
||||||
|
fnc: 'createSong',
|
||||||
|
permission: 'master'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
this.task = [
|
||||||
|
{
|
||||||
|
// 设置十分钟左右的浮动
|
||||||
|
cron: '0/1 * * * ?',
|
||||||
|
// cron: '*/2 * * * *',
|
||||||
|
name: '保持suno心跳',
|
||||||
|
fnc: this.heartbeat.bind(this)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async heartbeat (e) {
|
||||||
|
let sessTokens = Config.sunoSessToken.split(',')
|
||||||
|
let clientTokens = Config.sunoClientToken.split(',')
|
||||||
|
for (let i = 0; i < sessTokens.length; i++) {
|
||||||
|
let sessToken = sessTokens[i]
|
||||||
|
let clientToken = clientTokens[i]
|
||||||
|
if (sessToken && clientToken) {
|
||||||
|
let client = new SunoClient({ sessToken, clientToken })
|
||||||
|
await client.heartbeat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSong (e) {
|
||||||
|
if (!Config.sunoClientToken || !Config.sunoSessToken) {
|
||||||
|
await e.reply('未配置Suno Token')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let description = e.msg.replace(/#((创作)?歌曲|suno|Suno)/, '')
|
||||||
|
if (description === '额度' || description === 'credit' || description === '余额') {
|
||||||
|
let sessTokens = Config.sunoSessToken.split(',')
|
||||||
|
let clientTokens = Config.sunoClientToken.split(',')
|
||||||
|
let msg = ''
|
||||||
|
for (let i = 0; i < sessTokens.length; i++) {
|
||||||
|
let sess = sessTokens[i]
|
||||||
|
let clientToken = clientTokens[i]
|
||||||
|
let client = new SunoClient({ sessToken: sess, clientToken })
|
||||||
|
let { credit, email } = await client.queryCredit()
|
||||||
|
logger.info({ credit, email })
|
||||||
|
msg += `用户: ${maskEmail(email)} 余额:${credit}\n`
|
||||||
|
}
|
||||||
|
msg += '-------------------\n'
|
||||||
|
msg += 'Notice:每首歌消耗5credit,每次生成2首歌'
|
||||||
|
await e.reply(msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
await e.reply('正在生成,请稍后')
|
||||||
|
try {
|
||||||
|
let sessTokens = Config.sunoSessToken.split(',')
|
||||||
|
let clientTokens = Config.sunoClientToken.split(',')
|
||||||
|
let tried = 0
|
||||||
|
while (tried < sessTokens.length) {
|
||||||
|
let index = tried
|
||||||
|
let sess = sessTokens[index]
|
||||||
|
let clientToken = clientTokens[index]
|
||||||
|
let client = new SunoClient({ sessToken: sess, clientToken })
|
||||||
|
let { credit, email } = await client.queryCredit()
|
||||||
|
logger.info({ credit, email })
|
||||||
|
if (credit < 10) {
|
||||||
|
tried++
|
||||||
|
logger.info(`账户${email}余额不足,尝试下一个账户`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let songs = await client.createSong(description)
|
||||||
|
let messages = ['提示词:' + description]
|
||||||
|
for (let song of songs) {
|
||||||
|
messages.push(`歌名:${song.title}\n风格: ${song.metadata.tags}\n长度: ${lodash.round(song.metadata.duration, 0)}秒\n歌词:\n${song.metadata.prompt}\n`)
|
||||||
|
messages.push(`音频链接:${song.audio_url}\n视频链接:${song.video_url}\n封面链接:${song.image_url}\n`)
|
||||||
|
messages.push(segment.image(song.image_url))
|
||||||
|
// let videoPath = await downloadFile(song.video_url, `suno/${song.title}.mp4`, false, false, {
|
||||||
|
// 'User-Agent': ''
|
||||||
|
// })
|
||||||
|
messages.push(segment.video(song.video_url))
|
||||||
|
}
|
||||||
|
await e.reply(common.makeForwardMsg(e, messages, '音乐合成结果'))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
await e.reply('所有账户余额不足')
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
await e.reply('生成失败,请查看日志')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
client/SunoClient.js
Normal file
149
client/SunoClient.js
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
import { newFetch } from '../utils/proxy.js'
|
||||||
|
import common from '../../../lib/common/common.js'
|
||||||
|
import { decrypt } from '../utils/jwt.js'
|
||||||
|
import { FormData } from 'node-fetch'
|
||||||
|
|
||||||
|
export class SunoClient {
|
||||||
|
constructor (options) {
|
||||||
|
this.options = options
|
||||||
|
this.sessToken = options.sessToken
|
||||||
|
this.clientToken = options.clientToken
|
||||||
|
if (!this.clientToken || !this.sessToken) {
|
||||||
|
throw new Error('Token is required')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getToken () {
|
||||||
|
let lastToken = this.sessToken
|
||||||
|
let payload = decrypt(lastToken)
|
||||||
|
let sid = JSON.parse(payload).sid
|
||||||
|
logger.debug('sid: ' + sid)
|
||||||
|
let tokenRes = await newFetch(`https://clerk.suno.ai/v1/client/sessions/${sid}/tokens/api?_clerk_js_version=4.70.0`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Cookie: `__client=${this.clientToken};`,
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||||
|
Origin: 'https://app.suno.ai',
|
||||||
|
Referer: 'https://app.suno.ai/create/'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let tokenData = await tokenRes.json()
|
||||||
|
let token = tokenData.jwt
|
||||||
|
logger.info('new token got: ' + token)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSong (description) {
|
||||||
|
let sess = await this.getToken()
|
||||||
|
let createRes = await newFetch('https://studio-api.suno.ai/api/generate/v2/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${sess}`,
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||||
|
Origin: 'https://app.suno.ai',
|
||||||
|
Referer: 'https://app.suno.ai/create/',
|
||||||
|
Cookie: `__sess=${sess}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ gpt_description_prompt: description, mv: 'chirp-v2-engine-v13', prompt: '' })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (createRes.status !== 200) {
|
||||||
|
console.log(await createRes.json())
|
||||||
|
throw new Error('Failed to create song ' + createRes.status)
|
||||||
|
}
|
||||||
|
let createData = await createRes.json()
|
||||||
|
let ids = createData?.clips?.map(clip => clip.id)
|
||||||
|
let queryUrl = `https://studio-api.suno.ai/api/feed/?ids=${ids[0]}%2C${ids[1]}`
|
||||||
|
let allDone = false; let songs = []
|
||||||
|
let timeout = 60
|
||||||
|
while (timeout > 0 && !allDone) {
|
||||||
|
try {
|
||||||
|
let queryRes = await newFetch(queryUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${sess}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (queryRes.status !== 200) {
|
||||||
|
logger.error(await queryRes.text())
|
||||||
|
console.error('Failed to query song')
|
||||||
|
}
|
||||||
|
let queryData = await queryRes.json()
|
||||||
|
logger.debug(queryData)
|
||||||
|
allDone = queryData.every(clip => clip.status === 'complete')
|
||||||
|
songs = queryData
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
await common.sleep(1000)
|
||||||
|
timeout--
|
||||||
|
}
|
||||||
|
return songs
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryUser (sess) {
|
||||||
|
if (!sess) {
|
||||||
|
sess = await this.getToken()
|
||||||
|
}
|
||||||
|
let userRes = await newFetch('https://studio-api.suno.ai/api/session/', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${sess}`,
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||||
|
Origin: 'https://app.suno.ai',
|
||||||
|
Referer: 'https://app.suno.ai/create/',
|
||||||
|
Cookie: `__sess=${sess}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let userData = await userRes.json()
|
||||||
|
logger.debug(userData)
|
||||||
|
let user = userData?.user.email
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryCredit () {
|
||||||
|
let sess = await this.getToken()
|
||||||
|
let infoRes = await newFetch('https://studio-api.suno.ai/api/billing/info/', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${sess}`,
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||||
|
Origin: 'https://app.suno.ai',
|
||||||
|
Referer: 'https://app.suno.ai/create/',
|
||||||
|
Cookie: `__sess=${sess}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let infoData = await infoRes.json()
|
||||||
|
logger.debug(infoData)
|
||||||
|
let credit = infoData?.total_credits_left
|
||||||
|
let email = await this.queryUser(sess)
|
||||||
|
return {
|
||||||
|
email, credit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async heartbeat () {
|
||||||
|
let lastToken = this.sessToken
|
||||||
|
let payload = decrypt(lastToken)
|
||||||
|
let sid = JSON.parse(payload).sid
|
||||||
|
logger.debug('sid: ' + sid)
|
||||||
|
let heartbeatUrl = `https://clerk.suno.ai/v1/client/sessions/${sid}/touch?_clerk_js_version=4.70.0`
|
||||||
|
let heartbeatRes = await fetch(heartbeatUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Cookie: `__client=${this.clientToken};`,
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||||
|
Origin: 'https://app.suno.ai',
|
||||||
|
Referer: 'https://app.suno.ai/create/'
|
||||||
|
},
|
||||||
|
body: 'active_organization_id='
|
||||||
|
})
|
||||||
|
logger.debug(await heartbeatRes.text())
|
||||||
|
if (heartbeatRes.status === 200) {
|
||||||
|
logger.debug('heartbeat success')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
client/test/SunoClientTest.js
Normal file
11
client/test/SunoClientTest.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { SunoClient } from '../SunoClient.js'
|
||||||
|
|
||||||
|
async function test () {
|
||||||
|
const options = {
|
||||||
|
}
|
||||||
|
let client = new SunoClient(options)
|
||||||
|
let res = await client.createSong('guacamole')
|
||||||
|
console.log(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
test()
|
||||||
278
guoba.support.js
278
guoba.support.js
|
|
@ -223,45 +223,12 @@ export function supportGuoba () {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'groupMerge',
|
|
||||||
label: '群组消息合并',
|
|
||||||
bottomHelpMessage: '开启后,群聊消息将被视为同一对话',
|
|
||||||
component: 'Switch'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'allowOtherMode',
|
field: 'allowOtherMode',
|
||||||
label: '允许其他模式',
|
label: '允许其他模式',
|
||||||
bottomHelpMessage: '开启后,则允许用户使用#chat1/#chat3/#chatglm/#bing等命令无视全局模式进行聊天',
|
bottomHelpMessage: '开启后,则允许用户使用#chat1/#chat3/#chatglm/#bing等命令无视全局模式进行聊天',
|
||||||
component: 'Switch'
|
component: 'Switch'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'quoteReply',
|
|
||||||
label: '图片引用消息',
|
|
||||||
bottomHelpMessage: '在回复图片时引用原始消息',
|
|
||||||
component: 'Switch'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'showQRCode',
|
|
||||||
label: '启用二维码',
|
|
||||||
bottomHelpMessage: '在图片模式中启用二维码。该对话内容将被发送至第三方服务器以进行渲染展示,如果不希望对话内容被上传到第三方服务器请关闭此功能',
|
|
||||||
component: 'Switch'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'drawCD',
|
|
||||||
label: '绘图CD',
|
|
||||||
helpMessage: '单位:秒',
|
|
||||||
bottomHelpMessage: '绘图指令的CD时间,主人不受限制',
|
|
||||||
component: 'InputNumber',
|
|
||||||
componentProps: {
|
|
||||||
min: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'enableDraw',
|
|
||||||
label: '绘图功能开关',
|
|
||||||
component: 'Switch'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'proxy',
|
field: 'proxy',
|
||||||
label: '代理服务器地址',
|
label: '代理服务器地址',
|
||||||
|
|
@ -274,6 +241,20 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '将输出更多调试信息,如果不希望控制台刷屏的话,可以关闭',
|
bottomHelpMessage: '将输出更多调试信息,如果不希望控制台刷屏的话,可以关闭',
|
||||||
component: 'Switch'
|
component: 'Switch'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'translateSource',
|
||||||
|
label: '翻译来源',
|
||||||
|
bottomHelpMessage: '#gpt翻译使用的AI来源',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'OpenAI', value: 'openai' },
|
||||||
|
{ label: 'Gemini', value: 'gemini' },
|
||||||
|
{ label: '星火', value: 'xh' },
|
||||||
|
{ label: '通义千问', value: 'qwen' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '以下为服务超时配置。',
|
label: '以下为服务超时配置。',
|
||||||
component: 'Divider'
|
component: 'Divider'
|
||||||
|
|
@ -798,6 +779,237 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '对https://generativelanguage.googleapis.com的反代',
|
bottomHelpMessage: '对https://generativelanguage.googleapis.com的反代',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '以下为一些杂项配置。',
|
||||||
|
component: 'Divider'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'blockWords',
|
||||||
|
label: '输出黑名单',
|
||||||
|
bottomHelpMessage: '检查输出结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开',
|
||||||
|
component: 'InputTextArea'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'promptBlockWords',
|
||||||
|
label: '输入黑名单',
|
||||||
|
bottomHelpMessage: '检查输入结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开',
|
||||||
|
component: 'InputTextArea'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'whitelist',
|
||||||
|
label: '对话白名单',
|
||||||
|
bottomHelpMessage: '默认设置为添加群号。优先级高于黑名单。\n' +
|
||||||
|
'注意:需要添加QQ号时在前面添加^(例如:^123456),此全局添加白名单,即除白名单以外的所有人都不能使用插件对话。\n' +
|
||||||
|
'如果需要在某个群里独享moment,即群聊中只有白名单上的qq号能用,则使用(群号^qq)的格式(例如:123456^123456)。\n' +
|
||||||
|
'白名单优先级:混合制 > qq > 群号。\n' +
|
||||||
|
'黑名单优先级: 群号 > qq > 混合制。',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'blacklist',
|
||||||
|
label: '对话黑名单',
|
||||||
|
bottomHelpMessage: '参考白名单设置规则。',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'imgOcr',
|
||||||
|
label: '图片识别',
|
||||||
|
bottomHelpMessage: '是否识别消息中图片的文字内容,需要同时包含图片和消息才生效',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'enablePrivateChat',
|
||||||
|
label: '是否允许私聊机器人',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'defaultUsePicture',
|
||||||
|
label: '全局图片模式',
|
||||||
|
bottomHelpMessage: '全局默认以图片形式回复',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'defaultUseTTS',
|
||||||
|
label: '全局语音模式',
|
||||||
|
bottomHelpMessage: '全局默认以语音形式回复,使用默认角色音色',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ttsMode',
|
||||||
|
label: '语音模式源',
|
||||||
|
bottomHelpMessage: '语音模式下使用何种语音源进行文本->音频转换',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'vits-uma-genshin-honkai',
|
||||||
|
value: 'vits-uma-genshin-honkai'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '微软Azure',
|
||||||
|
value: 'azure'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'VoiceVox',
|
||||||
|
value: 'voicevox'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'defaultTTSRole',
|
||||||
|
label: 'vits默认角色',
|
||||||
|
bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [{
|
||||||
|
label: '随机',
|
||||||
|
value: '随机'
|
||||||
|
}].concat(speakers.map(s => { return { label: s, value: s } }))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'azureTTSSpeaker',
|
||||||
|
label: 'Azure默认角色',
|
||||||
|
bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [{
|
||||||
|
label: '随机',
|
||||||
|
value: '随机'
|
||||||
|
},
|
||||||
|
...azureRoleList.flatMap(item => [
|
||||||
|
item.roleInfo
|
||||||
|
]).map(s => ({
|
||||||
|
label: s,
|
||||||
|
value: s
|
||||||
|
}))]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'voicevoxTTSSpeaker',
|
||||||
|
label: 'VoiceVox默认角色',
|
||||||
|
bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [{
|
||||||
|
label: '随机',
|
||||||
|
value: '随机'
|
||||||
|
},
|
||||||
|
...voxRoleList.flatMap(item => [
|
||||||
|
...item.styles.map(style => `${item.name}-${style.name}`),
|
||||||
|
item.name
|
||||||
|
]).map(s => ({
|
||||||
|
label: s,
|
||||||
|
value: s
|
||||||
|
}))]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ttsRegex',
|
||||||
|
label: '语音过滤正则表达式',
|
||||||
|
bottomHelpMessage: '语音模式下,配置此项以过滤不想被读出来的内容。表达式测试地址:https://www.runoob.com/regexp/regexp-syntax.html',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ttsAutoFallbackThreshold',
|
||||||
|
label: '语音转文字阈值',
|
||||||
|
helpMessage: '语音模式下,字数超过这个阈值就降级为文字',
|
||||||
|
bottomHelpMessage: '语音转为文字的阈值',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0,
|
||||||
|
max: 299
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'alsoSendText',
|
||||||
|
label: '语音同时发送文字',
|
||||||
|
bottomHelpMessage: '语音模式下,同时发送文字版,避免音质较低听不懂',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'autoJapanese',
|
||||||
|
label: 'vits模式日语输出',
|
||||||
|
bottomHelpMessage: '使用vits语音时,将机器人的文字回复翻译成日文后获取语音。' +
|
||||||
|
'若想使用插件的翻译功能,发送"#chatgpt翻译帮助"查看使用方法,支持图片翻译,引用翻译...',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'autoUsePicture',
|
||||||
|
label: '长文本自动转图片',
|
||||||
|
bottomHelpMessage: '字数大于阈值会自动用图片发送,即使是文本模式',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'autoUsePictureThreshold',
|
||||||
|
label: '自动转图片阈值',
|
||||||
|
helpMessage: '长文本自动转图片开启后才生效',
|
||||||
|
bottomHelpMessage: '自动转图片的字数阈值',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'conversationPreserveTime',
|
||||||
|
label: '对话保留时长',
|
||||||
|
helpMessage: '单位:秒',
|
||||||
|
bottomHelpMessage: '每个人发起的对话保留时长。超过这个时长没有进行对话,再进行对话将开启新的对话。',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'groupMerge',
|
||||||
|
label: '群组消息合并',
|
||||||
|
bottomHelpMessage: '开启后,群聊消息将被视为同一对话',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'quoteReply',
|
||||||
|
label: '图片引用消息',
|
||||||
|
bottomHelpMessage: '在回复图片时引用原始消息',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'showQRCode',
|
||||||
|
label: '启用二维码',
|
||||||
|
bottomHelpMessage: '在图片模式中启用二维码。该对话内容将被发送至第三方服务器以进行渲染展示,如果不希望对话内容被上传到第三方服务器请关闭此功能',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'drawCD',
|
||||||
|
label: '绘图CD',
|
||||||
|
helpMessage: '单位:秒',
|
||||||
|
bottomHelpMessage: '绘图指令的CD时间,主人不受限制',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'enableDraw',
|
||||||
|
label: '绘图功能开关',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '以下为Suno音乐合成的配置。',
|
||||||
|
component: 'Divider'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sunoSessToken',
|
||||||
|
label: 'sunoSessToken',
|
||||||
|
bottomHelpMessage: 'suno的__sess token,需要与sunoClientToken一一对应数量相同,多个用逗号隔开',
|
||||||
|
component: 'InputTextArea'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sunoClientToken',
|
||||||
|
label: 'sunoClientToken',
|
||||||
|
bottomHelpMessage: 'suno的__client token,需要与sunoSessToken一一对应数量相同,多个用逗号隔开',
|
||||||
|
component: 'InputTextArea'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '以下为杂七杂八的配置',
|
label: '以下为杂七杂八的配置',
|
||||||
component: 'Divider'
|
component: 'Divider'
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export default class BingDrawClient {
|
||||||
fetchOptions.agent = proxy(Config.proxy)
|
fetchOptions.agent = proxy(Config.proxy)
|
||||||
}
|
}
|
||||||
let success = false
|
let success = false
|
||||||
let retry = 5
|
let retry = 1
|
||||||
let response
|
let response
|
||||||
while (!success && retry >= 0) {
|
while (!success && retry >= 0) {
|
||||||
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST', credentials: 'include' }))
|
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST', credentials: 'include' }))
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export async function getChatHistoryGroup (e, num) {
|
||||||
}
|
}
|
||||||
chats = chats.slice(0, num)
|
chats = chats.slice(0, num)
|
||||||
try {
|
try {
|
||||||
let mm = await e.group.getMemberMap()
|
let mm = await e.bot.gml
|
||||||
for (const chat of chats) {
|
for (const chat of chats) {
|
||||||
if (e.adapter === 'shamrock') {
|
if (e.adapter === 'shamrock') {
|
||||||
if (chat.sender?.user_id === 0) {
|
if (chat.sender?.user_id === 0) {
|
||||||
|
|
|
||||||
|
|
@ -130,19 +130,14 @@ export class ClaudeAIClient {
|
||||||
|
|
||||||
async sendMessage (text, conversationId, attachments = []) {
|
async sendMessage (text, conversationId, attachments = []) {
|
||||||
let body = {
|
let body = {
|
||||||
conversation_uuid: conversationId,
|
|
||||||
organization_uuid: this.organizationId,
|
|
||||||
text,
|
|
||||||
attachments,
|
attachments,
|
||||||
completion: {
|
files: [],
|
||||||
incremental: true,
|
model: 'claude-2.1',
|
||||||
model: 'claude-2.1',
|
prompt: text,
|
||||||
prompt: text,
|
timezone: 'Asia/Hong_Kong'
|
||||||
timezone: 'Asia/Hong_Kong'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let host = Config.claudeAIReverseProxy || 'https://claude.ai'
|
let host = Config.claudeAIReverseProxy || 'https://claude.ai'
|
||||||
let url = host + '/api/append_message'
|
let url = host + `/api/organizations/${this.organizationId}/chat_conversations/${conversationId}/completion`
|
||||||
const cycleTLS = await initCycleTLS()
|
const cycleTLS = await initCycleTLS()
|
||||||
let streamDataRes = await cycleTLS(url, {
|
let streamDataRes = await cycleTLS(url, {
|
||||||
ja3: this.JA3,
|
ja3: this.JA3,
|
||||||
|
|
@ -160,7 +155,7 @@ export class ClaudeAIClient {
|
||||||
let streamData = streamDataRes.body
|
let streamData = streamDataRes.body
|
||||||
// console.log(streamData)
|
// console.log(streamData)
|
||||||
let responseText = ''
|
let responseText = ''
|
||||||
let streams = streamData.split('\n\n')
|
let streams = streamData.split('\n').filter(s => s?.includes('data: '))
|
||||||
for (let s of streams) {
|
for (let s of streams) {
|
||||||
let jsonStr = s.replace('data: ', '').trim()
|
let jsonStr = s.replace('data: ', '').trim()
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { translate } from './translate.js'
|
||||||
import uploadRecord from './uploadRecord.js'
|
import uploadRecord from './uploadRecord.js'
|
||||||
import Version from './version.js'
|
import Version from './version.js'
|
||||||
import fetch, { FormData, fileFromSync } from 'node-fetch'
|
import fetch, { FormData, fileFromSync } from 'node-fetch'
|
||||||
import https from "https";
|
import https from 'https'
|
||||||
let pdfjsLib
|
let pdfjsLib
|
||||||
try {
|
try {
|
||||||
pdfjsLib = (await import('pdfjs-dist')).default
|
pdfjsLib = (await import('pdfjs-dist')).default
|
||||||
|
|
@ -1055,10 +1055,14 @@ export async function getOrDownloadFile (destPath, url, ignoreCertificateError =
|
||||||
* @param destPath 目标路径,如received/abc.pdf. 目前如果文件名重复会覆盖。
|
* @param destPath 目标路径,如received/abc.pdf. 目前如果文件名重复会覆盖。
|
||||||
* @param absolute 是否是绝对路径,默认为false,此时拼接在data/chatgpt下
|
* @param absolute 是否是绝对路径,默认为false,此时拼接在data/chatgpt下
|
||||||
* @param ignoreCertificateError 忽略证书错误
|
* @param ignoreCertificateError 忽略证书错误
|
||||||
|
* @param headers
|
||||||
* @returns {Promise<string>} 最终下载文件的存储位置
|
* @returns {Promise<string>} 最终下载文件的存储位置
|
||||||
*/
|
*/
|
||||||
export async function downloadFile (url, destPath, absolute = false, ignoreCertificateError = true) {
|
export async function downloadFile (url, destPath, absolute = false, ignoreCertificateError = true, headers) {
|
||||||
let init = {}
|
let init = {}
|
||||||
|
if (headers) {
|
||||||
|
init.headers = headers
|
||||||
|
}
|
||||||
if (ignoreCertificateError && url.startsWith('https')) {
|
if (ignoreCertificateError && url.startsWith('https')) {
|
||||||
init.agent = new https.Agent({
|
init.agent = new https.Agent({
|
||||||
rejectUnauthorized: !ignoreCertificateError
|
rejectUnauthorized: !ignoreCertificateError
|
||||||
|
|
@ -1261,3 +1265,52 @@ export async function extractContentFromFile (fileMsgElem, e) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generated by ai
|
||||||
|
* @param email
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function maskEmail (email) {
|
||||||
|
// 使用正则表达式匹配电子邮件地址的用户名和域名部分
|
||||||
|
const regex = /^([^@]+)@([^@]+)$/
|
||||||
|
const match = email.match(regex)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new Error('Invalid email format')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户名和域名
|
||||||
|
const username = match[1]
|
||||||
|
const domain = match[2]
|
||||||
|
|
||||||
|
// 对用户名部分进行部分打码
|
||||||
|
const maskedUsername = maskString(username)
|
||||||
|
|
||||||
|
// 对域名部分进行部分打码
|
||||||
|
const maskedDomain = maskString(domain)
|
||||||
|
|
||||||
|
// 构造新的电子邮件地址
|
||||||
|
const maskedEmail = maskedUsername + '@' + maskedDomain
|
||||||
|
|
||||||
|
return maskedEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generated by ai
|
||||||
|
* @param str
|
||||||
|
* @returns {*|string}
|
||||||
|
*/
|
||||||
|
function maskString (str) {
|
||||||
|
// 如果字符串长度小于等于2,直接返回原字符串
|
||||||
|
if (str.length <= 2) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取字符串的前三个字符和后三个字符,中间使用*代替
|
||||||
|
const firstThreeChars = str.substring(0, 3)
|
||||||
|
const lastThreeChars = str.substring(str.length - 3)
|
||||||
|
const maskedChars = '*'.repeat(str.length - 6)
|
||||||
|
|
||||||
|
return firstThreeChars + maskedChars + lastThreeChars
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,9 @@ const defaultConfig = {
|
||||||
// origin: https://generativelanguage.googleapis.com
|
// origin: https://generativelanguage.googleapis.com
|
||||||
geminiBaseUrl: 'https://gemini.ikechan8370.com',
|
geminiBaseUrl: 'https://gemini.ikechan8370.com',
|
||||||
chatglmRefreshToken: '',
|
chatglmRefreshToken: '',
|
||||||
|
sunoSessToken: '',
|
||||||
|
sunoClientToken: '',
|
||||||
|
translateSource: 'openai',
|
||||||
version: 'v2.7.10'
|
version: 'v2.7.10'
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,7 @@ export async function convertFaces (msg, handleAt = false, e) {
|
||||||
let groupCardQQMap = {}
|
let groupCardQQMap = {}
|
||||||
if (handleAt) {
|
if (handleAt) {
|
||||||
try {
|
try {
|
||||||
groupMembers = await e.group.getMemberMap()
|
groupMembers = e.bot.gml
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to get group members: ${err}`)
|
console.error(`Failed to get group members: ${err}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
utils/jwt.js
Normal file
8
utils/jwt.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export function decrypt (jwtToken) {
|
||||||
|
const [encodedHeader, encodedPayload, signature] = jwtToken.split('.')
|
||||||
|
|
||||||
|
const decodedHeader = Buffer.from(encodedHeader, 'base64').toString('utf-8')
|
||||||
|
const decodedPayload = Buffer.from(encodedPayload, 'base64').toString('utf-8')
|
||||||
|
|
||||||
|
return decodedPayload
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import md5 from 'md5'
|
import md5 from 'md5'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { Config } from './config.js'
|
||||||
|
import { ChatGPTAPI } from './openai/chatgpt-api.js'
|
||||||
|
import { newFetch } from './proxy.js'
|
||||||
|
import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
|
||||||
|
import XinghuoClient from './xinghuo/xinghuo.js'
|
||||||
|
import {getImg, getMessageById, upsertMessage} from './common.js'
|
||||||
|
import {QwenApi} from "./alibaba/qwen-api.js";
|
||||||
|
import {v4 as uuid} from "uuid";
|
||||||
|
|
||||||
// 代码参考:https://github.com/yeyang52/yenai-plugin/blob/b50b11338adfa5a4ef93912eefd2f1f704e8b990/model/api/funApi.js#L25
|
// 代码参考:https://github.com/yeyang52/yenai-plugin/blob/b50b11338adfa5a4ef93912eefd2f1f704e8b990/model/api/funApi.js#L25
|
||||||
export const translateLangSupports = [
|
export const translateLangSupports = [
|
||||||
|
|
@ -20,7 +28,7 @@ export const translateLangSupports = [
|
||||||
{ code: 'zh-CHS', label: '中文', abbr: '中', alphabet: 'Z' }
|
{ code: 'zh-CHS', label: '中文', abbr: '中', alphabet: 'Z' }
|
||||||
]
|
]
|
||||||
const API_ERROR = '出了点小问题,待会再试试吧'
|
const API_ERROR = '出了点小问题,待会再试试吧'
|
||||||
export async function translate (msg, to = 'auto') {
|
export async function translateOld (msg, to = 'auto') {
|
||||||
let from = 'auto'
|
let from = 'auto'
|
||||||
if (to !== 'auto') to = translateLangSupports.find(item => item.abbr == to)?.code
|
if (to !== 'auto') to = translateLangSupports.find(item => item.abbr == to)?.code
|
||||||
if (!to) return `未找到翻译的语种,支持的语言为:\n${translateLangSupports.map(item => item.abbr).join(',')}\n`
|
if (!to) return `未找到翻译的语种,支持的语言为:\n${translateLangSupports.map(item => item.abbr).join(',')}\n`
|
||||||
|
|
@ -95,3 +103,113 @@ export async function translate (msg, to = 'auto') {
|
||||||
return API_ERROR
|
return API_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param msg 要翻译的
|
||||||
|
* @param from 语种
|
||||||
|
* @param to 语种
|
||||||
|
* @param ai ai来源,支持openai, gemini, xh, qwen
|
||||||
|
* @returns {Promise<*|string>}
|
||||||
|
*/
|
||||||
|
export async function translate (msg, to = 'auto', from = 'auto', ai = Config.translateSource) {
|
||||||
|
try {
|
||||||
|
let lang = '中'
|
||||||
|
if (to !== 'auto') {
|
||||||
|
lang = translateLangSupports.find(item => item.abbr == to)?.code
|
||||||
|
}
|
||||||
|
if (!lang) return `未找到翻译的语种,支持的语言为:\n${translateLangSupports.map(item => item.abbr).join(',')}\n`
|
||||||
|
// if ai is not in the list, throw error
|
||||||
|
if (!['openai', 'gemini', 'xh', 'qwen'].includes(ai)) throw new Error('ai来源错误')
|
||||||
|
let system = `You will be provided with a sentence in the language with language code [${from}], and your task is to translate it into [${lang}]. Just print the result without any other words.`
|
||||||
|
if (Array.isArray(msg)) {
|
||||||
|
let result = []
|
||||||
|
for (let i = 0; i < msg.length; i++) {
|
||||||
|
let item = msg[i]
|
||||||
|
let res = await translate(item, to, from, ai)
|
||||||
|
result.push(res)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
switch (ai) {
|
||||||
|
case 'openai': {
|
||||||
|
let api = new ChatGPTAPI({
|
||||||
|
apiBaseUrl: Config.openAiBaseUrl,
|
||||||
|
apiKey: Config.apiKey,
|
||||||
|
fetch: newFetch
|
||||||
|
})
|
||||||
|
const res = await api.sendMessage(msg, {
|
||||||
|
systemMessage: system,
|
||||||
|
completionParams: {
|
||||||
|
model: 'gpt-3.5-turbo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res.text
|
||||||
|
}
|
||||||
|
case 'gemini': {
|
||||||
|
let client = new CustomGoogleGeminiClient({
|
||||||
|
key: Config.geminiKey,
|
||||||
|
model: Config.geminiModel,
|
||||||
|
baseUrl: Config.geminiBaseUrl,
|
||||||
|
debug: Config.debug
|
||||||
|
})
|
||||||
|
let option = {
|
||||||
|
stream: false,
|
||||||
|
onProgress: (data) => {
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info(data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
system
|
||||||
|
}
|
||||||
|
let res = await client.sendMessage(msg, option)
|
||||||
|
return res.text
|
||||||
|
}
|
||||||
|
case 'xh': {
|
||||||
|
let client = new XinghuoClient({
|
||||||
|
ssoSessionId: Config.xinghuoToken
|
||||||
|
})
|
||||||
|
let response = await client.sendMessage(msg, { system })
|
||||||
|
return response.text
|
||||||
|
}
|
||||||
|
case 'qwen': {
|
||||||
|
let completionParams = {
|
||||||
|
parameters: {
|
||||||
|
top_p: Config.qwenTopP || 0.5,
|
||||||
|
top_k: Config.qwenTopK || 50,
|
||||||
|
seed: Config.qwenSeed > 0 ? Config.qwenSeed : Math.floor(Math.random() * 114514),
|
||||||
|
temperature: Config.qwenTemperature || 1,
|
||||||
|
enable_search: !!Config.qwenEnableSearch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Config.qwenModel) {
|
||||||
|
completionParams.model = Config.qwenModel
|
||||||
|
}
|
||||||
|
let opts = {
|
||||||
|
apiKey: Config.qwenApiKey,
|
||||||
|
debug: false,
|
||||||
|
systemMessage: system,
|
||||||
|
completionParams,
|
||||||
|
fetch: newFetch
|
||||||
|
}
|
||||||
|
let client = new QwenApi(opts)
|
||||||
|
let option = {
|
||||||
|
timeoutMs: 600000,
|
||||||
|
completionParams
|
||||||
|
}
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
result = await client.sendMessage(msg, option)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
throw new Error(err)
|
||||||
|
}
|
||||||
|
return result.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e)
|
||||||
|
logger.info('基于LLM的翻译失败,转用老版翻译')
|
||||||
|
return await translateOld(msg, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,7 @@ export default class XinghuoClient {
|
||||||
logger.warn('星火设定序列化失败,本次对话不附带设定')
|
logger.warn('星火设定序列化失败,本次对话不附带设定')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Prompt = Config.xhPrompt ? [{ role: 'system', content: Config.xhPrompt }] : []
|
Prompt = option.system ? [{ role: 'system', content: option.system }] : []
|
||||||
}
|
}
|
||||||
if (Config.xhPromptEval) {
|
if (Config.xhPromptEval) {
|
||||||
Prompt.forEach(obj => {
|
Prompt.forEach(obj => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue