mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 05:47:11 +00:00
Merge branch 'v2' of https://github.com/ikechan8370/chatgpt-plugin into v2
This commit is contained in:
commit
f63af3349a
93 changed files with 5184 additions and 5145 deletions
357
apps/button.js
Normal file
357
apps/button.js
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
|
import { Config } from '../utils/config.js'
|
||||||
|
|
||||||
|
const PLUGIN_CHAT = 'ChatGpt 对话'
|
||||||
|
const PLUGIN_MANAGEMENT = 'ChatGPT-Plugin 管理'
|
||||||
|
const PLUGIN_ENTERTAINMENT = 'ChatGPT-Plugin 娱乐小功能'
|
||||||
|
const FUNCTION_CHAT = 'chatgpt'
|
||||||
|
const FUNCTION_CHAT3 = 'chatgpt3'
|
||||||
|
const FUNCTION_CHAT1 = 'chatgpt1'
|
||||||
|
const FUNCTION_BING = 'bing'
|
||||||
|
const FUNCTION_GEMINI = 'gemini'
|
||||||
|
const FUNCTION_XH = 'xh'
|
||||||
|
const FUNCTION_QWEN = 'qwen'
|
||||||
|
const FUNCTION_GLM4 = 'glm4'
|
||||||
|
const FUNCTION_CLAUDE2 = 'claude2'
|
||||||
|
const FUNCTION_CLAUDE = 'claude'
|
||||||
|
|
||||||
|
const FUNCTION_END = 'destroyConversations'
|
||||||
|
const FUNCTION_END_ALL = 'endAllConversations'
|
||||||
|
|
||||||
|
const FUNCTION_PIC = 'switch2Picture'
|
||||||
|
const FUNCTION_TEXT = 'switch2Text'
|
||||||
|
const FUNCTION_AUDIO = 'switch2Audio'
|
||||||
|
|
||||||
|
const FUNCTION_CONFIRM_ON = 'turnOnConfirm'
|
||||||
|
const FUNCTION_CONFIRM_OFF = 'turnOffConfirm'
|
||||||
|
const FUNCTION_VERSION = 'versionChatGPTPlugin'
|
||||||
|
const FUNCTION_SHUTUP = 'shutUp'
|
||||||
|
const FUNCTION_OPEN_MOUTH = 'openMouth'
|
||||||
|
const FUNCTION_QUERY_CONFIG = 'queryConfig'
|
||||||
|
const FUNCTION_ENABLE_CONTEXT = 'enableGroupContext'
|
||||||
|
const FUNCTION_MODELS = 'viewAPIModel'
|
||||||
|
|
||||||
|
const FUNCTION_SWITCH_BING = 'useBingSolution'
|
||||||
|
|
||||||
|
const FUNCTION_WORDCLOUD = 'wordcloud'
|
||||||
|
const FUNCTION_WORDCLOUD_LATEST = 'wordcloud_latest'
|
||||||
|
const FUNCTION_WORDCLOUD_NEW = 'wordcloud_new'
|
||||||
|
const FUNCTION_TRANSLATE = 'translate'
|
||||||
|
const FUNCTION_TRANSLATE_SOURCE = 'translateSource'
|
||||||
|
const FUNCTION_TRANSLATE_OCR = 'ocr'
|
||||||
|
const FUNCTION_TRANSLATE_SCREENSHOT = 'screenshotUrl'
|
||||||
|
export class ChatGPTButtonHandler extends plugin {
|
||||||
|
constructor () {
|
||||||
|
super({
|
||||||
|
name: 'chatgpt按钮处理器',
|
||||||
|
priority: -100,
|
||||||
|
namespace: 'chatgpt-plugin',
|
||||||
|
handler: [{
|
||||||
|
key: 'chatgpt.button.post',
|
||||||
|
fn: 'btnHandler'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async btnHandler (e, options, reject) {
|
||||||
|
// logger.mark('[chatgpt按钮处理器]')
|
||||||
|
if (!Config.enableMd) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const fnc = e.logFnc
|
||||||
|
switch (fnc) {
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_CHAT3}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_CHAT1}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_BING}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_GEMINI}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_XH}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_QWEN}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_CLAUDE2}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_CLAUDE}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_GLM4}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_CHAT}]`: {
|
||||||
|
return this.makeButtonChat(options?.btnData)
|
||||||
|
}
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_END}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_END_ALL}]`: {
|
||||||
|
return this.makeButtonEnd(options?.btnData)
|
||||||
|
}
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_PIC}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_AUDIO}]`:
|
||||||
|
case `[${PLUGIN_CHAT}][${FUNCTION_TEXT}]`: {
|
||||||
|
return this.makeButtonMode(options?.btnData)
|
||||||
|
}
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_VERSION}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_SHUTUP}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_OPEN_MOUTH}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_MODELS}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_QUERY_CONFIG}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_ENABLE_CONTEXT}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_CONFIRM_OFF}]`:
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_CONFIRM_ON}]`: {
|
||||||
|
return this.makeButtonConfirm(options?.btnData)
|
||||||
|
}
|
||||||
|
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_SWITCH_BING}]`: {
|
||||||
|
return this.makeButtonBingMode(options?.btnData)
|
||||||
|
}
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_WORDCLOUD}]`:
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_WORDCLOUD_LATEST}]`:
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_WORDCLOUD_NEW}]`:
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE}]`:
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE_SOURCE}]`:
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE_OCR}]`:
|
||||||
|
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE_SCREENSHOT}]`: {
|
||||||
|
return this.makeButtonEntertainment(options?.btnData)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{suggested: string?, use: string}?} options
|
||||||
|
*/
|
||||||
|
async makeButtonChat (options) {
|
||||||
|
let endCommand = '#摧毁对话'
|
||||||
|
switch (options?.use) {
|
||||||
|
case 'api': {
|
||||||
|
endCommand = '#api结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'api3': {
|
||||||
|
endCommand = '#api3结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'bing': {
|
||||||
|
endCommand = '#必应结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'claude2': {
|
||||||
|
endCommand = '#克劳德结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'gemini': {
|
||||||
|
endCommand = '#双子星结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'xh': {
|
||||||
|
endCommand = '#星火结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'qwen': {
|
||||||
|
endCommand = '#通义千问结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'chatglm4': {
|
||||||
|
endCommand = '#智谱结束对话'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rows = [
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('结束对话', '#毁灭对话'),
|
||||||
|
createButtonBase('结束当前对话', endCommand),
|
||||||
|
createButtonBase('at我对话', '', false)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
let buttons = [[], []]
|
||||||
|
if (Config.apiKey) {
|
||||||
|
buttons[0].push(createButtonBase('OpenAI', '#chat1', false))
|
||||||
|
}
|
||||||
|
if (await redis.get('CHATGPT:TOKEN')) {
|
||||||
|
buttons[0].push(createButtonBase('ChatGPT', '#chat3', false))
|
||||||
|
}
|
||||||
|
if (await redis.get('CHATGPT:BING_TOKENS')) {
|
||||||
|
buttons[0].push(createButtonBase('Copilot', '#bing', false))
|
||||||
|
}
|
||||||
|
if (Config.geminiKey) {
|
||||||
|
buttons[0].push(createButtonBase('Gemini', '#gemini', false))
|
||||||
|
}
|
||||||
|
if (Config.xhAPIKey) {
|
||||||
|
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('讯飞星火', '#xh', false))
|
||||||
|
}
|
||||||
|
if (Config.qwenApiKey) {
|
||||||
|
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('通义千问', '#qwen', false))
|
||||||
|
}
|
||||||
|
if (Config.chatglmRefreshToken) {
|
||||||
|
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('ChatGLM4', '#glm4', false))
|
||||||
|
}
|
||||||
|
// 两个claude只显示一个 优先API
|
||||||
|
if (Config.claudeApiKey) {
|
||||||
|
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude', '#claude', false))
|
||||||
|
} else if (Config.claudeAISessionKey) {
|
||||||
|
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude.ai', '#claude.ai', false))
|
||||||
|
}
|
||||||
|
rows.push({
|
||||||
|
buttons: buttons[0]
|
||||||
|
})
|
||||||
|
if (buttons[1].length > 0) {
|
||||||
|
rows.push({
|
||||||
|
buttons: buttons[1]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (options?.suggested) {
|
||||||
|
rows.unshift({
|
||||||
|
buttons: options.suggested.split('\n').map(s => {
|
||||||
|
return createButtonBase(s, s)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
appid: 1,
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeButtonEnd (options) {
|
||||||
|
return {
|
||||||
|
appid: 1,
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('重新开始', '#摧毁对话'),
|
||||||
|
createButtonBase('全部结束', '#摧毁全部对话'),
|
||||||
|
createButtonBase('切换模式', '#chatgpt切换', false)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeButtonMode (options) {
|
||||||
|
return {
|
||||||
|
appid: 1,
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('以文字回复', '#chatgpt文本模式'),
|
||||||
|
createButtonBase('以图片回复', '#chatgpt图片模式'),
|
||||||
|
createButtonBase('以语音回复', '#chatgpt语音模式')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeButtonConfirm (options) {
|
||||||
|
return {
|
||||||
|
appid: 1,
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('开启确认', '#chatgpt开启确认'),
|
||||||
|
createButtonBase('关闭确认', '#chatgpt关闭确认'),
|
||||||
|
createButtonBase('暂停本群回复', '#chatgpt本群闭嘴', false)
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('恢复本群回复', '#chatgpt本群张嘴', false),
|
||||||
|
createButtonBase('开启上下文', '#打开群聊上下文'),
|
||||||
|
createButtonBase('关闭上下文 ', '#关闭群聊上下文')
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('查看指令表', '#chatgpt指令表', false),
|
||||||
|
createButtonBase('查看帮助', '#chatgpt帮助'),
|
||||||
|
createButtonBase('查看配置', '#chatgpt查看当前配置')
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('查看配置', '#chatgpt查看当前配置'),
|
||||||
|
createButtonBase('查看模型列表', '#chatgpt模型列表'),
|
||||||
|
createButtonBase('版本信息', '#chatgpt版本信息')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeButtonBingMode (options) {
|
||||||
|
return {
|
||||||
|
appid: 1,
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('创意模式', '#chatgpt必应切换创意'),
|
||||||
|
createButtonBase('精准模式', '#chatgpt必应切换精准'),
|
||||||
|
createButtonBase('使用设定', '#chatgpt使用设定', false)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('禁用搜索', '#chatgpt必应禁用搜索'),
|
||||||
|
createButtonBase('开启搜索', '#chatgpt必应开启搜索'),
|
||||||
|
createButtonBase('设定列表', '#chatgpt浏览设定', false)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('切换到API', '#chatgpt切换API'),
|
||||||
|
createButtonBase('切换到Gemini', '#chatgpt切换gemini'),
|
||||||
|
createButtonBase('切换到星火', '#chatgpt切换xh')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeButtonEntertainment (options) {
|
||||||
|
return {
|
||||||
|
appid: 1,
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('今日词云', '#今日词云'),
|
||||||
|
createButtonBase('最新词云', '#最新词云', false),
|
||||||
|
createButtonBase('我的词云', '#我的今日词云')
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('翻译', '#翻译', false),
|
||||||
|
createButtonBase('OCR', '#ocr', false),
|
||||||
|
createButtonBase('截图', '#url:', false)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
buttons: [
|
||||||
|
createButtonBase('设置OPENAI翻译源', '#chatgpt设置翻译来源openai'),
|
||||||
|
createButtonBase('设置gemini翻译源', '#chatgpt设置翻译来源gemini'),
|
||||||
|
createButtonBase('设置星火翻译源', '#chatgpt设置翻译来源xh'),
|
||||||
|
createButtonBase('设置通义千问翻译源', '#chatgpt设置翻译来源qwen')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createButtonBase (label, data, enter = true, style = 1) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
render_data: {
|
||||||
|
label,
|
||||||
|
style,
|
||||||
|
visited_label: label
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: 2,
|
||||||
|
permission: {
|
||||||
|
type: 2
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
enter,
|
||||||
|
unsupport_tips: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1691
apps/chat.js
1691
apps/chat.js
File diff suppressed because it is too large
Load diff
65
apps/draw.js
65
apps/draw.js
|
|
@ -4,6 +4,7 @@ import { makeForwardMsg } from '../utils/common.js'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from '../utils/config.js'
|
||||||
import BingDrawClient from '../utils/BingDraw.js'
|
import BingDrawClient from '../utils/BingDraw.js'
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
|
||||||
export class dalle extends plugin {
|
export class dalle extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
|
|
@ -32,11 +33,67 @@ export class dalle extends plugin {
|
||||||
{
|
{
|
||||||
reg: '^#bing(画图|绘图)',
|
reg: '^#bing(画图|绘图)',
|
||||||
fnc: 'bingDraw'
|
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) {
|
async draw (e) {
|
||||||
if (!Config.enableDraw) {
|
if (!Config.enableDraw) {
|
||||||
this.reply('画图功能未开启')
|
this.reply('画图功能未开启')
|
||||||
|
|
@ -267,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 {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { generateHello } from '../utils/randomMessage.js'
|
||||||
import { generateVitsAudio } from '../utils/tts.js'
|
import { generateVitsAudio } from '../utils/tts.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
|
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
|
||||||
import uploadRecord from '../utils/uploadRecord.js'
|
import uploadRecord from '../utils/uploadRecord.js'
|
||||||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||||||
|
|
@ -13,6 +12,7 @@ import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||||
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||||
import { URL } from 'node:url'
|
import { URL } from 'node:url'
|
||||||
import { getBots } from '../utils/bot.js'
|
import { getBots } from '../utils/bot.js'
|
||||||
|
import {CustomGoogleGeminiClient} from "../client/CustomGoogleGeminiClient.js";
|
||||||
|
|
||||||
let useSilk = false
|
let useSilk = false
|
||||||
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'
|
||||||
|
|
@ -66,6 +70,10 @@ export class Entertainment extends plugin {
|
||||||
{
|
{
|
||||||
reg: '^#url(:|:)',
|
reg: '^#url(:|:)',
|
||||||
fnc: 'screenshotUrl'
|
fnc: 'screenshotUrl'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#(识图|图片识别|VQA|vqa)',
|
||||||
|
fnc: 'vqa'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
@ -78,6 +86,23 @@ export class Entertainment extends plugin {
|
||||||
fnc: this.sendRandomMessage.bind(this)
|
fnc: this.sendRandomMessage.bind(this)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
this.reply = async (msg, quote, data) => {
|
||||||
|
if (!Config.enableMd) {
|
||||||
|
return e.reply(msg, quote, data)
|
||||||
|
}
|
||||||
|
let handler = e.runtime?.handler || {}
|
||||||
|
const btns = await handler.call('chatgpt.button.post', this.e)
|
||||||
|
const btnElement = {
|
||||||
|
type: 'button',
|
||||||
|
content: btns
|
||||||
|
}
|
||||||
|
if (Array.isArray(msg)) {
|
||||||
|
msg.push(btnElement)
|
||||||
|
} else {
|
||||||
|
msg = [msg, btnElement]
|
||||||
|
}
|
||||||
|
return e.reply(msg, quote, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ocr (e) {
|
async ocr (e) {
|
||||||
|
|
@ -112,7 +137,7 @@ ${translateLangLabels}
|
||||||
let result = []
|
let result = []
|
||||||
let multiText = false
|
let multiText = false
|
||||||
if (languageCode !== 'auto' && !translateLangLabelAbbrS.includes(languageCode)) {
|
if (languageCode !== 'auto' && !translateLangLabelAbbrS.includes(languageCode)) {
|
||||||
e.reply(`输入格式有误或暂不支持该语言,\n当前支持${translateLangLabels}`, e.isGroup)
|
this.reply(`输入格式有误或暂不支持该语言,\n当前支持${translateLangLabels}`, e.isGroup)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// 引用回复
|
// 引用回复
|
||||||
|
|
@ -166,10 +191,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,25 +212,46 @@ ${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 {
|
||||||
|
this.reply('暂不支持该翻译源')
|
||||||
|
}
|
||||||
|
this.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
|
||||||
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
if (lock) {
|
if (lock) {
|
||||||
await e.reply('别着急,上次统计还没完呢')
|
await this.reply('别着急,上次统计还没完呢')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
await e.reply('在统计啦,请稍等...')
|
await this.reply('在统计啦,请稍等...')
|
||||||
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
||||||
try {
|
try {
|
||||||
await makeWordcloud(e, e.group_id)
|
let img = await makeWordcloud(e, e.group_id)
|
||||||
|
this.reply(img, true)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
await e.reply(err)
|
await this.reply(err)
|
||||||
}
|
}
|
||||||
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
|
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
} else {
|
} else {
|
||||||
await e.reply('请在群里发送此命令')
|
await this.reply('请在群里发送此命令')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,7 +260,7 @@ ${translateLangLabels}
|
||||||
let groupId = e.group_id
|
let groupId = e.group_id
|
||||||
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
if (lock) {
|
if (lock) {
|
||||||
await e.reply('别着急,上次统计还没完呢')
|
await this.reply('别着急,上次统计还没完呢')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,21 +269,21 @@ ${translateLangLabels}
|
||||||
const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h
|
const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h
|
||||||
|
|
||||||
if (duration > 24) {
|
if (duration > 24) {
|
||||||
await e.reply('最多只能统计24小时内的记录哦,你可以使用#本周词云和#本月词云获取更长时间的统计~')
|
await this.reply('最多只能统计24小时内的记录哦,你可以使用#本周词云和#本月词云获取更长时间的统计~')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
await e.reply('在统计啦,请稍等...')
|
await this.reply('在统计啦,请稍等...')
|
||||||
|
|
||||||
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
|
||||||
try {
|
try {
|
||||||
await makeWordcloud(e, e.group_id, duration)
|
await makeWordcloud(e, e.group_id, duration)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
await e.reply(err)
|
await this.reply(err)
|
||||||
}
|
}
|
||||||
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
|
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
|
||||||
} else {
|
} else {
|
||||||
await e.reply('请在群里发送此命令')
|
await this.reply('请在群里发送此命令')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,10 +300,10 @@ ${translateLangLabels}
|
||||||
}
|
}
|
||||||
let lock = await redis.get(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
|
let lock = await redis.get(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
|
||||||
if (lock) {
|
if (lock) {
|
||||||
await e.reply('别着急,上次统计还没完呢')
|
await this.reply('别着急,上次统计还没完呢')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
await e.reply('在统计啦,请稍等...')
|
await this.reply('在统计啦,请稍等...')
|
||||||
let duration = 24
|
let duration = 24
|
||||||
if (e.msg.includes('本周')) {
|
if (e.msg.includes('本周')) {
|
||||||
const now = new Date() // Get the current date and time
|
const now = new Date() // Get the current date and time
|
||||||
|
|
@ -283,11 +329,11 @@ ${translateLangLabels}
|
||||||
await makeWordcloud(e, e.group_id, duration, userId)
|
await makeWordcloud(e, e.group_id, duration, userId)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
await e.reply(err)
|
await this.reply(err)
|
||||||
}
|
}
|
||||||
await redis.del(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
|
await redis.del(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
|
||||||
} else {
|
} else {
|
||||||
await e.reply('请在群里发送此命令')
|
await this.reply('请在群里发送此命令')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,9 +347,9 @@ ${translateLangLabels}
|
||||||
logger.info('combine ' + e.msg)
|
logger.info('combine ' + e.msg)
|
||||||
let resultFileLoc = `data/chatgpt/emoji/${left}_${right}.jpg`
|
let resultFileLoc = `data/chatgpt/emoji/${left}_${right}.jpg`
|
||||||
if (fs.existsSync(resultFileLoc)) {
|
if (fs.existsSync(resultFileLoc)) {
|
||||||
let image = segment.image(fs.createReadStream(resultFileLoc))
|
let image = segment.image(resultFileLoc)
|
||||||
image.asface = true
|
image.asface = true
|
||||||
await e.reply(image, true)
|
await this.reply(image, true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
|
|
@ -325,17 +371,17 @@ ${translateLangLabels}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!url) {
|
if (!url) {
|
||||||
await e.reply('不支持合成', true)
|
await this.reply('不支持合成', true)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let response = await fetch(url)
|
// let response = await fetch(url)
|
||||||
const resultBlob = await response.blob()
|
// const resultBlob = await response.blob()
|
||||||
const resultArrayBuffer = await resultBlob.arrayBuffer()
|
// const resultArrayBuffer = await resultBlob.arrayBuffer()
|
||||||
const resultBuffer = Buffer.from(resultArrayBuffer)
|
// const resultBuffer = Buffer.from(resultArrayBuffer)
|
||||||
await fs.writeFileSync(resultFileLoc, resultBuffer)
|
// await fs.writeFileSync(resultFileLoc, resultBuffer)
|
||||||
let image = segment.image(fs.createReadStream(resultFileLoc))
|
let image = segment.image(url)
|
||||||
image.asface = true
|
image.asface = true
|
||||||
await e.reply(image, true)
|
await this.reply(image, true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +398,7 @@ ${translateLangLabels}
|
||||||
logger.info(groupId)
|
logger.info(groupId)
|
||||||
groupId = parseInt(groupId)
|
groupId = parseInt(groupId)
|
||||||
if (groupId && !e.bot.gl.get(groupId)) {
|
if (groupId && !e.bot.gl.get(groupId)) {
|
||||||
await e.reply('机器人不在这个群里!')
|
await this.reply('机器人不在这个群里!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let message = await generateHello()
|
let message = await generateHello()
|
||||||
|
|
@ -363,10 +409,10 @@ ${translateLangLabels}
|
||||||
sendable = segment.record(audio)
|
sendable = segment.record(audio)
|
||||||
}
|
}
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
await e.reply(sendable)
|
await this.reply(sendable)
|
||||||
} else {
|
} else {
|
||||||
await e.bot.sendGroupMsg(groupId, sendable)
|
await e.bot.sendGroupMsg(groupId, sendable)
|
||||||
await e.reply('发送成功!')
|
await this.reply('发送成功!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,7 +585,7 @@ ${translateLangLabels}
|
||||||
url = 'http://' + url
|
url = 'http://' + url
|
||||||
}
|
}
|
||||||
let urlLink = new URL(url)
|
let urlLink = new URL(url)
|
||||||
await e.reply(
|
await this.reply(
|
||||||
await renderUrl(
|
await renderUrl(
|
||||||
e, urlLink.href,
|
e, urlLink.href,
|
||||||
{
|
{
|
||||||
|
|
@ -557,4 +603,36 @@ ${translateLangLabels}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async vqa (e) {
|
||||||
|
if (!Config.geminiKey) {
|
||||||
|
e.reply('需要配置Gemini密钥以使用识图')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let img = await getImg(e)
|
||||||
|
if (!img?.[0]) {
|
||||||
|
await e.reply('请发送或引用一张图片', e.isGroup)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let client = new CustomGoogleGeminiClient({
|
||||||
|
e,
|
||||||
|
userId: e.sender.user_id,
|
||||||
|
key: Config.geminiKey,
|
||||||
|
model: 'gemini-pro-vision',
|
||||||
|
baseUrl: Config.geminiBaseUrl,
|
||||||
|
debug: Config.debug
|
||||||
|
})
|
||||||
|
const response = await fetch(img[0])
|
||||||
|
const base64Image = Buffer.from(await response.arrayBuffer())
|
||||||
|
let msg = e.msg.replace(/#(识图|图片识别|VQA|vqa)/, '') || 'describe this image in Simplified Chinese'
|
||||||
|
try {
|
||||||
|
let res = await client.sendMessage(msg, {
|
||||||
|
image: base64Image.toString('base64')
|
||||||
|
})
|
||||||
|
await e.reply(res.text, true)
|
||||||
|
} catch (err) {
|
||||||
|
await e.reply('❌识图失败:' + err.message, true)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ let helpData = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'confirm',
|
icon: 'confirm',
|
||||||
title: '#chatgpt必应切换(精准|均衡|创意|悉尼|自设定)',
|
title: '#chatgpt必应切换(精准|创意)',
|
||||||
desc: '切换Bing风格。'
|
desc: '切换Bing风格。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ export class history extends plugin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case 'bing': {
|
case 'bing': {
|
||||||
if (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') {
|
|
||||||
const cacheOptions = {
|
const cacheOptions = {
|
||||||
namespace: Config.toneStyle,
|
namespace: Config.toneStyle,
|
||||||
store: new KeyvFile({ filename: 'cache.json' })
|
store: new KeyvFile({ filename: 'cache.json' })
|
||||||
|
|
@ -81,10 +80,7 @@ export class history extends plugin {
|
||||||
tmp = {}
|
tmp = {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
await e.reply('还不支持BING模式呢')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,22 +22,8 @@ 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'
|
||||||
|
import { createServer, runServer, stopServer } from '../server/index.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,39 +73,34 @@ 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',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
reg: '^#chatgpt切换(ChatGLM|chatglm)$',
|
// reg: '^#chatgpt切换(ChatGLM|chatglm)$',
|
||||||
fnc: 'useChatGLMSolution',
|
// fnc: 'useChatGLMSolution',
|
||||||
permission: 'master'
|
// permission: 'master'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt切换API3$',
|
reg: '^#chatgpt切换API3$',
|
||||||
fnc: 'useReversedAPIBasedSolution2',
|
fnc: 'useReversedAPIBasedSolution2',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt切换(必应|Bing)$',
|
reg: '^#chatgpt切换(必应|Bing|Copilot|copilot)$',
|
||||||
fnc: 'useBingSolution',
|
fnc: 'useBingSolution',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt切换(Poe|poe)$',
|
reg: '^#chatgpt切换(Claude|claude)$',
|
||||||
fnc: 'useClaudeBasedSolution',
|
fnc: 'useClaudeAPIBasedSolution',
|
||||||
permission: 'master'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reg: '^#chatgpt切换(Claude|claude|slack)$',
|
|
||||||
fnc: 'useSlackClaudeBasedSolution',
|
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -152,6 +133,11 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'useQwenSolution',
|
fnc: 'useQwenSolution',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt切换(智谱|智谱清言|ChatGLM|ChatGLM4|chatglm)$',
|
||||||
|
fnc: 'useGLM4Solution',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(必应|Bing)切换',
|
reg: '^#chatgpt(必应|Bing)切换',
|
||||||
fnc: 'changeBingTone',
|
fnc: 'changeBingTone',
|
||||||
|
|
@ -190,6 +176,11 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'setAPIKey',
|
fnc: 'setAPIKey',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt设置(claude|Claude)(Key|key)$',
|
||||||
|
fnc: 'setClaudeKey',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt设置(Gemini|gemini)(Key|key)$',
|
reg: '^#chatgpt设置(Gemini|gemini)(Key|key)$',
|
||||||
fnc: 'setGeminiKey',
|
fnc: 'setGeminiKey',
|
||||||
|
|
@ -232,7 +223,7 @@ export class ChatgptManagement extends plugin {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
/** 命令正则匹配 */
|
/** 命令正则匹配 */
|
||||||
reg: '^#(关闭|打开)群聊上下文$',
|
reg: '^#(chatgpt)?(关闭|打开)群聊上下文$',
|
||||||
/** 执行方法 */
|
/** 执行方法 */
|
||||||
fnc: 'enableGroupContext',
|
fnc: 'enableGroupContext',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
|
|
@ -243,16 +234,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'
|
||||||
},
|
},
|
||||||
|
|
@ -270,7 +261,7 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'commandHelp'
|
fnc: 'commandHelp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#语音切换.*',
|
reg: '^#(chatgpt)?语音切换.*',
|
||||||
fnc: 'ttsSwitch',
|
fnc: 'ttsSwitch',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
|
@ -326,13 +317,52 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'setXinghuoModel',
|
fnc: 'setXinghuoModel',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt设置(claude|Claude)模型$',
|
||||||
|
fnc: 'setClaudeModel',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt必应(禁用|禁止|关闭|启用|开启)搜索$',
|
reg: '^#chatgpt必应(禁用|禁止|关闭|启用|开启)搜索$',
|
||||||
fnc: 'switchBingSearch',
|
fnc: 'switchBingSearch',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt查看当前配置$',
|
||||||
|
fnc: 'queryConfig',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt(开启|关闭)(api|API)流$',
|
||||||
|
fnc: 'switchStream',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt(开启|关闭)(工具箱|后台服务)$',
|
||||||
|
fnc: 'switchToolbox',
|
||||||
|
permission: 'master'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
this.reply = async (msg, quote, data) => {
|
||||||
|
if (!Config.enableMd) {
|
||||||
|
return e.reply(msg, quote, data)
|
||||||
|
}
|
||||||
|
let handler = e.runtime?.handler || {}
|
||||||
|
const btns = await handler.call('chatgpt.button.post', this.e)
|
||||||
|
if (btns) {
|
||||||
|
const btnElement = {
|
||||||
|
type: 'button',
|
||||||
|
content: btns
|
||||||
|
}
|
||||||
|
if (Array.isArray(msg)) {
|
||||||
|
msg.push(btnElement)
|
||||||
|
} else {
|
||||||
|
msg = [msg, btnElement]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.reply(msg, quote, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewUserSetting (e) {
|
async viewUserSetting (e) {
|
||||||
|
|
@ -882,29 +912,19 @@ 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模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async useClaudeBasedSolution (e) {
|
async useClaudeAPIBasedSolution () {
|
||||||
let use = await redis.get('CHATGPT:USE')
|
|
||||||
if (use !== 'poe') {
|
|
||||||
await redis.set('CHATGPT:USE', 'poe')
|
|
||||||
await this.reply('已切换到基于Quora\'s POE的解决方案')
|
|
||||||
} else {
|
|
||||||
await this.reply('当前已经是POE模式了')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async useSlackClaudeBasedSolution () {
|
|
||||||
let use = await redis.get('CHATGPT:USE')
|
let use = await redis.get('CHATGPT:USE')
|
||||||
if (use !== 'claude') {
|
if (use !== 'claude') {
|
||||||
await redis.set('CHATGPT:USE', 'claude')
|
await redis.set('CHATGPT:USE', 'claude')
|
||||||
await this.reply('已切换到基于slack claude机器人的解决方案')
|
await this.reply('已切换到基于ClaudeAPI的解决方案')
|
||||||
} else {
|
} else {
|
||||||
await this.reply('当前已经是claude模式了')
|
await this.reply('当前已经是Claude模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -914,7 +934,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
await redis.set('CHATGPT:USE', 'claude2')
|
await redis.set('CHATGPT:USE', 'claude2')
|
||||||
await this.reply('已切换到基于claude.ai的解决方案')
|
await this.reply('已切换到基于claude.ai的解决方案')
|
||||||
} else {
|
} else {
|
||||||
await this.reply('当前已经是claude2模式了')
|
await this.reply('当前已经是claude.ai模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1001,9 +1021,9 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
logger.error(stderr)
|
logger.error(stderr)
|
||||||
logger.info(stdout)
|
logger.info(stdout)
|
||||||
this.e.reply('失败,请查看日志手动操作')
|
this.reply('失败,请查看日志手动操作')
|
||||||
} else {
|
} else {
|
||||||
this.e.reply('修补完成,请手动重启')
|
this.reply('修补完成,请手动重启')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1019,38 +1039,48 @@ 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) {
|
async changeBingTone (e) {
|
||||||
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
|
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
|
||||||
if (!tongStyle) {
|
if (!tongStyle) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let map = {
|
let map = {
|
||||||
精准: 'Sydney',
|
精准: 'Precise',
|
||||||
创意: 'Sydney',
|
创意: 'Creative',
|
||||||
均衡: 'Sydney',
|
均衡: 'Balanced',
|
||||||
Sydney: 'Sydney',
|
Sydney: 'Creative',
|
||||||
sydney: 'Sydney',
|
sydney: 'Creative',
|
||||||
悉尼: 'Sydney',
|
悉尼: 'Creative',
|
||||||
默认: 'Sydney',
|
默认: 'Creative',
|
||||||
自设定: 'Custom',
|
自设定: 'Creative',
|
||||||
自定义: 'Custom'
|
自定义: 'Creative'
|
||||||
}
|
}
|
||||||
if (map[tongStyle]) {
|
if (map[tongStyle]) {
|
||||||
Config.toneStyle = map[tongStyle]
|
Config.toneStyle = map[tongStyle]
|
||||||
await e.reply('切换成功')
|
await this.reply('切换成功')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('没有这种风格。支持的风格:默认/创意/悉尼、自设定')
|
await this.reply('没有这种风格。支持的风格:`精准`、`均衡`和`创意`,均支持设定')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async bingOpenSuggestedResponses (e) {
|
async bingOpenSuggestedResponses (e) {
|
||||||
Config.enableSuggestedResponses = e.msg.indexOf('开启') > -1
|
Config.enableSuggestedResponses = e.msg.indexOf('开启') > -1
|
||||||
await e.reply('操作成功')
|
await this.reply('操作成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkAuth (e) {
|
async checkAuth (e) {
|
||||||
if (!e.isMaster) {
|
if (!e.isMaster) {
|
||||||
e.reply(`只有主人才能命令ChatGPT哦~
|
this.reply(`只有主人才能命令ChatGPT哦~
|
||||||
(*/ω\*)`)
|
(*/ω\*)`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1058,7 +1088,8 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
}
|
}
|
||||||
|
|
||||||
async versionChatGPTPlugin (e) {
|
async versionChatGPTPlugin (e) {
|
||||||
await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/version`, { Viewport: { width: 800, height: 600 } })
|
let img = await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/version`, { Viewport: { width: 800, height: 600 }, retType: 'base64' })
|
||||||
|
this.reply(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
async modeHelp () {
|
async modeHelp () {
|
||||||
|
|
@ -1072,9 +1103,11 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
api3: 'API3',
|
api3: 'API3',
|
||||||
chatglm: 'ChatGLM-6B',
|
chatglm: 'ChatGLM-6B',
|
||||||
claude: 'Claude',
|
claude: 'Claude',
|
||||||
poe: 'Poe',
|
claude2: 'claude.ai',
|
||||||
|
chatglm4: 'ChatGLM-4',
|
||||||
xh: '星火',
|
xh: '星火',
|
||||||
qwen: '通义千问'
|
qwen: '通义千问',
|
||||||
|
gemini: 'Gemini'
|
||||||
}
|
}
|
||||||
let modeText = modeMap[mode || 'api']
|
let modeText = modeMap[mode || 'api']
|
||||||
let message = `请访问yunzai.chat查看文档。当前为 ${modeText} 模式。`
|
let message = `请访问yunzai.chat查看文档。当前为 ${modeText} 模式。`
|
||||||
|
|
@ -1097,13 +1130,13 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
||||||
await e.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`)
|
await this.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`)
|
||||||
} else {
|
} else {
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
|
||||||
await e.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`)
|
await this.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('主人,这里好像不是群哦')
|
await this.reply('主人,这里好像不是群哦')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (match) {
|
} else if (match) {
|
||||||
|
|
@ -1112,23 +1145,23 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
||||||
await e.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`)
|
await this.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`)
|
||||||
} else {
|
} else {
|
||||||
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
|
||||||
await e.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`)
|
await this.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('主人还没告诉我群号呢')
|
await this.reply('主人还没告诉我群号呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
||||||
await redis.del('CHATGPT:SHUT_UP:ALL')
|
await redis.del('CHATGPT:SHUT_UP:ALL')
|
||||||
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
||||||
await e.reply(`好的,我会延长休眠时间${formatDuration(time)}`)
|
await this.reply(`好的,我会延长休眠时间${formatDuration(time)}`)
|
||||||
} else {
|
} else {
|
||||||
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
|
||||||
await e.reply(`好的,我会延长休眠时间${formatDuration(time)}`)
|
await this.reply(`好的,我会延长休眠时间${formatDuration(time)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1137,36 +1170,36 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
const match = e.msg.match(/^#chatgpt群(\d+)/)
|
const match = e.msg.match(/^#chatgpt群(\d+)/)
|
||||||
if (e.msg.indexOf('本群') > -1) {
|
if (e.msg.indexOf('本群') > -1) {
|
||||||
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
||||||
await e.reply('当前为休眠模式,没办法做出回应呢')
|
await this.reply('当前为休眠模式,没办法做出回应呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let scope = e.group.group_id
|
let scope = e.group.group_id
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
|
||||||
await e.reply('好的主人,我又可以和大家聊天啦')
|
await this.reply('好的主人,我又可以和大家聊天啦')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('主人,我已经启动过了哦')
|
await this.reply('主人,我已经启动过了哦')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('主人,这里好像不是群哦')
|
await this.reply('主人,这里好像不是群哦')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (match) {
|
} else if (match) {
|
||||||
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
|
||||||
await e.reply('当前为休眠模式,没办法做出回应呢')
|
await this.reply('当前为休眠模式,没办法做出回应呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const groupId = parseInt(match[1], 10)
|
const groupId = parseInt(match[1], 10)
|
||||||
if (e.bot.getGroupList().get(groupId)) {
|
if (e.bot.getGroupList().get(groupId)) {
|
||||||
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
|
||||||
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
|
||||||
await e.reply(`好的主人,我终于又可以在群${groupId}和大家聊天了`)
|
await this.reply(`好的主人,我终于又可以在群${groupId}和大家聊天了`)
|
||||||
} else {
|
} else {
|
||||||
await e.reply(`主人,我在群${groupId}中已经是启动状态了哦`)
|
await this.reply(`主人,我在群${groupId}中已经是启动状态了哦`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await e.reply('主人还没告诉我群号呢')
|
await this.reply('主人还没告诉我群号呢')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1176,14 +1209,14 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
await redis.del(keys[i])
|
await redis.del(keys[i])
|
||||||
}
|
}
|
||||||
await e.reply('好的,我会开启所有群聊响应')
|
await this.reply('好的,我会开启所有群聊响应')
|
||||||
} else if (keys || keys.length > 0) {
|
} else if (keys || keys.length > 0) {
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
await redis.del(keys[i])
|
await redis.del(keys[i])
|
||||||
}
|
}
|
||||||
await e.reply('已经开启过全群响应啦')
|
await this.reply('已经开启过全群响应啦')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('我没有在任何群休眠哦')
|
await this.reply('我没有在任何群休眠哦')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1225,6 +1258,25 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
this.finish('saveAPIKey')
|
this.finish('saveAPIKey')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setClaudeKey (e) {
|
||||||
|
this.setContext('saveClaudeKey')
|
||||||
|
await this.reply('请发送Claude API Key。\n如果要设置多个key请用逗号隔开。\n此操作会覆盖当前配置,请谨慎操作', true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveClaudeKey () {
|
||||||
|
if (!this.e.msg) return
|
||||||
|
let token = this.e.msg
|
||||||
|
if (!token.startsWith('sk-ant')) {
|
||||||
|
await this.reply('Claude API Key格式错误。如果是格式特殊的非官方Key请前往锅巴或工具箱手动设置', true)
|
||||||
|
this.finish('saveClaudeKey')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Config.claudeApiKey = token
|
||||||
|
await this.reply('Claude API Key设置成功', true)
|
||||||
|
this.finish('saveClaudeKey')
|
||||||
|
}
|
||||||
|
|
||||||
async setGeminiKey (e) {
|
async setGeminiKey (e) {
|
||||||
this.setContext('saveGeminiKey')
|
this.setContext('saveGeminiKey')
|
||||||
await this.reply('请发送Gemini API Key.获取地址:https://makersuite.google.com/app/apikey', true)
|
await this.reply('请发送Gemini API Key.获取地址:https://makersuite.google.com/app/apikey', true)
|
||||||
|
|
@ -1417,7 +1469,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
|
|
||||||
async setOpenAIPlatformToken (e) {
|
async setOpenAIPlatformToken (e) {
|
||||||
this.setContext('doSetOpenAIPlatformToken')
|
this.setContext('doSetOpenAIPlatformToken')
|
||||||
await e.reply('请发送refreshToken\n你可以在已登录的platform.openai.com后台界面打开调试窗口,在终端中执行\nJSON.parse(localStorage.getItem(Object.keys(localStorage).filter(k => k.includes(\'auth0\'))[0])).body.refresh_token\n如果仍不能查看余额,请退出登录重新获取刷新令牌.设置后可以发送#chatgpt设置sessKey来将sessKey作为API Key使用')
|
await this.reply('请发送refreshToken\n你可以在已登录的platform.openai.com后台界面打开调试窗口,在终端中执行\nJSON.parse(localStorage.getItem(Object.keys(localStorage).filter(k => k.includes(\'auth0\'))[0])).body.refresh_token\n如果仍不能查看余额,请退出登录重新获取刷新令牌.设置后可以发送#chatgpt设置sessKey来将sessKey作为API Key使用')
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSessKey (e) {
|
async getSessKey (e) {
|
||||||
|
|
@ -1445,9 +1497,9 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
let errMsg = await refreshRes.json()
|
let errMsg = await refreshRes.json()
|
||||||
logger.error(JSON.stringify(errMsg))
|
logger.error(JSON.stringify(errMsg))
|
||||||
if (errMsg.error === 'access_denied') {
|
if (errMsg.error === 'access_denied') {
|
||||||
await e.reply('刷新令牌失效,请重新发送【#chatgpt设置后台刷新token】进行配置。建议退出platform.openai.com重新登录后再获取和配置')
|
await this.reply('刷新令牌失效,请重新发送【#chatgpt设置后台刷新token】进行配置。建议退出platform.openai.com重新登录后再获取和配置')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('获取失败')
|
await this.reply('获取失败')
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1470,9 +1522,9 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
let sess = authRes.user.session.sensitive_id
|
let sess = authRes.user.session.sensitive_id
|
||||||
if (sess) {
|
if (sess) {
|
||||||
Config.apiKey = sess
|
Config.apiKey = sess
|
||||||
await e.reply('已成功将sessKey设置为apiKey,您可以发送#openai余额来查看该账号余额')
|
await this.reply('已成功将sessKey设置为apiKey,您可以发送#openai余额来查看该账号余额')
|
||||||
} else {
|
} else {
|
||||||
await e.reply('设置失败!')
|
await this.reply('设置失败!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1483,7 +1535,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
Config.OpenAiPlatformRefreshToken = token.replaceAll('\'', '')
|
Config.OpenAiPlatformRefreshToken = token.replaceAll('\'', '')
|
||||||
await this.e.reply('设置成功')
|
await this.reply('设置成功')
|
||||||
this.finish('doSetOpenAIPlatformToken')
|
this.finish('doSetOpenAIPlatformToken')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1525,7 +1577,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
this.setContext('doImportConfig')
|
this.setContext('doImportConfig')
|
||||||
await e.reply('请发送配置文件')
|
await this.reply('请发送配置文件')
|
||||||
}
|
}
|
||||||
|
|
||||||
async doImportConfig (e) {
|
async doImportConfig (e) {
|
||||||
|
|
@ -1539,7 +1591,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,
|
||||||
|
|
@ -1581,7 +1633,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
await this.reply(await makeForwardMsg(this.e, changeConfig.map(msg => `修改项:${msg.item}\n旧数据\n\n${msg.old}\n\n新数据\n ${msg.value}`)))
|
await this.reply(await makeForwardMsg(this.e, changeConfig.map(msg => `修改项:${msg.item}\n旧数据\n\n${msg.old}\n\n新数据\n ${msg.value}`)))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
await e.reply('配置文件错误')
|
await this.reply('配置文件错误')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1595,18 +1647,18 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
async switchSmartMode (e) {
|
async switchSmartMode (e) {
|
||||||
if (e.msg.includes('开启')) {
|
if (e.msg.includes('开启')) {
|
||||||
if (Config.smartMode) {
|
if (Config.smartMode) {
|
||||||
await e.reply('已经开启了')
|
await this.reply('已经开启了')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Config.smartMode = true
|
Config.smartMode = true
|
||||||
await e.reply('好的,已经打开智能模式,注意API额度哦。配合开启读取群聊上下文效果更佳!')
|
await this.reply('好的,已经打开智能模式,注意API额度哦。配合开启读取群聊上下文效果更佳!')
|
||||||
} else {
|
} else {
|
||||||
if (!Config.smartMode) {
|
if (!Config.smartMode) {
|
||||||
await e.reply('已经是关闭得了')
|
await this.reply('已经是关闭得了')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Config.smartMode = false
|
Config.smartMode = false
|
||||||
await e.reply('好的,已经关闭智能模式')
|
await this.reply('好的,已经关闭智能模式')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1628,7 +1680,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
// console.log(value)
|
// console.log(value)
|
||||||
modelList.push(value)
|
modelList.push(value)
|
||||||
})
|
})
|
||||||
await this.e.reply(makeForwardMsg(e, modelList, '模型列表'))
|
await this.reply(makeForwardMsg(e, modelList, '模型列表'))
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAPIModel (e) {
|
async setAPIModel (e) {
|
||||||
|
|
@ -1645,6 +1697,20 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
this.finish('saveAPIModel')
|
this.finish('saveAPIModel')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setClaudeModel (e) {
|
||||||
|
this.setContext('saveClaudeModel')
|
||||||
|
await this.reply('请发送Claude模型,官方推荐模型:\nclaude-3-opus-20240229\nclaude-3-sonnet-20240229\nclaude-3-haiku-20240307', true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveClaudeModel () {
|
||||||
|
if (!this.e.msg) return
|
||||||
|
let token = this.e.msg
|
||||||
|
Config.claudeApiModel = token
|
||||||
|
await this.reply('Claude模型设置成功', true)
|
||||||
|
this.finish('saveClaudeModel')
|
||||||
|
}
|
||||||
|
|
||||||
async setOpenAiBaseUrl (e) {
|
async setOpenAiBaseUrl (e) {
|
||||||
this.setContext('saveOpenAiBaseUrl')
|
this.setContext('saveOpenAiBaseUrl')
|
||||||
await this.reply('请发送API反代', true)
|
await this.reply('请发送API反代', true)
|
||||||
|
|
@ -1667,26 +1733,20 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
|
|
||||||
async setXinghuoModel (e) {
|
async setXinghuoModel (e) {
|
||||||
this.setContext('saveXinghuoModel')
|
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)
|
await this.reply('请发送序号', true)
|
||||||
return false
|
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) {
|
async saveXinghuoModel (e) {
|
||||||
if (!this.e.msg) return
|
if (!this.e.msg) return
|
||||||
let token = this.e.msg
|
let token = this.e.msg
|
||||||
let ver
|
let ver
|
||||||
switch (token) {
|
switch (token) {
|
||||||
|
case '4':
|
||||||
|
ver = 'V3.5'
|
||||||
|
Config.xhmode = 'apiv3.5'
|
||||||
|
break
|
||||||
case '3':
|
case '3':
|
||||||
ver = 'V3'
|
ver = 'V3'
|
||||||
Config.xhmode = 'apiv3'
|
Config.xhmode = 'apiv3'
|
||||||
|
|
@ -1699,7 +1759,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
ver = 'V1.5'
|
ver = 'V1.5'
|
||||||
Config.xhmode = 'api'
|
Config.xhmode = 'api'
|
||||||
break
|
break
|
||||||
case '4':
|
case '5':
|
||||||
ver = '助手'
|
ver = '助手'
|
||||||
Config.xhmode = 'assistants'
|
Config.xhmode = 'assistants'
|
||||||
break
|
break
|
||||||
|
|
@ -1709,4 +1769,67 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
await this.reply(`已成功切换到星火${ver}`, true)
|
await this.reply(`已成功切换到星火${ver}`, true)
|
||||||
this.finish('saveXinghuoModel')
|
this.finish('saveXinghuoModel')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async switchBingSearch (e) {
|
||||||
|
if (e.msg.includes('启用') || e.msg.includes('开启')) {
|
||||||
|
Config.sydneyEnableSearch = true
|
||||||
|
await this.reply('已开启必应搜索')
|
||||||
|
} else {
|
||||||
|
Config.sydneyEnableSearch = false
|
||||||
|
await this.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}`)
|
||||||
|
this.reply(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async switchStream (e) {
|
||||||
|
if (e.msg.includes('开启')) {
|
||||||
|
if (Config.apiStream) {
|
||||||
|
await this.reply('已经开启了')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Config.apiStream = true
|
||||||
|
await this.reply('好的,已经打开API流式输出')
|
||||||
|
} else {
|
||||||
|
if (!Config.apiStream) {
|
||||||
|
await this.reply('已经是关闭得了')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Config.apiStream = false
|
||||||
|
await this.reply('好的,已经关闭API流式输出')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async switchToolbox (e) {
|
||||||
|
if (e.msg.includes('开启')) {
|
||||||
|
if (Config.enableToolbox) {
|
||||||
|
await this.reply('已经开启了')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Config.enableToolbox = true
|
||||||
|
await this.reply('开启中', true)
|
||||||
|
await runServer()
|
||||||
|
await this.reply('好的,已经打开工具箱')
|
||||||
|
} else {
|
||||||
|
if (!Config.enableToolbox) {
|
||||||
|
await this.reply('已经是关闭的了')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Config.enableToolbox = false
|
||||||
|
await stopServer()
|
||||||
|
await this.reply('好的,已经关闭工具箱')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
apps/md.js
Normal file
41
apps/md.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
|
import { Config } from '../utils/config.js'
|
||||||
|
|
||||||
|
export class ChatGPTMarkdownHandler extends plugin {
|
||||||
|
constructor () {
|
||||||
|
super({
|
||||||
|
name: 'chatgptmd处理器',
|
||||||
|
priority: -100,
|
||||||
|
namespace: 'chatgpt-plugin',
|
||||||
|
handler: [{
|
||||||
|
key: 'chatgpt.markdown.convert',
|
||||||
|
fn: 'mdHandler'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async mdHandler (e, options, reject) {
|
||||||
|
const { content, prompt, use } = options
|
||||||
|
if (Config.enableMd) {
|
||||||
|
let mode = transUse(use)
|
||||||
|
return `> ${prompt}\n\n---\n${content}\n\n---\n*当前模式:${mode}*`
|
||||||
|
} else {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transUse (use) {
|
||||||
|
let useMap = {
|
||||||
|
api: Config.model,
|
||||||
|
bing: '必应(Copilot)' + Config.toneStyle,
|
||||||
|
gemini: Config.geminiModel,
|
||||||
|
xh: '讯飞星火 ' + Config.xhmode,
|
||||||
|
qwen: '通义千问 ' + Config.qwenModel,
|
||||||
|
claude2: 'Claude 3 Sonnet',
|
||||||
|
glm4: 'ChatGLM4',
|
||||||
|
chat3: 'ChatGPT官网',
|
||||||
|
claude: Config.claudeApiModel
|
||||||
|
}
|
||||||
|
return useMap[use] || use
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import plugin from '../../../lib/plugins/plugin.js'
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
import fs from 'fs'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from '../utils/config.js'
|
||||||
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
|
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
|
||||||
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
|
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
|
||||||
import AzureTTS from "../utils/tts/microsoft-azure.js";
|
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||||
export class help extends plugin {
|
export class help extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
super({
|
super({
|
||||||
|
|
@ -66,21 +64,6 @@ export class help extends plugin {
|
||||||
fnc: 'helpPrompt',
|
fnc: 'helpPrompt',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
}
|
}
|
||||||
// {
|
|
||||||
// reg: '^#(chatgpt|ChatGPT)(开启|关闭)洗脑$',
|
|
||||||
// fnc: 'setSydneyBrainWash',
|
|
||||||
// permission: 'master'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// reg: '^#(chatgpt|ChatGPT)(设置)?洗脑强度',
|
|
||||||
// fnc: 'setSydneyBrainWashStrength',
|
|
||||||
// permission: 'master'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// reg: '^#(chatgpt|ChatGPT)(设置)?洗脑名称',
|
|
||||||
// fnc: 'setSydneyBrainWashName',
|
|
||||||
// permission: 'master'
|
|
||||||
// }
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -149,17 +132,13 @@ export class help extends plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let use = await redis.get('CHATGPT:USE') || 'api'
|
let use = await redis.get('CHATGPT:USE') || 'api'
|
||||||
if (use.toLowerCase() === 'bing') {
|
|
||||||
if (Config.toneStyle === 'Custom') {
|
|
||||||
use = 'Custom'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const keyMap = {
|
const keyMap = {
|
||||||
api: 'promptPrefixOverride',
|
api: 'promptPrefixOverride',
|
||||||
Custom: 'sydney',
|
bing: 'sydney',
|
||||||
claude: 'slackClaudeGlobalPreset',
|
claude: 'claudeSystemPrompt',
|
||||||
qwen: 'promptPrefixOverride',
|
qwen: 'promptPrefixOverride',
|
||||||
gemini: 'geminiPrompt'
|
gemini: 'geminiPrompt',
|
||||||
|
xh: 'xhPrompt'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyMap[use]) {
|
if (keyMap[use]) {
|
||||||
|
|
@ -169,10 +148,27 @@ export class help extends plugin {
|
||||||
} else {
|
} else {
|
||||||
Config[keyMap[use]] = prompt.content
|
Config[keyMap[use]] = prompt.content
|
||||||
}
|
}
|
||||||
|
if (use === 'xh') {
|
||||||
|
Config.xhPromptSerialize = false
|
||||||
|
}
|
||||||
|
if (use === 'bing') {
|
||||||
|
/**
|
||||||
|
* @type {{user: string, bot: string}[]} examples
|
||||||
|
*/
|
||||||
|
let examples = prompt.example
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
Config[`chatExampleUser${i}`] = ''
|
||||||
|
Config[`chatExampleBot${i}`] = ''
|
||||||
|
}
|
||||||
|
for (let i = 1; i <= examples.length; i++) {
|
||||||
|
Config[`chatExampleUser${i}`] = examples[i - 1].user
|
||||||
|
Config[`chatExampleBot${i}`] = examples[i - 1].bot
|
||||||
|
}
|
||||||
|
}
|
||||||
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
|
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
|
||||||
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
|
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
|
||||||
} else {
|
} else {
|
||||||
await e.reply(`你当前正在使用${use}模式,该模式不支持设定。支持设定的模式有:API、自定义、Claude、通义千问和Gemini`, true)
|
await e.reply(`你当前正在使用${use}模式,该模式不支持设定。支持设定的模式有:API、必应、Claude、通义千问、星火和Gemini`, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,11 +269,6 @@ export class help extends plugin {
|
||||||
// return
|
// return
|
||||||
}
|
}
|
||||||
let use = await redis.get('CHATGPT:USE') || 'api'
|
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)(上传|分享|共享)设定/, '')
|
let currentUse = e.msg.replace(/^#(chatgpt|ChatGPT)(上传|分享|共享)设定/, '')
|
||||||
if (!currentUse) {
|
if (!currentUse) {
|
||||||
currentUse = await redis.get(`CHATGPT:PROMPT_USE_${use}`)
|
currentUse = await redis.get(`CHATGPT:PROMPT_USE_${use}`)
|
||||||
|
|
@ -353,13 +344,23 @@ export class help extends plugin {
|
||||||
let extraData = JSON.parse(await redis.get('CHATGPT:UPLOAD_PROMPT'))
|
let extraData = JSON.parse(await redis.get('CHATGPT:UPLOAD_PROMPT'))
|
||||||
const { currentUse, description } = extraData
|
const { currentUse, description } = extraData
|
||||||
const { content } = getPromptByName(currentUse)
|
const { content } = getPromptByName(currentUse)
|
||||||
|
let examples = []
|
||||||
|
for (let i = 1; i < 4; i++) {
|
||||||
|
if (Config[`chatExampleUser${i}`]) {
|
||||||
|
examples.push({
|
||||||
|
user: Config[`chatExampleUser${i}`],
|
||||||
|
bot: Config[`chatExampleBot${i}`]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
let toUploadBody = {
|
let toUploadBody = {
|
||||||
title: currentUse,
|
title: currentUse,
|
||||||
prompt: content,
|
prompt: content,
|
||||||
qq: master || (getUin(this.e) + ''), // 上传者设定为主人qq或机器人qq
|
qq: master || (getUin(this.e) + ''), // 上传者设定为主人qq或机器人qq
|
||||||
use: extraData.use === 'Custom' ? 'Sydney' : 'ChatGPT',
|
use: extraData.use === 'bing' ? 'Bing' : 'ChatGPT',
|
||||||
r18,
|
r18,
|
||||||
description
|
description,
|
||||||
|
examples
|
||||||
}
|
}
|
||||||
logger.info(toUploadBody)
|
logger.info(toUploadBody)
|
||||||
let response = await fetch('https://chatgpt.roki.best/prompt', {
|
let response = await fetch('https://chatgpt.roki.best/prompt', {
|
||||||
|
|
@ -454,8 +455,8 @@ export class help extends plugin {
|
||||||
await e.reply('没有这个设定', true)
|
await e.reply('没有这个设定', true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const { prompt, title } = r.data
|
const { prompt, title, examples } = r.data
|
||||||
saveOnePrompt(title, prompt)
|
saveOnePrompt(title, prompt, examples)
|
||||||
e.reply(`导入成功。您现在可以使用 #chatgpt使用设定${title} 来体验这个设定了。`)
|
e.reply(`导入成功。您现在可以使用 #chatgpt使用设定${title} 来体验这个设定了。`)
|
||||||
} else {
|
} else {
|
||||||
await e.reply('导入失败:' + r.msg)
|
await e.reply('导入失败:' + r.msg)
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,11 @@ import plugin from '../../../lib/plugins/plugin.js'
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Restart } from '../../other/restart.js'
|
import { Restart } from '../../other/restart.js'
|
||||||
import fs from 'fs'
|
|
||||||
import {} from '../utils/common.js'
|
import {} from '../utils/common.js'
|
||||||
|
|
||||||
const _path = process.cwd()
|
|
||||||
const require = createRequire(import.meta.url)
|
const require = createRequire(import.meta.url)
|
||||||
const { exec, execSync } = require('child_process')
|
const { exec, execSync } = require('child_process')
|
||||||
|
|
||||||
const checkAuth = async function (e) {
|
|
||||||
if (!e.isMaster) {
|
|
||||||
e.reply('只有主人才能命令ChatGPT哦~(*/ω\*)')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否在更新中
|
// 是否在更新中
|
||||||
let uping = false
|
let uping = false
|
||||||
|
|
||||||
|
|
|
||||||
112
apps/vocal.js
Normal file
112
apps/vocal.js
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
|
import { SunoClient } from '../client/SunoClient.js'
|
||||||
|
import { Config } from '../utils/config.js'
|
||||||
|
import { 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)
|
||||||
|
if (!songs || songs.length === 0) {
|
||||||
|
e.reply('生成失败,可能是提示词太长或者违规,请检查日志')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
185
client/ChatGLM4Client.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
client/ClaudeAPIClient.js
Normal file
195
client/ClaudeAPIClient.js
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import { newFetch } from '../utils/proxy.js'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { getMessageById, upsertMessage } from '../utils/history.js'
|
||||||
|
import { BaseClient } from './BaseClient.js'
|
||||||
|
|
||||||
|
const BASEURL = 'https://api.anthropic.com'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Content
|
||||||
|
* @property {string} model
|
||||||
|
* @property {string} system
|
||||||
|
* @property {number} max_tokens
|
||||||
|
* @property {boolean} stream
|
||||||
|
* @property {Array<{
|
||||||
|
* role: 'user'|'assistant',
|
||||||
|
* content: string|Array<{
|
||||||
|
* type: 'text'|'image',
|
||||||
|
* text?: string,
|
||||||
|
* source?: {
|
||||||
|
* type: 'base64',
|
||||||
|
* media_type: 'image/jpeg'|'image/png'|'image/gif'|'image/webp',
|
||||||
|
* data: string
|
||||||
|
* }
|
||||||
|
* }>
|
||||||
|
* }>} messages
|
||||||
|
*
|
||||||
|
* Claude消息的基本格式
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ClaudeResponse
|
||||||
|
* @property {string} id
|
||||||
|
* @property {string} type
|
||||||
|
* @property {number} role
|
||||||
|
* @property {number} model
|
||||||
|
* @property {number} stop_reason
|
||||||
|
* @property {number} stop_sequence
|
||||||
|
* @property {number} role
|
||||||
|
* @property {boolean} stream
|
||||||
|
* @property {Array<{
|
||||||
|
* type: string,
|
||||||
|
* text: string
|
||||||
|
* }>} content
|
||||||
|
* @property {Array<{
|
||||||
|
* input_tokens: number,
|
||||||
|
* output_tokens: number,
|
||||||
|
* }>} usage
|
||||||
|
* @property {{
|
||||||
|
* type: string,
|
||||||
|
* message: string,
|
||||||
|
* }} error
|
||||||
|
* Claude响应的基本格式
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class ClaudeAPIClient extends BaseClient {
|
||||||
|
constructor (props) {
|
||||||
|
if (!props.upsertMessage) {
|
||||||
|
props.upsertMessage = async function umGemini (message) {
|
||||||
|
return await upsertMessage(message, 'Claude')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!props.getMessageById) {
|
||||||
|
props.getMessageById = async function umGemini (message) {
|
||||||
|
return await getMessageById(message, 'Claude')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super(props)
|
||||||
|
this.model = props.model
|
||||||
|
this.key = props.key
|
||||||
|
if (!this.key) {
|
||||||
|
throw new Error('no claude API key')
|
||||||
|
}
|
||||||
|
this.baseUrl = props.baseUrl || BASEURL
|
||||||
|
this.supportFunction = false
|
||||||
|
this.debug = props.debug
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHistory (parentMessageId, userId = this.userId, opt = {}) {
|
||||||
|
const history = []
|
||||||
|
let cursor = parentMessageId
|
||||||
|
if (!cursor) {
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let parentMessage = await this.getMessageById(cursor)
|
||||||
|
if (!parentMessage) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
history.push(parentMessage)
|
||||||
|
cursor = parentMessage.parentMessageId
|
||||||
|
if (!cursor) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
return history.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?, functionResponse: FunctionResponse?, system: string?, image: string?, model: string?}} opt
|
||||||
|
* @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>}
|
||||||
|
*/
|
||||||
|
async sendMessage (text, opt = {}) {
|
||||||
|
let history = await this.getHistory(opt.parentMessageId)
|
||||||
|
/**
|
||||||
|
* 发送的body
|
||||||
|
* @type {Content}
|
||||||
|
* @see https://docs.anthropic.com/claude/reference/messages_post
|
||||||
|
*/
|
||||||
|
let body = {}
|
||||||
|
if (opt.system) {
|
||||||
|
body.system = opt.system
|
||||||
|
}
|
||||||
|
const idThis = crypto.randomUUID()
|
||||||
|
const idModel = crypto.randomUUID()
|
||||||
|
/**
|
||||||
|
* @type {Array<{
|
||||||
|
* role: 'user'|'assistant',
|
||||||
|
* content: string|Array<{
|
||||||
|
* type: 'text'|'image',
|
||||||
|
* text?: string,
|
||||||
|
* source?: {
|
||||||
|
* type: 'base64',
|
||||||
|
* media_type: 'image/jpeg'|'image/png'|'image/gif'|'image/webp',
|
||||||
|
* data: string
|
||||||
|
* }
|
||||||
|
* }>
|
||||||
|
* }>}
|
||||||
|
*/
|
||||||
|
let thisContent = [{ type: 'text', text }]
|
||||||
|
if (opt.image) {
|
||||||
|
thisContent.push({
|
||||||
|
type: 'image',
|
||||||
|
source: {
|
||||||
|
type: 'base64',
|
||||||
|
media_type: 'image/jpeg',
|
||||||
|
data: opt.image
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const thisMessage = {
|
||||||
|
role: 'user',
|
||||||
|
content: thisContent,
|
||||||
|
id: idThis,
|
||||||
|
parentMessageId: opt.parentMessageId || undefined
|
||||||
|
}
|
||||||
|
history.push(_.cloneDeep(thisMessage))
|
||||||
|
let messages = history.map(h => { return { role: h.role, content: h.content } })
|
||||||
|
body = Object.assign(body, {
|
||||||
|
model: opt.model || this.model || 'claude-3-opus-20240229',
|
||||||
|
max_tokens: opt.max_tokens || 1024,
|
||||||
|
messages,
|
||||||
|
stream: false
|
||||||
|
})
|
||||||
|
let url = `${this.baseUrl}/v1/messages`
|
||||||
|
let result = await newFetch(url, {
|
||||||
|
headers: {
|
||||||
|
'anthropic-version': '2023-06-01',
|
||||||
|
'x-api-key': this.key,
|
||||||
|
'content-type': 'application/json'
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
if (result.status !== 200) {
|
||||||
|
throw new Error(await result.text())
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @type {ClaudeResponse}
|
||||||
|
*/
|
||||||
|
let response = await result.json()
|
||||||
|
if (this.debug) {
|
||||||
|
console.log(JSON.stringify(response))
|
||||||
|
}
|
||||||
|
if (response.type === 'error') {
|
||||||
|
logger.error(response.error.message)
|
||||||
|
throw new Error(response.error.type)
|
||||||
|
}
|
||||||
|
await this.upsertMessage(thisMessage)
|
||||||
|
const respMessage = Object.assign(response, {
|
||||||
|
id: idModel,
|
||||||
|
parentMessageId: idThis
|
||||||
|
})
|
||||||
|
await this.upsertMessage(respMessage)
|
||||||
|
return {
|
||||||
|
text: response.content[0].text,
|
||||||
|
conversationId: '',
|
||||||
|
parentMessageId: idThis,
|
||||||
|
id: idModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,36 @@
|
||||||
import { Config } from '../config.js'
|
import { BaseClient } from './BaseClient.js'
|
||||||
import slack from '@slack/bolt'
|
import slack from '@slack/bolt'
|
||||||
import { limitString } from '../common.js'
|
// import { limitString } from '../utils/common.js'
|
||||||
import common from '../../../../lib/common/common.js'
|
// import common from '../../../lib/common/common.js'
|
||||||
let proxy
|
import { getProxy } from '../utils/proxy.js'
|
||||||
if (Config.proxy) {
|
const proxy = getProxy()
|
||||||
try {
|
const common = {
|
||||||
proxy = (await import('https-proxy-agent')).default
|
sleep: function (ms) {
|
||||||
} catch (e) {
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class SlackClaudeClient {
|
|
||||||
|
/**
|
||||||
|
* 失败品
|
||||||
|
*/
|
||||||
|
export class SlackCozeClient {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
this.config = props
|
this.config = props
|
||||||
if (Config.slackSigningSecret && Config.slackBotUserToken && Config.slackUserToken) {
|
const {
|
||||||
|
slackSigningSecret, slackBotUserToken, slackUserToken, proxy: proxyAddr, debug
|
||||||
|
} = props
|
||||||
|
if (slackSigningSecret && slackBotUserToken && slackUserToken) {
|
||||||
let option = {
|
let option = {
|
||||||
signingSecret: Config.slackSigningSecret,
|
signingSecret: slackSigningSecret,
|
||||||
token: Config.slackBotUserToken,
|
token: slackBotUserToken,
|
||||||
// socketMode: true,
|
// socketMode: true,
|
||||||
appToken: Config.slackUserToken
|
appToken: slackUserToken
|
||||||
// port: 45912
|
// port: 45912
|
||||||
}
|
}
|
||||||
if (Config.proxy) {
|
if (proxyAddr) {
|
||||||
option.agent = proxy(Config.proxy)
|
option.agent = proxy(proxyAddr)
|
||||||
}
|
}
|
||||||
option.logLevel = Config.debug ? 'debug' : 'info'
|
option.logLevel = debug ? 'debug' : 'info'
|
||||||
this.app = new slack.App(option)
|
this.app = new slack.App(option)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未配置Slack信息')
|
throw new Error('未配置Slack信息')
|
||||||
|
|
@ -37,68 +43,74 @@ export class SlackClaudeClient {
|
||||||
}
|
}
|
||||||
if (prompt.length > 3990) {
|
if (prompt.length > 3990) {
|
||||||
logger.warn('消息长度大于slack限制,长度剪切至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)
|
prompt = limitString(prompt, 3990, false)
|
||||||
}
|
}
|
||||||
let channel
|
let channel
|
||||||
let qq = e.sender.user_id
|
let qq = e.sender.user_id
|
||||||
if (Config.slackClaudeSpecifiedChannel) {
|
if (this.config.slackCozeSpecifiedChannel) {
|
||||||
channel = { id: Config.slackClaudeSpecifiedChannel }
|
channel = { id: this.config.slackCozeSpecifiedChannel }
|
||||||
} else {
|
} else {
|
||||||
let channels = await this.app.client.conversations.list({
|
let channels = await this.app.client.conversations.list({
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
types: 'public_channel,private_channel'
|
types: 'public_channel,private_channel'
|
||||||
})
|
})
|
||||||
channel = channels.channels.filter(c => c.name === '' + qq)
|
channel = channels.channels.filter(c => c.name === 'coze' + qq)
|
||||||
if (!channel || channel.length === 0) {
|
if (!channel || channel.length === 0) {
|
||||||
let createChannelResponse = await this.app.client.conversations.create({
|
let createChannelResponse = await this.app.client.conversations.create({
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
name: qq + '',
|
name: 'coze' + qq,
|
||||||
is_private: true
|
is_private: true
|
||||||
})
|
})
|
||||||
channel = createChannelResponse.channel
|
channel = createChannelResponse.channel
|
||||||
await this.app.client.conversations.invite({
|
await this.app.client.conversations.invite({
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
channel: channel.id,
|
channel: channel.id,
|
||||||
users: Config.slackClaudeUserId
|
users: this.config.slackCozeUserId
|
||||||
})
|
})
|
||||||
await common.sleep(1000)
|
await common.sleep(1000)
|
||||||
} else {
|
} else {
|
||||||
channel = channel[0]
|
channel = channel[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${qq}`)
|
let conversationId = await redis.get(`CHATGPT:SLACK_COZE_CONVERSATION:${qq}`)
|
||||||
|
let toSend = `<@${this.config.slackCozeUserId}> ${prompt}`
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
let sendResponse = await this.app.client.chat.postMessage({
|
let sendResponse = await this.app.client.chat.postMessage({
|
||||||
as_user: true,
|
as_user: true,
|
||||||
text: `<@${Config.slackClaudeUserId}> ${prompt}`,
|
text: toSend,
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
channel: channel.id
|
channel: channel.id
|
||||||
})
|
})
|
||||||
let ts = sendResponse.ts
|
let ts = sendResponse.ts
|
||||||
let response = '_Typing…_'
|
let response = toSend
|
||||||
let tryTimes = 0
|
let tryTimes = 0
|
||||||
// 发完先等3喵
|
// 发完先等3喵
|
||||||
await common.sleep(3000)
|
await common.sleep(3000)
|
||||||
while (response.trim().endsWith('_Typing…_')) {
|
while (response === toSend) {
|
||||||
let replies = await this.app.client.conversations.replies({
|
let replies = await this.app.client.conversations.replies({
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
channel: channel.id,
|
channel: channel.id,
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
ts
|
ts
|
||||||
})
|
})
|
||||||
await await redis.set(`CHATGPT:SLACK_CONVERSATION:${qq}`, `${ts}`)
|
await await redis.set(`CHATGPT:SLACK_COZE_CONVERSATION:${qq}`, `${ts}`)
|
||||||
if (replies.messages.length > 0) {
|
if (replies.messages.length > 0) {
|
||||||
let formalMessages = replies.messages
|
let formalMessages = replies.messages
|
||||||
.filter(m => m.metadata?.event_type !== 'claude_moderation')
|
|
||||||
.filter(m => !m.text.startsWith('_'))
|
|
||||||
if (!formalMessages[formalMessages.length - 1].bot_profile) {
|
|
||||||
// 问题的下一句不是bot回复的,这属于意料之外的问题,可能是多人同时问问题导致 再问一次吧
|
|
||||||
return await this.sendMessage(prompt, e, t + 1)
|
|
||||||
}
|
|
||||||
let reply = formalMessages[formalMessages.length - 1]
|
let reply = formalMessages[formalMessages.length - 1]
|
||||||
if (!reply.text.startsWith(`<@${Config.slackClaudeUserId}>`)) {
|
if (!reply.text.startsWith(`<@${this.config.slackCozeUserId}>`)) {
|
||||||
response = reply.text
|
response = reply.text
|
||||||
if (Config.debug) {
|
if (this.config.debug) {
|
||||||
let text = response.replace('_Typing…_', '')
|
let text = response.replace('_Typing…_', '')
|
||||||
if (text) {
|
if (text) {
|
||||||
logger.info(response.replace('_Typing…_', ''))
|
logger.info(response.replace('_Typing…_', ''))
|
||||||
|
|
@ -108,27 +120,28 @@ export class SlackClaudeClient {
|
||||||
}
|
}
|
||||||
await common.sleep(2000)
|
await common.sleep(2000)
|
||||||
tryTimes++
|
tryTimes++
|
||||||
if (tryTimes > 3 && response === '_Typing…_') {
|
if (tryTimes > 30 && response === toSend) {
|
||||||
// 过了6秒还没任何回复,就重新发一下试试
|
// 过了60秒还没任何回复,就重新发一下试试
|
||||||
logger.warn('claude没有响应,重试中')
|
logger.warn('claude没有响应,重试中')
|
||||||
return await this.sendMessage(prompt, e, t + 1)
|
return await this.sendMessage(prompt, e, t + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
} else {
|
} else {
|
||||||
|
let toSend = `<@${this.config.slackCozeUserId}> ${prompt}`
|
||||||
let postResponse = await this.app.client.chat.postMessage({
|
let postResponse = await this.app.client.chat.postMessage({
|
||||||
as_user: true,
|
as_user: true,
|
||||||
text: `<@${Config.slackClaudeUserId}> ${prompt}`,
|
text: toSend,
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
channel: channel.id,
|
channel: channel.id,
|
||||||
thread_ts: conversationId
|
thread_ts: conversationId
|
||||||
})
|
})
|
||||||
let postTs = postResponse.ts
|
let postTs = postResponse.ts
|
||||||
let response = '_Typing…_'
|
let response = toSend
|
||||||
let tryTimes = 0
|
let tryTimes = 0
|
||||||
// 发完先等3喵
|
// 发完先等3喵
|
||||||
await common.sleep(3000)
|
await common.sleep(3000)
|
||||||
while (response.trim().endsWith('_Typing…_')) {
|
while (response === toSend) {
|
||||||
let replies = await this.app.client.conversations.replies({
|
let replies = await this.app.client.conversations.replies({
|
||||||
token: this.config.slackUserToken,
|
token: this.config.slackUserToken,
|
||||||
channel: channel.id,
|
channel: channel.id,
|
||||||
|
|
@ -139,16 +152,10 @@ export class SlackClaudeClient {
|
||||||
|
|
||||||
if (replies.messages.length > 0) {
|
if (replies.messages.length > 0) {
|
||||||
let formalMessages = replies.messages
|
let formalMessages = replies.messages
|
||||||
.filter(m => m.metadata?.event_type !== 'claude_moderation')
|
|
||||||
.filter(m => !m.text.startsWith('_'))
|
|
||||||
if (!formalMessages[formalMessages.length - 1].bot_profile) {
|
|
||||||
// 问题的下一句不是bot回复的,这属于意料之外的问题,可能是多人同时问问题导致 再问一次吧
|
|
||||||
return await this.sendMessage(prompt, e, t + 1)
|
|
||||||
}
|
|
||||||
let reply = formalMessages[formalMessages.length - 1]
|
let reply = formalMessages[formalMessages.length - 1]
|
||||||
if (!reply.text.startsWith(`<@${Config.slackClaudeUserId}>`)) {
|
if (!reply.text.startsWith(`<@${this.config.slackCozeUserId}>`)) {
|
||||||
response = reply.text
|
response = reply.text
|
||||||
if (Config.debug) {
|
if (this.config.debug) {
|
||||||
let text = response.replace('_Typing…_', '')
|
let text = response.replace('_Typing…_', '')
|
||||||
if (text) {
|
if (text) {
|
||||||
logger.info(response.replace('_Typing…_', ''))
|
logger.info(response.replace('_Typing…_', ''))
|
||||||
|
|
@ -158,8 +165,8 @@ export class SlackClaudeClient {
|
||||||
}
|
}
|
||||||
await common.sleep(2000)
|
await common.sleep(2000)
|
||||||
tryTimes++
|
tryTimes++
|
||||||
if (tryTimes > 3 && response === '_Typing…_') {
|
if (tryTimes > 30 && response === '_Typing…_') {
|
||||||
// 过了6秒还没任何回复,就重新发一下试试
|
// 过了60秒还没任何回复,就重新发一下试试
|
||||||
logger.warn('claude没有响应,重试中')
|
logger.warn('claude没有响应,重试中')
|
||||||
return await this.sendMessage(prompt, e, t + 1)
|
return await this.sendMessage(prompt, e, t + 1)
|
||||||
}
|
}
|
||||||
|
|
@ -168,3 +175,22 @@ export class SlackClaudeClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {}) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
* @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}>}
|
* @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 history = await this.getHistory(opt.parentMessageId)
|
||||||
let systemMessage = opt.system
|
let systemMessage = opt.system
|
||||||
if (systemMessage) {
|
if (systemMessage) {
|
||||||
|
|
@ -157,6 +157,9 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
if (opt.image) {
|
||||||
|
delete body.tools
|
||||||
|
}
|
||||||
body.contents.forEach(content => {
|
body.contents.forEach(content => {
|
||||||
delete content.id
|
delete content.id
|
||||||
delete content.parentMessageId
|
delete content.parentMessageId
|
||||||
|
|
@ -208,9 +211,10 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
|
||||||
// execute function
|
// execute function
|
||||||
try {
|
try {
|
||||||
let args = Object.assign(functionCall.args, {
|
let args = Object.assign(functionCall.args, {
|
||||||
isAdmin: this.e.group.is_admin,
|
isAdmin: this.e.group?.is_admin,
|
||||||
isOwner: this.e.group.is_owner,
|
isOwner: this.e.group?.is_owner,
|
||||||
sender: this.e.sender
|
sender: this.e.sender,
|
||||||
|
mode: 'gemini'
|
||||||
})
|
})
|
||||||
functionResponse.response.content = await chosenTool.func(args, this.e)
|
functionResponse.response.content = await chosenTool.func(args, this.e)
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { BaseClient } from './BaseClient.js'
|
import { BaseClient } from './BaseClient.js'
|
||||||
|
|
||||||
import { getMessageById, upsertMessage } from '../utils/common.js'
|
import { getMessageById, upsertMessage } from '../utils/history.js'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
let GoogleGenerativeAI, HarmBlockThreshold, HarmCategory
|
let GoogleGenerativeAI, HarmBlockThreshold, HarmCategory
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
153
client/SunoClient.js
Normal file
153
client/SunoClient.js
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
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 === 401) {
|
||||||
|
sess = await this.getToken()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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' || clip.status === 'error')
|
||||||
|
songs = queryData.filter(clip => clip.status === 'complete')
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
client/test/ChatGLM4ClientTest.js
Normal file
17
client/test/ChatGLM4ClientTest.js
Normal 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()
|
||||||
27
client/test/ClaudeApiClientTest.js
Normal file
27
client/test/ClaudeApiClientTest.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// import { ClaudeAPIClient } from '../ClaudeAPIClient.js'
|
||||||
|
//
|
||||||
|
// async function test () {
|
||||||
|
// const client = new ClaudeAPIClient({
|
||||||
|
// key: 'sk-ant-api03-**************************************',
|
||||||
|
// model: 'claude-3-opus-20240229',
|
||||||
|
// debug: true,
|
||||||
|
// // baseUrl: 'http://claude-api.ikechan8370.com'
|
||||||
|
// })
|
||||||
|
// let rsp = await client.sendMessage('你好')
|
||||||
|
// console.log(rsp)
|
||||||
|
// }
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
// test()
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { GoogleGeminiClient } from './GoogleGeminiClient.js'
|
import { GoogleGeminiClient } from '../GoogleGeminiClient.js'
|
||||||
|
|
||||||
async function test () {
|
async function test () {
|
||||||
const client = new GoogleGeminiClient({
|
const client = new GoogleGeminiClient({
|
||||||
31
client/test/GozeClientTest.js
Normal file
31
client/test/GozeClientTest.js
Normal 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()
|
||||||
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()
|
||||||
|
|
@ -89,13 +89,6 @@
|
||||||
"whitelist": [],
|
"whitelist": [],
|
||||||
"blacklist": [],
|
"blacklist": [],
|
||||||
"ttsRegex": "/匹配规则/匹配模式",
|
"ttsRegex": "/匹配规则/匹配模式",
|
||||||
"slackUserToken": "",
|
|
||||||
"slackBotUserToken": "",
|
|
||||||
"slackSigningSecret": "",
|
|
||||||
"slackClaudeUserId": "",
|
|
||||||
"slackClaudeEnableGlobalPreset": true,
|
|
||||||
"slackClaudeGlobalPreset": "",
|
|
||||||
"slackClaudeSpecifiedChannel": "",
|
|
||||||
"cloudTranscode": "https://silk.201666.xyz",
|
"cloudTranscode": "https://silk.201666.xyz",
|
||||||
"cloudRender": false,
|
"cloudRender": false,
|
||||||
"cloudMode": "url",
|
"cloudMode": "url",
|
||||||
|
|
|
||||||
1139
guoba.support.js
1139
guoba.support.js
File diff suppressed because it is too large
Load diff
23
index.js
23
index.js
|
|
@ -1,9 +1,16 @@
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import { Config } from './utils/config.js'
|
import { Config } from './utils/config.js'
|
||||||
import { createServer } from './server/index.js'
|
import { createServer, runServer } from './server/index.js'
|
||||||
|
|
||||||
|
logger.info('**************************************')
|
||||||
|
logger.info('chatgpt-plugin加载中')
|
||||||
|
|
||||||
if (!global.segment) {
|
if (!global.segment) {
|
||||||
|
try {
|
||||||
|
global.segment = (await import('icqq')).segment
|
||||||
|
} catch (err) {
|
||||||
global.segment = (await import('oicq')).segment
|
global.segment = (await import('oicq')).segment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = fs.readdirSync('./plugins/chatgpt-plugin/apps').filter(file => file.endsWith('.js'))
|
const files = fs.readdirSync('./plugins/chatgpt-plugin/apps').filter(file => file.endsWith('.js'))
|
||||||
|
|
@ -19,7 +26,6 @@ ret = await Promise.allSettled(ret)
|
||||||
let apps = {}
|
let apps = {}
|
||||||
for (let i in files) {
|
for (let i in files) {
|
||||||
let name = files[i].replace('.js', '')
|
let name = files[i].replace('.js', '')
|
||||||
|
|
||||||
if (ret[i].status !== 'fulfilled') {
|
if (ret[i].status !== 'fulfilled') {
|
||||||
logger.error(`载入插件错误:${logger.red(name)}`)
|
logger.error(`载入插件错误:${logger.red(name)}`)
|
||||||
logger.error(ret[i].reason)
|
logger.error(ret[i].reason)
|
||||||
|
|
@ -27,13 +33,22 @@ for (let i in files) {
|
||||||
}
|
}
|
||||||
apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]
|
apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]
|
||||||
}
|
}
|
||||||
|
global.chatgpt = {
|
||||||
|
|
||||||
|
}
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
await createServer()
|
if (Config.enableToolbox) {
|
||||||
logger.info('**************************************')
|
logger.info('开启工具箱配置项,工具箱启动中')
|
||||||
|
await createServer()
|
||||||
|
await runServer()
|
||||||
|
logger.info('工具箱启动成功')
|
||||||
|
} else {
|
||||||
|
logger.info('提示:当前配置未开启chatgpt工具箱,可通过锅巴或`#chatgpt开启工具箱`指令开启')
|
||||||
|
}
|
||||||
logger.info('chatgpt-plugin加载成功')
|
logger.info('chatgpt-plugin加载成功')
|
||||||
logger.info(`当前版本${Config.version}`)
|
logger.info(`当前版本${Config.version}`)
|
||||||
logger.info('仓库地址 https://github.com/ikechan8370/chatgpt-plugin')
|
logger.info('仓库地址 https://github.com/ikechan8370/chatgpt-plugin')
|
||||||
|
logger.info('文档地址 https://www.yunzai.chat')
|
||||||
logger.info('插件群号 559567232')
|
logger.info('插件群号 559567232')
|
||||||
logger.info('**************************************')
|
logger.info('**************************************')
|
||||||
|
|
||||||
|
|
|
||||||
362
model/conversation.js
Normal file
362
model/conversation.js
Normal file
|
|
@ -0,0 +1,362 @@
|
||||||
|
import { getUin, getUserData } from '../utils/common.js'
|
||||||
|
import { Config } from '../utils/config.js'
|
||||||
|
import { KeyvFile } from 'keyv-file'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德', '双子星', '双子座', '智谱']
|
||||||
|
export const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard', 'gemini', 'gemini', 'chatglm4']
|
||||||
|
|
||||||
|
export class ConversationManager {
|
||||||
|
async endConversation (e) {
|
||||||
|
const userData = await getUserData(e.user_id)
|
||||||
|
const match = e.msg.trim().match('^#?(.*)(结束|新开|摧毁|毁灭|完结)对话')
|
||||||
|
console.log(match[1])
|
||||||
|
let use
|
||||||
|
if (match[1] && match[1] != 'chatgpt') {
|
||||||
|
use = correspondingValues[originalValues.indexOf(match[1])]
|
||||||
|
} else {
|
||||||
|
use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
||||||
|
}
|
||||||
|
console.log(use)
|
||||||
|
await redis.del(`CHATGPT:WRONG_EMOTION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
|
// fast implementation
|
||||||
|
if (use === 'claude') {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_CLAUDE:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
|
await this.reply('claude对话已结束')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (use === 'claude2') {
|
||||||
|
await redis.del(`CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`)
|
||||||
|
await this.reply('claude.ai对话已结束')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (use === 'xh') {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
|
await this.reply('星火对话已结束')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (use === 'bard') {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
|
await this.reply('Bard对话已结束')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let ats = e.message.filter(m => m.type === 'at')
|
||||||
|
const isAtMode = Config.toggleMode === 'at'
|
||||||
|
if (isAtMode) ats = ats.filter(item => item.qq !== getUin(e))
|
||||||
|
if (ats.length === 0) {
|
||||||
|
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') {
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||||
|
}
|
||||||
|
const conversation = {
|
||||||
|
store: new KeyvFile({ filename: 'cache.json' }),
|
||||||
|
namespace: Config.toneStyle
|
||||||
|
}
|
||||||
|
let Keyv
|
||||||
|
try {
|
||||||
|
Keyv = (await import('keyv')).default
|
||||||
|
} catch (err) {
|
||||||
|
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||||
|
}
|
||||||
|
const conversationsCache = new Keyv(conversation)
|
||||||
|
logger.info(`SydneyUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`))
|
||||||
|
await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`)
|
||||||
|
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||||
|
} else if (use === 'chatglm') {
|
||||||
|
const conversation = {
|
||||||
|
store: new KeyvFile({ filename: 'cache.json' }),
|
||||||
|
namespace: 'chatglm_6b'
|
||||||
|
}
|
||||||
|
let Keyv
|
||||||
|
try {
|
||||||
|
Keyv = (await import('keyv')).default
|
||||||
|
} catch (err) {
|
||||||
|
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||||
|
}
|
||||||
|
const conversationsCache = new Keyv(conversation)
|
||||||
|
logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`ChatGLMUser_${e.sender.user_id}`))
|
||||||
|
await conversationsCache.delete(`ChatGLMUser_${e.sender.user_id}`)
|
||||||
|
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||||
|
} else if (use === 'api') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply('当前没有开启对话', true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||||
|
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||||
|
}
|
||||||
|
} else if (use === 'qwen') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_QWEN:${e.sender.user_id}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply('当前没有开启对话', true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_QWEN:${e.sender.user_id}`)
|
||||||
|
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||||
|
}
|
||||||
|
} else if (use === 'gemini') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_GEMINI:${e.sender.user_id}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply('当前没有开启对话', true)
|
||||||
|
} else {
|
||||||
|
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) {
|
||||||
|
await this.reply('当前没有开启对话', true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
||||||
|
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||||
|
}
|
||||||
|
} else if (use === 'browser') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_BROWSER:${e.sender.user_id}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply('当前没有开启对话', true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BROWSER:${e.sender.user_id}`)
|
||||||
|
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let at = ats[0]
|
||||||
|
let qq = at.qq
|
||||||
|
let atUser = _.trimStart(at.text, '@')
|
||||||
|
if (use === 'api3') {
|
||||||
|
await redis.del(`CHATGPT:QQ_CONVERSATION:${qq}`)
|
||||||
|
await this.reply(`${atUser}已退出TA当前的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||||
|
} else if (use === 'bing') {
|
||||||
|
const conversation = {
|
||||||
|
store: new KeyvFile({ filename: 'cache.json' }),
|
||||||
|
namespace: Config.toneStyle
|
||||||
|
}
|
||||||
|
let Keyv
|
||||||
|
try {
|
||||||
|
Keyv = (await import('keyv')).default
|
||||||
|
} catch (err) {
|
||||||
|
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||||
|
}
|
||||||
|
const conversationsCache = new Keyv(conversation)
|
||||||
|
await conversationsCache.delete(`SydneyUser_${qq}`)
|
||||||
|
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||||
|
} else if (use === 'chatglm') {
|
||||||
|
const conversation = {
|
||||||
|
store: new KeyvFile({ filename: 'cache.json' }),
|
||||||
|
namespace: 'chatglm_6b'
|
||||||
|
}
|
||||||
|
let Keyv
|
||||||
|
try {
|
||||||
|
Keyv = (await import('keyv')).default
|
||||||
|
} catch (err) {
|
||||||
|
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||||
|
}
|
||||||
|
const conversationsCache = new Keyv(conversation)
|
||||||
|
logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`ChatGLMUser_${e.sender.user_id}`))
|
||||||
|
await conversationsCache.delete(`ChatGLMUser_${qq}`)
|
||||||
|
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||||
|
} else if (use === 'api') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS:${qq}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS:${qq}`)
|
||||||
|
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||||
|
}
|
||||||
|
} else if (use === 'qwen') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_QWEN:${qq}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_QWEN:${qq}`)
|
||||||
|
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||||
|
}
|
||||||
|
} else if (use === 'gemini') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_GEMINI:${qq}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||||
|
} else {
|
||||||
|
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) {
|
||||||
|
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BING:${qq}`)
|
||||||
|
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||||
|
}
|
||||||
|
} else if (use === 'browser') {
|
||||||
|
let c = await redis.get(`CHATGPT:CONVERSATIONS_BROWSER:${qq}`)
|
||||||
|
if (!c) {
|
||||||
|
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||||
|
} else {
|
||||||
|
await redis.del(`CHATGPT:CONVERSATIONS_BROWSER:${qq}`)
|
||||||
|
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async endAllConversations (e) {
|
||||||
|
const match = e.msg.trim().match('^#?(.*)(结束|新开|摧毁|毁灭|完结)全部对话')
|
||||||
|
console.log(match[1])
|
||||||
|
let use
|
||||||
|
if (match[1] && match[1] != 'chatgpt') {
|
||||||
|
use = correspondingValues[originalValues.indexOf(match[1])]
|
||||||
|
} else {
|
||||||
|
use = await redis.get('CHATGPT:USE') || 'api'
|
||||||
|
}
|
||||||
|
console.log(use)
|
||||||
|
let deleted = 0
|
||||||
|
switch (use) {
|
||||||
|
case 'claude': {
|
||||||
|
let cs = await redis.keys('CHATGPT:CONVERSATIONS_CLAUDE:*')
|
||||||
|
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||||
|
for (let i = 0; i < cs.length; i++) {
|
||||||
|
await redis.del(cs[i])
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete claude conversation of qq: ' + cs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
for (const element of we) {
|
||||||
|
await redis.del(element)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'xh': {
|
||||||
|
let cs = await redis.keys('CHATGPT:CONVERSATIONS_XH:*')
|
||||||
|
for (let i = 0; i < cs.length; i++) {
|
||||||
|
await redis.del(cs[i])
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete xh conversation of qq: ' + cs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'bard': {
|
||||||
|
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BARD:*')
|
||||||
|
for (let i = 0; i < cs.length; i++) {
|
||||||
|
await redis.del(cs[i])
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete bard conversation of qq: ' + cs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'bing': {
|
||||||
|
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BING:*')
|
||||||
|
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||||
|
for (let i = 0; i < cs.length; i++) {
|
||||||
|
await redis.del(cs[i])
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete bing conversation of qq: ' + cs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
for (const element of we) {
|
||||||
|
await redis.del(element)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'api': {
|
||||||
|
let cs = await redis.keys('CHATGPT:CONVERSATIONS:*')
|
||||||
|
for (let i = 0; i < cs.length; i++) {
|
||||||
|
await redis.del(cs[i])
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete api conversation of qq: ' + cs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'api3': {
|
||||||
|
let qcs = await redis.keys('CHATGPT:QQ_CONVERSATION:*')
|
||||||
|
for (let i = 0; i < qcs.length; i++) {
|
||||||
|
await redis.del(qcs[i])
|
||||||
|
// todo clean last message id
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete conversation bind: ' + qcs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'chatglm': {
|
||||||
|
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_CHATGLM:*')
|
||||||
|
for (let i = 0; i < qcs.length; i++) {
|
||||||
|
await redis.del(qcs[i])
|
||||||
|
// todo clean last message id
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete chatglm conversation bind: ' + qcs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'qwen': {
|
||||||
|
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_QWEN:*')
|
||||||
|
for (let i = 0; i < qcs.length; i++) {
|
||||||
|
await redis.del(qcs[i])
|
||||||
|
// todo clean last message id
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete qwen conversation bind: ' + qcs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'gemini': {
|
||||||
|
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_GEMINI:*')
|
||||||
|
for (let i = 0; i < qcs.length; i++) {
|
||||||
|
await redis.del(qcs[i])
|
||||||
|
// todo clean last message id
|
||||||
|
if (Config.debug) {
|
||||||
|
logger.info('delete gemini conversation bind: ' + qcs[i])
|
||||||
|
}
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
1166
model/core.js
Normal file
1166
model/core.js
Normal file
File diff suppressed because it is too large
Load diff
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -14,7 +14,6 @@
|
||||||
"@google/generative-ai": "^0.1.1",
|
"@google/generative-ai": "^0.1.1",
|
||||||
"@slack/bolt": "^3.13.2",
|
"@slack/bolt": "^3.13.2",
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
"delay": "^6.0.0",
|
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"emoji-strip": "^1.0.1",
|
"emoji-strip": "^1.0.1",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
|
|
@ -2390,17 +2389,6 @@
|
||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "chatgpt-plugin",
|
"name": "chatgpt-plugin",
|
||||||
|
"version": "2.8.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "ikechan8370",
|
"author": "ikechan8370",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -9,7 +10,6 @@
|
||||||
"@fastify/static": "^6.9.0",
|
"@fastify/static": "^6.9.0",
|
||||||
"@fastify/websocket": "^8.2.0",
|
"@fastify/websocket": "^8.2.0",
|
||||||
"@google/generative-ai": "^0.1.1",
|
"@google/generative-ai": "^0.1.1",
|
||||||
"@slack/bolt": "^3.13.2",
|
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"emoji-strip": "^1.0.1",
|
"emoji-strip": "^1.0.1",
|
||||||
|
|
@ -40,9 +40,6 @@
|
||||||
"node-silk": "^0.1.0",
|
"node-silk": "^0.1.0",
|
||||||
"nodejs-pptx": "^1.2.4",
|
"nodejs-pptx": "^1.2.4",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
"puppeteer-extra": "^3.3.6",
|
|
||||||
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
||||||
"sharp": "^0.32.3",
|
"sharp": "^0.32.3",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -472,8 +472,7 @@
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Bing模式",
|
"label": "Bing模式",
|
||||||
"data": "toneStyle",
|
"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",
|
"type": "check",
|
||||||
|
|
|
||||||
113
server/index.js
113
server/index.js
|
|
@ -20,39 +20,9 @@ import Guoba from './modules/guoba.js'
|
||||||
import SettingView from './modules/setting_view.js'
|
import SettingView from './modules/setting_view.js'
|
||||||
|
|
||||||
const __dirname = path.resolve()
|
const __dirname = path.resolve()
|
||||||
const server = fastify({
|
|
||||||
logger: Config.debug
|
|
||||||
})
|
|
||||||
|
|
||||||
async function setUserData(qq, data) {
|
|
||||||
const dir = 'resources/ChatGPTCache/user'
|
|
||||||
const filename = `${qq}.json`
|
|
||||||
const filepath = path.join(dir, filename)
|
|
||||||
fs.mkdirSync(dir, { recursive: true })
|
|
||||||
fs.writeFileSync(filepath, JSON.stringify(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
await server.register(cors, {
|
|
||||||
origin: '*'
|
|
||||||
})
|
|
||||||
await server.register(fstatic, {
|
|
||||||
root: path.join(__dirname, 'plugins/chatgpt-plugin/server/static/')
|
|
||||||
})
|
|
||||||
await server.register(websocket, {
|
|
||||||
cors: true,
|
|
||||||
options: {
|
|
||||||
maxPayload: 1048576
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await server.register(fastifyCookie)
|
|
||||||
await server.register(webRoute)
|
|
||||||
await server.register(webUser)
|
|
||||||
await server.register(SettingView)
|
|
||||||
await server.register(webPrompt)
|
|
||||||
await server.register(Guoba)
|
|
||||||
|
|
||||||
// 无法访问端口的情况下创建与media的通讯
|
// 无法访问端口的情况下创建与media的通讯
|
||||||
async function mediaLink() {
|
async function mediaLink () {
|
||||||
const ip = await getPublicIP()
|
const ip = await getPublicIP()
|
||||||
const testServer = await fetch(`${Config.cloudTranscode}/check`,
|
const testServer = await fetch(`${Config.cloudTranscode}/check`,
|
||||||
{
|
{
|
||||||
|
|
@ -74,7 +44,7 @@ async function mediaLink() {
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
command: 'register',
|
command: 'register',
|
||||||
region: getUin(),
|
region: getUin(),
|
||||||
type: 'server',
|
type: 'server'
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
ws.on('message', async (message) => {
|
ws.on('message', async (message) => {
|
||||||
|
|
@ -108,14 +78,13 @@ async function mediaLink() {
|
||||||
if (data.qq && data.passwd) {
|
if (data.qq && data.passwd) {
|
||||||
const token = randomString(32)
|
const token = randomString(32)
|
||||||
if (data.qq == getUin() && await redis.get('CHATGPT:ADMIN_PASSWD') == data.passwd) {
|
if (data.qq == getUin() && await redis.get('CHATGPT:ADMIN_PASSWD') == data.passwd) {
|
||||||
AddUser({ user: data.qq, token: token, autho: 'admin' })
|
AddUser({ user: data.qq, token, autho: 'admin' })
|
||||||
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'admin', token: token, region: getUin(), type: 'server' }))
|
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'admin', token, region: getUin(), type: 'server' }))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const user = await getUserData(data.qq)
|
const user = await getUserData(data.qq)
|
||||||
if (user.passwd != '' && user.passwd === data.passwd) {
|
if (user.passwd != '' && user.passwd === data.passwd) {
|
||||||
AddUser({ user: data.qq, token: token, autho: 'user' })
|
AddUser({ user: data.qq, token, autho: 'user' })
|
||||||
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'user', token: token, region: getUin(), type: 'server' }))
|
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'user', token, region: getUin(), type: 'server' }))
|
||||||
} else {
|
} else {
|
||||||
ws.send(JSON.stringify({ command: data.command, state: false, error: `用户名密码错误,如果忘记密码请私聊机器人输入 ${data.qq == getUin() ? '#修改管理密码' : '#修改用户密码'} 进行修改`, region: getUin(), type: 'server' }))
|
ws.send(JSON.stringify({ command: data.command, state: false, error: `用户名密码错误,如果忘记密码请私聊机器人输入 ${data.qq == getUin() ? '#修改管理密码' : '#修改用户密码'} 进行修改`, region: getUin(), type: 'server' }))
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +110,6 @@ async function mediaLink() {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log('本地服务网络正常,无需开启通讯')
|
console.log('本地服务网络正常,无需开启通讯')
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +120,38 @@ async function mediaLink() {
|
||||||
// 未完工,暂不开启这个功能
|
// 未完工,暂不开启这个功能
|
||||||
// mediaLink()
|
// mediaLink()
|
||||||
|
|
||||||
export async function createServer() {
|
export async function createServer () {
|
||||||
|
let server = fastify({
|
||||||
|
logger: Config.debug
|
||||||
|
})
|
||||||
|
|
||||||
|
async function setUserData (qq, data) {
|
||||||
|
const dir = 'resources/ChatGPTCache/user'
|
||||||
|
const filename = `${qq}.json`
|
||||||
|
const filepath = path.join(dir, filename)
|
||||||
|
fs.mkdirSync(dir, { recursive: true })
|
||||||
|
fs.writeFileSync(filepath, JSON.stringify(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.register(cors, {
|
||||||
|
origin: '*'
|
||||||
|
})
|
||||||
|
await server.register(fstatic, {
|
||||||
|
root: path.join(__dirname, 'plugins/chatgpt-plugin/server/static/')
|
||||||
|
})
|
||||||
|
await server.register(websocket, {
|
||||||
|
cors: true,
|
||||||
|
options: {
|
||||||
|
maxPayload: 1048576
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await server.register(fastifyCookie)
|
||||||
|
await server.register(webRoute)
|
||||||
|
await server.register(webUser)
|
||||||
|
await server.register(SettingView)
|
||||||
|
await server.register(webPrompt)
|
||||||
|
await server.register(Guoba)
|
||||||
|
|
||||||
// 页面数据获取
|
// 页面数据获取
|
||||||
server.post('/page', async (request, reply) => {
|
server.post('/page', async (request, reply) => {
|
||||||
const body = request.body || {}
|
const body = request.body || {}
|
||||||
|
|
@ -316,7 +315,7 @@ export async function createServer() {
|
||||||
Bot.sendPrivateMsg(parseInt(data.id), data.message, data.quotable)
|
Bot.sendPrivateMsg(parseInt(data.id), data.message, data.quotable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await connection.socket.send(JSON.stringify({ command: data.command, state: true, }))
|
await connection.socket.send(JSON.stringify({ command: data.command, state: true }))
|
||||||
} else {
|
} else {
|
||||||
await connection.socket.send(JSON.stringify({ command: data.command, state: false, error: '参数不足' }))
|
await connection.socket.send(JSON.stringify({ command: data.command, state: false, error: '参数不足' }))
|
||||||
}
|
}
|
||||||
|
|
@ -370,7 +369,7 @@ export async function createServer() {
|
||||||
seq: e.seq,
|
seq: e.seq,
|
||||||
rand: e.rand,
|
rand: e.rand,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
user_name: e.sender.nickname,
|
user_name: e.sender.nickname
|
||||||
},
|
},
|
||||||
read: true
|
read: true
|
||||||
}
|
}
|
||||||
|
|
@ -380,12 +379,12 @@ export async function createServer() {
|
||||||
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
await connection.socket.send(JSON.stringify({ "data": data }))
|
await connection.socket.send(JSON.stringify({ data }))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
await connection.socket.send(JSON.stringify({ "error": error.message }))
|
await connection.socket.send(JSON.stringify({ error: error.message }))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
connection.socket.on('close', () => {
|
connection.socket.on('close', () => {
|
||||||
|
|
@ -395,7 +394,7 @@ export async function createServer() {
|
||||||
})
|
})
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
Bot.on("message", e => {
|
Bot.on('message', e => {
|
||||||
const messageData = {
|
const messageData = {
|
||||||
notice: 'clientMessage',
|
notice: 'clientMessage',
|
||||||
message: e.message,
|
message: e.message,
|
||||||
|
|
@ -411,7 +410,7 @@ export async function createServer() {
|
||||||
seq: e.seq,
|
seq: e.seq,
|
||||||
rand: e.rand,
|
rand: e.rand,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
user_name: e.sender.nickname,
|
user_name: e.sender.nickname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clients) {
|
if (clients) {
|
||||||
|
|
@ -486,10 +485,10 @@ export async function createServer() {
|
||||||
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) {
|
||||||
//检查云服务api
|
// 检查云服务api
|
||||||
if (keyPath === 'cloudTranscode') {
|
if (keyPath === 'cloudTranscode') {
|
||||||
const referer = request.headers.referer;
|
const referer = request.headers.referer
|
||||||
const origin = referer.match(/(https?:\/\/[^/]+)/)[1];
|
const origin = referer.match(/(https?:\/\/[^/]+)/)[1]
|
||||||
const checkCloud = await fetch(`${value}/check`,
|
const checkCloud = await fetch(`${value}/check`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -562,7 +561,7 @@ export async function createServer() {
|
||||||
// 系统服务测试
|
// 系统服务测试
|
||||||
server.post('/serverTest', async (request, reply) => {
|
server.post('/serverTest', async (request, reply) => {
|
||||||
let serverState = {
|
let serverState = {
|
||||||
cache: false, //待移除
|
cache: false, // 待移除
|
||||||
cloud: false
|
cloud: false
|
||||||
}
|
}
|
||||||
if (Config.cloudTranscode) {
|
if (Config.cloudTranscode) {
|
||||||
|
|
@ -575,6 +574,15 @@ export async function createServer() {
|
||||||
return reply
|
return reply
|
||||||
})
|
})
|
||||||
|
|
||||||
|
global.chatgpt.server = server
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runServer () {
|
||||||
|
let server = global.chatgpt.server
|
||||||
|
if (!server) {
|
||||||
|
server = await createServer()
|
||||||
|
}
|
||||||
server.listen({
|
server.listen({
|
||||||
port: Config.serverPort || 3321,
|
port: Config.serverPort || 3321,
|
||||||
host: '::'
|
host: '::'
|
||||||
|
|
@ -586,3 +594,10 @@ export async function createServer() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function stopServer () {
|
||||||
|
let server = global.chatgpt.server
|
||||||
|
if (server) {
|
||||||
|
await server.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { UserInfo } from './user_data.js'
|
||||||
import { Config } from '../../utils/config.js'
|
import { Config } from '../../utils/config.js'
|
||||||
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../../utils/prompts.js'
|
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../../utils/prompts.js'
|
||||||
|
|
||||||
async function Prompt(fastify, options) {
|
async function Prompt (fastify, options) {
|
||||||
// 获取设定列表
|
// 获取设定列表
|
||||||
fastify.post('/getPromptList', async (request, reply) => {
|
fastify.post('/getPromptList', async (request, reply) => {
|
||||||
const token = request.cookies.token || request.body?.token || 'unknown'
|
const token = request.cookies.token || request.body?.token || 'unknown'
|
||||||
|
|
@ -89,12 +89,7 @@ async function Prompt(fastify, options) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
prompt = false
|
prompt = false
|
||||||
reply.send({ state: false, use: use, error: '未找到设定' })
|
reply.send({ state: false, use, error: '未找到设定' })
|
||||||
}
|
|
||||||
}
|
|
||||||
if (use.toLowerCase() === 'bing') {
|
|
||||||
if (Config.toneStyle === 'Custom') {
|
|
||||||
use = 'Custom'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const keyMap = {
|
const keyMap = {
|
||||||
|
|
@ -111,9 +106,9 @@ async function Prompt(fastify, options) {
|
||||||
Config[keyMap[use]] = prompt.content
|
Config[keyMap[use]] = prompt.content
|
||||||
}
|
}
|
||||||
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
|
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
|
||||||
reply.send({ state: true, use: use })
|
reply.send({ state: true, use })
|
||||||
} else {
|
} else {
|
||||||
reply.send({ state: false, use: use, error: '当前模式不支持设定修改' })
|
reply.send({ state: false, use, error: '当前模式不支持设定修改' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -124,6 +119,5 @@ async function Prompt(fastify, options) {
|
||||||
}
|
}
|
||||||
return reply
|
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
|
|
@ -19,9 +19,9 @@ export default class BingDrawClient {
|
||||||
// let d = Math.ceil(Math.random() * 255)
|
// let d = Math.ceil(Math.random() * 255)
|
||||||
// let randomIp = '141.11.138.' + d
|
// let randomIp = '141.11.138.' + d
|
||||||
let headers = {
|
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: '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',
|
// 'accept-language': 'en-US,en;q=0.9',
|
||||||
'cache-control': 'max-age=0',
|
// 'cache-control': 'max-age=0',
|
||||||
'content-type': 'application/x-www-form-urlencoded',
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
referrer: 'https://www.bing.com/images/create/',
|
referrer: 'https://www.bing.com/images/create/',
|
||||||
origin: 'https://www.bing.com',
|
origin: 'https://www.bing.com',
|
||||||
|
|
@ -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' }))
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import fetch, {
|
import fetch, {
|
||||||
Headers,
|
// Headers,
|
||||||
Request,
|
// Request,
|
||||||
Response,
|
// Response,
|
||||||
FormData
|
FormData
|
||||||
} from 'node-fetch'
|
} from 'node-fetch'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import WebSocket from 'ws'
|
import WebSocket from 'ws'
|
||||||
import { Config, pureSydneyInstruction } from './config.js'
|
import { Config } from './config.js'
|
||||||
import { formatDate, getMasterQQ, isCN, getUserData, limitString } from './common.js'
|
import { formatDate, getMasterQQ, isCN, getUserData, limitString } from './common.js'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { getProxy } from './proxy.js'
|
import { getProxy } from './proxy.js'
|
||||||
import common from '../../../lib/common/common.js'
|
import common from '../../../lib/common/common.js'
|
||||||
|
//
|
||||||
if (!globalThis.fetch) {
|
// if (!globalThis.fetch) {
|
||||||
globalThis.fetch = fetch
|
// globalThis.fetch = fetch
|
||||||
globalThis.Headers = Headers
|
// globalThis.Headers = Headers
|
||||||
globalThis.Request = Request
|
// globalThis.Request = Request
|
||||||
globalThis.Response = Response
|
// globalThis.Response = Response
|
||||||
}
|
// }
|
||||||
// workaround for ver 7.x and ver 5.x
|
// workaround for ver 7.x and ver 5.x
|
||||||
let proxy = getProxy()
|
let proxy = getProxy()
|
||||||
|
|
||||||
|
|
@ -65,31 +65,57 @@ export default class SydneyAIClient {
|
||||||
accept: 'application/json',
|
accept: 'application/json',
|
||||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
// 'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
|
||||||
// 'sec-ch-ua-arch': '"x86"',
|
'sec-ch-ua-arch': '"x86"',
|
||||||
// 'sec-ch-ua-bitness': '"64"',
|
'sec-ch-ua-bitness': '"64"',
|
||||||
// 'sec-ch-ua-full-version': '"112.0.1722.7"',
|
'sec-ch-ua-full-version': '"112.0.1722.7"',
|
||||||
// 'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.20", "Microsoft Edge";v="112.0.1722.7", "Not:A-Brand";v="99.0.0.0"',
|
||||||
// 'sec-ch-ua-mobile': '?0',
|
'sec-ch-ua-mobile': '?0',
|
||||||
// 'sec-ch-ua-model': '',
|
'sec-ch-ua-model': '',
|
||||||
// 'sec-ch-ua-platform': '"macOS"',
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
// 'sec-ch-ua-platform-version': '"15.0.0"',
|
'sec-ch-ua-platform-version': '"15.0.0"',
|
||||||
// 'sec-fetch-dest': 'empty',
|
'sec-fetch-dest': 'empty',
|
||||||
// 'sec-fetch-mode': 'cors',
|
'sec-fetch-mode': 'cors',
|
||||||
// 'sec-fetch-site': 'same-origin',
|
'sec-fetch-site': 'same-origin',
|
||||||
// 'x-ms-client-request-id': crypto.randomUUID(),
|
'x-ms-client-request-id': crypto.randomUUID(),
|
||||||
// 'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/macOS',
|
'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/macOS',
|
||||||
// cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
// cookie: this.opts.cookies || `_U=${this.opts.userToken}`,
|
||||||
Referer: 'https://edgeservices.bing.com/edgesvc/chat?udsframed=1&form=SHORUN&clientscopes=chat,noheader,channelstable,'
|
Referer: 'https://edgeservices.bing.com/edgesvc/chat?udsframed=1&form=SHORUN&clientscopes=chat,noheader,channelstable,',
|
||||||
// 'Referrer-Policy': 'origin-when-cross-origin',
|
'Referrer-Policy': 'origin-when-cross-origin'
|
||||||
// Workaround for request being blocked due to geolocation
|
// Workaround for request being blocked due to geolocation
|
||||||
// 'x-forwarded-for': '1.1.1.1'
|
// 'x-forwarded-for': '1.1.1.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let initCk = 'SRCHD=AF=NOFORM; PPLState=1; SRCHHPGUSR=HV=' + new Date().getTime() + ';'
|
let initCk = 'SRCHD=AF=NOFORM; PPLState=1; SRCHHPGUSR=HV=' + new Date().getTime() + ';'
|
||||||
if (this.opts.userToken) {
|
if (this.opts.userToken || this.opts.cookies) {
|
||||||
// 疑似无需token了
|
// 疑似无需token了
|
||||||
|
if (!this.opts.cookies) {
|
||||||
fetchOptions.headers.cookie = `${initCk} _U=${this.opts.userToken}`
|
fetchOptions.headers.cookie = `${initCk} _U=${this.opts.userToken}`
|
||||||
|
} else {
|
||||||
|
fetchOptions.headers.cookie = this.opts.cookies
|
||||||
|
}
|
||||||
|
// let hash = md5(this.opts.cookies || this.opts.userToken)
|
||||||
|
let hash = crypto.createHash('md5').update(this.opts.cookies || this.opts.userToken).digest('hex')
|
||||||
|
let proTag = await redis.get('CHATGPT:COPILOT_PRO_TAG:' + hash)
|
||||||
|
if (!proTag) {
|
||||||
|
let indexContentRes = await fetch('https://www.bing.com/chat', {
|
||||||
|
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:' + hash, proTag, { EX: 7200 })
|
||||||
|
}
|
||||||
|
if (proTag === 'true') {
|
||||||
|
logger.info('当前账户为copilot pro用户')
|
||||||
|
this.pro = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchOptions.headers.cookie = initCk
|
fetchOptions.headers.cookie = initCk
|
||||||
}
|
}
|
||||||
|
|
@ -103,12 +129,12 @@ export default class SydneyAIClient {
|
||||||
this.opts.host = 'https://edgeservices.bing.com/edgesvc'
|
this.opts.host = 'https://edgeservices.bing.com/edgesvc'
|
||||||
}
|
}
|
||||||
logger.mark('使用host:' + this.opts.host)
|
logger.mark('使用host:' + this.opts.host)
|
||||||
let response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1381.12`, fetchOptions)
|
let response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1626.12`, fetchOptions)
|
||||||
let text = await response.text()
|
let text = await response.text()
|
||||||
let retry = 10
|
let retry = 10
|
||||||
while (retry >= 0 && response.status === 200 && !text) {
|
while (retry >= 0 && response.status === 200 && !text) {
|
||||||
await common.sleep(400)
|
await common.sleep(400)
|
||||||
response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1381.12`, fetchOptions)
|
response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1626.12`, fetchOptions)
|
||||||
text = await response.text()
|
text = await response.text()
|
||||||
retry--
|
retry--
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +256,8 @@ export default class SydneyAIClient {
|
||||||
groupId, nickname, qq, groupName, chats, botName, masterName,
|
groupId, nickname, qq, groupName, chats, botName, masterName,
|
||||||
messageType = 'Chat',
|
messageType = 'Chat',
|
||||||
toSummaryFileContent,
|
toSummaryFileContent,
|
||||||
onImageCreateRequest = prompt => {}
|
onImageCreateRequest = prompt => {},
|
||||||
|
isPro = this.pro
|
||||||
} = opts
|
} = opts
|
||||||
// if (messageType === 'Chat') {
|
// if (messageType === 'Chat') {
|
||||||
// logger.warn('该Bing账户token已被限流,降级至使用非搜索模式。本次对话AI将无法使用Bing搜索返回的内容')
|
// logger.warn('该Bing账户token已被限流,降级至使用非搜索模式。本次对话AI将无法使用Bing搜索返回的内容')
|
||||||
|
|
@ -262,7 +289,6 @@ export default class SydneyAIClient {
|
||||||
encryptedconversationsignature
|
encryptedconversationsignature
|
||||||
} = createNewConversationResponse)
|
} = 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.
|
// 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 stopToken = '\n\nUser:'
|
||||||
const conversationKey = `SydneyUser_${this.opts.user}`
|
const conversationKey = `SydneyUser_${this.opts.user}`
|
||||||
|
|
@ -307,28 +333,29 @@ export default class SydneyAIClient {
|
||||||
const groupContextTip = Config.groupContextTip
|
const groupContextTip = Config.groupContextTip
|
||||||
const masterTip = `注意:${masterName ? '我是' + masterName + ',' : ''}。我的qq号是${master},其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要~${whoAmI}`
|
const masterTip = `注意:${masterName ? '我是' + masterName + ',' : ''}。我的qq号是${master},其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要~${whoAmI}`
|
||||||
const moodTip = Config.sydneyMoodTip
|
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.enableGroupContext && groupId) ? groupContextTip : '') +
|
||||||
((Config.enforceMaster && master) ? masterTip : '') +
|
((Config.enforceMaster && master) ? masterTip : '') +
|
||||||
(Config.sydneyMood ? moodTip : '')
|
(Config.sydneyMood ? moodTip : '')
|
||||||
// logger.info(text)
|
if (!text) {
|
||||||
if (pureSydney) {
|
previousMessages = pm
|
||||||
previousMessages = invocationId === 0
|
|
||||||
? [
|
|
||||||
// {
|
|
||||||
// text,
|
|
||||||
// author: 'bot'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// text: `好的,我是${botName || defaultBotName},你的AI助手。`,
|
|
||||||
// author: 'bot'
|
|
||||||
// },
|
|
||||||
...pm
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
} else {
|
} else {
|
||||||
previousMessages = invocationId === 0
|
let example = []
|
||||||
? [
|
for (let i = 1; i < 4; i++) {
|
||||||
|
if (Config[`chatExampleUser${i}`]) {
|
||||||
|
example.push(...[
|
||||||
|
{
|
||||||
|
text: Config[`chatExampleUser${i}`],
|
||||||
|
author: 'user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: Config[`chatExampleBot${i}`],
|
||||||
|
author: 'bot'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousMessages = [
|
||||||
{
|
{
|
||||||
text,
|
text,
|
||||||
author: 'bot'
|
author: 'bot'
|
||||||
|
|
@ -337,9 +364,9 @@ export default class SydneyAIClient {
|
||||||
text: '好的。',
|
text: '好的。',
|
||||||
author: 'bot'
|
author: 'bot'
|
||||||
},
|
},
|
||||||
|
...example,
|
||||||
...pm
|
...pm
|
||||||
]
|
]
|
||||||
: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMessage = {
|
const userMessage = {
|
||||||
|
|
@ -352,59 +379,60 @@ export default class SydneyAIClient {
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
logger.mark('sydney websocket constructed successful')
|
logger.mark('sydney websocket constructed successful')
|
||||||
}
|
}
|
||||||
const toneOption = 'h3imaginative'
|
let tone = Config.toneStyle || 'Creative'
|
||||||
let optionsSets = [
|
// 兼容老版本
|
||||||
'nlu_direct_response_filter',
|
if (tone.toLowerCase() === 'sydney' || tone.toLowerCase() === 'custom') {
|
||||||
'deepleo',
|
Config.toneStyle = 'Creative'
|
||||||
'disable_emoji_spoken_text',
|
|
||||||
'responsible_ai_policy_235',
|
|
||||||
'enablemm',
|
|
||||||
toneOption,
|
|
||||||
// 'dagslnv1',
|
|
||||||
// 'sportsansgnd',
|
|
||||||
// 'dl_edge_desc',
|
|
||||||
// 'noknowimg',
|
|
||||||
// 'dtappid',
|
|
||||||
// 'cricinfo',
|
|
||||||
// 'cricinfov2',
|
|
||||||
'dv3sugg',
|
|
||||||
// 'gencontentv3',
|
|
||||||
'iycapbing',
|
|
||||||
'iyxapbing',
|
|
||||||
// 'revimglnk',
|
|
||||||
// 'revimgsrc1',
|
|
||||||
// 'revimgur',
|
|
||||||
'clgalileo',
|
|
||||||
'eredirecturl'
|
|
||||||
]
|
|
||||||
if (Config.enableGenerateContents) {
|
|
||||||
optionsSets.push(...['gencontentv3'])
|
|
||||||
}
|
}
|
||||||
|
let optionsSets = getOptionSet(Config.toneStyle, Config.enableGenerateContents)
|
||||||
|
let source = 'cib-ccp'; let gptId = 'copilot'
|
||||||
if (!Config.sydneyEnableSearch || toSummaryFileContent?.content) {
|
if (!Config.sydneyEnableSearch || toSummaryFileContent?.content) {
|
||||||
optionsSets.push(...['nosearchall'])
|
optionsSets.push(...['nosearchall'])
|
||||||
}
|
}
|
||||||
|
if (isPro) {
|
||||||
|
tone = tone + 'Classic'
|
||||||
|
invocationId = 2
|
||||||
|
}
|
||||||
|
// 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
|
let maxConv = Config.maxNumUserMessagesInConversation
|
||||||
const currentDate = moment().format('YYYY-MM-DDTHH:mm:ssZ')
|
const currentDate = moment().format('YYYY-MM-DDTHH:mm:ssZ')
|
||||||
const imageDate = await this.kblobImage(opts.imageUrl)
|
const imageDate = await this.kblobImage(opts.imageUrl)
|
||||||
let argument0 = {
|
let argument0 = {
|
||||||
source: 'cib',
|
source,
|
||||||
optionsSets,
|
optionsSets,
|
||||||
allowedMessageTypes: ['ActionRequest', 'Chat', 'Context',
|
allowedMessageTypes: [
|
||||||
// 'InternalSearchQuery', 'InternalSearchResult', 'Disengaged', 'InternalLoaderMessage', 'Progress', 'RenderCardRequest', 'AdsQuery',
|
'ActionRequest',
|
||||||
'InvokeAction', 'SemanticSerp', 'GenerateContentQuery', 'SearchQuery'],
|
'Chat',
|
||||||
sliceIds: [
|
'ConfirmationCard',
|
||||||
// 'e2eperf',
|
'Context',
|
||||||
// 'gbacf',
|
// 'InternalSearchQuery',
|
||||||
// 'srchqryfix',
|
// 'InternalSearchResult',
|
||||||
// 'caccnctacf',
|
// 'Disengaged',
|
||||||
// 'translref',
|
// 'InternalLoaderMessage',
|
||||||
// 'fluxnosearchc',
|
// 'Progress',
|
||||||
// 'fluxnosearch',
|
// 'RenderCardRequest',
|
||||||
// '1115rai289s0',
|
// 'RenderContentRequest',
|
||||||
// '1130deucs0',
|
'AdsQuery',
|
||||||
// '1116pythons0',
|
'SemanticSerp',
|
||||||
// 'cacmuidarb'
|
'GenerateContentQuery',
|
||||||
|
'SearchQuery',
|
||||||
|
'GeneratedCode'
|
||||||
],
|
],
|
||||||
|
sliceIds: [],
|
||||||
requestId: crypto.randomUUID(),
|
requestId: crypto.randomUUID(),
|
||||||
traceId: genRanHex(32),
|
traceId: genRanHex(32),
|
||||||
scenario: 'SERP',
|
scenario: 'SERP',
|
||||||
|
|
@ -415,7 +443,8 @@ export default class SydneyAIClient {
|
||||||
'uprofupd',
|
'uprofupd',
|
||||||
'uprofgen'
|
'uprofgen'
|
||||||
],
|
],
|
||||||
isStartOfSession: invocationId === 0,
|
gptId,
|
||||||
|
isStartOfSession: true,
|
||||||
message: {
|
message: {
|
||||||
locale: 'zh-CN',
|
locale: 'zh-CN',
|
||||||
market: 'zh-CN',
|
market: 'zh-CN',
|
||||||
|
|
@ -438,22 +467,6 @@ export default class SydneyAIClient {
|
||||||
PopulatedPlaceConfidence: 0,
|
PopulatedPlaceConfidence: 0,
|
||||||
UtcOffset: 9,
|
UtcOffset: 9,
|
||||||
Dma: 0
|
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',
|
author: 'user',
|
||||||
|
|
@ -467,7 +480,7 @@ export default class SydneyAIClient {
|
||||||
privacy: 'Internal'
|
privacy: 'Internal'
|
||||||
// messageType: 'SearchQuery'
|
// messageType: 'SearchQuery'
|
||||||
},
|
},
|
||||||
tone: 'Creative',
|
tone,
|
||||||
// privacy: 'Internal',
|
// privacy: 'Internal',
|
||||||
conversationSignature,
|
conversationSignature,
|
||||||
participant: {
|
participant: {
|
||||||
|
|
@ -477,14 +490,24 @@ export default class SydneyAIClient {
|
||||||
conversationId,
|
conversationId,
|
||||||
previousMessages,
|
previousMessages,
|
||||||
plugins: [
|
plugins: [
|
||||||
// {
|
{
|
||||||
// id: 'c310c353-b9f0-4d76-ab0d-1dd5e979cf68'
|
id: 'c310c353-b9f0-4d76-ab0d-1dd5e979cf68',
|
||||||
// }
|
category: 1
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
extraExtensionParameters: {
|
||||||
|
'gpt-creator-persona': {
|
||||||
|
personaId: 'copilot'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (encryptedconversationsignature) {
|
if (encryptedconversationsignature) {
|
||||||
delete argument0.conversationSignature
|
delete argument0.conversationSignature
|
||||||
}
|
}
|
||||||
|
if (isPro) {
|
||||||
|
invocationId = 1
|
||||||
|
}
|
||||||
const obj = {
|
const obj = {
|
||||||
arguments: [
|
arguments: [
|
||||||
argument0
|
argument0
|
||||||
|
|
@ -926,3 +949,73 @@ async function generateRandomIP () {
|
||||||
await redis.set('CHATGPT:BING_IP', ip, { EX: 86400 * 7 })
|
await redis.set('CHATGPT:BING_IP', ip, { EX: 86400 * 7 })
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {'Precise' | 'Balanced' | 'Creative'} tone
|
||||||
|
*/
|
||||||
|
function getOptionSet (tone, generateContent = false) {
|
||||||
|
let optionset = [
|
||||||
|
'nlu_direct_response_filter',
|
||||||
|
'deepleo',
|
||||||
|
'disable_emoji_spoken_text',
|
||||||
|
'responsible_ai_policy_235',
|
||||||
|
'enablemm',
|
||||||
|
'dv3sugg',
|
||||||
|
'autosave',
|
||||||
|
'iyxapbing',
|
||||||
|
'iycapbing',
|
||||||
|
'enable_user_consent',
|
||||||
|
'fluxmemcst'
|
||||||
|
]
|
||||||
|
switch (tone) {
|
||||||
|
case 'Precise':
|
||||||
|
optionset.push(...[
|
||||||
|
'h3precise',
|
||||||
|
'sunoupsell',
|
||||||
|
'botthrottle',
|
||||||
|
'dlimitationnc',
|
||||||
|
'hourthrot',
|
||||||
|
'elec2t',
|
||||||
|
'elecgnd',
|
||||||
|
'gndlogcf',
|
||||||
|
'eredirecturl',
|
||||||
|
'clgalileo',
|
||||||
|
'gencontentv3'
|
||||||
|
])
|
||||||
|
break
|
||||||
|
case 'Balance':
|
||||||
|
optionset.push(...[
|
||||||
|
'galileo',
|
||||||
|
'saharagenconv5',
|
||||||
|
'sunoupsell',
|
||||||
|
'botthrottle',
|
||||||
|
'dlimitationnc',
|
||||||
|
'hourthrot',
|
||||||
|
'elec2t',
|
||||||
|
'elecgnd',
|
||||||
|
'gndlogcf',
|
||||||
|
'eredirecturl'
|
||||||
|
])
|
||||||
|
break
|
||||||
|
case 'Creative':
|
||||||
|
optionset.push(...[
|
||||||
|
'h3imaginative',
|
||||||
|
'sunoupsell',
|
||||||
|
'botthrottle',
|
||||||
|
'dlimitationnc',
|
||||||
|
'hourthrot',
|
||||||
|
'elec2t',
|
||||||
|
'elecgnd',
|
||||||
|
'gndlogcf',
|
||||||
|
'eredirecturl',
|
||||||
|
'clgalileo',
|
||||||
|
'gencontentv3'
|
||||||
|
])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (generateContent) {
|
||||||
|
optionset.push('gencontentv3')
|
||||||
|
}
|
||||||
|
return optionset
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ import * as types from './types.js';
|
||||||
import globalFetch from 'node-fetch';
|
import globalFetch from 'node-fetch';
|
||||||
var CHATGPT_MODEL = 'qwen-turbo'; // qwen-plus
|
var CHATGPT_MODEL = 'qwen-turbo'; // qwen-plus
|
||||||
var USER_LABEL_DEFAULT = 'User';
|
var USER_LABEL_DEFAULT = 'User';
|
||||||
var ASSISTANT_LABEL_DEFAULT = '同义千问';
|
var ASSISTANT_LABEL_DEFAULT = '通义千问';
|
||||||
var QwenApi = /** @class */ (function () {
|
var QwenApi = /** @class */ (function () {
|
||||||
/**
|
/**
|
||||||
* Creates a new client wrapper around Qwen's chat completion API, mimicing the official ChatGPT webapp's functionality as closely as possible.
|
* Creates a new client wrapper around Qwen's chat completion API, mimicing the official ChatGPT webapp's functionality as closely as possible.
|
||||||
|
|
@ -76,11 +76,11 @@ var QwenApi = /** @class */ (function () {
|
||||||
this._apiBaseUrl = apiBaseUrl;
|
this._apiBaseUrl = apiBaseUrl;
|
||||||
this._debug = !!debug;
|
this._debug = !!debug;
|
||||||
this._fetch = fetch;
|
this._fetch = fetch;
|
||||||
this._completionParams = __assign({ model: CHATGPT_MODEL, parameters: __assign({ top_p: 0.5, top_k: 50, temperature: 1.0, seed: 114514, enable_search: true, result_format: "text", incremental_output: false }, parameters) }, completionParams);
|
this._completionParams = __assign({ model: CHATGPT_MODEL, parameters: __assign({ top_p: 0.5, top_k: 50, temperature: 1.0, seed: 114514, enable_search: true, result_format: "message", incremental_output: false }, parameters) }, completionParams);
|
||||||
this._systemMessage = systemMessage;
|
this._systemMessage = systemMessage;
|
||||||
if (this._systemMessage === undefined) {
|
if (this._systemMessage === undefined) {
|
||||||
var currentDate = new Date().toISOString().split('T')[0];
|
var currentDate = new Date().toISOString().split('T')[0];
|
||||||
this._systemMessage = "You are ChatGPT, a large language model trained by Qwen. Answer as concisely as possible.\nKnowledge cutoff: 2021-09-01\nCurrent date: ".concat(currentDate);
|
this._systemMessage = "You are Qwen, a large language model trained by Alibaba Cloud. Answer as concisely as possible.\nCurrent date: ".concat(currentDate);
|
||||||
}
|
}
|
||||||
this._getMessageById = getMessageById !== null && getMessageById !== void 0 ? getMessageById : this._defaultGetMessageById;
|
this._getMessageById = getMessageById !== null && getMessageById !== void 0 ? getMessageById : this._defaultGetMessageById;
|
||||||
this._upsertMessage = upsertMessage !== null && upsertMessage !== void 0 ? upsertMessage : this._defaultUpsertMessage;
|
this._upsertMessage = upsertMessage !== null && upsertMessage !== void 0 ? upsertMessage : this._defaultUpsertMessage;
|
||||||
|
|
@ -120,7 +120,7 @@ var QwenApi = /** @class */ (function () {
|
||||||
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
|
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
|
||||||
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
|
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
|
||||||
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
||||||
* @param completionParams - Optional overrides to send to the [Qwen chat completion API](https://platform.openai.com/docs/api-reference/chat/create). Options like `temperature` and `presence_penalty` can be tweaked to change the personality of the assistant.
|
* @param opts.completionParams - Optional overrides to send to the [Qwen chat completion API](https://platform.openai.com/docs/api-reference/chat/create). Options like `temperature` and `presence_penalty` can be tweaked to change the personality of the assistant.
|
||||||
*
|
*
|
||||||
* @returns The response from ChatGPT
|
* @returns The response from ChatGPT
|
||||||
*/
|
*/
|
||||||
|
|
@ -128,7 +128,7 @@ var QwenApi = /** @class */ (function () {
|
||||||
if (opts === void 0) { opts = {}; }
|
if (opts === void 0) { opts = {}; }
|
||||||
if (role === void 0) { role = 'user'; }
|
if (role === void 0) { role = 'user'; }
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
var parentMessageId, _a, messageId, timeoutMs, completionParams, conversationId, abortSignal, abortController, message, latestQuestion, _b, messages, maxTokens, numTokens, result, responseP;
|
var parentMessageId, _a, messageId, timeoutMs, completionParams, conversationId, abortSignal, abortController, message, latestQuestion, parameters, _b, messages, maxTokens, numTokens, result, responseP;
|
||||||
var _this = this;
|
var _this = this;
|
||||||
return __generator(this, function (_c) {
|
return __generator(this, function (_c) {
|
||||||
switch (_c.label) {
|
switch (_c.label) {
|
||||||
|
|
@ -148,6 +148,9 @@ var QwenApi = /** @class */ (function () {
|
||||||
text: text,
|
text: text,
|
||||||
};
|
};
|
||||||
latestQuestion = message;
|
latestQuestion = message;
|
||||||
|
parameters = Object.assign(this._completionParams.parameters, completionParams.parameters);
|
||||||
|
completionParams = Object.assign(this._completionParams, completionParams);
|
||||||
|
completionParams.parameters = parameters;
|
||||||
return [4 /*yield*/, this._buildMessages(text, role, opts, completionParams)];
|
return [4 /*yield*/, this._buildMessages(text, role, opts, completionParams)];
|
||||||
case 1:
|
case 1:
|
||||||
_b = _c.sent(), messages = _b.messages, maxTokens = _b.maxTokens, numTokens = _b.numTokens;
|
_b = _c.sent(), messages = _b.messages, maxTokens = _b.maxTokens, numTokens = _b.numTokens;
|
||||||
|
|
@ -158,28 +161,31 @@ var QwenApi = /** @class */ (function () {
|
||||||
conversationId: conversationId,
|
conversationId: conversationId,
|
||||||
parentMessageId: messageId,
|
parentMessageId: messageId,
|
||||||
text: undefined,
|
text: undefined,
|
||||||
|
functionCall: undefined,
|
||||||
|
conversation: []
|
||||||
};
|
};
|
||||||
this._completionParams.input = { messages: messages };
|
completionParams.input = { messages: messages };
|
||||||
responseP = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
responseP = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
||||||
var url, headers, body, res, reason, msg, error, response, err_1;
|
var url, headers, body, res, reason, msg, error, response, err_1;
|
||||||
return __generator(this, function (_a) {
|
var _a, _b, _c, _d, _e;
|
||||||
switch (_a.label) {
|
return __generator(this, function (_f) {
|
||||||
|
switch (_f.label) {
|
||||||
case 0:
|
case 0:
|
||||||
url = "".concat(this._apiBaseUrl, "/services/aigc/text-generation/generation");
|
url = "".concat(this._apiBaseUrl, "/services/aigc/text-generation/generation");
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: "Bearer ".concat(this._apiKey)
|
Authorization: "Bearer ".concat(this._apiKey)
|
||||||
};
|
};
|
||||||
body = __assign(__assign({}, this._completionParams), completionParams);
|
body = completionParams;
|
||||||
if (this._debug) {
|
if (this._debug) {
|
||||||
console.log(JSON.stringify(body));
|
console.log(JSON.stringify(body));
|
||||||
}
|
}
|
||||||
if (this._debug) {
|
if (this._debug) {
|
||||||
console.log("sendMessage (".concat(numTokens, " tokens)"), body);
|
console.log("sendMessage (".concat(numTokens, " tokens)"), body);
|
||||||
}
|
}
|
||||||
_a.label = 1;
|
_f.label = 1;
|
||||||
case 1:
|
case 1:
|
||||||
_a.trys.push([1, 6, , 7]);
|
_f.trys.push([1, 6, , 7]);
|
||||||
return [4 /*yield*/, this._fetch(url, {
|
return [4 /*yield*/, this._fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: headers,
|
headers: headers,
|
||||||
|
|
@ -187,11 +193,11 @@ var QwenApi = /** @class */ (function () {
|
||||||
signal: abortSignal
|
signal: abortSignal
|
||||||
})];
|
})];
|
||||||
case 2:
|
case 2:
|
||||||
res = _a.sent();
|
res = _f.sent();
|
||||||
if (!!res.ok) return [3 /*break*/, 4];
|
if (!!res.ok) return [3 /*break*/, 4];
|
||||||
return [4 /*yield*/, res.text()];
|
return [4 /*yield*/, res.text()];
|
||||||
case 3:
|
case 3:
|
||||||
reason = _a.sent();
|
reason = _f.sent();
|
||||||
msg = "Qwen error ".concat(res.status || res.statusText, ": ").concat(reason);
|
msg = "Qwen error ".concat(res.status || res.statusText, ": ").concat(reason);
|
||||||
error = new types.ChatGPTError(msg, { cause: res });
|
error = new types.ChatGPTError(msg, { cause: res });
|
||||||
error.statusCode = res.status;
|
error.statusCode = res.status;
|
||||||
|
|
@ -199,18 +205,23 @@ var QwenApi = /** @class */ (function () {
|
||||||
return [2 /*return*/, reject(error)];
|
return [2 /*return*/, reject(error)];
|
||||||
case 4: return [4 /*yield*/, res.json()];
|
case 4: return [4 /*yield*/, res.json()];
|
||||||
case 5:
|
case 5:
|
||||||
response = _a.sent();
|
response = _f.sent();
|
||||||
if (this._debug) {
|
if (this._debug) {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
}
|
}
|
||||||
|
if (((_e = (_d = (_c = (_b = (_a = response.output) === null || _a === void 0 ? void 0 : _a.choices) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.tool_calls) === null || _e === void 0 ? void 0 : _e.length) > 0) {
|
||||||
|
// function call result
|
||||||
|
result.functionCall = response.output.choices[0].message.tool_calls[0].function;
|
||||||
|
}
|
||||||
if (response === null || response === void 0 ? void 0 : response.request_id) {
|
if (response === null || response === void 0 ? void 0 : response.request_id) {
|
||||||
result.id = response.request_id;
|
result.id = response.request_id;
|
||||||
}
|
}
|
||||||
result.detail = response;
|
result.detail = response;
|
||||||
result.text = response.output.text;
|
result.text = response.output.choices[0].message.content;
|
||||||
|
result.conversation = messages;
|
||||||
return [2 /*return*/, resolve(result)];
|
return [2 /*return*/, resolve(result)];
|
||||||
case 6:
|
case 6:
|
||||||
err_1 = _a.sent();
|
err_1 = _f.sent();
|
||||||
return [2 /*return*/, reject(err_1)];
|
return [2 /*return*/, reject(err_1)];
|
||||||
case 7: return [2 /*return*/];
|
case 7: return [2 /*return*/];
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +289,8 @@ var QwenApi = /** @class */ (function () {
|
||||||
? messages.concat([
|
? messages.concat([
|
||||||
{
|
{
|
||||||
role: role,
|
role: role,
|
||||||
content: text
|
content: text,
|
||||||
|
name: role === 'tool' ? opts.name : undefined
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
: messages;
|
: messages;
|
||||||
|
|
@ -337,7 +349,8 @@ var QwenApi = /** @class */ (function () {
|
||||||
nextMessages = nextMessages.slice(0, systemMessageOffset).concat(__spreadArray([
|
nextMessages = nextMessages.slice(0, systemMessageOffset).concat(__spreadArray([
|
||||||
{
|
{
|
||||||
role: parentMessageRole,
|
role: parentMessageRole,
|
||||||
content: parentMessage.text
|
content: parentMessage.functionCall ? parentMessage.functionCall.arguments : parentMessage.text,
|
||||||
|
name: parentMessage.functionCall ? parentMessage.functionCall.name : undefined
|
||||||
}
|
}
|
||||||
], nextMessages.slice(systemMessageOffset), true));
|
], nextMessages.slice(systemMessageOffset), true));
|
||||||
parentMessageId = parentMessage.parentMessageId;
|
parentMessageId = parentMessage.parentMessageId;
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@ import * as tokenizer from './tokenizer'
|
||||||
import * as types from './types'
|
import * as types from './types'
|
||||||
import globalFetch from 'node-fetch'
|
import globalFetch from 'node-fetch'
|
||||||
import {qwen, Role} from "./types";
|
import {qwen, Role} from "./types";
|
||||||
|
import {openai} from "../openai/types";
|
||||||
|
|
||||||
const CHATGPT_MODEL = 'qwen-turbo' // qwen-plus
|
const CHATGPT_MODEL = 'qwen-turbo' // qwen-plus
|
||||||
|
|
||||||
const USER_LABEL_DEFAULT = 'User'
|
const USER_LABEL_DEFAULT = 'User'
|
||||||
const ASSISTANT_LABEL_DEFAULT = '同义千问'
|
const ASSISTANT_LABEL_DEFAULT = '通义千问'
|
||||||
|
|
||||||
export class QwenApi {
|
export class QwenApi {
|
||||||
protected _apiKey: string
|
protected _apiKey: string
|
||||||
|
|
@ -64,7 +65,7 @@ export class QwenApi {
|
||||||
temperature: 1.0,
|
temperature: 1.0,
|
||||||
seed: 114514,
|
seed: 114514,
|
||||||
enable_search: true,
|
enable_search: true,
|
||||||
result_format: "text",
|
result_format: "message",
|
||||||
incremental_output: false,
|
incremental_output: false,
|
||||||
...parameters
|
...parameters
|
||||||
},
|
},
|
||||||
|
|
@ -75,7 +76,7 @@ export class QwenApi {
|
||||||
|
|
||||||
if (this._systemMessage === undefined) {
|
if (this._systemMessage === undefined) {
|
||||||
const currentDate = new Date().toISOString().split('T')[0]
|
const currentDate = new Date().toISOString().split('T')[0]
|
||||||
this._systemMessage = `You are ChatGPT, a large language model trained by Qwen. Answer as concisely as possible.\nKnowledge cutoff: 2021-09-01\nCurrent date: ${currentDate}`
|
this._systemMessage = `You are Qwen, a large language model trained by Alibaba Cloud. Answer as concisely as possible.\nCurrent date: ${currentDate}`
|
||||||
}
|
}
|
||||||
|
|
||||||
this._getMessageById = getMessageById ?? this._defaultGetMessageById
|
this._getMessageById = getMessageById ?? this._defaultGetMessageById
|
||||||
|
|
@ -120,7 +121,7 @@ export class QwenApi {
|
||||||
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
|
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
|
||||||
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
|
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
|
||||||
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
|
||||||
* @param completionParams - Optional overrides to send to the [Qwen chat completion API](https://platform.openai.com/docs/api-reference/chat/create). Options like `temperature` and `presence_penalty` can be tweaked to change the personality of the assistant.
|
* @param opts.completionParams - Optional overrides to send to the [Qwen chat completion API](https://platform.openai.com/docs/api-reference/chat/create). Options like `temperature` and `presence_penalty` can be tweaked to change the personality of the assistant.
|
||||||
*
|
*
|
||||||
* @returns The response from ChatGPT
|
* @returns The response from ChatGPT
|
||||||
*/
|
*/
|
||||||
|
|
@ -129,7 +130,7 @@ export class QwenApi {
|
||||||
opts: types.SendMessageOptions = {},
|
opts: types.SendMessageOptions = {},
|
||||||
role: Role = 'user',
|
role: Role = 'user',
|
||||||
): Promise<types.ChatMessage> {
|
): Promise<types.ChatMessage> {
|
||||||
const {
|
let {
|
||||||
parentMessageId,
|
parentMessageId,
|
||||||
messageId = uuidv4(),
|
messageId = uuidv4(),
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
|
|
@ -155,21 +156,30 @@ export class QwenApi {
|
||||||
|
|
||||||
const latestQuestion = message
|
const latestQuestion = message
|
||||||
|
|
||||||
|
let parameters = Object.assign(
|
||||||
|
this._completionParams.parameters,
|
||||||
|
completionParams.parameters
|
||||||
|
)
|
||||||
|
completionParams = Object.assign(this._completionParams, completionParams)
|
||||||
|
completionParams.parameters = parameters
|
||||||
const { messages, maxTokens, numTokens } = await this._buildMessages(
|
const { messages, maxTokens, numTokens } = await this._buildMessages(
|
||||||
text,
|
text,
|
||||||
role,
|
role,
|
||||||
opts,
|
opts,
|
||||||
completionParams
|
completionParams
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(`maxTokens: ${maxTokens}, numTokens: ${numTokens}`)
|
console.log(`maxTokens: ${maxTokens}, numTokens: ${numTokens}`)
|
||||||
const result: types.ChatMessage = {
|
const result: types.ChatMessage & { conversation: qwen.ChatCompletionRequestMessage[] } = {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
conversationId,
|
conversationId,
|
||||||
parentMessageId: messageId,
|
parentMessageId: messageId,
|
||||||
text: undefined,
|
text: undefined,
|
||||||
|
functionCall: undefined,
|
||||||
|
conversation: []
|
||||||
}
|
}
|
||||||
this._completionParams.input = { messages }
|
completionParams.input = { messages }
|
||||||
const responseP = new Promise<types.ChatMessage>(
|
const responseP = new Promise<types.ChatMessage>(
|
||||||
async (resolve, reject) => {
|
async (resolve, reject) => {
|
||||||
const url = `${this._apiBaseUrl}/services/aigc/text-generation/generation`
|
const url = `${this._apiBaseUrl}/services/aigc/text-generation/generation`
|
||||||
|
|
@ -177,10 +187,7 @@ export class QwenApi {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${this._apiKey}`
|
Authorization: `Bearer ${this._apiKey}`
|
||||||
}
|
}
|
||||||
const body = {
|
const body = completionParams
|
||||||
...this._completionParams,
|
|
||||||
...completionParams
|
|
||||||
}
|
|
||||||
if (this._debug) {
|
if (this._debug) {
|
||||||
console.log(JSON.stringify(body))
|
console.log(JSON.stringify(body))
|
||||||
}
|
}
|
||||||
|
|
@ -212,12 +219,16 @@ export class QwenApi {
|
||||||
if (this._debug) {
|
if (this._debug) {
|
||||||
console.log(response)
|
console.log(response)
|
||||||
}
|
}
|
||||||
|
if (response.output?.choices?.[0]?.message?.tool_calls?.length > 0) {
|
||||||
|
// function call result
|
||||||
|
result.functionCall = response.output.choices[0].message.tool_calls[0].function
|
||||||
|
}
|
||||||
if (response?.request_id) {
|
if (response?.request_id) {
|
||||||
result.id = response.request_id
|
result.id = response.request_id
|
||||||
}
|
}
|
||||||
result.detail = response
|
result.detail = response
|
||||||
result.text = response.output.text
|
result.text = response.output.choices[0].message.content
|
||||||
|
result.conversation = messages
|
||||||
return resolve(result)
|
return resolve(result)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return reject(err)
|
return reject(err)
|
||||||
|
|
@ -283,7 +294,8 @@ export class QwenApi {
|
||||||
? messages.concat([
|
? messages.concat([
|
||||||
{
|
{
|
||||||
role,
|
role,
|
||||||
content: text
|
content: text,
|
||||||
|
name: role === 'tool' ? opts.name : undefined
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
: messages
|
: messages
|
||||||
|
|
@ -338,7 +350,8 @@ export class QwenApi {
|
||||||
nextMessages = nextMessages.slice(0, systemMessageOffset).concat([
|
nextMessages = nextMessages.slice(0, systemMessageOffset).concat([
|
||||||
{
|
{
|
||||||
role: parentMessageRole,
|
role: parentMessageRole,
|
||||||
content: parentMessage.text
|
content: parentMessage.functionCall ? parentMessage.functionCall.arguments : parentMessage.text,
|
||||||
|
name: parentMessage.functionCall ? parentMessage.functionCall.name : undefined
|
||||||
},
|
},
|
||||||
...nextMessages.slice(systemMessageOffset)
|
...nextMessages.slice(systemMessageOffset)
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import Keyv from 'keyv'
|
import Keyv from 'keyv'
|
||||||
|
import {openai} from "../openai/types";
|
||||||
|
|
||||||
export type Role = 'user' | 'assistant' | 'system'
|
export type Role = 'user' | 'assistant' | 'system' | 'tool'
|
||||||
|
|
||||||
export type FetchFn = typeof fetch
|
export type FetchFn = typeof fetch
|
||||||
|
|
||||||
|
|
@ -68,6 +69,7 @@ export interface ChatMessage {
|
||||||
detail?:
|
detail?:
|
||||||
| qwen.CreateChatCompletionResponse
|
| qwen.CreateChatCompletionResponse
|
||||||
| CreateChatCompletionStreamResponse
|
| CreateChatCompletionStreamResponse
|
||||||
|
functionCall?: qwen.FunctionCall
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChatGPTError extends Error {
|
export class ChatGPTError extends Error {
|
||||||
|
|
@ -191,7 +193,7 @@ export namespace qwen {
|
||||||
delta: {
|
delta: {
|
||||||
role: Role
|
role: Role
|
||||||
content?: string,
|
content?: string,
|
||||||
function_call?: {name: string, arguments: string}
|
function_call?: { name: string, arguments: string }
|
||||||
}
|
}
|
||||||
index: number
|
index: number
|
||||||
finish_reason: string | null
|
finish_reason: string | null
|
||||||
|
|
@ -217,12 +219,23 @@ export namespace qwen {
|
||||||
* @memberof ChatCompletionRequestMessage
|
* @memberof ChatCompletionRequestMessage
|
||||||
*/
|
*/
|
||||||
content: string
|
content: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* role为tool表示当前message为function_call的调用结果,name是function的名称,需要和上轮response中的tool_calls[i].function.name参数保持一致,content为function的输出。
|
||||||
|
*/
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FunctionCall {
|
||||||
|
name: string
|
||||||
|
arguments: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare const ChatCompletionRequestMessageRoleEnum: {
|
export declare const ChatCompletionRequestMessageRoleEnum: {
|
||||||
readonly System: 'system'
|
readonly System: 'system'
|
||||||
readonly User: 'user'
|
readonly User: 'user'
|
||||||
readonly Assistant: 'assistant'
|
readonly Assistant: 'assistant'
|
||||||
|
readonly Tool: 'tool'
|
||||||
}
|
}
|
||||||
export declare type ChatCompletionRequestMessageRoleEnum =
|
export declare type ChatCompletionRequestMessageRoleEnum =
|
||||||
(typeof ChatCompletionRequestMessageRoleEnum)[keyof typeof ChatCompletionRequestMessageRoleEnum]
|
(typeof ChatCompletionRequestMessageRoleEnum)[keyof typeof ChatCompletionRequestMessageRoleEnum]
|
||||||
|
|
@ -233,14 +246,43 @@ export namespace qwen {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QWenParameters {
|
export interface QWenParameters {
|
||||||
result_format: string
|
result_format: "text" | "message"
|
||||||
top_p: number
|
top_p: number
|
||||||
top_k: number
|
top_k: number
|
||||||
seed: number
|
seed: number
|
||||||
temperature: number
|
temperature: number
|
||||||
enable_search: boolean
|
enable_search: boolean
|
||||||
incremental_output: boolean
|
incremental_output: boolean
|
||||||
|
tools: Tools[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Tools {
|
||||||
|
type: "function"
|
||||||
|
function: QwenFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QwenFunction {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
parameters: QwenFunctionParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QwenFunctionParameters {
|
||||||
|
type: "object"
|
||||||
|
properties: Properties;
|
||||||
|
required?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Properties {
|
||||||
|
[key: string]: Property;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Property {
|
||||||
|
type: string;
|
||||||
|
description?: string;
|
||||||
|
enum?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -262,6 +304,7 @@ export namespace qwen {
|
||||||
|
|
||||||
parameters: QWenParameters
|
parameters: QWenParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -287,9 +330,27 @@ export namespace qwen {
|
||||||
*/
|
*/
|
||||||
usage?: CreateCompletionResponseUsage
|
usage?: CreateCompletionResponseUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QWenOutput {
|
export interface QWenOutput {
|
||||||
finish_reason: string
|
finish_reason: string
|
||||||
text: string
|
text?: string
|
||||||
|
choices?: Choice[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Choice {
|
||||||
|
finish_reason: string
|
||||||
|
message: ResponseMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseMessage {
|
||||||
|
role: Role
|
||||||
|
content: string
|
||||||
|
tool_calls: ToolCall[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolCall {
|
||||||
|
function: FunctionCall
|
||||||
|
type: "function"
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
|
||||||
298
utils/bard.js
298
utils/bard.js
|
|
@ -1,215 +1,215 @@
|
||||||
// https://github.com/EvanZhouDev/bard-ai
|
// https://github.com/EvanZhouDev/bard-ai
|
||||||
|
|
||||||
class Bard {
|
class Bard {
|
||||||
static JSON = "json";
|
static JSON = 'json'
|
||||||
static MD = "markdown";
|
static MD = 'markdown'
|
||||||
|
|
||||||
// ID derived from Cookie
|
// ID derived from Cookie
|
||||||
SNlM0e;
|
SNlM0e
|
||||||
|
|
||||||
// HTTPS Headers
|
// HTTPS Headers
|
||||||
#headers;
|
#headers
|
||||||
|
|
||||||
// Resolution status of initialization call
|
// Resolution status of initialization call
|
||||||
#initPromise;
|
#initPromise
|
||||||
|
|
||||||
#bardURL = "https://bard.google.com";
|
#bardURL = 'https://bard.google.com'
|
||||||
|
|
||||||
// Wether or not to log events to console
|
// Wether or not to log events to console
|
||||||
#verbose = false;
|
#verbose = false
|
||||||
|
|
||||||
// Fetch function
|
// Fetch function
|
||||||
#fetch = fetch;
|
#fetch = fetch
|
||||||
|
|
||||||
constructor(cookie, config) {
|
constructor (cookie, config) {
|
||||||
// Register some settings
|
// Register some settings
|
||||||
if (config?.verbose == true) this.#verbose = true;
|
if (config?.verbose == true) this.#verbose = true
|
||||||
if (config?.fetch) this.#fetch = config.fetch;
|
if (config?.fetch) this.#fetch = config.fetch
|
||||||
// 可变更访问地址,利用反向代理绕过区域限制
|
// 可变更访问地址,利用反向代理绕过区域限制
|
||||||
if (config?.bardURL) this.#bardURL = config.bardURL;
|
if (config?.bardURL) this.#bardURL = config.bardURL
|
||||||
|
|
||||||
// If a Cookie is provided, initialize
|
// If a Cookie is provided, initialize
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
this.#initPromise = this.#init(cookie);
|
this.#initPromise = this.#init(cookie)
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Please provide a Cookie when initializing Bard.");
|
throw new Error('Please provide a Cookie when initializing Bard.')
|
||||||
}
|
}
|
||||||
this.cookie = cookie;
|
this.cookie = cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can also choose to initialize manually
|
// You can also choose to initialize manually
|
||||||
async #init(cookie) {
|
async #init (cookie) {
|
||||||
this.#verbose && console.log("🚀 Starting intialization");
|
this.#verbose && console.log('🚀 Starting intialization')
|
||||||
// Assign headers
|
// Assign headers
|
||||||
this.#headers = {
|
this.#headers = {
|
||||||
Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1],
|
Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1],
|
||||||
"X-Same-Domain": "1",
|
'X-Same-Domain': '1',
|
||||||
"User-Agent":
|
'User-Agent':
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
|
||||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
Origin: this.#bardURL,
|
Origin: this.#bardURL,
|
||||||
Referer: this.#bardURL,
|
Referer: this.#bardURL,
|
||||||
Cookie: (typeof cookie === "object") ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join("")) : ("__Secure-1PSID=" + cookie),
|
Cookie: (typeof cookie === 'object') ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join('')) : ('__Secure-1PSID=' + cookie)
|
||||||
};
|
}
|
||||||
|
|
||||||
let responseText;
|
let responseText
|
||||||
// Attempt to retrieve SNlM0e
|
// Attempt to retrieve SNlM0e
|
||||||
try {
|
try {
|
||||||
this.#verbose &&
|
this.#verbose &&
|
||||||
console.log("🔒 Authenticating your Google account");
|
console.log('🔒 Authenticating your Google account')
|
||||||
responseText = await this.#fetch(this.#bardURL, {
|
responseText = await this.#fetch(this.#bardURL, {
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
headers: this.#headers,
|
headers: this.#headers,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
})
|
})
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Failure to get server
|
// Failure to get server
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Could not fetch Google Bard. You may be disconnected from internet: " +
|
'Could not fetch Google Bard. You may be disconnected from internet: ' +
|
||||||
e
|
e
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1];
|
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1]
|
||||||
// Assign SNlM0e and return it
|
// Assign SNlM0e and return it
|
||||||
this.SNlM0e = SNlM0e;
|
this.SNlM0e = SNlM0e
|
||||||
this.#verbose && console.log("✅ Initialization finished\n");
|
this.#verbose && console.log('✅ Initialization finished\n')
|
||||||
return SNlM0e;
|
return SNlM0e
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Could not use your Cookie. Make sure that you copied correctly the Cookie with name __Secure-1PSID exactly. If you are sure your cookie is correct, you may also have reached your rate limit."
|
'Could not use your Cookie. Make sure that you copied correctly the Cookie with name __Secure-1PSID exactly. If you are sure your cookie is correct, you may also have reached your rate limit.'
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #uploadImage(name, buffer) {
|
async #uploadImage (name, buffer) {
|
||||||
this.#verbose && console.log("🖼️ Starting image processing");
|
this.#verbose && console.log('🖼️ Starting image processing')
|
||||||
let size = buffer.byteLength;
|
let size = buffer.byteLength
|
||||||
let formBody = [
|
let formBody = [
|
||||||
`${encodeURIComponent("File name")}=${encodeURIComponent([name])}`,
|
`${encodeURIComponent('File name')}=${encodeURIComponent([name])}`
|
||||||
];
|
]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.#verbose &&
|
this.#verbose &&
|
||||||
console.log("💻 Finding Google server destination");
|
console.log('💻 Finding Google server destination')
|
||||||
let response = await this.#fetch(
|
let response = await this.#fetch(
|
||||||
"https://content-push.googleapis.com/upload/",
|
'https://content-push.googleapis.com/upload/',
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"X-Goog-Upload-Command": "start",
|
'X-Goog-Upload-Command': 'start',
|
||||||
"X-Goog-Upload-Protocol": "resumable",
|
'X-Goog-Upload-Protocol': 'resumable',
|
||||||
"X-Goog-Upload-Header-Content-Length": size,
|
'X-Goog-Upload-Header-Content-Length': size,
|
||||||
"X-Tenant-Id": "bard-storage",
|
'X-Tenant-Id': 'bard-storage',
|
||||||
"Push-Id": "feeds/mcudyrk2a4khkz",
|
'Push-Id': 'feeds/mcudyrk2a4khkz'
|
||||||
},
|
},
|
||||||
body: formBody,
|
body: formBody,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const uploadUrl = response.headers.get("X-Goog-Upload-URL");
|
const uploadUrl = response.headers.get('X-Goog-Upload-URL')
|
||||||
this.#verbose && console.log("📤 Sending your image");
|
this.#verbose && console.log('📤 Sending your image')
|
||||||
response = await this.#fetch(uploadUrl, {
|
response = await this.#fetch(uploadUrl, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"X-Goog-Upload-Command": "upload, finalize",
|
'X-Goog-Upload-Command': 'upload, finalize',
|
||||||
"X-Goog-Upload-Offset": 0,
|
'X-Goog-Upload-Offset': 0,
|
||||||
"X-Tenant-Id": "bard-storage",
|
'X-Tenant-Id': 'bard-storage'
|
||||||
},
|
},
|
||||||
body: buffer,
|
body: buffer,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
});
|
})
|
||||||
|
|
||||||
const imageFileLocation = await response.text();
|
const imageFileLocation = await response.text()
|
||||||
|
|
||||||
this.#verbose && console.log("✅ Image finished working\n");
|
this.#verbose && console.log('✅ Image finished working\n')
|
||||||
return imageFileLocation;
|
return imageFileLocation
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Could not fetch Google Bard. You may be disconnected from internet: " +
|
'Could not fetch Google Bard. You may be disconnected from internet: ' +
|
||||||
e
|
e
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query Bard
|
// Query Bard
|
||||||
async #query(message, config) {
|
async #query (message, config) {
|
||||||
let formatMarkdown = (text, images) => {
|
let formatMarkdown = (text, images) => {
|
||||||
if (!images) return text;
|
if (!images) return text
|
||||||
|
|
||||||
for (let imageData of images) {
|
for (let imageData of images) {
|
||||||
const formattedTag = `!${imageData.tag}(${imageData.url})`;
|
const formattedTag = `!${imageData.tag}(${imageData.url})`
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
|
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
|
||||||
formattedTag
|
formattedTag
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
let { ids, imageBuffer } = config;
|
let { ids, imageBuffer } = config
|
||||||
|
|
||||||
// Wait until after init
|
// Wait until after init
|
||||||
await this.#initPromise;
|
await this.#initPromise
|
||||||
|
|
||||||
this.#verbose && console.log("🔎 Starting Bard Query");
|
this.#verbose && console.log('🔎 Starting Bard Query')
|
||||||
|
|
||||||
// If user has not run init
|
// If user has not run init
|
||||||
if (!this.SNlM0e) {
|
if (!this.SNlM0e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Please initialize Bard first. If you haven't passed in your Cookie into the class, run Bard.init(cookie)."
|
"Please initialize Bard first. If you haven't passed in your Cookie into the class, run Bard.init(cookie)."
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#verbose && console.log("🏗️ Building Request");
|
this.#verbose && console.log('🏗️ Building Request')
|
||||||
// HTTPS parameters
|
// HTTPS parameters
|
||||||
const params = {
|
const params = {
|
||||||
bl: "boq_assistant-bard-web-server_20230711.08_p0",
|
bl: 'boq_assistant-bard-web-server_20230711.08_p0',
|
||||||
_reqID: ids?._reqID ?? "0",
|
_reqID: ids?._reqID ?? '0',
|
||||||
rt: "c",
|
rt: 'c'
|
||||||
};
|
}
|
||||||
|
|
||||||
// If IDs are provided, but doesn't have every one of the expected IDs, error
|
// If IDs are provided, but doesn't have every one of the expected IDs, error
|
||||||
const messageStruct = [
|
const messageStruct = [
|
||||||
[message],
|
[message],
|
||||||
null,
|
null,
|
||||||
[null, null, null],
|
[null, null, null]
|
||||||
];
|
]
|
||||||
|
|
||||||
if (imageBuffer) {
|
if (imageBuffer) {
|
||||||
let imageLocation = await this.#uploadImage(
|
let imageLocation = await this.#uploadImage(
|
||||||
`bard-ai_upload`,
|
'bard-ai_upload',
|
||||||
imageBuffer
|
imageBuffer
|
||||||
);
|
)
|
||||||
messageStruct[0].push(0, null, [
|
messageStruct[0].push(0, null, [
|
||||||
[[imageLocation, 1], "bard-ai_upload"],
|
[[imageLocation, 1], 'bard-ai_upload']
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ids) {
|
if (ids) {
|
||||||
const { conversationID, responseID, choiceID } = ids;
|
const { conversationID, responseID, choiceID } = ids
|
||||||
messageStruct[2] = [conversationID, responseID, choiceID];
|
messageStruct[2] = [conversationID, responseID, choiceID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPs data
|
// HTTPs data
|
||||||
const data = {
|
const data = {
|
||||||
"f.req": JSON.stringify([null, JSON.stringify(messageStruct)]),
|
'f.req': JSON.stringify([null, JSON.stringify(messageStruct)]),
|
||||||
at: this.SNlM0e,
|
at: this.SNlM0e
|
||||||
};
|
}
|
||||||
|
|
||||||
// URL that we are submitting to
|
// URL that we are submitting to
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
"/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
|
'/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate',
|
||||||
this.#bardURL
|
this.#bardURL
|
||||||
);
|
)
|
||||||
|
|
||||||
// Append parameters to the URL
|
// Append parameters to the URL
|
||||||
for (const key in params) {
|
for (const key in params) {
|
||||||
url.searchParams.append(key, params[key]);
|
url.searchParams.append(key, params[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the data
|
// Encode the data
|
||||||
|
|
@ -220,30 +220,30 @@ class Bard {
|
||||||
value
|
value
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
.join("&");
|
.join('&')
|
||||||
|
|
||||||
this.#verbose && console.log("💭 Sending message to Bard");
|
this.#verbose && console.log('💭 Sending message to Bard')
|
||||||
// Send the fetch request
|
// Send the fetch request
|
||||||
const chatData = await this.#fetch(url.toString(), {
|
const chatData = await this.#fetch(url.toString(), {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: this.#headers,
|
headers: this.#headers,
|
||||||
body: formBody,
|
body: formBody,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.text();
|
return response.text()
|
||||||
})
|
})
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
return JSON.parse(text.split("\n")[3])[0][2];
|
return JSON.parse(text.split('\n')[3])[0][2]
|
||||||
})
|
})
|
||||||
.then((rawData) => JSON.parse(rawData));
|
.then((rawData) => JSON.parse(rawData))
|
||||||
|
|
||||||
this.#verbose && console.log("🧩 Parsing output");
|
this.#verbose && console.log('🧩 Parsing output')
|
||||||
// Get first Bard-recommended answer
|
// Get first Bard-recommended answer
|
||||||
const answer = chatData[4][0];
|
const answer = chatData[4][0]
|
||||||
|
|
||||||
// Text of that answer
|
// Text of that answer
|
||||||
const text = answer[1][0];
|
const text = answer[1][0]
|
||||||
|
|
||||||
// Get data about images in that answer
|
// Get data about images in that answer
|
||||||
const images =
|
const images =
|
||||||
|
|
@ -255,44 +255,44 @@ class Bard {
|
||||||
source: x[1][0][0],
|
source: x[1][0][0],
|
||||||
alt: x[0][4],
|
alt: x[0][4],
|
||||||
website: x[1][1],
|
website: x[1][1],
|
||||||
favicon: x[1][3],
|
favicon: x[1][3]
|
||||||
},
|
}
|
||||||
})) ?? [];
|
})) ?? []
|
||||||
|
|
||||||
this.#verbose && console.log("✅ All done!\n");
|
this.#verbose && console.log('✅ All done!\n')
|
||||||
// Put everything together and return
|
// Put everything together and return
|
||||||
return {
|
return {
|
||||||
content: formatMarkdown(text, images),
|
content: formatMarkdown(text, images),
|
||||||
images: images,
|
images,
|
||||||
ids: {
|
ids: {
|
||||||
conversationID: chatData[1][0],
|
conversationID: chatData[1][0],
|
||||||
responseID: chatData[1][1],
|
responseID: chatData[1][1],
|
||||||
choiceID: answer[0],
|
choiceID: answer[0],
|
||||||
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000),
|
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000)
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #parseConfig(config) {
|
async #parseConfig (config) {
|
||||||
let result = {
|
let result = {
|
||||||
useJSON: false,
|
useJSON: false,
|
||||||
imageBuffer: undefined, // Returns as {extension, filename}
|
imageBuffer: undefined, // Returns as {extension, filename}
|
||||||
ids: undefined,
|
ids: undefined
|
||||||
};
|
}
|
||||||
|
|
||||||
// Verify that format is one of the two types
|
// Verify that format is one of the two types
|
||||||
if (config?.format) {
|
if (config?.format) {
|
||||||
switch (config.format) {
|
switch (config.format) {
|
||||||
case Bard.JSON:
|
case Bard.JSON:
|
||||||
result.useJSON = true;
|
result.useJSON = true
|
||||||
break;
|
break
|
||||||
case Bard.MD:
|
case Bard.MD:
|
||||||
result.useJSON = false;
|
result.useJSON = false
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output."
|
'Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output.'
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,73 +301,73 @@ class Bard {
|
||||||
if (
|
if (
|
||||||
config.image instanceof ArrayBuffer
|
config.image instanceof ArrayBuffer
|
||||||
) {
|
) {
|
||||||
result.imageBuffer = config.image;
|
result.imageBuffer = config.image
|
||||||
} else if (
|
} else if (
|
||||||
typeof config.image === "string" &&
|
typeof config.image === 'string' &&
|
||||||
/\.(jpeg|jpg|png|webp)$/.test(config.image)
|
/\.(jpeg|jpg|png|webp)$/.test(config.image)
|
||||||
) {
|
) {
|
||||||
let fs;
|
let fs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs = await import("fs")
|
fs = await import('fs')
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Loading from an image file path is not supported in a browser environment.",
|
'Loading from an image file path is not supported in a browser environment.'
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.imageBuffer = fs.readFileSync(
|
result.imageBuffer = fs.readFileSync(
|
||||||
config.image,
|
config.image
|
||||||
).buffer;
|
).buffer
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer."
|
'Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer.'
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that all values in IDs exist
|
// Verify that all values in IDs exist
|
||||||
if (config?.ids) {
|
if (config?.ids) {
|
||||||
if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) {
|
if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) {
|
||||||
result.ids = config.ids;
|
result.ids = config.ids
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Please provide the IDs exported exactly as given."
|
'Please provide the IDs exported exactly as given.'
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask Bard a question!
|
// Ask Bard a question!
|
||||||
async ask(message, config) {
|
async ask (message, config) {
|
||||||
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config);
|
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config)
|
||||||
let response = await this.#query(message, { imageBuffer, ids });
|
let response = await this.#query(message, { imageBuffer, ids })
|
||||||
return useJSON ? response : response.content;
|
return useJSON ? response : response.content
|
||||||
}
|
}
|
||||||
|
|
||||||
createChat(ids) {
|
createChat (ids) {
|
||||||
let bard = this;
|
let bard = this
|
||||||
class Chat {
|
class Chat {
|
||||||
ids = ids;
|
ids = ids
|
||||||
|
|
||||||
async ask(message, config) {
|
async ask (message, config) {
|
||||||
let { useJSON, imageBuffer } = await bard.#parseConfig(config);
|
let { useJSON, imageBuffer } = await bard.#parseConfig(config)
|
||||||
let response = await bard.#query(message, {
|
let response = await bard.#query(message, {
|
||||||
imageBuffer,
|
imageBuffer,
|
||||||
ids: this.ids,
|
ids: this.ids
|
||||||
});
|
})
|
||||||
this.ids = response.ids;
|
this.ids = response.ids
|
||||||
return useJSON ? response : response.content;
|
return useJSON ? response : response.content
|
||||||
}
|
}
|
||||||
|
|
||||||
export() {
|
export () {
|
||||||
return this.ids;
|
return this.ids
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Chat();
|
return new Chat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Bard;
|
export default Bard
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
|
|
||||||
// this file is deprecated
|
// this file is deprecated
|
||||||
import {Config} from './config.js'
|
import { Config } from './config.js'
|
||||||
import HttpsProxyAgent from 'https-proxy-agent'
|
import HttpsProxyAgent from 'https-proxy-agent'
|
||||||
|
|
||||||
const newFetch = (url, options = {}) => {
|
const newFetch = (url, options = {}) => {
|
||||||
|
|
|
||||||
932
utils/browser.js
932
utils/browser.js
|
|
@ -1,10 +1,5 @@
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from './config.js'
|
||||||
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
|
||||||
import { getOpenAIAuth } from './openai-auth.js'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import common from '../../../lib/common/common.js'
|
|
||||||
const chatUrl = 'https://chat.openai.com/chat'
|
|
||||||
let puppeteer = {}
|
let puppeteer = {}
|
||||||
|
|
||||||
class Puppeteer {
|
class Puppeteer {
|
||||||
|
|
@ -48,19 +43,9 @@ class Puppeteer {
|
||||||
|
|
||||||
async initPupp () {
|
async initPupp () {
|
||||||
if (!lodash.isEmpty(puppeteer)) return puppeteer
|
if (!lodash.isEmpty(puppeteer)) return puppeteer
|
||||||
puppeteer = (await import('puppeteer-extra')).default
|
puppeteer = (await import('puppeteer')).default
|
||||||
const pluginStealth = StealthPlugin()
|
// const pluginStealth = StealthPlugin()
|
||||||
puppeteer.use(pluginStealth)
|
// puppeteer.use(pluginStealth)
|
||||||
if (Config['2captchaToken']) {
|
|
||||||
const pluginCaptcha = (await import('puppeteer-extra-plugin-recaptcha')).default
|
|
||||||
puppeteer.use(pluginCaptcha({
|
|
||||||
provider: {
|
|
||||||
id: '2captcha',
|
|
||||||
token: Config['2captchaToken'] // REPLACE THIS WITH YOUR OWN 2CAPTCHA API KEY ⚡
|
|
||||||
},
|
|
||||||
visualFeedback: true
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
return puppeteer
|
return puppeteer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,25 +94,10 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
super()
|
super()
|
||||||
const {
|
const {
|
||||||
email,
|
debug = false
|
||||||
password,
|
|
||||||
markdown = true,
|
|
||||||
debug = false,
|
|
||||||
isGoogleLogin = false,
|
|
||||||
minimize = true,
|
|
||||||
captchaToken,
|
|
||||||
executablePath
|
|
||||||
} = opts
|
} = opts
|
||||||
|
|
||||||
this._email = email
|
|
||||||
this._password = password
|
|
||||||
|
|
||||||
this._markdown = !!markdown
|
|
||||||
this._debug = !!debug
|
this._debug = !!debug
|
||||||
this._isGoogleLogin = !!isGoogleLogin
|
|
||||||
this._minimize = !!minimize
|
|
||||||
this._captchaToken = captchaToken
|
|
||||||
this._executablePath = executablePath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBrowser () {
|
async getBrowser () {
|
||||||
|
|
@ -138,394 +108,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async init () {
|
|
||||||
// if (this.inited) {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
logger.info('init chatgpt browser')
|
|
||||||
try {
|
|
||||||
// this.browser = await getBrowser({
|
|
||||||
// captchaToken: this._captchaToken,
|
|
||||||
// executablePath: this._executablePath
|
|
||||||
// })
|
|
||||||
this.browser = await this.getBrowser()
|
|
||||||
this._page =
|
|
||||||
(await this.browser.pages())[0] || (await this.browser.newPage())
|
|
||||||
await maximizePage(this._page)
|
|
||||||
this._page.on('request', this._onRequest.bind(this))
|
|
||||||
this._page.on('response', this._onResponse.bind(this))
|
|
||||||
// bypass cloudflare and login
|
|
||||||
let preCookies = await redis.get('CHATGPT:RAW_COOKIES')
|
|
||||||
if (preCookies) {
|
|
||||||
await this._page.setCookie(...JSON.parse(preCookies))
|
|
||||||
}
|
|
||||||
// const url = this._page.url().replace(/\/$/, '')
|
|
||||||
// bypass annoying popup modals
|
|
||||||
await this._page.evaluateOnNewDocument(() => {
|
|
||||||
window.localStorage.setItem('oai/apps/hasSeenOnboarding/chat', 'true')
|
|
||||||
const chatGPTUpdateDates = ['2022-12-15', '2022-12-19', '2023-01-09', '2023-01-30', '2023-02-10']
|
|
||||||
chatGPTUpdateDates.forEach(date => {
|
|
||||||
window.localStorage.setItem(
|
|
||||||
`oai/apps/hasSeenReleaseAnnouncement/${date}`,
|
|
||||||
'true'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
await this._page.goto(chatUrl, {
|
|
||||||
waitUntil: 'networkidle2'
|
|
||||||
})
|
|
||||||
let timeout = 30000
|
|
||||||
try {
|
|
||||||
while (timeout > 0 && (await this._page.title()).toLowerCase().indexOf('moment') > -1) {
|
|
||||||
// if meet captcha
|
|
||||||
if (Config['2captchaToken']) {
|
|
||||||
await this._page.solveRecaptchas()
|
|
||||||
}
|
|
||||||
await common.sleep(300)
|
|
||||||
timeout = timeout - 300
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// navigation后获取title会报错,报错说明已经在navigation了正合我意。
|
|
||||||
}
|
|
||||||
if (timeout < 0) {
|
|
||||||
logger.error('wait for cloudflare navigation timeout. 可能遇见验证码')
|
|
||||||
throw new Error('wait for cloudflare navigation timeout. 可能遇见验证码')
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this._page.waitForNavigation({ timeout: 3000 })
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
if (!await this.getIsAuthenticated()) {
|
|
||||||
await redis.del('CHATGPT:RAW_COOKIES')
|
|
||||||
logger.info('需要登录,准备进行自动化登录')
|
|
||||||
await getOpenAIAuth({
|
|
||||||
email: this._email,
|
|
||||||
password: this._password,
|
|
||||||
browser: this.browser,
|
|
||||||
page: this._page,
|
|
||||||
isGoogleLogin: this._isGoogleLogin
|
|
||||||
})
|
|
||||||
logger.info('登录完成')
|
|
||||||
} else {
|
|
||||||
logger.info('无需登录')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (this.browser) {
|
|
||||||
await this.browser.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.browser = null
|
|
||||||
this._page = null
|
|
||||||
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = this._page.url().replace(/\/$/, '')
|
|
||||||
|
|
||||||
if (url !== chatUrl) {
|
|
||||||
await this._page.goto(chatUrl, {
|
|
||||||
waitUntil: 'networkidle2'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// dismiss welcome modal (and other modals)
|
|
||||||
do {
|
|
||||||
const modalSelector = '[data-headlessui-state="open"]'
|
|
||||||
|
|
||||||
if (!(await this._page.$(modalSelector))) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this._page.click(`${modalSelector} button:last-child`)
|
|
||||||
} catch (err) {
|
|
||||||
// "next" button not found in welcome modal
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
await common.sleep(300)
|
|
||||||
} while (true)
|
|
||||||
|
|
||||||
if (!await this.getIsAuthenticated()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._minimize) {
|
|
||||||
await minimizePage(this._page)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
_onRequest = (request) => {
|
|
||||||
const url = request.url()
|
|
||||||
if (!isRelevantRequest(url)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const method = request.method()
|
|
||||||
let body
|
|
||||||
|
|
||||||
if (method === 'POST') {
|
|
||||||
body = request.postData()
|
|
||||||
|
|
||||||
try {
|
|
||||||
body = JSON.parse(body)
|
|
||||||
} catch (_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (url.endsWith('/conversation') && typeof body === 'object') {
|
|
||||||
// const conversationBody: types.ConversationJSONBody = body
|
|
||||||
// const conversationId = conversationBody.conversation_id
|
|
||||||
// const parentMessageId = conversationBody.parent_message_id
|
|
||||||
// const messageId = conversationBody.messages?.[0]?.id
|
|
||||||
// const prompt = conversationBody.messages?.[0]?.content?.parts?.[0]
|
|
||||||
|
|
||||||
// // TODO: store this info for the current sendMessage request
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._debug) {
|
|
||||||
console.log('\nrequest', {
|
|
||||||
url,
|
|
||||||
method,
|
|
||||||
headers: request.headers(),
|
|
||||||
body
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onResponse = async (response) => {
|
|
||||||
const request = response.request()
|
|
||||||
|
|
||||||
const url = response.url()
|
|
||||||
if (!isRelevantRequest(url)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = response.status()
|
|
||||||
|
|
||||||
let body
|
|
||||||
try {
|
|
||||||
body = await response.json()
|
|
||||||
} catch (_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._debug) {
|
|
||||||
console.log('\nresponse', {
|
|
||||||
url,
|
|
||||||
ok: response.ok(),
|
|
||||||
status,
|
|
||||||
statusText: response.statusText(),
|
|
||||||
headers: response.headers(),
|
|
||||||
body,
|
|
||||||
request: {
|
|
||||||
method: request.method(),
|
|
||||||
headers: request.headers(),
|
|
||||||
body: request.postData()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.endsWith('/conversation')) {
|
|
||||||
if (status === 403) {
|
|
||||||
await this.handle403Error()
|
|
||||||
}
|
|
||||||
} else if (url.endsWith('api/auth/session')) {
|
|
||||||
if (status === 403) {
|
|
||||||
await this.handle403Error()
|
|
||||||
} else {
|
|
||||||
const session = body
|
|
||||||
if (session?.accessToken) {
|
|
||||||
this._accessToken = session.accessToken
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle403Error () {
|
|
||||||
console.log(`ChatGPT "${this._email}" session expired; refreshing...`)
|
|
||||||
try {
|
|
||||||
await maximizePage(this._page)
|
|
||||||
await this._page.reload({
|
|
||||||
waitUntil: 'networkidle2',
|
|
||||||
timeout: Config.chromeTimeoutMS // 2 minutes
|
|
||||||
})
|
|
||||||
if (this._minimize) {
|
|
||||||
await minimizePage(this._page)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
`ChatGPT "${this._email}" error refreshing session`,
|
|
||||||
err.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getIsAuthenticated () {
|
|
||||||
try {
|
|
||||||
const inputBox = await this._getInputBox()
|
|
||||||
return !!inputBox
|
|
||||||
} catch (err) {
|
|
||||||
// can happen when navigating during login
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMessage (
|
|
||||||
message,
|
|
||||||
opts = {}
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
conversationId,
|
|
||||||
parentMessageId = uuidv4(),
|
|
||||||
messageId = uuidv4(),
|
|
||||||
action = 'next',
|
|
||||||
// TODO
|
|
||||||
timeoutMs,
|
|
||||||
// onProgress,
|
|
||||||
onConversationResponse
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
const inputBox = await this._getInputBox()
|
|
||||||
if (!inputBox || !this._accessToken) {
|
|
||||||
console.log(`chatgpt re-authenticating ${this._email}`)
|
|
||||||
let isAuthenticated = false
|
|
||||||
|
|
||||||
try {
|
|
||||||
isAuthenticated = await this.init()
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(
|
|
||||||
`chatgpt error re-authenticating ${this._email}`,
|
|
||||||
err.toString()
|
|
||||||
)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
let timeout = 100000
|
|
||||||
if (isAuthenticated) {
|
|
||||||
while (!this._accessToken) {
|
|
||||||
// wait for async response hook result
|
|
||||||
await common.sleep(300)
|
|
||||||
timeout = timeout - 300
|
|
||||||
if (timeout < 0) {
|
|
||||||
const error = new Error('Not signed in')
|
|
||||||
error.statusCode = 401
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (!this._accessToken) {
|
|
||||||
const error = new Error('Not signed in')
|
|
||||||
error.statusCode = 401
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = 'https://chat.openai.com/backend-api/conversation'
|
|
||||||
const body = {
|
|
||||||
action,
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
id: messageId,
|
|
||||||
role: 'user',
|
|
||||||
content: {
|
|
||||||
content_type: 'text',
|
|
||||||
parts: [message]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
model: Config.plus ? Config.useGPT4 ? 'gpt-4' : 'text-davinci-002-render-sha' : 'text-davinci-002-render-sha',
|
|
||||||
parent_message_id: parentMessageId
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conversationId) {
|
|
||||||
body.conversation_id = conversationId
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('>>> EVALUATE', url, this._accessToken, body)
|
|
||||||
const result = await this._page.evaluate(
|
|
||||||
browserPostEventStream,
|
|
||||||
url,
|
|
||||||
this._accessToken,
|
|
||||||
body,
|
|
||||||
timeoutMs
|
|
||||||
)
|
|
||||||
// console.log('<<< EVALUATE', result)
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
const error = new Error(result.error.message)
|
|
||||||
error.statusCode = result.error.statusCode
|
|
||||||
error.statusText = result.error.statusText
|
|
||||||
|
|
||||||
if (error.statusCode === 403) {
|
|
||||||
await this.handle403Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support sending partial response events
|
|
||||||
if (onConversationResponse) {
|
|
||||||
onConversationResponse(result.conversationResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
text: result.response,
|
|
||||||
conversationId: result.conversationResponse.conversation_id,
|
|
||||||
id: messageId,
|
|
||||||
parentMessageId
|
|
||||||
}
|
|
||||||
|
|
||||||
// const lastMessage = await this.getLastMessage()
|
|
||||||
|
|
||||||
// await inputBox.focus()
|
|
||||||
// const paragraphs = message.split('\n')
|
|
||||||
// for (let i = 0; i < paragraphs.length; i++) {
|
|
||||||
// await inputBox.type(paragraphs[i], { delay: 0 })
|
|
||||||
// if (i < paragraphs.length - 1) {
|
|
||||||
// await this._page.keyboard.down('Shift')
|
|
||||||
// await inputBox.press('Enter')
|
|
||||||
// await this._page.keyboard.up('Shift')
|
|
||||||
// } else {
|
|
||||||
// await inputBox.press('Enter')
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const responseP = new Promise<string>(async (resolve, reject) => {
|
|
||||||
// try {
|
|
||||||
// do {
|
|
||||||
// await common.sleep(1000)
|
|
||||||
|
|
||||||
// // TODO: this logic needs some work because we can have repeat messages...
|
|
||||||
// const newLastMessage = await this.getLastMessage()
|
|
||||||
// if (
|
|
||||||
// newLastMessage &&
|
|
||||||
// lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase()
|
|
||||||
// ) {
|
|
||||||
// return resolve(newLastMessage)
|
|
||||||
// }
|
|
||||||
// } while (true)
|
|
||||||
// } catch (err) {
|
|
||||||
// return reject(err)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// if (timeoutMs) {
|
|
||||||
// return pTimeout(responseP, {
|
|
||||||
// milliseconds: timeoutMs
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// return responseP
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetThread () {
|
|
||||||
try {
|
|
||||||
await this._page.click('nav > a:nth-child(1)')
|
|
||||||
} catch (err) {
|
|
||||||
// ignore for now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async close () {
|
async close () {
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
await this.browser.close()
|
await this.browser.close()
|
||||||
|
|
@ -533,510 +115,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
||||||
this._page = null
|
this._page = null
|
||||||
this.browser = null
|
this.browser = null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
async _getInputBox () {
|
|
||||||
// [data-id="root"]
|
|
||||||
return this._page?.$('textarea')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ChatGPTPuppeteer()
|
export default new ChatGPTPuppeteer()
|
||||||
|
|
||||||
export async function minimizePage (page) {
|
|
||||||
const session = await page.target().createCDPSession()
|
|
||||||
const goods = await session.send('Browser.getWindowForTarget')
|
|
||||||
const { windowId } = goods
|
|
||||||
await session.send('Browser.setWindowBounds', {
|
|
||||||
windowId,
|
|
||||||
bounds: { windowState: 'minimized' }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function maximizePage (page) {
|
|
||||||
const session = await page.target().createCDPSession()
|
|
||||||
const goods = await session.send('Browser.getWindowForTarget')
|
|
||||||
const { windowId } = goods
|
|
||||||
await session.send('Browser.setWindowBounds', {
|
|
||||||
windowId,
|
|
||||||
bounds: { windowState: 'normal' }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isRelevantRequest (url) {
|
|
||||||
let pathname
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedUrl = new URL(url)
|
|
||||||
pathname = parsedUrl.pathname
|
|
||||||
url = parsedUrl.toString()
|
|
||||||
} catch (_) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url.startsWith('https://chat.openai.com')) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!pathname.startsWith('/backend-api/') &&
|
|
||||||
!pathname.startsWith('/api/auth/session')
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathname.endsWith('backend-api/moderations')) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is injected into the ChatGPT webapp page using puppeteer. It
|
|
||||||
* has to be fully self-contained, so we copied a few third-party sources and
|
|
||||||
* included them in here.
|
|
||||||
*/
|
|
||||||
export async function browserPostEventStream (
|
|
||||||
url,
|
|
||||||
accessToken,
|
|
||||||
body,
|
|
||||||
timeoutMs
|
|
||||||
) {
|
|
||||||
// Workaround for https://github.com/esbuild-kit/tsx/issues/113
|
|
||||||
globalThis.__name = () => undefined
|
|
||||||
|
|
||||||
const BOM = [239, 187, 191]
|
|
||||||
|
|
||||||
let conversationResponse
|
|
||||||
let conversationId = body?.conversation_id
|
|
||||||
let messageId = body?.messages?.[0]?.id
|
|
||||||
let response = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('browserPostEventStream', url, accessToken, body)
|
|
||||||
|
|
||||||
let abortController = null
|
|
||||||
if (timeoutMs) {
|
|
||||||
abortController = new AbortController()
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
signal: abortController?.signal,
|
|
||||||
headers: {
|
|
||||||
accept: 'text/event-stream',
|
|
||||||
'x-openai-assistant-app-id': '',
|
|
||||||
authorization: `Bearer ${accessToken}`,
|
|
||||||
'content-type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('browserPostEventStream response', res)
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
return {
|
|
||||||
error: {
|
|
||||||
message: `ChatGPTAPI error ${res.status || res.statusText}`,
|
|
||||||
statusCode: res.status,
|
|
||||||
statusText: res.statusText
|
|
||||||
},
|
|
||||||
response: null,
|
|
||||||
conversationId,
|
|
||||||
messageId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseP = new Promise(
|
|
||||||
async (resolve, reject) => {
|
|
||||||
function onMessage (data) {
|
|
||||||
if (data === '[DONE]') {
|
|
||||||
return resolve({
|
|
||||||
error: null,
|
|
||||||
response,
|
|
||||||
conversationId,
|
|
||||||
messageId,
|
|
||||||
conversationResponse
|
|
||||||
})
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const _checkJson = JSON.parse(data)
|
|
||||||
} catch (error) {
|
|
||||||
console.log('warning: parse error.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const convoResponseEvent =
|
|
||||||
JSON.parse(data)
|
|
||||||
conversationResponse = convoResponseEvent
|
|
||||||
if (convoResponseEvent.conversation_id) {
|
|
||||||
conversationId = convoResponseEvent.conversation_id
|
|
||||||
}
|
|
||||||
|
|
||||||
if (convoResponseEvent.message?.id) {
|
|
||||||
messageId = convoResponseEvent.message.id
|
|
||||||
}
|
|
||||||
|
|
||||||
const partialResponse =
|
|
||||||
convoResponseEvent.message?.content?.parts?.[0]
|
|
||||||
if (partialResponse) {
|
|
||||||
response = partialResponse
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('fetchSSE onMessage unexpected error', err)
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = createParser((event) => {
|
|
||||||
if (event.type === 'event') {
|
|
||||||
onMessage(event.data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for await (const chunk of streamAsyncIterable(res.body)) {
|
|
||||||
const str = new TextDecoder().decode(chunk)
|
|
||||||
parser.feed(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (timeoutMs) {
|
|
||||||
if (abortController) {
|
|
||||||
// This will be called when a timeout occurs in order for us to forcibly
|
|
||||||
// ensure that the underlying HTTP request is aborted.
|
|
||||||
responseP.cancel = () => {
|
|
||||||
abortController.abort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log({ pTimeout })
|
|
||||||
return await pTimeout(responseP, {
|
|
||||||
milliseconds: timeoutMs,
|
|
||||||
message: 'ChatGPT timed out waiting for response'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return await responseP
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
const errMessageL = err.toString().toLowerCase()
|
|
||||||
|
|
||||||
if (
|
|
||||||
response &&
|
|
||||||
(errMessageL === 'error: typeerror: terminated' ||
|
|
||||||
errMessageL === 'typeerror: terminated')
|
|
||||||
) {
|
|
||||||
// OpenAI sometimes forcefully terminates the socket from their end before
|
|
||||||
// the HTTP request has resolved cleanly. In my testing, these cases tend to
|
|
||||||
// happen when OpenAI has already send the last `response`, so we can ignore
|
|
||||||
// the `fetch` error in this case.
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
response,
|
|
||||||
conversationId,
|
|
||||||
messageId,
|
|
||||||
conversationResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
error: {
|
|
||||||
message: err.toString(),
|
|
||||||
statusCode: err.statusCode || err.status || err.response?.statusCode,
|
|
||||||
statusText: err.statusText || err.response?.statusText
|
|
||||||
},
|
|
||||||
response: null,
|
|
||||||
conversationId,
|
|
||||||
messageId,
|
|
||||||
conversationResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// async function pTimeout (promise, option) {
|
|
||||||
// return await pTimeout(promise, option)
|
|
||||||
// }
|
|
||||||
async function * streamAsyncIterable (stream) {
|
|
||||||
const reader = stream.getReader()
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read()
|
|
||||||
if (done) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
yield value
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
reader.releaseLock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @see https://github.com/rexxars/eventsource-parser
|
|
||||||
function createParser (onParse) {
|
|
||||||
// Processing state
|
|
||||||
let isFirstChunk
|
|
||||||
let buffer
|
|
||||||
let startingPosition
|
|
||||||
let startingFieldLength
|
|
||||||
|
|
||||||
// Event state
|
|
||||||
let eventId
|
|
||||||
let eventName
|
|
||||||
let data
|
|
||||||
|
|
||||||
reset()
|
|
||||||
return { feed, reset }
|
|
||||||
|
|
||||||
function reset () {
|
|
||||||
isFirstChunk = true
|
|
||||||
buffer = ''
|
|
||||||
startingPosition = 0
|
|
||||||
startingFieldLength = -1
|
|
||||||
|
|
||||||
eventId = undefined
|
|
||||||
eventName = undefined
|
|
||||||
data = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function feed (chunk) {
|
|
||||||
buffer = buffer ? buffer + chunk : chunk
|
|
||||||
|
|
||||||
// Strip any UTF8 byte order mark (BOM) at the start of the stream.
|
|
||||||
// Note that we do not strip any non - UTF8 BOM, as eventsource streams are
|
|
||||||
// always decoded as UTF8 as per the specification.
|
|
||||||
if (isFirstChunk && hasBom(buffer)) {
|
|
||||||
buffer = buffer.slice(BOM.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
isFirstChunk = false
|
|
||||||
|
|
||||||
// Set up chunk-specific processing state
|
|
||||||
const length = buffer.length
|
|
||||||
let position = 0
|
|
||||||
let discardTrailingNewline = false
|
|
||||||
|
|
||||||
// Read the current buffer byte by byte
|
|
||||||
while (position < length) {
|
|
||||||
// EventSource allows for carriage return + line feed, which means we
|
|
||||||
// need to ignore a linefeed character if the previous character was a
|
|
||||||
// carriage return
|
|
||||||
// @todo refactor to reduce nesting, consider checking previous byte?
|
|
||||||
// @todo but consider multiple chunks etc
|
|
||||||
if (discardTrailingNewline) {
|
|
||||||
if (buffer[position] === '\n') {
|
|
||||||
++position
|
|
||||||
}
|
|
||||||
discardTrailingNewline = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let lineLength = -1
|
|
||||||
let fieldLength = startingFieldLength
|
|
||||||
let character
|
|
||||||
|
|
||||||
for (
|
|
||||||
let index = startingPosition;
|
|
||||||
lineLength < 0 && index < length;
|
|
||||||
++index
|
|
||||||
) {
|
|
||||||
character = buffer[index]
|
|
||||||
if (character === ':' && fieldLength < 0) {
|
|
||||||
fieldLength = index - position
|
|
||||||
} else if (character === '\r') {
|
|
||||||
discardTrailingNewline = true
|
|
||||||
lineLength = index - position
|
|
||||||
} else if (character === '\n') {
|
|
||||||
lineLength = index - position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lineLength < 0) {
|
|
||||||
startingPosition = length - position
|
|
||||||
startingFieldLength = fieldLength
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
startingPosition = 0
|
|
||||||
startingFieldLength = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
parseEventStreamLine(buffer, position, fieldLength, lineLength)
|
|
||||||
|
|
||||||
position += lineLength + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position === length) {
|
|
||||||
// If we consumed the entire buffer to read the event, reset the buffer
|
|
||||||
buffer = ''
|
|
||||||
} else if (position > 0) {
|
|
||||||
// If there are bytes left to process, set the buffer to the unprocessed
|
|
||||||
// portion of the buffer only
|
|
||||||
buffer = buffer.slice(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEventStreamLine (
|
|
||||||
lineBuffer,
|
|
||||||
index,
|
|
||||||
fieldLength,
|
|
||||||
lineLength
|
|
||||||
) {
|
|
||||||
if (lineLength === 0) {
|
|
||||||
// We reached the last line of this event
|
|
||||||
if (data.length > 0) {
|
|
||||||
onParse({
|
|
||||||
type: 'event',
|
|
||||||
id: eventId,
|
|
||||||
event: eventName || undefined,
|
|
||||||
data: data.slice(0, -1) // remove trailing newline
|
|
||||||
})
|
|
||||||
|
|
||||||
data = ''
|
|
||||||
eventId = undefined
|
|
||||||
}
|
|
||||||
eventName = undefined
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const noValue = fieldLength < 0
|
|
||||||
const field = lineBuffer.slice(
|
|
||||||
index,
|
|
||||||
index + (noValue ? lineLength : fieldLength)
|
|
||||||
)
|
|
||||||
let step = 0
|
|
||||||
|
|
||||||
if (noValue) {
|
|
||||||
step = lineLength
|
|
||||||
} else if (lineBuffer[index + fieldLength + 1] === ' ') {
|
|
||||||
step = fieldLength + 2
|
|
||||||
} else {
|
|
||||||
step = fieldLength + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = index + step
|
|
||||||
const valueLength = lineLength - step
|
|
||||||
const value = lineBuffer
|
|
||||||
.slice(position, position + valueLength)
|
|
||||||
.toString()
|
|
||||||
|
|
||||||
if (field === 'data') {
|
|
||||||
data += value ? `${value}\n` : '\n'
|
|
||||||
} else if (field === 'event') {
|
|
||||||
eventName = value
|
|
||||||
} else if (field === 'id' && !value.includes('\u0000')) {
|
|
||||||
eventId = value
|
|
||||||
} else if (field === 'retry') {
|
|
||||||
const retry = parseInt(value, 10)
|
|
||||||
if (!Number.isNaN(retry)) {
|
|
||||||
onParse({ type: 'reconnect-interval', value: retry })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasBom (buffer) {
|
|
||||||
return BOM.every(
|
|
||||||
(charCode, index) => buffer.charCodeAt(index) === charCode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @see https://github.com/sindresorhus/p-timeout
|
|
||||||
function pTimeout (
|
|
||||||
promise,
|
|
||||||
options
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
milliseconds,
|
|
||||||
fallback,
|
|
||||||
message,
|
|
||||||
customTimers = { setTimeout, clearTimeout }
|
|
||||||
} = options
|
|
||||||
|
|
||||||
let timer
|
|
||||||
|
|
||||||
const cancelablePromise = new Promise((resolve, reject) => {
|
|
||||||
if (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {
|
|
||||||
throw new TypeError(
|
|
||||||
`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
||||||
resolve(promise)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.signal) {
|
|
||||||
const { signal } = options
|
|
||||||
if (signal.aborted) {
|
|
||||||
reject(getAbortedReason(signal))
|
|
||||||
}
|
|
||||||
|
|
||||||
signal.addEventListener('abort', () => {
|
|
||||||
reject(getAbortedReason(signal))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
timer = customTimers.setTimeout.call(
|
|
||||||
undefined,
|
|
||||||
() => {
|
|
||||||
if (fallback) {
|
|
||||||
try {
|
|
||||||
resolve(fallback())
|
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMessage =
|
|
||||||
typeof message === 'string'
|
|
||||||
? message
|
|
||||||
: `Promise timed out after ${milliseconds} milliseconds`
|
|
||||||
const timeoutError =
|
|
||||||
message instanceof Error ? message : new Error(errorMessage)
|
|
||||||
|
|
||||||
if (typeof promise.cancel === 'function') {
|
|
||||||
promise.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
reject(timeoutError)
|
|
||||||
},
|
|
||||||
milliseconds
|
|
||||||
)
|
|
||||||
;(async () => {
|
|
||||||
try {
|
|
||||||
resolve(await promise)
|
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
} finally {
|
|
||||||
customTimers.clearTimeout.call(undefined, timer)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
})
|
|
||||||
|
|
||||||
cancelablePromise.clear = () => {
|
|
||||||
customTimers.clearTimeout.call(undefined, timer)
|
|
||||||
timer = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return cancelablePromise
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
TODO: Remove below function and just 'reject(signal.reason)' when targeting Node 18.
|
|
||||||
*/
|
|
||||||
function getAbortedReason (signal) {
|
|
||||||
const reason =
|
|
||||||
signal.reason === undefined
|
|
||||||
? getDOMException('This operation was aborted.')
|
|
||||||
: signal.reason
|
|
||||||
|
|
||||||
return reason instanceof Error ? reason : getDOMException(reason)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
TODO: Remove AbortError and just throw DOMException when targeting Node 18.
|
|
||||||
*/
|
|
||||||
function getDOMException (errorMessage) {
|
|
||||||
return globalThis.DOMException === undefined
|
|
||||||
? new Error(errorMessage)
|
|
||||||
: new DOMException(errorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Config } from './config.js'
|
||||||
|
import { newFetch } from './proxy.js'
|
||||||
|
|
||||||
export async function getChatHistoryGroup (e, num) {
|
export async function getChatHistoryGroup (e, num) {
|
||||||
// if (e.adapter === 'shamrock') {
|
// if (e.adapter === 'shamrock') {
|
||||||
|
|
@ -16,7 +18,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) {
|
||||||
|
|
@ -58,3 +60,43 @@ async function pickMemberAsync (e, userId) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function generateSuggestedResponse (conversations) {
|
||||||
|
let prompt = 'Attention! you do not need to answer any question according to the provided conversation! \nYou are a suggested questions generator, you should generate three suggested questions according to the provided conversation for the user in the next turn, the three questions should not be too long, and must be superated with newline. The suggested questions should be suitable in the context of the provided conversation, and should not be too long. \nNow give your 3 suggested questions, use the same language with the user.'
|
||||||
|
const res = await newFetch(`${Config.openAiBaseUrl}/chat/completions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${Config.apiKey}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: 'gpt-3.5-turbo-16k',
|
||||||
|
temperature: 0.7,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: 'you are a suggested questions generator, you should generate three suggested questions according to the provided conversation for the user in the next turn, the three questions should not be too long, and must be superated with newline. Always use the same language with the user\'s content in the last turn. you should response like: \nWhat is ChatGPT?\nCan you write a poem aboud spring?\nWhat can you do?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'User:\n\n我想知道今天的天气\n\nAI:\n\n今天北京的天气是晴转多云,最高气温12度,最低气温2度,空气质量优。\n\n' + prompt
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: '这个天气适合穿什么衣物?\n今天北京的湿度怎么样?\n这个季节北京有什么适合游玩的地方?'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: JSON.stringify(conversations) + prompt
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (res.status === 200) {
|
||||||
|
const resJson = await res.json()
|
||||||
|
if (resJson) { return resJson.choices[0].message.content }
|
||||||
|
} else {
|
||||||
|
logger.error('generateSuggestedResponse error: ' + res.status)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,19 +130,15 @@ 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 +156,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 {
|
||||||
|
|
|
||||||
128
utils/common.js
128
utils/common.js
|
|
@ -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
|
||||||
|
|
@ -74,21 +74,6 @@ export function randomString (length = 5) {
|
||||||
return str.substr(0, length)
|
return str.substr(0, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upsertMessage (message, suffix = '') {
|
|
||||||
if (suffix) {
|
|
||||||
suffix = '_' + suffix
|
|
||||||
}
|
|
||||||
await redis.set(`CHATGPT:MESSAGE${suffix}:${message.id}`, JSON.stringify(message))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMessageById (id, suffix = '') {
|
|
||||||
if (suffix) {
|
|
||||||
suffix = '_' + suffix
|
|
||||||
}
|
|
||||||
let messageStr = await redis.get(`CHATGPT:MESSAGE${suffix}:${id}`)
|
|
||||||
return JSON.parse(messageStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function tryTimes (promiseFn, maxTries = 10) {
|
export async function tryTimes (promiseFn, maxTries = 10) {
|
||||||
try {
|
try {
|
||||||
return await promiseFn()
|
return await promiseFn()
|
||||||
|
|
@ -102,63 +87,7 @@ export async function tryTimes (promiseFn, maxTries = 10) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function makeForwardMsg (e, msg = [], dec = '') {
|
export async function makeForwardMsg (e, msg = [], dec = '') {
|
||||||
if (Version.isTrss) {
|
|
||||||
return common.makeForwardMsg(e, msg, dec)
|
return common.makeForwardMsg(e, msg, dec)
|
||||||
}
|
|
||||||
let nickname = e.bot.nickname
|
|
||||||
if (e.isGroup) {
|
|
||||||
try {
|
|
||||||
let info = await e.bot.getGroupMemberInfo(e.group_id, getUin(e))
|
|
||||||
nickname = info.card || info.nickname
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Failed to get group member info: ${err}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let userInfo = {
|
|
||||||
user_id: getUin(e),
|
|
||||||
nickname
|
|
||||||
}
|
|
||||||
|
|
||||||
let forwardMsg = []
|
|
||||||
msg.forEach((v) => {
|
|
||||||
forwardMsg.push({
|
|
||||||
...userInfo,
|
|
||||||
message: v
|
|
||||||
})
|
|
||||||
})
|
|
||||||
let is_sign = true
|
|
||||||
/** 制作转发内容 */
|
|
||||||
if (e.isGroup) {
|
|
||||||
forwardMsg = await e.group.makeForwardMsg(forwardMsg)
|
|
||||||
} else if (e.friend) {
|
|
||||||
forwardMsg = await e.friend.makeForwardMsg(forwardMsg)
|
|
||||||
} else {
|
|
||||||
return msg.join('\n')
|
|
||||||
}
|
|
||||||
let forwardMsg_json = forwardMsg.data
|
|
||||||
if (typeof (forwardMsg_json) === 'object') {
|
|
||||||
if (forwardMsg_json.app === 'com.tencent.multimsg' && forwardMsg_json.meta?.detail) {
|
|
||||||
let detail = forwardMsg_json.meta.detail
|
|
||||||
let resid = detail.resid
|
|
||||||
let fileName = detail.uniseq
|
|
||||||
let preview = ''
|
|
||||||
for (let val of detail.news) {
|
|
||||||
preview += `<title color="#777777" size="26">${val.text}</title>`
|
|
||||||
}
|
|
||||||
forwardMsg.data = `<?xml version="1.0" encoding="utf-8"?><msg brief="[聊天记录]" m_fileName="${fileName}" action="viewMultiMsg" tSum="1" flag="3" m_resid="${resid}" serviceID="35" m_fileSize="0"><item layout="1"><title color="#000000" size="34">转发的聊天记录</title>${preview}<hr></hr><summary color="#808080" size="26">${detail.summary}</summary></item><source name="聊天记录"></source></msg>`
|
|
||||||
forwardMsg.type = 'xml'
|
|
||||||
forwardMsg.id = 35
|
|
||||||
}
|
|
||||||
}
|
|
||||||
forwardMsg.data = forwardMsg.data
|
|
||||||
.replace(/\n/g, '')
|
|
||||||
.replace(/<title color="#777777" size="26">(.+?)<\/title>/g, '___')
|
|
||||||
.replace(/___+/, `<title color="#777777" size="26">${dec}</title>`)
|
|
||||||
if (!is_sign) {
|
|
||||||
forwardMsg.data = forwardMsg.data
|
|
||||||
.replace('转发的', '不可转发的')
|
|
||||||
}
|
|
||||||
return forwardMsg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @see https://github.com/sindresorhus/p-timeout
|
// @see https://github.com/sindresorhus/p-timeout
|
||||||
|
|
@ -1055,10 +984,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 +1194,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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,14 @@ const defaultConfig = {
|
||||||
openAiBaseUrl: defaultOpenAIReverseProxy,
|
openAiBaseUrl: defaultOpenAIReverseProxy,
|
||||||
OpenAiPlatformRefreshToken: '',
|
OpenAiPlatformRefreshToken: '',
|
||||||
openAiForceUseReverse: false,
|
openAiForceUseReverse: false,
|
||||||
|
apiStream: false,
|
||||||
drawCD: 30,
|
drawCD: 30,
|
||||||
model: '',
|
model: '',
|
||||||
temperature: 0.8,
|
temperature: 0.8,
|
||||||
toneStyle: 'Sydney', // or creative, precise
|
/**
|
||||||
|
* @type {'Precise' | 'Balanced' | 'Creative'}
|
||||||
|
*/
|
||||||
|
toneStyle: 'Creative',
|
||||||
sydney: pureSydneyInstruction,
|
sydney: pureSydneyInstruction,
|
||||||
sydneyReverseProxy: 'https://666102.201666.xyz',
|
sydneyReverseProxy: 'https://666102.201666.xyz',
|
||||||
sydneyForceUseReverse: false,
|
sydneyForceUseReverse: false,
|
||||||
|
|
@ -39,8 +43,15 @@ const defaultConfig = {
|
||||||
sydneyBrainWashStrength: 15,
|
sydneyBrainWashStrength: 15,
|
||||||
sydneyBrainWashName: 'Sydney',
|
sydneyBrainWashName: 'Sydney',
|
||||||
sydneyMood: false,
|
sydneyMood: false,
|
||||||
|
sydneyGPTs: 'Copilot',
|
||||||
sydneyImageRecognition: false,
|
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.',
|
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.',
|
||||||
|
chatExampleUser1: '',
|
||||||
|
chatExampleUser2: '',
|
||||||
|
chatExampleUser3: '',
|
||||||
|
chatExampleBot1: '',
|
||||||
|
chatExampleBot2: '',
|
||||||
|
chatExampleBot3: '',
|
||||||
enableSuggestedResponses: false,
|
enableSuggestedResponses: false,
|
||||||
sydneyEnableSearch: false,
|
sydneyEnableSearch: false,
|
||||||
api: defaultChatGPTAPI,
|
api: defaultChatGPTAPI,
|
||||||
|
|
@ -63,13 +74,8 @@ const defaultConfig = {
|
||||||
xhRetReplace: '',
|
xhRetReplace: '',
|
||||||
promptPrefixOverride: 'Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
|
promptPrefixOverride: 'Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
|
||||||
assistantLabel: 'ChatGPT',
|
assistantLabel: 'ChatGPT',
|
||||||
// thinkingTips: true,
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
UA: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
|
|
||||||
headless: false,
|
headless: false,
|
||||||
chromePath: '',
|
chromePath: '',
|
||||||
'2captchaToken': '',
|
|
||||||
proxy: '',
|
proxy: '',
|
||||||
debug: true,
|
debug: true,
|
||||||
defaultTimeoutMs: 120000,
|
defaultTimeoutMs: 120000,
|
||||||
|
|
@ -123,6 +129,10 @@ const defaultConfig = {
|
||||||
slackClaudeEnableGlobalPreset: true,
|
slackClaudeEnableGlobalPreset: true,
|
||||||
slackClaudeGlobalPreset: '',
|
slackClaudeGlobalPreset: '',
|
||||||
slackClaudeSpecifiedChannel: '',
|
slackClaudeSpecifiedChannel: '',
|
||||||
|
// slackCozeUserId: '',
|
||||||
|
// slackCozeEnableGlobalPreset: true,
|
||||||
|
// slackCozeGlobalPreset: '',
|
||||||
|
// slackCozeSpecifiedChannel: '',
|
||||||
bardPsid: '',
|
bardPsid: '',
|
||||||
bardReverseProxy: '',
|
bardReverseProxy: '',
|
||||||
bardForceUseReverse: false,
|
bardForceUseReverse: false,
|
||||||
|
|
@ -168,7 +178,20 @@ const defaultConfig = {
|
||||||
geminiPrompt: 'You are Gemini. Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
|
geminiPrompt: 'You are Gemini. Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
|
||||||
// origin: https://generativelanguage.googleapis.com
|
// origin: https://generativelanguage.googleapis.com
|
||||||
geminiBaseUrl: 'https://gemini.ikechan8370.com',
|
geminiBaseUrl: 'https://gemini.ikechan8370.com',
|
||||||
version: 'v2.7.8'
|
chatglmRefreshToken: '',
|
||||||
|
sunoSessToken: '',
|
||||||
|
sunoClientToken: '',
|
||||||
|
|
||||||
|
claudeApiKey: '',
|
||||||
|
claudeApiBaseUrl: 'http://claude-api.ikechan8370.com',
|
||||||
|
claudeApiMaxToken: 1024,
|
||||||
|
claudeApiTemperature: 0.8,
|
||||||
|
claudeApiModel: '', // claude-3-opus-20240229 claude-3-sonnet-20240229
|
||||||
|
claudeSystemPrompt: '', // claude api 设定
|
||||||
|
translateSource: 'openai',
|
||||||
|
enableMd: false, // 第三方md,非QQBot。需要适配器实现segment.markdown和segment.button方可使用,否则不建议开启,会造成各种错误
|
||||||
|
enableToolbox: true, // 默认关闭工具箱节省占用和加速启动
|
||||||
|
version: 'v2.8.1'
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
let config = {}
|
let config = {}
|
||||||
|
|
|
||||||
|
|
@ -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}`)
|
||||||
}
|
}
|
||||||
|
|
@ -479,7 +479,7 @@ export async function convertFaces (msg, handleAt = false, e) {
|
||||||
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
groupCardQQMap[groupMembers.get(key).card || groupMembers.get(key).nickname] = groupMembers.get(key).user_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tmpMsg = ''
|
let tmpMsg = ''
|
||||||
let tmpFace = ''
|
let tmpFace = ''
|
||||||
let tmpAt = ''
|
let tmpAt = ''
|
||||||
|
|
|
||||||
14
utils/history.js
Normal file
14
utils/history.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
export async function upsertMessage (message, suffix = '') {
|
||||||
|
if (suffix) {
|
||||||
|
suffix = '_' + suffix
|
||||||
|
}
|
||||||
|
await redis.set(`CHATGPT:MESSAGE${suffix}:${message.id}`, JSON.stringify(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMessageById (id, suffix = '') {
|
||||||
|
if (suffix) {
|
||||||
|
suffix = '_' + suffix
|
||||||
|
}
|
||||||
|
let messageStr = await redis.get(`CHATGPT:MESSAGE${suffix}:${id}`)
|
||||||
|
return JSON.parse(messageStr)
|
||||||
|
}
|
||||||
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,281 +0,0 @@
|
||||||
import { Config } from '../utils/config.js'
|
|
||||||
import random from 'random'
|
|
||||||
import common from '../../../lib/common/common.js'
|
|
||||||
|
|
||||||
let hasRecaptchaPlugin = !!Config['2captchaToken']
|
|
||||||
|
|
||||||
export async function getOpenAIAuth (opt) {
|
|
||||||
let {
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
browser,
|
|
||||||
page,
|
|
||||||
timeoutMs = Config.chromeTimeoutMS,
|
|
||||||
isGoogleLogin = false,
|
|
||||||
captchaToken = Config['2captchaToken'],
|
|
||||||
executablePath = Config.chromePath
|
|
||||||
} = opt
|
|
||||||
const origBrowser = browser
|
|
||||||
const origPage = page
|
|
||||||
|
|
||||||
try {
|
|
||||||
const userAgent = await browser.userAgent()
|
|
||||||
if (!page) {
|
|
||||||
page = (await browser.pages())[0] || (await browser.newPage())
|
|
||||||
page.setDefaultTimeout(timeoutMs)
|
|
||||||
}
|
|
||||||
await page.goto('https://chat.openai.com/auth/login', {
|
|
||||||
waitUntil: 'networkidle2'
|
|
||||||
})
|
|
||||||
logger.mark('chatgpt checkForChatGPTAtCapacity')
|
|
||||||
|
|
||||||
await checkForChatGPTAtCapacity(page)
|
|
||||||
|
|
||||||
// NOTE: this is where you may encounter a CAPTCHA
|
|
||||||
if (hasRecaptchaPlugin) {
|
|
||||||
logger.mark('RecaptchaPlugin key exists, try to solve recaptchas')
|
|
||||||
await page.solveRecaptchas()
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.mark('chatgpt checkForChatGPTAtCapacity again')
|
|
||||||
await checkForChatGPTAtCapacity(page)
|
|
||||||
|
|
||||||
// once we get to this point, the Cloudflare cookies should be available
|
|
||||||
|
|
||||||
// login as well (optional)
|
|
||||||
if (email && password) {
|
|
||||||
let retry = 3
|
|
||||||
while (retry > 0) {
|
|
||||||
try {
|
|
||||||
await waitForConditionOrAtCapacity(page, () =>
|
|
||||||
page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 })
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
await checkForChatGPTAtCapacity(page)
|
|
||||||
}
|
|
||||||
retry--
|
|
||||||
}
|
|
||||||
await waitForConditionOrAtCapacity(page, () =>
|
|
||||||
page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 })
|
|
||||||
)
|
|
||||||
await common.sleep(500)
|
|
||||||
|
|
||||||
// click login button and wait for navigation to finish
|
|
||||||
do {
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation({
|
|
||||||
waitUntil: 'networkidle2',
|
|
||||||
timeout: timeoutMs
|
|
||||||
}),
|
|
||||||
page.click('#__next .btn-primary')
|
|
||||||
])
|
|
||||||
await common.sleep(1000)
|
|
||||||
} while (page.url().endsWith('/auth/login'))
|
|
||||||
logger.mark('进入登录页面')
|
|
||||||
await checkForChatGPTAtCapacity(page)
|
|
||||||
|
|
||||||
let submitP
|
|
||||||
|
|
||||||
if (isGoogleLogin) {
|
|
||||||
await page.click('button[data-provider="google"]')
|
|
||||||
await page.waitForSelector('input[type="email"]')
|
|
||||||
await page.type('input[type="email"]', email, { delay: 10 })
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
])
|
|
||||||
await page.waitForSelector('input[type="password"]', { visible: true })
|
|
||||||
await page.type('input[type="password"]', password, { delay: 10 })
|
|
||||||
submitP = () => page.keyboard.press('Enter')
|
|
||||||
} else {
|
|
||||||
await page.waitForSelector('#username')
|
|
||||||
await page.type('#username', email, { delay: 20 })
|
|
||||||
await common.sleep(100)
|
|
||||||
|
|
||||||
if (hasRecaptchaPlugin) {
|
|
||||||
// console.log('solveRecaptchas()')
|
|
||||||
const res = await page.solveRecaptchas()
|
|
||||||
// console.log('solveRecaptchas result', res)
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.click('button[type="submit"]')
|
|
||||||
await page.waitForSelector('#password', { timeout: timeoutMs })
|
|
||||||
await page.type('#password', password, { delay: 10 })
|
|
||||||
submitP = () => page.click('button[type="submit"]')
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
waitForConditionOrAtCapacity(page, () =>
|
|
||||||
page.waitForNavigation({
|
|
||||||
waitUntil: 'networkidle2',
|
|
||||||
timeout: timeoutMs
|
|
||||||
})
|
|
||||||
),
|
|
||||||
submitP()
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
await common.sleep(2000)
|
|
||||||
await checkForChatGPTAtCapacity(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageCookies = await page.cookies()
|
|
||||||
await redis.set('CHATGPT:RAW_COOKIES', JSON.stringify(pageCookies))
|
|
||||||
const cookies = pageCookies.reduce(
|
|
||||||
(map, cookie) => ({ ...map, [cookie.name]: cookie }),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
const authInfo = {
|
|
||||||
userAgent,
|
|
||||||
clearanceToken: cookies.cf_clearance?.value,
|
|
||||||
sessionToken: cookies['__Secure-next-auth.session-token']?.value,
|
|
||||||
cookies
|
|
||||||
}
|
|
||||||
logger.info('chatgpt登录成功')
|
|
||||||
|
|
||||||
return authInfo
|
|
||||||
} catch (err) {
|
|
||||||
throw err
|
|
||||||
} finally {
|
|
||||||
await page.screenshot({
|
|
||||||
type: 'png',
|
|
||||||
path: './error.png'
|
|
||||||
})
|
|
||||||
if (origBrowser) {
|
|
||||||
if (page && page !== origPage) {
|
|
||||||
await page.close()
|
|
||||||
}
|
|
||||||
} else if (browser) {
|
|
||||||
await browser.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
page = null
|
|
||||||
browser = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkForChatGPTAtCapacity (page, opts = {}) {
|
|
||||||
const {
|
|
||||||
timeoutMs = Config.chromeTimeoutMS, // 2 minutes
|
|
||||||
pollingIntervalMs = 3000,
|
|
||||||
retries = 10
|
|
||||||
} = opts
|
|
||||||
|
|
||||||
// console.log('checkForChatGPTAtCapacity', page.url())
|
|
||||||
let isAtCapacity = false
|
|
||||||
let numTries = 0
|
|
||||||
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
await solveSimpleCaptchas(page)
|
|
||||||
|
|
||||||
const res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]")
|
|
||||||
isAtCapacity = !!res?.length
|
|
||||||
|
|
||||||
if (isAtCapacity) {
|
|
||||||
if (++numTries >= retries) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// try refreshing the page if chatgpt is at capacity
|
|
||||||
await page.reload({
|
|
||||||
waitUntil: 'networkidle2',
|
|
||||||
timeout: timeoutMs
|
|
||||||
})
|
|
||||||
|
|
||||||
await common.sleep(pollingIntervalMs)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// ignore errors likely due to navigation
|
|
||||||
++numTries
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} while (isAtCapacity)
|
|
||||||
|
|
||||||
if (isAtCapacity) {
|
|
||||||
const error = new Error('ChatGPT is at capacity')
|
|
||||||
error.statusCode = 503
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function waitForConditionOrAtCapacity (
|
|
||||||
page,
|
|
||||||
condition,
|
|
||||||
opts = {}
|
|
||||||
) {
|
|
||||||
const { pollingIntervalMs = 500 } = opts
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let resolved = false
|
|
||||||
|
|
||||||
async function waitForCapacityText () {
|
|
||||||
if (resolved) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await checkForChatGPTAtCapacity(page)
|
|
||||||
|
|
||||||
if (!resolved) {
|
|
||||||
setTimeout(waitForCapacityText, pollingIntervalMs)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
condition()
|
|
||||||
.then(() => {
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (!resolved) {
|
|
||||||
resolved = true
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(waitForCapacityText, pollingIntervalMs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function solveSimpleCaptchas (page) {
|
|
||||||
try {
|
|
||||||
const verifyYouAreHuman = await page.$('text=Verify you are human')
|
|
||||||
if (verifyYouAreHuman) {
|
|
||||||
logger.mark('encounter cloudflare simple captcha "Verify you are human"')
|
|
||||||
await common.sleep(2000)
|
|
||||||
await verifyYouAreHuman.click({
|
|
||||||
delay: random.int(5, 25)
|
|
||||||
})
|
|
||||||
await common.sleep(1000)
|
|
||||||
}
|
|
||||||
const verifyYouAreHumanCN = await page.$('text=确认您是真人')
|
|
||||||
if (verifyYouAreHumanCN) {
|
|
||||||
logger.mark('encounter cloudflare simple captcha "确认您是真人"')
|
|
||||||
await common.sleep(2000)
|
|
||||||
await verifyYouAreHumanCN.click({
|
|
||||||
delay: random.int(5, 25)
|
|
||||||
})
|
|
||||||
await common.sleep(1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudflareButton = await page.$('.hcaptcha-box')
|
|
||||||
if (cloudflareButton) {
|
|
||||||
await common.sleep(2000)
|
|
||||||
await cloudflareButton.click({
|
|
||||||
delay: random.int(5, 25)
|
|
||||||
})
|
|
||||||
await common.sleep(1000)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// ignore errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -173,7 +173,8 @@ var ChatGPTAPI = /** @class */ (function () {
|
||||||
conversationId: conversationId,
|
conversationId: conversationId,
|
||||||
parentMessageId: messageId,
|
parentMessageId: messageId,
|
||||||
text: '',
|
text: '',
|
||||||
functionCall: null
|
functionCall: undefined,
|
||||||
|
conversation: []
|
||||||
};
|
};
|
||||||
responseP = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
responseP = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
||||||
var url, headers, body, res, reason, msg, error, response, message_1, res_1, err_1;
|
var url, headers, body, res, reason, msg, error, response, message_1, res_1, err_1;
|
||||||
|
|
@ -208,6 +209,7 @@ var ChatGPTAPI = /** @class */ (function () {
|
||||||
var _a;
|
var _a;
|
||||||
if (data === '[DONE]') {
|
if (data === '[DONE]') {
|
||||||
result.text = result.text.trim();
|
result.text = result.text.trim();
|
||||||
|
result.conversation = messages;
|
||||||
return resolve(result);
|
return resolve(result);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -293,6 +295,7 @@ var ChatGPTAPI = /** @class */ (function () {
|
||||||
return [2 /*return*/, reject(new Error("OpenAI error: ".concat(((_b = res_1 === null || res_1 === void 0 ? void 0 : res_1.detail) === null || _b === void 0 ? void 0 : _b.message) || (res_1 === null || res_1 === void 0 ? void 0 : res_1.detail) || 'unknown')))];
|
return [2 /*return*/, reject(new Error("OpenAI error: ".concat(((_b = res_1 === null || res_1 === void 0 ? void 0 : res_1.detail) === null || _b === void 0 ? void 0 : _b.message) || (res_1 === null || res_1 === void 0 ? void 0 : res_1.detail) || 'unknown')))];
|
||||||
}
|
}
|
||||||
result.detail = response;
|
result.detail = response;
|
||||||
|
result.conversation = messages;
|
||||||
return [2 /*return*/, resolve(result)];
|
return [2 /*return*/, resolve(result)];
|
||||||
case 6:
|
case 6:
|
||||||
err_1 = _c.sent();
|
err_1 = _c.sent();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import * as tokenizer from './tokenizer'
|
||||||
import * as types from './types'
|
import * as types from './types'
|
||||||
import globalFetch from 'node-fetch'
|
import globalFetch from 'node-fetch'
|
||||||
import { fetchSSE } from './fetch-sse'
|
import { fetchSSE } from './fetch-sse'
|
||||||
import {openai, Role} from "./types";
|
import {ChatCompletionRequestMessage, openai, Role} from "./types";
|
||||||
|
|
||||||
const CHATGPT_MODEL = 'gpt-3.5-turbo-0613'
|
const CHATGPT_MODEL = 'gpt-3.5-turbo-0613'
|
||||||
|
|
||||||
|
|
@ -176,16 +176,17 @@ export class ChatGPTAPI {
|
||||||
completionParams
|
completionParams
|
||||||
)
|
)
|
||||||
console.log(`maxTokens: ${maxTokens}, numTokens: ${numTokens}`)
|
console.log(`maxTokens: ${maxTokens}, numTokens: ${numTokens}`)
|
||||||
const result: types.ChatMessage = {
|
const result: types.ChatMessage & { conversation: openai.ChatCompletionRequestMessage[] } = {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
conversationId,
|
conversationId,
|
||||||
parentMessageId: messageId,
|
parentMessageId: messageId,
|
||||||
text: undefined,
|
text: '',
|
||||||
functionCall: undefined
|
functionCall: undefined,
|
||||||
|
conversation: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseP = new Promise<types.ChatMessage>(
|
const responseP = new Promise<types.ChatMessage & { conversation: openai.ChatCompletionRequestMessage[] }>(
|
||||||
async (resolve, reject) => {
|
async (resolve, reject) => {
|
||||||
const url = `${this._apiBaseUrl}/chat/completions`
|
const url = `${this._apiBaseUrl}/chat/completions`
|
||||||
const headers = {
|
const headers = {
|
||||||
|
|
@ -223,6 +224,7 @@ export class ChatGPTAPI {
|
||||||
onMessage: (data: string) => {
|
onMessage: (data: string) => {
|
||||||
if (data === '[DONE]') {
|
if (data === '[DONE]') {
|
||||||
result.text = result.text.trim()
|
result.text = result.text.trim()
|
||||||
|
result.conversation = messages
|
||||||
return resolve(result)
|
return resolve(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,7 +320,7 @@ export class ChatGPTAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
result.detail = response
|
result.detail = response
|
||||||
|
result.conversation = messages
|
||||||
return resolve(result)
|
return resolve(result)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return reject(err)
|
return reject(err)
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import { readFileSync, writeFile } from 'fs'
|
|
||||||
|
|
||||||
const scrape = async (pbCookie, proxy) => {
|
|
||||||
let option = { headers: { cookie: `${pbCookie}` } }
|
|
||||||
if (proxy) {
|
|
||||||
option.agent = proxy
|
|
||||||
}
|
|
||||||
const _setting = await fetch(
|
|
||||||
'https://poe.com/api/settings',
|
|
||||||
option
|
|
||||||
)
|
|
||||||
if (_setting.status !== 200) throw new Error('Failed to fetch token')
|
|
||||||
const appSettings = await _setting.json()
|
|
||||||
console.log(appSettings)
|
|
||||||
const { tchannelData: { channel: channelName } } = appSettings
|
|
||||||
return {
|
|
||||||
channelName,
|
|
||||||
appSettings,
|
|
||||||
formKey: appSettings.formKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getUpdatedSettings = async (channelName, pbCookie, proxy) => {
|
|
||||||
let option = { headers: { cookie: `${pbCookie}` } }
|
|
||||||
if (proxy) {
|
|
||||||
option.agent = proxy
|
|
||||||
}
|
|
||||||
const _setting = await fetch(
|
|
||||||
`https://poe.com/api/settings?channel=${channelName}`,
|
|
||||||
option
|
|
||||||
)
|
|
||||||
if (_setting.status !== 200) throw new Error('Failed to fetch token')
|
|
||||||
const appSettings = await _setting.json()
|
|
||||||
const { tchannelData: { minSeq } } = appSettings
|
|
||||||
const credentials = JSON.parse(readFileSync('config.json', 'utf8'))
|
|
||||||
credentials.app_settings.tchannelData.minSeq = minSeq
|
|
||||||
writeFile('config.json', JSON.stringify(credentials, null, 4), function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
minSeq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { scrape, getUpdatedSettings }
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
mutation AddHumanMessageMutation(
|
|
||||||
$chatId: BigInt!
|
|
||||||
$bot: String!
|
|
||||||
$query: String!
|
|
||||||
$source: MessageSource
|
|
||||||
$withChatBreak: Boolean! = false
|
|
||||||
) {
|
|
||||||
messageCreateWithStatus(
|
|
||||||
chatId: $chatId
|
|
||||||
bot: $bot
|
|
||||||
query: $query
|
|
||||||
source: $source
|
|
||||||
withChatBreak: $withChatBreak
|
|
||||||
) {
|
|
||||||
message {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
messageId
|
|
||||||
text
|
|
||||||
linkifiedText
|
|
||||||
authorNickname
|
|
||||||
state
|
|
||||||
vote
|
|
||||||
voteReason
|
|
||||||
creationTime
|
|
||||||
suggestedReplies
|
|
||||||
chat {
|
|
||||||
id
|
|
||||||
shouldShowDisclaimer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messageLimit{
|
|
||||||
canSend
|
|
||||||
numMessagesRemaining
|
|
||||||
resetTime
|
|
||||||
shouldShowReminder
|
|
||||||
}
|
|
||||||
chatBreak {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
messageId
|
|
||||||
text
|
|
||||||
linkifiedText
|
|
||||||
authorNickname
|
|
||||||
state
|
|
||||||
vote
|
|
||||||
voteReason
|
|
||||||
creationTime
|
|
||||||
suggestedReplies
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
mutation AddMessageBreakMutation($chatId: BigInt!) {
|
|
||||||
messageBreakCreate(chatId: $chatId) {
|
|
||||||
message {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
messageId
|
|
||||||
text
|
|
||||||
linkifiedText
|
|
||||||
authorNickname
|
|
||||||
state
|
|
||||||
vote
|
|
||||||
voteReason
|
|
||||||
creationTime
|
|
||||||
suggestedReplies
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) {
|
|
||||||
autoSubscribe(subscriptions: $subscriptions) {
|
|
||||||
viewer {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fragment BioFragment on Viewer {
|
|
||||||
id
|
|
||||||
poeUser {
|
|
||||||
id
|
|
||||||
uid
|
|
||||||
bio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
subscription ChatAddedSubscription {
|
|
||||||
chatAdded {
|
|
||||||
...ChatFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
fragment ChatFragment on Chat {
|
|
||||||
id
|
|
||||||
chatId
|
|
||||||
defaultBotNickname
|
|
||||||
shouldShowDisclaimer
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
query ChatPaginationQuery($bot: String!, $before: String, $last: Int! = 10) {
|
|
||||||
chatOfBot(bot: $bot) {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
messagesConnection(before: $before, last: $last) {
|
|
||||||
pageInfo {
|
|
||||||
hasPreviousPage
|
|
||||||
}
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
messageId
|
|
||||||
text
|
|
||||||
linkifiedText
|
|
||||||
authorNickname
|
|
||||||
state
|
|
||||||
vote
|
|
||||||
voteReason
|
|
||||||
creationTime
|
|
||||||
suggestedReplies
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
query ChatViewQuery($bot: String!) {
|
|
||||||
chatOfBot(bot: $bot) {
|
|
||||||
id
|
|
||||||
chatId
|
|
||||||
defaultBotNickname
|
|
||||||
shouldShowDisclaimer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) {
|
|
||||||
messagesDelete(messageIds: $messageIds) {
|
|
||||||
viewer {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
fragment HandleFragment on Viewer {
|
|
||||||
id
|
|
||||||
poeUser {
|
|
||||||
id
|
|
||||||
uid
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
mutation LoginWithVerificationCodeMutation(
|
|
||||||
$verificationCode: String!
|
|
||||||
$emailAddress: String
|
|
||||||
$phoneNumber: String
|
|
||||||
) {
|
|
||||||
loginWithVerificationCode(
|
|
||||||
verificationCode: $verificationCode
|
|
||||||
emailAddress: $emailAddress
|
|
||||||
phoneNumber: $phoneNumber
|
|
||||||
) {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
subscription MessageAddedSubscription($chatId: BigInt!) {
|
|
||||||
messageAdded(chatId: $chatId) {
|
|
||||||
...MessageFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
subscription MessageDeletedSubscription($chatId: BigInt!) {
|
|
||||||
messageDeleted(chatId: $chatId) {
|
|
||||||
id
|
|
||||||
messageId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
fragment MessageFragment on Message {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
messageId
|
|
||||||
text
|
|
||||||
linkifiedText
|
|
||||||
authorNickname
|
|
||||||
state
|
|
||||||
vote
|
|
||||||
voteReason
|
|
||||||
creationTime
|
|
||||||
suggestedReplies
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mutation MessageRemoveVoteMutation($messageId: BigInt!) {
|
|
||||||
messageRemoveVote(messageId: $messageId) {
|
|
||||||
message {
|
|
||||||
...MessageFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) {
|
|
||||||
messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) {
|
|
||||||
message {
|
|
||||||
...MessageFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
mutation SendVerificationCodeForLoginMutation(
|
|
||||||
$emailAddress: String
|
|
||||||
$phoneNumber: String
|
|
||||||
) {
|
|
||||||
sendVerificationCode(
|
|
||||||
verificationReason: login
|
|
||||||
emailAddress: $emailAddress
|
|
||||||
phoneNumber: $phoneNumber
|
|
||||||
) {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
mutation ShareMessagesMutation(
|
|
||||||
$chatId: BigInt!
|
|
||||||
$messageIds: [BigInt!]!
|
|
||||||
$comment: String
|
|
||||||
) {
|
|
||||||
messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) {
|
|
||||||
shareCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
mutation SignupWithVerificationCodeMutation(
|
|
||||||
$verificationCode: String!
|
|
||||||
$emailAddress: String
|
|
||||||
$phoneNumber: String
|
|
||||||
) {
|
|
||||||
signupWithVerificationCode(
|
|
||||||
verificationCode: $verificationCode
|
|
||||||
emailAddress: $emailAddress
|
|
||||||
phoneNumber: $phoneNumber
|
|
||||||
) {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mutation StaleChatUpdateMutation($chatId: BigInt!) {
|
|
||||||
staleChatUpdate(chatId: $chatId) {
|
|
||||||
message {
|
|
||||||
...MessageFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
query SummarizePlainPostQuery($comment: String!) {
|
|
||||||
summarizePlainPost(comment: $comment)
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) {
|
|
||||||
summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId)
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) {
|
|
||||||
summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds)
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
fragment UserSnippetFragment on PoeUser {
|
|
||||||
id
|
|
||||||
uid
|
|
||||||
bio
|
|
||||||
handle
|
|
||||||
fullName
|
|
||||||
viewerIsFollowing
|
|
||||||
isPoeOnlyUser
|
|
||||||
profilePhotoURLTiny: profilePhotoUrl(size: tiny)
|
|
||||||
profilePhotoURLSmall: profilePhotoUrl(size: small)
|
|
||||||
profilePhotoURLMedium: profilePhotoUrl(size: medium)
|
|
||||||
profilePhotoURLLarge: profilePhotoUrl(size: large)
|
|
||||||
isFollowable
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
query ViewerInfoQuery {
|
|
||||||
viewer {
|
|
||||||
id
|
|
||||||
uid
|
|
||||||
...ViewerStateFragment
|
|
||||||
...BioFragment
|
|
||||||
...HandleFragment
|
|
||||||
hasCompletedMultiplayerNux
|
|
||||||
poeUser {
|
|
||||||
id
|
|
||||||
...UserSnippetFragment
|
|
||||||
}
|
|
||||||
messageLimit{
|
|
||||||
canSend
|
|
||||||
numMessagesRemaining
|
|
||||||
resetTime
|
|
||||||
shouldShowReminder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
fragment ViewerStateFragment on Viewer {
|
|
||||||
id
|
|
||||||
__typename
|
|
||||||
iosMinSupportedVersion: integerGate(gateName: "poe_ios_min_supported_version")
|
|
||||||
iosMinEncouragedVersion: integerGate(
|
|
||||||
gateName: "poe_ios_min_encouraged_version"
|
|
||||||
)
|
|
||||||
macosMinSupportedVersion: integerGate(
|
|
||||||
gateName: "poe_macos_min_supported_version"
|
|
||||||
)
|
|
||||||
macosMinEncouragedVersion: integerGate(
|
|
||||||
gateName: "poe_macos_min_encouraged_version"
|
|
||||||
)
|
|
||||||
showPoeDebugPanel: booleanGate(gateName: "poe_show_debug_panel")
|
|
||||||
enableCommunityFeed: booleanGate(gateName: "enable_poe_shares_feed")
|
|
||||||
linkifyText: booleanGate(gateName: "poe_linkify_response")
|
|
||||||
enableSuggestedReplies: booleanGate(gateName: "poe_suggested_replies")
|
|
||||||
removeInviteLimit: booleanGate(gateName: "poe_remove_invite_limit")
|
|
||||||
enableInAppPurchases: booleanGate(gateName: "poe_enable_in_app_purchases")
|
|
||||||
availableBots {
|
|
||||||
nickname
|
|
||||||
displayName
|
|
||||||
profilePicture
|
|
||||||
isDown
|
|
||||||
disclaimer
|
|
||||||
subtitle
|
|
||||||
poweredBy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
subscription ViewerStateUpdatedSubscription {
|
|
||||||
viewerStateUpdated {
|
|
||||||
...ViewerStateFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,299 +0,0 @@
|
||||||
import { readFileSync } from 'fs'
|
|
||||||
import { scrape } from './credential.js'
|
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import crypto from 'crypto'
|
|
||||||
import { Config } from '../config.js'
|
|
||||||
|
|
||||||
let proxy
|
|
||||||
if (Config.proxy) {
|
|
||||||
try {
|
|
||||||
proxy = (await import('https-proxy-agent')).default
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// used when test as a single file
|
|
||||||
// const _path = process.cwd()
|
|
||||||
const _path = process.cwd() + '/plugins/chatgpt-plugin/utils/poe'
|
|
||||||
const gqlDir = `${_path}/graphql`
|
|
||||||
const queries = {
|
|
||||||
// chatViewQuery: readFileSync(gqlDir + '/ChatViewQuery.graphql', 'utf8'),
|
|
||||||
addMessageBreakMutation: readFileSync(gqlDir + '/AddMessageBreakMutation.graphql', 'utf8'),
|
|
||||||
chatPaginationQuery: readFileSync(gqlDir + '/ChatPaginationQuery.graphql', 'utf8'),
|
|
||||||
addHumanMessageMutation: readFileSync(gqlDir + '/AddHumanMessageMutation.graphql', 'utf8'),
|
|
||||||
loginMutation: readFileSync(gqlDir + '/LoginWithVerificationCodeMutation.graphql', 'utf8'),
|
|
||||||
signUpWithVerificationCodeMutation: readFileSync(gqlDir + '/SignupWithVerificationCodeMutation.graphql', 'utf8'),
|
|
||||||
sendVerificationCodeMutation: readFileSync(gqlDir + '/SendVerificationCodeForLoginMutation.graphql', 'utf8')
|
|
||||||
}
|
|
||||||
const optionMap = [
|
|
||||||
{ title: 'Claude (Powered by Anthropic)', value: 'a2' },
|
|
||||||
{ title: 'Sage (Powered by OpenAI - logical)', value: 'capybara' },
|
|
||||||
{ title: 'Dragonfly (Powered by OpenAI - simpler)', value: 'nutria' },
|
|
||||||
{ title: 'ChatGPT (Powered by OpenAI - current)', value: 'chinchilla' },
|
|
||||||
{ title: 'Claude+', value: 'a2_2' },
|
|
||||||
{ title: 'GPT-4', value: 'beaver' }
|
|
||||||
]
|
|
||||||
export class PoeClient {
|
|
||||||
constructor (props) {
|
|
||||||
this.config = props
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Referrer: 'https://poe.com/',
|
|
||||||
Origin: 'https://poe.com',
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
|
||||||
}
|
|
||||||
|
|
||||||
chatId = 0
|
|
||||||
bot = ''
|
|
||||||
|
|
||||||
reConnectWs = false
|
|
||||||
|
|
||||||
async setCredentials () {
|
|
||||||
let result = await scrape(this.config.quora_cookie, this.config.proxy ? proxy(Config.proxy) : null)
|
|
||||||
console.log(result)
|
|
||||||
this.config.quora_formkey = result.appSettings.formkey
|
|
||||||
this.config.channel_name = result.channelName
|
|
||||||
this.config.app_settings = result.appSettings
|
|
||||||
|
|
||||||
// set value
|
|
||||||
this.headers['poe-formkey'] = this.config.quora_formkey // unused
|
|
||||||
this.headers['poe-tchannel'] = this.config.channel_name
|
|
||||||
this.headers.Cookie = this.config.quora_cookie
|
|
||||||
console.log(this.headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
async subscribe () {
|
|
||||||
const query = {
|
|
||||||
queryName: 'subscriptionsMutation',
|
|
||||||
variables: {
|
|
||||||
subscriptions: [
|
|
||||||
{
|
|
||||||
subscriptionName: 'messageAdded',
|
|
||||||
query: 'subscription subscriptions_messageAdded_Subscription(\n $chatId: BigInt!\n) {\n messageAdded(chatId: $chatId) {\n id\n messageId\n creationTime\n state\n ...ChatMessage_message\n ...chatHelpers_isBotMessage\n }\n}\n\nfragment ChatMessageDownvotedButton_message on Message {\n ...MessageFeedbackReasonModal_message\n ...MessageFeedbackOtherModal_message\n}\n\nfragment ChatMessageDropdownMenu_message on Message {\n id\n messageId\n vote\n text\n ...chatHelpers_isBotMessage\n}\n\nfragment ChatMessageFeedbackButtons_message on Message {\n id\n messageId\n vote\n voteReason\n ...ChatMessageDownvotedButton_message\n}\n\nfragment ChatMessageOverflowButton_message on Message {\n text\n ...ChatMessageDropdownMenu_message\n ...chatHelpers_isBotMessage\n}\n\nfragment ChatMessageSuggestedReplies_SuggestedReplyButton_message on Message {\n messageId\n}\n\nfragment ChatMessageSuggestedReplies_message on Message {\n suggestedReplies\n ...ChatMessageSuggestedReplies_SuggestedReplyButton_message\n}\n\nfragment ChatMessage_message on Message {\n id\n messageId\n text\n author\n linkifiedText\n state\n ...ChatMessageSuggestedReplies_message\n ...ChatMessageFeedbackButtons_message\n ...ChatMessageOverflowButton_message\n ...chatHelpers_isHumanMessage\n ...chatHelpers_isBotMessage\n ...chatHelpers_isChatBreak\n ...chatHelpers_useTimeoutLevel\n ...MarkdownLinkInner_message\n}\n\nfragment MarkdownLinkInner_message on Message {\n messageId\n}\n\nfragment MessageFeedbackOtherModal_message on Message {\n id\n messageId\n}\n\nfragment MessageFeedbackReasonModal_message on Message {\n id\n messageId\n}\n\nfragment chatHelpers_isBotMessage on Message {\n ...chatHelpers_isHumanMessage\n ...chatHelpers_isChatBreak\n}\n\nfragment chatHelpers_isChatBreak on Message {\n author\n}\n\nfragment chatHelpers_isHumanMessage on Message {\n author\n}\n\nfragment chatHelpers_useTimeoutLevel on Message {\n id\n state\n text\n messageId\n}\n'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subscriptionName: 'viewerStateUpdated',
|
|
||||||
query: 'subscription subscriptions_viewerStateUpdated_Subscription {\n viewerStateUpdated {\n id\n ...ChatPageBotSwitcher_viewer\n }\n}\n\nfragment BotHeader_bot on Bot {\n displayName\n ...BotImage_bot\n}\n\nfragment BotImage_bot on Bot {\n profilePicture\n displayName\n}\n\nfragment BotLink_bot on Bot {\n displayName\n}\n\nfragment ChatPageBotSwitcher_viewer on Viewer {\n availableBots {\n id\n ...BotLink_bot\n ...BotHeader_bot\n }\n}\n'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
query: 'mutation subscriptionsMutation(\n $subscriptions: [AutoSubscriptionQuery!]!\n) {\n autoSubscribe(subscriptions: $subscriptions) {\n viewer {\n id\n }\n }\n}\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.makeRequest(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
async makeRequest (request) {
|
|
||||||
let payload = JSON.stringify(request)
|
|
||||||
let baseString = payload + this.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
|
|
||||||
const md5 = crypto.createHash('md5').update(baseString).digest('hex')
|
|
||||||
let option = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: Object.assign(this.headers, {
|
|
||||||
'poe-tag-id': md5,
|
|
||||||
'content-type': 'application/json'
|
|
||||||
}),
|
|
||||||
body: payload
|
|
||||||
}
|
|
||||||
if (this.config.proxy) {
|
|
||||||
option.agent = proxy(Config.proxy)
|
|
||||||
}
|
|
||||||
const response = await fetch('https://poe.com/api/gql_POST', option)
|
|
||||||
let text = await response.text()
|
|
||||||
try {
|
|
||||||
let result = JSON.parse(text)
|
|
||||||
console.log({ result })
|
|
||||||
return result
|
|
||||||
} catch (e) {
|
|
||||||
console.error(text)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBot (displayName) {
|
|
||||||
let r
|
|
||||||
let retry = 10
|
|
||||||
while (retry >= 0) {
|
|
||||||
let url = `https://poe.com/_next/data/${this.nextData.buildId}/${displayName}.json`
|
|
||||||
let option = {
|
|
||||||
headers: this.headers
|
|
||||||
}
|
|
||||||
if (this.config.proxy) {
|
|
||||||
option.agent = proxy(Config.proxy)
|
|
||||||
}
|
|
||||||
let r = await fetch(url, option)
|
|
||||||
let res = await r.text()
|
|
||||||
try {
|
|
||||||
let chatData = (JSON.parse(res)).pageProps.payload.chatOfBotDisplayName
|
|
||||||
return chatData
|
|
||||||
} catch (e) {
|
|
||||||
r = res
|
|
||||||
retry--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getChatId () {
|
|
||||||
let option = {
|
|
||||||
headers: this.headers
|
|
||||||
}
|
|
||||||
if (this.config.proxy) {
|
|
||||||
option.agent = proxy(Config.proxy)
|
|
||||||
}
|
|
||||||
let r = await fetch('https://poe.com', option)
|
|
||||||
let text = await r.text()
|
|
||||||
const jsonRegex = /<script id="__NEXT_DATA__" type="application\/json">(.+?)<\/script>/
|
|
||||||
const jsonText = text.match(jsonRegex)[1]
|
|
||||||
const nextData = JSON.parse(jsonText)
|
|
||||||
this.nextData = nextData
|
|
||||||
this.viewer = nextData.props.pageProps.payload.viewer
|
|
||||||
|
|
||||||
this.formkey = this.extract_formkey(text)
|
|
||||||
this.headers['poe-formkey'] = this.formkey
|
|
||||||
let bots = this.viewer.availableBots
|
|
||||||
this.bots = {}
|
|
||||||
for (let i = 0; i < bots.length; i++) {
|
|
||||||
let bot = bots[i]
|
|
||||||
let chatData = await this.getBot(bot.displayName)
|
|
||||||
this.bots[chatData.defaultBotObject.nickname] = chatData
|
|
||||||
}
|
|
||||||
console.log(this.bots)
|
|
||||||
}
|
|
||||||
|
|
||||||
extract_formkey (html) {
|
|
||||||
const scriptRegex = /<script>if\(.+\)throw new Error;(.+)<\/script>/
|
|
||||||
const scriptText = html.match(scriptRegex)[1]
|
|
||||||
const keyRegex = /var .="([0-9a-f]+)",/
|
|
||||||
const keyText = scriptText.match(keyRegex)[1]
|
|
||||||
const cipherRegex = /.\[(\d+)]=.\[(\d+)]/g
|
|
||||||
const cipherPairs = scriptText.match(cipherRegex)
|
|
||||||
|
|
||||||
const formkeyList = Array(cipherPairs.length).fill('')
|
|
||||||
for (const pair of cipherPairs) {
|
|
||||||
const [formkeyIndex, keyIndex] = pair.match(/\d+/g).map(Number)
|
|
||||||
formkeyList[formkeyIndex] = keyText[keyIndex]
|
|
||||||
}
|
|
||||||
const formkey = formkeyList.join('')
|
|
||||||
|
|
||||||
return formkey
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearContext (bot) {
|
|
||||||
try {
|
|
||||||
const data = await this.makeRequest({
|
|
||||||
query: `${queries.addMessageBreakMutation}`,
|
|
||||||
variables: { chatId: this.config.chat_ids[bot] }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!data.data) {
|
|
||||||
this.reConnectWs = true // for websocket purpose
|
|
||||||
console.log('ON TRY! Could not clear context! Trying to reLogin..')
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
} catch (e) {
|
|
||||||
this.reConnectWs = true // for websocket purpose
|
|
||||||
console.log('ON CATCH! Could not clear context! Trying to reLogin..')
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMsg (bot, query) {
|
|
||||||
try {
|
|
||||||
const data = await this.makeRequest({
|
|
||||||
query: `${queries.addHumanMessageMutation}`,
|
|
||||||
variables: {
|
|
||||||
bot,
|
|
||||||
chatId: this.bots[bot].chatId,
|
|
||||||
query,
|
|
||||||
source: null,
|
|
||||||
withChatBreak: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log(data)
|
|
||||||
if (!data.data) {
|
|
||||||
this.reConnectWs = true // for cli websocket purpose
|
|
||||||
console.log('Could not send message! Trying to reLogin..')
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
} catch (e) {
|
|
||||||
this.reConnectWs = true // for cli websocket purpose
|
|
||||||
console.error(e)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getHistory (bot) {
|
|
||||||
try {
|
|
||||||
let response = await this.makeRequest({
|
|
||||||
query: `${queries.chatPaginationQuery}`,
|
|
||||||
variables: {
|
|
||||||
before: null,
|
|
||||||
bot,
|
|
||||||
last: 25
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data.chatOfBot.messagesConnection.edges
|
|
||||||
.map(({ node: { messageId, text, authorNickname } }) => ({
|
|
||||||
messageId,
|
|
||||||
text,
|
|
||||||
authorNickname
|
|
||||||
}))
|
|
||||||
} catch (e) {
|
|
||||||
console.log('There has been an error while fetching your history!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteMessages (msgIds) {
|
|
||||||
await this.makeRequest({
|
|
||||||
queryName: 'MessageDeleteConfirmationModal_deleteMessageMutation_Mutation',
|
|
||||||
variables: {
|
|
||||||
messageIds: msgIds
|
|
||||||
},
|
|
||||||
query: 'mutation MessageDeleteConfirmationModal_deleteMessageMutation_Mutation(\n $messageIds: [BigInt!]!\n){\n messagesDelete(messageIds: $messageIds) {\n edgeIds\n }\n}\n'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getResponse (bot) {
|
|
||||||
let text
|
|
||||||
let state
|
|
||||||
let authorNickname
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
||||||
let response = await this.makeRequest({
|
|
||||||
query: `${queries.chatPaginationQuery}`,
|
|
||||||
variables: {
|
|
||||||
before: null,
|
|
||||||
bot,
|
|
||||||
last: 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let base = response.data.chatOfBot.messagesConnection.edges
|
|
||||||
let lastEdgeIndex = base.length - 1
|
|
||||||
text = base[lastEdgeIndex].node.text
|
|
||||||
authorNickname = base[lastEdgeIndex].node.authorNickname
|
|
||||||
state = base[lastEdgeIndex].node.state
|
|
||||||
if (state === 'complete' && authorNickname === bot) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Could not get response!')
|
|
||||||
return {
|
|
||||||
status: false,
|
|
||||||
message: 'failed',
|
|
||||||
data: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: true,
|
|
||||||
message: 'success',
|
|
||||||
data: text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import WebSocket from 'ws'
|
|
||||||
import * as diff from 'diff'
|
|
||||||
import { readFileSync } from 'fs'
|
|
||||||
|
|
||||||
const getSocketUrl = async () => {
|
|
||||||
const tchRand = Math.floor(100000 + Math.random() * 900000) // They're surely using 6 digit random number for ws url.
|
|
||||||
const socketUrl = `wss://tch${tchRand}.tch.quora.com`
|
|
||||||
const credentials = JSON.parse(readFileSync('config.json', 'utf8'))
|
|
||||||
const appSettings = credentials.app_settings.tchannelData
|
|
||||||
const boxName = appSettings.boxName
|
|
||||||
const minSeq = appSettings.minSeq
|
|
||||||
const channel = appSettings.channel
|
|
||||||
const hash = appSettings.channelHash
|
|
||||||
return `${socketUrl}/up/${boxName}/updates?min_seq=${minSeq}&channel=${channel}&hash=${hash}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectWs = async () => {
|
|
||||||
const url = await getSocketUrl()
|
|
||||||
const ws = new WebSocket(url)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ws.on('open', function open () {
|
|
||||||
console.log('Connected to websocket')
|
|
||||||
return resolve(ws)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const disconnectWs = async (ws) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ws.on('close', function close () {
|
|
||||||
return resolve(true)
|
|
||||||
})
|
|
||||||
ws.close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const listenWs = async (ws) => {
|
|
||||||
let previousText = ''
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const onMessage = function incoming (data) {
|
|
||||||
let jsonData = JSON.parse(data)
|
|
||||||
if (jsonData.messages && jsonData.messages.length > 0) {
|
|
||||||
const messages = JSON.parse(jsonData.messages[0])
|
|
||||||
const dataPayload = messages.payload.data
|
|
||||||
const text = dataPayload.messageAdded.text
|
|
||||||
const state = dataPayload.messageAdded.state
|
|
||||||
if (state !== 'complete') {
|
|
||||||
const differences = diff.diffChars(previousText, text)
|
|
||||||
let result = ''
|
|
||||||
differences.forEach((part) => {
|
|
||||||
if (part.added) {
|
|
||||||
result += part.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
previousText = text
|
|
||||||
process.stdout.write(result)
|
|
||||||
} else {
|
|
||||||
ws.removeListener('message', onMessage)
|
|
||||||
return resolve(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.on('message', onMessage)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import WebSocket from 'ws'
|
|
||||||
import * as diff from 'diff'
|
|
||||||
import { readFileSync } from 'fs'
|
|
||||||
|
|
||||||
const getSocketUrl = async () => {
|
|
||||||
const tchRand = Math.floor(100000 + Math.random() * 900000) // They're surely using 6 digit random number for ws url.
|
|
||||||
const socketUrl = `wss://tch${tchRand}.tch.quora.com`
|
|
||||||
const credentials = JSON.parse(readFileSync('config.json', 'utf8'))
|
|
||||||
const appSettings = credentials.app_settings.tchannelData
|
|
||||||
const boxName = appSettings.boxName
|
|
||||||
const minSeq = appSettings.minSeq
|
|
||||||
const channel = appSettings.channel
|
|
||||||
const hash = appSettings.channelHash
|
|
||||||
return `${socketUrl}/up/${boxName}/updates?min_seq=${minSeq}&channel=${channel}&hash=${hash}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectWs = async () => {
|
|
||||||
const url = await getSocketUrl()
|
|
||||||
const ws = new WebSocket(url)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ws.on('open', function open () {
|
|
||||||
console.log('Connected to websocket')
|
|
||||||
return resolve(ws)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const disconnectWs = async (ws) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ws.on('close', function close () {
|
|
||||||
return resolve(true)
|
|
||||||
})
|
|
||||||
ws.close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const listenWs = async (ws) => {
|
|
||||||
let previousText = ''
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const onMessage = function incoming (data) {
|
|
||||||
let jsonData = JSON.parse(data)
|
|
||||||
if (jsonData.messages && jsonData.messages.length > 0) {
|
|
||||||
const messages = JSON.parse(jsonData.messages[0])
|
|
||||||
const dataPayload = messages.payload.data
|
|
||||||
const text = dataPayload.messageAdded.text
|
|
||||||
const state = dataPayload.messageAdded.state
|
|
||||||
if (state !== 'complete') {
|
|
||||||
const differences = diff.diffChars(previousText, text)
|
|
||||||
let result = ''
|
|
||||||
differences.forEach((part) => {
|
|
||||||
if (part.added) {
|
|
||||||
result += part.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
previousText = text
|
|
||||||
process.stdout.write(result)
|
|
||||||
} else {
|
|
||||||
ws.removeListener('message', onMessage)
|
|
||||||
return resolve(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.on('message', onMessage)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -11,9 +11,19 @@ export function readPrompts () {
|
||||||
txtFiles.forEach(txtFile => {
|
txtFiles.forEach(txtFile => {
|
||||||
let name = _.trimEnd(txtFile, '.txt')
|
let name = _.trimEnd(txtFile, '.txt')
|
||||||
const content = fs.readFileSync(`${_path}/plugins/chatgpt-plugin/prompts/${txtFile}`, 'utf8')
|
const content = fs.readFileSync(`${_path}/plugins/chatgpt-plugin/prompts/${txtFile}`, 'utf8')
|
||||||
|
let example = []
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(`${_path}/plugins/chatgpt-plugin/prompts/${name}_example.json`)) {
|
||||||
|
example = fs.readFileSync(`${_path}/plugins/chatgpt-plugin/prompts/${name}_example.json`, 'utf8')
|
||||||
|
example = JSON.parse(example)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.debug(err)
|
||||||
|
}
|
||||||
prompts.push({
|
prompts.push({
|
||||||
name,
|
name,
|
||||||
content
|
content,
|
||||||
|
example
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -34,11 +44,15 @@ export function getPromptByName (name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveOnePrompt (name, content) {
|
export function saveOnePrompt (name, content, examples) {
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
mkdirs(`${_path}/plugins/chatgpt-plugin/prompts`)
|
mkdirs(`${_path}/plugins/chatgpt-plugin/prompts`)
|
||||||
let filePath = `${_path}/plugins/chatgpt-plugin/prompts/${name}.txt`
|
let filePath = `${_path}/plugins/chatgpt-plugin/prompts/${name}.txt`
|
||||||
fs.writeFileSync(filePath, content)
|
fs.writeFileSync(filePath, content)
|
||||||
|
if (examples) {
|
||||||
|
let examplePath = `${_path}/plugins/chatgpt-plugin/prompts/${name}_example.json`
|
||||||
|
fs.writeFileSync(examplePath, JSON.stringify(examples))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteOnePrompt (name) {
|
export function deleteOnePrompt (name) {
|
||||||
|
|
@ -46,4 +60,8 @@ export function deleteOnePrompt (name) {
|
||||||
mkdirs(`${_path}/plugins/chatgpt-plugin/prompts`)
|
mkdirs(`${_path}/plugins/chatgpt-plugin/prompts`)
|
||||||
let filePath = `${_path}/plugins/chatgpt-plugin/prompts/${name}.txt`
|
let filePath = `${_path}/plugins/chatgpt-plugin/prompts/${name}.txt`
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
|
try {
|
||||||
|
let examplePath = `${_path}/plugins/chatgpt-plugin/prompts/${name}_example.json`
|
||||||
|
fs.unlinkSync(examplePath)
|
||||||
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import fetch from 'node-fetch'
|
||||||
import proxy from 'https-proxy-agent'
|
import proxy from 'https-proxy-agent'
|
||||||
import { getMaxModelTokens } from '../common.js'
|
import { getMaxModelTokens } from '../common.js'
|
||||||
import { ChatGPTPuppeteer } from '../browser.js'
|
import { ChatGPTPuppeteer } from '../browser.js'
|
||||||
|
import { CustomGoogleGeminiClient } from '../../client/CustomGoogleGeminiClient.js'
|
||||||
export class WebsiteTool extends AbstractTool {
|
export class WebsiteTool extends AbstractTool {
|
||||||
name = 'website'
|
name = 'website'
|
||||||
|
|
||||||
|
|
@ -19,7 +20,8 @@ export class WebsiteTool extends AbstractTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func = async function (opts) {
|
func = async function (opts) {
|
||||||
let { url } = opts
|
let { url, mode, e } = opts
|
||||||
|
let browser
|
||||||
try {
|
try {
|
||||||
// let res = await fetch(url, {
|
// let res = await fetch(url, {
|
||||||
// headers: {
|
// headers: {
|
||||||
|
|
@ -33,7 +35,7 @@ export class WebsiteTool extends AbstractTool {
|
||||||
origin = true
|
origin = true
|
||||||
}
|
}
|
||||||
let ppt = new ChatGPTPuppeteer()
|
let ppt = new ChatGPTPuppeteer()
|
||||||
let browser = await ppt.getBrowser()
|
browser = await ppt.getBrowser()
|
||||||
let page = await browser.newPage()
|
let page = await browser.newPage()
|
||||||
await page.goto(url, {
|
await page.goto(url, {
|
||||||
waitUntil: 'networkidle2'
|
waitUntil: 'networkidle2'
|
||||||
|
|
@ -58,6 +60,20 @@ export class WebsiteTool extends AbstractTool {
|
||||||
.replace(/[\n\r]/gi, '') // 去除回车换行
|
.replace(/[\n\r]/gi, '') // 去除回车换行
|
||||||
.replace(/\s{2}/g, '') // 多个空格只保留一个空格
|
.replace(/\s{2}/g, '') // 多个空格只保留一个空格
|
||||||
.replace('<!DOCTYPE html>', '') // 去除<!DOCTYPE>声明
|
.replace('<!DOCTYPE html>', '') // 去除<!DOCTYPE>声明
|
||||||
|
|
||||||
|
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)
|
let maxModelTokens = getMaxModelTokens(Config.model)
|
||||||
text = text.slice(0, Math.min(text.length, maxModelTokens - 1600))
|
text = text.slice(0, Math.min(text.length, maxModelTokens - 1600))
|
||||||
let completionParams = {
|
let completionParams = {
|
||||||
|
|
@ -86,8 +102,15 @@ export class WebsiteTool extends AbstractTool {
|
||||||
const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分,从中整理出主体内容并转换成md格式,不需要主观描述性的语言与冗余的空白行。${text}`, { completionParams })
|
const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分,从中整理出主体内容并转换成md格式,不需要主观描述性的语言与冗余的空白行。${text}`, { completionParams })
|
||||||
let htmlContentSummary = htmlContentSummaryRes.text
|
let htmlContentSummary = htmlContentSummaryRes.text
|
||||||
return `this is the main content of website:\n ${htmlContentSummary}`
|
return `this is the main content of website:\n ${htmlContentSummary}`
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return `failed to visit the website, error: ${err.toString()}`
|
return `failed to visit the website, error: ${err.toString()}`
|
||||||
|
} finally {
|
||||||
|
if (browser) {
|
||||||
|
try {
|
||||||
|
await browser.close()
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
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 { QwenApi } from './alibaba/qwen-api.js'
|
||||||
|
|
||||||
// 代码参考: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 +26,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 +101,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,5 @@
|
||||||
import { Config } from '../config.js'
|
import { Config } from '../config.js'
|
||||||
|
import { newFetch } from '../proxy.js'
|
||||||
let proxy
|
|
||||||
if (Config.proxy) {
|
|
||||||
try {
|
|
||||||
proxy = (await import('https-proxy-agent')).default
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newFetch = (url, options = {}) => {
|
|
||||||
const defaultOptions = Config.proxy
|
|
||||||
? {
|
|
||||||
agent: proxy(Config.proxy)
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
|
|
||||||
const mergedOptions = {
|
|
||||||
...defaultOptions,
|
|
||||||
...options
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url, mergedOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成voxTTSMode下的wav音频
|
* 生成voxTTSMode下的wav音频
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
import { Config } from '../config.js'
|
import { Config } from '../config.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import nodejieba from '@node-rs/jieba'
|
||||||
|
|
||||||
let nodejieba
|
class Tokenizer {
|
||||||
try {
|
|
||||||
nodejieba = (await import('@node-rs/jieba')).default
|
|
||||||
nodejieba.load()
|
|
||||||
} catch (err) {
|
|
||||||
logger.info('未安装@node-rs/jieba,娱乐功能-词云统计不可用')
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Tokenizer {
|
|
||||||
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
throw new Error('no valid group id')
|
throw new Error('no valid group id')
|
||||||
|
|
@ -78,6 +71,10 @@ export class Tokenizer {
|
||||||
if (!nodejieba) {
|
if (!nodejieba) {
|
||||||
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
||||||
}
|
}
|
||||||
|
if (!this.loaded) {
|
||||||
|
nodejieba.load()
|
||||||
|
this.loaded = true
|
||||||
|
}
|
||||||
// duration represents the number of hours to go back, should in range [0, 24]
|
// duration represents the number of hours to go back, should in range [0, 24]
|
||||||
let chats = await this.getHistory(e, groupId, new Date(), duration, userId)
|
let chats = await this.getHistory(e, groupId, new Date(), duration, userId)
|
||||||
let durationStr = duration > 0 ? `${duration}小时` : '今日'
|
let durationStr = duration > 0 ? `${duration}小时` : '今日'
|
||||||
|
|
@ -139,7 +136,7 @@ export class Tokenizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ShamrockTokenizer extends Tokenizer {
|
class ShamrockTokenizer extends Tokenizer {
|
||||||
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
||||||
logger.mark('当前使用Shamrock适配器')
|
logger.mark('当前使用Shamrock适配器')
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
|
|
@ -227,3 +224,8 @@ function isTimestampInDateRange (timestamp, startOfSpecifiedDate, endOfSpecified
|
||||||
// Step 5: Compare the given timestamp with the start and end of the specified date
|
// Step 5: Compare the given timestamp with the start and end of the specified date
|
||||||
return timestamp >= startOfSpecifiedDate && timestamp < endOfSpecifiedDate
|
return timestamp >= startOfSpecifiedDate && timestamp < endOfSpecifiedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
default: new Tokenizer(),
|
||||||
|
shamrock: new ShamrockTokenizer()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ShamrockTokenizer, Tokenizer } from './tokenizer.js'
|
import Tokenizer from './tokenizer.js'
|
||||||
import { render } from '../common.js'
|
import { render } from '../common.js'
|
||||||
|
|
||||||
export async function makeWordcloud (e, groupId, duration = 0, userId) {
|
export async function makeWordcloud (e, groupId, duration = 0, userId) {
|
||||||
|
|
@ -7,13 +7,13 @@ export async function makeWordcloud (e, groupId, duration = 0, userId) {
|
||||||
let list = JSON.stringify(topK)
|
let list = JSON.stringify(topK)
|
||||||
logger.info(list)
|
logger.info(list)
|
||||||
let img = await render(e, 'chatgpt-plugin', 'wordcloud/index', { list }, { retType: 'base64' })
|
let img = await render(e, 'chatgpt-plugin', 'wordcloud/index', { list }, { retType: 'base64' })
|
||||||
await e.reply(img, true)
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTokenizer (e) {
|
function getTokenizer (e) {
|
||||||
if (e.adapter === 'shamrock') {
|
if (e.adapter === 'shamrock') {
|
||||||
return new ShamrockTokenizer()
|
return Tokenizer.shamrock
|
||||||
} else {
|
} else {
|
||||||
return new Tokenizer()
|
return Tokenizer.default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,8 @@ export default class XinghuoClient {
|
||||||
APILink = '/v2.1/chat'
|
APILink = '/v2.1/chat'
|
||||||
} else if (Config.xhmode === 'apiv3') {
|
} else if (Config.xhmode === 'apiv3') {
|
||||||
APILink = '/v3.1/chat'
|
APILink = '/v3.1/chat'
|
||||||
|
} else if (Config.xhmode === 'apiv3.5') {
|
||||||
|
APILink = '/v3.5/chat'
|
||||||
}
|
}
|
||||||
const date = new Date().toGMTString()
|
const date = new Date().toGMTString()
|
||||||
const algorithm = 'hmac-sha256'
|
const algorithm = 'hmac-sha256'
|
||||||
|
|
@ -176,7 +178,13 @@ export default class XinghuoClient {
|
||||||
const wsUrl = Config.xhmode == 'assistants' ? Config.xhAssistants : await this.getWsUrl()
|
const wsUrl = Config.xhmode == 'assistants' ? Config.xhAssistants : await this.getWsUrl()
|
||||||
if (!wsUrl) throw new Error('获取ws链接失败')
|
if (!wsUrl) throw new Error('获取ws链接失败')
|
||||||
let domain = 'general'
|
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 = {
|
const wsSendData = {
|
||||||
header: {
|
header: {
|
||||||
|
|
@ -375,7 +383,7 @@ export default class XinghuoClient {
|
||||||
let chatId = option?.chatId
|
let chatId = option?.chatId
|
||||||
let image = option?.image
|
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')
|
if (!Config.xhAppId || !Config.xhAPISecret || !Config.xhAPIKey) throw new Error('未配置api')
|
||||||
let Prompt = []
|
let Prompt = []
|
||||||
// 设定
|
// 设定
|
||||||
|
|
@ -387,7 +395,7 @@ export default class XinghuoClient {
|
||||||
logger.warn('星火设定序列化失败,本次对话不附带设定')
|
logger.warn('星火设定序列化失败,本次对话不附带设定')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Prompt = Config.xhPrompt ? [{ role: 'user', content: Config.xhPrompt }] : []
|
Prompt = option.system ? [{ role: 'system', content: option.system }] : []
|
||||||
}
|
}
|
||||||
if (Config.xhPromptEval) {
|
if (Config.xhPromptEval) {
|
||||||
Prompt.forEach(obj => {
|
Prompt.forEach(obj => {
|
||||||
|
|
|
||||||
17
yarn.lock
17
yarn.lock
|
|
@ -461,7 +461,7 @@
|
||||||
resolved "https://registry.npmmirror.com/@lukeed/ms/-/ms-2.0.1.tgz"
|
resolved "https://registry.npmmirror.com/@lukeed/ms/-/ms-2.0.1.tgz"
|
||||||
integrity sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==
|
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"
|
version "1.0.10"
|
||||||
resolved "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz"
|
resolved "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz"
|
||||||
integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==
|
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"
|
has-property-descriptors "^1.0.0"
|
||||||
object-keys "^1.1.1"
|
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:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
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"
|
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
|
||||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
nan@^2.15.0, nan@^2.17.0:
|
nan@^2.17.0:
|
||||||
version "2.17.0"
|
version "2.17.0"
|
||||||
resolved "https://registry.npmmirror.com/nan/-/nan-2.17.0.tgz"
|
resolved "https://registry.npmmirror.com/nan/-/nan-2.17.0.tgz"
|
||||||
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
|
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
|
||||||
|
|
@ -2866,14 +2861,6 @@ node-fetch@^3.3.1:
|
||||||
fetch-blob "^3.1.4"
|
fetch-blob "^3.1.4"
|
||||||
formdata-polyfill "^4.0.10"
|
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:
|
nodejs-pptx@^1.2.4:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.npmjs.org/nodejs-pptx/-/nodejs-pptx-1.2.4.tgz"
|
resolved "https://registry.npmjs.org/nodejs-pptx/-/nodejs-pptx-1.2.4.tgz"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue