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()