threadkiller 3.0.3__tar.gz → 4.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {threadkiller-3.0.3 → threadkiller-4.0.0}/.editorconfig +6 -0
  2. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/inspectionProfiles/Project_Default.xml +3 -8
  3. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/misc.xml +3 -0
  4. {threadkiller-3.0.3 → threadkiller-4.0.0}/Containerfile +1 -1
  5. {threadkiller-3.0.3 → threadkiller-4.0.0}/PKG-INFO +3 -1
  6. {threadkiller-3.0.3 → threadkiller-4.0.0}/pyproject.toml +6 -1
  7. threadkiller-4.0.0/threadkiller/__main__.py +36 -0
  8. threadkiller-4.0.0/threadkiller/application.py +144 -0
  9. threadkiller-4.0.0/threadkiller/config.py +83 -0
  10. threadkiller-4.0.0/threadkiller/handlers.py +305 -0
  11. threadkiller-4.0.0/threadkiller/locale/__init__.py +51 -0
  12. threadkiller-4.0.0/threadkiller/locale/en/main.ftl +199 -0
  13. threadkiller-4.0.0/threadkiller/log.py +54 -0
  14. threadkiller-4.0.0/threadkiller/metadata.py +39 -0
  15. threadkiller-4.0.0/threadkiller/types.py +30 -0
  16. threadkiller-4.0.0/uv.lock +232 -0
  17. threadkiller-3.0.3/threadkiller/__main__.py +0 -55
  18. threadkiller-3.0.3/threadkiller/config.py +0 -22
  19. threadkiller-3.0.3/threadkiller/handlers.py +0 -63
  20. threadkiller-3.0.3/threadkiller/strings.py +0 -20
  21. threadkiller-3.0.3/uv.lock +0 -148
  22. {threadkiller-3.0.3 → threadkiller-4.0.0}/.containerignore +0 -0
  23. {threadkiller-3.0.3 → threadkiller-4.0.0}/.forgejo/workflows/build-container-image-v.yaml +0 -0
  24. {threadkiller-3.0.3 → threadkiller-4.0.0}/.forgejo/workflows/build-package.yaml +0 -0
  25. {threadkiller-3.0.3 → threadkiller-4.0.0}/.forgejo/workflows/test-package.yaml +0 -0
  26. {threadkiller-3.0.3 → threadkiller-4.0.0}/.gitignore +0 -0
  27. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/.gitignore +0 -0
  28. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/codeStyles/Project.xml +0 -0
  29. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/codeStyles/codeStyleConfig.xml +0 -0
  30. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/discord.xml +0 -0
  31. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/icon.svg +0 -0
  32. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/jsonSchemas.xml +0 -0
  33. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/modules.xml +0 -0
  34. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/threadkiller.iml +0 -0
  35. {threadkiller-3.0.3 → threadkiller-4.0.0}/.idea/vcs.xml +0 -0
  36. {threadkiller-3.0.3 → threadkiller-4.0.0}/.media/icon-512.png +0 -0
  37. {threadkiller-3.0.3 → threadkiller-4.0.0}/.media/icon.svg +0 -0
  38. {threadkiller-3.0.3 → threadkiller-4.0.0}/.noai +0 -0
  39. {threadkiller-3.0.3 → threadkiller-4.0.0}/.vscode/extensions.json +0 -0
  40. {threadkiller-3.0.3 → threadkiller-4.0.0}/.vscode/settings.json +0 -0
  41. {threadkiller-3.0.3 → threadkiller-4.0.0}/LICENSE.txt +0 -0
  42. {threadkiller-3.0.3 → threadkiller-4.0.0}/compose.yml +0 -0
  43. {threadkiller-3.0.3 → threadkiller-4.0.0}/threadkiller/__init__.py +0 -0
@@ -24,3 +24,9 @@ indent_style = space
24
24
  # YAML does not support tabs, and looks weird with 4 spaces
25
25
  indent_size = 2
26
26
  indent_style = space
27
+
28
+ [*.ftl]
29
+
30
+ # Fluent does not support tabs
31
+ indent_size = 4
32
+ indent_style = space
@@ -6,7 +6,7 @@
6
6
  <inspection_tool class="LessResolvedByNameOnly" enabled="true" level="WARNING" enabled_by_default="true" />
7
7
  <inspection_tool class="LessUnresolvedMixin" enabled="true" level="ERROR" enabled_by_default="true" />
8
8
  <inspection_tool class="LessUnresolvedVariable" enabled="true" level="ERROR" enabled_by_default="true" />
9
- <inspection_tool class="LongLine" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
9
+ <inspection_tool class="LongLine" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
10
10
  <inspection_tool class="PoetryPackageVersion" enabled="true" level="SERVER PROBLEM" enabled_by_default="true" />
11
11
  <inspection_tool class="ProblematicWhitespace" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
12
12
  <inspection_tool class="PyAbstractClassInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -26,17 +26,12 @@
26
26
  <inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
27
27
  <inspection_tool class="PyNestedDecoratorsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
28
28
  <inspection_tool class="PyNoneFunctionAssignmentInspection" enabled="true" level="WARNING" enabled_by_default="true" />
29
- <inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="ERROR" enabled_by_default="false">
30
- <option name="ignoredPackages">
31
- <value>
32
- <list size="0" />
33
- </value>
34
- </option>
35
- </inspection_tool>
29
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="ERROR" enabled_by_default="false" />
36
30
  <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
37
31
  <option name="ignoredErrors">
38
32
  <list>
39
33
  <option value="N806" />
34
+ <option value="N802" />
40
35
  </list>
41
36
  </option>
42
37
  </inspection_tool>
@@ -6,4 +6,7 @@
6
6
  <component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="Poetry (threadkiller)" project-jdk-type="Python SDK">
7
7
  <output url="file://$PROJECT_DIR$/out" />
8
8
  </component>
9
+ <component name="TaskProjectConfiguration">
10
+ <server type="Forgejo" url="https://g.starshard.space" />
11
+ </component>
9
12
  </project>
@@ -23,7 +23,7 @@ CMD []
23
23
  LABEL org.opencontainers.image.title="Threadkiller"
24
24
  LABEL org.opencontainers.image.description="Telegram bot to delete messages sent outside of threads"
25
25
  LABEL org.opencontainers.image.authors="Stefano Pigozzi <me@steffo.eu>"
26
- LABEL org.opencontainers.image.version="3.0.0"
26
+ LABEL org.opencontainers.image.version="4.0.0"
27
27
  LABEL org.opencontainers.image.licenses="EUPL-1.2"
28
28
  LABEL org.opencontainers.image.url="https://g.starshard.space/steffo/-/packages/container/threadkiller/latest"
29
29
  LABEL org.opencontainers.image.source="https://g.starshard.space/steffo/threadkiller"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: threadkiller
3
- Version: 3.0.3
3
+ Version: 4.0.0
4
4
  Summary: Telegram bot to delete messages sent outside of threads
5
5
  Project-URL: Source, https://g.starshard.space/steffo/threadkiller
6
6
  Project-URL: Issues, https://g.starshard.space/steffo/threadkiller/issues
@@ -23,4 +23,6 @@ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Communications :: Chat
24
24
  Requires-Python: >=3.14
25
25
  Requires-Dist: cfig[cli]>=0.3.10
26
+ Requires-Dist: coloredlogs>=15.0.1
27
+ Requires-Dist: fluent-runtime
26
28
  Requires-Dist: python-telegram-bot>=22.8
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "threadkiller"
3
- version = "3.0.3"
3
+ version = "4.0.0"
4
4
  description = "Telegram bot to delete messages sent outside of threads"
5
5
  authors = [
6
6
  { name = "Stefano Pigozzi", email = "me@steffo.eu" },
@@ -33,6 +33,8 @@ requires-python = ">=3.14"
33
33
  dependencies = [
34
34
  "python-telegram-bot>=22.8",
35
35
  "cfig[cli]>=0.3.10",
36
+ "fluent-runtime", # From git
37
+ "coloredlogs>=15.0.1",
36
38
  ]
37
39
 
38
40
  [project.scripts]
@@ -49,6 +51,9 @@ Telegram = "https://t.me/the_threadkiller_bot?startgroup=yes"
49
51
  requires = ["hatchling"]
50
52
  build-backend = "hatchling.build"
51
53
 
54
+ [tool.uv.sources]
55
+ fluent-runtime = { git = "https://github.com/projectfluent/python-fluent", subdirectory = "fluent.runtime" }
56
+
52
57
  [[tool.uv.index]]
53
58
  name = "pypi"
54
59
  url = "https://upload.pypi.org/simple/"
@@ -0,0 +1,36 @@
1
+ from asyncio import new_event_loop, set_event_loop
2
+ from logging import getLogger
3
+
4
+ from threadkiller.application import make_application
5
+ from .config import config, TG_BOT_TOKEN
6
+ from .log import install_logging
7
+ from .metadata import set_commands
8
+
9
+ log = getLogger(__name__)
10
+
11
+
12
+ def main():
13
+ log.debug("Verifying configuration")
14
+ config.proxies.resolve()
15
+
16
+ log.debug("Initializing logging")
17
+ install_logging()
18
+
19
+ log.info("Initializing")
20
+
21
+ log.debug("Creating event loop")
22
+ loop = new_event_loop()
23
+ set_event_loop(loop)
24
+
25
+ log.debug("Creating application")
26
+ application = make_application(token=TG_BOT_TOKEN.__wrapped__)
27
+
28
+ log.debug("Registering commands")
29
+ loop.run_until_complete(set_commands(bot=application.bot))
30
+
31
+ log.info("Initializing updater")
32
+ application.run_polling()
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
@@ -0,0 +1,144 @@
1
+ import logging
2
+
3
+ from telegram.ext import ApplicationBuilder, MessageHandler, CommandHandler, Application, filters
4
+
5
+ from . import handlers
6
+
7
+ log = logging.getLogger(__name__)
8
+
9
+
10
+ def make_application(token: str) -> Application:
11
+ """
12
+ Create the :class:`telegram.ext.Application` object powering the bot, and register all commands to it.
13
+
14
+ :param token: The bot token to use to authenticate to Telegram.
15
+ """
16
+ log.debug("Creating application")
17
+ application: Application = ApplicationBuilder().token(token).build()
18
+
19
+ log.debug("Registering start command for private chats")
20
+ # noinspection bad-argument-type
21
+ application.add_handler(
22
+ CommandHandler(
23
+ callback=handlers.on_command_start_private,
24
+ command="start",
25
+ filters=filters.ChatType.PRIVATE,
26
+ block=False,
27
+ )
28
+ )
29
+
30
+ log.debug("Registering start command for group chats")
31
+ # noinspection bad-argument-type
32
+ application.add_handler(
33
+ CommandHandler(
34
+ callback=handlers.on_command_start_group,
35
+ command="start",
36
+ filters=filters.ChatType.GROUPS,
37
+ block=False,
38
+ )
39
+ )
40
+
41
+ log.debug("Registering addtogroup command")
42
+ # noinspection bad-argument-type
43
+ application.add_handler(
44
+ CommandHandler(
45
+ callback=handlers.on_command_addtogroup,
46
+ command="addtogroup",
47
+ filters=filters.ChatType.PRIVATE,
48
+ block=False,
49
+ )
50
+ )
51
+
52
+ log.debug("Registering help command")
53
+ # noinspection bad-argument-type
54
+ application.add_handler(
55
+ CommandHandler(
56
+ callback=handlers.on_command_help,
57
+ command="help",
58
+ filters=filters.ChatType.PRIVATE,
59
+ block=False,
60
+ )
61
+ )
62
+
63
+ log.debug("Registering settings command")
64
+ # noinspection bad-argument-type
65
+ application.add_handler(
66
+ CommandHandler(
67
+ callback=handlers.on_command_settings,
68
+ command="settings",
69
+ filters=filters.ChatType.PRIVATE,
70
+ block=False,
71
+ )
72
+ )
73
+
74
+ log.debug("Registering settings command")
75
+ # noinspection bad-argument-type
76
+ application.add_handler(
77
+ CommandHandler(
78
+ callback=handlers.on_command_settings,
79
+ command="settings",
80
+ filters=filters.ChatType.PRIVATE,
81
+ block=False,
82
+ )
83
+ )
84
+
85
+ log.debug("Registering support command")
86
+ # noinspection bad-argument-type
87
+ application.add_handler(
88
+ CommandHandler(
89
+ callback=handlers.on_command_support,
90
+ command="support",
91
+ filters=filters.ChatType.PRIVATE,
92
+ block=False,
93
+ )
94
+ )
95
+
96
+ log.debug("Registering version command")
97
+ # noinspection bad-argument-type
98
+ application.add_handler(
99
+ CommandHandler(
100
+ callback=handlers.on_command_version,
101
+ command="version",
102
+ filters=filters.ChatType.PRIVATE,
103
+ block=False,
104
+ )
105
+ )
106
+
107
+ log.debug("Registering privacy command")
108
+ # noinspection bad-argument-type
109
+ application.add_handler(
110
+ CommandHandler(
111
+ callback=handlers.on_command_privacy,
112
+ command="privacy",
113
+ filters=filters.ChatType.PRIVATE,
114
+ block=False,
115
+ )
116
+ )
117
+
118
+ log.debug("Registering donate command")
119
+ # noinspection bad-argument-type
120
+ application.add_handler(
121
+ CommandHandler(
122
+ callback=handlers.on_command_donate,
123
+ command="donate",
124
+ filters=filters.ChatType.PRIVATE,
125
+ block=False,
126
+ )
127
+ )
128
+
129
+ log.debug("Registering group message handler")
130
+ # noinspection bad-argument-type
131
+ application.add_handler(
132
+ MessageHandler(
133
+ callback=handlers.on_message_group,
134
+ filters=~filters.REPLY & filters.ChatType.GROUPS & ~filters.User(user_id=777000) & ~filters.StatusUpdate.ALL & ~filters.COMMAND,
135
+ block=False,
136
+ )
137
+ )
138
+
139
+ return application
140
+
141
+
142
+ __all__ = (
143
+ "make_application",
144
+ )
@@ -0,0 +1,83 @@
1
+ import cfig
2
+
3
+ config = cfig.Configuration()
4
+
5
+
6
+ @config.required()
7
+ def TG_BOT_TOKEN(val: str) -> str:
8
+ """
9
+ Token used to authenticate as a Telegram bot.
10
+
11
+ Obtain one at https://t.me/BotFather .
12
+ """
13
+ return val
14
+
15
+
16
+ @config.optional()
17
+ def SUPPORT_GROUP_LINK(val: str | None) -> str | None:
18
+ """
19
+ Invite link to the bot's support group.
20
+
21
+ Displayed in /support.
22
+ """
23
+ return val
24
+
25
+
26
+ @config.optional()
27
+ def SUPPORT_EMAIL_RAW(val: str | None) -> str | None:
28
+ """
29
+ Email of the bot's owner, without `mailto:` prefix.
30
+
31
+ Displayed in /support.
32
+ """
33
+ return val
34
+
35
+
36
+ @config.optional()
37
+ def SUPPORT_OWNER_LINK(val: str | None) -> str | None:
38
+ """
39
+ t.me link of the bot's owner.
40
+
41
+ Displayed in /support.
42
+ """
43
+ return val
44
+
45
+
46
+ @config.optional()
47
+ def LINK_SOURCE(val: str | None) -> str:
48
+ """
49
+ Link to the bot's source code.
50
+
51
+ Displayed in /version.
52
+ """
53
+ if val is None:
54
+ return "https://g.starshard.space/steffo/threadkiller"
55
+ else:
56
+ return val
57
+
58
+
59
+ @config.optional()
60
+ def LINK_DONATE(val: str | None) -> str:
61
+ """
62
+ Link to the bot's source code.
63
+
64
+ Displayed in /version.
65
+ """
66
+ if val is None:
67
+ return "https://liberapay.com/Steffo/"
68
+ else:
69
+ return val
70
+
71
+
72
+ __all__ = (
73
+ "config",
74
+ "TG_BOT_TOKEN",
75
+ "SUPPORT_GROUP_LINK",
76
+ "SUPPORT_EMAIL_RAW",
77
+ "SUPPORT_OWNER_LINK",
78
+ "LINK_SOURCE",
79
+ "LINK_DONATE",
80
+ )
81
+
82
+ if __name__ == "__main__":
83
+ config.cli()
@@ -0,0 +1,305 @@
1
+ from importlib.metadata import version
2
+ from logging import getLogger
3
+
4
+ from fluent.runtime import FluentLocalization
5
+ from telegram import Update, Chat, User, Message, error
6
+ from telegram.constants import ParseMode
7
+ from telegram.ext import CallbackContext
8
+
9
+ from .config import SUPPORT_EMAIL_RAW, SUPPORT_GROUP_LINK, SUPPORT_OWNER_LINK, LINK_DONATE, LINK_SOURCE
10
+ from .locale import localize
11
+
12
+ log = getLogger(__name__)
13
+
14
+
15
+ @localize
16
+ async def on_command_start_private(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
17
+ """
18
+ Handler for the /start command.
19
+ """
20
+ log.debug("Handling /start in private chat")
21
+
22
+ # This invariant is covered by the application, before the handler is called
23
+ chat = update.effective_chat
24
+ assert isinstance(chat, Chat)
25
+
26
+ await chat.send_message(
27
+ text=locale.format_value("command-start-private-success", {
28
+ "bot-name": context.bot.username,
29
+ }),
30
+ parse_mode=ParseMode.HTML,
31
+ )
32
+
33
+
34
+ @localize
35
+ async def on_command_start_group(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
36
+ """
37
+ Handler for the /start command.
38
+ """
39
+ log.debug("Handling /start in group")
40
+
41
+ # These invariants are covered by the application, before the handler is called
42
+ chat = update.effective_chat
43
+ message = update.effective_message
44
+ sender = update.effective_sender
45
+ assert isinstance(chat, Chat)
46
+ assert isinstance(message, Message)
47
+ assert isinstance(sender, User)
48
+
49
+ await chat.send_message(
50
+ text=locale.format_value("command-start-group-success", {
51
+ "bot-name": context.bot.username,
52
+ "url-donate": LINK_DONATE.__wrapped__,
53
+ }),
54
+ parse_mode=ParseMode.HTML,
55
+ )
56
+
57
+
58
+ @localize
59
+ async def on_command_addtogroup(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
60
+ """
61
+ Handler for the /addtogroup command.
62
+ """
63
+ log.debug("Handling /addtogroup")
64
+
65
+ # These invariants are covered by the application, before the handler is called
66
+ chat = update.effective_chat
67
+ message = update.effective_message
68
+ sender = update.effective_sender
69
+ assert isinstance(chat, Chat)
70
+ assert isinstance(message, Message)
71
+ assert isinstance(sender, User)
72
+
73
+ await chat.send_message(
74
+ text=locale.format_value("command-addtogroup-success", {
75
+ "bot-name": context.bot.username,
76
+ "url-notify": f"https://t.me/{context.bot.username}?startgroup=yes&admin=anonymous",
77
+ "url-delete": f"https://t.me/{context.bot.username}?startgroup=yes&admin=anonymous+delete_messages",
78
+ }),
79
+ parse_mode=ParseMode.HTML,
80
+ )
81
+
82
+
83
+ @localize
84
+ async def on_command_help(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
85
+ """
86
+ Handler for the /help command.
87
+ """
88
+ log.debug("Handling /help")
89
+
90
+ # These invariants are covered by the application, before the handler is called
91
+ chat = update.effective_chat
92
+ message = update.effective_message
93
+ sender = update.effective_sender
94
+ assert isinstance(chat, Chat)
95
+ assert isinstance(message, Message)
96
+ assert isinstance(sender, User)
97
+
98
+ await chat.send_message(
99
+ text=locale.format_value("command-help-success", {
100
+ "bot-name": context.bot.username,
101
+ }),
102
+ parse_mode=ParseMode.HTML,
103
+ )
104
+
105
+
106
+ @localize
107
+ async def on_command_settings(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
108
+ """
109
+ Handler for the /settings command.
110
+ """
111
+ log.debug("Handling /settings")
112
+
113
+ # These invariants are covered by the application, before the handler is called
114
+ chat = update.effective_chat
115
+ message = update.effective_message
116
+ sender = update.effective_sender
117
+ assert isinstance(chat, Chat)
118
+ assert isinstance(message, Message)
119
+ assert isinstance(sender, User)
120
+
121
+ await chat.send_message(
122
+ text=locale.format_value("command-settings-success", {
123
+ "bot-name": context.bot.username,
124
+ }),
125
+ parse_mode=ParseMode.HTML,
126
+ )
127
+
128
+
129
+ @localize
130
+ async def on_command_support(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
131
+ """
132
+ Handler for the /support command.
133
+ """
134
+ log.debug("Handling /support")
135
+
136
+ # These invariants are covered by the application, before the handler is called
137
+ chat = update.effective_chat
138
+ message = update.effective_message
139
+ sender = update.effective_sender
140
+ assert isinstance(chat, Chat)
141
+ assert isinstance(message, Message)
142
+ assert isinstance(sender, User)
143
+
144
+ fluent_message = locale.format_message("command-support-success", {
145
+ "bot-name": context.bot.username,
146
+ "bot-group": SUPPORT_GROUP_LINK.__wrapped__ if SUPPORT_GROUP_LINK is not None else None,
147
+ "bot-email": SUPPORT_EMAIL_RAW.__wrapped__ if SUPPORT_EMAIL_RAW is not None else None,
148
+ "bot-owner": SUPPORT_OWNER_LINK.__wrapped__ if SUPPORT_OWNER_LINK is not None else None,
149
+ })
150
+
151
+ text = fluent_message.value
152
+ assert text is not None
153
+
154
+ if SUPPORT_GROUP_LINK:
155
+ text += "\n"
156
+ text += fluent_message.attributes["group"]
157
+
158
+ if SUPPORT_EMAIL_RAW:
159
+ text += "\n"
160
+ text += fluent_message.attributes["email"]
161
+
162
+ if SUPPORT_OWNER_LINK:
163
+ text += "\n"
164
+ text += fluent_message.attributes["owner"]
165
+
166
+ if not (SUPPORT_GROUP_LINK or SUPPORT_EMAIL_RAW or SUPPORT_OWNER_LINK):
167
+ text += "\n"
168
+ text += fluent_message.attributes["none"]
169
+
170
+ await chat.send_message(
171
+ text=text,
172
+ parse_mode=ParseMode.HTML,
173
+ )
174
+
175
+
176
+ @localize
177
+ async def on_command_version(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
178
+ """
179
+ Handler for the /version command.
180
+ """
181
+ log.debug("Handling /version")
182
+
183
+ # These invariants are covered by the application, before the handler is called
184
+ chat = update.effective_chat
185
+ message = update.effective_message
186
+ sender = update.effective_sender
187
+ assert isinstance(chat, Chat)
188
+ assert isinstance(message, Message)
189
+ assert isinstance(sender, User)
190
+
191
+ bot_software = "threadkiller"
192
+ bot_version = version(bot_software)
193
+
194
+ await chat.send_message(
195
+ text=locale.format_value("command-version-success", {
196
+ "bot-name": context.bot.username,
197
+ "bot-software": bot_software,
198
+ "bot-version": bot_version,
199
+ "url-source": LINK_SOURCE.__wrapped__,
200
+ }),
201
+ parse_mode=ParseMode.HTML,
202
+ )
203
+
204
+
205
+ @localize
206
+ async def on_command_privacy(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
207
+ """
208
+ Handler for the /privacy command.
209
+ """
210
+ log.debug("Handling /privacy")
211
+
212
+ # These invariants are covered by the application, before the handler is called
213
+ chat = update.effective_chat
214
+ message = update.effective_message
215
+ sender = update.effective_sender
216
+ assert isinstance(chat, Chat)
217
+ assert isinstance(message, Message)
218
+ assert isinstance(sender, User)
219
+
220
+ await chat.send_message(
221
+ text=locale.format_value("command-privacy-success", {
222
+ "bot-name": context.bot.username,
223
+ "url-telegram-api": "https://core.telegram.org/bots",
224
+ }),
225
+ parse_mode=ParseMode.HTML,
226
+ )
227
+
228
+
229
+ @localize
230
+ async def on_command_donate(update: Update, context: CallbackContext, *, locale: FluentLocalization) -> None:
231
+ """
232
+ Handler for the /donate command.
233
+ """
234
+ log.debug("Handling /donate")
235
+
236
+ # These invariants are covered by the application, before the handler is called
237
+ chat = update.effective_chat
238
+ message = update.effective_message
239
+ sender = update.effective_sender
240
+ assert isinstance(chat, Chat)
241
+ assert isinstance(message, Message)
242
+ assert isinstance(sender, User)
243
+
244
+ await chat.send_message(
245
+ text=locale.format_value("command-donate-success", {
246
+ "bot-name": context.bot.username,
247
+ "url-donate": LINK_DONATE.__wrapped__,
248
+ }),
249
+ parse_mode=ParseMode.HTML,
250
+ )
251
+
252
+
253
+ @localize
254
+ async def on_message_group(update: Update, context: CallbackContext, locale: FluentLocalization) -> None:
255
+ """
256
+ Handler for any message received in a group.
257
+ """
258
+ log.debug("Handling group message")
259
+
260
+ # These invariants are covered by the application, before the handler is called
261
+ chat = update.effective_chat
262
+ message = update.effective_message
263
+ sender = update.effective_sender
264
+ assert isinstance(chat, Chat)
265
+ assert isinstance(message, Message)
266
+ assert isinstance(sender, User)
267
+
268
+ await message.copy(
269
+ chat_id=chat.id,
270
+ api_kwargs=dict(
271
+ ephemeral_message_parameters=dict(
272
+ receiver_user_id=sender.id,
273
+ )
274
+ ),
275
+ )
276
+
277
+ try:
278
+ await message.delete()
279
+ except (error.Forbidden, error.BadRequest):
280
+ text = locale.format_value("message-notified")
281
+ else:
282
+ text = locale.format_value("message-deleted")
283
+
284
+ await chat.send_message(
285
+ text=text,
286
+ parse_mode=ParseMode.HTML,
287
+ api_kwargs=dict(
288
+ ephemeral_message_parameters=dict(
289
+ receiver_user_id=sender.id,
290
+ )
291
+ ),
292
+ )
293
+
294
+
295
+ __all__ = (
296
+ "on_command_start_private",
297
+ "on_command_addtogroup",
298
+ "on_command_help",
299
+ "on_command_settings",
300
+ "on_command_support",
301
+ "on_command_version",
302
+ "on_command_privacy",
303
+ "on_command_donate",
304
+ "on_message_group",
305
+ )