DiscordBotLinuxMonitor 1.6.6__tar.gz → 1.6.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DiscordBotLinuxMonitor
3
- Version: 1.6.6
3
+ Version: 1.6.7
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
@@ -22,7 +22,7 @@ Classifier: Environment :: Console
22
22
  Requires-Python: >=3.3
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE.md
25
- Requires-Dist: linuxmonitor~=1.5.8
25
+ Requires-Dist: linuxmonitor~=1.5.9
26
26
  Requires-Dist: discord.py
27
27
  Requires-Dist: typing
28
28
  Requires-Dist: asyncio
@@ -1,4 +1,4 @@
1
- linuxmonitor~=1.5.8
1
+ linuxmonitor~=1.5.9
2
2
  discord.py
3
3
  typing
4
4
  asyncio
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DiscordBotLinuxMonitor
3
- Version: 1.6.6
3
+ Version: 1.6.7
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
@@ -22,7 +22,7 @@ Classifier: Environment :: Console
22
22
  Requires-Python: >=3.3
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE.md
25
- Requires-Dist: linuxmonitor~=1.5.8
25
+ Requires-Dist: linuxmonitor~=1.5.9
26
26
  Requires-Dist: discord.py
27
27
  Requires-Dist: typing
28
28
  Requires-Dist: asyncio
@@ -5,6 +5,7 @@ import sys
5
5
  import logging
6
6
 
7
7
  import discord
8
+ from discord import app_commands
8
9
 
9
10
  def main() -> None:
10
11
  parser = argparse.ArgumentParser(description='System Management Tool controled from Discord')
@@ -139,6 +140,7 @@ def main() -> None:
139
140
  await discord_bot_linux_monitor.list_commands(interaction)
140
141
 
141
142
  @discord_bot.tree.command(name="execute_command", description="🚀 Execute a command (optional parameters) 🚀")
143
+ @app_commands.autocomplete(command_name=discord_bot_linux_monitor.autocomplete_command_name)
142
144
  async def execute_command(interaction: discord.Interaction, command_name: str, parameters: str = "") -> None: # type: ignore
143
145
  await discord_bot_linux_monitor.execute_command(interaction, command_name=command_name, parameters=parameters)
144
146
 
@@ -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.6 (2026/08/24)"
36
+ __version__ = "1.6.7 (2026/08/24)"
37
37
  __status__ = "Usable for any Linux project"
38
38
 
39
39
  # pyright: reportMissingTypeStubs=false
@@ -41,6 +41,7 @@ from linuxmonitor import LinuxMonitor
41
41
 
42
42
  import discord
43
43
  from discord.app_commands.models import AppCommand
44
+ from discord import app_commands
44
45
  from discord.ext import commands
45
46
  import json
46
47
  from typing import List, Union, Awaitable, Callable, Any, Dict, Optional
@@ -71,7 +72,6 @@ class DiscordBotLinuxMonitor:
71
72
 
72
73
  # Initialize the bot
73
74
  self.force_sync_on_startup: bool = force_sync_on_startup
74
- self.MAX_LENGTH_OF_DISCORD_MESSAGE = 2000 # Forced by Discord API
75
75
  intents: discord.Intents = discord.Intents.default()
76
76
  self.bot = commands.Bot(command_prefix=self.command_prefix, intents=intents)
77
77
 
@@ -208,68 +208,6 @@ class DiscordBotLinuxMonitor:
208
208
  res: bool = (self.channel_name_for_public_commands == channel.name) # type: ignore
209
209
  return res # type: ignore
210
210
 
211
- async def _channel_send_no_limit(self, channel: discord.TextChannel, msg: str) -> None:
212
- logging.info(msg=f"Sending message to channel '{channel.name}':\n{msg}")
213
-
214
- while len(msg) > self.MAX_LENGTH_OF_DISCORD_MESSAGE:
215
- # Find the last newline within the limit
216
- split_point = msg.rfind('\n', 0, self.MAX_LENGTH_OF_DISCORD_MESSAGE)
217
- if split_point == -1: # No newline found, split at max_length
218
- split_point = self.MAX_LENGTH_OF_DISCORD_MESSAGE
219
-
220
- # Send the chunk and remove it from the message
221
- await channel.send(content=msg[:split_point].rstrip())
222
- msg = msg[split_point:].lstrip()
223
-
224
- # Send the remaining message
225
- if msg:
226
- await channel.send(content=msg)
227
-
228
- async def _interaction_followup_send_no_limit(self, interaction: discord.Interaction, msg: str) -> None:
229
- logging.info(msg=f"Sending follow-up message:\n{msg}")
230
-
231
- # Send the first chunk as a follow-up message
232
- try:
233
- if msg == "":
234
- # Send generic interaction response if no message is returned
235
- msg = "No answer."
236
-
237
- if len(msg) > self.MAX_LENGTH_OF_DISCORD_MESSAGE:
238
- # Find the last newline within the limit or just split at max_length
239
- split_point = msg.rfind('\n', 0, self.MAX_LENGTH_OF_DISCORD_MESSAGE)
240
- if split_point == -1: # No newline found, split at max_length
241
- split_point = self.MAX_LENGTH_OF_DISCORD_MESSAGE
242
-
243
- await interaction.followup.send(content=msg[:split_point].rstrip())
244
- msg = msg[split_point:].lstrip()
245
- else:
246
- await interaction.followup.send(content=msg)
247
- return # Exit if the message fits within the limit
248
-
249
- except Exception as e:
250
- logging.error(msg=f"Error while sending follow-up message: {e}")
251
- return
252
-
253
- # Handle additional messages that exceed the initial follow-up limit
254
- while len(msg) > self.MAX_LENGTH_OF_DISCORD_MESSAGE:
255
- split_point = msg.rfind('\n', 0, self.MAX_LENGTH_OF_DISCORD_MESSAGE)
256
- if split_point == -1:
257
- split_point = self.MAX_LENGTH_OF_DISCORD_MESSAGE
258
-
259
- try:
260
- await interaction.channel.send(content=msg[:split_point].rstrip()) # type: ignore
261
- except Exception as e:
262
- logging.error(msg=f"Error while sending message to channel: {e}")
263
-
264
- msg = msg[split_point:].lstrip()
265
-
266
- # Send any remaining message directly to the channel
267
- if msg:
268
- try:
269
- await interaction.channel.send(content=msg) # type: ignore
270
- except Exception as e:
271
- logging.error(msg=f"Error while sending message to channel: {e}")
272
-
273
211
  async def _force_sync(self) -> str:
274
212
  try:
275
213
  logging.info(msg="Forcing the sync of the bot's commands...")
@@ -390,6 +328,69 @@ class DiscordBotLinuxMonitor:
390
328
  embed.set_footer(text=f"Bot v{__version__} • Last update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
391
329
  return embed
392
330
 
331
+ def _infer_embed_color(self, text: str, is_error: bool = False) -> "discord.Color":
332
+ # Color-code the embed depending on the status hints found in the text.
333
+ if is_error or "❌" in text or "🔴" in text:
334
+ return discord.Color.red()
335
+ if "⚠️" in text or "🟠" in text or "🟡" in text:
336
+ return discord.Color.orange()
337
+ return discord.Color.green()
338
+
339
+ def _build_result_embed(self, title: str, description: str, color: "discord.Color") -> "discord.Embed":
340
+ embed = discord.Embed(title=title[:256], color=color)
341
+ embed.description = (description if description.strip() != "" else "No answer.")[:4096]
342
+ embed.set_footer(text=f"Bot v{__version__} • {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
343
+ return embed
344
+
345
+ def _split_text_for_embed(self, text: str, max_length: int = 4000) -> List[str]:
346
+ chunks: List[str] = []
347
+ remaining: str = text
348
+ while len(remaining) > max_length:
349
+ split_point = remaining.rfind('\n', 0, max_length)
350
+ if split_point == -1:
351
+ split_point = max_length
352
+ chunks.append(remaining[:split_point].rstrip())
353
+ remaining = remaining[split_point:].lstrip()
354
+ if remaining:
355
+ chunks.append(remaining)
356
+ return chunks
357
+
358
+ async def _interaction_followup_send_embed(self, interaction: discord.Interaction, title: str, msg: str, icon: str = "", is_error: bool = False, ephemeral: bool = False) -> None:
359
+ if msg is None or msg.strip() == "":
360
+ msg = "No answer."
361
+
362
+ logging.info(msg=f"Sending embed follow-up '{title}':\n{msg}")
363
+
364
+ color: "discord.Color" = self._infer_embed_color(text=msg, is_error=is_error)
365
+ display_title: str = f"{icon} {title}".strip()
366
+ chunks: List[str] = self._split_text_for_embed(text=msg)
367
+
368
+ try:
369
+ for index, chunk in enumerate(chunks):
370
+ page_title: str = display_title if len(chunks) == 1 else f"{display_title} ({index + 1}/{len(chunks)})"
371
+ embed: "discord.Embed" = self._build_result_embed(title=page_title, description=chunk, color=color)
372
+ await interaction.followup.send(embed=embed, ephemeral=ephemeral)
373
+ except Exception as e:
374
+ logging.error(msg=f"Error while sending embed follow-up message: {e}")
375
+
376
+ async def _channel_send_embed(self, channel: discord.TextChannel, title: str, msg: str, icon: str = "", is_error: bool = False, color: Optional["discord.Color"] = None) -> None:
377
+ if msg is None or msg.strip() == "":
378
+ return
379
+
380
+ logging.info(msg=f"Sending embed to channel '{channel.name}' ('{title}'):\n{msg}")
381
+
382
+ embed_color: "discord.Color" = color if color is not None else self._infer_embed_color(text=msg, is_error=is_error)
383
+ display_title: str = f"{icon} {title}".strip()
384
+ chunks: List[str] = self._split_text_for_embed(text=msg)
385
+
386
+ for index, chunk in enumerate(chunks):
387
+ page_title: str = display_title if len(chunks) == 1 else f"{display_title} ({index + 1}/{len(chunks)})"
388
+ embed: "discord.Embed" = self._build_result_embed(title=page_title, description=chunk, color=embed_color)
389
+ try:
390
+ await channel.send(embed=embed)
391
+ except Exception as e:
392
+ logging.error(msg=f"Error while sending embed message to channel: {e}")
393
+
393
394
  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:
394
395
  for attempt in range(max_retries + 1):
395
396
  try:
@@ -462,24 +463,24 @@ class DiscordBotLinuxMonitor:
462
463
  logging.info(msg="Found the public channel, it will be possible to do public commands.")
463
464
  public_channel_cmd_ready = True
464
465
  if self.welcome_message_for_public_commands != "":
465
- await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_public_commands))
466
+ await self._channel_send_embed(channel=channel, title="Welcome", icon="👋", msg=self._welcome_message_with_version(self.welcome_message_for_public_commands), color=discord.Color.blurple())
466
467
 
467
468
  if self.channel_name_for_private_commands != "" and channel.name == self.channel_name_for_private_commands:
468
469
  logging.info(msg="Found the private channel, it will be possible to do private commands.")
469
470
  private_channel_cmd_ready = True
470
471
  if self.welcome_message_for_private_commands != "":
471
- await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_private_commands))
472
+ await self._channel_send_embed(channel=channel, title="Welcome", icon="👋", msg=self._welcome_message_with_version(self.welcome_message_for_private_commands), color=discord.Color.blurple())
472
473
 
473
474
  if self.channel_name_for_public_error_tasks != "" and channel.name == self.channel_name_for_public_error_tasks:
474
475
  logging.info(msg="Found the public channel for error task, it will be possible to show public status issues if found periodically.")
475
476
  public_channel_error_task_ready = True
476
477
 
477
478
  if self.welcome_message_for_public_error_tasks != "":
478
- await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_public_error_tasks))
479
+ await self._channel_send_embed(channel=channel, title="Welcome", icon="👋", msg=self._welcome_message_with_version(self.welcome_message_for_public_error_tasks), color=discord.Color.blurple())
479
480
 
480
481
  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}').")
481
482
  public_channel_for_error_task: discord.TextChannel = channel
482
- send_message_public_error_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_no_limit(channel=public_channel_for_error_task, msg=msg))
483
+ send_message_public_error_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_embed(channel=public_channel_for_error_task, title="Monitoring Alert", icon="🚨", msg=msg, is_error=True))
483
484
 
484
485
  # Start the public schedule task
485
486
  self.bot.loop.create_task(self.monitoring.schedule_task(handle_error_message=send_message_public_error_task_func, is_private=False))
@@ -489,11 +490,11 @@ class DiscordBotLinuxMonitor:
489
490
  public_channel_info_task_ready = True
490
491
 
491
492
  if self.welcome_message_for_public_infos_tasks != "":
492
- await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_public_infos_tasks))
493
+ await self._channel_send_embed(channel=channel, title="Welcome", icon="👋", msg=self._welcome_message_with_version(self.welcome_message_for_public_infos_tasks), color=discord.Color.blurple())
493
494
 
494
495
  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}').")
495
496
  public_channel_for_info_task: discord.TextChannel = channel
496
- send_message_public_info_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_no_limit(channel=public_channel_for_info_task, msg=msg))
497
+ send_message_public_info_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_embed(channel=public_channel_for_info_task, title="Periodic Status", icon="📊", msg=msg))
497
498
 
498
499
  # Start the public schedule task
499
500
  self.bot.loop.create_task(self.monitoring.schedule_task_show_info(show_message=send_message_public_info_task_func, is_private=False))
@@ -503,11 +504,11 @@ class DiscordBotLinuxMonitor:
503
504
  private_channel_error_task_ready = True
504
505
 
505
506
  if self.welcome_message_for_private_error_tasks != "":
506
- await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_private_error_tasks))
507
+ await self._channel_send_embed(channel=channel, title="Welcome", icon="👋", msg=self._welcome_message_with_version(self.welcome_message_for_private_error_tasks), color=discord.Color.blurple())
507
508
 
508
509
  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}').")
509
510
  private_channel_for_error_task: discord.TextChannel = channel
510
- send_message_private_error_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_no_limit(channel=private_channel_for_error_task, msg=msg))
511
+ send_message_private_error_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_embed(channel=private_channel_for_error_task, title="Monitoring Alert", icon="🚨", msg=msg, is_error=True))
511
512
 
512
513
  # Start the private schedule task
513
514
  self.bot.loop.create_task(self.monitoring.schedule_task(handle_error_message=send_message_private_error_task_func, is_private=True))
@@ -517,11 +518,11 @@ class DiscordBotLinuxMonitor:
517
518
  private_channel_info_task_ready = True
518
519
 
519
520
  if self.welcome_message_for_private_infos_tasks != "":
520
- await self._channel_send_no_limit(channel=channel, msg=self._welcome_message_with_version(self.welcome_message_for_private_infos_tasks))
521
+ await self._channel_send_embed(channel=channel, title="Welcome", icon="👋", msg=self._welcome_message_with_version(self.welcome_message_for_private_infos_tasks), color=discord.Color.blurple())
521
522
 
522
523
  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}').")
523
524
  private_channel_for_info_task: discord.TextChannel = channel
524
- send_message_private_info_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_no_limit(channel=private_channel_for_info_task, msg=msg))
525
+ send_message_private_info_task_func: Callable[[str], Awaitable[None]] = lambda msg: asyncio.create_task(self._channel_send_embed(channel=private_channel_for_info_task, title="Periodic Status", icon="📊", msg=msg))
525
526
 
526
527
  # Start the private schedule task
527
528
  self.bot.loop.create_task(self.monitoring.schedule_task_show_info(show_message=send_message_private_info_task_func, is_private=True))
@@ -576,7 +577,7 @@ class DiscordBotLinuxMonitor:
576
577
  out_msg: str = await self._force_sync()
577
578
 
578
579
  # Respond to the user
579
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
580
+ await self._interaction_followup_send_embed(interaction=interaction, title="Command Synchronization", icon="🔄", msg=out_msg)
580
581
 
581
582
  async def version(self, interaction: discord.Interaction) -> None:
582
583
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -586,9 +587,11 @@ class DiscordBotLinuxMonitor:
586
587
 
587
588
  out_msg: str = (
588
589
  f"🤖 Bot version: {__version__}\n"
589
- f"🐍 Python compatibility: {__python_version__}"
590
+ f" Linux Monitor library version: {self.monitoring.get_raw_version()}\n"
591
+ f"�🐍 Python compatibility: {__python_version__}"
590
592
  )
591
- await interaction.response.send_message(content=out_msg, ephemeral=True)
593
+ embed = self._build_result_embed(title="🤖 Bot Version", description=out_msg, color=discord.Color.blurple())
594
+ await interaction.response.send_message(embed=embed, ephemeral=True)
592
595
 
593
596
  async def usage(self, interaction: discord.Interaction) -> None:
594
597
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -619,11 +622,11 @@ class DiscordBotLinuxMonitor:
619
622
  out_msg += self.monitoring.get_network_info()
620
623
 
621
624
  # Respond to the user
622
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
625
+ await self._interaction_followup_send_embed(interaction=interaction, title="System Usage", icon="📊", msg=out_msg)
623
626
  except Exception as e:
624
627
  out_msg = f"**Internal error retrieving usage info**:\n```sh\n{e}\n```"
625
628
  logging.exception(msg=out_msg)
626
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
629
+ await self._interaction_followup_send_embed(interaction=interaction, title="System Usage", icon="📊", msg=out_msg, is_error=True)
627
630
 
628
631
  async def os_infos(self, interaction: discord.Interaction) -> None:
629
632
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -642,11 +645,11 @@ class DiscordBotLinuxMonitor:
642
645
  out_msg += self.monitoring.get_server_datetime()
643
646
 
644
647
  # Respond to the user
645
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
648
+ await self._interaction_followup_send_embed(interaction=interaction, title="OS Information", icon="🖥️", msg=out_msg)
646
649
  except Exception as e:
647
650
  out_msg = f"**Internal error retrieving OS info**:\n```sh\n{e}\n```"
648
651
  logging.exception(msg=out_msg)
649
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
652
+ await self._interaction_followup_send_embed(interaction=interaction, title="OS Information", icon="🖥️", msg=out_msg, is_error=True)
650
653
 
651
654
  async def users(self, interaction: discord.Interaction) -> None:
652
655
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -664,11 +667,11 @@ class DiscordBotLinuxMonitor:
664
667
  out_msg: str = self.monitoring.get_connected_users()
665
668
 
666
669
  # Respond to the user
667
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
670
+ await self._interaction_followup_send_embed(interaction=interaction, title="Connected Users", icon="👥", msg=out_msg)
668
671
  except Exception as e:
669
672
  out_msg = f"**Internal error retrieving connected users**:\n```sh\n{e}\n```"
670
673
  logging.exception(msg=out_msg)
671
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
674
+ await self._interaction_followup_send_embed(interaction=interaction, title="Connected Users", icon="👥", msg=out_msg, is_error=True)
672
675
 
673
676
  async def user_logins(self, interaction: discord.Interaction) -> None:
674
677
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -688,12 +691,12 @@ class DiscordBotLinuxMonitor:
688
691
  out_msg: str = self.monitoring.check_all_recent_user_logins(display_only_if_critical=False)
689
692
 
690
693
  # Répondre à l'utilisateur
691
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
694
+ await self._interaction_followup_send_embed(interaction=interaction, title="Recent User Logins", icon="🔐", msg=out_msg)
692
695
 
693
696
  except Exception as e:
694
697
  out_msg = f"**Internal error retrieving last user connections**:\n```sh\n{e}\n```"
695
698
  logging.exception(msg=out_msg)
696
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
699
+ await self._interaction_followup_send_embed(interaction=interaction, title="Recent User Logins", icon="🔐", msg=out_msg, is_error=True)
697
700
 
698
701
  async def ping(self, interaction: discord.Interaction) -> None:
699
702
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -709,11 +712,11 @@ class DiscordBotLinuxMonitor:
709
712
  out_msg: str = await self.monitoring.ping_all_websites(is_private=is_private, display_only_if_critical=False)
710
713
 
711
714
  # Respond to the user
712
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
715
+ await self._interaction_followup_send_embed(interaction=interaction, title="Websites Ping", icon="📡", msg=out_msg)
713
716
  except Exception as e:
714
717
  out_msg = f"**Internal error during websites ping **:\n```sh\n{e}\n```"
715
718
  logging.exception(msg=out_msg)
716
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
719
+ await self._interaction_followup_send_embed(interaction=interaction, title="Websites Ping", icon="📡", msg=out_msg, is_error=True)
717
720
 
718
721
  async def websites(self, interaction: discord.Interaction) -> None:
719
722
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -729,11 +732,11 @@ class DiscordBotLinuxMonitor:
729
732
  out_msg: str = await self.monitoring.check_all_websites(is_private=is_private, display_only_if_critical=False)
730
733
 
731
734
  # Respond to the user
732
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
735
+ await self._interaction_followup_send_embed(interaction=interaction, title="Websites Access", icon="🌐", msg=out_msg)
733
736
  except Exception as e:
734
737
  out_msg = f"**Internal error during websites access check **:\n```sh\n{e}\n```"
735
738
  logging.exception(msg=out_msg)
736
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
739
+ await self._interaction_followup_send_embed(interaction=interaction, title="Websites Access", icon="🌐", msg=out_msg, is_error=True)
737
740
 
738
741
  async def certificates(self, interaction: discord.Interaction) -> None:
739
742
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -749,11 +752,11 @@ class DiscordBotLinuxMonitor:
749
752
  out_msg: str = self.monitoring.check_all_certificates(is_private=is_private, display_only_if_critical=False)
750
753
 
751
754
  # Respond to the user
752
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
755
+ await self._interaction_followup_send_embed(interaction=interaction, title="SSL Certificates", icon="🔒", msg=out_msg)
753
756
  except Exception as e:
754
757
  out_msg = f"**Internal error during SSL certificate checks **:\n```sh\n{e}\n```"
755
758
  logging.exception(msg=out_msg)
756
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
759
+ await self._interaction_followup_send_embed(interaction=interaction, title="SSL Certificates", icon="🔒", msg=out_msg, is_error=True)
757
760
 
758
761
  async def reboot(self, interaction: discord.Interaction) -> None:
759
762
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -769,12 +772,12 @@ class DiscordBotLinuxMonitor:
769
772
 
770
773
  try:
771
774
  out_msg: str = await self.monitoring.reboot_server()
772
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
775
+ await self._interaction_followup_send_embed(interaction=interaction, title="Server Reboot", icon="🔁", msg=out_msg)
773
776
 
774
777
  except Exception as e:
775
778
  out_msg = f"**Internal error during server reboot**:\n```sh\n{e}\n```"
776
779
  logging.exception(msg=out_msg)
777
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
780
+ await self._interaction_followup_send_embed(interaction=interaction, title="Server Reboot", icon="🔁", msg=out_msg, is_error=True)
778
781
 
779
782
  async def services_status(self, interaction: discord.Interaction) -> None:
780
783
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -790,11 +793,11 @@ class DiscordBotLinuxMonitor:
790
793
  out_msg: str = await self.monitoring.check_all_services_status(is_private=is_private)
791
794
 
792
795
  # Respond to the user
793
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
796
+ await self._interaction_followup_send_embed(interaction=interaction, title="Services Status", icon="⚙️", msg=out_msg)
794
797
  except Exception as e:
795
798
  out_msg = f"**Internal error checking services are running**:\n```sh\n{e}\n```"
796
799
  logging.exception(msg=out_msg)
797
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
800
+ await self._interaction_followup_send_embed(interaction=interaction, title="Services Status", icon="⚙️", msg=out_msg, is_error=True)
798
801
 
799
802
  async def restart_all(self, interaction: discord.Interaction) -> None:
800
803
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -811,11 +814,11 @@ class DiscordBotLinuxMonitor:
811
814
  out_msg: str = await self.monitoring.restart_all_services(is_private=is_private)
812
815
 
813
816
  # Respond to the user
814
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
817
+ await self._interaction_followup_send_embed(interaction=interaction, title="Restart All Services", icon="🔄", msg=out_msg)
815
818
  except Exception as e:
816
819
  out_msg = f"**Internal error restarting all services**:\n```sh\n{e}\n```"
817
820
  logging.exception(msg=out_msg)
818
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
821
+ await self._interaction_followup_send_embed(interaction=interaction, title="Restart All Services", icon="🔄", msg=out_msg, is_error=True)
819
822
 
820
823
  async def restart_service(self, interaction: discord.Interaction, service_name: str) -> None:
821
824
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -832,12 +835,12 @@ class DiscordBotLinuxMonitor:
832
835
  out_msg: str = await self.monitoring.restart_service(is_private=is_private, service_name=service_name, force_restart=True)
833
836
 
834
837
  # Répondre à l'utilisateur
835
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
838
+ await self._interaction_followup_send_embed(interaction=interaction, title="Restart Service", icon="🔄", msg=out_msg)
836
839
 
837
840
  except Exception as e:
838
841
  out_msg = f"**Internal error restarting service {service_name}**:\n```sh\n{e}\n```"
839
842
  logging.exception(msg=out_msg)
840
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
843
+ await self._interaction_followup_send_embed(interaction=interaction, title="Restart Service", icon="🔄", msg=out_msg, is_error=True)
841
844
 
842
845
  async def stop_service(self, interaction: discord.Interaction, service_name: str) -> None:
843
846
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -854,11 +857,11 @@ class DiscordBotLinuxMonitor:
854
857
  out_msg: str = await self.monitoring.stop_service(is_private=is_private, service_name=service_name)
855
858
 
856
859
  # Répondre à l'utilisateur
857
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
860
+ await self._interaction_followup_send_embed(interaction=interaction, title="Stop Service", icon="🛑", msg=out_msg)
858
861
  except Exception as e:
859
862
  out_msg = f"**Internal error stopping service {service_name}**:\n```sh\n{e}\n```"
860
863
  logging.exception(msg=out_msg)
861
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
864
+ await self._interaction_followup_send_embed(interaction=interaction, title="Stop Service", icon="🛑", msg=out_msg, is_error=True)
862
865
 
863
866
  async def list_services(self, interaction: discord.Interaction) -> None:
864
867
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -875,11 +878,11 @@ class DiscordBotLinuxMonitor:
875
878
  out_msg: str = self.monitoring.get_all_services(is_private=is_private)
876
879
 
877
880
  # Répondre à l'utilisateur
878
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
881
+ await self._interaction_followup_send_embed(interaction=interaction, title="Available Services", icon="📋", msg=out_msg)
879
882
  except Exception as e:
880
883
  out_msg = f"**Internal error retrieving available services**:\n```sh\n{e}\n```"
881
884
  logging.exception(msg=out_msg)
882
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
885
+ await self._interaction_followup_send_embed(interaction=interaction, title="Available Services", icon="📋", msg=out_msg, is_error=True)
883
886
 
884
887
  async def ports(self, interaction: discord.Interaction) -> None:
885
888
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -896,11 +899,11 @@ class DiscordBotLinuxMonitor:
896
899
  out_msg: str = await self.monitoring.check_all_ports(is_private=is_private, display_only_if_critical=False)
897
900
 
898
901
  # Répondre à l'utilisateur
899
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
902
+ await self._interaction_followup_send_embed(interaction=interaction, title="Ports Status", icon="🔌", msg=out_msg)
900
903
  except Exception as e:
901
904
  out_msg = f"**Internal error checking ports**:\n```sh\n{e}\n```"
902
905
  logging.exception(msg=out_msg)
903
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
906
+ await self._interaction_followup_send_embed(interaction=interaction, title="Ports Status", icon="🔌", msg=out_msg, is_error=True)
904
907
 
905
908
  async def list_processes(self, interaction: discord.Interaction, order_by_ram: bool) -> None:
906
909
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -919,11 +922,11 @@ class DiscordBotLinuxMonitor:
919
922
  out_msg: str = await self.monitoring.get_ordered_processes(get_non_consuming_processes=False, order_by_ram=order_by_ram, max_processes=20)
920
923
 
921
924
  # Répondre à l'utilisateur
922
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
925
+ await self._interaction_followup_send_embed(interaction=interaction, title="Processes List", icon="🧮", msg=out_msg)
923
926
  except Exception as e:
924
927
  out_msg = f"**Internal error retrieving active processes**:\n```sh\n{e}\n```"
925
928
  logging.exception(msg=out_msg)
926
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
929
+ await self._interaction_followup_send_embed(interaction=interaction, title="Processes List", icon="🧮", msg=out_msg, is_error=True)
927
930
 
928
931
  async def kill_process(self, interaction: discord.Interaction, pid: int) -> None:
929
932
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -942,11 +945,11 @@ class DiscordBotLinuxMonitor:
942
945
  out_msg: str = await self.monitoring.kill_process(pid=pid)
943
946
 
944
947
  # Répondre à l'utilisateur
945
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
948
+ await self._interaction_followup_send_embed(interaction=interaction, title="Kill Process", icon="☠️", msg=out_msg)
946
949
  except Exception as e:
947
950
  out_msg = f"**Internal error stopping process of PID {pid}**:\n```sh\n{e}\n```"
948
951
  logging.exception(msg=out_msg)
949
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
952
+ await self._interaction_followup_send_embed(interaction=interaction, title="Kill Process", icon="☠️", msg=out_msg, is_error=True)
950
953
 
951
954
  async def clear_channel_messages(self, interaction: discord.Interaction, channel: discord.TextChannel) -> None:
952
955
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -1161,11 +1164,11 @@ class DiscordBotLinuxMonitor:
1161
1164
  out_msg += "**✅ Clearable:**\n" + ("\n".join(clearable_lines) if clearable_lines else "None") + "\n\n"
1162
1165
  out_msg += "**❌ Blocked (missing bot permission):**\n" + ("\n".join(blocked_lines) if blocked_lines else "None")
1163
1166
 
1164
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1167
+ await self._interaction_followup_send_embed(interaction=interaction, title="Clearable Channels", icon="🧹", msg=out_msg, ephemeral=True)
1165
1168
  except Exception as e:
1166
1169
  out_msg = f"**Internal error listing clearable channels**:\n```sh\n{e}\n```"
1167
1170
  logging.exception(msg=out_msg)
1168
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1171
+ await self._interaction_followup_send_embed(interaction=interaction, title="Clearable Channels", icon="🧹", msg=out_msg, is_error=True, ephemeral=True)
1169
1172
 
1170
1173
  async def list_commands(self, interaction: discord.Interaction) -> None:
1171
1174
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -1182,12 +1185,29 @@ class DiscordBotLinuxMonitor:
1182
1185
  out_msg: str = await self.monitoring.list_commands(is_private=is_private)
1183
1186
 
1184
1187
  # Répondre à l'utilisateur
1185
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1188
+ await self._interaction_followup_send_embed(interaction=interaction, title="Available Commands", icon="📖", msg=out_msg)
1186
1189
  except Exception as e:
1187
1190
  out_msg = f"**Internal error retrieving available commands**:\n```sh\n{e}\n```"
1188
1191
  logging.exception(msg=out_msg)
1189
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1192
+ await self._interaction_followup_send_embed(interaction=interaction, title="Available Commands", icon="📖", msg=out_msg, is_error=True)
1193
+
1190
1194
 
1195
+ async def autocomplete_command_name(self, interaction: discord.Interaction, current: str) -> List["app_commands.Choice[str]"]:
1196
+ # Suggest the configured command names so the user picks from a list instead of typing them.
1197
+ try:
1198
+ is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
1199
+ current_lower: str = current.lower()
1200
+ choices: List["app_commands.Choice[str]"] = []
1201
+ for command_name, display_name in self.monitoring.get_command_names(is_private=is_private):
1202
+ if current_lower == "" or current_lower in command_name.lower() or current_lower in display_name.lower():
1203
+ label: str = f"{command_name} — {display_name}"
1204
+ choices.append(app_commands.Choice(name=label[:100], value=command_name))
1205
+ if len(choices) >= 25: # Discord limits autocomplete to 25 choices
1206
+ break
1207
+ return choices
1208
+ except Exception as e:
1209
+ logging.error(msg=f"Error while building command autocomplete: {e}")
1210
+ return []
1191
1211
 
1192
1212
  async def execute_command(self, interaction: discord.Interaction, command_name: str, parameters: str = "") -> None:
1193
1213
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -1204,11 +1224,11 @@ class DiscordBotLinuxMonitor:
1204
1224
  out_msg: str = await self.monitoring.execute_command(is_private=is_private, command_name=command_name, parameters=parameters)
1205
1225
 
1206
1226
  # Répondre à l'utilisateur
1207
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1227
+ await self._interaction_followup_send_embed(interaction=interaction, title=f"Execute Command — {command_name}", icon="▶️", msg=out_msg)
1208
1228
  except Exception as e:
1209
1229
  out_msg = f"**Internal error executing command '{command_name}'**:\n```sh\n{e}\n```"
1210
1230
  logging.exception(msg=out_msg)
1211
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1231
+ await self._interaction_followup_send_embed(interaction=interaction, title=f"Execute Command — {command_name}", icon="▶️", msg=out_msg, is_error=True)
1212
1232
 
1213
1233
  async def execute_all_commands(self, interaction: discord.Interaction) -> None:
1214
1234
  if not self._check_if_valid_guild(guild=interaction.guild):
@@ -1225,10 +1245,10 @@ class DiscordBotLinuxMonitor:
1225
1245
  out_msg: str = await self.monitoring.execute_all_commands(is_private=is_private)
1226
1246
 
1227
1247
  # Répondre à l'utilisateur
1228
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1248
+ await self._interaction_followup_send_embed(interaction=interaction, title="Execute All Commands", icon="⏩", msg=out_msg)
1229
1249
  except Exception as e:
1230
1250
  out_msg = f"**Internal error executing all commands**:\n```sh\n{e}\n```"
1231
1251
  logging.exception(msg=out_msg)
1232
- await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
1252
+ await self._interaction_followup_send_embed(interaction=interaction, title="Execute All Commands", icon="⏩", msg=out_msg, is_error=True)
1233
1253
 
1234
1254
  #endregion
@@ -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.6",
9
+ version="1.6.7",
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",
@@ -36,7 +36,7 @@ setup(
36
36
  keywords='discord bot warning info linux monitor monitoring server service port ping ssl certificate disk folder cpu ram swap usage ip connection',
37
37
  platforms='Linux',
38
38
  install_requires=[
39
- "linuxmonitor~=1.5.8", # follows the {MAJOR}.{MINOR}.x version range (because LinuxMonitor and DiscordBotLinuxMonitor are tightly coupled and should have same major and minor version)
39
+ "linuxmonitor~=1.5.9", # follows the {MAJOR}.{MINOR}.x version range (because LinuxMonitor and DiscordBotLinuxMonitor are tightly coupled and should have same major and minor version)
40
40
  "discord.py",
41
41
  "typing",
42
42
  "asyncio",