diff --git a/guoba.support.js b/guoba.support.js index 7ca12608..4e85cd63 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -315,7 +315,7 @@ export function supportGuoba () { { field: 'model', label: 'OpenAI 模型', - bottomHelpMessage: 'gpt-4, gpt-4-0613, gpt-4-32k, gpt-4-32k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0613, gpt-3.5-turbo-16k-0613。默认为gpt-3.5-turbo,gpt-4需账户支持', + bottomHelpMessage: 'gpt-4, gpt-4-0613, gpt-4-1106, gpt-4-32k, gpt-4-32k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0613, gpt-3.5-turbo-1106, gpt-3.5-turbo-16k-0613。默认为gpt-3.5-turbo,gpt-4需账户支持', component: 'Input' }, { @@ -456,7 +456,7 @@ export function supportGuoba () { { field: 'sydneyWebsocketUseProxy', label: '对话使用sydney反代', - bottomHelpMessage: '【一般情况无需也不建议开启】默认情况下仅创建对话走反代,对话时仍然直连微软。开启本选项将使对话过程也走反,需反代支持', + bottomHelpMessage: '默认情况下仅创建对话走反代,对话时仍然直连微软。开启本选项将使对话过程也走反代,需反代支持。默认开启', component: 'Switch' }, { @@ -505,40 +505,6 @@ export function supportGuoba () { bottomHelpMessage: '使用GPT-4,注意试用配额较低,如果用不了就关掉', component: 'Switch' }, - { - label: '以下为浏览器方式的配置.(Deprecated)', - component: 'Divider' - }, - { - field: 'username', - label: '用户名', - bottomHelpMessage: 'OpenAI用户名。', - component: 'Input' - }, - { - field: 'password', - label: '密码', - bottomHelpMessage: 'OpenAI密码。', - component: 'InputPassword' - }, - { - field: 'UA', - label: '浏览器UA', - bottomHelpMessage: '模拟浏览器UA,无特殊需求保持默认即可', - component: 'InputTextArea' - }, - { - field: 'headless', - label: '无头模式', - bottomHelpMessage: '无界面的服务器可以开启,但遇到验证码时可能无法使用。(实测很容易卡住,几乎不可用)', - component: 'Switch' - }, - { - field: 'chromePath', - label: 'Chrome路径', - bottomHelpMessage: '为空使用默认puppeteer的chromium,也可以传递自己本机安装的Chrome可执行文件地址,提高通过率。windows可以是‘C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe’,linux通过which查找路径', - component: 'Input' - }, { label: '以下为Slack Claude方式的配置', component: 'Divider' @@ -625,16 +591,6 @@ export function supportGuoba () { bottomHelpMessage: '等待响应的超时时间,单位为秒,默认为120。如果不使用反代而是使用代理可以适当调低。', component: 'InputNumber' }, - { - label: '以下为ChatGLM方式的配置', - component: 'Divider' - }, - { - field: 'chatglmBaseUrl', - label: 'ChatGLM API地址', - bottomHelpMessage: '如 http://localhost:8080', - component: 'Input' - }, { label: '以下为星火方式的配置', component: 'Divider' @@ -649,6 +605,7 @@ export function supportGuoba () { { label: '体验版', value: 'web' }, { label: '讯飞星火认知大模型V1.5', value: 'api' }, { label: '讯飞星火认知大模型V2.0', value: 'apiv2' }, + { label: '讯飞星火认知大模型V3.0', value: 'apiv3' }, { label: '讯飞星火助手', value: 'assistants' } ] } diff --git a/server/modules/user.js b/server/modules/user.js index 1677c226..d5f41a48 100644 --- a/server/modules/user.js +++ b/server/modules/user.js @@ -2,170 +2,182 @@ import { UserInfo, AddUser } from './user_data.js' import { randomString, getUserData, getMasterQQ, getUin } from '../../utils/common.js' import fs from 'fs' -async function User(fastify, options) { - // 登录 - fastify.post('/login', async (request, reply) => { - const body = request.body || {} - let guobaLoginService - let guobaAPI = '' - try { - let { LoginService } = await import('../../../Guoba-Plugin/server/service/both/LoginService.js') - let { getAllWebAddress } = await import('../../../Guoba-Plugin/utils/common.js') - guobaLoginService = new LoginService() - guobaAPI = await getAllWebAddress() - } - catch (err) { - console.error(err) - guobaLoginService = { - signToken: () => {return null} - } - } - if (body.qq && body.passwd) { - const token = randomString(32) - if (body.qq == getUin() && await redis.get('CHATGPT:ADMIN_PASSWD') == body.passwd) { - const guobaToken = await guobaLoginService.signToken(body.qq) - AddUser({ user: body.qq, token: token, autho: 'admin' }) - reply.setCookie('token', token, { path: '/' }) - reply.send({ login: true, autho: 'admin', token: token, guobaToken: guobaToken, guoba: guobaAPI }) - } else { - const user = await getUserData(body.qq) - if (user.passwd != '' && user.passwd === body.passwd) { - AddUser({ user: body.qq, token: token, autho: 'user' }) - reply.setCookie('token', token, { path: '/' }) - reply.send({ login: true, autho: 'user', token: token }) - } else { - reply.send({ login: false, err: `用户名密码错误,如果忘记密码请私聊机器人输入 ${body.qq == getUin() ? '#修改管理密码' : '#修改用户密码'} 进行修改` }) - } - } - } else if (body.otp) { - const token = randomString(32) - const opt = await redis.get(`CHATGPT:SERVER_QUICK`) - if (opt && body.otp == opt) { - const guobaToken = await guobaLoginService.signToken(getUin()) - AddUser({ user: getUin(), token: token, autho: 'admin' }) - reply.setCookie('token', token, { path: '/' }) - reply.send({ login: true, autho: 'admin', token: token, user: getUin(), guobaToken: guobaToken, guoba: guobaAPI }) - } else { - reply.send({ login: false, err: `快捷登录代码错误,请检查后重试` }) - } - } else { - reply.send({ login: false, err: '未输入用户名或密码' }) - } - return reply +function getBots () { + if (Bot.uin === 88888) { + // 找适配器 + let adapters = Bot.adapter + return adapters?.map(uin => Bot[uin]) + } else if (Bot.adapter && Bot.adapter.length > 0) { + let bots = [Bot] + Bot.adapter.forEach(uin => { + bots.push(Bot[uin]) }) - // 快速登录 - fastify.post('/quick', async (request, reply) => { - const otp = randomString(6) - await redis.set( - `CHATGPT:SERVER_QUICK`, - otp, - { EX: 60000 } - ) - const master = (await getMasterQQ())[0] - if (Array.isArray(Bot.uin)) { - Bot.pickFriend(master).sendMsg(`收到工具箱快捷登录请求,1分钟内有效:${otp}`) + return bots + } +} + +async function User (fastify, options) { + // 登录 + fastify.post('/login', async (request, reply) => { + const body = request.body || {} + let guobaLoginService + let guobaAPI = '' + try { + let { LoginService } = await import('../../../Guoba-Plugin/server/service/both/LoginService.js') + let { getAllWebAddress } = await import('../../../Guoba-Plugin/utils/common.js') + guobaLoginService = new LoginService() + guobaAPI = await getAllWebAddress() + } catch (err) { + console.error(err) + guobaLoginService = { + signToken: () => { return null } + } + } + if (body.qq && body.passwd) { + const token = randomString(32) + if (body.qq == getUin() && await redis.get('CHATGPT:ADMIN_PASSWD') == body.passwd) { + const guobaToken = await guobaLoginService.signToken(body.qq) + AddUser({ user: body.qq, token, autho: 'admin' }) + reply.setCookie('token', token, { path: '/' }) + reply.send({ login: true, autho: 'admin', token, guobaToken, guoba: guobaAPI }) + } else { + const user = await getUserData(body.qq) + if (user.passwd != '' && user.passwd === body.passwd) { + AddUser({ user: body.qq, token, autho: 'user' }) + reply.setCookie('token', token, { path: '/' }) + reply.send({ login: true, autho: 'user', token }) } else { - Bot.sendPrivateMsg(master, `收到工具箱快捷登录请求,1分钟内有效:${otp}`, false) + reply.send({ login: false, err: `用户名密码错误,如果忘记密码请私聊机器人输入 ${body.qq == getUin() ? '#修改管理密码' : '#修改用户密码'} 进行修改` }) } - reply.send({ state: true }) - return reply - }) - // 检查用户是否存在 - fastify.post('/verify', async (request, reply) => { - const token = request.cookies.token || request.body?.token || 'unknown' - const user = UserInfo(token) - if (!user || token === 'unknown') { - reply.send({ - verify: false, - }) - return - } - reply.send({ - verify: true, - user: user.user, - autho: user.autho, - version: 10016, - }) - return reply + } + } else if (body.otp) { + const token = randomString(32) + const opt = await redis.get('CHATGPT:SERVER_QUICK') + if (opt && body.otp == opt) { + const guobaToken = await guobaLoginService.signToken(getUin()) + AddUser({ user: getUin(), token, autho: 'admin' }) + reply.setCookie('token', token, { path: '/' }) + reply.send({ login: true, autho: 'admin', token, user: getUin(), guobaToken, guoba: guobaAPI }) + } else { + reply.send({ login: false, err: '快捷登录代码错误,请检查后重试' }) + } + } else { + reply.send({ login: false, err: '未输入用户名或密码' }) + } + return reply + }) + // 快速登录 + fastify.post('/quick', async (request, reply) => { + const otp = randomString(6) + await redis.set( + 'CHATGPT:SERVER_QUICK', + otp, + { EX: 60000 } + ) + const master = (await getMasterQQ())[0] + let bots = getBots() + for (let bot of bots) { + bot.pickUser(master).sendMsg(`收到工具箱快捷登录请求,1分钟内有效:${otp}`) + } + reply.send({ state: true }) + return reply + }) + // 检查用户是否存在 + fastify.post('/verify', async (request, reply) => { + const token = request.cookies.token || request.body?.token || 'unknown' + const user = UserInfo(token) + if (!user || token === 'unknown') { + reply.send({ + verify: false + }) + return + } + reply.send({ + verify: true, + user: user.user, + autho: user.autho, + version: 10016 }) - // 获取用户数据 - fastify.post('/userData', async (request, reply) => { - const token = request.cookies.token || request.body?.token || 'unknown' - let user = UserInfo(token) - if (!user) user = { user: '' } - const userData = await getUserData(user.user) - reply.send({ - chat: userData.chat || [], - mode: userData.mode || '', - cast: userData.cast || { - api: '', //API设定 - bing: '', //必应设定 - bing_resource: '', //必应扩展资料 - slack: '', //Slack设定 - } - }) - return reply + return reply + }) + // 获取用户数据 + fastify.post('/userData', async (request, reply) => { + const token = request.cookies.token || request.body?.token || 'unknown' + let user = UserInfo(token) + if (!user) user = { user: '' } + const userData = await getUserData(user.user) + reply.send({ + chat: userData.chat || [], + mode: userData.mode || '', + cast: userData.cast || { + api: '', // API设定 + bing: '', // 必应设定 + bing_resource: '', // 必应扩展资料 + slack: '' // Slack设定 + } }) - // 删除用户 - fastify.post('/deleteUser', async (request, reply) => { - const token = request.cookies.token || request.body?.token || 'unknown' - const user = UserInfo(token) - if (!user || user === 'unknown') { - reply.send({ state: false, error: '无效token' }) - return - } - const filepath = `resources/ChatGPTCache/user/${user.user}.json` - fs.unlinkSync(filepath) - reply.send({ state: true }) - return reply - }) - // 修改密码 - fastify.post('/changePassword', async (request, reply) => { - const token = request.cookies.token || request.body?.token || 'unknown' - const user = UserInfo(token) - if (!user || user === 'unknown') { - reply.send({ state: false, error: '无效的用户信息' }) - return - } - const userData = await getUserData(user.user) - const body = request.body || {} - if (!body.newPasswd) { - reply.send({ state: false, error: '无效参数' }) - return - } - if (body.passwd && body.passwd != userData.passwd) { - reply.send({ state: false, error: '原始密码错误' }) + return reply + }) + // 删除用户 + fastify.post('/deleteUser', async (request, reply) => { + const token = request.cookies.token || request.body?.token || 'unknown' + const user = UserInfo(token) + if (!user || user === 'unknown') { + reply.send({ state: false, error: '无效token' }) + return + } + const filepath = `resources/ChatGPTCache/user/${user.user}.json` + fs.unlinkSync(filepath) + reply.send({ state: true }) + return reply + }) + // 修改密码 + fastify.post('/changePassword', async (request, reply) => { + const token = request.cookies.token || request.body?.token || 'unknown' + const user = UserInfo(token) + if (!user || user === 'unknown') { + reply.send({ state: false, error: '无效的用户信息' }) + return + } + const userData = await getUserData(user.user) + const body = request.body || {} + if (!body.newPasswd) { + reply.send({ state: false, error: '无效参数' }) + return + } + if (body.passwd && body.passwd != userData.passwd) { + reply.send({ state: false, error: '原始密码错误' }) + return + } + if (user.autho === 'admin') { + await redis.set('CHATGPT:ADMIN_PASSWD', body.newPasswd) + } else if (user.autho === 'user') { + const dir = 'resources/ChatGPTCache/user' + const filename = `${user.user}.json` + const filepath = path.join(dir, filename) + fs.mkdirSync(dir, { recursive: true }) + if (fs.existsSync(filepath)) { + fs.readFile(filepath, 'utf8', (err, data) => { + if (err) { + console.error(err) return - } - if (user.autho === 'admin') { - await redis.set('CHATGPT:ADMIN_PASSWD', body.newPasswd) - } else if (user.autho === 'user') { - const dir = 'resources/ChatGPTCache/user' - const filename = `${user.user}.json` - const filepath = path.join(dir, filename) - fs.mkdirSync(dir, { recursive: true }) - if (fs.existsSync(filepath)) { - fs.readFile(filepath, 'utf8', (err, data) => { - if (err) { - console.error(err) - return - } - const config = JSON.parse(data) - config.passwd = body.newPasswd - fs.writeFile(filepath, JSON.stringify(config), 'utf8', (err) => { - if (err) { - console.error(err) - } - }) - }) - } else { - reply.send({ state: false, error: '错误的用户数据' }) - return + } + const config = JSON.parse(data) + config.passwd = body.newPasswd + fs.writeFile(filepath, JSON.stringify(config), 'utf8', (err) => { + if (err) { + console.error(err) } - } - reply.send({ state: true }) - return reply - }) + }) + }) + } else { + reply.send({ state: false, error: '错误的用户数据' }) + return + } + } + reply.send({ state: true }) + return reply + }) } -export default User \ No newline at end of file +export default User diff --git a/utils/common.js b/utils/common.js index b3feb7f7..66e6b6ab 100644 --- a/utils/common.js +++ b/utils/common.js @@ -820,6 +820,7 @@ export async function getImageOcrText (e) { // logger.warn('resultArr', resultArr) return resultArr } catch (err) { + logger.warn('OCR失败,可能使用的适配器不支持OCR') return false // logger.error(err) } diff --git a/utils/config.js b/utils/config.js index 308338d3..9b145e67 100644 --- a/utils/config.js +++ b/utils/config.js @@ -144,7 +144,7 @@ const defaultConfig = { serpSource: 'ikechan8370', extraUrl: 'https://cpe.ikechan8370.com', smartMode: false, - bingCaptchaOneShotUrl: 'http://bingcaptcha.ikechan8370.com/bing', + bingCaptchaOneShotUrl: '', // claude2 claudeAIOrganizationId: '', claudeAISessionKey: '', diff --git a/utils/xinghuo/xinghuo.js b/utils/xinghuo/xinghuo.js index 04aa7280..b090410f 100644 --- a/utils/xinghuo/xinghuo.js +++ b/utils/xinghuo/xinghuo.js @@ -3,6 +3,7 @@ import { Config } from '../config.js' import { createParser } from 'eventsource-parser' import https from 'https' import WebSocket from 'ws' +import { createHmac } from 'crypto' const referer = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNuL2NoYXQ/aWQ9') const origin = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNu') @@ -14,13 +15,7 @@ try { } catch (err) { logger.warn('未安装form-data,无法使用星火模式') } -let crypto -try { - crypto = (await import('crypto')).default -} catch (err) { - logger.warn('未安装crypto,无法使用星火api模式') -} -async function getKeyv() { +async function getKeyv () { let Keyv try { Keyv = (await import('keyv')).default @@ -30,7 +25,7 @@ async function getKeyv() { return Keyv } export default class XinghuoClient { - constructor(opts) { + constructor (opts) { this.cache = opts.cache this.ssoSessionId = opts.ssoSessionId this.headers = { @@ -41,7 +36,7 @@ export default class XinghuoClient { } } - apiErrorInfo(code) { + apiErrorInfo (code) { switch (code) { case 10000: return '升级为ws出现错误' case 10001: return '通过ws读取用户的消息出错' @@ -74,7 +69,7 @@ export default class XinghuoClient { } } - async initCache() { + async initCache () { if (!this.conversationsCache) { const cacheOptions = this.cache || {} cacheOptions.namespace = cacheOptions.namespace || 'xh' @@ -83,38 +78,37 @@ export default class XinghuoClient { } } - async getWsUrl() { - if (!crypto) return false + async getWsUrl () { const APISecret = Config.xhAPISecret const APIKey = Config.xhAPIKey let APILink = '/v1.1/chat' - if (Config.xhmode == 'apiv2') { + if (Config.xhmode === 'apiv2') { APILink = '/v2.1/chat' - } else if (Config.xhmode == 'apiv3') { + } else if (Config.xhmode === 'apiv3') { APILink = '/v3.1/chat' } const date = new Date().toGMTString() const algorithm = 'hmac-sha256' const headers = 'host date request-line' const signatureOrigin = `host: spark-api.xf-yun.com\ndate: ${date}\nGET ${APILink} HTTP/1.1` - const hmac = crypto.createHmac('sha256', APISecret) + const hmac = createHmac('sha256', APISecret) hmac.update(signatureOrigin) const signature = hmac.digest('base64') const authorizationOrigin = `api_key="${APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"` const authorization = Buffer.from(authorizationOrigin).toString('base64') const v = { - authorization: authorization, - date: date, - host: "spark-api.xf-yun.com" + authorization, + date, + host: 'spark-api.xf-yun.com' } const url = `wss://spark-api.xf-yun.com${APILink}?${Object.keys(v).map(key => `${key}=${v[key]}`).join('&')}` return url } - async uploadImage(url) { + async uploadImage (url) { // 获取图片 let response = await fetch(url, { - method: 'GET', + method: 'GET' }) const blob = await response.blob() const arrayBuffer = await blob.arrayBuffer() @@ -125,7 +119,7 @@ export default class XinghuoClient { const respOss = await fetch('https://xinghuo.xfyun.cn/iflygpt/oss/sign', { method: 'POST', headers: { - Cookie: 'ssoSessionId=' + this.ssoSessionId + ';', + Cookie: 'ssoSessionId=' + this.ssoSessionId + ';' }, body: formData }) @@ -167,7 +161,7 @@ export default class XinghuoClient { } } - async apiMessage(prompt, chatId, ePrompt = []) { + async apiMessage (prompt, chatId, ePrompt = []) { if (!chatId) chatId = (Math.floor(Math.random() * 1000000) + 100000).toString() // 初始化缓存 @@ -180,12 +174,9 @@ export default class XinghuoClient { // 获取ws链接 const wsUrl = Config.xhmode == 'assistants' ? Config.xhAssistants : await this.getWsUrl() - if (!wsUrl) throw new Error('缺少依赖:crypto。请安装依赖后重试') + if (!wsUrl) throw new Error('获取ws链接失败') let domain = 'general' - if (Config.xhmode == 'apiv2') - domain = "generalv2" - else if (Config.xhmode == 'apiv3') - domain = "generalv3" + if (Config.xhmode == 'apiv2') { domain = 'generalv2' } else if (Config.xhmode == 'apiv3') { domain = 'generalv3' } // 编写消息内容 const wsSendData = { header: { @@ -194,7 +185,7 @@ export default class XinghuoClient { }, parameter: { chat: { - domain: domain, + domain, temperature: Config.xhTemperature, // 核采样阈值 max_tokens: Config.xhMaxTokens, // tokens最大长度 chat_id: chatId, @@ -203,10 +194,10 @@ export default class XinghuoClient { }, payload: { message: { - "text": [ + text: [ ...ePrompt, ...conversation.messages, - { "role": "user", "content": prompt } + { role: 'user', content: prompt } ] } } @@ -229,8 +220,8 @@ export default class XinghuoClient { const half = Math.floor(conversation.messages.length / 2) conversation.messages.splice(0, half) await this.conversationsCache.set(conversationKey, conversation) - resolve({ - id: (Math.floor(Math.random() * 1000000) + 100000).toString() , + resolve({ + id: (Math.floor(Math.random() * 1000000) + 100000).toString(), response: '对话以达到上限,已自动清理对话,请重试' }) } else { @@ -256,8 +247,8 @@ export default class XinghuoClient { conversation.messages.splice(0, half) } await this.conversationsCache.set(conversationKey, conversation) - resolve({ - id: chatId , + resolve({ + id: chatId, response: resMessage }) } @@ -271,7 +262,7 @@ export default class XinghuoClient { }) } - async webMessage(prompt, chatId, botId) { + async webMessage (prompt, chatId, botId) { if (!FormData) { throw new Error('缺少依赖:form-data。请安装依赖后重试') } @@ -281,7 +272,7 @@ export default class XinghuoClient { formData.append('clientType', '2') formData.append('chatId', chatId) if (prompt.image) { - prompt.text = prompt.text.replace("[图片]", "") // 清理消息中中首个被使用的图片 + prompt.text = prompt.text.replace('[图片]', '') // 清理消息中中首个被使用的图片 const imgdata = await this.uploadImage(prompt.image) if (imgdata) { formData.append('fileUrl', imgdata.url) @@ -313,7 +304,7 @@ export default class XinghuoClient { logger.error('星火statusCode:' + statusCode) } let response = '' - function onMessage(data) { + function onMessage (data) { // console.log(data) if (data === '') { return resolve({ @@ -380,7 +371,7 @@ export default class XinghuoClient { }) } - async sendMessage(prompt, option) { + async sendMessage (prompt, option) { let chatId = option?.chatId let image = option?.image @@ -396,9 +387,9 @@ export default class XinghuoClient { logger.warn('星火设定序列化失败,本次对话不附带设定') } } else { - Prompt = Config.xhPrompt ? [{ "role": "user", "content": Config.xhPrompt }] : [] + Prompt = Config.xhPrompt ? [{ role: 'user', content: Config.xhPrompt }] : [] } - if(Config.xhPromptEval) { + if (Config.xhPromptEval) { Prompt.forEach(obj => { try { obj.content = obj.content.replace(/{{(.*?)}}/g, (match, variable) => { @@ -427,7 +418,7 @@ export default class XinghuoClient { if (!chatId) { chatId = (await this.createChatList()).chatListId } - let { response } = await this.webMessage({ text: prompt, image: image }, chatId, botId) + let { response } = await this.webMessage({ text: prompt, image }, chatId, botId) // logger.info(response) // let responseText = atob(response) // 处理图片 @@ -451,14 +442,14 @@ export default class XinghuoClient { return { conversationId: chatId, text: response, - images: images + images } } else { throw new Error('星火模式错误') } } - async createChatList(bot = false) { + async createChatList (bot = false) { let createChatListRes = await fetch(createChatUrl, { method: 'POST', headers: Object.assign(this.headers, { @@ -487,6 +478,6 @@ export default class XinghuoClient { } } -function atob(s) { +function atob (s) { return Buffer.from(s, 'base64').toString() }