From 5ebef9dcbb08d6ef138fe959a4cd0f0a486d2e07 Mon Sep 17 00:00:00 2001 From: ddrwode <2797r9234@ferfrfgr> Date: Mon, 24 Nov 2025 15:11:44 +0800 Subject: [PATCH] gtrtgrtg --- telegram/2349073562091.session | Bin 45056 -> 0 bytes telegram/bot_session.session | Bin 28672 -> 28672 bytes telegram/bot_session_23612.session | Bin 28672 -> 0 bytes telegram/bot_session_35072.session | Bin 28672 -> 0 bytes telegram/gpt.py | 591 ++++++++++++-------- telegram/gpt1.py | 844 +++++++++++++++++++++++++++++ telegram/haha.session | Bin 28672 -> 0 bytes telegram/sign.db | Bin 28672 -> 32768 bytes 8 files changed, 1223 insertions(+), 212 deletions(-) delete mode 100644 telegram/2349073562091.session delete mode 100644 telegram/bot_session_23612.session delete mode 100644 telegram/bot_session_35072.session create mode 100644 telegram/gpt1.py delete mode 100644 telegram/haha.session diff --git a/telegram/2349073562091.session b/telegram/2349073562091.session deleted file mode 100644 index e601da79b15ba1100da7ff9e91d1baa2f5d4548c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45056 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 delta 611 zcmV-p0-XJT-~oW(0gxI30b`LILIMF}v0$Jse@*=}c986QZwu?}Xo$%up<3~ql%v?> z-j!u%fKtB3rOEv)Y8e37-LTKOYo@-6nuxhtxNC?%x9}sANk)yA&y`*J%qU+qUr_S- z32(`;13j#7J?tkVg!8jyk2oei@QhdSXUG%;fxIl(C0pI%Qv3Y=JJ|UYUpp+o=+MNg zf0F)*lwAiWYB!LujXaLqfR?9$JuDlaMuI7!bedL#>nkY=XmdXQ=U{L(T2LJ{g02>53Sh?wH7#}=nb!lg5VPtbTI5;_KX=rI4-SLwTKOa6L&$c3-h~v%~ z2LJ{i02u^^N!jNbr)o`YY+`A2V{d70RA_H)VRLB|Ev69w2LJ^C000C40r$fcf@veu z$CGS7D+43bn3F_5B?A>g$dj5sFE1n0m?odTjHv<#00tuf9Rx74hF{6b@~dQTV`Xb{ zb7f+0bYyR1WovSCWoaYNO|n1%APWEh00Ix#0}t*G*s~E3xeq%L00sa71Ox&dG<#k1 xX&&fG0Wc7Zh_kd%1^@yG2M7o(LE1?4@zTuu4FCoJ0SE*F9%&=fmjRSQ$Y@n|`WFBI diff --git a/telegram/bot_session_23612.session b/telegram/bot_session_23612.session deleted file mode 100644 index 6b4eb94b749c146cd91ac3af18f09aa3fbb0a32c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI)e@q)?7zgmXuCx_a$`WIzSa*cr3IjrAm0&V67z~%;qJV;%raQQ!9JD>SULBA) zMJ}@eU8i) z+jv(c$I86LDU*3%UA-kQJEI_*Dagpo&1M4MVT|ESkRou5;E;JMFEN#pU}vQorh>0Y z3>sT0JAJ{y1FAv=c#>Xv7f!y)7qB3Qcz3!|t&fSJ8#Z|noV-J}lnOT9>92*&1MqKO zr1^HvY~che@06Khn^+vYS)M~!oI-7g)a9DW5D|Xt;0G#Won^k(2agEoBcng}MCQK~ z6B$n$@eA!z!&Lg57@86s9AE9MvXRRv3+s|a@4CegWik2dDl*bFp3&}(U8U9=40OZ2 zUU=dz$O7*P^e&b2(0yAMA@?JM9&v#nj6;+ehs$OQF5`0Yl7qGLjLcWd!9yy`L`R4o z3bBX4@w<@N9j@>y)m!I=r_(8j4l+1rm#gL}`L-^i#Fbe74-{$q^SoqXIZh%+TgpI` zJH3n8-=yy%hxGF*s}ORMzJ(R7uUg5>6*m|H03LCkGrjJ`^c^HIF# zBA?dXrpSZ>0SG_<0uX=z1Rwwb2tWV=5P-l66i}!Y-kbb%M#o99`JglyCfLXl+Z@ zp`uG4{MOI)ogAEQ`E&jyP)|!S9NRG8>3=+&XM68-E8aU<{MWM8h6?az2p7odmeeJuDw^c zi`#bO&BRe%Tguj+ys=*gU)V-xjYheScbaeJ#{9quXO5jXTAN>S%>0h#+Ww@D=l1Mv z?%QGBEq&kD`&G@wkGI7A{9R$o9^@sf)hh9P=}>pwFXy_3n_j=(`3m_&k+JE$3!gknq7Bq~ zi^W_amkIi_B0U009U<00Izz00bZa0SG_<0uWe!0#S`G jw|6|Dq7)jnMtw8mN>1nHgdKlGP%4VnDDN_Oo)!2PO=>cb diff --git a/telegram/bot_session_35072.session b/telegram/bot_session_35072.session deleted file mode 100644 index cb3289af2cf4a9d170fcc78de7f2842bed706eb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI(e@q)?7zgn8y3!(BS7yu_nYaUj!oa!)wvHeQGsw0OhYryhLM`;AoRnVMyDB&` zL%}&2i08Zh9I(r`x4PsC0R891-jmS}i$kY&vB!yJgY0Zwf$}41vCiReGei+C2#i)VkPMuHjA} zJaZRhfqDadTjdmV|Ir!90|?BOEWg-@y8I$55QB#3raoHw@%rYQN3&Y)x|GyNYZHu&eL%Vf4#T6hAOHafKmY;|fB*y_009U<00Q$+K%rI;-~4~^MZz9v5P$##AOHafKmY;| zfB*y_0D(C!;L$17Bw5k0h#1l{^oCXGdc$g?KEuGBbU14hM&pZ*JhlG7eaH4*-Wx;4 zE{H`pj04?!w%Da7UO(NxDZALI$;`9#?uq(Tx3(@d8b-Dn4A(x2KczbOM#uK$W8WkUACD<%Y#u(lWB>h+Y|Khq^MHI{F#WBk ze@GdqIM=0WY$1m;A4-4nYSG&_*1c0AeL}jnR6G0LT<%`VgyY<~s-xq8hZ8>4QVkj8=?ytzPfnzgca zaE{+-qz?fIKmY;|fB*y_009U<00I!WH3Cc68v2_n=ehAO@9+qBf18%H@4epR52}eO zmfWj++1Py72cpYp5z8vspZRHy-7ZjmooKU}?G_fjB57h`;+OWL`}ef6Pb?>i%H2af zE5>vG8Ky3))#{?OeDveG3n#lT?b~&+a|ioG$~^}^Z91x8(Fr70xw37WsjvU)`|SL* zlAXU)l>J?Atatv;7X70^00Izz00bZa0SG_<0uX=z1RyYv1){uT!~B09-(d_50uX=z z1Rwwb2tWV=5P$##AV7TU{}tNvMEfgyL4yDUAOHafKmY;|fB*y_009U Optional[str]: + conn = _get_conn() cursor = conn.cursor() cursor.execute("SELECT last_sign_date FROM users WHERE user_id = ?", (user_id,)) row = cursor.fetchone() conn.close() - today = datetime.datetime.now(LOCAL_TZ).date().isoformat() - return False if row and row[0] == today else True + return row[0] if row else None -def add_points_immediate(user_id, points, reason=""): - """立即增加 points 并记录日志""" +def _add_points_immediate_sync(user_id: int, points: int, reason: str = ""): now = datetime.datetime.now(LOCAL_TZ).isoformat() - conn = get_conn() + conn = _get_conn() cursor = conn.cursor() - cursor.execute("UPDATE users SET points = points + ? WHERE user_id = ?", (points, user_id)) + # 如果用户可能不存在,先插入默认记录(避免 update 失败) + cursor.execute("INSERT OR IGNORE INTO users (user_id, username, points) VALUES (?, ?, 0)", (user_id, None)) + cursor.execute("UPDATE users SET points = COALESCE(points,0) + ? WHERE user_id = ?", (points, user_id)) cursor.execute("INSERT INTO points_log (user_id, points, reason, created_at) VALUES (?, ?, ?, ?)", (user_id, points, reason, now)) conn.commit() conn.close() -def get_points(user_id): - conn = get_conn() +def _get_points_sync(user_id: int) -> int: + conn = _get_conn() cursor = conn.cursor() cursor.execute("SELECT points FROM users WHERE user_id = ?", (user_id,)) row = cursor.fetchone() @@ -156,37 +195,43 @@ def get_points(user_id): return row[0] if row else 0 -def set_last_sign(user_id): +def _set_last_sign_sync(user_id: int): today = datetime.datetime.now(LOCAL_TZ).date().isoformat() - conn = get_conn() + conn = _get_conn() cursor = conn.cursor() + cursor.execute("INSERT OR IGNORE INTO users (user_id, username) VALUES (?, ?)", (user_id, None)) cursor.execute("UPDATE users SET last_sign_date = ? WHERE user_id = ?", (today, user_id)) conn.commit() conn.close() -def inc_invite(inviter_id, invitee_id): - """记录邀请关系并奖励 inviter(只奖励第一次记录)""" +def _insert_invite_sync(inviter_id: int, invitee_id: int) -> bool: + """ + 插入邀请记录,返回 True 表示插入成功(即第一次邀请) + 如果已存在则抛出 sqlite3.IntegrityError 或返回 False + """ now = datetime.datetime.now(LOCAL_TZ).isoformat() - conn = get_conn() + conn = _get_conn() cursor = conn.cursor() try: cursor.execute("INSERT INTO invites (inviter_id, invitee_id, created_at) VALUES (?, ?, ?)", (inviter_id, invitee_id, now)) - # 增加 inviter 的 total_invites - cursor.execute("UPDATE users SET total_invites = total_invites + 1 WHERE user_id = ?", (inviter_id,)) - # 立即奖励积分 - add_points_immediate(inviter_id, INVITE_POINTS, reason="invite_reward") - except sqlite3.IntegrityError: - # 已经存在,不重复奖励 - pass - finally: + # 更新邀请计数 + cursor.execute("INSERT OR IGNORE INTO users (user_id, username) VALUES (?, ?)", (inviter_id, None)) + cursor.execute("UPDATE users SET total_invites = COALESCE(total_invites,0) + 1 WHERE user_id = ?", + (inviter_id,)) conn.commit() + return True + except sqlite3.IntegrityError: + # 已存在 + conn.rollback() + return False + finally: conn.close() -def get_invite_count(user_id): - conn = get_conn() +def _get_invite_count_sync(user_id: int) -> int: + conn = _get_conn() cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM invites WHERE inviter_id = ?", (user_id,)) count = cursor.fetchone()[0] @@ -194,68 +239,148 @@ def get_invite_count(user_id): return count -# ---------- 发言统计 ---------- -def add_message_count(user_id): +def _add_message_count_sync(user_id: int): today = datetime.datetime.now(LOCAL_TZ).date().isoformat() - conn = get_conn() + conn = _get_conn() cursor = conn.cursor() cursor.execute(''' - INSERT INTO daily_messages (user_id, date, count) - VALUES (?, ?, 1) - ON CONFLICT(user_id, date) DO UPDATE SET count = count + 1 - ''', (user_id, today)) + INSERT INTO daily_messages (user_id, date, count) + VALUES (?, ?, 1) ON CONFLICT(user_id, date) DO + UPDATE SET count = count + 1 + ''', (user_id, today)) conn.commit() conn.close() -def get_daily_message_ranking(date_iso=None, limit=None): +def _get_daily_message_ranking_sync(date_iso: Optional[str] = None, limit: Optional[int] = None) -> List[ + Tuple[int, Optional[str], int]]: date_iso = date_iso or datetime.datetime.now(LOCAL_TZ).date().isoformat() - conn = get_conn() + conn = _get_conn() cursor = conn.cursor() - cursor.execute(''' - SELECT u.user_id, u.username, d.count - FROM daily_messages d - JOIN users u ON d.user_id = u.user_id - WHERE d.date = ? - ORDER BY d.count DESC - {} - '''.format(f"LIMIT {limit}" if limit else ""), (date_iso,)) + query = ''' + SELECT u.user_id, u.username, d.count + FROM daily_messages d + JOIN users u ON d.user_id = u.user_id + WHERE d.date = ? + ORDER BY d.count DESC \ + ''' + if limit: + query += f" LIMIT {limit}" + cursor.execute(query, (date_iso,)) rows = cursor.fetchall() conn.close() return rows -def reset_daily_messages(date_iso=None): - """删除指定日期的 daily_messages(结算后调用)""" +def _reset_daily_messages_sync(date_iso: Optional[str] = None): date_iso = date_iso or datetime.datetime.now(LOCAL_TZ).date().isoformat() - conn = get_conn() + conn = _get_conn() cursor = conn.cursor() cursor.execute("DELETE FROM daily_messages WHERE date = ?", (date_iso,)) conn.commit() conn.close() -# ---------- 币价获取 ---------- -def get_price(symbol: str): - """ - 使用外部 API 获取当日 K 线数据并返回当前价、24h 变化、今日高、今日低 - 这里调用的是 websea 的接口(和你原来一致) - """ +# ---------- 异步包装(供 bot 使用) ---------- +async def init_db(): + # 初始化 DB(放在 to_thread 中) + await asyncio.to_thread(_init_db_sync) + + +async def add_or_update_user(user_id: int, username: Optional[str]): + username = username or None + # use lock to avoid many threads trying to create the file at once + async with DB_LOCK: + await asyncio.to_thread(_add_or_update_user_sync, user_id, username) + + +async def can_sign(user_id: int) -> bool: + async with DB_LOCK: + last = await asyncio.to_thread(_get_last_sign_date_sync, user_id) + today = datetime.datetime.now(LOCAL_TZ).date().isoformat() + return False if last and last == today else True + + +async def add_points_immediate(user_id: int, points: int, reason: str = ""): + async with DB_LOCK: + await asyncio.to_thread(_add_points_immediate_sync, user_id, points, reason) + + +async def get_points(user_id: int) -> int: + async with DB_LOCK: + return await asyncio.to_thread(_get_points_sync, user_id) + + +async def set_last_sign(user_id: int): + async with DB_LOCK: + await asyncio.to_thread(_set_last_sign_sync, user_id) + + +async def inc_invite(inviter_id: int, invitee_id: int): + """记录邀请关系并奖励 inviter(只奖励第一次记录)""" + if not inviter_id or not invitee_id: + print(f"[invite] 无效的邀请者或受邀者ID: inviter={inviter_id}, invitee={invitee_id}") + return False + + if inviter_id == invitee_id: + print(f"[invite] 不能邀请自己: user_id={inviter_id}") + return False + + # 使用锁,保证并发情况下不会重复奖励 + async with DB_LOCK: + inserted = await asyncio.to_thread(_insert_invite_sync, inviter_id, invitee_id) + + if inserted: + # 插入成功后再给积分(不在 DB_LOCK 内执行,将奖励记录写进 DB) + try: + await add_points_immediate(inviter_id, INVITE_POINTS, reason="invite_reward") + print( + f"[invite] 成功记录邀请并奖励积分: inviter={inviter_id}, invitee={invitee_id}, points={INVITE_POINTS}") + return True + except Exception as e: + print(f"[invite] 奖励积分失败: inviter={inviter_id}, error={e}") + return False + else: + print(f"[invite] 邀请记录已存在(重复邀请): inviter={inviter_id}, invitee={invitee_id}") + return False + + +async def get_invite_count(user_id: int) -> int: + async with DB_LOCK: + return await asyncio.to_thread(_get_invite_count_sync, user_id) + + +async def add_message_count(user_id: int): + async with DB_LOCK: + await asyncio.to_thread(_add_message_count_sync, user_id) + + +async def get_daily_message_ranking(date_iso: Optional[str] = None, limit: Optional[int] = None): + async with DB_LOCK: + return await asyncio.to_thread(_get_daily_message_ranking_sync, date_iso, limit) + + +async def reset_daily_messages(date_iso: Optional[str] = None): + async with DB_LOCK: + await asyncio.to_thread(_reset_daily_messages_sync, date_iso) + + +# ---------- 币价获取(阻塞 requests -> 放到线程池) ---------- +def _get_price_sync(symbol: str) -> Tuple[float, float, float, float]: headers = {'user-agent': 'Mozilla/5.0'} tz = LOCAL_TZ today = datetime.datetime.now(tz).date() start_of_day = datetime.datetime.combine(today, datetime.time.min, tzinfo=tz) end_of_day = datetime.datetime.combine(today, datetime.time.max, tzinfo=tz) - params = { 'symbol': symbol, 'period': '30min', 'start': int(start_of_day.timestamp()), 'end': int(end_of_day.timestamp()), } - try: - response = requests.get('https://eapi.websea.com/webApi/market/getKline', params=params, headers=headers, timeout=10) + response = requests.get('https://eapi.websea.com/webApi/market/getKline', params=params, headers=headers, + timeout=10) data = response.json().get("result", {}).get("data", []) if not data: return 0.0, 0.0, 0.0, 0.0 @@ -270,25 +395,33 @@ def get_price(symbol: str): return 0.0, 0.0, 0.0, 0.0 -def get_price_msg(symbol: str): - current_price, change_24h, today_high, today_low = get_price(symbol) - name = symbol.split('-')[0] - url = crypto_currencies.get(name, {}).get("url", "#") +async def get_price(symbol: str): + return await asyncio.to_thread(_get_price_sync, symbol) + + +def get_price_msg_from_values(name: str, current_price: float, change_24h: float, today_high: float, today_low: float, + url: str): msg = ( - f"📊 {name}/USDT 实时行情\n\n" - f"💰 当前价格:{current_price:.4f} USDT\n" - f"📈 24小时涨跌幅:{change_24h:.2f}%\n" - f"⬆️ 最高价:{today_high:.4f} USDT\n" - f"⬇️ 最低价:{today_low:.4f} USDT\n\n" - f"🔗 交易链接:{url}\n" - f"🎁 注册邀请:{INVITE_LINK}\n\n" - f"(此消息每小时自动更新)" + f"📊 {name}/USDT Real-time Quotes\n\n" + f"💰 Current Price:{current_price:.4f} USDT\n" + f"📈 24-hour price change:{change_24h:.2f}%\n" + f"⬆️ Highest Price:{today_high:.4f} USDT\n" + f"⬇️ Lowest Price:{today_low:.4f} USDT\n\n" + f"🔗 Transaction Link:{url}\n" + f"🎁 Registration Invitation:{INVITE_LINK}\n\n" + f"(This message is automatically updated hourly.)" ) return msg +async def get_price_msg(symbol: str): + current_price, change_24h, today_high, today_low = await get_price(symbol) + name = symbol.split('-')[0] + url = crypto_currencies.get(name, {}).get("url", "#") + return get_price_msg_from_values(name, current_price, change_24h, today_high, today_low, url) + + def get_crypto_buttons(): - """生成币种选择按钮""" buttons = [] row = [] for name in crypto_currencies.keys(): @@ -298,50 +431,54 @@ def get_crypto_buttons(): row = [] if row: buttons.append(row) - buttons.append([Button.inline("返回菜单", b"/help")]) + buttons.append([Button.inline("Return to Menu", b"/help")]) return buttons -# ---------- 机器人命令处理 ---------- +# ---------- 命令帮助文本 ---------- def get_command_list_text(): - msg = "🤖 机器人命令列表:\n" + msg = "🤖 Robot Command List:\n" for cmd, desc in COMMANDS_TEXT.items(): msg += f"{cmd} → {desc}\n" - msg += f"\n签到奖励:每次 {SIGN_POINTS} 积分,100积分可兑换 5 USDT 合约手续费代金券。\n发言奖励:每日排行前 {DAILY_SPEAK_TOP_N} 每人 {DAILY_SPEAK_REWARD} 积分。\n邀请奖励:每邀请一人 {INVITE_POINTS} 积分。\n每日结算时间:每天 12:00(America/New_York 时区)。" + msg += f"\nSign-in Reward: {SIGN_POINTS} points each time, and 100 points can be redeemed for a 5 USDT futures fee voucher.\nChat Reward: The top {DAILY_SPEAK_TOP_N} users in daily chat activity each receive {DAILY_SPEAK_REWARD} points.\nInvitation Reward: Each invited user grants {INVITE_POINTS} points.\nDaily Settlement Time: Every day at 12:00 PM (America/New_York timezone)." return msg -async def handle_command(event, cmd): +# ---------- 事件处理(异步) ---------- +async def handle_command(event, cmd: str): user = await event.get_sender() + if user is None: + return user_id = user.id username = user.username or user.first_name or "未知用户" + await add_or_update_user(user_id, username) - add_or_update_user(user_id, username) reply = "" - - if cmd in ["/sign", "签到", "/tanda"]: - if not can_sign(user_id): - reply = f"🌞 @{username},你今天已经签到过啦!" + # 统一处理命令名(可能传进来已经小写) + if cmd in ["/sign", "sign in","/tanda"]: + if not await can_sign(user_id): + reply = f"🌞 @{username},You've already signed in today.!" else: - add_points_immediate(user_id, SIGN_POINTS, reason="sign") - set_last_sign(user_id) - total = get_points(user_id) - reply = f"✅ @{username} 签到成功!获得 {SIGN_POINTS} 积分\n当前总积分:{total}\n\n100积分可兑换 5USDT 合约手续费代金券。" - elif cmd in ["/daily_rank", "发言", "/kedudukan_harian"]: - ranking = get_daily_message_ranking() + await add_points_immediate(user_id, SIGN_POINTS, reason="sign") + await set_last_sign(user_id) + total = await get_points(user_id) + reply = f"✅ @{username} Check-in successful!obtain {SIGN_POINTS} Points\nCurrent total points:{total}\n\n100 points can be redeemed for a 5 USDT contract fee voucher.。" + elif cmd in ["/daily_rank", "rankings","/kedudukan_harian"]: + ranking = await get_daily_message_ranking() if not ranking: - reply = "📊 今日还没有人发言哦~" + reply = "📊 No one has posted yet today.~" else: reply_lines = [] for i, (uid, uname, cnt) in enumerate(ranking): - reply_lines.append(f"{i + 1}. @{uname or uid}: {cnt} 条") - reply = "📊 今日发言排行:\n" + "\n".join(reply_lines) - elif cmd in ["/my_invites", "邀请", "/daily_position"]: - count = get_invite_count(user_id) - total_pts = get_points(user_id) - reply = f"👥 @{username},你邀请了 {count} 人。\n当前积分:{total_pts}" - elif cmd in ["/btc", "币价"]: - await event.respond("请选择要查看的币种:", buttons=get_crypto_buttons()) + display = f"@{uname}" if uname else str(uid) + reply_lines.append(f"{i + 1}. {display}: {cnt} 条") + reply = "📊 Today's Top Speakers:\n" + "\n".join(reply_lines) + elif cmd in ["/my_invites", "invitation","/daily_position"]: + count = await get_invite_count(user_id) + total_pts = await get_points(user_id) + reply = f"👥 @{username},You have invited {count} person。\nCurrent points:{total_pts}" + elif cmd in ["/btc"]: + await event.respond("Please select the currency you wish to view:", buttons=get_crypto_buttons()) return else: await event.reply(get_command_list_text(), buttons=main_buttons) @@ -352,9 +489,9 @@ async def handle_command(event, cmd): async def handle_price_command(event, symbol_name: str): symbol = crypto_currencies[symbol_name]["type"] - msg = get_price_msg(symbol) - # event.edit 用于 CallbackQuery,event.respond/edit 都可 + msg = await get_price_msg(symbol) try: + # CallbackQuery 用 edit 更友好 await event.edit(msg, buttons=get_crypto_buttons(), link_preview=False) except Exception: await event.respond(msg, buttons=get_crypto_buttons(), link_preview=False) @@ -366,8 +503,7 @@ async def send_price_periodically(bot, chat_id): while True: try: random_key = random.choice(list(crypto_currencies.keys())) - msg = get_price_msg(crypto_currencies[random_key]["type"]) - # 注意:如果群组比较多,此处简单发送。可优化为只在特定时间或轮询。 + msg = await get_price_msg(crypto_currencies[random_key]["type"]) await bot.send_message(chat_id, msg, buttons=get_crypto_buttons(), link_preview=False) except Exception as e: print(f"发送价格失败: {e}") @@ -380,9 +516,10 @@ async def send_price_periodically(bot, chat_id): # ---------- 定时任务:每天结算(每天 12:00 America/New_York) ---------- async def daily_settlement_task(bot): - """每天在 LOCAL_TZ 12:00 执行: - - 计算当日发言排名,给前 N 名每人奖励 - - 记录日志并清空当天的发言统计 + """ + 每天在 LOCAL_TZ 12:00 执行: + - 计算当日发言排名,给前 N 名每人奖励 + - 记录日志并清空当天的发言统计 """ while True: now = datetime.datetime.now(LOCAL_TZ) @@ -391,35 +528,36 @@ async def daily_settlement_task(bot): if now >= target: target = target + datetime.timedelta(days=1) wait_seconds = (target - now).total_seconds() - print(f"[settlement] 下次结算(12:00)在 {target.isoformat()},等待 {int(wait_seconds)} s") + print(f"[settlement] Next settlement(12:00)in {target.isoformat()},Waiting {int(wait_seconds)} s") await asyncio.sleep(wait_seconds) - # 执行结算 - date_iso = (target - datetime.timedelta(days=1)).date().isoformat() # 结算前一天(也可结算今天到目前为止) - print(f"[settlement] 执行结算,目标日期:{date_iso}") - ranking = get_daily_message_ranking(date_iso=date_iso, limit=DAILY_SPEAK_TOP_N) + # 执行结算(结算 target 的前一天) + date_iso = (target - datetime.timedelta(days=1)).date().isoformat() + print(f"[settlement] Execute settlement, target date:{date_iso}") + ranking = await get_daily_message_ranking(date_iso=date_iso, limit=DAILY_SPEAK_TOP_N) if ranking: for i, (uid, uname, cnt) in enumerate(ranking): try: - add_points_immediate(uid, DAILY_SPEAK_REWARD, reason=f"daily_speak_rank_{i+1}") + await add_points_immediate(uid, DAILY_SPEAK_REWARD, reason=f"daily_speak_rank_{i + 1}") except Exception as e: - print(f"[settlement] 给用户 {uid} 发奖励失败: {e}") + print(f"[settlement] For users {uid} Failed to issue rewards: {e}") # 给管理员或群组发送结算通知(可选) try: - summary_lines = [f"📅 {date_iso} 发言排行榜结算结果:"] + summary_lines = [f"📅 {date_iso} Speaker Ranking Settlement Results:"] for i, (uid, uname, cnt) in enumerate(ranking): - summary_lines.append(f"{i+1}. @{uname or uid} — {cnt} 条 — 获得 {DAILY_SPEAK_REWARD} 积分") + display = f"@{uname}" if uname else str(uid) + summary_lines.append(f"{i + 1}. {display} — {cnt} Article — obtain {DAILY_SPEAK_REWARD} Points") summary = "\n".join(summary_lines) for gid in ALLOWED_GROUPS: await bot.send_message(gid, summary) except Exception as e: - print("[settlement] 发送结算通知失败:", e) + print("[settlement] Failed to send settlement notification:", e) else: - print("[settlement] 当日无发言记录,跳过发言奖励") + print("[settlement] No speaking records for the day; skip speaking rewards.") # 清空那天的统计 try: - reset_daily_messages(date_iso=date_iso) + await reset_daily_messages(date_iso=date_iso) print(f"[settlement] 清理完成:{date_iso}") except Exception as e: print("[settlement] 清理 daily_messages 失败:", e) @@ -427,8 +565,10 @@ async def daily_settlement_task(bot): # ---------- 主逻辑 ---------- async def main(): - init_db() + await init_db() + # 使用代理时传 proxy=PROXY,若不需要代理则移除该参数 + # Telethon proxy 格式可能和你的代理不同,请按需调整(或移除) bot = TelegramClient('bot_session', API_ID, API_HASH, proxy=PROXY) await bot.start(bot_token=BOT_TOKEN) @@ -442,82 +582,109 @@ async def main(): @bot.on(events.ChatAction) async def welcome(event: events.ChatAction.Event): # 欢迎新成员并尝试记录邀请者(若存在) - if event.user_added or event.user_joined: - for user in event.users: - name = user.username or user.first_name or "新成员" - # 尝试获得 inviter id(不同版本的 Telethon 结构不完全一致) - inviter_id = None + if not (event.user_added or event.user_joined): + return + + # 尝试获取 inviter id(兼容多种 Telethon 结构) + inviter_id = None + try: + # 优先 actor_id(某些 Telethon 版本和场景) + if hasattr(event, "actor_id") and event.actor_id: + inviter_id = event.actor_id + # 再尝试 action_message.from_id(另一些版本) + elif hasattr(event, "action_message") and event.action_message: + maybe = getattr(event.action_message, "from_id", None) + if isinstance(maybe, types.PeerUser): + inviter_id = maybe.user_id + elif isinstance(maybe, int): + inviter_id = maybe + # 在某些情形下 from_id 是 a User 或 MessageEntity,处理兼容性 + elif hasattr(maybe, "user_id"): + inviter_id = getattr(maybe, "user_id", None) + except Exception as e: + print(f"[welcome] 获取邀请者ID时出错: {e}") + inviter_id = None + + # 对 event.users 中的新成员逐个处理 + for user in event.users: + try: + new_uid = user.id + new_name = user.username or user.first_name or "新成员" + + # 保证用户存在于 users 表中 + await add_or_update_user(new_uid, new_name) + + # 如果找到邀请者并且不是自己,则记录邀请 + if inviter_id and inviter_id != new_uid: + # 确保 inviter 在 users 表中(用户名未知时填 None) + await add_or_update_user(inviter_id, None) + + # 调用 inc_invite 并检查返回值 + success = await inc_invite(inviter_id, new_uid) + if success: + print(f"[welcome] 成功处理邀请: {inviter_id} 邀请了 {new_uid}") + else: + print(f"[welcome] 邀请处理失败或已存在: {inviter_id} -> {new_uid}") + elif not inviter_id: + print(f"[welcome] 未找到邀请者,新成员 {new_uid} 可能是自己加入的") + else: + print(f"[welcome] 跳过自己邀请自己: {new_uid}") + + # 欢迎消息 try: - # 若是邀请添加,action_message 的 from_id 通常是 inviter - if hasattr(event, 'action_message') and event.action_message: - maybe = getattr(event.action_message, "from_id", None) - if isinstance(maybe, types.PeerUser): - inviter_id = maybe.user_id - elif isinstance(maybe, (int,)): - inviter_id = maybe - # Telethon 也可能直接暴露 actor_id - if getattr(event, "actor_id", None): - inviter_id = event.actor_id - except Exception: - inviter_id = None - - # 如果 inviter_id 存在且与新成员不同,则记录邀请并奖励 - try: - if inviter_id and inviter_id != user.id: - # 确保 inviter 存在 users 表 - add_or_update_user(inviter_id, "unknown") - inc_invite(inviter_id, user.id) - except Exception as e: - print("记录邀请失败:", e) - - # 更新/插入新成员到 users 表 - add_or_update_user(user.id, user.username or user.first_name or "新成员") - - # 欢迎消息(发送到群内) - try: - await event.reply(f"欢迎 @{name} 加入群聊!\n{get_command_list_text()}") + await event.reply(f"欢迎 @{new_name} 加入群聊!\n{get_command_list_text()}") except Exception: + # 某些场景 event.reply 不允许(例如系统消息),忽略 pass + except Exception as e: + print(f"[welcome] 处理新成员异常: user_id={user.id if user else 'unknown'}, error={e}") + @bot.on(events.NewMessage) async def on_message(event): # 只处理白名单群组 if event.chat_id not in ALLOWED_GROUPS: return - # 普通消息处理:命令或记录发言计数 text = (event.raw_text or "").strip() if not text: return - # 去掉命令中的 @botname(如 /sign@mybot) + # 去掉命令末尾的 @botname if "@" in text and text.startswith("/"): text = text.split("@")[0] - # 小写匹配命令(命令与 data 映射) lowered = text.lower() - # 命令集合 + + # 命令集合(注意使用小写比较) cmds_map = { "签到": ["/sign", "签到", "/tanda"], "发言": ["/daily_rank", "发言", "/kedudukan_harian"], "邀请": ["/my_invites", "邀请", "/daily_position"], "币价": ["/btc", "币价"] } + for group_cmds in cmds_map.values(): if lowered in [c.lower() for c in group_cmds]: + # 传入原始命令(保持原有行为) await handle_command(event, lowered) return - # 不是命令则记录发言次数 + # 非命令,记录发言次数 sender = await event.get_sender() if sender: - add_or_update_user(sender.id, sender.username or sender.first_name or "未知用户") - add_message_count(sender.id) + await add_or_update_user(sender.id, sender.username or sender.first_name or "未知用户") + await add_message_count(sender.id) @bot.on(events.CallbackQuery) async def callback(event): - await event.answer("正在处理...", alert=False) - cmd = event.data.decode() + # 快速响应 UI + try: + await event.answer("Currently being processed...", alert=False) + except Exception: + pass + + cmd = event.data.decode() if event.data else "" if "@" in cmd and cmd.startswith("/"): cmd = cmd.split("@")[0] diff --git a/telegram/gpt1.py b/telegram/gpt1.py new file mode 100644 index 0000000..2ea9a20 --- /dev/null +++ b/telegram/gpt1.py @@ -0,0 +1,844 @@ +import asyncio +import datetime +import sqlite3 +import random +import requests +from zoneinfo import ZoneInfo +from typing import Optional, Tuple, List +from telethon import TelegramClient, events, Button, types +from telethon.tl.functions.bots import SetBotCommandsRequest +from telethon.tl.types import BotCommand, BotCommandScopeDefault + +# ========== 配置区 ========== +API_ID = 2040 +API_HASH = "b18441a1ff607e10a989891a5462e627" +BOT_TOKEN = "8451724418:AAGTGqCmc1JiUr88IABhMiQTHeVLcAcnT5Y" +DB_PATH = "sign.db" + +# 积分配置 +SIGN_POINTS = 10 +INVITE_POINTS = 10 +DAILY_SPEAK_TOP_N = 10 +DAILY_SPEAK_REWARD = 10 + +# 允许机器人运行的群组(只处理这些群的消息) +ALLOWED_GROUPS = [-1003238845008] + +# 时区(结算规则用) +LOCAL_TZ = ZoneInfo("America/New_York") + +# 代理(可选) +PROXY = { + 'proxy_type': "socks5", + 'addr': "202.155.144.102", + 'port': 31102, + 'username': "SyNuejCtrQ", + 'password': "MH8ioL7EXf" +} + +INVITE_LINK = "https://www.websea.my/en/signup?key=77346588" + +crypto_currencies = { + "BTC": {"type": "BTC-USDT", "url": "https://www.websea.com/zh-CN/trade/BTC-USDT"}, + "ETH": {"type": "ETH-USDT", "url": "https://www.websea.com/zh-CN/trade/ETH-USDT"}, + "SOL": {"type": "SOL-USDT", "url": "https://www.websea.com/zh-CN/trade/SOL-USDT"}, + "BNB": {"type": "BNB-USDT", "url": "https://www.websea.com/zh-CN/trade/BNB-USDT"}, + "WBS": {"type": "WBS-USDT", "url": "https://www.websea.com/zh-CN/trade/WBS-USDT"} +} + + +# ---------- 命令解析工具函数 ---------- +def normalize_command(cmd: str) -> str: + """ + 标准化命令:去除 @botname,转换为小写,去除前后空格 + 例如: /sign@docekrsebot -> /sign + 支持多种格式: /sign@botname, /sign@BOTNAME, /SIGN@botname 等 + """ + if not cmd: + return "" + cmd = cmd.strip() + if "@" in cmd: + cmd = cmd.split("@")[0].strip() + return cmd.lower().strip() + + +def command_definition(display: str, description: str, aliases: Optional[List[str]] = None) -> dict: + aliases = aliases or [] + return { + "display": display, + "description": description, + "aliases": list(set([display] + aliases)), + } + + +COMMAND_DEFINITIONS = { + "sign": command_definition( + "/sign", + "Daily check-in, available once per day to earn points", + aliases=["sign", "/tanda"], + ), + "daily_rank": command_definition( + "/daily_rank", + "View today's message activity ranking", + aliases=["rankings", "/kedudukan_harian"], + ), + "my_invites": command_definition( + "/my_invites", + "View the number of people you have invited", + aliases=["invitation", "/daily_position"], + ), + "points": command_definition( + "/points", + "View your current points balance", + aliases=["/balance", "/积分", "points"], + ), + "btc": command_definition( + "/btc", + "Get the Bitcoin price menu", + aliases=["coin price"], + ), + "help": command_definition( + "/help", + "Show all available commands and descriptions", + aliases=["help"], + ), +} + +COMMAND_ORDER = ["sign", "daily_rank", "my_invites", "points", "btc", "help"] + + +def build_alias_map() -> dict: + alias_map = {} + for key, info in COMMAND_DEFINITIONS.items(): + for alias in info["aliases"]: + alias_map[normalize_command(alias)] = key + alias_map[normalize_command(key)] = key + return alias_map + + +COMMAND_ALIAS_MAP = build_alias_map() + + +def resolve_command(cmd: str) -> Optional[str]: + return COMMAND_ALIAS_MAP.get(normalize_command(cmd)) + + +def command_payload(command_key: str) -> bytes: + return COMMAND_DEFINITIONS[command_key]["display"].encode() + + +main_buttons = [ + [Button.inline("Sign in", command_payload("sign")), + Button.inline("Today's Top Speakers", command_payload("daily_rank"))], + [Button.inline("My invitation", command_payload("my_invites")), + Button.inline("coin price", command_payload("btc"))], + [Button.inline("My Points", command_payload("points")), Button.inline("Assistance", command_payload("help"))], +] + + +# ============================ + +def get_primary_command_display(command_key: str) -> str: + return COMMAND_DEFINITIONS[command_key]["display"] + + +# ---------- 全局并发控制 ---------- +# 使用 asyncio.Lock 在同一时间避免大量数据库并发写入(与 to_thread 配合) +DB_LOCK = asyncio.Lock() + + +# ---------- SQLite 同步底层实现(运行在线程池) ---------- +def _get_conn(): + # 每次操作创建连接,避免 sqlite 的线程/并发问题 + # 不设置 check_same_thread:因为连接会在同一线程(to_thread)中被使用 + return sqlite3.connect(DB_PATH, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES) + + +def _init_db_sync(): + conn = _get_conn() + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users + ( + user_id + INTEGER + PRIMARY + KEY, + username + TEXT, + points + INTEGER + DEFAULT + 0, + last_sign_date + TEXT, + total_invites + INTEGER + DEFAULT + 0 + ) + ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS daily_messages + ( + user_id + INTEGER, + date + TEXT, + count + INTEGER + DEFAULT + 0, + PRIMARY + KEY + ( + user_id, + date + ) + ) + ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS invites + ( + inviter_id + INTEGER, + invitee_id + INTEGER, + created_at + TEXT, + PRIMARY + KEY + ( + inviter_id, + invitee_id + ) + ) + ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS points_log + ( + id + INTEGER + PRIMARY + KEY + AUTOINCREMENT, + user_id + INTEGER, + points + INTEGER, + reason + TEXT, + created_at + TEXT + ) + ''') + conn.commit() + conn.close() + + +def _add_or_update_user_sync(user_id: int, username: str): + conn = _get_conn() + cursor = conn.cursor() + cursor.execute("INSERT OR IGNORE INTO users (user_id, username) VALUES (?, ?)", (user_id, username)) + cursor.execute("UPDATE users SET username = ? WHERE user_id = ?", (username, user_id)) + conn.commit() + conn.close() + + +def _get_last_sign_date_sync(user_id: int) -> Optional[str]: + conn = _get_conn() + cursor = conn.cursor() + cursor.execute("SELECT last_sign_date FROM users WHERE user_id = ?", (user_id,)) + row = cursor.fetchone() + conn.close() + return row[0] if row else None + + +def _add_points_immediate_sync(user_id: int, points: int, reason: str = ""): + now = datetime.datetime.now(LOCAL_TZ).isoformat() + conn = _get_conn() + cursor = conn.cursor() + # 如果用户可能不存在,先插入默认记录(避免 update 失败) + cursor.execute("INSERT OR IGNORE INTO users (user_id, username, points) VALUES (?, ?, 0)", (user_id, None)) + cursor.execute("UPDATE users SET points = COALESCE(points,0) + ? WHERE user_id = ?", (points, user_id)) + cursor.execute("INSERT INTO points_log (user_id, points, reason, created_at) VALUES (?, ?, ?, ?)", + (user_id, points, reason, now)) + conn.commit() + conn.close() + + +def _get_points_sync(user_id: int) -> int: + conn = _get_conn() + cursor = conn.cursor() + cursor.execute("SELECT points FROM users WHERE user_id = ?", (user_id,)) + row = cursor.fetchone() + conn.close() + return row[0] if row else 0 + + +def _set_last_sign_sync(user_id: int): + today = datetime.datetime.now(LOCAL_TZ).date().isoformat() + conn = _get_conn() + cursor = conn.cursor() + cursor.execute("INSERT OR IGNORE INTO users (user_id, username) VALUES (?, ?)", (user_id, None)) + cursor.execute("UPDATE users SET last_sign_date = ? WHERE user_id = ?", (today, user_id)) + conn.commit() + conn.close() + + +def _insert_invite_sync(inviter_id: int, invitee_id: int) -> bool: + """ + 插入邀请记录,返回 True 表示插入成功(即第一次邀请) + 如果已存在则抛出 sqlite3.IntegrityError 或返回 False + """ + now = datetime.datetime.now(LOCAL_TZ).isoformat() + conn = _get_conn() + cursor = conn.cursor() + try: + cursor.execute("INSERT INTO invites (inviter_id, invitee_id, created_at) VALUES (?, ?, ?)", + (inviter_id, invitee_id, now)) + # 更新邀请计数 + cursor.execute("INSERT OR IGNORE INTO users (user_id, username) VALUES (?, ?)", (inviter_id, None)) + cursor.execute("UPDATE users SET total_invites = COALESCE(total_invites,0) + 1 WHERE user_id = ?", + (inviter_id,)) + conn.commit() + return True + except sqlite3.IntegrityError: + # 已存在 + conn.rollback() + return False + finally: + conn.close() + + +def _get_invite_count_sync(user_id: int) -> int: + conn = _get_conn() + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM invites WHERE inviter_id = ?", (user_id,)) + count = cursor.fetchone()[0] + conn.close() + return count + + +def _add_message_count_sync(user_id: int): + today = datetime.datetime.now(LOCAL_TZ).date().isoformat() + conn = _get_conn() + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO daily_messages (user_id, date, count) + VALUES (?, ?, 1) ON CONFLICT(user_id, date) DO + UPDATE SET count = count + 1 + ''', (user_id, today)) + conn.commit() + conn.close() + + +def _get_daily_message_ranking_sync(date_iso: Optional[str] = None, limit: Optional[int] = None) -> List[ + Tuple[int, Optional[str], int]]: + date_iso = date_iso or datetime.datetime.now(LOCAL_TZ).date().isoformat() + conn = _get_conn() + cursor = conn.cursor() + query = ''' + SELECT u.user_id, u.username, d.count + FROM daily_messages d + JOIN users u ON d.user_id = u.user_id + WHERE d.date = ? + ORDER BY d.count DESC \ + ''' + if limit: + query += f" LIMIT {limit}" + cursor.execute(query, (date_iso,)) + rows = cursor.fetchall() + conn.close() + return rows + + +def _reset_daily_messages_sync(date_iso: Optional[str] = None): + date_iso = date_iso or datetime.datetime.now(LOCAL_TZ).date().isoformat() + conn = _get_conn() + cursor = conn.cursor() + cursor.execute("DELETE FROM daily_messages WHERE date = ?", (date_iso,)) + conn.commit() + conn.close() + + +# ---------- 异步包装(供 bot 使用) ---------- +async def init_db(): + # 初始化 DB(放在 to_thread 中) + await asyncio.to_thread(_init_db_sync) + + +async def add_or_update_user(user_id: int, username: Optional[str]): + username = username or None + # use lock to avoid many threads trying to create the file at once + async with DB_LOCK: + await asyncio.to_thread(_add_or_update_user_sync, user_id, username) + + +async def can_sign(user_id: int) -> bool: + async with DB_LOCK: + last = await asyncio.to_thread(_get_last_sign_date_sync, user_id) + today = datetime.datetime.now(LOCAL_TZ).date().isoformat() + return False if last and last == today else True + + +async def add_points_immediate(user_id: int, points: int, reason: str = ""): + async with DB_LOCK: + await asyncio.to_thread(_add_points_immediate_sync, user_id, points, reason) + + +async def get_points(user_id: int) -> int: + async with DB_LOCK: + return await asyncio.to_thread(_get_points_sync, user_id) + + +async def set_last_sign(user_id: int): + async with DB_LOCK: + await asyncio.to_thread(_set_last_sign_sync, user_id) + + +async def inc_invite(inviter_id: int, invitee_id: int): + """Record invite relationships and reward the inviter (only first invite).""" + if not inviter_id or not invitee_id: + print(f"[invite] Invalid inviter or invitee id: inviter={inviter_id}, invitee={invitee_id}") + return False + + if inviter_id == invitee_id: + print(f"[invite] Cannot invite yourself: user_id={inviter_id}") + return False + + # 使用锁,保证并发情况下不会重复奖励 + async with DB_LOCK: + inserted = await asyncio.to_thread(_insert_invite_sync, inviter_id, invitee_id) + + if inserted: + # 插入成功后再给积分(不在 DB_LOCK 内执行,将奖励记录写进 DB) + try: + await add_points_immediate(inviter_id, INVITE_POINTS, reason="invite_reward") + print( + f"[invite] Invite recorded and points rewarded: inviter={inviter_id}, invitee={invitee_id}, points={INVITE_POINTS}") + return True + except Exception as e: + print(f"[invite] Failed to reward inviter={inviter_id}: {e}") + return False + else: + print(f"[invite] Invite already exists (duplicate): inviter={inviter_id}, invitee={invitee_id}") + return False + + +async def get_invite_count(user_id: int) -> int: + async with DB_LOCK: + return await asyncio.to_thread(_get_invite_count_sync, user_id) + + +async def add_message_count(user_id: int): + async with DB_LOCK: + await asyncio.to_thread(_add_message_count_sync, user_id) + + +async def get_daily_message_ranking(date_iso: Optional[str] = None, limit: Optional[int] = None): + async with DB_LOCK: + return await asyncio.to_thread(_get_daily_message_ranking_sync, date_iso, limit) + + +async def reset_daily_messages(date_iso: Optional[str] = None): + async with DB_LOCK: + await asyncio.to_thread(_reset_daily_messages_sync, date_iso) + + +# ---------- 币价获取(阻塞 requests -> 放到线程池) ---------- +def _get_price_sync(symbol: str) -> Tuple[float, float, float, float]: + headers = {'user-agent': 'Mozilla/5.0'} + tz = LOCAL_TZ + today = datetime.datetime.now(tz).date() + start_of_day = datetime.datetime.combine(today, datetime.time.min, tzinfo=tz) + end_of_day = datetime.datetime.combine(today, datetime.time.max, tzinfo=tz) + params = { + 'symbol': symbol, + 'period': '30min', + 'start': int(start_of_day.timestamp()), + 'end': int(end_of_day.timestamp()), + } + try: + response = requests.get('https://eapi.websea.com/webApi/market/getKline', params=params, headers=headers, + timeout=10) + data = response.json().get("result", {}).get("data", []) + if not data: + return 0.0, 0.0, 0.0, 0.0 + current_price = float(data[0]['close']) + today_high = max(float(i['high']) for i in data) + today_low = min(float(i['low']) for i in data) + price_24h_ago = float(data[-1]['open']) + change_24h = (current_price - price_24h_ago) / price_24h_ago * 100 if price_24h_ago != 0 else 0.0 + return current_price, change_24h, today_high, today_low + except Exception as e: + print("[price] Failed to fetch price data:", e) + return 0.0, 0.0, 0.0, 0.0 + + +async def get_price(symbol: str): + return await asyncio.to_thread(_get_price_sync, symbol) + + +def get_price_msg_from_values(name: str, current_price: float, change_24h: float, today_high: float, today_low: float, + url: str): + msg = ( + f"📊 {name}/USDT Real-time Quotes\n\n" + f"💰 Current Price:{current_price:.4f} USDT\n" + f"📈 24-hour price change:{change_24h:.2f}%\n" + f"⬆️ Highest Price:{today_high:.4f} USDT\n" + f"⬇️ Lowest Price:{today_low:.4f} USDT\n\n" + f"🔗 Transaction Link:{url}\n" + f"🎁 Registration Invitation:{INVITE_LINK}\n\n" + f"(This message is automatically updated hourly.)" + ) + return msg + + +async def get_price_msg(symbol: str): + current_price, change_24h, today_high, today_low = await get_price(symbol) + name = symbol.split('-')[0] + url = crypto_currencies.get(name, {}).get("url", "#") + return get_price_msg_from_values(name, current_price, change_24h, today_high, today_low, url) + + +def get_crypto_buttons(): + buttons = [] + row = [] + for name in crypto_currencies.keys(): + row.append(Button.inline(name, f"price_{name}".encode())) + if len(row) == 3: + buttons.append(row) + row = [] + if row: + buttons.append(row) + buttons.append([Button.inline("Return to Menu", command_payload("help"))]) + return buttons + + +# ---------- 命令辅助 ---------- +async def get_sender_identity(event) -> Optional[Tuple[int, str]]: + user = await event.get_sender() + if user is None: + return None + username = user.username or user.first_name or "Unknown User" + await add_or_update_user(user.id, username) + return user.id, username + + +async def build_sign_message(user_id: int, username: str) -> str: + if not await can_sign(user_id): + return f"🌞 @{username}, you have already checked in today!" + + await add_points_immediate(user_id, SIGN_POINTS, reason="sign") + await set_last_sign(user_id) + total = await get_points(user_id) + return ( + f"✅ @{username} check-in succeed! Earned {SIGN_POINTS} points.\n" + f"Current total points: {total}\n\n" + f"100 points can be exchanged for a 5 USDT futures fee voucher." + ) + + +async def build_daily_rank_message() -> str: + ranking = await get_daily_message_ranking() + if not ranking: + return "📊 No one has spoken yet today." + + lines = [] + for idx, (uid, uname, cnt) in enumerate(ranking, start=1): + mention = f"@{uname}" if uname else str(uid) + lines.append(f"{idx}. {mention}: {cnt} messages") + return "📊 Today's speaker leaderboard:\n" + "\n".join(lines) + + +async def build_invite_message(user_id: int, username: str) -> str: + count = await get_invite_count(user_id) + total_pts = await get_points(user_id) + return f"👥 @{username}, you have invited {count} users.\nCurrent points: {total_pts}" + + +async def build_points_message(user_id: int, username: str) -> str: + total_pts = await get_points(user_id) + invite_count = await get_invite_count(user_id) + return ( + f"💰 @{username} Points Overview\n\n" + f"📊 Current points: {total_pts}\n" + f"👥 Invited users: {invite_count}\n\n" + f"💡 100 points can be exchanged for a 5 USDT futures fee voucher." + ) + + +def get_command_list_text() -> str: + msg = "🤖 Robot Command List:\n\n" + for key in COMMAND_ORDER: + info = COMMAND_DEFINITIONS[key] + msg += f"`{info['display']}` → {info['description']}\n" + msg += ( + f"\nSign-in Reward: {SIGN_POINTS} points each time, and 100 points can be redeemed for a 5 USDT futures fee voucher.\n" + f"Chat Reward: The top {DAILY_SPEAK_TOP_N} users in daily chat activity each receive {DAILY_SPEAK_REWARD} points.\n" + f"Invitation Reward: Each invited user grants {INVITE_POINTS} points.\n" + f"Daily Settlement Time: Every day at 12:00 PM (America/New_York timezone)." + ) + return msg + + +async def send_help(event): + await event.reply(get_command_list_text(), buttons=main_buttons, parse_mode="md") + + +# ---------- 事件处理(异步) ---------- +async def handle_command(event, cmd: str, canonical_cmd: Optional[str] = None): + identity = await get_sender_identity(event) + if not identity: + return + user_id, username = identity + + command_key = canonical_cmd or resolve_command(cmd) + if not command_key: + await send_help(event) + return + + if command_key == "sign": + reply = await build_sign_message(user_id, username) + elif command_key == "daily_rank": + reply = await build_daily_rank_message() + elif command_key == "my_invites": + reply = await build_invite_message(user_id, username) + elif command_key == "points": + reply = await build_points_message(user_id, username) + elif command_key == "btc": + await event.respond("Please select the currency you wish to view:", buttons=get_crypto_buttons()) + return + elif command_key == "help": + await send_help(event) + return + else: + await send_help(event) + return + + await event.reply(reply) + + +async def handle_price_command(event, symbol_name: str): + symbol = crypto_currencies[symbol_name]["type"] + msg = await get_price_msg(symbol) + try: + # CallbackQuery 用 edit 更友好 + await event.edit(msg, buttons=get_crypto_buttons(), link_preview=False) + except Exception: + await event.respond(msg, buttons=get_crypto_buttons(), link_preview=False) + + +# ---------- 定时任务:每小时推送随机币价 ---------- +async def send_price_periodically(bot, chat_id): + await asyncio.sleep(5) # 启动缓冲 + while True: + try: + random_key = random.choice(list(crypto_currencies.keys())) + msg = await get_price_msg(crypto_currencies[random_key]["type"]) + await bot.send_message(chat_id, msg, buttons=get_crypto_buttons(), link_preview=False) + except Exception as e: + print(f"[price] Failed to send price update: {e}") + # 等待下一小时整点(保持每小时推送一次) + now = datetime.datetime.now(LOCAL_TZ) + next_hour = (now.replace(minute=0, second=0, microsecond=0) + datetime.timedelta(hours=1)) + wait_seconds = (next_hour - now).total_seconds() + await asyncio.sleep(wait_seconds) + + +# ---------- 定时任务:每天结算(每天 12:00 America/New_York) ---------- +async def daily_settlement_task(bot): + """ + 每天在 LOCAL_TZ 12:00 执行: + - 计算当日发言排名,给前 N 名每人奖励 + - 记录日志并清空当天的发言统计 + """ + while True: + now = datetime.datetime.now(LOCAL_TZ) + # 目标时间:今天 12:00(如果已过则到明天) + target = now.replace(hour=12, minute=0, second=0, microsecond=0) + if now >= target: + target = target + datetime.timedelta(days=1) + wait_seconds = (target - now).total_seconds() + print(f"[settlement] Next settlement (12:00) at {target.isoformat()}, waiting {int(wait_seconds)} s") + await asyncio.sleep(wait_seconds) + + # 执行结算(结算 target 的前一天) + date_iso = (target - datetime.timedelta(days=1)).date().isoformat() + print(f"[settlement] Executing settlement, target date: {date_iso}") + ranking = await get_daily_message_ranking(date_iso=date_iso, limit=DAILY_SPEAK_TOP_N) + if ranking: + for i, (uid, uname, cnt) in enumerate(ranking): + try: + await add_points_immediate(uid, DAILY_SPEAK_REWARD, reason=f"daily_speak_rank_{i + 1}") + except Exception as e: + print(f"[settlement] For users {uid} Failed to issue rewards: {e}") + # 给管理员或群组发送结算通知(可选) + try: + summary_lines = [f"📅 {date_iso} speaker ranking settlement results:"] + for i, (uid, uname, cnt) in enumerate(ranking): + display = f"@{uname}" if uname else str(uid) + summary_lines.append(f"{i + 1}. {display} — {cnt} messages — awarded {DAILY_SPEAK_REWARD} points") + summary = "\n".join(summary_lines) + for gid in ALLOWED_GROUPS: + await bot.send_message(gid, summary) + except Exception as e: + print("[settlement] Failed to send settlement notification:", e) + else: + print("[settlement] No speaking records for the day; skip speaking rewards.") + + # 清空那天的统计 + try: + await reset_daily_messages(date_iso=date_iso) + print(f"[settlement] Cleared daily messages for {date_iso}") + except Exception as e: + print("[settlement] Failed to clear daily_messages:", e) + + +# ---------- 主逻辑 ---------- +async def main(): + await init_db() + + # 使用代理时传 proxy=PROXY,若不需要代理则移除该参数 + # Telethon proxy 格式可能和你的代理不同,请按需调整(或移除) + bot = TelegramClient('bot_session', API_ID, API_HASH, proxy=PROXY) + await bot.start(bot_token=BOT_TOKEN) + + # 设置 Bot Commands(让用户可以通过命令菜单选择命令,避免自动添加 @botname) + try: + commands = [ + BotCommand( + command=COMMAND_DEFINITIONS[key]["display"].lstrip("/"), + description=COMMAND_DEFINITIONS[key]["description"], + ) + for key in COMMAND_ORDER + ] + + await bot( + SetBotCommandsRequest( + scope=BotCommandScopeDefault(), + lang_code="en", + commands=commands, + ) + ) + print("[bot] Bot commands set successfully") + except Exception as e: + print(f"[bot] Failed to set bot commands: {e}") + + # 启动每小时发送币价任务(对每个白名单群) + for gid in ALLOWED_GROUPS: + asyncio.create_task(send_price_periodically(bot, gid)) + + # 启动每日结算任务(12:00 America/New_York) + asyncio.create_task(daily_settlement_task(bot)) + + @bot.on(events.ChatAction) + async def welcome(event: events.ChatAction.Event): + # 欢迎新成员并尝试记录邀请者(若存在) + if not (event.user_added or event.user_joined): + return + + # 尝试获取 inviter id(兼容多种 Telethon 结构) + inviter_id = None + try: + # 优先 actor_id(某些 Telethon 版本和场景) + if hasattr(event, "actor_id") and event.actor_id: + inviter_id = event.actor_id + # 再尝试 action_message.from_id(另一些版本) + elif hasattr(event, "action_message") and event.action_message: + maybe = getattr(event.action_message, "from_id", None) + if isinstance(maybe, types.PeerUser): + inviter_id = maybe.user_id + elif isinstance(maybe, int): + inviter_id = maybe + # 在某些情形下 from_id 是 a User 或 MessageEntity,处理兼容性 + elif hasattr(maybe, "user_id"): + inviter_id = getattr(maybe, "user_id", None) + except Exception as e: + print(f"[welcome] Failed to obtain inviter id: {e}") + inviter_id = None + + # 对 event.users 中的新成员逐个处理 + for user in event.users: + try: + new_uid = user.id + new_name = user.username or user.first_name or "New member" + + # 保证用户存在于 users 表中 + await add_or_update_user(new_uid, new_name) + + # 如果找到邀请者并且不是自己,则记录邀请 + if inviter_id and inviter_id != new_uid: + # 确保 inviter 在 users 表中(用户名未知时填 None) + await add_or_update_user(inviter_id, None) + + # 调用 inc_invite 并检查返回值 + success = await inc_invite(inviter_id, new_uid) + if success: + print(f"[welcome] Invite processed: {inviter_id} invited {new_uid}") + else: + print(f"[welcome] Invite already handled or failed: {inviter_id} -> {new_uid}") + elif not inviter_id: + print(f"[welcome] Inviter not found, new member {new_uid} likely joined independently") + else: + print(f"[welcome] Skipping self-invite: {new_uid}") + + # 欢迎消息 + try: + await event.reply(f"Welcome @{new_name} to the group!\n{get_command_list_text()}") + except Exception: + # 某些场景 event.reply 不允许(例如系统消息),忽略 + pass + + except Exception as e: + print(f"[welcome] Failed to handle new member: user_id={user.id if user else 'unknown'}, error={e}") + + @bot.on(events.NewMessage) + async def on_message(event): + if event.chat_id not in ALLOWED_GROUPS: + return + + text = (event.raw_text or "").strip() + if not text: + return + + command_key = resolve_command(text) + if command_key: + if "@" in text: + print(f"[command] Raw: {text} -> Resolved: {get_primary_command_display(command_key)}") + await handle_command(event, text, canonical_cmd=command_key) + return + + identity = await get_sender_identity(event) + if identity: + await add_message_count(identity[0]) + + @bot.on(events.CallbackQuery) + async def callback(event): + # 快速响应 UI + try: + await event.answer("Currently being processed...", alert=False) + except Exception: + pass + + cmd = event.data.decode() if event.data else "" + normalized_cmd = normalize_command(cmd) + + if normalized_cmd.startswith("price_"): + symbol = normalized_cmd.split("_", 1)[1] + await handle_price_command(event, symbol) + else: + command_key = resolve_command(cmd) + if command_key and "@" in cmd: + print(f"[callback] Raw: {cmd} -> Resolved: {get_primary_command_display(command_key)}") + await handle_command(event, cmd, canonical_cmd=command_key) + + print("🤖 Bot is running and waiting for group messages...") + await bot.run_until_disconnected() + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("Bot stopped") diff --git a/telegram/haha.session b/telegram/haha.session deleted file mode 100644 index 8c753531b29f345a1196c11c732c87bf6a036643..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI(eM}o=90%~{y3#@!>*QsX0r5e?pc}#nZ{ZK;%7!x;6W9nd>UJL7(OxKbZSQbn znk?yXi-^H7#BBy8nVRXMCetNbHWw9PIGu4B3dZ<{?k&p}6F1}3n9Qez+7b$le=Q^5 zC)eI{Pk(vtc|P}RlPBr3slG!7Vt zM?utBkFJG+qX&3n1q22?cNd!E4F${?gWX?gG~2SXxz2}!2%4nme2eUsv~VqM8bG*x zmg%>NWxOamB~7PIZnY`;v;w!_HF-yjq3a!<7!hIY=r?i+nkzK>=n)amC_g;*0{Y(& z3us^%_L+7iaVFckY>vo^C~eogZnm7_g?3#HUh`onzBpVru}qH-WV=6Sf!UUo#dW3! z;hDRv%TgdPcs5Q$54Fxh9!7|ragiWYQFW@cyWP=c+BM0i2yGJ8rR{q35RXe$VmzT3 zJw)d3Ok%&q5LDXboJvnklU0Q!=j3_8G^J4MEJ|WC|9_y^IM)5eR-?e+cEyknM{x^LR3 zc%uA_{@NP@d1J>G_r7AeMwSeBCLWyl+A(Z7^N?mcHU8(s(9(eivqqA~w)}p)?%XHl z{j_Mab~Naeezr_gcv##067JcnsF4Xpo-Ru{2;W_4Nj zS;y}22aVxF00Izz00bZa0SG_<0uX=z1n#Op?yE1o<6mPYrVNr}EGioiesKPMnr$lB zrizj-3re#~rcObXQ3)?AKRlpxig=;J(TKaP1l#bdAj>u1M47%WEoc;-OYb__T~}Rfz--6Xj1i1HE^@Q z`k!t3M}YtYAOHafKmY;|fB*y_009U*xt0&~Oqe;z+!Obr4MfB*y_009U<00Izz z00baFCinkaZxME&KmY;|fB*y_009U<00Izz00bZ~e*_YViE#18geC0%|M&k3W%mwd diff --git a/telegram/sign.db b/telegram/sign.db index eea0466429d408fe83b80bb5ce65b7cb1158031a..aefd235bd77bd9aeaef3b0afabac2e8e2f9ecbf8 100644 GIT binary patch literal 32768 zcmeI*U1-}z00;0pJC+g$S1lxn5khVHu$b0CvTM?MQ08vxFwOT|Xem^vu@*!ed$HuL zEsQ}k)(p1iK{{IK+xE1VQSy>L6#BM36bgCo%f<@rTMLEKlO^LS)j4UsK~w&RElYR4 z)9H72HokM>&*vsB$4q`vZ!8;5GDr3kO3BB`Bq3y!-TT?S9s1agdv6EY-s$obAMk zG;0gn5Boov*NSB=SuUQO)RNntJ61E6%_A&ldUZt*9Y2jHqT3*oi9xl=g;wyPix$y;+gVHX_{4jN}Dbp;T6!d z&4#{X?YP-J4qoB@wQ+MdOvA3%c)82kS$@42SYF4h=!VlRdstd}wbUodBO~;CtJUUK z?JL%gZ2ut^Xl*|-tQdY%x611U)jO-%L$mBohubc1*Xed4!aZa6OHPM1fIFt-C<{Z zE~Hyk_f3&kbl0A?t2Y5&$#31&s)p_8wzXK(dA(rAA>z~>quPFz;8n24p%PYXIw7r- zK53Omx1|fws+!3j{owj<_cl%lWR#}dTuW@?$BcK9_uW|g z`(cwMvqG;dEn3Fqq3sutU=b1NK9T-m8w3bI00Izz00bZa0SG_<0uX=z1onu) zaHKDmqJ;i2_seEyK45T|deQcat%IHDXq;VDM0!A^f7u2B0uX=z1Rwwb2tWV=5P$## zAOL~ABQP8`0}vTvu>*t#Cji{_e=7Y&*bM;!5P$##AOHafKmY;|fB*y_0D(Orps+>$ z*p&AfpU|gwhpu*(`pZhTpd2maa^u;YlFc5=DEWe-xa=>n1dl-Ah;0t6rc0SG_<0uX=z1Rwwb2tWV= zZ!A!V3M589^jGBL-j9Fi4fAWGQ3+p}Kc=V?-pahn{v}|H4!85Vzc27=7PY|E?{oQa zRn2Ere=%RB?JVve|HrpyQ40Cvg?w&2mzz-Xs&^Le`~Odf^yG~%4jCW-0SG_<0uX=z Z1Rwwb2tWV=5O_xg_R(mHc78fI`Vy`&LoomV literal 28672 zcmeI*PiP!f90%|>yV*b4X7&ZmbW=mjEJ4W9q|Cfc#imeQ<0`=NnLDtF6xQO5_pqr^32k_)iC83>RTInLoSj*5{r??WSJ16^GAw5f~}A5xQ}2Hf}MSy zb#mnGKSla5Ne)yAJ&}DV`zQn`Fyc;kLA=gW1= zZa9_odabjUjAZJ><%x_O+AKq!Thz)=m6zpH%X15*DG`}qJJ>xtug9&+^?RA2>4X(Rg)GHVxk5qMdCn`u(5>||l?~Hz^qT2JkK&QC zqHSH(?JH?;l3rHzrrij9z_?>M-ltfE!3*s_`!IZTHg0G4%I~2O zz1(iav93FfieuGmKC3orhe4#fBt^#s`UM@QCHf=1KwqNTz4&bibAkW_AOHafKmY;| zfB*y_009W>D=_^2`M*CIOOR0^uW8%ns$=R8GKC$TQq?J@h#y@4=eHPdO_LX!R?X54 zXYl-$w=VL~c$ygXs=2o1n5*^1@VVWe_L^Rr*Vg!XXEru7K^m3u3P#;QMudFRUbE}l zc4!Rc(7D~cuMZ{2n2=wXU$q)lew!lHIrrY17j|a7&W5qIT{p~76Hg8&2|009U<00Izz00bZa z0SG|gZV*s~BuNO;g8!C)jQcnLf_D)xMN_6{7@JYmBh&2I^sz^#l%q3>;;;X&5qj-z mm>_C|00bZa0SG_<0uX=z1Rwwb2teRJ35Y^G-{1Lv82