fix: 修复一些功能易用性

This commit is contained in:
ikechan8370 2023-06-23 17:33:55 +08:00
parent be7ceafb4b
commit 599f37e627
8 changed files with 249 additions and 61 deletions

View file

@ -51,6 +51,9 @@ import { KickOutTool } from '../utils/tools/KickOutTool.js'
import { SendAvatarTool } from '../utils/tools/SendAvatarTool.js' import { SendAvatarTool } from '../utils/tools/SendAvatarTool.js'
import { SendDiceTool } from '../utils/tools/SendDiceTool.js' import { SendDiceTool } from '../utils/tools/SendDiceTool.js'
import { EditCardTool } from '../utils/tools/EditCardTool.js' import { EditCardTool } from '../utils/tools/EditCardTool.js'
import {SearchVideoTool} from "../utils/tools/SearchBilibiliTool.js";
import {SearchMusicTool} from "../utils/tools/SearchMusicTool.js";
import {QueryStarRailTool} from "../utils/tools/QueryStarRailTool.js";
try { try {
await import('emoji-strip') await import('emoji-strip')
} catch (err) { } catch (err) {
@ -1809,7 +1812,7 @@ export class chatgpt extends plugin {
let promptPrefix = `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix} let promptPrefix = `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix}
Knowledge cutoff: 2021-09. Current date: ${currentDate}` Knowledge cutoff: 2021-09. Current date: ${currentDate}`
let maxModelTokens = getMaxModelTokens(completionParams.model) let maxModelTokens = getMaxModelTokens(completionParams.model)
let system = Config.promptPrefixOverride let system = promptPrefix
if (maxModelTokens >= 16000 && Config.enableGroupContext) { if (maxModelTokens >= 16000 && Config.enableGroupContext) {
try { try {
let opt = {} let opt = {}
@ -1917,22 +1920,22 @@ export class chatgpt extends plugin {
timeoutMs: 120000 timeoutMs: 120000
// systemMessage: promptPrefix // systemMessage: promptPrefix
} }
if (Math.floor(Math.random() * 100) < 5) { option.systemMessage = system
// 小概率再次发送系统消息
option.systemMessage = promptPrefix
}
if (conversation) { if (conversation) {
option = Object.assign(option, conversation) option = Object.assign(option, conversation)
} }
let tools = [ let tools = [
new JinyanTool(), new JinyanTool(),
new SearchVideoTool(),
new SendVideoTool(), new SendVideoTool(),
new SearchMusicTool(),
new SendMusicTool(), new SendMusicTool(),
new KickOutTool(), new KickOutTool(),
new SendAvatarTool(), new SendAvatarTool(),
new SendDiceTool(), // new SendDiceTool(),
new KickOutTool(), new KickOutTool(),
new EditCardTool() new EditCardTool(),
new QueryStarRailTool()
] ]
let funcMap = {} let funcMap = {}
tools.forEach(tool => { tools.forEach(tool => {

View file

@ -23,17 +23,22 @@ export class JinyanTool extends AbstractTool {
func = async function (opts) { func = async function (opts) {
let { qq, groupId, time = '600' } = opts let { qq, groupId, time = '600' } = opts
let group = await Bot.pickGroup(groupId)
time = parseInt(time.trim())
if (time < 60) { if (time < 60) {
time = 60 time = 60
} }
if (time > 86400 * 30) { if (time > 86400 * 30) {
time = 86400 * 30 time = 86400 * 30
} }
let group = await Bot.pickGroup(groupId)
time = parseInt(time.trim())
if (qq.trim() === 'all') { if (qq.trim() === 'all') {
await group.sendMsg('[日志]试图开启全员禁言') if (time > 0) {
// await group.muteAll(time > 0) await group.sendMsg('[日志]试图开启全员禁言,但被系统阻止了')
return 'error: you are not allowed to mute all in this group'
} else {
await group.muteAll(false)
return '该群的全体禁言已经被解除'
}
} else { } else {
qq = parseInt(qq.trim()) qq = parseInt(qq.trim())
await group.muteMember(qq, time) await group.muteMember(qq, time)
@ -41,5 +46,5 @@ export class JinyanTool extends AbstractTool {
return `the user ${qq} has been muted for ${time} seconds` 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\'' description = 'Useful when you want to ban someone. If you want to mute all, just replace the qq number with \'all\''
} }

View file

@ -1,4 +1,4 @@
import {AbstractTool} from "./AbstractTool.js"; import { AbstractTool } from './AbstractTool.js'
export class KickOutTool extends AbstractTool { export class KickOutTool extends AbstractTool {
name = 'kickOut' name = 'kickOut'

View file

@ -0,0 +1,76 @@
import { AbstractTool } from './AbstractTool.js'
export class QueryStarRailTool extends AbstractTool {
name = 'queryStarRail'
parameters = {
properties: {
qq: {
type: 'string',
description: '要查询的用户的qq号将使用该qq号绑定的uid进行查询'
},
groupId: {
type: 'string',
description: '群号'
},
uid: {
type: 'string',
description: '游戏的uid如果用户提供了则传入并优先使用'
}
},
required: ['qq', 'groupId']
}
func = async function (opts) {
let { qq, groupId, uid } = opts
if (!uid) {
try {
let { Panel } = await import('../../../StarRail-plugin/apps/panel.js')
uid = await redis.get(`STAR_RAILWAY:UID:${qq}`)
if (!uid) {
return '用户没有绑定uid无法查询。可以让用户主动提供uid进行查询'
}
} catch (e) {
return '未安装StarRail-Plugin无法查询'
}
}
try {
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) {
return `failed to query, error: ${err.toString()}`
}
}
description = 'Useful when you want to query player information of Honkai Star Rail(崩坏:星穹铁道). '
}

View file

@ -0,0 +1,76 @@
import fetch from 'node-fetch'
import { formatDate, mkdirs } from '../common.js'
import fs from 'fs'
import { AbstractTool } from './AbstractTool.js'
export class SearchVideoTool extends AbstractTool {
name = 'searchVideo'
parameters = {
properties: {
keyword: {
type: 'string',
description: '要搜索的视频的标题或关键词'
}
},
required: ['keyword']
}
func = async function (opts) {
let { keyword } = opts
try {
return await searchBilibili(keyword)
} catch (err) {
logger.error(err)
return `fail to search video, error: ${err.toString()}`
}
}
description = 'Useful when you want to search a video by keywords. you should remember the id of the video if you want to share it'
}
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 result = json.data.result.map(r => {
return `id: ${r.bvid},标题:${r.title},作者:${r.author},播放量:${r.play},发布日期:${formatDate(new Date(r.pubdate * 1000))}`
}).slice(0, Math.min(json.data?.numResults, 5)).join('\n')
return `这些是关键词“${name}”的搜索结果:\n${result}`
} else {
return `没有找到关键词“${name}”的搜索结果`
}
}
return {}
}

View file

@ -0,0 +1,39 @@
import fetch from 'node-fetch'
import { AbstractTool } from './AbstractTool.js'
export class SearchMusicTool extends AbstractTool {
name = 'searchMusic'
parameters = {
properties: {
keyword: {
type: 'string',
description: '音乐的标题或关键词'
}
},
required: ['keyword']
}
func = async function (opts) {
let { keyword } = opts
try {
let result = await searchMusic163(keyword)
return `search result: ${result}`
} catch (e) {
return `music search failed: ${e}`
}
}
description = 'Useful when you want to search music by keyword.'
}
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=6`)
let json = await response.json()
if (json.result?.songCount > 0) {
return json.result.songs.map(song => {
return `id: ${song.id}, name: ${song.name}, artists: ${song.artists.map(a => a.name).join('&')}, alias: ${song.alias || 'none'}`
}).join('\n')
}
return null
}

View file

@ -8,31 +8,33 @@ export class SendVideoTool extends AbstractTool {
parameters = { parameters = {
properties: { properties: {
keyword: { id: {
type: 'string', type: 'string',
description: '要发的视频的标题或关键词,用于搜索' description: '要发的视频的id'
}, },
groupId: { groupId: {
type: 'string', type: 'string',
description: '群号或qq号发送目标' description: '群号或qq号发送目标'
} }
}, },
required: ['keyword', 'groupId'] required: ['id', 'groupId']
} }
func = async function (opts) { func = async function (opts) {
let { keyword, groupId } = opts let { id, groupId } = opts
groupId = parseInt(groupId.trim()) groupId = parseInt(groupId.trim())
let msg = [] let msg = []
try { try {
let { arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like } = await searchBilibili(keyword) let { arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like, honor } = await getBilibili(id)
let group = await Bot.pickGroup(groupId) let group = await Bot.pickGroup(groupId)
console.log({ arcurl, title, pic, description, videoUrl })
msg.push(title.replace(/(<([^>]+)>)/ig, '') + '\n') msg.push(title.replace(/(<([^>]+)>)/ig, '') + '\n')
msg.push(`UP主${author} 发布日期:${formatDate(new Date(pubdate * 1000))} 播放量:${play} 点赞:${like}\n`) msg.push(`UP主${author} 发布日期:${formatDate(new Date(pubdate * 1000))} 播放量:${play} 点赞:${like}\n`)
msg.push(arcurl + '\n') msg.push(arcurl + '\n')
msg.push(segment.image('https:' + pic)) msg.push(segment.image('https:' + pic))
msg.push('\n' + description) msg.push('\n' + description)
if (honor) {
msg.push(`本视频曾获得过${honor}称号`)
}
msg.push('\n视频在路上啦') msg.push('\n视频在路上啦')
await group.sendMsg(msg) await group.sendMsg(msg)
const videoResponse = await fetch(videoUrl, { headers }) const videoResponse = await fetch(videoUrl, { headers })
@ -45,7 +47,7 @@ export class SendVideoTool extends AbstractTool {
await fs.writeFileSync(fileLoc, buffer) await fs.writeFileSync(fileLoc, buffer)
await group.sendMsg(segment.video(fileLoc)) await group.sendMsg(segment.video(fileLoc))
}) })
return `the video ${title.replace(/(<([^>]+)>)/ig, '')} will be shared to ${groupId} after a while, please wait` return `the video ${title.replace(/(<([^>]+)>)/ig, '')} was shared to ${groupId}. the video information: ${msg}`
} catch (err) { } catch (err) {
logger.error(err) logger.error(err)
if (msg.length > 0) { if (msg.length > 0) {
@ -56,10 +58,10 @@ export class SendVideoTool extends AbstractTool {
} }
} }
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' description = 'Useful when you want to share a video. You must use searchVideo to get search result and choose one video and get its id'
} }
export async function searchBilibili (name) { export async function getBilibili (bvid) {
let biliRes = await fetch('https://www.bilibili.com', let biliRes = await fetch('https://www.bilibili.com',
{ {
// headers: { // headers: {
@ -87,28 +89,28 @@ export async function searchBilibili (name) {
'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', '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 cookie: cookieHeader
} }
let response = await fetch(`https://api.bilibili.com/x/web-interface/search/type?keyword=${name}&search_type=video`, let videoInfo = await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`, {
{ headers
headers })
}) videoInfo = await videoInfo.json()
let json = await response.json() let cid = videoInfo.data.cid
if (json.data?.numResults > 0) { let arcurl = `http://www.bilibili.com/video/av${videoInfo.data.aid}`
let index = randomIndex() let title = videoInfo.data.title
let { arcurl, title, pic, description, bvid, author, play, pubdate, like } = json.data.result[Math.min(index, json.data.numResults)] let pic = videoInfo.data.pic
let videoInfo = await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`, { let description = videoInfo.data.desc
headers let author = videoInfo.data.owner.name
}) let play = videoInfo.data.stat.view
videoInfo = await videoInfo.json() let pubdate = videoInfo.data.pubdate
let cid = videoInfo.data.cid let like = videoInfo.data.stat.like
let downloadInfo = await fetch(`https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${cid}`, { headers }) let honor = videoInfo.data.honor_reply?.honor?.map(h => h.desc)?.join('、')
let videoUrl = (await downloadInfo.json()).data.durl[0].url let downloadInfo = await fetch(`https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${cid}`, {headers})
return { let videoUrl = (await downloadInfo.json()).data.durl[0].url
arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like return {
} arcurl, title, pic, description, videoUrl, headers, bvid, author, play, pubdate, like, honor
} }
} else {
return {}
} }
return {}
} }
function randomIndex () { function randomIndex () {

View file

@ -1,14 +1,13 @@
import fetch from 'node-fetch' import { AbstractTool } from './AbstractTool.js'
import {AbstractTool} from "./AbstractTool.js";
export class SendMusicTool extends AbstractTool { export class SendMusicTool extends AbstractTool {
name = 'sendMusic' name = 'sendMusic'
parameters = { parameters = {
properties: { properties: {
keyword: { id: {
type: 'string', type: 'string',
description: '音乐的标题或关键词' description: '音乐的id'
}, },
groupId: { groupId: {
type: 'string', type: 'string',
@ -19,28 +18,16 @@ export class SendMusicTool extends AbstractTool {
} }
func = async function (opts) { func = async function (opts) {
let { keyword, groupId } = opts let { id, groupId } = opts
groupId = parseInt(groupId.trim()) groupId = parseInt(groupId.trim())
try { try {
let { id, name } = await searchMusic163(keyword)
let group = await Bot.pickGroup(groupId) let group = await Bot.pickGroup(groupId)
await group.shareMusic('163', id) await group.shareMusic('163', id)
return `the music ${name} has been shared to ${groupId}` return `the music has been shared to ${groupId}`
} catch (e) { } catch (e) {
return `music share failed: ${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' description = 'Useful when you want to share music. You must use searchMusic first to get the music id'
}
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
} }