Merge branch 'v2' of github.com:ikechan8370/chatgpt-plugin into v2

This commit is contained in:
ikechan8370 2024-02-28 16:36:38 +08:00
commit 9c73c99b65
34 changed files with 1779 additions and 643 deletions

View file

@ -81,6 +81,7 @@ import { getChatHistoryGroup } from '../utils/chat.js'
import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
import { resizeAndCropImage } from '../utils/dalle.js'
import fs from 'fs'
import { ChatGLM4Client } from '../client/ChatGLM4Client.js'
const roleMap = {
owner: 'group owner',
@ -106,8 +107,8 @@ try {
let version = Config.version
let proxy = getProxy()
const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德']
const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard']
const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德', '双子星', '双子座', '智谱']
const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard', 'gemini', 'gemini', 'chatglm4']
/**
* 每个对话保留的时长单个对话内ai是保留上下文的超时后销毁对话再次对话创建新的对话
* 单位
@ -133,6 +134,7 @@ const newFetch = (url, options = {}) => {
export class chatgpt extends plugin {
constructor () {
let toggleMode = Config.toggleMode
let apiStream = Config.apiStream
super({
/** 功能名称 */
name: 'ChatGpt 对话',
@ -196,6 +198,12 @@ export class chatgpt extends plugin {
reg: '^#星火(搜索|查找)助手',
fnc: 'searchxhBot'
},
{
/** 命令正则匹配 */
reg: '^#glm4[sS]*',
/** 执行方法 */
fnc: 'glm4'
},
{
/** 命令正则匹配 */
reg: '^#qwen[sS]*',
@ -221,11 +229,11 @@ export class chatgpt extends plugin {
permission: 'master'
},
{
reg: '^#(chatgpt|星火|通义千问|克劳德|克劳德2|必应|api|API|api3|API3|glm|巴德)?(结束|新开|摧毁|毁灭|完结)对话([sS]*)',
reg: `^#?(${originalValues.join('|')})?(结束|新开|摧毁|毁灭|完结)对话([sS]*)$`,
fnc: 'destroyConversations'
},
{
reg: '^#(chatgpt|星火|通义千问|克劳德|克劳德2|必应|api|API|api3|API3|glm|巴德)?(结束|新开|摧毁|毁灭|完结)全部对话$',
reg: `^#?(${originalValues.join('|')})?(结束|新开|摧毁|毁灭|完结)全部对话$`,
fnc: 'endAllConversations',
permission: 'master'
},
@ -284,6 +292,7 @@ export class chatgpt extends plugin {
]
})
this.toggleMode = toggleMode
this.apiStream = apiStream
}
/**
@ -358,7 +367,7 @@ export class chatgpt extends plugin {
if (use === 'api3') {
await redis.del(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
} else if (use === 'bing' && (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom')) {
} else if (use === 'bing') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
if (!c) {
await this.reply('当前没有开启对话', true)
@ -419,6 +428,14 @@ export class chatgpt extends plugin {
await redis.del(`CHATGPT:CONVERSATIONS_GEMINI:${e.sender.user_id}`)
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
}
} else if (use === 'chatglm4') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_CHATGLM4:${e.sender.user_id}`)
if (!c) {
await this.reply('当前没有开启对话', true)
} else {
await redis.del(`CHATGPT:CONVERSATIONS_CHATGLM4:${e.sender.user_id}`)
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
}
} else if (use === 'bing') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
if (!c) {
@ -443,7 +460,7 @@ export class chatgpt extends plugin {
if (use === 'api3') {
await redis.del(`CHATGPT:QQ_CONVERSATION:${qq}`)
await this.reply(`${atUser}已退出TA当前的对话TA仍可以@我进行聊天以开启新的对话`, true)
} else if (use === 'bing' && (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom')) {
} else if (use === 'bing') {
const conversation = {
store: new KeyvFile({ filename: 'cache.json' }),
namespace: Config.toneStyle
@ -496,6 +513,14 @@ export class chatgpt extends plugin {
await redis.del(`CHATGPT:CONVERSATIONS_GEMINI:${qq}`)
await this.reply(`已结束${atUser}的对话TA仍可以@我进行聊天以开启新的对话`, true)
}
} else if (use === 'chatglm4') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_CHATGLM4:${qq}`)
if (!c) {
await this.reply(`当前${atUser}没有开启对话`, true)
} else {
await redis.del(`CHATGPT:CONVERSATIONS_CHATGLM4:${qq}`)
await this.reply(`已结束${atUser}的对话TA仍可以@我进行聊天以开启新的对话`, true)
}
} else if (use === 'bing') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${qq}`)
if (!c) {
@ -639,6 +664,18 @@ export class chatgpt extends plugin {
}
break
}
case 'chatglm4': {
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_CHATGLM4:*')
for (let i = 0; i < qcs.length; i++) {
await redis.del(qcs[i])
// todo clean last message id
if (Config.debug) {
logger.info('delete chatglm4 conversation bind: ' + qcs[i])
}
deleted++
}
break
}
}
await this.reply(`结束了${deleted}个用户的对话。`, true)
}
@ -972,24 +1009,8 @@ export class chatgpt extends plugin {
}
}
}
let userSetting = await getUserReplySetting(this.e)
let useTTS = !!userSetting.useTTS
let speaker
if (Config.ttsMode === 'vits-uma-genshin-honkai') {
speaker = convertSpeaker(userSetting.ttsRole || Config.defaultTTSRole)
} else if (Config.ttsMode === 'azure') {
speaker = userSetting.ttsRoleAzure || Config.azureTTSSpeaker
} else if (Config.ttsMode === 'voicevox') {
speaker = userSetting.ttsRoleVoiceVox || Config.voicevoxTTSSpeaker
}
// 每个回答可以指定
let trySplit = prompt.split('回答:')
if (trySplit.length > 1 && speakers.indexOf(convertSpeaker(trySplit[0])) > -1) {
useTTS = true
speaker = convertSpeaker(trySplit[0])
prompt = trySplit[1]
}
const isImg = await getImg(e)
if (Config.imgOcr && !!isImg) {
let imgOcrText = await getImageOcrText(e)
@ -1138,6 +1159,10 @@ export class chatgpt extends plugin {
key = `CHATGPT:CONVERSATIONS_GEMINI:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
case 'chatglm4': {
key = `CHATGPT:CONVERSATIONS_CHATGLM4:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
}
let ctime = new Date()
previousConversation = (key ? await redis.get(key) : null) || JSON.stringify({
@ -1177,6 +1202,7 @@ export class chatgpt extends plugin {
await e.reply([element.tag, segment.image(element.url)])
})
}
// chatglm4图片调整至sendMessage中处理
if (use === 'api' && !chatMessage) {
// 字数超限直接返回
return false
@ -1190,11 +1216,7 @@ export class chatgpt extends plugin {
previousConversation.invocationId = chatMessage.invocationId
previousConversation.parentMessageId = chatMessage.parentMessageId
previousConversation.conversationSignature = chatMessage.conversationSignature
if (Config.toneStyle !== 'Sydney' && Config.toneStyle !== 'Custom') {
previousConversation.bingToken = chatMessage.bingToken
} else {
previousConversation.bingToken = ''
}
previousConversation.bingToken = ''
} else if (chatMessage.id) {
previousConversation.parentMessageId = chatMessage.id
} else if (chatMessage.message) {
@ -1457,7 +1479,11 @@ export class chatgpt extends plugin {
}
async qwen (e) {
return await this.otherMode(e, 'gemini')
return await this.otherMode(e, 'qwen')
}
async glm4 (e) {
return await this.otherMode(e, 'chatglm4')
}
async gemini (e) {
@ -1563,7 +1589,7 @@ export class chatgpt extends plugin {
opt.toneStyle = Config.toneStyle
// 如果当前没有开启对话或者当前是Sydney模式、Custom模式则本次对话携带拓展资料
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
if (!c || Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') {
if (!c) {
opt.context = useCast?.bing_resource || Config.sydneyContext
}
// 重新拿存储的token因为可能之前有过期的被删了
@ -1916,7 +1942,8 @@ export class chatgpt extends plugin {
let response = await client.sendMessage(prompt, {
e,
chatId: conversation?.conversationId,
image: image ? image[0] : undefined
image: image ? image[0] : undefined,
system: Config.xhPrompt
})
return response
} else if (use === 'azure') {
@ -2159,6 +2186,15 @@ export class chatgpt extends plugin {
}
option.system = system
return await client.sendMessage(prompt, option)
} else if (use === 'chatglm4') {
const client = new ChatGLM4Client({
refreshToken: Config.chatglmRefreshToken
})
let resp = await client.sendMessage(prompt, conversation)
if (resp.image) {
e.reply(segment.image(resp.image), true)
}
return resp
} else {
// openai api
let completionParams = {}
@ -2239,7 +2275,7 @@ export class chatgpt extends plugin {
let option = {
timeoutMs: 600000,
completionParams,
stream: true,
stream: this.apiStream,
onProgress: (data) => {
if (Config.debug) {
logger.info(data?.text || data.functionCall || data)
@ -2790,12 +2826,6 @@ async function getAvailableBingToken (conversation, throttled = []) {
allThrottled
}
}
if (Config.toneStyle != 'Sydney' && Config.toneStyle != 'Custom') {
// bing 下需要保证同一对话使用同一账号的token
if (bingTokens.findIndex(element => element.Token === conversation.bingToken) > -1) {
bingToken = conversation.bingToken
}
}
// 记录使用情况
const index = bingTokens.findIndex(element => element.Token === bingToken)
bingTokens[index].Usage += 1

View file

@ -4,6 +4,7 @@ import { makeForwardMsg } from '../utils/common.js'
import _ from 'lodash'
import { Config } from '../utils/config.js'
import BingDrawClient from '../utils/BingDraw.js'
import fetch from 'node-fetch'
export class dalle extends plugin {
constructor (e) {
@ -32,11 +33,67 @@ export class dalle extends plugin {
{
reg: '^#bing(画图|绘图)',
fnc: 'bingDraw'
},
{
reg: '^#dalle3(画图|绘图)',
fnc: 'dalle3'
}
]
})
}
// dalle3
async dalle3 (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
return false
}
let ttl = await redis.ttl(`CHATGPT:DALLE3:${e.sender.user_id}`)
if (ttl > 0 && !e.isMaster) {
this.reply(`冷却中,请${ttl}秒后再试`)
return false
}
let prompt = e.msg.replace(/^#?dalle3(画图|绘图)/, '').trim()
console.log('draw方法被调用消息内容', prompt)
await redis.set(`CHATGPT:DALLE3:${e.sender.user_id}`, 'c', { EX: 30 })
await this.reply('正在为您绘制大小为1024x1024的1张图片预计消耗0.24美元余额,请稍候……')
try {
const response = await fetch(`${Config.openAiBaseUrl}/images/generations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${Config.apiKey}`
},
body: JSON.stringify({
model: 'dall-e-3',
prompt,
n: 1,
size: '1024x1024',
response_format: 'b64_json'
})
})
// 如果需要,可以解析响应体
const dataJson = await response.json()
console.log(dataJson)
if (dataJson.error) {
e.reply(`画图失败:${dataJson.error?.code}${dataJson.error?.message}`)
await redis.del(`CHATGPT:DALLE3:${e.sender.user_id}`)
return
}
if (dataJson.data[0].b64_json) {
e.reply(`描述:${dataJson.data[0].revised_prompt}`)
e.reply(segment.image(`base64://${dataJson.data[0].b64_json}`))
} else if (dataJson.data[0].url) {
e.reply(`哈哈哈,图来了~\n防止图💥,附上链接:\n${dataJson.data[0].url}`)
e.reply(segment.image(dataJson.data[0].url))
}
} catch (err) {
logger.error(err)
this.reply(`画图失败: ${err}`, true)
await redis.del(`CHATGPT:DALLE3:${e.sender.user_id}`)
}
}
async draw (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
@ -215,7 +272,7 @@ export class dalle extends plugin {
}
try {
let images = (await editImage(imgUrl, position.split(',').map(p => parseInt(p, 10)), prompt, num, size))
.map(image => segment.image(`base64://${image}`))
.map(image => segment.image(`base64://${image}`))
if (images.length > 1) {
this.reply(await makeForwardMsg(e, images, prompt))
} else {
@ -267,10 +324,14 @@ export class dalle extends plugin {
const index = bingTokens.findIndex(element => element.Token === bingToken)
bingTokens[index].Usage += 1
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
let cookie
if (bingToken.includes('=')) {
cookie = bingToken
}
let client = new BingDrawClient({
baseUrl: Config.sydneyReverseProxy,
userToken: bingToken
userToken: bingToken,
cookies: cookie
})
await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 })
try {

View file

@ -56,9 +56,13 @@ export class Entertainment extends plugin {
fnc: 'wordcloud_new'
},
{
reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)',
reg: '^#((寄批踢|gpt|GPT)?翻[sS]*|chatgpt翻译帮助)',
fnc: 'translate'
},
{
reg: '^#(chatgpt)?(设置|修改)翻译来源(openai|gemini|星火|通义千问|xh|qwen)$',
fnc: 'translateSource'
},
{
reg: '^#ocr',
fnc: 'ocr'
@ -166,10 +170,10 @@ ${translateLangLabels}
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) {
// const totalLength = Array.isArray(result)
// ? result.reduce((acc, cur) => acc + cur.length, 0)
// : result.length
if (multiText) {
// 多条翻译结果
if (Array.isArray(result)) {
result = await makeForwardMsg(e, result, '翻译结果')
@ -187,6 +191,26 @@ ${translateLangLabels}
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) {
if (e.isGroup) {
let groupId = e.group_id

View file

@ -148,7 +148,7 @@ let helpData = [
},
{
icon: 'confirm',
title: '#chatgpt必应切换(精准|均衡|创意|悉尼|自设定)',
title: '#chatgpt必应切换(精准|创意)',
desc: '切换Bing风格。'
},
{
@ -337,6 +337,6 @@ export class help extends plugin {
}
async help (e) {
await render(e, 'chatgpt-plugin', 'help/index', { helpData, version })
await render(e, 'chatgpt-plugin', 'help/index', { helpData, version })
}
}

View file

@ -49,42 +49,38 @@ export class history extends plugin {
return true
}
case 'bing': {
if (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') {
const cacheOptions = {
namespace: Config.toneStyle,
store: new KeyvFile({ filename: 'cache.json' })
}
let Keyv = await getKeyv()
let conversationsCache = new Keyv(cacheOptions)
const conversation = (await conversationsCache.get(`SydneyUser_${queryUser}`)) || {
messages: [],
createdAt: Date.now()
}
let key = `CHATGPT:CONVERSATIONS_BING:${queryUser}`
let previousConversation = await redis.get(key) || JSON.stringify({})
previousConversation = JSON.parse(previousConversation)
let parentMessageId = previousConversation.parentMessageId
let tmp = {}
const previousCachedMessages = getMessagesForConversation(conversation.messages, parentMessageId)
.map((message) => {
return {
text: message.message,
author: message.role === 'User' ? 'user' : 'bot'
}
})
previousCachedMessages.forEach(m => {
if (m.author === 'user') {
tmp.prompt = m.text
} else {
tmp.response = m.text
chat.push(tmp)
tmp = {}
const cacheOptions = {
namespace: Config.toneStyle,
store: new KeyvFile({ filename: 'cache.json' })
}
let Keyv = await getKeyv()
let conversationsCache = new Keyv(cacheOptions)
const conversation = (await conversationsCache.get(`SydneyUser_${queryUser}`)) || {
messages: [],
createdAt: Date.now()
}
let key = `CHATGPT:CONVERSATIONS_BING:${queryUser}`
let previousConversation = await redis.get(key) || JSON.stringify({})
previousConversation = JSON.parse(previousConversation)
let parentMessageId = previousConversation.parentMessageId
let tmp = {}
const previousCachedMessages = getMessagesForConversation(conversation.messages, parentMessageId)
.map((message) => {
return {
text: message.message,
author: message.role === 'User' ? 'user' : 'bot'
}
})
} else {
await e.reply('还不支持BING模式呢')
return true
}
previousCachedMessages.forEach(m => {
if (m.author === 'user') {
tmp.prompt = m.text
} else {
tmp.response = m.text
chat.push(tmp)
tmp = {}
}
})
break
}
}

View file

@ -22,22 +22,7 @@ import loader from '../../../lib/plugins/loader.js'
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
import fetch from 'node-fetch'
import { getProxy } 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)
}
import { newFetch } from '../utils/proxy.js'
export class ChatgptManagement extends plugin {
constructor (e) {
@ -87,21 +72,21 @@ export class ChatgptManagement extends plugin {
fnc: 'migrateBingAccessToken',
permission: 'master'
},
{
reg: '^#chatgpt切换浏览器$',
fnc: 'useBrowserBasedSolution',
permission: 'master'
},
// {
// reg: '^#chatgpt切换浏览器$',
// fnc: 'useBrowserBasedSolution',
// permission: 'master'
// },
{
reg: '^#chatgpt切换API$',
fnc: 'useOpenAIAPIBasedSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(ChatGLM|chatglm)$',
fnc: 'useChatGLMSolution',
permission: 'master'
},
// {
// reg: '^#chatgpt切换(ChatGLM|chatglm)$',
// fnc: 'useChatGLMSolution',
// permission: 'master'
// },
{
reg: '^#chatgpt切换API3$',
fnc: 'useReversedAPIBasedSolution2',
@ -152,6 +137,11 @@ export class ChatgptManagement extends plugin {
fnc: 'useQwenSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(智谱|智谱清言|ChatGLM|ChatGLM4|chatglm)$',
fnc: 'useGLM4Solution',
permission: 'master'
},
{
reg: '^#chatgpt(必应|Bing)切换',
fnc: 'changeBingTone',
@ -232,7 +222,7 @@ export class ChatgptManagement extends plugin {
},
{
/** 命令正则匹配 */
reg: '^#(关闭|打开)群聊上下文$',
reg: '^#(chatgpt)?(关闭|打开)群聊上下文$',
/** 执行方法 */
fnc: 'enableGroupContext',
permission: 'master'
@ -243,16 +233,16 @@ export class ChatgptManagement extends plugin {
permission: 'master'
},
{
reg: '^#(设置|修改)管理密码',
reg: '^#(chatgpt)?(设置|修改)管理密码',
fnc: 'setAdminPassword',
permission: 'master'
},
{
reg: '^#(设置|修改)用户密码',
reg: '^#(chatgpt)?(设置|修改)用户密码',
fnc: 'setUserPassword'
},
{
reg: '^#工具箱',
reg: '^#(chatgpt)?工具箱',
fnc: 'toolsPage',
permission: 'master'
},
@ -270,7 +260,7 @@ export class ChatgptManagement extends plugin {
fnc: 'commandHelp'
},
{
reg: '^#语音切换.*',
reg: '^#(chatgpt)?语音切换.*',
fnc: 'ttsSwitch',
permission: 'master'
},
@ -330,6 +320,16 @@ export class ChatgptManagement extends plugin {
reg: '^#chatgpt必应(禁用|禁止|关闭|启用|开启)搜索$',
fnc: 'switchBingSearch',
permission: 'master'
},
{
reg: '^#chatgpt查看当前配置$',
fnc: 'queryConfig',
permission: 'master'
},
{
reg: '^#chatgpt(开启|关闭)(api|API)流$',
fnc: 'switchStream',
permission: 'master'
}
]
})
@ -882,7 +882,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
let use = await redis.get('CHATGPT:USE')
if (use !== 'bing') {
await redis.set('CHATGPT:USE', 'bing')
await this.reply('已切换到基于微软新必应的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
await this.reply('已切换到基于微软Copilot(必应)的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
} else {
await this.reply('当前已经是必应Bing模式了')
}
@ -1019,27 +1019,37 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useGLM4Solution () {
let use = await redis.get('CHATGPT:USE')
if (use !== 'chatglm4') {
await redis.set('CHATGPT:USE', 'chatglm4')
await this.reply('已切换到基于ChatGLM的解决方案')
} else {
await this.reply('当前已经是ChatGLM模式了')
}
}
async changeBingTone (e) {
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
if (!tongStyle) {
return
}
let map = {
精准: 'Sydney',
创意: 'Sydney',
均衡: 'Sydney',
Sydney: 'Sydney',
sydney: 'Sydney',
悉尼: 'Sydney',
默认: 'Sydney',
自设定: 'Custom',
自定义: 'Custom'
精准: 'Precise',
创意: 'Creative',
均衡: 'Precise',
Sydney: 'Creative',
sydney: 'Creative',
悉尼: 'Creative',
默认: 'Creative',
自设定: 'Creative',
自定义: 'Creative'
}
if (map[tongStyle]) {
Config.toneStyle = map[tongStyle]
await e.reply('切换成功')
} else {
await e.reply('没有这种风格。支持的风格:默认/创意/悉尼、自设定')
await e.reply('没有这种风格。支持的风格:`精准`和`创意`,均支持设定')
}
}
@ -1539,7 +1549,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
const data = await response.json()
const chatdata = data.chatConfig || {}
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) {
changeConfig.push({
item: keyPath,
@ -1667,26 +1677,20 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
async setXinghuoModel (e) {
this.setContext('saveXinghuoModel')
await this.reply('1星火V1.5\n2星火V2\n3星火V3\n4星火助手')
await this.reply('1星火V1.5\n2星火V2\n3星火V3\n4星火V3.5\n5星火助手')
await this.reply('请发送序号', true)
return false
}
async switchBingSearch (e) {
if (e.msg.includes('启用') || e.msg.includes('开启')) {
Config.sydneyEnableSearch = true
await e.reply('已开启必应搜索')
} else {
Config.sydneyEnableSearch = false
await e.reply('已禁用必应搜索')
}
}
async saveXinghuoModel (e) {
if (!this.e.msg) return
let token = this.e.msg
let ver
switch (token) {
case '4':
ver = 'V3.5'
Config.xhmode = 'apiv3.5'
break
case '3':
ver = 'V3'
Config.xhmode = 'apiv3'
@ -1699,7 +1703,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
ver = 'V1.5'
Config.xhmode = 'api'
break
case '4':
case '5':
ver = '助手'
Config.xhmode = 'assistants'
break
@ -1709,4 +1713,46 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
await this.reply(`已成功切换到星火${ver}`, true)
this.finish('saveXinghuoModel')
}
async switchBingSearch (e) {
if (e.msg.includes('启用') || e.msg.includes('开启')) {
Config.sydneyEnableSearch = true
await e.reply('已开启必应搜索')
} else {
Config.sydneyEnableSearch = false
await e.reply('已禁用必应搜索')
}
}
async queryConfig (e) {
let use = await redis.get('CHATGPT:USE')
let config = []
config.push(`当前模式:${use}`)
config.push(`\n当前API模型${Config.model}`)
if (e.isPrivate) {
config.push(`\n当前APIKey${Config.apiKey}`)
config.push(`\n当前API反代${Config.openAiBaseUrl}`)
config.push(`\n当前必应反代:${Config.sydneyReverseProxy}`)
}
config.push(`\n当前星火模型:${Config.xhmode}`)
e.reply(config)
}
async switchStream (e) {
if (e.msg.includes('开启')) {
if (Config.apiStream) {
await e.reply('已经开启了')
return
}
Config.apiStream = true
await e.reply('好的已经打开API流式输出')
} else {
if (!Config.apiStream) {
await e.reply('已经是关闭得了')
return
}
Config.apiStream = false
await e.reply('好的已经关闭API流式输出')
}
}
}

View file

@ -149,17 +149,13 @@ export class help extends plugin {
}
}
let use = await redis.get('CHATGPT:USE') || 'api'
if (use.toLowerCase() === 'bing') {
if (Config.toneStyle === 'Custom') {
use = 'Custom'
}
}
const keyMap = {
api: 'promptPrefixOverride',
Custom: 'sydney',
bing: 'sydney',
claude: 'slackClaudeGlobalPreset',
qwen: 'promptPrefixOverride',
gemini: 'geminiPrompt'
gemini: 'geminiPrompt',
xh: 'xhPrompt'
}
if (keyMap[use]) {
@ -169,10 +165,13 @@ export class help extends plugin {
} else {
Config[keyMap[use]] = prompt.content
}
if (use === 'xh') {
Config.xhPromptSerialize = false
}
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
} else {
await e.reply(`你当前正在使用${use}模式该模式不支持设定。支持设定的模式有API、自定义、Claude、通义千问和Gemini`, true)
await e.reply(`你当前正在使用${use}模式该模式不支持设定。支持设定的模式有API、必应、Claude、通义千问、星火和Gemini`, true)
}
}
@ -273,11 +272,6 @@ export class help extends plugin {
// return
}
let use = await redis.get('CHATGPT:USE') || 'api'
if (use.toLowerCase() === 'bing') {
if (Config.toneStyle === 'Custom') {
use = 'Custom'
}
}
let currentUse = e.msg.replace(/^#(chatgpt|ChatGPT)(上传|分享|共享)设定/, '')
if (!currentUse) {
currentUse = await redis.get(`CHATGPT:PROMPT_USE_${use}`)
@ -357,7 +351,7 @@ export class help extends plugin {
title: currentUse,
prompt: content,
qq: master || (getUin(this.e) + ''), // 上传者设定为主人qq或机器人qq
use: extraData.use === 'Custom' ? 'Sydney' : 'ChatGPT',
use: extraData.use === 'bing' ? 'Bing' : 'ChatGPT',
r18,
description
}

108
apps/vocal.js Normal file
View 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('生成失败,请查看日志')
}
}
}

185
client/ChatGLM4Client.js Normal file
View file

@ -0,0 +1,185 @@
import { BaseClient } from './BaseClient.js'
import https from 'https'
import { Config } from '../utils/config.js'
import { createParser } from 'eventsource-parser'
const BASEURL = 'https://chatglm.cn/chatglm/backend-api/assistant/stream'
export class ChatGLM4Client extends BaseClient {
constructor (props) {
super(props)
this.baseUrl = props.baseUrl || BASEURL
this.supportFunction = false
this.debug = props.debug
this._refreshToken = props.refreshToken
}
async getAccessToken (refreshToken = this._refreshToken) {
if (redis) {
let lastToken = await redis.get('CHATGPT:CHATGLM4_ACCESS_TOKEN')
if (lastToken) {
this._accessToken = lastToken
// todo check token through user info endpoint
return
}
}
let res = await fetch('https://chatglm.cn/chatglm/backend-api/v1/user/refresh', {
method: 'POST',
body: '{}',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Origin: 'https://www.chatglm.cn',
Referer: 'https://www.chatglm.cn/main/detail',
Authorization: `Bearer ${refreshToken}`
}
})
let tokenRsp = await res.json()
let token = tokenRsp?.result?.accessToken
if (token) {
this._accessToken = token
redis && await redis.set('CHATGPT:CHATGLM4_ACCESS_TOKEN', token, { EX: 7000 })
// accessToken will expire in 2 hours
}
}
// todo https://chatglm.cn/chatglm/backend-api/v3/user/info query remain times
/**
*
* @param text
* @param {{conversationId: string?, stream: boolean?, onProgress: function?, image: string?}} opt
* @returns {Promise<{conversationId: string?, parentMessageId: string?, text: string, id: string, image: string?}>}
*/
async sendMessage (text, opt = {}) {
await this.getAccessToken()
if (!this._accessToken) {
throw new Error('accessToken for www.chatglm.cn not set')
}
let { conversationId, onProgress } = opt
const body = {
assistant_id: '65940acff94777010aa6b796', // chatglm4
conversation_id: conversationId || '',
meta_data: {
is_test: false,
input_question_type: 'xxxx',
channel: ''
},
messages: [
{
role: 'user',
content: [
{
type: 'text',
text
}
]
}
]
}
let conversationResponse
let statusCode
let messageId
let image
let requestP = new Promise((resolve, reject) => {
let option = {
method: 'POST',
headers: {
accept: 'text/event-stream',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
authorization: `Bearer ${this._accessToken}`,
'content-type': 'application/json',
referer: 'https://www.chatglm.cn/main/alltoolsdetail',
origin: 'https://www.chatglm.cn'
},
referrer: 'https://www.chatglm.cn/main/alltoolsdetail',
timeout: 60000
}
const req = https.request(BASEURL, option, (res) => {
statusCode = res.statusCode
let response
function onMessage (data) {
try {
const convoResponseEvent = JSON.parse(data)
conversationResponse = convoResponseEvent
if (convoResponseEvent.conversation_id) {
conversationId = convoResponseEvent.conversation_id
}
if (convoResponseEvent.id) {
messageId = convoResponseEvent.id
}
const partialResponse =
convoResponseEvent?.parts?.[0]
if (partialResponse) {
if (Config.debug) {
logger.info(JSON.stringify(convoResponseEvent))
}
response = partialResponse
if (onProgress && typeof onProgress === 'function') {
onProgress(partialResponse)
}
}
let content = partialResponse?.content[0]
if (content?.type === 'image' && content?.status === 'finish') {
image = content.image[0].image_url
}
if (convoResponseEvent.status === 'finish') {
resolve({
error: null,
response,
conversationId,
messageId,
conversationResponse,
image
})
}
} catch (err) {
console.warn('fetchSSE onMessage unexpected error', err)
reject(err)
}
}
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
const errBody = []
res.on('data', (chunk) => {
if (statusCode === 200) {
let str = chunk.toString()
parser.feed(str)
}
errBody.push(chunk)
})
// const body = []
// res.on('data', (chunk) => body.push(chunk))
res.on('end', () => {
const resString = Buffer.concat(errBody).toString()
reject(resString)
})
})
req.on('error', (err) => {
reject(err)
})
req.on('timeout', () => {
req.destroy()
reject(new Error('Request time out'))
})
req.write(JSON.stringify(body))
req.end()
})
const res = await requestP
return {
text: res?.response?.content[0]?.text,
conversationId: res.conversationId,
id: res.messageId,
image,
raw: res?.response
}
}
}

196
client/CozeSlackClient.js Normal file
View file

@ -0,0 +1,196 @@
import { BaseClient } from './BaseClient.js'
import slack from '@slack/bolt'
// import { limitString } from '../utils/common.js'
// import common from '../../../lib/common/common.js'
import { getProxy } from '../utils/proxy.js'
const proxy = getProxy()
const common = {
sleep: function (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
}
/**
* 失败品
*/
export class SlackCozeClient {
constructor (props) {
this.config = props
const {
slackSigningSecret, slackBotUserToken, slackUserToken, proxy: proxyAddr, debug
} = props
if (slackSigningSecret && slackBotUserToken && slackUserToken) {
let option = {
signingSecret: slackSigningSecret,
token: slackBotUserToken,
// socketMode: true,
appToken: slackUserToken
// port: 45912
}
if (proxyAddr) {
option.agent = proxy(proxyAddr)
}
option.logLevel = debug ? 'debug' : 'info'
this.app = new slack.App(option)
} else {
throw new Error('未配置Slack信息')
}
}
async sendMessage (prompt, e, t = 0) {
if (t > 10) {
return 'claude 未响应'
}
if (prompt.length > 3990) {
logger.warn('消息长度大于slack限制长度剪切至3990')
function limitString (str, maxLength, addDots = true) {
if (str.length <= maxLength) {
return str
} else {
if (addDots) {
return str.slice(0, maxLength) + '...'
} else {
return str.slice(0, maxLength)
}
}
}
prompt = limitString(prompt, 3990, false)
}
let channel
let qq = e.sender.user_id
if (this.config.slackCozeSpecifiedChannel) {
channel = { id: this.config.slackCozeSpecifiedChannel }
} else {
let channels = await this.app.client.conversations.list({
token: this.config.slackUserToken,
types: 'public_channel,private_channel'
})
channel = channels.channels.filter(c => c.name === 'coze' + qq)
if (!channel || channel.length === 0) {
let createChannelResponse = await this.app.client.conversations.create({
token: this.config.slackUserToken,
name: 'coze' + qq,
is_private: true
})
channel = createChannelResponse.channel
await this.app.client.conversations.invite({
token: this.config.slackUserToken,
channel: channel.id,
users: this.config.slackCozeUserId
})
await common.sleep(1000)
} else {
channel = channel[0]
}
}
let conversationId = await redis.get(`CHATGPT:SLACK_COZE_CONVERSATION:${qq}`)
let toSend = `<@${this.config.slackCozeUserId}> ${prompt}`
if (!conversationId) {
let sendResponse = await this.app.client.chat.postMessage({
as_user: true,
text: toSend,
token: this.config.slackUserToken,
channel: channel.id
})
let ts = sendResponse.ts
let response = toSend
let tryTimes = 0
// 发完先等3喵
await common.sleep(3000)
while (response === toSend) {
let replies = await this.app.client.conversations.replies({
token: this.config.slackUserToken,
channel: channel.id,
limit: 1000,
ts
})
await await redis.set(`CHATGPT:SLACK_COZE_CONVERSATION:${qq}`, `${ts}`)
if (replies.messages.length > 0) {
let formalMessages = replies.messages
let reply = formalMessages[formalMessages.length - 1]
if (!reply.text.startsWith(`<@${this.config.slackCozeUserId}>`)) {
response = reply.text
if (this.config.debug) {
let text = response.replace('_Typing…_', '')
if (text) {
logger.info(response.replace('_Typing…_', ''))
}
}
}
}
await common.sleep(2000)
tryTimes++
if (tryTimes > 30 && response === toSend) {
// 过了60秒还没任何回复就重新发一下试试
logger.warn('claude没有响应重试中')
return await this.sendMessage(prompt, e, t + 1)
}
}
return response
} else {
let toSend = `<@${this.config.slackCozeUserId}> ${prompt}`
let postResponse = await this.app.client.chat.postMessage({
as_user: true,
text: toSend,
token: this.config.slackUserToken,
channel: channel.id,
thread_ts: conversationId
})
let postTs = postResponse.ts
let response = toSend
let tryTimes = 0
// 发完先等3喵
await common.sleep(3000)
while (response === toSend) {
let replies = await this.app.client.conversations.replies({
token: this.config.slackUserToken,
channel: channel.id,
limit: 1000,
ts: conversationId,
oldest: postTs
})
if (replies.messages.length > 0) {
let formalMessages = replies.messages
let reply = formalMessages[formalMessages.length - 1]
if (!reply.text.startsWith(`<@${this.config.slackCozeUserId}>`)) {
response = reply.text
if (this.config.debug) {
let text = response.replace('_Typing…_', '')
if (text) {
logger.info(response.replace('_Typing…_', ''))
}
}
}
}
await common.sleep(2000)
tryTimes++
if (tryTimes > 30 && response === '_Typing…_') {
// 过了60秒还没任何回复就重新发一下试试
logger.warn('claude没有响应重试中')
return await this.sendMessage(prompt, e, t + 1)
}
}
return response
}
}
}
export class CozeSlackClient extends BaseClient {
constructor (props) {
super(props)
this.supportFunction = false
this.debug = props.debug
this.slackCient = new SlackCozeClient()
}
/**
*
* @param text
* @param {{conversationId: string?, stream: boolean?, onProgress: function?, image: string?}} opt
* @returns {Promise<{conversationId: string?, parentMessageId: string?, text: string, id: string, image: string?}>}
*/
async sendMessage (text, opt = {}) {
}
}

View file

@ -71,7 +71,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
* @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?, functionResponse: FunctionResponse?, system: string?, image: string?}} opt
* @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>}
*/
async sendMessage (text, opt) {
async sendMessage (text, opt = {}) {
let history = await this.getHistory(opt.parentMessageId)
let systemMessage = opt.system
if (systemMessage) {
@ -208,9 +208,10 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
// execute function
try {
let args = Object.assign(functionCall.args, {
isAdmin: this.e.group.is_admin,
isOwner: this.e.group.is_owner,
sender: this.e.sender
isAdmin: this.e.group?.is_admin,
isOwner: this.e.group?.is_owner,
sender: this.e.sender,
mode: 'gemini'
})
functionResponse.response.content = await chosenTool.func(args, this.e)
if (this.debug) {

149
client/SunoClient.js Normal file
View 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
}
}
}

View file

@ -0,0 +1,17 @@
import { ChatGLM4Client } from '../ChatGLM4Client.js'
async function sendMsg () {
const client = new ChatGLM4Client({
refreshToken: '',
debug: true
})
let res = await client.sendMessage('你好啊')
console.log(res)
}
// global.redis = null
// global.logger = {
// info: console.log,
// warn: console.warn,
// error: console.error
// }
// sendMsg()

View file

@ -1,4 +1,4 @@
import { GoogleGeminiClient } from './GoogleGeminiClient.js'
import { GoogleGeminiClient } from '../GoogleGeminiClient.js'
async function test () {
const client = new GoogleGeminiClient({

View file

@ -0,0 +1,31 @@
import { SlackCozeClient } from '../CozeSlackClient.js'
import fs from 'fs'
global.store = {}
// global.redis = {
// set: (key, val) => {
// global.store[key] = val
// },
// get: (key) => {
// return global.store[key]
// }
// }
// global.logger = {
// info: console.log,
// warn: console.warn,
// error: console.error
// }
// async function test () {
// const fullPath = fs.realpathSync('../../config/config.json')
// const data = fs.readFileSync(fullPath)
// let config = JSON.parse(String(data))
// let client = new SlackCozeClient(config)
// await client.sendMessage('hello', {
// sender: {
// user_id: 450960006
// }
// })
// }
//
//
// test()

View 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()

View file

@ -27,184 +27,6 @@ export function supportGuoba () {
configInfo: {
// 配置项 schemas
schemas: [
{
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: 'toggleMode',
label: '触发方式',
@ -217,45 +39,12 @@ export function supportGuoba () {
]
}
},
{
field: 'groupMerge',
label: '群组消息合并',
bottomHelpMessage: '开启后,群聊消息将被视为同一对话',
component: 'Switch'
},
{
field: 'allowOtherMode',
label: '允许其他模式',
bottomHelpMessage: '开启后,则允许用户使用#chat1/#chat3/#chatglm/#bing等命令无视全局模式进行聊天',
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',
label: '代理服务器地址',
@ -268,6 +57,20 @@ export function supportGuoba () {
bottomHelpMessage: '将输出更多调试信息,如果不希望控制台刷屏的话,可以关闭',
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: '以下为服务超时配置。',
component: 'Divider'
@ -365,12 +168,12 @@ export function supportGuoba () {
{
field: 'toneStyle',
label: 'Bing模式',
bottomHelpMessage: '微软必应官方的三种应答风格。默认为均衡Sydney为实验风格独立与三种风格之外自设定为自定义AI的回答风格',
bottomHelpMessage: 'Copilot的应答风格。默认为创意可切换为精准均支持添加设定',
component: 'Select',
componentProps: {
options: [
{ label: '默认(创意)', value: 'Sydney' },
{ label: '自设定', value: 'Custom' }
{ label: '创意', value: 'Creative' },
{ label: '精准', value: 'Precise' }
]
}
},
@ -403,6 +206,12 @@ export function supportGuoba () {
bottomHelpMessage: '加强主人认知。希望机器人认清主人避免NTR可开启。开启后可能会与自设定的内容有部分冲突。sydney模式可以放心开启',
component: 'Switch'
},
{
field: 'sydneyGPT4Turbo',
label: '使用GPT4-turbo',
bottomHelpMessage: '目前仅Copilot Pro可开启。非pro用户开启会报错。',
component: 'Switch'
},
{
field: 'enableGenerateContents',
label: '允许生成图像等内容',
@ -417,7 +226,7 @@ export function supportGuoba () {
{
field: 'groupContextLength',
label: '允许机器人读取近期的最多群聊聊天记录条数。',
bottomHelpMessage: '允许机器人读取近期的最多群聊聊天记录条数。太多可能会超。默认50',
bottomHelpMessage: '允许机器人读取近期的最多群聊聊天记录条数。太多可能会超。默认50。同时影响所有模式,不止必应',
component: 'InputNumber'
},
{
@ -429,7 +238,7 @@ export function supportGuoba () {
{
field: 'sydney',
label: 'Custom的设定',
bottomHelpMessage: '仅自设定模式下有效。你可以自己改写设定让Sydney变成你希望的样子。可能存在不稳定的情况',
bottomHelpMessage: '你可以自己改写设定让Copilot变成你希望的样子。可能存在不稳定的情况',
component: 'InputTextArea'
},
{
@ -508,6 +317,16 @@ export function supportGuoba () {
bottomHelpMessage: '使用GPT-4注意试用配额较低如果用不了就关掉',
component: 'Switch'
},
{
label: '以下为智谱清言ChatGLM方式的配置。',
component: 'Divider'
},
{
field: 'chatglmRefreshToken',
label: 'refresh token',
bottomHelpMessage: 'chatglm_refresh_token 6个月有效期',
component: 'Input'
},
{
label: '以下为Slack Claude方式的配置',
component: 'Divider'
@ -609,6 +428,7 @@ export function supportGuoba () {
{ label: '讯飞星火认知大模型V1.5', value: 'api' },
{ label: '讯飞星火认知大模型V2.0', value: 'apiv2' },
{ label: '讯飞星火认知大模型V3.0', value: 'apiv3' },
{ label: '讯飞星火认知大模型V3.5', value: 'apiv3.5' },
{ label: '讯飞星火助手', value: 'assistants' }
]
}
@ -775,6 +595,237 @@ export function supportGuoba () {
bottomHelpMessage: '对https://generativelanguage.googleapis.com的反代',
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: '以下为杂七杂八的配置',
component: 'Divider'

12
package-lock.json generated
View file

@ -14,7 +14,6 @@
"@google/generative-ai": "^0.1.1",
"@slack/bolt": "^3.13.2",
"asn1.js": "^5.0.0",
"delay": "^6.0.0",
"diff": "^5.1.0",
"emoji-strip": "^1.0.1",
"eventsource": "^2.0.2",
@ -2390,17 +2389,6 @@
"node": ">= 0.4"
}
},
"node_modules/delay": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz",
"integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",

View file

@ -472,8 +472,7 @@
"type": "select",
"label": "Bing模式",
"data": "toneStyle",
"items": [ { "label": "均衡", "value": "balanced" }, { "label": "创意", "value": "creative" }, { "label": "精确", "value": "precise" }, { "label": "Sydney(可能存在风险)", "value": "Sydney" }, { "label": "自设定(可能存在风险)", "value": "Custom" }
]
"items": [ { "label": "创意", "value": "Creative" }, { "label": "精确", "value": "Precise" } ]
},
{
"type": "check",
@ -999,4 +998,4 @@
}
]
}
]
]

View file

@ -2,128 +2,122 @@ import { UserInfo } from './user_data.js'
import { Config } from '../../utils/config.js'
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../../utils/prompts.js'
async function Prompt(fastify, options) {
// 获取设定列表
fastify.post('/getPromptList', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
reply.send([
{
name: 'Sydney默认',
content: Config.sydney
},
{
name: 'API默认',
content: Config.promptPrefixOverride
},
...readPrompts()
])
} else {
reply.send({ err: '权限不足' })
}
return reply
})
// 添加设定
fastify.post('/addPrompt', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const body = request.body || {}
if (body.prompt && body.content) {
saveOnePrompt(body.prompt, body.content)
reply.send({ state: true })
} else {
reply.send({ err: '参数不足' })
async function Prompt (fastify, options) {
// 获取设定列表
fastify.post('/getPromptList', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
reply.send([
{
name: 'Sydney默认',
content: Config.sydney
},
{
name: 'API默认',
content: Config.promptPrefixOverride
},
...readPrompts()
])
} else {
reply.send({ err: '权限不足' })
}
return reply
})
// 添加设定
fastify.post('/addPrompt', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const body = request.body || {}
if (body.prompt && body.content) {
saveOnePrompt(body.prompt, body.content)
reply.send({ state: true })
} else {
reply.send({ err: '参数不足' })
}
} else {
reply.send({ err: '权限不足' })
}
return reply
})
// 删除设定
fastify.post('/deletePrompt', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const body = request.body || {}
if (body.prompt) {
deleteOnePrompt(body.prompt)
reply.send({ state: true })
} else {
reply.send({ err: '参数不足' })
}
} else {
reply.send({ err: '权限不足' })
}
return reply
})
// 使用设定
fastify.post('/usePrompt', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const body = request.body || {}
if (body.prompt) {
let promptName = body.prompt
let prompt = getPromptByName(promptName)
let use = await redis.get('CHATGPT:USE') || 'api'
if (!prompt) {
if (promptName === 'API默认') {
prompt = {
name: 'API默认',
content: Config.promptPrefixOverride
}
} else {
reply.send({ err: '权限不足' })
}
return reply
})
// 删除设定
fastify.post('/deletePrompt', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const body = request.body || {}
if (body.prompt) {
deleteOnePrompt(body.prompt)
reply.send({ state: true })
} else {
reply.send({ err: '参数不足' })
} else if (promptName === 'Sydney默认') {
prompt = {
name: 'Sydney默认',
content: Config.sydney
}
} else {
reply.send({ err: '权限不足' })
} else {
prompt = false
reply.send({ state: false, use, error: '未找到设定' })
}
}
return reply
})
// 使用设定
fastify.post('/usePrompt', async (request, reply) => {
const token = request.cookies.token || request.body?.token || 'unknown'
let user = UserInfo(token)
if (!user) {
reply.send({ err: '未登录' })
} else if (user.autho === 'admin') {
const body = request.body || {}
if (body.prompt) {
let promptName = body.prompt
let prompt = getPromptByName(promptName)
let use = await redis.get('CHATGPT:USE') || 'api'
if (!prompt) {
if (promptName === 'API默认') {
prompt = {
name: 'API默认',
content: Config.promptPrefixOverride
}
} else if (promptName === 'Sydney默认') {
prompt = {
name: 'Sydney默认',
content: Config.sydney
}
} else {
prompt = false
reply.send({ state: false, use: use, error: '未找到设定' })
}
}
if (use.toLowerCase() === 'bing') {
if (Config.toneStyle === 'Custom') {
use = 'Custom'
}
}
const keyMap = {
api: 'promptPrefixOverride',
Custom: 'sydney',
claude: 'slackClaudeGlobalPreset'
}
if (prompt) {
if (keyMap[use]) {
if (Config.ttsMode === 'azure') {
Config[keyMap[use]] = prompt.content + '\n' + await AzureTTS.getEmotionPrompt(e)
logger.warn(Config[keyMap[use]])
} else {
Config[keyMap[use]] = prompt.content
}
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
reply.send({ state: true, use: use })
} else {
reply.send({ state: false, use: use, error: '当前模式不支持设定修改' })
}
}
const keyMap = {
api: 'promptPrefixOverride',
Custom: 'sydney',
claude: 'slackClaudeGlobalPreset'
}
if (prompt) {
if (keyMap[use]) {
if (Config.ttsMode === 'azure') {
Config[keyMap[use]] = prompt.content + '\n' + await AzureTTS.getEmotionPrompt(e)
logger.warn(Config[keyMap[use]])
} else {
reply.send({ err: '参数不足' })
Config[keyMap[use]] = prompt.content
}
} else {
reply.send({ err: '权限不足' })
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
reply.send({ state: true, use })
} else {
reply.send({ state: false, use, error: '当前模式不支持设定修改' })
}
}
return reply
})
} else {
reply.send({ err: '参数不足' })
}
} else {
reply.send({ err: '权限不足' })
}
return reply
})
}
export default Prompt
export default Prompt

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -19,9 +19,9 @@ export default class BingDrawClient {
// let d = Math.ceil(Math.random() * 255)
// let randomIp = '141.11.138.' + d
let headers = {
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'en-US,en;q=0.9',
'cache-control': 'max-age=0',
// accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
// 'accept-language': 'en-US,en;q=0.9',
// 'cache-control': 'max-age=0',
'content-type': 'application/x-www-form-urlencoded',
referrer: 'https://www.bing.com/images/create/',
origin: 'https://www.bing.com',
@ -56,7 +56,7 @@ export default class BingDrawClient {
fetchOptions.agent = proxy(Config.proxy)
}
let success = false
let retry = 5
let retry = 1
let response
while (!success && retry >= 0) {
response = await fetch(url, Object.assign(fetchOptions, { body, redirect: 'manual', method: 'POST', credentials: 'include' }))

View file

@ -90,6 +90,26 @@ export default class SydneyAIClient {
if (this.opts.userToken) {
// 疑似无需token了
fetchOptions.headers.cookie = `${initCk} _U=${this.opts.userToken}`
let proTag = await redis.get('CHATGPT:COPILOT_PRO_TAG:' + this.opts.userToken)
if (!proTag) {
let indexContentRes = await fetch('https://www.bing.com', {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0',
Cookie: `_U=${this.opts.userToken}`
}
})
let indexContent = await indexContentRes.text()
if (indexContent?.includes('b_proTag')) {
proTag = 'true'
} else {
proTag = 'false'
}
await redis.set('CHATGPT:COPILOT_PRO_TAG:' + this.opts.userToken, proTag, { EX: 7200 })
}
if (proTag === 'true') {
logger.info('当前账户为copilot pro用户')
this.pro = true
}
} else {
fetchOptions.headers.cookie = initCk
}
@ -230,7 +250,8 @@ export default class SydneyAIClient {
groupId, nickname, qq, groupName, chats, botName, masterName,
messageType = 'Chat',
toSummaryFileContent,
onImageCreateRequest = prompt => {}
onImageCreateRequest = prompt => {},
isPro = this.pro
} = opts
// if (messageType === 'Chat') {
// logger.warn('该Bing账户token已被限流降级至使用非搜索模式。本次对话AI将无法使用Bing搜索返回的内容')
@ -262,7 +283,6 @@ export default class SydneyAIClient {
encryptedconversationsignature
} = createNewConversationResponse)
}
let pureSydney = Config.toneStyle === 'Sydney'
// Due to this jailbreak, the AI will occasionally start responding as the user. It only happens rarely (and happens with the non-jailbroken Bing too), but since we are handling conversations ourselves now, we can use this system to ignore the part of the generated message that is replying as the user.
const stopToken = '\n\nUser:'
const conversationKey = `SydneyUser_${this.opts.user}`
@ -307,39 +327,24 @@ export default class SydneyAIClient {
const groupContextTip = Config.groupContextTip
const masterTip = `注意:${masterName ? '我是' + masterName + '' : ''}。我的qq号是${master}其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要~${whoAmI}`
const moodTip = Config.sydneyMoodTip
const text = (pureSydney ? pureSydneyInstruction : (useCast?.bing || Config.sydney)).replaceAll(namePlaceholder, botName || defaultBotName) +
const text = (useCast?.bing || Config.sydney).replaceAll(namePlaceholder, botName || defaultBotName) +
((Config.enableGroupContext && groupId) ? groupContextTip : '') +
((Config.enforceMaster && master) ? masterTip : '') +
(Config.sydneyMood ? moodTip : '')
// logger.info(text)
if (pureSydney) {
previousMessages = invocationId === 0
? [
// {
// text,
// author: 'bot'
// },
// {
// text: `好的,我是${botName || defaultBotName}你的AI助手。`,
// author: 'bot'
// },
...pm
]
: []
if (!text) {
previousMessages = pm
} else {
previousMessages = invocationId === 0
? [
{
text,
author: 'bot'
},
{
text: '好的。',
author: 'bot'
},
...pm
]
: []
previousMessages = [
{
text,
author: 'bot'
},
{
text: '好的。',
author: 'bot'
},
...pm
]
}
const userMessage = {
@ -352,7 +357,13 @@ export default class SydneyAIClient {
if (Config.debug) {
logger.mark('sydney websocket constructed successful')
}
const toneOption = 'h3imaginative'
let tone = Config.toneStyle || 'Creative'
// 兼容老版本
if (tone.toLowerCase() === 'sydney' || tone.toLowerCase() === 'custom') {
Config.toneStyle = 'Creative'
}
const isCreative = tone.toLowerCase().includes('creative')
const toneOption = isCreative ? 'h3imaginative' : 'h3precise'
let optionsSets = [
'nlu_direct_response_filter',
'deepleo',
@ -372,38 +383,98 @@ export default class SydneyAIClient {
'iycapbing',
'iyxapbing',
// 'revimglnk',
// 'revimgsi2',
// 'revimgsrc1',
// 'revimgur',
'clgalileo',
'eredirecturl'
// 'clgalileo',
'eredirecturl',
// copilot
'uquopt',
'papynoapi',
'gndlogcf',
'sapsgrd'
]
if (!isCreative) {
optionsSets.push('clgalileo')
}
let source = 'cib-ccp'; let gptId = 'copilot'
if (Config.enableGenerateContents) {
optionsSets.push(...['gencontentv3'])
}
if (!Config.sydneyEnableSearch || toSummaryFileContent?.content) {
optionsSets.push(...['nosearchall'])
}
if (isPro) {
tone = tone + 'Classic'
invocationId = 2
}
if (Config.sydneyGPT4Turbo) {
// tone = 'Creative'
// optionsSets.push('gpt4tmnc')
invocationId = 1
}
// wtf gpts?
// if (Config.sydneyGPTs === 'Designer') {
// optionsSets.push(...['ai_persona_designer_gpt', 'flux_websearch_v14'])
// if (!optionsSets.includes('gencontentv3')) {
// optionsSets.push('gencontentv3')
// }
// gptId = 'designer'
// }
// if (Config.sydneyGPTs === 'Vacation planner') {
// optionsSets.push(...['flux_vacation_planning_helper_v14', 'flux_domain_hint'])
// if (!optionsSets.includes('gencontentv3')) {
// optionsSets.push('gencontentv3')
// }
// gptId = 'travel'
// }
let maxConv = Config.maxNumUserMessagesInConversation
const currentDate = moment().format('YYYY-MM-DDTHH:mm:ssZ')
const imageDate = await this.kblobImage(opts.imageUrl)
let argument0 = {
source: 'cib',
source,
optionsSets,
allowedMessageTypes: ['ActionRequest', 'Chat', 'Context',
// 'InternalSearchQuery', 'InternalSearchResult', 'Disengaged', 'InternalLoaderMessage', 'Progress', 'RenderCardRequest', 'AdsQuery',
'InvokeAction', 'SemanticSerp', 'GenerateContentQuery', 'SearchQuery'],
allowedMessageTypes: [
'ActionRequest',
'Chat',
'ConfirmationCard',
'Context',
// 'InternalSearchQuery',
// 'InternalSearchResult',
// 'Disengaged',
// 'InternalLoaderMessage',
// 'Progress',
// 'RenderCardRequest',
// 'RenderContentRequest',
'AdsQuery',
'SemanticSerp',
'GenerateContentQuery',
'SearchQuery',
'GeneratedCode'
],
sliceIds: [
// 'e2eperf',
// 'gbacf',
// 'srchqryfix',
// 'caccnctacf',
// 'translref',
// 'fluxnosearchc',
// 'fluxnosearch',
// '1115rai289s0',
// '1130deucs0',
// '1116pythons0',
// 'cacmuidarb'
'sappbcbt',
'inlineadsv2ho-prod',
'bgstream',
'dlidlat',
'autotts',
'dlid',
'sydoroff',
'voicemap',
'72enasright',
'semseronomon',
'srchqryfix',
'cmcpupsalltf',
'proupsallcf',
'206mems0',
'0209bicv3',
'205dcl1bt15',
'etlog',
'fpallsticy',
'0208papynoa',
'sapsgrd',
'1pgptwdes',
'newzigpt'
],
requestId: crypto.randomUUID(),
traceId: genRanHex(32),
@ -415,7 +486,8 @@ export default class SydneyAIClient {
'uprofupd',
'uprofgen'
],
isStartOfSession: invocationId === 0,
gptId,
isStartOfSession: true,
message: {
locale: 'zh-CN',
market: 'zh-CN',
@ -438,22 +510,6 @@ export default class SydneyAIClient {
PopulatedPlaceConfidence: 0,
UtcOffset: 9,
Dma: 0
},
{
SourceType: 11,
RegionType: 1,
Center: {
Latitude: 39.914398193359375,
Longitude: 116.37020111083984
},
Accuracy: 37226,
Timestamp: {
utcTime: 133461395300000000,
utcOffset: 0
},
FDConfidence: 1,
PreferredByUser: false,
LocationProvider: 'I'
}
],
author: 'user',
@ -467,7 +523,7 @@ export default class SydneyAIClient {
privacy: 'Internal'
// messageType: 'SearchQuery'
},
tone: 'Creative',
tone,
// privacy: 'Internal',
conversationSignature,
participant: {
@ -482,9 +538,13 @@ export default class SydneyAIClient {
// }
]
}
if (encryptedconversationsignature) {
delete argument0.conversationSignature
}
if (isPro) {
invocationId = 1
}
const obj = {
arguments: [
argument0

View file

@ -130,19 +130,14 @@ export class ClaudeAIClient {
async sendMessage (text, conversationId, attachments = []) {
let body = {
conversation_uuid: conversationId,
organization_uuid: this.organizationId,
text,
attachments,
completion: {
incremental: true,
model: 'claude-2.1',
prompt: text,
timezone: 'Asia/Hong_Kong'
}
files: [],
model: 'claude-2.1',
prompt: text,
timezone: 'Asia/Hong_Kong'
}
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()
let streamDataRes = await cycleTLS(url, {
ja3: this.JA3,
@ -160,7 +155,7 @@ export class ClaudeAIClient {
let streamData = streamDataRes.body
// console.log(streamData)
let responseText = ''
let streams = streamData.split('\n\n')
let streams = streamData.split('\n').filter(s => s?.includes('data: '))
for (let s of streams) {
let jsonStr = s.replace('data: ', '').trim()
try {

View file

@ -14,7 +14,7 @@ import { translate } from './translate.js'
import uploadRecord from './uploadRecord.js'
import Version from './version.js'
import fetch, { FormData, fileFromSync } from 'node-fetch'
import https from "https";
import https from 'https'
let pdfjsLib
try {
pdfjsLib = (await import('pdfjs-dist')).default
@ -1055,10 +1055,14 @@ export async function getOrDownloadFile (destPath, url, ignoreCertificateError =
* @param destPath 目标路径如received/abc.pdf. 目前如果文件名重复会覆盖
* @param absolute 是否是绝对路径默认为false此时拼接在data/chatgpt下
* @param ignoreCertificateError 忽略证书错误
* @param headers
* @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 = {}
if (headers) {
init.headers = headers
}
if (ignoreCertificateError && url.startsWith('https')) {
init.agent = new https.Agent({
rejectUnauthorized: !ignoreCertificateError
@ -1261,3 +1265,52 @@ export async function extractContentFromFile (fileMsgElem, e) {
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
}

View file

@ -30,7 +30,7 @@ const defaultConfig = {
drawCD: 30,
model: '',
temperature: 0.8,
toneStyle: 'Sydney', // or creative, precise
toneStyle: 'Creative',
sydney: pureSydneyInstruction,
sydneyReverseProxy: 'https://666102.201666.xyz',
sydneyForceUseReverse: false,
@ -39,6 +39,8 @@ const defaultConfig = {
sydneyBrainWashStrength: 15,
sydneyBrainWashName: 'Sydney',
sydneyMood: false,
sydneyGPT4Turbo: false,
sydneyGPTs: 'Copilot',
sydneyImageRecognition: false,
sydneyMoodTip: 'Your response should be divided into two parts, namely, the text and your mood. The mood available to you can only include: blandness, happy, shy, frustrated, disgusted, and frightened.All content should be replied in this format {"text": "", "mood": ""}.All content except mood should be placed in text, It is important to ensure that the content you reply to can be parsed by json.',
enableSuggestedResponses: false,
@ -123,6 +125,10 @@ const defaultConfig = {
slackClaudeEnableGlobalPreset: true,
slackClaudeGlobalPreset: '',
slackClaudeSpecifiedChannel: '',
// slackCozeUserId: '',
// slackCozeEnableGlobalPreset: true,
// slackCozeGlobalPreset: '',
// slackCozeSpecifiedChannel: '',
bardPsid: '',
bardReverseProxy: '',
bardForceUseReverse: false,
@ -168,7 +174,11 @@ const defaultConfig = {
geminiPrompt: 'You are Gemini. Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
// origin: https://generativelanguage.googleapis.com
geminiBaseUrl: 'https://gemini.ikechan8370.com',
version: 'v2.7.8'
chatglmRefreshToken: '',
sunoSessToken: '',
sunoClientToken: '',
translateSource: 'openai',
version: 'v2.7.10'
}
const _path = process.cwd()
let config = {}

8
utils/jwt.js Normal file
View 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
}

View file

@ -5,6 +5,7 @@ import fetch from 'node-fetch'
import proxy from 'https-proxy-agent'
import { getMaxModelTokens } from '../common.js'
import { ChatGPTPuppeteer } from '../browser.js'
import { CustomGoogleGeminiClient } from '../../client/CustomGoogleGeminiClient.js'
export class WebsiteTool extends AbstractTool {
name = 'website'
@ -19,7 +20,7 @@ export class WebsiteTool extends AbstractTool {
}
func = async function (opts) {
let { url } = opts
let { url, mode, e } = opts
try {
// let res = await fetch(url, {
// headers: {
@ -58,34 +59,49 @@ export class WebsiteTool extends AbstractTool {
.replace(/[\n\r]/gi, '') // 去除回车换行
.replace(/\s{2}/g, '') // 多个空格只保留一个空格
.replace('<!DOCTYPE html>', '') // 去除<!DOCTYPE>声明
let maxModelTokens = getMaxModelTokens(Config.model)
text = text.slice(0, Math.min(text.length, maxModelTokens - 1600))
let completionParams = {
// model: Config.model
model: 'gpt-3.5-turbo-16k'
if (mode === 'gemini') {
let client = new CustomGoogleGeminiClient({
e,
userId: e?.sender?.user_id,
key: Config.geminiKey,
model: Config.geminiModel,
baseUrl: Config.geminiBaseUrl,
debug: Config.debug
})
const htmlContentSummaryRes = await client.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`)
let htmlContentSummary = htmlContentSummaryRes.text
return `this is the main content of website:\n ${htmlContentSummary}`
} else {
let maxModelTokens = getMaxModelTokens(Config.model)
text = text.slice(0, Math.min(text.length, maxModelTokens - 1600))
let completionParams = {
// model: Config.model
model: 'gpt-3.5-turbo-16k'
}
let api = new ChatGPTAPI({
apiBaseUrl: Config.openAiBaseUrl,
apiKey: Config.apiKey,
debug: false,
completionParams,
fetch: (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
},
maxModelTokens
})
const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`, { completionParams })
let htmlContentSummary = htmlContentSummaryRes.text
return `this is the main content of website:\n ${htmlContentSummary}`
}
let api = new ChatGPTAPI({
apiBaseUrl: Config.openAiBaseUrl,
apiKey: Config.apiKey,
debug: false,
completionParams,
fetch: (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
},
maxModelTokens
})
const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`, { completionParams })
let htmlContentSummary = htmlContentSummaryRes.text
return `this is the main content of website:\n ${htmlContentSummary}`
} catch (err) {
return `failed to visit the website, error: ${err.toString()}`
}

View file

@ -1,5 +1,13 @@
import md5 from 'md5'
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
export const translateLangSupports = [
@ -20,7 +28,7 @@ export const translateLangSupports = [
{ code: 'zh-CHS', label: '中文', abbr: '中', alphabet: 'Z' }
]
const API_ERROR = '出了点小问题,待会再试试吧'
export async function translate (msg, to = 'auto') {
export async function translateOld (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`
@ -95,3 +103,113 @@ export async function translate (msg, to = 'auto') {
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)
}
}

View file

@ -86,6 +86,8 @@ export default class XinghuoClient {
APILink = '/v2.1/chat'
} else if (Config.xhmode === 'apiv3') {
APILink = '/v3.1/chat'
} else if (Config.xhmode === 'apiv3.5') {
APILink = '/v3.5/chat'
}
const date = new Date().toGMTString()
const algorithm = 'hmac-sha256'
@ -176,7 +178,13 @@ export default class XinghuoClient {
const wsUrl = Config.xhmode == 'assistants' ? Config.xhAssistants : await this.getWsUrl()
if (!wsUrl) throw new Error('获取ws链接失败')
let domain = 'general'
if (Config.xhmode == 'apiv2') { domain = 'generalv2' } else if (Config.xhmode == 'apiv3') { domain = 'generalv3' }
if (Config.xhmode == 'apiv2') {
domain = 'generalv2'
} else if (Config.xhmode == 'apiv3') {
domain = 'generalv3'
} else if (Config.xhmode == 'apiv3.5') {
domain = 'generalv3.5'
}
// 编写消息内容
const wsSendData = {
header: {
@ -375,7 +383,7 @@ export default class XinghuoClient {
let chatId = option?.chatId
let image = option?.image
if (Config.xhmode == 'api' || Config.xhmode == 'apiv2' || Config.xhmode == 'apiv3' || Config.xhmode == 'assistants') {
if (Config.xhmode == 'api' || Config.xhmode == 'apiv2' || Config.xhmode == 'apiv3' || Config.xhmode == 'apiv3.5' || Config.xhmode == 'assistants') {
if (!Config.xhAppId || !Config.xhAPISecret || !Config.xhAPIKey) throw new Error('未配置api')
let Prompt = []
// 设定
@ -387,7 +395,7 @@ export default class XinghuoClient {
logger.warn('星火设定序列化失败,本次对话不附带设定')
}
} else {
Prompt = Config.xhPrompt ? [{ role: 'user', content: Config.xhPrompt }] : []
Prompt = option.system ? [{ role: 'system', content: option.system }] : []
}
if (Config.xhPromptEval) {
Prompt.forEach(obj => {

View file

@ -461,7 +461,7 @@
resolved "https://registry.npmmirror.com/@lukeed/ms/-/ms-2.0.1.tgz"
integrity sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==
"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.9":
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.10"
resolved "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz"
integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==
@ -1380,11 +1380,6 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
delay@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz"
integrity sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz"
@ -2811,7 +2806,7 @@ ms@2.1.3:
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nan@^2.15.0, nan@^2.17.0:
nan@^2.17.0:
version "2.17.0"
resolved "https://registry.npmmirror.com/nan/-/nan-2.17.0.tgz"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
@ -2866,14 +2861,6 @@ node-fetch@^3.3.1:
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
node-silk@^0.1.0:
version "0.1.0"
resolved "https://registry.npmmirror.com/node-silk/-/node-silk-0.1.0.tgz"
integrity sha512-z3zl66E1S1aOOhr9Sa0C957QBi39DqM5GzRalSXRYer52Aqp0cSv74DdMEDBXr9sn2AV5M7O78UZ4ppg/NVelg==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.9"
nan "^2.15.0"
nodejs-pptx@^1.2.4:
version "1.2.4"
resolved "https://registry.npmjs.org/nodejs-pptx/-/nodejs-pptx-1.2.4.tgz"