123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import pickle
- import os
- import math
- import asyncio
- from mcstatus import MinecraftServer
- import datetime
- #we use the ips of mc servers like ids of discord servers
- #Important note: DCServer objects must be created before MCServer objects
- client = None
- #these dictionaries store objects
- # with discord server ids/mc server ips as keys
- MCServers = {}
- DCServers = {}
- #this dictionaries store sets of mc server ips with
- # discord server id as keys and the other way around.
- linksMC_DC = {}
- linksDC_MC = {}
- def link(mc_ip, dc_id):
- try:
- linksMC_DC[mc_ip] = linksMC_DC[mc_ip].union([dc_id])
- except KeyError:
- linksMC_DC[mc_ip] = set([dc_id])
- try:
- linksDC_MC[dc_id] = linksDC_MC[dc_id].union([mc_ip])
- except KeyError:
- linksDC_MC[dc_id] = set([mc_ip])
- def get_time_for_next_update(count, threshold):
- if count < threshold / 2:
- return 15
- if count >= threshold:
- return 1
- return 5
- class MCServer:
- def __init__(self, ip, name, game_threshold, DCs):
- self.ip = ip
- self.name = name
- self.game_threshold = game_threshold
- MCServers[ip] = self
- self.time_since_last_update = math.inf
- self.count = 0
- for id in DCs:
- link(ip, id)
- #runs once per minute
- def update_tick(self):
- self.time_since_last_update += 1
- if self.time_since_last_update >= get_time_for_next_update(self.count, self.game_threshold):
- self.time_since_last_update = 0
- return self
- else:
- return None
- def set_count(self, count):
- changed = self.count == count
- self.count = count
- return changed
-
-
- class DCServer:
- def __init__(self, id, channelID):
- self.id = id
- self.channelID = channelID
- DCServers[id] = self
- def get_player_count_string(self):
- countstr = "Players Online: "
- for mc in linksDC_MC[self.id]:
- mc = MCServers[mc]
- if mc.count == -1:
- countstr += "Can't reach " + mc.name
- else:
- countstr += mc.name + " " + str(mc.count)
- if mc.count >= mc.game_threshold:
- countstr += "✳"
- countstr += " +++ "
- return countstr
-
- #Read servers and links from file if file exists
- try:
- dirname, filename = os.path.split(os.path.abspath(__file__))
- with open(os.path.join(dirname, "servers.ref"), "rb") as f:
- MCServers, DCServers, linksMC_DC, linksDC_MC = pickle.load(f)
- for server in MCServers:
- #update all servers on start
- MCServers[server].time_since_last_update = math.inf
- except FileNotFoundError:
- pass #use empty dictionaries as defined earlier
- #creates DCServer object if it doesn't exist yet
- def ensure_discord(id, channelID):
- try:
- DCServers[id] #already exists
- except KeyError:
- #create new DCServer object,
- # gets put into dict in constructor
- DCServer(id, channelID)
- def parse_arguments(command_content):
- words = command_content.split()
- return words[1], words[2], int(words[3])
-
- #returns response to message
- def add_mc_server(command):
- try:
- ip, name, threshold = parse_arguments(command.content)
- except IndexError:
- return "Could not instantiate Server object: Too few arguments"
- except ValueError:
- return "Could not instantiate Server object: The 3rd argument must be an integer"
-
- try:
- ensure_discord(command.guild.id, command.channel.id)
- except AttributeError:
- return "This command does not work in DM channels"
- try:
- MCServers[ip]
- #mc server already exists, link with discord
- try:
- if command.guild.id in linksMC_DC[ip]:
- return "already linked" #maybe make this unlink
- else:
- link(ip, command.guild.id)
- except KeyError:
- link(ip, command.guild.id)
- except KeyError:
- #create new mc server object
- MCServer(ip, name, threshold, [command.guild.id])
- #save servers to file
- dirname, filename = os.path.split(os.path.abspath(__file__))
- with open(os.path.join(dirname, "servers.ref"), "wb") as f:
- pickle.dump((MCServers, DCServers, linksMC_DC, linksDC_MC), f)
- return "Successfully linked MC Server"
- class Updater:
- mcServersToUpdate = set()
- dcServersToUpdate = set()
- force = False
- async def force_update(self, discordID):
- self.force = True
- mcs = linksDC_MC[discordID]
- for mc in mcs:
- mc = MCServers[mc]
- mc.time_since_last_update = math.inf
- self.update_tick()
- await self.wait_for_force()
- return DCServers[discordID].get_player_count_string()
- async def wait_for_force(self):
- while self.force:
- await asyncio.sleep(1)
- async def start(self):
- while True:
- now = datetime.datetime.now()
- after_minute = now.second + now.microsecond / 1_000_000
- if after_minute:
- await asyncio.sleep(60 - after_minute)
- self.update_tick()
-
- #called when all player counts are retrieved
- def done_updating(self):
- for update in self.updateResults:
- should_update_header, mcserver, _ = update.result()
- if should_update_header:
- self.dcServersToUpdate = self.dcServersToUpdate.union(linksMC_DC[mcserver.ip])
- for dc in self.dcServersToUpdate:
- dc = DCServers[dc]
- channel = client.get_channel(dc.channelID)
- if channel != None:
- loop = asyncio.get_event_loop()
- loop.create_task(channel.edit(topic = dc.get_player_count_string()))
- print("Updated player counter:", datetime.datetime.now())
- self.force = False
- #reSET these
- self.mcServersToUpdate = set()
- self.dcServersToUpdate = set()
-
- #called on the start of every minute
- def update_tick(self):
- for name in MCServers:
- s = MCServers[name].update_tick()
- if s != None:
- self.mcServersToUpdate = self.mcServersToUpdate.union([s])
-
- self.updatesToDo = len(self.mcServersToUpdate)
- self.updateResults = set()
- loop = asyncio.get_event_loop()
-
- def on_done(future):
- a, b, updater = future.result()
- updater.updatesToDo -= 1
- if updater.updatesToDo < 1:
- updater.done_updating()
-
- for server in self.mcServersToUpdate:
- f = loop.create_future()
- asyncio.ensure_future(self.update_server(f, server))
- f.add_done_callback(on_done)
- self.updateResults = self.updateResults.union([f])
-
- async def update_server(self, future, server):
- try:
- m_server = MinecraftServer.lookup(server.ip)
- count = m_server.status().players.online
- future.set_result((server.set_count(count), server, self))
- except:
- future.set_result((server.set_count(-1), server, self))
|