From 9f2b31d9aac7e529ecc9981b6b66f675f2429144 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A6=AC=E5=89=83=20=E5=A4=A9=E6=84=9B=E6=98=9F?=
<131457234+TiaraBasori@users.noreply.github.com>
Date: Sat, 20 Dec 2025 20:21:28 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=B8=80=E4=BA=9B?=
=?UTF-8?q?=E4=BB=8E=E7=BE=A4=E9=87=8C=E9=81=93=E5=90=AC=E9=80=94=E8=AF=B4?=
=?UTF-8?q?=E6=9D=A5=E7=9A=84=E6=8F=92=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
详情请咨询原作者(也不知道是谁,反正不保证可用性)
---
epic/epic.ts | 168 +++++++++++++++
jupai/jupai.ts | 100 +++++++++
nezha/nezha.ts | 557 +++++++++++++++++++++++++++++++++++++++++++++++++
plugins.json | 18 +-
4 files changed, 840 insertions(+), 3 deletions(-)
create mode 100644 epic/epic.ts
create mode 100644 jupai/jupai.ts
create mode 100644 nezha/nezha.ts
diff --git a/epic/epic.ts b/epic/epic.ts
new file mode 100644
index 00000000..a1fc8232
--- /dev/null
+++ b/epic/epic.ts
@@ -0,0 +1,168 @@
+import { Plugin } from "@utils/pluginBase";
+import { Api } from "telegram";
+import { getGlobalClient } from "@utils/globalClient";
+import { getPrefixes } from "@utils/pluginManager";
+import axios from "axios";
+
+const prefixes = getPrefixes();
+const mainPrefix = prefixes[0];
+
+const htmlEscape = (text: string): string => {
+ if (typeof text !== "string") return "";
+ return text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'");
+};
+
+const help_text = `⚙️ Epic 限免游戏
+
+📝 功能描述:
+• 获取 Epic Games 每周限免游戏信息
+• 显示游戏详情、原价、限免时间
+
+🔧 使用方法:
+• ${mainPrefix}epic - 查看当前限免游戏
+• ${mainPrefix}epic help - 显示帮助
+
+📊 数据来源:
+• Epic Games Store API`;
+
+// Epic API URL
+const EPIC_API_URL = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions?locale=zh-CN&country=CN&allowCountries=CN";
+
+interface EpicGame {
+ title: string;
+ description: string;
+ originalPrice: string;
+ startDate: string;
+ endDate: string;
+ url: string;
+ imageUrl: string;
+}
+
+// 解析限免游戏数据
+function parseFreeGames(data: any): { current: EpicGame[]; upcoming: EpicGame[] } {
+ const current: EpicGame[] = [];
+ const upcoming: EpicGame[] = [];
+
+ const elements = data?.data?.Catalog?.searchStore?.elements || [];
+
+ for (const game of elements) {
+ // 跳过非游戏类型
+ if (!game.categories?.some((c: any) => c.path === "freegames")) continue;
+
+ const promotions = game.promotions;
+ if (!promotions) continue;
+
+ // 获取游戏URL
+ const pageSlug = game.offerMappings?.[0]?.pageSlug || game.catalogNs?.mappings?.[0]?.pageSlug || game.productSlug || game.urlSlug;
+ const url = pageSlug ? `https://store.epicgames.com/zh-CN/p/${pageSlug}` : "";
+
+ // 获取图片
+ const imageUrl = game.keyImages?.find((img: any) => img.type === "OfferImageWide" || img.type === "Thumbnail")?.url || "";
+
+ // 获取价格
+ const price = game.price?.totalPrice;
+ const originalPrice = price?.fmtPrice?.originalPrice || "免费";
+
+ const baseInfo: Omit = {
+ title: game.title || "未知游戏",
+ description: game.description || "",
+ originalPrice,
+ url,
+ imageUrl,
+ };
+
+ // 当前限免
+ const currentPromo = promotions.promotionalOffers?.[0]?.promotionalOffers?.[0];
+ if (currentPromo && price?.discountPrice === 0) {
+ current.push({
+ ...baseInfo,
+ startDate: currentPromo.startDate,
+ endDate: currentPromo.endDate,
+ });
+ continue;
+ }
+
+ // 即将限免
+ const upcomingPromo = promotions.upcomingPromotionalOffers?.[0]?.promotionalOffers?.[0];
+ if (upcomingPromo && upcomingPromo.discountSetting?.discountPercentage === 0) {
+ upcoming.push({
+ ...baseInfo,
+ startDate: upcomingPromo.startDate,
+ endDate: upcomingPromo.endDate,
+ });
+ }
+ }
+
+ return { current, upcoming };
+}
+
+// 格式化日期
+function formatDate(dateStr: string): string {
+ try {
+ const date = new Date(dateStr);
+ return date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
+ } catch {
+ return dateStr;
+ }
+}
+
+// 构建游戏信息文本
+function buildGameText(game: EpicGame, index: number): string {
+ const title = htmlEscape(game.title);
+ const desc = game.description.length > 100 ? htmlEscape(game.description.slice(0, 100)) + "..." : htmlEscape(game.description);
+ const start = formatDate(game.startDate);
+ const end = formatDate(game.endDate);
+ const link = game.url ? `🔗 领取` : "";
+
+ return `${index}. ${title}
+💰 原价: ${htmlEscape(game.originalPrice)} → 免费
+📅 ${start} ~ ${end}
+${desc}
+${link}`;
+}
+
+class EpicPlugin extends Plugin {
+ description: string = help_text;
+
+ cmdHandlers: Record Promise> = {
+ epic: async (msg: Api.Message) => {
+ const client = await getGlobalClient();
+ if (!client) {
+ await msg.edit({ text: "❌ 客户端未初始化", parseMode: "html" });
+ return;
+ }
+
+ const parts = msg.text?.trim().split(/\s+/) || [];
+ const sub = (parts[1] || "").toLowerCase();
+
+ if (sub === "help" || sub === "h") {
+ await msg.edit({ text: help_text, parseMode: "html" });
+ return;
+ }
+
+ try {
+ await msg.edit({ text: "🎮 获取 Epic 限免游戏中...", parseMode: "html" });
+
+ const res = await axios.get(EPIC_API_URL, { timeout: 15000 });
+ const { current, upcoming } = parseFreeGames(res.data);
+
+ let text = "🎮 Epic Games 限免游戏\n\n";
+
+ if (current.length > 0) {
+ text += "📢 当前限免:\n\n";
+ current.forEach((g, i) => (text += buildGameText(g, i + 1) + "\n\n"));
+ } else {
+ text += "📢 当前限免: 暂无\n\n";
+ }
+
+ await msg.edit({ text, parseMode: "html", linkPreview: false });
+ } catch (error: any) {
+ console.error("[epic] 获取失败:", error);
+ await msg.edit({ text: `❌ 获取失败: ${htmlEscape(error.message || "网络错误")}`, parseMode: "html" });
+ }
+ },
+ };
+}
+
+export default new EpicPlugin();
+
diff --git a/jupai/jupai.ts b/jupai/jupai.ts
new file mode 100644
index 00000000..5e5965c7
--- /dev/null
+++ b/jupai/jupai.ts
@@ -0,0 +1,100 @@
+import axios from "axios";
+import { getPrefixes } from "@utils/pluginManager";
+import { Plugin } from "@utils/pluginBase";
+import { Api } from "telegram";
+import { getGlobalClient } from "@utils/globalClient";
+import { CustomFile } from "telegram/client/uploads.js";
+
+const timeout = 60000;
+const prefixes = getPrefixes();
+const mainPrefix = prefixes[0];
+const pluginName = "jupai";
+const commandName = `${mainPrefix}${pluginName}`;
+const juPaiApi = "https://api.txqq.pro/api/zt.php";
+
+const help_text = `
+生成举牌小人图片
+
+${commandName} [文本] - 生成举牌小人
+或回复消息使用 ${commandName} - 将回复的消息内容生成举牌小人
+
+示例:
+${commandName} 你好世界
+`;
+
+class JuPaiPlugin extends Plugin {
+ description: string = `\n举牌小人\n\n${help_text}`;
+
+ cmdHandlers: Record Promise> = {
+ jupai: async (msg: Api.Message) => {
+ try {
+ // 获取文本内容
+ const args = msg.message.split(/\s+/).slice(1);
+ let text = args.join(" ");
+
+ // 如果命令后没有文本,检查是否回复了消息
+ if (!text) {
+ const replied = msg.replyTo ? await msg.getReplyMessage() : null;
+ if (replied && replied.message) {
+ text = replied.message;
+ }
+ }
+
+ // 如果还是没有文本,显示帮助信息
+ if (!text) {
+ await msg.edit({
+ text: help_text,
+ parseMode: "html"
+ });
+ return;
+ }
+
+ await msg.edit({ text: "正在生成举牌小人..." });
+
+ try {
+ // 构建 API URL,对文本进行 URL 编码
+ const imageUrl = `${juPaiApi}?msg=${encodeURIComponent(text)}`;
+
+ // 获取图片数据
+ const response = await axios.get(imageUrl, {
+ responseType: "arraybuffer",
+ timeout,
+ });
+
+ const imageBuffer = Buffer.from(response.data);
+
+ if (!imageBuffer || imageBuffer.length === 0) {
+ await msg.edit({ text: "图片获取失败或为空" });
+ return;
+ }
+
+ // 发送图片
+ const client = await getGlobalClient();
+ const file = new CustomFile(
+ "jupai.jpg",
+ imageBuffer.length,
+ "",
+ imageBuffer
+ );
+
+ await client.sendFile(msg.peerId, {
+ file,
+ replyTo: msg.replyTo?.replyToMsgId || msg.id,
+ });
+
+ await msg.delete();
+ } catch (error) {
+ const errorMsg = error instanceof Error ? error.message : String(error);
+ await msg.edit({ text: `获取失败: ${errorMsg}` });
+ }
+ } catch (error) {
+ console.error("JuPai Plugin Error:", error);
+ const errorMsg = error instanceof Error ? error.message : String(error);
+ await msg.edit({ text: `插件执行失败: ${errorMsg}` });
+ }
+ },
+ };
+}
+
+export default new JuPaiPlugin();
+
diff --git a/nezha/nezha.ts b/nezha/nezha.ts
new file mode 100644
index 00000000..68d00c5d
--- /dev/null
+++ b/nezha/nezha.ts
@@ -0,0 +1,557 @@
+import { Plugin } from "@utils/pluginBase";
+import { Api } from "telegram";
+import axios from "axios";
+import * as crypto from "crypto";
+import * as fs from "fs";
+import * as path from "path";
+import * as yaml from "js-yaml";
+import { createDirectoryInAssets } from "@utils/pathHelpers";
+
+interface NeZhaConfig {
+ url: string;
+ secret: string;
+ configPath?: string;
+ serviceMonitor?: boolean;
+}
+
+interface ServerHost {
+ platform?: string;
+ cpu?: string[];
+ mem_total?: number;
+ disk_total?: number;
+ version?: string;
+}
+
+interface ServerState {
+ cpu?: number;
+ mem_used?: number;
+ swap_used?: number;
+ disk_used?: number;
+ net_in_speed?: number;
+ net_out_speed?: number;
+ net_in_transfer?: number;
+ net_out_transfer?: number;
+ load_1?: number;
+ load_5?: number;
+ load_15?: number;
+ uptime?: number;
+ tcp_conn_count?: number;
+ udp_conn_count?: number;
+ process_count?: number;
+}
+
+interface ServerGeoIP {
+ ip?: {
+ ipv4_addr?: string;
+ ipv6_addr?: string;
+ };
+ country_code?: string;
+}
+
+interface Server {
+ id: number;
+ name: string;
+ display_index?: number;
+ host?: ServerHost;
+ state?: ServerState;
+ geoip?: ServerGeoIP;
+ last_active?: string;
+}
+
+interface ApiResponse {
+ success: boolean;
+ data?: Server[];
+ error?: string;
+}
+
+interface ServiceMonitorItem {
+ monitor_id: number;
+ server_id: number;
+ monitor_name: string;
+ server_name: string;
+ created_at: number[];
+ avg_delay: number[];
+}
+
+interface ServiceMonitorData {
+ success: boolean;
+ data?: ServiceMonitorItem[];
+}
+
+let configCache: NeZhaConfig | null = null;
+let configDir: string = "";
+let configFile: string = "";
+
+function getConfigPath(): string {
+ if (!configDir) {
+ configDir = createDirectoryInAssets("nezha");
+ configFile = path.join(configDir, "config.json");
+ }
+ return configFile;
+}
+
+function loadConfig(): NeZhaConfig | null {
+ if (configCache) return configCache;
+ try {
+ const file = getConfigPath();
+ if (fs.existsSync(file)) {
+ const content = fs.readFileSync(file, "utf-8");
+ configCache = JSON.parse(content);
+ return configCache;
+ }
+ } catch {}
+ return null;
+}
+
+function saveConfig(config: NeZhaConfig): void {
+ try {
+ const file = getConfigPath();
+ fs.writeFileSync(file, JSON.stringify(config, null, 2), "utf-8");
+ configCache = config;
+ } catch (e) {
+ console.error("Failed to save nezha config:", e);
+ }
+}
+
+function htmlEscape(text: string): string {
+ if (typeof text !== "string") return "";
+ return text
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+function formatBytes(bytes: number): string {
+ if (bytes === 0) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return (bytes / Math.pow(k, i)).toFixed(2) + " " + sizes[i];
+}
+
+function formatSpeed(bytes: number): string {
+ return formatBytes(bytes) + "/s";
+}
+
+function formatUptime(seconds: number): string {
+ const days = Math.floor(seconds / 86400);
+ const hours = Math.floor((seconds % 86400) / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ const parts: string[] = [];
+ if (days > 0) parts.push(`${days}天`);
+ if (hours > 0) parts.push(`${hours}时`);
+ if (minutes > 0) parts.push(`${minutes}分`);
+ return parts.join("") || "0分";
+}
+
+function getStatusEmoji(isOnline: boolean): string {
+ return isOnline ? "🟢" : "🔴";
+}
+
+function getUsageBar(percent: number): string {
+ const filled = Math.round(percent / 10);
+ return "█".repeat(filled) + "░".repeat(10 - filled);
+}
+
+function base64UrlEncode(data: string): string {
+ return Buffer.from(data)
+ .toString("base64")
+ .replace(/=/g, "")
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_");
+}
+
+function generateJWT(secret: string, userId: string = "1"): string {
+ const header = { alg: "HS256", typ: "JWT" };
+ const now = Math.floor(Date.now() / 1000);
+ const payload = {
+ user_id: userId,
+ orig_iat: now,
+ exp: now + 3600,
+ ip: "",
+ };
+
+ const headerB64 = base64UrlEncode(JSON.stringify(header));
+ const payloadB64 = base64UrlEncode(JSON.stringify(payload));
+ const signature = crypto
+ .createHmac("sha256", secret)
+ .update(`${headerB64}.${payloadB64}`)
+ .digest("base64")
+ .replace(/=/g, "")
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_");
+
+ return `${headerB64}.${payloadB64}.${signature}`;
+}
+
+function readSecretFromConfig(configPath: string): string | null {
+ try {
+ if (!fs.existsSync(configPath)) {
+ return null;
+ }
+ const content = fs.readFileSync(configPath, "utf-8");
+ const config = yaml.load(content) as any;
+ return config?.jwt_secret_key || config?.jwtSecretKey || null;
+ } catch {
+ return null;
+ }
+}
+
+async function fetchServers(config: NeZhaConfig): Promise {
+ let secret = config.secret;
+
+ if (config.configPath) {
+ const fileSecret = readSecretFromConfig(config.configPath);
+ if (fileSecret) {
+ secret = fileSecret;
+ }
+ }
+
+ const token = generateJWT(secret);
+ const apiUrl = config.url.replace(/\/$/, "") + "/api/v1/server";
+ const response = await axios.get(apiUrl, {
+ timeout: 15000,
+ headers: {
+ Cookie: `nz-jwt=${token}`,
+ "User-Agent": "TeleBox-NeZha-Plugin/1.0",
+ },
+ });
+
+ if (response.data.success && response.data.data) {
+ return response.data.data;
+ }
+ if (Array.isArray(response.data)) {
+ return response.data;
+ }
+ throw new Error((response.data as any).error || "获取服务器列表失败");
+}
+
+function isServerOnline(server: Server): boolean {
+ if (!server.last_active) return false;
+ const lastActive = new Date(server.last_active).getTime();
+ const now = Date.now();
+ return now - lastActive < 60000;
+}
+
+async function fetchServiceMonitor(
+ config: NeZhaConfig,
+ serverId: number
+): Promise