DiscordBotLinuxMonitor 1.6.1__tar.gz → 1.6.3__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.
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/DiscordBotLinuxMonitor.egg-info/PKG-INFO +5 -1
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/PKG-INFO +5 -1
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/README.md +4 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/discordbotlinuxmonitor/__main__.py +8 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/discordbotlinuxmonitor/discordbotlinuxmonitor.py +210 -8
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/setup.py +1 -1
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/DiscordBotLinuxMonitor.egg-info/SOURCES.txt +0 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/DiscordBotLinuxMonitor.egg-info/dependency_links.txt +0 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/DiscordBotLinuxMonitor.egg-info/requires.txt +0 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/DiscordBotLinuxMonitor.egg-info/top_level.txt +0 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/LICENSE.md +0 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/discordbotlinuxmonitor/__init__.py +0 -0
- {discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DiscordBotLinuxMonitor
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.3
|
|
4
4
|
Summary: From discord channels: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)
|
|
5
5
|
Home-page: https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library
|
|
6
6
|
Author: Quentin Comte-Gaz
|
|
@@ -98,6 +98,8 @@ Displayed infos are not the same if you do the command in private or public chan
|
|
|
98
98
|
|
|
99
99
|
- `/list_commands`: 📋 List all available commands 📋
|
|
100
100
|
|
|
101
|
+
- `/version`: 🤖 Show current bot version 🤖
|
|
102
|
+
|
|
101
103
|
- `/execute_command {command_name} [parameters]`: 🚀 Execute a command (optional parameters, only for commands with `accept_parameters` or a `{arg1}`/`{args}` placeholder) 🚀
|
|
102
104
|
|
|
103
105
|
- `/execute_all_commands`: 🚀 Execute all commands 🚀
|
|
@@ -121,6 +123,8 @@ Displayed infos are not the same if you do the command in private or public chan
|
|
|
121
123
|
- `/kill_process`: 🚫 Stop a process by PID 🚫
|
|
122
124
|
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/kill_process.jpg" height="200">
|
|
123
125
|
|
|
126
|
+
- `/clear_channel_messages {channel}`: 🧹 Remove all messages from a target channel (can clean any channel, command itself is private-only) 🧹
|
|
127
|
+
|
|
124
128
|
## How to install (for first launch)
|
|
125
129
|
|
|
126
130
|
- Install package calling `python -m pip install discordbotlinuxmonitor` (or `python setup.py install` from the root of this repository)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DiscordBotLinuxMonitor
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.3
|
|
4
4
|
Summary: From discord channels: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)
|
|
5
5
|
Home-page: https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library
|
|
6
6
|
Author: Quentin Comte-Gaz
|
|
@@ -98,6 +98,8 @@ Displayed infos are not the same if you do the command in private or public chan
|
|
|
98
98
|
|
|
99
99
|
- `/list_commands`: 📋 List all available commands 📋
|
|
100
100
|
|
|
101
|
+
- `/version`: 🤖 Show current bot version 🤖
|
|
102
|
+
|
|
101
103
|
- `/execute_command {command_name} [parameters]`: 🚀 Execute a command (optional parameters, only for commands with `accept_parameters` or a `{arg1}`/`{args}` placeholder) 🚀
|
|
102
104
|
|
|
103
105
|
- `/execute_all_commands`: 🚀 Execute all commands 🚀
|
|
@@ -121,6 +123,8 @@ Displayed infos are not the same if you do the command in private or public chan
|
|
|
121
123
|
- `/kill_process`: 🚫 Stop a process by PID 🚫
|
|
122
124
|
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/kill_process.jpg" height="200">
|
|
123
125
|
|
|
126
|
+
- `/clear_channel_messages {channel}`: 🧹 Remove all messages from a target channel (can clean any channel, command itself is private-only) 🧹
|
|
127
|
+
|
|
124
128
|
## How to install (for first launch)
|
|
125
129
|
|
|
126
130
|
- Install package calling `python -m pip install discordbotlinuxmonitor` (or `python setup.py install` from the root of this repository)
|
|
@@ -56,6 +56,8 @@ Displayed infos are not the same if you do the command in private or public chan
|
|
|
56
56
|
|
|
57
57
|
- `/list_commands`: 📋 List all available commands 📋
|
|
58
58
|
|
|
59
|
+
- `/version`: 🤖 Show current bot version 🤖
|
|
60
|
+
|
|
59
61
|
- `/execute_command {command_name} [parameters]`: 🚀 Execute a command (optional parameters, only for commands with `accept_parameters` or a `{arg1}`/`{args}` placeholder) 🚀
|
|
60
62
|
|
|
61
63
|
- `/execute_all_commands`: 🚀 Execute all commands 🚀
|
|
@@ -79,6 +81,8 @@ Displayed infos are not the same if you do the command in private or public chan
|
|
|
79
81
|
- `/kill_process`: 🚫 Stop a process by PID 🚫
|
|
80
82
|
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/kill_process.jpg" height="200">
|
|
81
83
|
|
|
84
|
+
- `/clear_channel_messages {channel}`: 🧹 Remove all messages from a target channel (can clean any channel, command itself is private-only) 🧹
|
|
85
|
+
|
|
82
86
|
## How to install (for first launch)
|
|
83
87
|
|
|
84
88
|
- Install package calling `python -m pip install discordbotlinuxmonitor` (or `python setup.py install` from the root of this repository)
|
{discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/discordbotlinuxmonitor/__main__.py
RENAMED
|
@@ -54,6 +54,10 @@ def main() -> None:
|
|
|
54
54
|
async def force_sync(interaction: discord.Interaction) -> None: # type: ignore
|
|
55
55
|
await discord_bot_linux_monitor.force_sync(interaction)
|
|
56
56
|
|
|
57
|
+
@discord_bot.tree.command(name="version", description="🤖 Show bot version 🤖")
|
|
58
|
+
async def version(interaction: discord.Interaction) -> None: # type: ignore
|
|
59
|
+
await discord_bot_linux_monitor.version(interaction)
|
|
60
|
+
|
|
57
61
|
@discord_bot.tree.command(name="usage", description="📊 View disk space, CPU, RAM, ... 📊")
|
|
58
62
|
async def usage(interaction: discord.Interaction) -> None: # type: ignore
|
|
59
63
|
await discord_bot_linux_monitor.usage(interaction)
|
|
@@ -122,6 +126,10 @@ def main() -> None:
|
|
|
122
126
|
async def kill_process(interaction: discord.Interaction, pid: int) -> None: # type: ignore
|
|
123
127
|
await discord_bot_linux_monitor.kill_process(interaction, pid)
|
|
124
128
|
|
|
129
|
+
@discord_bot.tree.command(name="clear_channel_messages", description="[Private] 🧹 Remove all messages from a channel 🧹")
|
|
130
|
+
async def clear_channel_messages(interaction: discord.Interaction, channel: discord.TextChannel) -> None: # type: ignore
|
|
131
|
+
await discord_bot_linux_monitor.clear_channel_messages(interaction, channel)
|
|
132
|
+
|
|
125
133
|
@discord_bot.tree.command(name="list_commands", description="📋 List all available commands 📋")
|
|
126
134
|
async def list_commands(interaction: discord.Interaction) -> None: # type: ignore
|
|
127
135
|
await discord_bot_linux_monitor.list_commands(interaction)
|
|
@@ -33,7 +33,7 @@ __email__ = "quentin@comte-gaz.com"
|
|
|
33
33
|
__license__ = "MIT License"
|
|
34
34
|
__copyright__ = "Copyright Quentin Comte-Gaz (2026)"
|
|
35
35
|
__python_version__ = "3.+"
|
|
36
|
-
__version__ = "1.6.
|
|
36
|
+
__version__ = "1.6.3 (2026/08/24)"
|
|
37
37
|
__status__ = "Usable for any Linux project"
|
|
38
38
|
|
|
39
39
|
# pyright: reportMissingTypeStubs=false
|
|
@@ -43,7 +43,8 @@ import discord
|
|
|
43
43
|
from discord.app_commands.models import AppCommand
|
|
44
44
|
from discord.ext import commands
|
|
45
45
|
import json
|
|
46
|
-
from typing import List, Union, Awaitable, Callable
|
|
46
|
+
from typing import List, Union, Awaitable, Callable, Any, Dict, Optional
|
|
47
|
+
from datetime import datetime, timedelta, timezone
|
|
47
48
|
|
|
48
49
|
import asyncio
|
|
49
50
|
|
|
@@ -285,6 +286,77 @@ class DiscordBotLinuxMonitor:
|
|
|
285
286
|
logging.exception(msg=out_msg)
|
|
286
287
|
return out_msg
|
|
287
288
|
|
|
289
|
+
def _welcome_message_with_version(self, welcome_message: str) -> str:
|
|
290
|
+
return f"{welcome_message}\n\n🤖 Bot version: {__version__}"
|
|
291
|
+
|
|
292
|
+
def _get_rate_limit_wait_seconds(self, error: discord.HTTPException, fallback_seconds: float = 1.5) -> float:
|
|
293
|
+
retry_after = getattr(error, "retry_after", None)
|
|
294
|
+
if isinstance(retry_after, (int, float)) and retry_after > 0:
|
|
295
|
+
return float(retry_after)
|
|
296
|
+
|
|
297
|
+
response = getattr(error, "response", None)
|
|
298
|
+
if response is not None:
|
|
299
|
+
headers = getattr(response, "headers", None)
|
|
300
|
+
if headers is not None:
|
|
301
|
+
header_retry_after = headers.get("Retry-After")
|
|
302
|
+
if header_retry_after is not None:
|
|
303
|
+
try:
|
|
304
|
+
parsed_retry_after = float(header_retry_after)
|
|
305
|
+
if parsed_retry_after > 0:
|
|
306
|
+
return parsed_retry_after
|
|
307
|
+
except (ValueError, TypeError):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
return fallback_seconds
|
|
311
|
+
|
|
312
|
+
async def _delete_message_with_rate_limit_retry(self, message: discord.Message, reason: str, max_retries: int = 10, on_rate_limit: Optional[Callable[[], None]] = None) -> None:
|
|
313
|
+
for attempt in range(max_retries + 1):
|
|
314
|
+
try:
|
|
315
|
+
try:
|
|
316
|
+
await message.delete(reason=reason)
|
|
317
|
+
except TypeError:
|
|
318
|
+
# Some discord.py objects (e.g. PartialMessage) do not accept `reason`.
|
|
319
|
+
await message.delete()
|
|
320
|
+
return
|
|
321
|
+
except discord.NotFound:
|
|
322
|
+
return
|
|
323
|
+
except discord.HTTPException as e:
|
|
324
|
+
if e.status == 429 and attempt < max_retries:
|
|
325
|
+
if on_rate_limit is not None:
|
|
326
|
+
on_rate_limit()
|
|
327
|
+
wait_seconds = self._get_rate_limit_wait_seconds(error=e)
|
|
328
|
+
logging.warning(msg=f"Rate limited while deleting message {message.id}, waiting {wait_seconds:.2f}s before retry...")
|
|
329
|
+
await asyncio.sleep(wait_seconds)
|
|
330
|
+
continue
|
|
331
|
+
raise
|
|
332
|
+
|
|
333
|
+
async def _bulk_delete_messages_with_rate_limit_retry(self, channel: discord.TextChannel, messages: List[discord.Message], reason: str, max_retries: int = 10, on_rate_limit: Optional[Callable[[], None]] = None) -> None:
|
|
334
|
+
if len(messages) == 0:
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
for attempt in range(max_retries + 1):
|
|
338
|
+
try:
|
|
339
|
+
if len(messages) == 1:
|
|
340
|
+
try:
|
|
341
|
+
await messages[0].delete(reason=reason)
|
|
342
|
+
except TypeError:
|
|
343
|
+
# Some discord.py objects (e.g. PartialMessage) do not accept `reason`.
|
|
344
|
+
await messages[0].delete()
|
|
345
|
+
else:
|
|
346
|
+
await channel.delete_messages(messages, reason=reason)
|
|
347
|
+
return
|
|
348
|
+
except discord.NotFound:
|
|
349
|
+
return
|
|
350
|
+
except discord.HTTPException as e:
|
|
351
|
+
if e.status == 429 and attempt < max_retries:
|
|
352
|
+
if on_rate_limit is not None:
|
|
353
|
+
on_rate_limit()
|
|
354
|
+
wait_seconds = self._get_rate_limit_wait_seconds(error=e)
|
|
355
|
+
logging.warning(msg=f"Rate limited while bulk deleting {len(messages)} messages in '{channel.name}', waiting {wait_seconds:.2f}s before retry...")
|
|
356
|
+
await asyncio.sleep(wait_seconds)
|
|
357
|
+
continue
|
|
358
|
+
raise
|
|
359
|
+
|
|
288
360
|
#endregion
|
|
289
361
|
|
|
290
362
|
#region BOT COMMANDS AND EVENTS DEFINITIONS
|
|
@@ -309,20 +381,20 @@ class DiscordBotLinuxMonitor:
|
|
|
309
381
|
logging.info(msg="Found the public channel, it will be possible to do public commands.")
|
|
310
382
|
public_channel_cmd_ready = True
|
|
311
383
|
if self.welcome_message_for_public_commands != "":
|
|
312
|
-
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_public_commands)
|
|
384
|
+
await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_public_commands))
|
|
313
385
|
|
|
314
386
|
if self.channel_name_for_private_commands != "" and channel.name == self.channel_name_for_private_commands:
|
|
315
387
|
logging.info(msg="Found the private channel, it will be possible to do private commands.")
|
|
316
388
|
private_channel_cmd_ready = True
|
|
317
389
|
if self.welcome_message_for_private_commands != "":
|
|
318
|
-
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_private_commands)
|
|
390
|
+
await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_private_commands))
|
|
319
391
|
|
|
320
392
|
if self.channel_name_for_public_error_tasks != "" and channel.name == self.channel_name_for_public_error_tasks:
|
|
321
393
|
logging.info(msg="Found the public channel for error task, it will be possible to show public status issues if found periodically.")
|
|
322
394
|
public_channel_error_task_ready = True
|
|
323
395
|
|
|
324
396
|
if self.welcome_message_for_public_error_tasks != "":
|
|
325
|
-
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_public_error_tasks)
|
|
397
|
+
await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_public_error_tasks))
|
|
326
398
|
|
|
327
399
|
logging.info(msg=f"Activating automatic public follow and public service restart if down with '{self.bot.user}' and guild '{guild.name}' (id: '{guild.id}') on channel '{channel.name}' (id '{channel.id}').")
|
|
328
400
|
public_channel_for_error_task: discord.TextChannel = channel
|
|
@@ -336,7 +408,7 @@ class DiscordBotLinuxMonitor:
|
|
|
336
408
|
public_channel_info_task_ready = True
|
|
337
409
|
|
|
338
410
|
if self.welcome_message_for_public_infos_tasks != "":
|
|
339
|
-
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_public_infos_tasks)
|
|
411
|
+
await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_public_infos_tasks))
|
|
340
412
|
|
|
341
413
|
logging.info(msg=f"Activating automatic public follow info with '{self.bot.user}' and guild '{guild.name}' (id: '{guild.id}') on channel '{channel.name}' (id '{channel.id}').")
|
|
342
414
|
public_channel_for_info_task: discord.TextChannel = channel
|
|
@@ -350,7 +422,7 @@ class DiscordBotLinuxMonitor:
|
|
|
350
422
|
private_channel_error_task_ready = True
|
|
351
423
|
|
|
352
424
|
if self.welcome_message_for_private_error_tasks != "":
|
|
353
|
-
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_private_error_tasks)
|
|
425
|
+
await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_private_error_tasks))
|
|
354
426
|
|
|
355
427
|
logging.info(msg=f"Activating automatic private follow and public service restart if down with '{self.bot.user}' and guild '{guild.name}' (id: '{guild.id}') on channel '{channel.name}' (id '{channel.id}').")
|
|
356
428
|
private_channel_for_error_task: discord.TextChannel = channel
|
|
@@ -364,7 +436,7 @@ class DiscordBotLinuxMonitor:
|
|
|
364
436
|
private_channel_info_task_ready = True
|
|
365
437
|
|
|
366
438
|
if self.welcome_message_for_private_infos_tasks != "":
|
|
367
|
-
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_private_infos_tasks)
|
|
439
|
+
await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_private_infos_tasks))
|
|
368
440
|
|
|
369
441
|
logging.info(msg=f"Activating automatic private follow info with '{self.bot.user}' and guild '{guild.name}' (id: '{guild.id}') on channel '{channel.name}' (id '{channel.id}').")
|
|
370
442
|
private_channel_for_info_task: discord.TextChannel = channel
|
|
@@ -425,6 +497,18 @@ class DiscordBotLinuxMonitor:
|
|
|
425
497
|
# Respond to the user
|
|
426
498
|
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
427
499
|
|
|
500
|
+
async def version(self, interaction: discord.Interaction) -> None:
|
|
501
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
502
|
+
return
|
|
503
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
504
|
+
return
|
|
505
|
+
|
|
506
|
+
out_msg: str = (
|
|
507
|
+
f"🤖 Bot version: {__version__}\n"
|
|
508
|
+
f"🐍 Python compatibility: {__python_version__}"
|
|
509
|
+
)
|
|
510
|
+
await interaction.response.send_message(content=out_msg, ephemeral=True)
|
|
511
|
+
|
|
428
512
|
async def usage(self, interaction: discord.Interaction) -> None:
|
|
429
513
|
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
430
514
|
return
|
|
@@ -783,6 +867,124 @@ class DiscordBotLinuxMonitor:
|
|
|
783
867
|
logging.exception(msg=out_msg)
|
|
784
868
|
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
785
869
|
|
|
870
|
+
async def clear_channel_messages(self, interaction: discord.Interaction, channel: discord.TextChannel) -> None:
|
|
871
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
872
|
+
return
|
|
873
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
874
|
+
return
|
|
875
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
876
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
877
|
+
return
|
|
878
|
+
|
|
879
|
+
# Make sure only authorized users can trigger a destructive command.
|
|
880
|
+
permissions = channel.permissions_for(interaction.user) # type: ignore
|
|
881
|
+
if not permissions.manage_messages:
|
|
882
|
+
await interaction.response.send_message(content=f"❌ You need 'Manage Messages' permission on channel '{channel.name}' to clear it.", ephemeral=True)
|
|
883
|
+
return
|
|
884
|
+
|
|
885
|
+
bot_member = interaction.guild.me if interaction.guild is not None else None # type: ignore
|
|
886
|
+
if bot_member is None:
|
|
887
|
+
await interaction.response.send_message(content="❌ Unable to resolve bot permissions.", ephemeral=True)
|
|
888
|
+
return
|
|
889
|
+
|
|
890
|
+
bot_permissions = channel.permissions_for(bot_member)
|
|
891
|
+
if not bot_permissions.manage_messages or not bot_permissions.read_message_history:
|
|
892
|
+
await interaction.response.send_message(content=f"❌ Bot is missing required permissions on channel '{channel.name}' (Manage Messages and Read Message History).", ephemeral=True)
|
|
893
|
+
return
|
|
894
|
+
|
|
895
|
+
await interaction.response.defer(ephemeral=True)
|
|
896
|
+
|
|
897
|
+
deleted_count: int = 0
|
|
898
|
+
rate_limit_retries: int = 0
|
|
899
|
+
delete_reason: str = f"Requested by {interaction.user} from private channel"
|
|
900
|
+
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
|
|
901
|
+
progress_every_seconds: float = 2.5
|
|
902
|
+
progress_every_deleted_messages: int = 200
|
|
903
|
+
started_monotonic: float = asyncio.get_running_loop().time()
|
|
904
|
+
last_progress_monotonic: float = started_monotonic
|
|
905
|
+
last_reported_deleted_count: int = 0
|
|
906
|
+
|
|
907
|
+
def _on_rate_limit_hit() -> None:
|
|
908
|
+
nonlocal rate_limit_retries
|
|
909
|
+
rate_limit_retries += 1
|
|
910
|
+
|
|
911
|
+
async def _update_progress(force: bool = False) -> None:
|
|
912
|
+
nonlocal last_progress_monotonic
|
|
913
|
+
nonlocal last_reported_deleted_count
|
|
914
|
+
|
|
915
|
+
now = asyncio.get_running_loop().time()
|
|
916
|
+
should_update = force
|
|
917
|
+
if not should_update:
|
|
918
|
+
enough_time_elapsed = (now - last_progress_monotonic) >= progress_every_seconds
|
|
919
|
+
enough_messages_deleted = (deleted_count - last_reported_deleted_count) >= progress_every_deleted_messages
|
|
920
|
+
should_update = enough_time_elapsed or enough_messages_deleted
|
|
921
|
+
|
|
922
|
+
if not should_update:
|
|
923
|
+
return
|
|
924
|
+
|
|
925
|
+
elapsed_seconds = int(now - started_monotonic)
|
|
926
|
+
progress_msg = (
|
|
927
|
+
f"🧹 Cleaning channel '{channel.name}'...\n"
|
|
928
|
+
f"Deleted messages: {deleted_count}\n"
|
|
929
|
+
f"Rate-limit waits: {rate_limit_retries}\n"
|
|
930
|
+
f"Elapsed: {elapsed_seconds}s"
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
try:
|
|
934
|
+
await interaction.edit_original_response(content=progress_msg)
|
|
935
|
+
last_progress_monotonic = now
|
|
936
|
+
last_reported_deleted_count = deleted_count
|
|
937
|
+
except discord.HTTPException as e:
|
|
938
|
+
if e.status == 429:
|
|
939
|
+
_on_rate_limit_hit()
|
|
940
|
+
wait_seconds = self._get_rate_limit_wait_seconds(error=e)
|
|
941
|
+
await asyncio.sleep(wait_seconds)
|
|
942
|
+
else:
|
|
943
|
+
logging.warning(msg=f"Failed to update cleanup progress message: {e}")
|
|
944
|
+
|
|
945
|
+
try:
|
|
946
|
+
await _update_progress(force=True)
|
|
947
|
+
recent_batch: List[discord.Message] = []
|
|
948
|
+
|
|
949
|
+
async for message in channel.history(limit=None, oldest_first=False):
|
|
950
|
+
if message.created_at >= two_weeks_ago:
|
|
951
|
+
recent_batch.append(message)
|
|
952
|
+
|
|
953
|
+
# Bulk delete by chunks of 100 to reduce API calls and rate-limit pressure.
|
|
954
|
+
if len(recent_batch) == 100:
|
|
955
|
+
await self._bulk_delete_messages_with_rate_limit_retry(channel=channel, messages=recent_batch, reason=delete_reason, on_rate_limit=_on_rate_limit_hit)
|
|
956
|
+
deleted_count += len(recent_batch)
|
|
957
|
+
recent_batch = []
|
|
958
|
+
await _update_progress()
|
|
959
|
+
else:
|
|
960
|
+
await self._delete_message_with_rate_limit_retry(message=message, reason=delete_reason, on_rate_limit=_on_rate_limit_hit)
|
|
961
|
+
deleted_count += 1
|
|
962
|
+
await _update_progress()
|
|
963
|
+
|
|
964
|
+
if len(recent_batch) > 0:
|
|
965
|
+
await self._bulk_delete_messages_with_rate_limit_retry(channel=channel, messages=recent_batch, reason=delete_reason, on_rate_limit=_on_rate_limit_hit)
|
|
966
|
+
deleted_count += len(recent_batch)
|
|
967
|
+
await _update_progress()
|
|
968
|
+
|
|
969
|
+
elapsed_seconds = int(asyncio.get_running_loop().time() - started_monotonic)
|
|
970
|
+
out_msg = (
|
|
971
|
+
f"✅ Cleared channel '{channel.name}'.\n"
|
|
972
|
+
f"Deleted messages: {deleted_count}\n"
|
|
973
|
+
f"Rate-limit waits: {rate_limit_retries}\n"
|
|
974
|
+
f"Elapsed: {elapsed_seconds}s"
|
|
975
|
+
)
|
|
976
|
+
await interaction.edit_original_response(content=out_msg)
|
|
977
|
+
except discord.HTTPException as e:
|
|
978
|
+
if e.status == 429:
|
|
979
|
+
_on_rate_limit_hit()
|
|
980
|
+
out_msg = f"**Discord API error while clearing messages in channel '{channel.name}'** (status {e.status}, retries: {rate_limit_retries}):\n```sh\n{e}\n```"
|
|
981
|
+
logging.exception(msg=out_msg)
|
|
982
|
+
await interaction.edit_original_response(content=out_msg)
|
|
983
|
+
except Exception as e:
|
|
984
|
+
out_msg = f"**Internal error while clearing messages in channel '{channel.name}'**:\n```sh\n{e}\n```"
|
|
985
|
+
logging.exception(msg=out_msg)
|
|
986
|
+
await interaction.edit_original_response(content=out_msg)
|
|
987
|
+
|
|
786
988
|
async def list_commands(self, interaction: discord.Interaction) -> None:
|
|
787
989
|
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
788
990
|
return
|
|
@@ -6,7 +6,7 @@ with io.open(file='README.md', mode='r', encoding='utf-8') as readme_file:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="DiscordBotLinuxMonitor",
|
|
9
|
-
version="1.6.
|
|
9
|
+
version="1.6.3",
|
|
10
10
|
description="From discord channels: Get information and warning status of Linux server like service, port, ping, ssl certificate, disk/folder/cpu/ram/swap usage, ip connection, ... (Python and shell library, Linux ONLY)",
|
|
11
11
|
long_description=readme,
|
|
12
12
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{discordbotlinuxmonitor-1.6.1 → discordbotlinuxmonitor-1.6.3}/discordbotlinuxmonitor/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|