azils3 commited on
Commit
b202865
·
verified ·
1 Parent(s): f218229

Update utils.py

Browse files
Files changed (1) hide show
  1. utils.py +182 -136
utils.py CHANGED
@@ -1,31 +1,22 @@
1
- import logging
2
- import os
3
- import re
4
- import asyncio
5
- import requests
6
- import aiohttp
7
- from pyrogram.errors import InputUserDeactivated, UserNotParticipant, FloodWait, UserIsBlocked, PeerIdInvalid
8
  from pyrogram.types import Message, InlineKeyboardButton
9
  from pyrogram import filters, enums
10
  from info import AUTH_CHANNEL, LONG_IMDB_DESCRIPTION, MAX_LIST_ELM, SHORT_URL, SHORT_API
11
  from imdb import Cinemagoer
12
  from typing import Union, List
13
  from datetime import datetime, timedelta
14
- from database.users_chats_db import db
15
- from bs4 import BeautifulSoup
16
 
17
- logging.basicConfig(level=logging.INFO)
18
  logger = logging.getLogger(__name__)
19
- logger.info("Imported necessary modules.")
20
-
21
- BTN_URL_REGEX = re.compile(r"($$([^\[]+?)$$$$(buttonurl|buttonalert):(?:/{0,2})(.+?)(:same)?$$)")
22
- logger.info("Defined regex for button URLs.")
23
 
 
 
24
  SMART_OPEN = '“'
25
  SMART_CLOSE = '”'
26
  START_CHAR = ('\'', '"', SMART_OPEN)
27
- logger.info("Defined smart quote constants.")
28
 
 
29
  class temp(object):
30
  BANNED_USERS = []
31
  BANNED_CHATS = []
@@ -39,28 +30,35 @@ class temp(object):
39
  PM_BUTTONS = {}
40
  PM_SPELL = {}
41
  GP_SPELL = {}
42
- logger.info("Initialized temporary object for caching data.")
43
 
44
  async def is_subscribed(bot, query):
45
- logger.debug(f"Checking subscription for user {query.from_user.id} in channel {AUTH_CHANNEL}.")
 
 
 
46
  try:
47
  user = await bot.get_chat_member(AUTH_CHANNEL, query.from_user.id)
 
48
  except UserNotParticipant:
49
- logger.debug(f"User {query.from_user.id} is not a participant in {AUTH_CHANNEL}.")
50
  pass
51
  except Exception as e:
52
- logger.error(f"Error in is_subscribed: {e}")
 
53
  else:
54
  if user.status != enums.ChatMemberStatus.BANNED:
55
- logger.debug(f"User {query.from_user.id} is subscribed and not banned in {AUTH_CHANNEL}.")
56
  return True
57
- logger.debug(f"User {query.from_user.id} is either not subscribed or banned in {AUTH_CHANNEL}.")
58
  return False
59
 
60
  async def get_poster(query, bulk=False, id=False, file=None):
61
- logger.debug(f"Fetching poster for query: {query}, bulk: {bulk}, id: {id}, file: {file}")
62
- imdb = Cinemagoer()
63
- if not id:
 
 
 
64
  query = (query.strip()).lower()
65
  title = query
66
  year = re.findall(r'[1-2]\d{3}$', query, re.IGNORECASE)
@@ -70,37 +68,35 @@ async def get_poster(query, bulk=False, id=False, file=None):
70
  elif file is not None:
71
  year = re.findall(r'[1-2]\d{3}', file, re.IGNORECASE)
72
  if year:
73
- year = list_to_str(year[:1])
74
  else:
75
  year = None
76
- logger.debug(f"Searching IMDb for title: {title}, year: {year}")
77
  try:
78
  movieid = imdb.search_movie(title.lower(), results=10)
 
79
  except Exception as e:
80
- logger.error(f"Error searching IMDb: {e}")
81
  return None
82
  if not movieid:
83
- logger.debug("No movies found.")
84
  return None
85
  if year:
86
- logger.debug(f"Filtering movies by year: {year}")
87
  filtered = list(filter(lambda k: str(k.get('year')) == str(year), movieid))
88
  if not filtered:
89
- logger.debug("No movies found for specified year. Using all results.")
90
  filtered = movieid
91
  else:
92
  filtered = movieid
93
  movieid = list(filter(lambda k: k.get('kind') in ['movie', 'tv series'], filtered))
94
  if not movieid:
95
- logger.debug("No movies or TV series found. Using all results.")
96
  movieid = filtered
97
  if bulk:
98
- logger.debug("Returning bulk movie IDs.")
99
- return movieid
100
  movieid = movieid[0].movieID
 
101
  else:
102
  movieid = query
103
- logger.debug(f"Fetching movie data for ID: {movieid}")
104
  movie = imdb.get_movie(movieid)
105
  if movie.get("original air date"):
106
  date = movie["original air date"]
@@ -117,7 +113,8 @@ async def get_poster(query, bulk=False, id=False, file=None):
117
  plot = movie.get('plot outline')
118
  if plot and len(plot) > 800:
119
  plot = plot[0:800] + "..."
120
- logger.debug("Constructing poster dictionary.")
 
121
  return {
122
  'title': movie.get('title'),
123
  'votes': movie.get('votes'),
@@ -135,7 +132,7 @@ async def get_poster(query, bulk=False, id=False, file=None):
135
  "director": list_to_str(movie.get("director")),
136
  "writer": list_to_str(movie.get("writer")),
137
  "producer": list_to_str(movie.get("producer")),
138
- "composer": list_to_str(movie.get("composer")),
139
  "cinematographer": list_to_str(movie.get("cinematographer")),
140
  "music_team": list_to_str(movie.get("music department")),
141
  "distributors": list_to_str(movie.get("distributors")),
@@ -147,74 +144,94 @@ async def get_poster(query, bulk=False, id=False, file=None):
147
  'rating': str(movie.get("rating")),
148
  'url': f'https://www.imdb.com/title/tt{movieid}'
149
  }
150
-
151
  def list_to_str(k):
 
 
 
152
  logger.debug(f"Converting list to string: {k}")
153
- if not k:
 
154
  return "N/A"
155
- elif len(k) == 1:
 
156
  return str(k[0])
157
  elif MAX_LIST_ELM:
158
  k = k[:int(MAX_LIST_ELM)]
159
  result = ' '.join(f'{elem}, ' for elem in k)
160
- logger.debug(f"Trimmed and joined list: {result}")
161
  return result
162
  else:
163
  result = ' '.join(f'{elem}, ' for elem in k)
164
- logger.debug(f"Joined list: {result}")
165
  return result
166
 
167
  __repo__ = "https://github.com/MrMKN/PROFESSOR-BOT"
 
 
168
  __version__ = "PROFESSOR-BOT ᴠ4.5.0"
 
 
169
  __license__ = "GNU GENERAL PUBLIC LICENSE V2"
170
- __copyright__ = "Copyright (C) 2023-present MrMKN <https://github.com/MrMKN>"
171
 
172
- logger.info(f"Repository: {__repo__}")
173
- logger.info(f"Version: {__version__}")
174
- logger.info(f"License: {__license__}")
175
- logger.info(f"Copyright: {__copyright__}")
176
 
177
  async def search_gagala(text):
178
- logger.debug(f"Performing Google search for: {text}")
 
 
 
179
  usr_agent = {
180
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
 
181
  }
182
  text = text.replace(" ", '+')
183
  url = f'https://www.google.com/search?q={text}'
184
- logger.debug(f"Sending GET request to: {url}")
185
  try:
186
  response = requests.get(url, headers=usr_agent)
187
  response.raise_for_status()
188
- except requests.RequestException as e:
189
- logger.error(f"Error in search_gagala: {e}")
 
190
  return []
191
  soup = BeautifulSoup(response.text, 'html.parser')
192
  titles = soup.find_all('h3')
193
- results = [title.getText() for title in titles]
194
- logger.debug(f"Found {len(results)} search results.")
195
- return results
196
 
197
  async def get_settings(group_id):
198
- logger.debug(f"Getting settings for group ID: {group_id}")
 
 
 
199
  settings = temp.SETTINGS.get(group_id)
200
  if not settings:
201
- logger.debug("Settings not found in cache. Retrieving from database.")
202
  settings = await db.get_settings(group_id)
203
  temp.SETTINGS[group_id] = settings
 
204
  else:
205
- logger.debug("Settings found in cache.")
206
  return settings
207
-
208
  async def save_group_settings(group_id, key, value):
209
- logger.debug(f"Saving group settings for group ID: {group_id}, key: {key}, value: {value}")
 
 
 
210
  current = await get_settings(group_id)
211
  current[key] = value
212
  temp.SETTINGS[group_id] = current
213
  await db.update_settings(group_id, current)
214
- logger.debug("Settings updated successfully.")
215
 
216
  def get_size(size):
217
- logger.debug(f"Converting size: {size}")
 
 
 
218
  units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"]
219
  size = float(size)
220
  i = 0
@@ -222,72 +239,79 @@ def get_size(size):
222
  i += 1
223
  size /= 1024.0
224
  result = "%.2f %s" % (size, units[i])
225
- logger.debug(f"Converted size: {result}")
226
  return result
227
 
228
  def get_file_id(msg: Message):
229
- if not msg.media:
230
- logger.debug("Message has no media.")
 
 
 
 
231
  return None
232
  for message_type in ("photo", "animation", "audio", "document", "video", "video_note", "voice", "sticker"):
233
  obj = getattr(msg, message_type)
234
  if obj:
235
- logger.debug(f"Found media type: {message_type}")
236
  setattr(obj, "message_type", message_type)
 
237
  return obj
238
- logger.debug("No supported media type found.")
239
  return None
240
 
241
  def extract_user(message: Message) -> Union[int, str]:
242
- logger.debug("Extracting user information.")
 
 
 
243
  user_id = None
244
  user_first_name = None
245
  if message.reply_to_message:
246
- logger.debug("Extracting user from replied message.")
247
  user_id = message.reply_to_message.from_user.id
248
  user_first_name = message.reply_to_message.from_user.first_name
 
249
  elif len(message.command) > 1:
250
- logger.debug("Extracting user from command arguments.")
251
  if (len(message.entities) > 1 and message.entities[1].type == enums.MessageEntityType.TEXT_MENTION):
252
  required_entity = message.entities[1]
253
  user_id = required_entity.user.id
254
  user_first_name = required_entity.user.first_name
 
255
  else:
256
  user_id = message.command[1]
257
  user_first_name = user_id
258
- try:
259
- user_id = int(user_id)
260
- except ValueError:
261
- logger.warning("User ID is not an integer.")
262
- pass
 
 
263
  else:
264
- logger.debug("Extracting user from message sender.")
265
  user_id = message.from_user.id
266
  user_first_name = message.from_user.first_name
267
- logger.debug(f"Extracted user ID: {user_id}, first name: {user_first_name}")
268
  return (user_id, user_first_name)
269
 
270
  def split_quotes(text: str) -> List:
271
- logger.debug(f"Splitting quotes in text: {text}")
 
 
 
272
  if not any(text.startswith(char) for char in START_CHAR):
273
- logger.debug("No starting quote found. Splitting by first space.")
274
- parts = text.split(None, 1)
275
- logger.debug(f"Split parts: {parts}")
276
- return parts
277
  counter = 1 # ignore first char -> is some kind of quote
278
  while counter < len(text):
279
  if text[counter] == "\\":
280
- logger.debug("Escaped character found.")
281
  counter += 1
282
  elif text[counter] == text[0] or (text[0] == SMART_OPEN and text[counter] == SMART_CLOSE):
283
- logger.debug("Ending quote found.")
284
  break
285
  counter += 1
286
  else:
287
- logger.debug("No ending quote found. Splitting by first space.")
288
- parts = text.split(None, 1)
289
- logger.debug(f"Split parts: {parts}")
290
- return parts
291
 
292
  # 1 to avoid starting quote, and counter is exclusive so avoids ending
293
  key = remove_escapes(text[1:counter].strip())
@@ -295,14 +319,15 @@ def split_quotes(text: str) -> List:
295
  rest = text[counter + 1:].strip()
296
  if not key:
297
  key = text[0] + text[0]
298
- result = list(filter(None, [key, rest]))
299
- logger.debug(f"Split parts: {result}")
300
- return result
301
 
302
  def parser(text, keyword, cb_data):
303
- logger.debug(f"Parsing text: {text}, keyword: {keyword}, cb_data: {cb_data}")
304
- if "buttonalert" in text:
305
- logger.debug("Found 'buttonalert' in text. Replacing newlines and tabs.")
 
 
306
  text = (text.replace("\n", "\\n").replace("\t", "\\t"))
307
  buttons = []
308
  note_data = ""
@@ -320,32 +345,36 @@ def parser(text, keyword, cb_data):
320
  note_data += text[prev:match.start(1)]
321
  prev = match.end(1)
322
  if match.group(3) == "buttonalert":
323
- logger.debug(f"Found buttonalert: {match.group(2)}, {match.group(4)}, same_line: {bool(match.group(5))}")
324
  if bool(match.group(5)) and buttons:
325
  buttons[-1].append(InlineKeyboardButton(match.group(2), callback_data=f"{cb_data}:{i}:{keyword}"))
326
  else:
327
  buttons.append([InlineKeyboardButton(match.group(2), callback_data=f"{cb_data}:{i}:{keyword}")])
328
  i += 1
329
  alerts.append(match.group(4))
 
330
  elif bool(match.group(5)) and buttons:
331
- logger.debug(f"Found URL button: {match.group(2)}, {match.group(4)}, same_line: {bool(match.group(5))}")
332
  buttons[-1].append(InlineKeyboardButton(match.group(2), url=match.group(4).replace(" ", "")))
 
333
  else:
334
- logger.debug(f"Found URL button: {match.group(2)}, {match.group(4)}, new line.")
335
  buttons.append([InlineKeyboardButton(match.group(2), url=match.group(4).replace(" ", ""))])
 
336
  else:
337
  note_data += text[prev:to_check]
338
  prev = match.start(1) - 1
339
- else:
340
  note_data += text[prev:]
341
- try:
342
- logger.debug(f"Parsed note_data: {note_data}, buttons: {buttons}, alerts: {alerts}")
343
  return note_data, buttons, alerts
344
  except Exception as e:
345
- logger.error(f"Error in parser: {e}")
346
  return note_data, buttons, None
347
 
348
  def remove_escapes(text: str) -> str:
 
 
 
349
  logger.debug(f"Removing escapes from text: {text}")
350
  res = ""
351
  is_escaped = False
@@ -357,13 +386,16 @@ def remove_escapes(text: str) -> str:
357
  is_escaped = True
358
  else:
359
  res += text[counter]
360
- logger.debug(f"Text after removing escapes: {res}")
361
  return res
362
 
363
  def humanbytes(size):
364
- logger.debug(f"Converting size to human-readable: {size}")
 
 
 
365
  if not size:
366
- logger.debug("Size is zero or empty.")
367
  return ""
368
  power = 2**10
369
  n = 0
@@ -372,22 +404,28 @@ def humanbytes(size):
372
  size /= power
373
  n += 1
374
  result = str(round(size, 2)) + " " + Dic_powerN[n] + 'B'
375
- logger.debug(f"Human-readable size: {result}")
376
  return result
377
-
378
  def get_time(seconds):
379
- logger.debug(f"Formatting time: {seconds} seconds")
 
 
 
380
  periods = [('ᴅ', 86400), ('ʜ', 3600), ('ᴍ', 60), ('ꜱ', 1)]
381
  result = ''
382
  for period_name, period_seconds in periods:
383
  if seconds >= period_seconds:
384
  period_value, seconds = divmod(seconds, period_seconds)
385
  result += f'{int(period_value)}{period_name}'
386
- logger.debug(f"Formatted time: {result}")
387
  return result
388
-
389
  async def get_shortlink(link):
390
- logger.debug(f"Generating shortlink for: {link}")
 
 
 
391
  url = f'{SHORT_URL}/api'
392
  params = {'api': SHORT_API, 'url': link}
393
  try:
@@ -395,27 +433,30 @@ async def get_shortlink(link):
395
  async with session.get(url, params=params, raise_for_status=True, ssl=False) as response:
396
  data = await response.json()
397
  if data["status"] == "success":
398
- shortened_url = data['shortenedUrl']
399
- logger.debug(f"Shortlink generated: {shortened_url}")
400
- return shortened_url
401
  else:
402
- logger.error(f"Error generating shortlink: {data['message']}")
403
  return link
404
  except Exception as e:
405
- logger.error(f"Exception in get_shortlink: {e}")
406
  return link
407
 
 
408
  def extract_time(time_val):
409
- logger.debug(f"Extracting time from: {time_val}")
 
 
 
410
  if any(time_val.endswith(unit) for unit in ("s", "m", "h", "d")):
411
  unit = time_val[-1]
412
  time_num = time_val[:-1] # type: str
413
  if not time_num.isdigit():
414
- logger.debug("Time number is not a digit.")
415
  return None
416
 
417
  if unit == "s":
418
- bantime = datetime.now() + timedelta(seconds=int(time_num))
419
  elif unit == "m":
420
  bantime = datetime.now() + timedelta(minutes=int(time_num))
421
  elif unit == "h":
@@ -423,43 +464,48 @@ def extract_time(time_val):
423
  elif unit == "d":
424
  bantime = datetime.now() + timedelta(days=int(time_num))
425
  else:
426
- logger.debug("Invalid unit.")
427
  return None
428
- logger.debug(f"Calculated bantime: {bantime}")
429
  return bantime
430
  else:
431
- logger.debug("No valid unit found in time_val.")
432
  return None
433
 
434
  async def admin_check(message: Message) -> bool:
435
- logger.debug(f"Checking if user {message.from_user.id} is admin in chat {message.chat.id}")
436
- if not message.from_user:
437
- logger.debug("Message does not have a from_user.")
 
 
 
438
  return False
439
- if message.chat.type not in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]:
440
- logger.debug("Chat is not a group or supergroup.")
441
  return False
442
- if message.from_user.id in [777000, 1087968824]:
443
- logger.debug("User is a special bot ID.")
444
  return True
445
  client = message._client
446
  chat_id = message.chat.id
447
  user_id = message.from_user.id
448
  try:
449
  check_status = await client.get_chat_member(chat_id=chat_id, user_id=user_id)
 
450
  except Exception as e:
451
- logger.error(f"Error checking chat member: {e}")
452
  return False
453
  admin_strings = [enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR]
454
- if check_status.status not in admin_strings:
455
- logger.debug("User is not an admin or owner.")
456
  return False
457
- else:
458
- logger.debug("User is an admin or owner.")
459
  return True
460
 
461
  async def admin_filter(filt, client, message):
462
- logger.debug(f"Admin filter checking for message: {message.id}")
463
- result = await admin_check(message)
464
- logger.debug(f"Admin filter result: {result}")
465
- return result
 
 
1
+ import logging, os, re, asyncio, requests, aiohttp
2
+ from pyrogram.errors import InputUserDeactivated, UserNotParticipant, FloodWait, UserIsBlocked, PeerIdInvalid
 
 
 
 
 
3
  from pyrogram.types import Message, InlineKeyboardButton
4
  from pyrogram import filters, enums
5
  from info import AUTH_CHANNEL, LONG_IMDB_DESCRIPTION, MAX_LIST_ELM, SHORT_URL, SHORT_API
6
  from imdb import Cinemagoer
7
  from typing import Union, List
8
  from datetime import datetime, timedelta
 
 
9
 
 
10
  logger = logging.getLogger(__name__)
11
+ logger.setLevel(logging.INFO)
 
 
 
12
 
13
+ BTN_URL_REGEX = re.compile(r"(\[([^\[]+?)\]\((buttonurl|buttonalert):(?:/{0,2})(.+?)(:same)?\))")
14
+ BANNED = {}
15
  SMART_OPEN = '“'
16
  SMART_CLOSE = '”'
17
  START_CHAR = ('\'', '"', SMART_OPEN)
 
18
 
19
+ # temp db for banned
20
  class temp(object):
21
  BANNED_USERS = []
22
  BANNED_CHATS = []
 
30
  PM_BUTTONS = {}
31
  PM_SPELL = {}
32
  GP_SPELL = {}
 
33
 
34
  async def is_subscribed(bot, query):
35
+ """
36
+ Check if a user is subscribed to a specific channel.
37
+ """
38
+ logger.info(f"Checking if user {query.from_user.id} is subscribed to channel {AUTH_CHANNEL}.")
39
  try:
40
  user = await bot.get_chat_member(AUTH_CHANNEL, query.from_user.id)
41
+ logger.info(f"User {query.from_user.id} is subscribed to channel {AUTH_CHANNEL}.")
42
  except UserNotParticipant:
43
+ logger.info(f"User {query.from_user.id} is not subscribed to channel {AUTH_CHANNEL}.")
44
  pass
45
  except Exception as e:
46
+ logger.error(f"Error checking subscription for user {query.from_user.id}: {e}")
47
+ print(e)
48
  else:
49
  if user.status != enums.ChatMemberStatus.BANNED:
50
+ logger.info(f"User {query.from_user.id} is not banned in channel {AUTH_CHANNEL}.")
51
  return True
52
+ logger.info(f"User {query.from_user.id} is not subscribed or is banned in channel {AUTH_CHANNEL}.")
53
  return False
54
 
55
  async def get_poster(query, bulk=False, id=False, file=None):
56
+ """
57
+ Fetch movie details from IMDb.
58
+ """
59
+ logger.info(f"Fetching poster for query: {query}, bulk: {bulk}, id: {id}, file: {file}.")
60
+ imdb = Cinemagoer()
61
+ if not id:
62
  query = (query.strip()).lower()
63
  title = query
64
  year = re.findall(r'[1-2]\d{3}$', query, re.IGNORECASE)
 
68
  elif file is not None:
69
  year = re.findall(r'[1-2]\d{3}', file, re.IGNORECASE)
70
  if year:
71
+ year = list_to_str(year[:1])
72
  else:
73
  year = None
 
74
  try:
75
  movieid = imdb.search_movie(title.lower(), results=10)
76
+ logger.info(f"Found {len(movieid)} movie results for title: {title}.")
77
  except Exception as e:
78
+ logger.error(f"Error searching movie: {e}")
79
  return None
80
  if not movieid:
81
+ logger.info(f"No movie results found for title: {title}.")
82
  return None
83
  if year:
 
84
  filtered = list(filter(lambda k: str(k.get('year')) == str(year), movieid))
85
  if not filtered:
 
86
  filtered = movieid
87
  else:
88
  filtered = movieid
89
  movieid = list(filter(lambda k: k.get('kind') in ['movie', 'tv series'], filtered))
90
  if not movieid:
 
91
  movieid = filtered
92
  if bulk:
93
+ logger.info(f"Returning bulk movie results: {filtered}.")
94
+ return filtered
95
  movieid = movieid[0].movieID
96
+ logger.info(f"Selected movie ID: {movieid}.")
97
  else:
98
  movieid = query
99
+ logger.info(f"Using provided movie ID: {movieid}.")
100
  movie = imdb.get_movie(movieid)
101
  if movie.get("original air date"):
102
  date = movie["original air date"]
 
113
  plot = movie.get('plot outline')
114
  if plot and len(plot) > 800:
115
  plot = plot[0:800] + "..."
116
+
117
+ logger.info(f"Movie details fetched: {movie.get('title')}, Year: {date}, Plot: {plot}.")
118
  return {
119
  'title': movie.get('title'),
120
  'votes': movie.get('votes'),
 
132
  "director": list_to_str(movie.get("director")),
133
  "writer": list_to_str(movie.get("writer")),
134
  "producer": list_to_str(movie.get("producer")),
135
+ "composer": list_to_str(movie.get("composer")) ,
136
  "cinematographer": list_to_str(movie.get("cinematographer")),
137
  "music_team": list_to_str(movie.get("music department")),
138
  "distributors": list_to_str(movie.get("distributors")),
 
144
  'rating': str(movie.get("rating")),
145
  'url': f'https://www.imdb.com/title/tt{movieid}'
146
  }
147
+
148
  def list_to_str(k):
149
+ """
150
+ Convert a list to a comma-separated string.
151
+ """
152
  logger.debug(f"Converting list to string: {k}")
153
+ if not k:
154
+ logger.debug("List is empty, returning 'N/A'.")
155
  return "N/A"
156
+ elif len(k) == 1:
157
+ logger.debug(f"List has one element, returning: {k[0]}")
158
  return str(k[0])
159
  elif MAX_LIST_ELM:
160
  k = k[:int(MAX_LIST_ELM)]
161
  result = ' '.join(f'{elem}, ' for elem in k)
162
+ logger.debug(f"List truncated to {MAX_LIST_ELM} elements, returning: {result}")
163
  return result
164
  else:
165
  result = ' '.join(f'{elem}, ' for elem in k)
166
+ logger.debug(f"Returning full list as string: {result}")
167
  return result
168
 
169
  __repo__ = "https://github.com/MrMKN/PROFESSOR-BOT"
170
+ logger.info(f"Repository URL set to: {__repo__}")
171
+
172
  __version__ = "PROFESSOR-BOT ᴠ4.5.0"
173
+ logger.info(f"Version set to: {__version__}")
174
+
175
  __license__ = "GNU GENERAL PUBLIC LICENSE V2"
176
+ logger.info(f"License set to: {__license__}")
177
 
178
+ __copyright__ = "Copyright (C) 2023-present MrMKN <https://github.com/MrMKN>"
179
+ logger.info(f"Copyright set to: {__copyright__}")
 
 
180
 
181
  async def search_gagala(text):
182
+ """
183
+ Search Google for a given text.
184
+ """
185
+ logger.info(f"Searching Google for text: {text}")
186
  usr_agent = {
187
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
188
+ 'Chrome/61.0.3163.100 Safari/537.36'
189
  }
190
  text = text.replace(" ", '+')
191
  url = f'https://www.google.com/search?q={text}'
 
192
  try:
193
  response = requests.get(url, headers=usr_agent)
194
  response.raise_for_status()
195
+ logger.info(f"Google search successful for text: {text}")
196
+ except Exception as e:
197
+ logger.error(f"Error searching Google: {e}")
198
  return []
199
  soup = BeautifulSoup(response.text, 'html.parser')
200
  titles = soup.find_all('h3')
201
+ result = [title.getText() for title in titles]
202
+ logger.info(f"Found {len(result)} Google search results.")
203
+ return result
204
 
205
  async def get_settings(group_id):
206
+ """
207
+ Get settings for a specific group.
208
+ """
209
+ logger.info(f"Getting settings for group ID: {group_id}")
210
  settings = temp.SETTINGS.get(group_id)
211
  if not settings:
 
212
  settings = await db.get_settings(group_id)
213
  temp.SETTINGS[group_id] = settings
214
+ logger.info(f"Settings retrieved from database for group ID: {group_id}")
215
  else:
216
+ logger.info(f"Settings retrieved from cache for group ID: {group_id}")
217
  return settings
218
+
219
  async def save_group_settings(group_id, key, value):
220
+ """
221
+ Save settings for a specific group.
222
+ """
223
+ logger.info(f"Saving setting '{key}' with value '{value}' for group ID: {group_id}")
224
  current = await get_settings(group_id)
225
  current[key] = value
226
  temp.SETTINGS[group_id] = current
227
  await db.update_settings(group_id, current)
228
+ logger.info(f"Setting '{key}' saved for group ID: {group_id}")
229
 
230
  def get_size(size):
231
+ """
232
+ Convert file size in bytes to a human-readable format.
233
+ """
234
+ logger.debug(f"Converting size {size} bytes to human-readable format.")
235
  units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"]
236
  size = float(size)
237
  i = 0
 
239
  i += 1
240
  size /= 1024.0
241
  result = "%.2f %s" % (size, units[i])
242
+ logger.debug(f"Converted size to: {result}")
243
  return result
244
 
245
  def get_file_id(msg: Message):
246
+ """
247
+ Extract file ID from a message.
248
+ """
249
+ logger.debug(f"Extracting file ID from message: {msg.id}")
250
+ if not msg.media:
251
+ logger.debug("Message does not contain media.")
252
  return None
253
  for message_type in ("photo", "animation", "audio", "document", "video", "video_note", "voice", "sticker"):
254
  obj = getattr(msg, message_type)
255
  if obj:
 
256
  setattr(obj, "message_type", message_type)
257
+ logger.debug(f"File ID extracted: {obj.file_id}, Type: {message_type}")
258
  return obj
259
+ logger.debug("No file ID found in message.")
260
  return None
261
 
262
  def extract_user(message: Message) -> Union[int, str]:
263
+ """
264
+ Extract user ID and first name from a message.
265
+ """
266
+ logger.debug(f"Extracting user from message: {message.id}")
267
  user_id = None
268
  user_first_name = None
269
  if message.reply_to_message:
 
270
  user_id = message.reply_to_message.from_user.id
271
  user_first_name = message.reply_to_message.from_user.first_name
272
+ logger.debug(f"User extracted from reply: ID: {user_id}, First Name: {user_first_name}")
273
  elif len(message.command) > 1:
 
274
  if (len(message.entities) > 1 and message.entities[1].type == enums.MessageEntityType.TEXT_MENTION):
275
  required_entity = message.entities[1]
276
  user_id = required_entity.user.id
277
  user_first_name = required_entity.user.first_name
278
+ logger.debug(f"User extracted from text mention: ID: {user_id}, First Name: {user_first_name}")
279
  else:
280
  user_id = message.command[1]
281
  user_first_name = user_id
282
+ try:
283
+ user_id = int(user_id)
284
+ logger.debug(f"User ID converted to integer: {user_id}")
285
+ except ValueError:
286
+ logger.debug("User ID conversion failed, keeping as string.")
287
+ pass
288
+ logger.debug(f"User extracted from command: ID: {user_id}, First Name: {user_first_name}")
289
  else:
 
290
  user_id = message.from_user.id
291
  user_first_name = message.from_user.first_name
292
+ logger.debug(f"User extracted from sender: ID: {user_id}, First Name: {user_first_name}")
293
  return (user_id, user_first_name)
294
 
295
  def split_quotes(text: str) -> List:
296
+ """
297
+ Split a quoted text into key and rest.
298
+ """
299
+ logger.debug(f"Splitting quotes from text: {text}")
300
  if not any(text.startswith(char) for char in START_CHAR):
301
+ key, rest = text.split(None, 1)
302
+ logger.debug(f"Text does not start with quote, split into key: {key}, rest: {rest}")
303
+ return [key, rest]
 
304
  counter = 1 # ignore first char -> is some kind of quote
305
  while counter < len(text):
306
  if text[counter] == "\\":
 
307
  counter += 1
308
  elif text[counter] == text[0] or (text[0] == SMART_OPEN and text[counter] == SMART_CLOSE):
 
309
  break
310
  counter += 1
311
  else:
312
+ key, rest = text.split(None, 1)
313
+ logger.debug(f"Text does not end with quote, split into key: {key}, rest: {rest}")
314
+ return [key, rest]
 
315
 
316
  # 1 to avoid starting quote, and counter is exclusive so avoids ending
317
  key = remove_escapes(text[1:counter].strip())
 
319
  rest = text[counter + 1:].strip()
320
  if not key:
321
  key = text[0] + text[0]
322
+ logger.debug(f"Text split into key: {key}, rest: {rest}")
323
+ return list(filter(None, [key, rest]))
 
324
 
325
  def parser(text, keyword, cb_data):
326
+ """
327
+ Parse button URLs and alerts from text.
328
+ """
329
+ logger.debug(f"Parsing text for buttons and alerts: {text}")
330
+ if "buttonalert" in text:
331
  text = (text.replace("\n", "\\n").replace("\t", "\\t"))
332
  buttons = []
333
  note_data = ""
 
345
  note_data += text[prev:match.start(1)]
346
  prev = match.end(1)
347
  if match.group(3) == "buttonalert":
348
+ # create a triple with button label, url, and newline status
349
  if bool(match.group(5)) and buttons:
350
  buttons[-1].append(InlineKeyboardButton(match.group(2), callback_data=f"{cb_data}:{i}:{keyword}"))
351
  else:
352
  buttons.append([InlineKeyboardButton(match.group(2), callback_data=f"{cb_data}:{i}:{keyword}")])
353
  i += 1
354
  alerts.append(match.group(4))
355
+ logger.debug(f"Button alert added: Label: {match.group(2)}, Callback Data: {cb_data}:{i}:{keyword}")
356
  elif bool(match.group(5)) and buttons:
 
357
  buttons[-1].append(InlineKeyboardButton(match.group(2), url=match.group(4).replace(" ", "")))
358
+ logger.debug(f"URL button added: Label: {match.group(2)}, URL: {match.group(4)}")
359
  else:
 
360
  buttons.append([InlineKeyboardButton(match.group(2), url=match.group(4).replace(" ", ""))])
361
+ logger.debug(f"URL button added: Label: {match.group(2)}, URL: {match.group(4)}")
362
  else:
363
  note_data += text[prev:to_check]
364
  prev = match.start(1) - 1
365
+ else:
366
  note_data += text[prev:]
367
+ try:
368
+ logger.debug(f"Parsed note data: {note_data}, buttons: {buttons}, alerts: {alerts}")
369
  return note_data, buttons, alerts
370
  except Exception as e:
371
+ logger.error(f"Error parsing text: {e}")
372
  return note_data, buttons, None
373
 
374
  def remove_escapes(text: str) -> str:
375
+ """
376
+ Remove escape characters from text.
377
+ """
378
  logger.debug(f"Removing escapes from text: {text}")
379
  res = ""
380
  is_escaped = False
 
386
  is_escaped = True
387
  else:
388
  res += text[counter]
389
+ logger.debug(f"Escapes removed, resulting text: {res}")
390
  return res
391
 
392
  def humanbytes(size):
393
+ """
394
+ Convert size in bytes to a human-readable format.
395
+ """
396
+ logger.debug(f"Converting size {size} bytes to human-readable format.")
397
  if not size:
398
+ logger.debug("Size is zero, returning empty string.")
399
  return ""
400
  power = 2**10
401
  n = 0
 
404
  size /= power
405
  n += 1
406
  result = str(round(size, 2)) + " " + Dic_powerN[n] + 'B'
407
+ logger.debug(f"Converted size to: {result}")
408
  return result
409
+
410
  def get_time(seconds):
411
+ """
412
+ Convert seconds to a human-readable time format.
413
+ """
414
+ logger.debug(f"Converting {seconds} seconds to human-readable time format.")
415
  periods = [('ᴅ', 86400), ('ʜ', 3600), ('ᴍ', 60), ('ꜱ', 1)]
416
  result = ''
417
  for period_name, period_seconds in periods:
418
  if seconds >= period_seconds:
419
  period_value, seconds = divmod(seconds, period_seconds)
420
  result += f'{int(period_value)}{period_name}'
421
+ logger.debug(f"Converted time to: {result}")
422
  return result
423
+
424
  async def get_shortlink(link):
425
+ """
426
+ Generate a short link using a URL shortener service.
427
+ """
428
+ logger.info(f"Generating short link for: {link}")
429
  url = f'{SHORT_URL}/api'
430
  params = {'api': SHORT_API, 'url': link}
431
  try:
 
433
  async with session.get(url, params=params, raise_for_status=True, ssl=False) as response:
434
  data = await response.json()
435
  if data["status"] == "success":
436
+ logger.info(f"Short link generated: {data['shortenedUrl']}")
437
+ return data['shortenedUrl']
 
438
  else:
439
+ logger.error(f"Error generating short link: {data['message']}")
440
  return link
441
  except Exception as e:
442
+ logger.error(f"Exception occurred while generating short link: {e}")
443
  return link
444
 
445
+ # from Midukki-RoBoT
446
  def extract_time(time_val):
447
+ """
448
+ Extract time from a time value string.
449
+ """
450
+ logger.debug(f"Extracting time from value: {time_val}")
451
  if any(time_val.endswith(unit) for unit in ("s", "m", "h", "d")):
452
  unit = time_val[-1]
453
  time_num = time_val[:-1] # type: str
454
  if not time_num.isdigit():
455
+ logger.debug("Time value is not a digit, returning None.")
456
  return None
457
 
458
  if unit == "s":
459
+ bantime = datetime.now() + timedelta(seconds=int(time_num))
460
  elif unit == "m":
461
  bantime = datetime.now() + timedelta(minutes=int(time_num))
462
  elif unit == "h":
 
464
  elif unit == "d":
465
  bantime = datetime.now() + timedelta(days=int(time_num))
466
  else:
467
+ logger.debug("Unknown time unit, returning None.")
468
  return None
469
+ logger.debug(f"Extracted time: {bantime}")
470
  return bantime
471
  else:
472
+ logger.debug("Time value does not end with valid unit, returning None.")
473
  return None
474
 
475
  async def admin_check(message: Message) -> bool:
476
+ """
477
+ Check if a user is an admin in the chat.
478
+ """
479
+ logger.debug(f"Checking if user {message.from_user.id} is an admin in chat {message.chat.id}.")
480
+ if not message.from_user:
481
+ logger.debug("Message does not contain a user, returning False.")
482
  return False
483
+ if message.chat.type not in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]:
484
+ logger.debug("Chat type is not GROUP or SUPERGROUP, returning False.")
485
  return False
486
+ if message.from_user.id in [777000, 1087968824]:
487
+ logger.debug("User is a Telegram service account, returning True.")
488
  return True
489
  client = message._client
490
  chat_id = message.chat.id
491
  user_id = message.from_user.id
492
  try:
493
  check_status = await client.get_chat_member(chat_id=chat_id, user_id=user_id)
494
+ logger.debug(f"User {user_id} status in chat {chat_id}: {check_status.status}")
495
  except Exception as e:
496
+ logger.error(f"Error checking user status: {e}")
497
  return False
498
  admin_strings = [enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR]
499
+ if check_status.status not in admin_strings:
500
+ logger.debug(f"User {user_id} is not an admin, returning False.")
501
  return False
502
+ else:
503
+ logger.debug(f"User {user_id} is an admin, returning True.")
504
  return True
505
 
506
  async def admin_filter(filt, client, message):
507
+ """
508
+ Filter for admin checks.
509
+ """
510
+ logger.debug(f"Applying admin filter for message {message.id}.")
511
+ return await admin_check(message)