Browse Source

modernize module system: 5.0.0

dave/xdcc
dave 5 years ago
parent
commit
d55e111767
  1. 4
      README.md
  2. 131
      bin/pubsubbot
  3. 3
      bin/pyircbot
  4. 1
      docs/index.rst
  5. 26
      docs/module_guide/_module_guide.rst
  6. 31
      docs/module_guide/_pubsub_mode.rst
  7. 2
      examples/config.json
  8. 6
      examples/data/config/Inventory.json
  9. 2
      examples/run-example.sh
  10. 65
      pyircbot/common.py
  11. 3
      pyircbot/irccore.py
  12. 12
      pyircbot/modulebase.py
  13. 1
      pyircbot/modules/AttributeStorage.py
  14. 1
      pyircbot/modules/AttributeStorageLite.py
  15. 36
      pyircbot/modules/BitcoinPrice.py
  16. 15
      pyircbot/modules/Calc.py
  17. 1
      pyircbot/modules/CryptoWalletRPC.py
  18. 1
      pyircbot/modules/DogeRPC.py
  19. 194
      pyircbot/modules/DuckHunt.py
  20. 70
      pyircbot/modules/Inventory.py
  21. 22
      pyircbot/modules/LMGTFY.py
  22. 18
      pyircbot/modules/LinkTitler.py
  23. 15
      pyircbot/modules/ModInfo.py
  24. 1
      pyircbot/modules/MySQL.py
  25. 208
      pyircbot/modules/NFLLive.py
  26. 33
      pyircbot/modules/NickUser.py
  27. 29
      pyircbot/modules/PingResponder.py
  28. 8
      pyircbot/modules/PubSubClient.py
  29. 33
      pyircbot/modules/RandQuote.py
  30. 210
      pyircbot/modules/Remind.py
  31. 4
      pyircbot/modules/SMS.py
  32. 1
      pyircbot/modules/SQLite.py
  33. 6
      pyircbot/modules/ServerPassword.py
  34. 24
      pyircbot/modules/Services.py
  35. 62
      pyircbot/modules/Tell.py
  36. 12
      pyircbot/modules/Triggered.py
  37. 34
      pyircbot/modules/UnoPlay.py
  38. 38
      pyircbot/modules/Urban.py
  39. 134
      pyircbot/modules/Weather.py
  40. 42
      pyircbot/modules/Youtube.py
  41. 338
      pyircbot/pyircbot.py
  42. 8
      run-example.sh

4
README.md

@ -6,8 +6,8 @@ Quick start
-----------
* Install: `python3 setup.py install`
* Configure: `cd examples ; vim config.json data/config/Services.json`
* Run: `pyircbot -c config.json`
* Configure: `vim examples/config.json examples/data/config/Services.json`
* Run: `pyircbot -c examples/config.json`
Running in docker
-----------------

131
bin/pubsubbot

@ -0,0 +1,131 @@
#!/usr/bin/env python3
import sys
import os
import logging
from contextlib import closing
from argparse import ArgumentParser
from json import loads, load
from msgbus.client import MsgbusSubClient
import pyircbot
import traceback
from pyircbot.pyircbot import PrimitiveBot
from pyircbot.irccore import IRCEvent, UserPrefix
from json import dumps
# IRCEvent = namedtuple("IRCEvent", "command args prefix trailing")
# UserPrefix = namedtuple("UserPrefix", "nick username hostname")
# ServerPrefix = namedtuple("ServerPrefix", "hostname")
class PyIRCBotSub(PrimitiveBot):
def __init__(self, name, host, port, config):
super().__init__(config)
self.name = name
self.host = host
self.port = port
self.client = None # PubSub socket
def run(self):
# Connect to msgbus and loop through messages
with closing(MsgbusSubClient(self.host, self.port)) as self.client:
self.client.sub("pyircbot_privmsg")#TODO More of these
while True:
try:
channel, body = self.client.recv()
self.process_line(channel, body)
except Exception as e:
traceback.print_exc()
def process_line(self, channel, body):
name, rest = body.split(" ", 1)
if name != self.name:
return
# pyircbot_privmsg default
# [["#jesusandhacking"], "xMopxShell", "test", {"prefix": ["xMopxShell", "~xMopxShel", "192.95.23.134"]}]
args, sender, trailing, extras = loads(rest)
nick, username, hostname = extras["prefix"]
msg = IRCEvent("PRIVMSG",
args,
UserPrefix(nick,
username,
hostname),
trailing)
for module_name, module in self.moduleInstances.items():
for hook in module.irchooks:
validation = hook.validator(msg, self)
if validation:
hook.method(msg, validation)
# client.pub("pyircbot_send", "default privmsg {}".format(dumps([channel, "{}: pong".format(sender)])))
" Filesystem Methods "
def getConfigPath(self, moduleName):
"""Return the absolute path for a module's config file
:param moduleName: the module who's config file we want
:type moduleName: str"""
basepath = "%s/config/%s" % (self.botconfig["bot"]["datadir"], moduleName)
if os.path.exists("%s.json" % basepath):
return "%s.json" % basepath
return None
def getDataPath(self, moduleName):
"""Return the absolute path for a module's data dir
:param moduleName: the module who's data dir we want
:type moduleName: str"""
module_dir = os.path.join(self.botconfig["bot"]["datadir"], "data", moduleName)
if not os.path.exists(module_dir):
os.mkdir(module_dir)
return module_dir
" IRC methods "
def act_PRIVMSG(self, towho, message):
"""Use the `/msg` command
:param towho: the target #channel or user's name
:type towho: str
:param message: the message to send
:type message: str"""
# self.sendRaw("PRIVMSG %s :%s" % (towho, message))
self.client.pub("pyircbot_send", "{} {} {}".format(self.name, "privmsg", dumps([towho, message])))
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO,
format="%(asctime)-15s %(levelname)-8s %(filename)s:%(lineno)d %(message)s")
log = logging.getLogger('main')
# parse command line args
parser = ArgumentParser(description="Run pyircbot plugins behind a pubsub client")
parser.add_argument("-c", "--config", help="Pyircbot config file")
parser.add_argument("-s", "--server", default="localhost", help="Msgbus server address")
parser.add_argument("-p", "--port", default=7100, type=int, help="Msgbus server port")
parser.add_argument("-n", "--name", default="default", help="bot name")
parser.add_argument("--debug", action="store_true", help="")
args = parser.parse_args()
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
log.debug(args)
# Load config
with open(args.config) as f:
config = load(f)
bot = PyIRCBotSub(args.name, args.server, int(args.port), config)
# Load modules in config
sys.path.append(os.path.dirname(pyircbot.__file__) + "/modules/")
for modulename in config["modules"]:
print("Load: ", modulename)
bot.loadmodule(modulename)
bot.run()

3
bin/pyircbot

@ -3,6 +3,7 @@ import sys
import logging
import signal
from argparse import ArgumentParser
from pyircbot.common import load
from pyircbot import PyIRCBot
from json import loads
@ -30,7 +31,7 @@ if __name__ == "__main__":
log.critical("No bot config file specified (-c). Exiting.")
sys.exit(1)
botconfig = loads(sys.stdin.read()) if args.config == "-" else PyIRCBot.load(args.config)
botconfig = loads(sys.stdin.read()) if args.config == "-" else load(args.config)
log.debug(botconfig)

1
docs/index.rst

@ -13,6 +13,7 @@ Contents:
modules/_modules.rst
rpc/_rpc.rst
module_guide/_module_guide.rst
module_guide/_pubsub_mode.rst
changelog.rst
More Information

26
docs/module_guide/_module_guide.rst

@ -74,6 +74,8 @@ Prefix may also be a ``ServerPrefix`` object, if the hook is for an IRC method
that interacts with the server directly, such as PING. It would have the
properties ``event.prefix.hostname`` and ``event.prefix.str``.
There are more hook-like decorators. See @regex and @command.
Since the module described above echos messages, let's do that:
.. code-block:: python
@ -134,30 +136,6 @@ In usage:
4:40:17 PM <Beefpile> test
4:40:17 PM <derpbot420> test
New Style Module Hooks
----------------------
Instead of receiving the values of the IRC event a module is responding to in
3 separate arguments, hooks can receive them as one object. The hook system
will automatically determine which argument style to use.
The reason for this change is to eliminate some unnecessary code in modules.
Any module that looks at a user's nick or hostname may find itself doing
something like this in every hook:
.. code-block:: python
def saynick(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
self.bot.act_PRIVMSG(args[0], "Hello, %s. You are connecting from %s" % (prefixObj.nick, prefixObj.hostname))
With the new style, one line can be eliminated, as the passed ``IRCEvent``
event has the prefix already parsed:
.. code-block:: python
def saynick(self, event):
self.bot.act_PRIVMSG(event.args[0], "Hello, %s. You are connecting from %s" % (event.prefix.nick, event.prefix.hostname))
Advanced Usage
==============

31
docs/module_guide/_pubsub_mode.rst

@ -0,0 +1,31 @@
***********
PubSub Mode
***********
The PubSubClient module provides a ZeroMQ-based PubSub remote control capability to pyircbot. Well, what if you could
also run pyircbot modules as a client of the pubsub bus? Look no further!
Obviously, the :py:class:`pyircbot.modules.PubSubClient.PubSubClient` module should be enabled. Take note of the `name`
parameter (which may be left at the default "default").
The so-called footless (as opposed to headless, as the bot's head is the IRC client connection) client needs only a
small set of bootstrap modules:
- PingResponder (:py:class:`pyircbot.modules.PingResponder.PingResponder`)
- Services (:py:class:`pyircbot.modules.Services.Services`)
- PubSubClient (:py:class:`pyircbot.modules.PubSubClient.PubSubClient`)
Launch the bot and let it connect to the msgbus server.
Next, create a config identical to that of a normal pyircbot, but with any modules desired enabled. Also, read the
`--help` text for the `pubsubbot` program. Launch the `pubsubbot` process:
.. code-block:: shell
pubsubbot -c config-sub.json --name <name>
After connecting to the message bus, `pubsubbot` will be hosting any configured modules. It can be exited and restarted
any time without affecting the IRC client connection!
For a module to support running in this mode, it must use only methods described in the Module Developers Guide. Using
APIs outside of this - while not discouraged - will definitely break compatibility.

2
examples/config.json

@ -1,6 +1,6 @@
{
"bot":{
"datadir":"./data/",
"datadir":"./examples/data/",
"rpcbind":"0.0.0.0",
"rpcport":1876,
"usermodules": [ "./data/modules/" ]

6
examples/data/config/Inventory.json

@ -1,8 +1,8 @@
{
"limit": 25,
"recv_msg": "Oh, thanks, I'll keep %(adjective)s%(item)s safe",
"inv_msg": "\u0000\u0001ACTION is carrying %(itemlist)s\u0000\u0001",
"swap_msg": "\u0000\u0001ACTION takes %(adjective)s%(recv_item)s but drops %(drop_item)s\u0000\u0010",
"inv_msg": "\u0001ACTION is carrying %(itemlist)s\u0001",
"swap_msg": "\u0001ACTION takes %(adjective)s%(recv_item)s but drops %(drop_item)s\u0010",
"dupe_msg": "No thanks, I've already got %(item)s",
"adjectives": [
"some",
@ -10,4 +10,4 @@
"an",
"these"
]
}
}

2
examples/run-example.sh

@ -1,2 +0,0 @@
#!/bin/sh
pyircbot --config config.json

65
pyircbot/common.py

@ -1,5 +1,10 @@
from time import time
from math import floor
from json import load as json_load
from collections import namedtuple
ParsedCommand = namedtuple("ParsedCommand", "command args args_str message")
class burstbucket(object):
@ -39,3 +44,63 @@ class burstbucket(object):
self.bucket -= 1
return 0
return self.bucket_period - since_fill
def messageHasCommand(command, message, requireArgs=False):
"""Check if a message has a command with or without args in it
:param command: the command string to look for, like !ban. If a list is passed, the first match is returned.
:type command: str or list
:param message: the message string to look in, like "!ban Lil_Mac"
:type message: str
:param requireArgs: if true, only validate if the command use has any amount of trailing text
:type requireArgs: bool"""
if not type(command) == list:
command = [command]
for item in command:
cmd = messageHasCommandSingle(item, message, requireArgs)
if cmd:
return cmd
return False
def messageHasCommandSingle(command, message, requireArgs=False):
# Check if the message at least starts with the command
messageBeginning = message[0:len(command)]
if messageBeginning != command:
return False
# Make sure it's not a subset of a longer command (ie .meme being set off by .memes)
subsetCheck = message[len(command):len(command) + 1]
if subsetCheck != " " and subsetCheck != "":
return False
# We've got the command! Do we need args?
argsStart = len(command)
args = ""
if argsStart > 0:
args = message[argsStart + 1:].strip()
if requireArgs and args == '':
return False
# Verified! Return the set.
return ParsedCommand(command,
args.split(" ") if args else [],
args,
message)
def load(filepath):
"""Return an object from the passed filepath
:param filepath: path to a json file. filename must end with .json
:type filepath: str
:Returns: | dict
"""
if filepath.endswith(".json"):
with open(filepath, 'r') as f:
return json_load(f)
else:
raise Exception("Unknown config format")

3
pyircbot/irccore.py

@ -172,6 +172,7 @@ class IRCCore(object):
def initHooks(self):
"""Defines hooks that modules can listen for events of"""
self.hooks = [
'_ALL',
'_CONNECT',
'_DISCONNECT',
'_RECV',
@ -222,7 +223,7 @@ class IRCCore(object):
:type prefix: str
:param trailing: data payload of the command
:type trailing: str"""
for hook in self.hookcalls[command]:
for hook in self.hookcalls["_ALL"] + self.hookcalls[command]:
try:
if len(getargspec(hook).args) == 2:
hook(IRCCore.packetAsObject(command, args, prefix, trailing))

12
pyircbot/modulebase.py

@ -9,7 +9,8 @@
import re
import os
import logging
from .pyircbot import PyIRCBot
from .common import load as pload
from .common import messageHasCommand
class ModuleBase:
@ -28,9 +29,6 @@ class ModuleBase:
self.bot = bot
"""Reference to the master PyIRCBot object"""
self.hooks = []
"""Low-level protocol hooks this module has"""
self.irchooks = []
"""IRC Hooks this module has"""
@ -73,7 +71,7 @@ class ModuleBase:
if not self.config:
configPath = self.getConfigPath()
if configPath is not None:
self.config = PyIRCBot.load(configPath)
self.config = pload(configPath)
def onenable(self):
"""Called when the module is enabled"""
@ -228,7 +226,7 @@ class command(hook):
"""
if not super().validate(msg, bot):
return False
if not self.allow_private and msg.args[0] == "#":
if msg.args[0][0] != "#" and not self.allow_private:
return False
for keyword in self.keywords:
single = self._validate_one(msg, keyword)
@ -238,7 +236,7 @@ class command(hook):
def _validate_one(self, msg, keyword):
with_prefix = "{}{}".format(self.prefix, keyword)
return PyIRCBot.messageHasCommand(with_prefix, msg.trailing, requireArgs=self.require_args)
return messageHasCommand(with_prefix, msg.trailing, requireArgs=self.require_args)
class regex(hook):

1
pyircbot/modules/AttributeStorage.py

@ -13,7 +13,6 @@ from pyircbot.modulebase import ModuleBase
class AttributeStorage(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.services = ["attributes"]
self.db = None
serviceProviders = self.bot.getmodulesbyservice("mysql")

1
pyircbot/modules/AttributeStorageLite.py

@ -13,7 +13,6 @@ from pyircbot.modulebase import ModuleBase
class AttributeStorageLite(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.services = ["attributes"]
self.db = None
serviceProviders = self.bot.getmodulesbyservice("sqlite")

36
pyircbot/modules/BitcoinPrice.py

@ -6,7 +6,9 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
from decimal import Decimal
from requests import get
from time import time
@ -18,25 +20,23 @@ class BitcoinPrice(ModuleBase):
self.cache = None
self.cacheAge = 0
self.hooks = [
ModuleHook(["PRIVMSG"], self.btc)
]
def btc(self, args, prefix, trailing):
prefix = self.bot.decodePrefix(prefix)
replyTo = prefix.nick if "#" not in args[0] else args[0]
cmd = self.bot.messageHasCommand([".btc", ".bitcoin"], trailing)
if cmd:
data = self.getApi()
self.bot.act_PRIVMSG(replyTo, "%s: %s" % (
prefix.nick,
"\x02\x0307Bitcoin:\x03\x02 \x0307{buy:.0f}\x0f$ - High: \x0307{high:.0f}\x0f$ - "
"Low: \x0307{low:.0f}\x0f$ - Volume: \x0307{vol_cur:.0f}\x03฿".format(**data['ticker'])
))
@info("btc retrieve the current price of bitcoin", cmds=["btc"])
@command("btc", "bitcoin")
def btc(self, msg, cmd):
replyTo = msg.prefix.nick if "#" not in msg.args[0] else msg.args[0]
data = self.getApi()
self.bot.act_PRIVMSG(replyTo, "%s: %s" % (
msg.prefix.nick,
"\x02\x0307Bitcoin:\x03\x02 \x0307${price:.2f}\x0f - "
"24h change: \x0307${change:.2f}\x0f - "
"24h volume: \x0307${volume:.0f}M\x0f".format(price=Decimal(data["price_usd"]),
change=Decimal(data["percent_change_24h"]),
volume=Decimal(data["24h_volume_usd"]) / 10**6)
))
def getApi(self):
if self.cache is None or time() - self.cacheAge > self.config["cache"]:
self.cache = get("https://btc-e.com/api/2/btc_usd/ticker").json()
self.cache = get("https://api.coinmarketcap.com/v1/ticker/bitcoin/").json()[0]
self.cacheAge = time()
return self.cache

15
pyircbot/modules/Calc.py

@ -1,5 +1,5 @@
from pyircbot.modulebase import ModuleBase, ModuleHook, MissingDependancyException, regex, command
from pyircbot.modulebase import ModuleBase, MissingDependancyException, regex, command
from pyircbot.modules.ModInfo import info
import datetime
import time
@ -93,12 +93,13 @@ class Calc(ModuleBase):
if word and changeit:
# Add a new calc or delete
if self.config["allowDelete"] and not value:
result = self.deleteCalc(channel, word)
if result:
self.bot.act_PRIVMSG(channel, "Calc deleted, %s." % sender)
else:
self.bot.act_PRIVMSG(channel, "Sorry %s, I don't know what '%s' is." % (sender, word))
if not value:
if self.config["allowDelete"]:
result = self.deleteCalc(channel, word)
if result:
self.bot.act_PRIVMSG(channel, "Calc deleted, %s." % sender)
else:
self.bot.act_PRIVMSG(channel, "Sorry %s, I don't know what '%s' is." % (sender, word))
else:
if self.config["delaySubmit"] > 0 and self.timeSince(channel, "add") < self.config["delaySubmit"]:
self.bot.act_PRIVMSG(channel, self.remainingToStr(self.config["delaySubmit"],

1
pyircbot/modules/CryptoWalletRPC.py

@ -15,7 +15,6 @@ from threading import Thread
class CryptoWalletRPC(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.services = ["bitcoinrpc"]
self.rpcservices = {}
self.loadrpcservices()

1
pyircbot/modules/DogeRPC.py

@ -14,7 +14,6 @@ from bitcoinrpc.authproxy import AuthServiceProxy
class DogeRPC(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.services = ["dogerpc"]
self.rpc = DogeController(self)

194
pyircbot/modules/DuckHunt.py

@ -7,7 +7,8 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
import time
import json
import random
@ -18,7 +19,6 @@ import os
class DuckHunt(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("PRIVMSG", self.hunt)]
self.jsonPath = self.getFilePath("scores.json")
@ -29,109 +29,103 @@ class DuckHunt(ModuleBase):
self.startHunt()
def hunt(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
fromWho = prefixObj.nick
@info("huntscore show your duckhunt score", cmds=["huntscore"])
@command("huntscore", allow_private=True)
def hunt(self, msg, cmd):
scores = self.loadScores()
fromWho = msg.prefix.nick
if fromWho not in scores:
self.bot.act_PRIVMSG(fromWho, "You have no points :(")
else:
scores = scores[fromWho]
kills = 0
runts = 0
prime = 0
weight = 0.0
shots = 0
misses = 0
for kill in scores:
if kill["prime"]:
prime += 1
if kill["runt"]:
runts += 1
kills += 1
weight += kill["weight"]
shots += 1
shots += kill["misses"]
misses += kill["misses"]
self.bot.act_PRIVMSG(fromWho, "You've shot %s %s for a total weight of %s lbs." %
(kills, self.config["animalSpeciesPlural"], weight))
self.bot.act_PRIVMSG(fromWho, "%s prime catches, %s runts, %s bullets used and %s misses." %
(prime, runts, shots, misses))
# self.bot.act_PRIVMSG(fromWho, "More info & highscores: http://duckhunt.xmopx.net/")
@info("shoot shoot active targets", cmds=["shoot"])
@command("shoot")
def cmd_shoot(self, msg, args):
if self.isDuckOut:
fromWho = msg.prefix.nick
if fromWho not in self.misses:
self.misses[fromWho] = 0
shotIn = round(time.time() - self.outStart, 2)
if random.randint(0, 100) <= self.config["missChance"]:
self.bot.act_PRIVMSG(self.config["activeChannel"], "%s fires after %s seconds and misses!" %
(fromWho, shotIn))
self.misses[fromWho] += 1
return
self.isDuckOut = False
bagged = {
"species": self.config["animalSpecies"],
"gender": "M" if random.randint(0, 1) == 1 else "F",
"time": shotIn,
"prime": False,
"runt": False,
"weight": 0.0,
"date": time.time(),
"misses": self.misses[fromWho]
}
message = "%s %s " % (fromWho, "bags")
if random.randint(0, 100) <= self.config["primeChance"]:
bagged["prime"] = True
bagged["weight"] = self.getRandWeight(self.config["weightMax"], self.config["weightFat"])
message += "a prime catch, a "
elif random.randint(0, 100) <= self.config["runtChance"]:
bagged["runt"] = True
bagged["weight"] = self.getRandWeight(self.config["weightRunt"], self.config["weightMin"])
message += "a runt of a catch, a "
else:
bagged["weight"] = self.getRandWeight(self.config["weightMin"], self.config["weightMax"])
message += "a "
cmd = self.bot.messageHasCommand("!huntscore", trailing, False)
if cmd:
scores = self.loadScores()
if fromWho not in scores:
self.bot.act_PRIVMSG(fromWho, "You have no points :(")
message += "%s lb " % (bagged["weight"])
if bagged["gender"] == "M":
message += self.config["animalNameMale"] + " "
else:
scores = scores[fromWho]
kills = 0
runts = 0
prime = 0
weight = 0.0
shots = 0
misses = 0
for kill in scores:
if kill["prime"]:
prime += 1
if kill["runt"]:
runts += 1
kills += 1
weight += kill["weight"]
shots += 1
shots += kill["misses"]
misses += kill["misses"]
self.bot.act_PRIVMSG(fromWho, "You've shot %s %s for a total weight of %s lbs." %
(kills, self.config["animalSpeciesPlural"], weight))
self.bot.act_PRIVMSG(fromWho, "%s prime catches, %s runts, %s bullets used and %s misses." %
(prime, runts, shots, misses))
# self.bot.act_PRIVMSG(fromWho, "More info & highscores: http://duckhunt.xmopx.net/")
self.log.info("DuckHunt: %s used !huntscore" % fromWho)
return
# Channel only
if not args[0][0] == "#":
return
cmd = self.bot.messageHasCommand("!shoot", trailing, False)
if cmd:
if self.isDuckOut:
if fromWho not in self.misses:
self.misses[fromWho] = 0
shotIn = round(time.time() - self.outStart, 2)
if random.randint(0, 100) <= self.config["missChance"]:
self.bot.act_PRIVMSG(self.config["activeChannel"], "%s fires after %s seconds and misses!" %
(fromWho, shotIn))
self.misses[fromWho] += 1
return
self.isDuckOut = False
bagged = {
"species": self.config["animalSpecies"],
"gender": "M" if random.randint(0, 1) == 1 else "F",
"time": shotIn,
"prime": False,
"runt": False,
"weight": 0.0,
"date": time.time(),
"misses": self.misses[fromWho]
}
message = "%s %s " % (fromWho, "bags")
if random.randint(0, 100) <= self.config["primeChance"]:
bagged["prime"] = True
bagged["weight"] = self.getRandWeight(self.config["weightMax"], self.config["weightFat"])
message += "a prime catch, a "
elif random.randint(0, 100) <= self.config["runtChance"]:
bagged["runt"] = True
bagged["weight"] = self.getRandWeight(self.config["weightRunt"], self.config["weightMin"])
message += "a runt of a catch, a "
else:
bagged["weight"] = self.getRandWeight(self.config["weightMin"], self.config["weightMax"])
message += "a "
message += "%s lb " % (bagged["weight"])
if bagged["gender"] == "M":
message += self.config["animalNameMale"] + " "
else:
message += self.config["animalNameFemale"] + " "
message += "in %s seconds!" % shotIn
self.bot.act_PRIVMSG(self.config["activeChannel"], message)
self.addKillFor(fromWho, bagged)
self.misses = {}
self.startHunt()
message += self.config["animalNameFemale"] + " "
message += "in %s seconds!" % shotIn
self.bot.act_PRIVMSG(self.config["activeChannel"], message)
self.addKillFor(fromWho, bagged)
self.misses = {}
self.startHunt()
def startHunt(self):
" Creates a timer that waits a certain amount of time then sends out a bird \\_o< quack"
delay = self.config["delayMin"] + random.randint(0, self.config["delayMax"] - self.config["delayMin"])
self.timer = Timer(delay, self.duckOut)
self.timer.start()
print("DuckHunt: Sending out animal in %s seconds" % delay)
self.log.info(" Sending out animal in %s seconds" % delay)
def duckOut(self):
self.isDuckOut = True
@ -143,12 +137,6 @@ class DuckHunt(ModuleBase):
weight = float(weight) * random.random()
return round(weight + minW, 2)
def getScoreFor(self, playername):
return 1
def getScoresFor(self, playername):
return 1
def addKillFor(self, playername, kill):
scores = self.loadScores()
if scores is None:

70
pyircbot/modules/Inventory.py

@ -7,14 +7,14 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
from datetime import datetime
class Inventory(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.db = None
serviceProviders = self.bot.getmodulesbyservice("sqlite")
if not serviceProviders:
@ -33,42 +33,38 @@ class Inventory(ModuleBase):
) ;""")
c.close()
self.hooks = [ModuleHook("PRIVMSG", self.checkInv)]
def checkInv(self, args, prefix, trailing):
if not args[0][0] == "#":
@info("have <item> give the bot an item", cmds=["have"])
@command("have")
def checkInv(self, msg, cmd):
if len(cmd.args) < 1:
return
adjective = None
newItem = cmd.args_str
if cmd.args[0] in self.config["adjectives"]:
newItem = cmd.args_str[len(cmd.args[0]):].strip()
adjective = cmd.args[0]
if self.has_item(newItem):
self.bot.act_PRIVMSG(msg.args[0], self.config["dupe_msg"] % {"item": newItem})
return
prefixObj = self.bot.decodePrefix(prefix)
cmd = self.bot.messageHasCommand([".have"], trailing, True)
if cmd:
if len(cmd.args) < 1:
return
adjective = None
if cmd.args[0] in self.config["adjectives"]:
cmd.args_str = cmd.args_str[len(cmd.args[0]):].strip()
adjective = cmd.args[0]
newItem = cmd.args_str
if self.has_item(newItem):
self.bot.act_PRIVMSG(args[0], self.config["dupe_msg"] % {"item": newItem})
return
dropped = self.add_item(prefixObj.nick, newItem)
if len(dropped) > 0:
self.bot.act_PRIVMSG(args[0], self.config["swap_msg"] %
{"adjective": (adjective + " ") if adjective else "",
"recv_item": newItem, "drop_item": ", ".join(dropped)})
else:
self.bot.act_PRIVMSG(args[0], self.config["recv_msg"] %
{"item": newItem, "adjective": "these " if newItem[-1:] == "s" else "this "})
cmd = self.bot.messageHasCommand([".inventory", ".inv"], trailing)
if cmd:
inv = self.getinventory()
if len(inv) == 0:
self.bot.act_PRIVMSG(args[0], self.config["inv_msg"] % {"itemlist": "nothing!"})
else:
self.bot.act_PRIVMSG(args[0], self.config["inv_msg"] % {"itemlist": ", ".join(inv)})
dropped = self.add_item(msg.prefix.nick, newItem)
if len(dropped) > 0:
self.bot.act_PRIVMSG(msg.args[0], self.config["swap_msg"] %
{"adjective": (adjective + " ") if adjective else "",
"recv_item": newItem, "drop_item": ", ".join(dropped)})
else:
self.bot.act_PRIVMSG(msg.args[0], self.config["recv_msg"] %
{"item": newItem, "adjective": "these " if newItem[-1:] == "s" else "this "})
@info("inventory show the bot's inventory", cmds=["inventory", "inv"])
@command("inventory", "inv")
def cmd_inv(self, msg, cmd):
inv = self.getinventory()
if len(inv) == 0:
self.bot.act_PRIVMSG(msg.args[0], self.config["inv_msg"] % {"itemlist": "nothing!"})
else:
self.bot.act_PRIVMSG(msg.args[0], self.config["inv_msg"] % {"itemlist": ", ".join(inv)})
def has_item(self, itemName):
c = self.db.query("SELECT COUNT(*) as `num` FROM `inventory` WHERE `item`=? COLLATE NOCASE", (itemName,))

22
pyircbot/modules/LMGTFY.py

@ -7,24 +7,20 @@
"""
import urllib.parse
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
BASE_URL = "http://lmgtfy.com/?q="
class LMGTFY(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks.append(ModuleHook("PRIVMSG", self.handleMessage))
self.bot = bot
def handleMessage(self, args, prefix, trailing):
channel = args[0]
prefix = self.bot.decodePrefix(prefix)
if self.bot.messageHasCommand(".lmgtfy", trailing):
message = trailing.split(" ")[1:]
link = self.createLink(message)
self.bot.act_PRIVMSG(channel, "%s: %s" % (prefix.nick, link))
@info("lmgtfy <term> display a condescending internet query", cmds=["lmgtfy"])
@command("lmgtfy", require_args=True)
def handleMessage(self, msg, cmd):
message = msg.trailing.split(" ")[1:]
link = self.createLink(message)
self.bot.act_PRIVMSG(msg.args[0], "%s: %s" % (msg.prefix.nick, link))
def createLink(self, message):
finalUrl = BASE_URL

18
pyircbot/modules/LinkTitler.py

@ -7,7 +7,7 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook
from requests import get
import re
import time
@ -22,16 +22,14 @@ class LinkTitler(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.REQUEST_SIZE_LIMIT = 10 * 1024
self.hooks = [ModuleHook("PRIVMSG", self.searches)]
def searches(self, args, prefix, trailing):
t = Thread(target=self.doLinkTitle, args=(args, prefix, trailing))
@hook("PRIVMSG")
def searches(self, msg, cmd):
t = Thread(target=self.doLinkTitle, args=(msg.args, msg.prefix.nick, msg.trailing))
t.daemon = True
t.start()
def doLinkTitle(self, args, prefix, trailing):
sender = self.bot.decodePrefix(prefix)
def doLinkTitle(self, args, sender, trailing):
# Youtube
matches = re.compile(r'(?:youtube.*?(?:v=|/v/)|youtu\.be/|yooouuutuuube.*?id=)([-_a-z0-9]+)', re.I) \
.findall(trailing)
@ -67,7 +65,7 @@ class LinkTitler(ModuleBase):
"domain": submission.domain,
"nsfw": "[NSFW]" if submission.over_18 else "",
"points": submission.ups,
"percent": "%s%%" % int(submission.upvote_ratio *100),
"percent": "%s%%" % int(submission.upvote_ratio * 100),
"comments": submission.num_comments,
"author": submission.author.name,
"date": datetime.datetime.fromtimestamp(submission.created).strftime("%Y.%m.%d")
@ -98,11 +96,11 @@ class LinkTitler(ModuleBase):
# Fetch HTML title
title = self.url_htmltitle(match[0])
if title:
self.bot.act_PRIVMSG(args[0], "%s: \x02%s\x02" % (sender.nick, title))
self.bot.act_PRIVMSG(args[0], "%s: \x02%s\x02" % (sender, title))
else:
# Unknown types, just print type and size
self.bot.act_PRIVMSG(args[0], "%s: \x02%s\x02, %s" %
(sender.nick, headers["Content-Type"],
(sender, headers["Content-Type"],
self.nicesize(int(headers["Content-Length"])) if
"Content-Length" in headers else "unknown size"))

15
pyircbot/modules/ModInfo.py

@ -34,8 +34,16 @@ class info(object):
self.commands = cmds or []
def __call__(self, func):
setattr(func, "irchelp", self.docstring)
setattr(func, "irchelpc", self.commands)
if hasattr(func, "irchelp"):
func.irchelp.append(self.docstring)
else:
setattr(func, "irchelp", [self.docstring])
if hasattr(func, "irchelpc"):
func.irchelpc.append(self.commands)
else:
setattr(func, "irchelpc", [self.commands])
return func
@ -77,5 +85,6 @@ class ModInfo(ModuleBase):
for attr_name in dir(module):
attr = getattr(module, attr_name)
if callable(attr) and hasattr(attr, "irchelp"):
yield (modname, module, getattr(attr, "irchelp"), getattr(attr, "irchelpc"), )
for cmdhelp, cmdaliases in zip(getattr(attr, "irchelp"), getattr(attr, "irchelpc")):
yield (modname, module, cmdhelp, cmdaliases, )
raise StopIteration()

1
pyircbot/modules/MySQL.py

@ -19,7 +19,6 @@ except:
class MySQL(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.services = ["mysql"]
self.connection = self.getConnection()

208
pyircbot/modules/NFLLive.py

@ -6,7 +6,8 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
from time import time
from requests import get
from lxml import etree
@ -19,112 +20,107 @@ class NFLLive(ModuleBase):
self.cache = None
self.cacheAge = 0
self.hooks = [ModuleHook(["PRIVMSG"], self.nflitup)]
def nflitup(self, args, prefix, trailing):
prefix = self.bot.decodePrefix(prefix)
replyTo = prefix.nick if "#" not in args[0] else args[0]
cmd = self.bot.messageHasCommand(".nfl", trailing)
if cmd:
games = self.getNflGamesCached()
msg = []
liveGames = []
gamesLaterToday = []
gamesToday = []
gamesUpcoming = []
gamesEarlierWeek = []
# sort games
for game in games["games"]:
if game["time"] is not None:
liveGames.append(game)
elif game["quarter"] == "P" and game["startdate"].day == datetime.now().day:
gamesLaterToday.append(game)
elif game["startdate"].day == datetime.now().day:
gamesToday.append(game)
elif game["startdate"].day > datetime.now().day:
gamesUpcoming.append(game)
else:
gamesEarlierWeek.append(game)
# create list of formatted games
liveGamesStr = []
for game in liveGames:
liveGamesStr.append(self.formatGameLive(game))
liveGamesStr = ", ".join(liveGamesStr)
gamesLaterTodayStr = []
for game in gamesLaterToday:
gamesLaterTodayStr.append(self.formatGameFuture(game))
gamesLaterTodayStr = ", ".join(gamesLaterTodayStr)
gamesTodayStr = []
for game in gamesToday:
gamesTodayStr.append(self.formatGamePast(game))
gamesTodayStr = ", ".join(gamesTodayStr)
gamesUpcomingStr = []
for game in gamesUpcoming:
gamesUpcomingStr.append(self.formatGameFuture(game))
gamesUpcomingStr = ", ".join(gamesUpcomingStr)
gamesEarlierWeekStr = []
for game in gamesEarlierWeek:
gamesEarlierWeekStr.append(self.formatGamePast(game))
gamesEarlierWeekStr = ", ".join(gamesEarlierWeekStr)
msgPieces = []
msgPieces.append("\x02NFL week %s\x02:" % (games["season"]["week"]))
# Depending on args build the respon pieces
if len(cmd.args) > 0 and cmd.args[0] == "today":
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
if not gamesLaterTodayStr == "":
msgPieces.append("\x02Later today:\x02 %s" % gamesLaterTodayStr)
if not gamesTodayStr == "":
msgPieces.append("\x02Earlier today:\x02 %s" % gamesTodayStr)
elif len(cmd.args) > 0 and cmd.args[0] == "live":
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
elif len(cmd.args) > 0 and cmd.args[0] == "scores":
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
if not gamesTodayStr == "":
msgPieces.append("\x02Earlier today:\x02 %s" % gamesTodayStr)
if not gamesEarlierWeekStr == "":
msgPieces.append("\x02Earlier this week: \x02 %s" % gamesEarlierWeekStr)
@info("nfl show nfl schedule & score", cmds=["nfl"])
@command("nfl")
def nflitup(self, message, cmd):
games = self.getNflGamesCached()
msg = []
liveGames = []
gamesLaterToday = []
gamesToday = []
gamesUpcoming = []
gamesEarlierWeek = []
# sort games
for game in games["games"]:
if game["time"] is not None:
liveGames.append(game)
elif game["quarter"] == "P" and game["startdate"].day == datetime.now().day:
gamesLaterToday.append(game)
elif game["startdate"].day == datetime.now().day:
gamesToday.append(game)
elif game["startdate"].day > datetime.now().day:
gamesUpcoming.append(game)
else:
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
if not gamesLaterTodayStr == "":
msgPieces.append("\x02Later today:\x02 %s" % gamesLaterTodayStr)
if not gamesTodayStr == "":
msgPieces.append("\x02Earlier today:\x02 %s" % gamesTodayStr)
if not gamesEarlierWeekStr == "":
msgPieces.append("\x02Earlier this week: \x02 %s" % gamesEarlierWeekStr)
if not gamesUpcomingStr == "":
msgPieces.append("\x02Upcoming:\x02 %s" % gamesUpcomingStr)
# Collaspe the list into a repsonse string. Fix grammar
msg = ", ".join(msgPieces).replace(":, ", ": ")
# Nothing means there were probably no games
if len(msgPieces) == 1:
msg = "No games!"
if len(msg) > 0:
# The message can be long so chunk it into pieces splitting at commas
while len(msg) > 0:
piece = msg[0:330]
msg = msg[330:]
while not piece[-1:] == "," and len(msg) > 0:
piece += msg[0:1]
msg = msg[1:]
self.bot.act_PRIVMSG(replyTo, "%s: %s" % (prefix.nick, piece.strip()))
gamesEarlierWeek.append(game)
# create list of formatted games
liveGamesStr = []
for game in liveGames:
liveGamesStr.append(self.formatGameLive(game))
liveGamesStr = ", ".join(liveGamesStr)
gamesLaterTodayStr = []
for game in gamesLaterToday:
gamesLaterTodayStr.append(self.formatGameFuture(game))
gamesLaterTodayStr = ", ".join(gamesLaterTodayStr)
gamesTodayStr = []
for game in gamesToday:
gamesTodayStr.append(self.formatGamePast(game))
gamesTodayStr = ", ".join(gamesTodayStr)
gamesUpcomingStr = []
for game in gamesUpcoming:
gamesUpcomingStr.append(self.formatGameFuture(game))
gamesUpcomingStr = ", ".join(gamesUpcomingStr)
gamesEarlierWeekStr = []
for game in gamesEarlierWeek:
gamesEarlierWeekStr.append(self.formatGamePast(game))
gamesEarlierWeekStr = ", ".join(gamesEarlierWeekStr)
msgPieces = []
msgPieces.append("\x02NFL week %s\x02:" % (games["season"]["week"]))
# Depending on args build the respon pieces
if len(cmd.args) > 0 and cmd.args[0] == "today":
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
if not gamesLaterTodayStr == "":
msgPieces.append("\x02Later today:\x02 %s" % gamesLaterTodayStr)
if not gamesTodayStr == "":
msgPieces.append("\x02Earlier today:\x02 %s" % gamesTodayStr)
elif len(cmd.args) > 0 and cmd.args[0] == "live":
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
elif len(cmd.args) > 0 and cmd.args[0] == "scores":
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
if not gamesTodayStr == "":
msgPieces.append("\x02Earlier today:\x02 %s" % gamesTodayStr)
if not gamesEarlierWeekStr == "":
msgPieces.append("\x02Earlier this week: \x02 %s" % gamesEarlierWeekStr)
else:
if not liveGamesStr == "":
msgPieces.append("\x02Playing now:\x02 %s" % liveGamesStr)
if not gamesLaterTodayStr == "":
msgPieces.append("\x02Later today:\x02 %s" % gamesLaterTodayStr)
if not gamesTodayStr == "":
msgPieces.append("\x02Earlier today:\x02 %s" % gamesTodayStr)
if not gamesEarlierWeekStr == "":
msgPieces.append("\x02Earlier this week: \x02 %s" % gamesEarlierWeekStr)
if not gamesUpcomingStr == "":
msgPieces.append("\x02Upcoming:\x02 %s" % gamesUpcomingStr)
# Collaspe the list into a repsonse string. Fix grammar
msg = ", ".join(msgPieces).replace(":, ", ": ")
# Nothing means there were probably no games
if len(msgPieces) == 1:
msg = "No games!"
if len(msg) > 0:
# The message can be long so chunk it into pieces splitting at commas
while len(msg) > 0:
piece = msg[0:330]
msg = msg[330:]
while not piece[-1:] == "," and len(msg) > 0:
piece += msg[0:1]
msg = msg[1:]