Files
post_bot/handlers/share_channel.py
Choi A.K. 73ce8b745d
Some checks failed
continuous-integration/drone/push Build is failing
minor fix
2025-09-06 15:13:09 +09:00

141 lines
5.8 KiB
Python

# handlers/share_channel.py
from datetime import datetime, timedelta
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
ContextTypes, ConversationHandler, CommandHandler, CallbackQueryHandler
)
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)
async def share_channel_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
async with AsyncSessionLocal() as session:
me = await get_or_create_admin(session, update.effective_user.id)
q = await session.execute(select(Channel).where(Channel.admin_id == me.id))
channels = q.scalars().all()
if not channels:
if update.message:
await update.message.reply_text("Нет каналов, которыми вы владеете.")
return ConversationHandler.END
kb = [[InlineKeyboardButton(f"{c.name} ({c.link})", callback_data=f"sch_{c.id}")] for c in channels]
rm = InlineKeyboardMarkup(kb)
if update.message:
await update.message.reply_text("Выберите канал для выдачи доступа:", reply_markup=rm)
return SELECT_CHANNEL
async def select_channel(update: Update, context: ContextTypes.DEFAULT_TYPE):
q = update.callback_query
if not q: return ConversationHandler.END
await q.answer()
if not q.data.startswith("sch_"): return ConversationHandler.END
channel_id = int(q.data.split("_")[1])
context.user_data["share_channel_id"] = channel_id
kb = [
[InlineKeyboardButton("Срок: 7 дней", callback_data="ttl_7"),
InlineKeyboardButton("30 дней", callback_data="ttl_30"),
InlineKeyboardButton("", callback_data="ttl_inf")],
[InlineKeyboardButton("Выдать право постинга", callback_data="scope_post")],
[InlineKeyboardButton("Сгенерировать ссылку", callback_data="go")],
]
await q.edit_message_text("Настройте приглашение:", reply_markup=InlineKeyboardMarkup(kb))
context.user_data["ttl_days"] = 7
context.user_data["scopes"] = SCOPE_POST
return CONFIRM_INVITE
async def confirm_invite(update: Update, context: ContextTypes.DEFAULT_TYPE):
q = update.callback_query
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]
# Нечего редактировать — markup не менялся. Просто остаёмся в состоянии.
return CONFIRM_INVITE
# --- права: сейчас фиксировано SCOPE_POST, разметку не меняем ---
if data == "scope_post":
# если позже сделаешь тумблеры прав — тут можно перестраивать клавиатуру
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", SCOPE_POST)
async with AsyncSessionLocal() as session:
me = await get_or_create_admin(session, update.effective_user.id)
token = make_token(9)
thash = token_hash(token)
expires_at = None
if ttl_days:
from datetime import datetime, timedelta
expires_at = datetime.utcnow() + timedelta(days=ttl_days)
acc = ChannelAccess(
channel_id=channel_id,
invited_by_admin_id=me.id,
token_hash=thash,
scopes=scopes,
status="pending",
created_at=datetime.utcnow(),
expires_at=expires_at,
)
session.add(acc)
await session.commit()
invite_id = acc.id
payload = f"sch_{invite_id}_{token}"
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)],
states={
SELECT_CHANNEL: [CallbackQueryHandler(select_channel, pattern="^sch_")],
CONFIRM_INVITE: [CallbackQueryHandler(confirm_invite)],
},
fallbacks=[],
)