import logging import json import os from datetime import datetime from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Updater, MessageHandler, Filters, CallbackContext # 需要安装 pytz: pip install pytz import pytz DUBAI_TZ = pytz.timezone("Asia/Dubai") logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO ) DATA_FILE = "finance_data.json" def load_data(): if os.path.exists(DATA_FILE): with open(DATA_FILE, "r", encoding="utf-8") as f: return json.load(f) return {} def save_data(data): with open(DATA_FILE, "w", encoding="utf-8") as f: json.dump(data, f, indent=4) def ensure_group_data_structure(group_data): required_fields = { "deposits": [], "payouts": [], "fee": 0.0, "rate": 0, "admins": [] } for key, default in required_fields.items(): if key not in group_data: group_data[key] = default return group_data def get_group_data(chat_id): data = load_data() str_id = str(chat_id) if str_id not in data: data[str_id] = { "deposits": [], "payouts": [], "fee": 0.0, "rate": 0, "admins": [] } save_data(data) else: data[str_id] = ensure_group_data_structure(data[str_id]) return data[str_id] def calculate_payouts(group_data): total_deposit = sum(d["amount"] for d in group_data["deposits"]) fee = group_data["fee"] rate = group_data["rate"] or 1 # 防止除以零 should_payout_local = total_deposit * (1 - fee) total_payouts_usdt = sum(p["amount"] for p in group_data["payouts"]) # USDT换算成本地货币 total_payouts_local = total_payouts_usdt * rate remaining_local = should_payout_local - total_payouts_local # USDT Calculations should_payout_usdt = should_payout_local / rate if rate else 0 remaining_usdt = remaining_local / rate if rate else 0 return ( should_payout_local, remaining_local, should_payout_usdt, total_payouts_usdt, remaining_usdt ) def show_report(update: Update, chat_id: str): """ 展示财务面板:上半部分为已入账/已下发,底部为统计概览。 - 统计概览中中文标签加粗,并用空格和右对齐的方式尽量让数值看起来整齐。 - 使用迪拜时间在 handle_transaction() 中记录,这里只做展示。 """ group_data = get_group_data(chat_id) rate = group_data["rate"] fee = group_data["fee"] * 100 (should_payout_local, remaining_local, should_payout_usdt, total_payouts_usdt, remaining_usdt) = calculate_payouts(group_data) # 最近5笔入款 deposits = group_data["deposits"][-5:] if deposits: deposits_str = "\n".join( f" {'+' if item['amount'] >= 0 else ''}{item['amount']:>10,.2f} | {item['time'][5:16]}" for item in deposits ) else: deposits_str = "暂无记录\nNo Record" # 最近5笔下发 payouts = group_data["payouts"][-5:] if payouts: payouts_str = "\n".join( f" USDT{item['amount']:>8,.2f} | {item['time'][5:16]} ({item['amount']*rate:,.2f})" for item in payouts ) else: payouts_str = "暂无记录\nNo Record" # 上半部分 report_top = ( " 此处名称可自定义\n" "Atlantic Group India Payment\n" "━━━━━━━━━━━━━━━━━━━━\n" " 已入账(最近5笔)\n" "Last 5 Deposits\n" f"{deposits_str}\n" "━━━━━━━━━━━━━━━━━━━━\n" " 已下发(最近5笔)\n" "Last 5 Payouts\n" f"{payouts_str}\n" "━━━━━━━━━━━━━━━━━━━━\n" ) # 统计概览 total_deposit_str = f"{sum(d['amount'] for d in group_data['deposits']):,.2f}" fee_rate_str = f"{fee:,.1f}%" current_rate_str = f"1 USDT = {rate}" # 右对齐,保证同等宽度 # 注:在比例字体下不可能完美对齐,只能尽量美观 should_payout_val = f"{should_payout_local:>10,.2f}|{should_payout_usdt:>10,.2f}USDT" already_paid_val = f"{(total_payouts_usdt*rate):>10,.2f}|{total_payouts_usdt:>10,.2f}USDT" remaining_val = f"{remaining_local:>10,.2f}|{remaining_usdt:>10,.2f}USDT" report_bottom = ( f"统计概览(Overview)\n" f"总入账(Total Deposit): {total_deposit_str}\n" f"手续费率(Fee Rate): {fee_rate_str}\n" f"当前汇率(Current Rate): {current_rate_str}\n" f"应下发(Should Payout): {should_payout_val}\n" f"已下发(Already Paid): {already_paid_val}\n" f"未下发(Remaining): {remaining_val}\n" ) report = report_top + report_bottom keyboard = [[InlineKeyboardButton("此处名称可自定义", url="此处链接可自定义")]] update.message.reply_text( report, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(keyboard) ) def is_admin(group_data, username): return username in group_data["admins"] def handle_command(update: Update, context: CallbackContext): text = update.message.text.strip() chat_id = str(update.message.chat_id) user = update.message.from_user.username data = load_data() group_data = data.setdefault(chat_id, get_group_data(chat_id)) group_data = ensure_group_data_structure(group_data) try: if text == "开始": # 如果没有管理员,则将发送此命令的用户设为管理员 if not group_data["admins"] and user: group_data["admins"].append(user) save_data(data) update.message.reply_text( "🤖 此处名称可自定义\n" "Atlantic Finance Bot Activated\n\n" "使用指南:\n" "1)+100 或 -100 记录入账\n" "2)下发100 或 下发-100 记录USDT下发\n" "3)财务面板 查看报表\n" "4)设置汇率 70 / 设置费率 1.5 / 清空数据", parse_mode="HTML" ) return elif text == "财务面板": show_report(update, chat_id) return if not is_admin(group_data, user): update.message.reply_text("❌ 权限不足!") return if text.startswith("设置汇率"): group_data["rate"] = float(text.split()[1]) elif text.startswith("设置费率"): group_data["fee"] = float(text.split()[1]) / 100 elif text.startswith("添加管理员"): admin = text.split()[1].lstrip("@") if admin not in group_data["admins"]: group_data["admins"].append(admin) elif text == "清空数据": group_data.update({ "deposits": [], "payouts": [] }) else: # 如果是管理员,但输入不匹配任何已知指令,则不做处理或给个提示 return save_data(data) show_report(update, chat_id) except Exception as e: logging.error(f"Error: {str(e)}") update.message.reply_text("❌ 操作失败,请检查输入格式!") def handle_transaction(update: Update, context: CallbackContext): """ 只匹配 +数字 或 -数字,或者 下发 +数字 / 下发 -数字 将其记录为入账或下发 """ text = update.message.text.strip() chat_id = str(update.message.chat_id) user = update.message.from_user.username data = load_data() group_data = data.get(chat_id, get_group_data(chat_id)) if not is_admin(group_data, user): update.message.reply_text("❌ 权限不足!") return try: if text.startswith("下发"): # 处理USDT下发 usdt_str = text[2:].strip() # 可能是 "100" 或 "-100" usdt = float(usdt_str) time_now = datetime.now(DUBAI_TZ).strftime("%Y-%m-%d %H:%M:%S") group_data["payouts"].append({ "amount": usdt, "time": time_now }) else: # +100 或 -100 记录入账 amount = float(text) time_now = datetime.now(DUBAI_TZ).strftime("%Y-%m-%d %H:%M:%S") group_data["deposits"].append({ "amount": amount, "time": time_now }) data[chat_id] = group_data save_data(data) show_report(update, chat_id) except ValueError: # 如果解析失败,则不做回复或提示 return def do_nothing(update: Update, context: CallbackContext): """ 对于不匹配任何正则的消息,不做任何回复。 """ pass def main(): TOKEN = "机器人秘钥" updater = Updater(TOKEN, use_context=True) dp = updater.dispatcher # 命令类:开始、财务面板、设置汇率、设置费率、添加管理员、清空数据 dp.add_handler(MessageHandler( Filters.regex(r'^(开始|财务面板|设置汇率\s?\S+|设置费率\s?\S+|添加管理员\s?\S+|清空数据)$'), handle_command )) # 交易类:+数字 或 -数字 或 下发 +数字 或 下发-数字 # 例如 +100 -100 下发100 下发-100 dp.add_handler(MessageHandler( Filters.regex(r'^[+-]\d+(\.\d+)?$|^下发\s?[+-]?\d+(\.\d+)?$'), handle_transaction )) # 对其它文本消息不做任何处理(不回复) dp.add_handler(MessageHandler(Filters.text, do_nothing)) updater.start_polling() updater.idle() if __name__ == "__main__": main()