mirror of
https://github.com/Dinnerbone/mcstatus.git
synced 2026-04-06 03:51:23 +08:00
Removed everything; we're starting from scratch.
This commit is contained in:
103
.gitignore
vendored
103
.gitignore
vendored
@@ -1,2 +1,105 @@
|
||||
# Created by http://www.gitignore.io
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
|
||||
### PyCharm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
||||
|
||||
## Directory-based project format
|
||||
.idea/
|
||||
/*.iml
|
||||
# if you remove the above rule, at least ignore user-specific stuff:
|
||||
# .idea/workspace.xml
|
||||
# .idea/tasks.xml
|
||||
# .idea/dictionaries
|
||||
# and these sensitive or high-churn files:
|
||||
# .idea/dataSources.ids
|
||||
# .idea/dataSources.xml
|
||||
# .idea/sqlDataSources.xml
|
||||
# .idea/dynamic.xml
|
||||
# and, if using gradle::
|
||||
# .idea/gradle.xml
|
||||
# .idea/libraries
|
||||
|
||||
## File-based project format
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
## Additional for IntelliJ
|
||||
out/
|
||||
|
||||
# generated by mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# generated by JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# generated by Crashlytics plugin (for Android Studio and Intellij)
|
||||
com_crashlytics_export_strings.xml
|
||||
|
||||
|
||||
### SublimeText ###
|
||||
# workspace files are user-specific
|
||||
*.sublime-workspace
|
||||
|
||||
# project files should be checked into the repository, unless a significant
|
||||
# proportion of contributors will probably not be using SublimeText
|
||||
# *.sublime-project
|
||||
|
||||
#sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
|
||||
44
cli.py
44
cli.py
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from pprint import pprint
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from minecraft_query import MinecraftQuery
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description="Query status of Minecraft multiplayer server",
|
||||
epilog="Exit status: 0 if the server can be reached, otherwise nonzero."
|
||||
)
|
||||
parser.add_argument("host", help="target hostname")
|
||||
parser.add_argument("-q", "--quiet", action='store_true', default=False,
|
||||
help='don\'t print anything, just check if the server is running')
|
||||
parser.add_argument("-p", "--port", type=int, default=25565,
|
||||
help='UDP port of server\'s "query" service [25565]')
|
||||
parser.add_argument("-r", "--retries", type=int, default=3,
|
||||
help='retry query at most this number of times [3]')
|
||||
parser.add_argument("-t", "--timeout", type=int, default=10,
|
||||
help='retry timeout in seconds [10]')
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
try:
|
||||
query = MinecraftQuery(options.host, options.port,
|
||||
timeout=options.timeout,
|
||||
retries=options.retries)
|
||||
server_data = query.get_rules()
|
||||
except socket.error as e:
|
||||
if not options.quiet:
|
||||
print "socket exception caught:", e.message
|
||||
print "Server is down or unreachable."
|
||||
sys.exit(1)
|
||||
|
||||
if not options.quiet:
|
||||
print "Server response data:"
|
||||
pprint(server_data)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
0
mcstatus/__init__.py
Normal file
0
mcstatus/__init__.py
Normal file
@@ -1,2 +0,0 @@
|
||||
# Backwards-compatibility
|
||||
from query import *
|
||||
@@ -1,146 +0,0 @@
|
||||
import socket
|
||||
import struct
|
||||
|
||||
class MinecraftQuery:
|
||||
MAGIC_PREFIX = '\xFE\xFD'
|
||||
PACKET_TYPE_CHALLENGE = 9
|
||||
PACKET_TYPE_QUERY = 0
|
||||
HUMAN_READABLE_NAMES = dict(
|
||||
game_id = "Game Name",
|
||||
gametype = "Game Type",
|
||||
motd = "Message of the Day",
|
||||
hostname = "Server Address",
|
||||
hostport = "Server Port",
|
||||
map = "Main World Name",
|
||||
maxplayers = "Maximum Players",
|
||||
numplayers = "Players Online",
|
||||
players = "List of Players",
|
||||
plugins = "List of Plugins",
|
||||
raw_plugins = "Raw Plugin Info",
|
||||
software = "Server Software",
|
||||
version = "Game Version",
|
||||
)
|
||||
|
||||
def __init__(self, host, port, timeout=10, id=0, retries=2):
|
||||
self.addr = (host, port)
|
||||
self.id = id
|
||||
self.id_packed = struct.pack('>l', id)
|
||||
self.challenge_packed = struct.pack('>l', 0)
|
||||
self.retries = 0
|
||||
self.max_retries = retries
|
||||
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket.settimeout(timeout)
|
||||
|
||||
def send_raw(self, data):
|
||||
self.socket.sendto(self.MAGIC_PREFIX + data, self.addr)
|
||||
|
||||
def send_packet(self, type, data=''):
|
||||
self.send_raw(struct.pack('>B', type) + self.id_packed + self.challenge_packed + data)
|
||||
|
||||
def read_packet(self):
|
||||
buff = self.socket.recvfrom(1460)[0]
|
||||
type = struct.unpack('>B', buff[0])[0]
|
||||
id = struct.unpack('>l', buff[1:5])[0]
|
||||
return type, id, buff[5:]
|
||||
|
||||
def handshake(self, bypass_retries=False):
|
||||
self.send_packet(self.PACKET_TYPE_CHALLENGE)
|
||||
|
||||
try:
|
||||
type, id, buff = self.read_packet()
|
||||
except:
|
||||
if not bypass_retries:
|
||||
self.retries += 1
|
||||
|
||||
if self.retries < self.max_retries:
|
||||
self.handshake(bypass_retries=bypass_retries)
|
||||
return
|
||||
else:
|
||||
raise
|
||||
|
||||
self.challenge = int(buff[:-1])
|
||||
self.challenge_packed = struct.pack('>l', self.challenge)
|
||||
|
||||
def get_status(self):
|
||||
if not hasattr(self, 'challenge'):
|
||||
self.handshake()
|
||||
|
||||
self.send_packet(self.PACKET_TYPE_QUERY)
|
||||
|
||||
try:
|
||||
type, id, buff = self.read_packet()
|
||||
except:
|
||||
self.handshake()
|
||||
return self.get_status()
|
||||
|
||||
data = {}
|
||||
|
||||
data['motd'], data['gametype'], data['map'], data['numplayers'], data['maxplayers'], buff = buff.split('\x00', 5)
|
||||
data['hostport'] = struct.unpack('<h', buff[:2])[0]
|
||||
buff = buff[2:]
|
||||
data['hostname'] = buff[:-1]
|
||||
|
||||
for key in ('numplayers', 'maxplayers'):
|
||||
try:
|
||||
data[key] = int(data[key])
|
||||
except:
|
||||
pass
|
||||
|
||||
return data
|
||||
|
||||
def get_rules(self):
|
||||
if not hasattr(self, 'challenge'):
|
||||
self.handshake()
|
||||
|
||||
self.send_packet(self.PACKET_TYPE_QUERY, self.id_packed)
|
||||
|
||||
try:
|
||||
type, id, buff = self.read_packet()
|
||||
except:
|
||||
self.retries += 1
|
||||
if self.retries < self.max_retries:
|
||||
self.handshake(bypass_retries=True)
|
||||
return self.get_rules()
|
||||
else:
|
||||
raise
|
||||
|
||||
data = {}
|
||||
|
||||
buff = buff[11:] # splitnum + 2 ints
|
||||
items, players = buff.split('\x00\x00\x01player_\x00\x00') # Shamefully stole from https://github.com/barneygale/MCQuery
|
||||
|
||||
if items[:8] == 'hostname':
|
||||
items = 'motd' + items[8:]
|
||||
|
||||
items = items.split('\x00')
|
||||
data = dict(zip(items[::2], items[1::2]))
|
||||
|
||||
players = players[:-2]
|
||||
|
||||
if players:
|
||||
data['players'] = players.split('\x00')
|
||||
else:
|
||||
data['players'] = []
|
||||
|
||||
for key in ('numplayers', 'maxplayers', 'hostport'):
|
||||
try:
|
||||
data[key] = int(data[key])
|
||||
except:
|
||||
pass
|
||||
|
||||
data['raw_plugins'] = data['plugins']
|
||||
data['software'], data['plugins'] = self.parse_plugins(data['raw_plugins'])
|
||||
|
||||
return data
|
||||
|
||||
def parse_plugins(self, raw):
|
||||
parts = raw.split(':', 1)
|
||||
server = parts[0].strip()
|
||||
plugins = []
|
||||
|
||||
if len(parts) == 2:
|
||||
plugins = parts[1].split(';')
|
||||
plugins = map(lambda s: s.strip(), plugins)
|
||||
|
||||
return server, plugins
|
||||
Reference in New Issue
Block a user