From 5ba66cb0cef78f75a50592ad11b642720ac377d9 Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 15 Oct 2023 20:27:38 +0200 Subject: [PATCH] Several Smaller Updates (#121) * Remove User Data Tracking For Join Requests * Ignore some frequent exceptions * Improve Reply-Search * Make use of `post_shutdown` * Add `/traceback` TagHint and link to alternative pastebin services --- components/callbacks.py | 76 +++++++++++++++++++------------------- components/errorhandler.py | 23 +----------- components/joinrequests.py | 38 +++++-------------- components/taghints.py | 17 ++++++++- rules_bot.py | 4 +- 5 files changed, 66 insertions(+), 92 deletions(-) diff --git a/components/callbacks.py b/components/callbacks.py index 06f18de..6630825 100644 --- a/components/callbacks.py +++ b/components/callbacks.py @@ -216,52 +216,54 @@ async def reply_search(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No If the message is a reply, the bot will reply to the referenced message directly. """ message = cast(Message, update.effective_message) + if not message.text: + return + last = 0.0 github_matches: List[Tuple[int, Tuple[str, str, str, str, str]]] = [] found_entries: List[Tuple[int, BaseEntry]] = [] - no_entity_text = get_text_not_in_entities(message).strip() search = cast(Search, context.bot_data["search"]) github = search.github # Parse exact matches for GitHub threads & ptbcontrib found_entries first - if not (no_entity_text.startswith("!search") or no_entity_text.endswith("!search")): - for match in GITHUB_PATTERN.finditer(no_entity_text): - logging.debug(match.groupdict()) - owner, repo, number, sha, ptbcontrib = ( - cast(str, match.groupdict()[x]) - for x in ("owner", "repo", "number", "sha", "ptbcontrib") - ) - if number or sha or ptbcontrib: - github_matches.append((match.start(), (owner, repo, number, sha, ptbcontrib))) - - for gh_match in github_matches: - last = keep_typing( - last, - cast(Chat, update.effective_chat), - ChatAction.TYPING, - application=context.application, - ) - owner, repo, number, sha, ptbcontrib = gh_match[1] - owner = owner or DEFAULT_REPO_OWNER - repo = repo or DEFAULT_REPO_NAME - if number: - issue = await github.get_thread(int(number), owner, repo) - if issue is not None: - found_entries.append((gh_match[0], issue)) - elif sha: - commit = await github.get_commit(sha, owner, repo) - if commit is not None: - found_entries.append((gh_match[0], commit)) - elif ptbcontrib: - contrib = github.ptb_contribs.get(ptbcontrib) - if contrib: - found_entries.append((gh_match[0], contrib)) - - else: - # Parse fuzzy search next - for match in ENCLOSED_REGEX.finditer(no_entity_text): + for match in GITHUB_PATTERN.finditer(no_entity_text): + logging.debug(match.groupdict()) + owner, repo, number, sha, ptbcontrib = ( + cast(str, match.groupdict()[x]) + for x in ("owner", "repo", "number", "sha", "ptbcontrib") + ) + if number or sha or ptbcontrib: + github_matches.append((match.start(), (owner, repo, number, sha, ptbcontrib))) + + for gh_match in github_matches: + last = keep_typing( + last, + cast(Chat, update.effective_chat), + ChatAction.TYPING, + application=context.application, + ) + owner, repo, number, sha, ptbcontrib = gh_match[1] + owner = owner or DEFAULT_REPO_OWNER + repo = repo or DEFAULT_REPO_NAME + if number: + issue = await github.get_thread(int(number), owner, repo) + if issue is not None: + found_entries.append((gh_match[0], issue)) + elif sha: + commit = await github.get_commit(sha, owner, repo) + if commit is not None: + found_entries.append((gh_match[0], commit)) + elif ptbcontrib: + contrib = github.ptb_contribs.get(ptbcontrib) + if contrib: + found_entries.append((gh_match[0], contrib)) + + # Parse fuzzy search next, if requested. Here we use message.text instead of no_entity_text + # to avoid tlds like .bot and .app to mess things up for us + if message.text.startswith("!search") or message.text.endswith("!search"): + for match in ENCLOSED_REGEX.finditer(message.text): last = keep_typing( last, cast(Chat, update.effective_chat), diff --git a/components/errorhandler.py b/components/errorhandler.py index 53ad011..e9ddd23 100644 --- a/components/errorhandler.py +++ b/components/errorhandler.py @@ -2,7 +2,7 @@ import json import logging import traceback -from typing import Optional, cast +from typing import cast from telegram import Update from telegram.error import BadRequest @@ -34,20 +34,6 @@ async def error_handler(update: object, context: CallbackContext) -> None: ) message_2 = f"
{html.escape(tb_string)}
" - user_id: Optional[int] = None - message_3 = "" - if isinstance(update, Update) and update.effective_user: - user_id = update.effective_user.id - if context.job: - user_id = context.job.user_id - if user_id: - data_str = html.escape( - json.dumps(context.application.user_data[user_id], indent=2, ensure_ascii=False) - ) - message_3 = ( - "user_data associated with this exception:\n\n" f"
{data_str}
" - ) - # Finally, send the messages # We send update and traceback in two parts to reduce the chance of hitting max length try: @@ -61,11 +47,6 @@ async def error_handler(update: object, context: CallbackContext) -> None: f"Hey.\nThe error {html.escape(str(context.error))} happened." f" The traceback is too long to send, but it was written to the log." ) - sent_message = await context.bot.send_message( - chat_id=ERROR_CHANNEL_CHAT_ID, text=message - ) + await context.bot.send_message(chat_id=ERROR_CHANNEL_CHAT_ID, text=message) else: raise exc - finally: - if message_3: - await sent_message.reply_html(message_3) diff --git a/components/joinrequests.py b/components/joinrequests.py index b03ea6a..1687f0a 100644 --- a/components/joinrequests.py +++ b/components/joinrequests.py @@ -1,5 +1,5 @@ import datetime -from typing import Any, Dict, Tuple, Union, cast +from typing import Tuple, Union, cast from telegram import ( CallbackQuery, @@ -30,19 +30,14 @@ def get_dtm_str() -> str: async def approve_user( user: Union[int, User], chat_id: int, group_name: str, context: ContextTypes.DEFAULT_TYPE ) -> None: - user_data = cast(Dict[Any, Any], context.user_data) try: if isinstance(user, User): await user.approve_join_request(chat_id=chat_id) else: await context.bot.approve_chat_join_request(user_id=user, chat_id=chat_id) - user_data.setdefault(int(chat_id), {}).setdefault("approved", []).append(get_dtm_str()) except BadRequest as exc: user_mention = f"{user.username} - {user.id}" if isinstance(user, User) else str(user) error_message = f"{exc} - {user_mention} - {group_name}" - user_data.setdefault(int(chat_id), {}).setdefault("approve failed", []).append( - f"{get_dtm_str()}: {exc}" - ) raise BadRequest(error_message) from exc except Forbidden as exc: if "user is deactivated" not in exc.message: @@ -52,19 +47,14 @@ async def approve_user( async def decline_user( user: Union[int, User], chat_id: int, group_name: str, context: ContextTypes.DEFAULT_TYPE ) -> None: - user_data = cast(Dict[Any, Any], context.user_data) try: if isinstance(user, User): await user.decline_join_request(chat_id=chat_id) else: await context.bot.decline_chat_join_request(user_id=user, chat_id=chat_id) - user_data.setdefault(int(chat_id), {}).setdefault("declined", []).append(get_dtm_str()) except BadRequest as exc: user_mention = f"{user.username} - {user.id}" if isinstance(user, User) else str(user) error_message = f"{exc} - {user_mention} - {group_name}" - user_data.setdefault(int(chat_id), {}).setdefault("declined failed", []).append( - f"{get_dtm_str()}: {exc}" - ) raise BadRequest(error_message) from exc except Forbidden as exc: if "user is deactivated" not in exc.message: @@ -81,16 +71,9 @@ async def join_request_callback(update: Update, context: ContextTypes.DEFAULT_TY # No need to ping the user again if we already did return - user_data = cast(Dict[Any, Any], context.user_data) on_topic = join_request.chat.username == ONTOPIC_USERNAME group_mention = ONTOPIC_CHAT_ID if on_topic else OFFTOPIC_CHAT_ID - user_data.setdefault(int(chat_id), {}).setdefault("received join request", []).append( - get_dtm_str() - ) - user_data[int(chat_id)]["user_chat_id"] = user_chat_id - user_data[int(chat_id)]["user_id"] = user.id - text = ( f"Hi, {user.mention_html()}! I'm {context.bot.bot.mention_html()}, the " f"guardian of the group {group_mention}, that you requested to join.\n\nBefore you can " @@ -114,7 +97,6 @@ async def join_request_callback(update: Update, context: ContextTypes.DEFAULT_TY # If the user blocked the bot, let's give the admins a chance to handle that # TG also notifies the user and forwards the message once the user unblocks the bot, but # forwarding it still doesn't hurt ... - user_data.setdefault("blocked bot", []).append(get_dtm_str()) text = ( f"User {user.mention_html()} with id {user.id} requested to join the group " f"{join_request.chat.username} but has blocked me. Please manually handle this." @@ -133,23 +115,16 @@ async def join_request_callback(update: Update, context: ContextTypes.DEFAULT_TY async def join_request_buttons(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - user_data = cast(Dict[Any, Any], context.user_data) callback_query = cast(CallbackQuery, update.callback_query) user = cast(User, update.effective_user) _, press, chat_id = cast(str, callback_query.data).split() if press == "2": - user_data.setdefault(int(chat_id), {}).setdefault("pressed button 2", []).append( - get_dtm_str() - ) jobs = cast(JobQueue, context.job_queue).get_jobs_by_name( f"JOIN_TIMEOUT {chat_id} {user.id}" ) if jobs: for job in jobs: job.schedule_removal() - user_data.setdefault(int(chat_id), {}).setdefault( - "removed join timeout", [] - ).append(get_dtm_str()) try: await approve_user( @@ -162,9 +137,6 @@ async def join_request_buttons(update: Update, context: ContextTypes.DEFAULT_TYP reply_markup = None else: - user_data.setdefault(int(chat_id), {}).setdefault("pressed button 1", []).append( - get_dtm_str() - ) reply_markup = InlineKeyboardMarkup.from_button( InlineKeyboardButton( text="⚠️ Tap again to confirm", @@ -193,3 +165,11 @@ async def join_request_timeout_job(context: ContextTypes.DEFAULT_TYPE) -> None: except Forbidden as exc: if "user is deactivated" not in exc.message: raise exc + except BadRequest as exc: + # These apparently happen frequently, e.g. when user clear the chat + if exc.message not in [ + "Message to edit not found", + "Can't access the chat", + "Chat not found", + ]: + raise exc diff --git a/components/taghints.py b/components/taghints.py index ae873e3..0ac228f 100644 --- a/components/taghints.py +++ b/components/taghints.py @@ -120,8 +120,9 @@ "pastebin": { "message": ( "{query} Please post code or tracebacks using a pastebin rather than via plain text " - "or a picture. https://pastebin.com/ is quite popular, but there are many " - "alternatives out there. Of course, for very short snippets, text is fine. Please at " + "or a picture. https://pastebin.com/ is quite popular, but there are " + "many alternatives " + "out there. Of course, for very short snippets, text is fine. Please at " "least format it as monospace in that case." ), "help": "Ask users not to post code as text or images.", @@ -283,6 +284,18 @@ "help": "Tell users not to use AI/LLM generated answers", "group_command": True, }, + "traceback": { + "message": ( + "{query} Please show the full traceback via a pastebin. Make sure to include " + "everything from the first Traceback (most recent call last): until the " + "last error message. https://pastebin.com/ is a popular pastebin service, but there " + "are many alternatives out " + "there." + ), + "default": "Hey.", + "help": "Ask for the full traceback", + "group_command": True, + }, } diff --git a/rules_bot.py b/rules_bot.py index 511632f..9051850 100644 --- a/rules_bot.py +++ b/rules_bot.py @@ -1,4 +1,3 @@ -import asyncio import configparser import logging import os @@ -111,6 +110,7 @@ def main() -> None: .token(config["KEYS"]["bot_api"]) .defaults(defaults) .post_init(post_init) + .post_shutdown(post_shutdown) .job_queue(RulesJobQueue()) .build() ) @@ -207,8 +207,6 @@ def main() -> None: application.add_error_handler(error_handler) application.run_polling(allowed_updates=Update.ALL_TYPES, close_loop=False) - # Can be used in AppBuilder.post_shutdown once #3126 is released - asyncio.get_event_loop().run_until_complete(post_shutdown(application)) if __name__ == "__main__":