koishi-plugin-jrys-prpr
Version:
[<ruby>**jrys-prpr**<rp>(</rp><rt>点我查看预览图</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/article/ae33f1b2e9dbc3fe89363a40fbf040703493298333289018.png)😽QQ官方json按钮支持,20个群即可发按钮!支持 monetary!很好看的字体! 支持自动清理记录内容。
251 lines (228 loc) • 8.81 kB
text/typescript
import crypto from 'node:crypto'
import type { Context, Session } from 'koishi'
import type { Config, JrysData } from '../types'
import { getJrys } from './jrys'
/**
* 发送 Markdown 消息
*/
export async function sendmarkdownMessage(
ctx: Context,
session: Session,
message: any,
logInfo: (...args: any[]) => void
): Promise<void> {
logInfo(message)
try {
const { guild, user } = session.event
const { qq, qqguild, channelId } = session as any
if (guild?.id) {
if (qq) {
await qq.sendMessage(channelId, message)
} else if (qqguild) {
await qqguild.sendMessage(channelId, message)
}
} else if (user?.id && qq) {
await qq.sendPrivateMessage(user.id, message)
}
} catch (error) {
ctx.logger.error(`发送markdown消息时出错:`, error)
}
}
/**
* 上传图片到 QQ 频道
*/
export async function uploadImageToChannel(
ctx: Context,
imageBuffer: Buffer,
appId: string,
secret: string,
token: string,
channelId: string,
config: Config
): Promise<{ url: string }> {
async function refreshToken(bot: any) {
const { access_token: accessToken, expires_in: expiresIn } = await ctx.http.post('https://bots.qq.com/app/getAppAccessToken', {
appId: bot.appId,
clientSecret: bot.secret
})
bot.token = accessToken
ctx.setTimeout(() => refreshToken(bot), (expiresIn - 30) * 1000)
}
// 临时的bot对象
const bot = { appId, secret, token, channelId }
// 刷新令牌
await refreshToken(bot)
const payload = new FormData()
payload.append('msg_id', '0')
payload.append('file_image', new Blob([imageBuffer as any], { type: 'image/png' }), 'image.jpg')
await ctx.http.post(`https://api.sgroup.qq.com/channels/${bot.channelId}/messages`, payload, {
headers: {
Authorization: `QQBot ${bot.token}`,
'X-Union-Appid': bot.appId
}
})
// 计算MD5并返回图片URL
const md5 = crypto.createHash('md5').update(imageBuffer).digest('hex').toUpperCase()
if (channelId !== undefined && config.consoleinfo) {
ctx.logger.info(`使用本地图片*QQ频道 发送URL为: https://gchat.qpic.cn/qmeetpic/0/0-0-${md5}/0`)
}
return { url: `https://gchat.qpic.cn/qmeetpic/0/0-0-${md5}/0` }
}
/**
* 构建 Markdown 消息
*/
export async function markdown(
ctx: Context,
session: Session,
encodedMessageTime: string,
imageUrl: string,
imageToload: string,
config: Config,
logInfo: (...args: any[]) => void
): Promise<any> {
const markdownMessage: any = {
msg_type: 2,
markdown: {},
keyboard: {},
}
// 只有在非主动模式下才添加 msg_id
if (!config.markdown_button_mode_initiative) {
markdownMessage.msg_id = session.messageId
}
let originalWidth: number
let originalHeight: number
// 尝试从 URL 中解析尺寸
const sizeMatch = imageUrl.match(/\?px=(\d+)x(\d+)$/)
if (sizeMatch) {
originalWidth = parseInt(sizeMatch[1], 10)
originalHeight = parseInt(sizeMatch[2], 10)
} else {
const canvasimage = await ctx.canvas.loadImage(imageToload || imageUrl)
// @ts-ignore
originalWidth = canvasimage.naturalWidth || canvasimage.width
// @ts-ignore
originalHeight = canvasimage.naturalHeight || canvasimage.height
}
// 获取 dJson
const dJson = await getJrys(session, config, logInfo)
if (config.markdown_button_mode === "markdown") {
const templateId = config.nested.markdown_button_template_id
const keyboardId = config.nested.markdown_button_keyboard_id
const contentTable = config.nested.markdown_button_content_table
const params = contentTable.map(item => ({
key: item.raw_parameters,
values: replacePlaceholders(item.replace_parameters, { session, config, img_pxpx: `img#${originalWidth}px #${originalHeight}px`, img_url: imageUrl, encodedMessageTime, dJson }),
}))
markdownMessage.markdown = {
custom_template_id: templateId,
params: params,
}
if (config.markdown_button_mode_keyboard) {
markdownMessage.keyboard = {
id: keyboardId,
}
}
} else if (config.markdown_button_mode === "markdown_raw_json") {
const templateId = config.nested.markdown_raw_json_button_template_id
const contentTable = config.nested.markdown_raw_json_button_content_table
let keyboard = JSON.parse(config.nested.markdown_raw_json_button_keyboard)
keyboard = replacePlaceholders(keyboard, { session, config, img_pxpx: `img#${originalWidth}px #${originalHeight}px`, img_url: imageUrl, encodedMessageTime, dJson }, true)
const params = contentTable.map(item => ({
key: item.raw_parameters,
values: replacePlaceholders(item.replace_parameters, { session, config, img_pxpx: `img#${originalWidth}px #${originalHeight}px`, img_url: imageUrl, encodedMessageTime, dJson }),
}))
markdownMessage.markdown = {
custom_template_id: templateId,
params: params,
}
if (config.markdown_button_mode_keyboard) {
markdownMessage.keyboard = {
content: keyboard,
}
}
} else if (config.markdown_button_mode === "raw") {
try {
const rawMarkdownContent = config.nested.raw_markdown_button_content
const rawMarkdownKeyboard = config.nested.raw_markdown_button_keyboard
// 将 atUserString 插入到原始字符串中
const qqbotatuser = session.isDirect ? "\n" : `<qqbot-at-user id="${session.userId}" />`
const replacedMarkdownContent = replacePlaceholders(rawMarkdownContent, { session, qqbotatuser, config, img_pxpx: `img#${originalWidth}px #${originalHeight}px`, img_url: imageUrl, encodedMessageTime, dJson }, true)
const replacedMarkdownKeyboard = replacePlaceholders(rawMarkdownKeyboard, { session, qqbotatuser, config, encodedMessageTime, dJson }, true)
.replace(/^[\s\S]*?"keyboard":\s*/, '')
.replace(/\\n/g, '')
.replace(/\\"/g, '"')
.trim()
const keyboard = JSON.parse(replacedMarkdownKeyboard)
markdownMessage.markdown = {
content: replacedMarkdownContent,
}
if (config.markdown_button_mode_keyboard) {
markdownMessage.keyboard = {
content: keyboard,
}
}
} catch (error) {
ctx.logger.error(`解析原生 Markdown 出错: ${error}`)
return null
}
} else if (config.markdown_button_mode === "raw_jrys") {
try {
const raw_jrysMarkdownContent = config.nested.raw_jrys_markdown_button_content
const raw_jrysMarkdownKeyboard = config.nested.raw_jrys_markdown_button_keyboard
// 将 atUserString 插入到原始字符串中
const qqbotatuser = session.isDirect ? "\n" : `<qqbot-at-user id="${session.userId}" />`
const replacedMarkdownContent = replacePlaceholders(raw_jrysMarkdownContent, { session, qqbotatuser, dJson, config, img_pxpx: `img#${originalWidth}px #${originalHeight}px`, img_url: imageUrl, encodedMessageTime }, true)
const replacedMarkdownKeyboard = replacePlaceholders(raw_jrysMarkdownKeyboard, { session, qqbotatuser, config, encodedMessageTime, dJson }, true)
.replace(/^[\s\S]*?"keyboard":\s*/, '')
.replace(/\\n/g, '')
.replace(/\\"/g, '"')
.trim()
const keyboard = JSON.parse(replacedMarkdownKeyboard)
markdownMessage.markdown = {
content: replacedMarkdownContent,
}
if (config.markdown_button_mode_keyboard) {
markdownMessage.keyboard = {
content: keyboard,
}
}
} catch (error) {
ctx.logger.error(`解析原生 Markdown 出错: ${error}`)
return null
}
}
logInfo(`Markdown 模板参数: ${JSON.stringify(markdownMessage, null, 2)}`)
return markdownMessage
}
/**
* 替换占位符
*/
export function replacePlaceholders(content: any, context: any, isRawMode = false): any {
// 如果 content 是字符串,直接替换占位符
if (typeof content === 'string') {
if (!/\{\{\.([^}]+)\}\}|\$\{([^}]+)\}/.test(content)) {
return isRawMode ? content : [content]
}
const value = content.replace(/\{\{\.([^}]+)\}\}|\$\{([^}]+)\}/g, (match, p1, p2) => {
const key = p1 || p2
// 从 context 中查找占位符对应的值
const replacement = key.split('.').reduce((obj, k) => obj?.[k], context) || match
return replacement
})
return isRawMode ? value : [value]
}
// 如果 content 是对象或数组,递归处理
if (typeof content === 'object' && content !== null) {
if (Array.isArray(content)) {
return content.map(item => replacePlaceholders(item, context, isRawMode))
} else {
const result = {}
for (const key in content) {
result[key] = replacePlaceholders(content[key], context, isRawMode)
}
return result
}
}
// 其他情况直接返回
return content
}