security, audit, fom features
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-09-06 05:03:45 +09:00
parent df9d8b295d
commit 9793648ee3
11 changed files with 144 additions and 22 deletions

View File

@@ -1,3 +1,6 @@
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes, ConversationHandler, CommandHandler, CallbackQueryHandler, MessageHandler, filters
from db import AsyncSessionLocal
@@ -90,8 +93,27 @@ async def input_url(update: Update, context: ContextTypes.DEFAULT_TYPE):
except Exception as e:
if update.message:
await update.message.reply_text(f'Ошибка при добавлении кнопки: {e}')
try:
type_, obj_id = target.split('_', 1)
obj_id = int(obj_id)
if type_ == 'channel':
button = Button(name=name, url=url, channel_id=obj_id)
elif type_ == 'group':
button = Button(name=name, url=url, group_id=obj_id)
else:
await update.message.reply_text('Ошибка: неверный тип объекта.')
session.close()
return ConversationHandler.END
session.add(button)
session.commit()
from db import log_action
user_id = update.effective_user.id if update.effective_user else None
log_action(user_id, "add_button", f"type={type_}, obj_id={obj_id}, name={name}, url={url}")
await update.message.reply_text('Кнопка добавлена.')
except Exception as e:
await update.message.reply_text(f'Ошибка при добавлении кнопки: {e}')
finally:
await session.close()
session.close()
return ConversationHandler.END
add_button_conv = ConversationHandler(
@@ -102,4 +124,4 @@ add_button_conv = ConversationHandler(
INPUT_URL: [MessageHandler(filters.TEXT & ~filters.COMMAND, input_url)],
},
fallbacks=[]
)
)

View File

@@ -46,6 +46,9 @@ async def save_channel(update: Update, context: ContextTypes.DEFAULT_TYPE):
channel = Channel(name=name, link=link)
session.add(channel)
await session.commit()
from db import log_action
user_id = update.effective_user.id if update.effective_user else None
await log_action(user_id, "add_channel", f"name={name}, link={link}")
if update.message:
await update.message.reply_text(f'Канал "{name}" добавлен.')
return ConversationHandler.END

View File

@@ -46,6 +46,9 @@ async def save_group(update: Update, context: ContextTypes.DEFAULT_TYPE):
group = Group(name=name, link=link)
session.add(group)
await session.commit()
from db import log_action
user_id = update.effective_user.id if update.effective_user else None
log_action(user_id, "add_group", f"name={name}, link={link}")
if update.message:
await update.message.reply_text(f'Группа "{name}" добавлена.')
return ConversationHandler.END

View File

@@ -1,6 +1,6 @@
from telegram import Update
from telegram.ext import CommandHandler, ContextTypes
from db import AsyncSessionLocal
from db import AsyncSessionLocal, log_action
from models import Button
async def del_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -10,9 +10,8 @@ async def del_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text('Используйте: /del_button <название>')
return
name = args[0]
session = AsyncSessionLocal()
try:
from sqlalchemy import select
from sqlalchemy import select
async with AsyncSessionLocal() as session:
result = await session.execute(select(Button).where(Button.name == name))
button = result.scalar_one_or_none()
if not button:
@@ -21,7 +20,7 @@ async def del_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
return
await session.delete(button)
await session.commit()
user_id = update.effective_user.id if update.effective_user else None
await log_action(user_id, "del_button", f"name={name}")
if update.message:
await update.message.reply_text(f'Кнопка "{name}" удалена.')
finally:
await session.close()
await update.message.reply_text(f'Кнопка \"{name}\" удалена.')

View File

@@ -20,6 +20,9 @@ async def edit_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
button.name = new_name
button.url = new_url
await session.commit()
from db import log_action
user_id = update.effective_user.id if update.effective_user else None
log_action(user_id, "edit_button", f"old_name={name}, new_name={new_name}, new_url={new_url}")
if update.message:
await update.message.reply_text(f'Кнопка "{name}" изменена.')
finally:

26
handlers/invite_admin.py Normal file
View File

@@ -0,0 +1,26 @@
from telegram import Update
from telegram.ext import CommandHandler, ContextTypes
from db import AsyncSessionLocal, log_action
from models import Admin
async def invite_admin(update: Update, context: ContextTypes.DEFAULT_TYPE):
args = context.args
if len(args) < 3:
await update.message.reply_text("Неверная ссылка.")
return
channel_id, inviter_id, token = args
user_id = update.effective_user.id
session = AsyncSessionLocal()
admin = session.query(Admin).filter_by(invite_token=token, channel_id=channel_id).first()
if not admin:
await update.message.reply_text("Ссылка недействительна.")
session.close()
return
new_admin = Admin(tg_id=user_id, channel_id=channel_id, inviter_id=inviter_id)
session.add(new_admin)
session.commit()
session.close()
await update.message.reply_text("Вы добавлены как администратор канала!")
log_action(user_id, "invite_admin", f"channel_id={channel_id}, inviter_id={inviter_id}")
invite_admin_handler = CommandHandler("invite_admin", invite_admin)

View File

@@ -2,7 +2,7 @@ from telegram import Update, InputMediaPhoto, InlineKeyboardMarkup, InlineKeyboa
from telegram.ext import ContextTypes, ConversationHandler, MessageHandler, CommandHandler, filters, CallbackQueryHandler, ContextTypes
from db import AsyncSessionLocal
from models import Channel, Group, Button
from models import Channel, Group, Button, Admin
SELECT_MEDIA, SELECT_TEXT, SELECT_TARGET = range(3)
@@ -29,10 +29,20 @@ async def select_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.user_data['text'] = getattr(update.message, 'text', None) or getattr(update.message, 'caption', None)
from sqlalchemy import select
session = AsyncSessionLocal()
user_id = update.effective_user.id if update.effective_user else None
try:
channels_result = await session.execute(select(Channel))
# Ограничиваем каналы и группы только теми, где пользователь — админ
channels_result = await session.execute(
select(Channel).join(Button, isouter=True).join(Group, isouter=True)
.join(Admin, Channel.id == Admin.channel_id)
.where(Admin.tg_id == user_id)
)
channels = channels_result.scalars().all()
groups_result = await session.execute(select(Group))
groups_result = await session.execute(
select(Group).join(Button, isouter=True)
.join(Admin, Group.id == Admin.channel_id)
.where(Admin.tg_id == user_id)
)
groups = groups_result.scalars().all()
keyboard = []
for c in channels:
@@ -41,6 +51,9 @@ async def select_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
keyboard.append([InlineKeyboardButton(f'Группа: {getattr(g, "name", str(g.name))}', callback_data=f'group_{getattr(g, "id", str(g.id))}')])
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text('Выберите, куда отправить пост:', reply_markup=reply_markup)
# Сохраняем id исходного сообщения для пересылки
context.user_data['forward_message_id'] = update.message.message_id
context.user_data['forward_chat_id'] = update.message.chat_id
return SELECT_TARGET
finally:
await session.close()
@@ -80,14 +93,18 @@ async def select_target(update: Update, context: ContextTypes.DEFAULT_TYPE):
await query.edit_message_text('Ошибка: ссылка должна быть username (@channel) или числовой ID (-100...)')
return ConversationHandler.END
try:
photo = context.user_data.get('photo') if context.user_data else None
if photo:
await context.bot.send_photo(chat_id=chat_id, photo=photo, caption=context.user_data.get('text') if context.user_data else None, reply_markup=markup)
await query.edit_message_text('Пост отправлен!')
else:
await query.edit_message_text('Ошибка: не выбрано фото для поста.')
# Пересылка исходного сообщения
await context.bot.forward_message(
chat_id=chat_id,
from_chat_id=context.user_data.get('forward_chat_id'),
message_id=context.user_data.get('forward_message_id')
)
from db import log_action
user_id = update.effective_user.id if update.effective_user else None
log_action(user_id, "forward_post", f"chat_id={chat_id}, from_chat_id={context.user_data.get('forward_chat_id')}, message_id={context.user_data.get('forward_message_id')}")
await query.edit_message_text('Пост переслан!')
except Exception as e:
await query.edit_message_text(f'Ошибка отправки поста: {e}')
await query.edit_message_text(f'Ошибка пересылки поста: {e}')
finally:
await session.close()
return ConversationHandler.END

23
handlers/share_bot.py Normal file
View File

@@ -0,0 +1,23 @@
import secrets
from telegram import Update
from telegram.ext import CommandHandler, ContextTypes
from db import AsyncSessionLocal, log_action
from models import Admin
async def share_bot(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
channel_id = context.user_data.get("channel_id")
if not channel_id:
await update.message.reply_text("Сначала выберите канал через /channel_buttons.")
return
token = secrets.token_urlsafe(16)
session = AsyncSessionLocal()
admin = Admin(tg_id=user_id, channel_id=channel_id, inviter_id=user_id, invite_token=token)
session.add(admin)
session.commit()
session.close()
link = f"/invite_admin {channel_id} {user_id} {token}"
await update.message.reply_text(f"Инвайт-ссылка для нового администратора:\n{link}")
log_action(user_id, "share_bot", f"channel_id={channel_id}, token={token}")
share_bot_handler = CommandHandler("share_bot", share_bot)