Skip to content

Commit

Permalink
Several Smaller Updates (#121)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Bibo-Joshi authored Oct 15, 2023
1 parent c318ec8 commit 5ba66cb
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 92 deletions.
76 changes: 39 additions & 37 deletions components/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
23 changes: 2 additions & 21 deletions components/errorhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -34,20 +34,6 @@ async def error_handler(update: object, context: CallbackContext) -> None:
)
message_2 = f"<pre>{html.escape(tb_string)}</pre>"

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 = (
"<code>user_data</code> associated with this exception:\n\n" f"<pre>{data_str}</pre>"
)

# Finally, send the messages
# We send update and traceback in two parts to reduce the chance of hitting max length
try:
Expand All @@ -61,11 +47,6 @@ async def error_handler(update: object, context: CallbackContext) -> None:
f"Hey.\nThe error <code>{html.escape(str(context.error))}</code> 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)
38 changes: 9 additions & 29 deletions components/joinrequests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from typing import Any, Dict, Tuple, Union, cast
from typing import Tuple, Union, cast

from telegram import (
CallbackQuery,
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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 "
Expand All @@ -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."
Expand All @@ -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(
Expand All @@ -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",
Expand Down Expand Up @@ -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
17 changes: 15 additions & 2 deletions components/taghints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
"<a href='https://github.com/lorien/awesome-pastebin'>many alternatives</a> "
"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.",
Expand Down Expand Up @@ -283,6 +284,18 @@
"help": "Tell users not to use AI/LLM generated answers",
"group_command": True,
},
"traceback": {
"message": (
"{query} Please show the <i>full</i> traceback via a pastebin. Make sure to include "
"everything from the first <code>Traceback (most recent call last):</code> until the "
"last error message. https://pastebin.com/ is a popular pastebin service, but there "
"are <a href='https://github.com/lorien/awesome-pastebin'>many alternatives</a> out "
"there."
),
"default": "Hey.",
"help": "Ask for the full traceback",
"group_command": True,
},
}


Expand Down
4 changes: 1 addition & 3 deletions rules_bot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
import configparser
import logging
import os
Expand Down Expand Up @@ -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()
)
Expand Down Expand Up @@ -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__":
Expand Down

0 comments on commit 5ba66cb

Please sign in to comment.