-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadminhandlers.py
More file actions
280 lines (230 loc) · 10.7 KB
/
adminhandlers.py
File metadata and controls
280 lines (230 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import logging
from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import ContextTypes, ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler
from config import ADMIN_IDS, CHANNEL_ID
from database import Movie
from utils import parse_movie_title, is_admin
from peewee import IntegrityError
# Conversation states
TITLE, DESCRIPTION, MESSAGE_ID, CONFIRM = range(4)
async def start_add_movie(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Start the add movie conversation."""
user_id = update.effective_user.id
if not is_admin(user_id, ADMIN_IDS):
await update.message.reply_text("Sorry, only admins can use this command.")
return ConversationHandler.END
# Check if we have movie details already
args = context.args
if args:
movie_text = ' '.join(args)
title, year = parse_movie_title(movie_text)
context.user_data['movie_title'] = title
context.user_data['movie_year'] = year
await update.message.reply_text(
f"Adding movie: *{title}*" + (f" ({year})" if year else "") +
"\n\nPlease provide a brief description of the movie, or send /skip to skip this step.",
parse_mode='Markdown'
)
return DESCRIPTION
await update.message.reply_text(
"Please send the movie title with optional year in parentheses.\n"
"Example: `The Matrix (1999)`",
parse_mode='Markdown'
)
return TITLE
async def title_received(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Process the movie title."""
title, year = parse_movie_title(update.message.text)
context.user_data['movie_title'] = title
context.user_data['movie_year'] = year
await update.message.reply_text(
f"Adding movie: *{title}*" + (f" ({year})" if year else "") +
"\n\nPlease provide a brief description of the movie, or send /skip to skip this step.",
parse_mode='Markdown'
)
return DESCRIPTION
async def description_received(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Process the movie description."""
if update.message.text != '/skip':
context.user_data['movie_description'] = update.message.text
else:
context.user_data['movie_description'] = None
await update.message.reply_text(
"Now, please send the message ID from the channel that contains this movie.\n"
"You can get this by forwarding the message to @getidsbot or using the 'Copy Message ID' option."
)
return MESSAGE_ID
async def message_id_received(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Process the message ID."""
try:
# Parse and validate the message ID
text = update.message.text.strip()
try:
message_id = int(text)
except ValueError:
await update.message.reply_text(
"Please provide a valid numeric message ID, or use /cancel to abort."
)
return MESSAGE_ID
# Check if the message ID is within reasonable range
if message_id <= 0:
await update.message.reply_text(
"Message ID must be a positive number. Please try again, or use /cancel to abort."
)
return MESSAGE_ID
# Store the message ID
context.user_data['message_id'] = message_id
logging.info(f"Validating message ID: {message_id}")
# Try to verify the message exists in the channel
try:
message = await context.bot.forward_message(
chat_id=update.effective_chat.id,
from_chat_id=CHANNEL_ID,
message_id=message_id
)
# Message found, delete the forwarded copy to keep chat clean
await message.delete()
logging.info(f"Successfully verified message ID: {message_id}")
# Show confirmation
title = context.user_data['movie_title']
year = context.user_data.get('movie_year')
year_str = f" ({year})" if year else ""
keyboard = [
[
InlineKeyboardButton("✅ Confirm", callback_data="confirm_add"),
InlineKeyboardButton("❌ Cancel", callback_data="cancel_add")
]
]
await update.message.reply_text(
f"Ready to add *{title}*{year_str} to the database.\n\n"
f"Message ID: `{message_id}`\n"
f"Description: {context.user_data.get('movie_description', 'None')}\n\n"
"Is this correct?",
reply_markup=InlineKeyboardMarkup(keyboard),
parse_mode='Markdown'
)
return CONFIRM
except Exception as e:
error_message = str(e)
logging.error(f"Error verifying message ID {message_id}: {error_message}")
await update.message.reply_text(
f"Error: Could not find message with ID {message_id} in the channel.\n"
"Please check the ID and try again, or use /cancel to abort."
)
return MESSAGE_ID
except Exception as e:
logging.error(f"Unexpected error processing message ID: {str(e)}")
await update.message.reply_text(
"An unexpected error occurred. Please try again or use /cancel to abort."
)
return MESSAGE_ID
async def confirm_add_movie(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Confirm and save the movie to the database."""
query = update.callback_query
await query.answer()
if query.data == "cancel_add":
await query.edit_message_text("Movie addition cancelled.")
return ConversationHandler.END
# Debug information
title = context.user_data['movie_title']
year = context.user_data.get('movie_year')
message_id = context.user_data['message_id']
user_id = update.effective_user.id
# Log details for debugging
logging.info(f"Adding movie: '{title}' ({year}) with message_id={message_id}, added_by={user_id}")
# Add movie to database
try:
movie = Movie.create(
title=title,
year=year,
description=context.user_data.get('movie_description'),
message_id=message_id,
added_by=user_id
)
await query.edit_message_text(
f"✅ Movie *{movie.title}*" + (f" ({movie.year})" if movie.year else "") +
" has been successfully added to the database!",
parse_mode='Markdown'
)
except IntegrityError as e:
logging.error(f"IntegrityError adding movie: {str(e)}")
await query.edit_message_text(
"Error: This movie or message ID already exists in the database."
)
except Exception as e:
# Comprehensive error handling
error_message = str(e)
logging.error(f"Error adding movie: {error_message}")
# Provide a user-friendly error message
if "out of range" in error_message.lower():
await query.edit_message_text(
"Error: One of the values (likely the message ID) is too large for the database. "
"Please contact the administrator to fix this issue."
)
else:
await query.edit_message_text(
f"Error adding movie: {error_message}\n"
"Please try again or contact the administrator."
)
return ConversationHandler.END
async def cancel_add_movie(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Cancel the add movie conversation."""
await update.message.reply_text("Operation cancelled.")
return ConversationHandler.END
async def list_movies(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""List all movies in the database."""
user_id = update.effective_user.id
if not is_admin(user_id, ADMIN_IDS):
await update.message.reply_text("Sorry, only admins can use this command.")
return
movies = Movie.select().order_by(Movie.title)
if not movies:
await update.message.reply_text("No movies in the database yet.")
return
movie_list = []
for movie in movies:
year_str = f" ({movie.year})" if movie.year else ""
movie_list.append(f"• {movie.title}{year_str} - ID: {movie.id}")
# Split into chunks if the list is too long
chunks = [movie_list[i:i+50] for i in range(0, len(movie_list), 50)]
for i, chunk in enumerate(chunks):
header = "🎬 *Movie Database*" if i == 0 else f"*Movie Database (continued {i+1}/{len(chunks)})*"
message = header + "\n\n" + "\n".join(chunk)
await update.message.reply_text(message, parse_mode='Markdown')
async def delete_movie(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Delete a movie by ID."""
user_id = update.effective_user.id
if not is_admin(user_id, ADMIN_IDS):
await update.message.reply_text("Sorry, only admins can use this command.")
return
args = context.args
if not args or not args[0].isdigit():
await update.message.reply_text("Please provide a valid movie ID: `/deletemovie <id>`", parse_mode='Markdown')
return
movie_id = int(args[0])
try:
movie = Movie.get_by_id(movie_id)
title = movie.title
year = movie.year
movie.delete_instance()
year_str = f" ({year})" if year else ""
await update.message.reply_text(f"Movie *{title}*{year_str} has been deleted.", parse_mode='Markdown')
except Movie.DoesNotExist:
await update.message.reply_text(f"No movie found with ID {movie_id}.")
# Create the conversation handler for adding movies
add_movie_handler = ConversationHandler(
entry_points=[CommandHandler("addmovie", start_add_movie)],
states={
TITLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, title_received)],
DESCRIPTION: [
MessageHandler(filters.TEXT & ~filters.COMMAND, description_received),
CommandHandler("skip", description_received)
],
MESSAGE_ID: [MessageHandler(filters.TEXT & ~filters.COMMAND, message_id_received)],
CONFIRM: [CallbackQueryHandler(confirm_add_movie, pattern=r"^(confirm_add|cancel_add)$")]
},
fallbacks=[CommandHandler("cancel", cancel_add_movie)]
)
# Other admin handlers
list_movies_handler = CommandHandler("listmovies", list_movies)
delete_movie_handler = CommandHandler("deletemovie", delete_movie)