diff --git a/calc/calc.ts b/calc/calc.ts
new file mode 100644
index 00000000..7c357b12
--- /dev/null
+++ b/calc/calc.ts
@@ -0,0 +1,284 @@
+import { Plugin } from "@utils/pluginBase";
+import { getPrefixes } from "@utils/pluginManager";
+import { Api } from "telegram";
+
+const prefixes = getPrefixes();
+const mainPrefix = prefixes[0];
+const MAX_EXPR_LENGTH = 120;
+
+const help_text = `🧮 计算器插件
+
+📝 功能描述:
+• 执行安全的四则运算表达式
+• 支持括号、小数以及负数
+
+🔧 使用方法:
+• ${mainPrefix}calc 2+2*5
+• ${mainPrefix}calc (10-3)*4
+• ${mainPrefix}calc -(2-5)/3
+
+💡 示例:
+• ${mainPrefix}calc 3+7 → 10
+• ${mainPrefix}calc 8/2+5 → 9`;
+
+class SafeMathParser {
+ private static readonly operators: Record = {
+ "+": { precedence: 1 },
+ "-": { precedence: 1 },
+ "*": { precedence: 2 },
+ "/": { precedence: 2 },
+ };
+
+ private static tokenize(expr: string): string[] {
+ const cleaned = expr.replace(/\s+/g, "");
+ if (!cleaned) {
+ throw new Error("表达式为空");
+ }
+ if (!/^[0-9+\-*/().]+$/.test(cleaned)) {
+ throw new Error("表达式包含不支持的字符");
+ }
+
+ const tokens: string[] = [];
+ let current = "";
+
+ const pushCurrent = () => {
+ if (!current) return;
+ if (!this.isNumber(current)) {
+ throw new Error(`无效的数字: ${current}`);
+ }
+ tokens.push(current);
+ current = "";
+ };
+
+ const isUnaryPosition = (index: number) =>
+ index === 0 || cleaned[index - 1] === "(" || cleaned[index - 1] in this.operators;
+
+ for (let i = 0; i < cleaned.length; i++) {
+ const char = cleaned[i];
+
+ if (/[0-9.]/.test(char)) {
+ current += char;
+ continue;
+ }
+
+ pushCurrent();
+
+ if ((char === "-" || char === "+") && isUnaryPosition(i)) {
+ if (char === "-") {
+ if (i + 1 < cleaned.length && cleaned[i + 1] === "(") {
+ tokens.push("-1");
+ tokens.push("*");
+ continue;
+ }
+ current = "-";
+ }
+ continue;
+ }
+
+ if (!(char in this.operators) && char !== "(" && char !== ")") {
+ throw new Error(`未知操作符: ${char}`);
+ }
+
+ tokens.push(char);
+ }
+
+ pushCurrent();
+ return tokens;
+ }
+
+ private static infixToPostfix(tokens: string[]): string[] {
+ const output: string[] = [];
+ const operators: string[] = [];
+
+ for (const token of tokens) {
+ if (this.isNumber(token)) {
+ output.push(token);
+ continue;
+ }
+
+ if (token === "(") {
+ operators.push(token);
+ continue;
+ }
+
+ if (token === ")") {
+ while (operators.length && operators[operators.length - 1] !== "(") {
+ output.push(operators.pop()!);
+ }
+ if (!operators.length) {
+ throw new Error("括号不匹配");
+ }
+ operators.pop();
+ continue;
+ }
+
+ while (
+ operators.length &&
+ operators[operators.length - 1] !== "(" &&
+ operators[operators.length - 1] in this.operators &&
+ this.operators[operators[operators.length - 1]].precedence >= this.operators[token].precedence
+ ) {
+ output.push(operators.pop()!);
+ }
+ operators.push(token);
+ }
+
+ while (operators.length) {
+ const op = operators.pop()!;
+ if (op === "(" || op === ")") {
+ throw new Error("括号不匹配");
+ }
+ output.push(op);
+ }
+
+ return output;
+ }
+
+ private static evaluatePostfix(postfix: string[]): number {
+ const stack: number[] = [];
+
+ for (const token of postfix) {
+ if (this.isNumber(token)) {
+ stack.push(parseFloat(token));
+ continue;
+ }
+
+ if (!(token in this.operators)) {
+ throw new Error(`未知操作符: ${token}`);
+ }
+
+ if (stack.length < 2) {
+ throw new Error("表达式格式错误");
+ }
+
+ const b = stack.pop()!;
+ const a = stack.pop()!;
+
+ let result: number;
+ switch (token) {
+ case "+":
+ result = a + b;
+ break;
+ case "-":
+ result = a - b;
+ break;
+ case "*":
+ result = a * b;
+ break;
+ case "/":
+ if (b === 0) {
+ throw new Error("除零错误");
+ }
+ result = a / b;
+ break;
+ default:
+ throw new Error(`未知操作符: ${token}`);
+ }
+
+ stack.push(result);
+ }
+
+ if (stack.length !== 1) {
+ throw new Error("表达式格式错误");
+ }
+
+ return stack[0];
+ }
+
+ private static isNumber(token: string): boolean {
+ return /^-?\d+(\.\d+)?$/.test(token);
+ }
+
+ static calculate(expression: string): number {
+ const tokens = this.tokenize(expression);
+ const postfix = this.infixToPostfix(tokens);
+ return this.evaluatePostfix(postfix);
+ }
+}
+
+class CalcPlugin extends Plugin {
+ description: string = help_text;
+
+ cmdHandlers: Record Promise> = {
+ calc: async (msg: Api.Message) => await this.handleCalc(msg),
+ };
+
+ private async handleCalc(msg: Api.Message): Promise {
+ try {
+ const rawText = (msg.message ?? msg.text ?? "").trim();
+ const parts = rawText.split(/\s+/);
+ const [, ...args] = parts;
+
+ if (!args.length) {
+ await msg.edit({
+ text: help_text,
+ parseMode: "html",
+ linkPreview: false,
+ });
+ return;
+ }
+
+ const expression = args.join(" ");
+
+ if (expression.length > MAX_EXPR_LENGTH) {
+ await msg.edit({
+ text: `❌ 表达式过长
最大长度: ${MAX_EXPR_LENGTH} 字符
当前长度: ${expression.length}`,
+ parseMode: "html",
+ });
+ return;
+ }
+
+ let result: number;
+ try {
+ result = SafeMathParser.calculate(expression);
+ } catch (error: any) {
+ await msg.edit({
+ text: `🚫 计算失败
表达式: ${this.htmlEscape(expression)}
错误: ${this.htmlEscape(error?.message ?? "未知错误")}`,
+ parseMode: "html",
+ });
+ return;
+ }
+
+ if (!Number.isFinite(result)) {
+ await msg.edit({
+ text: `🚫 计算结果无效
表达式: ${this.htmlEscape(expression)}`,
+ parseMode: "html",
+ });
+ return;
+ }
+
+ const formatted = this.formatResult(result);
+
+ await msg.edit({
+ text: `🧮 计算结果
${this.htmlEscape(expression)}
= ${formatted}`,
+ parseMode: "html",
+ linkPreview: false,
+ });
+ } catch (error: any) {
+ await msg.edit({
+ text: `❌ 插件错误
${this.htmlEscape(error?.message ?? "未知错误")}`,
+ parseMode: "html",
+ });
+ }
+ }
+
+ private htmlEscape(text: string): string {
+ return text
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ }
+
+ private formatResult(value: number): string {
+ if (Number.isInteger(value)) {
+ return value.toString();
+ }
+
+ const rounded = Math.round(value * 1e12) / 1e12;
+ return rounded.toString().replace(/\.?0+$/, "");
+ }
+}
+
+export default new CalcPlugin();
diff --git a/plugins.json b/plugins.json
index 8b07a321..49f127ef 100644
--- a/plugins.json
+++ b/plugins.json
@@ -282,5 +282,9 @@
"aitc": {
"url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/aitc/aitc.ts?raw=true",
"desc": "AI Prompt 转写"
+ },
+ "calc": {
+ "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/calc/calc.ts?raw=true",
+ "desc": "计算器"
}
}