diff --git a/package.json b/package.json index 94adf27..bfe9852 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,12 @@ "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json", "pub": "npm publish --access public" }, + "dependencies": { + "@karinjs/md-html": "*", + "whois-json": "*", + "systeminformation": "*", + "ioredis": "*" + }, "devDependencies": { "@types/express": "^4.17.21", "@types/lodash": "^4.17.7", diff --git a/src/apps/Houmen.ts b/src/apps/Houmen.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/SystemStatus.ts b/src/apps/SystemStatus.ts new file mode 100644 index 0000000..c722322 --- /dev/null +++ b/src/apps/SystemStatus.ts @@ -0,0 +1,311 @@ +import os from 'os' +import si from 'systeminformation' + +import { karin } from 'node-karin' + +export const unicodeCommand = karin.command(/^#(?:memz)?(?:插件)?系统状态(?:pro(max)?)?$/i, async (e) => { + // 判断匹配的模式,选择相应的处理函数 + const match = e.raw_message.match(/^#(?:memz)?(?:插件)?系统状态(?:pro(max)?)?$/i) + const mode = match && match[1] ? 'max' : match && match[0].includes('pro') ? 'extended' : 'basic' + + try { + switch (mode) { + case 'basic': + await getSystemInfo(e) + break + case 'extended': + await getExtendedSystemInfo(e) + break + case 'max': + await getMaxExtendedSystemInfo(e) + break + } + } catch (error: unknown) { + if (error instanceof Error) { await e.reply(`获取系统状态信息时出错: ${error.message}`) } + } + return true +}, { + priority: 11, + log: true, + name: '系统状态', + permission: 'all', +}) + +async function getSystemInfo (e:any) { + try { + const info = await basicInfo(e) + await e.reply(info) + } catch (error: unknown) { + if (error instanceof Error) { await e.reply(`获取系统信息时出错: ${error.message}`) } + } +} + +async function getExtendedSystemInfo (e:any) { + try { + const [ + BasicInfo, + additionalInfo, + gpuInfo, + batteryInfo, + processInfo, + ] = await Promise.all([ + basicInfo(e), + getAdditionalSystemInfo(), + getGPUInfo(), + getBatteryInfo(), + getProcessInfo(), + ]) + + const responses = [ + BasicInfo, + additionalInfo, + gpuInfo, + batteryInfo, + processInfo, + ].filter((info) => info && info.trim() !== '') + + await e.reply(responses.join('\n')) + } catch (error: unknown) { + if (error instanceof Error) { await e.reply(`获取扩展系统信息时出错: ${error.message}`, true) } + } +} + +async function getMaxExtendedSystemInfo (e:any) { + try { + const [ + BasicInfo, + additionalInfo, + gpuInfo, + batteryInfo, + processInfo, + diskDetailedInfo + ] = await Promise.all([ + basicInfo(e), + getAdditionalSystemInfo(), + getGPUInfo(), + getBatteryInfo(), + getProcessInfo(), + getDiskDetailedInfo() + ]) + + const responses = [ + BasicInfo, + additionalInfo, + gpuInfo, + batteryInfo, + processInfo, + diskDetailedInfo + ].filter((info) => info && info.trim() !== '') + + await e.reply(responses.join('\n')) + } catch (error: unknown) { + if (error instanceof Error) { + await e.reply(`获取最大扩展系统信息时出错: ${error.message}`, true) + } + } +} + +// 基本系统信息 +async function basicInfo (e: any) { + try { + const [osInfo, cpuInfo, currentLoad, memoryInfo] = await Promise.all([ + si.osInfo(), + si.cpu(), + si.currentLoad(), + si.mem() + ]) + + const systemArchitecture = `${osInfo.distro} ${osInfo.release} ${osInfo.arch}` + const cpuUsage = `${currentLoad.currentLoad.toFixed(2)}%` + const cpuSpeed = cpuInfo.speed ? `${cpuInfo.speed} GHz` : null + const cpuDetails = `${cpuInfo.physicalCores}核 ${cpuInfo.brand}` + const usedMemoryGiB = (memoryInfo.active / 1024 / 1024 / 1024).toFixed(2) + const totalMemoryGiB = (memoryInfo.total / 1024 / 1024 / 1024).toFixed(2) + const memoryUsagePercent = `${((memoryInfo.active / memoryInfo.total) * 100).toFixed(2)}%` + const memoryUsage = `${usedMemoryGiB} GiB / ${totalMemoryGiB} GiB (${memoryUsagePercent})` + + const swapUsage = + memoryInfo.swaptotal > 0 + ? `${((memoryInfo.swaptotal - memoryInfo.swapfree) / 1024 / 1024 / 1024).toFixed(2)} GiB / ${(memoryInfo.swaptotal / 1024 / 1024 / 1024).toFixed(2)} GiB` + : null + + let output = `📊 系统状态 +标准协议: ${e.bot.adapter.name} +适配器: ${e.bot.version.app_name || e.bot.version.name} +操作系统: ${osInfo.platform} +系统架构: ${systemArchitecture} +主机名: ${os.hostname()} +Node.js 版本: ${process.version} +CPU 信息: ${cpuDetails} +CPU 使用率: ${cpuUsage} +内存: ${memoryUsage} +系统运行时间: ${(os.uptime() / 86400).toFixed(2)} 天 + `.trim() + + if (cpuSpeed) output += `\nCPU 频率: ${cpuSpeed}` + if (swapUsage) output += `\n内存交换: ${swapUsage}` + + return output + } catch (error: unknown) { + if (error instanceof Error) { return `获取基本系统信息时出错: ${error.message}` } + } +} + +// 获取扩展系统信息 +async function getAdditionalSystemInfo () { + try { + const [diskInfo, cpuTemperature, networkStats, users, sshService, httpdService] = await Promise.all([ + si.fsSize(), + si.cpuTemperature(), + getNetworkBandwidth(), + si.users(), + si.services('ssh'), + si.services('httpd') + ]) + + const services = [sshService, httpdService] + + const diskDetails = diskInfo + .map((disk) => { + const total = disk.size ? `${(disk.size / 1024 / 1024 / 1024).toFixed(2)} GB` : null + const free = disk.available ? `${(disk.available / 1024 / 1024 / 1024).toFixed(2)} GB` : null + const used = disk.used ? `${(disk.used / 1024 / 1024 / 1024).toFixed(2)} GB` : null + let diskLine = `• ${disk.fs} (${disk.type})` + if (total) diskLine += `: 总量 ${total}` + if (free) diskLine += `, 可用 ${free}` + if (used) diskLine += `, 已用 ${used}` + return diskLine + }) + .filter((line) => !line.includes('N/A')) + .join('\n') || null + + const systemTemperature = cpuTemperature.main ? `${cpuTemperature.main} °C` : null + const networkBandwidth = networkStats || null + const loadAvg = os.loadavg().map((val) => val.toFixed(2)).join(' ') + const loggedInUsers = users.length > 0 ? users.map((user) => `• ${user.user}`).join('\n') : null + const serviceStatus = services.length > 0 + ? services.map( + (service:any) => `• ${service.name}: ${service.running ? '✅ Active' : '❌ Inactive'}` + ).join('\n') + : null + + let output = `💾 磁盘信息 +${diskDetails} +📈 系统负载 +${loadAvg} + `.trim() + + if (systemTemperature) output += `\n🌡️ 系统温度: ${systemTemperature}` + if (networkBandwidth) output += `\n${networkBandwidth}` + if (loggedInUsers) output += `\n👥 登录用户:\n${loggedInUsers}` + if (serviceStatus) output += `\n🛠️ 服务状态:\n${serviceStatus}` + + return output + } catch (error: unknown) { + if (error instanceof Error) { + return `获取扩展系统信息时出错: ${error.message}` + } + } +} + +// 获取磁盘详细信息 +async function getDiskDetailedInfo () { + try { + const diskPartitions = await si.diskLayout() + if (!diskPartitions || diskPartitions.length === 0) { + return null + } + + const partitionsInfo = diskPartitions + .map((partition) => { + const size = partition.size ? `${(partition.size / 1024 / 1024 / 1024).toFixed(2)} GB` : null + return `• ${partition.name}: ${size}` + }) + .join('\n') + + return `📂 磁盘分区:\n${partitionsInfo}` + } catch (error: unknown) { + if (error instanceof Error) { + return `获取磁盘分区信息时出错: ${error.message}` + } + } +} + +// 获取 GPU 信息 +async function getGPUInfo () { + try { + const gpuInfo = await si.graphics() + if (!gpuInfo.controllers || gpuInfo.controllers.length === 0) { + return '没有检测到 GPU 信息' + } + + const gpuDetails = gpuInfo.controllers + .map( + (gpu:any) => `• ${gpu.model} (VRAM: ${gpu.memory || '未知'})` + ) + .join('\n') + + return `🎮 GPU 信息:\n${gpuDetails}` + } catch (error: unknown) { + if (error instanceof Error) { return `获取 GPU 信息时出错: ${error.message}` } + } +} + +// 获取电池信息 +async function getBatteryInfo () { + try { + const batteryInfo:any = await si.battery() + if (!batteryInfo || !Object.prototype.hasOwnProperty.call(batteryInfo, 'percent')) { + return '没有检测到电池信息' + } + + return `🔋 电池信息: ${batteryInfo.percent}%` + } catch (error: unknown) { + if (error instanceof Error) { + return `获取电池信息时出错: ${error.message}` + } + } +} + +// 获取进程信息 +async function getProcessInfo () { + try { + const processes = await si.processes() + if (!processes || !Array.isArray(processes)) { + return '没有检测到进程信息' + } + + const processList = processes.slice(0, 10) + .map((proc: any) => `• ${proc.pid} ${proc.name} (${proc.cpu}%)`) + .join('\n') + + return `🧑‍💻 进程信息:\n${processList}` + } catch (error: unknown) { + if (error instanceof Error) { + return `获取进程信息时出错: ${error.message}` + } + } +} + +// 获取网络带宽信息 +async function getNetworkBandwidth () { + try { + const networkStats = await si.networkStats() + if (!networkStats || networkStats.length === 0) { + return '没有网络带宽信息' + } + + const bandwidth = networkStats + .map( + (stats:any) => + `• ${stats.interface}: 收入 ${stats.rx_bytes} 字节,输出 ${stats.tx_bytes} 字节` + ) + .join('\n') + + return `🌐 网络带宽:\n${bandwidth}` + } catch (error: unknown) { + if (error instanceof Error) { + return `获取网络带宽信息时出错: ${error.message}` + } + } +} diff --git a/src/apps/TheFilmAndTelevision.ts b/src/apps/TheFilmAndTelevision.ts deleted file mode 100644 index ddede49..0000000 --- a/src/apps/TheFilmAndTelevision.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { karin, common, segment } from 'node-karin' -export const magnetSearch = karin.command(/^#?搜影视\\s*(\\S+)$/, - async (e) => await TheFilmAndTelevision(e), { - priority: 9999, - log: true, - name: '搜影视', - permission: 'all', - }) -async function TheFilmAndTelevision (e:any) { - const match = e.msg.match(/^#?搜影视\s*(\\S+)$/) - const keyword = match ? match[1] : null - - if (!keyword) { - return await e.reply('请输入关键词进行搜索!', true) - } - - try { - const results = await searchResources(keyword) - if (results.length > 0) { - const forward = results.map((row:any) => - segment.text(`名称: ${row.title}\n文件大小: ${row.size}\n下载链接: ${row.link}`) - ) - const msg = common.makeForward(forward, e.self_id, e.bot.account.name) - await e.bot.sendForwardMessage(e.contact, msg) - } else { - await e.reply('未找到匹配的结果。', true) - } - } catch (error) { - await e.reply(`搜索过程中发生错误:${(error as Error).message}`, true) - } -} - -async function searchResources (keyword: string) { - const apiUrl = `https://ysxjjkl.souyisou.top/api_searchtxt.php?name=${encodeURIComponent(keyword)}` - - try { - const response = await fetch(apiUrl) - const text = await response.text() - - if (text.includes('[可怜]对不起,本资源暂未收录')) { - return [] - } - - const results = [] - const items = text.split('\n名称:').slice(1) - - for (const item of items) { - const nameMatch = item.match(/^(.*?)\s*链接:/) - const linkMatch = item.match(/链接:(https:\/\/.+?)(?=\s|$)/) - - if (nameMatch && linkMatch) { - results.push({ - name: nameMatch[1].trim(), - category: '影视资源', - link: linkMatch[1] - }) - } - } - - return results - } catch (error) { - console.error('请求出错:', error) - throw new Error('资源搜索失败') - } -} diff --git a/src/apps/WebTools.ts b/src/apps/WebTools.ts new file mode 100644 index 0000000..06cd425 --- /dev/null +++ b/src/apps/WebTools.ts @@ -0,0 +1,100 @@ +import { karin, logger, segment, render } from 'node-karin' + +export const unicodeCommand = karin.command(/^#?unicode(编码|解码)\s*(.+)/, async (e) => { + const commandType = e.msg.match(/^#?unicode(编码|解码)/)?.[1] + const text = e.msg.match(/^#?unicode(编码|解码)\s*(.+)$/)?.[2]?.replace(/\s+/g, '') + if (!text) { + await e.reply(`请输入有效的 Unicode ${commandType === '编码' ? '文本' : '编码'}!`, { reply: true }) + return true + } + try { + let result: string + if (commandType === '编码') { + result = Array.from(text) + .map(char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`) + .join('') + await e.reply(`编码结果:${result}`, { reply: true }) + } else if (commandType === '解码') { + result = text + .split('\\u') + .filter(s => s) + .map(hex => String.fromCharCode(parseInt(hex, 16))) + .join('') + await e.reply(`解码结果:${result}`, { reply: true }) + } + } catch (error) { + await e.reply(`${commandType === '编码' ? '编码' : '解码'}过程中发生错误,请稍后再试。`, { reply: true }) + logger.error(error) + } + return true +}, { + priority: 17, + name: 'Unicode编码解码', + permission: 'all', +}) + +export const urlCommand = karin.command(/^#?url(编码|解码)\s*(.+)/, async (e) => { + const commandType = e.msg.match(/^#?url(编码|解码)/)?.[1] + const text = e.msg.match(/^#?url(编码|解码)\s*(.+)$/)?.[2]?.replace(/\s+/g, '') + + if (!text) { + await e.reply(`请输入有效的 URL ${commandType === '编码' ? '文本' : '编码'}!`, { reply: true }) + return true + } + try { + let result: string + if (commandType === '编码') { + result = encodeURIComponent(text) + await e.reply(`编码结果:${result}`, { reply: true }) + } else if (commandType === '解码') { + result = decodeURIComponent(text) + await e.reply(`解码结果:${result}`, { reply: true }) + } + } catch (error) { + await e.reply(`${commandType === '编码' ? '编码' : '解码'}过程中发生错误,请稍后再试。`, { reply: true }) + logger.error(error) + } + + return true +}, { + priority: 17, + name: 'URL编码解码', + permission: 'all', +}) +export const screenshotCommand = karin.command(/^#网页截图\s*(\S+.*)/, async (e) => { + let url = e.msg.match(/^#网页截图\s*(\S+.*)/)?.[1].trim() + if (!url) { + await e.reply('请输入有效的网址', { reply: true }) + return true + } + + url = url.startsWith('http://') || url.startsWith('https://') ? url : 'http://' + url + + try { + const img = await render.render({ + name: '网页截图', + file: url, + type: 'png', + pageGotoParams: { + waitUntil: 'networkidle2', // 等待网络空闲时再截图 + }, + setViewport: { + width: 1920, + height: 1080, + deviceScaleFactor: 1, + }, + }) as string + + await e.reply(segment.image(img)) + return true + } catch (error: any) { + logger.error('[karin-plugin-memz]网页截图错误:', error) + await e.reply(`网页截图失败: ${error.message}`, { reply: true }) + return true + } +}, { + priority: 11, + log: true, + name: '网页截图', + permission: 'all', +}) diff --git a/src/apps/handler.ts b/src/apps/handler.ts deleted file mode 100644 index 1c6ff23..0000000 --- a/src/apps/handler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { karin, handler } from 'node-karin' - -export const test = karin.handler('test.image', async (args: any, reject: (msg?: string) => void) => { - /** 取消注释告知karin继续使用下一个处理器 */ - reject('继续循环下一个handler') - return 'Handler处理完成' -}) - -export const testHandler = karin.command(/^#?测试handler$/, async (e) => { - const msg = '测试handler' - /** 对于传参,开发者传自行需要的参数即可,无任何参数强制需求... */ - const res = await handler.call('test.image', { e, msg }) - await e.reply(res) - return true -}, { - /** 插件优先级 */ - priority: 9999, - - /** 插件触发是否打印触发日志 */ - log: true, - - /** 插件名称 */ - name: '测试handler', - - /** 谁可以触发这个插件 'all' | 'master' | 'admin' | 'group.owner' | 'group.admin' */ - permission: 'all', -}) diff --git a/src/apps/sendMsg.ts b/src/apps/sendMsg.ts deleted file mode 100644 index 3ab378d..0000000 --- a/src/apps/sendMsg.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { karin, segment, Bot, common } from 'node-karin' - -/** - * 发送主动消息插件demo - * 触发指令: #测试主动消息 - */ -export const sendMsg = karin.command(/^#测试主动消息$/, async (e) => { - /** Bot的uid 哪个Bot发就填哪个的 */ - const uid = e.bot.account.uid || e.bot.account.uin - - /** 发送目标 */ - const contact = e.contact - - /** 发送内容 */ - const message = segment.text('\n这是一条主动消息,10秒后自动撤回~') - - /** 发送消息 */ - const { message_id } = await Bot.sendMsg(uid, contact, [message], { recallMsg: 10 }) - - /** 打印返回的消息ID */ - console.log(`发送成功,消息ID:${message_id}`) - return true -}, { - /** 插件优先级 */ - priority: 9999, - - /** 插件触发是否打印触发日志 */ - log: true, - - /** 插件名称 */ - name: '主动消息demo', - - /** 谁可以触发这个插件 'all' | 'master' | 'admin' | 'group.owner' | 'group.admin' */ - permission: 'all', -}) - -/** - * 转发插件demo - * 触发指令: #测试转发 - */ -export const forwardMessage = karin.command(/^#测试转发$/, async (e) => { - /** 定义具体的转发消息 */ - const message = [ - segment.text('这是一条测试转发消息'), - segment.text('这是一条测试转发消息'), - segment.text('这是一条测试转发消息'), - ] - - /** 构建转发消息体 */ - const content = common.makeForward(message, e.self_id, e.bot.account.name) - - /** 发送转发消息 */ - await e.bot.sendForwardMessage(e.contact, content) - - /** 返回true 插件将不再继续执行下一个插件 */ - return true -}, { - /** 插件优先级 */ - priority: 9999, - - /** 插件触发是否打印触发日志 */ - log: true, - - /** 插件名称 */ - name: '转发demo', - - /** 谁可以触发这个插件 'all' | 'master' | 'admin' | 'group.owner' | 'group.admin' */ - permission: 'all', -}) diff --git a/src/apps/task.ts b/src/apps/task.ts deleted file mode 100644 index c3c1214..0000000 --- a/src/apps/task.ts +++ /dev/null @@ -1,11 +0,0 @@ -// import { karin, logger } from 'node-karin' - -/** - * 定时任务模板 - * 参数1: 任务名称 - * 参数2: cron表达式 - * 参数3: 任务方法 - */ -// export const Task = karin.task('1分钟打印1次hello', '0 */1 * * * *', async () => { -// logger.info('hello') -// }) diff --git a/src/index.ts b/src/index.ts index 2c8e402..3118c9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import { logger } from 'node-karin' import { basename, config } from '@/utils' -/** 请不要在这编写插件 不会有任何效果~ */ -logger.info(`${logger.violet(`[插件:${config.package.version}]`)} ${logger.green(basename)} 初始化完成~`) +logger.info(`${logger.violet(`[karin-plugin-memz:${config.package.version}]`)} ${logger.green(basename)} 初始化完成~`) diff --git a/src/utils/config.ts b/src/utils/config.ts index 1e50823..da906b0 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -59,7 +59,7 @@ class Cfg { try { if (!yaml.has(key)) yaml.set(key, val) } catch (error: any) { - logger.error(`[common] 更新yaml文件时出错:${error.stack || error.message || error}`) + logger.error(`[karin-plugin-memz] 更新yaml文件时出错:${error.stack || error.message || error}`) } }) /** 先保存 */ @@ -71,7 +71,7 @@ class Cfg { try { yaml.comment(key, comment, type) } catch (error: any) { - logger.error(`[common] 更新yaml文件时出错:${error.stack || error.message || error}`) + logger.error(`[karin-plugin-memz] 更新yaml文件时出错:${error.stack || error.message || error}`) } }) yaml.save()