diff --git a/README.md b/README.md index 6d0e971..ec523ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![chatgpt-plugin](https://user-images.githubusercontent.com/21212372/232115814-de9a0633-371f-4733-8da0-dd6e912c8a1e.png) -

云崽系机器人的智能聊天插件

+![chatgpt-plugin](https://socialify.git.ci/ikechan8370/chatgpt-plugin/image?description=1&font=Jost&forks=1&issues=1&language=1&name=1&owner=1&pulls=1&stargazers=1&theme=Light)
diff --git a/apps/chat.js b/apps/chat.js index 77bc9f4..94f79d9 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -1,12 +1,12 @@ import plugin from '../../../lib/plugins/plugin.js' import _ from 'lodash' -import {Config, defaultOpenAIAPI} from '../utils/config.js' -import {v4 as uuid} from 'uuid' +import { Config, defaultOpenAIAPI } from '../utils/config.js' +import { v4 as uuid } from 'uuid' import delay from 'delay' -import {ChatGPTAPI} from '../utils/openai/chatgpt-api.js' -import {BingAIClient} from '@waylaidwanderer/chatgpt-api' +import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js' +import { BingAIClient } from '@waylaidwanderer/chatgpt-api' import SydneyAIClient from '../utils/SydneyAIClient.js' -import {PoeClient} from '../utils/poe/index.js' +import { PoeClient } from '../utils/poe/index.js' import AzureTTS from '../utils/tts/microsoft-azure.js' import VoiceVoxTTS from '../utils/tts/voicevox.js' import Version from '../utils/version.js' @@ -33,51 +33,51 @@ import { renderUrl, upsertMessage } from '../utils/common.js' -import {ChatGPTPuppeteer} from '../utils/browser.js' -import {KeyvFile} from 'keyv-file' -import {OfficialChatGPTClient} from '../utils/message.js' +import { ChatGPTPuppeteer } from '../utils/browser.js' +import { KeyvFile } from 'keyv-file' +import { OfficialChatGPTClient } from '../utils/message.js' import fetch from 'node-fetch' -import {deleteConversation, getConversations, getLatestMessageIdByConversationId} from '../utils/conversation.js' -import {convertSpeaker, speakers} from '../utils/tts.js' +import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js' +import { convertSpeaker, speakers } from '../utils/tts.js' import ChatGLMClient from '../utils/chatglm.js' -import {convertFaces} from '../utils/face.js' -import {SlackClaudeClient} from '../utils/slack/slackClient.js' -import {getPromptByName} from '../utils/prompts.js' +import { convertFaces } from '../utils/face.js' +import { SlackClaudeClient } from '../utils/slack/slackClient.js' +import { getPromptByName } from '../utils/prompts.js' import BingDrawClient from '../utils/BingDraw.js' import XinghuoClient from '../utils/xinghuo/xinghuo.js' import Bard from '../utils/bard.js' -import {JinyanTool} from '../utils/tools/JinyanTool.js' -import {SendVideoTool} from '../utils/tools/SendBilibiliTool.js' -import {KickOutTool} from '../utils/tools/KickOutTool.js' -import {EditCardTool} from '../utils/tools/EditCardTool.js' -import {SearchVideoTool} from '../utils/tools/SearchBilibiliTool.js' -import {SearchMusicTool} from '../utils/tools/SearchMusicTool.js' -import {QueryStarRailTool} from '../utils/tools/QueryStarRailTool.js' -import {WebsiteTool} from '../utils/tools/WebsiteTool.js' -import {WeatherTool} from '../utils/tools/WeatherTool.js' -import {SerpTool} from '../utils/tools/SerpTool.js' -import {SerpIkechan8370Tool} from '../utils/tools/SerpIkechan8370Tool.js' -import {SendPictureTool} from '../utils/tools/SendPictureTool.js' -import {SerpImageTool} from '../utils/tools/SearchImageTool.js' -import {ImageCaptionTool} from '../utils/tools/ImageCaptionTool.js' -import {SendAudioMessageTool} from '../utils/tools/SendAudioMessageTool.js' -import {ProcessPictureTool} from '../utils/tools/ProcessPictureTool.js' -import {APTool} from '../utils/tools/APTool.js' -import {QueryGenshinTool} from '../utils/tools/QueryGenshinTool.js' -import {HandleMessageMsgTool} from '../utils/tools/HandleMessageMsgTool.js' -import {QueryUserinfoTool} from '../utils/tools/QueryUserinfoTool.js' -import {EliMovieTool} from '../utils/tools/EliMovieTool.js' -import {EliMusicTool} from '../utils/tools/EliMusicTool.js' -import {SendMusicTool} from '../utils/tools/SendMusicTool.js' -import {SendDiceTool} from '../utils/tools/SendDiceTool.js' -import {SendAvatarTool} from '../utils/tools/SendAvatarTool.js' -import {SendMessageToSpecificGroupOrUserTool} from '../utils/tools/SendMessageToSpecificGroupOrUserTool.js' -import {SetTitleTool} from '../utils/tools/SetTitleTool.js' -import {solveCaptchaOneShot} from '../utils/bingCaptcha.js' -import {ClaudeAIClient} from '../utils/claude.ai/index.js' -import {getProxy} from '../utils/proxy.js' -import {QwenApi} from '../utils/alibaba/qwen-api.js' -import {getChatHistoryGroup} from '../utils/chat.js' +import { JinyanTool } from '../utils/tools/JinyanTool.js' +import { SendVideoTool } from '../utils/tools/SendBilibiliTool.js' +import { KickOutTool } from '../utils/tools/KickOutTool.js' +import { EditCardTool } from '../utils/tools/EditCardTool.js' +import { SearchVideoTool } from '../utils/tools/SearchBilibiliTool.js' +import { SearchMusicTool } from '../utils/tools/SearchMusicTool.js' +import { QueryStarRailTool } from '../utils/tools/QueryStarRailTool.js' +import { WebsiteTool } from '../utils/tools/WebsiteTool.js' +import { WeatherTool } from '../utils/tools/WeatherTool.js' +import { SerpTool } from '../utils/tools/SerpTool.js' +import { SerpIkechan8370Tool } from '../utils/tools/SerpIkechan8370Tool.js' +import { SendPictureTool } from '../utils/tools/SendPictureTool.js' +import { SerpImageTool } from '../utils/tools/SearchImageTool.js' +import { ImageCaptionTool } from '../utils/tools/ImageCaptionTool.js' +import { SendAudioMessageTool } from '../utils/tools/SendAudioMessageTool.js' +import { ProcessPictureTool } from '../utils/tools/ProcessPictureTool.js' +import { APTool } from '../utils/tools/APTool.js' +import { QueryGenshinTool } from '../utils/tools/QueryGenshinTool.js' +import { HandleMessageMsgTool } from '../utils/tools/HandleMessageMsgTool.js' +import { QueryUserinfoTool } from '../utils/tools/QueryUserinfoTool.js' +import { EliMovieTool } from '../utils/tools/EliMovieTool.js' +import { EliMusicTool } from '../utils/tools/EliMusicTool.js' +import { SendMusicTool } from '../utils/tools/SendMusicTool.js' +import { SendDiceTool } from '../utils/tools/SendDiceTool.js' +import { SendAvatarTool } from '../utils/tools/SendAvatarTool.js' +import { SendMessageToSpecificGroupOrUserTool } from '../utils/tools/SendMessageToSpecificGroupOrUserTool.js' +import { SetTitleTool } from '../utils/tools/SetTitleTool.js' +import { solveCaptchaOneShot } from '../utils/bingCaptcha.js' +import { ClaudeAIClient } from '../utils/claude.ai/index.js' +import { getProxy } from '../utils/proxy.js' +import { QwenApi } from '../utils/alibaba/qwen-api.js' +import { getChatHistoryGroup } from '../utils/chat.js' try { await import('@azure/openai') @@ -97,6 +97,9 @@ try { } let version = Config.version let proxy = getProxy() + +const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德'] +const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard'] /** * 每个对话保留的时长。单个对话内ai是保留上下文的。超时后销毁对话,再次对话创建新的对话。 * 单位:秒 @@ -204,11 +207,11 @@ export class chatgpt extends plugin { permission: 'master' }, { - reg: '^#(chatgpt)?(结束|新开|摧毁|毁灭|完结)对话([sS]*)', + reg: '^#(chatgpt|星火|通义千问|克劳德|克劳德2|必应|api|API|api3|API3|glm|巴德)?(结束|新开|摧毁|毁灭|完结)对话([sS]*)', fnc: 'destroyConversations' }, { - reg: '^#(chatgpt)?(结束|新开|摧毁|毁灭|完结)全部对话$', + reg: '^#(chatgpt|星火|通义千问|克劳德|克劳德2|必应|api|API|api3|API3|glm|巴德)?(结束|新开|摧毁|毁灭|完结)全部对话$', fnc: 'endAllConversations', permission: 'master' }, @@ -299,7 +302,15 @@ export class chatgpt extends plugin { */ async destroyConversations (e) { const userData = await getUserData(e.user_id) - const use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE') + 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}`) if (use === 'claude') { // let client = new SlackClaudeClient({ @@ -476,7 +487,15 @@ export class chatgpt extends plugin { } async endAllConversations (e) { - let use = await redis.get('CHATGPT:USE') || 'api' + 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': { @@ -797,7 +816,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('#')) { @@ -1577,36 +1596,22 @@ export class chatgpt extends plugin { cookies = bingToken } let bingAIClient - if (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') { - const cacheOptions = { - namespace: Config.toneStyle, - store: new KeyvFile({ filename: 'cache.json' }) - } - bingAIClient = new SydneyAIClient({ - userToken: bingToken, // "_U" cookie from bing.com - cookies, - debug: Config.debug, - cache: cacheOptions, - user: e.sender.user_id, - proxy: Config.proxy - }) - // Sydney不实现上下文传递,删除上下文索引 - delete conversation.clientId - delete conversation.invocationId - delete conversation.conversationSignature - } else { - let bingOption = { - userToken: bingToken, // "_U" cookie from bing.com - cookies, - debug: Config.debug, - proxy: Config.proxy, - host: Config.sydneyReverseProxy - } - if (Config.proxy && Config.sydneyReverseProxy && !Config.sydneyForceUseReverse) { - delete bingOption.host - } - bingAIClient = new BingAIClient(bingOption) + const cacheOptions = { + namespace: Config.toneStyle, + store: new KeyvFile({ filename: 'cache.json' }) } + bingAIClient = new SydneyAIClient({ + userToken: bingToken, // "_U" cookie from bing.com + cookies, + debug: Config.debug, + cache: cacheOptions, + user: e.sender.user_id, + proxy: Config.proxy + }) + // Sydney不实现上下文传递,删除上下文索引 + delete conversation.clientId + delete conversation.invocationId + delete conversation.conversationSignature let response let reply = '' let retry = 3 @@ -1658,9 +1663,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) } @@ -1691,6 +1700,31 @@ export class chatgpt extends plugin { const image = await getImg(e) opt.imageUrl = image ? image[0] : undefined } + if (Config.enableGenerateContents) { + opt.onImageCreateRequest = prompt => { + logger.mark(`开始生成内容:${prompt}`) + if (Config.bingAPDraw) { + // 调用第三方API进行绘图 + let apDraw = new APTool() + apDraw.func({ + prompt + }, e) + } else { + let client = new BingDrawClient({ + baseUrl: Config.sydneyReverseProxy, + userToken: bingToken + }) + redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => { + try { + client.getImages(prompt, e) + } catch (err) { + redis.del(`CHATGPT:DRAW:${e.sender.user_id}`) + e.reply('绘图失败:' + err) + } + }) + } + } + } response = await bingAIClient.sendMessage(prompt, opt, (token) => { reply += token }) @@ -1716,32 +1750,6 @@ export class chatgpt extends plugin { }) } } - // 处理内容生成的图片 - if (response.details.imageTag) { - if (Config.debug) { - logger.mark(`开始生成内容:${response.details.imageTag}`) - } - if (Config.bingAPDraw) { - // 调用第三方API进行绘图 - let apDraw = new APTool() - apDraw.func({ - prompt: response.details.imageTag - }, e) - } else { - let client = new BingDrawClient({ - baseUrl: Config.sydneyReverseProxy, - userToken: bingToken - }) - await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 }) - try { - await client.getImages(response.details.imageTag, e) - } catch (err) { - await redis.del(`CHATGPT:DRAW:${e.sender.user_id}`) - await e.reply('绘图失败:' + err) - } - } - } - // 如果token曾经有异常,则清除异常 let Tokens = JSON.parse((await redis.get('CHATGPT:BING_TOKENS')) || '[]') const TokenIndex = Tokens?.findIndex(element => element.Token === abtrs.bingToken) @@ -1757,7 +1765,7 @@ export class chatgpt extends plugin { const { maxConv } = error if (message && typeof message === 'string' && message.indexOf('CaptchaChallenge') > -1) { if (bingToken) { - if (maxConv >= 20) { + if (maxConv >= 20 && Config.bingCaptchaOneShotUrl) { // maxConv为30说明token有效,可以通过解验证码码服务过码 await e.reply('出现必应验证码,尝试解决中') try { @@ -1766,6 +1774,7 @@ export class chatgpt extends plugin { await e.reply('验证码已解决') } else { logger.error(captchaResolveResult) + errorMessage = message await e.reply('验证码解决失败: ' + captchaResolveResult.error) retry = 0 } @@ -1776,7 +1785,8 @@ export class chatgpt extends plugin { } } else { // 未登录用户maxConv目前为5或10,出验证码没救 - logger.warn(`token [${bingToken}] 无效或已过期,如确认token无误,请前往网页版必应对话一次`) + logger.warn(`token [${bingToken}] 出现必应验证码,请前往网页版或app手动解决`) + errorMessage = message retry = 0 } } else { @@ -1827,9 +1837,9 @@ export class chatgpt extends plugin { response = response || {} if (errorMessage.includes('CaptchaChallenge')) { if (bingToken) { - errorMessage = '出现验证码,请使用当前账户前往https://www.bing.com/chat或Edge侧边栏手动解除验证码' + errorMessage = '出现验证码,请使用当前账户前往https://www.bing.com/chat或Edge侧边栏或移动端APP手动解除验证码' } else { - errorMessage = '出现验证码,且未配置必应账户,请尝试更换代理/反代或绑定必应账户以解除验证码' + errorMessage = '未配置必应账户,请绑定必应账户再使用必应模式' } } return { @@ -2116,6 +2126,7 @@ export class chatgpt extends plugin { } } default: { + // openai api let completionParams = {} if (Config.model) { completionParams.model = Config.model @@ -2306,28 +2317,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/utils/BingDraw.js b/utils/BingDraw.js index 80e69b1..5dc30ab 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('❌绘图失败:绘图完成但被屏蔽,请调整提示词。', 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/SydneyAIClient.js b/utils/SydneyAIClient.js index 843014c..acf692b 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -227,7 +227,8 @@ export default class SydneyAIClient { firstMessageTimeout = Config.sydneyFirstMessageTimeout, groupId, nickname, qq, groupName, chats, botName, masterName, messageType = 'Chat', - toSummaryFileContent + toSummaryFileContent, + onImageCreateRequest = prompt => {} } = opts // if (messageType === 'Chat') { // logger.warn('该Bing账户token已被限流,降级至使用非搜索模式。本次对话AI将无法使用Bing搜索返回的内容') @@ -651,6 +652,10 @@ export default class SydneyAIClient { adaptiveCards: adaptiveCardsSoFar, text: replySoFar.join('') } + if (messages[0].contentType === 'IMAGE') { + onImageCreateRequest(messages[0].text) + return + } if (messages[0].contentOrigin === 'Apology') { console.log('Apology found') if (!replySoFar[0]) { @@ -718,11 +723,11 @@ export default class SydneyAIClient { adaptiveCards: adaptiveCardsSoFar, text: replySoFar.join('') } - // 获取到图片内容 - if (messages.some(obj => obj.contentType === 'IMAGE')) { - message.imageTag = messages.filter(m => m.contentType === 'IMAGE').map(m => m.text).join('') - } - message.text = messages.filter(m => m.author === 'bot' && m.contentType != 'IMAGE').map(m => m.text).join('') + // // 获取到图片内容 + // if (messages.some(obj => obj.contentType === 'IMAGE')) { + // message.imageTag = messages.filter(m => m.contentType === 'IMAGE').map(m => m.text).join('') + // } + message.text = messages.filter(m => m.author === 'bot' && m.contentType !== 'IMAGE').map(m => m.text).join('') if (!message) { reject('No message was generated.') return 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..cfe04f3 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,17 @@ 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 init = {} + if (ignoreCertificateError && url.startsWith('https')) { + init.agent = new https.Agent({ + rejectUnauthorized: !ignoreCertificateError + }) + } + let response = await fetch(url, init) if (!response.ok) { throw new Error(`download file http error: status: ${response.status}`) } @@ -1061,7 +1100,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 6714244..c342e0b 100644 --- a/utils/config.js +++ b/utils/config.js @@ -163,7 +163,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 } }