Temporarily remove some plugins.

This commit is contained in:
oddluck 2019-12-05 06:16:32 +00:00
parent acafa13069
commit 82e5a2df7d
21 changed files with 0 additions and 1794 deletions

View File

@ -1,15 +0,0 @@
Forked from: https://github.com/ProgVal/Supybot-plugins/tree/master/Markovgen
Additions:
plugins.Markovgen.ignorePattern (set regex patterns for the bot to ignore)
plugins.Markovgen.stripPattern (set regex patterns for the bot to strip)
plugins.Markovgen.stripURL (determine if the bot will strip URLs)
plugins.Markovgen.ignoreNicks (list of nicks to ignore)
plugins.Markovgen.ignoreCommands (ignore commands sent to the bot)
plugins.Markovgen.stripFormatting (strip bold, underline, and color from messages)

View File

@ -1,73 +0,0 @@
###
# Copyright (c) 2014, Valentin Lorentz
# 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.
###
"""
Add a description of the plugin (to be presented to the user inside the wizard)
here. This should describe *what* the plugin does.
"""
import supybot
import supybot.world as 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__ = getattr(supybot.authors, 'progval',
supybot.Author('Valentin Lorentz', 'ProgVal', 'progval@gmail.com'))
__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
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:

View File

@ -1,88 +0,0 @@
###
# Copyright (c) 2014, Valentin Lorentz
# 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 supybot.conf as conf
import supybot.registry as registry
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('Markovgen')
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('Markovgen', True)
Markovgen = conf.registerPlugin('Markovgen')
# This is where your configuration variables (if any) should go. For example:
# conf.registerGlobalValue(Markovgen, 'someConfigVariableName',
# registry.Boolean(False, _("""Help for someConfigVariableName.""")))
conf.registerChannelValue(Markovgen, 'enable',
registry.Boolean(False, _("""Determines whether the plugin is enabled
on a channel. This defaults to False to avoid useless resources
consumption.""")))
conf.registerChannelValue(Markovgen, 'probability',
registry.Probability(0, _("""Determine the probability the bot has to
reply to a message.""")))
conf.registerChannelValue(Markovgen, 'stripRelayedNick',
registry.Boolean(True, _("""Determines whether the bot will strip
strings like <XXX> at the beginning of messages.""")))
conf.registerChannelValue(Markovgen, 'ignorePattern',
registry.Regexp("", _("""Mesages matching this pattern will be ignored.""")))
conf.registerChannelValue(Markovgen, 'stripPattern',
registry.Regexp("", _("""Text matching this pattern will be stripped.""")))
conf.registerChannelValue(Markovgen, 'stripURL',
registry.Boolean(True, _("""Determines whether the bot will strip
URLs from messages.""")))
conf.registerChannelValue(Markovgen, 'ignoreNicks',
registry.SpaceSeparatedListOfStrings([], _("""A list of nicks to be ignored by the bot""")))
conf.registerChannelValue(Markovgen, 'stripFormatting',
registry.Boolean(True, _("""Determines whether the bot will strip
bold, underline, and colors from messages.""")))
conf.registerChannelValue(Markovgen, 'ignoreCommands',
registry.Boolean(True, _("""Determines whether the bot will ignore commands.""")))
conf.registerGroup(Markovgen, 'onNick')
conf.registerChannelValue(Markovgen.onNick, 'probability',
registry.Probability(0, _("""Determine the probability the bot has to
reply to a message containing its nick.""")))
conf.registerChannelValue(Markovgen.onNick, 'replaceNick',
registry.Boolean(True, _("""Determine whether the bot will replace its
nick by the original author's when replying to a message containing
its nick.""")))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -1 +0,0 @@
# Stub so local is a module, used for third-party modules

View File

@ -1,255 +0,0 @@
# coding: utf8
###
# Copyright (c) 2014, Valentin Lorentz
# 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 os
import re
import sys
import glob
import random
import functools
import supybot.conf as conf
import supybot.world as world
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.log as log
try:
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization('Markovgen')
except ImportError:
# Placeholder that allows to run the plugin on a bot
# without the i18n module
_ = lambda x:x
try:
import markovgen
except ImportError:
raise callbacks.Error('Cannot load markovgen library. Make sure you '
'installed it (%s -m pip install markovgen).'
% sys.executable)
from imp import reload as r
r(markovgen)
MATCH_MESSAGE_STRIPNICK = re.compile('^(<[^ ]+> )?(?P<message>.*)$')
CHANNELLOGER_REGEXP_BASE = re.compile('^[^ ]* (<[^ ]+> )?(?P<message>.*)$')
CHANNELLOGER_REGEXP_STRIPNICK = re.compile('^[^ ]* (<[^ ]+> )?(<[^ ]+> )?(?P<message>.*)$')
def get_channelloger_extracter(stripRelayedNick):
@markovgen.mixed_encoding_extracting
def channelloger_extracter(x):
regexp = CHANNELLOGER_REGEXP_STRIPNICK if stripRelayedNick else \
CHANNELLOGER_REGEXP_BASE
m = regexp.match(x)
if m:
return m.group('message')
return channelloger_extracter
def get_extracter(name):
regexp = re.compile(markovgen.REGEXPS[name])
@markovgen.mixed_encoding_extracting
def extracter(x):
msg = regexp.match(x)
if msg:
return msg.group('message')
return extracter
def rec_list_files(path):
return (os.path.join(dp, f)
for dp, dn, filenames in os.walk(path)
for f in filenames)
class Markovgen(callbacks.Plugin):
"""Add the help for "@plugin help Markovgen" here
This should describe *how* to use this plugin."""
threaded = True
def __init__(self, irc):
super(Markovgen, self).__init__(irc)
self._markovs = {}
def _load_from_channellogger(self, irc, channel, m):
cb = irc.getCallback('ChannelLogger')
if not cb:
return
extracter = get_channelloger_extracter(
self.registryValue('stripRelayedNick', channel))
for irc in world.ircs:
for filename in glob.glob(cb.getLogDir(irc, channel) + '/*.log'):
with open(filename, 'rb') as fd:
m.feed_from_file(fd, extracter)
def _load_from_data(self, irc, channel, m):
base_path = os.path.join(conf.supybot.directories.data(), 'Markovgen', channel)
if not os.path.isdir(base_path):
return
for extracter_name in os.listdir(base_path):
extracter = get_extracter(extracter_name)
path = os.path.join(base_path, extracter_name)
path = glob.escape(path)
filenames = rec_list_files(path)
for filename in filenames:
with open(filename, 'rb') as fd:
m.feed_from_file(fd, extracter)
def _get_markov(self, irc, channel):
if channel not in self._markovs:
m = markovgen.Markov()
self._markovs[channel] = m
self._load_from_channellogger(irc, channel, m)
self._load_from_data(irc, channel, m)
else:
m = self._markovs[channel]
return m
def doPrivmsg(self, irc, msg):
(channel, message) = msg.args
if not irc.isChannel(channel):
return
if not self.registryValue('enable', channel):
return
if self.registryValue('ignoreCommands', channel) and callbacks.addressed(irc.nick, msg):
return
match = False
ignore = self.registryValue("ignorePattern", channel)
strip = self.registryValue("stripPattern", channel)
if ignore:
match = re.search(ignore, message)
if match:
log.debug("Markovgen: %s matches ignorePattern for %s" % (message, channel))
return
if msg.nick.lower() in self.registryValue('ignoreNicks', channel):
log.debug("Markovgen: nick %s in ignoreNicks for %s" % (msg.nick, channel))
return
m = self._get_markov(irc, channel)
if self.registryValue('stripFormatting', channel):
message = ircutils.stripFormatting(message)
if strip:
match = re.findall(strip, message)
if match:
for x in match:
message = message.replace(x, '')
message = re.sub('\s+', ' ', message)
log.debug("Markovgen: %s matches stripPattern for %s. New message text: %s" % (x, channel, message))
if self.registryValue('stripURL', channel):
new_message = re.sub(r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', '', message)
new_message = re.sub('\s+', ' ', new_message)
if new_message != message:
log.debug("Markovgen: url(s) stripped from message for %s. New message text: %s" % (channel, new_message))
message = new_message
if self.registryValue('stripRelayedNick', channel):
message = MATCH_MESSAGE_STRIPNICK.match(message).group('message')
m.feed(message)
tokenized_message = (w.strip(':;,.!?')
for w in message.lower().split())
if irc.nick.lower() in tokenized_message:
if random.random() < self.registryValue('onNick.probability', channel):
def replace_nick(s):
return re.sub(re.escape(irc.nick), msg.nick, s, re.IGNORECASE)
self._answer(irc, message, m, False,
postprocessing=replace_nick)
else:
if random.random() < self.registryValue('probability', channel):
self._answer(irc, message, m, False)
@wrap(['channel', optional('text')])
def gen(self, irc, msg, args, channel, message):
"""[<channel>] [<seed>]
Generates a random message based on the logs of a channel
and a seed"""
if not self.registryValue('enable', channel):
irc.error(_('Markovgen is disabled for this channel.'),
Raise=True)
m = self._get_markov(irc, channel)
if message:
m.feed(message)
self._answer(irc, message or '', m, True)
def _answer(self, irc, message, m, allow_duplicate,
postprocessing=lambda x: x):
words = message.split()
if len(words) == 0:
possibilities = list(m.available_seeds())
elif len(words) == 1:
word = words[0]
seeds = list(m.available_seeds())
possibilities = [x for x in seeds if word in x]
else:
message_tuples = set(zip(words, words[1:]))
if not message_tuples:
return
seeds = list(m.available_seeds())
possibilities = [x for x in seeds if x in message_tuples]
seed = list(random.choice(possibilities))
backward_seed = list(reversed(seed))
forward = m.generate_markov_text(seed=seed, backward=False)
backward = m.generate_markov_text(seed=backward_seed,
backward=True)
try:
answer = '%s %s' % (backward, forward.split(' ', 2)[2])
except IndexError:
answer = backward
if allow_duplicate or message != answer:
irc.reply(postprocessing(answer), prefixNick=False)
@wrap(['channel'])
def doge(self, irc, msg, args, channel):
"""takes no arguments
Generates a doge."""
if not self.registryValue('enable', channel):
irc.error(_('Markovgen is disabled for this channel.'),
Raise=True)
r = re.compile('^[a-zA-Zéèàù]{5,}$')
def pred(x):
if not r.match(x):
return None
else:
return x
m = self._get_markov(irc, channel)
words = m.words
words = filter(bool, map(pred, words))
words = [x.strip(',?;.:/!') for x in m.words if pred(x)]
w2 = random.choice(words)
w1 = random.choice(['such', 'many', 'very'])
irc.reply('%s %s' % (w1, w2))
Class = Markovgen
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -1 +0,0 @@
markovgen

View File

@ -1,37 +0,0 @@
###
# Copyright (c) 2014, Valentin Lorentz
# 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.
###
from supybot.test import *
class MarkovgenTestCase(PluginTestCase):
plugins = ('Markovgen',)
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -1 +0,0 @@
Fetches scores and game information from NFL.com

View File

@ -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:

View File

@ -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:

View File

@ -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 <pre/reg/post> | --inp | --date <YYYY-MM-DD|MM/DD/YYYY>) (<team abbreviation>)
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):
"""<player name>
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:

View File

@ -1,3 +0,0 @@
pendulum
requests
roman_numerals

View File

@ -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:

View File

@ -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.

View File

@ -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
```
<cottongin> @schedule --country GB --tz GMT
<bot> 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)
<cottongin> @tvshow the orville
<bot> 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
```

View File

@ -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:

View File

@ -1,112 +0,0 @@
###
# Copyright (c) 2019, James Lu <james@overdrivenetworks.com>
# 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)

View File

@ -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:

View File

@ -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 <country> | --detail|--d] <TV Show Title>
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 <IANA timezone> | --network <network> | --country <country> | --date <YYYY-MM-DD>]
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 <country> | --tz <IANA timezone> | --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:

View File

@ -1,2 +0,0 @@
requests
pendulum

View File

@ -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: