diff --git a/README.md b/README.md
index 6d0e971..ec523ae 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-
-

diff --git a/apps/chat.js b/apps/chat.js
index 1d8a2dc..36976b4 100644
--- a/apps/chat.js
+++ b/apps/chat.js
@@ -797,7 +797,7 @@ export class chatgpt extends plugin {
* #chatgpt
*/
async chatgpt (e) {
- let msg = Version.isTrss ? e.msg : e.raw_message
+ let msg = (Version.isTrss || e.adapter === 'shamrock') ? e.msg : e.raw_message
let prompt
if (this.toggleMode === 'at') {
if (!msg || e.msg?.startsWith('#')) {
@@ -1655,9 +1655,13 @@ export class chatgpt extends plugin {
let toSummaryFileContent
try {
if (e.source) {
- let msgs = e.isGroup ? await e.group.getChatHistory(e.source.seq, 1) : await e.friend.getChatHistory(e.source.time, 1)
- let sourceMsg = msgs[0]
- let fileMsgElem = sourceMsg.message.find(msg => msg.type === 'file')
+ let seq = e.isGroup ? e.source.seq : e.source.time
+ if (e.adapter === 'shamrock') {
+ seq = e.source.message_id
+ }
+ let msgs = e.isGroup ? await e.group.getChatHistory(seq, 1) : await e.friend.getChatHistory(seq, 1)
+ let sourceMsg = msgs[msgs.length - 1]
+ let fileMsgElem = sourceMsg.file || sourceMsg.message.find(msg => msg.type === 'file')
if (fileMsgElem) {
toSummaryFileContent = await extractContentFromFile(fileMsgElem, e)
}
@@ -2114,6 +2118,7 @@ export class chatgpt extends plugin {
}
}
default: {
+ // openai api
let completionParams = {}
if (Config.model) {
completionParams.model = Config.model
@@ -2304,28 +2309,8 @@ export class chatgpt extends plugin {
}
}
}
- let img = []
- if (e.source) {
- // 优先从回复找图
- let reply
- if (e.isGroup) {
- reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
- } else {
- reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
- }
- if (reply) {
- for (let val of reply) {
- if (val.type === 'image') {
- console.log(val)
- img.push(val.url)
- }
- }
- }
- }
- if (e.img) {
- img.push(...e.img)
- }
- if (img.length > 0 && Config.extraUrl) {
+ let img = await getImg(e)
+ if (img?.length > 0 && Config.extraUrl) {
tools.push(new ImageCaptionTool())
tools.push(new ProcessPictureTool())
prompt += `\nthe url of the picture(s) above: ${img.join(', ')}`
diff --git a/apps/draw.js b/apps/draw.js
index 10eed61..f45722b 100644
--- a/apps/draw.js
+++ b/apps/draw.js
@@ -277,7 +277,7 @@ export class dalle extends plugin {
await client.getImages(prompt, e)
} catch (err) {
await redis.del(`CHATGPT:DRAW:${e.sender.user_id}`)
- await e.reply('绘图失败:' + err)
+ await e.reply('❌绘图失败:' + err)
}
}
}
diff --git a/apps/help.js b/apps/help.js
index 8de904c..1c2a7c6 100644
--- a/apps/help.js
+++ b/apps/help.js
@@ -209,7 +209,12 @@ let helpData = [
{
icon: 'token',
title: '#chatgpt设置后台刷新token',
- desc: '用于查看API余额。注意和配置的key保持同一账号。'
+ desc: '用于获取刷新令牌,以便获取sessKey。'
+ },
+ {
+ icon: 'key',
+ title: '#chatgpt设置sessKey',
+ desc: '使用sessKey作为APIKey,适用于未手机号验证的用户'
},
{
icon: 'token',
diff --git a/apps/management.js b/apps/management.js
index 2a8d364..689dd28 100644
--- a/apps/management.js
+++ b/apps/management.js
@@ -20,6 +20,23 @@ import fs from 'fs'
import loader from '../../../lib/plugins/loader.js'
import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
+import fetch from 'node-fetch'
+import { getProxy } from '../utils/proxy.js'
+
+let proxy = getProxy()
+const newFetch = (url, options = {}) => {
+ const defaultOptions = Config.proxy
+ ? {
+ agent: proxy(Config.proxy)
+ }
+ : {}
+ const mergedOptions = {
+ ...defaultOptions,
+ ...options
+ }
+
+ return fetch(url, mergedOptions)
+}
export class ChatgptManagement extends plugin {
constructor (e) {
@@ -252,7 +269,13 @@ export class ChatgptManagement extends plugin {
},
{
reg: '^#chatgpt设置后台(刷新|refresh)(t|T)oken$',
- fnc: 'setOpenAIPlatformToken'
+ fnc: 'setOpenAIPlatformToken',
+ permission: 'master'
+ },
+ {
+ reg: '^#chatgpt设置sessKey$',
+ fnc: 'getSessKey',
+ permission: 'master'
},
{
reg: '^#(chatgpt)?查看回复设置$',
@@ -1114,8 +1137,8 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
async saveAPIKey () {
if (!this.e.msg) return
let token = this.e.msg
- if (!token.startsWith('sk-')) {
- await this.reply('OpenAI API Key格式错误', true)
+ if (!token.startsWith('sk-') && !token.startsWith('sess-')) {
+ await this.reply('OpenAI API Key格式错误。如果是格式特殊的非官方Key请前往锅巴或工具箱手动设置', true)
this.finish('saveAPIKey')
return
}
@@ -1302,7 +1325,64 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
async setOpenAIPlatformToken (e) {
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如果仍不能查看余额,请退出登录重新获取刷新令牌')
+ 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使用')
+ }
+
+ async getSessKey (e) {
+ if (!Config.OpenAiPlatformRefreshToken) {
+ this.reply('当前未配置platform.openai.com的刷新token,请发送【#chatgpt设置后台刷新token】进行配置。')
+ return false
+ }
+ let authHost = 'https://auth0.openai.com'
+ if (Config.openAiBaseUrl && !Config.openAiBaseUrl.startsWith('https://api.openai.com')) {
+ authHost = Config.openAiBaseUrl.replace('/v1', '').replace('/v1/', '')
+ }
+ let refreshRes = await newFetch(`${authHost}/oauth/token`, {
+ method: 'POST',
+ body: JSON.stringify({
+ refresh_token: Config.OpenAiPlatformRefreshToken,
+ client_id: 'DRivsnm2Mu42T3KOpqdtwB3NYviHYzwD',
+ grant_type: 'refresh_token'
+ }),
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
+ 'Content-Type': 'application/json'
+ }
+ })
+ if (refreshRes.status !== 200) {
+ let errMsg = await refreshRes.json()
+ logger.error(JSON.stringify(errMsg))
+ if (errMsg.error === 'access_denied') {
+ await e.reply('刷新令牌失效,请重新发送【#chatgpt设置后台刷新token】进行配置。建议退出platform.openai.com重新登录后再获取和配置')
+ } else {
+ await e.reply('获取失败')
+ }
+ return false
+ }
+ let newToken = await refreshRes.json()
+ // eslint-disable-next-line camelcase
+ const { access_token, refresh_token } = newToken
+ // eslint-disable-next-line camelcase
+ Config.OpenAiPlatformRefreshToken = refresh_token
+ let host = Config.openAiBaseUrl.replace('/v1', '').replace('/v1/', '')
+ let res = await newFetch(`${host}/dashboard/onboarding/login`, {
+ headers: {
+ // eslint-disable-next-line camelcase
+ Authorization: `Bearer ${access_token}`,
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
+ },
+ method: 'POST'
+ })
+ if (res.status === 200) {
+ let authRes = await res.json()
+ let sess = authRes.user.session.sensitive_id
+ if (sess) {
+ Config.apiKey = sess
+ await e.reply('已成功将sessKey设置为apiKey,您可以发送#openai余额来查看该账号余额')
+ } else {
+ await e.reply('设置失败!')
+ }
+ }
}
async doSetOpenAIPlatformToken () {
diff --git a/server/modules/user.js b/server/modules/user.js
index 38b9830..0bb7157 100644
--- a/server/modules/user.js
+++ b/server/modules/user.js
@@ -57,6 +57,7 @@ async function User (fastify, options) {
// 快速登录
fastify.post('/quick', async (request, reply) => {
const otp = randomString(6)
+ const isTrss = Array.isArray(Bot.uin)
await redis.set(
'CHATGPT:SERVER_QUICK',
otp,
@@ -65,7 +66,15 @@ async function User (fastify, options) {
const master = (await getMasterQQ())[0]
let bots = getBots()
for (let bot of bots) {
- bot.pickUser(master).sendMsg(`收到工具箱快捷登录请求,1分钟内有效:${otp}`)
+ if(isTrss) {
+ try {
+ bot.pickFriend(master).sendMsg(`收到工具箱快捷登录请求,1分钟内有效:${otp}`)
+ } catch (error) {
+ logger.error(error)
+ }
+ } else {
+ bot.pickUser(master).sendMsg(`收到工具箱快捷登录请求,1分钟内有效:${otp}`)
+ }
}
reply.send({ state: true })
return reply
diff --git a/utils/BingDraw.js b/utils/BingDraw.js
index 80e69b1..53ac92c 100644
--- a/utils/BingDraw.js
+++ b/utils/BingDraw.js
@@ -95,7 +95,7 @@ export default class BingDrawClient {
let pollingUrl = `${this.opts.baseUrl}/images/create/async/results/${requestId}?q=${urlEncodedPrompt}`
logger.info({ pollingUrl })
logger.info('waiting for bing draw results...')
- let timeoutTimes = 30
+ let timeoutTimes = 50
let found = false
let timer = setInterval(async () => {
if (found) {
@@ -113,15 +113,20 @@ export default class BingDrawClient {
// 很可能是微软内部error,重试即可
return
}
- imageLinks = imageLinks.map(link => link.split('?w=')[0]).map(link => link.replace('src="', ''))
+ imageLinks = imageLinks
+ .map(link => link.split('?w=')[0])
+ .map(link => link.replace('src="', ''))
+ .filter(link => !link.includes('.svg'))
imageLinks = [...new Set(imageLinks)]
const badImages = [
+ 'https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png"',
+ 'https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg"',
'https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png',
'https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg'
]
for (let imageLink of imageLinks) {
if (badImages.indexOf(imageLink) > -1) {
- await e.reply('绘图失败:Bad images', true)
+ await e.reply('❌绘图失败:Bad images', true)
logger.error(rText)
}
}
@@ -132,7 +137,7 @@ export default class BingDrawClient {
clearInterval(timer)
} else {
if (timeoutTimes === 0) {
- await e.reply('绘图超时', true)
+ await e.reply('❌绘图超时', true)
clearInterval(timer)
timer = null
} else {
@@ -140,6 +145,6 @@ export default class BingDrawClient {
timeoutTimes--
}
}
- }, 2000)
+ }, 3000)
}
}
diff --git a/utils/chat.js b/utils/chat.js
index e249d0f..097cee4 100644
--- a/utils/chat.js
+++ b/utils/chat.js
@@ -1,3 +1,4 @@
+
export async function getChatHistoryGroup (e, num) {
// if (e.adapter === 'shamrock') {
// return await e.group.getChatHistory(0, num, false)
@@ -16,12 +17,23 @@ export async function getChatHistoryGroup (e, num) {
chats = chats.slice(0, num)
try {
let mm = await e.group.getMemberMap()
- chats.forEach(chat => {
- let sender = mm.get(chat.sender.user_id)
- if (sender) {
- chat.sender = sender
+ for (const chat of chats) {
+ if (e.adapter === 'shamrock') {
+ if (chat.sender?.user_id === 0) {
+ // 奇怪格式的历史消息,过滤掉
+ continue
+ }
+ let sender = await pickMemberAsync(e, chat.sender.user_id)
+ if (sender) {
+ chat.sender = sender
+ }
+ } else {
+ let sender = mm.get(chat.sender.user_id)
+ if (sender) {
+ chat.sender = sender
+ }
}
- })
+ }
} catch (err) {
logger.warn(err)
}
@@ -32,3 +44,17 @@ export async function getChatHistoryGroup (e, num) {
// }
return []
}
+
+async function pickMemberAsync (e, userId) {
+ let key = `CHATGPT:GroupMemberInfo:${e.group_id}:${userId}`
+ let cache = await redis.get(key)
+ if (cache) {
+ return JSON.parse(cache)
+ }
+ return new Promise((resolve, reject) => {
+ e.group.pickMember(userId, true, (sender) => {
+ redis.set(key, JSON.stringify(sender), { EX: 86400 })
+ resolve(sender)
+ })
+ })
+}
diff --git a/utils/common.js b/utils/common.js
index f9b2de9..ab27aa5 100644
--- a/utils/common.js
+++ b/utils/common.js
@@ -13,7 +13,8 @@ import AzureTTS, { supportConfigurations as azureRoleList } from './tts/microsof
import { translate } from './translate.js'
import uploadRecord from './uploadRecord.js'
import Version from './version.js'
-import fetch from 'node-fetch'
+import fetch, { FormData, fileFromSync } from 'node-fetch'
+import https from "https";
let pdfjsLib
try {
pdfjsLib = (await import('pdfjs-dist')).default
@@ -785,10 +786,14 @@ export async function getImg (e) {
}
if (e.source) {
let reply
+ let seq = e.isGroup ? e.source.seq : e.source.time
+ if (e.adapter === 'shamrock') {
+ seq = e.source.message_id
+ }
if (e.isGroup) {
- reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
+ reply = (await e.group.getChatHistory(seq, 1)).pop()?.message
} else {
- reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
+ reply = (await e.friend.getChatHistory(seq, 1)).pop()?.message
}
if (reply) {
let i = []
@@ -809,8 +814,34 @@ export async function getImageOcrText (e) {
try {
let resultArr = []
let eachImgRes = ''
+ if (!e.bot.imageOcr || typeof e.bot.imageOcr !== 'function') {
+ e.bot.imageOcr = async (image) => {
+ if (Config.extraUrl) {
+ let md5 = image.split(/[/-]/).find(s => s.length === 32)?.toUpperCase()
+ let filePath = await downloadFile(image, `ocr/${md5}.png`)
+ let formData = new FormData()
+ formData.append('file', fileFromSync(filePath))
+ let res = await fetch(`${Config.extraUrl}/ocr?lang=chi_sim%2Beng`, {
+ body: formData,
+ method: 'POST',
+ headers: {
+ from: 'ikechan8370'
+ }
+ })
+ if (res.status === 200) {
+ return {
+ wordslist: [{ words: await res.text() }]
+ }
+ }
+ }
+ return {
+ wordslist: []
+ }
+ }
+ }
for (let i in img) {
const imgOCR = await e.bot.imageOcr(img[i])
+
for (let text of imgOCR.wordslist) {
eachImgRes += (`${text?.words} \n`)
}
@@ -820,6 +851,7 @@ export async function getImageOcrText (e) {
// logger.warn('resultArr', resultArr)
return resultArr
} catch (err) {
+ logger.warn(err)
logger.warn('OCR失败,可能使用的适配器不支持OCR')
return false
// logger.error(err)
@@ -1003,10 +1035,15 @@ export function getUserSpeaker (userSetting) {
* @param url 要下载的文件链接
* @param destPath 目标路径,如received/abc.pdf. 目前如果文件名重复会覆盖。
* @param absolute 是否是绝对路径,默认为false,此时拼接在data/chatgpt下
+ * @param ignoreCertificateError 忽略证书错误
* @returns {Promise
} 最终下载文件的存储位置
*/
-export async function downloadFile (url, destPath, absolute = false) {
- let response = await fetch(url)
+export async function downloadFile (url, destPath, absolute = false, ignoreCertificateError = true) {
+ let response = await fetch(url, {
+ agent: new https.Agent({
+ rejectUnauthorized: !ignoreCertificateError
+ })
+ })
if (!response.ok) {
throw new Error(`download file http error: status: ${response.status}`)
}
@@ -1061,7 +1098,7 @@ export async function extractContentFromFile (fileMsgElem, e) {
let fileType = isPureText(fileMsgElem.name)
if (fileType) {
// 可读的文件类型
- let fileUrl = e.isGroup ? await e.group.getFileUrl(fileMsgElem.fid) : await e.friend.getFileUrl(fileMsgElem.fid)
+ let fileUrl = fileMsgElem.url || (e.isGroup ? await e.group.getFileUrl(fileMsgElem.fid) : await e.friend.getFileUrl(fileMsgElem.fid))
let filePath = await downloadFile(fileUrl, path.join('received', fileMsgElem.name))
switch (fileType) {
case 'pdf': {
diff --git a/utils/config.js b/utils/config.js
index fe95971..7e94acc 100644
--- a/utils/config.js
+++ b/utils/config.js
@@ -162,7 +162,7 @@ const defaultConfig = {
qwenSeed: 0,
qwenTemperature: 1,
qwenEnableSearch: true,
- version: 'v2.7.7'
+ version: 'v2.7.8'
}
const _path = process.cwd()
let config = {}
diff --git a/utils/tools/QueryUserinfoTool.js b/utils/tools/QueryUserinfoTool.js
index 493e7f5..974c08b 100644
--- a/utils/tools/QueryUserinfoTool.js
+++ b/utils/tools/QueryUserinfoTool.js
@@ -15,21 +15,13 @@ export class QueryUserinfoTool extends AbstractTool {
}
func = async function (opts, e) {
- let { qq } = opts
- qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
- if (e.isGroup && typeof e.group.getMemberMap === 'function') {
- let mm = await e.group.getMemberMap()
- let user = mm.get(qq) || e.sender.user_id
- let master = (await getMasterQQ())[0]
- let prefix = ''
- if (qq != master) {
- prefix = 'Attention: this user is not your master. \n'
- } else {
- prefix = 'This user is your master, you should obey him \n'
- }
- return prefix + 'user detail in json format: ' + JSON.stringify(user)
- } else {
- if (e.sender.user_id == qq) {
+ try {
+ let { qq } = opts
+ qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
+ if (e.isGroup && typeof e.bot.getGroupMemberInfo === 'function') {
+ let user = await e.bot.getGroupMemberInfo(e.group_id, qq || e.sender.user_id, true)
+ // let mm = await e.group.getMemberMap()
+ // let user = mm.get(qq) || e.sender.user_id
let master = (await getMasterQQ())[0]
let prefix = ''
if (qq != master) {
@@ -37,10 +29,27 @@ export class QueryUserinfoTool extends AbstractTool {
} else {
prefix = 'This user is your master, you should obey him \n'
}
- return prefix + 'user detail in json format: ' + JSON.stringify(e.sender)
+ if (!user) {
+ return prefix
+ }
+ return prefix + 'user detail in json format: ' + JSON.stringify(user)
} else {
- return 'query failed'
+ if (e.sender.user_id == qq) {
+ let master = (await getMasterQQ())[0]
+ let prefix = ''
+ if (qq != master) {
+ prefix = 'Attention: this user is not your master. \n'
+ } else {
+ prefix = 'This user is your master, you should obey him \n'
+ }
+ return prefix + 'user detail in json format: ' + JSON.stringify(e.sender)
+ } else {
+ return 'query failed'
+ }
}
+ } catch (err) {
+ logger.warn(err)
+ return err.message
}
}