This commit is contained in:
10
db.py
10
db.py
@@ -1,10 +1,12 @@
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
import os
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import declarative_base
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite+aiosqlite:///bot.db")
|
||||
|
||||
if DATABASE_URL.startswith("sqlite+aiosqlite:///"):
|
||||
@@ -73,3 +75,9 @@ async def init_db():
|
||||
print(f"Созданы таблицы: {', '.join(tables)}")
|
||||
else:
|
||||
print("База данных уже существует и содержит таблицы, создание пропущено.")
|
||||
|
||||
async def log_action(admin_id, action, details=""):
|
||||
async with AsyncSessionLocal() as session:
|
||||
log = ActionLog(admin_id=admin_id, action=action, details=details)
|
||||
session.add(log)
|
||||
await session.commit()
|
||||
@@ -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=[]
|
||||
)
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
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}\" удалена.')
|
||||
|
||||
@@ -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
26
handlers/invite_admin.py
Normal 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)
|
||||
@@ -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
23
handlers/share_bot.py
Normal 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)
|
||||
4
main.py
4
main.py
@@ -1,3 +1,5 @@
|
||||
from handlers.share_bot import share_bot_handler
|
||||
from handlers.invite_admin import invite_admin_handler
|
||||
import sys
|
||||
import asyncio
|
||||
if sys.platform.startswith('win'):
|
||||
@@ -93,6 +95,8 @@ def main():
|
||||
application.add_handler(CommandHandler('edit_button', edit_button))
|
||||
application.add_handler(CommandHandler('del_button', del_button))
|
||||
application.add_handler(admin_panel_conv)
|
||||
application.add_handler(share_bot_handler)
|
||||
application.add_handler(invite_admin_handler)
|
||||
import sys
|
||||
import asyncio
|
||||
if sys.platform.startswith('win'):
|
||||
|
||||
16
models.py
16
models.py
@@ -1,11 +1,25 @@
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, Text
|
||||
from sqlalchemy.orm import relationship
|
||||
from db import Base
|
||||
|
||||
class ActionLog(Base):
|
||||
__tablename__ = 'action_logs'
|
||||
id = Column(Integer, primary_key=True)
|
||||
admin_id = Column(Integer)
|
||||
action = Column(String)
|
||||
details = Column(String)
|
||||
timestamp = Column(String, default=lambda: datetime.utcnow().isoformat())
|
||||
|
||||
class Admin(Base):
|
||||
__tablename__ = 'admins'
|
||||
id = Column(Integer, primary_key=True)
|
||||
tg_id = Column(Integer, unique=True, nullable=False)
|
||||
tg_id = Column(Integer, nullable=False)
|
||||
channel_id = Column(Integer, ForeignKey('channels.id'), nullable=True)
|
||||
inviter_id = Column(Integer, nullable=True)
|
||||
invite_token = Column(String, nullable=True, unique=True)
|
||||
|
||||
class Channel(Base):
|
||||
__tablename__ = 'channels'
|
||||
|
||||
Reference in New Issue
Block a user