diff --git a/apps/chat.js b/apps/chat.js index 875402c2..b4072961 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -77,6 +77,7 @@ 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') @@ -1660,28 +1661,7 @@ export class chatgpt extends plugin { if (master && !e.group) { opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname } - let latestChats = await e.group.getChatHistory(0, 1) - if (latestChats.length > 0) { - let latestChat = latestChats[0] - if (latestChat) { - let seq = latestChat.seq - let chats = [] - while (chats.length < Config.groupContextLength) { - let chatHistory = await e.group.getChatHistory(seq, 20) - chats.push(...chatHistory) - } - chats = chats.slice(0, Config.groupContextLength) - let mm = await e.group.getMemberMap() - chats.forEach(chat => { - let sender = mm.get(chat.sender.user_id) - if (sender) { - chat.sender = sender - } - }) - // console.log(chats) - opt.chats = chats - } - } + opt.chats = await getChatHistoryGroup(e, Config.groupContextLength) } catch (err) { logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err) } @@ -2162,7 +2142,7 @@ export class chatgpt extends plugin { opt.groupId = e.group_id opt.qq = e.sender.user_id opt.nickname = e.sender.card - opt.groupName = e.group.name + opt.groupName = e.group.name || e.group_name opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname let master = (await getMasterQQ())[0] if (master && e.group) { @@ -2171,21 +2151,7 @@ export class chatgpt extends plugin { if (master && !e.group) { opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname } - let latestChat = await e.group.getChatHistory(0, 1) - let seq = latestChat[0].seq - let chats = [] - while (chats.length < Config.groupContextLength) { - let chatHistory = await e.group.getChatHistory(seq, 20) - chats.push(...chatHistory.reverse()) - } - chats = chats.slice(0, Config.groupContextLength) - // 太多可能会干扰AI对自身qq号和用户qq的判断,感觉gpt3.5也处理不了那么多信息 - chats = chats > 50 ? 50 : chats - let mm = await e.group.getMemberMap() - chats.forEach(chat => { - let sender = mm.get(chat.sender.user_id) - chat.sender = sender - }) + let chats = await getChatHistoryGroup(e, Config.groupContextLength) opt.chats = chats const namePlaceholder = '[name]' const defaultBotName = 'ChatGPT' diff --git a/apps/entertainment.js b/apps/entertainment.js index fb8ff7fc..eb6e9d85 100644 --- a/apps/entertainment.js +++ b/apps/entertainment.js @@ -50,6 +50,10 @@ export class Entertainment extends plugin { reg: '^#(|最新)词云(\\d{1,2}h{0,1}|)$', fnc: 'wordcloud_latest' }, + { + reg: '^#(我的)?(本月|本周|今日)?词云$', + fnc: 'wordcloud_new' + }, { reg: '^#((寄批踢|gpt|GPT)?翻.*|chatgpt翻译帮助)', fnc: 'translate' @@ -215,10 +219,10 @@ ${translateLangLabels} const regExp = /词云(\d{0,2})(|h)/ const match = e.msg.trim().match(regExp) - const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h + const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h if (duration > 24) { - await e.reply('最多只能统计24小时内的记录哦') + await e.reply('最多只能统计24小时内的记录哦,你可以使用#本周词云和#本月词云获取更长时间的统计~') return false } await e.reply('在统计啦,请稍等...') @@ -236,6 +240,56 @@ ${translateLangLabels} } } + async wordcloud_new (e) { + if (e.isGroup) { + let groupId = e.group_id + let userId + if (e.msg.includes('我的')) { + userId = e.sender.user_id + } + let at = e.message.find(m => m.type === 'at') + if (at) { + userId = at.qq + } + let lock = await redis.get(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`) + if (lock) { + await e.reply('别着急,上次统计还没完呢') + return true + } + await e.reply('在统计啦,请稍等...') + let duration = 24 + if (e.msg.includes('本周')) { + const now = new Date() // Get the current date and time + let day = now.getDay() + let diff = now.getDate() - day + (day === 0 ? -6 : 1) + const startOfWeek = new Date(new Date().setDate(diff)) + startOfWeek.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day) + duration = (now - startOfWeek) / 1000 / 60 / 60 + } else if (e.msg.includes('本月')) { + const now = new Date() // Get the current date and time + const startOfMonth = new Date(new Date().setDate(0)) + startOfMonth.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day) + duration = (now - startOfMonth) / 1000 / 60 / 60 + } else { + // 默认今天 + const now = new Date() + const startOfToday = new Date() // Get the current date and time + startOfToday.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day) + duration = (now - startOfToday) / 1000 / 60 / 60 + } + await redis.set(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`, '1', { EX: 600 }) + try { + await makeWordcloud(e, e.group_id, duration, userId) + } catch (err) { + logger.error(err) + await e.reply(err) + } + await redis.del(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`) + } else { + await e.reply('请在群里发送此命令') + } + } + async combineEmoj (e) { let left = e.msg.codePointAt(0).toString(16).toLowerCase() let right = e.msg.codePointAt(2).toString(16).toLowerCase() diff --git a/package.json b/package.json index dbd30402..b593ce48 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "js-tiktoken": "^1.0.5", "keyv": "^4.5.3", "keyv-file": "^0.2.0", - "microsoft-cognitiveservices-speech-sdk": "^1.30.1", + "microsoft-cognitiveservices-speech-sdk": "1.32.0", "node-fetch": "^3.3.1", "openai": "^3.2.1", "p-timeout": "^6.1.2", diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index c425f7c7..7f2e315c 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -476,7 +476,7 @@ export default class SydneyAIClient { context += chats .map(chat => { let sender = chat.sender || chat || {} - if (chat.raw_message.startsWith('建议的回复')) { + if (chat.raw_message?.startsWith('建议的回复')) { // 建议的回复太容易污染设定导致对话太固定跑偏了 return '' } diff --git a/utils/chat.js b/utils/chat.js new file mode 100644 index 00000000..e249d0ff --- /dev/null +++ b/utils/chat.js @@ -0,0 +1,34 @@ +export async function getChatHistoryGroup (e, num) { + // if (e.adapter === 'shamrock') { + // return await e.group.getChatHistory(0, num, false) + // } else { + let latestChats = await e.group.getChatHistory(0, 1) + if (latestChats.length > 0) { + let latestChat = latestChats[0] + if (latestChat) { + let seq = latestChat.seq || latestChat.message_id + let chats = [] + while (chats.length < num) { + let chatHistory = await e.group.getChatHistory(seq, 20) + chats.push(...chatHistory) + seq = chatHistory[0].seq || chatHistory[0].message_id + } + 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 + } + }) + } catch (err) { + logger.warn(err) + } + // console.log(chats) + return chats + } + } + // } + return [] +} diff --git a/utils/common.js b/utils/common.js index 5493bc4b..b3feb7f7 100644 --- a/utils/common.js +++ b/utils/common.js @@ -832,8 +832,10 @@ export function getMaxModelTokens (model = 'gpt-3.5-turbo') { if (model.startsWith('gpt-3.5-turbo')) { if (model.includes('16k')) { return 16000 - } else { + } else if (model.includes('0613') || model.includes('0314')) { return 4000 + } else { + return 16000 } } else { if (model.includes('32k')) { @@ -870,7 +872,7 @@ export async function generateAudio (e, pendingText, speakingEmotion, emotionDeg if (!Config.ttsSpace && !Config.azureTTSKey && !Config.voicevoxSpace) return false let wav const speaker = getUserSpeaker(await getUserReplySetting(e)) - let ignoreEncode = getUin(e) === 88888 + let ignoreEncode = e.adapter === 'shamrock' try { if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) { if (Config.autoJapanese) { diff --git a/utils/tools/SendAudioMessageTool.js b/utils/tools/SendAudioMessageTool.js index cdfdef7e..65945a09 100644 --- a/utils/tools/SendAudioMessageTool.js +++ b/utils/tools/SendAudioMessageTool.js @@ -102,7 +102,12 @@ export class SendAudioMessageTool extends AbstractTool { return `audio generation failed, error: ${JSON.stringify(err)}` } if (sendable) { - let groupList = e.bot.gl || new Map() + let groupList + try { + groupList = await e.bot.getGroupList() + } catch (err) { + groupList = e.bot.gl + } try { if (groupList.get(target)) { let group = await e.bot.pickGroup(target) diff --git a/utils/tools/SendAvatarTool.js b/utils/tools/SendAvatarTool.js index 2973db90..ed6fde4c 100644 --- a/utils/tools/SendAvatarTool.js +++ b/utils/tools/SendAvatarTool.js @@ -27,8 +27,12 @@ export class SendAvatarTool extends AbstractTool { const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber ? defaultTarget : parseInt(targetGroupIdOrQQNumber) === e.bot.uin ? defaultTarget : parseInt(targetGroupIdOrQQNumber) - - let groupList = e.bot.gl || new Map() + let groupList + try { + groupList = await e.bot.getGroupList() + } catch (err) { + groupList = e.bot.gl + } console.log('sendAvatar', target, pictures) if (groupList.get(target)) { let group = await e.bot.pickGroup(target) diff --git a/utils/tools/SendDiceTool.js b/utils/tools/SendDiceTool.js index ec34dd1d..ae3df919 100644 --- a/utils/tools/SendDiceTool.js +++ b/utils/tools/SendDiceTool.js @@ -24,7 +24,12 @@ export class SendDiceTool extends AbstractTool { const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber ? defaultTarget : parseInt(targetGroupIdOrQQNumber) === e.bot.uin ? defaultTarget : parseInt(targetGroupIdOrQQNumber) - let groupList = e.bot.gl || new Map() + let groupList + try { + groupList = await e.bot.getGroupList() + } catch (err) { + groupList = e.bot.gl + } num = isNaN(num) || !num ? 1 : num > 5 ? 5 : num if (groupList.get(target)) { let group = await e.bot.pickGroup(target, true) diff --git a/utils/tools/SendMessageToSpecificGroupOrUserTool.js b/utils/tools/SendMessageToSpecificGroupOrUserTool.js index bdb5cb85..6c71310e 100644 --- a/utils/tools/SendMessageToSpecificGroupOrUserTool.js +++ b/utils/tools/SendMessageToSpecificGroupOrUserTool.js @@ -25,7 +25,12 @@ export class SendMessageToSpecificGroupOrUserTool extends AbstractTool { ? defaultTarget : parseInt(targetGroupIdOrQQNumber) === e.bot.uin ? defaultTarget : parseInt(targetGroupIdOrQQNumber) - let groupList = e.bot.gl || new Map() + let groupList + try { + groupList = await e.bot.getGroupList() + } catch (err) { + groupList = e.bot.gl + } try { if (groupList.get(target)) { let group = await e.bot.pickGroup(target) diff --git a/utils/tools/SendPictureTool.js b/utils/tools/SendPictureTool.js index da46c5ce..d3d81cc7 100644 --- a/utils/tools/SendPictureTool.js +++ b/utils/tools/SendPictureTool.js @@ -32,7 +32,12 @@ export class SendPictureTool extends AbstractTool { let pictures = urlOfPicture.trim().split(' ') logger.mark('pictures to send: ', pictures) pictures = pictures.map(img => segment.image(img)) - let groupList = e.bot.gl || new Map() + let groupList + try { + groupList = await e.bot.getGroupList() + } catch (err) { + groupList = e.bot.gl + } try { if (groupList.get(target)) { let group = await e.bot.pickGroup(target) diff --git a/utils/tools/SendRPSTool.js b/utils/tools/SendRPSTool.js index 73f87d05..0481ba3c 100644 --- a/utils/tools/SendRPSTool.js +++ b/utils/tools/SendRPSTool.js @@ -20,7 +20,12 @@ export class SendRPSTool extends AbstractTool { const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber ? defaultTarget : parseInt(targetGroupIdOrQQNumber) === e.bot.uin ? defaultTarget : parseInt(targetGroupIdOrQQNumber) - let groupList = await e.bot.getGroupList() + let groupList + try { + groupList = await e.bot.getGroupList() + } catch (err) { + groupList = e.bot.gl + } if (groupList.get(target)) { let group = await e.bot.pickGroup(target, true) await group.sendMsg(segment.rps(num)) diff --git a/utils/wordcloud/tokenizer.js b/utils/wordcloud/tokenizer.js index dec121a0..8593e11e 100644 --- a/utils/wordcloud/tokenizer.js +++ b/utils/wordcloud/tokenizer.js @@ -10,11 +10,11 @@ try { } export class Tokenizer { - async getHistory (groupId, date = new Date(), duration = 0) { + async getHistory (e, groupId, date = new Date(), duration = 0, userId) { if (!groupId) { throw new Error('no valid group id') } - let group = Bot.pickGroup(groupId, true) + let group = e.bot.pickGroup(groupId, true) let latestChat = await group.getChatHistory(0, 1) let seq = latestChat[0].seq let chats = latestChat @@ -41,14 +41,15 @@ export class Tokenizer { let startOfSpecifiedDate = date.getTime() // if duration > 0, go back to the specified number of hours if (duration > 0) { - // duration should be in range [0, 24] - duration = Math.min(duration, 24) - startOfSpecifiedDate = currentTime - (duration * 60 * 60 * 1000) + // duration should be in range [0, 24] + // duration = Math.min(duration, 24) + startOfSpecifiedDate = currentTime - (duration * 60 * 60 * 1000) } - // Step 4: Get the end of the specified date by adding 24 hours (in milliseconds) - const endOfSpecifiedDate = startOfSpecifiedDate + (24 * 60 * 60 * 1000) - while (isTimestampInDateRange(chats[0]?.time, startOfSpecifiedDate, endOfSpecifiedDate) && isTimestampInDateRange(chats[chats.length - 1]?.time, startOfSpecifiedDate, endOfSpecifiedDate)) { + // Step 4: Get the end of the specified date by current time + const endOfSpecifiedDate = currentTime + while (isTimestampInDateRange(chats[0]?.time, startOfSpecifiedDate, endOfSpecifiedDate) && + isTimestampInDateRange(chats[chats.length - 1]?.time, startOfSpecifiedDate, endOfSpecifiedDate)) { let chatHistory = await group.getChatHistory(seq, 20) if (chatHistory.length === 1) { if (chats[0].seq === chatHistory[0].seq) { @@ -58,45 +59,51 @@ export class Tokenizer { } chats.push(...chatHistory) chats.sort(compareByTime) - seq = chatHistory[0].seq + seq = chatHistory?.[0]?.seq + if (!seq) { + break + } if (Config.debug) { logger.info(`拉取到${chatHistory.length}条聊天记录,当前已累计获取${chats.length}条聊天记录,继续拉...`) } } chats = chats.filter(chat => isTimestampInDateRange(chat.time, startOfSpecifiedDate, endOfSpecifiedDate)) + if (userId) { + chats = chats.filter(chat => chat.sender.user_id === userId) + } return chats } - async getKeywordTopK (groupId, topK = 100, duration = 0) { + async getKeywordTopK (e, groupId, topK = 100, duration = 0, userId) { if (!nodejieba) { throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用') } // duration represents the number of hours to go back, should in range [0, 24] - let chats = await this.getHistory(groupId, new Date(), duration) - let duration_str = duration > 0 ? `${duration}小时` : '今日' - logger.mark(`聊天记录拉取完成,获取到${duration_str}内${chats.length}条聊天记录,准备分词中`) - + let chats = await this.getHistory(e, groupId, new Date(), duration, userId) + let durationStr = duration > 0 ? `${duration}小时` : '今日' + logger.mark(`聊天记录拉取完成,获取到${durationStr}内${chats.length}条聊天记录,准备分词中`) + const _path = process.cwd() let stopWordsPath = `${_path}/plugins/chatgpt-plugin/utils/wordcloud/cn_stopwords.txt` const data = fs.readFileSync(stopWordsPath) const stopWords = String(data)?.split('\n') || [] let chatContent = chats .map(c => c.message - //只统计文本内容 - .filter(item => item.type == 'text') - .map(textItem => `${textItem.text}`) - .join("").trim() + // 只统计文本内容 + .filter(item => item.type == 'text') + .map(textItem => `${textItem.text}`) + .join('').trim() ) .map(c => { - let length = c.length - let threshold = 10 - if (length < 100 && length > 50) { - threshold = 6 - } else if (length <= 50 && length > 25) { - threshold = 3 - } else if (length <= 25) { - threshold = 2 - } + // let length = c.length + let threshold = 2 + // if (length < 100 && length > 50) { + // threshold = 6 + // } else if (length <= 50 && length > 25) { + // threshold = 3 + // } else if (length <= 25) { + // threshold = 2 + // } return nodejieba.extract(c, threshold) }) .reduce((acc, curr) => acc.concat(curr), []) @@ -132,6 +139,85 @@ export class Tokenizer { } } +export class ShamrockTokenizer extends Tokenizer { + async getHistory (e, groupId, date = new Date(), duration = 0, userId) { + logger.mark('当前使用Shamrock适配器') + if (!groupId) { + throw new Error('no valid group id') + } + let group = e.bot.pickGroup(groupId, true) + // 直接加大力度 + let pageSize = 500 + let chats = (await group.getChatHistory(0, pageSize, false)) || [] + // Get the current timestamp + let currentTime = date.getTime() + + // Step 2: Set the hours, minutes, seconds, and milliseconds to 0 + date.setHours(0, 0, 0, 0) + + // Step 3: Calculate the timestamp representing the start of the specified date + // duration represents the number of hours to go back + // if duration is 0, keeping the original date (start of today) + let startOfSpecifiedDate = date.getTime() + // if duration > 0, go back to the specified number of hours + if (duration > 0) { + // duration should be in range [0, 24] + // duration = Math.min(duration, 24) + startOfSpecifiedDate = currentTime - (duration * 60 * 60 * 1000) + } + + // Step 4: Get the end of the specified date by currentTime + const endOfSpecifiedDate = currentTime + let cursor = chats.length + // ------------------------------------------------------- + // | | | + // ------------------------------------------------------- + // ^ ^ + // long ago cursor+pageSize cursor current + while (isTimestampInDateRange(chats[0]?.time, startOfSpecifiedDate, endOfSpecifiedDate)) { + // 由于Shamrock消息是从最新的开始拉,结束时由于动态更新,一旦有人发送消息就会立刻停止,所以不判断结束时间 + // 拉到后面会巨卡,所以增大page减少次数 + pageSize = Math.floor(Math.max(cursor / 2, pageSize)) + cursor = cursor + pageSize + let retries = 3 + let chatHistory + while (retries >= 0) { + try { + chatHistory = await group.getChatHistory(0, cursor, false) + break + } catch (err) { + if (retries === 0) { + logger.error(err) + } + retries-- + } + } + if (retries < 0) { + logger.warn('拉不动了,就这样吧') + break + } + if (chatHistory.length === 1) { + break + } + if (chatHistory.length === chats.length) { + // 没有了!再拉也没有了 + break + } + let oldLength = chats.length + chats = chatHistory + // chats.sort(compareByTime) + if (Config.debug) { + logger.info(`拉取到${chats.length - oldLength}条聊天记录,当前已累计获取${chats.length}条聊天记录,继续拉...`) + } + } + chats = chats.filter(chat => isTimestampInDateRange(chat.time, startOfSpecifiedDate, endOfSpecifiedDate)) + if (userId) { + chats = chats.filter(chat => chat.sender.user_id === userId) + } + return chats + } +} + function isTimestampInDateRange (timestamp, startOfSpecifiedDate, endOfSpecifiedDate) { if (!timestamp) { return false diff --git a/utils/wordcloud/wordcloud.js b/utils/wordcloud/wordcloud.js index 08b13925..09d7614e 100644 --- a/utils/wordcloud/wordcloud.js +++ b/utils/wordcloud/wordcloud.js @@ -1,11 +1,19 @@ -import { Tokenizer } from './tokenizer.js' +import { ShamrockTokenizer, Tokenizer } from './tokenizer.js' import { render } from '../common.js' -export async function makeWordcloud (e, groupId, duration = 0) { - let tokenizer = new Tokenizer() - let topK = await tokenizer.getKeywordTopK(groupId, 100, duration) +export async function makeWordcloud (e, groupId, duration = 0, userId) { + let tokenizer = getTokenizer(e) + let topK = await tokenizer.getKeywordTopK(e, groupId, 100, duration, userId) let list = JSON.stringify(topK) - // let list = topK - console.log(list) - await render(e, 'chatgpt-plugin', 'wordcloud/index', { list }) + logger.info(list) + let img = await render(e, 'chatgpt-plugin', 'wordcloud/index', { list }, { retType: 'base64' }) + await e.reply(img, true) +} + +function getTokenizer (e) { + if (e.adapter === 'shamrock') { + return new ShamrockTokenizer() + } else { + return new Tokenizer() + } }