Skip to content

Commit f4fae2a

Browse files
committed
Refine online command interface
1 parent f0cdf49 commit f4fae2a

5 files changed

Lines changed: 139 additions & 51 deletions

File tree

cogs/looped_tasks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ def __init__(self, bot: commands.Bot):
1313
self._bot = bot
1414
self._current_activity = 0
1515
self._messages_to_delete = []
16+
self._previous_messages = []
1617
self._cooldown_for_deletion = []
1718
self._make_wish = True
1819

20+
self.online_player_cache = []
1921

2022
def add_message_to_delete(self, cooldown: int, message: discord.Message):
2123
"""
@@ -30,6 +32,10 @@ def add_message_to_delete(self, cooldown: int, message: discord.Message):
3032
self._messages_to_delete.append(message)
3133

3234

35+
def add_message_to_previous(self, message: discord.Message):
36+
self._previous_messages.append(message)
37+
38+
3339
async def _delete_messages(self):
3440
"""
3541
Deletes all messages which have a cooldown of zero,
@@ -105,6 +111,13 @@ async def update_activity(self):
105111
"""
106112
activity = None
107113

114+
new_player_cache = get_players_online()
115+
if new_player_cache != self.online_player_cache:
116+
for message in self._previous_messages:
117+
self.add_message_to_delete(config.DELETION_COOLDOWN, message)
118+
self.online_player_cache = new_player_cache
119+
self._previous_messages.clear()
120+
108121
await self._delete_messages()
109122
self._make_wish = await handle_server_commands(self._make_wish)
110123

cogs/mcstatus_commands.py

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,80 @@
66
from discord.ext import commands
77
from typing import List
88
from server import get_players_online, setup_player
9-
9+
from PIL import Image
10+
from io import BytesIO
11+
import os
1012

1113
class McStatus(commands.Cog, name = config.MCSTATUS_COG_NAME):
1214
"""Commands which give info about the server's status"""
1315
def __init__(self, bot: commands.Bot):
1416
self._bot = bot
1517

16-
1718
@staticmethod
18-
def _create_online_embed(players: List[str]) -> discord.Embed:
19+
def _create_embed_player_image(previous_images, next_image):
20+
image1 = previous_images.convert("RGBA")
21+
image2 = next_image.convert("RGBA")
22+
gap = 10
23+
24+
(width1, height1) = image1.size
25+
(width2, height2) = image2.size
26+
27+
result_width = width1 + gap + width2
28+
result_height = max(height1, height2)
29+
30+
# Create a new image with RGBA mode to support transparency
31+
result = Image.new('RGBA', (result_width, result_height), (255, 255, 255, 0))
32+
result.paste(im = image1, box = (0, 0))
33+
34+
# Extract the alpha channel of the second image
35+
image2_alpha = image2.split()[-1]
36+
result.paste(im = image2, box = (width1 + gap, 0), mask = image2_alpha)
37+
38+
# Save the merged image as PNG to preserve transparency
39+
return result
40+
41+
def _create_online_embed(self, players: List[tuple[str, Image.Image]]) -> discord.Embed:
1942
"""
2043
Creates an embed for the !online command
2144
2245
:param players: List of players currently online
2346
:return: discord.Embed to be sent by the !online command
2447
"""
25-
print(players)
48+
file = None
49+
2650
if players:
27-
players_formatted = '\n'.join(players)
51+
script_dir = os.path.dirname(os.path.abspath(__file__))
52+
file_path = os.path.join(script_dir, "temp.png")
53+
54+
usernames = []
55+
for player in players:
56+
usernames.append(player[0])
57+
58+
image = players[0][1]
59+
if len(players) > 1:
60+
for player in players[1:]:
61+
image = self._create_embed_player_image(image, player[1])
62+
63+
64+
image.save(file_path)
65+
66+
players_formatted = '\n'.join(usernames)
2867
players_formatted = f'```\n{players_formatted}\n```'
2968

3069
embed = discord.Embed(title = 'The following players are online!',
3170
color = discord.Color.green(),
3271
description = players_formatted)
72+
embed.set_footer(text = "This message will disappear when outdated")
73+
74+
file = discord.File(file_path, filename = "image.png")
75+
embed.set_image(url = "attachment://image.png")
76+
77+
os.remove(file_path)
3378
else:
3479
embed = discord.Embed(title = 'No one is online!',
3580
color = discord.Color.red())
3681

37-
return embed
38-
82+
return embed, file
3983

4084
@commands.command(name = 'online', description = config.ONLINE_DESCRIPTION, help = config.ONLINE_HELP)
4185
async def _online(self, ctx: commands.Context):
@@ -44,16 +88,19 @@ async def _online(self, ctx: commands.Context):
4488
4589
:param ctx: Message context sent by the Discord API
4690
"""
47-
original_message = await ctx.send('Loading...')
91+
loading_message = await ctx.send('Loading...')
4892
try:
49-
players = get_players_online()
93+
_loop_cog = self._bot.get_cog('LoopedTasks')
94+
players = _loop_cog.online_player_cache
95+
96+
embed, file = self._create_online_embed(players)
97+
# await original_message.edit(content = None, embed = embed)
98+
await loading_message.delete()
99+
original_message = await ctx.send(content = None, embed = embed, file = file)
50100

51-
embed = self._create_online_embed(players)
52-
await original_message.edit(content = None, embed = embed)
53101

54-
loop_cog = self._bot.get_cog('LoopedTasks')
55-
loop_cog.add_message_to_delete(config.DELETION_COOLDOWN, original_message)
56-
loop_cog.add_message_to_delete(config.DELETION_COOLDOWN, ctx.message)
102+
_loop_cog.add_message_to_previous(original_message)
103+
_loop_cog.add_message_to_previous(ctx.message)
57104
except Exception as exc:
58105
print(f"ERROR during !online: {exc}")
59106
await original_message.edit(content = config.ONLINE_ERROR)

server/commands.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from .status import _send_rcon_command
1+
from .utils import send_rcon_command
22
import asyncio
33
import datetime
44

55

66
async def send_eleven_eleven():
7-
_send_rcon_command('/tellraw @a ["",{"text":"it is 11:11, ","color":"dark_purple"},{"text":"make a wish! ✨","color":"dark_purple","italic":true}]')
7+
send_rcon_command('/tellraw @a ["",{"text":"it is 11:11, ","color":"dark_purple"},{"text":"make a wish! ✨","color":"dark_purple","italic":true}]')
88

99
async def handle_server_commands(make_wish):
1010
# Handle the 11:11 notification
@@ -19,6 +19,5 @@ async def handle_server_commands(make_wish):
1919
return make_wish
2020

2121
def setup_player(player_name: str):
22-
_send_rcon_command(f'/whitelist add {player_name}')
23-
_send_rcon_command(f'/spawnpoint {player_name} 2969 67 -9608')
24-
_send_rcon_command(f'/tellraw @a "Added player {player_name}"')
22+
send_rcon_command(f'/whitelist add {player_name}')
23+
send_rcon_command(f'/tellraw @a "Added player {player_name}"')

server/status.py

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from .utils import send_rcon_command, get_player_head
12
from mcstatus import JavaServer
2-
from mcrcon import MCRcon
33
from collections import namedtuple
4+
from PIL import Image
45
import config
56

67
serverInfo = namedtuple('serverInfo', ['players', 'num_online'])
@@ -10,43 +11,47 @@
1011

1112

1213
def connect_to_server() -> JavaServer:
13-
"""
14-
Connects to the server defined in config
15-
:return: JavaServer connection
16-
"""
17-
server = JavaServer(config.SERVER, config.QUERY_PORT)
18-
return server
14+
"""
15+
Connects to the server defined in config
16+
:return: JavaServer connection
17+
"""
18+
server = JavaServer(config.SERVER, config.QUERY_PORT)
19+
return server
1920

2021

2122
def get_info_from_server(server: JavaServer) -> serverInfo:
22-
"""
23-
Gets info from JavaServer connection
24-
25-
:param server: JavaServer connection from connect_to_server method
26-
:return: ServerInfo namedtuple
27-
"""
28-
status = server.status().raw
29-
num_online = status['players']['online']
30-
if num_online == 0:
31-
server_info = serverInfo([], num_online)
32-
else:
33-
players = [user['name'] for user in status['players']['sample']]
34-
server_info = serverInfo(players, num_online)
35-
36-
return server_info
37-
38-
def _send_rcon_command(command: str) -> str:
39-
with MCRcon(f"{config.SERVER}", config.RCON_PASSWORD, port=config.RCON_PORT) as mcr:
40-
resp = mcr.command(command)
41-
return resp
42-
43-
def get_players_online() -> list[str]:
44-
raw_players = _send_rcon_command("/list")
23+
"""
24+
Gets info from JavaServer connection
25+
26+
:param server: JavaServer connection from connect_to_server method
27+
:return: ServerInfo namedtuple
28+
"""
29+
status = server.status().raw
30+
num_online = status['players']['online']
31+
if num_online == 0:
32+
server_info = serverInfo([], num_online)
33+
else:
34+
players = [user['name'] for user in status['players']['sample']]
35+
server_info = serverInfo(players, num_online)
36+
37+
return server_info
38+
39+
def get_players_online() -> list[tuple[str, Image.Image]]:
40+
player_head_url = "https://minotar.net/helm/{username}/50.png"
41+
42+
raw_players = send_rcon_command("/list")
4543
raw_players = raw_players.split(":")[1].split(",")
4644
players = []
4745
for player in raw_players:
4846
players.append(player.strip())
4947

5048
if players == [""]:
51-
return []
52-
return players
49+
players = []
50+
51+
response = []
52+
for player in players:
53+
_formatted_url = player_head_url.format(username = player)
54+
player_head = get_player_head(_formatted_url)
55+
response.append((player, player_head))
56+
57+
return response

server/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from mcrcon import MCRcon
2+
from io import BytesIO
3+
from PIL import Image
4+
import requests
5+
import config
6+
7+
def send_rcon_command(command: str) -> str:
8+
with MCRcon(f"{config.SERVER}", config.RCON_PASSWORD, port=config.RCON_PORT) as mcr:
9+
resp = mcr.command(command)
10+
return resp
11+
12+
def _make_api_request(url: str) -> requests.Response:
13+
response = requests.get(url)
14+
response.raise_for_status() # Raise an error on bad status code
15+
return response
16+
17+
def _get_image_from_response(response: requests.Response) -> Image.Image:
18+
image = Image.open(BytesIO(response.content))
19+
return image
20+
21+
def get_player_head(url: str) -> Image.Image:
22+
response = _make_api_request(url)
23+
head = _get_image_from_response(response)
24+
return head

0 commit comments

Comments
 (0)