mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 05:17:10 +00:00
feat: init v3
This commit is contained in:
parent
d6cb085c40
commit
531986b2dc
284 changed files with 618 additions and 405179 deletions
365
apps/button.js
365
apps/button.js
|
|
@ -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: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
118
apps/bym.js
118
apps/bym.js
|
|
@ -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
|
||||
}
|
||||
}
|
||||
1397
apps/chat.js
1397
apps/chat.js
File diff suppressed because it is too large
Load diff
344
apps/draw.js
344
apps/draw.js
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
342
apps/help.js
342
apps/help.js
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
119
apps/history.js
119
apps/history.js
|
|
@ -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
|
||||
}
|
||||
1951
apps/management.js
1951
apps/management.js
File diff suppressed because it is too large
Load diff
41
apps/md.js
41
apps/md.js
|
|
@ -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
|
||||
}
|
||||
473
apps/prompts.js
473
apps/prompts.js
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
314
apps/update.js
314
apps/update.js
|
|
@ -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
|
||||
}
|
||||
}
|
||||
131
apps/vocal.js
131
apps/vocal.js
|
|
@ -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('生成失败,请查看日志')
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue