diff --git a/oxost/oxost.ts b/oxost/oxost.ts
new file mode 100644
index 00000000..0a38edc2
--- /dev/null
+++ b/oxost/oxost.ts
@@ -0,0 +1,177 @@
+
+import axios from "axios";
+import FormData from "form-data";
+import { getPrefixes } from "@utils/pluginManager";
+import { Plugin } from "@utils/pluginBase";
+import { Api } from "telegram";
+import { getGlobalClient } from "@utils/globalClient";
+
+// HTML转义
+const htmlEscape = (text: string): string =>
+ text.replace(/[&<>"]|'/g, m => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ }[m] || m));
+
+// 消息分割与发送
+const MAX_MESSAGE_LENGTH = 4096;
+function splitMessage(text: string): string[] {
+ if (text.length <= MAX_MESSAGE_LENGTH) return [text];
+ const parts: string[] = [];
+ let currentPart = "";
+ const lines = text.split("\n");
+ for (const line of lines) {
+ if (currentPart.length + line.length + 1 > MAX_MESSAGE_LENGTH) {
+ parts.push(currentPart);
+ currentPart = line;
+ } else {
+ currentPart += (currentPart ? "\n" : "") + line;
+ }
+ }
+ if (currentPart) parts.push(currentPart);
+ return parts;
+}
+async function sendLongMessage(msg: Api.Message, text: string) {
+ const parts = splitMessage(text);
+ if (parts.length === 1) {
+ await msg.edit({ text: parts[0], parseMode: "html" });
+ } else {
+ await msg.edit({ text: parts[0] + "\n\n📄 (1/" + parts.length + ")", parseMode: "html" });
+ for (let i = 1; i < parts.length; i++) {
+ await msg.reply({ message: parts[i] + "\n\n📄 (" + (i + 1) + "/" + parts.length + ")", parseMode: "html" });
+ }
+ }
+}
+
+const timeout = 60000;
+const prefixes = getPrefixes();
+const mainPrefix = prefixes[0];
+const pluginName = "0x0";
+const commandName = `${mainPrefix}${pluginName}`;
+
+const help_text = `🗂️ 0x0.st 文件上传插件\n\n命令格式:\n${commandName} [expires=小时] [secret]\n\n用法:\n• 回复一条带文件/视频/语音的消息,自动上传到 0x0.st 并返回下载链接\n• ${commandName} expires=72 secret 设置72小时有效期并启用难猜链接\n• ${commandName} help 显示帮助\n\n参数说明:\n• expires=xx 设置有效期(小时)\n• secret 生成更难猜的链接\n`;
+
+class Ox0Plugin extends Plugin {
+ description: string = `文件上传到 0x0.st\n\n${help_text}`;
+ cmdHandlers: Record Promise> = {
+ "0x0": async (msg: Api.Message) => {
+ const lines = msg.text?.trim()?.split(/\r?\n/g) || [];
+ const parts = lines?.[0]?.split(/\s+/) || [];
+ const [, ...args] = parts;
+ const sub = (args[0] || "").toLowerCase();
+
+ // 仅当明确输入 help/h 时显示帮助
+ if (sub === "help" || sub === "h") {
+ await sendLongMessage(msg, help_text);
+ return;
+ }
+ if (args[1] && (args[1].toLowerCase() === "help" || args[1].toLowerCase() === "h")) {
+ await sendLongMessage(msg, help_text);
+ return;
+ }
+
+ let expires: string | undefined;
+ let secret = false;
+ for (const arg of args) {
+ if (/^expires=\d+$/.test(arg)) {
+ expires = arg.split("=")[1];
+ } else if (arg === "secret") {
+ secret = true;
+ }
+ }
+
+ let replied: Api.Message | undefined;
+ try {
+ replied = await msg.getReplyMessage();
+ } catch (e: any) {
+ await sendLongMessage(msg, `❌ 错误: ${htmlEscape(e.message)}`);
+ return;
+ }
+ if (!replied || !replied.media) {
+ await sendLongMessage(msg, `❌ 错误: 请回复一条带文件、视频、语音、图片等消息`);
+ return;
+ }
+
+ await msg.edit({ text: "⏳ 正在下载并上传..." });
+ try {
+ const buffer = await replied.downloadMedia();
+ if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
+ await sendLongMessage(msg, `❌ 错误: 媒体下载失败或为空`);
+ return;
+ }
+
+ // 文件名只保留英文、数字、下划线和扩展名,最长32位
+ let filename = "file";
+ if (replied.document && replied.document.attributes) {
+ const attr = replied.document.attributes.find(
+ (a: any) => a instanceof Api.DocumentAttributeFilename
+ );
+ if (attr && attr.fileName) filename = attr.fileName;
+ } else if (replied.message) {
+ filename = replied.message.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 32) || filename;
+ } else if (replied.video) {
+ filename = "video.mp4";
+ } else if (replied.audio) {
+ filename = "audio.ogg";
+ } else if (replied.voice) {
+ filename = "voice.ogg";
+ } else if (replied.photo) {
+ // 自动识别图片类型
+ const head = buffer.slice(0, 8).toString('hex').toLowerCase();
+ if (head.startsWith('ffd8ff')) {
+ filename = "photo.jpg";
+ } else if (head.startsWith('89504e47')) {
+ filename = "photo.png";
+ } else if (head.startsWith('47494638')) {
+ filename = "photo.gif";
+ } else if (head.startsWith('52494646')) {
+ filename = "photo.webp";
+ } else {
+ filename = "photo.bin";
+ }
+ }
+ if (!filename || filename.length < 3) filename = "file";
+
+ // 临时调试输出
+ let debugInfo = `调试信息\n`;
+ debugInfo += `filename: ${htmlEscape(filename)}\n`;
+ debugInfo += `buffer.length: ${buffer.length}\n`;
+ debugInfo += `buffer[0:32]: ${buffer.slice(0,32).toString('hex')}\n`;
+ debugInfo += `expires: ${htmlEscape(expires || "")} secret: ${secret ? "1" : "0"}\n`;
+
+ const form = new FormData();
+ form.append("file", buffer, { filename });
+ if (expires) form.append("expires", expires);
+ if (secret) form.append("secret", "1");
+ const headers = form.getHeaders ? form.getHeaders() : {};
+ // 伪造 User-Agent,模拟 curl/浏览器
+ headers['User-Agent'] = 'curl/8.0.1';
+ debugInfo += `headers: ${JSON.stringify(headers)}\n`;
+
+ try {
+ const response = await axios.post("https://0x0.st", form, {
+ headers,
+ timeout,
+ });
+ const url = response.data?.toString().trim();
+ if (!url || !url.startsWith("https://0x0.st/")) {
+ await sendLongMessage(msg, `❌ 错误: 上传失败或未获取到链接\n${debugInfo}`);
+ return;
+ }
+ await sendLongMessage(msg, `${htmlEscape(url)}`);
+ } catch (err: any) {
+ debugInfo += `\n异常: ${htmlEscape(err?.message || String(err))}`;
+ await sendLongMessage(msg, `❌ 错误: 上传失败\n${debugInfo}`);
+ }
+ } catch (error: any) {
+ if (error.message?.includes("FLOOD_WAIT")) {
+ const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60");
+ await new Promise(res => setTimeout(res, (waitTime + 1) * 1000));
+ }
+ await sendLongMessage(msg, `❌ 错误: ${htmlEscape(error.message)}`);
+ }
+ },
+ };
+}
+
+export default new Ox0Plugin();
diff --git a/plugins.json b/plugins.json
index efdf8489..b03db6af 100644
--- a/plugins.json
+++ b/plugins.json
@@ -214,5 +214,9 @@
"httpcat": {
"url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/httpcat/httpcat.ts?raw=true",
"desc": "发送一张http状态码主题的猫猫图片"
+ },
+ "oxost": {
+ "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/oxost/oxost.ts?raw=true",
+ "desc": "回复聊天中的文件与媒体 得到一个临时的下载链接"
}
}