2020-06-30 22:03:05 +00:00

621 lines
22 KiB
Python

###
# Copyright (c) 2018, cottongin
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
# 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 #] [<team>]
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: