modernize module system: 5.0.0

This commit is contained in:
dave 2017-11-27 18:58:20 -08:00
parent 5b68793f4f
commit d55e111767
42 changed files with 968 additions and 871 deletions

View File

@ -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 Executable file
View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
]
}
}

View File

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

View File

@ -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")

View File

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

View File

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

View File

@ -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")

View File

@ -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")

View File

@ -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)
]
@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]
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'])
))
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

View File

@ -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"],

View File

@ -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()

View File

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

View File

@ -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"]
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 :(")
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:
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"]
bagged["weight"] = self.getRandWeight(self.config["weightMin"], self.config["weightMax"])
message += "a "
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
message += "%s lb " % (bagged["weight"])
if bagged["gender"] == "M":
message += self.config["animalNameMale"] + " "
else:
message += self.config["animalNameFemale"] + " "
# Channel only
if not args[0][0] == "#":
return
message += "in %s seconds!" % shotIn
self.bot.act_PRIVMSG(self.config["activeChannel"], message)
cmd = self.bot.messageHasCommand("!shoot", trailing, False)
if cmd:
if self.isDuckOut:
self.addKillFor(fromWho, bagged)
if fromWho not in self.misses:
self.misses[fromWho] = 0
self.misses = {}
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()
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:

View File

@ -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
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
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(args[0], self.config["dupe_msg"] % {"item": newItem})
return
if self.has_item(newItem):
self.bot.act_PRIVMSG(msg.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 "})
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 "})
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)})
@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,))

View File

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

View File

@ -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"))

View File

@ -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()

View File

@ -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()

View File

@ -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)]
@info("nfl show nfl schedule & score", cmds=["nfl"])
@command("nfl")
def nflitup(self, message, cmd):
games = self.getNflGamesCached()
msg = []
def nflitup(self, args, prefix, trailing):
prefix = self.bot.decodePrefix(prefix)
replyTo = prefix.nick if "#" not in args[0] else args[0]
liveGames = []
gamesLaterToday = []
gamesToday = []
gamesUpcoming = []
gamesEarlierWeek = []
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)
# 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)
gamesEarlierWeek.append(game)
# Collaspe the list into a repsonse string. Fix grammar
msg = ", ".join(msgPieces).replace(":, ", ": ")
# create list of formatted games
liveGamesStr = []
for game in liveGames:
liveGamesStr.append(self.formatGameLive(game))
liveGamesStr = ", ".join(liveGamesStr)
# Nothing means there were probably no games
if len(msgPieces) == 1:
msg = "No games!"
gamesLaterTodayStr = []
for game in gamesLaterToday:
gamesLaterTodayStr.append(self.formatGameFuture(game))
gamesLaterTodayStr = ", ".join(gamesLaterTodayStr)
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()))
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:]
self.bot.act_PRIVMSG(message.args[0], "%s: %s" % (message.prefix.nick, piece.strip()))
def formatGameLive(self, game):
c_vis = 3 if int(game["visitor_score"]) > int(game["home_score"]) else 4

View File

@ -7,14 +7,14 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
import hashlib
from pyircbot.modulebase import ModuleBase, hook
from pyircbot.common import messageHasCommand
from pyircbot.modules.ModInfo import info
class NickUser(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("PRIVMSG", self.gotmsg)]
self.services = ["login"]
def check(self, nick, hostname):
@ -28,17 +28,19 @@ class NickUser(ModuleBase):
pass
# TODO: log out all users
def gotmsg(self, args, prefix, trailing):
channel = args[0]
if channel[0] == "#":
@hook("PRIVMSG")
def gotmsg(self, msg, cmd):
if msg.args[0][0] == "#":
# Ignore channel messages
pass
return
else:
self.handlePm(args, prefix, trailing)
self.handlePm(msg.prefix, msg.trailing)