Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions epic/epic.ts
Original file line number Diff line number Diff line change
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
};

const help_text = `⚙️ <b>Epic 限免游戏</b>

<b>📝 功能描述:</b>
• 获取 Epic Games 每周限免游戏信息
• 显示游戏详情、原价、限免时间

<b>🔧 使用方法:</b>
• <code>${mainPrefix}epic</code> - 查看当前限免游戏
• <code>${mainPrefix}epic help</code> - 显示帮助

<b>📊 数据来源:</b>
• 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<EpicGame, "startDate" | "endDate"> = {
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 ? `<a href="${game.url}">🔗 领取</a>` : "";

return `<b>${index}. ${title}</b>
💰 原价: <code>${htmlEscape(game.originalPrice)}</code> → <b>免费</b>
📅 ${start} ~ ${end}
${desc}
${link}`;
}

class EpicPlugin extends Plugin {
description: string = help_text;

cmdHandlers: Record<string, (msg: Api.Message) => Promise<void>> = {
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 = "🎮 <b>Epic Games 限免游戏</b>\n\n";

if (current.length > 0) {
text += "📢 <b>当前限免:</b>\n\n";
current.forEach((g, i) => (text += buildGameText(g, i + 1) + "\n\n"));
} else {
text += "📢 <b>当前限免:</b> 暂无\n\n";
}

await msg.edit({ text, parseMode: "html", linkPreview: false });
} catch (error: any) {
console.error("[epic] 获取失败:", error);
await msg.edit({ text: `❌ <b>获取失败:</b> ${htmlEscape(error.message || "网络错误")}`, parseMode: "html" });
}
},
};
}

export default new EpicPlugin();

100 changes: 100 additions & 0 deletions jupai/jupai.ts
Original file line number Diff line number Diff line change
@@ -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 = `
生成举牌小人图片

<code>${commandName} [文本]</code> - 生成举牌小人
或回复消息使用 <code>${commandName}</code> - 将回复的消息内容生成举牌小人

示例:
<code>${commandName} 你好世界</code>
`;

class JuPaiPlugin extends Plugin {
description: string = `\n举牌小人\n\n${help_text}`;

cmdHandlers: Record<string, (msg: Api.Message) => Promise<void>> = {
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();

Loading
Loading