/// /// /// // @ts-check declare type Color = { R: number, G: number, B: number, Transparent: Color, Black: Color, White: Color, FromArgb(r: number, g: number, b: number): Color, FromArgb(a: number, r: number, g: number, b: number): Color, } declare type Config = { urls: string[], originType: 'api' | 'local', title: string | null, font: string | null, shadow: boolean, theme: 'dark' | 'light' | 'auto', defaultColor: string } import stdio = require('./modules/stdio.js'); const { existFile, existDirectory, createDirectory, deleteFile, getFiles, getExtension, getFileName, writeAllTextToFile, readAllTextFromFile } = stdio; /** * 导入命名空间 */ const { Drawing: { Drawing2D: { CompositingQuality, InterpolationMode, SmoothingMode, }, Imaging: { ImageFormat, }, Text: { TextRenderingHint, }, Bitmap, Color, Font, Graphics, Rectangle, RectangleF, StringAlignment, StringFormat, StringTrimming, SolidBrush, }, IO: { DriveInfo, File, SearchOption, }, Net: { Http: { HttpClient } }, Convert, Environment, GC } = System; /** * 图片缓存 */ const caches = new Map(); /** * 路径 */ const PATH = { main: './plugins/StatPictureGenerater/', preLoadConfig: './plugins/StatPictureGenerater/PreLoadConfig.json', caches: './plugins/StatPictureGenerater/cache', config: './plugins/StatPictureGenerater/config.json', } /** * 尺寸 */ const SIZES = { width: 1080, height: 1440, padding: 30, get shadowOffset() { return config.shadow ? 1.5 : 0 } }; /** * 版本 */ const VERSION = 'v1.0'; /** * 默认配置 */ const BASECONFIG: Config = { urls: [ 'https://t.mwm.moe/ysmp/', 'https://t.mwm.moe/mp/', 'https://t.mwm.moe/moemp/', ], originType: 'api', title: 'Serein · 状态', font: '得意黑', shadow: true, theme: 'auto', defaultColor: '' }; serein.registerPlugin('状态图片生成', VERSION, 'Zaiton', ''); checkPreLoadConfig(); clearAllCache(); /** * http客户端 */ const httpClient = new HttpClient(); /** * 对齐格式 */ const FORMATS = getFormats(); /** * 日志输出对象 */ const logger = new Logger('StatPictureGenerater'); /** * 配置文件 */ let config = loadConfig() || BASECONFIG; /** * 上次触发时间 */ let lastTime = 0; /** * 检查预加载配置 */ function checkPreLoadConfig() { if (!existFile(PATH.preLoadConfig)) { createDirectory(PATH.caches); const assemblies = [ 'System.Drawing', 'System.Net.Http' ]; if (Environment.Version.Major === 6) assemblies.push('System.IO.FileSystem.DriveInfo'); serein.setPreLoadConfig(assemblies); throw new Error('请重新加载此插件'); } } /** * 生成 * @param packet * @returns CQ码 */ function generate(packet: Packet) { const { group_id, user_id } = packet || {}; logger.info(`群聊${group_id || '未知'}`, `用户${user_id || '未知'}`, '触发。'); const time = Date.now(); if (time - lastTime < 10_000) { logger.warn('生成失败:冷却中'); return '技能冷却中...'; } if (!existDirectory(PATH.caches)) createDirectory(PATH.caches); if (Array.from(caches.keys()).length > 100) clearCaches(); const fileId = Math.floor((Math.random() * 256 * 256 * 256 * 256)).toString(16).padStart(8, '0'); const file = `${PATH.caches}/${fileId}.png`; const bitmap = new Bitmap(SIZES.width, SIZES.height); const graphics = Graphics.FromImage(bitmap); logger.debug(`开始生成,PicId:${fileId}。`); // 图片质量 graphics.CompositingQuality = CompositingQuality.HighQuality; // 高质量、低速度复合 graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; // 高质量的双三次插值法 graphics.TextRenderingHint = TextRenderingHint.AntiAlias; // 抗锯齿 graphics.SmoothingMode = SmoothingMode.AntiAlias; // 抗锯齿 const colors = { text: null, shadow: null, background: null, main: [0, 0, 0], }; let size: [number, number] = [0, 0]; switch (config.originType.toLowerCase()) { case 'api': if (config.urls.length === 0) throw new Error('没有可用的API呢!!'); const netBitmap = getImgFromUrl(config.urls[Math.floor(Math.random() * config.urls.length)]); size = calculateSize(netBitmap.Width, netBitmap.Height); graphics.DrawImage( netBitmap, new Rectangle(0, 0, size[0], size[1]) ); logger.debug(`缩放完毕。[${netBitmap.Width},${netBitmap.Height}]→[${size.join(',')}]`); colors.main = getMainColor(netBitmap); netBitmap.Dispose(); break; case 'local': const backgroundFiles = getFiles(PATH.main, '*.*', SearchOption.TopDirectoryOnly) .filter((f: string) => ['.png', '.jpg', '.jpeg', '.bmp'].includes(getExtension(f)?.toLowerCase() || '')); if (backgroundFiles.length == 0) throw new Error('没有可用的背景图片呢!!'); const bgFileName = backgroundFiles[Math.floor(Math.random() * backgroundFiles.length)]; logger.debug(`随机背景图:${getFileName(bgFileName)}。`); const randomBitmap = Bitmap.FromFile(bgFileName); size = calculateSize(randomBitmap.Width, randomBitmap.Height); graphics.DrawImage( randomBitmap, new Rectangle(0, 0, size[0], size[1]) ); logger.debug(`缩放完毕。[${randomBitmap.Width},${randomBitmap.Height}]→[${size.join(',')}]`); colors.main = getMainColor(randomBitmap); randomBitmap.Dispose(); break; default: logger.warn('图片不见辣!背景将保持纯白。'); graphics.DrawImage( new SolidBrush(Color.FromArgb(255, 255, 255)), new Rectangle(0, 0, SIZES.width, SIZES.height) ); colors.main = [0x78, 0xb3, 0xaa]; break; } // 从配置中读取主题颜色 if (config.defaultColor && typeof (config.defaultColor) === 'string') { const defaultColor: Color = getColor(config.defaultColor); if (defaultColor.R || defaultColor.G || defaultColor.B) { colors.main = [defaultColor.R, defaultColor.G, defaultColor.B]; serein.log([defaultColor.R, defaultColor.G, defaultColor.B].join(',')) } } /** * 主颜色的RGB平均值 */ const avarage = (colors.main[0] + colors.main[1] + colors.main[2]) / 3; // 决定主题 if (config.theme === 'auto' && avarage > 150 || config.theme === 'light') { colors.text = Color.Black; colors.background = Color.FromArgb(60, Color.White); colors.shadow = getColor('#bbb'); colors.main = colors.main.map((value) => value * 1.2).map((value) => value > 255 ? 255 : value); } else { colors.text = Color.White; colors.background = Color.FromArgb(140, Color.Black); colors.shadow = Color.Gray; colors.main = colors.main.map((value) => value * 0.8); } // 标题-阴影 graphics.DrawString( config.title || 'Serein · 状态', new Font(config.font, 45), new SolidBrush(colors.shadow), new RectangleF( 20 + SIZES.shadowOffset * 2, 30 + SIZES.shadowOffset * 2, SIZES.width - 40, 160), FORMATS.center ); // 标题 graphics.DrawString( config.title || 'Serein · 状态', new Font(config.font, 45), new SolidBrush(colors.text), new RectangleF( 20, 30, SIZES.width - 40, 160), FORMATS.center ); const [ sys_top, // 顶部偏移 sys_left, // 左侧偏移 sys_size // 大小 ] = [200, 140, 250]; // 系统信息 // 长方形背景 graphics.FillRectangle( new SolidBrush(colors.background), new Rectangle( SIZES.padding, sys_top, SIZES.width - 2 * SIZES.padding, sys_size * 2 + 2 * SIZES.padding + 50, )); // 获取相关变量 const { Name: os, Hardware: { RAM: { Free: ram_free, Total: ram_total }, CPUs: [ { Name: cpu_name, PhysicalCores: cpu_cores } ] } } = serein.getSysInfo(); const cpu_usage = serein.getCPUUsage() || 0; const [uploadSpeed, downloadSpeed] = serein.getNetSpeed(); { // CPU总-背景阴影 graphics.FillPie( new SolidBrush(Color.FromArgb(50, 0, 0, 0)), sys_left + SIZES.shadowOffset + SIZES.padding, sys_top + SIZES.shadowOffset + SIZES.padding, sys_size, sys_size, -90, 360); // CPU总-背景 graphics.FillPie( new SolidBrush(colors.background), sys_left + SIZES.padding, sys_top + SIZES.padding, sys_size, sys_size, -60, 360); // CPU占用扇形 graphics.FillPie( new SolidBrush(Color.FromArgb(175, colors.main[0], colors.main[1], colors.main[2])), sys_left + SIZES.padding, sys_top + SIZES.padding, sys_size, sys_size, -90, 360 * cpu_usage / 100); // CPU占用文本-阴影 graphics.DrawString( `${cpu_usage.toFixed(1)}%`, new Font(config.font, 30), new SolidBrush(colors.shadow), new RectangleF( sys_left + SIZES.shadowOffset + SIZES.padding, sys_top + SIZES.shadowOffset + SIZES.padding, sys_size, sys_size), FORMATS.centerCenter ); // CPU占用文本 graphics.DrawString( `${cpu_usage.toFixed(1)}%`, new Font(config.font, 30), new SolidBrush(colors.text), new RectangleF( sys_left + SIZES.padding, sys_top + SIZES.padding, sys_size, sys_size), FORMATS.centerCenter ); // 内存总-背景阴影 graphics.FillPie( new SolidBrush(Color.FromArgb(50, 0, 0, 0)), SIZES.width - sys_left - SIZES.padding - sys_size + SIZES.shadowOffset, sys_top + SIZES.padding + SIZES.shadowOffset, sys_size, sys_size, -60, 360 ); // 内存总-背景 graphics.FillPie( new SolidBrush(colors.background), SIZES.width - sys_left - SIZES.padding - sys_size, sys_top + SIZES.padding, sys_size, sys_size, -60, 360); // 内存占用扇形 graphics.FillPie( new SolidBrush(Color.FromArgb(175, colors.main[0], colors.main[1], colors.main[2])), SIZES.width - sys_left - SIZES.padding - sys_size, sys_top + SIZES.padding, sys_size, sys_size, -90, 360 * ((ram_total - ram_free) / ram_total)); // 内存占用文本-阴影 graphics.DrawString( `${((ram_total - ram_free) / ram_total * 100).toFixed(1)}%`, new Font(config.font, 30), new SolidBrush(colors.shadow), new RectangleF( SIZES.width - sys_left + SIZES.shadowOffset - SIZES.padding - sys_size, sys_top + SIZES.shadowOffset + SIZES.padding, sys_size, sys_size), FORMATS.centerCenter ); // 内存占用文本 graphics.DrawString( `${((ram_total - ram_free) / ram_total * 100).toFixed(1)}%`, new Font(config.font, 30), new SolidBrush(colors.text), new RectangleF( SIZES.width - sys_left - SIZES.padding - sys_size, sys_top + SIZES.padding, sys_size, sys_size), FORMATS.centerCenter ); } { // CPU核数-阴影 graphics.DrawString( `CPU (${cpu_cores || '未知'}核)`, new Font(config.font, 20), new SolidBrush(colors.shadow), new RectangleF( sys_left + SIZES.padding + SIZES.shadowOffset, sys_top + SIZES.shadowOffset + SIZES.padding + sys_size + 10, sys_size, 90), FORMATS.center ); // CPU核数 graphics.DrawString( `CPU (${cpu_cores || '未知'}核)`, new Font(config.font, 20), new SolidBrush(colors.text), new RectangleF( sys_left + SIZES.padding, sys_top + SIZES.padding + sys_size + 10, sys_size, 90), FORMATS.center ); // CPU名称-阴影 graphics.DrawString( cpu_name || '', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( sys_left + SIZES.padding + SIZES.shadowOffset - sys_size / 2, sys_top + SIZES.shadowOffset + SIZES.padding + sys_size + 10 + 70, sys_size * 2, 90), FORMATS.center ); // CPU名称 graphics.DrawString( cpu_name || '', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( sys_left + SIZES.padding - sys_size / 2, sys_top + SIZES.padding + sys_size + 10 + 70, sys_size * 2, 90), FORMATS.center ); // 内存标题-阴影 graphics.DrawString( 'RAM', new Font(config.font, 20), new SolidBrush(colors.shadow), new RectangleF( SIZES.width - sys_left - SIZES.padding - sys_size + SIZES.shadowOffset, sys_top + SIZES.padding + sys_size + 10 + SIZES.shadowOffset, sys_size, 90), FORMATS.center ); // 内存标题 graphics.DrawString( 'RAM', new Font(config.font, 20), new SolidBrush(colors.text), new RectangleF( SIZES.width - sys_left - SIZES.padding - sys_size, sys_top + SIZES.padding + sys_size + 10, sys_size, 90), FORMATS.center ); // 已用内存-阴影 graphics.DrawString( `已用内存 ${((ram_total - ram_free) / 1024 / 1024).toFixed(1)}GB\n总内存 ${(ram_total / 1024 / 1024).toFixed(1)}GB`, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.width - sys_left - SIZES.padding - sys_size + SIZES.shadowOffset, sys_top + SIZES.padding + sys_size + 10 + 70 + SIZES.shadowOffset, sys_size, 90), FORMATS.center ); // 已用内存 graphics.DrawString( `已用内存 ${((ram_total - ram_free) / 1024 / 1024).toFixed(1)}GB\n总内存 ${(ram_total / 1024 / 1024).toFixed(1)}GB`, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.width - sys_left - SIZES.padding - sys_size, sys_top + SIZES.padding + sys_size + 10 + 70, sys_size, 90), FORMATS.center ); // 网速-阴影 graphics.DrawString( `上传: ${uploadSpeed} · 下载: ${downloadSpeed}`, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding + SIZES.shadowOffset, sys_top + SIZES.shadowOffset + SIZES.padding + sys_size + 30 + 180, SIZES.width - 2 * SIZES.padding, 90), FORMATS.center ); // 网速 graphics.DrawString( `上传: ${uploadSpeed} · 下载: ${downloadSpeed}`, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding, sys_top + SIZES.padding + sys_size + 30 + 180, SIZES.width - 2 * SIZES.padding, 90), FORMATS.center ); // 系统-阴影 graphics.DrawString( `OS: ${os}`, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding + SIZES.shadowOffset, sys_top + SIZES.shadowOffset + SIZES.padding + sys_size + 30 + 230, SIZES.width - 2 * SIZES.padding, 90), FORMATS.center ); // 系统 graphics.DrawString( `OS: ${os}`, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding, sys_top + SIZES.padding + sys_size + 30 + 230, SIZES.width - 2 * SIZES.padding, 90), FORMATS.center ); } const [ subBar_top, // 顶部距离 subBar_lineHeight // 行高 ] = [sys_top + SIZES.padding * 3 + sys_size + 10 + 320, 70]; // 长方形背景 graphics.FillRectangle( new SolidBrush(colors.background), new Rectangle( SIZES.padding, subBar_top, SIZES.width / 2 - 2 * SIZES.padding, subBar_lineHeight * 6 + 2 * SIZES.padding - 30, )); // 获取相关信息 const empty = '—'; const status = serein.getServerStatus(); const type = ['未知/其他', '基岩版', 'Java版'][serein.getSettingsObject().server.type]; const motd = serein.getServerMotd(); // 服务器状态-阴影 graphics.DrawString( '服务器状态', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset, 200, subBar_lineHeight), FORMATS.left ); // 服务器状态 graphics.DrawString( '服务器状态', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2, subBar_top + SIZES.padding, 200, subBar_lineHeight), FORMATS.left ); // 服务器状态-阴影 graphics.DrawString( status ? '运行中' : '未启动', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + 200 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset, 200, subBar_lineHeight), FORMATS.right ); // 服务器状态 graphics.DrawString( status ? '运行中' : '未启动', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + 200, subBar_top + SIZES.padding, 200, subBar_lineHeight), FORMATS.right ); // 类型-阴影 graphics.DrawString( '类型', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight, 200, subBar_lineHeight), FORMATS.left ); // 类型 graphics.DrawString( '类型', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2, subBar_top + SIZES.padding + subBar_lineHeight, 200, subBar_lineHeight), FORMATS.left ); // 类型-阴影 graphics.DrawString( status && type || empty, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + 200 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight, 200, subBar_lineHeight), FORMATS.right ); // 类型 graphics.DrawString( status && type || empty, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + 200, subBar_top + SIZES.padding + subBar_lineHeight, 200, subBar_lineHeight), FORMATS.right ); // 版本-阴影 graphics.DrawString( '版本', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 2, 200, subBar_lineHeight), FORMATS.left ); // 版本 graphics.DrawString( '版本', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2, subBar_top + SIZES.padding + subBar_lineHeight * 2, 200, subBar_lineHeight), FORMATS.left ); // 版本-阴影 graphics.DrawString( status && `${motd?.version || '未知'} (${motd?.protocol || '未知'})` || empty, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + 200 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 2, 200, subBar_lineHeight), FORMATS.right ); // 版本 graphics.DrawString( status && `${motd?.version || '未知'} (${motd?.protocol || '未知'})` || empty, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + 200, subBar_top + SIZES.padding + subBar_lineHeight * 2, 200, subBar_lineHeight), FORMATS.right ); // 在线人数-阴影 graphics.DrawString( '在线人数', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 3, 200, subBar_lineHeight), FORMATS.left ); // 在线人数 graphics.DrawString( '在线人数', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2, subBar_top + SIZES.padding + subBar_lineHeight * 3, 200, subBar_lineHeight), FORMATS.left ); // 在线人数-阴影 graphics.DrawString( status && `${motd?.onlinePlayer}/${motd?.maxPlayer}` || empty, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + 200 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 3, 200, subBar_lineHeight), FORMATS.right ); // 在线人数 graphics.DrawString( status && `${motd?.onlinePlayer}/${motd?.maxPlayer}` || empty, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + 200, subBar_top + SIZES.padding + subBar_lineHeight * 3, 200, subBar_lineHeight), FORMATS.right ); // 运行时间-阴影 graphics.DrawString( '运行时间', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 4, 200, subBar_lineHeight), FORMATS.left ); // 运行时间 graphics.DrawString( '运行时间', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2, subBar_top + SIZES.padding + subBar_lineHeight * 4, 200, subBar_lineHeight), FORMATS.left ); // 运行时间-阴影 graphics.DrawString( status && serein.getServerTime() || empty, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + 200 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 4, 200, subBar_lineHeight), FORMATS.right ); // 运行时间 graphics.DrawString( status && serein.getServerTime() || empty, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + 200, subBar_top + SIZES.padding + subBar_lineHeight * 4, 200, subBar_lineHeight), FORMATS.right ); // 进程占用-阴影 graphics.DrawString( '进程占用', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 5, 200, subBar_lineHeight), FORMATS.left ); // 进程占用 graphics.DrawString( '进程占用', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2, subBar_top + SIZES.padding + subBar_lineHeight * 5, 200, subBar_lineHeight), FORMATS.left ); // 进程占用-阴影 const server_usage = `${serein.getServerCPUUsage().toFixed(1)}%`; graphics.DrawString( status && server_usage || empty, new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + 200 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * 5, 200, subBar_lineHeight), FORMATS.right ); // 进程占用 graphics.DrawString( status && server_usage || empty, new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + 200, subBar_top + SIZES.padding + subBar_lineHeight * 5, 200, subBar_lineHeight), FORMATS.right ); // 长方形背景 graphics.FillRectangle( new SolidBrush(colors.background), new Rectangle( SIZES.padding + SIZES.width / 2, subBar_top, SIZES.width / 2 - 2 * SIZES.padding, subBar_lineHeight * 6 + 2 * SIZES.padding - 30, )); // 硬盘用量-阴影 graphics.DrawString( '硬盘用量', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding + SIZES.width / 2 + SIZES.shadowOffset, subBar_top + 30 + SIZES.shadowOffset, SIZES.width / 2 - SIZES.padding * 2, subBar_lineHeight), FORMATS.center ); // 硬盘用量 graphics.DrawString( '硬盘用量', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding + SIZES.width / 2, subBar_top + 30, SIZES.width / 2 - SIZES.padding * 2, subBar_lineHeight), FORMATS.center ); // 获取所有的驱动器 const allDirves = DriveInfo.GetDrives(); for (let index = 1; index < allDirves.length + 1 && index < 6; index++) { const drive = allDirves[index - 1]; // 解构对象 const { TotalFreeSpace: totalFreeSpace, TotalSize: totalSize, Name: [name] } = drive; // 盘符-阴影 graphics.DrawString( name + ':\\', new Font(config.font, 13), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.width / 2 + SIZES.shadowOffset, subBar_top + SIZES.padding + SIZES.shadowOffset + subBar_lineHeight * index, 200, subBar_lineHeight), FORMATS.left ); // 盘符 graphics.DrawString( name + ':\\', new Font(config.font, 13), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + SIZES.width / 2, subBar_top + SIZES.padding + subBar_lineHeight * index, 200, subBar_lineHeight), FORMATS.left ); // 占用空间长方形背景 graphics.FillRectangle( new SolidBrush(colors.background), new RectangleF( SIZES.padding * 2 + SIZES.width / 2 + 80, subBar_top + subBar_lineHeight * index + 30, SIZES.width / 2 - SIZES.padding * 2 - 160, subBar_lineHeight * 0.5 )); // 占用比例 graphics.FillRectangle( new SolidBrush(Color.FromArgb(175, colors.main[0], colors.main[1], colors.main[2])), new RectangleF( SIZES.padding * 2 + SIZES.width / 2 + 80, subBar_top + subBar_lineHeight * index + 30, (SIZES.width / 2 - SIZES.padding * 2 - 160) * (1 - totalFreeSpace / totalSize), subBar_lineHeight * 0.5 )); // 具体百分比-阴影 graphics.DrawString( `${(100 - totalFreeSpace / totalSize * 100).toFixed(1)}%`, new Font(config.font, 10), new SolidBrush(colors.shadow), new RectangleF( SIZES.padding * 2 + SIZES.width / 2 + 80 + SIZES.shadowOffset, subBar_top + subBar_lineHeight * index + 30 + SIZES.shadowOffset, SIZES.width / 2 - SIZES.padding * 2 - 160, subBar_lineHeight), FORMATS.center ); // 具体百分比 graphics.DrawString( `${(100 - totalFreeSpace / totalSize * 100).toFixed(1)}%`, new Font(config.font, 10), new SolidBrush(colors.text), new RectangleF( SIZES.padding * 2 + SIZES.width / 2 + 80, subBar_top + subBar_lineHeight * index + 30, SIZES.width / 2 - SIZES.padding * 2 - 160, subBar_lineHeight), FORMATS.center ); } // 水印 graphics.DrawString( `Serein@${serein.version} StatPictureGenerater@${VERSION}\nPicId: ${fileId} Time: ${new Date().toLocaleTimeString()}`, new Font(config.font, 6), new SolidBrush(Color.FromArgb(50, 128, 128, 128)), new RectangleF( SIZES.padding, SIZES.height - 40, SIZES.width - 2 * SIZES.padding, 40), FORMATS.centerBottom ); // 保存 bitmap.Save(file, ImageFormat.Png); bitmap.Dispose(); graphics.Dispose(); // 设置缓存 caches.set(file, Date.now()); // 清理内存 GC.Collect(); logger.info(`生成完毕。用时:${(Date.now() - time) / 1000}s`) return `[CQ:image,file=base64://${Convert.ToBase64String(File.ReadAllBytes(file))}]` // 返回base64CQ🐴 } /** * 清除缓存 */ function clearCaches() { caches.forEach((value, key) => { if (Number.isInteger(value) && Date.now() - value > 5000 && existFile(key)) { deleteFile(key); caches.delete(key); } }) } /** * 清除所有缓存 */ function clearAllCache() { for (const file of getFiles(PATH.caches, '*.png')) deleteFile(file); } /** * 处理 * @param packet 数据包 */ function handle(packet: Packet) { try { return generate(packet); } catch (e: any) { serein.sendGroup(packet.group_id, e?.message || e.toString() || e); throw e; } } /** * 注册命令 */ function reg() { const CHregCommand: CHregCommand = serein.imports('CHregCommand'); if (!CHregCommand) throw new Error('你需要安装`CommandHelper.js`'); CHregCommand({ name: '当前状态图片', keywords: ['zt', '状态', '服务器状态'], callback: handle, needAdmin: false, description: ['以图片方式返回当前状态', '用法:发送“zt”|“状态”|“服务器状态”'], author: 'Zaitonn', version: VERSION }); } /** * 获取颜色 * @param hexColor 十六进制颜色代码 * @returns 颜色 */ function getColor(hexColor: string): Color { if (typeof (hexColor) !== 'string' || !/^#?[a-zA-Z0-9]{positions.offset}$/.test(hexColor) && !/^#?[a-zA-Z0-9]{6}$/.test(hexColor) && !/^#?[a-zA-Z0-9]{8}$/.test(hexColor)) return Color.Transparent; const hexs = hexColor .replace(/^#/, '') .split(new RegExp(`([a-zA-Z0-9]{${hexColor.length <= 4 ? 1 : 2}})`)) .filter(value => value); const numbers = hexs.map((num) => parseInt(`0x${num}`)); serein.log(numbers.join(',')); switch (numbers.length) { case 3: return Color.FromArgb(numbers[0], numbers[1], numbers[2]); case 4: return Color.FromArgb(numbers[3], numbers[0], numbers[1], numbers[2]); default: return Color.Transparent; } } /** * 获取主要颜色 * @param file 文件名 * @returns */ function getMainColor(file: string): [number, number, number] { const bitmap = new Bitmap(10, 10); const graphics = Graphics.FromImage(bitmap); graphics.DrawImage( file, new Rectangle(0, 0, 10, 10) ); let r = 0, g = 0, b = 0, t = 0; for (let x = 0; x < 10; x++) for (let y = 0; y < 10; y++) { const c = bitmap.GetPixel(x, y); if (c.R || c.G || c.B) r += c.R, g += c.G, b += c.B, t += 1; } bitmap.Dispose(); graphics.Dispose(); return [r / t, g / t, b / t]; } /** * 获取所有的对齐格式 * @returns */ function getFormats() { const center = new StringFormat(); center.Alignment = StringAlignment.Center; center.Trimming = StringTrimming.Character; const centerCenter = new StringFormat(); centerCenter.Alignment = StringAlignment.Center; centerCenter.LineAlignment = StringAlignment.Center; centerCenter.Trimming = StringTrimming.Character; const centerBottom = new StringFormat(); centerBottom.Alignment = StringAlignment.Center; centerBottom.LineAlignment = StringAlignment.Center; centerBottom.Trimming = StringTrimming.Character; const left = new StringFormat(); left.Alignment = StringAlignment.Near; left.Trimming = StringTrimming.Character; const right = new StringFormat(); right.Alignment = StringAlignment.Far; right.Trimming = StringTrimming.Character; return { left, right, center, centerCenter, centerBottom }; } /** * 从链接获取图片 * @param url * @returns 图片 */ function getImgFromUrl(url: string) { return Bitmap.FromStream( httpClient .GetAsync(url) .GetAwaiter() .GetResult() .Content .ReadAsStreamAsync() .GetAwaiter() .GetResult() ); } /** * 计算缩放尺寸 * @param width 宽 * @param height 高 * @returns 计算结果 */ function calculateSize(width: number, height: number): [number, number] { // 分别计算长宽偏差的百分比 const percent = [ Math.abs((width - SIZES.width) / SIZES.width), Math.abs((height - SIZES.height) / SIZES.height) ]; // 取小的一边作为基准 if (percent[0] < percent[1]) { return [SIZES.width, height / width * SIZES.width]; } return [width / height * SIZES.height, SIZES.height]; } /** * 加载配置 * @returns 配置对象 */ function loadConfig() { if (!existFile(PATH.config)) { createDirectory(PATH.main); writeAllTextToFile(PATH.config, JSON.stringify(BASECONFIG, null, 2)); logger.warn('配置文件已创建。'); return BASECONFIG; } const tempConfig: Config = JSON.parse(readAllTextFromFile(PATH.config)); if (!['api', 'local'].includes(tempConfig.originType.toLowerCase())) logger.warn('图片源类型错误'); if (tempConfig.originType.toLowerCase() === 'url' && (!Array.isArray(tempConfig.urls) || tempConfig.urls.length === 0)) logger.warn('图片链接列表为空'); if (!tempConfig.title) logger.warn('标题栏为空'); if (!tempConfig.font) logger.warn('字体未指定'); if (!['auto', 'light', 'dark'].includes(tempConfig.theme)) logger.warn('主题不正确'); return { urls: tempConfig.urls, title: tempConfig.title, originType: tempConfig.originType, font: tempConfig.font || '得意黑', shadow: Boolean(tempConfig.shadow), theme: tempConfig.theme, defaultColor: tempConfig.defaultColor || null }; } // @ts-ignore setInterval(clearCaches, 100_000); serein.setListener('onPluginsLoaded', reg); serein.setListener('onPluginsReload', clearAllCache);