DiscordBotLinuxMonitor 1.0.1__py3-none-any.whl
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.0.1.dist-info/LICENSE.md +21 -0
- DiscordBotLinuxMonitor-1.0.1.dist-info/METADATA +200 -0
- DiscordBotLinuxMonitor-1.0.1.dist-info/RECORD +8 -0
- DiscordBotLinuxMonitor-1.0.1.dist-info/WHEEL +5 -0
- DiscordBotLinuxMonitor-1.0.1.dist-info/top_level.txt +1 -0
- discordbotlinuxmonitor/__init__.py +1 -0
- discordbotlinuxmonitor/__main__.py +125 -0
- discordbotlinuxmonitor/discordbotlinuxmonitor.py +741 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Quentin Comte-Gaz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: DiscordBotLinuxMonitor
|
|
3
|
+
Version: 1.0.1
|
|
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
|
+
Home-page: https://github.com/QuentinCG/Linux-Monitor-Python-Library
|
|
6
|
+
Author: Quentin Comte-Gaz
|
|
7
|
+
Author-email: quentin@comte-gaz.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: discord bot warning info linux monitor monitoring server service port ping ssl certificate disk folder cpu ram swap usage ip connection
|
|
10
|
+
Platform: Linux
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
19
|
+
Classifier: Topic :: System :: Systems Administration
|
|
20
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
21
|
+
Classifier: Environment :: Console
|
|
22
|
+
Requires-Python: >=3.3
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE.md
|
|
25
|
+
Requires-Dist: linuxmonitor
|
|
26
|
+
Requires-Dist: discord.py
|
|
27
|
+
Requires-Dist: typing
|
|
28
|
+
Requires-Dist: asyncio
|
|
29
|
+
|
|
30
|
+
# Discord Bot Linux Monitor (Python library)
|
|
31
|
+
[](https://pypi.org/project/DiscordBotLinuxMonitor/) [](https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/blob/master/LICENSE.md) [](https://paypal.me/QuentinCG) [](https://pepy.tech/project/DiscordBotLinuxMonitor) [](https://pepy.tech/project/DiscordBotLinuxMonitor)
|
|
32
|
+
|
|
33
|
+
## What is it
|
|
34
|
+
|
|
35
|
+
This python library is designed to be used as a Linux service to monitor the Linux server manually with discord commands and to send warning or information of the Linux server status to a discord channel periodically (if relevant).
|
|
36
|
+
|
|
37
|
+
It is possible to have separate 'private discord channel' and 'public discord channel' for:
|
|
38
|
+
- Sendind discord commands to the bot (visual example in next section)
|
|
39
|
+
- Getting periodic status of the Linux server (doing periodically some commands from next section)
|
|
40
|
+
- Getting warnings if there is an issue in the Linux server (periodic status check)
|
|
41
|
+
|
|
42
|
+
Example of warning:
|
|
43
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/warning_from_periodic_check.jpg" height="400">
|
|
44
|
+
|
|
45
|
+
It is compatible with python 3+ and usable only on Linux.
|
|
46
|
+
|
|
47
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/discord.png" width="300">
|
|
48
|
+
|
|
49
|
+
## List of Discord commands:
|
|
50
|
+
|
|
51
|
+
### Public and private discord channel commands
|
|
52
|
+
Displayed infos are not the same if you do the command in private or public channel (all info are displayed in private channel, some are hidden from public channels)
|
|
53
|
+
|
|
54
|
+
- `/usage`: 📊 View disk space, CPU, RAM, ... 📊
|
|
55
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/usage.jpg" height="400">
|
|
56
|
+
|
|
57
|
+
- `/os_infos`: 🖥 View basic system information 🖥
|
|
58
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/os_infos.jpg" height="300">
|
|
59
|
+
|
|
60
|
+
- `/ping`: 🌐 Ping websites 🌐
|
|
61
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/ping.jpg" height="150">
|
|
62
|
+
|
|
63
|
+
- `/certificates`: 🔒 Check SSL certificates 🔒
|
|
64
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/certificates.jpg" height="400">
|
|
65
|
+
|
|
66
|
+
- `/services_status`: 🩺 Check services are running 🩺
|
|
67
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/services_status.jpg" height="150">
|
|
68
|
+
|
|
69
|
+
- `/restart_all`: 🚀 Restart all services 🚀
|
|
70
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/restart_all.jpg" height="150">
|
|
71
|
+
|
|
72
|
+
- `/restart_service {service_name}`: 🚀 Restart a service 🚀
|
|
73
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/restart_service.jpg" height="150">
|
|
74
|
+
|
|
75
|
+
- `/list_services`: 📋 List all available services 📋
|
|
76
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/list_services.jpg" height="150">
|
|
77
|
+
|
|
78
|
+
- `/ports`: 🔒 Check ports 🔒
|
|
79
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/ports.jpg" height="200">
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### Private discord channel commands:
|
|
83
|
+
- `/force_sync`: 🔄 Force discord command synchronization 🔄
|
|
84
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/force_sync.jpg" height="300">
|
|
85
|
+
|
|
86
|
+
- `/users`: 👥 View connected users 👥
|
|
87
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/users.jpg" height="150">
|
|
88
|
+
|
|
89
|
+
- `/user_logins`: 👥 View last user connections 👥
|
|
90
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/user_logins.jpg" height="150">
|
|
91
|
+
|
|
92
|
+
- `/reboot_server`: 🔄 Restart the entire server 🔄
|
|
93
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/reboot_server.jpg" height="100">
|
|
94
|
+
|
|
95
|
+
- `/list_processes`: 📋 List active processes 📋
|
|
96
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/list_processes.jpg" height="200">
|
|
97
|
+
|
|
98
|
+
- `/kill_process`: 🚫 Stop a process by PID 🚫
|
|
99
|
+
<img src="https://raw.githubusercontent.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/master/example/kill_process.jpg" height="200">
|
|
100
|
+
|
|
101
|
+
## How to install (for first launch)
|
|
102
|
+
|
|
103
|
+
- Install package calling `python -m pip install discordbotlinuxmonitor` (or `python setup.py install` from the root of this repository)
|
|
104
|
+
- Copy and edit [config-example.json file](https://github.com/QuentinCG/Discord-Bot-Linux-Monitor-Python-Library/blob/master/config-example.json) depending on your need (on first launch, remove all `restart_command` from config file to prevent potential looping service restart issues on your server in case your config file is not well configured)
|
|
105
|
+
- Launch the lib for testing it works:
|
|
106
|
+
```shell
|
|
107
|
+
# Get help
|
|
108
|
+
python3 -m discordbotlinuxmonitor --help
|
|
109
|
+
# Use "--debug" to show more information during command
|
|
110
|
+
# Use "--nodebug" to not show any warning information during command
|
|
111
|
+
|
|
112
|
+
# Start the discord bot Linux monitor (First time)
|
|
113
|
+
python3 -m discordbotlinuxmonitor --config_file config-example.json --force_sync_on_startup True --debug
|
|
114
|
+
|
|
115
|
+
# Start the discord bot Linux monitor (after first time)
|
|
116
|
+
python3 -m discordbotlinuxmonitor --config_file config-example.json --force_sync_on_startup False
|
|
117
|
+
```
|
|
118
|
+
- Go to discord (restart discord if you were already in it)
|
|
119
|
+
- You should see welcome messages on channels you configured in the config file and be able to communicate with the bot using command defined in previous section
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## How to install this lib as a service (to keep it running in the Linux server as a monitor)
|
|
123
|
+
|
|
124
|
+
- Stop the discord bot and create a service to have it running even after computer reboot:
|
|
125
|
+
```sh
|
|
126
|
+
### Define all base info
|
|
127
|
+
DISCORD_BOT_SERVICE_USER="discordbotlinuxmonitor"
|
|
128
|
+
DISCORD_BOT_SERVICE_GROUP="discordbotlinuxmonitor"
|
|
129
|
+
DISCORD_BOT_SERVICE_NAME="discord-bot"
|
|
130
|
+
DISCORD_BOT_SERVICE_FILE="/etc/systemd/system/${DISCORD_BOT_SERVICE_NAME}.service"
|
|
131
|
+
DISCORD_BOT_FOLDER="/opt/Discord/"
|
|
132
|
+
|
|
133
|
+
### Add rights to user launching the library depending on what you want it to do ###
|
|
134
|
+
# Only if this library should be able to reboot the server on demand:
|
|
135
|
+
echo "$DISCORD_BOT_SERVICE_USER ALL=(ALL) NOPASSWD: /sbin/reboot" >> /etc/sudoers.d/$DISCORD_BOT_SERVICE_USER
|
|
136
|
+
# Only if this library should be able to kill a process on demand:
|
|
137
|
+
echo "$DISCORD_BOT_SERVICE_USER ALL=(ALL) NOPASSWD: /bin/kill" >> /etc/sudoers.d/$DISCORD_BOT_SERVICE_USER
|
|
138
|
+
# Add also all processes added in your config JSON file you want the library to be able to execute
|
|
139
|
+
# Example for the existing config-example.json file:
|
|
140
|
+
echo "$DISCORD_BOT_SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl" >> /etc/sudoers.d/$DISCORD_BOT_SERVICE_USER
|
|
141
|
+
echo "$DISCORD_BOT_SERVICE_USER ALL=(ALL) NOPASSWD: /etc/init.d/apache2" >> /etc/sudoers.d/$DISCORD_BOT_SERVICE_USER
|
|
142
|
+
echo "$DISCORD_BOT_SERVICE_USER ALL=(ALL) NOPASSWD: /etc/init.d/mariadb" >> /etc/sudoers.d/$DISCORD_BOT_SERVICE_USER
|
|
143
|
+
|
|
144
|
+
### Create a specific user and group to launch the discord bot service ###
|
|
145
|
+
echo "Creating user $DISCORD_BOT_SERVICE_USER..."
|
|
146
|
+
sudo useradd -r -s /usr/sbin/nologin $DISCORD_BOT_SERVICE_USER
|
|
147
|
+
sudo mkdir -p /home/$DISCORD_BOT_SERVICE_USER
|
|
148
|
+
sudo chown $DISCORD_BOT_SERVICE_USER:$DISCORD_BOT_SERVICE_GROUP /home/$DISCORD_BOT_SERVICE_USER
|
|
149
|
+
sudo usermod -d /home/$DISCORD_BOT_SERVICE_USER $DISCORD_BOT_SERVICE_USER
|
|
150
|
+
|
|
151
|
+
### Install DiscordBotLinuxMonitor lib for the user ###
|
|
152
|
+
echo "Installing DiscordBotLinuxMonitor for user $DISCORD_BOT_SERVICE_USER..."
|
|
153
|
+
sudo -u $DISCORD_BOT_SERVICE_USER -s
|
|
154
|
+
python3 -m venv /home/$DISCORD_BOT_SERVICE_USER/venv
|
|
155
|
+
source /home/$DISCORD_BOT_SERVICE_USER/venv/bin/activate
|
|
156
|
+
python3 -m pip install discordbotlinuxmonitor
|
|
157
|
+
deactivate
|
|
158
|
+
exit
|
|
159
|
+
|
|
160
|
+
# Create the Discord bot folder where you should put the json file
|
|
161
|
+
sudo mkdir $DISCORD_BOT_FOLDER
|
|
162
|
+
|
|
163
|
+
### Create a systemd service file for the Discord bot ###
|
|
164
|
+
cat <<EOF > $DISCORD_BOT_SERVICE_FILE
|
|
165
|
+
[Unit]
|
|
166
|
+
Description=Discord Bot Linux Monitor Service
|
|
167
|
+
After=network.target
|
|
168
|
+
|
|
169
|
+
[Service]
|
|
170
|
+
ExecStart=/home/$DISCORD_BOT_SERVICE_USER/venv/bin/python3 -m discordbotlinuxmonitor --config_file ${DISCORD_BOT_FOLDER}config.json
|
|
171
|
+
WorkingDirectory=$DISCORD_BOT_FOLDER
|
|
172
|
+
User=$DISCORD_BOT_SERVICE_USER
|
|
173
|
+
Group=$DISCORD_BOT_SERVICE_GROUP
|
|
174
|
+
Restart=always
|
|
175
|
+
RestartSec=3
|
|
176
|
+
|
|
177
|
+
[Install]
|
|
178
|
+
WantedBy=multi-user.target
|
|
179
|
+
EOF
|
|
180
|
+
|
|
181
|
+
echo "Service file created at $DISCORD_BOT_SERVICE_FILE"
|
|
182
|
+
|
|
183
|
+
### Reload systemd to recognize the new service ###
|
|
184
|
+
echo "Reloading systemd..."
|
|
185
|
+
sudo systemctl daemon-reload
|
|
186
|
+
|
|
187
|
+
### Enable and start the service ###
|
|
188
|
+
echo "Enabling and starting the Discord bot service..."
|
|
189
|
+
sudo systemctl enable $DISCORD_BOT_SERVICE_NAME
|
|
190
|
+
```
|
|
191
|
+
- Copy your config file into `$DISCORD_BOT_FOLDER/config.json`
|
|
192
|
+
- Launch the service: `sudo systemctl start $DISCORD_BOT_SERVICE_NAME`
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
This project is under MIT license. This means you can use it as you want (just don't delete the library header).
|
|
197
|
+
|
|
198
|
+
## Contribute
|
|
199
|
+
|
|
200
|
+
If you want to add more examples or improve the library, just create a pull request with proper commit message and right wrapping.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
discordbotlinuxmonitor/__init__.py,sha256=YwLKm4ALTaT7YNOTCO3X3m6yDOpybb1r5MvOF6Bt_Lg,74
|
|
2
|
+
discordbotlinuxmonitor/__main__.py,sha256=tOwcXUgZhtBDV9SzYButAsNdcux97vaKsoOXL9Xu13I,6138
|
|
3
|
+
discordbotlinuxmonitor/discordbotlinuxmonitor.py,sha256=YE6tydHspmMlEUeFj7T6HljxkgtR0R4i_ULwaBgGqpo,42432
|
|
4
|
+
DiscordBotLinuxMonitor-1.0.1.dist-info/LICENSE.md,sha256=nVjNnWWojuUQdnMdUf_YVeRMocBeapYhuLfIRQXXziI,1084
|
|
5
|
+
DiscordBotLinuxMonitor-1.0.1.dist-info/METADATA,sha256=l0IyJh0R6YKQnFA86Oek6rqjJzVPfCMD6IB1Uqut5wU,10942
|
|
6
|
+
DiscordBotLinuxMonitor-1.0.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
7
|
+
DiscordBotLinuxMonitor-1.0.1.dist-info/top_level.txt,sha256=xQ2GrL3jLRaO6NVSLrgtD3wGaqS7MeZzICTeqhyr5_w,23
|
|
8
|
+
DiscordBotLinuxMonitor-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
discordbotlinuxmonitor
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .discordbotlinuxmonitor import DiscordBotLinuxMonitor # type: ignore
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from .discordbotlinuxmonitor import DiscordBotLinuxMonitor
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import discord
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
parser = argparse.ArgumentParser(description='System Management Tool controled from Discord')
|
|
11
|
+
|
|
12
|
+
# Define available arguments
|
|
13
|
+
parser.add_argument('--config_file', type=str, required=True, help='Path to the configuration file (must be a JSON file)')
|
|
14
|
+
parser.add_argument('--force_sync_on_startup', type=bool, required=True, help='Force discord command synchronization on startup (do it only the first time, because after, you will have a discord command to do it if really needed)')
|
|
15
|
+
|
|
16
|
+
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
|
17
|
+
parser.add_argument('--nodebug', action='store_true', help='Disable all logs')
|
|
18
|
+
|
|
19
|
+
# Parse arguments
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
# Enable or disable debug mode
|
|
23
|
+
if args.debug:
|
|
24
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
25
|
+
elif args.nodebug:
|
|
26
|
+
logging.getLogger().setLevel(logging.CRITICAL)
|
|
27
|
+
else:
|
|
28
|
+
# Default to warning
|
|
29
|
+
logging.getLogger().setLevel(logging.WARNING)
|
|
30
|
+
|
|
31
|
+
# Ensure the config file is provided
|
|
32
|
+
if not args.config_file:
|
|
33
|
+
print("Error: --config_file is required")
|
|
34
|
+
parser.print_help()
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
config_file: str = args.config_file
|
|
37
|
+
|
|
38
|
+
# Ensure the force_sync_on_startup is provided
|
|
39
|
+
if not args.force_sync_on_startup:
|
|
40
|
+
print("Error: --force_sync_on_startup is required")
|
|
41
|
+
parser.print_help()
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
force_sync_on_startup: bool = args.force_sync_on_startup
|
|
44
|
+
|
|
45
|
+
# Prepare the Discord bot (will throw an exception if the configuration is invalid)
|
|
46
|
+
discord_bot_linux_monitor = DiscordBotLinuxMonitor(config_file=config_file, force_sync_on_startup=force_sync_on_startup)
|
|
47
|
+
discord_bot = discord_bot_linux_monitor.bot
|
|
48
|
+
|
|
49
|
+
#region BOT COMMANDS AND EVENTS REGIRSTRATION
|
|
50
|
+
|
|
51
|
+
@discord_bot.event
|
|
52
|
+
async def on_ready() -> None: # type: ignore
|
|
53
|
+
await discord_bot_linux_monitor.on_ready()
|
|
54
|
+
|
|
55
|
+
@discord_bot.tree.command(name="force_sync", description="[Private] 🔄 Force command synchronization 🔄")
|
|
56
|
+
async def force_sync(interaction: discord.Interaction) -> None: # type: ignore
|
|
57
|
+
await discord_bot_linux_monitor.force_sync(interaction)
|
|
58
|
+
|
|
59
|
+
@discord_bot.tree.command(name="usage", description="📊 View disk space, CPU, RAM, ... 📊")
|
|
60
|
+
async def usage(interaction: discord.Interaction) -> None: # type: ignore
|
|
61
|
+
await discord_bot_linux_monitor.usage(interaction)
|
|
62
|
+
|
|
63
|
+
@discord_bot.tree.command(name="os_infos", description="🖥️ View basic system information 🖥️")
|
|
64
|
+
async def os_infos(interaction: discord.Interaction) -> None: # type: ignore
|
|
65
|
+
await discord_bot_linux_monitor.os_infos(interaction)
|
|
66
|
+
|
|
67
|
+
@discord_bot.tree.command(name="users", description="[Private] 👥 View connected users 👥")
|
|
68
|
+
async def users(interaction: discord.Interaction) -> None: # type: ignore
|
|
69
|
+
await discord_bot_linux_monitor.users(interaction)
|
|
70
|
+
|
|
71
|
+
@discord_bot.tree.command(name="user_logins", description="[Private] 👥 View last user connections 👥")
|
|
72
|
+
async def user_logins(interaction: discord.Interaction) -> None: # type: ignore
|
|
73
|
+
await discord_bot_linux_monitor.user_logins(interaction)
|
|
74
|
+
|
|
75
|
+
@discord_bot.tree.command(name="ping", description="🌐 Ping websites 🌐")
|
|
76
|
+
async def ping(interaction: discord.Interaction) -> None: # type: ignore
|
|
77
|
+
await discord_bot_linux_monitor.ping(interaction)
|
|
78
|
+
|
|
79
|
+
@discord_bot.tree.command(name="certificates", description="🔒 Check SSL certificates 🔒")
|
|
80
|
+
async def certificates(interaction: discord.Interaction) -> None: # type: ignore
|
|
81
|
+
await discord_bot_linux_monitor.certificates(interaction)
|
|
82
|
+
|
|
83
|
+
@discord_bot.tree.command(name="reboot_server", description="[Private] 🔄 Restart the entire server 🔄")
|
|
84
|
+
async def reboot(interaction: discord.Interaction) -> None: # type: ignore
|
|
85
|
+
await discord_bot_linux_monitor.reboot(interaction)
|
|
86
|
+
|
|
87
|
+
@discord_bot.tree.command(name="services_status", description="🩺 Check services are running 🩺")
|
|
88
|
+
async def services_status(interaction: discord.Interaction) -> None: # type: ignore
|
|
89
|
+
await discord_bot_linux_monitor.services_status(interaction)
|
|
90
|
+
|
|
91
|
+
@discord_bot.tree.command(name="restart_all", description="🚀 Restart all services 🚀")
|
|
92
|
+
async def restart_all(interaction: discord.Interaction) -> None: # type: ignore
|
|
93
|
+
await discord_bot_linux_monitor.restart_all(interaction)
|
|
94
|
+
|
|
95
|
+
@discord_bot.tree.command(name="restart_service", description="🚀 Restart a service 🚀")
|
|
96
|
+
async def restart_service(interaction: discord.Interaction, service_name: str) -> None: # type: ignore
|
|
97
|
+
await discord_bot_linux_monitor.restart_service(interaction, service_name)
|
|
98
|
+
|
|
99
|
+
@discord_bot.tree.command(name="list_services", description="📋 List all available services 📋")
|
|
100
|
+
async def list_services(interaction: discord.Interaction) -> None: # type: ignore
|
|
101
|
+
await discord_bot_linux_monitor.list_services(interaction)
|
|
102
|
+
|
|
103
|
+
@discord_bot.tree.command(name="ports", description="🔒 Check ports 🔒")
|
|
104
|
+
async def ports(interaction: discord.Interaction) -> None: # type: ignore
|
|
105
|
+
await discord_bot_linux_monitor.ports(interaction)
|
|
106
|
+
|
|
107
|
+
@discord_bot.tree.command(name="list_processes", description="[Private] 📋 List active processes 📋")
|
|
108
|
+
async def list_processes(interaction: discord.Interaction) -> None: # type: ignore
|
|
109
|
+
await discord_bot_linux_monitor.list_processes(interaction)
|
|
110
|
+
|
|
111
|
+
@discord_bot.tree.command(name="kill_process", description="[Private] 🚫 Stop a process by PID 🚫")
|
|
112
|
+
async def kill_process(interaction: discord.Interaction, pid: int) -> None: # type: ignore
|
|
113
|
+
await discord_bot_linux_monitor.kill_process(interaction, pid)
|
|
114
|
+
|
|
115
|
+
#endregion
|
|
116
|
+
|
|
117
|
+
# Start the discord bot
|
|
118
|
+
logging.info(msg="Starting the Discord bot...")
|
|
119
|
+
try:
|
|
120
|
+
discord_bot.run(token=discord_bot_linux_monitor.server_token)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logging.exception(msg=f"Error while running the bot: {e}")
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Discord bot to:
|
|
3
|
+
- monitor a Linux server and inform about its status (in case of issues or periodical info) through Discord channels
|
|
4
|
+
- execute commands from Discord channels
|
|
5
|
+
|
|
6
|
+
Non exhaustive list of features (available by using it in shell or in python script):
|
|
7
|
+
- Do all checks bellow in a scheduled tasks and display the results only if there is an issue (only in console if using only the library)
|
|
8
|
+
- Do all checks bellow in a scheduled tasks and display the results every time (only in console if using only the library)
|
|
9
|
+
|
|
10
|
+
- Check CPU, RAM, SWAP, Temperature
|
|
11
|
+
- Check disk usage
|
|
12
|
+
- Check folder usage
|
|
13
|
+
- Check websites basic availability (ping)
|
|
14
|
+
- Check services status and restart them if needed
|
|
15
|
+
- Check certificates expiration and validity
|
|
16
|
+
- Check last user connections IPs
|
|
17
|
+
- Check uptime (to inform if the server has been rebooted)
|
|
18
|
+
|
|
19
|
+
- Get hostname, OS details, kernel version, server datetime, uptime
|
|
20
|
+
- Get connected users
|
|
21
|
+
|
|
22
|
+
- Get processes list (PID and name)
|
|
23
|
+
- Kill a process by PID
|
|
24
|
+
|
|
25
|
+
- Reboot server
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__author__ = 'Quentin Comte-Gaz'
|
|
29
|
+
__email__ = "quentin@comte-gaz.com"
|
|
30
|
+
__license__ = "MIT License"
|
|
31
|
+
__copyright__ = "Copyright Quentin Comte-Gaz (2024)"
|
|
32
|
+
__python_version__ = "3.+"
|
|
33
|
+
__version__ = "1.0.1 (2024/09/14)"
|
|
34
|
+
__status__ = "Usable for any Linux project"
|
|
35
|
+
|
|
36
|
+
# pyright: reportMissingTypeStubs=false
|
|
37
|
+
from linuxmonitor import LinuxMonitor
|
|
38
|
+
|
|
39
|
+
import discord
|
|
40
|
+
from discord.app_commands.models import AppCommand
|
|
41
|
+
from discord.ext import commands
|
|
42
|
+
import json
|
|
43
|
+
from typing import List, Union, Awaitable, Callable
|
|
44
|
+
|
|
45
|
+
import asyncio
|
|
46
|
+
|
|
47
|
+
import logging
|
|
48
|
+
|
|
49
|
+
class DiscordBotLinuxMonitor:
|
|
50
|
+
|
|
51
|
+
#region Initialization
|
|
52
|
+
|
|
53
|
+
def __init__(self, config_file: str, force_sync_on_startup: bool) -> None:
|
|
54
|
+
logging.debug(msg=f"Loading configuration file {config_file}...")
|
|
55
|
+
with open(file=config_file, mode='r') as file:
|
|
56
|
+
self.config = json.load(file)
|
|
57
|
+
|
|
58
|
+
# Check if the configuration is correct
|
|
59
|
+
self._init_and_check_configuration()
|
|
60
|
+
|
|
61
|
+
# Initialize the LinuxMonitor
|
|
62
|
+
self.monitoring = LinuxMonitor(
|
|
63
|
+
config_file=config_file,
|
|
64
|
+
allow_scheduled_tasks_check_for_issues=(self.channel_name_for_public_error_tasks != "" or self.channel_name_for_private_error_tasks != ""), # type: ignore
|
|
65
|
+
allow_scheduled_task_show_info=(self.channel_name_for_public_infos_tasks != "" or self.channel_name_for_private_infos_tasks != "") # type: ignore
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Initialize the bot
|
|
69
|
+
self.force_sync_on_startup: bool = force_sync_on_startup
|
|
70
|
+
self.MAX_LENGTH_OF_DISCORD_MESSAGE = 2000 # Forced by Discord API
|
|
71
|
+
intents: discord.Intents = discord.Intents.default()
|
|
72
|
+
self.bot = commands.Bot(command_prefix=self.command_prefix, intents=intents)
|
|
73
|
+
|
|
74
|
+
def _init_and_check_configuration(self) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Check if the JSON configuration file is correct for discord usage.
|
|
77
|
+
"""
|
|
78
|
+
# Check if the configuration is a dictionary
|
|
79
|
+
if not isinstance(self.config, dict):
|
|
80
|
+
raise ValueError("The configuration must be a dictionary")
|
|
81
|
+
|
|
82
|
+
# Check if the configuration contains the necessary keys
|
|
83
|
+
if 'discord_config' not in self.config: # type: ignore
|
|
84
|
+
raise ValueError("The configuration must contain the 'discord_config' key")
|
|
85
|
+
|
|
86
|
+
# Get the discord configuration (check if it is a dictionary)
|
|
87
|
+
discord_config: Dict[str, Any] = self.config.get('discord_config', {}) # type: ignore
|
|
88
|
+
if not isinstance(discord_config, dict):
|
|
89
|
+
raise ValueError("The discord configuration (discord_config) must be a dictionary")
|
|
90
|
+
|
|
91
|
+
if 'server_id' not in discord_config:
|
|
92
|
+
raise ValueError("The basic configuration must contain the 'server_id' key")
|
|
93
|
+
self.server_id: int = discord_config.get('server_id') # type: ignore
|
|
94
|
+
|
|
95
|
+
if 'server_token' not in discord_config:
|
|
96
|
+
raise ValueError("The basic configuration must contain the 'server_token' key")
|
|
97
|
+
self.server_token: str = discord_config.get('server_token') # type: ignore
|
|
98
|
+
|
|
99
|
+
if 'command_prefix' not in discord_config:
|
|
100
|
+
raise ValueError("The basic configuration must contain the 'command_prefix' key")
|
|
101
|
+
self.command_prefix: str = discord_config.get('command_prefix', "/") # type: ignore
|
|
102
|
+
|
|
103
|
+
if 'channel_name_for_private_commands' not in discord_config:
|
|
104
|
+
raise ValueError("The basic configuration must contain the 'channel_name_for_private_commands' key")
|
|
105
|
+
self.channel_name_for_private_commands: str = discord_config.get('channel_name_for_private_commands', "") # type: ignore
|
|
106
|
+
|
|
107
|
+
if 'channel_name_for_public_commands' not in discord_config:
|
|
108
|
+
raise ValueError("The basic configuration must contain the 'channel_name_for_public_commands' key")
|
|
109
|
+
self.channel_name_for_public_commands: str = discord_config.get('channel_name_for_public_commands', "") # type: ignore
|
|
110
|
+
|
|
111
|
+
if 'channel_name_for_private_error_tasks' not in discord_config:
|
|
112
|
+
raise ValueError("The basic configuration must contain the 'channel_name_for_private_error_tasks' key")
|
|
113
|
+
self.channel_name_for_private_error_tasks: str = discord_config.get('channel_name_for_private_error_tasks', "") # type: ignore
|
|
114
|
+
|
|
115
|
+
if 'channel_name_for_public_error_tasks' not in discord_config:
|
|
116
|
+
raise ValueError("The basic configuration must contain the 'channel_name_for_public_error_tasks' key")
|
|
117
|
+
self.channel_name_for_public_error_tasks: str = discord_config.get('channel_name_for_public_error_tasks', "") # type: ignore
|
|
118
|
+
|
|
119
|
+
if 'channel_name_for_private_infos_tasks' not in discord_config:
|
|
120
|
+
raise ValueError("The basic configuration must contain the 'channel_name_for_private_infos_tasks' key")
|
|
121
|
+
self.channel_name_for_private_infos_tasks: str = discord_config.get('channel_name_for_private_infos_tasks', "") # type: ignore
|
|
122
|
+
|
|
123
|
+
if 'channel_name_for_public_infos_tasks' not in discord_config:
|
|
124
|
+
raise ValueError("The basic configuration must contain the 'channel_name_for_public_infos_tasks' key")
|
|
125
|
+
self.channel_name_for_public_infos_tasks: str = discord_config.get('channel_name_for_public_infos_tasks', "") # type: ignore
|
|
126
|
+
|
|
127
|
+
if self.channel_name_for_private_commands != "": # type: ignore
|
|
128
|
+
if 'welcome_message_for_private_commands' not in discord_config:
|
|
129
|
+
raise ValueError("The basic configuration must contain the 'welcome_message_for_private_commands' key")
|
|
130
|
+
self.welcome_message_for_private_commands: str = discord_config.get('welcome_message_for_private_commands', "") # type: ignore
|
|
131
|
+
|
|
132
|
+
if self.channel_name_for_public_commands != "": # type: ignore
|
|
133
|
+
if 'welcome_message_for_public_commands' not in discord_config:
|
|
134
|
+
raise ValueError("The basic configuration must contain the 'welcome_message_for_public_commands' key")
|
|
135
|
+
self.welcome_message_for_public_commands: str = discord_config.get('welcome_message_for_public_commands', "") # type: ignore
|
|
136
|
+
|
|
137
|
+
if self.channel_name_for_private_error_tasks != "": # type: ignore
|
|
138
|
+
if 'welcome_message_for_private_error_tasks' not in discord_config:
|
|
139
|
+
raise ValueError("The basic configuration must contain the 'welcome_message_for_private_error_tasks' key")
|
|
140
|
+
self.welcome_message_for_private_error_tasks: str = discord_config.get('welcome_message_for_private_error_tasks', "") # type: ignore
|
|
141
|
+
|
|
142
|
+
if self.channel_name_for_public_error_tasks != "": # type: ignore
|
|
143
|
+
if 'welcome_message_for_public_error_tasks' not in discord_config:
|
|
144
|
+
raise ValueError("The basic configuration must contain the 'welcome_message_for_public_error_tasks' key")
|
|
145
|
+
self.welcome_message_for_public_error_tasks: str = discord_config.get('welcome_message_for_public_error_tasks', "") # type: ignore
|
|
146
|
+
|
|
147
|
+
if self.channel_name_for_private_infos_tasks != "": # type: ignore
|
|
148
|
+
if 'welcome_message_for_private_infos_tasks' not in discord_config:
|
|
149
|
+
raise ValueError("The basic configuration must contain the 'welcome_message_for_private_infos_tasks' key")
|
|
150
|
+
self.welcome_message_for_private_infos_tasks: str = discord_config.get('welcome_message_for_private_infos_tasks', "") # type: ignore
|
|
151
|
+
|
|
152
|
+
if self.channel_name_for_public_infos_tasks != "": # type: ignore
|
|
153
|
+
if 'welcome_message_for_public_infos_tasks' not in discord_config:
|
|
154
|
+
raise ValueError("The basic configuration must contain the 'welcome_message_for_public_infos_tasks' key")
|
|
155
|
+
self.welcome_message_for_public_infos_tasks: str = discord_config.get('welcome_message_for_public_infos_tasks', "") # type: ignore
|
|
156
|
+
|
|
157
|
+
# Check that at least one channel is defined
|
|
158
|
+
if self.channel_name_for_private_commands == "" and self.channel_name_for_public_commands == "" and self.channel_name_for_private_error_tasks == "" and self.channel_name_for_public_error_tasks == "" and self.channel_name_for_private_infos_tasks == "" and self.channel_name_for_public_infos_tasks == "": # type: ignore
|
|
159
|
+
raise ValueError("At least one channel must be defined (private or public) in the configuration file, else there is no point to use this lib (channel_name_for_private_commands, channel_name_for_public_commands, channel_name_for_private_error_tasks, channel_name_for_public_error_tasks, channel_name_for_private_infos_tasks, channel_name_for_public_infos_tasks)")
|
|
160
|
+
|
|
161
|
+
#endregion
|
|
162
|
+
|
|
163
|
+
#region Private methods
|
|
164
|
+
|
|
165
|
+
def _check_if_valid_guild(self, guild: Union[None,discord.Guild]) -> bool:
|
|
166
|
+
if guild is None:
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
# Be sure it is from the correct server
|
|
170
|
+
if guild.id != self.server_id:
|
|
171
|
+
logging.warning(msg=f"Message received from invalid guild '{guild.name}' (id: '{guild.id}'), IGNORING THIS MESSAGE.")
|
|
172
|
+
return False
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
async def _is_bot_channel_interaction(self, interaction: discord.Interaction, send_message_if_not_bot: bool) -> bool:
|
|
176
|
+
res: bool = self._is_bot_channel(channel=interaction.channel) # type: ignore
|
|
177
|
+
if not res and send_message_if_not_bot:
|
|
178
|
+
await interaction.response.send_message(content=f"❌ This is not the right channel to send commands to the bot. You need to communicate in the private channel '{self.channel_name_for_private_commands}' or the public channel '{self.channel_name_for_public_commands}'.", ephemeral=True)
|
|
179
|
+
return res
|
|
180
|
+
|
|
181
|
+
def _is_bot_channel(self, channel) -> bool: # type: ignore
|
|
182
|
+
return self._is_private_channel(channel=channel) or self._is_public_channel(channel=channel) # type: ignore
|
|
183
|
+
|
|
184
|
+
def _is_private_channel(self, channel) -> bool: # type: ignore
|
|
185
|
+
if channel is None:
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
if self.channel_name_for_private_commands == "":
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
res: bool = (self.channel_name_for_private_commands == channel.name) # type: ignore
|
|
192
|
+
return res # type: ignore
|
|
193
|
+
|
|
194
|
+
def _is_public_channel(self, channel) -> bool: # type: ignore
|
|
195
|
+
if channel is None:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
if self._is_private_channel(channel=channel): # type: ignore
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
if self.channel_name_for_public_commands == "":
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
res: bool = (self.channel_name_for_public_commands == channel.name) # type: ignore
|
|
205
|
+
return res # type: ignore
|
|
206
|
+
|
|
207
|
+
async def _channel_send_no_limit(self, channel: discord.TextChannel, msg: str) -> None:
|
|
208
|
+
logging.info(msg=f"Sending message to channel '{channel.name}':\n{msg}")
|
|
209
|
+
|
|
210
|
+
while len(msg) > self.MAX_LENGTH_OF_DISCORD_MESSAGE:
|
|
211
|
+
# Find the last newline within the limit
|
|
212
|
+
split_point = msg.rfind('\n', 0, self.MAX_LENGTH_OF_DISCORD_MESSAGE)
|
|
213
|
+
if split_point == -1: # No newline found, split at max_length
|
|
214
|
+
split_point = self.MAX_LENGTH_OF_DISCORD_MESSAGE
|
|
215
|
+
|
|
216
|
+
# Send the chunk and remove it from the message
|
|
217
|
+
await channel.send(content=msg[:split_point].rstrip())
|
|
218
|
+
msg = msg[split_point:].lstrip()
|
|
219
|
+
|
|
220
|
+
# Send the remaining message
|
|
221
|
+
if msg:
|
|
222
|
+
await channel.send(content=msg)
|
|
223
|
+
|
|
224
|
+
async def _interaction_followup_send_no_limit(self, interaction: discord.Interaction, msg: str) -> None:
|
|
225
|
+
logging.info(msg=f"Sending follow-up message:\n{msg}")
|
|
226
|
+
|
|
227
|
+
# Send the first chunk as a follow-up message
|
|
228
|
+
try:
|
|
229
|
+
if msg == "":
|
|
230
|
+
# Send generic interaction response if no message is returned
|
|
231
|
+
msg = "No answer."
|
|
232
|
+
|
|
233
|
+
if len(msg) > self.MAX_LENGTH_OF_DISCORD_MESSAGE:
|
|
234
|
+
# Find the last newline within the limit or just split at max_length
|
|
235
|
+
split_point = msg.rfind('\n', 0, self.MAX_LENGTH_OF_DISCORD_MESSAGE)
|
|
236
|
+
if split_point == -1: # No newline found, split at max_length
|
|
237
|
+
split_point = self.MAX_LENGTH_OF_DISCORD_MESSAGE
|
|
238
|
+
|
|
239
|
+
await interaction.followup.send(content=msg[:split_point].rstrip())
|
|
240
|
+
msg = msg[split_point:].lstrip()
|
|
241
|
+
else:
|
|
242
|
+
await interaction.followup.send(content=msg)
|
|
243
|
+
return # Exit if the message fits within the limit
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logging.error(msg=f"Error while sending follow-up message: {e}")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
# Handle additional messages that exceed the initial follow-up limit
|
|
250
|
+
while len(msg) > self.MAX_LENGTH_OF_DISCORD_MESSAGE:
|
|
251
|
+
split_point = msg.rfind('\n', 0, self.MAX_LENGTH_OF_DISCORD_MESSAGE)
|
|
252
|
+
if split_point == -1:
|
|
253
|
+
split_point = self.MAX_LENGTH_OF_DISCORD_MESSAGE
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
await interaction.channel.send(content=msg[:split_point].rstrip()) # type: ignore
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logging.error(msg=f"Error while sending message to channel: {e}")
|
|
259
|
+
|
|
260
|
+
msg = msg[split_point:].lstrip()
|
|
261
|
+
|
|
262
|
+
# Send any remaining message directly to the channel
|
|
263
|
+
if msg:
|
|
264
|
+
try:
|
|
265
|
+
await interaction.channel.send(content=msg) # type: ignore
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logging.error(msg=f"Error while sending message to channel: {e}")
|
|
268
|
+
|
|
269
|
+
async def _force_sync(self) -> str:
|
|
270
|
+
try:
|
|
271
|
+
logging.info(msg="Forcing the sync of the bot's commands...")
|
|
272
|
+
synced: List[AppCommand] = await self.bot.tree.sync()
|
|
273
|
+
|
|
274
|
+
out_msg: str = f"🔄 **Commands synchronized successfully** 🔄"
|
|
275
|
+
for s in synced:
|
|
276
|
+
out_msg += f"\n- `{s.name}`: {s.description}"
|
|
277
|
+
|
|
278
|
+
logging.info(msg=out_msg)
|
|
279
|
+
return out_msg
|
|
280
|
+
except Exception as e:
|
|
281
|
+
out_msg = f"**Internal error during command synchronization**:\n```sh\n{e}\n```"
|
|
282
|
+
logging.exception(msg=out_msg)
|
|
283
|
+
return out_msg
|
|
284
|
+
|
|
285
|
+
#endregion
|
|
286
|
+
|
|
287
|
+
#region BOT COMMANDS AND EVENTS DEFINITIONS
|
|
288
|
+
|
|
289
|
+
async def on_ready(self) -> None:
|
|
290
|
+
logging.info(msg=f"Discord bot '{self.bot.user}' is ready.")
|
|
291
|
+
logging.info(msg=f"Connected to the following guilds (will check them): {[guild.name for guild in self.bot.guilds]}")
|
|
292
|
+
for guild in self.bot.guilds:
|
|
293
|
+
if guild.id == self.server_id:
|
|
294
|
+
# Found the correct guild
|
|
295
|
+
logging.info(msg=f"Bot '{self.bot.user}' is connected to the following wanted guild: '{guild.name}' (id: '{guild.id}')..")
|
|
296
|
+
|
|
297
|
+
public_channel_cmd_ready: bool = (self.channel_name_for_public_commands == "")
|
|
298
|
+
private_channel_cmd_ready: bool = (self.channel_name_for_private_commands == "")
|
|
299
|
+
public_channel_error_task_ready: bool = (self.channel_name_for_public_error_tasks == "")
|
|
300
|
+
private_channel_error_task_ready: bool = (self.channel_name_for_private_error_tasks == "")
|
|
301
|
+
public_channel_info_task_ready: bool = (self.channel_name_for_public_infos_tasks == "")
|
|
302
|
+
private_channel_info_task_ready: bool = (self.channel_name_for_private_infos_tasks == "")
|
|
303
|
+
|
|
304
|
+
for channel in guild.text_channels:
|
|
305
|
+
if self.channel_name_for_public_commands != "" and channel.name == self.channel_name_for_public_commands:
|
|
306
|
+
logging.info(msg="Found the public channel, it will be possible to do public commands.")
|
|
307
|
+
public_channel_cmd_ready = True
|
|
308
|
+
if self.welcome_message_for_public_commands != "":
|
|
309
|
+
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_public_commands)
|
|
310
|
+
|
|
311
|
+
if self.channel_name_for_private_commands != "" and channel.name == self.channel_name_for_private_commands:
|
|
312
|
+
logging.info(msg="Found the private channel, it will be possible to do private commands.")
|
|
313
|
+
private_channel_cmd_ready = True
|
|
314
|
+
if self.welcome_message_for_private_commands != "":
|
|
315
|
+
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_private_commands)
|
|
316
|
+
|
|
317
|
+
if self.channel_name_for_public_error_tasks != "" and channel.name == self.channel_name_for_public_error_tasks:
|
|
318
|
+
logging.info(msg="Found the public channel for error task, it will be possible to show public status issues if found periodically.")
|
|
319
|
+
public_channel_error_task_ready = True
|
|
320
|
+
|
|
321
|
+
if self.welcome_message_for_public_error_tasks != "":
|
|
322
|
+
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_public_error_tasks)
|
|
323
|
+
|
|
324
|
+
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}').")
|
|
325
|
+
public_channel_for_error_task: discord.TextChannel = channel
|
|
326
|
+
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))
|
|
327
|
+
|
|
328
|
+
# Start the public schedule task
|
|
329
|
+
self.bot.loop.create_task(self.monitoring.schedule_task(handle_error_message=send_message_public_error_task_func, is_private=False))
|
|
330
|
+
|
|
331
|
+
if self.channel_name_for_public_infos_tasks != "" and channel.name == self.channel_name_for_public_infos_tasks:
|
|
332
|
+
logging.info(msg="Found the public channel for info task, it will be possible to show public info periodically.")
|
|
333
|
+
public_channel_info_task_ready = True
|
|
334
|
+
|
|
335
|
+
if self.welcome_message_for_public_infos_tasks != "":
|
|
336
|
+
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_public_infos_tasks)
|
|
337
|
+
|
|
338
|
+
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}').")
|
|
339
|
+
public_channel_for_info_task: discord.TextChannel = channel
|
|
340
|
+
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))
|
|
341
|
+
|
|
342
|
+
# Start the public schedule task
|
|
343
|
+
self.bot.loop.create_task(self.monitoring.schedule_task_show_info(show_message=send_message_public_info_task_func, is_private=False))
|
|
344
|
+
|
|
345
|
+
if self.channel_name_for_private_error_tasks != "" and channel.name == self.channel_name_for_private_error_tasks:
|
|
346
|
+
logging.info(msg="Found the private channel for error task, it will be possible to show private and public status issues if found periodically.")
|
|
347
|
+
private_channel_error_task_ready = True
|
|
348
|
+
|
|
349
|
+
if self.welcome_message_for_private_error_tasks != "":
|
|
350
|
+
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_private_error_tasks)
|
|
351
|
+
|
|
352
|
+
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}').")
|
|
353
|
+
private_channel_for_error_task: discord.TextChannel = channel
|
|
354
|
+
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))
|
|
355
|
+
|
|
356
|
+
# Start the private schedule task
|
|
357
|
+
self.bot.loop.create_task(self.monitoring.schedule_task(handle_error_message=send_message_private_error_task_func, is_private=True))
|
|
358
|
+
|
|
359
|
+
if self.channel_name_for_private_infos_tasks != "" and channel.name == self.channel_name_for_private_infos_tasks:
|
|
360
|
+
logging.info(msg="Found the private channel for info task, it will be possible to show private info periodically.")
|
|
361
|
+
private_channel_info_task_ready = True
|
|
362
|
+
|
|
363
|
+
if self.welcome_message_for_private_infos_tasks != "":
|
|
364
|
+
await self._channel_send_no_limit(channel=channel, msg=self.welcome_message_for_private_infos_tasks)
|
|
365
|
+
|
|
366
|
+
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}').")
|
|
367
|
+
private_channel_for_info_task: discord.TextChannel = channel
|
|
368
|
+
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))
|
|
369
|
+
|
|
370
|
+
# Start the private schedule task
|
|
371
|
+
self.bot.loop.create_task(self.monitoring.schedule_task_show_info(show_message=send_message_private_info_task_func, is_private=True))
|
|
372
|
+
|
|
373
|
+
# Show a warning if a channel is not found and should be found
|
|
374
|
+
if not public_channel_cmd_ready:
|
|
375
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
376
|
+
logging.critical(msg=f"Public channel '{self.channel_name_for_public_commands}' not found in server: '{guild.name}', public commands will not be available.")
|
|
377
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
378
|
+
if not private_channel_cmd_ready:
|
|
379
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
380
|
+
logging.critical(msg=f"Private channel '{self.channel_name_for_private_commands}' not found in server: '{guild.name}', private commands will not be available.")
|
|
381
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
382
|
+
if not public_channel_error_task_ready:
|
|
383
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
384
|
+
logging.critical(msg=f"Public channel '{self.channel_name_for_public_error_tasks}' not found in server: '{guild.name}', public status issues will not be shown periodically if found.")
|
|
385
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
386
|
+
if not private_channel_error_task_ready:
|
|
387
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
388
|
+
logging.critical(msg=f"Private channel '{self.channel_name_for_private_error_tasks}' not found in server: '{guild.name}', private and public status issues will not be shown periodically if found.")
|
|
389
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
390
|
+
if not public_channel_info_task_ready:
|
|
391
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
392
|
+
logging.critical(msg=f"Public channel '{self.channel_name_for_public_infos_tasks}' not found in server: '{guild.name}', public info will not be shown periodically.")
|
|
393
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
394
|
+
if not private_channel_info_task_ready:
|
|
395
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
396
|
+
logging.critical(msg=f"Private channel '{self.channel_name_for_private_infos_tasks}' not found in server: '{guild.name}', private info will not be shown periodically.")
|
|
397
|
+
logging.critical(msg=f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
398
|
+
else:
|
|
399
|
+
# Found an undesired guild, disconnect
|
|
400
|
+
logging.info(msg=f"Bot '{self.bot.user}' is connected to the following UNDESIRED guild: '{guild.name}' (id: '{guild.id}'), IGNORING THIS GUILD.")
|
|
401
|
+
|
|
402
|
+
# Sync the bot's commands globally
|
|
403
|
+
if self.force_sync_on_startup:
|
|
404
|
+
await self._force_sync()
|
|
405
|
+
|
|
406
|
+
async def force_sync(self, interaction: discord.Interaction) -> None:
|
|
407
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
408
|
+
return
|
|
409
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
413
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
# Say to the user that the command is being processed
|
|
417
|
+
await interaction.response.defer()
|
|
418
|
+
|
|
419
|
+
# Sync the bot's commands only on the 2 public and private channels
|
|
420
|
+
out_msg: str = await self._force_sync()
|
|
421
|
+
|
|
422
|
+
# Respond to the user
|
|
423
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
424
|
+
|
|
425
|
+
async def usage(self, interaction: discord.Interaction) -> None:
|
|
426
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
427
|
+
return
|
|
428
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
# Say to the user that the command is being processed
|
|
432
|
+
await interaction.response.defer()
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
436
|
+
out_msg: str = self.monitoring.check_all_disk_usage(is_private=is_private, display_only_if_critical=False)
|
|
437
|
+
|
|
438
|
+
msg: str = self.monitoring.check_all_folder_usage(is_private=is_private, display_only_if_critical=False)
|
|
439
|
+
if msg != "":
|
|
440
|
+
if out_msg != "":
|
|
441
|
+
out_msg += "\n"
|
|
442
|
+
out_msg += msg
|
|
443
|
+
|
|
444
|
+
if is_private:
|
|
445
|
+
out_msg += "\n"
|
|
446
|
+
out_msg += self.monitoring.check_cpu_usage(display_only_if_critical=False) + "\n"
|
|
447
|
+
out_msg += self.monitoring.check_ram_usage(display_only_if_critical=False) + "\n"
|
|
448
|
+
out_msg += self.monitoring.check_swap_usage(display_only_if_critical=False) + "\n"
|
|
449
|
+
out_msg += self.monitoring.check_cpu_temperature(display_only_if_critical=False) + "\n"
|
|
450
|
+
out_msg += self.monitoring.get_network_info()
|
|
451
|
+
|
|
452
|
+
# Respond to the user
|
|
453
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
454
|
+
except Exception as e:
|
|
455
|
+
out_msg = f"**Internal error retrieving usage info**:\n```sh\n{e}\n```"
|
|
456
|
+
logging.exception(msg=out_msg)
|
|
457
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
458
|
+
|
|
459
|
+
async def os_infos(self, interaction: discord.Interaction) -> None:
|
|
460
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
461
|
+
return
|
|
462
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
# Say to the user that the command is being processed
|
|
466
|
+
await interaction.response.defer()
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
out_msg: str = self.monitoring.get_hostname() + "\n"
|
|
470
|
+
out_msg += self.monitoring.get_os_details() + "\n"
|
|
471
|
+
out_msg += self.monitoring.get_kernel_version() + "\n"
|
|
472
|
+
out_msg += self.monitoring.check_uptime(display_only_if_critical=False) + "\n"
|
|
473
|
+
out_msg += self.monitoring.get_server_datetime()
|
|
474
|
+
|
|
475
|
+
# Respond to the user
|
|
476
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
477
|
+
except Exception as e:
|
|
478
|
+
out_msg = f"**Internal error retrieving OS info**:\n```sh\n{e}\n```"
|
|
479
|
+
logging.exception(msg=out_msg)
|
|
480
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
481
|
+
|
|
482
|
+
async def users(self, interaction: discord.Interaction) -> None:
|
|
483
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
484
|
+
return
|
|
485
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
486
|
+
return
|
|
487
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
488
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
# Say to the user that the command is being processed
|
|
492
|
+
await interaction.response.defer()
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
out_msg: str = self.monitoring.get_connected_users()
|
|
496
|
+
|
|
497
|
+
# Respond to the user
|
|
498
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
499
|
+
except Exception as e:
|
|
500
|
+
out_msg = f"**Internal error retrieving connected users**:\n```sh\n{e}\n```"
|
|
501
|
+
logging.exception(msg=out_msg)
|
|
502
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
503
|
+
|
|
504
|
+
async def user_logins(self, interaction: discord.Interaction) -> None:
|
|
505
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
506
|
+
return
|
|
507
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
511
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
# Indiquer que la commande est en cours de traitement
|
|
515
|
+
await interaction.response.defer()
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
# Récupérer les dernières connexions des utilisateurs
|
|
519
|
+
out_msg: str = self.monitoring.check_all_recent_user_logins(display_only_if_critical=False)
|
|
520
|
+
|
|
521
|
+
# Répondre à l'utilisateur
|
|
522
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
out_msg = f"**Internal error retrieving last user connections**:\n```sh\n{e}\n```"
|
|
526
|
+
logging.exception(msg=out_msg)
|
|
527
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
528
|
+
|
|
529
|
+
async def ping(self, interaction: discord.Interaction) -> None:
|
|
530
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
531
|
+
return
|
|
532
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
533
|
+
return
|
|
534
|
+
|
|
535
|
+
# Say to the user that the command is being processed
|
|
536
|
+
await interaction.response.defer()
|
|
537
|
+
|
|
538
|
+
try:
|
|
539
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
540
|
+
out_msg: str = self.monitoring.ping_all_websites(is_private=is_private, display_only_if_critical=False)
|
|
541
|
+
|
|
542
|
+
# Respond to the user
|
|
543
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
544
|
+
except Exception as e:
|
|
545
|
+
out_msg = f"**Internal error during websites ping **:\n```sh\n{e}\n```"
|
|
546
|
+
logging.exception(msg=out_msg)
|
|
547
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
548
|
+
|
|
549
|
+
async def certificates(self, interaction: discord.Interaction) -> None:
|
|
550
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
551
|
+
return
|
|
552
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
# Say to the user that the command is being processed
|
|
556
|
+
await interaction.response.defer()
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
560
|
+
out_msg: str = self.monitoring.check_all_certificates(is_private=is_private, display_only_if_critical=False)
|
|
561
|
+
|
|
562
|
+
# Respond to the user
|
|
563
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
564
|
+
except Exception as e:
|
|
565
|
+
out_msg = f"**Internal error during SSL certificate checks **:\n```sh\n{e}\n```"
|
|
566
|
+
logging.exception(msg=out_msg)
|
|
567
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
568
|
+
|
|
569
|
+
async def reboot(self, interaction: discord.Interaction) -> None:
|
|
570
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
571
|
+
return
|
|
572
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
573
|
+
return
|
|
574
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
575
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
576
|
+
return
|
|
577
|
+
|
|
578
|
+
# Say to the user that the command is being processed
|
|
579
|
+
await interaction.response.defer()
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
out_msg: str = self.monitoring.reboot_server()
|
|
583
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
584
|
+
|
|
585
|
+
except Exception as e:
|
|
586
|
+
out_msg = f"**Internal error during server reboot**:\n```sh\n{e}\n```"
|
|
587
|
+
logging.exception(msg=out_msg)
|
|
588
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
589
|
+
|
|
590
|
+
async def services_status(self, interaction: discord.Interaction) -> None:
|
|
591
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
592
|
+
return
|
|
593
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
# Say to the user that the command is being processed
|
|
597
|
+
await interaction.response.defer()
|
|
598
|
+
|
|
599
|
+
try:
|
|
600
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
601
|
+
out_msg: str = self.monitoring.check_all_services_status(is_private=is_private)
|
|
602
|
+
|
|
603
|
+
# Respond to the user
|
|
604
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
605
|
+
except Exception as e:
|
|
606
|
+
out_msg = f"**Internal error checking services are running**:\n```sh\n{e}\n```"
|
|
607
|
+
logging.exception(msg=out_msg)
|
|
608
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
609
|
+
|
|
610
|
+
async def restart_all(self, interaction: discord.Interaction) -> None:
|
|
611
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
612
|
+
return
|
|
613
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
# Say to the user that the command is being processed
|
|
617
|
+
await interaction.response.defer()
|
|
618
|
+
|
|
619
|
+
try:
|
|
620
|
+
# Restart all services and get the results
|
|
621
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
622
|
+
out_msg: str = self.monitoring.restart_all_services(is_private=is_private)
|
|
623
|
+
|
|
624
|
+
# Respond to the user
|
|
625
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
626
|
+
except Exception as e:
|
|
627
|
+
out_msg = f"**Internal error restarting all services**:\n```sh\n{e}\n```"
|
|
628
|
+
logging.exception(msg=out_msg)
|
|
629
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
630
|
+
|
|
631
|
+
async def restart_service(self, interaction: discord.Interaction, service_name: str) -> None:
|
|
632
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
633
|
+
return
|
|
634
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
635
|
+
return
|
|
636
|
+
|
|
637
|
+
# Indiquer que la commande est en cours de traitement
|
|
638
|
+
await interaction.response.defer()
|
|
639
|
+
|
|
640
|
+
try:
|
|
641
|
+
# Redémarrer le service et récupérer le message de sortie
|
|
642
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
643
|
+
out_msg: str = self.monitoring.restart_service(is_private=is_private, service_name=service_name)
|
|
644
|
+
|
|
645
|
+
# Répondre à l'utilisateur
|
|
646
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
647
|
+
|
|
648
|
+
except Exception as e:
|
|
649
|
+
out_msg = f"**Internal error restarting service {service_name}**:\n```sh\n{e}\n```"
|
|
650
|
+
logging.exception(msg=out_msg)
|
|
651
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
652
|
+
|
|
653
|
+
async def list_services(self, interaction: discord.Interaction) -> None:
|
|
654
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
655
|
+
return
|
|
656
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
# Indiquer que la commande est en cours de traitement
|
|
660
|
+
await interaction.response.defer()
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
# Récupérer la liste des services
|
|
664
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
665
|
+
out_msg: str = self.monitoring.get_all_services_allowed_to_restart(is_private=is_private)
|
|
666
|
+
|
|
667
|
+
# Répondre à l'utilisateur
|
|
668
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
669
|
+
except Exception as e:
|
|
670
|
+
out_msg = f"**Internal error retrieving available services**:\n```sh\n{e}\n```"
|
|
671
|
+
logging.exception(msg=out_msg)
|
|
672
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
673
|
+
|
|
674
|
+
async def ports(self, interaction: discord.Interaction) -> None:
|
|
675
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
676
|
+
return
|
|
677
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
678
|
+
return
|
|
679
|
+
|
|
680
|
+
# Indiquer que la commande est en cours de traitement
|
|
681
|
+
await interaction.response.defer()
|
|
682
|
+
|
|
683
|
+
try:
|
|
684
|
+
# Récupérer le statut des ports
|
|
685
|
+
is_private: bool = self._is_private_channel(channel=interaction.channel) # type: ignore
|
|
686
|
+
out_msg: str = self.monitoring.check_all_ports(restart_if_down=True, is_private=is_private, display_only_if_critical=False)
|
|
687
|
+
|
|
688
|
+
# Répondre à l'utilisateur
|
|
689
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
690
|
+
except Exception as e:
|
|
691
|
+
out_msg = f"**Internal error checking ports**:\n```sh\n{e}\n```"
|
|
692
|
+
logging.exception(msg=out_msg)
|
|
693
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
694
|
+
|
|
695
|
+
async def list_processes(self, interaction: discord.Interaction) -> None:
|
|
696
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
697
|
+
return
|
|
698
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
699
|
+
return
|
|
700
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
701
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
702
|
+
return
|
|
703
|
+
|
|
704
|
+
# Indiquer que la commande est en cours de traitement
|
|
705
|
+
await interaction.response.defer()
|
|
706
|
+
|
|
707
|
+
try:
|
|
708
|
+
# Récupérer la liste des processus actifs
|
|
709
|
+
out_msg: str = self.monitoring.get_ordered_processes()
|
|
710
|
+
|
|
711
|
+
# Répondre à l'utilisateur
|
|
712
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
713
|
+
except Exception as e:
|
|
714
|
+
out_msg = f"**Internal error retrieving active processes**:\n```sh\n{e}\n```"
|
|
715
|
+
logging.exception(msg=out_msg)
|
|
716
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
717
|
+
|
|
718
|
+
async def kill_process(self, interaction: discord.Interaction, pid: int) -> None:
|
|
719
|
+
if not self._check_if_valid_guild(guild=interaction.guild):
|
|
720
|
+
return
|
|
721
|
+
if not (await self._is_bot_channel_interaction(interaction=interaction, send_message_if_not_bot=True)):
|
|
722
|
+
return
|
|
723
|
+
if not self._is_private_channel(channel=interaction.channel): # type: ignore
|
|
724
|
+
await interaction.response.send_message(content="❌ Public channels do not allow this command.", ephemeral=True)
|
|
725
|
+
return
|
|
726
|
+
|
|
727
|
+
# Indiquer que la commande est en cours de traitement
|
|
728
|
+
await interaction.response.defer()
|
|
729
|
+
|
|
730
|
+
try:
|
|
731
|
+
# Arrêter le processus et récupérer le message de sortie
|
|
732
|
+
out_msg: str = self.monitoring.kill_process(pid=pid)
|
|
733
|
+
|
|
734
|
+
# Répondre à l'utilisateur
|
|
735
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
736
|
+
except Exception as e:
|
|
737
|
+
out_msg = f"**Internal error stopping process of PID {pid}**:\n```sh\n{e}\n```"
|
|
738
|
+
logging.exception(msg=out_msg)
|
|
739
|
+
await self._interaction_followup_send_no_limit(interaction=interaction, msg=out_msg)
|
|
740
|
+
|
|
741
|
+
#endregion
|