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__":