Files
minecraft-monitor-skill/scripts/minecraft-status.py
Nova 87a9013ffb Initial commit: Minecraft server monitoring skill
- Basic server status checking (online/offline)
- Player counts, latency, version info
- Example: corejourney.org
- No external dependencies required
2026-02-11 21:05:29 +00:00

216 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Minecraft Server Status Checker
Queries a Minecraft server for basic status information:
- Online status
- Player count (current/max)
- Server motd/message
- Ping response time
- Version information
Uses Minecraft Server List Ping protocol (SLP).
"""
import socket
import struct
import json
import sys
import time
def encode_varint(num):
"""Encode an integer as a Minecraft varint."""
if num < 0:
num = (1 << 32) + num
out = b""
while num > 0x7F:
out += bytes([(num & 0x7F) | 0x80])
num >>= 7
out += bytes([num])
return out
def decode_varint(sock):
"""Decode a Minecraft varint from socket."""
num = 0
shift = 0
while True:
byte = sock.recv(1)
if not byte:
raise IOError("Connection closed")
byte = byte[0]
num |= (byte & 0x7F) << shift
if not (byte & 0x80):
break
shift += 7
return num
def send_handshake(sock, host, port):
"""Send handshake packet."""
data = b""
# Packet ID (handshake = 0x00)
data += b"\x00"
# Protocol version (use latest: 765 for 1.19.3+)
data += encode_varint(765)
# Server address
host_bytes = host.encode('utf-8') if isinstance(host, str) else host
data += encode_varint(len(host_bytes))
data += host_bytes
# Server port
data += struct.pack(">H", port)
# Next state (status = 1)
data += b"\x01"
# Length prefix
length = encode_varint(len(data))
sock.send(length + data)
def send_status_request(sock):
"""Send status request packet."""
data = b"\x01\x00" # Request (empty)
length = encode_varint(len(data))
sock.send(length + data)
def read_status_response(sock):
"""Read server status response."""
# Packet length
length = decode_varint(sock)
# Packet ID should be 0x00
packet_id = decode_varint(sock)
# Response length
response_len = decode_varint(sock)
# Response data (JSON)
response = b""
while len(response) < response_len:
response += sock.recv(response_len - len(response))
return json.loads(response.decode('utf-8'))
def minecraft_status(host, port=25565, timeout=5):
"""Get Minecraft server status."""
try:
start_time = time.time()
# Connect to server
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((host, port))
# Handshake
send_handshake(sock, host, port)
send_status_request(sock)
# Read response
data = read_status_response(sock)
sock.close()
ping_ms = round((time.time() - start_time) * 1000, 1)
# Parse response
result = {
'online': True,
'host': host,
'port': port,
'ping': ping_ms,
}
# Version info
if 'version' in data:
result['version'] = data['version'].get('name', 'Unknown')
result['protocol'] = data['version'].get('protocol', 'Unknown')
# Player count
if 'players' in data:
players = data['players']
result['players_online'] = players.get('online', 0)
result['players_max'] = players.get('max', 0)
if 'sample' in players and players['sample']:
result['player_list'] = [p['name'] for p in players['sample']]
# MOTD (message of the day)
if 'description' in data:
motd = data['description']
if isinstance(motd, dict):
motd = motd.get('text', '')
result['motd'] = motd
return result
except socket.timeout:
return {
'online': False,
'host': host,
'port': port,
'error': 'Connection timed out'
}
except ConnectionRefusedError:
return {
'online': False,
'host': host,
'port': port,
'error': 'Connection refused'
}
except Exception as e:
return {
'online': False,
'host': host,
'port': port,
'error': str(e)
}
def format_status(status):
"""Format status for human-readable output."""
if not status['online']:
print(f"🔴 {status['host']}:{status['port']} - OFFLINE")
print(f" Error: {status.get('error', 'Unknown')}")
return
emoji = "🟢" if status['ping'] < 100 else "🟡" if status['ping'] < 200 else "🟠"
print(f"{emoji} {status['host']}:{status['port']} - ONLINE ({status['ping']}ms)")
print(f" Version: {status.get('version', 'Unknown')}")
print(f" Players: {status.get('players_online', 0)}/{status.get('players_max', 0)}")
if 'player_list' in status and status['player_list']:
print(f" Online: {', '.join(status['player_list'][:5])}" +
(f" ... +{len(status['player_list'])-5} more" if len(status['player_list']) > 5 else ""))
if 'motd' in status and status['motd']:
motd = status['motd'].strip()
if motd:
print(f" MOTD: {motd[:80]}{'...' if len(motd) > 80 else ''}")
def main():
if len(sys.argv) < 2:
print("Usage: python minecraft-status.py <host[:port]> [timeout]")
print("")
print("Examples:")
print(" python minecraft-status.py corejourney.org")
print(" python minecraft-status.py corejourney.org:25565")
print(" python minecraft-status.py 192.168.1.10:25566 10")
sys.exit(1)
# Parse host:port
hostport = sys.argv[1]
if ':' in hostport:
host, port_str = hostport.rsplit(':', 1)
try:
port = int(port_str)
except ValueError:
print(f"Invalid port: {port_str}")
sys.exit(1)
else:
host = hostport
port = 25565
# Parse timeout
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 5
status = minecraft_status(host, port, timeout)
format_status(status)
# Return exit code 0 if online, 1 if offline
sys.exit(0 if status['online'] else 1)
if __name__ == '__main__':
main()