Skip to content

Commit 88c22c3

Browse files
committed
feat: Implement to-do list items and fix runtime error
This commit implements all items from the to-do list and fixes the 'no running event loop' error: - Fix event loop error by moving scheduler initialization to on_ready() function - Add Docker support with Dockerfile and docker-compose.yml for containerization - Implement real-time status command (*status) showing online players - Add automatic update system with manual (*update) and startup options - Restructure project with organized directories (cogs, utils, scripts, docker) - Add GitHub Actions workflows for CI/CD and automated Docker image publishing - Create documentation for the release process - Implement VERSION file for better version tracking - Update help command with separate sections for user and admin commands All requested features are now complete and the application structure is improved for better maintainability and deployment.
1 parent cf0654b commit 88c22c3

File tree

15 files changed

+500
-12
lines changed

15 files changed

+500
-12
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Docker Image CI/CD
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
tags: [ 'v*.*.*' ]
7+
release:
8+
types: [published]
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
IMAGE_NAME: ${{ github.repository }}
13+
14+
jobs:
15+
build-and-push:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
packages: write
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v3
24+
25+
- name: Log into registry ${{ env.REGISTRY }}
26+
uses: docker/login-action@v2
27+
with:
28+
registry: ${{ env.REGISTRY }}
29+
username: ${{ github.actor }}
30+
password: ${{ secrets.GITHUB_TOKEN }}
31+
32+
- name: Extract Docker metadata
33+
id: meta
34+
uses: docker/metadata-action@v4
35+
with:
36+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
37+
tags: |
38+
type=schedule
39+
type=ref,event=branch
40+
type=ref,event=tag
41+
type=ref,event=pr
42+
type=semver,pattern={{version}}
43+
type=semver,pattern={{major}}.{{minor}}
44+
type=semver,pattern={{major}}
45+
type=sha
46+
47+
- name: Get version from VERSION file
48+
id: get_version
49+
run: echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
50+
51+
- name: Build and push Docker image
52+
uses: docker/build-push-action@v3
53+
with:
54+
context: .
55+
file: ./docker/Dockerfile
56+
push: true
57+
tags: |
58+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
59+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
60+
labels: ${{ steps.meta.outputs.labels }}

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
venv/
2+
env/
3+
ENV/
4+
5+
*.py[cod]
6+
*$py.class
7+
__pycache__/
8+
9+
logs/
10+
*.log
11+
12+
*.bak
13+
config.json.bak
14+
data.json.bak
15+
16+
temp_update/
17+
.temp/
18+
19+
.env
20+
21+
token.txt
22+
23+
.docker/

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030

3131
# TO-DO LIST
3232
- [x] Add players online on the presence status
33-
- [ ] Bring the code on docker system.
34-
- [ ] Add command for show the status in real time
35-
- [ ] Add automatically update on startup for new version
33+
- [x] Bring the code on docker system.
34+
- [x] Add command for show the status in real time
35+
- [x] Add automatically update on startup for new version
3636

3737

3838

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v1.4

cogs/commands.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import nextcord
22
import json
3+
import sys
4+
import os
35
from nextcord.ext import commands
46

7+
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'utils'))
8+
59
class Commands(commands.Cog):
610
def __init__(self, client):
711
self.client = client
@@ -32,16 +36,66 @@ async def createstatusmsg(self, ctx):
3236
await ctx.send("MCStatusBot: You don't have the permission for use this command, only the owner can do this command.")
3337

3438

39+
@commands.command()
40+
async def update(self, ctx):
41+
"""Checks for updates and updates the bot if available"""
42+
if ctx.message.author.id != self.config['owner_id']:
43+
return await ctx.send("MCStatusBot: You don't have the permission to use this command, only the owner can do this.")
44+
45+
from update import check_for_update, download_and_install_update
46+
47+
await ctx.send("Checking for updates...")
48+
update_available, data = check_for_update()
49+
50+
if update_available:
51+
await ctx.send(f"Update available! Current version: `{data['current_version']}`, Latest version: `{data['latest_version']}`")
52+
confirmation_msg = await ctx.send("Do you want to install this update? (React with ✅ to confirm)")
53+
await confirmation_msg.add_reaction("✅")
54+
55+
def check(reaction, user):
56+
return user == ctx.author and str(reaction.emoji) == "✅" and reaction.message.id == confirmation_msg.id
57+
58+
try:
59+
await self.client.wait_for('reaction_add', timeout=60.0, check=check)
60+
61+
status_msg = await ctx.send("Installing update...")
62+
success = download_and_install_update(data)
63+
64+
if success:
65+
await status_msg.edit(content="✅ Update installed successfully! Please restart the bot to apply changes.")
66+
else:
67+
await status_msg.edit(content="❌ Update failed. Check logs for more details.")
68+
except:
69+
await ctx.send("Update cancelled or timed out.")
70+
else:
71+
await ctx.send("You're already running the latest version!")
72+
73+
3574
@commands.command()
3675
async def help(self, ctx):
3776
embed = nextcord.Embed(
3877
title="Commands of MCStatusBot",
39-
description=f"{self.config['bot_prefix']}createstatusmsg - allow you to create a message where will be configured the status message.",
78+
description=f"",
4079
color=nextcord.Colour.dark_blue())
4180

81+
embed.add_field(
82+
name="User Commands",
83+
value=f"`{self.config['bot_prefix']}status` - Get real-time status of all Minecraft servers\n"
84+
f"`{self.config['bot_prefix']}help` - Shows this help message",
85+
inline=False
86+
)
87+
88+
if ctx.author.id == self.config['owner_id']:
89+
embed.add_field(
90+
name="Admin Commands",
91+
value=f"`{self.config['bot_prefix']}createstatusmsg` - Create a message that will be updated with server status\n"
92+
f"`{self.config['bot_prefix']}update` - Check for and install bot updates",
93+
inline=False
94+
)
95+
4296
embed.set_footer(text="Bot developed by SuperKali#8716")
4397

4498
await ctx.send(embed=embed)
4599

46100
async def setup(client):
47-
client.add_cog(Commands(client))
101+
await client.add_cog(Commands(client))

cogs/status.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import nextcord
2+
import json
3+
import time
4+
from nextcord.ext import commands
5+
from mcstatus import JavaServer, BedrockServer
6+
7+
class Status(commands.Cog):
8+
def __init__(self, client):
9+
self.client = client
10+
with open('config.json') as config:
11+
self.config = json.load(config)
12+
13+
@commands.command()
14+
async def status(self, ctx):
15+
"""Shows the current status of all servers in real-time"""
16+
await ctx.message.add_reaction('⏳')
17+
18+
embed = nextcord.Embed(
19+
title=f"{self.config['message_title']} - Real-time status",
20+
description=self.config['message_description'],
21+
color=nextcord.Colour.blue()
22+
)
23+
24+
for server in self.config["servers_to_ping"]:
25+
if server["is_maintenance"]:
26+
embed.add_field(name=server['server_name'], value=f"🟠 MAINTENANCE", inline=False)
27+
continue
28+
29+
try:
30+
if server["is_bedrock"]:
31+
status = BedrockServer.lookup(f"{server['server_ip']}:{server['port']}").status()
32+
players_online = status.players.online
33+
embed.add_field(name=server['server_name'],
34+
value=f"🟢 ONLINE ({players_online} players)",
35+
inline=False)
36+
else:
37+
status = JavaServer.lookup(f"{server['server_ip']}:{server['port']}").status()
38+
players_online = status.players.online
39+
embed.add_field(name=server['server_name'],
40+
value=f"🟢 ONLINE ({players_online} players)",
41+
inline=False)
42+
43+
# Add player list if available and players are online
44+
if players_online > 0 and hasattr(status.players, 'sample') and status.players.sample:
45+
player_names = [player.name for player in status.players.sample]
46+
if player_names:
47+
embed.add_field(
48+
name=f"Players on {server['server_name']}",
49+
value="• " + "\n• ".join(player_names[:10]) +
50+
(f"\n*...and {players_online - 10} more*" if players_online > 10 else ""),
51+
inline=True
52+
)
53+
except Exception as e:
54+
embed.add_field(name=server['server_name'], value=f"🔴 OFFLINE", inline=False)
55+
56+
embed.set_footer(text=f"Requested by {ctx.author}{time.strftime('%d/%m/%y %H:%M:%S')}")
57+
await ctx.message.remove_reaction('⏳', self.client.user)
58+
await ctx.message.add_reaction('✅')
59+
await ctx.send(embed=embed)
60+
61+
async def setup(client):
62+
await client.add_cog(Status(client))

config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"bot_prefix": "*",
44
"is_maintenance_status": false,
55
"owner_id": "",
6+
"auto_update": false,
67

78
"message_title": "MCStatusBot",
89
"message_description": "_Here you can find the status of the servers._",

docker/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM python:3.11-slim
2+
3+
WORKDIR /app
4+
5+
COPY ../requirements.txt .
6+
COPY ../main.py .
7+
COPY ../config.json .
8+
COPY ../data.json .
9+
COPY ../VERSION .
10+
COPY ../cogs/ ./cogs/
11+
COPY ../utils/ ./utils/
12+
COPY ../scripts/docker-run.sh .
13+
14+
RUN pip install --no-cache-dir -r requirements.txt
15+
16+
ENV PYTHONUNBUFFERED=1
17+
18+
RUN chmod +x docker-run.sh
19+
20+
CMD ["./docker-run.sh"]

docker/docker-compose.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
services:
2+
mcstatusbot:
3+
build:
4+
context: ..
5+
dockerfile: docker/Dockerfile
6+
container_name: mcstatusbot
7+
restart: unless-stopped
8+
volumes:
9+
- ../config.json:/app/config.json
10+
- ../data.json:/app/data.json
11+
- ../logs:/app/logs
12+
environment:
13+
- TZ=Europe/Rome

main.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import time
44
import json
55
import os
6+
import sys
7+
import requests
8+
9+
sys.path.append(os.path.join(os.path.dirname(__file__), 'utils'))
610

711
from nextcord.ext import commands
812
from mcstatus import JavaServer, BedrockServer
@@ -11,6 +15,9 @@
1115

1216
with open('config.json') as config_file:
1317
config = json.load(config_file)
18+
19+
if "auto_update" not in config:
20+
config["auto_update"] = False
1421

1522
client = commands.Bot(command_prefix=config["bot_prefix"], help_command=None, intents=nextcord.Intents.all())
1623

@@ -52,16 +59,59 @@ async def on_ready():
5259
client.load_extension(f'cogs.{i[:-3]}')
5360
enabled_cogs = i
5461

62+
update_available, update_data = await check_for_updates()
63+
if update_available:
64+
if config["auto_update"]:
65+
print(Style.NORMAL + Fore.YELLOW + "[MCStatusBot] " + Fore.RESET + f"Update available! Auto-updating from {update_data['current_version']} to {update_data['latest_version']}...")
66+
from update import download_and_install_update
67+
if download_and_install_update(update_data):
68+
print(Style.NORMAL + Fore.GREEN + "[MCStatusBot] " + Fore.RESET + "Auto-update successful! Please restart the bot to apply changes.")
69+
sys.exit(42)
70+
else:
71+
print(Style.NORMAL + Fore.RED + "[MCStatusBot] " + Fore.RESET + "Auto-update failed. Please update manually.")
72+
else:
73+
print(Style.NORMAL + Fore.YELLOW + "[MCStatusBot] " + Fore.RESET + f"Update available! ({update_data['current_version']}{update_data['latest_version']}) Run the *update command.")
5574

75+
with open('VERSION', 'r') as version_file:
76+
version = version_file.read().strip()
77+
5678
print(Style.NORMAL + Fore.LIGHTMAGENTA_EX + "╔═══════════════════╗")
5779
print(Style.NORMAL + Fore.GREEN + "Name: " + Fore.RESET + Fore.RED + "MCStatusBot")
58-
print(Style.NORMAL + Fore.GREEN + "Version: " + Fore.RESET + Fore.RED + "v1.3")
80+
print(Style.NORMAL + Fore.GREEN + "Version: " + Fore.RESET + Fore.RED + version)
5981
print(Style.NORMAL + Fore.GREEN + "Refresh Time: " + Fore.RESET + Fore.RED + str(config["refresh_time"]) + " seconds")
6082
print(Style.NORMAL + Fore.GREEN + "Bot Status: " + Fore.RESET + Fore.RED + "Online")
6183
print(Style.NORMAL + Fore.GREEN + "Enabled Cogs: " + Fore.RESET + Fore.RED + str(enabled_cogs.replace('.py', '')))
6284
print(Style.NORMAL + Fore.GREEN + "Support: " + Fore.RESET + Fore.RED + "https://discord.superkali.me")
6385
print(Style.NORMAL + Fore.LIGHTMAGENTA_EX + "╚═══════════════════╝")
6486

87+
scheduler = AsyncIOScheduler()
88+
scheduler.add_job(update_servers_status, "interval", seconds=config["refresh_time"])
89+
scheduler.start()
90+
91+
92+
async def check_for_updates():
93+
"""Check for updates from GitHub repository"""
94+
try:
95+
with open('VERSION', 'r') as version_file:
96+
current_version = version_file.read().strip()
97+
98+
response = requests.get("https://api.github.com/repos/superkali/MCStatusBot/releases/latest")
99+
if response.status_code == 200:
100+
data = response.json()
101+
latest_version = data["tag_name"]
102+
103+
if latest_version != current_version:
104+
return True, {
105+
"current_version": current_version,
106+
"latest_version": latest_version,
107+
"zipball_url": data["zipball_url"],
108+
"tag_name": latest_version
109+
}
110+
except Exception as e:
111+
print(f"Error checking for updates: {e}")
112+
return False, None
113+
114+
return False, None
65115

66116

67117
async def update_servers_status():
@@ -145,9 +195,4 @@ async def send_console_status():
145195
print(Style.NORMAL + Fore.RED + "[MCStatusBot] " + Fore.RESET + Fore.CYAN + f"{status.count(True)} Online servers")
146196
print(Style.NORMAL + Fore.RED + "[MCStatusBot] " + Fore.RESET + Fore.CYAN + f"{status.count(False)} Offline servers")
147197

148-
scheduler = AsyncIOScheduler()
149-
scheduler.add_job(update_servers_status, "interval", seconds=config["refresh_time"])
150-
scheduler.start()
151-
152-
153198
client.run(bot_token)

0 commit comments

Comments
 (0)