async def sendMatchEvent(channel: Channel, match: Match, event: MatchEventData): """ This function encapsulates the look and feel of the message that is sent when a matchEvent happens. It will build the matchString, the embed object, etc. and than send it to the appropiate channel. :param channel: The channel where we want to send things to :param match: The match that this message applies to (Metadata!) :param event: The actual event that happened. It consists of a MatchEvents enum and a DataDict, which in itself contains the minute, team and player(s) the event applies to. """ title, content, goalString = await LiveMatch.beautifyEvent( event, match) embObj = Embed(title=title, description=content) embObj.set_author(name=match.competition.clear_name) try: await client.send_message(channel, embed=embObj) except: await asyncio.sleep(10) for i in client.get_all_channels(): if i.name == channel.name: logger.debug(f"Sending {embObj} to {i.name}") await client.send_message(i, embed=embObj) return title, goalString
async def createChannel(server: Server, channelName: str): """ Creates a channel on the discord server. :param server: Server object --> relevant server for the channel :param channelName: Name of the channel that is to be created """ for i in client.get_all_channels(): if i.name == toDiscordChannelName(channelName) and i.server == server: logger.debug(f"Channel {channelName} already available ") return logger.info(f"Creating channel {channelName} on {server.name}") await client.create_channel(server, channelName)
async def sendMatchEvent(self, channel: TextChannel, match: Match, event: MatchEventData): """ This function encapsulates the look and feel of the message that is sent when a matchEvent happens. It will build the matchString, the embed object, etc. and than send it to the appropiate channel. :param channel: The channel where we want to send things to :param match: The match that this message applies to (Metadata!) :param event: The actual event that happened. It consists of a MatchEvents enum and a DataDict, which in itself contains the minute, team and player(s) the event applies to. """ title, content, goalString = await LiveMatch.beautifyEvent( event, match) embObj = Embed(title=title, description=content) embObj.set_author(name=match.competition.clear_name) embObj.colour = self.color lastName = event.player.split(" ")[-1].lower() firstName = event.player.split(" ")[0].lower() imageList = Player.objects.filter(lastName=lastName) image = None if len(imageList) != 0: if len(imageList) == 1: image = imageList.first().imageLink else: imageList = imageList.filter(firstName=firstName) if len(imageList) != 0: image = imageList.first().imageLink else: imageList = Player.objects.filter(lastName=lastName) image = imageList.first().imageLink if image is not None: embObj.set_thumbnail(url=image) try: msg = await channel.send(embed=embObj) except: await asyncio.sleep(10) for i in client.get_all_channels(): if i.name == channel.name: logger.debug(f"Sending {embObj} to {i.name}") msg = await channel.send(embed=embObj) msgEvent = RedditEvent(event, datetime.utcnow(), self.match, self.updateMsg) self.msgList[msgEvent] = msg RedditParser.addEvent(msgEvent) return title, goalString
async def removeOldChannels(): """ Removes all channels with the name *-matchday-* in them. """ deleteChannelList = [] #these two loops are split up, as the it raises an error when the dict changes. for i in client.get_all_channels(): if "-matchday-" in i.name: logger.info(f"Deleting old channel {i.name}") deleteChannelList.append((i.server, i.name)) for i in deleteChannelList: await deleteChannel(i[0], i[1])
async def deleteChannel(server: Server, channelName: str): """ Deletes a channel on the discord server :param server: Server object --> relevant server for the channel :param channelName: Name of the channel that is to be deleted """ for i in client.get_all_channels(): if i.name == toDiscordChannelName(channelName) and i.server == server: logger.debug( f"Deleting channel {toDiscordChannelName(channelName)} on {server.name}" ) await client.delete_channel(i) break
async def deleteChannel(guild: Guild, channelName: str): """ Deletes a channel on the discord server :param guild: Server object --> relevant server for the channel :param channelName: Name of the channel that is to be deleted """ for i in client.get_all_channels(): if i.name == toDiscordChannelName(channelName) and i.guild == guild: logger.debug( f"Deleting channel {toDiscordChannelName(channelName)} on {guild.name}" ) try: await i.delete() except discord.errors.NotFound: pass break
async def createChannel(guild: Guild, channelName: str, role: str = None, category: str = None): """ Creates a channel on the discord server. :param guild: Server object --> relevant server for the channel :param channelName: Name of the channel that is to be created """ for i in client.get_all_channels(): if i.name == toDiscordChannelName(channelName) and i.guild == guild: logger.debug(f"Channel {channelName} already available ") return logger.info(f"Creating channel {channelName} on {guild.name}") targetRole = None for i in guild.roles: if int(i.id) == role: targetRole = i break if category is not None: channelCat = None for i in guild.categories: if i.name == category: channelCat = i break if channelCat is None: channelCat = await guild.create_category(category) else: channelCat = None if targetRole is None: overwrites = None else: overwrites = { guild.default_role: discord.PermissionOverwrite(read_messages=False, send_messages=False), targetRole: discord.PermissionOverwrite(read_messages=True, send_messages=True) } await guild.create_text_channel(channelName, overwrites=overwrites, category=channelCat)
async def runMatchThread(self): """ Start a match threader for a given match. Will read the live data from the middleWare API (data.fifa.com) every 20 seconds and post the events to the channel that corresponds to the match. This channel has to be created previously. :param match: Match object. Will post to discord channels if the object is a database.models.Match object """ if self.runningStarted: logger.warning(f"Match {self.title} already started!") return else: logger.info(f"Starting match {self.title}") self.runningStarted = True pastEvents = [] eventList = [] sleepTime = 600 endCycles = 10 matchid = self.match.id channelName = toDiscordChannelName( f"{self.match.competition.clear_name} Matchday {self.match.matchday}" ) lineupsPosted = False while True: try: data = makeMiddlewareCall(DataCalls.liveData + f"/{matchid}") except JSONDecodeError: break if data["match"]["isFinished"] and not self.running: logger.info(f"Match {self.match} allready passed") break self.running = True if data["match"]["isLive"]: self.started = True else: self.started = False if not self.lock.is_set(): self.lock.set() if not lineupsPosted and data["match"]["hasLineup"]: logger.info(f"Posting lineups for {self.title}") await asyncio.sleep(5) try: for channel in client.get_all_channels(): if channel.name == channelName: await LiveMatch.postLineups( channel, self.match, data["match"]) lineupsPosted = True sleepTime = 20 except RuntimeError: lineupsPosted = False logger.warning("Size of channels has changed") else: if not lineupsPosted: logger.info(f"Lineups not yet available for {self.title}") newEvents, pastEvents = LiveMatch.parseEvents( data["match"]["events"], pastEvents) eventList += newEvents for i in eventList: try: for channel in client.get_all_channels(): if channel.name == channelName: self.started = True self.title, goalString = await LiveMatch.sendMatchEvent( channel, self.match, i) self.goalList.append(goalString) try: eventList.remove(i) except ValueError: pass logger.info(f"Posting event: {i}") except RuntimeError: logger.warning("Size of channels has changed!") break if self.lock.is_set(): self.lock.clear() if data["match"]["isFinished"]: if endCycles <= 0: logger.info(f"Match {match} finished!") break endCycles -= 1 await asyncio.sleep(sleepTime) now = datetime.utcnow().replace(tzinfo=UTC) if now < (self.match.date + timedelta(hours=3)).replace(tzinfo=UTC): self.passed = True self.running = False self.started = False self.runningStarted = False self.lock.set() logger.info(f"Ending match {self.title}")
async def postLineups(channel: Channel, match: Match, data: Dict): lineup = OrderedDict() for i in ['home', 'away']: lineup[i] = OrderedDict() lineup[i]['starting'] = [] lineup[i]['bench'] = [] lineup[i]['coach'] = [] for player in data['lineups']['teams'][i]: playerInfo = OrderedDict() playerInfo['name'] = player['personName'] playerInfo['number'] = player['shirtNumber'] playerInfo['captain'] = player['isCaptain'] playerInfo['gk'] = player['isGoalKeeper'] if player['isCoach']: lineup[i]['coach'].append(playerInfo) elif player['startingLineUp']: lineup[i]['starting'].append(playerInfo) else: lineup[i]['bench'].append(playerInfo) def getLineupPlayerString(teamString): def listPlayers(position): fullLineupString = "" for startingPlayer in lineup[teamString][position]: lineupString = LiveMatch.styleSheetLineups( "PlayerTemplate") lineupString = lineupString.replace( "$number$", str(startingPlayer['number'])) lineupString = lineupString.replace( "$player$", startingPlayer['name']) if startingPlayer['gk']: lineupString = lineupString.replace( "$gkTemplate$", LiveMatch.styleSheetLineups("GKTemplate")) else: lineupString = lineupString.replace("$gkTemplate$", "") if startingPlayer['captain']: lineupString = lineupString.replace( "$captainTemplate$", LiveMatch.styleSheetLineups("CaptainTemplate")) else: lineupString = lineupString.replace( "$captainTemplate$", "") fullLineupString += lineupString return fullLineupString lineupString = LiveMatch.styleSheetLineups("Layout") lineupString = lineupString.replace("$playerTemplate$", listPlayers('starting')) coachString = LiveMatch.styleSheetLineups("CoachTemplate") coachString = coachString.replace( "$coach$", lineup[teamString]['coach'][0]['name']) lineupString = lineupString.replace("$coachTemplate$", coachString) return lineupString homeString = getLineupPlayerString('home') awayString = getLineupPlayerString('away') title = LiveMatch.styleSheetLineups("cardTitle") description = LiveMatch.styleSheetLineups("cardDescription") description = description.replace("$home_team$", match.home_team.clear_name) description = description.replace("$away_team$", match.away_team.clear_name) embObj = Embed(title=title, description=description) teamTitle = LiveMatch.styleSheetLineups("TeamTitle") homeTeamTitle = teamTitle.replace("$team$", match.home_team.clear_name) awayTeamTitle = teamTitle.replace("$team$", match.away_team.clear_name) embObj.add_field(name=homeTeamTitle, value=homeString) embObj.add_field(name=awayTeamTitle, value=awayString) try: await client.send_message(channel, embed=embObj) except: await asyncio.sleep(10) for i in client.get_all_channels(): if channel.name == i.name: await client.send_message(channel, embed=embObj)
async def matchScheduler(): """ Parses all available matches for a given matchday and starts them if necessary. """ logger.debug("Waiting for client ready.") await client.wait_until_ready() logger.debug("Client ready, starting loop") Scheduler.matchDayObject = getNextMatchDayObjects( ) #add competition adds new competitions to this. Scheduler.runMatchSchedulerFlag = True try: while True: if Scheduler.stopFlag: logger.info("Stop Flag raised! Match ends") break Scheduler.matchSchedulerRunning.set() Scheduler.maintananceSynchronizer.wait() time = datetime.utcnow() try: for competition, matchObject in Scheduler.matchDayObject.items( ): for md, data in matchObject.items(): currentTime = datetime.utcnow().replace(tzinfo=UTC) if data['start'] < currentTime and data[ 'end'] > currentTime: logger.debug( "Current time within boundaries, starting match" ) await asyncCreateChannel( data['channel_name'], role=data['role'], category=data['category']) logger.debug("Looking into upcoming matches: ") for i in data['upcomingMatches']: logger.debug( f"Match {i}, flag runningStarted {i.runningStarted}" ) if not i.runningStarted: logger.debug(f"Starting task {i}") client.loop.create_task( i.runMatchThread()) logger.debug( f"Waiting for {i} to have started") i.lock.wait() logger.debug(f"{i} started") data['currentMatches'].append(i) data['upcomingMatches'].remove(i) if datetime.utcnow() - time > timedelta( seconds=30): client.get_all_channels() logger.warning( "Didnt sleep for 30 seconds, sleeping now" ) await asyncio.sleep(10) time = datetime.utcnow() await asyncio.sleep(5) logger.debug("Looking into currentMatches") for i in data['currentMatches']: logger.debug( f"Match {i}, flags runningStarted {i.runningStarted}, passed {i.passed}" ) if not i.runningStarted: logger.debug( f"Starting i, as it is not started but in currentMatches" ) client.loop.create_task( i.runMatchThread()) logger.debug( f"Watiting for {i} to have started" ) i.lock.wait() if i.passed: logger.debug( f"{i} has passed, moving it to passedMatches" ) data['passedMatches'].append(i) data['currentMatches'].remove(i) if datetime.utcnow() - time > timedelta( seconds=30): client.get_all_channels() logger.warning( "Didnt sleep for 30 seconds, sleeping now" ) await asyncio.sleep(10) time = datetime.utcnow() await asyncio.sleep(5) await asyncio.sleep(10) except RuntimeError: logger.error("Dict size changed!") await asyncio.sleep(5) continue Scheduler.matchSchedulerRunning.clear() await asyncio.sleep(60) finally: Scheduler.runMatchSchedulerFlag = False