### # Copyright (c) 2018, cottongin # Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import requests import pendulum import json import html from bs4 import BeautifulSoup import re import os from supybot import utils, plugins, ircutils, callbacks, schedule from supybot.commands import * import json try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization("CFB") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x class CFB(callbacks.Plugin): """Fetches CFB scores""" threaded = True def __init__(self, irc): self.__parent = super(CFB, self) self.__parent.__init__(irc) self.SCOREBOARD = ( "http://site.api.espn.com/apis/site/v2/sports/" "football/college-football/scoreboard" ) self.FUZZY_DAYS = [ "yesterday", "tonight", "today", "tomorrow", "sun", "mon", "tue", "wed", "thu", "fri", "sat", ] self._current_week = 0 self._cfb_byes = {} with open( "{0}/abbrv.json".format(os.path.dirname(os.path.abspath(__file__))), "r" ) as json_file: self.abbrv = json.load(json_file) if not self.abbrv: self.abbrv = requests.get( "https://raw.githubusercontent.com/diagonalfish/FootballBotX2/master/abbrv.json" ) self.abbrv = json.loads(self.abbrv.content) @wrap def cfbbyes(self, irc, msg, args): """Gets teams on bye week for current week""" url = "https://247sports.com/Article/Schedule-of-bye-weeks-for-college-footballs-top-2018-contenders-120880121/" headers = { "User-Agent": ( "Mozilla/5.0 (X11; CrOS x86_64 11151.4.0) AppleWebKit/537.36 (KHTML," " like Gecko) Chrome/71.0.3578.8 Safari/537.36" ) } if not self._cfb_byes: data = requests.get(url, headers=headers) soup = BeautifulSoup(data.content) pattern = re.compile(r"Week") section = soup.find("section", class_="article-body") ps = section.find_all("p") output = {} for p in ps: if p.text.startswith("Week"): week = p.text.split(":")[0] week = week.split("(")[0].strip() byes = p.text.split(":")[1].strip() if byes: output[week] = byes self._cfb_byes = output try: irc.reply(self._cfb_byes[self._current_week]) return except: irc.reply("No teams on bye this week") return @wrap([getopts({"week": "positiveInt"}), optional("text")]) def cfbrankings(self, irc, msg, args, optlist, filter_team=None): """Fetches CFB Rankings""" optlist = dict(optlist) week = optlist.get("week") url = "http://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings" if week: url += "?weeks={}".format(week) try: data = requests.get(url) data = json.loads(data.content) except: irc.reply("Error fetching rankings") return if not week: week = " " + data["latestWeek"]["displayValue"] else: if week > data["latestWeek"]["number"]: irc.reply("Sorry, I cannot predict the future") return week = " Week {}".format(week) rankings = data["rankings"][0] self._current_week = week.strip() title = data["rankings"][0]["name"] output = [] for team in rankings["ranks"]: tmp = "#{0}{3} {1} {2}" if "+" in team["trend"]: trend = self._green(team["trend"]) elif "-" in team["trend"] and team["trend"] != "-": trend = self._red(team["trend"]) else: trend = team["trend"] tmp = tmp.format( team["current"], self._bold(team["team"]["abbreviation"]), team["recordSummary"], "({})".format(trend) if trend != "-" else "", ) if filter_team: if filter_team.upper() == team["team"]["abbreviation"]: tmp += " :: {} points".format(team["points"]) output.append(tmp) break else: output.append(tmp) if filter_team and len(output) == 1: irc.reply("{}: {}".format(title + week, output[0])) elif filter_team and not output: irc.reply("No results found for {}".format(filter_team)) else: irc.reply( "{}: {}".format(title + week, " | ".join(team for team in output[:11])) ) irc.reply("{}".format(" | ".join(team for team in output[11:]))) @wrap([getopts({"week": "positiveInt", "conf": "positiveInt"}), optional("text")]) def cfb(self, irc, msg, args, optlist, team=None): """[--conf #] [--week #] [] Fetches CFB Scores. Defaults to current week and AP Top 25 teams. Use --conf # (ESPN league #) to fetch a specific conference. Use --week # to look up a specific week. """ optlist = dict(optlist) week = optlist.get("week") conf = optlist.get("conf") games = None team = self._parseInput(team) if (conf == 80 or conf == 81 or conf == 35) and not team: irc.reply("ERROR: You must provide a team") return if week or conf or team == "today": games = self._fetchGames(team, conf, week) if not games: games = self._fetchGames(team, conf) if not games: irc.reply("No games found") return games = self._parseGames(games, team) games = self._sortGames(games) reply_string = self._replyAsString(team, games) reply = " | ".join(reply_string) irc.reply(reply, prefixNick=False) def _parseInput(self, team): if not team: return None else: # tbd return team def _fetchGames(self, team, conf="80", week=None): team = "all" if not team else team.upper() date = pendulum.now("US/Pacific") conf = "" if not conf else conf url = self.SCOREBOARD + "?groups={}".format(conf) if team != "all" and team != "TODAY" and team != "INP": url += "&limit=300" if week: url += "&week={}".format(week) games = requests.get(url) games = json.loads(games.content) games = games["events"] if team != "all" and team != "TODAY" and team != "INP": ngames = [] # check abbreviation first for game in games: if ( team == game["competitions"][0]["competitors"][0]["team"]["abbreviation"] or team == game["competitions"][0]["competitors"][1]["team"]["abbreviation"] ): ngames.append(game) if not ngames: if team == "HAWAII": team = "HAWAI'I" for game in games: if ( team == html.unescape( game["competitions"][0]["competitors"][0]["team"][ "location" ] ).upper() or team == html.unescape( game["competitions"][0]["competitors"][1]["team"][ "location" ] ).upper() ): ngames.append(game) return ngames return games def _parseGames(self, games, team=None): new_games = [] if team: if ( team.lower() != "all" and team.lower() != "today" and team.lower() != "inp" ): for idx, game in enumerate(games): if ( team.upper() == game["competitions"][0]["competitors"][0]["team"][ "abbreviation" ] or team.upper() == game["competitions"][0]["competitors"][1]["team"][ "abbreviation" ] ): games = [games.pop(idx)] break for game in games: date = pendulum.parse(game["date"]).in_tz("US/Pacific") today = pendulum.today("US/Pacific") new_game = {} new_game["id"] = game["id"] new_game["time"] = ( pendulum.parse(game["date"]).in_tz("US/Eastern").format("ddd h:mm A zz") ) new_game["date"] = ( pendulum.parse(game["date"]) .in_tz("US/Eastern") .format("dddd, MMMM Do, h:mm A zz") ) new_game["home_full"] = game["competitions"][0]["competitors"][0]["team"][ "location" ] new_game["home"] = game["competitions"][0]["competitors"][0]["team"][ "abbreviation" ] new_game["home_id"] = game["competitions"][0]["competitors"][0]["team"][ "id" ] new_game["away_full"] = game["competitions"][0]["competitors"][1]["team"][ "location" ] new_game["away"] = game["competitions"][0]["competitors"][1]["team"][ "abbreviation" ] new_game["away_id"] = game["competitions"][0]["competitors"][1]["team"][ "id" ] new_game["status"] = game["status"]["type"]["state"] new_game["shortDetail"] = game["status"]["type"]["shortDetail"] new_game["final"] = game["status"]["type"]["completed"] new_game["in_progress"] = False # Rankings new_game["home_team_rank"] = game["competitions"][0]["competitors"][0][ "curatedRank" ].get("current") new_game["away_team_rank"] = game["competitions"][0]["competitors"][1][ "curatedRank" ].get("current") # Odds try: new_game["odds"] = "{} (O/U: {:.0f})".format( game["competitions"][0]["odds"][0]["details"], game["competitions"][0]["odds"][0]["overUnder"], ) except Exception as e: new_game["odds"] = "" print(e) if new_game["status"] == "in" and not new_game["final"]: new_game["in_progress"] = True try: new_game["last_play"] = game["competitions"][0]["situation"][ "lastPlay" ]["text"] except: new_game["last_play"] = "" new_game["pos"] = game["competitions"][0]["situation"].get("possession") new_game["rz"] = game["competitions"][0]["situation"].get("isRedZone") new_game["desc"] = game["competitions"][0]["situation"].get( "downDistanceText" ) new_game["clock"] = game["status"]["type"]["shortDetail"] try: new_game["clock"] = ( new_game["clock"].split("-")[0].strip() + " " + self._green(new_game["clock"].split("-")[1].strip()) ) except: new_game["clock"] = new_game["clock"] if "Delayed" in new_game["clock"]: new_game["clock"] = self._orange("DLY") if "Halftime" in new_game["clock"]: new_game["clock"] = "HT" new_game["HT"] = True else: new_game["HT"] = False elif new_game["status"] == "post": new_game["in_progress"] = False new_game["broadcasts"] = "{}".format( ", ".join( item["media"]["shortName"] for item in game["competitions"][0]["geoBroadcasts"] ) ) new_game["h_score"] = int( game["competitions"][0]["competitors"][0]["score"] ) new_game["a_score"] = int( game["competitions"][0]["competitors"][1]["score"] ) if team == "today": if date.day == today.day: new_games.append(new_game) elif team == "inp": if new_game["in_progress"]: new_games.append(new_game) else: new_games.append(new_game) return new_games def _sortGames(self, games): sorted_games = sorted(games, key=lambda k: k["final"]) return sorted_games def _replyAsString(self, team, games): reply_strings = [] tmp_strings = [] half_point = len(games) // 2 def _parseScores(away, ascr, home, hscr, arnk, hrnk): print(ascr, arnk, hscr, hrnk) if ascr > hscr: astr = "{} {}".format(self._bold(away), self._bold(ascr)) hstr = "{} {}".format(home, hscr) if arnk > hrnk: upset = True else: upset = False elif ascr < hscr: hstr = "{} {}".format(self._bold(home), self._bold(hscr)) astr = "{} {}".format(away, ascr) if hrnk > arnk: upset = True else: upset = False else: astr = "{} {}".format(away, ascr) hstr = "{} {}".format(home, hscr) upset = False print(upset) return astr, hstr, upset if len(games) == 2: if games[0]["away"] == games[1]["away"]: for idx, game in enumerate(games): if game["shortDetail"] == "Postponed": games.pop(idx) single = True if len(games) == 1 else False for game in games: string = "" if single: away = game["away_full"] home = game["home_full"] time = game["date"] else: away = game["away"] home = game["home"] time = game["time"] if game["status"] == "pre": string = "{} @ {} - {}".format(away, home, time) if single: string += " - \x02TV:\x02 {}".format(game["broadcasts"]) if game["odds"]: string += " - \x02Odds:\x02 {}".format(game["odds"]) tmp_strings.append(string) elif game["in_progress"]: if not game["HT"]: if game["pos"] == game["away_id"]: if game["rz"]: away = self._red("<>{}".format(away)) else: away = "<>" + away if game["pos"] == game["home_id"]: if game["rz"]: home = self._red("<>{}".format(home)) else: home = "<>" + home away_str, home_str, upset = _parseScores( away, game["a_score"], home, game["h_score"], game["away_team_rank"], game["home_team_rank"], ) if game["clock"] == "HT": if single: game["clock"] = self._orange("Halftime") else: game["clock"] = self._orange(game["clock"]) if single: game["clock"] = "({})".format(game["clock"]) string = "{} @ {} {}".format(away_str, home_str, game["clock"]) if upset: if single: string += " :: {}".format(self._orange("UPSET")) string = self._ul(string) if not game["HT"] and single: if game["desc"]: desc = " :: {}".format(game["desc"]) else: desc = "" if game["last_play"]: string += "{} :: \x02Last Play:\x02 {}".format( desc, game["last_play"] ) if game["broadcasts"]: string += " :: \x02TV:\x02 {}".format(game["broadcasts"]) if game["odds"]: string += " :: \x02Odds:\x02 {}".format(game["odds"]) tmp_strings.append(string) elif game["status"] == "post": if game["shortDetail"] != "Final": endCode = game["shortDetail"] if "Postponed" in endCode: if not single: endCode = self._red("PPD") else: endCode = self._red(endCode) elif "Canceled" in endCode: if not single: endCode = self._red("C") else: endCode = self._red(endCode) elif "OT" in endCode: if single: endCode = self._red(endCode) else: endCode = self._red("F/OT") elif "OT" in game["shortDetail"]: if single: endCode = self._red(endCode) else: endCode = self._red("F/OT") else: if single: endCode = self._red("Final") else: endCode = self._red("F") away_str, home_str, upset = _parseScores( away, game["a_score"], home, game["h_score"], game["away_team_rank"], game["home_team_rank"], ) string = "{} @ {} {}".format(away_str, home_str, endCode) if upset and not single: string = self._ul(string) if single: if upset: string += " :: {}".format(self._orange("UPSET")) tmp_strings.append(string) print(len(tmp_strings), half_point) if len(tmp_strings) > 1 and half_point >= 6: reply_strings.append( " | ".join(string for string in tmp_strings[:half_point]) ) reply_strings.append( " | ".join(string for string in tmp_strings[half_point:]) ) else: reply_strings.append(" | ".join(string for string in tmp_strings)) return reply_strings def _red(self, string): """Returns a red string.""" return ircutils.mircColor(string, "red") def _yellow(self, string): """Returns a yellow string.""" return ircutils.mircColor(string, "yellow") def _orange(self, string): return ircutils.mircColor(string, "orange") def _green(self, string): """Returns a green string.""" return ircutils.mircColor(string, "green") def _blue(self, string): """Returns a blue string.""" return ircutils.mircColor(string, "blue") def _bold(self, string): """Returns a bold string.""" return ircutils.bold(string) def _ul(self, string): """Returns an underline string.""" return ircutils.underline(string) def _bu(self, string): """Returns a bold/underline string.""" return ircutils.bold(ircutils.underline(string)) Class = CFB # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: