Skip to content

Commit 5ba66cb

Browse files
authored
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
1 parent c318ec8 commit 5ba66cb

5 files changed

Lines changed: 66 additions & 92 deletions

File tree

components/callbacks.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -216,52 +216,54 @@ async def reply_search(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
216216
If the message is a reply, the bot will reply to the referenced message directly.
217217
"""
218218
message = cast(Message, update.effective_message)
219+
if not message.text:
220+
return
221+
219222
last = 0.0
220223
github_matches: List[Tuple[int, Tuple[str, str, str, str, str]]] = []
221224
found_entries: List[Tuple[int, BaseEntry]] = []
222-
223225
no_entity_text = get_text_not_in_entities(message).strip()
224226

225227
search = cast(Search, context.bot_data["search"])
226228
github = search.github
227229

228230
# Parse exact matches for GitHub threads & ptbcontrib found_entries first
229-
if not (no_entity_text.startswith("!search") or no_entity_text.endswith("!search")):
230-
for match in GITHUB_PATTERN.finditer(no_entity_text):
231-
logging.debug(match.groupdict())
232-
owner, repo, number, sha, ptbcontrib = (
233-
cast(str, match.groupdict()[x])
234-
for x in ("owner", "repo", "number", "sha", "ptbcontrib")
235-
)
236-
if number or sha or ptbcontrib:
237-
github_matches.append((match.start(), (owner, repo, number, sha, ptbcontrib)))
238-
239-
for gh_match in github_matches:
240-
last = keep_typing(
241-
last,
242-
cast(Chat, update.effective_chat),
243-
ChatAction.TYPING,
244-
application=context.application,
245-
)
246-
owner, repo, number, sha, ptbcontrib = gh_match[1]
247-
owner = owner or DEFAULT_REPO_OWNER
248-
repo = repo or DEFAULT_REPO_NAME
249-
if number:
250-
issue = await github.get_thread(int(number), owner, repo)
251-
if issue is not None:
252-
found_entries.append((gh_match[0], issue))
253-
elif sha:
254-
commit = await github.get_commit(sha, owner, repo)
255-
if commit is not None:
256-
found_entries.append((gh_match[0], commit))
257-
elif ptbcontrib:
258-
contrib = github.ptb_contribs.get(ptbcontrib)
259-
if contrib:
260-
found_entries.append((gh_match[0], contrib))
261-
262-
else:
263-
# Parse fuzzy search next
264-
for match in ENCLOSED_REGEX.finditer(no_entity_text):
231+
for match in GITHUB_PATTERN.finditer(no_entity_text):
232+
logging.debug(match.groupdict())
233+
owner, repo, number, sha, ptbcontrib = (
234+
cast(str, match.groupdict()[x])
235+
for x in ("owner", "repo", "number", "sha", "ptbcontrib")
236+
)
237+
if number or sha or ptbcontrib:
238+
github_matches.append((match.start(), (owner, repo, number, sha, ptbcontrib)))
239+
240+
for gh_match in github_matches:
241+
last = keep_typing(
242+
last,
243+
cast(Chat, update.effective_chat),
244+
ChatAction.TYPING,
245+
application=context.application,
246+
)
247+
owner, repo, number, sha, ptbcontrib = gh_match[1]
248+
owner = owner or DEFAULT_REPO_OWNER
249+
repo = repo or DEFAULT_REPO_NAME
250+
if number:
251+
issue = await github.get_thread(int(number), owner, repo)
252+
if issue is not None:
253+
found_entries.append((gh_match[0], issue))
254+
elif sha:
255+
commit = await github.get_commit(sha, owner, repo)
256+
if commit is not None:
257+
found_entries.append((gh_match[0], commit))
258+
elif ptbcontrib:
259+
contrib = github.ptb_contribs.get(ptbcontrib)
260+
if contrib:
261+
found_entries.append((gh_match[0], contrib))
262+
263+
# Parse fuzzy search next, if requested. Here we use message.text instead of no_entity_text
264+
# to avoid tlds like .bot and .app to mess things up for us
265+
if message.text.startswith("!search") or message.text.endswith("!search"):
266+
for match in ENCLOSED_REGEX.finditer(message.text):
265267
last = keep_typing(
266268
last,
267269
cast(Chat, update.effective_chat),

components/errorhandler.py

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import logging
44
import traceback
5-
from typing import Optional, cast
5+
from typing import cast
66

77
from telegram import Update
88
from telegram.error import BadRequest
@@ -34,20 +34,6 @@ async def error_handler(update: object, context: CallbackContext) -> None:
3434
)
3535
message_2 = f"<pre>{html.escape(tb_string)}</pre>"
3636

37-
user_id: Optional[int] = None
38-
message_3 = ""
39-
if isinstance(update, Update) and update.effective_user:
40-
user_id = update.effective_user.id
41-
if context.job:
42-
user_id = context.job.user_id
43-
if user_id:
44-
data_str = html.escape(
45-
json.dumps(context.application.user_data[user_id], indent=2, ensure_ascii=False)
46-
)
47-
message_3 = (
48-
"<code>user_data</code> associated with this exception:\n\n" f"<pre>{data_str}</pre>"
49-
)
50-
5137
# Finally, send the messages
5238
# We send update and traceback in two parts to reduce the chance of hitting max length
5339
try:
@@ -61,11 +47,6 @@ async def error_handler(update: object, context: CallbackContext) -> None:
6147
f"Hey.\nThe error <code>{html.escape(str(context.error))}</code> happened."
6248
f" The traceback is too long to send, but it was written to the log."
6349
)
64-
sent_message = await context.bot.send_message(
65-
chat_id=ERROR_CHANNEL_CHAT_ID, text=message
66-
)
50+
await context.bot.send_message(chat_id=ERROR_CHANNEL_CHAT_ID, text=message)
6751
else:
6852
raise exc
69-
finally:
70-
if message_3:
71-
await sent_message.reply_html(message_3)

components/joinrequests.py

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime
2-
from typing import Any, Dict, Tuple, Union, cast
2+
from typing import Tuple, Union, cast
33

44
from telegram import (
55
CallbackQuery,
@@ -30,19 +30,14 @@ def get_dtm_str() -> str:
3030
async def approve_user(
3131
user: Union[int, User], chat_id: int, group_name: str, context: ContextTypes.DEFAULT_TYPE
3232
) -> None:
33-
user_data = cast(Dict[Any, Any], context.user_data)
3433
try:
3534
if isinstance(user, User):
3635
await user.approve_join_request(chat_id=chat_id)
3736
else:
3837
await context.bot.approve_chat_join_request(user_id=user, chat_id=chat_id)
39-
user_data.setdefault(int(chat_id), {}).setdefault("approved", []).append(get_dtm_str())
4038
except BadRequest as exc:
4139
user_mention = f"{user.username} - {user.id}" if isinstance(user, User) else str(user)
4240
error_message = f"{exc} - {user_mention} - {group_name}"
43-
user_data.setdefault(int(chat_id), {}).setdefault("approve failed", []).append(
44-
f"{get_dtm_str()}: {exc}"
45-
)
4641
raise BadRequest(error_message) from exc
4742
except Forbidden as exc:
4843
if "user is deactivated" not in exc.message:
@@ -52,19 +47,14 @@ async def approve_user(
5247
async def decline_user(
5348
user: Union[int, User], chat_id: int, group_name: str, context: ContextTypes.DEFAULT_TYPE
5449
) -> None:
55-
user_data = cast(Dict[Any, Any], context.user_data)
5650
try:
5751
if isinstance(user, User):
5852
await user.decline_join_request(chat_id=chat_id)
5953
else:
6054
await context.bot.decline_chat_join_request(user_id=user, chat_id=chat_id)
61-
user_data.setdefault(int(chat_id), {}).setdefault("declined", []).append(get_dtm_str())
6255
except BadRequest as exc:
6356
user_mention = f"{user.username} - {user.id}" if isinstance(user, User) else str(user)
6457
error_message = f"{exc} - {user_mention} - {group_name}"
65-
user_data.setdefault(int(chat_id), {}).setdefault("declined failed", []).append(
66-
f"{get_dtm_str()}: {exc}"
67-
)
6858
raise BadRequest(error_message) from exc
6959
except Forbidden as exc:
7060
if "user is deactivated" not in exc.message:
@@ -81,16 +71,9 @@ async def join_request_callback(update: Update, context: ContextTypes.DEFAULT_TY
8171
# No need to ping the user again if we already did
8272
return
8373

84-
user_data = cast(Dict[Any, Any], context.user_data)
8574
on_topic = join_request.chat.username == ONTOPIC_USERNAME
8675
group_mention = ONTOPIC_CHAT_ID if on_topic else OFFTOPIC_CHAT_ID
8776

88-
user_data.setdefault(int(chat_id), {}).setdefault("received join request", []).append(
89-
get_dtm_str()
90-
)
91-
user_data[int(chat_id)]["user_chat_id"] = user_chat_id
92-
user_data[int(chat_id)]["user_id"] = user.id
93-
9477
text = (
9578
f"Hi, {user.mention_html()}! I'm {context.bot.bot.mention_html()}, the "
9679
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
11497
# If the user blocked the bot, let's give the admins a chance to handle that
11598
# TG also notifies the user and forwards the message once the user unblocks the bot, but
11699
# forwarding it still doesn't hurt ...
117-
user_data.setdefault("blocked bot", []).append(get_dtm_str())
118100
text = (
119101
f"User {user.mention_html()} with id {user.id} requested to join the group "
120102
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
133115

134116

135117
async def join_request_buttons(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
136-
user_data = cast(Dict[Any, Any], context.user_data)
137118
callback_query = cast(CallbackQuery, update.callback_query)
138119
user = cast(User, update.effective_user)
139120
_, press, chat_id = cast(str, callback_query.data).split()
140121
if press == "2":
141-
user_data.setdefault(int(chat_id), {}).setdefault("pressed button 2", []).append(
142-
get_dtm_str()
143-
)
144122
jobs = cast(JobQueue, context.job_queue).get_jobs_by_name(
145123
f"JOIN_TIMEOUT {chat_id} {user.id}"
146124
)
147125
if jobs:
148126
for job in jobs:
149127
job.schedule_removal()
150-
user_data.setdefault(int(chat_id), {}).setdefault(
151-
"removed join timeout", []
152-
).append(get_dtm_str())
153128

154129
try:
155130
await approve_user(
@@ -162,9 +137,6 @@ async def join_request_buttons(update: Update, context: ContextTypes.DEFAULT_TYP
162137

163138
reply_markup = None
164139
else:
165-
user_data.setdefault(int(chat_id), {}).setdefault("pressed button 1", []).append(
166-
get_dtm_str()
167-
)
168140
reply_markup = InlineKeyboardMarkup.from_button(
169141
InlineKeyboardButton(
170142
text="⚠️ Tap again to confirm",
@@ -193,3 +165,11 @@ async def join_request_timeout_job(context: ContextTypes.DEFAULT_TYPE) -> None:
193165
except Forbidden as exc:
194166
if "user is deactivated" not in exc.message:
195167
raise exc
168+
except BadRequest as exc:
169+
# These apparently happen frequently, e.g. when user clear the chat
170+
if exc.message not in [
171+
"Message to edit not found",
172+
"Can't access the chat",
173+
"Chat not found",
174+
]:
175+
raise exc

components/taghints.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@
120120
"pastebin": {
121121
"message": (
122122
"{query} Please post code or tracebacks using a pastebin rather than via plain text "
123-
"or a picture. https://pastebin.com/ is quite popular, but there are many "
124-
"alternatives out there. Of course, for very short snippets, text is fine. Please at "
123+
"or a picture. https://pastebin.com/ is quite popular, but there are "
124+
"<a href='https://github.com/lorien/awesome-pastebin'>many alternatives</a> "
125+
"out there. Of course, for very short snippets, text is fine. Please at "
125126
"least format it as monospace in that case."
126127
),
127128
"help": "Ask users not to post code as text or images.",
@@ -283,6 +284,18 @@
283284
"help": "Tell users not to use AI/LLM generated answers",
284285
"group_command": True,
285286
},
287+
"traceback": {
288+
"message": (
289+
"{query} Please show the <i>full</i> traceback via a pastebin. Make sure to include "
290+
"everything from the first <code>Traceback (most recent call last):</code> until the "
291+
"last error message. https://pastebin.com/ is a popular pastebin service, but there "
292+
"are <a href='https://github.com/lorien/awesome-pastebin'>many alternatives</a> out "
293+
"there."
294+
),
295+
"default": "Hey.",
296+
"help": "Ask for the full traceback",
297+
"group_command": True,
298+
},
286299
}
287300

288301

rules_bot.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import configparser
32
import logging
43
import os
@@ -111,6 +110,7 @@ def main() -> None:
111110
.token(config["KEYS"]["bot_api"])
112111
.defaults(defaults)
113112
.post_init(post_init)
113+
.post_shutdown(post_shutdown)
114114
.job_queue(RulesJobQueue())
115115
.build()
116116
)
@@ -207,8 +207,6 @@ def main() -> None:
207207
application.add_error_handler(error_handler)
208208

209209
application.run_polling(allowed_updates=Update.ALL_TYPES, close_loop=False)
210-
# Can be used in AppBuilder.post_shutdown once #3126 is released
211-
asyncio.get_event_loop().run_until_complete(post_shutdown(application))
212210

213211

214212
if __name__ == "__main__":

0 commit comments

Comments
 (0)