feat: 初步支持function call(WIP)

This commit is contained in:
ikechan8370 2023-06-23 01:09:12 +08:00
parent 4a4dceec18
commit 97b3acbf3b
24 changed files with 13607 additions and 841 deletions

View 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
}
}
}

View 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
View 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\''
}

View 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. '
}

View 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. 如果是在群聊中,优先选择群号发送。'
}

View 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')

View 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 numberand they should be concat with a space'
}

View 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
}

View 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. 如果是在群聊中,优先选择群号发送。'
}

View 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 numberand they should be concat with a space'
}