From 506acfcde5029abd512ad51319a46cd35b1b6f04 Mon Sep 17 00:00:00 2001 From: "Choi A.K." Date: Sat, 6 Sep 2025 14:56:46 +0900 Subject: [PATCH] sharing channels and more --- handlers/share_channel.py | 55 +++++++++++++++++++++++++++++---------- main.py | 12 ++++++++- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/handlers/share_channel.py b/handlers/share_channel.py index 307b46b..82742cc 100644 --- a/handlers/share_channel.py +++ b/handlers/share_channel.py @@ -9,6 +9,17 @@ from sqlalchemy import select from db import AsyncSessionLocal from models import Channel, ChannelAccess, SCOPE_POST from .permissions import get_or_create_admin, make_token, token_hash +from telegram.error import BadRequest +import os +from telegram import InlineKeyboardMarkup, InlineKeyboardButton + +async def _get_bot_username(context: ContextTypes.DEFAULT_TYPE) -> str: + # кэшируем, чтобы не дёргать get_me() каждый раз + uname = context.application.bot.username + if uname: + return uname + me = await context.bot.get_me() + return me.username SELECT_CHANNEL, CONFIRM_INVITE = range(2) @@ -51,24 +62,32 @@ async def select_channel(update: Update, context: ContextTypes.DEFAULT_TYPE): async def confirm_invite(update: Update, context: ContextTypes.DEFAULT_TYPE): q = update.callback_query - if not q: return ConversationHandler.END + if not q: + return ConversationHandler.END + # Лёгкий ACK, чтобы исчез «часик» на кнопке await q.answer() data = q.data + + # --- настройки TTL (ничего не меняем в разметке, только сохраняем выбор) --- if data.startswith("ttl_"): - context.user_data["ttl_days"] = {"ttl_7":7, "ttl_30":30, "ttl_inf":None}[data] - await q.edit_message_reply_markup(reply_markup=q.message.reply_markup) + context.user_data["ttl_days"] = {"ttl_7": 7, "ttl_30": 30, "ttl_inf": None}[data] + # Нечего редактировать — markup не менялся. Просто остаёмся в состоянии. return CONFIRM_INVITE + + # --- права: сейчас фиксировано SCOPE_POST, разметку не меняем --- if data == "scope_post": - # пока фиксируем только POST - await q.edit_message_reply_markup(reply_markup=q.message.reply_markup) + # если позже сделаешь тумблеры прав — тут можно перестраивать клавиатуру + context.user_data["scopes"] = SCOPE_POST return CONFIRM_INVITE + + # --- генерация ссылки приглашения --- if data != "go": return CONFIRM_INVITE channel_id = context.user_data.get("share_channel_id") ttl_days = context.user_data.get("ttl_days") - scopes = context.user_data.get("scopes") + scopes = context.user_data.get("scopes", SCOPE_POST) async with AsyncSessionLocal() as session: me = await get_or_create_admin(session, update.effective_user.id) @@ -78,6 +97,7 @@ async def confirm_invite(update: Update, context: ContextTypes.DEFAULT_TYPE): expires_at = None if ttl_days: + from datetime import datetime, timedelta expires_at = datetime.utcnow() + timedelta(days=ttl_days) acc = ChannelAccess( @@ -94,14 +114,21 @@ async def confirm_invite(update: Update, context: ContextTypes.DEFAULT_TYPE): invite_id = acc.id payload = f"sch_{invite_id}_{token}" - await q.edit_message_text( - "Ссылка для выдачи доступа к каналу:\n" - f"`https://t.me/?start={payload}`\n\n" - "Передайте её коллеге. Срок действия — " - + ("не ограничен." if ttl_days is None else f"{ttl_days} дней."), - parse_mode="Markdown", - ) - return ConversationHandler.END + bot_username = await _get_bot_username(context) + deep_link = f"https://t.me/{bot_username}?start={payload}" + + # Кнопка для удобства + kb = InlineKeyboardMarkup([[InlineKeyboardButton("Открыть ссылку", url=deep_link)]]) + + await q.edit_message_text( + "Ссылка для предоставления доступа к каналу:\n" + f"`{deep_link}`\n\n" + "Передайте её коллеге. Срок действия — " + + ("не ограничен." if ttl_days is None else f"{ttl_days} дней."), + parse_mode="Markdown", + reply_markup=kb, + ) + return ConversationHandler.END share_channel_conv = ConversationHandler( entry_points=[CommandHandler("share_channel", share_channel_start)], diff --git a/main.py b/main.py index b67104e..7c9e8ee 100644 --- a/main.py +++ b/main.py @@ -99,6 +99,16 @@ from handlers.edit_button import edit_button from handlers.del_button import del_button from handlers.share_channel import share_channel_conv +import logging +from telegram.error import BadRequest +logger = logging.getLogger(__name__) + +async def on_error(update: Update, context: ContextTypes.DEFAULT_TYPE): + err = context.error + # подавляем шумные 400-е, когда контент/markup не меняется + if isinstance(err, BadRequest) and "Message is not modified" in str(err): + return + logger.exception("Unhandled exception", exc_info=err) @@ -130,7 +140,7 @@ def main(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) application.run_polling() - + if __name__ == "__main__": main()