Merge pull request #8 from ikechan8370/v2

v2
This commit is contained in:
ycxom 2025-02-17 02:01:51 +08:00 committed by GitHub
commit eddb7c7685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 106 additions and 171 deletions

View file

@ -238,11 +238,11 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
// ANY要笑死人的效果 // ANY要笑死人的效果
let mode = opt.toolMode || 'AUTO' let mode = opt.toolMode || 'AUTO'
let lastFuncName = opt.functionResponse?.name let lastFuncName = (/** @type {FunctionResponse[] | undefined}**/ opt.functionResponse)?.map(rsp => rsp.name)
const mustSendNextTurn = [ const mustSendNextTurn = [
'searchImage', 'searchMusic', 'searchVideo' 'searchImage', 'searchMusic', 'searchVideo'
] ]
if (lastFuncName && mustSendNextTurn.includes(lastFuncName)) { if (lastFuncName && lastFuncName?.find(name => mustSendNextTurn.includes(name))) {
mode = 'ANY' mode = 'ANY'
} }
body.tool_config = { body.tool_config = {
@ -328,7 +328,7 @@ export class CustomGoogleGeminiClient extends GoogleGeminiClient {
let args = Object.assign(fc.args, { let args = Object.assign(fc.args, {
isAdmin, isAdmin,
isOwner, isOwner,
sender: this.e.sender, sender: this.e.sender.user_id,
mode: 'gemini' mode: 'gemini'
}) })
functionResponse.response.content = await chosenTool.func(args, this.e) functionResponse.response.content = await chosenTool.func(args, this.e)

View file

@ -75,12 +75,12 @@ export function supportGuoba () {
bottomHelpMessage: '独立的后台管理面板默认3321端口与锅巴类似。工具箱会有额外占用启动速度稍慢酌情开启。修改后需重启生效', bottomHelpMessage: '独立的后台管理面板默认3321端口与锅巴类似。工具箱会有额外占用启动速度稍慢酌情开启。修改后需重启生效',
component: 'Switch' component: 'Switch'
}, },
// { {
// field: 'enableMd', field: 'enableToolPrivateSend',
// label: 'QQ开启markdown', label: '允许智能模式私聊',
// bottomHelpMessage: 'qq的第三方md非QQBot。需要适配器实现segment.markdown和segment.button方可使用否则不建议开启会造成各种错误。默认关闭', bottomHelpMessage: '是否允许智能模式下发起临时对话骚扰其他群友。默认开启如果怕Bot乱骚扰其他人可以关闭。主人不受影响。',
// component: 'Switch' component: 'Switch'
// }, },
{ {
field: 'translateSource', field: 'translateSource',
label: '翻译来源', label: '翻译来源',

View file

@ -29,7 +29,7 @@ import { SendAvatarTool } from '../utils/tools/SendAvatarTool.js'
import { SerpImageTool } from '../utils/tools/SearchImageTool.js' import { SerpImageTool } from '../utils/tools/SearchImageTool.js'
import { SearchMusicTool } from '../utils/tools/SearchMusicTool.js' import { SearchMusicTool } from '../utils/tools/SearchMusicTool.js'
import { SendMusicTool } from '../utils/tools/SendMusicTool.js' import { SendMusicTool } from '../utils/tools/SendMusicTool.js'
import { SendAudioMessageTool } from '../utils/tools/SendAudioMessageTool.js' // import { SendAudioMessageTool } from '../utils/tools/SendAudioMessageTool.js'
import { SendMessageToSpecificGroupOrUserTool } from '../utils/tools/SendMessageToSpecificGroupOrUserTool.js' import { SendMessageToSpecificGroupOrUserTool } from '../utils/tools/SendMessageToSpecificGroupOrUserTool.js'
import { QueryGenshinTool } from '../utils/tools/QueryGenshinTool.js' import { QueryGenshinTool } from '../utils/tools/QueryGenshinTool.js'
import { WeatherTool } from '../utils/tools/WeatherTool.js' import { WeatherTool } from '../utils/tools/WeatherTool.js'
@ -42,11 +42,11 @@ import { SerpIkechan8370Tool } from '../utils/tools/SerpIkechan8370Tool.js'
import { SerpTool } from '../utils/tools/SerpTool.js' import { SerpTool } from '../utils/tools/SerpTool.js'
import common from '../../../lib/common/common.js' import common from '../../../lib/common/common.js'
import { SendDiceTool } from '../utils/tools/SendDiceTool.js' import { SendDiceTool } from '../utils/tools/SendDiceTool.js'
import { EliMovieTool } from '../utils/tools/EliMovieTool.js' // import { EliMovieTool } from '../utils/tools/EliMovieTool.js'
import { EliMusicTool } from '../utils/tools/EliMusicTool.js' // import { EliMusicTool } from '../utils/tools/EliMusicTool.js'
import { HandleMessageMsgTool } from '../utils/tools/HandleMessageMsgTool.js' import { HandleMessageMsgTool } from '../utils/tools/HandleMessageMsgTool.js'
import { ProcessPictureTool } from '../utils/tools/ProcessPictureTool.js' import { ProcessPictureTool } from '../utils/tools/ProcessPictureTool.js'
import { ImageCaptionTool } from '../utils/tools/ImageCaptionTool.js' // import { ImageCaptionTool } from '../utils/tools/ImageCaptionTool.js'
import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js' import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
import { newFetch } from '../utils/proxy.js' import { newFetch } from '../utils/proxy.js'
import { ChatGLM4Client } from '../client/ChatGLM4Client.js' import { ChatGLM4Client } from '../client/ChatGLM4Client.js'
@ -530,56 +530,10 @@ class Core {
option.image = base64Image.toString('base64') option.image = base64Image.toString('base64')
} }
if (opt.enableSmart) { if (opt.enableSmart) {
/** const {
* @type {AbstractTool[]} funcMap
*/ } = await collectTools(e)
let tools = [ let tools = Object.keys(funcMap).map(k => funcMap[k].tool)
new QueryStarRailTool(),
new WebsiteTool(),
new SendPictureTool(),
new SendVideoTool(),
new SearchVideoTool(),
new SendAvatarTool(),
new SerpImageTool(),
new SearchMusicTool(),
new SendMusicTool(),
new SendAudioMessageTool(),
new APTool(),
new SendMessageToSpecificGroupOrUserTool(),
new QueryGenshinTool()
]
if (Config.amapKey) {
tools.push(new WeatherTool())
}
if (e.isGroup) {
tools.push(new QueryUserinfoTool())
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) client.addTools(tools)
} }
let system = opt.system.gemini let system = opt.system.gemini
@ -821,12 +775,12 @@ async function collectTools (e) {
new SetTitleTool() new SetTitleTool()
] ]
// todo 3.0再重构tool的插拔和管理 // todo 3.0再重构tool的插拔和管理
let /** @type{AbstractTool} **/ tools = [ let /** @type{AbstractTool[]} **/ tools = [
new SendAvatarTool(), new SendAvatarTool(),
new SendDiceTool(), new SendDiceTool(),
new SendMessageToSpecificGroupOrUserTool(), new SendMessageToSpecificGroupOrUserTool(),
// new EditCardTool(), // new EditCardTool(),
// new QueryStarRailTool(), new QueryStarRailTool(),
new QueryGenshinTool(), new QueryGenshinTool(),
new SendMusicTool(), new SendMusicTool(),
new SearchMusicTool(), new SearchMusicTool(),
@ -873,13 +827,15 @@ async function collectTools (e) {
tools.forEach(tool => { tools.forEach(tool => {
funcMap[tool.name] = { funcMap[tool.name] = {
exec: tool.func, exec: tool.func,
function: tool.function() function: tool.function(),
tool
} }
}) })
fullTools.forEach(tool => { fullTools.forEach(tool => {
fullFuncMap[tool.name] = { fullFuncMap[tool.name] = {
exec: tool.func, exec: tool.func,
function: tool.function() function: tool.function(),
tool
} }
}) })
return { return {

View file

@ -227,7 +227,8 @@ const defaultConfig = {
_2captchaKey: '', _2captchaKey: '',
bingReasoning: false, // 是否深度思考 bingReasoning: false, // 是否深度思考
apiMaxToken: 4096, apiMaxToken: 4096,
version: 'v2.8.3' enableToolPrivateSend: true, // 是否允许智能模式下私聊骚扰其他群友。主人不受影响。
version: 'v2.8.4'
} }
const _path = process.cwd() const _path = process.cwd()
let config = {} let config = {}

View file

@ -24,7 +24,7 @@ export class EditCardTool extends AbstractTool {
description = 'Useful when you want to edit someone\'s card in the group(群名片)' description = 'Useful when you want to edit someone\'s card in the group(群名片)'
func = async function (opts, e) { func = async function (opts, e) {
let { qq, card, groupId, isAdmin } = opts let { qq, card, groupId, sender, isAdmin } = opts
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim()) qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
groupId = isNaN(groupId) || !groupId ? e.group_id : parseInt(groupId.trim()) groupId = isNaN(groupId) || !groupId ? e.group_id : parseInt(groupId.trim())
@ -41,7 +41,7 @@ export class EditCardTool extends AbstractTool {
logger.error('获取群信息失败,可能使用的底层协议不完善') logger.error('获取群信息失败,可能使用的底层协议不完善')
} }
logger.info('edit card: ', groupId, qq) logger.info('edit card: ', groupId, qq)
if (isAdmin) { if (isAdmin || sender == qq) {
await group.setCard(qq, card) await group.setCard(qq, card)
} else { } else {
return 'the user is not admin, he can\'t edit card of other people.' return 'the user is not admin, he can\'t edit card of other people.'

View file

@ -15,7 +15,7 @@ export class JinyanTool extends AbstractTool {
}, },
time: { time: {
type: 'string', type: 'string',
description: '禁言时长单位为秒默认为600' description: '禁言时长单位为秒默认为600。如果需要解除禁言则填0.'
}, },
isPunish: { isPunish: {
type: 'string', type: 'string',

View file

@ -22,65 +22,29 @@ export class QueryStarRailTool extends AbstractTool {
} }
func = async function (opts, e) { func = async function (opts, e) {
let { qq, uid, character } = opts let { qq, uid = '', character = '' } = opts
qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim()) qq = isNaN(qq) || !qq ? e.sender.user_id : parseInt(qq.trim())
if (e.at === e.bot.uin) { if (e.at === e.bot.uin) {
e.at = null e.at = null
} }
e.atBot = false e.atBot = false
if (!uid) {
try { try {
let { Panel } = await import('../../../StarRail-plugin/apps/panel.js') if (character) {
uid = await redis.get(`STAR_RAILWAY:UID:${qq}`) let ProfileDetail = (await import('../../../miao-plugin/apps/profile/ProfileDetail.js')).default
if (!uid) { // e.msg = `#${character}面板${uid}`
return '用户没有绑定uid无法查询。可以让用户主动提供uid进行查询' e.original_msg = `*${character}面板${uid}`
} e.user_id = parseInt(qq)
} catch (e) { e.isSr = true
// todo support miao-plugin and sruid await ProfileDetail.detail(e)
return '未安装StarRail-Plugin无法查询' return 'the character panel of star rail has been sent to group. you don\'t need text version'
} } else {
} let ProfileList = (await import('../../../miao-plugin/apps/profile/ProfileList.js')).default
try { e.msg = `*面板${uid}`
let { Panel } = await import('../../../StarRail-plugin/apps/panel.js')
e.msg = character ? `*${character}面板${uid}` : '*更新面板' + uid
e.user_id = qq e.user_id = qq
e.isSr = true e.isSr = true
let panel = new Panel(e) await ProfileList.render(e)
panel.e = e return 'the player panel of genshin impact has been sent to group. you don\'t need text version'
panel.panel(e).catch(e => logger.warn(e))
let uidRes = await fetch('https://avocado.wiki/v1/info/' + uid)
uidRes = await uidRes.json()
let { assistAvatar, displayAvatars } = uidRes.playerDetailInfo
function dealAvatar (avatar) {
delete avatar.position
delete avatar.vo_tag
delete avatar.desc
delete avatar.promption
delete avatar.relics
delete avatar.behaviorList
delete avatar.images
delete avatar.ranks
if (avatar.equipment) {
avatar.equipment = {
level: avatar.equipment.level,
rank: avatar.equipment.rank,
name: avatar.equipment.name,
skill_desc: avatar.equipment.skill_desc
} }
}
}
dealAvatar(assistAvatar)
if (displayAvatars) {
displayAvatars.forEach(avatar => {
dealAvatar(avatar)
})
}
uidRes.playerDetailInfo.assistAvatar = assistAvatar
uidRes.playerDetailInfo.displayAvatars = displayAvatars
delete uidRes.repository
delete uidRes.version
return `the player info in json format is: \n${JSON.stringify(uidRes)}`
} catch (err) { } catch (err) {
return `failed to query, error: ${err.toString()}` return `failed to query, error: ${err.toString()}`
} }

View file

@ -1,5 +1,7 @@
import { AbstractTool } from './AbstractTool.js' import { AbstractTool } from './AbstractTool.js'
import { convertFaces } from '../face.js' import { convertFaces } from '../face.js'
import {getMasterQQ} from '../common.js'
import {Config} from '../config.js'
export class SendMessageToSpecificGroupOrUserTool extends AbstractTool { export class SendMessageToSpecificGroupOrUserTool extends AbstractTool {
name = 'sendMessage' name = 'sendMessage'
@ -19,7 +21,7 @@ export class SendMessageToSpecificGroupOrUserTool extends AbstractTool {
} }
func = async function (opt, e) { func = async function (opt, e) {
let { msg, targetGroupIdOrQQNumber } = opt let { msg, sender, targetGroupIdOrQQNumber } = opt
const defaultTarget = e.isGroup ? e.group_id : e.sender.user_id const defaultTarget = e.isGroup ? e.group_id : e.sender.user_id
const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber const target = isNaN(targetGroupIdOrQQNumber) || !targetGroupIdOrQQNumber
? defaultTarget ? defaultTarget
@ -37,6 +39,10 @@ export class SendMessageToSpecificGroupOrUserTool extends AbstractTool {
await group.sendMsg(await convertFaces(msg, true, e)) await group.sendMsg(await convertFaces(msg, true, e))
return 'msg has been sent to group' + target return 'msg has been sent to group' + target
} else { } else {
let masters = (await getMasterQQ())
if (!Config.enableToolPrivateSend && !masters.includes(sender + '')) {
return 'you are not allowed to pm other group members'
}
let user = e.bot.pickUser(target) let user = e.bot.pickUser(target)
if (e.group_id) { if (e.group_id) {
user = user.asMember(e.group_id) user = user.asMember(e.group_id)

View file

@ -1,4 +1,6 @@
import { AbstractTool } from './AbstractTool.js' import { AbstractTool } from './AbstractTool.js'
import {getMasterQQ} from '../common.js'
import {Config} from '../config.js'
export class SendPictureTool extends AbstractTool { export class SendPictureTool extends AbstractTool {
name = 'sendPicture' name = 'sendPicture'
@ -18,7 +20,7 @@ export class SendPictureTool extends AbstractTool {
} }
func = async function (opt, e) { func = async function (opt, e) {
let { urlOfPicture, targetGroupIdOrQQNumber } = opt let { urlOfPicture, targetGroupIdOrQQNumber, sender } = opt
if (typeof urlOfPicture === 'object') { if (typeof urlOfPicture === 'object') {
urlOfPicture = urlOfPicture.join(' ') urlOfPicture = urlOfPicture.join(' ')
} }
@ -55,6 +57,10 @@ export class SendPictureTool extends AbstractTool {
// await group.sendMsg(pictures) // await group.sendMsg(pictures)
return 'picture has been sent to group' + target + (errs.length > 0 ? `, but some pictures failed to send (${errs.join('、')})` : '') return 'picture has been sent to group' + target + (errs.length > 0 ? `, but some pictures failed to send (${errs.join('、')})` : '')
} else { } else {
let masters = (await getMasterQQ())
if (!Config.enableToolPrivateSend && !masters.includes(sender + '')) {
return 'you are not allowed to pm other group members'
}
let user = e.bot.pickUser(target) let user = e.bot.pickUser(target)
if (e.group_id) { if (e.group_id) {
user = user.asMember(e.group_id) user = user.asMember(e.group_id)

View file

@ -11,7 +11,8 @@ export class SerpIkechan8370Tool extends AbstractTool {
}, },
source: { source: {
type: 'string', type: 'string',
enum: ['google', 'bing', 'baidu'] enum: ['google', 'bing', 'baidu', 'duckduckgo'],
description: 'search source, default value is bing'
} }
}, },
required: ['q'] required: ['q']
@ -19,7 +20,7 @@ export class SerpIkechan8370Tool extends AbstractTool {
func = async function (opts) { func = async function (opts) {
let { q, source } = opts let { q, source } = opts
if (!source || !['google', 'bing', 'baidu'].includes(source)) { if (!source || !['google', 'bing', 'baidu', 'duckduckgo'].includes(source)) {
source = 'bing' source = 'bing'
} }
let serpRes = await fetch(`https://serp.ikechan8370.com/${source}?q=${encodeURIComponent(q)}&lang=zh-CN&limit=5`, { let serpRes = await fetch(`https://serp.ikechan8370.com/${source}?q=${encodeURIComponent(q)}&lang=zh-CN&limit=5`, {
@ -29,11 +30,11 @@ export class SerpIkechan8370Tool extends AbstractTool {
}) })
serpRes = await serpRes.json() serpRes = await serpRes.json()
let res = serpRes.data let res = serpRes.data || serpRes.results
res?.forEach(r => { res?.forEach(r => {
delete r?.rank delete r?.rank
}) })
return `the search results are here in json format:\n${JSON.stringify(res)}` return `the search results are here in json format:\n${JSON.stringify(res)} \n(Notice that these information are only available for you, the user cannot see them, you next answer should consider about the information)`
} }
description = 'Useful when you want to search something from the Internet. If you don\'t know much about the user\'s question, prefer to search about it! If you want to know further details of a result, you can use website tool' description = 'Useful when you want to search something from the Internet. If you don\'t know much about the user\'s question, prefer to search about it! If you want to know further details of a result, you can use website tool'

View file

@ -61,48 +61,49 @@ export class WebsiteTool extends AbstractTool {
.replace(/\s{2}/g, '') // 多个空格只保留一个空格 .replace(/\s{2}/g, '') // 多个空格只保留一个空格
.replace('<!DOCTYPE html>', '') // 去除<!DOCTYPE>声明 .replace('<!DOCTYPE html>', '') // 去除<!DOCTYPE>声明
if (mode === 'gemini') { // if (mode === 'gemini') {
let client = new CustomGoogleGeminiClient({ // let client = new CustomGoogleGeminiClient({
e, // e,
userId: e?.sender?.user_id, // userId: e?.sender?.user_id,
key: Config.getGeminiKey(), // key: Config.getGeminiKey(),
model: Config.geminiModel, // model: Config.geminiModel,
baseUrl: Config.geminiBaseUrl, // baseUrl: Config.geminiBaseUrl,
debug: Config.debug // debug: Config.debug
}) // })
const htmlContentSummaryRes = await client.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`) // const htmlContentSummaryRes = await client.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`)
let htmlContentSummary = htmlContentSummaryRes.text // let htmlContentSummary = htmlContentSummaryRes.text
return `this is the main content of website:\n ${htmlContentSummary}` // return `this is the main content of website:\n ${htmlContentSummary}`
} else { // } else {
let maxModelTokens = getMaxModelTokens(Config.model) // let maxModelTokens = getMaxModelTokens(Config.model)
text = text.slice(0, Math.min(text.length, maxModelTokens - 1600)) // text = text.slice(0, Math.min(text.length, maxModelTokens - 1600))
let completionParams = { // let completionParams = {
// model: Config.model // // model: Config.model
model: 'gpt-3.5-turbo-16k' // model: 'gpt-3.5-turbo-16k'
} // }
let api = new ChatGPTAPI({ // let api = new ChatGPTAPI({
apiBaseUrl: Config.openAiBaseUrl, // apiBaseUrl: Config.openAiBaseUrl,
apiKey: Config.apiKey, // apiKey: Config.apiKey,
debug: false, // debug: false,
completionParams, // completionParams,
fetch: (url, options = {}) => { // fetch: (url, options = {}) => {
const defaultOptions = Config.proxy // const defaultOptions = Config.proxy
? { // ? {
agent: proxy(Config.proxy) // agent: proxy(Config.proxy)
} // }
: {} // : {}
const mergedOptions = { // const mergedOptions = {
...defaultOptions, // ...defaultOptions,
...options // ...options
} // }
return fetch(url, mergedOptions) // return fetch(url, mergedOptions)
}, // },
maxModelTokens // maxModelTokens
}) // })
const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`, { completionParams }) // const htmlContentSummaryRes = await api.sendMessage(`去除与主体内容无关的部分从中整理出主体内容并转换成md格式不需要主观描述性的语言与冗余的空白行。${text}`, { completionParams })
let htmlContentSummary = htmlContentSummaryRes.text // let htmlContentSummary = htmlContentSummaryRes.text
return `this is the main content of website:\n ${htmlContentSummary}` // return `this is the main content of website:\n ${htmlContentSummary}`
} // }
return `the content of the website is:\n${text}`
} catch (err) { } catch (err) {
return `failed to visit the website, error: ${err.toString()}` return `failed to visit the website, error: ${err.toString()}`
} finally { } finally {