mirror of
https://github.com/oddluck/limnoria-plugins.git
synced 2025-04-25 20:41:21 -05:00
508 lines
20 KiB
Python
508 lines
20 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 *
|
|
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 = self.abbrv.json()
|
|
|
|
|
|
@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 = data.json()
|
|
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 = games.json()
|
|
|
|
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:
|