-
Notifications
You must be signed in to change notification settings - Fork 1
/
discord_bot.py
445 lines (302 loc) · 12.9 KB
/
discord_bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
import discord
import subprocess
import psutil
from datetime import datetime
from discord.ext import tasks
from mcipc.query import Client as mc_query
from user_settings import *
################################################################
# EDIT BELLOW CODE ONLY AS LAST RESORT #
################################################################
###################################
# Variables & Parameters #
###################################
#EMOJI ID
START_ID = int(START.split(':')[-1].split('>')[0])
STOP_ID = int(STOP.split(':')[-1].split('>')[0])
RESTART_ID = int(RESTART.split(':')[-1].split('>')[0])
#Global
msg_id = 0
auto_restart = 0
#Discord parameters
intents = discord.Intents.default()
intents.members = True
client = discord.Client(intents=intents)
###################################
# Functions #
###################################
#Function to transform a player list given by a minecraft query to an discord markdown message
def player_list_to_markdown(player_list):
#Start the markdown
online_players = "```"
counter = 0
#Check if there is no player online
if len(player_list) != 0:
#Go through the list of players
for player in player_list:
counter += 1
#Set one player by line with a number (1 - Username)
online_players += str(counter) + " - " + str(player) + "\n"
else:
#Default message if no players are online
online_players += "No player online..."
#End the markdown
online_players += "```"
return online_players
#Function to get the CPU usage
def get_cpu_usage():
#Try to get the CPU usage
try:
#Get the current CPU usage via top command and some pipes
result = subprocess.run(["top", "-b", "-n", "1"], stdout=subprocess.PIPE, check=True)
#Name of the user on which the server is running
output = subprocess.run(["grep", server_user], input=result.stdout, stdout=subprocess.PIPE, check=True)
output = subprocess.run(["grep", "java"], input=output.stdout, stdout=subprocess.PIPE, check=True)
output = subprocess.run(["awk", "{print $9}"], input=output.stdout, stdout=subprocess.PIPE, check=True)
output_str = output.stdout.decode('utf-8').strip()
#Do a savant calculation to get the total CPU usage and not the CPU usage by tread (more than 100%)
cpu_percent = psutil.cpu_percent(interval=1)
cpu_usage = round(float(output_str) * cpu_percent / 100, 2)
#If we get an error, set the value to default
except:
cpu_usage = 0
return cpu_usage
#Function to get the Memory usage
def get_mem_usage():
#Try to get the memory usage
try:
#Get the current Memory usage via top command and some pipes
result = subprocess.run(["top", "-b", "-n", "1"], stdout=subprocess.PIPE)
#Name of the user on which the server is running
output = subprocess.run(["grep", server_user], input=result.stdout, stdout=subprocess.PIPE)
output = subprocess.run(["grep", "java"], input=output.stdout, stdout=subprocess.PIPE)
output = subprocess.run(["awk", "{print $10}"], input=output.stdout, stdout=subprocess.PIPE)
output_str = output.stdout.decode('utf-8').strip()
#Do a calculation to get the correct total memory usage
memory_usage = round((float(output_str) / 100) * psutil.virtual_memory().total / (1024 * 1024 * 1024),2)
#If we get an error, set the value to default
except:
memory_usage = 0
return memory_usage
#Function to get the server information via query
def get_srv_info_query():
#Try to get the information of the server via query
try:
#If the server responded, everything is running
with mc_query('192.168.1.4', 25565) as client_mc:
full_stats = client_mc.full_stats
#Number of players connected
number_of_player = full_stats.num_players
#Max number of players of the server
max_players = full_stats.max_players
#List of players currently online
player_list = full_stats.players
#Set the status to "Running"
status = ":green_circle: Running"
#If we get an error, that means that the server is currently restarting
except:
#Put all our information to default values
number_of_player = 0
max_players = 0
player_list = []
#Set the status to "Restarting"
status = ":orange_circle: Restarting"
return number_of_player, max_players, player_list, status
#Function to create the embedded message
def create_embed(status, cpu_usage, memory_usage, number_of_player, max_players, online_players):
#Title of the message
embed=discord.Embed(title="Server Info", description=" ", color=0x38761D)
#Image icon
embed.set_thumbnail(url=image_link)
#Current status of the server
embed.add_field(name="Server Status", value=status, inline=False)
#Modpack
embed.add_field(name=modpack_name, value=modpack_info, inline=True)
#IP
embed.add_field(name="IP", value=ip, inline=True)
#CPU usage
embed.add_field(name="CPU Usage", value=str(cpu_usage)+" %", inline=True)
#Memory usage
embed.add_field(name="Memory Usage", value=str(memory_usage)+" GB", inline=True)
#Player count
embed.add_field(name="Player Count", value=str(number_of_player)+"/"+str(max_players), inline=True)
#Online players
embed.add_field(name="Online Players", value=online_players, inline=False)
#Footer
embed.set_footer(text="Power By Padi • "+datetime.now().strftime('%A %H:%M'))
return embed
#Function to create the embedded message for the logs
def create_embed_logs(username, action):
if action == 'stop':
#Title of the message
embed=discord.Embed(title="Minecraft Server", description=username + " has sent a [stop] command via the discord bot.", color=0xa80000)
#Edit the Author Field
embed.set_author(name="⚠️ Alert - Server Stopping ⚠️", icon_url=image_link)
elif action == 'start':
#Title of the message
embed=discord.Embed(title="Minecraft Server", description=username + " has sent a [start] command via the discord bot.", color=0xa80000)
#Edit the Author Field
embed.set_author(name="⚠️ Alert - Server Starting ⚠️", icon_url=image_link)
elif action == 'restart':
#Title of the message
embed=discord.Embed(title="Minecraft Server", description=username + " has sent a [restart] command via the discord bot.", color=0xa80000)
#Edit the Author Field
embed.set_author(name="⚠️ Alert - Server Restarting ⚠️", icon_url=image_link)
#Footer
embed.set_footer(text="Power By Padi • "+datetime.now().strftime('%A %H:%M'))
return embed
#Discord Bot task that will loop every X seconds, or minutes, or hours
#Main task = edit the embedded message
#Secondary task = Try to restart the server in case it crashes
@tasks.loop(seconds=30)
async def send_message_loop(msg):
#Get the status of the Auto Restart
global auto_restart
#Check if the screen session of the minecraft server is running
result = subprocess.run(["screen", "-S", screen_name, "-Q", "select", "."], stdout=subprocess.PIPE)
server_status = result.stdout.decode('utf-8').strip()
#If the screen session is running
if server_status == "":
#Reset the Auto Restart status
auto_restart = 0
#Get the information of the server via query
number_of_player, max_players, player_list, status = get_srv_info_query()
#Get the CPU usage
cpu_usage = get_cpu_usage()
#Get the memory usage
memory_usage = get_mem_usage()
#If the screen session is not running (Server is stopped)
else:
#Set the status to "Stopped"
status = ":red_circle: Stopped"
#Put all our information to default values
number_of_player = 0
max_players = 0
player_list = []
cpu_usage = 0
memory_usage = 0
#Try to restart the server ones
if auto_restart == 0:
#Set the status to "Auto Restart"
status = ":red_circle: Stopped - Auto Restart"
auto_restart = 1
subprocess.run(restart_script.split())
#Format players list to be a markdown for the embedded message
online_players = player_list_to_markdown(player_list)
#Create the embedded message
embed = create_embed(status, cpu_usage, memory_usage, number_of_player, max_players, online_players)
#Edit the message
await msg.edit(embed=embed)
#First action of the Discord Bot
@client.event
async def on_ready():
#Get the msg_id to set it with the first message
global msg_id
#Small log just to confirm that the bot has successfully started
print('I am connected as {0.user}'.format(client))
#Set the Bot Status on Discord
await client.change_presence(activity=discord.Game(name="monitor the server"))
#Check if the screen session of the minecraft server is running
result = subprocess.run(["screen", "-S", screen_name, "-Q", "select", "."], stdout=subprocess.PIPE)
server_status = result.stdout.decode('utf-8').strip()
#If the screen session is running
if server_status == "":
#Get the information of the server via query
number_of_player, max_players, player_list, status = get_srv_info_query()
#Get the CPU usage
cpu_usage = get_cpu_usage()
#Get the memory usage
memory_usage = get_mem_usage()
#If the screen session is not running (Server is stopped)
else:
#Set the status to "Stopped"
status = ":red_circle: Stopped"
#Put all our information to default values
number_of_player = 0
max_players = 0
player_list = []
cpu_usage = 0
memory_usage = 0
#Format players list to be a markdown for the embedded message
online_players = player_list_to_markdown(player_list)
#Create the embedded message
embed = create_embed(status, cpu_usage, memory_usage, number_of_player, max_players, online_players)
#Set the Discord channel
channel = client.get_channel(channel_minecraft)
#Check if the last message on the channel is from the Bot to avoid a delete + a resend (SPAM)
last_message = None
async for message in channel.history(limit=1):
last_message = message
if last_message is not None and last_message.author == client.user:
#Use the last message as our new message
msg_id = last_message.id
msg = last_message
await msg.edit(embed=embed)
await msg.clear_reactions()
else:
#Remove ALL old messages
await channel.purge()
#Send the message and take it's ID and information for future editing
msg = await channel.send(embed=embed)
msg_id = msg.id
#Set the emojis that will be added to the message
emojis = [START, STOP, RESTART]
#Loop thought the emojis table and add every emoji to the message
for emoji in emojis:
await msg.add_reaction(emoji)
#Start the loop task
send_message_loop.start(msg)
#When an emoji / reaction is added to the message
@client.event
async def on_raw_reaction_add(payload):
#Get the discord information
guild = client.guilds[0]
#Set the specific role that will be allowed to start actions with the reactions
role = discord.utils.get(guild.roles, name=discord_role)
#Get the information about the reaction
channel = await client.fetch_channel(payload.channel_id)
message = await channel.fetch_message(payload.message_id)
#Get the user
user = await client.fetch_user(payload.user_id)
member = guild.get_member(user.id)
#Check if the user is not the bot / if the reaction is on our embedded message / if the user has the good role
if message.author == client.user and payload.user_id != client.user.id and role in member.roles:
#Remove the reaction
await message.remove_reaction(payload.emoji, user)
#Execute the the correct action depending on the reaction
if payload.emoji.id == START_ID:
#Create the embedded message
embed = create_embed_logs(member.name, 'start')
#Set the Discord channel
channel = client.get_channel(channel_logs)
#Send the Log
await channel.send(embed=embed)
#Start the server
subprocess.run(start_script.split())
elif payload.emoji.id == STOP_ID:
#Create the embedded message
embed = create_embed_logs(member.name, 'stop')
#Set the Discord channel
channel = client.get_channel(channel_logs)
#Send the Log
await channel.send(embed=embed)
#Stop the server
subprocess.run(stop_script.split())
elif payload.emoji.id == RESTART_ID:
#Create the embedded message
embed = create_embed_logs(member.name, 'restart')
#Set the Discord channel
channel = client.get_channel(channel_logs)
#Send the Log
await channel.send(embed=embed)
#Restart the server
subprocess.run(restart_script.split())
#If the user has not the good role, simply remove the reaction
elif message.author == client.user and payload.user_id != client.user.id :
#Remove the reaction
await message.remove_reaction(payload.emoji, user)
def main():
#Start the bot (API KEY)
client.run(API_KEY)
if __name__ == "__main__":
main()