diff --git a/NFLScores/README.md b/NFLScores/README.md deleted file mode 100644 index 8f490d3..0000000 --- a/NFLScores/README.md +++ /dev/null @@ -1 +0,0 @@ -Fetches scores and game information from NFL.com diff --git a/NFLScores/__init__.py b/NFLScores/__init__.py deleted file mode 100644 index 68cffcb..0000000 --- a/NFLScores/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -""" -NFLScores: Fetches scores and game information from NFL.com -""" - -import sys -import supybot -from supybot import world - -# Use this for the version of this plugin. You may wish to put a CVS keyword -# in here if you're keeping the plugin in CVS or some similar system. -__version__ = "" - -# XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('cottongin', 'cottongin', - 'cottongin@cottongin.club') -__maintainer__ = getattr(supybot.authors, 'oddluck', - supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) - -# This is a dictionary mapping supybot.Author instances to lists of -# contributions. -__contributors__ = {} - -# This is a url where the most recent plugin package can be downloaded. -__url__ = 'https://github.com/oddluck/limnoria-plugins/' - -from . import config -from . import plugin -if sys.version_info >= (3, 4): - from importlib import reload -else: - from imp import reload -# In case we're being reloaded. -reload(config) -reload(plugin) -# Add more reloads here if you add third-party modules and want them to be -# reloaded when this plugin is reloaded. Don't forget to import them as well! - -if world.testing: - from . import test - -Class = plugin.Class -configure = config.configure - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/NFLScores/config.py b/NFLScores/config.py deleted file mode 100644 index efd909e..0000000 --- a/NFLScores/config.py +++ /dev/null @@ -1,33 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -from supybot import conf, registry -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('NFLScores') -except: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x: x - - -def configure(advanced): - # This will be called by supybot to configure this module. advanced is - # a bool that specifies whether the user identified themself as an advanced - # user or not. You should effect your configuration by manipulating the - # registry as appropriate. - from supybot.questions import expect, anything, something, yn - conf.registerPlugin('NFLScores', True) - - -NFLScores = conf.registerPlugin('NFLScores') -# This is where your configuration variables (if any) should go. For example: -# conf.registerGlobalValue(NFLScores, 'someConfigVariableName', -# registry.Boolean(False, _("""Help for someConfigVariableName."""))) - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/NFLScores/plugin.py b/NFLScores/plugin.py deleted file mode 100644 index bb04b38..0000000 --- a/NFLScores/plugin.py +++ /dev/null @@ -1,518 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -import pendulum -import requests, json -from roman_numerals import convert_to_numeral - -from supybot import utils, plugins, ircutils, callbacks -from supybot.commands import * -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('NFLScores') -except ImportError: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x: x - -BASE_URL = "https://feeds.nfl.com/feeds-rs{}.json" - -def getValidDateFmt(irc, msg, args, state): - date = args[0] - valid = ['yesterday', 'tomorrow'] - check = None - try: - if date.lower() in valid: - if date.lower() == 'yesterday': - check = pendulum.yesterday().format('MM/DD/YYYY') - else: - check = pendulum.tomorrow().format('MM/DD/YYYY') - else: - check = pendulum.parse(date, strict=False).format('MM/DD/YYYY') - except: - pass - if not check: - state.errorInvalid(_('date format'), str(date)) - else: - state.args.append(check) - del args[0] -addConverter('validDate', getValidDateFmt) - -class NFLScores(callbacks.Plugin): - """Fetches scores and game information from NFL.com""" - threaded = True - - def __init__(self, irc): - super().__init__(irc) - self.GOOG = irc.getCallback('Google') - - @wrap([getopts({"week": "positiveInt", - "season": "positiveInt", - "type": ("literal", ("hof","pre", "reg", "post", "pro", "sb")), - "inp": "", - "pro": "", - "date": "validDate"}), optional("somethingWithoutSpaces")]) - def nfl(self, irc, msg, args, options, team): - """(--week <#> | --type
| --inp | --date) ( ) - Fetches scores. - """ - options = dict(options) - inp = options.get('inp') - seasonType = options.get('type') - date = options.get('date') or pendulum.now().format('MM/DD/YYYY') - week = options.get('week') - season = options.get('season') - gameIds = [] - network = None - - date = dict(zip(['month', 'day', 'year'], date.split('/'))) - if 1 <= int(date['month']) <= 6: - url = BASE_URL.format(f"/schedules/{int(date['year'])-1}") - else: - url = BASE_URL.format(f"/schedules/{date['year']}") - data = requests.get(url).json() - - if not week: - url = BASE_URL.format('/currentWeek') - week = requests.get(url).json()['week'] - if not season: - url = BASE_URL.format('/currentWeek') - season = requests.get(url).json()['seasonId'] - if not seasonType: - url = BASE_URL.format('/currentWeek') - tmp = requests.get(url).json()['seasonType'] - if tmp == "PRO": - if not options.get('pro'): - tmp = "POST" - week = 22 if not week or week == 4 or week == 21 else week - seasonType = tmp - - if options.get('date'): - found = False - for game in data['gameSchedules']: - if game['gameDate'] == f"{'/'.join(date.values())}": - if team: - teams = [game['visitorTeamAbbr'], game['homeTeamAbbr']] - if team.upper() in teams: - gameIds.append(game['gameId']) - network = ' :: {}'.format(game['networkChannel']) - week = str(game['week']) - season = game['season'] - seasonType = game['seasonType'] - found = True - break - else: - gameIds.append(game['gameId']) - network = ' :: {}'.format(game['networkChannel']) - week = str(game['week']) - season = game['season'] - seasonType = game['seasonType'] - found = True - if not found: - date = '/'.join(date.values()) - irc.reply('Error: No games found on {}'.format( - f"{date if not team else date + ' for ' + team.upper()}")) - return - - if seasonType.upper() in ['POST']: - if int(week) <= 5: week += 17 - url = BASE_URL.format('/scores/{}/{}/{}'.format( - season, seasonType.upper(), week - )) - try: - scores = requests.get(url).json()['gameScores'] - except json.decoder.JSONDecodeError: - irc.error('invalid input', Raise=True) - except Exception as e: - print(e) - irc.error('something went wrong parsing data', Raise=True) - - new_scores = [] - if gameIds or team: - for game in scores: - if gameIds and not team: - if game['gameSchedule']['gameId'] in gameIds: - new_scores.append(game) - if team: - teams = [game['gameSchedule']['visitorTeamAbbr'], game['gameSchedule']['homeTeamAbbr']] - if team.upper() in teams: - new_scores.append(game) - break - else: - new_scores = scores - - week = int(week) - if week >= 18: week -= 17 - if seasonType in ['PRE']: - if week != 0: - prefix = self._bold("Preseason Week {}:".format(week)) - else: - prefix = self._bold("Hall of Fame Game:") - elif seasonType in ['REG']: - prefix = self._bold("Week {}:".format(week)) - else: - prefix = self._bold("{}:") - if new_scores[0]['gameSchedule']['gameType'] == "WC": - prefix = prefix.format("Wildcard Weekend") - elif new_scores[0]['gameSchedule']['gameType'] == "DIV": - prefix = prefix.format("Divisional Round") - elif new_scores[0]['gameSchedule']['gameType'] == "CON": - prefix = prefix.format("Conference Finals") - elif new_scores[0]['gameSchedule']['gameType'] == "PRO": - prefix = prefix.format("Pro Bowl") - elif new_scores[0]['gameSchedule']['gameType'] == "SB": - prefix = prefix.format(f"Super Bowl {convert_to_numeral(int(season)-1965)}") - else: - prefix = prefix.format("Week {}".format(week)) - - games = [] - print(new_scores) - for game in new_scores: - if len(new_scores) == 1: - long_ = True - home = "homeDisplayName" - away = "visitorDisplayName" - time_format = "dddd, M/D, h:mm A zz" - sep = " :: " - if not network: - for s in data['gameSchedules']: - if game['gameSchedule']['gameId'] == s['gameId']: - network = ' :: {}'.format(s['networkChannel']) - break - else: - long_ = False - home = "homeTeamAbbr" - away = "visitorTeamAbbr" - time_format = "ddd h:mm A zz" - sep = " " - network = '' - score = game['score'] - info = game['gameSchedule'] - time = f"{pendulum.from_timestamp(info['isoTime']/1000).in_tz('US/Eastern').format(time_format)}" - if not score: - string = (f"{info[away]} @ {info[home]}{sep}" - f"{time}{network}") - if info['gameType'] == "SB": - string += f" :: {info['site']['siteFullname']}{' ({})'.format(info['site']['roofType'].title()) if info['site']['roofType'] else ''}, {info['site']['siteCity']}, {info['site']['siteState']}" - games.append(string) - else: - if "FINAL" in score['phase']: - phase = score['phase'] - if "OVERTIME" in phase: - phase = "Final/Overtime" if long_ else "F/OT" - else: - phase = "Final" if long_ else "F" - phase = self._color(phase, 'red') - h_score = score['homeTeamScore']['pointTotal'] - v_score = score['visitorTeamScore']['pointTotal'] - if v_score > h_score: - v_str = self._bold(f"{info[away]} {v_score}") - h_str = f"{info[home]} {h_score}" - elif h_score > v_score: - v_str = f"{info[away]} {v_score}" - h_str = self._bold(f"{info[home]} {h_score}") - else: - v_str = f"{info[away]} {v_score}" - h_str = f"{info[home]} {h_score}" - string = (f"{v_str} @ {h_str}{sep}{phase}") - if info['gameType'] == "SB": - string += f" :: {info['site']['siteFullname']}{' ({})'.format(info['site']['roofType'].title()) if info['site']['roofType'] else ''}, {info['site']['siteCity']}, {info['site']['siteState']}" - games.append(string) - elif "PRE" in score['phase']: - string = (f"{info[away]} @ {info[home]}{sep}" - f"{time}{network}") - if info['gameType'] == "SB": - string += f" :: {info['site']['siteFullname']}{' ({})'.format(info['site']['roofType'].title()) if info['site']['roofType'] else ''}, {info['site']['siteCity']}, {info['site']['siteState']}" - games.append(string) - elif "HALFTIME" in score['phase']: - phase = "Halftime" if long_ else "HT" - phase = self._color(phase, 'orange') - h_score = score['homeTeamScore']['pointTotal'] - v_score = score['visitorTeamScore']['pointTotal'] - if v_score > h_score: - v_str = self._bold(f"{info[away]} {v_score}") - h_str = f"{info[home]} {h_score}" - elif h_score > v_score: - v_str = f"{info[away]} {v_score}" - h_str = self._bold(f"{info[home]} {h_score}") - else: - v_str = f"{info[away]} {v_score}" - h_str = f"{info[home]} {h_score}" - string = (f"{v_str} @ {h_str}{sep}{phase}") - games.append(string) - else: - phase = score['phaseDescription'] if long_ else score['phase'] - phase = self._color(phase, 'green') - time = self._color(score['time'], 'green') - h_score = score['homeTeamScore']['pointTotal'] - v_score = score['visitorTeamScore']['pointTotal'] - if v_score > h_score: - v_str = self._bold(f"{info[away]} {v_score}") - h_str = f"{info[home]} {h_score}" - elif h_score > v_score: - v_str = f"{info[away]} {v_score}" - h_str = self._bold(f"{info[home]} {h_score}") - else: - v_str = f"{info[away]} {v_score}" - h_str = f"{info[home]} {h_score}" - string = (f"{v_str} @ {h_str}{sep}{time} {phase}") - status = None - try: - pos_team = score.get('possessionTeamAbbr') - at = score['yardline'] - down = "{} and {}".format(score['down'], score['yardsToGo']) - status = " :: {}".format(down) - last_play = None - if pos_team: - status += " :: {} has the ball at {}".format(pos_team, at) - if len(new_scores) == 1: - gameId = info['gameId'] - url = BASE_URL.format('/playbyplay/{}/latest'.format(gameId)) - try: - last_play = requests.get(url).json() - last_play = last_play['plays'][-1]['playDescription'] - except: - pass - if last_play: - status += " :: {}".format(last_play) - except: - pass - if status: - string += status - games.append(string) - - irc.reply(f"{prefix} {' | '.join(games)}") - - - - @wrap([ "text"]) - def nflgame(self, irc, msg, args, player): - """ - Fetches current/previous game stats for given player. - """ - player_id = None - try: - try: - burl = "site:nfl.com {} stats".format(player.lower()) - search = self.GOOG.search('{0}'.format(burl),'#reddit-nfl',{'smallsearch': True}) - search = self.GOOG.decode(search) - if search: - url = search[0]['url'] - print(url) - player_id = url.split('/')[-2] - player_id = player_id.replace('-', ' ') - print(player_id) - - except: - self.log.exception("ERROR :: NFLScores :: failed to get link for {0}".format(burl)) - pass - except Exception as e: - self.log.info("ERROR :: NFLScores :: {0}".format(e)) - pass - - if not player_id: - irc.reply('ERROR: Could not find a player id for {}'.format(player)) - return - - endpoint = '/playerGameStats/{}'.format(player_id) - data = requests.get(BASE_URL.format(endpoint)).json() - game_stats = data['playerGameStats'] - player_info = data['teamPlayer'] - - if not game_stats: - irc.reply("I couln't find any current or previous game stats for {}".format(player_info['displayName'])) - return - - recent = game_stats[-1] - - name = (f"{self._bold(self._color(player_info['displayName'], 'red'))}" - f" (#{player_info['jerseyNumber']} {player_info['position']})" - f" [{player_info['yearsOfExperience']}yrs exp]" - f" :: {player_info['teamFullName']}") - - game_time = recent['gameSchedule']['isoTime'] / 1000 - info = (f"{recent['gameSchedule']['visitorTeamAbbr']} " - f"{recent['score']['visitorTeamScore']['pointTotal']} @ " - f"{recent['gameSchedule']['homeTeamAbbr']} " - f"{recent['score']['homeTeamScore']['pointTotal']} - " - f"{pendulum.from_timestamp(game_time).in_tz('US/Eastern').format('ddd MM/DD h:mm A zz')}") - - if player_info['positionGroup'] == 'QB': - #passing, rush, fumble - tmp = recent['playerPassingStat'] - stats = [(f"{self._ul('Passing')}: {self._bold('Comp')} {tmp.get('passingCompletions', '-')} " - f"{self._bold('Att')} {tmp.get('passingAttempts', '-')} " - f"{self._bold('Pct')} {tmp.get('passingCompletionPercentage', '-')} " - f"{self._bold('Yds')} {tmp.get('passingYards', '-')} " - f"{self._bold('Avg')} {tmp.get('passingYardsPerAttempts', '-')} " - f"{self._bold('TD')} {tmp.get('passingTouchdowns', '-')} " - f"{self._bold('Int')} {tmp.get('passingInterceptions', '-')} " - f"{self._bold('Sck')} {tmp.get('passingSacked', '-')} " - f"{self._bold('SckY')} {tmp.get('passingSackedYardsLost', '-')} " - f"{self._bold('Rate')} {tmp.get('passingRating', '-')}")] - tmp = recent['playerRushingStat'] - line2 = [] - line2.append( - (f"{self._ul('Rushing')}: {self._bold('Att')} {tmp.get('rushingAttempts', '-')} " - f"{self._bold('Yds')} {tmp.get('rushingYards', '-')} " - f"{self._bold('Avg')} {tmp.get('rushingYardsPerAttempt', '-')} " - f"{self._bold('TD')} {tmp.get('rushingTouchdowns', '-')}")) - tmp = recent['playerFumbleStat'] - line2.append( - (f"{self._ul('Fumbles')}: {self._bold('Fum')} {tmp.get('fumbles', '-')} " - f"{self._bold('Lst')} {tmp.get('fumblesLost', '-')}")) - stats.append(' :: '.join(line2)) - elif player_info['positionGroup'] == 'RB': - #rush, recev, fumble - line1 = [] - line2 = [] - stats = [] - tmp = recent['playerRushingStat'] - line1 = [(f"{self._ul('Rushing')}: {self._bold('Att')} {tmp.get('rushingAttempts', '-')} " - f"{self._bold('Att')} {tmp.get('rushingYards', '-')} " - f"{self._bold('Avg')} {tmp.get('rushingYardsPerAttempt', '-')} " - f"{self._bold('Lng')} {tmp.get('rushingLong', '-')} " - f"{self._bold('TD')} {tmp.get('rushingTouchdowns', '-')}")] if tmp else [] - tmp = recent['playerReceivingStat'] - if tmp: line1.append( - (f"{self._ul('Receiving')}: {self._bold('Rec')} {tmp.get('receivingReceptions', '-')} " - f"{self._bold('Yds')} {tmp.get('receivingYards', '-')} " - f"{self._bold('Avg')} {tmp.get('receivingYardsPerReception', '-')} " - f"{self._bold('Lng')} {tmp.get('receivingLong', '-')} " - f"{self._bold('TD')} {tmp.get('receivingTouchdowns', '-')}")) - tmp = recent['playerFumbleStat'] - line2.append( - (f"{self._ul('Fumbles')}: {self._bold('Fum')} {tmp.get('fumbles', '-')} " - f"{self._bold('Lst')} {tmp.get('fumblesLost', '-')}")) - if len(line1) == 1 and len(line2) == 1: - stats.append('{} :: {}'.format(line1[0], line2[0])) - else: - if line1: stats.append(' :: '.join(line1)) - if line2: stats.append(' :: '.join(line2)) - elif player_info['positionGroup'] in ['WR', 'TE']: - #recv, rush, fumble - line1 = [] - line2 = [] - stats = [] - tmp = recent['playerReceivingStat'] - line1 = [(f"{self._ul('Receiving')}: {self._bold('Rec')} {tmp.get('receivingReceptions', '-')} " - f"{self._bold('Yds')} {tmp.get('receivingYards', '-')} " - f"{self._bold('Avg')} {tmp.get('receivingYardsPerReception', '-')} " - f"{self._bold('Lng')} {tmp.get('receivingLong', '-')} " - f"{self._bold('TD')} {tmp.get('receivingTouchdowns', '-')}")] if tmp else [] - tmp = recent['playerRushingStat'] - if tmp: line1.append( - (f"{self._ul('Rushing')}: {self._bold('Att')} {tmp.get('rushingAttempts', '-')} " - f"{self._bold('Att')} {tmp.get('rushingYards', '-')} " - f"{self._bold('Avg')} {tmp.get('rushingYardsPerAttempt', '-')} " - f"{self._bold('Lng')} {tmp.get('rushingLong', '-')} " - f"{self._bold('TD')} {tmp.get('rushingTouchdowns', '-')}")) - tmp = recent['playerFumbleStat'] - line2.append( - (f"{self._ul('Fumbles')}: {self._bold('Fum')} {tmp.get('fumbles', '-')} " - f"{self._bold('Lst')} {tmp.get('fumblesLost', '-')}")) - if len(line1) == 1 and len(line2) == 1: - stats.append('{} :: {}'.format(line1[0], line2[0])) - else: - if line1: stats.append(' :: '.join(line1)) - if line2: stats.append(' :: '.join(line2)) - elif player_info['position'] == 'K': - #overall fg, pats, koffs - line1 = [] - line2 = [] - stats = [] - tmp = recent['playerKickingStat'] - line1 = [(f"{self._ul('Field Goals')}: {self._bold('FG Att')} {tmp.get('kickingFgAttempts', '-')} " - f"{self._bold('FGM')} {tmp.get('kickingFgMade', '-')} " - f"{self._bold('Pct')} {tmp.get('kickingFgPercentage', '-')} " - f"{self._bold('Lng')} {tmp.get('kickingFgLong', '-')} " - f"{self._bold('Blk')} {tmp.get('kickingFgBlocked', '-')}")] if tmp else [] - if tmp: line1.append( - (f"{self._ul('PATs')}: {self._bold('XP Att')} {tmp.get('kickingXkAttempts', '-')} " - f"{self._bold('XPM')} {tmp.get('kickingXkMade', '-')} " - f"{self._bold('Pct')} {tmp.get('kickingXkPercentage', '-')} " - f"{self._bold('Blk')} {tmp.get('kickingXkBlocked', '-')} ")) - line2.append( - (f"{self._ul('Kickoffs')}: {self._bold('KO')} {tmp.get('kickoffs', '-')} " - f"{self._bold('Avg')} {tmp.get('kickoffAverageYards', '-')} " - f"{self._bold('TB')} {tmp.get('kickoffTouchbacks', '-')} " - f"{self._bold('Ret')} {tmp.get('kickoffReturns', '-')} " - f"{self._bold('Avg')} {tmp.get('kickoffReturnsAverageYards', '- ')}")) - if len(line1) == 1 and len(line2) == 1: - stats.append('{} :: {}'.format(line1[0], line2[0])) - else: - if line1: stats.append(' :: '.join(line1)) - if line2: stats.append(' :: '.join(line2)) - elif player_info['positionGroup'] in ['LB', 'DB', 'DL']: - #defense - line1 = [] - line2 = [] - stats = [] - tmp = recent['playerDefensiveStat'] - line1 = [(f"{self._ul('Tackles')}: {self._bold('Comb')} {tmp.get('defensiveCombineTackles', '-')} " - f"{self._bold('Total')} {tmp.get('defensiveTotalTackles', '-')} " - f"{self._bold('Ast')} {tmp.get('defensiveAssist', '-')} " - f"{self._bold('Sck')} {tmp.get('defensiveSacks', '-')} " - f"{self._bold('SFTY')} {tmp.get('defensiveSafeties', '-')}")] if tmp else [] - if tmp: line1.append( - (f"{self._ul('Interceptions')}: {self._bold('PDef')} {tmp.get('defensivePassesDefensed', '-')} " - f"{self._bold('Int')} {tmp.get('defensiveInterceptions', '-')} " - f"{self._bold('Yds')} {tmp.get('defensiveInterceptionYards', '-')} " - f"{self._bold('Avg')} {tmp.get('defensiveInterceptionsAvgyds', '-')} " - f"{self._bold('Lng')} {tmp.get('defensiveInterceptionsLong', '-')} " - f"{self._bold('TDs')} {tmp.get('defensiveInterceptionsTds', '-')} ")) - line2.append( - (f"{self._ul('Fumbles')}: {self._bold('FF')} {tmp.get('kickoffs', '-')} ")) - if len(line1) == 1 and len(line2) == 1: - stats.append('{} :: {}'.format(line1[0], line2[0])) - else: - if line1: stats.append(' :: '.join(line1)) - if line2: stats.append(' :: '.join(line2)) - elif player_info['position'] == 'P': - line1 = [] - stats = [] - tmp = recent['playerPuntingStat'] - line1 = [(f"{self._ul('Punting')}: {self._bold('Punts')} {tmp.get('puntingPunts', '-')} " - f"{self._bold('Yds')} {tmp.get('puntingYards', '-')} " - f"{self._bold('Net Yds')} {tmp.get('puntingNetYardage', '-')} " - f"{self._bold('Lng')} {tmp.get('puntingLong', '-')} " - f"{self._bold('Avg')} {tmp.get('puntingAverageYards', '-')} " - f"{self._bold('Net Avg')} {tmp.get('puntingNetAverage', '-')} " - f"{self._bold('Blk')} {tmp.get('puntingBlocked', '-')} " - f"{self._bold('OOB')} {tmp.get('puntingOutOfBounds', '-')} " - f"{self._bold('Dn')} {tmp.get('puntingDowned', '-')} " - f"{self._bold('In 20')} {tmp.get('puntingPuntsInside20', '-')} " - f"{self._bold('TB')} {tmp.get('puntingTouchbacks', '-')} " - f"{self._bold('FC')} {tmp.get('puntingPuntsFairCaught', '-')} " - f"{self._bold('Ret')} {tmp.get('puntingNumberReturned', '-')} " - f"{self._bold('RetY')} {tmp.get('puntingReturnYards', '-')} " - f"{self._bold('TD')} {tmp.get('puntingReturnTouchdowns', '-')}")] if tmp else [] - stats.append(' :: '.join(line1)) - else: - stats = ["No stats found"] - - strings = [f"{name} :: {info}"] - - for string in strings: - irc.reply(string) - for stat in stats: - irc.reply(stat) - - def _color(self, string, color): - return ircutils.mircColor(string, color) - - def _bold(self, string): - return ircutils.bold(string) - - def _ul(self, string): - return ircutils.underline(string) - - -Class = NFLScores - - -# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/NFLScores/requirements.txt b/NFLScores/requirements.txt deleted file mode 100644 index bbcdbef..0000000 --- a/NFLScores/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pendulum -requests -roman_numerals diff --git a/NFLScores/test.py b/NFLScores/test.py deleted file mode 100644 index 8a42ab7..0000000 --- a/NFLScores/test.py +++ /dev/null @@ -1,15 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -from supybot.test import * - - -class NFLScoresTestCase(PluginTestCase): - plugins = ('NFLScores',) - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/TVMaze/LICENSE b/TVMaze/LICENSE deleted file mode 100644 index 9464006..0000000 --- a/TVMaze/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 cottongin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/TVMaze/README.md b/TVMaze/README.md deleted file mode 100644 index 01a3aee..0000000 --- a/TVMaze/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# TVMaze - -## Limnoria plugin to fetch TV show information and schedules from tvmaze.com API - -### Instructions - -#### This plugin requires Python 3 and Limnoria - -1. Install with PluginDownloader @install oddluck TVMaze - -2. Install requirements for the plugin via pip -``` -pip install -r requirements.txt -``` - -3. Load the plugin on your bot -``` -@load TVMaze -``` - -### Example Usage -``` - @schedule --country GB --tz GMT - Today's Shows: Cuckoo: Ivy Arrives [S05E01] (10:00 AM GMT), Cuckoo: Ivy Nanny [S05E02] (10:00 AM GMT), Cuckoo: Weed Farm [S05E03] (10:00 AM GMT), Cuckoo: Macbeth [S05E04] (10:00 AM GMT), Cuckoo: Divorce Party [S05E05] (10:00 AM GMT), Cuckoo: Two Engagements and a Funeral [S05E06] (10:00 AM GMT), Cuckoo: Election [S05E07] (10:00 AM GMT), The Dumping Ground: Rage [S08E01] (5:00 PM GMT), (1 more message) - - @tvshow the orville - The Orville (2017) | Next: Home [S02E03] (2019-01-10 in 6 days) | Prev: Primal Urges [S02E02] (2019-01-03) | Running | English | 60m | FOX | Comedy/Adventure/Science-Fiction | http://www.tvmaze.com/shows/20263/the-orville | https://imdb.com/title/tt5691552/ | http://www.fox.com/the-orville -``` -Use @help tvshow|schedule to see details on each command. - ---- - -You can use @settvmazeoptions to save common command options to make using commands easier: -``` -@settvmazeoptions --country GB -@settvmazeoptions --tz US/Central -@settvmazeoptions --country AU --tz US/Pacific -``` -This stores settings per nick, you can clear them via --clear: -``` -@settvmazeoptions --clear -``` diff --git a/TVMaze/__init__.py b/TVMaze/__init__.py deleted file mode 100644 index ec5fd2e..0000000 --- a/TVMaze/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -""" -TVMaze: Limnoria plugin to fetch TV show information and schedules from tvmaze.com API -""" - -import sys -import supybot -from supybot import world - -# Use this for the version of this plugin. You may wish to put a CVS keyword -# in here if you're keeping the plugin in CVS or some similar system. -__version__ = "" - -# XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('cottongin', 'cottongin', - 'cottongin@cottongin.club') -__maintainer__ = getattr(supybot.authors, 'oddluck', - supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) - -# This is a dictionary mapping supybot.Author instances to lists of -# contributions. -__contributors__ = {} - -# This is a url where the most recent plugin package can be downloaded. -__url__ = 'https://github.com/oddluck/limnoria-plugins/' - -from . import config -from . import plugin -if sys.version_info >= (3, 4): - from importlib import reload -else: - from imp import reload -# In case we're being reloaded. -reload(config) -reload(plugin) -# Add more reloads here if you add third-party modules and want them to be -# reloaded when this plugin is reloaded. Don't forget to import them as well! -from . import accountsdb -reload(accountsdb) - -if world.testing: - from . import test - -Class = plugin.Class -configure = config.configure - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/TVMaze/accountsdb.py b/TVMaze/accountsdb.py deleted file mode 100644 index 371c098..0000000 --- a/TVMaze/accountsdb.py +++ /dev/null @@ -1,112 +0,0 @@ -### -# Copyright (c) 2019, James Lu -# 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. -### - -""" -accountsdb: Provides storage for user-specific data via Supybot accounts, ident@host, or nicks. -""" - -import pickle - -from supybot import ircdb, log, conf, registry - -MODES = ["accounts", "identhost", "nicks"] -DEFAULT_MODE = MODES[2] - -class _AccountsDBAddressConfig(registry.OnlySomeStrings): - validStrings = MODES - -CONFIG_OPTION_NAME = "DBAddressingMode" -CONFIG_OPTION = _AccountsDBAddressConfig(DEFAULT_MODE, """Sets the DB addressing mode. - This requires reloading the plugin to take effect. Valid settings include accounts - (save users by Supybot accounts and ident@host if not registered), identhost - (save users by ident@host), and nicks (save users by nicks). - When changing addressing modes, existing keys will be left intact, but migration between - addressing modes is NOT supported.""") - -class AccountsDB(): - """ - Abstraction to map users to third-party account names. - - This stores users by their bot account first, falling back to their - ident@host if they are not logged in. - """ - - def __init__(self, plugin_name, filename, addressing_mode=DEFAULT_MODE): - """ - Loads the existing database, creating a new one in memory if none - exists. - """ - self.db = {} - self._plugin_name = plugin_name - self.filename = conf.supybot.directories.data.dirize(filename) - - self.addressing_mode = addressing_mode - - try: - with open(self.filename, 'rb') as f: - self.db = pickle.load(f) - except Exception as e: - log.debug('%s: Unable to load database, creating ' - 'a new one: %s', self._plugin_name, e) - - def flush(self): - """Exports the database to a file.""" - try: - with open(self.filename, 'wb') as f: - pickle.dump(self.db, f, 2) - except Exception as e: - log.warning('%s: Unable to write database: %s', self._plugin_name, e) - - def _get_key(self, prefix): - nick, identhost = prefix.split('!', 1) - - if self.addressing_mode == "accounts": - try: # Try to first look up the caller as a bot account. - userobj = ircdb.users.getUser(prefix) - return userobj.name - except KeyError: # If that fails, store them by nick@host. - return identhost - elif self.addressing_mode == "identhost": - return identhost - elif self.addressing_mode == "nicks": - return nick - else: - raise ValueError("Unknown addressing mode %r" % self.addressing_mode) - - def set(self, prefix, newId): - """Sets a user ID given the user's prefix.""" - user = self._get_key(prefix) - self.db[user] = newId - - def get(self, prefix): - """Sets a user ID given the user's prefix.""" - user = self._get_key(prefix) - - # Automatically returns None if entry does not exist - return self.db.get(user) diff --git a/TVMaze/config.py b/TVMaze/config.py deleted file mode 100644 index 191b1ea..0000000 --- a/TVMaze/config.py +++ /dev/null @@ -1,39 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -from supybot import conf, registry -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('TVMaze') -except: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x: x - -from . import accountsdb - - -def configure(advanced): - # This will be called by supybot to configure this module. advanced is - # a bool that specifies whether the user identified themself as an advanced - # user or not. You should effect your configuration by manipulating the - # registry as appropriate. - from supybot.questions import expect, anything, something, yn - conf.registerPlugin('TVMaze', True) - - -TVMaze = conf.registerPlugin('TVMaze') -# This is where your configuration variables (if any) should go. For example: -# conf.registerGlobalValue(TVMaze, 'someConfigVariableName', -# registry.Boolean(False, _("""Help for someConfigVariableName."""))) -conf.registerGlobalValue(TVMaze, accountsdb.CONFIG_OPTION_NAME, accountsdb.CONFIG_OPTION) - -conf.registerChannelValue(TVMaze, 'showEpisodeTitle', - registry.Boolean(True, _("""Determines whether the episode title will be displayed in the schedule output."""))) - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/TVMaze/plugin.py b/TVMaze/plugin.py deleted file mode 100644 index 2c4c7e9..0000000 --- a/TVMaze/plugin.py +++ /dev/null @@ -1,417 +0,0 @@ -# TVMaze v0.0.1 -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# See LICENSE.txt -### - -import requests -import pendulum -import urllib.parse - -from . import accountsdb - -from supybot import utils, plugins, ircutils, callbacks, world -from supybot.commands import * -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('TVMaze') -except ImportError: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x: x - - -class TVMaze(callbacks.Plugin): - """Limnoria plugin to fetch TV show information and schedules from tvmaze.com API""" - threaded = True - - def __init__(self, irc): - super().__init__(irc) - self.db = accountsdb.AccountsDB("TVMaze", 'TVMaze.db', self.registryValue(accountsdb.CONFIG_OPTION_NAME)) - world.flushers.append(self.db.flush) - - def die(self): - world.flushers.remove(self.db.flush) - self.db.flush() - super().die() - - #--------------------# - # Formatting helpers # - #--------------------# - - def _bold(self, string): - return ircutils.bold(string) - - def _ul(self, string): - return ircutils.underline(string) - - def _color(self, string, color): - return ircutils.mircColor(string, color) - - #--------------------# - # Internal functions # - #--------------------# - - def _get(self, mode, country='US', date=None, query=None, id_=None): - """wrapper for requests tailored to TVMaze API""" - - base_url = 'http://api.tvmaze.com' - - if mode == 'search': - if not query: - return - query = urllib.parse.quote_plus(query) - base_url += '/search/shows?q={}'.format(query) - try: - data = requests.get(base_url).json() - except: - data = None - elif mode == 'schedule': - if not date: - date = pendulum.now().format('YYYY-MM-DD') - base_url += '/schedule?country={}&date={}'.format(country, date) - try: - data = requests.get(base_url).json() - except: - data = None - elif mode == 'shows': - if not id_: - return - base_url += '/shows/{}?embed[]=previousepisode&embed[]=nextepisode'.format(id_) - try: - data = requests.get(base_url).json() - except: - data = None - else: - data = None - - return data - - #------------------# - # Public functions # - #------------------# - - @wrap([getopts({'country': 'somethingWithoutSpaces', - 'detail': '', - 'd': '', - 'search': '', - 'record': 'positiveInt'}), 'text']) - def tvshow(self, irc, msg, args, options, query): - """[--country | --detail|--d] - Fetches information about provided TV Show from TVMaze.com. - Optionally include --country to find shows with the same name from another country. - Optionally include --detail (or --d) to show additional details. - Ex: tvshow --country GB the office - """ - # prefer manually passed options, then saved user options - # this merges the two possible dictionaries, prefering manually passed - # options if they already exist - user_options = self.db.get(msg.prefix) or dict() - options = {**user_options, **dict(options)} - - # filter out any manually passed options - country = options.get('country') - show_detail = options.get('d') or options.get('detail') - - # search for the queried TV show - show_search = self._get('search', query=query) - if not show_search: - irc.reply('Nothing found for your query: {}'.format(query)) - return - - # if the user is using --search let's just output the results - if options.get('search'): - results = [] - for idx, show in enumerate(show_search): - # try to pin the year of release to the show name - if show['show'].get('premiered'): - premiered = show['show']['premiered'][:4] - else: - premiered = "TBD" - name = "{} ({})".format(show['show']['name'], premiered) - results.append("{}. {}".format( - idx+1, - self._bold(name) - )) - irc.reply("Results: {}".format(" | ".join(results))) - return - - # pull a specific show from --search results - if options.get('record'): - if options.get('record') > len(show_search): - irc.reply('Invalid record!') - return - result_to_show = options.get('record') - 1 - else: - result_to_show = 0 - - # if we have a country, look for that first instead of the first result - if country: - show_id = None - for show in show_search: - if show['show'].get('network'): - if show['show']['network']['country']['code'].upper() == country.upper(): - show_id = show['show']['id'] - break - # if we can't find it, default to the first result anyway - if not show_id: - show_id = show_search[result_to_show]['show']['id'] - else: - show_id = show_search[result_to_show]['show']['id'] - - # fetch the show information - show_info = self._get('shows', id_=show_id) - - # grab the included URLs and generate an imdb one - urls = [] - urls.append(show_info['url']) - urls.append('https://imdb.com/title/{}/'.format(show_info['externals']['imdb'])) - if show_info['officialSite']: - urls.append(show_info['officialSite']) - - # grab the genres - genres = '{}: {}'.format( - self._bold('Genre(s)'), - '/'.join(show_info['genres']) - ) - - # show name - name = self._bold(show_info['name']) - - # show language - lang = "{}: {}".format( - self._bold('Language'), - show_info['language'] - ) - - # show status - status = show_info['status'] - if status == 'Ended': - status = self._color(status, 'red') - elif status == 'Running': - status = self._color(status, 'green') - - # show duration - runtime = "{}: {}m".format( - self._bold('Duration'), - show_info['runtime'] - ) - - # show premiere date, stripped to year and added to name - if show_info.get('premiered'): - premiered = show_info['premiered'][:4] - else: - premiered = "TBD" - name = "{} ({})".format(name, premiered) - - # is the show on television or web (netflix, amazon, etc) - if show_info.get('network'): - # we use this if --detail/--d is asked for - network = show_info['network']['name'] - schedule = "{}: {} at {} on {}".format( - self._bold('Schedule'), - ', '.join(show_info['schedule']['days']), - show_info['schedule']['time'], - network - ) - elif show_info.get('webChannel'): - # we use this if --detail/--d is asked for - network = show_info['webChannel']['name'] - schedule = "Watch on {}".format( - network - ) - - # try to get previous and/or next episode details - if show_info['_embedded']: - # previous episode - if show_info['_embedded'].get('previousepisode'): - try: - ep = "S{:02d}E{:02d}".format( - show_info['_embedded']['previousepisode']['season'], - show_info['_embedded']['previousepisode']['number'] - ) - except: - ep = "?" - ep = self._color(ep, 'orange') - previous = " | {}: {ep_name} [{ep}] ({ep_date})".format( - self._bold('Prev'), - ep_name=show_info['_embedded']['previousepisode']['name'], - ep=ep, - ep_date=show_info['_embedded']['previousepisode']['airdate'] - ) - else: - previous = "" - # next episode - if show_info['_embedded'].get('nextepisode'): - try: - ep = "S{:02d}E{:02d}".format( - show_info['_embedded']['nextepisode']['season'], - show_info['_embedded']['nextepisode']['number'] - ) - except: - ep = "?" - ep = self._color(ep, 'orange') - next_ = " | {}: {ep_name} [{ep}] ({ep_date} {when})".format( - self._bold('Next'), - ep_name=show_info['_embedded']['nextepisode']['name'], - ep=ep, - ep_date=show_info['_embedded']['nextepisode']['airdate'], - when=pendulum.parse(show_info['_embedded']['nextepisode']['airstamp']).diff_for_humans() - ) - else: - next_ = "" - - # now finally put it all together and reply - reply = "{0} ({3}){1}{2} | {4}".format( - name, - next_, - previous, - status, - ' | '.join(urls) - ) - irc.reply(reply) - - # add a second line for details if requested - if show_detail: - reply = "{} | {} | {} | {}".format( - schedule, - runtime, - lang, - genres - ) - irc.reply(reply) - - - @wrap([getopts({'all': '', - 'tz': 'somethingWithoutSpaces', - 'network': 'somethingWithoutSpaces', - 'country': 'somethingWithoutSpaces', - 'date': 'somethingWithoutSpaces', - 'showEpisodeTitle': '', - 'debug': ''})]) - def schedule(self, irc, msg, args, options): - """[--all | --tz | --network | --country | --date ] - Fetches upcoming TV schedule from TVMaze.com. - """ - # prefer manually passed options, then saved user options - # this merges the two possible dictionaries, prefering manually passed - # options if they already exist - user_options = self.db.get(msg.prefix) or dict() - options = {**user_options, **dict(options)} - - # parse manually passed options, if any - tz = options.get('tz') or 'US/Eastern' - country = options.get('country') - date = options.get('date') - # TO-DO: add a --filter option(s) - if country: - country = country.upper() - # if user isn't asking for a specific timezone, - # default to some sane ones given the country - if not options.get('tz'): - if country == 'GB': - tz = 'GMT' - elif country == 'AU': - tz = 'Australia/Sydney' - else: - tz = 'US/Eastern' - else: - country = 'US' - # we don't need to default tz here because it's already set - - # parse date input - if date: - date = pendulum.parse(date, strict=False).format('YYYY-MM-DD') - else: - date = pendulum.now(tz).format('YYYY-MM-DD') - - # fetch the schedule - schedule_data = self._get('schedule', country=country, date=date) - - if not schedule_data: - irc.reply('Something went wrong fetching TVMaze schedule data.') - return - - # parse schedule - shows = [] - for show in schedule_data: - tmp = "{show_name} [{ep}] ({show_time})" - # by default we show the episode title, there is a channel config option to disable this - # and users can override with --showEpisodeTitle flag - show_title = options.get('showEpisodeTitle') or self.registryValue('showEpisodeTitle', msg.args[0]) - if show_title: - name = "{1}: {0}".format(show['name'], show['show']['name']) - else: - name = "{0}".format(show['show']['name']) - # try to build some season/episode information - try: - ep_id = "S{:02d}E{:02d}".format(show['season'], show['number']) - except: - ep_id = '?' - time = pendulum.parse(show['airstamp']).in_tz(tz) - # put it all together - tmp = tmp.format(show_name=self._bold(name), - ep=self._color(ep_id, 'orange'), - show_time=time.format('h:mm A zz')) - # depending on any options, append to list - if options.get('all'): - shows.append(tmp) - elif options.get('network'): - if show['show'].get('network'): - if show['show']['network']['name'].lower() == options.get('network').lower(): - shows.append(tmp) - else: - # for now, defaults to only upcoming 'Scripted' shows - if show['show']['type'] == 'Scripted' and pendulum.now(tz) <= time: - shows.append(tmp) - - # set a default message if no shows were found - if not shows: - shows.append('No upcoming shows found') - - # finally reply - reply = "{}: {}".format(self._ul("Today's Shows"), ", ".join(shows)) - if options.get('debug'): - #irc.reply(repr(reply)) - print(repr(reply)) - irc.reply(reply) - - - @wrap([getopts({'country': 'somethingWithoutSpaces', - 'tz': 'somethingWithoutSpaces', - 'showEpisodeTitle': 'boolean', - 'detail': 'boolean', - 'd': 'boolean', - 'clear': ''})]) - def settvmazeoptions(self, irc, msg, args, options): - """--country | --tz | --showEpisodeTitle (True/False) | --detail/--d (True/False) - Allows user to set options for easier use of TVMaze commands. - Use --clear to reset all options. - """ - if not options: - irc.reply('You must give me some options!') - return - - # prefer manually passed options, then saved user options - # this merges the two possible dictionaries, prefering manually passed - # options if they already exist - user_options = self.db.get(msg.prefix) or dict() - options = {**user_options, **dict(options)} - - if options.get('clear'): - self.db.set(msg.prefix, {}) - irc.replySuccess() - return - - self.db.set(msg.prefix, options) - irc.replySuccess() - - - - -Class = TVMaze - - -# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/TVMaze/requirements.txt b/TVMaze/requirements.txt deleted file mode 100644 index b47cb15..0000000 --- a/TVMaze/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests -pendulum \ No newline at end of file diff --git a/TVMaze/test.py b/TVMaze/test.py deleted file mode 100644 index 4ae931e..0000000 --- a/TVMaze/test.py +++ /dev/null @@ -1,15 +0,0 @@ -### -# Copyright (c) 2019, cottongin -# All rights reserved. -# -# -### - -from supybot.test import * - - -class TVMazeTestCase(PluginTestCase): - plugins = ('TVMaze',) - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: