feat: init v3

This commit is contained in:
ikechan8370 2025-03-05 23:56:41 +08:00
parent d6cb085c40
commit 531986b2dc
284 changed files with 618 additions and 405179 deletions

View file

@ -1,365 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
const PLUGIN_CHAT = 'ChatGpt 对话'
const PLUGIN_MANAGEMENT = 'ChatGPT-Plugin 管理'
const PLUGIN_ENTERTAINMENT = 'ChatGPT-Plugin 娱乐小功能'
const FUNCTION_CHAT = 'chatgpt'
const FUNCTION_CHAT3 = 'chatgpt3'
const FUNCTION_CHAT1 = 'chatgpt1'
const FUNCTION_BING = 'bing'
const FUNCTION_GEMINI = 'gemini'
const FUNCTION_XH = 'xh'
const FUNCTION_QWEN = 'qwen'
const FUNCTION_GLM4 = 'glm4'
const FUNCTION_CLAUDE2 = 'claude2'
const FUNCTION_CLAUDE = 'claude'
const FUNCTION_END = 'destroyConversations'
const FUNCTION_END_ALL = 'endAllConversations'
const FUNCTION_PIC = 'switch2Picture'
const FUNCTION_TEXT = 'switch2Text'
const FUNCTION_AUDIO = 'switch2Audio'
const FUNCTION_CONFIRM_ON = 'turnOnConfirm'
const FUNCTION_CONFIRM_OFF = 'turnOffConfirm'
const FUNCTION_VERSION = 'versionChatGPTPlugin'
const FUNCTION_SHUTUP = 'shutUp'
const FUNCTION_OPEN_MOUTH = 'openMouth'
const FUNCTION_QUERY_CONFIG = 'queryConfig'
const FUNCTION_ENABLE_CONTEXT = 'enableGroupContext'
const FUNCTION_MODELS = 'viewAPIModel'
const FUNCTION_SWITCH_BING = 'useBingSolution'
const FUNCTION_WORDCLOUD = 'wordcloud'
const FUNCTION_WORDCLOUD_LATEST = 'wordcloud_latest'
const FUNCTION_WORDCLOUD_NEW = 'wordcloud_new'
const FUNCTION_TRANSLATE = 'translate'
const FUNCTION_TRANSLATE_SOURCE = 'translateSource'
const FUNCTION_TRANSLATE_OCR = 'ocr'
const FUNCTION_TRANSLATE_SCREENSHOT = 'screenshotUrl'
export class ChatGPTButtonHandler extends plugin {
constructor () {
super({
name: 'chatgpt按钮处理器',
priority: -100,
namespace: 'chatgpt-plugin',
handler: [{
key: 'chatgpt.button.post',
fn: 'btnHandler'
}]
})
}
/**
* 按钮处理器
* @param e
* @param options
* @param reject
* @deprecated
* @return {Promise<{appid: number, rows: [{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]}]}|{appid: number, rows: [{buttons: [{render_data: {style: number, label: *, visited_label: *}, action: {data: *, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string},{render_data: {style: number, label: *, visited_label: *}, action: {data: *, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string},{render_data: {style: number, label: *, visited_label: *}, action: {data: *, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}]}]}|{appid: number, rows: [{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]},{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]},{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]},{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]}]}|{appid: number, rows: [{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]},{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]},{buttons: {render_data: {style: number, label, visited_label}, action: {data, permission: {type: number}, unsupport_tips: string, enter: boolean, type: number}, id: string}[]}]}|null>}
*/
async btnHandler (e, options, reject) {
// logger.mark('[chatgpt按钮处理器]')
if (!Config.enableMd) {
return null
}
const fnc = e.logFnc
switch (fnc) {
case `[${PLUGIN_CHAT}][${FUNCTION_CHAT3}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_CHAT1}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_BING}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_GEMINI}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_XH}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_QWEN}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_CLAUDE2}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_CLAUDE}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_GLM4}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_CHAT}]`: {
return this.makeButtonChat(options?.btnData)
}
case `[${PLUGIN_CHAT}][${FUNCTION_END}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_END_ALL}]`: {
return this.makeButtonEnd(options?.btnData)
}
case `[${PLUGIN_CHAT}][${FUNCTION_PIC}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_AUDIO}]`:
case `[${PLUGIN_CHAT}][${FUNCTION_TEXT}]`: {
return this.makeButtonMode(options?.btnData)
}
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_VERSION}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_SHUTUP}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_OPEN_MOUTH}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_MODELS}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_QUERY_CONFIG}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_ENABLE_CONTEXT}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_CONFIRM_OFF}]`:
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_CONFIRM_ON}]`: {
return this.makeButtonConfirm(options?.btnData)
}
case `[${PLUGIN_MANAGEMENT}][${FUNCTION_SWITCH_BING}]`: {
return this.makeButtonBingMode(options?.btnData)
}
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_WORDCLOUD}]`:
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_WORDCLOUD_LATEST}]`:
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_WORDCLOUD_NEW}]`:
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE}]`:
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE_SOURCE}]`:
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE_OCR}]`:
case `[${PLUGIN_ENTERTAINMENT}][${FUNCTION_TRANSLATE_SCREENSHOT}]`: {
return this.makeButtonEntertainment(options?.btnData)
}
default:
}
return null
}
/**
*
* @param {{suggested: string?, use: string}?} options
*/
async makeButtonChat (options) {
let endCommand = '#摧毁对话'
switch (options?.use) {
case 'api': {
endCommand = '#api结束对话'
break
}
case 'api3': {
endCommand = '#api3结束对话'
break
}
case 'bing': {
endCommand = '#必应结束对话'
break
}
case 'claude2': {
endCommand = '#克劳德结束对话'
break
}
case 'gemini': {
endCommand = '#双子星结束对话'
break
}
case 'xh': {
endCommand = '#星火结束对话'
break
}
case 'qwen': {
endCommand = '#通义千问结束对话'
break
}
case 'chatglm4': {
endCommand = '#智谱结束对话'
break
}
}
let rows = [
{
buttons: [
createButtonBase('结束对话', '#毁灭对话'),
createButtonBase('结束当前对话', endCommand),
createButtonBase('at我对话', '', false)
]
}
]
let buttons = [[], []]
if (Config.apiKey) {
buttons[0].push(createButtonBase('OpenAI', '#chat1', false))
}
if (await redis.get('CHATGPT:TOKEN')) {
buttons[0].push(createButtonBase('ChatGPT', '#chat3', false))
}
if (await redis.get('CHATGPT:BING_TOKENS')) {
buttons[0].push(createButtonBase('Copilot', '#bing', false))
}
if (Config.geminiKey) {
buttons[0].push(createButtonBase('Gemini', '#gemini', false))
}
if (Config.xhAPIKey) {
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('讯飞星火', '#xh', false))
}
if (Config.qwenApiKey) {
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('通义千问', '#qwen', false))
}
if (Config.chatglmRefreshToken) {
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('ChatGLM4', '#glm4', false))
}
// 两个claude只显示一个 优先API
if (Config.claudeApiKey) {
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude', '#claude', false))
} else if (Config.claudeAISessionKey) {
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude.ai', '#claude.ai', false))
}
rows.push({
buttons: buttons[0]
})
if (buttons[1].length > 0) {
rows.push({
buttons: buttons[1]
})
}
if (options?.suggested) {
rows.unshift({
buttons: options.suggested.split('\n').map(s => {
return createButtonBase(s, s)
})
})
}
return {
appid: 1,
rows
}
}
makeButtonEnd (options) {
return {
appid: 1,
rows: [
{
buttons: [
createButtonBase('重新开始', '#摧毁对话'),
createButtonBase('全部结束', '#摧毁全部对话'),
createButtonBase('切换模式', '#chatgpt切换', false)
]
}
]
}
}
makeButtonMode (options) {
return {
appid: 1,
rows: [
{
buttons: [
createButtonBase('以文字回复', '#chatgpt文本模式'),
createButtonBase('以图片回复', '#chatgpt图片模式'),
createButtonBase('以语音回复', '#chatgpt语音模式')
]
}
]
}
}
makeButtonConfirm (options) {
return {
appid: 1,
rows: [
{
buttons: [
createButtonBase('开启确认', '#chatgpt开启确认'),
createButtonBase('关闭确认', '#chatgpt关闭确认'),
createButtonBase('暂停本群回复', '#chatgpt本群闭嘴', false)
]
},
{
buttons: [
createButtonBase('恢复本群回复', '#chatgpt本群张嘴', false),
createButtonBase('开启上下文', '#打开群聊上下文'),
createButtonBase('关闭上下文 ', '#关闭群聊上下文')
]
},
{
buttons: [
createButtonBase('查看指令表', '#chatgpt指令表', false),
createButtonBase('查看帮助', '#chatgpt帮助'),
createButtonBase('查看配置', '#chatgpt查看当前配置')
]
},
{
buttons: [
createButtonBase('查看配置', '#chatgpt查看当前配置'),
createButtonBase('查看模型列表', '#chatgpt模型列表'),
createButtonBase('版本信息', '#chatgpt版本信息')
]
}
]
}
}
makeButtonBingMode (options) {
return {
appid: 1,
rows: [
{
buttons: [
createButtonBase('创意模式', '#chatgpt必应切换创意'),
createButtonBase('精准模式', '#chatgpt必应切换精准'),
createButtonBase('使用设定', '#chatgpt使用设定', false)
]
},
{
buttons: [
createButtonBase('禁用搜索', '#chatgpt必应禁用搜索'),
createButtonBase('开启搜索', '#chatgpt必应开启搜索'),
createButtonBase('设定列表', '#chatgpt浏览设定', false)
]
},
{
buttons: [
createButtonBase('切换到API', '#chatgpt切换API'),
createButtonBase('切换到Gemini', '#chatgpt切换gemini'),
createButtonBase('切换到星火', '#chatgpt切换xh')
]
}
]
}
}
makeButtonEntertainment (options) {
return {
appid: 1,
rows: [
{
buttons: [
createButtonBase('今日词云', '#今日词云'),
createButtonBase('最新词云', '#最新词云', false),
createButtonBase('我的词云', '#我的今日词云')
]
},
{
buttons: [
createButtonBase('翻译', '#翻译', false),
createButtonBase('OCR', '#ocr', false),
createButtonBase('截图', '#url:', false)
]
},
{
buttons: [
createButtonBase('设置OPENAI翻译源', '#chatgpt设置翻译来源openai'),
createButtonBase('设置gemini翻译源', '#chatgpt设置翻译来源gemini'),
createButtonBase('设置星火翻译源', '#chatgpt设置翻译来源xh'),
createButtonBase('设置通义千问翻译源', '#chatgpt设置翻译来源qwen')
]
}
]
}
}
}
function createButtonBase (label, data, enter = true, style = 1) {
return {
id: '',
render_data: {
label,
style,
visited_label: label
},
action: {
type: 2,
permission: {
type: 2
},
data,
enter,
unsupport_tips: ''
}
}
}

View file

@ -1,118 +0,0 @@
import { Config } from '../utils/config.js'
import { getChatHistoryGroup } from '../utils/chat.js'
import { convertFaces } from '../utils/face.js'
import { customSplitRegex, filterResponseChunk } from '../utils/text.js'
import core, { roleMap } from '../model/core.js'
import { formatDate } from '../utils/common.js'
export class bym extends plugin {
constructor () {
super({
name: 'ChatGPT-Plugin 伪人bym',
dsc: 'bym',
/** https://oicqjs.github.io/oicq/#events */
event: 'message',
priority: 5000,
rule: [
{
reg: '^[^#][sS]*',
fnc: 'bym',
priority: '-1000000',
log: false
}
]
})
}
/** 复读 */
async bym (e) {
if (!Config.enableBYM) {
return false
}
// 伪人禁用群
if (Config.bymDisableGroup?.includes(e.group_id?.toString())) {
return false
}
let sender = e.sender.user_id
let card = e.sender.card || e.sender.nickname
let group = e.group_id
let prop = Math.floor(Math.random() * 100)
if (Config.assistantLabel && e.msg?.includes(Config.assistantLabel)) {
prop = -1
}
// 去掉吧 频率有点逆天
// if (e.msg?.endsWith('')) {
// prop = prop / 10
// }
let fuck = false
let candidate = Config.bymPreset
if (Config.bymFuckList?.find(i => e.msg?.includes(i))) {
fuck = true
candidate = candidate + Config.bymFuckPrompt
}
if (prop < Config.bymRate) {
logger.info('random chat hit')
// let chats = await getChatHistoryGroup(e, Config.groupContextLength)
let system = `你的名字是“${Config.assistantLabel}你在一个qq群里群号是${group},当前和你说话的人群名片是${card}, qq号是${sender}, 请你结合用户的发言和聊天记录作出回应,要求表现得随性一点,最好参与讨论,混入其中。不要过分插科打诨,不知道说什么可以复读群友的话。要求你做搜索、发图、发视频和音乐等操作时要使用工具。不可以直接发[图片]这样蒙混过关。要求优先使用中文进行对话。如果此时不需要自己说话,可以只回复<EMPTY>` +
candidate +
`\n你的回复应该尽可能简练,像人类一样随意,不要附加任何奇怪的东西,如聊天记录的格式(比如${Config.assistantLabel}:),禁止重复聊天记录。`
let rsp = await core.sendMessage(e.msg, {}, Config.bymMode, e, {
enableSmart: Config.smartMode,
system: {
api: system,
qwen: system,
bing: system,
claude: system,
claude2: system,
gemini: system,
xh: system
},
settings: {
replyPureTextCallback: msg => {
msg = filterResponseChunk(msg)
msg && e.reply(msg)
},
// 强制打开上下文,不然伪人笨死了
enableGroupContext: true
}
})
// let rsp = await client.sendMessage(e.msg, opt)
let text = rsp.text
let texts = customSplitRegex(text, /(?<!\?)[。?\n](?!\?)/, 3)
// let texts = text.split(/(?<!\?)[。?\n](?!\?)/, 3)
for (let t of texts) {
if (!t) {
continue
}
t = t.trim()
if (text[text.indexOf(t) + t.length] === '') {
t += ''
}
let finalMsg = await convertFaces(t, true, e)
logger.info(JSON.stringify(finalMsg))
finalMsg = finalMsg.map(filterResponseChunk).filter(i => !!i)
if (finalMsg && finalMsg.length > 0) {
if (Math.floor(Math.random() * 100) < 10) {
await this.reply(finalMsg, true, {
recallMsg: fuck ? 10 : 0
})
} else {
await this.reply(finalMsg, false, {
recallMsg: fuck ? 10 : 0
})
}
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, Math.min(t.length * 200, 3000))
})
}
}
}
return false
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,344 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { createImage, editImage, imageVariation } from '../utils/dalle.js'
import { makeForwardMsg } from '../utils/common.js'
import _ from 'lodash'
import { Config } from '../utils/config.js'
import BingDrawClient from '../utils/BingDraw.js'
import fetch from 'node-fetch'
export class dalle extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin Dalle 绘图',
dsc: 'ChatGPT-Plugin基于OpenAI Dalle的绘图插件',
event: 'message',
priority: 600,
rule: [
{
reg: '^#(chatgpt|ChatGPT|dalle|Dalle)(绘图|画图)',
fnc: 'draw'
},
{
reg: '^#(chatgpt|ChatGPT|dalle|Dalle)(修图|图片变形|改图)$',
fnc: 'variation'
},
{
reg: '^#(搞|改)(她|他)头像',
fnc: 'avatarVariation'
},
{
reg: '^#(chatgpt|dalle)编辑图片',
fnc: 'edit'
},
{
reg: '^#bing(画图|绘图)',
fnc: 'bingDraw'
},
{
reg: '^#dalle3(画图|绘图)',
fnc: 'dalle3'
}
]
})
}
// dalle3
async dalle3 (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
return false
}
let ttl = await redis.ttl(`CHATGPT:DALLE3:${e.sender.user_id}`)
if (ttl > 0 && !e.isMaster) {
this.reply(`冷却中,请${ttl}秒后再试`)
return false
}
let prompt = e.msg.replace(/^#?dalle3(画图|绘图)/, '').trim()
console.log('draw方法被调用消息内容', prompt)
await redis.set(`CHATGPT:DALLE3:${e.sender.user_id}`, 'c', { EX: 30 })
await this.reply('正在为您绘制大小为1024x1024的1张图片预计消耗0.24美元余额,请稍候……')
try {
const response = await fetch(`${Config.openAiBaseUrl}/images/generations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${Config.apiKey}`
},
body: JSON.stringify({
model: 'dall-e-3',
prompt,
n: 1,
size: '1024x1024',
response_format: 'b64_json'
})
})
// 如果需要,可以解析响应体
const dataJson = await response.json()
console.log(dataJson)
if (dataJson.error) {
e.reply(`画图失败:${dataJson.error?.code}${dataJson.error?.message}`)
await redis.del(`CHATGPT:DALLE3:${e.sender.user_id}`)
return
}
if (dataJson.data[0].b64_json) {
e.reply(`描述:${dataJson.data[0].revised_prompt}`)
e.reply(segment.image(`base64://${dataJson.data[0].b64_json}`))
} else if (dataJson.data[0].url) {
e.reply(`哈哈哈,图来了~\n防止图💥,附上链接:\n${dataJson.data[0].url}`)
e.reply(segment.image(dataJson.data[0].url))
}
} catch (err) {
logger.error(err)
this.reply(`画图失败: ${err}`, true)
await redis.del(`CHATGPT:DALLE3:${e.sender.user_id}`)
}
}
async draw (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
return false
}
let ttl = await redis.ttl(`CHATGPT:DRAW:${e.sender.user_id}`)
if (ttl > 0 && !e.isMaster) {
this.reply(`冷却中,请${ttl}秒后再试`)
return false
}
let splits = _.split(e.msg, '图', 2)
if (splits.length < 2) {
this.reply('请带上绘图要求')
return false
}
let rules = _.split(splits[1], '/')
let [prompt = '', num = '1', size = '512x512'] = rules.slice(0, 3)
if (['256x256', '512x512', '1024x1024'].indexOf(size) === -1) {
this.reply('大小不符合要求必须是256x256/512x512/1024x1024中的一个')
return false
}
await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 })
let priceMap = {
'1024x1024': 0.02,
'512x512': 0.018,
'256x256': 0.016
}
num = parseInt(num, 10)
if (num > 5) {
this.reply('太多啦!你要花光我的余额吗!')
return false
}
await this.reply(`正在为您绘制大小为${size}${num}张图片,预计消耗${priceMap[size] * num}美元余额,请稍候……`)
try {
let images = (await createImage(prompt, num, size)).map(image => segment.image(`base64://${image}`))
if (images.length > 1) {
this.reply(await makeForwardMsg(e, images, prompt))
} else {
this.reply(images[0], true)
}
} catch (err) {
logger.error(err.response?.data?.error?.message)
this.reply(`绘图失败: ${err.response?.data?.error?.message}`, true)
await redis.del(`CHATGPT:DRAW:${e.sender.user_id}`)
}
}
async variation (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
return false
}
let ttl = await redis.ttl(`CHATGPT:VARIATION:${e.sender.user_id}`)
if (ttl > 0 && !e.isMaster) {
this.reply(`冷却中,请${ttl}秒后再试`)
return false
}
let imgUrl
if (e.source) {
let reply
if (e.isGroup) {
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
} else {
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
}
if (reply) {
for (let val of reply) {
if (val.type === 'image') {
console.log(val)
imgUrl = val.url
break
}
}
}
} else if (e.img) {
console.log(e.img)
imgUrl = e.img[0]
}
if (!imgUrl) {
this.reply('图呢?')
return false
}
await redis.set(`CHATGPT:VARIATION:${e.sender.user_id}`, 'c', { EX: 30 })
await this.reply('正在为您生成图片变形,请稍候……')
try {
let images = (await imageVariation(imgUrl)).map(image => segment.image(`base64://${image}`))
if (images.length > 1) {
this.reply(await makeForwardMsg(e, images))
} else {
this.reply(images[0], true)
}
} catch (err) {
console.log(err.response?.data?.error?.message || err.message || JSON.stringify(err.response || {}))
this.reply(`绘图失败: ${err.response?.data?.error?.message || err.message || JSON.stringify(err.response || {})}`, true)
await redis.del(`CHATGPT:VARIATION:${e.sender.user_id}`)
}
}
async avatarVariation (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
return false
}
let ats = e.message.filter(m => m.type === 'at').filter(at => at.qq !== e.self_id)
if (ats.length > 0) {
for (let i = 0; i < ats.length; i++) {
let qq = ats[i].qq
let imgUrl = `https://q1.qlogo.cn/g?b=qq&s=0&nk=${qq}`
try {
let images = (await imageVariation(imgUrl)).map(image => segment.image(`base64://${image}`))
if (images.length > 1) {
this.reply(await makeForwardMsg(e, images))
} else {
this.reply(images[0], true)
}
} catch (err) {
console.log(err.response?.data?.error?.message || err.message || JSON.stringify(err.response || {}))
this.reply(`搞失败了: ${err.response?.data?.error?.message || err.message || JSON.stringify(err.response || {})}`, true)
await redis.del(`CHATGPT:VARIATION:${e.sender.user_id}`)
}
}
}
}
async edit (e) {
if (!Config.enableDraw) {
this.reply('画图功能未开启')
return false
}
let ttl = await redis.ttl(`CHATGPT:EDIT:${e.sender.user_id}`)
if (ttl > 0 && !e.isMaster) {
this.reply(`冷却中,请${ttl}秒后再试`)
return false
}
let imgUrl
if (e.source) {
let reply
if (e.isGroup) {
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
} else {
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
}
if (reply) {
for (let val of reply) {
if (val.type === 'image') {
console.log(val)
imgUrl = val.url
break
}
}
}
} else if (e.img) {
console.log(e.img)
imgUrl = e.img[0]
}
if (!imgUrl) {
this.reply('图呢?')
return false
}
await redis.set(`CHATGPT:EDIT:${e.sender.user_id}`, 'c', { EX: 30 })
await this.reply('正在为您编辑图片,请稍候……')
let command = _.trimStart(e.msg, '#chatgpt编辑图片')
command = _.trimStart(command, '#dalle编辑图片')
// command = 'A bird on it/100,100,300,200/2/512x512'
let args = command.split('/')
let [prompt = '', position = '', num = '1', size = '512x512'] = args.slice(0, 4)
if (!prompt || !position) {
this.reply('编辑图片必须填写prompt和涂抹位置.参考格式A bird on it/100,100,300,200/2/512x512')
return false
}
num = parseInt(num, 10)
if (num > 5) {
this.reply('太多啦!你要花光我的余额吗!')
return false
}
try {
let images = (await editImage(imgUrl, position.split(',').map(p => parseInt(p, 10)), prompt, num, size))
.map(image => segment.image(`base64://${image}`))
if (images.length > 1) {
this.reply(await makeForwardMsg(e, images, prompt))
} else {
this.reply(images[0], true)
}
} catch (err) {
logger.error(err.response?.data?.error?.message || err.message || JSON.stringify(err.response || {}))
this.reply(`图片编辑失败: ${err.response?.data?.error?.message || err.message || JSON.stringify(err.response || {})}`, true)
await redis.del(`CHATGPT:EDIT:${e.sender.user_id}`)
}
}
async bingDraw (e) {
let ttl = await redis.ttl(`CHATGPT:DRAW:${e.sender.user_id}`)
if (ttl > 0 && !e.isMaster) {
this.reply(`冷却中,请${ttl}秒后再试`)
return false
}
let prompt = e.msg.replace(/^#bing(画图|绘图)/, '')
if (!prompt) {
this.reply('请提供绘图prompt')
return false
}
this.reply('在画了,请稍等……')
let bingToken = ''
if (await redis.exists('CHATGPT:BING_TOKENS') != 0) {
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
const normal = bingTokens.filter(element => element.State === '正常')
const restricted = bingTokens.filter(element => element.State === '受限')
if (normal.length > 0) {
const minElement = normal.reduce((min, current) => {
return current.Usage < min.Usage ? current : min
})
bingToken = minElement.Token
} else if (restricted.length > 0) {
const minElement = restricted.reduce((min, current) => {
return current.Usage < min.Usage ? current : min
})
bingToken = minElement.Token
} else {
throw new Error('全部Token均已失效暂时无法使用')
}
}
if (!bingToken) {
throw new Error('未绑定Bing Cookie请使用#chatgpt设置必应token命令绑定Bing Cookie')
}
// 记录token使用
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
const index = bingTokens.findIndex(element => element.Token === bingToken)
bingTokens[index].Usage += 1
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
let cookie
if (bingToken.includes('=')) {
cookie = bingToken
}
let client = new BingDrawClient({
baseUrl: Config.sydneyReverseProxy,
userToken: bingToken,
cookies: cookie
})
await redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 })
try {
await client.getImages(prompt, e)
} catch (err) {
await redis.del(`CHATGPT:DRAW:${e.sender.user_id}`)
await e.reply('❌绘图失败:' + err)
}
}
}

View file

@ -1,638 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
import { generateHello } from '../utils/randomMessage.js'
import { generateVitsAudio } from '../utils/tts.js'
import fs from 'fs'
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
import uploadRecord from '../utils/uploadRecord.js'
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
import { translate, translateLangSupports } from '../utils/translate.js'
import AzureTTS from '../utils/tts/microsoft-azure.js'
import VoiceVoxTTS from '../utils/tts/voicevox.js'
import { URL } from 'node:url'
import { getBots } from '../utils/bot.js'
import {CustomGoogleGeminiClient} from "../client/CustomGoogleGeminiClient.js";
let useSilk = false
try {
await import('node-silk')
useSilk = true
} catch (e) {
useSilk = false
}
export class Entertainment extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin 娱乐小功能',
dsc: '让你的聊天更有趣现已支持主动打招呼、表情合成、群聊词云统计、文本翻译与图片ocr小功能',
event: 'message',
priority: 500,
rule: [
{
reg: '^#chatgpt打招呼(帮助)?',
fnc: 'sendMessage',
permission: 'master'
},
{
reg: '^#chatgpt(查看|设置|删除)打招呼',
fnc: 'handleSentMessage',
permission: 'master'
},
{
reg: `^(${emojiRegex()}){2}$`,
fnc: 'combineEmoj'
},
{
reg: '^#?(今日词云|群友在聊什么)$',
fnc: 'wordcloud'
},
{
reg: '^#(|最新)词云(\\d{1,2}h{0,1}|)$',
fnc: 'wordcloud_latest'
},
{
reg: '^#(我的)?(本月|本周|今日)?词云$',
fnc: 'wordcloud_new'
},
{
reg: '^#((寄批踢|gpt|GPT)?翻[sS]*|chatgpt翻译帮助)',
fnc: 'translate'
},
{
reg: '^#(chatgpt)?(设置|修改)翻译来源(openai|gemini|星火|通义千问|xh|qwen)$',
fnc: 'translateSource'
},
{
reg: '^#ocr',
fnc: 'ocr'
},
{
reg: '^#url(|:)',
fnc: 'screenshotUrl'
},
{
reg: '^#(识图|图片识别|VQA|vqa)',
fnc: 'vqa'
}
]
})
this.task = [
{
// 设置十分钟左右的浮动
cron: '0 ' + Math.ceil(Math.random() * 10) + ' 7-23/' + Config.helloInterval + ' * * ?',
// cron: '*/2 * * * *',
name: 'ChatGPT主动随机说话',
fnc: this.sendRandomMessage.bind(this)
}
]
this.reply = async (msg, quote, data) => {
if (!Config.enableMd) {
return e.reply(msg, quote, data)
}
let handler = e.runtime?.handler || {}
const btns = await handler.call('chatgpt.button.post', this.e)
const btnElement = {
type: 'button',
content: btns
}
if (Array.isArray(msg)) {
msg.push(btnElement)
} else {
msg = [msg, btnElement]
}
return e.reply(msg, quote, data)
}
}
async ocr (e) {
let replyMsg
let imgOcrText = await getImageOcrText(e)
if (!imgOcrText) {
await this.reply('没有识别到文字', e.isGroup)
return false
}
replyMsg = await makeForwardMsg(e, imgOcrText, 'OCR结果')
await this.reply(replyMsg, e.isGroup)
}
async translate (e) {
const translateLangLabels = translateLangSupports.map(item => item.label).join('')
const translateLangLabelAbbrS = translateLangSupports.map(item => item.abbr).join('')
if (e.msg.trim() === '#chatgpt翻译帮助') {
await this.reply(`支持以下语种的翻译:
${translateLangLabels}
在使用本工具时请采用简写的方式描述目标语言此外可以引用消息或图片来进行翻译
示例
1. #gpt翻英 你好
2. #gpt翻中 你好
3. #gpt翻译 hello`)
return true
}
const regExp = /^#(寄批踢|gpt|GPT)?翻(.)([\s\S]*)/
const match = e.msg.trim().match(regExp)
let languageCode = match[2] === '译' ? 'auto' : match[2]
let pendingText = match[3]
const isImg = !!(await getImg(e))?.length
let result = []
let multiText = false
if (languageCode !== 'auto' && !translateLangLabelAbbrS.includes(languageCode)) {
this.reply(`输入格式有误或暂不支持该语言,\n当前支持${translateLangLabels}`, e.isGroup)
return false
}
// 引用回复
if (e.source) {
if (pendingText.length) {
await this.reply('引用模式下不需要添加翻译文本,已自动忽略输入文本...((*・∀・)ゞ→→”', e.isGroup)
}
} else {
if (isImg && pendingText) {
await this.reply('检测到图片输入,已自动忽略输入文本...((*・∀・)ゞ→→', e.isGroup)
}
if (!pendingText && !isImg) {
await this.reply('你让我翻译啥呢 ̄へ ̄!', e.isGroup)
return false
}
}
if (isImg) {
let imgOcrText = await getImageOcrText(e)
multiText = Array.isArray(imgOcrText)
if (imgOcrText) {
pendingText = imgOcrText
} else {
await this.reply('没有识别到有效文字(・-・*)', e.isGroup)
return false
}
} else {
if (e.source) {
let previousMsg
if (e.isGroup) {
previousMsg = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
} else {
previousMsg = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
}
// logger.warn('previousMsg', previousMsg)
if (previousMsg.find(msg => msg.type === 'text')?.text) {
pendingText = previousMsg.find(msg => msg.type === 'text')?.text
} else {
await this.reply('这是什么怪东西!(⊙ˍ⊙)', e.isGroup)
return false
}
}
}
try {
if (multiText) {
result = await Promise.all(pendingText.map(text => translate(text, languageCode)))
} else {
result = await translate(pendingText, languageCode)
}
// logger.warn(multiText, result)
} catch (err) {
await this.reply(err.message, e.isGroup)
return false
}
// const totalLength = Array.isArray(result)
// ? result.reduce((acc, cur) => acc + cur.length, 0)
// : result.length
if (multiText) {
// 多条翻译结果
if (Array.isArray(result)) {
result = await makeForwardMsg(e, result, '翻译结果')
} else {
result = ('译文:\n' + result.trim()).split()
result.unshift('原文:\n' + pendingText.trim())
result = await makeForwardMsg(e, result, '翻译结果')
}
await this.reply(result, e.isGroup)
return true
}
// 保持原格式输出
result = Array.isArray(result) ? result.join('\n') : result
await this.reply(result, e.isGroup)
return true
}
translateSource (e) {
let command = e.msg
if (command.includes('openai')) {
Config.translateSource = 'openai'
} else if (command.includes('gemini')) {
Config.translateSource = 'gemini'
} else if (command.includes('星火')) {
Config.translateSource = 'xh'
} else if (command.includes('通义千问')) {
Config.translateSource = 'qwen'
} else if (command.includes('xh')) {
Config.translateSource = 'xh'
} else if (command.includes('qwen')) {
Config.translateSource = 'qwen'
} else {
this.reply('暂不支持该翻译源')
}
this.reply('√成功设置翻译源为' + Config.translateSource)
}
async wordcloud (e) {
if (e.isGroup) {
let groupId = e.group_id
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
if (lock) {
await this.reply('别着急,上次统计还没完呢')
return true
}
await this.reply('在统计啦,请稍等...')
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
try {
let img = await makeWordcloud(e, e.group_id)
this.reply(img, true)
} catch (err) {
logger.error(err)
await this.reply(err)
}
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
} else {
await this.reply('请在群里发送此命令')
}
}
async wordcloud_latest (e) {
if (e.isGroup) {
let groupId = e.group_id
let lock = await redis.get(`CHATGPT:WORDCLOUD:${groupId}`)
if (lock) {
await this.reply('别着急,上次统计还没完呢')
return true
}
const regExp = /词云(\d{0,2})(|h)/
const match = e.msg.trim().match(regExp)
const duration = !match[1] ? 12 : parseInt(match[1]) // default 12h
if (duration > 24) {
await this.reply('最多只能统计24小时内的记录哦你可以使用#本周词云和#本月词云获取更长时间的统计~')
return false
}
await this.reply('在统计啦,请稍等...')
await redis.set(`CHATGPT:WORDCLOUD:${groupId}`, '1', { EX: 600 })
try {
await makeWordcloud(e, e.group_id, duration)
} catch (err) {
logger.error(err)
await this.reply(err)
}
await redis.del(`CHATGPT:WORDCLOUD:${groupId}`)
} else {
await this.reply('请在群里发送此命令')
}
}
async wordcloud_new (e) {
if (e.isGroup) {
let groupId = e.group_id
let userId
if (e.msg.includes('我的')) {
userId = e.sender.user_id
}
let at = e.message.find(m => m.type === 'at')
if (at) {
userId = at.qq
}
let lock = await redis.get(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
if (lock) {
await this.reply('别着急,上次统计还没完呢')
return true
}
await this.reply('在统计啦,请稍等...')
let duration = 24
if (e.msg.includes('本周')) {
const now = new Date() // Get the current date and time
let day = now.getDay()
let diff = now.getDate() - day + (day === 0 ? -6 : 1)
const startOfWeek = new Date(new Date().setDate(diff))
startOfWeek.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day)
duration = (now - startOfWeek) / 1000 / 60 / 60
} else if (e.msg.includes('本月')) {
const now = new Date() // Get the current date and time
const startOfMonth = new Date(new Date().setDate(0))
startOfMonth.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day)
duration = (now - startOfMonth) / 1000 / 60 / 60
} else {
// 默认今天
const now = new Date()
const startOfToday = new Date() // Get the current date and time
startOfToday.setHours(0, 0, 0, 0) // Set the time to midnight (start of the day)
duration = (now - startOfToday) / 1000 / 60 / 60
}
await redis.set(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`, '1', { EX: 600 })
try {
await makeWordcloud(e, e.group_id, duration, userId)
} catch (err) {
logger.error(err)
await this.reply(err)
}
await redis.del(`CHATGPT:WORDCLOUD_NEW:${groupId}_${userId}`)
} else {
await this.reply('请在群里发送此命令')
}
}
async combineEmoj (e) {
let left = e.msg.codePointAt(0).toString(16).toLowerCase()
let right = e.msg.codePointAt(2).toString(16).toLowerCase()
if (left === right) {
return false
}
mkdirs('data/chatgpt/emoji')
logger.info('combine ' + e.msg)
let resultFileLoc = `data/chatgpt/emoji/${left}_${right}.jpg`
if (fs.existsSync(resultFileLoc)) {
let image = segment.image(resultFileLoc)
image.asface = true
await this.reply(image, true)
return true
}
const _path = process.cwd()
const fullPath = fs.realpathSync(`${_path}/plugins/chatgpt-plugin/resources/emojiData.json`)
const data = fs.readFileSync(fullPath)
let emojDataJson = JSON.parse(data)
logger.mark(`合成emoji${left} ${right}`)
let url
if (emojDataJson[right]) {
let find = emojDataJson[right].find(item => item.leftEmoji === left)
if (find) {
url = googleRequestUrl(find)
}
}
if (!url && emojDataJson[left]) {
let find = emojDataJson[left].find(item => item.leftEmoji === right)
if (find) {
url = googleRequestUrl(find)
}
}
if (!url) {
await this.reply('不支持合成', true)
return false
}
// let response = await fetch(url)
// const resultBlob = await response.blob()
// const resultArrayBuffer = await resultBlob.arrayBuffer()
// const resultBuffer = Buffer.from(resultArrayBuffer)
// await fs.writeFileSync(resultFileLoc, resultBuffer)
let image = segment.image(url)
image.asface = true
await this.reply(image, true)
return true
}
async sendMessage (e) {
if (e.msg.match(/^#chatgpt打招呼帮助/) !== null) {
await this.reply('设置主动打招呼的群聊名单,群号之间以,隔开,参数之间空格隔开\n' +
'#chatgpt打招呼+群号:立即在指定群聊发起打招呼' +
'#chatgpt查看打招呼\n' +
'#chatgpt删除打招呼删除主动打招呼群聊可指定若干个群号\n' +
'#chatgpt设置打招呼可指定1-3个参数依次是更新打招呼列表、打招呼间隔时间和触发概率、更新打招呼所有配置项')
return false
}
let groupId = e.msg.replace(/^#chatgpt打招呼/, '')
logger.info(groupId)
groupId = parseInt(groupId)
if (groupId && !e.bot.gl.get(groupId)) {
await this.reply('机器人不在这个群里!')
return
}
let message = await generateHello()
let sendable = message
logger.info(`打招呼给群聊${groupId}` + message)
if (Config.defaultUseTTS) {
let audio = await generateVitsAudio(message, Config.defaultTTSRole)
sendable = segment.record(audio)
}
if (!groupId) {
await this.reply(sendable)
} else {
await e.bot.sendGroupMsg(groupId, sendable)
await this.reply('发送成功!')
}
}
async sendRandomMessage () {
if (Config.debug) {
logger.info('开始处理ChatGPT随机打招呼。')
}
let toSend = Config.initiativeChatGroups || []
for (const element of toSend) {
if (!element) {
continue
}
let groupId = parseInt(element)
let bots = this.e ? [this.e.bot] : getBots()
for (let bot of bots) {
if (bot.gl?.get(groupId)) {
// 打招呼概率
if (Math.floor(Math.random() * 100) < Config.helloProbability) {
let message = await generateHello()
logger.info(`打招呼给群聊${groupId}` + message)
if (Config.defaultUseTTS) {
let audio
const [defaultVitsTTSRole, defaultAzureTTSRole, defaultVoxTTSRole] = [Config.defaultTTSRole, Config.azureTTSSpeaker, Config.voicevoxTTSSpeaker]
let ttsSupportKinds = []
if (Config.azureTTSKey) ttsSupportKinds.push(1)
if (Config.ttsSpace) ttsSupportKinds.push(2)
if (Config.voicevoxSpace) ttsSupportKinds.push(3)
if (!ttsSupportKinds.length) {
logger.warn('没有配置任何语音服务!')
return false
}
const randomIndex = Math.floor(Math.random() * ttsSupportKinds.length)
switch (ttsSupportKinds[randomIndex]) {
case 1 : {
const isEn = AzureTTS.supportConfigurations.find(config => config.code === defaultAzureTTSRole)?.language.includes('en')
if (isEn) {
message = (await translate(message, '英')).replace('\n', '')
}
audio = await AzureTTS.generateAudio(message, {
defaultAzureTTSRole
})
break
}
case 2 : {
if (Config.autoJapanese) {
try {
message = await translate(message, '日')
} catch (err) {
logger.error(err)
}
}
try {
audio = await generateVitsAudio(message, defaultVitsTTSRole, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
} catch (err) {
logger.error(err)
}
break
}
case 3 : {
message = (await translate(message, '日')).replace('\n', '')
try {
audio = await VoiceVoxTTS.generateAudio(message, {
speaker: defaultVoxTTSRole
})
} catch (err) {
logger.error(err)
}
break
}
}
if (useSilk) {
await this.e.bot.sendGroupMsg(groupId, await uploadRecord(audio))
} else {
await this.e.bot.sendGroupMsg(groupId, segment.record(audio))
}
} else {
await this.e.bot.sendGroupMsg(groupId, message)
}
} else {
logger.info(`时机未到,这次就不打招呼给群聊${groupId}`)
}
} else {
logger.warn('机器人不在要发送的群组里,忽略群。同时建议检查配置文件修改要打招呼的群号。' + groupId)
}
}
}
}
async handleSentMessage (e) {
const addReg = /^#chatgpt设置打招呼[:]?\s?(\S+)(?:\s+(\d+))?(?:\s+(\d+))?$/
const delReg = /^#chatgpt删除打招呼[:\s]?(\S+)/
const checkReg = /^#chatgpt查看打招呼$/
let replyMsg = ''
Config.initiativeChatGroups = Config.initiativeChatGroups.filter(group => group.trim() !== '')
if (e.msg.match(checkReg)) {
if (Config.initiativeChatGroups.length === 0) {
replyMsg = '当前没有需要打招呼的群聊'
} else {
replyMsg = `当前打招呼设置为:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
}
} else if (e.msg.match(delReg)) {
const groupsToDelete = e.msg.trim().match(delReg)[1].split(/[,]\s?/).filter(group => group.trim() !== '')
let deletedGroups = []
for (const element of groupsToDelete) {
if (!/^[1-9]\d{8,9}$/.test(element)) {
await this.reply(`群号${element}不合法请输入9-10位不以0开头的数字`, true)
return false
}
if (!Config.initiativeChatGroups.includes(element)) {
continue
}
Config.initiativeChatGroups.splice(Config.initiativeChatGroups.indexOf(element), 1)
deletedGroups.push(element)
}
Config.initiativeChatGroups = Config.initiativeChatGroups.filter(group => group.trim() !== '')
if (deletedGroups.length === 0) {
replyMsg = '没有可删除的群号,请输入正确的群号\n'
} else {
replyMsg = `已删除打招呼群号:${deletedGroups.join(', ')}\n`
}
replyMsg += `当前打招呼设置为:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
} else if (e.msg.match(addReg)) {
let paramArray = e.msg.match(addReg)
if (typeof paramArray[3] === 'undefined' && typeof paramArray[2] !== 'undefined') {
Config.helloInterval = Math.min(Math.max(parseInt(paramArray[1]), 1), 24)
Config.helloProbability = Math.min(Math.max(parseInt(paramArray[2]), 0), 100)
replyMsg = `已更新打招呼设置:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
} else {
const validGroups = []
const groups = paramArray ? paramArray[1].split(/[,]\s?/) : []
for (const element of groups) {
if (!/^[1-9]\d{8,9}$/.test(element)) {
await this.reply(`群号${element}不合法请输入9-10位不以0开头的数字`, true)
return false
}
if (Config.initiativeChatGroups.includes(element)) {
continue
}
validGroups.push(element)
}
if (validGroups.length === 0) {
await this.reply('没有可添加的群号,请输入新的群号')
return false
} else {
Config.initiativeChatGroups = Config.initiativeChatGroups
.filter(group => group.trim() !== '')
.concat(validGroups)
}
if (typeof paramArray[2] === 'undefined' && typeof paramArray[3] === 'undefined') {
replyMsg = `已更新打招呼设置:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
} else {
Config.helloInterval = Math.min(Math.max(parseInt(paramArray[2]), 1), 24)
Config.helloProbability = Math.min(Math.max(parseInt(paramArray[3]), 0), 100)
replyMsg = `已更新打招呼设置:\n${!e.isGroup ? '群号:' + Config.initiativeChatGroups.join(', ') + '\n' : ''}间隔时间:${Config.helloInterval}小时\n触发概率:${Config.helloProbability}%`
}
}
} else {
replyMsg = '无效的打招呼设置,请输入正确的命令。\n可发送”#chatgpt打招呼帮助“获取打招呼指北。'
}
await this.reply(replyMsg)
return false
}
async screenshotUrl (e) {
let url = e.msg.replace(/^#url(|:)/, '')
if (url.length === 0) { return false }
try {
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url
}
let urlLink = new URL(url)
await this.reply(
await renderUrl(
e, urlLink.href,
{
retType: 'base64',
Viewport: {
width: Config.chatViewWidth,
height: parseInt(Config.chatViewWidth * 0.56)
},
deviceScaleFactor: parseFloat(Config.cloudDPR)
}
),
e.isGroup && Config.quoteReply)
} catch (err) {
this.reply('无效url:' + url)
}
return true
}
async vqa (e) {
if (!Config.geminiKey) {
e.reply('需要配置Gemini密钥以使用识图')
return
}
let img = await getImg(e)
if (!img?.[0]) {
await e.reply('请发送或引用一张图片', e.isGroup)
return false
}
let client = new CustomGoogleGeminiClient({
e,
userId: e.sender.user_id,
key: Config.getGeminiKey(),
model: 'gemini-1.5-flash-latest',
baseUrl: Config.geminiBaseUrl,
debug: Config.debug
})
const response = await fetch(img[0])
const base64Image = Buffer.from(await response.arrayBuffer())
let msg = e.msg.replace(/#(识图|图片识别|VQA|vqa)/, '') || 'describe this image in Simplified Chinese'
try {
let res = await client.sendMessage(msg, {
image: base64Image.toString('base64')
})
await e.reply(res.text, true)
} catch (err) {
await e.reply('❌识图失败:' + err.message, true)
}
return true
}
}

View file

@ -1,46 +0,0 @@
/**
* 示例后处理器你可以在example下面写一个新的默认会调用所有此key的处理器
*/
export class ChatGPTResponsePostHandler extends plugin {
constructor () {
super({
name: 'chatgpt文本回复后处理器',
priority: -100,
namespace: 'chatgpt-plugin',
handler: [{
key: 'chatgpt.response.post', // key必须是chatgpt.response.post
fn: 'postHandler'
}]
})
}
async postHandler (e, options, reject) {
const { content, use, prompt } = options
// 你可以在这里处理返回的文本比如使用自定义的语音api来合成语音
// 返回值会被忽略
// 以下是一个简单的例子
// const response = await fetch('https://api.fish.audio/v1/tts', {
// method: 'POST',
// headers: {
// Authorization: 'Bearer + key',
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// text: content,
// reference_id: '1aacaeb1b840436391b835fd5513f4c4',
// format: 'mp3',
// latency: 'normal'
// })
// })
//
// if (!response.ok) {
// throw new Error(`无法从服务器获取音频数据:${response.statusText}`)
// }
//
// const audio = await response.blob()
// // to Buffer
// const buffer = await audio.arrayBuffer()
// e.reply(segment.record(Buffer.from(buffer)))
}
}

View file

@ -1,342 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
import { render } from '../utils/common.js'
let version = Config.version
let helpData = [
{
group: '聊天',
list: [
{
icon: 'chat',
title: Config.toggleMode === 'at' ? '@我+聊天内容' : '#chat+聊天内容',
desc: '与机器人聊天'
},
{
icon: 'chat',
title: '#chat1/#chat3/#chatglm/#bing/#claude/#xh',
desc: '分别使用API/API3/ChatGLM/Bing/Claude/星火模式与机器人聊天,无论主人设定了何种全局模式'
},
{
icon: 'chat-private',
title: '私聊与我对话',
desc: '与机器人聊天'
},
{
icon: 'switch',
title: '#chatgpt切换对话+对话id',
desc: '目前仅API3模式下可用切换到指定的对话中'
},
{
icon: 'switch',
title: '#chatgpt加入对话+@某人',
desc: '目前仅API3模式下可用加入到某人当前进行的对话中'
},
{
icon: 'destroy',
title: '#chatgpt删除对话+对话id或@用户',
desc: '删除指定对话,并清空与用户的关联信息。@用户时支持多个用户'
},
{
icon: 'destroy',
title: '#(结束|新开|摧毁|毁灭|完结)对话',
desc: '结束自己当前对话,下次开启对话机器人将遗忘掉本次对话内容。'
},
{
icon: 'destroy',
title: '#(结束|新开|摧毁|毁灭|完结)全部对话',
desc: '结束正在与本机器人进行对话的全部用户的对话。'
},
{
icon: 'destroy-other',
title: '#(结束|新开|摧毁|毁灭|完结)对话 @某人',
desc: '结束该用户当前对话,下次开启对话机器人将遗忘掉本次对话内容。'
},
{
icon: 'confirm',
title: '#chatgpt(导出)聊天记录',
desc: '图片形式导出聊天记录目前仅支持Bing下的Sydney和自定义'
},
{
icon: 'smiley-wink',
title: '#claude开启新对话+设定名',
desc: '结束之前的对话并开启一个新的Claude对话如果设定名不为空的话会使用这个设定。设定必须是设定列表中有的设定。'
}
]
},
{
group: '画图',
list: [
{
icon: 'draw',
title: '#chatgpt画图+prompt(/张数/图片大小)',
desc: '调用OpenAI Dalle API进行绘图需要有API key并消耗余额。图片大小只能是256x256/512x512/1024x1024中的一个.默认为1张、512x512'
},
{
icon: 'draw',
title: '#chatgpt改图',
desc: '调用OpenAI Dalle API进行改图需要有API key并消耗余额。可同时发送图片或回复图片'
},
{
icon: 'switch',
title: '#chatgpt开启/关闭画图',
desc: '开启或关闭画图功能'
}
]
},
{
group: '管理',
list: [
{
icon: 'picture',
title: '#chatgpt图片模式',
desc: '机器人以图片形式回答'
},
{
icon: 'text',
title: '#chatgpt文本模式',
desc: '机器人以文本形式回答,默认选项'
},
{
icon: 'sound',
title: '#chatgpt语音模式',
desc: '机器人以语音形式回答'
},
{
icon: 'game',
title: '#chatgpt设置语音角色',
desc: '设置语音模式下回复的角色音色。优先级高于默认语音角色'
},
{
icon: 'list',
title: '#chatgpt对话列表',
desc: '查询当前哪些人正在与机器人聊天.目前API3模式下支持切换对话'
},
{
icon: 'blue',
title: '#chatgpt(本群)?(群xxx)?闭嘴(x秒/分钟/小时)',
desc: '让机器人在本群/某群闭嘴。不指定群时认为全局闭嘴。'
},
{
icon: 'eye',
title: '#chatgpt(本群)?(群xxx)?(张嘴|开口|说话|上班)',
desc: '让机器人在本群/某群重新可以说话。不指定群时认为全局开口。'
},
{
icon: 'list',
title: '#chatgpt查看闭嘴',
desc: '查看当前闭嘴情况。'
},
{
icon: 'queue',
title: '#清空chat队列',
desc: '清空当前对话等待队列。仅建议前方卡死时使用。仅API3模式下可用'
},
{
icon: 'queue',
title: '#移出chat队列首位',
desc: '移出当前对话等待队列中的首位。若前方对话卡死可使用本命令。仅API3模式下可用'
},
{
icon: 'confirm',
title: '#chatgpt开启/关闭问题确认',
desc: '开启或关闭机器人收到消息后的确认回复消息。'
},
{
icon: 'switch',
title: '#chatgpt切换浏览器/API/API3/Bing/ChatGLM/Claude/Poe',
desc: '切换使用的后端为浏览器或OpenAI API/反代官网API/Bing/自建ChatGLM/Slack Claude/Poe'
},
{
icon: 'confirm',
title: '#chatgpt必应切换(精准|创意)',
desc: '切换Bing风格。'
},
{
icon: 'confirm',
title: '#chatgpt必应(开启|关闭)建议回复',
desc: '开关Bing模式下的建议回复。'
},
{
icon: 'list',
title: '#(关闭|打开)群聊上下文',
desc: '开启后将会发送近期群聊中的对话给机器人提供参考'
},
{
icon: 'switch',
title: '#chatgpt(允许|禁止|打开|关闭|同意)私聊',
desc: '开启后将关闭本插件的私聊通道。(主人不影响)'
},
{
icon: 'token',
title: '#chatgpt(设置|添加)群聊[白黑]名单',
desc: '白名单配置后只有白名单内的群可使用本插件,配置黑名单则会在对应群聊禁用本插件'
}
]
},
{
group: '设置',
list: [
{
icon: 'token',
title: '#chatgpt设置必应token',
desc: '设置ChatGPT或bing的Token'
},
{
icon: 'coin',
title: '#OpenAI剩余额度',
desc: '查询OpenAI API剩余试用额度'
},
{
icon: 'key',
title: '#chatgpt设置APIKey',
desc: '设置APIKey'
},
{
icon: 'key',
title: '#chatgpt设置星火token',
desc: '设置星火ssoSessionId对话页面的ssoSessionId cookie值'
},
{
icon: 'eat',
title: '#chatgpt设置(API|Sydney)设定',
desc: '设置AI的默认风格设定'
},
{
icon: 'eat',
title: '#chatgpt查看(API|Sydney)设定',
desc: '查看AI当前的风格设定文本形式返回设定太长可能发不出来'
},
{
icon: 'token',
title: '#chatgpt设置后台刷新token',
desc: '用于获取刷新令牌以便获取sessKey。'
},
{
icon: 'key',
title: '#chatgpt设置sessKey',
desc: '使用sessKey作为APIKey适用于未手机号验证的用户'
},
{
icon: 'token',
title: '#chatgpt(开启|关闭)智能模式',
desc: 'API模式下打开或关闭智能模式。'
}
]
},
{
group: '设定',
list: [
{
icon: 'smiley-wink',
title: '#chatgpt设定列表',
desc: '查看所有设定列表,以转发消息形式'
},
{
icon: 'eat',
title: '#chatgpt查看设定【设定名】',
desc: '查看指定名字的设定内容。其中API默认和Sydney默认为锅巴面板配置的设定'
},
{
icon: 'coin',
title: '#chatgpt添加设定',
desc: '添加一个设定,分此输入设定名称和设定内容。如果名字已存在,则会覆盖(相当于修改)'
},
{
icon: 'switch',
title: '#chatgpt使用设定【设定名】',
desc: '使用某个设定。'
},
{
icon: 'confirm',
title: '#chatgpt(上传|分享|共享)设定',
desc: '上传设定'
},
{
icon: 'confirm',
title: '#chatgpt(删除|取消|撤销)共享设定+设定名',
desc: '从远端删除只能删除自己上传的设定根据机器人主人qq号判断。'
},
{
icon: 'confirm',
title: '#chatgpt(在线)浏览设定(+关键词)(页码X)',
desc: '搜索公开的设定。默认返回前十条使用页码X可以翻页使用关键词可以检索。页码从1开始。'
},
{
icon: 'smiley-wink',
title: '#chatgpt预览设定详情(+设定名)',
desc: '根据设定名称预览云端设定的详情信息。'
},
{
icon: 'confirm',
title: '#chatgpt导入设定',
desc: '导入其他人分享的设定。注意:相同名字的设定,会覆盖本地已有的设定'
},
// {
// icon: 'confirm',
// title: '#chatgpt开启/关闭洗脑',
// desc: '开启或关闭洗脑'
// },
// {
// icon: 'confirm',
// title: '#chatgpt设置洗脑强度+【强度】',
// desc: '设置洗脑强度'
// },
// {
// icon: 'confirm',
// title: '#chatgpt设置洗脑名称+【名称】',
// desc: '设置洗脑名称'
// },
{
icon: 'help',
title: '#chatgpt设定帮助',
desc: '设定帮助'
}
]
},
{
group: '其他',
list: [
{
icon: 'smiley-wink',
title: '#chatgpt打招呼(群号|帮助)',
desc: '让AI随机到某个群去打招呼'
},
{
icon: 'help',
title: '#chatgpt模式帮助',
desc: '查看多种聊天模式的区别及当前使用的模式'
},
{
icon: 'help',
title: '#chatgpt全局回复帮助',
desc: '获取配置全局回复模式和全局语音角色的命令帮助'
},
{
icon: 'help',
title: '#chatgpt帮助',
desc: '获取本帮助'
}
]
}
]
export class help extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin 帮助',
dsc: 'ChatGPT-Plugin 帮助面板',
event: 'message',
priority: 500,
rule: [
{
reg: '^#(chatgpt|ChatGPT)(命令|帮助|菜单|help|说明|功能|指令|使用说明)$',
fnc: 'help'
}
]
})
}
async help (e) {
await render(e, 'chatgpt-plugin', 'help/index', { helpData, version })
}
}

View file

@ -1,119 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { render, getUin } from '../utils/common.js'
import { Config } from '../utils/config.js'
import { KeyvFile } from 'keyv-file'
async function getKeyv () {
let Keyv
try {
Keyv = (await import('keyv')).default
} catch (error) {
throw new Error('keyv依赖未安装请使用pnpm install keyv安装')
}
return Keyv
}
export class history extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin 聊天记录',
dsc: '让你的聊天更加便捷!本插件支持以图片的形式导出本次对话的聊天记录,方便随时分享精彩瞬间!',
event: 'message',
priority: 500,
rule: [
{
reg: '^#(chatgpt|ChatGPT)(导出)?聊天记录$',
fnc: 'history',
permission: 'master'
}
]
})
}
async history (e) {
let use = await redis.get('CHATGPT:USE') || 'api'
let chat = []
let filtered = e.message.filter(m => m.type === 'at').filter(m => m.qq !== getUin(e))
let queryUser = e.sender.user_id
let user = e.sender
if (filtered.length > 0) {
queryUser = filtered[0].qq
user = (await e.group.getMemberMap()).get(queryUser)
}
switch (use) {
case 'api': {
await e.reply('还不支持API模式呢')
return true
}
case 'api3': {
await e.reply('还不支持API3模式呢')
return true
}
case 'bing': {
const cacheOptions = {
namespace: Config.toneStyle,
store: new KeyvFile({ filename: 'cache.json' })
}
let Keyv = await getKeyv()
let conversationsCache = new Keyv(cacheOptions)
const conversation = (await conversationsCache.get(`SydneyUser_${queryUser}`)) || {
messages: [],
createdAt: Date.now()
}
let key = `CHATGPT:CONVERSATIONS_BING:${queryUser}`
let previousConversation = await redis.get(key) || JSON.stringify({})
previousConversation = JSON.parse(previousConversation)
let parentMessageId = previousConversation.parentMessageId
let tmp = {}
const previousCachedMessages = getMessagesForConversation(conversation.messages, parentMessageId)
.map((message) => {
return {
text: message.message,
author: message.role === 'User' ? 'user' : 'bot'
}
})
previousCachedMessages.forEach(m => {
if (m.author === 'user') {
tmp.prompt = m.text
} else {
tmp.response = m.text
chat.push(tmp)
tmp = {}
}
})
break
}
}
if (chat.length === 0) {
await e.reply('无聊天记录', e.isGroup)
return true
}
await render(e, 'chatgpt-plugin', 'content/History/index', {
version: Config.version,
user: {
qq: queryUser,
name: user.card || user.nickname || user.user_id
},
bot: {
qq: getUin(e),
name: e.bot.nickname
},
chat
}, {})
}
}
function getMessagesForConversation (messages, parentMessageId) {
const orderedMessages = []
let currentMessageId = parentMessageId
while (currentMessageId) {
const message = messages.find((m) => m.id === currentMessageId)
if (!message) {
break
}
orderedMessages.unshift(message)
currentMessageId = message.parentMessageId
}
return orderedMessages
}

File diff suppressed because it is too large Load diff

View file

@ -1,41 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
export class ChatGPTMarkdownHandler extends plugin {
constructor () {
super({
name: 'chatgptmd处理器',
priority: -100,
namespace: 'chatgpt-plugin',
handler: [{
key: 'chatgpt.markdown.convert',
fn: 'mdHandler'
}]
})
}
async mdHandler (e, options, reject) {
const { content, prompt, use } = options
if (Config.enableMd) {
let mode = transUse(use)
return `> ${prompt}\n\n---\n${content}\n\n---\n*当前模式:${mode}*`
} else {
return content
}
}
}
function transUse (use) {
let useMap = {
api: Config.model,
bing: '必应(Copilot) - ' + Config.toneStyle,
gemini: Config.geminiModel,
xh: '讯飞星火 ' + Config.xhmode,
qwen: '通义千问 ' + Config.qwenModel,
claude2: 'Claude 3 Sonnet',
glm4: 'ChatGLM4',
chat3: 'ChatGPT官网',
claude: Config.claudeApiModel
}
return useMap[use] || use
}

View file

@ -1,473 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { Config } from '../utils/config.js'
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
import AzureTTS from '../utils/tts/microsoft-azure.js'
export class help extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin 人物设定',
dsc: '让你的聊天更加有趣!本插件支持丰富的人物设定拓展,可以在线浏览并导入喜欢的设定和上传自己的设定。让你的聊天更加生动有趣!',
event: 'message',
priority: 500,
rule: [
{
reg: '^#(chatgpt|ChatGPT)设定列表$',
fnc: 'listPrompts',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)查看设定',
fnc: 'detailPrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)使用设定',
fnc: 'usePrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)添加设定',
fnc: 'addPrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)(删除|移除)设定',
fnc: 'removePrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)(上传|分享|共享)设定',
fnc: 'uploadPrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)(删除|取消|撤销)共享设定',
fnc: 'removeSharePrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)导入设定',
fnc: 'importPrompt',
permission: 'master'
},
{
reg: '^#(chatgpt|ChatGPT)(在线)?(浏览|查找)设定',
fnc: 'browsePrompt'
},
{
reg: '^#(chatgpt|ChatGPT)(在线)?预览设定详情',
fnc: 'detailCloudPrompt'
},
{
reg: '^#(chatgpt|ChatGPT)设定帮助$',
fnc: 'helpPrompt',
permission: 'master'
}
]
})
}
async listPrompts (e) {
let prompts = []
let defaultPrompt = {
name: 'API默认',
content: Config.promptPrefixOverride
}
let defaultSydneyPrompt = {
name: 'Sydney默认',
content: Config.sydney
}
prompts.push(...[defaultPrompt, defaultSydneyPrompt])
prompts.push(...readPrompts())
console.log(prompts)
e.reply(await makeForwardMsg(e, prompts.map(p => `${p.name}\n${limitString(p.content, 100)}`), '设定列表'))
}
async detailPrompt (e) {
let promptName = e.msg.replace(/^#(chatgpt|ChatGPT)查看设定/, '').trim()
let prompt = getPromptByName(promptName)
if (!prompt) {
if (promptName === 'API默认') {
prompt = {
name: 'API默认',
content: Config.promptPrefixOverride
}
} else if (promptName === 'Sydney默认') {
prompt = {
name: 'Sydney默认',
content: Config.sydney
}
} else {
await e.reply('没有这个设定', true)
return
}
}
await e.reply(`${prompt.name}\n${limitString(prompt.content, 500)}`, true)
}
async usePrompt (e) {
let promptName = e.msg.replace(/^#(chatgpt|ChatGPT)使用设定/, '').trim()
let prompt = getPromptByName(promptName)
if (!prompt) {
console.log(promptName)
if (promptName === 'API默认') {
prompt = {
name: 'API默认',
content: Config.promptPrefixOverride
}
} else if (promptName === 'Sydney默认') {
prompt = {
name: 'Sydney默认',
content: Config.sydney
}
} else {
e.msg = `#chatgpt导入设定${promptName}`
await this.importPrompt(e)
prompt = getPromptByName(promptName)
if (!prompt) {
await e.reply('没有这个设定', true)
return
}
}
}
let use = await redis.get('CHATGPT:USE') || 'api'
const keyMap = {
api: 'promptPrefixOverride',
bing: 'sydney',
claude: 'claudeSystemPrompt',
qwen: 'promptPrefixOverride',
gemini: 'geminiPrompt',
xh: 'xhPrompt'
}
if (keyMap[use]) {
if (Config.ttsMode === 'azure') {
Config[keyMap[use]] = prompt.content + '\n' + await AzureTTS.getEmotionPrompt(e)
logger.warn(Config[keyMap[use]])
} else {
Config[keyMap[use]] = prompt.content
}
if (use === 'xh') {
Config.xhPromptSerialize = false
}
if (use === 'bing') {
/**
* @type {{user: string, bot: string}[]} examples
*/
let examples = prompt.example
for (let i = 1; i <= 3; i++) {
Config[`chatExampleUser${i}`] = ''
Config[`chatExampleBot${i}`] = ''
}
for (let i = 1; i <= examples.length; i++) {
Config[`chatExampleUser${i}`] = examples[i - 1].user
Config[`chatExampleBot${i}`] = examples[i - 1].bot
}
}
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
} else {
await e.reply(`你当前正在使用${use}模式该模式不支持设定。支持设定的模式有API、必应、Claude、通义千问、星火和Gemini`, true)
}
}
async setSydneyBrainWashName (e) {
let name = e.msg.replace(/^#(chatgpt|ChatGPT)设置洗脑名称/, '')
if (name) {
Config.sydneyBrainWashName = name
await e.reply('操作成功', true)
}
}
async setSydneyBrainWash (e) {
if (e.msg.indexOf('开启') > -1) {
Config.sydneyBrainWash = true
} else {
Config.sydneyBrainWash = false
}
await e.reply('操作成功', true)
}
async setSydneyBrainWashStrength (e) {
let strength = e.msg.replace(/^#(chatgpt|ChatGPT)(设置)?洗脑强度/, '')
if (!strength) {
return
}
strength = parseInt(strength)
if (strength > 0) {
Config.sydneyBrainWashStrength = strength
await e.reply('操作成功', true)
}
}
async removePrompt (e) {
let promptName = e.msg.replace(/^#(chatgpt|ChatGPT)(删除|移除)设定/, '')
if (!promptName) {
await e.reply('你要删除哪个设定呢?')
return
}
deleteOnePrompt(promptName)
await e.reply(`设定${promptName}已删除。`)
}
async addPrompt (e) {
this.setContext('addPromptName')
await e.reply('请输入设定名称', true)
}
async addPromptName () {
if (!this.e.msg) return
let name = this.e.msg
let prompt = getPromptByName(name)
if (prompt) {
await this.e.reply('【警告】该设定已存在,新增的内容将会覆盖之前的设定', true)
// this.finish('addPromptName')
// return
}
await redis.set('CHATGPT:ADD_PROMPT_NAME', name)
await this.reply('请输入设定内容', true)
this.finish('addPromptName')
this.setContext('addPromptContext')
}
async addPromptContext () {
if (!this.e.msg) return
let content = this.e.msg
let name = await redis.get('CHATGPT:ADD_PROMPT_NAME')
saveOnePrompt(name, content)
await redis.del('CHATGPT:ADD_PROMPT_NAME')
await this.reply('设定添加成功', true)
this.finish('addPromptContext')
}
async removeSharePrompt (e) {
let master = (await getMasterQQ())[0]
let name = e.msg.replace(/^#(chatgpt|ChatGPT)(删除|取消|撤销)共享设定/, '')
let response = await fetch(`https://prompt.ikechan8370.com/prompt?name=${name}&qq=${master || (getUin(e) + '')}`, {
method: 'DELETE',
headers: {
'FROM-CHATGPT': 'ikechan8370'
}
})
if (response.status === 200) {
let json = await response.json()
if (json.code === 200 && json.data) {
await e.reply('已从云端删除该设定')
} else {
await e.reply('操作失败:' + json.msg)
}
} else {
await e.reply('操作失败:' + await response.text())
}
}
async uploadPrompt (e) {
if (await redis.get('CHATGPT:UPLOAD_PROMPT')) {
await redis.del('CHATGPT:UPLOAD_PROMPT')
// await this.reply('本机器人存在其他人正在上传设定,请稍后')
// return
}
let use = await redis.get('CHATGPT:USE') || 'api'
let currentUse = e.msg.replace(/^#(chatgpt|ChatGPT)(上传|分享|共享)设定/, '')
if (!currentUse) {
currentUse = await redis.get(`CHATGPT:PROMPT_USE_${use}`)
}
await this.reply(`即将向云端上传设定${currentUse},确定请回复确定,取消请回复取消,或者回复其他本地存在设定的名字`, true)
let extraData = {
currentUse,
use
}
await redis.set('CHATGPT:UPLOAD_PROMPT', JSON.stringify(extraData), 300)
this.setContext('uploadPromptConfirm')
}
async uploadPromptConfirm () {
if (!this.e.msg) return
let name = this.e.msg.trim()
if (name === '取消') {
await redis.del('CHATGPT:UPLOAD_PROMPT')
await this.reply('已取消上传', true)
this.finish('uploadPromptConfirm')
return
}
let extraData = JSON.parse(await redis.get('CHATGPT:UPLOAD_PROMPT'))
if (name !== '确定') {
extraData.currentUse = name
await redis.set('CHATGPT:UPLOAD_PROMPT', JSON.stringify(extraData), 300)
}
if (!getPromptByName(extraData.currentUse)) {
await redis.del('CHATGPT:UPLOAD_PROMPT')
await this.reply(`设定${extraData.currentUse}不存在,已取消上传`, true)
this.finish('uploadPromptConfirm')
return
}
// await redis.set('CHATGPT:UPLOAD_PROMPT', JSON.stringify(extraData), 300)
await this.reply('请输入对该设定的描述或备注,便于其他人快速了解该设定', true)
this.finish('uploadPromptConfirm')
this.setContext('uploadPromptDescription')
}
async uploadPromptDescription () {
if (!this.e.msg) return
let description = this.e.msg.trim()
if (description === '取消') {
// await redis.del('CHATGPT:UPLOAD_PROMPT')
await this.reply('已取消上传', true)
this.finish('uploadPromptDescription')
return
}
let extraData = JSON.parse(await redis.get('CHATGPT:UPLOAD_PROMPT'))
extraData.description = description
await redis.set('CHATGPT:UPLOAD_PROMPT', JSON.stringify(extraData), 300)
await this.reply('该设定是否是R18设定请回复是或否', true)
this.finish('uploadPromptDescription')
this.setContext('uploadPromptR18')
}
async uploadPromptR18 () {
let master = (await getMasterQQ())[0]
if (Config.debug) {
logger.mark('主人qq号' + master)
}
if (this.e.msg.trim() === '取消') {
await redis.del('CHATGPT:UPLOAD_PROMPT')
await this.reply('已取消上传', true)
this.finish('uploadPromptR18')
return
}
if (!this.e.msg || (this.e.msg !== '是' && this.e.msg !== '否')) {
return
}
let r18 = this.e.msg.trim() === '是'
await this.reply('资料录入完成,正在上传中……', true)
let extraData = JSON.parse(await redis.get('CHATGPT:UPLOAD_PROMPT'))
const { currentUse, description } = extraData
const { content } = getPromptByName(currentUse)
let examples = []
for (let i = 1; i < 4; i++) {
if (Config[`chatExampleUser${i}`]) {
examples.push({
user: Config[`chatExampleUser${i}`],
bot: Config[`chatExampleBot${i}`]
})
}
}
let toUploadBody = {
title: currentUse,
prompt: content,
qq: master || (getUin(this.e) + ''), // 上传者设定为主人qq或机器人qq
use: extraData.use === 'bing' ? 'Bing' : 'ChatGPT',
r18,
description,
examples
}
logger.info(toUploadBody)
let response = await fetch('https://prompt.ikechan8370.com/prompt', {
method: 'POST',
body: JSON.stringify(toUploadBody),
headers: {
'Content-Type': 'application/json',
'FROM-CHATGPT': 'ikechan8370'
}
})
await redis.del('CHATGPT:UPLOAD_PROMPT')
if (response.status === 200) {
response = await response.json()
if (response.data === true) {
await this.reply(`设定${currentUse}已上传,其他人可以通过#chatgpt导入设定${currentUse} 来快速导入该设定。感谢您的分享。`, true)
} else {
await this.reply(`设定上传失败,原因:${response.msg}`)
}
} else {
await this.reply(`设定上传失败: ${await response.text()}`)
}
this.finish('uploadPromptR18')
}
async detailCloudPrompt (e) {
let name = e.msg.replace(/^#(chatgpt|ChatGPT)(在线)?预览设定详情/, '')
let response = await fetch('https://prompt.ikechan8370.com/prompt?name=' + name, {
method: 'GET',
headers: {
'FROM-CHATGPT': 'ikechan8370'
}
})
if (response.status === 200) {
let r = await response.json()
if (r.code === 200) {
const { prompt, title, description, r18, qq, use } = r.data
await e.reply(`设定名称:【${title}\n贡献者:${qq}\n作者备注:${description}\n是否r18${r18 ? '是' : '否'}\n建议使用场景:${use}\n设定内容预览:${limitString(prompt, 500)}`)
} else {
await e.reply('获取设定详情失败:' + r.msg)
}
} else {
await this.reply('获取设定详情失败:' + await response.text())
}
}
async browsePrompt (e) {
let search = e.msg.replace(/^#(chatgpt|ChatGPT)(在线)?(浏览|查找)设定/, '')
let split = search.split('页码')
let page = 1
if (split.length > 1) {
search = split[0]
page = parseInt(split[1])
}
let response = await fetch('https://prompt.ikechan8370.com/prompt/list?search=' + search + `&page=${page - 1}`, {
method: 'GET',
headers: {
'FROM-CHATGPT': 'ikechan8370'
}
})
if (response.status === 200) {
const { totalElements, content, pageable } = (await response.json()).data
let output = '| 【设定名称】 | 上传者QQ | 上传时间 是否R18 使用场景 \n'
output += '----------------------------------------------------------------------------------------\n'
content.forEach(c => {
output += `| 【${c.title}】 | ${maskQQ(c.qq)} | ${c.createTime} | ${c.r18} | ${c.use}\n`
})
output += '**************************************************************************\n'
output += ` 当前为第${pageable.pageNumber + 1}页,共${totalElements}个设定\n`
output += ` 您可以使用#chatgpt浏览设定页码${pageable.pageNumber + 2}跳转到第${pageable.pageNumber + 2}\n`
await this.reply(output)
} else {
await this.reply('查询失败:' + await response.text())
}
}
async importPrompt (e) {
let promptName = e.msg.replace(/^#(chatgpt|ChatGPT)导入设定/, '')
if (!promptName) {
await e.reply('设定名字呢?', true)
return true
}
let response = await fetch('https://prompt.ikechan8370.com/prompt?name=' + promptName, {
method: 'GET',
headers: {
'FROM-CHATGPT': 'ikechan8370'
}
})
if (response.status === 200) {
let r = await response.json()
if (r.code === 200) {
if (!r.data) {
await e.reply('没有这个设定', true)
return true
}
const { prompt, title, examples } = r.data
saveOnePrompt(title, prompt, examples)
e.reply(`导入成功。您现在可以使用 #chatgpt使用设定${title} 来体验这个设定了。`)
} else {
await e.reply('导入失败:' + r.msg)
}
} else {
await this.reply('导入失败:' + await response.text())
}
// await this.reply('敬请期待', true)
}
async helpPrompt () {
await this.reply('设定目录为/plugins/chatgpt-plugin/prompts将会读取该目录下的所有[设定名].txt文件作为设定列表', true)
}
}

View file

@ -1,314 +0,0 @@
// modified from StarRail-plugin | 已经过StarRail-plugin作者本人同意
import plugin from '../../../lib/plugins/plugin.js'
import { createRequire } from 'module'
import _ from 'lodash'
import { Restart } from '../../other/restart.js'
import {} from '../utils/common.js'
const require = createRequire(import.meta.url)
const { exec, execSync } = require('child_process')
// 是否在更新中
let uping = false
/**
* 处理插件更新
*/
export class Update extends plugin {
constructor () {
super({
name: 'chatgpt更新插件',
event: 'message',
priority: 1000,
rule: [
{
reg: '^#?(chatgpt|柴特寄批踢|GPT|ChatGPT|柴特鸡批踢|Chat|CHAT|CHATGPT|柴特|ChatGPT-Plugin|ChatGPT-plugin|chatgpt-plugin)(插件)?(强制)?更新$',
fnc: 'update'
}
]
})
}
/**
* rule - 更新chatgpt插件
* @returns
*/
async update () {
if (!this.e.isMaster) return false
/** 检查是否正在更新中 */
if (uping) {
await this.reply('已有命令更新中..请勿重复操作')
return
}
/** 检查git安装 */
if (!(await this.checkGit())) return
const isForce = this.e.msg.includes('强制')
/** 执行更新 */
await this.runUpdate(isForce)
/** 是否需要重启 */
if (this.isUp) {
// await this.reply("更新完毕,请重启云崽后生效")
setTimeout(() => this.restart(), 2000)
}
}
restart () {
new Restart(this.e).restart()
}
/**
* chatgpt插件更新函数
* @param {boolean} isForce 是否为强制更新
* @returns
*/
async runUpdate (isForce) {
let command = 'git -C ./plugins/chatgpt-plugin/ pull --no-rebase'
if (isForce) {
command = `git -C ./plugins/chatgpt-plugin/ checkout . && ${command}`
this.e.reply('正在执行强制更新操作,请稍等')
} else {
this.e.reply('正在执行更新操作,请稍等')
}
/** 获取上次提交的commitId用于获取日志时判断新增的更新日志 */
this.oldCommitId = await this.getcommitId('chatgpt-plugin')
uping = true
let ret = await this.execSync(command)
uping = false
if (ret.error) {
logger.mark(`${this.e.logFnc} 更新失败chatgpt-plugin`)
this.gitErr(ret.error, ret.stdout)
return false
}
/** 获取插件提交的最新时间 */
let time = await this.getTime('chatgpt-plugin')
if (/(Already up[ -]to[ -]date|已经是最新的)/.test(ret.stdout)) {
await this.reply(`chatgpt-plugin已经是最新版本\n最后更新时间:${time}`)
} else {
await this.reply(`chatgpt-plugin\n最后更新时间:${time}`)
this.isUp = true
/** 获取chatgpt组件的更新日志 */
let log = await this.getLog('chatgpt-plugin')
await this.reply(log)
}
logger.mark(`${this.e.logFnc} 最后更新时间:${time}`)
return true
}
/**
* 获取chatgpt插件的更新日志
* @param {string} plugin 插件名称
* @returns
*/
async getLog (plugin = '') {
let cm = `cd ./plugins/${plugin}/ && git log -20 --oneline --pretty=format:"%h||[%cd] %s" --date=format:"%m-%d %H:%M"`
let logAll
try {
logAll = await execSync(cm, { encoding: 'utf-8' })
} catch (error) {
logger.error(error.toString())
this.reply(error.toString())
}
if (!logAll) return false
logAll = logAll.split('\n')
let log = []
for (let str of logAll) {
str = str.split('||')
if (str[0] == this.oldCommitId) break
if (str[1].includes('Merge branch')) continue
log.push(str[1])
}
let line = log.length
log = log.join('\n\n')
if (log.length <= 0) return ''
let end = ''
end =
'更多详细信息请前往github查看\nhttps://github.com/ikechan8370/chatgpt-plugin'
log = await this.makeForwardMsg(`chatgpt-plugin更新日志${line}`, log, end)
return log
}
/**
* 获取上次提交的commitId
* @param {string} plugin 插件名称
* @returns
*/
async getcommitId (plugin = '') {
let cm = `git -C ./plugins/${plugin}/ rev-parse --short HEAD`
let commitId = await execSync(cm, { encoding: 'utf-8' })
commitId = _.trim(commitId)
return commitId
}
/**
* 获取本次更新插件的最后一次提交时间
* @param {string} plugin 插件名称
* @returns
*/
async getTime (plugin = '') {
let cm = `cd ./plugins/${plugin}/ && git log -1 --oneline --pretty=format:"%cd" --date=format:"%m-%d %H:%M"`
let time = ''
try {
time = await execSync(cm, { encoding: 'utf-8' })
time = _.trim(time)
} catch (error) {
logger.error(error.toString())
time = '获取时间失败'
}
return time
}
/**
* 制作转发消息
* @param {string} title 标题 - 首条消息
* @param {string} msg 日志信息
* @param {string} end 最后一条信息
* @returns
*/
async makeForwardMsg (title, msg, end) {
const _bot = this.e.bot ?? Bot
let nickname = _bot.nickname
if (this.e.isGroup) {
let info = await _bot?.pickMember?.(this.e.group_id, _bot.uin) || await _bot?.getGroupMemberInfo?.(this.e.group_id, _bot.uin)
nickname = info.card || info.nickname
}
let userInfo = {
user_id: _bot.uin,
nickname
}
let forwardMsg = [
{
...userInfo,
message: title
},
{
...userInfo,
message: msg
}
]
if (end) {
forwardMsg.push({
...userInfo,
message: end
})
}
/** 制作转发内容 */
if (this.e.group?.makeForwardMsg) {
forwardMsg = await this.e.group.makeForwardMsg(forwardMsg)
} else if (this.e?.friend?.makeForwardMsg) {
forwardMsg = await this.e.friend.makeForwardMsg(forwardMsg)
} else {
return msg.join('\n')
}
let dec = 'chatgpt-plugin 更新日志'
/** 处理描述 */
if (typeof (forwardMsg.data) === 'object') {
let detail = forwardMsg.data?.meta?.detail
if (detail) {
detail.news = [{ text: dec }]
}
} else {
forwardMsg.data = forwardMsg.data
.replace(/\n/g, '')
.replace(/<title color="#777777" size="26">(.+?)<\/title>/g, '___')
.replace(/___+/, `<title color="#777777" size="26">${dec}</title>`)
}
return forwardMsg
}
/**
* 处理更新失败的相关函数
* @param {string} err
* @param {string} stdout
* @returns
*/
async gitErr (err, stdout) {
let msg = '更新失败!'
let errMsg = err.toString()
stdout = stdout.toString()
if (errMsg.includes('Timed out')) {
let remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '')
await this.reply(msg + `\n连接超时:${remote}`)
return
}
if (/Failed to connect|unable to access/g.test(errMsg)) {
let remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '')
await this.reply(msg + `\n连接失败:${remote}`)
return
}
if (errMsg.includes('be overwritten by merge')) {
await this.reply(
msg +
`存在冲突:\n${errMsg}\n` +
'请解决冲突后再更新,或者执行#强制更新,放弃本地修改'
)
return
}
if (stdout.includes('CONFLICT')) {
await this.reply([
msg + '存在冲突\n',
errMsg,
stdout,
'\n请解决冲突后再更新或者执行#强制更新,放弃本地修改'
])
return
}
await this.reply([errMsg, stdout])
}
/**
* 异步执行git相关命令
* @param {string} cmd git命令
* @returns
*/
async execSync (cmd) {
return new Promise((resolve, reject) => {
exec(cmd, { windowsHide: true }, (error, stdout, stderr) => {
resolve({ error, stdout, stderr })
})
})
}
/**
* 检查git是否安装
* @returns
*/
async checkGit () {
let ret = await execSync('git --version', { encoding: 'utf-8' })
if (!ret || !ret.includes('git version')) {
await this.reply('请先安装git')
return false
}
return true
}
}

View file

@ -1,131 +0,0 @@
import plugin from '../../../lib/plugins/plugin.js'
import { SunoClient } from '../client/SunoClient.js'
import { Config } from '../utils/config.js'
import { downloadFile, maskEmail } from '../utils/common.js'
import common from '../../../lib/common/common.js'
import lodash from 'lodash'
import fs from 'fs'
export class Vocal extends plugin {
constructor (e) {
super({
name: 'ChatGPT-Plugin 音乐合成',
dsc: '基于Suno等AI的饮月生成',
event: 'message',
priority: 500,
rule: [
{
reg: '^#((创作)?歌曲|suno|Suno)',
fnc: 'createSong',
permission: 'master'
}
]
})
// this.task = [
// {
// // 设置十分钟左右的浮动
// cron: '0/1 * * * ?',
// // cron: '*/2 * * * *',
// name: '保持suno心跳',
// fnc: this.heartbeat.bind(this)
// }
// ]
}
async heartbeat (e) {
let sessTokens = Config.sunoSessToken.split(',')
let clientTokens = Config.sunoClientToken.split(',')
for (let i = 0; i < sessTokens.length; i++) {
let sessToken = sessTokens[i]
let clientToken = clientTokens[i]
if (sessToken && clientToken) {
let client = new SunoClient({ sessToken, clientToken })
await client.heartbeat()
}
}
}
async createSong (e) {
if (!Config.sunoClientToken || !Config.sunoSessToken) {
await e.reply('未配置Suno Token')
return true
}
let description = e.msg.replace(/#((创作)?歌曲|suno|Suno)/, '')
if (description === '额度' || description === 'credit' || description === '余额') {
let sessTokens = Config.sunoSessToken.split(',')
let clientTokens = Config.sunoClientToken.split(',')
let msg = ''
for (let i = 0; i < sessTokens.length; i++) {
let sess = sessTokens[i]
let clientToken = clientTokens[i]
let client = new SunoClient({ sessToken: sess, clientToken })
let { credit, email } = await client.queryCredit()
logger.info({ credit, email })
msg += `用户: ${maskEmail(email)} 余额:${credit}\n`
}
msg += '-------------------\n'
msg += 'Notice每首歌消耗5credit每次生成2首歌'
await e.reply(msg)
return true
}
await e.reply('正在生成,请稍后')
try {
let sessTokens = Config.sunoSessToken.split(',')
let clientTokens = Config.sunoClientToken.split(',')
let tried = 0
while (tried < sessTokens.length) {
let index = tried
let sess = sessTokens[index]
let clientToken = clientTokens[index]
let client = new SunoClient({ sessToken: sess, clientToken })
let { credit, email } = await client.queryCredit()
logger.info({ credit, email })
if (credit < 10) {
tried++
logger.info(`账户${email}余额不足,尝试下一个账户`)
continue
}
let songs = await client.createSong(description)
if (!songs || songs.length === 0) {
e.reply('生成失败,可能是提示词太长或者违规,请检查日志')
return
}
let messages = ['提示词:' + description]
for (let song of songs) {
messages.push(`歌名:${song.title}\n风格: ${song.metadata.tags}\n长度: ${lodash.round(song.metadata.duration, 0)}\n歌词:\n${song.metadata.prompt}\n`)
messages.push(`音频链接:${song.audio_url}\n视频链接:${song.video_url}\n封面链接:${song.image_url}\n`)
messages.push(segment.image(song.image_url))
let retry = 3
let videoPath
while (!videoPath && retry >= 0) {
try {
videoPath = await downloadFile(song.video_url, `suno/${song.title}.mp4`, false, false, {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
})
} catch (err) {
retry--
await common.sleep(1000)
}
}
if (videoPath) {
const data = fs.readFileSync(videoPath)
messages.push(segment.video(`base64://${data.toString('base64')}`))
// 60秒后删除文件避免占用体积
setTimeout(() => {
fs.unlinkSync(videoPath)
}, 60000)
} else {
logger.warn(`${song.title}下载视频失败,仅发送视频链接`)
}
}
await e.reply(await common.makeForwardMsg(e, messages, '音乐合成结果'))
return true
}
await e.reply('所有账户余额不足')
} catch (err) {
console.error(err)
await e.reply('生成失败,请查看日志')
}
}
}