chatgpt-plugin/apps/management.js
HalcyonAlcedo 9b9b69634e
添加用户管理功能 (#345)
* fix: remove queue element only in non-bing mode

* 使用chatgpt-api自带的超时逻辑,文字过多时启动切换到图片输出防止被吞

* Update chat.js

* 添加Bing专用的图片输出样式

* 添加chatgpt的新图片模式,临时处理切换api导致的对话异常

* 修改bing样式表

* 为图片添加外部页面缓存

* 为图片模式添加MathJax

* feat: add switch for qrcode

* 防止script攻击

* 修复网页模板错误

* 修复bing页面引用错误

* 缓存服务器异常时处理

* 添加默认配置加载

* 修复配置文件路径错误

* 删除重复的模板文件,修复二维码地址错误

* 修正图片渲染错误

* 修复引用渲染错误

* 二维码网址统一改为使用本地配置

* 添加关闭思考提示的配置项

* 修复在Windows上无法载入配置文件的问题

* 修复关闭qr的情况下渲染错误

* 改为使用base64传递返回数据

* 当异常过多时使用图片输出

* 添加锅巴面板配置支持

* 补充遗漏的默认配置

* 修复qr模式下引用未被传递的问题

* 修复未将引用数据传输给缓存服务器的问题

* 删除无用的bingTimeoutMs配置项

* 添加消息队列超时弹出

* 优化图片模式处理,解决对话队列卡住的问题

* 添加对图片ocr的支持

* 添加图片识别配置项

* 添加黑名单配置项

* 修复一些bug

* 修改锅巴配置格式和描述

* 传入数据也使用markdown

* 图片识别换行改为marked兼容

* 添加绘图CD配置项

* 独立render模块,添加图片回复引用

* 添加必应风格

* 修复上下文,修改bing样式

* 修复上下文

* 添加Sydney上下文支持

* 调整不同模式下的bing渲染颜色

* 修复样式

* 修复无法结束会话的问题

* fix: 更新版本号

* 修复无法结束对话的问题

* 向缓存服务器传送样式

* 为网址格式的配置添加验证

* 去除重复的Keyv删除,取消锅巴配置格式检查

* 闭合中断的代码块

* 试添加Sydney图片模式的情感显示

* 修复at不兼容

* 处理意外的markdown包裹和结构解析修复

* 修复markdown处理的顺序错误

* 兼容json换行

* 重写completeJSON和使用

* 修复换行格式异常

* 均衡BingToken使用

* 修复删除token的数组处理错误

* 修改token文字描述

* 创建本地缓存服务

* 修复首次使用无法添加bingtoken的问题

* 修复意外的删除格式问题,添加查看token功能

* 修复路由错误,暂时固定ip测试

* 恢复引用功能

* 更新渲染页面

* 更换缓存目录

* 清除调试用消息

* 调整屏幕分辨率

* 使用服务器生成的访问地址

* 改为使用api获取公网ip

* 修复引用显示

* 添加依赖需求

* 更新渲染页面和渲染api

* 修复渲染页面错误

* 修复建议字符串切割,添加帮助路由

* 添加内容中图片数据获取功能

* 试修复suggestbug

* 修复图片导致服务器卡死的问题

* 暂时禁用图片

* 尝试恢复图片

* 添加链接图片识别

* 替换掉request

* 修复可能的responseUrls空值

* 优化格式

* 更新渲染页面

* 尝试新的引用索引

* 取消渲染时旧的策略

* 更新帮助页面

* 修复帮助路由

* 修复渲染页面错误

* 修复错误的正则

* 修改系统api服务

* 添加配置项

* 将新渲染方式加入配置并还原原渲染方式,进行并存

* 暂时取消端口设置功能

* 重新开启端口设置

* 修复旧渲染引用

* 更新帮助样式

* 更新帮助,增强功能

* 有cacheHost的情况下不再附带端口号

* 添加渲染图片的宽度设置

* 添加渲染页面宽度调整,修bug

* 修复二维码不显示

* 添加第三方渲染支持

* 修复一些渲染页面问题

* 更新渲染页面

* 修正错误的变量调用

* 添加新渲染模式bot命名

* 修复空消息问题

* 撤销之前的修复,使用新方法修复

* 修复返回空页面问题

* 尝试不依赖网络获取外网地址

* 修bug,初步创建管理系统

* 依赖名写错了

* 修复错误的异步

* 修正错误的配置调用

* 放弃本机设置的获取方案,对服务器获取多半失效

* 添加配置页面接口

* 更新渲染页面

* 添加依赖

* 修复bug

* 移除windows性能显示,更换依赖

* 添加依赖

* 修复图片异常时不反回文字而是直接报错的问题

* 修改必应token记录和均衡方法,更新渲染页面

* 修复错误

* 修复bug,更新渲染页面

* 更新渲染

* 修复ip错误

* 完善配置页面

* 渲染页面错误修复

* 更新版本号

* 只获取一次有效ip

* 修复渲染页面bug

* 删除旧依赖

* 优化用户系统

* 修复登录数据错误

* 修复用户密码创建失败问题

* 限制密码私聊发送

* 修复数据获取异常bug

* 修复用户数据获取错误

* 修复渲染页面错误

* 用户清除缓存支持

* 添加用户配置功能

* 修复错误

* 修复用户缓存读取错误

* 修改服务的错误提示方式

* 配置管理员路由引导

* 修复管理员和用户路由错误

* 修复错误的文件删除函数

* 更新渲染页面

* 暂时还原数据获取方法

* 修复未注册账号无法生成图片的问题

---------

Co-authored-by: ikechan8370 <geyinchibuaa@gmail.com>
Co-authored-by: Err0rCM <68117733+Err0rCM@users.noreply.github.com>
2023-04-13 22:47:20 +08:00

1005 lines
34 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
import { exec } from 'child_process'
import { checkPnpm, formatDuration, parseDuration, getPublicIP } from '../utils/common.js'
import SydneyAIClient from '../utils/SydneyAIClient.js'
import { convertSpeaker, speakers } from '../utils/tts.js'
import md5 from 'md5'
import path from 'path'
import fs from 'fs'
let isWhiteList = true
export class ChatgptManagement extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin管理',
dsc: 'ChatGPT-Plugin管理',
event: 'message',
priority: 500,
rule: [
{
reg: '#chatgpt开启(问题)?(回复)?确认',
fnc: 'turnOnConfirm',
permission: 'master'
},
{
reg: '#chatgpt关闭(问题)?(回复)?确认',
fnc: 'turnOffConfirm',
permission: 'master'
},
{
reg: '#chatgpt(设置|绑定)(token|Token)',
fnc: 'setAccessToken',
permission: 'master'
},
{
reg: '#chatgpt(设置|绑定|添加)(必应|Bing |bing )(token|Token)',
fnc: 'setBingAccessToken',
permission: 'master'
},
{
reg: '#chatgpt(删除|移除)(必应|Bing |bing )(token|Token)',
fnc: 'delBingAccessToken',
permission: 'master'
},
{
reg: '#chatgpt(查看|浏览)(必应|Bing |bing )(token|Token)',
fnc: 'getBingAccessToken',
permission: 'master'
},
{
reg: '#chatgpt(迁移|恢复)(必应|Bing |bing )(token|Token)',
fnc: 'migrateBingAccessToken',
permission: 'master'
},
{
reg: '^#chatgpt切换浏览器$',
fnc: 'useBrowserBasedSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换API$',
fnc: 'useOpenAIAPIBasedSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(ChatGLM|chatglm)$',
fnc: 'useChatGLMSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换API3$',
fnc: 'useReversedAPIBasedSolution2',
permission: 'master'
},
{
reg: '^#chatgpt切换(必应|Bing)$',
fnc: 'useBingSolution',
permission: 'master'
},
{
reg: '^#chatgpt(必应|Bing)切换',
fnc: 'changeBingTone',
permission: 'master'
},
{
reg: '^#chatgpt(必应|Bing)(开启|关闭)建议(回复)?',
fnc: 'bingOpenSuggestedResponses',
permission: 'master'
},
{
reg: '^#chatgpt模式(帮助)?$',
fnc: 'modeHelp'
},
{
reg: '^#chatgpt(强制)?更新$',
fnc: 'updateChatGPTPlugin'
},
{
reg: '^#chatgpt(本群)?(群\\d+)?闭嘴',
fnc: 'shutUp',
permission: 'master'
},
{
reg: '^#chatgpt(本群)?(群\\d+)?(张嘴|开口|说话|上班)',
fnc: 'openMouth',
permission: 'master'
},
{
reg: '^#chatgpt查看闭嘴',
fnc: 'listShutUp',
permission: 'master'
},
{
reg: '^#chatgpt设置(API|key)(Key|key)',
fnc: 'setAPIKey',
permission: 'master'
},
{
reg: '^#chatgpt设置(API|api)设定',
fnc: 'setAPIPromptPrefix',
permission: 'master'
},
{
reg: '^#chatgpt设置(Bing|必应|Sydney|悉尼|sydney|bing)设定',
fnc: 'setBingPromptPrefix',
permission: 'master'
},
{
reg: '^#chatgpt(开启|关闭)画图$',
fnc: 'switchDraw',
permission: 'master'
},
{
reg: '^#chatgpt查看(API|api)设定$',
fnc: 'queryAPIPromptPrefix',
permission: 'master'
},
{
reg: '^#chatgpt查看(Bing|必应|Sydney|悉尼|sydney|bing)设定$',
fnc: 'queryBingPromptPrefix',
permission: 'master'
},
{
reg: '^#chatgpt(打开|关闭|设置)?全局((图片模式|语音模式|(语音角色|角色语音|角色).*)|回复帮助)$',
fnc: 'setDefaultReplySetting',
permission: 'master'
},
{
/** 命令正则匹配 */
reg: '^#(关闭|打开)群聊上下文$',
/** 执行方法 */
fnc: 'enableGroupContext',
permission: 'master'
},
{
reg: '^#chatgpt(允许|禁止|打开|关闭|同意)私聊$',
fnc: 'enablePrivateChat',
permission: 'master'
},
{
reg: '^#chatgpt(设置|添加)群聊[白黑]名单$',
fnc: 'setList',
permission: 'master'
},
{
reg: '^#chatgpt查看群聊[白黑]名单$',
fnc: 'checkGroupList',
permission: 'master'
},
{
reg: '^#chatgpt(删除|移除)群聊[白黑]名单$',
fnc: 'delGroupList',
permission: 'master'
},
{
reg: '^#(设置|修改)管理密码',
fnc: 'setAdminPassword',
permission: 'master'
},
{
reg: '^#(设置|修改)用户密码',
fnc: 'setUserPassword',
},
{
reg: '^#chatgpt系统(设置|配置|管理)',
fnc: 'adminPage',
permission: 'master'
},
{
reg: '^#chatgpt用户(设置|配置|管理)',
fnc: 'userPage',
}
]
})
}
async setList (e) {
this.setContext('saveList')
isWhiteList = e.msg.includes('白')
const listType = isWhiteList ? '白名单' : '黑名单'
await this.reply(`请发送需要设置的群聊${listType},群号间使用,隔开`, e.isGroup)
return false
}
async saveList (e) {
if (!this.e.msg) return
const groupNums = this.e.msg.match(/\d+/g)
const groupList = Array.isArray(groupNums) ? this.e.msg.match(/\d+/g).filter(value => /^[1-9]\d{8,9}/.test(value)) : []
if (!groupList.length) {
await this.reply('没有可添加的群号,请检查群号是否正确', e.isGroup)
return false
}
let whitelist = []
let blacklist = []
for (const element of groupList) {
if (isWhiteList) {
Config.groupWhitelist = Config.groupWhitelist.filter(item => item !== element)
whitelist.push(element)
} else {
Config.groupBlacklist = Config.groupBlacklist.filter(item => item !== element)
blacklist.push(element)
}
}
if (!(whitelist.length || blacklist.length)) {
await this.reply('没有可添加的群号,请检查群号是否正确或重复添加', e.isGroup)
this.finish('saveList')
return false
} else {
if (isWhiteList) {
Config.groupWhitelist = Config.groupWhitelist
.filter(group => group.trim() !== '')
.concat(whitelist)
} else {
Config.groupBlacklist = Config.groupBlacklist
.filter(group => group.trim() !== '')
.concat(blacklist)
}
}
await this.reply(`群聊${isWhiteList ? '白' : '黑'}名单已更新,可通过\n'#chatgpt查看群聊${isWhiteList ? '白' : '黑'}名单'查看最新名单\n#chatgpt移除群聊${isWhiteList ? '白' : '黑'}名单'管理名单`, e.isGroup)
this.finish('saveList')
}
async checkGroupList (e) {
isWhiteList = e.msg.includes('白')
const list = isWhiteList ? Config.groupWhitelist : Config.groupBlacklist
const listType = isWhiteList ? '白名单' : '黑名单'
const replyMsg = list.length ? `当前群聊${listType}为:${list.join('')}` : `当前没有设置任何${listType}`
this.reply(replyMsg, e.isGroup)
return false
}
async delGroupList (e) {
isWhiteList = e.msg.includes('白')
const listType = isWhiteList ? '白名单' : '黑名单'
let replyMsg = ''
if (Config.groupWhitelist.length && Config.groupBlacklist.length) {
replyMsg = `当前群聊(白|黑)名单为空,请先添加${listType}吧~`
} else if ((isWhiteList && !Config.groupWhitelist.length) || (!isWhiteList && !Config.groupBlacklist.length)) {
replyMsg = `当前群聊${listType}为空,请先添加吧~`
}
if (replyMsg) {
await this.reply(replyMsg, e.isGroup)
return false
}
this.setContext('confirmDelGroup')
await this.reply(`请发送需要删除的群聊${listType},群号间使用,隔开。输入‘全部删除’清空${listType}`, e.isGroup)
return false
}
async confirmDelGroup (e) {
if (!this.e.msg) return
const isAllDeleted = this.e.msg.trim() === '全部删除'
const groupNumRegex = /^[1-9]\d{8,9}$/
const groupNums = this.e.msg.match(/\d+/g)
const validGroups = Array.isArray(groupNums) ? groupNums.filter(groupNum => groupNumRegex.test(groupNum)) : []
if (isAllDeleted) {
Config.groupWhitelist = isWhiteList ? [] : Config.groupWhitelist
Config.groupBlacklist = !isWhiteList ? [] : Config.groupBlacklist
} else {
if (!validGroups.length) {
await this.reply('没有可删除的群号,请检查输入的群号是否正确', e.isGroup)
return false
} else {
for (const element of validGroups) {
if (isWhiteList) {
Config.groupWhitelist = Config.groupWhitelist.filter(item => item !== element)
} else {
Config.groupBlacklist = Config.groupBlacklist.filter(item => item !== element)
}
}
}
}
const groupType = isWhiteList ? '白' : '黑'
await this.reply(`群聊${groupType}名单已更新,可通过'#chatgpt查看群聊${groupType}名单'命令查看最新名单`)
this.finish('confirmDelGroup')
}
async enablePrivateChat (e) {
Config.enablePrivateChat = !!e.msg.match(/(允许|打开|同意)/)
await this.reply('设置成功', e.isGroup)
return false
}
async enableGroupContext (e) {
const reg = /(关闭|打开)/
const match = e.msg.match(reg)
if (match) {
const action = match[1]
if (action === '关闭') {
Config.enableGroupContext = false // 关闭
await this.reply('已关闭群聊上下文功能', true)
} else {
Config.enableGroupContext = true // 打开
await this.reply('已打开群聊上下文功能', true)
}
}
return false
}
async setDefaultReplySetting (e) {
const reg = /^#chatgpt(打开|关闭|设置)?全局((图片模式|语音模式|(语音角色|角色语音|角色).*)|回复帮助)/
const matchCommand = e.msg.match(reg)
const settingType = matchCommand[2]
let replyMsg = ''
switch (settingType) {
case '图片模式':
if (matchCommand[1] === '打开') {
Config.defaultUsePicture = true
Config.defaultUseTTS = false
replyMsg = 'ChatGPT将默认以图片回复'
} else if (matchCommand[1] === '关闭') {
Config.defaultUsePicture = false
if (Config.defaultUseTTS) {
replyMsg = 'ChatGPT将默认以语音回复'
} else {
replyMsg = 'ChatGPT将默认以文本回复'
}
} else if (matchCommand[1] === '设置') {
replyMsg = '请使用“#chatgpt打开全局图片模式”或“#chatgpt关闭全局图片模式”命令来设置回复模式'
} break
case '语音模式':
if (!Config.ttsSpace) {
replyMsg = '您没有配置VITS API请前往锅巴面板进行配置'
break
}
if (matchCommand[1] === '打开') {
Config.defaultUseTTS = true
Config.defaultUsePicture = false
replyMsg = 'ChatGPT将默认以语音回复'
} else if (matchCommand[1] === '关闭') {
Config.defaultUseTTS = false
if (Config.defaultUsePicture) {
replyMsg = 'ChatGPT将默认以图片回复'
} else {
replyMsg = 'ChatGPT将默认以文本回复'
}
} else if (matchCommand[1] === '设置') {
replyMsg = '请使用“#chatgpt打开全局语音模式”或“#chatgpt关闭全局语音模式”命令来设置回复模式'
} break
case '回复帮助':
replyMsg = '可使用以下命令配置全局回复:\n#chatgpt(打开/关闭)全局(语音/图片)模式\n#chatgpt设置全局(语音角色|角色语音|角色)+角色名称(留空则为随机)'
break
default:
if (!Config.ttsSpace) {
replyMsg = '您没有配置VITS API请前往锅巴面板进行配置'
break
}
if (settingType.match(/(语音角色|角色语音|角色)/)) {
const speaker = matchCommand[2].replace(/(语音角色|角色语音|角色)/, '').trim() || ''
if (!speaker.length) {
replyMsg = 'ChatGpt将随机挑选角色回复'
Config.defaultTTSRole = ''
} else {
const ttsRole = convertSpeaker(speaker)
if (speakers.includes(ttsRole)) {
Config.defaultTTSRole = ttsRole
replyMsg = `ChatGPT默认语音角色已被设置为“${ttsRole}`
} else {
replyMsg = `抱歉,我还不认识“${ttsRole}”这个语音角色`
}
}
} else {
replyMsg = "无法识别的设置类型\n请使用'#chatgpt全局回复帮助'查看正确命令"
}
}
await this.reply(replyMsg, true)
}
async turnOnConfirm (e) {
await redis.set('CHATGPT:CONFIRM', 'on')
await this.reply('已开启消息确认', true)
return false
}
async turnOffConfirm (e) {
await redis.set('CHATGPT:CONFIRM', 'off')
await this.reply('已关闭消息确认', true)
return false
}
async setAccessToken (e) {
this.setContext('saveToken')
await this.reply('请发送ChatGPT AccessToken', true)
return false
}
async setBingAccessToken (e) {
this.setContext('saveBingToken')
await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true)
return false
}
async migrateBingAccessToken () {
let token = await redis.get('CHATGPT:BING_TOKEN')
if (token) {
token = token.split('|')
token = token.map((item, index) => (
{
Token: item,
State: '正常',
Usage: 0,
}
))
} else {
token = []
}
let tokens = await redis.get('CHATGPT:BING_TOKENS')
if (tokens) {
tokens = JSON.parse(tokens)
} else {
tokens = []
}
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify([...token, ...tokens]))
await this.reply(`迁移完成`, true)
}
async getBingAccessToken (e) {
let tokens = await redis.get('CHATGPT:BING_TOKENS')
if (tokens) tokens = JSON.parse(tokens)
else tokens = []
tokens = tokens.length > 0 ? tokens.map((item, index) => (
`${index}】 Token${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}`
)).join('\n') : '无必应Token记录'
await this.reply(`${tokens}`, true)
return false
}
async delBingAccessToken (e) {
this.setContext('deleteBingToken')
let tokens = await redis.get('CHATGPT:BING_TOKENS')
if (tokens) tokens = JSON.parse(tokens)
else tokens = []
tokens = tokens.length > 0 ? tokens.map((item, index) => (
`${index}】 Token${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}`
)).join('\n') : '无必应Token记录'
await this.reply(`请发送要删除的token编号\n${tokens}`, true)
if (tokens.length == 0) this.finish('saveBingToken')
return false
}
async saveBingToken () {
if (!this.e.msg) return
let token = this.e.msg
if (token.length < 100) {
await this.reply('Bing Token格式错误请确定获取了有效的_U Cookie或完整的Cookie', true)
this.finish('saveBingToken')
return
}
let cookie
if (token?.indexOf('=') > -1) {
cookie = token
}
const bingAIClient = new SydneyAIClient({
userToken: token, // "_U" cookie from bing.com
cookie,
debug: Config.debug
})
// 异步就好了不卡着这个context了
bingAIClient.createNewConversation().then(async res => {
if (res.clientId) {
logger.info('bing token 有效')
} else {
logger.error('bing token 无效', res)
// 移除无效token
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
let bingToken = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
const element = bingToken.findIndex(element => element.token === token)
if (element >= 0) {
bingToken[element].State = '异常'
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingToken))
}
}
await this.reply(`经检测Bing Token无效。来自Bing的错误提示${res.result?.message}`)
}
})
let bingToken = []
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
bingToken = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
if (!bingToken.some(element => element.token === token)) bingToken.push({
Token: token,
State: '正常',
Usage: 0,
})
} else {
bingToken = [{
Token: token,
State: '正常',
Usage: 0,
}]
}
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingToken))
await this.reply('Bing Token设置成功', true)
this.finish('saveBingToken')
}
async deleteBingToken () {
if (!this.e.msg) return
let tokenId = this.e.msg
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
let bingToken = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
if (tokenId >= 0 && tokenId < bingToken.length) {
const removeToken = bingToken[tokenId].Token
bingToken.splice(tokenId,1)
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingToken))
await this.reply(`Token ${removeToken.substring(0, 5 / 2) + '...' + removeToken.substring(removeToken.length - 5 / 2, removeToken.length)} 移除成功`, true)
this.finish('deleteBingToken')
} else {
await this.reply('Token编号错误', true)
this.finish('deleteBingToken')
return
}
} else {
await this.reply('Token记录异常', true)
this.finish('deleteBingToken')
}
}
async saveToken () {
if (!this.e.msg) return
let token = this.e.msg
if (!token.startsWith('ey') || token.length < 20) {
await this.reply('ChatGPT AccessToken格式错误', true)
this.finish('saveToken')
return
}
await redis.set('CHATGPT:TOKEN', token)
await this.reply('ChatGPT AccessToken设置成功', true)
this.finish('saveToken')
}
async useBrowserBasedSolution (e) {
await redis.set('CHATGPT:USE', 'browser')
await this.reply('已切换到基于浏览器的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误')
}
async useOpenAIAPIBasedSolution (e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'api') {
await redis.set('CHATGPT:USE', 'api')
await this.reply('已切换到基于OpenAI API的解决方案如果已经对话过建议执行`#结束对话`避免引起404错误')
} else {
await this.reply('当前已经是API模式了')
}
}
async useChatGLMSolution (e) {
await redis.set('CHATGPT:USE', 'chatglm')
await this.reply('已切换到ChatGLM-6B解决方案如果已经对话过建议执行`#结束对话`避免引起404错误')
}
async useReversedAPIBasedSolution2 (e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'api3') {
await redis.set('CHATGPT:USE', 'api3')
await this.reply('已切换到基于第三方Reversed Conversastion API(API3)的解决方案')
} else {
await this.reply('当前已经是API3模式了')
}
}
async useBingSolution (e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'bing') {
await redis.set('CHATGPT:USE', 'bing')
// 结束所有人的对话
const keys = await redis.keys('CHATGPT:CONVERSATIONS:*')
if (keys.length) {
const response = await redis.del(keys)
if (Config.debug) {
console.log('Deleted keys:', response)
}
} else {
console.log('No keys matched the pattern')
}
await this.reply('已切换到基于微软新必应的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
} else {
await this.reply('当前已经是必应Bing模式了')
}
}
async changeBingTone (e) {
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
if (!tongStyle) {
return
}
let map = {
精准: 'precise',
创意: 'creative',
均衡: 'balanced',
Sydney: 'Sydney',
sydney: 'Sydney',
悉尼: 'Sydney',
自设定: 'Custom',
自定义: 'Custom'
}
if (map[tongStyle]) {
Config.toneStyle = map[tongStyle]
await e.reply('切换成功')
} else {
await e.reply('没有这种风格。支持的风格:精准、创意、均衡、悉尼、自设定')
}
}
async bingOpenSuggestedResponses (e) {
Config.enableSuggestedResponses = e.msg.indexOf('开启') > -1
await e.reply('操作成功')
}
async checkAuth (e) {
if (!e.isMaster) {
e.reply(`只有主人才能命令ChatGPT哦~
(*/ω\*)`)
return false
}
return true
}
// modified from miao-plugin
async updateChatGPTPlugin (e) {
let timer
if (!await this.checkAuth(e)) {
return true
}
let isForce = e.msg.includes('强制')
let command = 'git pull'
if (isForce) {
command = 'git checkout . && git pull'
e.reply('正在执行强制更新操作,请稍等')
} else {
e.reply('正在执行更新操作,请稍等')
}
const _path = process.cwd()
exec(command, { cwd: `${_path}/plugins/chatgpt-plugin/` }, async function (error, stdout, stderr) {
if (/(Already up[ -]to[ -]date|已经是最新的)/.test(stdout)) {
e.reply('目前已经是最新版ChatGPT了~')
return true
}
if (error) {
e.reply('ChatGPT更新失败\nError code: ' + error.code + '\n' + error.stack + '\n 请稍后重试。')
return true
}
e.reply('ChatGPT更新成功正在尝试重新启动Yunzai以应用更新...')
e.reply('更新日志:\n' + stdout)
timer && clearTimeout(timer)
let data = JSON.stringify({
isGroup: !!e.isGroup,
id: e.isGroup ? e.group_id : e.user_id,
time: new Date().getTime()
})
await redis.set('Yz:restart', data, { EX: 120 })
let npm = await checkPnpm()
timer = setTimeout(function () {
let command = `${npm} start`
if (process.argv[1].includes('pm2')) {
command = `${npm} run restart`
}
exec(command, function (error, stdout, stderr) {
if (error) {
e.reply('自动重启失败请手动重启以应用新版ChatGPT。\nError code: ' + error.code + '\n' + error.stack + '\n')
Bot.logger.error(`重启失败\n${error.stack}`)
return true
} else if (stdout) {
Bot.logger.mark('重启成功运行已转为后台查看日志请用命令npm run log')
Bot.logger.mark('停止后台运行命令npm stop')
process.exit()
}
})
}, 1000)
})
return true
}
async modeHelp () {
let mode = await redis.get('CHATGPT:USE')
const modeMap = {
browser: '浏览器',
// apiReverse: 'API2',
api: 'API',
bing: '必应',
api3: 'API3',
chatglm: 'ChatGLM-6B'
}
let modeText = modeMap[mode || 'api']
let message = ` API模式和浏览器模式如何选择
// eslint-disable-next-line no-irregular-whitespace
API模式会调用OpenAI官方提供的gpt-3.5-turbo API只需要提供API Key。一般情况下该种方式响应速度更快不会像chatGPT官网一样总出现不可用的现象但注意gpt-3.5-turbo的API调用是收费的新用户有18美元试用金可用于支付价格为$0.0020/1K tokens.(问题和回答加起来算token)
API3模式会调用官网反代API他会帮你绕过CF防护需要提供ChatGPT的Token。效果与官网和浏览器一致。设置token指令#chatgpt设置token。
浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站使得获得和官方以及API2模式一模一样的回复质量同时保证安全性。缺点是本方法对环境要求较高需要提供桌面环境和一个可用的代理能够访问ChatGPT的IP地址且响应速度不如API而且高峰期容易无法使用。
必应Bing将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing 登录Cookie方可使用。#chatgpt设置必应token
自建ChatGLM模式会调用自建的ChatGLM-6B服务器API进行对话需要自建。参考https://github.com/ikechan8370/SimpleChatGLM6BAPI
您可以使用‘#chatgpt切换浏览器/API/API3/Bing/ChatGLM来切换到指定模式。
当前为${modeText}模式。
`
await this.reply(message)
}
async shutUp (e) {
let duration = e.msg.replace(/^#chatgpt(本群)?(群\d+)?闭嘴/, '')
let scope
let time = 3600000
if (duration === '永久') {
time = 0
} else if (duration) {
time = parseDuration(duration)
}
const match = e.msg.match(/#chatgpt群(\d+)闭嘴(.*)/)
if (e.msg.indexOf('本群') > -1) {
if (e.isGroup) {
scope = e.group.group_id
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
await e.reply(`好的,从现在开始我会在当前群聊闭嘴${formatDuration(time)}`)
} else {
await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time })
await e.reply(`好的,从现在开始我会在当前群聊闭嘴${formatDuration(time)}`)
}
} else {
await e.reply('本群是指?你也没在群聊里让我闭嘴啊?')
return false
}
} else if (match) {
const groupId = parseInt(match[1], 10)
if (Bot.getGroupList().get(groupId)) {
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
await e.reply(`好的,从现在开始我会在群聊${groupId}闭嘴${formatDuration(time)}`)
} else {
await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time })
await e.reply(`好的,从现在开始我会在群聊${groupId}闭嘴${formatDuration(time)}`)
}
} else {
await e.reply('这是什么群?')
return false
}
} else {
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
await redis.del('CHATGPT:SHUT_UP:ALL')
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
await e.reply(`好的,我会再闭嘴${formatDuration(time)}`)
} else {
await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time })
await e.reply(`好的,我会闭嘴${formatDuration(time)}`)
}
}
}
async openMouth (e) {
const match = e.msg.match(/^#chatgpt群(\d+)/)
if (e.msg.indexOf('本群') > -1) {
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
await e.reply('主人,我现在全局闭嘴呢,你让我在这个群张嘴咱也不敢张啊')
return false
}
if (e.isGroup) {
let scope = e.group.group_id
if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) {
await redis.del(`CHATGPT:SHUT_UP:${scope}`)
await e.reply('好的主人,我终于又可以在本群说话了')
} else {
await e.reply('啊?我也没闭嘴啊?')
}
} else {
await e.reply('本群是指?你也没在群聊里让我张嘴啊?')
return false
}
} else if (match) {
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
await e.reply('主人,我现在全局闭嘴呢,你让我在那个群张嘴咱也不敢张啊')
return false
}
const groupId = parseInt(match[1], 10)
if (Bot.getGroupList().get(groupId)) {
if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) {
await redis.del(`CHATGPT:SHUT_UP:${groupId}`)
await e.reply(`好的主人,我终于又可以在群${groupId}说话了`)
} else {
await e.reply(`啊?我也没在群${groupId}闭嘴啊?`)
}
} else {
await e.reply('这是什么群?')
return false
}
} else {
let keys = await redis.keys('CHATGPT:SHUT_UP:*')
if (await redis.get('CHATGPT:SHUT_UP:ALL')) {
await redis.del('CHATGPT:SHUT_UP:ALL')
for (let i = 0; i < keys.length; i++) {
await redis.del(keys[i])
}
await e.reply('好的,我会结束所有闭嘴')
} else if (keys || keys.length > 0) {
for (let i = 0; i < keys.length; i++) {
await redis.del(keys[i])
}
await e.reply('好的,我会结束所有闭嘴?')
} else {
await e.reply('我没有在任何地方闭嘴啊?')
}
}
}
async listShutUp () {
let keys = await redis.keys('CHATGPT:SHUT_UP:*')
if (!keys || keys.length === 0) {
await this.reply('我没有在任何群闭嘴', true)
} else {
let list = []
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
let groupId = key.replace('CHATGPT:SHUT_UP:', '')
let ttl = await redis.ttl(key)
let ttlFormat = formatDuration(ttl)
list.push({ groupId, ttlFormat })
}
await this.reply(list.map(item => item.groupId !== 'ALL' ? `群聊${item.groupId}: ${item.ttlFormat}` : `全局: ${item.ttlFormat}`).join('\n'))
}
}
async setAPIKey (e) {
this.setContext('saveAPIKey')
await this.reply('请发送OpenAI API Key.', true)
return false
}
async saveAPIKey () {
if (!this.e.msg) return
let token = this.e.msg
if (!token.startsWith('sk-')) {
await this.reply('OpenAI API Key格式错误', true)
this.finish('saveAPIKey')
return
}
// todo
Config.apiKey = token
await this.reply('OpenAI API Key设置成功', true)
this.finish('saveAPIKey')
}
async setAPIPromptPrefix (e) {
this.setContext('saveAPIPromptPrefix')
await this.reply('请发送用于API模式的设定', true)
return false
}
async saveAPIPromptPrefix (e) {
if (!this.e.msg) return
if (this.e.msg === '取消') {
await this.reply('已取消设置API设定', true)
this.finish('saveAPIPromptPrefix')
return
}
// todo
Config.promptPrefixOverride = this.e.msg
await this.reply('API模式的设定设置成功', true)
this.finish('saveAPIPromptPrefix')
}
async setBingPromptPrefix (e) {
this.setContext('saveBingPromptPrefix')
await this.reply('请发送用于Bing Sydney模式的设定', true)
return false
}
async saveBingPromptPrefix (e) {
if (!this.e.msg) return
if (this.e.msg === '取消') {
await this.reply('已取消设置Sydney设定', true)
this.finish('saveBingPromptPrefix')
return
}
Config.sydney = this.e.msg
await this.reply('Bing Sydney模式的设定设置成功', true)
this.finish('saveBingPromptPrefix')
}
async switchDraw (e) {
if (e.msg.indexOf('开启') > -1) {
if (Config.enableDraw) {
await this.reply('当前已经开启chatgpt画图功能', true)
} else {
Config.enableDraw = true
await this.reply('chatgpt画图功能开启成功', true)
}
} else {
if (!Config.enableDraw) {
await this.reply('当前未开启chatgpt画图功能', true)
} else {
Config.enableDraw = false
await this.reply('chatgpt画图功能关闭成功', true)
}
}
}
async queryAPIPromptPrefix (e) {
await this.reply(Config.promptPrefixOverride, true)
}
async queryBingPromptPrefix (e) {
await this.reply(Config.sydney, true)
}
async setAdminPassword (e) {
if (e.isGroup) {
await this.reply('请私聊发生命令', true)
return true
}
this.setContext('saveAdminPassword')
await this.reply('请发送系统管理密码', true)
return false
}
async setUserPassword (e) {
if (e.isGroup) {
await this.reply('请私聊发生命令', true)
return true
}
this.setContext('saveUserPassword')
await this.reply('请发送系统用户密码', true)
return false
}
async saveAdminPassword (e) {
if (!this.e.msg) return
const passwd = this.e.msg
await redis.set('CHATGPT:ADMIN_PASSWD', md5(passwd))
await this.reply('设置成功', true)
this.finish('saveAdminPassword')
}
async saveUserPassword (e) {
if (!this.e.msg) return
const passwd = this.e.msg
const dir = 'resources/ChatGPTCache/user'
const filename = `${this.e.user_id}.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 = md5(passwd)
fs.writeFile(filepath, JSON.stringify(config), 'utf8', (err) => {
if (err) {
console.error(err)
return
}
})
})
} else {
fs.writeFile(filepath, JSON.stringify({
user: this.e.user_id,
passwd: md5(passwd),
chat: []
}), 'utf8', (err) => {
if (err) {
console.error(err)
return
}
})
}
await this.reply('设置完成', true)
this.finish('saveUserPassword')
}
async adminPage (e) {
const viewHost = Config.serverHost ? `http://${Config.serverHost}/` : `http://${await getPublicIP()}:${Config.serverPort || 3321}/`
await this.reply(`请登录${viewHost + 'admin/settings'}进行系统配置`, true)
}
async userPage (e) {
const viewHost = Config.serverHost ? `http://${Config.serverHost}/` : `http://${await getPublicIP()}:${Config.serverPort || 3321}/`
await this.reply(`请登录${viewHost + 'admin/dashboard'}进行系统配置`, true)
}
}