class Rcon(commands.Cog): def __init__(self, bot): self.bot = bot self.mcr = MCRcon(os.environ["rcon_ip"], os.environ["rcon_pass"], int(os.environ["rcon_port"])) self.mcr.connect() async def send_resp(self, resp: str, ctx: commands.Context): if str(resp) == "": await ctx.channel.send( "There was no response text for this command execution.") return await ctx.channel.send(resp) @commands.group(name="rcon") async def rcon(self, ctx): self.mcr.command(f"//;{ctx.message.content}") @rcon.command(name="exec") @commands.has_any_role("Moderator", "Administrator") async def rcon_exec(self, ctx, *command): command = " ".join(command) resp = self.mcr.command(command) await self.send_resp(resp, ctx) @rcon.command(name="whitelist") @commands.has_any_role("Big Brain", "Moderator", "Administrator", "Developer") async def rcon_whitelist(self, ctx, ign, typ="allow"): if typ == "allow": resp = self.mcr.command(f"whitelist add {ign}") await self.send_resp(resp, ctx) else: resp = self.mcr.command(f"whitelist remove {ign}") await self.send_resp(resp, ctx)
def send_commands(server_ip, rcon_password, commands, buyer, rcon_port): server_ip = str(server_ip).split(':')[0] mcr = MCRcon(server_ip, rcon_password, int(rcon_port)) mcr.connect() for command in commands: mcr.command(command.replace("{PLAYER}", buyer)) mcr.disconnect()
def run_server(self) -> ContextManager[MCRcon]: server_process = Popen( ["java", "-jar", self.server_path.name, "--nogui"], cwd=self.server_dir) # There must be a better way rcon = None for i in range(72): sleep(5) try: rcon = MCRcon("localhost", self.RCON_PASSWORD, int(self.RCON_PORT)) # tries to create a connection with rcon: pass break except ConnectionError: continue else: server_process.kill() pytest.fail( "Could not connect to the minecraft server after 5 minutes") with rcon: try: yield rcon finally: rcon.command("stop") server_process.wait(30)
class RconInterface: def __init__(self, ip_address, port, password, name="None"): self.con = MCRcon(ip_address, password, port=port) self.name = name try: self.con.connect() except ConnectionRefusedError: print(f'rcon failed to connect {self.con.host}:{self.con.port}') def getstatus(self): return str(self.con.socket).find("0.0.0.0") == -1 def reconnect(self): if not self.getstatus(): self.con.connect() def command(self, command, retrys=0): try: return self.con.command(command) except (ConnectionResetError, ConnectionRefusedError): self.con.connect() if retrys < 5: self.command(command, retrys + 1) def __del__(self): self.con.disconnect()
def get_stats(): mcr = MCRcon("0.0.0.0", "factory") mcr.connect() resp = mcr.command(write_stats_command) #do something with the response mcr.disconnect() return resp
class Connection: def __init__(self, address, port, secret): self.mcr = MCRcon(address, secret) self.mcr.connect() def send(self, msg): resp = self.mcr.command(msg) return resp
def run_code(rcon: MCRcon, test_world: MCWorld, code: str) -> Tuple[Optional[int], str]: config = Config() config.output_dir = Path(test_world.getDatapackPath()) / "mcscript" config.input_string = TEST_TEMPLATE.format(code) datapack = compileMcScript(config) Logger.info(datapack.getMainDirectory().getPath("functions").files) generate_datapack(config, datapack) rcon.command("reload") result = rcon.command(f"scoreboard players get result mcscript_test") match = RESULT_PATTERN.match(result) if match is not None: value = int(match.group(1)) else: value = None return value, result
async def whitelist_add_user(user_id, username): rcon: tuple = await get_rcon() mcr = MCRcon(host=rcon[0], port=int(rcon[1]), password=rcon[2]) mcr.connect() if await has_whitelist(user_id): rcon_response = mcr.command( f'whitelist remove {await get_mc_username(user_id)}') print(rcon_response) db.execute('UPDATE minecraft_users SET mc_username=? WHERE user_id=?', (username, user_id)) else: db.execute('INSERT INTO minecraft_users VALUES(?,?)', (user_id, username)) connection.commit() rcon_response = mcr.command(f'whitelist add {username}') print(rcon_response) mcr.disconnect()
def main(): print("Factocli RCON Client Started") print("Connecting to RCON Address") mcr = MCRcon("0.0.0.0", "factory") mcr.connect() resp = mcr.command(write_stats_command) print("Sending command...") print("Response: "+resp) print("Disconnecting...") mcr.disconnect() print("Done")
async def whitelist_remove_user(user_id): rcon: tuple = await get_rcon() mcr = MCRcon(host=rcon[0], port=int(rcon[1]), password=rcon[2]) mcr.connect() rcon_response = mcr.command( f'whitelist remove {await get_mc_username(user_id)}') print(rcon_response) mcr.disconnect() db.execute('DELETE FROM minecraft_users WHERE user_id=?', (user_id, )) connection.commit()
def check_shutoff(config): sum_players = 0 server_store = open(config['server_store'], 'r') cached_statuses = json.loads(server_store.read()) server_store.close() for key, val in cached_statuses.items(): sum_players += sum(val) if config['debug']: print('========== Check shutoff ==========') pprint({'total_historical_num_players': sum_players}) print() all_long_running = len( list( filter( lambda count: count != 3, list( map(lambda server: len(cached_statuses[server]), cached_statuses.keys()))))) == 0 if sum_players == 0 and all_long_running: if config['debug']: print('========= Saving servers ==========') for server in cached_statuses.keys(): try: host = config['servers'][server]['host'] port = config['servers'][server]['port'] password = config['servers'][server]['password'] mcr = MCRcon(host=host, password=password, port=port) mcr.connect() resp = mcr.command('save-all') if config['debug']: print(f'save-all response for {server}: {resp}') logging.info(f'save-all response for {server}: {resp}') mcr.disconnect() except Exception as e: logging.warning( f'An error occured for server {server}\n{str(e)}') if config['debug']: print('===== Shutting down instance ======') else: with open(config['server_store'], 'w+') as server_store: server_store.write(json.dumps({}, indent=2)) EC2 = boto3.client('ec2', region_name=config['region_name']) resp = EC2.stop_instances(InstanceIds=[config['instance_id']])
class _RConsole: __lock_connection_action = Lock() __lock_auto_close_thread = Lock() __disconnect_seconds = 100 def __init__(self, host, port: int, password, use_tls: bool = True): tls = { True: 1, False: 0 } self.__auto_close_timer = AsyncCountdownTimer(self.__disconnect_seconds, self.__disconnect) self.__con = MCRcon(host, password, port, tls[use_tls]) def execute(self, command: str, timeout: int = 0) -> str: """ Execute a command, return its response. :param command: the command to be executed. :param timeout: timeout in seconds. If it is set to 0, the function waits until a response is received. If timed out, an `TimedOutException` will be thrown. :return: the response echo. """ # check connection with self.__lock_connection_action: if not self.__con.socket: self.__con.connect() with self.__lock_auto_close_thread: self.__auto_close_timer.reset() self.__auto_close_timer = AsyncCountdownTimer(self.__disconnect_seconds, self.__disconnect) self.__auto_close_timer.start() # TODO: implement timeout if timeout: raise NotImplementedError("Sorry, timeout has not been implemented") # execute command logging.info(f'Execute command: {command}') return self.__con.command(command) def __del__(self): if self.__auto_close_timer: self.__auto_close_timer.reset() def __disconnect(self): disconnected = False with self.__lock_connection_action: if self.__con.socket: self.__con.disconnect() disconnected = True if disconnected: logging.info('Console is inactive. Disconnected from RCON server.')
class Scoreboard(object): def __init__(self, host, password, port=25575): self.rcon = MCRcon(host, password, port=port) def __enter__(self): self.rcon.connect() return self def __exit__(self, type, value, tb): self.rcon.disconnect() def get_value_for_player(self, nickname: str, score: str) -> int: cmd_result = self.rcon.command('scoreboard players get {} {}'.format( nickname, score)) value = cmd_result[cmd_result.find('has') + 4:cmd_result.find('[') - 1] return int(value)
async def whitelist(ctx): if rconIp == "": await ctx.author.send( "please run $setup before using any commmands") return False if ctx.channel.id == CHANNELID or CHANNELID == 0: if poll(ctx.author.id) == False: first_set = await query(ctx) #print("done with first set: " + str(first_set[0]) + str(first_set[1]) + str(first_set[2]) + str(first_set[3])) if first_set != False: second_set = await newsletterQuery(ctx) #print("done with second set: " + str(second_set)) if second_set != False: conn = psycopg2.connect(database=pgdb, user=pgUser, password=pgPass, host=pgHost, port=pgPort) cur = conn.cursor() try: #print("------------showing insert to database---------\nuser_id:"+str(ctx.author.id)+"\nfirst_name:"+str(first_set[0])+"\nlast_name:"+str(first_set[1])+"\nuuid:"+str(first_set[2])+"\nusername:"******"\nemail:"+str(second_set)) cur.execute( 'INSERT INTO whitelist ("user_id", "first_name", "last_name", "uuid", "username", "email", "isBanned") VALUES (' + str(ctx.author.id) + ', \'' + str(first_set[0]) + '\', \'' + str(first_set[1]) + '\', N\'' + str(first_set[2]) + '\', N\'' + str(first_set[3]) + '\', N\'' + str(second_set) + '\', ' + str(0) + ');') conn.commit() except Exception as e: print("DB error: \n" + str(e)) mcr = MCRcon(str(rconIp), str(rconPass), port=int(rconPort)) mcr.connect() resp = mcr.command("whitelist add " + str(first_set[3])) print(resp) mcr.disconnect() await ctx.author.send(embed=addFinish_embed) conn.close() else: if poll(ctx.author.id)[6] == 0: await edit(ctx)
async def remove(ctx): if rconIp == "": await ctx.author.send( "please run $setup before using any commmands") return False if ctx.channel.id == CHANNELID or CHANNELID == 0: if poll(ctx.author.id) != False: if poll(ctx.author.id)[6] == 0: username = ctx.author msgPrompt = await username.send( embed=removeConfirmPrompt_embed) thumbsup, thumbsdown = '👍', '👎' await msgPrompt.add_reaction(thumbsup) await msgPrompt.add_reaction(thumbsdown) def checkreact(reaction, react): react = str(reaction.emoji) return ((react == '👍' or react == '👎') and (reaction.message.id == msgPrompt.id)) await asyncio.sleep(.1) try: confirmation = await bot.wait_for('reaction_add', check=checkreact, timeout=300) except TimeoutError: print( "User " + str(ctx.author) + "Timed out on removal confirmation, User remains in database" ) await ctx.author.send(embed=timeout_embed) return False else: if str(confirmation[0].emoji) == '👍': mcUser = poll(username.id)[4] mcr = MCRcon(str(rconIp), str(rconPass), port=int(rconPort)) mcr.connect() resp = mcr.command("whitelist remove " + mcUser) print(resp) mcr.disconnect() remove_player(username.id) await username.send(embed=removeConfirm_embed)
def rcon_thread(self): while self.running: try: s = socket() s.settimeout(1) s.connect(('localhost', 25575)) s.close() rcon = MCRcon('localhost', PASSWORD) rcon.connect() self.ready = True while self.running: time.sleep(2) self.players = [ name for name in rcon.command("list").split(':') [1].split(', ') if name ] except Exception as e: self.ready = False self.players = [] time.sleep(0.2) print("rcon thread stopped")
def Shutdown(): if not request.headers.get("User-Agent") == USER_AGENT: error_message = {"state": 0, "error": "Invalid User-Agent."} return make_response(jsonify(error_message), 400) if not request.headers.get("Content-Type") == CONTENT_TYPE: error_message = {"state": 0, "error": "Invalid Content-Type."} return make_response(jsonify(error_message), 400) try: mcr = MCRcon(IPADDRESS, PASSWORD, RCONPORT) mcr.connect() resp = mcr.command("saveworld") mcr.command( "Broadcast The world has been saved. The server will be shutdown in 1 minute." ) time.sleep(60) resp = mcr.command("saveworld") mcr.command("Broadcast Stop the server.") mcr.command("DoExit") print(resp) mcr.disconnect() except: pass session = winrm.Session(IPADDRESS, auth=(USER, PASSWORD)) try: session.run_ps("shutdown -s -f -t 120") success_message = { "state": 1, "body": "The request was executed successfully." } except: success_message = { "state": 0, "body": "The request was processed successfully, but the shutdown process was not executed properly.", } return make_response(jsonify(success_message), 200)
def sendRconCommands(cmds, delay = 0.03): mcr = MCRcon("127.0.0.1", _rconPass, _rconPort, 0) try: mcr.connect() except Exception as ex: logmsg(f'ERROR: Unable to connect to rcon server: {ex}') return # flatten all commands cmds = [item for sublist in cmds for item in sublist] for cmd in cmds: try: resp = mcr.command(cmd) logmsg(f'RCON Command: {cmd}: {resp}') time.sleep(delay) except Exception as ex: logmsg(f'ERROR: Unable to send rcon command: {cmd}') logmsg(f' {ex}') break mcr.disconnect()
async def on_ready(): print("starting bot and setting config") #load_dotenv() global prefix global CHANNELID global rconIp global rconPort global rconPass conn = psycopg2.connect(database=pgdb, user=pgUser, password=pgPass, host=pgHost, port=pgPort) cur = conn.cursor() cur.execute('SELECT * FROM serverconfig;') a = cur.fetchone() conn.close() if a != None: print('showing server stuff: \nserver: ' + str(a[0]) + '\nchannel_id:' + str(a[1]) + '\nprefix:' + str(a[2]) + '\nip:' + str(a[3]) + '\nport:' + str(a[4]) + '\npass:' + str(a[5])) if a != None: bot.command_prefix = str(a[2]) CHANNELID = int(a[1]) rconIp = str(a[3]) rconPort = int(a[4]) rconPass = str(a[5]) mcr = MCRcon(str(rconIp), str(rconPass)) mcr.connect() resp = mcr.command("/help") print(resp) mcr.disconnect() else: print("please run $setup") print("Bot ready")
class MinecraftCollector(object): def __init__(self): self.statsdirectory = "/world/stats" self.playerdirectory = "/world/playerdata" self.advancementsdirectory = "/world/advancements" self.betterquesting = "/world/betterquesting" self.map = dict() self.questsEnabled = False self.rcon = None if os.path.isdir(self.betterquesting): self.questsEnabled = True schedule.every().day.at("01:00").do(self.flush_playernamecache) def get_players(self): return [ f[:-5] for f in listdir(self.statsdirectory) if isfile(join(self.statsdirectory, f)) ] def flush_playernamecache(self): print("flushing playername cache") self.map = dict() return def uuid_to_player(self, uuid): uuid = uuid.replace('-', '') if uuid in self.map: return self.map[uuid] else: result = requests.get('https://api.mojang.com/user/profiles/' + uuid + '/names') self.map[uuid] = result.json()[-1]['name'] return (result.json()[-1]['name']) def rcon_command(self, command): if self.rcon == None: self.rcon = MCRcon(os.environ['RCON_HOST'], os.environ['RCON_PASSWORD'], port=int(os.environ['RCON_PORT'])) self.rcon.connect() try: response = self.rcon.command(command) except BrokenPipeError: print("Lost RCON Connection, trying to reconnect") self.rcon.connect() response = self.rcon.command(command) return response def get_server_stats(self): metrics = [] if not all(x in os.environ for x in ['RCON_HOST', 'RCON_PASSWORD']): return [] dim_tps = Metric('dim_tps', 'TPS of a dimension', "counter") dim_ticktime = Metric('dim_ticktime', "Time a Tick took in a Dimension", "counter") overall_tps = Metric('overall_tps', 'overall TPS', "counter") overall_ticktime = Metric('overall_ticktime', "overall Ticktime", "counter") player_online = Metric('player_online', "is 1 if player is online", "counter") entities = Metric('entities', "type and count of active entites", "counter") metrics.extend([ dim_tps, dim_ticktime, overall_tps, overall_ticktime, player_online, entities ]) if 'FORGE_SERVER' in os.environ and os.environ[ 'FORGE_SERVER'] == "True": # dimensions resp = self.rcon_command("forge tps") dimtpsregex = re.compile( "Dim\s*(-*\d*)\s\((.*?)\)\s:\sMean tick time:\s(.*?) ms\. Mean TPS: (\d*\.\d*)" ) for dimid, dimname, meanticktime, meantps in dimtpsregex.findall( resp): dim_tps.add_sample('dim_tps', value=meantps, labels={ 'dimension_id': dimid, 'dimension_name': dimname }) dim_ticktime.add_sample('dim_ticktime', value=meanticktime, labels={ 'dimension_id': dimid, 'dimension_name': dimname }) overallregex = re.compile( "Overall\s?: Mean tick time: (.*) ms. Mean TPS: (.*)") overall_tps.add_sample('overall_tps', value=overallregex.findall(resp)[0][1], labels={}) overall_ticktime.add_sample('overall_ticktime', value=overallregex.findall(resp)[0][0], labels={}) # entites resp = self.rcon_command("forge entity list") entityregex = re.compile("(\d+): (.*?:.*?)\s") for entitycount, entityname in entityregex.findall(resp): entities.add_sample('entities', value=entitycount, labels={'entity': entityname}) # dynmap if 'DYNMAP_ENABLED' in os.environ and os.environ[ 'DYNMAP_ENABLED'] == "True": dynmap_tile_render_statistics = Metric( 'dynmap_tile_render_statistics', 'Tile Render Statistics reported by Dynmap', "counter") dynmap_chunk_loading_statistics_count = Metric( 'dynmap_chunk_loading_statistics_count', 'Chunk Loading Statistics reported by Dynmap', "counter") dynmap_chunk_loading_statistics_duration = Metric( 'dynmap_chunk_loading_statistics_duration', 'Chunk Loading Statistics reported by Dynmap', "counter") metrics.extend([ dynmap_tile_render_statistics, dynmap_chunk_loading_statistics_count, dynmap_chunk_loading_statistics_duration ]) resp = self.rcon_command("dynmap stats") dynmaptilerenderregex = re.compile( " (.*?): processed=(\d*), rendered=(\d*), updated=(\d*)") for dim, processed, rendered, updated in dynmaptilerenderregex.findall( resp): dynmap_tile_render_statistics.add_sample( 'dynmap_tile_render_statistics', value=processed, labels={ 'type': 'processed', 'file': dim }) dynmap_tile_render_statistics.add_sample( 'dynmap_tile_render_statistics', value=rendered, labels={ 'type': 'rendered', 'file': dim }) dynmap_tile_render_statistics.add_sample( 'dynmap_tile_render_statistics', value=updated, labels={ 'type': 'updated', 'file': dim }) dynmapchunkloadingregex = re.compile( "Chunks processed: (.*?): count=(\d*), (\d*.\d*)") for state, count, duration_per_chunk in dynmapchunkloadingregex.findall( resp): dynmap_chunk_loading_statistics_count.add_sample( 'dynmap_chunk_loading_statistics', value=count, labels={'type': state}) dynmap_chunk_loading_statistics_duration.add_sample( 'dynmap_chunk_loading_duration', value=duration_per_chunk, labels={'type': state}) # player resp = self.rcon_command("list") playerregex = re.compile("players online:(.*)") if playerregex.findall(resp): for player in playerregex.findall(resp)[0].split(","): if not player.isspace(): player_online.add_sample( 'player_online', value=1, labels={'player': player.lstrip()}) return metrics def get_player_quests_finished(self, uuid): with open(self.betterquesting + "/QuestProgress.json") as json_file: data = json.load(json_file) json_file.close() counter = 0 for _, value in data['questProgress:9'].items(): for _, u in value['tasks:9']['0:10']['completeUsers:9'].items(): if u == uuid: counter += 1 return counter def get_player_stats(self, uuid): with open(self.statsdirectory + "/" + uuid + ".json") as json_file: data = json.load(json_file) json_file.close() nbtfile = nbt.nbt.NBTFile(self.playerdirectory + "/" + uuid + ".dat", 'rb') data["stat.XpTotal"] = nbtfile.get("XpTotal").value data["stat.XpLevel"] = nbtfile.get("XpLevel").value data["stat.Score"] = nbtfile.get("Score").value data["stat.Health"] = nbtfile.get("Health").value data["stat.foodLevel"] = nbtfile.get("foodLevel").value with open(self.advancementsdirectory + "/" + uuid + ".json") as json_file: count = 0 advancements = json.load(json_file) for key, value in advancements.items(): if key in ("DataVersion"): continue if value["done"] == True: count += 1 data["stat.advancements"] = count if self.questsEnabled: data["stat.questsFinished"] = self.get_player_quests_finished(uuid) return data def update_metrics_for_player(self, uuid): data = self.get_player_stats(uuid) name = self.uuid_to_player(uuid) blocks_mined = Metric('blocks_mined', 'Blocks a Player mined', "counter") blocks_picked_up = Metric('blocks_picked_up', 'Blocks a Player picked up', "counter") player_deaths = Metric('player_deaths', 'How often a Player died', "counter") player_jumps = Metric('player_jumps', 'How often a Player has jumped', "counter") cm_traveled = Metric( 'cm_traveled', 'How many cm a Player traveled, whatever that means', "counter") player_xp_total = Metric('player_xp_total', "How much total XP a player has", "counter") player_current_level = Metric('player_current_level', "How much current XP a player has", "counter") player_food_level = Metric('player_food_level', "How much food the player currently has", "counter") player_health = Metric('player_health', "How much Health the player currently has", "counter") player_score = Metric('player_score', "The Score of the player", "counter") entities_killed = Metric('entities_killed', "Entities killed by player", "counter") damage_taken = Metric('damage_taken', "Damage Taken by Player", "counter") damage_dealt = Metric('damage_dealt', "Damage dealt by Player", "counter") blocks_crafted = Metric('blocks_crafted', "Items a Player crafted", "counter") player_playtime = Metric('player_playtime', "Time in Minutes a Player was online", "counter") player_advancements = Metric( 'player_advancements', "Number of completed advances of a player", "counter") player_slept = Metric('player_slept', "Times a Player slept in a bed", "counter") player_quests_finished = Metric( 'player_quests_finished', 'Number of quests a Player has finished', 'counter') player_used_crafting_table = Metric( 'player_used_crafting_table', "Times a Player used a Crafting Table", "counter") mc_custom = Metric('mc_custom', "Custom Minectaft stat", "counter") for key, value in data.items(): # pre 1.15 if key in ("stats", "DataVersion"): continue stat = key.split(".")[1] # entityKilledBy if stat == "mineBlock": blocks_mined.add_sample("blocks_mined", value=value, labels={ 'player': name, 'block': '.'.join((key.split(".")[2], key.split(".")[3])) }) elif stat == "pickup": blocks_picked_up.add_sample("blocks_picked_up", value=value, labels={ 'player': name, 'block': '.'.join((key.split(".")[2], key.split(".")[3])) }) elif stat == "entityKilledBy": if len(key.split(".")) == 4: player_deaths.add_sample('player_deaths', value=value, labels={ 'player': name, 'cause': '.'.join((key.split(".")[2], key.split(".")[3])) }) else: player_deaths.add_sample('player_deaths', value=value, labels={ 'player': name, 'cause': key.split(".")[2] }) elif stat == "jump": player_jumps.add_sample("player_jumps", value=value, labels={'player': name}) elif stat == "walkOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "walking" }) elif stat == "swimOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "swimming" }) elif stat == "sprintOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "sprinting" }) elif stat == "diveOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "diving" }) elif stat == "fallOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "falling" }) elif stat == "flyOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "flying" }) elif stat == "boatOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "boat" }) elif stat == "horseOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "horse" }) elif stat == "climbOneCm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "climbing" }) elif stat == "XpTotal": player_xp_total.add_sample('player_xp_total', value=value, labels={'player': name}) elif stat == "XpLevel": player_current_level.add_sample('player_current_level', value=value, labels={'player': name}) elif stat == "foodLevel": player_food_level.add_sample('player_food_level', value=value, labels={'player': name}) elif stat == "Health": player_health.add_sample('player_health', value=value, labels={'player': name}) elif stat == "Score": player_score.add_sample('player_score', value=value, labels={'player': name}) elif stat == "killEntity": entities_killed.add_sample('entities_killed', value=value, labels={ 'player': name, "entity": key.split(".")[2] }) elif stat == "damageDealt": damage_dealt.add_sample('damage_dealt', value=value, labels={'player': name}) elif stat == "damageTaken": damage_dealt.add_sample('damage_taken', value=value, labels={'player': name}) elif stat == "craftItem": blocks_crafted.add_sample('blocks_crafted', value=value, labels={ 'player': name, 'block': '.'.join((key.split(".")[2], key.split(".")[3])) }) elif stat == "playOneMinute": player_playtime.add_sample('player_playtime', value=value, labels={'player': name}) elif stat == "advancements": player_advancements.add_sample('player_advancements', value=value, labels={'player': name}) elif stat == "sleepInBed": player_slept.add_sample('player_slept', value=value, labels={'player': name}) elif stat == "craftingTableInteraction": player_used_crafting_table.add_sample( 'player_used_crafting_table', value=value, labels={'player': name}) elif stat == "questsFinished": player_quests_finished.add_sample('player_quests_finished', value=value, labels={'player': name}) if "stats" in data: # Minecraft > 1.15 if "minecraft:crafted" in data["stats"]: for block, value in data["stats"]["minecraft:crafted"].items(): blocks_crafted.add_sample('blocks_crafted', value=value, labels={ 'player': name, 'block': block }) if "minecraft:mined" in data["stats"]: for block, value in data["stats"]["minecraft:mined"].items(): blocks_mined.add_sample("blocks_mined", value=value, labels={ 'player': name, 'block': block }) if "minecraft:picked_up" in data["stats"]: for block, value in data["stats"]["minecraft:picked_up"].items( ): blocks_picked_up.add_sample("blocks_picked_up", value=value, labels={ 'player': name, 'block': block }) if "minecraft:killed" in data["stats"]: for entity, value in data["stats"]["minecraft:killed"].items(): entities_killed.add_sample('entities_killed', value=value, labels={ 'player': name, "entity": entity }) if "minecraft:killed_by" in data["stats"]: for entity, value in data["stats"][ "minecraft:killed_by"].items(): player_deaths.add_sample('player_deaths', value=value, labels={ 'player': name, 'cause': entity }) for stat, value in data["stats"]["minecraft:custom"].items(): if stat == "minecraft:jump": player_jumps.add_sample("player_jumps", value=value, labels={'player': name}) elif stat == "minecraft:deaths": player_deaths.add_sample('player_deaths', value=value, labels={'player': name}) elif stat == "minecraft:damage_taken": damage_taken.add_sample('damage_taken', value=value, labels={'player': name}) elif stat == "minecraft:damage_dealt": damage_dealt.add_sample('damage_dealt', value=value, labels={'player': name}) elif stat == "minecraft:play_one_minute": player_playtime.add_sample('player_playtime', value=value, labels={'player': name}) elif stat == "minecraft:walk_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "walking" }) elif stat == "minecraft:walk_on_water_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "swimming" }) elif stat == "minecraft:sprint_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "sprinting" }) elif stat == "minecraft:walk_under_water_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "diving" }) elif stat == "minecraft:fall_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "falling" }) elif stat == "minecraft:fly_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "flying" }) elif stat == "minecraft:boat_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "boat" }) elif stat == "minecraft:horse_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "horse" }) elif stat == "minecraft:climb_one_cm": cm_traveled.add_sample("cm_traveled", value=value, labels={ 'player': name, 'method': "climbing" }) elif stat == "minecraft:sleep_in_bed": player_slept.add_sample('player_slept', value=value, labels={'player': name}) elif stat == "minecraft:interact_with_crafting_table": player_used_crafting_table.add_sample( 'player_used_crafting_table', value=value, labels={'player': name}) else: mc_custom.add_sample('mc_custom', value=value, labels={'stat': stat}) return [ blocks_mined, blocks_picked_up, player_deaths, player_jumps, cm_traveled, player_xp_total, player_current_level, player_food_level, player_health, player_score, entities_killed, damage_taken, damage_dealt, blocks_crafted, player_playtime, player_advancements, player_slept, player_used_crafting_table, player_quests_finished, mc_custom ] def collect(self): for player in self.get_players(): for metric in self.update_metrics_for_player(player): yield metric for metric in self.get_server_stats(): yield metric
class MCServerAdmin: def __init__(self, ip=None, rcon_port=None, rcon_pass=None): self.tk_window = tk.Tk() self.tk_style = ttk.Style(self.tk_window) self.tk_window.withdraw() if not ip and not rcon_port and not rcon_pass: self.generate_login_ui() else: self.connectToServer(ip, rcon_port, rcon_pass) self.generate_main_ui() self.tk_window.mainloop() def generate_main_ui(self): self.tk_window.deiconify() self.tk_window.title(f"PyMineCraft Administration - {self.ip}:{self.port}") commandLabel = ttk.Label(self.tk_window, text="Enter a command: ") commandLabel.grid(column=0, row=0, padx=10, pady=10, sticky='w') commandInput = ttk.Entry(self.tk_window, width=50) commandInput.grid(column=0, row=1, padx=10, pady=10, sticky='w') sendCommandButton = ttk.Button(self.tk_window, command=lambda: self.processCommand(commandInput.get()) or commandInput.delete(0, len(commandInput.get())), text='Send Command') sendCommandButton.grid(column=0, row=2, padx=10, pady=10, sticky='w') self.tk_window.bind('<Return>', lambda x: self.processCommand(commandInput.get()) or commandInput.delete(0, len(commandInput.get()))) serverResponseLabel = ttk.Label(self.tk_window, text="Server Response:") serverResponseLabel.grid(column=0, row=3, padx=10, pady=10, sticky='w') self.serverResponseText = ttk.Label(self.tk_window, text="N/A") self.serverResponseText.grid(column=0, row=4, padx=10, pady=10, sticky='NSEW') def generate_login_ui(self): self.tk_window.deiconify() self.tk_window.title("PyMineCraft Administration - Login") serverIPLabel = ttk.Label(self.tk_window, text="Minecraft Server IP: ") serverIPLabel.grid(column=0, row=0, padx=10, pady=10, sticky='w') serverIPInput = ttk.Entry(self.tk_window) serverIPInput.grid(column=0, row=1, padx=10, pady=10, sticky='w') rconPortLabel = ttk.Label(self.tk_window, text="RCON Port: ") rconPortLabel.grid(column=1, row=0, padx=10, pady=10, sticky='w') rconPortInput = ttk.Entry(self.tk_window) rconPortInput.grid(column=1, row=1, padx=10, pady=10, sticky='w') rconPasswordLabel = ttk.Label(self.tk_window, text="RCON Password: "******"Server Connectivity - ERROR", "The Minecraft Server RCON port must be an integer (1-65535)") raise ValueError("The Minecraft Server RCON port must be an integer (1-65535)") self.mcr_instance = MCRcon(host=self.ip, port=self.port, password=rcon_pass) self.mcr_instance.connect() if self.mcr_instance: messagebox.showinfo("Server Connectivity - Success", "Successfully connected to the server!") self.resetWindow() self.generate_main_ui() else: messagebox.showinfo("Server Connectivity - Failed", "Failed connecting to the server!") def resetWindow(self): self.tk_window.destroy() self.tk_window = tk.Tk() self.tk_style = ttk.Style(self.tk_window) def processCommand(self, command): if self.mcr_instance: cmd_input = command.strip() if len(cmd_input) == 0: return elif cmd_input == "!quit": self.exit() return elif cmd_input[0] != "/": resp = f"> {cmd_input}" cmd_input = f"say {cmd_input}" self.mcr_instance.command(cmd_input) else: cmd_input = cmd_input[1:] resp = self.mcr_instance.command(cmd_input) print(resp) if self.serverResponseText: self.serverResponseText.config(text=resp) def exit(self): if self.mcr_instance: self.mcr_instance.disconnect() if self.tk_window: self.tk_window.destroy()
def get_server_stats(self): if not all(x in os.environ for x in ['RCON_HOST', 'RCON_PASSWORD']): return [] dim_tps = Metric('dim_tps', 'TPS of a dimension', "counter") dim_ticktime = Metric('dim_ticktime', "Time a Tick took in a Dimension", "counter") overall_tps = Metric('overall_tps', 'overall TPS', "counter") overall_ticktime = Metric('overall_ticktime', "overall Ticktime", "counter") player_online = Metric('player_online', "is 1 if player is online", "counter") entities = Metric('entities', "type and count of active entites", "counter") mcr = MCRcon(os.environ['RCON_HOST'], os.environ['RCON_PASSWORD'], port=int(os.environ['RCON_PORT'])) mcr.connect() # dimensions resp = mcr.command("forge tps") dimtpsregex = re.compile( "Dim\s*(-*\d*)\s\((.*?)\)\s:\sMean tick time:\s(.*?) ms\. Mean TPS: (\d*\.\d*)" ) for dimid, dimname, meanticktime, meantps in dimtpsregex.findall(resp): dim_tps.add_sample('dim_tps', value=meantps, labels={ 'dimension_id': dimid, 'dimension_name': dimname }) dim_ticktime.add_sample('dim_ticktime', value=meanticktime, labels={ 'dimension_id': dimid, 'dimension_name': dimname }) overallregex = re.compile( "Overall\s?: Mean tick time: (.*) ms. Mean TPS: (.*)") overall_tps.add_sample('overall_tps', value=overallregex.findall(resp)[0][1], labels={}) overall_ticktime.add_sample('overall_ticktime', value=overallregex.findall(resp)[0][0], labels={}) # entites resp = mcr.command("forge entity list") entityregex = re.compile("(\d+): (.*?:.*?)\s") for entitycount, entityname in entityregex.findall(resp): entities.add_sample('entities', value=entitycount, labels={'entity': entityname}) # player resp = mcr.command("list") playerregex = re.compile("There are \d*\/20 players online:(.*)") if playerregex.findall(resp): for player in playerregex.findall(resp)[0].split(","): if player: player_online.add_sample( 'player_online', value=1, labels={'player': player.lstrip()}) return [ dim_tps, dim_ticktime, overall_tps, overall_ticktime, player_online, entities ]
class RCON(commands.Cog): """Interface with a Minecraft server theorugh Discord with RCON""" def __init__(self, bot): self.bot = bot self.rcon = MCRcon(RCON_ADDR, RCON_PASS, RCON_PORT) self.rcon.connect() self.am = AccountManager() @commands.command(name="rcon") @commands.has_any_role(*ADMIN_ROLES) async def rcon_send(self, ctx, *command): command = " ".join(command) resp = self.rcon.command(command) embed = discord.Embed(title="RCON", description=f"Command: {command}", colour=0x0FFF0F) embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) data = resp if resp else "No data was returned. This likely means the command succeeded." embed.add_field(name="Result", value=data) await ctx.channel.send(embed=embed) @commands.command(name="whitelist") async def rcon_whitelist(self, ctx, username: str): roles = [role.name for role in ctx.author.roles] force = any([role in BYPASS_ROLES for role in roles]) result, data = self.am.whitelist_add(username, ctx.author.id, override=force) resp = None if result: resp = self.rcon.command(f"whitelist add {username}") else: resp = data embed = discord.Embed( title="Whitelist", description=f"User: {username} ({ctx.author.id})", colour=0x0F0FFF) embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) embed.add_field(name="Result", value=resp) await ctx.channel.send(embed=embed) @commands.command(name="unwhitelist") @commands.has_any_role(*ADMIN_ROLES) async def rcon_unwhitelist(self, ctx, username: str): result, data = self.am.whitelist_remove(username) resp = None if result: resp = self.rcon.command(f"whitelist remove {username}") else: resp = data embed = discord.Embed(title="Unwhitelist", description=f"User: {username}", colour=0xFF0F0F) embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) embed.add_field(name="Result", value=resp) await ctx.channel.send(embed=embed) @commands.command(name="purgeuser") @commands.has_any_role(*ADMIN_ROLES) async def rcon_purge(self, ctx, username): result, data = self.am.whitelist_remove(username) resp = self.rcon.command(f"whitelist remove {username}") embed = discord.Embed(title="Purge", description=f"User: {username}", colour=0xFF0F0F) embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) embed.add_field(name="WLDB Result", value=data) embed.add_field(name="RCON Result", value=resp) await ctx.channel.send(embed=embed)
class MainCog(commands.Cog, name="main"): def __init__(self, bot: DioCraft): super().__init__() self.mcr = MCRcon(server_ip, server_password, int(server_port)) try: self.mcr.connect() except: print("Unexpected error: {}".format(sys.exc_info()[0])) self.mcr.disconnect() self.bot = bot @commands.command() async def login(self, ctx: commands.Command): allowed_roles = ["Admins"] if (await self.privilegeCheck(ctx, allowed_roles) and await self.channelCheck(ctx)): channel = self.bot.get_channel(729513577186066473) try: self.mcr.connect() await channel.send( "Successfully logged into the Minecraft server.") except: await channel.send("Unexpected error: {}".format( sys.exc_info()[0])) self.mcr.disconnect() @commands.command() async def awl(self, ctx: commands.Command): allowed_roles = ["Minecraft", "Admins"] if (await self.privilegeCheck(ctx, allowed_roles) and await self.channelCheck(ctx)): player_name = ctx.message.content[5:] resp = self.mcr.command("/whitelist add {}".format(player_name)) await ctx.send(resp) @commands.command() async def dwl(self, ctx: commands.Command): allowed_roles = ["Admins"] if (await self.privilegeCheck(ctx, allowed_roles) and await self.channelCheck(ctx)): player_name = ctx.message.content[5:] resp = self.mcr.command("/whitelist remove {}".format(player_name)) await ctx.send(resp) @commands.command() async def wl(self, ctx: commands.Command): if (await self.channelCheck(ctx)): await self.listWhitelist(ctx) @commands.command() async def online(self, ctx: commands.Command): if (await self.channelCheck(ctx)): await self.listPlayers(ctx) @commands.command() async def help(self, ctx: commands.Command): if (await self.channelCheck(ctx)): msg = "Thank you for using DioCraft! It is still currently under development :)\n\n" msg += "/online - Display list of all players that are online.\n" msg += "/wl - Display list of all players that are whitelisted.\n" msg += "/awl <name> - To add someone to the server whitelist.\n" msg += "/dwl <name> - To remove someone from the server whitelist.\n\n" msg += "/login - For administrators to connect to the server.\n\n" msg += "If you have any questions or suggestions, please contact primal#7602! Thank you!" await ctx.send(msg) async def privilegeCheck(self, ctx: commands.Command, allowed_roles): is_admin = False for role in ctx.message.author.roles: if (role.name in allowed_roles): is_admin = True if (not is_admin): await ctx.send("{}, does not have permission.".format( ctx.message.author.display_name)) return is_admin async def channelCheck(self, ctx: commands.Command): is_channel = False allowed_channels = [729859164209283104, 729513577186066473] # Change this to list to hold more channels. if (ctx.message.channel.id in allowed_channels): is_channel = True return is_channel async def listWhitelist(self, ctx: commands.Command): resp = self.mcr.command("/whitelist list").split(" ") result = "```The following {} players are whitelisted:\n".format( resp[2]) resp = resp[5:] lastName = resp[len(resp) - 1] resp = resp[:-1] for name in resp: result += name[:-1] + "\n" result += lastName + "```" await ctx.send(result) async def listPlayers(self, ctx: commands.Command): resp = self.mcr.command("/list").split(" ") result = "```There {} players online:\n".format(resp[2]) resp = resp[10:] lastName = resp[len(resp) - 1] resp = resp[:-1] for name in resp: result += name[:-1] + "\n" result += lastName + "```" await ctx.send(result)
from mcrcon import MCRcon import constants import utils import time import requests import subprocess import re import json import random import threading rcon = MCRcon('localhost', 'rcon') rcon.connect() setattr(rcon, 'say', lambda s: rcon.command(f'say {s}')) with open('warps.json') as fp: warps = json.load(fp) def write_warps(): with open('warps.json', 'w+') as fp: json.dump(warps, fp) def command_exec(name: str, args: str): rcon.say(utils.exec_python(args, locals(), globals())) def command_joke(name: str, args: str): rcon.say(utils.get_joke())
class MinecraftCollector(object): def __init__(self): prefix="/home/jiba/Minecraft/server" self.statsdirectory = prefix+"/world/stats" self.playerdirectory = prefix+"/world/playerdata" self.advancementsdirectory = prefix+"/world/advancements" self.betterquesting = prefix+"/world/betterquesting" self.map = dict() self.questsEnabled = False if all(x in os.environ for x in ['RCON_HOST','RCON_PASSWORD']): self.mcr = MCRcon(os.environ['RCON_HOST'],os.environ['RCON_PASSWORD'],port=int(os.environ['RCON_PORT'])) self.mcr.connect() if os.path.isdir(self.betterquesting): self.questsEnabled = True def get_players(self): return [f[:-5] for f in listdir(self.statsdirectory) if isfile(join(self.statsdirectory, f))] def uuid_to_player(self,uuid): uuid = uuid.replace('-','') if uuid in self.map: return self.map[uuid] else: result = requests.get('https://api.mojang.com/user/profiles/'+uuid+'/names') self.map[uuid] = result.json()[0]['name'] return(result.json()[0]['name']) def get_server_stats(self): if not all(x in os.environ for x in ['RCON_HOST','RCON_PASSWORD']): return [] dim_tps = Metric('dim_tps','TPS of a dimension',"counter") dim_ticktime = Metric('dim_ticktime',"Time a Tick took in a Dimension","counter") overall_tps = Metric('overall_tps','overall TPS',"counter") overall_ticktime = Metric('overall_ticktime',"overall Ticktime","counter") player_online = Metric('player_online',"is 1 if player is online","counter") entities = Metric('entities',"type and count of active entites", "counter") #moded by AceDroidX tps_from_last1 = Metric('tps_from_last1',"tps_from_last 1 min(tps)", "gauge") tps_from_last5 = Metric('tps_from_last5',"tps_from_last 5 min(tps)", "gauge") tps_from_last15 = Metric('tps_from_last15',"tps_from_last 15 min(tps)", "gauge") resp = self.mcr.command("tps") print('debug:tps'+resp) dimtpsregex = re.compile("([0-9]*\..*), §.*?([0-9]*\..*), §.*?([0-9]*\..*)") print('debug:tpsfindall:'+str(dimtpsregex.findall(resp))) tps_from_last1.add_sample('tps_from_last1',value=dimtpsregex.findall(resp)[0][0],labels={}) tps_from_last5.add_sample('tps_from_last5',value=dimtpsregex.findall(resp)[0][1],labels={}) tps_from_last15.add_sample('tps_from_last15',value=dimtpsregex.findall(resp)[0][2],labels={}) # dimensions """ resp = mcr.command("forge tps") print('debug1'+resp) dimtpsregex = re.compile("Dim\s*(-*\d*)\s\((.*?)\)\s:\sMean tick time:\s(.*?) ms\. Mean TPS: (\d*\.\d*)") for dimid, dimname, meanticktime, meantps in dimtpsregex.findall(resp): dim_tps.add_sample('dim_tps',value=meantps,labels={'dimension_id':dimid,'dimension_name':dimname}) dim_ticktime.add_sample('dim_ticktime',value=meanticktime,labels={'dimension_id':dimid,'dimension_name':dimname}) overallregex = re.compile("Overall\s?: Mean tick time: (.*) ms. Mean TPS: (.*)") overall_tps.add_sample('overall_tps',value=overallregex.findall(resp)[0][1],labels={}) overall_ticktime.add_sample('overall_ticktime',value=overallregex.findall(resp)[0][0],labels={}) """ # entites """ resp = mcr.command("forge entity list") entityregex = re.compile("(\d+): (.*?:.*?)\s") for entitycount, entityname in entityregex.findall(resp): entities.add_sample('entities',value=entitycount,labels={'entity':entityname}) """ # player resp = self.mcr.command("list") playerregex = re.compile("There are \d*\/20 players online:(.*)") if playerregex.findall(resp): for player in playerregex.findall(resp)[0].split(","): if player: player_online.add_sample('player_online',value=1,labels={'player':player.lstrip()}) #return[dim_tps,dim_ticktime,overall_tps,overall_ticktime,player_online,entities] return[player_online,tps_from_last1,tps_from_last5,tps_from_last15] def get_player_quests_finished(self,uuid): with open(self.betterquesting+"/QuestProgress.json") as json_file: data = json.load(json_file) json_file.close() counter = 0 for _, value in data['questProgress:9'].items(): for _, u in value['tasks:9']['0:10']['completeUsers:9'].items(): if u == uuid: counter +=1 return counter def get_player_stats(self,uuid): with open(self.statsdirectory+"/"+uuid+".json") as json_file: data = json.load(json_file) json_file.close() nbtfile = nbt.nbt.NBTFile(self.playerdirectory+"/"+uuid+".dat",'rb') data["stat.XpTotal"] = nbtfile.get("XpTotal").value data["stat.XpLevel"] = nbtfile.get("XpLevel").value data["stat.Score"] = nbtfile.get("Score").value data["stat.Health"] = nbtfile.get("Health").value data["stat.foodLevel"]= nbtfile.get("foodLevel").value with open(self.advancementsdirectory+"/"+uuid+".json") as json_file: count = 0 advancements = json.load(json_file) for key, value in advancements.items(): if key in ("DataVersion"): continue if value["done"] == True: count += 1 data["stat.advancements"] = count if self.questsEnabled: data["stat.questsFinished"] = self.get_player_quests_finished(uuid) return data def update_metrics_for_player(self,uuid): data = self.get_player_stats(uuid) name = self.uuid_to_player(uuid) blocks_mined = Metric('blocks_mined','Blocks a Player mined',"counter") blocks_picked_up = Metric('blocks_picked_up','Blocks a Player picked up',"counter") player_deaths = Metric('player_deaths','How often a Player died',"counter") player_jumps = Metric('player_jumps','How often a Player has jumped',"counter") cm_traveled = Metric('cm_traveled','How many cm a Player traveled, whatever that means',"counter") player_xp_total = Metric('player_xp_total',"How much total XP a player has","counter") player_current_level= Metric('player_current_level',"How much current XP a player has","counter") player_food_level = Metric('player_food_level',"How much food the player currently has","counter") player_health = Metric('player_health',"How much Health the player currently has","counter") player_score = Metric('player_score',"The Score of the player","counter") entities_killed = Metric('entities_killed',"Entities killed by player","counter") damage_taken = Metric('damage_taken',"Damage Taken by Player","counter") damage_dealt = Metric('damage_dealt',"Damage dealt by Player","counter") blocks_crafted = Metric('blocks_crafted',"Items a Player crafted","counter") player_playtime = Metric('player_playtime',"Time in Minutes a Player was online","counter") player_advancements = Metric('player_advancements', "Number of completed advances of a player","counter") player_slept = Metric('player_slept',"Times a Player slept in a bed","counter") player_quests_finished = Metric('player_quests_finished', 'Number of quests a Player has finished', 'counter') player_used_crafting_table = Metric('player_used_crafting_table',"Times a Player used a Crafting Table","counter") for key, value in data.items(): if key in ("stats", "DataVersion"): continue stat = key.split(".")[1] # entityKilledBy if stat == "mineBlock": blocks_mined.add_sample("blocks_mined",value=value,labels={'player':name,'block':'.'.join((key.split(".")[2],key.split(".")[3]))}) elif stat == "pickup": blocks_picked_up.add_sample("blocks_picked_up",value=value,labels={'player':name,'block':'.'.join((key.split(".")[2],key.split(".")[3]))}) elif stat == "entityKilledBy": if len(key.split(".")) == 4: player_deaths.add_sample('player_deaths',value=value,labels={'player':name,'cause':'.'.join((key.split(".")[2],key.split(".")[3]))}) else: player_deaths.add_sample('player_deaths',value=value,labels={'player':name,'cause':key.split(".")[2]}) elif stat == "jump": player_jumps.add_sample("player_jumps",value=value,labels={'player':name}) elif stat == "walkOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"walking"}) elif stat == "swimOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"swimming"}) elif stat == "sprintOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"sprinting"}) elif stat == "diveOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"diving"}) elif stat == "fallOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"falling"}) elif stat == "flyOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"flying"}) elif stat == "boatOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"boat"}) elif stat == "horseOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"horse"}) elif stat == "climbOneCm": cm_traveled.add_sample("cm_traveled",value=value,labels={'player':name,'method':"climbing"}) elif stat == "XpTotal": player_xp_total.add_sample('player_xp_total',value=value,labels={'player':name}) elif stat == "XpLevel": player_current_level.add_sample('player_current_level',value=value,labels={'player':name}) elif stat == "foodLevel": player_food_level.add_sample('player_food_level',value=value,labels={'player':name}) elif stat == "Health": player_health.add_sample('player_health',value=value,labels={'player':name}) elif stat == "Score": player_score.add_sample('player_score',value=value,labels={'player':name}) elif stat == "killEntity": entities_killed.add_sample('entities_killed',value=value,labels={'player':name,"entity":key.split(".")[2]}) elif stat == "damageDealt": damage_dealt.add_sample('damage_dealt',value=value,labels={'player':name}) elif stat == "damageTaken": damage_dealt.add_sample('damage_taken',value=value,labels={'player':name}) elif stat == "craftItem": blocks_crafted.add_sample('blocks_crafted',value=value,labels={'player':name,'block':'.'.join((key.split(".")[2],key.split(".")[3]))}) elif stat == "playOneMinute": player_playtime.add_sample('player_playtime',value=value,labels={'player':name}) elif stat == "advancements": player_advancements.add_sample('player_advancements',value=value,labels={'player':name}) elif stat == "sleepInBed": player_slept.add_sample('player_slept',value=value,labels={'player':name}) elif stat == "craftingTableInteraction": player_used_crafting_table.add_sample('player_used_crafting_table',value=value,labels={'player':name}) elif stat == "questsFinished": player_quests_finished.add_sample('player_quests_finished',value=value,labels={'player':name}) return [blocks_mined,blocks_picked_up,player_deaths,player_jumps,cm_traveled,player_xp_total,player_current_level,player_food_level,player_health,player_score,entities_killed,damage_taken,damage_dealt,blocks_crafted,player_playtime,player_advancements,player_slept,player_used_crafting_table,player_quests_finished] def collect(self): # for player in self.get_players(): # for metric in self.update_metrics_for_player(player)+self.get_server_stats(): # yield metric for metric in self.get_server_stats(): yield metric
def get_server_stats(self): if not all(x in os.environ for x in ['RCON_HOST', 'RCON_PASSWORD']): return [] dim_tps = Metric('dim_tps', 'TPS of a dimension', "counter") dim_ticktime = Metric('dim_ticktime', "Time a Tick took in a Dimension", "counter") overall_tps = Metric('overall_tps', 'overall TPS', "counter") overall_ticktime = Metric('overall_ticktime', "overall Ticktime", "counter") player_online = Metric('player_online', "is 1 if player is online", "counter") entities = Metric('entities', "type and count of active entites", "counter") mcr = MCRcon(os.environ['RCON_HOST'], os.environ['RCON_PASSWORD'], port=int(os.environ['RCON_PORT'])) mcr.connect() # dimensions resp = mcr.command("forge tps") dimtpsregex = re.compile( "Dim\s*(-*\d*)\s\((.*?)\)\s:\sMean tick time:\s(.*?) ms\. Mean TPS: (\d*\.\d*)" ) for dimid, dimname, meanticktime, meantps in dimtpsregex.findall(resp): dim_tps.add_sample('dim_tps', value=meantps, labels={ 'dimension_id': dimid, 'dimension_name': dimname }) dim_ticktime.add_sample('dim_ticktime', value=meanticktime, labels={ 'dimension_id': dimid, 'dimension_name': dimname }) overallregex = re.compile( "Overall\s?: Mean tick time: (.*) ms. Mean TPS: (.*)") overall_tps.add_sample('overall_tps', value=overallregex.findall(resp)[0][1], labels={}) overall_ticktime.add_sample('overall_ticktime', value=overallregex.findall(resp)[0][0], labels={}) # dynmap if os.environ['DYNMAP_ENABLED'] == "True": dynmap_tile_render_statistics = Metric( 'dynmap_tile_render_statistics', 'Tile Render Statistics reported by Dynmap', "counter") dynmap_chunk_loading_statistics_count = Metric( 'dynmap_chunk_loading_statistics_count', 'Chunk Loading Statistics reported by Dynmap', "counter") dynmap_chunk_loading_statistics_duration = Metric( 'dynmap_chunk_loading_statistics_duration', 'Chunk Loading Statistics reported by Dynmap', "counter") resp = mcr.command("dynmap stats") dynmaptilerenderregex = re.compile( " (.*?): processed=(\d*), rendered=(\d*), updated=(\d*)") for dim, processed, rendered, updated in dynmaptilerenderregex.findall( resp): dynmap_tile_render_statistics.add_sample( 'dynmap_tile_render_statistics', value=processed, labels={ 'type': 'processed', 'file': dim }) dynmap_tile_render_statistics.add_sample( 'dynmap_tile_render_statistics', value=rendered, labels={ 'type': 'rendered', 'file': dim }) dynmap_tile_render_statistics.add_sample( 'dynmap_tile_render_statistics', value=updated, labels={ 'type': 'updated', 'file': dim }) dynmapchunkloadingregex = re.compile( "Chunks processed: (.*?): count=(\d*), (\d*.\d*)") for state, count, duration_per_chunk in dynmapchunkloadingregex.findall( resp): dynmap_chunk_loading_statistics_count.add_sample( 'dynmap_chunk_loading_statistics', value=count, labels={'type': state}) dynmap_chunk_loading_statistics_duration.add_sample( 'dynmap_chunk_loading_duration', value=duration_per_chunk, labels={'type': state}) # entites resp = mcr.command("forge entity list") entityregex = re.compile("(\d+): (.*?:.*?)\s") for entitycount, entityname in entityregex.findall(resp): entities.add_sample('entities', value=entitycount, labels={'entity': entityname}) # player resp = mcr.command("list") playerregex = re.compile("There are \d*\/20 players online:(.*)") if playerregex.findall(resp): for player in playerregex.findall(resp)[0].split(","): if player: player_online.add_sample( 'player_online', value=1, labels={'player': player.lstrip()}) return [ dim_tps, dim_ticktime, overall_tps, overall_ticktime, player_online, entities, dynmap_tile_render_statistics, dynmap_chunk_loading_statistics_count, dynmap_chunk_loading_statistics_duration ]
class MinecraftStatus(): def __init__(self, serverUrl, localIp, rconPort, queryPort, rconPassword): self.serverUrl = serverUrl self.localServer = MinecraftServer(localIp, queryPort) self.urlServer = MinecraftServer(serverUrl, queryPort) self.rcon = MCRcon(localIp, rconPassword, port=rconPort) self.previousPlayerAmountOnline = None self.rconConnect() def rconConnect(self): try: self.rcon.connect() except ConnectionRefusedError: logger.error("RCON Connection refused") except: logger.error("Unexpected error while trying to connect to RCON") def generateStatus(self): tps = "" urlLatency = -1 localLatency = -1 playerAmountOnline = -1 maxPlayerAmount = -1 playerList = "" mapName = "" try: urlStatus = self.urlServer.status() urlLatency = str(urlStatus.latency) except: logger.error("Error while contacting server over url") try: localStatus = self.localServer.status() localLatency = str(localStatus.latency) playerAmountOnline = localStatus.players.online maxPlayerAmount = localStatus.players.max players = localStatus.players.sample if playerAmountOnline > 0: for player in players: playerList += player.name + ", " playerList = playerList[:-2] # Remove last comma playerList += "." except: logger.error("Error getting local server status") try: tps = self.rcon.command("tps") except: logger.error("Rcon connection failed") self.rconConnect() tps = tps[29:] tps = tps.replace('§a', '') response = "```" response += 'Status report for ' + self.serverUrl + ': \n' if urlLatency != -1: response += "The server replied over DNS in " + \ str(urlLatency) + 'ms\n' else: response += "The server did not reply over DNS\n" if localLatency != -1: response += "The server replied over the local network in " + \ str(localLatency) + " ms\n" if playerAmountOnline > 0: response += "The server has " + \ str(playerAmountOnline) + "/" + \ str(maxPlayerAmount) + " players online.\n" response += "Online players: " + playerList + "\n" else: response += "No players are currently playing. Please do something about that :)\n" response += "The TPS for the server (1m,5m,15m) are: " + tps + "\n" else: response += "The server did not reply over the local network\n" response += "```" return response async def watch(self, sendNotifications): if self.previousPlayerAmountOnline is None: self.previousPlayerAmountOnline = self.localServer.status( ).players.online logger.debug("Initting prev players online") while True: # TODO: add stop flag localStatus = self.localServer.status() playerAmountOnline = localStatus.players.online if playerAmountOnline != self.previousPlayerAmountOnline: logger.debug("Playercount changed!") self.previousPlayerAmountOnline = playerAmountOnline if playerAmountOnline == 1: await sendNotifications( "Someone started playing on the server :D") elif playerAmountOnline == 0: await sendNotifications( "Awh, the server is all empty now :(") await asyncio.sleep(0.5) def say(self, message): self.command("say " + message) def command(self, message): try: self.rcon.command(message) except BrokenPipeError: logger.error("No Pipe for RCON command") self.rconConnect()
class worldeatercommands(commands.Cog): def __init__(self, client): self.client = client self.rcon_smp = MCRcon(config_rcon['rcon_smp']['rcon-ip'], config_rcon['rcon_smp']['rcon-password'], config_rcon['rcon_smp']['rcon-port']) self.coords = () self.peri_size = 0 self.worldeater_crashed = False self.we_updates = False self.ylevel = 0 @commands.Cog.listener() async def on_ready(self): print('worldeatercommands is online.') self.guild = self.client.get_guild(config_discord['guild']) self.we_channel = discord.utils.get( self.guild.text_channels, id=config_discord["worldeater_channel"]) @commands.command( help= 'Use this to start the worldeater script. Arguments: peri_size , mandatory<x,z>. x and z argument: Postion on any part of worldeater with no blocks above it. Height control is now necessary' ) @commands.has_any_role('Member', 'Trial-Member') async def westart(self, ctx, peri_size: int, *coords): if self.check_worldeater.is_running(): await ctx.send(embed=discord.Embed( title='World eater is already running', color=0xFF0000)) return self.peri_size = peri_size if len(coords) == 2: self.coords = coords self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run top(\'surface\',{self.coords[0]}, 0, {self.coords[1]})' ) self.ylevel = int(resp.split()[1]) + 1 cycletime = peri_size / 2 self.check_worldeater.change_interval(seconds=cycletime) self.check_worldeater.start() title = 'Worldeater is now running' msg = f'The peri size is: {peri_size}\n' \ f'Coordinates for height control: x={coords[0]} y={coords[1]}' else: self.coords = () title = 'Command was done incorrectly' msg = f'You are running without height control' await ctx.send( embed=discord.Embed(title=title, colour=0xFF0000, description=msg)) @commands.command(help='stops the world eater script') @commands.has_any_role('Member', 'Trial-Member') async def westop(self, ctx): if not self.check_worldeater.is_running(): await ctx.send(embed=discord.Embed( title='No world eater is running', color=0xFF0000)) return self.check_worldeater.cancel() self.check_worldeater.stop() self.coords = () self.peri_size = 0 self.worldeater_crashed = False self.we_updates = False self.ylevel = 0 await ctx.send(embed=discord.Embed( title=f'World eater script is stopped now', colour=0x00FF00, )) @commands.command(help='use this to get info about the world eater') @commands.has_any_role('Member', 'Trial-Member') async def westatus(self, ctx): if not self.check_worldeater.is_running(): await ctx.send(embed=discord.Embed( title='No world eater is running', color=0xFF0000)) return if self.worldeater_crashed: title = 'World eater is stuck' color = 0xFF0000 else: title = 'World eater is running' color = 0x00FF00 if not self.coords: self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run reduce(last_tick_times(),_a+_,0)/100;') mspt = float(resp.split()[1]) msg = f'MSPT is ~{round((mspt),1)}\n' \ 'You are running without height control' else: self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run top(\'surface\',{self.coords[0]}, 0, {self.coords[1]})' ) yLevel = int(resp.split()[1]) + 1 timeleft = str( datetime.timedelta(seconds=(self.peri_size / 2) * (yLevel - 5))) self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run reduce(last_tick_times(),_a+_,0)/100;') mspt = float(resp.split()[1]) msg = f'y-level: ~{yLevel}\n' \ f'WE has to run for another ~{timeleft}\n' \ f'MSPT is ~{round((mspt),1)}' self.rcon_smp.disconnect() await ctx.send( embed=discord.Embed(title=title, colour=color, description=msg)) @commands.command( help= 'get or remove worldeater role. you get pinged if worldeater crashes') @commands.has_any_role('Member', 'Trial-Member') async def wehelper(self, ctx): we_role = get(self.guild.roles, name=config_discord['worldeater_role']) if not we_role in ctx.message.author.roles: await ctx.message.author.add_roles(we_role) await ctx.send('You now get pinged if a world eater crashes') else: await ctx.message.author.remove_roles(we_role) await ctx.send('You are no longer a world eater helper') @commands.command(help='get live updates on world eater progress') @commands.has_any_role('Member', 'Trial-Member') async def weupdates(self, ctx): if not self.check_worldeater.is_running(): await ctx.send(embed=discord.Embed( title='No world eater is running', color=0xFF0000)) return if self.we_updates: await ctx.send(embed=discord.Embed(title='Live updates turned off', color=0xFF0000)) else: await ctx.send(embed=discord.Embed(title='Live updates turned on', color=0x00FF00)) self.we_updates = not self.we_updates @tasks.loop() async def check_worldeater(self): self.rcon_smp.connect() self.ylevel = yLevel resp = self.rcon_smp.command( f'/script run top(\'surface\',{self.coords[0]}, 0, {self.coords[1]})' ) resp = int(resp.split()[1]) + 1 if resp < ylevel: self.ylevel = ylevel - 1 if not resp < yLevel: if not self.worldeater_crashed: await asyncio.sleep(20) self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run top(\'surface\',{self.coords[0]}, 0, {self.coords[1]})' ) resp = int(resp.split()[1]) + 1 if not resp < ylevel: role = get(self.guild.roles, name=config_discord['worldeater_role']) await self.we_channel.send( f'{role.mention} World eater is stuck.') self.worldeater_crashed = True self.check_worldeater.change_interval(seconds=10) elif self.worldeater_crashed: self.worldeater_crashed = False self.check_worldeater.change_interval(seconds=self.peri_size / 2) self.ylevel = ylevel - 1 await self.we_channel.send(f'World eater is fine again.') if self.we_updates and not self.worldeater_crashed: if not self.coords: msg = 'still running' else: self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run top(\'surface\',{self.coords[0]}, 0, {self.coords[1]})' ) yLevel = int(resp.split()[1]) + 1 timeleft = str( datetime.timedelta(seconds=(self.peri_size / 2) * (yLevel - 5))) self.rcon_smp.connect() resp = self.rcon_smp.command( f'/script run reduce(last_tick_times(),_a+_,0)/100;') mspt = float(resp.split()[1]) msg = f'y-level: ~{yLevel}\n' \ f'WE has to run for another ~{timeleft}\n' \ f'MSPT is ~{round((mspt), 1)}' self.rcon_smp.disconnect() await self.we_channel.send(embed=discord.Embed( title='WE Updates', colour=0x00FF00, description=msg)) self.rcon_smp.disconnect()
def update_player_numbers(config): new_statuses = {} for server in config['servers'].keys(): host = config['servers'][server]['host'] port = config['servers'][server]['port'] password = config['servers'][server]['password'] players_online = 0 try: mcr = MCRcon(host=host, password=password, port=port) mcr.connect() players_online = mcr.command('list') players_online = re.sub(r'(§[0-9]|§[a-z])', '', players_online) players_online = int(re.search(r'\d+|$', players_online).group()) mcr.disconnect() except Exception as e: logging.warning(f'{host}:{port} could not be reached.\n{str(e)}') new_statuses[server] = players_online server_store = open(config['server_store'], 'r') cached_statuses = json.loads(server_store.read()) server_store.close() if config['debug']: print('========= Loaded statuses =========') pprint(cached_statuses) print() for server in set(new_statuses.keys()).union(cached_statuses.keys()): if server in new_statuses and server in cached_statuses: cached_statuses[server].append(new_statuses[server]) cached_statuses[server] = cached_statuses[server][-3:] elif server in new_statuses and server not in cached_statuses: logging.info( f'Found server {server} from config, adding to cached statuses list' ) cached_statuses[server] = [new_statuses[server]] elif server not in new_statuses and server in cached_statuses: if len(cached_statuses[server]) == 3 and sum( cached_statuses[server]) == 0: logging.warning( f'Dropping check for server {server} due to inactivity') del cached_statuses[server] else: cached_statuses[server].append(0) cached_statuses[server] = cached_statuses[server][-3:] else: raise (Exception('Something\'s fucky')) server_store = open(config['server_store'], 'w') server_store.write(json.dumps(cached_statuses, indent=2)) server_store.close() if config['debug']: print('======== Updated statuses =========') pprint(cached_statuses) print()