From 8af1661b44813b88c0878d5ea4703c12c9b89a60 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:52:09 -0400 Subject: [PATCH] =?UTF-8?q?[+]=20=E6=B5=87=E6=B0=B4=E3=80=81=E6=88=90?= =?UTF-8?q?=E4=B8=BA=E6=A0=91=E5=8F=B6=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 41 +++++++++++++++++++++++++++++++++++++---- db.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/bot.py b/bot.py index 4959c36..4389f4e 100644 --- a/bot.py +++ b/bot.py @@ -9,8 +9,8 @@ from hypy_utils import ensure_dir from hypy_utils.logging_utils import setup_logger from starlette.responses import HTMLResponse from starlette.staticfiles import StaticFiles -from telegram import Update -from telegram.ext import Application, CommandHandler, ContextTypes +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes import db import utils @@ -90,13 +90,18 @@ async def plant(update: Update, context: ContextTypes.DEFAULT_TYPE): height = db.register(channel, info.title, parent) await update.message.reply_text(f"""频道 {channel} 上树成功!把下面这条转发到频道里吧~""".strip()) url_enc = urllib.parse.quote_plus(f"https://tree.aza.moe/c/{channel}") + leaf_text = urllib.parse.quote(f"/leaf {channel} ") + leaf_btn = InlineKeyboardMarkup([ + [InlineKeyboardButton("🌿 成为树叶", url=f"https://t.me/{BOT_NAME}?text={leaf_text}")], + [InlineKeyboardButton("💧 浇水", callback_data=f"water:{channel}")], + ]) return await update.message.reply_html(f""" 今天是植树节,想试试和大家一起种一颗 tgcn 频道树 🌳 qwq 这里是 {info.title},是 @{parent} 的树枝 🌿 在频道树的第 {height + 1} 层哦~ -(如果你也有公开频道,想成为这个频道的树叶的话,就去给 @tgtreebot 发送 /leaf {channel} {{你的频道名}} 吧! > <) \u200e -""".strip()) +(如果你也有公开频道,想成为这个频道的树叶的话,就点击下面的「成为树叶」吧! > <) \u200e +""".strip(), reply_markup=leaf_btn) if sha not in validating: logger.info(f"> Channel not validated, asking for validation.") @@ -113,9 +118,37 @@ async def plant(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text("(看了一下好像频道信息还没有更新的样子... 确定加上了吗?再试试吧)") +async def water_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + """Handle the 浇水 (water/upvote) button press.""" + query = update.callback_query + data = query.data + + if not data.startswith("water:"): + return + + channel = data[len("water:"):] + user_id = query.from_user.id + + logger.info(f"💧 Water from {user_id} {query.from_user.username or ''} for {channel}") + + # Check if the channel exists + info = db.channel_info(channel) + if not info: + return await query.answer("这个频道不在树上哦~", show_alert=False) + + # Try to add the vote + if db.add_vote(user_id, channel): + votes = db.get_votes(channel) + await query.answer(f"💧 浇水成功!这个树枝已经被浇了 {votes} 次水~", show_alert=False) + else: + votes = db.get_votes(channel) + await query.answer(f"你已经浇过水了哦~ 这个树枝已经被浇了 {votes} 次水~", show_alert=False) + + # Add handlers bot.add_handler(CommandHandler("start", start)) bot.add_handler(CommandHandler("leaf", plant)) +bot.add_handler(CallbackQueryHandler(water_callback, pattern=r"^water:")) @app.on_event("startup") diff --git a/db.py b/db.py index 3cd48d4..a5e1f2b 100644 --- a/db.py +++ b/db.py @@ -1,4 +1,4 @@ -from peewee import Model, CharField, ForeignKeyField, IntegerField, PostgresqlDatabase +from peewee import Model, CharField, ForeignKeyField, IntegerField, BigIntegerField, CompositeKey, PostgresqlDatabase from utils import CONFIG @@ -19,8 +19,16 @@ class Channel(BaseModel): height = IntegerField(default=0) # Tree height (depth) +class Vote(BaseModel): + user_id = BigIntegerField() # Telegram user ID + channel = ForeignKeyField(Channel, backref='votes', on_delete='CASCADE', field='username') + + class Meta: + primary_key = CompositeKey('user_id', 'channel') + + with db: - db.create_tables([Channel]) + db.create_tables([Channel, Vote]) def channel_info(username: str) -> Channel | None: @@ -47,6 +55,27 @@ def register(username: str, name: str, parent_username: str = None): return height +def add_vote(user_id: int, channel_username: str) -> bool: + """Add a vote for a channel. Returns True if the vote was added, False if already voted.""" + try: + Vote.create(user_id=user_id, channel=channel_username) + return True + except Exception: + return False + + +def get_votes(channel_username: str) -> int: + """Get the total number of votes for a channel.""" + return Vote.select().where(Vote.channel == channel_username).count() + + +def has_voted(user_id: int, channel_username: str) -> bool: + """Check if a user has already voted for a channel.""" + return Vote.select().where( + (Vote.user_id == user_id) & (Vote.channel == channel_username) + ).exists() + + if __name__ == '__main__': with db: # db.drop_tables([Channel])