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

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)
def handlePm(self, args, prefix, trailing):
prefix = self.bot.decodePrefix(prefix)
cmd = self.bot.messageHasCommand(".setpass", trailing)
@info("setpass [<oldpass>] <password> set or change password", cmds=["setpass"])
@info("login <password> authenticate with the bot", cmds=["login"])
@info("logout log out of the bot", cmds=["logout"])
def handlePm(self, prefix, trailing):
cmd = messageHasCommand(".setpass", trailing)
if cmd:
if len(cmd.args) == 0:
self.bot.act_PRIVMSG(prefix.nick, ".setpass: usage: \".setpass newpass\" or "
@ -61,7 +63,7 @@ class NickUser(ModuleBase):
self.bot.act_PRIVMSG(prefix.nick,
".setpass: You must provide the old password when setting a new one.")
cmd = self.bot.messageHasCommand(".login", trailing)
cmd = messageHasCommand(".login", trailing)
if cmd:
attr = self.bot.getBestModuleForService("attributes")
userpw = attr.getKey(prefix.nick, "password")
@ -78,7 +80,7 @@ class NickUser(ModuleBase):
self.bot.act_PRIVMSG(prefix.nick, ".login: incorrect password.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".login: usage: \".login password\"")
cmd = self.bot.messageHasCommand(".logout", trailing)
cmd = messageHasCommand(".logout", trailing)
if cmd:
attr = self.bot.getBestModuleForService("attributes")
loggedin = attr.getKey(prefix.nick, "loggedinfrom")
@ -87,8 +89,3 @@ class NickUser(ModuleBase):
else:
attr.setKey(prefix.nick, "loggedinfrom", None)
self.bot.act_PRIVMSG(prefix.nick, ".logout: You have been logged out.")
def md5(self, data):
m = hashlib.md5()
m.update(data.encode("ascii"))
return m.hexdigest()

View File

@ -9,26 +9,23 @@
from time import time, sleep
from threading import Thread
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook
class PingResponder(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.timer = PingRespondTimer(self)
self.hooks = [
ModuleHook("PING", self.pingrespond),
ModuleHook("_RECV", self.resettimer),
ModuleHook("_SEND", self.resettimer)
]
def pingrespond(self, args, prefix, trailing):
@hook("PING")
def pingrespond(self, msg, cmd):
"""Respond to the PING command"""
# got a ping? send it right back
self.bot.act_PONG(trailing)
self.log.info("%s Responded to a ping: %s" % (self.bot.get_nick(), trailing))
self.bot.act_PONG(msg.trailing)
self.log.info("%s Responded to a ping: %s" % (self.bot.get_nick(), msg.trailing))
def resettimer(self, msg):
@hook("_RECV", "_SEND")
def resettimer(self, msg, cmd):
"""Resets the connection failure timer"""
self.timer.reset()
@ -37,7 +34,9 @@ class PingResponder(ModuleBase):
class PingRespondTimer(Thread):
"Tracks last ping from server, and reconnects if over a threshold"
"""
Tracks last ping from server, and reconnects if over a threshold
"""
def __init__(self, master):
Thread.__init__(self)
self.daemon = True
@ -47,11 +46,15 @@ class PingRespondTimer(Thread):
self.start()
def reset(self):
"Reset the internal ping timeout counter"
"""
Reset the internal ping timeout counter
"""
self.lastping = time()
def disable(self):
"Allow the thread to die"
"""
Allow the thread to die
"""
self.alive = False
def run(self):

View File

@ -62,28 +62,28 @@ class PubSubClient(ModuleBase):
self.bus.pub(self.config.get("publish").format(subchannel), "{} {}".format("default", message))
@hook("PRIVMSG")
def bus_privmsg(self, msg):
def bus_privmsg(self, msg, cmd):
"""
Relay a privmsg to the event bus
"""
self.publish("privmsg", dumps([msg.args, msg.prefix[0], msg.trailing, {"prefix": msg.prefix}]))
@hook("JOIN")
def bus_join(self, msg):
def bus_join(self, msg, cmd):
"""
Relay a join message to the event bus
"""
self.publish("join", dumps([msg.prefix[0], msg.trailing, {"prefix": msg.prefix}]))
@hook("PART")
def bus_part(self, msg):
def bus_part(self, msg, cmd):
"""
Relay a part message to the event bus
"""
self.publish("part", dumps([msg.args, msg.prefix[0], msg.trailing, {"prefix": msg.prefix}]))
@hook("PRIVMSG")
def bus_command(self, msg):
def bus_command(self, msg, cmd):
"""
Parse commands and publish as separate channels on the bus. Commands like `.seen nick` will be published
to channel `command_seen`.

View File

@ -7,14 +7,14 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook, command
from datetime import datetime
from pyircbot.modules.ModInfo import info
class RandQuote(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,26 +33,19 @@ class RandQuote(ModuleBase):
) ;""")
c.close()
self.hooks = [ModuleHook("PRIVMSG", self.logquote),
ModuleHook("PRIVMSG", self.fetchquotes)]
@info("randquote print a random quote", cmds=["randquote", "randomquote", "rq"])
@command("randquote", "randomquote", "rq")
def fetchquotes(self, msg, cmd):
c = self.db.query("SELECT * FROM `chat` ORDER BY RANDOM() LIMIT 1;")
row = c.fetchone()
c.close()
if row:
self.bot.act_PRIVMSG(msg.args[0], "<%s> %s" % (row["sender"], row["message"],))
def fetchquotes(self, args, prefix, trailing):
if not args[0][0] == "#":
return
cmd = self.bot.messageHasCommand([".randomquote", ".randquote", ".rq"], trailing)
if cmd:
c = self.db.query("SELECT * FROM `chat` ORDER BY RANDOM() LIMIT 1;")
row = c.fetchone()
c.close()
if row:
self.bot.act_PRIVMSG(args[0], "<%s> %s" % (row["sender"], row["message"],))
def logquote(self, args, prefix, trailing):
if not args[0][0] == "#":
return
prefixObj = self.bot.decodePrefix(prefix)
@hook("PRIVMSG")
def logquote(self, msg, cmd):
self.db.query("INSERT INTO `chat` (`date`, `sender`, `message`) VALUES (?, ?, ?)",
(int(datetime.now().timestamp()), prefixObj.nick, trailing)).close()
(int(datetime.now().timestamp()), msg.prefix.nick, msg.trailing)).close()
# Trim quotes
c = self.db.query("SELECT * FROM `chat` ORDER BY `date` DESC LIMIT %s, 1000000;" % self.config["limit"])
while True:

View File

@ -6,12 +6,13 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from datetime import datetime, timedelta
from threading import Thread
from time import sleep
import re
import pytz
from pyircbot.modules.ModInfo import info
class Remind(ModuleBase):
@ -37,9 +38,6 @@ class Remind(ModuleBase):
) ;""")
c.close()
self.hooks = [ModuleHook("PRIVMSG", self.remindin),
ModuleHook("PRIVMSG", self.remindat)]
self.disabled = False
# Start monitor thread
@ -108,86 +106,81 @@ class Remind(ModuleBase):
def ondisable(self):
self.disabled = True
def remindat(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
@info("remind <time> have the bot remind you", cmds=["remind", "at"])
@command("remind", "at", allow_private=True)
def remindat(self, msg, cmd):
regex = re.compile(r'(\d+):(\d+)(?::(\d+))?([^\s\d]+)? (.*)')
match = regex.match(cmd.args_str)
replyTo = msg.args[0]
try:
hour, minute, second, tz, message = match.groups()
message = message.strip()
replyTo = prefixObj.nick if "#" not in args[0] else args[0]
assert not message == ""
# Lots of code borrowed from https://github.com/embolalia/willie/blob/master/willie/modules/remind.py
cmd = self.bot.messageHasCommand([".at", ".remind"], trailing, True)
if cmd:
regex = re.compile(r'(\d+):(\d+)(?::(\d+))?([^\s\d]+)? (.*)')
match = regex.match(cmd.args_str)
hour = int(hour)
minute = int(minute)
if second is not None:
second = int(second)
except:
self.bot.act_PRIVMSG(replyTo, "%s: .at - Remind at a time. Example: .at 20:45EST Do your homework!" %
msg.prefix.nick)
return
now = datetime.now()
remindAt = datetime.now()
# if there was timezone, make sure the time we're reminding them at is relative to their timezone
if tz is not None:
try:
hour, minute, second, tz, message = match.groups()
message = message.strip()
assert not message == ""
hour = int(hour)
minute = int(minute)
if second is not None:
second = int(second)
theirzone = pytz.timezone(Remind.translateZoneStr(tz))
except:
self.bot.act_PRIVMSG(replyTo, "%s: .at - Remind at a time. Example: .at 20:45EST Do your homework!" %
prefixObj.nick)
self.bot.act_PRIVMSG(replyTo, "%s: I don't recognize the timezone '%s'." % (msg.prefix.nick, tz))
return
remindAt = theirzone.localize(remindAt, is_dst=Remind.is_dst(theirzone))
now = datetime.now()
remindAt = datetime.now()
# Set the hour and minute we'll remind them at today.
# If the ends up being in the past, we'll add a day alter
remindAt = remindAt.replace(hour=hour).replace(minute=minute).replace(microsecond=0)
# if there was timezone, make sure the time we're reminding them at is relative to their timezone
if tz is not None:
try:
theirzone = pytz.timezone(Remind.translateZoneStr(tz))
except:
self.bot.act_PRIVMSG(replyTo, "%s: I don't recognize the timezone '%s'." % (prefixObj.nick, tz))
return
remindAt = theirzone.localize(remindAt, is_dst=Remind.is_dst(theirzone))
# Set seconds
if second is None:
remindAt = remindAt.replace(second=0)
else:
remindAt = remindAt.replace(second=second)
# Set the hour and minute we'll remind them at today.
# If the ends up being in the past, we'll add a day alter
remindAt = remindAt.replace(hour=hour).replace(minute=minute).replace(microsecond=0)
# if there was timezone, convert remindAt to our zone
if tz is not None:
try:
theirzone = pytz.timezone(Remind.translateZoneStr(tz))
except:
self.bot.act_PRIVMSG(replyTo, "%s: I don't recognize the timezone '%s'." % (msg.prefix.nick, tz))
return
remindAt = remindAt.astimezone(pytz.timezone(self.config["mytimezone"])).replace(tzinfo=None)
# Set seconds
if second is None:
remindAt = remindAt.replace(second=0)
else:
remindAt = remindAt.replace(second=second)
# Advance it a day if the time would have been earlier today
while remindAt < now:
remindAt += timedelta(days=1)
# if there was timezone, convert remindAt to our zone
if tz is not None:
try:
theirzone = pytz.timezone(Remind.translateZoneStr(tz))
except:
self.bot.act_PRIVMSG(replyTo, "%s: I don't recognize the timezone '%s'." % (prefixObj.nick, tz))
return
remindAt = remindAt.astimezone(pytz.timezone(self.config["mytimezone"])).replace(tzinfo=None)
timediff = remindAt - datetime.now()
# self.bot.act_PRIVMSG(replyTo, "Time: %s" % str(remindAt))
# self.bot.act_PRIVMSG(replyTo, "Diff: %s" % (timediff))
# Advance it a day if the time would have been earlier today
while remindAt < now:
remindAt += timedelta(days=1)
# Save the reminder
c = self.db.query("INSERT INTO `reminders` (`sender`, `senderch`, `when`, `message`) VALUES (?, ?, ?, ?)", (
msg.prefix.nick,
msg.args[0] if "#" in msg.args[0] else "",
remindAt,
message
))
c.close()
timediff = remindAt - datetime.now()
# self.bot.act_PRIVMSG(replyTo, "Time: %s" % str(remindAt))
# self.bot.act_PRIVMSG(replyTo, "Diff: %s" % (timediff))
diffHours = int(timediff.seconds / 60 / 60)
diffMins = int((timediff.seconds - diffHours * 60 * 60) / 60)
# Save the reminder
c = self.db.query("INSERT INTO `reminders` (`sender`, `senderch`, `when`, `message`) VALUES (?, ?, ?, ?)", (
prefixObj.nick,
args[0] if "#" in args[0] else "",
remindAt,
message
))
c.close()
diffHours = int(timediff.seconds / 60 / 60)
diffMins = int((timediff.seconds - diffHours * 60 * 60) / 60)
self.bot.act_PRIVMSG(replyTo, "%s: Ok, will do. Approx %sh%sm to go." %
(prefixObj.nick, diffHours, diffMins))
self.bot.act_PRIVMSG(replyTo, "%s: Ok, will do. Approx %sh%sm to go." %
(msg.prefix.nick, diffHours, diffMins))
@staticmethod
def is_dst(tz):
@ -205,46 +198,45 @@ class Remind(ModuleBase):
else:
return zonestr
def remindin(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
replyTo = prefixObj.nick if "#" not in args[0] else args[0]
@info("after <duration> have the bot remind after", cmds=["after", "in"])
@command("after", "in", allow_private=True)
def remindin(self, msg, cmd):
replyTo = msg.args[0]
cmd = self.bot.messageHasCommand([".in", ".after"], trailing, True)
if cmd:
if not cmd.args:
self.bot.act_PRIVMSG(replyTo, "%s: .in - Remind after x amount of time. Example: .in 1week5d2h1m Go "
"fuck yourself" % prefixObj.nick)
if not cmd.args:
self.bot.act_PRIVMSG(replyTo, "%s: .in - Remind after x amount of time. Example: .in 1week5d2h1m Go "
"fuck yourself" % msg.prefix.nick)
return
timepieces = re.compile(r'([0-9]+)([a-zA-Z]+)').findall(cmd.args[0])
if not timepieces:
self.bot.act_PRIVMSG(replyTo, "%s: .in - Remind after x amount of time. Example: .in 1week5d2h1m Go "
"fuck yourself" % msg.prefix.nick)
return
delaySeconds = 0
for match in timepieces:
# ('30', 'm')
if not match[1] in Remind.scaling:
self.bot.act_PRIVMSG(replyTo, "%s: Sorry, I don't understand the time unit '%s'" %
(msg.prefix.nick, match[1]))
return
timepieces = re.compile(r'([0-9]+)([a-zA-Z]+)').findall(cmd.args[0])
if not timepieces:
self.bot.act_PRIVMSG(replyTo, "%s: .in - Remind after x amount of time. Example: .in 1week5d2h1m Go "
"fuck yourself" % prefixObj.nick)
return
delaySeconds += (Remind.scaling[match[1]] * int(match[0]))
delaySeconds = 0
for match in timepieces:
# ('30', 'm')
if not match[1] in Remind.scaling:
self.bot.act_PRIVMSG(replyTo, "%s: Sorry, I don't understand the time unit '%s'" %
(prefixObj.nick, match[1]))
return
remindAt = datetime.now() + timedelta(seconds=delaySeconds)
delaySeconds += (Remind.scaling[match[1]] * int(match[0]))
self.db.query("INSERT INTO `reminders` (`sender`, `senderch`, `when`, `message`) VALUES (?, ?, ?, ?)", (
msg.prefix.nick,
msg.args[0] if "#" in msg.args[0] else "",
remindAt,
cmd.args_str[len(cmd.args[0]):].strip()
)).close()
remindAt = datetime.now() + timedelta(seconds=delaySeconds)
hours = int(delaySeconds / 60 / 60)
minutes = int((delaySeconds - (hours * 60 * 60)) / 60)
self.db.query("INSERT INTO `reminders` (`sender`, `senderch`, `when`, `message`) VALUES (?, ?, ?, ?)", (
prefixObj.nick,
args[0] if "#" in args[0] else "",
remindAt,
cmd.args_str[len(cmd.args[0]):].strip()
)).close()
hours = int(delaySeconds / 60 / 60)
minutes = int((delaySeconds - (hours * 60 * 60)) / 60)
self.bot.act_PRIVMSG(replyTo, "%s: Ok, talk to you in approx %sh%sm" % (prefixObj.nick, hours, minutes))
self.bot.act_PRIVMSG(replyTo, "%s: Ok, talk to you in approx %sh%sm" % (msg.prefix.nick, hours, minutes))
scaling = {
"years": 365.25 * 24 * 3600,

View File

@ -156,7 +156,9 @@ class SMS(ModuleBase):
try:
self.twilio.api.account.messages.create(to=self.config["contacts"][contact],
from_=self.config["number"],
body=msg.trailing[7 + len(contact):].strip())
body="{} <{}>: {}".format(msg.args[0],
msg.prefix.nick,
msg.trailing[7 + len(contact):].strip()))
except Exception as e:
self.bot.act_PRIVMSG(msg.args[0], "Could not send message: {}".format(repr(e)))
else:

View File

@ -14,7 +14,6 @@ import sqlite3
class SQLite(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = []
self.services = ["sqlite"]
def opendb(self, dbname):

View File

@ -7,15 +7,15 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook
class ServerPassword(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("_CONNECT", self.doConnect)]
def doConnect(self, args, prefix, trailing):
@hook("_CONNECT")
def doConnect(self, msg, cmd):
"""Hook for when the IRC conneciton is opened"""
if "password" in self.config and self.config["password"]:
self.log.info("Sending server password")

View File

@ -7,27 +7,25 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook
from time import sleep
class Services(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("_CONNECT", self.doConnect),
ModuleHook("433", self.nickTaken),
ModuleHook("001", self.initservices),
ModuleHook("INVITE", self.invited), ]
self.current_nick = 0
self.do_ghost = False
def doConnect(self, args, prefix, trailing):
@hook("_CONNECT")
def doConnect(self, msg, cmd):
"""Hook for when the IRC conneciton is opened"""
self.bot.act_NICK(self.config["user"]["nick"][0])
self.bot.act_USER(self.config["user"]["username"], self.config["user"]["hostname"],
self.config["user"]["realname"])
def nickTaken(self, args, prefix, trailing):
@hook("433")
def nickTaken(self, msg, cmd):
"""Hook that responds to 433, meaning our nick is taken"""
if self.config["ident"]["ghost"]:
self.do_ghost = True
@ -37,7 +35,8 @@ class Services(ModuleBase):
return
self.bot.act_NICK(self.config["user"]["nick"][self.current_nick])
def initservices(self, args, prefix, trailing):
@hook("001")
def initservices(self, msg, cmd):
"""Hook that sets our initial nickname"""
if self.do_ghost:
self.bot.act_PRIVMSG(self.config["ident"]["ghost_to"], self.config["ident"]["ghost_cmd"] %
@ -46,11 +45,12 @@ class Services(ModuleBase):
self.bot.act_NICK(self.config["user"]["nick"][0])
self.do_initservices()
def invited(self, args, prefix, trailing):
@hook("INVITE")
def invited(self, msg, cmd):
"""Hook responding to INVITE channel invitations"""
if trailing.lower() in self.config["privatechannels"]["list"]:
self.log.info("Invited to %s, joining" % trailing)
self.bot.act_JOIN(trailing)
if msg.trailing.lower() in self.config["privatechannels"]["list"]:
self.log.info("Invited to %s, joining" % msg.trailing)
self.bot.act_JOIN(msg.trailing)
def do_initservices(self):
"""Identify with nickserv and join startup channels"""

View File

@ -6,9 +6,10 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command, hook
import datetime
from time import mktime
from pyircbot.modules.ModInfo import info
class Tell(ModuleBase):
@ -38,27 +39,21 @@ class Tell(ModuleBase):
self.db.query("DELETE FROM `tells` WHERE `when`<?",
(int(mktime(datetime.datetime.now().timetuple())) - self.config["maxage"],)).close()
self.hooks = [
ModuleHook(["JOIN", "PRIVMSG"], self.showtell),
ModuleHook(["PRIVMSG"], self.tellcmds)
]
def showtell(self, args, prefix, trailing):
prefix = self.bot.decodePrefix(prefix)
@hook("PRIVMSG", "JOIN")
def showtell(self, msg, cmd):
# Look for tells for this person
c = self.db.query("SELECT * FROM `tells` WHERE `recip`=?", (prefix.nick,))
c = self.db.query("SELECT * FROM `tells` WHERE `recip`=?", (msg.prefix.nick,))
tells = c.fetchall()
c.close()
for tell in tells:
agostr = Tell.timesince(datetime.datetime.fromtimestamp(tell["when"]))
recip = None
if tell["channel"] == "":
recip = prefix.nick
recip = msg.prefix.nick
else:
recip = tell["channel"]
self.bot.act_PRIVMSG(recip, "%s: %s said %s ago: %s" % (
prefix.nick,
msg.prefix.nick,
tell["sender"],
agostr,
tell["message"]
@ -66,34 +61,31 @@ class Tell(ModuleBase):
# Delete
self.db.query("DELETE FROM `tells` WHERE `id`=?", (tell["id"],))
def tellcmds(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
replyTo = prefixObj.nick if "#" not in args[0] else args[0]
@info(".tell <person> <message> relay a message when the target is online", cmds=["tell"])
@command("tell", allow_private=True)
def tellcmds(self, msg, cmd):
if len(cmd.args) < 2:
self.bot.act_PRIVMSG(msg.args[0], "%s: .tell <person> <message> - Tell someone something the next time "
"they're seen. Example: .tell antiroach Do your homework!" % msg.prefix.nick)
return
cmd = self.bot.messageHasCommand(".tell", trailing)
if cmd:
if len(cmd.args) < 2:
self.bot.act_PRIVMSG(replyTo, "%s: .tell <person> <message> - Tell someone something the next time "
"they're seen. Example: .tell antiroach Do your homework!" % prefixObj.nick)
return
recip = cmd.args[0]
message = ' '.join(cmd.args[1:]).strip()
recip = cmd.args[0]
message = ' '.join(cmd.args[1:]).strip()
if not message:
self.bot.act_PRIVMSG(replyTo, "%s: .tell <person> <message> - Tell someone something the next time "
if not message:
self.bot.act_PRIVMSG(msg.args[0], "%s: .tell <person> <message> - Tell someone something the next time "
"they're seen. Example: .tell antiroach Do your homework!" %
prefixObj.nick)
return
msg.prefix.nick)
return
self.db.query("INSERT INTO `tells` (`sender`, `channel`, `when`, `recip`, `message`) VALUES "
"(?, ?, ?, ?, ?);", (prefixObj.nick,
args[0] if "#" in args[0] else "",
int(mktime(datetime.datetime.now().timetuple())),
recip,
message)).close()
self.db.query("INSERT INTO `tells` (`sender`, `channel`, `when`, `recip`, `message`) VALUES "
"(?, ?, ?, ?, ?);", (msg.prefix.nick,
msg.args[0] if "#" in msg.args[0] else "",
int(mktime(datetime.datetime.now().timetuple())),
recip,
message)).close()
self.bot.act_PRIVMSG(replyTo, "%s: I'll pass that along." % prefixObj.nick)
self.bot.act_PRIVMSG(msg.args[0], "%s: I'll pass that along." % msg.prefix.nick)
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.

View File

@ -7,7 +7,7 @@
from threading import Thread
from time import sleep, time
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook
from random import randrange, choice
@ -15,15 +15,15 @@ class Triggered(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.quietuntil = time()
self.hooks.append(ModuleHook("PRIVMSG", self.check))
def check(self, args):
@hook("PRIVMSG")
def check(self, msg, cmd):
if time() < self.quietuntil:
return
if not args.args[0].lower() in self.config["channels"]:
if not msg.args[0].lower() in self.config["channels"]:
return
message = args.trailing.lower()
message = msg.trailing.lower()
triggered = False
for word in self.config["words"]:
if word.lower() in message:
@ -33,7 +33,7 @@ class Triggered(ModuleBase):
if not triggered:
return
msg = Thread(target=self.scream, args=(args.args[0],))
msg = Thread(target=self.scream, args=(msg.args[0],))
msg.daemon = True
msg.start()

View File

@ -6,7 +6,7 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, hook
from random import randint
import time
import re
@ -19,9 +19,6 @@ class UnoPlay(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.servicesModule = self.bot.getmodulebyname("Services")
self.hooks = [ModuleHook("PRIVMSG", self.unoplay),
ModuleHook("PRIVMSG", self.trigger),
ModuleHook("NOTICE", self.decklisten)]
self.current_card = None
self.shouldgo = False
self.has_drawn = False
@ -32,30 +29,32 @@ class UnoPlay(ModuleBase):
assert self.config["strategy"] in self.strategies
self.cards = []
def trigger(self, args, prefix, trailing):
if trailing.startswith("["): # anti-znc buffer playback
@hook("PRIVMSG")
def trigger(self, msg, cmd):
if msg.trailing.startswith("["): # anti-znc buffer playback
return
if self.config["enable_trigger"] and "!jo" in trailing:
if self.config["enable_trigger"] and "!jo" in msg.trailing:
self.bot.act_PRIVMSG(self.config["unochannel"], "jo")
elif trailing == "jo":
elif msg.trailing == "jo":
# Reset streak counter & join if another human joins
self.games_played = 0
self.join_game()
def decklisten(self, args, prefix, trailing):
@hook("NOTICE")
def decklisten(self, msg, cmd):
"""
Listen for messages sent via NOTICe to the bot, this is usually a list of cards in our hand. Parse them.
"""
if trailing.startswith("["): # anti-znc buffer playback
if msg.trailing.startswith("["): # anti-znc buffer playback
return
if not prefix or self.config["unobot"] not in prefix:
if not msg.prefix or self.config["unobot"] not in msg.prefix:
return
if "You don't have that card" in trailing:
if "You don't have that card" in msg.trailing:
self.log.error("played invalid card!")
return
trailing = self.stripcolors(trailing)
trailing = self.stripcolors(msg.trailing)
cards = []
@ -73,13 +72,14 @@ class UnoPlay(ModuleBase):
self.shouldgo = False
self.taketurn()
def unoplay(self, args, prefix, trailing):
if trailing.startswith("["): # anti-znc buffer playback
@hook("PRIVMSG")
def unoplay(self, msg, cmd):
if msg.trailing.startswith("["): # anti-znc buffer playback
return
trailing = self.stripcolors(trailing)
trailing = self.stripcolors(msg.trailing)
if self.config["unobot"] not in prefix:
if self.config["unobot"] not in msg.prefix.nick:
return
# Parse card from beginning message

View File

@ -7,30 +7,24 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from requests import get
from pyircbot.modules.ModInfo import info
class Urban(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("PRIVMSG", self.urban)]
def urban(self, args, prefix, trailing):
cmd = self.bot.messageHasCommand(".urban", trailing)
if not cmd:
cmd = self.bot.messageHasCommand(".u", trailing)
if cmd and args[0][0:1] == "#":
if cmd.args_str.strip() == "":
self.bot.act_PRIVMSG(args[0], ".u/.urban <phrase> -- looks up <phrase> on urbandictionary.com")
return
definitions = get("http://www.urbandictionary.com/iphone/search/define",
params={"term": cmd.args_str}).json()["list"]
if len(definitions) == 0:
self.bot.act_PRIVMSG(args[0], "Urban definition: no results!")
else:
defstr = definitions[0]['definition'].replace('\n', ' ').replace('\r', '')
if len(defstr) > 360:
defstr = defstr[0:360] + "..."
self.bot.act_PRIVMSG(args[0], "Urban definition: %s - http://urbanup.com/%s" %
(defstr, definitions[0]['defid']))
@info("urban <term> lookup an urban dictionary definition", cmds=["urban", "u"])
@command("urban", "u")
def urban(self, msg, cmd):
print(cmd)
definitions = get("http://www.urbandictionary.com/iphone/search/define",
params={"term": cmd.args_str}).json()["list"]
if len(definitions) == 0:
self.bot.act_PRIVMSG(msg.args[0], "Urban definition: no results!")
else:
defstr = definitions[0]['definition'].replace('\n', ' ').replace('\r', '')
if len(defstr) > 360:
defstr = defstr[0:360] + "..."
self.bot.act_PRIVMSG(msg.args[0], "Urban definition: %s - http://urbanup.com/%s" %
(defstr, definitions[0]['defid']))

View File

@ -7,8 +7,9 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from requests import get
from pyircbot.modules.ModInfo import info
class Weather(ModuleBase):
@ -31,80 +32,71 @@ class Weather(ModuleBase):
self.log.error("Weather: An 'attributes' service is required")
return
self.hooks = [ModuleHook("PRIVMSG", self.weather)]
def weather(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
fromWho = prefixObj.nick
replyTo = args[0] if "#" in args[0] else fromWho
hasUnit = self.attr.get(fromWho, "weather-unit")
@info("weather [location] display the forecast", cmds=["weather", "w"])
@command("weather", "w")
def cmd_weather(self, msg, cmd):
hasUnit = self.attr.get(msg.prefix.nick, "weather-unit")
if hasUnit:
hasUnit = hasUnit.upper()
cmd = self.bot.messageHasCommand([".w", ".weather"], trailing)
if cmd:
if len(cmd.args_str) > 0:
self.send_weather(replyTo, fromWho, cmd.args_str, hasUnit)
return
if len(cmd.args_str) > 0:
self.send_weather(msg.args[0], msg.prefix.nick, cmd.args_str, hasUnit)
return
weatherZip = self.attr.get(fromWho, "weather-zip")
if weatherZip is None:
self.bot.act_PRIVMSG(replyTo, "%s: you must set a location with .setloc" % (fromWho,))
return
weatherZip = self.attr.get(msg.prefix.nick, "weather-zip")
if weatherZip is None:
self.bot.act_PRIVMSG(msg.args[0], "%s: you must set a location with .setloc" % (msg.prefix.nick,))
return
self.bot.act_PRIVMSG(replyTo, "%s: %s" % (fromWho, self.getWeather(weatherZip, hasUnit)))
self.bot.act_PRIVMSG(msg.args[0], "%s: %s" % (msg.prefix.nick, self.getWeather(weatherZip, hasUnit)))
cmd = self.bot.messageHasCommand(".setloc", trailing)
if cmd and not args[0] == "#":
@info("setloc <location> set your home location for weather lookups", cmds=["setloc"])
@command("setloc", allow_private=True)
def cmd_setloc(self, msg, cmd):
# if not cmd.args:
# self.bot.act_PRIVMSG(fromWho, ".setloc: set your location for weather lookup. Example: "
# ".setloc Rochester, NY")
# return
reply_to = msg.args[0] if msg.args[0].startswith("#") else msg.prefix.nick
weatherLoc = cmd.args_str
try:
result = self.getWeather(weatherLoc) # NOQA
except LocationNotSpecificException as lnse:
self.bot.act_PRIVMSG(reply_to, "'%s': location not specific enough. Did you mean: %s" %
(weatherLoc, self.alternates_to_str(lnse.alternates)))
return
except LocationException as le:
self.bot.act_PRIVMSG(reply_to, "'%s': location not found: %s" % (weatherLoc, le))
return
if not cmd.args:
self.bot.act_PRIVMSG(fromWho, ".setloc: set your location for weather lookup. Example: "
".setloc Rochester, NY")
return
if not self.login.check(msg.prefix.nick, msg.prefix.hostname):
self.bot.act_PRIVMSG(reply_to, ".setloc: you need to be logged in to do that (try .login)")
return
weatherLoc = cmd.args_str
self.attr.set(msg.prefix.nick, "weather-zip", weatherLoc)
self.bot.act_PRIVMSG(reply_to, "Saved your location as %s"
% self.attr.get(msg.prefix.nick, "weather-zip"))
if self.attr.get(msg.prefix.nick, "weather-zip") is None:
self.bot.act_PRIVMSG(reply_to, "Tip: choose C or F with .wunit <C/F>")
try:
result = self.getWeather(weatherLoc) # NOQA
except LocationNotSpecificException as lnse:
self.bot.act_PRIVMSG(fromWho, "'%s': location not specific enough. Did you mean: %s" %
(weatherLoc, self.alternates_to_str(lnse.alternates)))
return
except LocationException as le:
self.bot.act_PRIVMSG(fromWho, "'%s': location not found: %s" % (weatherLoc, le))
return
@info("wunit <c|f> set preferred weather unit", cmds=["wunit"])
@command("wunit", allow_private=True)
def cmd_wunit(self, msg, cmd):
if cmd.args[0].lower() not in ['c', 'f']:
return
unit = cmd.args[0].lower()
reply_to = msg.args[0] if msg.args[0].startswith("#") else msg.prefix.nick
if not self.login.check(prefixObj.nick, prefixObj.hostname):
self.bot.act_PRIVMSG(fromWho, ".setloc: you need to be logged in to do that (try .login)")
return
# if unit is None:
# self.bot.act_PRIVMSG(fromWho, ".wunit: set your preferred temperature unit to C or F")
# return
self.attr.set(fromWho, "weather-zip", weatherLoc)
self.bot.act_PRIVMSG(fromWho, "Saved your location as %s" % self.attr.get(fromWho, "weather-zip"))
if self.attr.get(fromWho, "weather-zip") is None:
self.bot.act_PRIVMSG(fromWho, "Tip: choose C or F with .wunit <C/F>")
if not self.login.check(msg.prefix.nick, msg.prefix.hostname):
self.bot.act_PRIVMSG(reply_to, ".wunit: you need to be logged in to do that (try .login)")
return
cmd = self.bot.messageHasCommand(".wunit", trailing)
if cmd and not args[0] == "#":
unit = None
try:
assert cmd.args[0].lower() in ['c', 'f']
unit = cmd.args[0]
except:
pass
if unit is None:
self.bot.act_PRIVMSG(fromWho, ".wunit: set your preferred temperature unit to C or F")
return
if not self.login.check(prefixObj.nick, prefixObj.hostname):
self.bot.act_PRIVMSG(fromWho, ".wunit: you need to be logged in to do that (try .login)")
return
self.attr.set(fromWho, "weather-unit", unit.lower())
self.bot.act_PRIVMSG(fromWho, "Saved your preferred unit as %s" %
self.attr.get(fromWho, "weather-unit").upper())
self.attr.set(msg.prefix.nick, "weather-unit", unit.lower())
self.bot.act_PRIVMSG(reply_to, "Saved your preferred unit as %s" % unit)
def send_weather(self, target, hilight, location, units=None):
try:

View File

@ -7,7 +7,9 @@
"""
from pyircbot.modulebase import ModuleBase, ModuleHook
from pyircbot.modulebase import ModuleBase, command
from pyircbot.modules.ModInfo import info
from random import shuffle
from requests import get
import time
import re
@ -16,7 +18,6 @@ import re
class Youtube(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.hooks = [ModuleHook("PRIVMSG", self.youtube)]
def getISOdurationseconds(self, stamp):
ISO_8601_period_rx = re.compile(
@ -33,25 +34,24 @@ class Youtube(ModuleBase):
) # http://stackoverflow.com/a/16742742
return ISO_8601_period_rx.match(stamp).groupdict()
def youtube(self, args, prefix, trailing):
@info("yt search for youtube videos", cmds=["yt", "youtube"])
@command("yt", "youtube")
def youtube(self, msg, cmd):
j = get("https://www.googleapis.com/youtube/v3/search",
params={"key": self.config["api_key"],
"part": "snippet",
"type": "video",
"maxResults": "25",
"safeSearch": self.config.get("safe_search", "none"),
"q": cmd.args_str}).json()
cmd = self.bot.messageHasCommand(".youtube", trailing)
if not cmd:
cmd = self.bot.messageHasCommand(".yt", trailing)
if cmd and args[0][0:1] == "#":
# TODO search youtube
if cmd.args_str.strip() == "":
self.bot.act_PRIVMSG(args[0], '.youtube <query> -- returns the first YouTube search result for <query>')
return
j = get("http://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc&max-results=1",
params={"q": trailing}).json()
if 'error' in j or j['data']['totalItems'] == 0:
self.bot.act_PRIVMSG(args[0], "YouTube: No results found.")
else:
vid_id = j['data']['items'][0]['id']
vidinfo = self.get_video_description(vid_id)
if vidinfo:
self.bot.act_PRIVMSG(args[0], "http://youtu.be/%s :: %s" % (vid_id, vidinfo))
if 'error' in j or len(j["items"]) == 0:
self.bot.act_PRIVMSG(msg.args[0], "No results found.")
else:
shuffle(j['items'])
vid_id = j["items"][0]['id']['videoId']
self.bot.act_PRIVMSG(msg.args[0], "http://youtu.be/{} :: {}".format(vid_id,
self.get_video_description(vid_id)))
def get_video_description(self, vid_id):
apidata = get('https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics&id=%s'

View File

@ -10,128 +10,21 @@ import logging
import sys
from pyircbot.rpc import BotRPC
from pyircbot.irccore import IRCCore
from collections import namedtuple
from socket import AF_INET, AF_INET6
from json import load
import os.path
import asyncio
import traceback
ParsedCommand = namedtuple("ParsedCommand", "command args args_str message")
class PyIRCBot(object):
""":param botconfig: The configuration of this instance of the bot. Passed by main.py.
:type botconfig: dict
"""
version = "4.1.0"
def __init__(self, botconfig):
self.log = logging.getLogger('PyIRCBot')
"""Reference to logger object"""
self.loop = asyncio.get_event_loop()
"""saved copy of the instance config"""
self.botconfig = botconfig
class ModuleLoader(object):
def __init__(self):
"""storage of imported modules"""
self.modules = {}
"""instances of modules"""
self.moduleInstances = {}
"""Reference to BotRPC thread"""
self.rpc = BotRPC(self)
ratelimit = self.botconfig["connection"].get("rate_limit", None) or dict(rate_max=5.0, rate_int=1.1)
"""IRC protocol handler"""
self.irc = IRCCore(servers=self.botconfig["connection"]["servers"],
loop=self.loop,
rate_limit=True if ratelimit else False,
rate_max=ratelimit["rate_max"],
rate_int=ratelimit["rate_int"])
if self.botconfig.get("connection").get("force_ipv6", False):
self.irc.connection_family = AF_INET6
elif self.botconfig.get("connection").get("force_ipv4", False):
self.irc.connection_family = AF_INET
self.irc.bind_addr = self.botconfig.get("connection").get("bind", None)
# legacy support
self.act_PONG = self.irc.act_PONG
self.act_USER = self.irc.act_USER
self.act_NICK = self.irc.act_NICK
self.act_JOIN = self.irc.act_JOIN
self.act_PRIVMSG = self.irc.act_PRIVMSG
self.act_MODE = self.irc.act_MODE
self.act_ACTION = self.irc.act_ACTION
self.act_KICK = self.irc.act_KICK
self.act_QUIT = self.irc.act_QUIT
self.act_PASS = self.irc.act_PASS
self.get_nick = self.irc.get_nick
self.decodePrefix = IRCCore.decodePrefix
# Load modules
self.initModules()
# Internal usage hook
self.irc.addHook("PRIVMSG", self._irchook_internal)
def run(self):
self.client = asyncio.ensure_future(self.irc.loop(self.loop), loop=self.loop)
try:
self.loop.set_debug(True)
self.loop.run_until_complete(self.client)
finally:
logging.debug("Escaped main loop")
def disconnect(self, message):
"""Send quit message and disconnect from IRC.
:param message: Quit message
:type message: str
:param reconnect: True causes a reconnection attempt to be made after the disconnect
:type reconnect: bool
"""
self.log.info("disconnect")
self.kill(message=message)
def kill(self, message="Help! Another thread is killing me :(", forever=True):
"""Close the connection violently
:param sys_exit: True causes sys.exit(0) to be called
:type sys_exit: bool
:param message: Quit message
:type message: str
"""
if forever:
self.closeAllModules()
asyncio.run_coroutine_threadsafe(self.irc.kill(message=message, forever=forever), self.loop)
def initModules(self):
"""load modules specified in instance config"""
" append module location to path "
sys.path.append(os.path.dirname(__file__) + "/modules/")
" append usermodule dir to beginning of path"
for path in self.botconfig["bot"]["usermodules"]:
sys.path.insert(0, path + "/")
for modulename in self.botconfig["modules"]:
self.loadmodule(modulename)
def _irchook_internal(self, msg):
"""
IRC hook handler. Calling point for IRCHook based module hooks. This method is called when a message is
received. It tests all hooks in all modules against the message can calls the hooked function on hits.
"""
for module_name, module in self.moduleInstances.items():
for hook in module.irchooks:
validation = hook.validator(msg, self)
if validation:
hook.method(msg, validation)
self.log = logging.getLogger('ModuleLoader')
def importmodule(self, name):
"""Import a module
@ -189,7 +82,7 @@ class PyIRCBot(object):
" init the module "
self.moduleInstances[name] = getattr(self.modules[name], name)(self, name)
" load hooks "
self.loadModuleHooks(self.moduleInstances[name])
# self.loadModuleHooks(self.moduleInstances[name])
self.moduleInstances[name].onenable()
def unloadmodule(self, name):
@ -201,7 +94,7 @@ class PyIRCBot(object):
" notify the module of disabling "
self.moduleInstances[name].ondisable()
" unload all hooks "
self.unloadModuleHooks(self.moduleInstances[name])
# self.unloadModuleHooks(self.moduleInstances[name])
" remove & delete the instance "
self.moduleInstances.pop(name)
self.log.info("Module %s unloaded" % name)
@ -246,32 +139,6 @@ class PyIRCBot(object):
self.loadmodule(name)
return (True, None)
def loadModuleHooks(self, module):
"""**Internal.** Enable (connect) hooks of a module
:param module: module object to hook in
:type module: object"""
" activate a module's hooks "
for hook in module.hooks:
if type(hook.hook) == list:
for hookcmd in hook.hook:
self.irc.addHook(hookcmd, hook.method)
else:
self.irc.addHook(hook.hook, hook.method)
def unloadModuleHooks(self, module):
"""**Internal.** Disable (disconnect) hooks of a module
:param module: module object to unhook
:type module: object"""
" remove a modules hooks "
for hook in module.hooks:
if type(hook.hook) == list:
for hookcmd in hook.hook:
self.irc.removeHook(hookcmd, hook.method)
else:
self.irc.removeHook(hook.hook, hook.method)
def getmodulebyname(self, name):
"""Get a module object by name
@ -305,8 +172,16 @@ class PyIRCBot(object):
return m[0]
return None
class PrimitiveBot(ModuleLoader):
def __init__(self, botconfig):
super().__init__()
self.botconfig = botconfig
def closeAllModules(self):
""" Deport all modules (for shutdown). Modules are unloaded in the opposite order listed in the config. """
"""
Deport all modules (for shutdown). Modules are unloaded in the opposite order listed in the config.
"""
loaded = list(self.moduleInstances.keys())
loadOrder = self.botconfig["modules"]
loadOrder.reverse()
@ -318,16 +193,6 @@ class PyIRCBot(object):
self.deportmodule(key)
" Filesystem Methods "
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
def getConfigPath(self, moduleName):
"""Return the absolute path for a module's config file
@ -340,63 +205,140 @@ class PyIRCBot(object):
return "%s.json" % basepath
return None
" Utility methods "
@staticmethod
def messageHasCommand(command, message, requireArgs=False):
"""Check if a message has a command with or without args in it
def getDataPath(self, moduleName):
"""Return the absolute path for a module's data dir
: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"
: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
class PyIRCBot(PrimitiveBot):
""":param botconfig: The configuration of this instance of the bot. Passed by main.py.
:type botconfig: dict
"""
version = "5.0.0"
def __init__(self, botconfig):
super().__init__(botconfig)
self.log = logging.getLogger('PyIRCBot')
"""Reference to logger object"""
self.loop = asyncio.get_event_loop()
"""Reference to BotRPC thread"""
self.rpc = BotRPC(self)
ratelimit = self.botconfig["connection"].get("rate_limit", None) or dict(rate_max=5.0, rate_int=1.1)
"""IRC protocol handler"""
self.irc = IRCCore(servers=self.botconfig["connection"]["servers"],
loop=self.loop,
rate_limit=True if ratelimit else False,
rate_max=ratelimit["rate_max"],
rate_int=ratelimit["rate_int"])
if self.botconfig.get("connection").get("force_ipv6", False):
self.irc.connection_family = AF_INET6
elif self.botconfig.get("connection").get("force_ipv4", False):
self.irc.connection_family = AF_INET
self.irc.bind_addr = self.botconfig.get("connection").get("bind", None)
self.act_PONG = self.irc.act_PONG
self.act_USER = self.irc.act_USER
self.act_NICK = self.irc.act_NICK
self.act_JOIN = self.irc.act_JOIN
self.act_PRIVMSG = self.irc.act_PRIVMSG
self.act_MODE = self.irc.act_MODE
self.act_ACTION = self.irc.act_ACTION
self.act_KICK = self.irc.act_KICK
self.act_QUIT = self.irc.act_QUIT
self.act_PASS = self.irc.act_PASS
self.get_nick = self.irc.get_nick
self.decodePrefix = IRCCore.decodePrefix
# Load modules
self.initModules()
# Internal usage hook
self.irc.addHook("_ALL", self._irchook_internal)
def initModules(self):
"""load modules specified in instance config"""
" append module location to path "
sys.path.append(os.path.dirname(__file__) + "/modules/")
" append usermodule dir to beginning of path"
for path in self.botconfig["bot"]["usermodules"]:
sys.path.insert(0, path + "/")
for modulename in self.botconfig["modules"]:
self.loadmodule(modulename)
def run(self):
self.client = asyncio.ensure_future(self.irc.loop(self.loop), loop=self.loop)
try:
self.loop.set_debug(True)
self.loop.run_until_complete(self.client)
finally:
logging.debug("Escaped main loop")
def disconnect(self, message):
"""Send quit message and disconnect from IRC.
:param message: Quit message
: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 = PyIRCBot.messageHasCommandSingle(item, message, requireArgs)
if cmd:
return cmd
return False
@staticmethod
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)
@staticmethod
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
:param reconnect: True causes a reconnection attempt to be made after the disconnect
:type reconnect: bool
"""
self.log.info("disconnect")
self.kill(message=message)
if filepath.endswith(".json"):
with open(filepath, 'r') as f:
return load(f)
else:
raise Exception("Unknown config format")
def kill(self, message="Help! Another thread is killing me :(", forever=True):
"""Close the connection violently
:param sys_exit: True causes sys.exit(0) to be called
:type sys_exit: bool
:param message: Quit message
:type message: str
"""
if forever:
self.closeAllModules()
asyncio.run_coroutine_threadsafe(self.irc.kill(message=message, forever=forever), self.loop)
def _irchook_internal(self, msg):
"""
IRC hook handler. Calling point for IRCHook based module hooks. This method is called when any message is
received. It tests all hooks in all modules against the message can calls the hooked function on hits.
"""
for module_name, module in self.moduleInstances.items():
for hook in module.irchooks:
validation = hook.validator(msg, self)
if validation:
hook.method(msg, validation)
" 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

8
run-example.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash -x
CONFPATH=${1:-examples/config.json}
export PYTHONUNBUFFERED=1
export PYTHONPATH=.
./bin/pyircbot -c $CONFPATH --debug