mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 13:57:10 +00:00
feat: 初步支持function call(WIP)
This commit is contained in:
parent
4a4dceec18
commit
97b3acbf3b
24 changed files with 13607 additions and 841 deletions
20
utils/tools/AbstractTool.js
Normal file
20
utils/tools/AbstractTool.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export class AbstractTool {
|
||||
name = ''
|
||||
|
||||
parameters = {}
|
||||
|
||||
description = ''
|
||||
|
||||
func = async function () {}
|
||||
|
||||
function () {
|
||||
if (!this.parameters.type) {
|
||||
this.parameters.type = 'object'
|
||||
}
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
parameters: this.parameters
|
||||
}
|
||||
}
|
||||
}
|
||||
35
utils/tools/EditCardTool.js
Normal file
35
utils/tools/EditCardTool.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { AbstractTool } from './AbstractTool.js'
|
||||
|
||||
export class EditCardTool extends AbstractTool {
|
||||
name = 'editCard'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
qq: {
|
||||
type: 'string',
|
||||
description: '你想改名片的那个人的qq号'
|
||||
},
|
||||
card: {
|
||||
type: 'string',
|
||||
description: '你想给他改的新名片'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号'
|
||||
}
|
||||
},
|
||||
required: ['qq', 'card', 'groupId']
|
||||
}
|
||||
|
||||
description = '当你想要修改某个群员的群名片时有用。输入应该是群号、qq号和群名片,用空格隔开。'
|
||||
|
||||
func = async function (opts) {
|
||||
let {qq, card, groupId} = opts
|
||||
groupId = parseInt(groupId.trim())
|
||||
qq = parseInt(qq.trim())
|
||||
logger.info('edit card: ', groupId, qq)
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
await group.setCard(qq, card)
|
||||
return `the user ${qq}'s card has been changed into ${card}`
|
||||
}
|
||||
}
|
||||
38
utils/tools/JinyanTool.js
Normal file
38
utils/tools/JinyanTool.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { AbstractTool } from './AbstractTool.js'
|
||||
|
||||
export class JinyanTool extends AbstractTool {
|
||||
name = 'jinyan'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
qq: {
|
||||
type: 'string',
|
||||
description: '你想禁言的那个人的qq号'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号'
|
||||
},
|
||||
time: {
|
||||
type: 'string',
|
||||
description: '禁言时长,单位为秒'
|
||||
}
|
||||
},
|
||||
required: ['qq', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (opts) {
|
||||
let { qq, groupId, time = '600' } = opts
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
time = parseInt(time.trim())
|
||||
if (qq.trim() === 'all') {
|
||||
await group.muteAll(time > 0)
|
||||
} else {
|
||||
qq = parseInt(qq.trim())
|
||||
await group.muteMember(qq, time)
|
||||
}
|
||||
return `the user ${qq} has been muted for ${time} seconds`
|
||||
}
|
||||
|
||||
description = 'Useful when you want to ban someone. The input to this tool should be the group number, the qq number of the one who should be banned and the mute duration in seconds(at least 60, at most 180, the number should be an integer multiple of 60), these three number should be concated with a space. If you want to mute all, just replace the qq number with \'all\''
|
||||
}
|
||||
31
utils/tools/KickOutTool.js
Normal file
31
utils/tools/KickOutTool.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
export class KickOutTool extends AbstractTool {
|
||||
name = 'kickOut'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
qq: {
|
||||
type: 'string',
|
||||
description: '你想踢出的那个人的qq号'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号'
|
||||
}
|
||||
},
|
||||
required: ['qq', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (opts) {
|
||||
let { qq, groupId } = opts
|
||||
groupId = parseInt(groupId.trim())
|
||||
qq = parseInt(qq.trim())
|
||||
console.log('kickout', groupId, qq)
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
await group.kickMember(qq)
|
||||
return `the user ${qq} has been kicked out from group ${groupId}`
|
||||
}
|
||||
|
||||
description = 'Useful when you want to kick someone out of the group. The input to this tool should be the group number, the qq number of the one who should be kicked out, these two number should be concated with a space. '
|
||||
}
|
||||
33
utils/tools/SendAvatarTool.js
Normal file
33
utils/tools/SendAvatarTool.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
export class SendAvatarTool extends AbstractTool {
|
||||
name = 'sendAvatar'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
qq: {
|
||||
type: 'string',
|
||||
description: '要发头像的人的qq号'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号或qq号,发送目标'
|
||||
}
|
||||
},
|
||||
required: ['qq', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (opts) {
|
||||
let {qq, groupId} = opts
|
||||
let groupList = await Bot.getGroupList()
|
||||
groupId = parseInt(groupId.trim())
|
||||
console.log('sendAvatar', groupId, qq)
|
||||
if (groupList.get(groupId)) {
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
await group.sendMsg(segment.image('https://q1.qlogo.cn/g?b=qq&s=0&nk=' + qq))
|
||||
}
|
||||
return `the user ${qq}'s avatar has been sent to group ${groupId}`
|
||||
}
|
||||
|
||||
description = 'Useful when you want to send the user avatar picture to the group. The input to this tool should be the user\'s qq number and the target group number, and they should be concated with a space. 如果是在群聊中,优先选择群号发送。'
|
||||
}
|
||||
134
utils/tools/SendBilibiliTool.js
Normal file
134
utils/tools/SendBilibiliTool.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import fetch from 'node-fetch'
|
||||
|
||||
import { formatDate, mkdirs } from '../common.js'
|
||||
import fs from 'fs'
|
||||
import { AbstractTool } from './AbstractTool.js'
|
||||
export class SendVideoTool extends AbstractTool {
|
||||
name = 'sendVideo'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
description: '要发的视频的标题或关键词,用于搜索'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号或qq号,发送目标'
|
||||
}
|
||||
},
|
||||
required: ['keyword', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (opts) {
|
||||
let { keyword, groupId } = opts
|
||||
groupId = parseInt(groupId.trim())
|
||||
let msg = []
|
||||
try {
|
||||
let { arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like } = await searchBilibili(keyword)
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
console.log({ arcurl, title, pic, description, videoUrl })
|
||||
msg.push(title.replace(/(<([^>]+)>)/ig, '') + '\n')
|
||||
msg.push(`UP主:${author} 发布日期:${formatDate(new Date(pubdate * 1000))} 播放量:${play} 点赞:${like}\n`)
|
||||
msg.push(arcurl + '\n')
|
||||
msg.push(segment.image('https:' + pic))
|
||||
msg.push('\n' + description)
|
||||
msg.push('\n视频在路上啦!')
|
||||
await group.sendMsg(msg)
|
||||
const videoResponse = await fetch(videoUrl, { headers })
|
||||
const fileType = videoResponse.headers.get('Content-Type').split('/')[1]
|
||||
let fileLoc = `data/chatgpt/videos/${bvid}.${fileType}`
|
||||
mkdirs('data/chatgpt/videos')
|
||||
videoResponse.blob().then(async blob => {
|
||||
const arrayBuffer = await blob.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
await fs.writeFileSync(fileLoc, buffer)
|
||||
await group.sendMsg(segment.video(fileLoc))
|
||||
})
|
||||
return `the video ${title.replace(/(<([^>]+)>)/ig, '')} will be shared to ${groupId} after a while, please wait`
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
if (msg.length > 0) {
|
||||
return `fail to share video, but the video msg is found: ${msg}, you can just tell the information of this video`
|
||||
} else {
|
||||
return `fail to share video, error: ${err.toString()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
description = 'Useful when you want to share a video. The input should be the group number and the keywords that can find the video, connected with a space. If you want to send a specific video, you can give more detailed keywords'
|
||||
}
|
||||
|
||||
export async function searchBilibili (name) {
|
||||
let biliRes = await fetch('https://www.bilibili.com',
|
||||
{
|
||||
// headers: {
|
||||
// accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
// Accept: '*/*',
|
||||
// 'Accept-Encoding': 'gzip, deflate, br',
|
||||
// 'accept-language': 'en-US,en;q=0.9',
|
||||
// Connection: 'keep-alive',
|
||||
// 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
||||
// }
|
||||
})
|
||||
const headers = biliRes.headers.raw()
|
||||
const setCookieHeaders = headers['set-cookie']
|
||||
if (setCookieHeaders) {
|
||||
const cookies = []
|
||||
setCookieHeaders.forEach(header => {
|
||||
const cookie = header.split(';')[0]
|
||||
cookies.push(cookie)
|
||||
})
|
||||
const cookieHeader = cookies.join('; ')
|
||||
let headers = {
|
||||
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'accept-language': 'en-US,en;q=0.9',
|
||||
Referer: 'https://www.bilibili.com',
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
||||
cookie: cookieHeader
|
||||
}
|
||||
let response = await fetch(`https://api.bilibili.com/x/web-interface/search/type?keyword=${name}&search_type=video`,
|
||||
{
|
||||
headers
|
||||
})
|
||||
let json = await response.json()
|
||||
if (json.data?.numResults > 0) {
|
||||
let index = randomIndex()
|
||||
let { arcurl, title, pic, description, bvid, author, play, pubdate, like } = json.data.result[Math.min(index, json.data.numResults)]
|
||||
let videoInfo = await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`, {
|
||||
headers
|
||||
})
|
||||
videoInfo = await videoInfo.json()
|
||||
let cid = videoInfo.data.cid
|
||||
let downloadInfo = await fetch(`https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${cid}`, { headers })
|
||||
let videoUrl = (await downloadInfo.json()).data.durl[0].url
|
||||
return {
|
||||
arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
function randomIndex () {
|
||||
// Define weights for each index
|
||||
const weights = [5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1]
|
||||
|
||||
// Compute the total weight
|
||||
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0)
|
||||
|
||||
// Generate a random number between 0 and the total weight
|
||||
const randomNumber = Math.floor(Math.random() * totalWeight)
|
||||
|
||||
// Choose the index based on the random number and weights
|
||||
let weightSum = 0
|
||||
for (let i = 0; i < weights.length; i++) {
|
||||
weightSum += weights[i]
|
||||
if (randomNumber < weightSum) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('send bilibili')
|
||||
35
utils/tools/SendDiceTool.js
Normal file
35
utils/tools/SendDiceTool.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
|
||||
export class SendDiceTool extends AbstractTool {
|
||||
name = 'sendDice'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
num: {
|
||||
type: 'number',
|
||||
description: '骰子的数量'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号或qq号,发送目标'
|
||||
}
|
||||
},
|
||||
required: ['num', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (opts) {
|
||||
let {num, groupId} = opts
|
||||
let groupList = await Bot.getGroupList()
|
||||
if (groupList.get(groupId)) {
|
||||
let group = await Bot.pickGroup(groupId, true)
|
||||
await group.sendMsg(segment.dice(num))
|
||||
} else {
|
||||
let friend = await Bot.pickFriend(groupId)
|
||||
await friend.sendMsg(segment.dice(num))
|
||||
}
|
||||
return `the dice has been sent`
|
||||
}
|
||||
|
||||
description = 'If you want to roll dice, use this tool. If you know the group number, use the group number instead of the qq number first. The input should be the number of dice to be cast (1-6) and the target group number or qq number,and they should be concat with a space'
|
||||
}
|
||||
46
utils/tools/SendMusicTool.js
Normal file
46
utils/tools/SendMusicTool.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import fetch from 'node-fetch'
|
||||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
export class SendMusicTool extends AbstractTool {
|
||||
name = 'sendMusic'
|
||||
|
||||
parameters = {
|
||||
properties: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
description: '音乐的标题或关键词'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号或qq号,发送目标'
|
||||
}
|
||||
},
|
||||
required: ['keyword', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (opts) {
|
||||
let { keyword, groupId } = opts
|
||||
groupId = parseInt(groupId.trim())
|
||||
try {
|
||||
let { id, name } = await searchMusic163(keyword)
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
await group.shareMusic('163', id)
|
||||
return `the music ${name} has been shared to ${groupId}`
|
||||
} catch (e) {
|
||||
return `music share failed: ${e}`
|
||||
}
|
||||
}
|
||||
|
||||
description = 'Useful when you want to share music. The input should be the group number and the name of the music to be sent or the keywords that can find the music, connected with a space'
|
||||
}
|
||||
|
||||
export async function searchMusic163 (name) {
|
||||
let response = await fetch(`http://music.163.com/api/search/get/web?s=${name}&type=1&offset=0&total=true&limit=20`)
|
||||
let json = await response.json()
|
||||
if (json.result?.songCount > 0) {
|
||||
let id = json.result.songs[0].id
|
||||
let name = json.result.songs[0].name
|
||||
return { id, name }
|
||||
}
|
||||
return null
|
||||
}
|
||||
33
utils/tools/SendPictureTool.js
Normal file
33
utils/tools/SendPictureTool.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
|
||||
export class SendPictureTool extends AbstractTool {
|
||||
name = 'sendPicture'
|
||||
|
||||
parameters = {
|
||||
picture: {
|
||||
type: 'string',
|
||||
description: '图片的url,多个用空格隔开'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号或qq号,发送目标'
|
||||
},
|
||||
required: ['picture', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (picture, groupId) {
|
||||
let pictures = picture.trim().split(' ')
|
||||
pictures = pictures.map(img => segment.image(img))
|
||||
let groupList = await Bot.getGroupList()
|
||||
if (groupList.get(groupId)) {
|
||||
let group = await Bot.pickGroup(groupId)
|
||||
await group.sendMsg(pictures)
|
||||
} else {
|
||||
let user = await Bot.pickFriend(groupId)
|
||||
await user.sendMsg(pictures)
|
||||
}
|
||||
}
|
||||
|
||||
description = 'Useful when you want to send some pictures. The input to this tool should be the url of the pictures and the group number or the user\'s qq number, each url and the group number or qq number should be concated with a space, and the group number or qq number should be the last. 如果是在群聊中,优先选择群号发送。'
|
||||
}
|
||||
30
utils/tools/SendRPSTool.js
Normal file
30
utils/tools/SendRPSTool.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import {AbstractTool} from "./AbstractTool.js";
|
||||
|
||||
export class SendRPSTool extends AbstractTool {
|
||||
name = 'sendRPS'
|
||||
|
||||
parameters = {
|
||||
num: {
|
||||
type: 'number',
|
||||
description: '石头剪刀布的代号'
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
description: '群号或qq号,发送目标'
|
||||
},
|
||||
required: ['num', 'groupId']
|
||||
}
|
||||
|
||||
func = async function (num, groupId) {
|
||||
let groupList = await Bot.getGroupList()
|
||||
if (groupList.get(groupId)) {
|
||||
let group = await Bot.pickGroup(groupId, true)
|
||||
await group.sendMsg(segment.rps(num))
|
||||
} else {
|
||||
let friend = await Bot.pickFriend(groupId)
|
||||
await friend.sendMsg(segment.rps(num))
|
||||
}
|
||||
}
|
||||
|
||||
description = 'Use this tool if you want to play rock paper scissors. If you know the group number, use the group number instead of the qq number first. The input should be the number 1, 2 or 3 to represent rock-paper-scissors and the target group number or qq number,and they should be concat with a space'
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue