Merge branch 'ikechan8370:v2' into v2

This commit is contained in:
ifeif 2023-12-21 16:42:16 +08:00 committed by GitHub
commit d537e8abc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1218 additions and 2838 deletions

View file

@ -42,6 +42,9 @@
* 2023-09-10 支持来自claude.ai的claude-2模型
* 2023-10-19 支持读取文件目前适配必应模式和Claude2模式
* 2023-10-25 增加支持通义千问官方API
* 2023-12-01 持续优先适配Shamrock
* 2023-12-14 增加支持Gemini 官方API
### 如果觉得这个插件有趣或者对你有帮助请点一个star吧
## 版本要求

View file

@ -4,7 +4,6 @@ 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 SydneyAIClient from '../utils/SydneyAIClient.js'
import { PoeClient } from '../utils/poe/index.js'
import AzureTTS from '../utils/tts/microsoft-azure.js'
@ -22,6 +21,7 @@ import {
getMasterQQ,
getMaxModelTokens,
getMessageById,
getOrDownloadFile,
getUin,
getUserData,
getUserReplySetting,
@ -78,6 +78,14 @@ 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 { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
import { resizeAndCropImage } from '../utils/dalle.js'
import fs from 'fs'
const roleMap = {
owner: 'group owner',
admin: 'group administrator'
}
try {
await import('@azure/openai')
@ -194,6 +202,12 @@ export class chatgpt extends plugin {
/** 执行方法 */
fnc: 'qwen'
},
{
/** 命令正则匹配 */
reg: '^#gemini[sS]*',
/** 执行方法 */
fnc: 'gemini'
},
{
/** 命令正则匹配 */
reg: toggleMode === 'at' ? '^[^#][sS]*' : '^#chat[^gpt][sS]*',
@ -397,6 +411,14 @@ export class chatgpt extends plugin {
await redis.del(`CHATGPT:CONVERSATIONS_QWEN:${e.sender.user_id}`)
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
}
} else if (use === 'gemini') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_GEMINI:${e.sender.user_id}`)
if (!c) {
await this.reply('当前没有开启对话', true)
} else {
await redis.del(`CHATGPT:CONVERSATIONS_GEMINI:${e.sender.user_id}`)
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
}
} else if (use === 'bing') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
if (!c) {
@ -466,6 +488,14 @@ export class chatgpt extends plugin {
await redis.del(`CHATGPT:CONVERSATIONS_QWEN:${qq}`)
await this.reply(`已结束${atUser}的对话TA仍可以@我进行聊天以开启新的对话`, true)
}
} else if (use === 'gemini') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_GEMINI:${qq}`)
if (!c) {
await this.reply(`当前${atUser}没有开启对话`, true)
} else {
await redis.del(`CHATGPT:CONVERSATIONS_GEMINI:${qq}`)
await this.reply(`已结束${atUser}的对话TA仍可以@我进行聊天以开启新的对话`, true)
}
} else if (use === 'bing') {
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${qq}`)
if (!c) {
@ -597,6 +627,18 @@ export class chatgpt extends plugin {
}
break
}
case 'gemini': {
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_GEMINI:*')
for (let i = 0; i < qcs.length; i++) {
await redis.del(qcs[i])
// todo clean last message id
if (Config.debug) {
logger.info('delete gemini conversation bind: ' + qcs[i])
}
deleted++
}
break
}
}
await this.reply(`结束了${deleted}个用户的对话。`, true)
}
@ -1092,6 +1134,10 @@ export class chatgpt extends plugin {
key = `CHATGPT:CONVERSATIONS_QWEN:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
case 'gemini': {
key = `CHATGPT:CONVERSATIONS_GEMINI:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
}
let ctime = new Date()
previousConversation = (key ? await redis.get(key) : null) || JSON.stringify({
@ -1376,155 +1422,39 @@ export class chatgpt extends plugin {
}
async chatgpt1 (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#chat1')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#chat1', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'api')
return true
return await this.otherMode(e, 'api', '#chat1')
}
async chatgpt3 (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#chat3')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#chat3', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'api3')
return true
return await this.otherMode(e, 'api3', '#chat3')
}
async chatglm (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#chatglm')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#chatglm', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'chatglm')
return true
return await this.otherMode(e, 'chatglm')
}
async bing (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#bing')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#bing', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'bing')
return true
return await this.otherMode(e, 'bing')
}
async claude2 (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#claude2')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#claude2', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'claude2')
return true
return await this.otherMode(e, 'claude2')
}
async claude (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#claude')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#claude', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'claude')
return true
return await this.otherMode(e, 'claude')
}
async qwen (e) {
if (!Config.allowOtherMode) {
return false
return await this.otherMode(e, 'gemini')
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#xh')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#qwen', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'qwen')
return true
async gemini (e) {
return await this.otherMode(e, 'gemini')
}
async xh (e) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略#xh')
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), '#xh', '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, 'xh')
return true
return await this.otherMode(e, 'xh')
}
async cacheContent (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) {
@ -1584,13 +1514,17 @@ export class chatgpt extends plugin {
}
const userData = await getUserData(e.user_id)
const useCast = userData.cast || {}
switch (use) {
case 'browser': {
if (use === 'browser') {
{
return await this.chatgptBrowserBased(prompt, conversation)
}
case 'bing': {
} else if (use === 'bing') {
{
let throttledTokens = []
let { bingToken, allThrottled } = await getAvailableBingToken(conversation, throttledTokens)
let {
bingToken,
allThrottled
} = await getAvailableBingToken(conversation, throttledTokens)
let cookies
if (bingToken?.indexOf('=') > -1) {
cookies = bingToken
@ -1628,7 +1562,6 @@ export class chatgpt extends plugin {
}
// 重新拿存储的token因为可能之前有过期的被删了
let abtrs = await getAvailableBingToken(conversation, throttledTokens)
if (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') {
bingToken = abtrs.bingToken
// eslint-disable-next-line no-unused-vars
allThrottled = abtrs.allThrottled
@ -1678,23 +1611,6 @@ export class chatgpt extends plugin {
logger.warn('读取文件内容出错, 忽略文件内容', err)
}
opt.toSummaryFileContent = toSummaryFileContent
} else {
// 重新创建client因为token可能换到别的了
if (bingToken?.indexOf('=') > -1) {
cookies = bingToken
}
let bingOption = {
userToken: abtrs.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)
}
// 写入图片数据
if (Config.sydneyImageRecognition) {
const image = await getImg(e)
@ -1792,8 +1708,7 @@ export class chatgpt extends plugin {
} else {
retry = 0
}
} else
if (message && typeof message === 'string' && message.indexOf('限流') > -1) {
} else if (message && typeof message === 'string' && message.indexOf('限流') > -1) {
throttledTokens.push(bingToken)
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
const badBingToken = bingTokens.findIndex(element => element.Token === bingToken)
@ -1865,7 +1780,8 @@ export class chatgpt extends plugin {
}
}
}
case 'api3': {
} else if (use === 'api3') {
{
// official without cloudflare
let accessToken = await redis.get('CHATGPT:TOKEN')
if (!accessToken) {
@ -1889,7 +1805,8 @@ export class chatgpt extends plugin {
}
return sendMessageResult
}
case 'chatglm': {
} else if (use === 'chatglm') {
{
const cacheOptions = {
namespace: 'chatglm_6b',
store: new KeyvFile({ filename: 'cache.json' })
@ -1901,7 +1818,8 @@ export class chatgpt extends plugin {
let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation)
return sendMessageResult
}
case 'poe': {
} else if (use === 'poe') {
{
const cookie = await redis.get('CHATGPT:POE_TOKEN')
if (!cookie) {
throw new Error('未绑定Poe Cookie请使用#chatgpt设置Poe token命令绑定cookie')
@ -1919,7 +1837,8 @@ export class chatgpt extends plugin {
text: response.data
}
}
case 'claude': {
} else if (use === 'claude') {
{
let client = new SlackClaudeClient({
slackUserToken: Config.slackUserToken,
slackChannelId: Config.slackChannelId
@ -1943,7 +1862,8 @@ export class chatgpt extends plugin {
text
}
}
case 'claude2': {
} else if (use === 'claude2') {
{
let { conversationId } = conversation
let client = new ClaudeAIClient({
organizationId: Config.claudeAIOrganizationId,
@ -1983,7 +1903,8 @@ export class chatgpt extends plugin {
return await client.sendMessage(prompt, conv.uuid, attachments)
}
}
case 'xh': {
} else if (use === 'xh') {
{
const cacheOptions = {
namespace: 'xh',
store: new KeyvFile({ filename: 'cache.json' })
@ -2006,7 +1927,8 @@ export class chatgpt extends plugin {
})
return response
}
case 'azure': {
} else if (use === 'azure') {
{
let azureModel
try {
azureModel = await import('@azure/openai')
@ -2016,15 +1938,22 @@ export class chatgpt extends plugin {
let OpenAIClient = azureModel.OpenAIClient
let AzureKeyCredential = azureModel.AzureKeyCredential
let msg = conversation.messages
let content = { role: 'user', content: prompt }
let content = {
role: 'user',
content: prompt
}
msg.push(content)
const client = new OpenAIClient(Config.azureUrl, new AzureKeyCredential(Config.azApiKey))
const deploymentName = Config.azureDeploymentName
const { choices } = await client.getChatCompletions(deploymentName, msg)
let completion = choices[0].message
return { text: completion.content, message: completion }
return {
text: completion.content,
message: completion
}
case 'qwen': {
}
} else if (use === 'qwen') {
{
let completionParams = {
parameters: {
top_p: Config.qwenTopP || 0.5,
@ -2038,12 +1967,15 @@ export class chatgpt extends plugin {
completionParams.model = Config.qwenModel
}
const currentDate = new Date().toISOString().split('T')[0]
async function um (message) {
async function um(message) {
return await upsertMessage(message, 'QWEN')
}
async function gm (id) {
async function gm(id) {
return await getMessageById(id, 'QWEN')
}
let opts = {
apiKey: Config.qwenApiKey,
debug: false,
@ -2075,7 +2007,8 @@ export class chatgpt extends plugin {
}
return msg
}
case 'bard': {
} else if (use === 'bard') {
{
// 处理cookie
const matchesPSID = /__Secure-1PSID=([^;]+)/.exec(Config.bardPsid)
const matchesPSIDTS = /__Secure-1PSIDTS=([^;]+)/.exec(Config.bardPsid)
@ -2125,7 +2058,125 @@ export class chatgpt extends plugin {
images: response.images
}
}
} else if (use === 'gemini') {
{
let client = new CustomGoogleGeminiClient({
e,
userId: e.sender.user_id,
key: Config.geminiKey,
model: Config.geminiModel,
baseUrl: Config.geminiBaseUrl,
debug: Config.debug
})
let option = {
stream: false,
onProgress: (data) => {
if (Config.debug) {
logger.info(data)
}
},
parentMessageId: conversation.parentMessageId,
conversationId: conversation.conversationId
}
if (Config.geminiModel.includes('vision')) {
const image = await getImg(e)
let imageUrl = image ? image[0] : undefined
if (imageUrl) {
let md5 = imageUrl.split(/[/-]/).find(s => s.length === 32)?.toUpperCase()
let imageLoc = await getOrDownloadFile(`ocr/${md5}.png`, imageUrl)
let outputLoc = imageLoc.replace(`${md5}.png`, `${md5}_512.png`)
await resizeAndCropImage(imageLoc, outputLoc, 512)
let buffer = fs.readFileSync(outputLoc)
option.image = buffer.toString('base64')
}
}
if (Config.smartMode) {
/**
* @type {AbstractTool[]}
*/
let tools = [
new QueryStarRailTool(),
new WebsiteTool(),
new SendPictureTool(),
new SendVideoTool(),
// new ImageCaptionTool(),
new SearchVideoTool(),
new SendAvatarTool(),
new SerpImageTool(),
new SearchMusicTool(),
new SendMusicTool(),
// new SerpIkechan8370Tool(),
// new SerpTool(),
new SendAudioMessageTool(),
// new ProcessPictureTool(),
new APTool(),
// new HandleMessageMsgTool(),
new SendMessageToSpecificGroupOrUserTool(),
// new SendDiceTool(),
new QueryGenshinTool()
]
if (Config.amapKey) {
tools.push(new WeatherTool())
}
if (e.isGroup) {
tools.push(new QueryUserinfoTool())
// let self = e.group.pickMember(e.self_id)
if (e.group.is_admin || e.group.is_owner) {
tools.push(new EditCardTool())
tools.push(new JinyanTool())
tools.push(new KickOutTool())
}
if (e.group.is_owner) {
tools.push(new SetTitleTool())
}
}
switch (Config.serpSource) {
case 'ikechan8370': {
tools.push(new SerpIkechan8370Tool())
break
}
case 'azure': {
if (!Config.azSerpKey) {
logger.warn('未配置bing搜索密钥转为使用ikechan8370搜索源')
tools.push(new SerpIkechan8370Tool())
} else {
tools.push(new SerpTool())
}
break
}
default: {
tools.push(new SerpIkechan8370Tool())
}
}
client.addTools(tools)
}
let system = Config.geminiPrompt
if (Config.enableGroupContext && e.isGroup) {
let chats = await getChatHistoryGroup(e, Config.groupContextLength)
const namePlaceholder = '[name]'
const defaultBotName = 'GeminiPro'
const groupContextTip = Config.groupContextTip
let botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname
system = system.replaceAll(namePlaceholder, botName || defaultBotName) +
((Config.enableGroupContext && e.group_id) ? groupContextTip : '')
system += 'Attention, you are currently chatting in a qq group, then one who asks you now is' + `${e.sender.card || e.sender.nickname}(${e.sender.user_id}).`
system += `the group name is ${e.group.name || e.group_name}, group id is ${e.group_id}.`
system += `Your nickname is ${botName} in the group,`
if (chats) {
system += 'There is the conversation history in the group, you must chat according to the conversation history context"'
system += chats
.map(chat => {
let sender = chat.sender || {}
return `${sender.card || sender.nickname}】(qq${sender.user_id}, ${roleMap[sender.role] || 'normal user'}${sender.area ? 'from ' + sender.area + ', ' : ''} ${sender.age} years old, 群头衔:${sender.title}, gender: ${sender.sex}, time${formatDate(new Date(chat.time * 1000))}, messageId: ${chat.message_id}) 说:${chat.raw_message}`
})
.join('\n')
}
}
option.system = system
return await client.sendMessage(prompt, option)
}
} else {
{
// openai api
let completionParams = {}
if (Config.model) {
@ -2163,11 +2214,6 @@ export class chatgpt extends plugin {
if (opt.botName) {
system += `Your nickname is ${opt.botName} in the group,`
}
// system += master ? `我的qq号是${master}其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要。` : ''
const roleMap = {
owner: 'group owner',
admin: 'group administrator'
}
if (chats) {
system += 'There is the conversation history in the group, you must chat according to the conversation history context"'
system += chats
@ -2353,7 +2399,10 @@ export class chatgpt extends plugin {
if (msg.text) {
await e.reply(msg.text.replace('\n\n\n', '\n'))
}
let { name, arguments: args } = msg.functionCall
let {
name,
arguments: args
} = msg.functionCall
args = JSON.parse(args)
// 感觉换成targetGroupIdOrUserQQNumber这种表意比较清楚的变量名效果会好一丢丢
if (!args.groupId) {
@ -2364,7 +2413,10 @@ export class chatgpt extends plugin {
} catch (err) {
args.groupId = e.group_id + '' || e.sender.user_id + ''
}
let functionResult = await fullFuncMap[name.trim()].exec(Object.assign({ isAdmin, sender }, args), e)
let functionResult = await fullFuncMap[name.trim()].exec(Object.assign({
isAdmin,
sender
}, args), e)
logger.mark(`function ${name} execution result: ${functionResult}`)
option.parentMessageId = msg.id
option.name = name
@ -2692,6 +2744,25 @@ export class chatgpt extends plugin {
}
return await this.chatGPTApi.sendMessage(prompt, sendMessageOption)
}
async otherMode (e, mode, pattern = `#${mode}`) {
if (!Config.allowOtherMode) {
return false
}
let ats = e.message.filter(m => m.type === 'at')
if (!(e.atme || e.atBot) && ats.length > 0) {
if (Config.debug) {
logger.mark('艾特别人了,没艾特我,忽略' + pattern)
}
return false
}
let prompt = _.replace(e.raw_message.trimStart(), pattern, '').trim()
if (prompt.length === 0) {
return false
}
await this.abstractChat(e, prompt, mode)
return true
}
}
async function getAvailableBingToken (conversation, throttled = []) {

View file

@ -1,4 +1,5 @@
import plugin from '../../../lib/plugins/plugin.js'
import { exec } from 'child_process'
import { Config } from '../utils/config.js'
import {
formatDuration,
@ -126,6 +127,11 @@ export class ChatgptManagement extends plugin {
fnc: 'useClaudeAISolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(Gemini|gemini)$',
fnc: 'useGeminiSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换星火$',
fnc: 'useXinghuoBasedSolution',
@ -184,6 +190,11 @@ export class ChatgptManagement extends plugin {
fnc: 'setAPIKey',
permission: 'master'
},
{
reg: '^#chatgpt设置(Gemini|gemini)(Key|key)$',
fnc: 'setGeminiKey',
permission: 'master'
},
{
reg: '^#chatgpt设置(API|api)设定$',
fnc: 'setAPIPromptPrefix',
@ -319,6 +330,11 @@ export class ChatgptManagement extends plugin {
reg: '^#chatgpt设置星火模型$',
fnc: 'setXinghuoModel',
permission: 'master'
},
{
reg: '^#chatgpt修补Gemini$',
fnc: 'patchGemini',
permission: 'master'
}
]
})
@ -914,6 +930,16 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useGeminiSolution () {
let use = await redis.get('CHATGPT:USE')
if (use !== 'gemini') {
await redis.set('CHATGPT:USE', 'gemini')
await this.reply('已切换到基于Google Gemini的解决方案')
} else {
await this.reply('当前已经是gemini模式了')
}
}
async useXinghuoBasedSolution () {
let use = await redis.get('CHATGPT:USE')
if (use !== 'xh') {
@ -944,6 +970,57 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async patchGemini () {
const _path = process.cwd()
let packageJson = fs.readFileSync(`${_path}/package.json`)
packageJson = JSON.parse(String(packageJson))
const packageName = '@google/generative-ai@0.1.1'
const patchLoc = 'plugins/chatgpt-plugin/patches/@google__generative-ai@0.1.1.patch'
if (!packageJson.pnpm) {
packageJson.pnpm = {
patchedDependencies: {
[packageName]: patchLoc
}
}
} else {
if (packageJson.pnpm.patchedDependencies) {
packageJson.pnpm.patchedDependencies[packageName] = patchLoc
} else {
packageJson.pnpm.patchedDependencies = {
[packageName]: patchLoc
}
}
}
fs.writeFileSync(`${_path}/package.json`, JSON.stringify(packageJson, null, 2))
function execSync (cmd) {
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
resolve({ error, stdout, stderr })
})
})
}
async function checkPnpm () {
let npm = 'npm'
let ret = await execSync('pnpm -v')
if (ret.stdout) npm = 'pnpm'
return npm
}
let npmv = await checkPnpm()
if (npmv === 'pnpm') {
exec('pnpm i', {}, (error, stdout, stderr) => {
if (error) {
logger.error(error)
logger.error(stderr)
logger.info(stdout)
this.e.reply('失败,请查看日志手动操作')
} else {
this.e.reply('修补完成,请手动重启')
}
})
}
}
async useQwenSolution () {
let use = await redis.get('CHATGPT:USE')
if (use !== 'qwen') {
@ -1160,6 +1237,21 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
this.finish('saveAPIKey')
}
async setGeminiKey (e) {
this.setContext('saveGeminiKey')
await this.reply('请发送Gemini API Key.获取地址https://makersuite.google.com/app/apikey', true)
return false
}
async saveGeminiKey () {
if (!this.e.msg) return
let token = this.e.msg
// todo
Config.geminiKey = token
await this.reply('请发送Gemini API Key设置成功', true)
this.finish('saveGeminiKey')
}
async setXinghuoToken () {
this.setContext('saveXinghuoToken')
await this.reply('请发送星火的ssoSessionId', true)

View file

@ -158,7 +158,8 @@ export class help extends plugin {
api: 'promptPrefixOverride',
Custom: 'sydney',
claude: 'slackClaudeGlobalPreset',
qwen: 'promptPrefixOverride'
qwen: 'promptPrefixOverride',
gemini: 'geminiPrompt'
}
if (keyMap[use]) {
@ -171,7 +172,7 @@ export class help extends plugin {
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
} else {
await e.reply(`你当前正在使用${use}模式该模式不支持设定。支持设定的模式有API、自定义、Claude`, true)
await e.reply(`你当前正在使用${use}模式该模式不支持设定。支持设定的模式有API、自定义、Claude、通义千问和Gemini`, true)
}
}

View file

@ -14,13 +14,18 @@ export class BaseClient {
constructor (props = {}) {
this.supportFunction = false
this.maxToken = 4096
/**
* @type {Array<AbstractTool>}
*/
this.tools = []
const {
e, getMessageById, upsertMessage
e, getMessageById, upsertMessage, deleteMessageById, userId
} = props
this.e = e
this.getMessageById = getMessageById
this.upsertMessage = upsertMessage
this.deleteMessageById = deleteMessageById || (() => {})
this.userId = userId
}
/**
@ -36,20 +41,28 @@ export class BaseClient {
* insert or update a message with the id
*
* @type function
* @param {string} id
* @param {object} message
* @return {Promise<void>}
*/
upsertMessage
/**
* delete a message with the id
*
* @type function
* @param {string} id
* @return {Promise<void>}
*/
deleteMessageById
/**
* Send prompt message with history and return response message \
* if function called, handled internally \
* override this method to implement logic of sending and receiving message
*
* @param msg
* @param opt other options, optional fields: [conversationId, parentMessageId], if not set, random uuid instead
* @returns {Promise<Message>} required fields: [text, conversationId, parentMessageId, id]
* @param {string} msg
* @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?}} opt other options, optional fields: [conversationId, parentMessageId], if not set, random uuid instead
* @returns {Promise<{text, conversationId, parentMessageId, id}>} required fields: [text, conversationId, parentMessageId, id]
*/
async sendMessage (msg, opt = {}) {
throw new Error('not implemented in abstract client')
@ -60,11 +73,12 @@ export class BaseClient {
* override this method to implement logic of getting history
* keyv with local file or redis recommended
*
* @param userId such as qq number
* @param opt other options
* @returns {Promise<void>}
* @param userId optional, such as qq number
* @param parentMessageId if blank, no history
* @param opt optional, other options
* @returns {Promise<object[]>}
*/
async getHistory (userId, opt = {}) {
async getHistory (parentMessageId, userId = this.userId, opt = {}) {
throw new Error('not implemented in abstract client')
}
@ -78,14 +92,18 @@ export class BaseClient {
throw new Error('not implemented in abstract client')
}
addTools (...tools) {
/**
* 增加tools
* @param {[AbstractTool]} tools
*/
addTools (tools) {
if (!this.isSupportFunction) {
throw new Error('function not supported')
}
if (!this.tools) {
this.tools = []
}
this.tools.push(tools)
this.tools.push(...tools)
}
getTools () {

View file

@ -0,0 +1,264 @@
import crypto from 'crypto'
import { GoogleGeminiClient } from './GoogleGeminiClient.js'
import { newFetch } from '../utils/proxy.js'
import _ from 'lodash'
const BASEURL = 'https://generativelanguage.googleapis.com'
export const HarmCategory = {
HARM_CATEGORY_UNSPECIFIED: 'HARM_CATEGORY_UNSPECIFIED',
HARM_CATEGORY_HATE_SPEECH: 'HARM_CATEGORY_HATE_SPEECH',
HARM_CATEGORY_SEXUALLY_EXPLICIT: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
HARM_CATEGORY_HARASSMENT: 'HARM_CATEGORY_HARASSMENT',
HARM_CATEGORY_DANGEROUS_CONTENT: 'HARM_CATEGORY_DANGEROUS_CONTENT'
}
export const HarmBlockThreshold = {
HARM_BLOCK_THRESHOLD_UNSPECIFIED: 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
BLOCK_LOW_AND_ABOVE: 'BLOCK_LOW_AND_ABOVE',
BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE',
BLOCK_ONLY_HIGH: 'BLOCK_ONLY_HIGH',
BLOCK_NONE: 'BLOCK_NONE'
}
/**
* @typedef {{
* role: string,
* parts: Array<{
* text?: string,
* functionCall?: FunctionCall,
* functionResponse?: FunctionResponse
* }>
* }} Content
*
* Gemini消息的基本格式
*/
/**
* @typedef {{
* name: string,
* args: {}
* }} FunctionCall
*
* Gemini的FunctionCall
*/
/**
* @typedef {{
* name: string,
* response: {
* name: string,
* content: {}
* }
* }} FunctionResponse
*
* Gemini的Function执行结果包裹
* 其中response可以为任意本项目根据官方示例封装为name和content两个字段
*/
export class CustomGoogleGeminiClient extends GoogleGeminiClient {
constructor (props) {
super(props)
this.model = props.model
this.baseUrl = props.baseUrl || BASEURL
this.supportFunction = true
this.debug = props.debug
}
/**
*
* @param text
* @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?, functionResponse: FunctionResponse?, system: string?, image: string?}} opt
* @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>}
*/
async sendMessage (text, opt) {
let history = await this.getHistory(opt.parentMessageId)
let systemMessage = opt.system
if (systemMessage) {
history = history.reverse()
history.push({
role: 'model',
parts: [
{
text: 'ok'
}
]
})
history.push({
role: 'user',
parts: [
{
text: systemMessage
}
]
})
history = history.reverse()
}
const idThis = crypto.randomUUID()
const idModel = crypto.randomUUID()
const thisMessage = opt.functionResponse
? {
role: 'function',
parts: [{
functionResponse: opt.functionResponse
}],
id: idThis,
parentMessageId: opt.parentMessageId || undefined
}
: {
role: 'user',
parts: [{ text }],
id: idThis,
parentMessageId: opt.parentMessageId || undefined
}
if (opt.image) {
thisMessage.parts.push({
inline_data: {
mime_type: 'image/jpeg',
data: opt.image
}
})
}
history.push(_.cloneDeep(thisMessage))
let url = `${this.baseUrl}/v1beta/models/${this.model}:generateContent?key=${this._key}`
let body = {
// 不去兼容官方的简单格式了直接用免得function还要转换
/**
* @type Array<Content>
*/
contents: history,
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_NONE
}
],
generationConfig: {
maxOutputTokens: 1000,
temperature: 0.9,
topP: 0.95,
topK: 16
},
tools: [
{
functionDeclarations: this.tools.map(tool => tool.function())
}
]
}
body.contents.forEach(content => {
delete content.id
delete content.parentMessageId
delete content.conversationId
})
let result = await newFetch(url, {
method: 'POST',
body: JSON.stringify(body)
})
if (result.status !== 200) {
throw new Error(await result.text())
}
/**
* @type {Content | undefined}
*/
let responseContent
/**
* @type {{candidates: Array<{content: Content}>}}
*/
let response = await result.json()
if (this.debug) {
console.log(JSON.stringify(response))
}
responseContent = response.candidates[0].content
if (responseContent.parts[0].functionCall) {
// functionCall
const functionCall = responseContent.parts[0].functionCall
// Gemini有时候只回复一个空的functionCall,无语死了
if (functionCall.name) {
logger.info(JSON.stringify(functionCall))
const funcName = functionCall.name
let chosenTool = this.tools.find(t => t.name === funcName)
/**
* @type {FunctionResponse}
*/
let functionResponse = {
name: funcName,
response: {
name: funcName,
content: null
}
}
if (!chosenTool) {
// 根本没有这个工具!
functionResponse.response.content = {
error: `Function ${funcName} doesn't exist`
}
} else {
// execute function
try {
let args = Object.assign(functionCall.args, {
isAdmin: this.e.group.is_admin,
isOwner: this.e.group.is_owner,
sender: this.e.sender
})
functionResponse.response.content = await chosenTool.func(args, this.e)
if (this.debug) {
logger.info(JSON.stringify(functionResponse.response.content))
}
} catch (err) {
logger.error(err)
functionResponse.response.content = {
error: `Function execute error: ${err.message}`
}
}
}
let responseOpt = _.cloneDeep(opt)
responseOpt.parentMessageId = idModel
responseOpt.functionResponse = functionResponse
// 递归直到返回text
// 先把这轮的消息存下来
await this.upsertMessage(thisMessage)
const respMessage = Object.assign(responseContent, {
id: idModel,
parentMessageId: idThis
})
await this.upsertMessage(respMessage)
return await this.sendMessage('', responseOpt)
} else {
// 谷歌抽风了,瞎调函数,不保存这轮,直接返回
return {
text: '',
conversationId: '',
parentMessageId: opt.parentMessageId,
id: '',
error: true
}
}
}
if (responseContent) {
await this.upsertMessage(thisMessage)
const respMessage = Object.assign(responseContent, {
id: idModel,
parentMessageId: idThis
})
await this.upsertMessage(respMessage)
}
return {
text: responseContent.parts[0].text,
conversationId: '',
parentMessageId: idThis,
id: idModel
}
}
}

View file

@ -0,0 +1,158 @@
import { BaseClient } from './BaseClient.js'
import { getMessageById, upsertMessage } from '../utils/common.js'
import crypto from 'crypto'
let GoogleGenerativeAI, HarmBlockThreshold, HarmCategory
try {
const GenerativeAI = await import('@google/generative-ai')
GoogleGenerativeAI = GenerativeAI.GoogleGenerativeAI
HarmBlockThreshold = GenerativeAI.HarmBlockThreshold
HarmCategory = GenerativeAI.HarmCategory
} catch (err) {
console.warn('未安装@google/generative-ai无法使用Gemini请在chatgpt-plugin目录下执行pnpm i安装新依赖')
}
export class GoogleGeminiClient extends BaseClient {
constructor (props) {
if (!GoogleGenerativeAI) {
throw new Error('未安装@google/generative-ai无法使用Gemini请在chatgpt-plugin目录下执行pnpm i安装新依赖')
}
if (!props.upsertMessage) {
props.upsertMessage = async function umGemini (message) {
return await upsertMessage(message, 'Gemini')
}
}
if (!props.getMessageById) {
props.getMessageById = async function umGemini (message) {
return await getMessageById(message, 'Gemini')
}
}
super(props)
this._key = props.key
this._client = new GoogleGenerativeAI(this._key)
this.model = this._client.getGenerativeModel({ model: props.model })
this.supportFunction = false
}
async getHistory (parentMessageId, userId = this.userId, opt = {}) {
const history = []
let cursor = parentMessageId
if (!cursor) {
return history
}
do {
let parentMessage = await this.getMessageById(cursor)
if (!parentMessage) {
break
} else {
history.push(parentMessage)
cursor = parentMessage.parentMessageId
if (!cursor) {
break
}
}
} while (true)
return history.reverse()
}
async sendMessage (text, opt) {
let history = await this.getHistory(opt.parentMessageId)
let systemMessage = opt.system
if (systemMessage) {
history = history.reverse()
history.push({
role: 'model',
parts: 'ok'
})
history.push({
role: 'user',
parts: systemMessage
})
history = history.reverse()
}
const idUser = crypto.randomUUID()
const idModel = crypto.randomUUID()
let responseText = ''
try {
const chat = this.model.startChat({
history,
// [
// {
// role: 'user',
// parts: 'Hello, I have 2 dogs in my house.'
// },
// {
// role: 'model',
// parts: 'Great to meet you. What would you like to know?'
// }
// ],
generationConfig: {
// todo configuration
maxOutputTokens: 1000,
temperature: 0.9,
topP: 0.95,
topK: 16
},
safetySettings: [
// todo configuration
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_NONE
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_NONE
}
]
})
if (opt.stream && (typeof opt.onProgress === 'function')) {
const result = await chat.sendMessageStream(text)
responseText = ''
for await (const chunk of result.stream) {
const chunkText = chunk.text()
responseText += chunkText
await opt.onProgress(responseText)
}
return {
text: responseText,
conversationId: '',
parentMessageId: idUser,
id: idModel
}
}
const result = await chat.sendMessage(text)
const response = await result.response
responseText = response.text()
return {
text: responseText,
conversationId: '',
parentMessageId: idUser,
id: idModel
}
} finally {
await this.upsertMessage({
role: 'user',
parts: text,
id: idUser,
parentMessageId: opt.parentMessageId || undefined
})
await this.upsertMessage({
role: 'model',
parts: responseText,
id: idModel,
parentMessageId: idUser
})
}
}
async destroyHistory (conversationId, opt = {}) {
// todo clean history
}
}

View file

@ -0,0 +1,10 @@
import { GoogleGeminiClient } from './GoogleGeminiClient.js'
async function test () {
const client = new GoogleGeminiClient({
e: {},
userId: 'test',
key: '',
model: 'gemini-pro'
})
}

View file

@ -749,15 +749,42 @@ export function supportGuoba () {
component: 'Switch'
},
{
label: '以下为杂七杂八的配置',
label: '以下为Gemini方式的配置',
component: 'Divider'
},
{
field: '2captchaToken',
label: '验证码平台Token',
bottomHelpMessage: '可注册2captcha实现跳过验证码收费服务但很便宜。否则可能会遇到验证码而卡住',
field: 'geminiKey',
label: 'API密钥',
bottomHelpMessage: '前往https://makersuite.google.com/app/apikey获取',
component: 'InputPassword'
},
{
field: 'geminiModel',
label: '模型',
bottomHelpMessage: '目前仅支持gemini-pro',
component: 'Input'
},
{
field: 'geminiPrompt',
label: '设定',
component: 'InputTextArea'
},
{
field: 'geminiBaseUrl',
label: 'Gemini反代',
bottomHelpMessage: '对https://generativelanguage.googleapis.com的反代',
component: 'Input'
},
{
label: '以下为杂七杂八的配置',
component: 'Divider'
},
// {
// field: '2captchaToken',
// label: '验证码平台Token',
// bottomHelpMessage: '可注册2captcha实现跳过验证码收费服务但很便宜。否则可能会遇到验证码而卡住',
// component: 'InputPassword'
// },
{
field: 'ttsSpace',
label: 'vits-uma-genshin-honkai语音转换API地址',

1483
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,11 +8,9 @@
"@fastify/cors": "^8.2.0",
"@fastify/static": "^6.9.0",
"@fastify/websocket": "^8.2.0",
"@google/generative-ai": "^0.1.1",
"@slack/bolt": "^3.13.2",
"@waylaidwanderer/chatgpt-api": "^1.37.1",
"asn1.js": "^5.0.0",
"chatgpt": "^5.2.4",
"crypto": "^1.0.1",
"delay": "^6.0.0",
"diff": "^5.1.0",
"emoji-strip": "^1.0.1",
@ -24,6 +22,7 @@
"js-tiktoken": "^1.0.5",
"keyv": "^4.5.3",
"keyv-file": "^0.2.0",
"lodash": "^4.17.21",
"microsoft-cognitiveservices-speech-sdk": "1.32.0",
"node-fetch": "^3.3.1",
"openai": "^3.2.1",
@ -35,21 +34,26 @@
"ws": "^8.13.0"
},
"optionalDependencies": {
"xlsx": "^0.18.5",
"mammoth": "^1.6.0",
"pdfjs-dist": "^3.11.174",
"nodejs-pptx": "^1.2.4",
"@node-rs/jieba": "^1.6.2",
"cycletls": "^1.0.21",
"jimp": "^0.22.7",
"mammoth": "^1.6.0",
"node-silk": "^0.1.0",
"nodejs-pptx": "^1.2.4",
"pdfjs-dist": "^3.11.174",
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
"puppeteer-extra-plugin-stealth": "^2.11.2",
"sharp": "^0.32.3"
"sharp": "^0.32.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
"ts-node": "^10.9.1",
"ts-node-register": "^1.0.0"
},
"pnpm": {
"patchedDependencies": {
"@google/generative-ai@0.1.1": "patches/@google__generative-ai@0.1.1.patch"
}
}
}

View file

@ -0,0 +1,26 @@
diff --git a/dist/index.js b/dist/index.js
index c71c104e7b8ee70ed1b5a5141d04c98109fe6439..2dd8b1f93de0e502729cb91c9618bf80e8559e1e 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -152,7 +152,7 @@ class GoogleGenerativeAIResponseError extends GoogleGenerativeAIError {
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const BASE_URL = "https://generativelanguage.googleapis.com";
+const BASE_URL = "https://gemini.ikechan8370.com";
const API_VERSION = "v1";
/**
* We can't `require` package.json if this runs on web. We will use rollup to
diff --git a/dist/index.mjs b/dist/index.mjs
index 402a0c7fa5b692dea07d2dfd83e0148f0a493ca2..c48ce6d612a8752a5161da574804e7a830700d2c 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -150,7 +150,7 @@ class GoogleGenerativeAIResponseError extends GoogleGenerativeAIError {
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const BASE_URL = "https://generativelanguage.googleapis.com";
+const BASE_URL = "https://gemini.ikechan8370.com";
const API_VERSION = "v1";
/**
* We can't `require` package.json if this runs on web. We will use rollup to

View file

@ -52,38 +52,42 @@
"model": "redisConfig",
"data": "useMode",
"items": [
{
"label": "必应",
"value": "bing"
},
{
"label": "Azure OpenAI",
"value": "azure"
},
{
"label": "ChatGPT API",
"value": "api"
},
{
"label": "ChatGPT API3",
"value": "api3"
},
{
"label": "Slack Claude",
"value": "claude"
},
{
"label": "ChatGLM",
"value": "chatglm"
"label": "必应",
"value": "bing"
},
{
"label": "星火",
"value": "xh"
},
{
"label": "Slack Claude",
"value": "claude"
},
{
"label": "Gemini",
"value": "gemini"
},
{
"label": "Azure OpenAI",
"value": "azure"
},
{
"label": "Bard",
"value": "bard"
},
{
"label": "ChatGPT API3",
"value": "api3"
},
{
"label": "ChatGLM",
"value": "chatglm"
},
{
"label": "浏览器",
"value": "browser"
@ -460,86 +464,6 @@
}
]
},
{
"title": "API3",
"tab": "api3",
"view": [
{
"type": "url",
"label": "ChatGPT API反代服务器地址",
"placeholder": "ChatGPT的API反代服务器用于绕过Cloudflare访问ChatGPT API。",
"data": "api"
},
{
"type": "url",
"label": "apiBaseUrl地址",
"data": "apiBaseUrl"
},
{
"type": "password",
"label": "OpenAI refreshToken",
"placeholder": "OpenAI的refreshToken用于刷新Access Token",
"data": "OpenAiPlatformRefreshToken"
},
{
"type": "password",
"label": "OpenAI AccessToken",
"model": "redisConfig",
"data": "openAiPlatformAccessToken"
},
{
"type": "check",
"label": "强制使用ChatGPT反代",
"data": "apiForceUseReverse"
},
{
"type": "check",
"label": "使用GPT-4",
"data": "useGPT4"
}
]
},
{
"title": "浏览器",
"tab": "browser",
"view": [
{
"type": "check",
"label": "无头模式",
"data": "headless"
},
{
"type": "text",
"label": "用户名",
"placeholder": "OpenAI用户名",
"data": "username"
},
{
"type": "password",
"label": "密码",
"placeholder": "OpenAI密码",
"data": "password"
},
{
"type": "text",
"label": "Chrome路径",
"placeholder": "为空使用默认puppeteer的chromium也可以传递自己本机安装的Chrome可执行文件地址提高通过率。",
"data": "chromePath"
},
{
"type": "textarea",
"label": "浏览器UA",
"placeholder": "模拟浏览器UA无特殊需求保持默认即可",
"data": "UA"
},
{
"type": "password",
"label": "验证码平台Token",
"placeholder": "可注册2captcha实现跳过验证码",
"data": "2captchaToken"
}
]
},
{
"title": "必应",
"tab": "bing",
@ -645,17 +569,6 @@
}
]
},
{
"title": "ChatGLM",
"tab": "chatglm",
"view": [
{
"type": "url",
"label": "ChatGLM API地址",
"data": "chatglmBaseUrl"
}
]
},
{
"title": "Slack Claude",
"tab": "claude",
@ -795,6 +708,34 @@
}
]
},
{
"title": "Gemini",
"tab": "gemini",
"view": [
{
"type": "password",
"label": "API密钥",
"data": "geminiKey"
},
{
"type": "text",
"label": "模型",
"placeholder": "目前仅支持gemini-pro",
"data": "geminiModel"
},
{
"type": "textarea",
"label": "设定",
"data": "geminiPrompt"
},
{
"type": "url",
"label": "Gemini反代",
"placeholder": "对https://generativelanguage.googleapis.com的反代",
"data": "geminiBaseUrl"
}
]
},
{
"title": "Bard",
"tab": "bard",
@ -836,6 +777,91 @@
"data": "azureDeploymentName"
}
]
},
{
"title": "ChatGLM",
"tab": "chatglm",
"view": [
{
"type": "url",
"label": "ChatGLM API地址",
"data": "chatglmBaseUrl"
}
]
},
{
"title": "浏览器",
"tab": "browser",
"view": [
{
"type": "check",
"label": "无头模式",
"data": "headless"
},
{
"type": "text",
"label": "用户名",
"placeholder": "OpenAI用户名",
"data": "username"
},
{
"type": "password",
"label": "密码",
"placeholder": "OpenAI密码",
"data": "password"
},
{
"type": "text",
"label": "Chrome路径",
"placeholder": "为空使用默认puppeteer的chromium也可以传递自己本机安装的Chrome可执行文件地址提高通过率。",
"data": "chromePath"
},
{
"type": "textarea",
"label": "浏览器UA",
"placeholder": "模拟浏览器UA无特殊需求保持默认即可",
"data": "UA"
}
]
},
{
"title": "API3",
"tab": "api3",
"view": [
{
"type": "url",
"label": "ChatGPT API反代服务器地址",
"placeholder": "ChatGPT的API反代服务器用于绕过Cloudflare访问ChatGPT API。",
"data": "api"
},
{
"type": "url",
"label": "apiBaseUrl地址",
"data": "apiBaseUrl"
},
{
"type": "password",
"label": "OpenAI refreshToken",
"placeholder": "OpenAI的refreshToken用于刷新Access Token",
"data": "OpenAiPlatformRefreshToken"
},
{
"type": "password",
"label": "OpenAI AccessToken",
"model": "redisConfig",
"data": "openAiPlatformAccessToken"
},
{
"type": "check",
"label": "强制使用ChatGPT反代",
"data": "apiForceUseReverse"
},
{
"type": "check",
"label": "使用GPT-4",
"data": "useGPT4"
}
]
}
]
}

View file

@ -1030,6 +1030,25 @@ export function getUserSpeaker (userSetting) {
}
}
/**
* 获取或者下载文件如果文件存在则直接返回不会重新下载
* @param destPath 相对路径如received/abc.pdf
* @param url
* @param ignoreCertificateError 忽略证书错误
* @return {Promise<string>} 最终下载文件的存储位置
*/
export async function getOrDownloadFile (destPath, url, ignoreCertificateError = true) {
const _path = process.cwd()
let dest = path.join(_path, 'data', 'chatgpt', destPath)
const p = path.dirname(dest)
mkdirs(p)
if (fs.existsSync(dest)) {
return dest
} else {
return await downloadFile(url, destPath, false, ignoreCertificateError)
}
}
/**
*
* @param url 要下载的文件链接

View file

@ -163,6 +163,11 @@ const defaultConfig = {
qwenSeed: 0,
qwenTemperature: 1,
qwenEnableSearch: true,
geminiKey: '',
geminiModel: 'gemini-pro',
geminiPrompt: 'You are Gemini. Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.',
// origin: https://generativelanguage.googleapis.com
geminiBaseUrl: 'https://gemini.ikechan8370.com',
version: 'v2.7.8'
}
const _path = process.cwd()

View file

@ -80,7 +80,7 @@ export async function imageVariation (imageUrl, n = 1, size = '512x512') {
return response.data.data?.map(pic => pic.b64_json)
}
async function resizeAndCropImage (inputFilePath, outputFilePath, size = 512) {
export async function resizeAndCropImage (inputFilePath, outputFilePath, size = 512) {
// Determine the maximum dimension of the input image
let sharp
try {

View file

@ -1,5 +1,7 @@
// workaround for ver 7.x and ver 5.x
import HttpsProxyAgent from 'https-proxy-agent'
import { Config } from './config.js'
import fetch from 'node-fetch'
let proxy = HttpsProxyAgent
if (typeof proxy !== 'function') {
@ -15,3 +17,17 @@ if (typeof proxy !== 'function') {
export function getProxy () {
return proxy
}
export const newFetch = (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
}

View file

@ -1,5 +1,5 @@
import { Config } from './config.js'
import { ChatGPTAPI } from 'chatgpt'
import { ChatGPTAPI } from './openai/chatgpt-api.js'
import fetch from 'node-fetch'
import { getProxy } from './proxy.js'
let proxy = getProxy()

View file

@ -19,6 +19,9 @@ export class SendPictureTool extends AbstractTool {
func = async function (opt, e) {
let { urlOfPicture, targetGroupIdOrQQNumber } = opt
if (typeof urlOfPicture === 'object') {
urlOfPicture = urlOfPicture.join(' ')
}
const defaultTarget = e.isGroup ? e.group_id : e.sender.user_id
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? defaultTarget

View file

@ -19,7 +19,7 @@ export class SerpIkechan8370Tool extends AbstractTool {
func = async function (opts) {
let { q, source } = opts
if (!source) {
if (!source || !['google', 'bing', 'baidu'].includes(source)) {
source = 'bing'
}
let serpRes = await fetch(`https://serp.ikechan8370.com/${source}?q=${encodeURIComponent(q)}&lang=zh-CN&limit=5`, {

View file

@ -34,7 +34,7 @@ export class SetTitleTool extends AbstractTool {
return `failed, the user ${qq} is not in group ${groupId}`
}
if (mm.get(e.bot.uin).role !== 'owner') {
return 'on group owner can give title'
return 'failed, only group owner can give title'
}
logger.info('edit card: ', groupId, qq)
let result = await group.setTitle(qq, title)

982
yarn.lock

File diff suppressed because it is too large Load diff