Initial commit

This commit is contained in:
Dave Pedu 2013-12-28 12:58:20 -05:00
commit 83d30ceab6
28 changed files with 3534 additions and 0 deletions

2
config.main.yml Normal file
View File

@ -0,0 +1,2 @@
botdir: /home/example/bot/pyircbot/
moduledir: /home/example/bot/pyircbot/modules/

5
data/config/DogeDice.yml Normal file
View File

@ -0,0 +1,5 @@
minBet: .01
lobbyIdleSeconds: 15
channelWhitelistOn: True
channelWhitelist:
- test

4
data/config/DogeRPC.yml Normal file
View File

@ -0,0 +1,4 @@
host: 127.0.0.1
username: root
password: root
port: 22555

View File

@ -0,0 +1,7 @@
hintDelay: 15
delayNext: 5
maxHints: 5
abortAfterNoGuesses: 2
winAmount: 0.1
categoryduration: 2
decreaseFactor: 0.83

3
data/config/GameBase.yml Normal file
View File

@ -0,0 +1,3 @@
channelWhitelistOn: True
channelWhitelist:
- test

4
data/config/MySQL.yml Normal file
View File

@ -0,0 +1,4 @@
host: 127.0.0.1
username: root
password: root
database: pyircbot

5
data/config/Scramble.yml Normal file
View File

@ -0,0 +1,5 @@
hintDelay: 15
delayNext: 5
maxHints: 5
abortAfterNoGuesses: 5
decreaseFactor: 0.83

23
data/config/Services.yml Normal file
View File

@ -0,0 +1,23 @@
user:
nick:
- pyircbot3
- pyircbot3_
- pyircbot3__
password: nickservpassword
username: pyircbot3
hostname: pyircbot3.domain.com
realname: pyircbot3
ident:
enable: yes
to: nickserv
command: identify %(password)s
ghost: yes
ghost_to: nickserv
ghost_cmd: ghost %(nick)s %(password)s
channels:
- "#pyircbot3"
privatechannels:
to: chanserv
command: invite %(channel)s
list:
- "#aprivatechannel"

View File

@ -0,0 +1,50 @@
Alabama
Alaska
Arizona
Arkansas
California
Colorado
Connecticut
Delaware
Florida
Georgia
Hawaii
Idaho
Illinois
Indiana
Iowa
Kansas
Kentucky
Louisiana
Maine
Maryland
Massachusetts
Michigan
Minnesota
Mississippi
Missouri
Montana
Nebraska
Nevada
New Hampshire
New Jersey
New Mexico
New York
North Carolina
North Dakota
Ohio
Oklahoma
Oregon
Pennsylvania
Rhode Island
South Carolina
South Dakota
Tennessee
Texas
Utah
Vermont
Virginia
Washington
West Virginia
Wisconsin
Wyoming

View File

@ -0,0 +1,313 @@
Air
Stone
Grass
Dirt
Cobblestone
Wooden Plank
Sapling
Redwood Sapling
Birch Sapling
Bedrock
Water
Lava
Sand
Gravel
Gold Ore
Iron Ore
Coal Ore
Wood
Redwood
Birchwood
Leaves
Redwood Leaves
Birchwood Leaves
Sponge
Glass
Lapis Lazuli Ore
Lapis Lazuli Block
Dispenser
Sandstone
Note Block
Bed Block
Powered Rail
Detector Rail
Sticky Piston
Web
Dead Shrub
Tall Grass
Live Shrub
Dead Shrub
Piston
Piston Head
White Wool
Orange Wool
Magenta Wool
Light Blue Wool
Yellow Wool
Light Green Wool
Pink Wool
Gray Wool
Light Gray Wool
Cyan Wool
Purple Wool
Blue Wool
Brown Wool
Dark Green Wool
Red Wool
Black Wool
Dandelion
Rose
Brown Mushroom
Red Mushroom
Gold Block
Iron Block
Double Stone Slab
Double Sandstone Slab
Double Wooden Slab
Double Cobblestone Slab
Double Brick Slab
Double Stone Brick Slab
Stone Slab
Sandstone Slab
Wooden Slab
Cobblestone Slab
Brick Slab
Stone Brick Slab
Brick
TNT
Bookshelf
Mossy Cobblestone
Obsidian
Torch
Fire
Monster Spawner
Wooden Stairs
Chest
Redstone Wire
Diamond Ore
Diamond Block
Workbench
Wheat Crops
Soil
Furnace
Sign Post
Wooden Door
Ladder
Rails
Cobblestone Stairs
Wall Sign
Lever
Stone Pressure Plate
Iron Door Block
Wooden Pressure Plate
Redstone Ore
Redstone Torch
Stone Button
Snow
Ice
Snow Block
Cactus
Clay
Sugar Cane
Jukebox
Fence
Pumpkin
Netherrack
Soul Sand
Glowstone
Portal
Jack-O-Lantern
Cake Block
Redstone Repeater Block
Locked Chest
Trapdoor
Stone (Silverfish)
Stone Brick
Mossy Stone Brick
Cracked Stone Brick
Red Mushroom Cap
Brown Mushroom Cap
Iron Bars
Glass Pane
Melon Block
Pumpkin Stem
Melon Stem
Vines
Fence Gate
Brick Stairs
Stone Brick Stairs
Mycelium
Lily Pad
Nether Brick
Nether Brick Fence
Nether Brick Stairs
Nether Wart
Iron Shovel
Iron Pickaxe
Iron Axe
Flint and Steel
Apple
Bow
Arrow
Coal
Charcoal
Diamond
Iron Ingot
Gold Ingot
Iron Sword
Wooden Sword
Wooden Shovel
Wooden Pickaxe
Wooden Axe
Stone Sword
Stone Shovel
Stone Pickaxe
Stone Axe
Diamond Sword
Diamond Shovel
Diamond Pickaxe
Diamond Axe
Stick
Bowl
Mushroom Soup
Gold Sword
Gold Shovel
Gold Pickaxe
Gold Axe
String
Feather
Sulphur
Wooden Hoe
Stone Hoe
Iron Hoe
Diamond Hoe
Gold Hoe
Wheat Seeds
Wheat
Bread
Leather Helmet
Leather Chestplate
Leather Leggings
Leather Boots
Chainmail Helmet
Chainmail Chestplate
Chainmail Leggings
Chainmail Boots
Iron Helmet
Iron Chestplate
Iron Leggings
Iron Boots
Diamond Helmet
Diamond Chestplate
Diamond Leggings
Diamond Boots
Gold Helmet
Gold Chestplate
Gold Leggings
Gold Boots
Flint
Raw Porkchop
Cooked Porkchop
Painting
Golden Apple
Sign
Wooden Door
Bucket
Water Bucket
Lava Bucket
Minecart
Saddle
Iron Door
Redstone
Snowball
Boat
Leather
Milk Bucket
Clay Brick
Clay Balls
Sugarcane
Paper
Book
Slimeball
Storage Minecart
Powered Minecart
Egg
Compass
Fishing Rod
Clock
Glowstone Dust
Raw Fish
Cooked Fish
Ink Sack
Rose Red
Cactus Green
Coco Beans
Lapis Lazuli
Purple Dye
Cyan Dye
Light Gray Dye
Gray Dye
Pink Dye
Lime Dye
Dandelion Yellow
Light Blue Dye
Magenta Dye
Orange Dye
Bone Meal
Bone
Sugar
Cake
Bed
Redstone Repeater
Cookie
Map
Shears
Melon
Pumpkin Seeds
Melon Seeds
Raw Beef
Steak
Raw Chicken
Cooked Chicken
Rotten Flesh
Ender Pearl
Blaze Rod
Ghast Tear
Gold Nugget
Nether Wart Seeds
Potion
Glass Bottle
Spider Eye
Fermented Spider Eye
Blaze Powder
Magma Cream
Gold Music Disc
Green Music Disc
Chicken
Cow
Mooshroom
Ocelot
Pig
Sheep
Squid
Villager
Enderman
Wolf
Zombie Pigman
Wolf
Ocelot
Blaze
Cave Spider
Creeper
Ghast
Magma Cube
Silverfish
Skeleton
Slime
Spider
Spider Jockey
Zombie
Snow Golem
Iron Golem
Ender Dragon
Rana

13
pyircbot.yml Normal file
View File

@ -0,0 +1,13 @@
bot:
datadir: /home/example/bot/data/
rpcbind: 0.0.0.0
rpcport: 1876
connection:
server: irc.freenode.net
ipv6: off
port: 6667
modules:
- PingResponder
- Services
- MySQL
- AttributeStorage

View File

1113
pyircbot/core/jsonrpc.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python
import logging
import os
import yaml
class ModuleBase:
" All modules must extend this class. "
def __init__(self, bot, moduleName):
" Module name is passed from the actual module "
self.moduleName=moduleName
" Reference to the bot is saved"
self.bot = bot
" Hooks are provided requested by the actual module "
self.hooks=[]
" Services provided by the actual module "
self.services=[]
" Config is blank until the Module calls loadConfig "
self.config={}
" Set up logging for this module "
self.log = logging.getLogger("Module.%s" % self.moduleName)
self.log.info("Loaded module %s" % self.moduleName)
def loadConfig(self):
configPath = self.bot.getConfigPath(self.moduleName)
if os.path.exists( configPath ):
self.config = yaml.load(open(configPath, 'r'))
def ondisable(self):
pass
def getConfigPath(self):
return self.bot.getConfigPath(self.moduleName)
def getFilePath(self, f=None):
return self.bot.getDataPath(self.moduleName) + (f if f else '')
class ModuleHook:
def __init__(self, hook, method):
self.hook=hook
self.method=method

454
pyircbot/core/pyircbot.py Normal file
View File

@ -0,0 +1,454 @@
#!/usr/bin/env python
import socket
import asynchat
import logging
import traceback
import time
import sys
from socket import SHUT_RDWR
from core.rpc import BotRPC
try:
from cStringIO import StringIO
except:
from io import BytesIO as StringIO
class PyIRCBot(asynchat.async_chat):
def __init__(self, coreconfig, botconfig):
asynchat.async_chat.__init__(self)
" logging "
self.log = logging.getLogger('PyIRCBot')
" config "
self.coreconfig = coreconfig
self.botconfig = botconfig
" rpc "
self.rpc = BotRPC(self)
" stringio object as buffer "
self.buffer = StringIO()
" line terminator "
self.set_terminator(b"\r\n")
" Setup hooks for modules "
self.initHooks()
" Load modules "
self.initModules()
self._connect()
self.connected=False
def kill(self):
" Close RPC Socket "
#try:
# self.rpc.server._Server__transport.shutdown(SHUT_RDWR)
#except Exception as e:
# self.log.error(str(e))
try:
self.rpc.server._Server__transport.close()
except Exception as e:
self.log.error(str(e))
" Kill RPC thread "
self.rpc._stop()
" Close all modules "
self.closeAllModules()
" Net related code "
def getBuf(self):
" return buffer and clear "
self.buffer.seek(0)
data = self.buffer.read()
self.buffer = StringIO()
return data
def collect_incoming_data(self, data):
" Recieve data from stream, add to buffer "
self.log.debug("<< %(message)s", {"message":repr(data)})
self.buffer.write(data)
def found_terminator(self):
" A complete command was pushed through, so clear the buffer and process it."
self.process_data(self.getBuf().decode("UTF-8"))
def handle_close(self):
" called on socket shutdown "
self.log.debug("handle_close")
self.connected=False
self.close()
self.log.warning("Connection was lost.")
#self.log.warning("Connection was lost. Reconnecting in 5 seconds.")
#time.sleep(5)
#self._connect()
def handle_error(self, *args, **kwargs):
raise
def _connect(self):
self.log.debug("Connecting to %(server)s:%(port)i", {"server":self.botconfig["connection"]["server"], "port":self.botconfig["connection"]["port"]})
socket_type = socket.AF_INET
if self.botconfig["connection"]["ipv6"]:
self.log.info("IPv6 is enabled.")
socket_type = socket.AF_INET6
socketInfo = socket.getaddrinfo(self.botconfig["connection"]["server"], self.botconfig["connection"]["port"], socket_type)
self.create_socket(socket_type, socket.SOCK_STREAM)
if "bindaddr" in self.botconfig["connection"]:
self.bind((self.botconfig["connection"]["bindaddr"], 0))
self.connect(socketInfo[0][4])
def handle_connect(self):
" Called when the first packets come through, so we ident here "
self.connected=True
self.log.debug("handle_connect: setting USER and NICK")
self.fire_hook("_CONNECT")
self.log.debug("handle_connect: complete")
def sendRaw(self, text):
if self.connected:
self.log.debug(">> "+text)
self.send( (text+"\r\n").encode("ascii"))
else:
self.log.warning("Send attempted while disconnected. >> "+text)
def process_data(self, data):
" called per line of irc sent through "
if data.strip() == "":
return
prefix = None
command = None
args=[]
trailing=None
if data[0]==":":
prefix=data.split(" ")[0][1:]
data=data[data.find(" ")+1:]
command = data.split(" ")[0]
data=data[data.find(" ")+1:]
if(data[0]==":"):
# no args
trailing = data[1:].strip()
else:
trailing = data[data.find(" :")+2:].strip()
data = data[:data.find(" :")]
args = data.split(" ")
for index,arg in enumerate(args):
args[index]=arg.strip()
if not command in self.hookcalls:
self.log.warning("Unknown command: cmd='%s' prefix='%s' args='%s' trailing='%s'" % (command, prefix, args, trailing))
else:
self.fire_hook(command, args=args, prefix=prefix, trailing=trailing)
" Module related code "
def initHooks(self):
self.hooks = [
'_CONNECT', # Called when the bot connects to IRC on the socket level
'NOTICE', # :irc.129irc.com NOTICE AUTH :*** Looking up your hostname...
'MODE', # :CloneABCD MODE CloneABCD :+iwx
'PING', # PING :irc.129irc.com
'JOIN', # :CloneA!dave@hidden-B4F6B1AA.rit.edu JOIN :#clonea
'QUIT', # :HCSMPBot!~HCSMPBot@108.170.48.18 QUIT :Quit: Disconnecting!
'NICK', # :foxiAway!foxi@irc.hcsmp.com NICK :foxi
'PART', # :CloneA!dave@hidden-B4F6B1AA.rit.edu PART #clonea
'PRIVMSG', # :CloneA!dave@hidden-B4F6B1AA.rit.edu PRIVMSG #clonea :aaa
'KICK', # :xMopxShell!~rduser@host KICK #xMopx2 xBotxShellTest :xBotxShellTest
'INVITE', # :gmx!~gmxgeek@irc.hcsmp.com INVITE Tyrone :#hcsmp'
'001', # :irc.129irc.com 001 CloneABCD :Welcome to the 129irc IRC Network CloneABCD!CloneABCD@djptwc-laptop1.rit.edu
'002', # :irc.129irc.com 002 CloneABCD :Your host is irc.129irc.com, running version Unreal3.2.8.1
'003', # :irc.129irc.com 003 CloneABCD :This server was created Mon Jul 19 2010 at 03:12:01 EDT
'004', # :irc.129irc.com 004 CloneABCD irc.129irc.com Unreal3.2.8.1 iowghraAsORTVSxNCWqBzvdHtGp lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGj
'005', # :irc.129irc.com 005 CloneABCD CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=10 CHANLIMIT=#:10 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server
'250', # :chaos.esper.net 250 xBotxShellTest :Highest connection count: 1633 (1632 clients) (186588 connections received)
'251', # :irc.129irc.com 251 CloneABCD :There are 1 users and 48 invisible on 2 servers
'252', # :irc.129irc.com 252 CloneABCD 9 :operator(s) online
'254', # :irc.129irc.com 254 CloneABCD 6 :channels formed
'255', # :irc.129irc.com 255 CloneABCD :I have 42 clients and 1 servers
'265', # :irc.129irc.com 265 CloneABCD :Current Local Users: 42 Max: 47
'266', # :irc.129irc.com 266 CloneABCD :Current Global Users: 49 Max: 53
'332', # :chaos.esper.net 332 xBotxShellTest #xMopx2 :/ #XMOPX2 / https://code.google.com/p/pyircbot/ (Channel Topic)
'333', # :chaos.esper.net 333 xBotxShellTest #xMopx2 xMopxShell!~rduser@108.170.60.242 1344370109
'353', # :irc.129irc.com 353 CloneABCD = #clonea :CloneABCD CloneABC
'366', # :irc.129irc.com 366 CloneABCD #clonea :End of /NAMES list.
'372', # :chaos.esper.net 372 xBotxShell :motd text here
'375', # :chaos.esper.net 375 xBotxShellTest :- chaos.esper.net Message of the Day -
'376', # :chaos.esper.net 376 xBotxShell :End of /MOTD command.
'422', # :irc.129irc.com 422 CloneABCD :MOTD File is missing
'433', # :nova.esper.net 433 * pyircbot3 :Nickname is already in use.
]
" mapping of hooks to methods "
self.hookcalls = {}
for command in self.hooks:
self.hookcalls[command]=[]
def fire_hook(self, command, args=None, prefix=None, trailing=None):
for hook in self.hookcalls[command]:
try:
hook(args, prefix, trailing)
except:
self.log.warning("Error processing hook: \n%s"% self.trace())
def initModules(self):
" load modules specified in config "
" storage of imported modules "
self.modules = {}
" instances of modules "
self.moduleInstances = {}
" append module location to path "
sys.path.append(self.coreconfig["moduledir"])
" append bot directory to path "
sys.path.append(self.coreconfig["botdir"]+"core/")
for modulename in self.botconfig["modules"]:
self.loadmodule(modulename)
def importmodule(self, name):
" import a module by name "
" check if already exists "
if not name in self.modules:
" attempt to load "
try:
moduleref = __import__(name)
self.modules[name]=moduleref
return (True, None)
except Exception as e:
" on failure (usually syntax error in Module code) print an error "
self.log.error("Module %s failed to load: " % name)
self.log.error("Module load failure reason: " + str(e))
return (False, str(e))
else:
self.log.warning("Module %s already imported" % name)
return (False, "Module already imported")
def deportmodule(self, name):
" remove a module from memory by name "
" unload if necessary "
if name in self.moduleInstances:
self.unloadmodule(name)
" delete all references to the module"
if name in self.modules:
item = self.modules[name]
del self.modules[name]
del item
" delete copy that python stores in sys.modules "
if name in sys.modules:
del sys.modules[name]
def loadmodule(self, name):
" load a module and activate it "
" check if already loaded "
if name in self.moduleInstances:
self.log.warning( "Module %s already loaded" % name )
return False
" check if needs to be imported, and verify it was "
if not name in self.modules:
importResult = self.importmodule(name)
if not importResult[0]:
return importResult
" init the module "
self.moduleInstances[name] = getattr(self.modules[name], name)(self, name)
" load hooks "
self.loadModuleHooks(self.moduleInstances[name])
def unloadmodule(self, name):
" unload a module by name "
if name in self.moduleInstances:
" notify the module of disabling "
self.moduleInstances[name].ondisable()
" unload all hooks "
self.unloadModuleHooks(self.moduleInstances[name])
" remove the instance "
item = self.moduleInstances.pop(name)
" delete the instance"
del item
self.log.info( "Module %s unloaded" % name )
return (True, None)
else:
self.log.info("Module %s not loaded" % name)
return (False, "Module not loaded")
def reloadmodule(self, name):
" unload then load a module by name "
" make sure it's imporeted"
if name in self.modules:
" remember if it was loaded before"
loadedbefore = name in self.moduleInstances
self.log.info("Reloading %s" % self.modules[name])
" unload "
self.unloadmodule(name)
" load "
if loadedbefore:
self.loadmodule(name)
return (True, None)
return (False, "Module is not loaded")
def redomodule(self, name):
" reload a modules code from disk "
" remember if it was loaded before "
loadedbefore = name in self.moduleInstances
" unload/deport "
self.deportmodule(name)
" import "
importResult = self.importmodule(name)
if not importResult[0]:
return importResult
" load "
if loadedbefore:
self.loadmodule(name)
return (True, None)
def loadModuleHooks(self, module):
" activate a module's hooks "
for hook in module.hooks:
self.addHook(hook.hook, hook.method)
def unloadModuleHooks(self, module):
" remove a modules hooks "
for hook in module.hooks:
self.removeHook(hook.hook, hook.method)
def addHook(self, command, method):
" add a single hook "
if command in self.hooks:
self.hookcalls[command].append(method)
else:
self.log.warning("Invalid hook - %s" % command)
return False
def removeHook(self, command, method):
" remove a single hook "
if command in self.hooks:
for hookedMethod in self.hookcalls[command]:
if hookedMethod == method:
self.hookcalls[command].remove(hookedMethod)
else:
self.log.warning("Invalid hook - %s" % command)
return False
def getmodulebyname(self, name):
" return a module specified by the name "
if not name in self.moduleInstances:
return None
return self.moduleInstances[name]
def getmodulesbyservice(self, service):
" get a list of modules that provide the specified service "
validModules = []
for module in self.moduleInstances:
if service in self.moduleInstances[module].services:
validModules.append(self.moduleInstances[module])
return validModules
def getBestModuleForService(self, service):
m = self.getmodulesbyservice(service)
if len(m)>0:
return m[0]
return None
def closeAllModules(self):
" 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()
for key in loadOrder:
if key in loaded:
loaded.remove(key)
self.deportmodule(key)
for key in loaded:
self.deportmodule(key)
" Filesystem Methods "
def getDataPath(self, moduleName):
return "%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName)
def getConfigPath(self, moduleName):
return "%s/config/%s.yml" % (self.botconfig["bot"]["datadir"], moduleName)
" Utility methods "
@staticmethod
def decodePrefix(prefix):
" Returns an object with nick, username, hostname attributes"
if "!" in prefix:
ob = type('UserPrefix', (object,), {})
ob.nick, prefix = prefix.split("!")
ob.username, ob.hostname = prefix.split("@")
return ob
else:
ob = type('ServerPrefix', (object,), {})
ob.hostname = prefix
return ob
@staticmethod
def trace():
return traceback.format_exc()
@staticmethod
def messageHasCommand(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:]
if requireArgs and args.strip() == '':
return False
# Verified! Return the set.
ob = type('ParsedCommand', (object,), {})
ob.command = command
ob.args = [] if args=="" else args.split(" ")
ob.args_str = args
ob.message = message
return ob
# return (True, command, args, message)
" Data Methods "
def get_nick(self):
return self.config["nick"]
" Action Methods "
def act_PONG(self, data):
self.sendRaw("PONG :%s" % data)
def act_USER(self, username, hostname, realname):
self.sendRaw("USER %s %s %s :%s" % (username, hostname, self.botconfig["connection"]["server"], realname))
def act_NICK(self, newNick):
self.sendRaw("NICK %s" % newNick)
def act_JOIN(self, channel):
self.sendRaw("JOIN %s"%channel)
def act_PRIVMSG(self, towho, message):
self.sendRaw("PRIVMSG %s :%s"%(towho,message))
def act_MODE(self, channel, mode, extra=None):
if extra != None:
self.sendRaw("MODE %s %s %s" % (channel,mode,extra))
else:
self.sendRaw("MODE %s %s" % (channel,mode))
def act_ACTION(self, channel, action):
self.sendRaw("PRIVMSG %s :\x01ACTION %s"%(channel,action))
def act_KICK(self, channel, who, comment):
self.sendRaw("KICK %s %s :%s" % (channel, who, comment))

52
pyircbot/core/rpc.py Normal file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
import traceback
import logging
from core import jsonrpc
from threading import Thread
class BotRPC(Thread):
def __init__(self, main):
Thread.__init__(self)
self.bot = main
self.log = logging.getLogger('RPC')
self.server = jsonrpc.Server(jsonrpc.JsonRpc20(), jsonrpc.TransportTcpIp(addr=(self.bot.botconfig["bot"]["rpcbind"], self.bot.botconfig["bot"]["rpcport"])))
self.server.register_function( self.importModule )
self.server.register_function( self.deportModule )
self.server.register_function( self.loadModule )
self.server.register_function( self.unloadModule )
self.server.register_function( self.reloadModule )
self.server.register_function( self.redoModule )
self.server.register_function( self.getTraceback )
self.start()
def run(self):
self.server.serve()
def importModule(self, moduleName):
return self.bot.importmodule(moduleName)
def deportModule(self, moduleName):
self.bot.deportmodule(moduleName)
def loadModule(self, moduleName):
return self.bot.loadmodule(moduleName)
def unloadModule(self, moduleName):
self.bot.unloadmodule(moduleName)
def reloadModule(self, moduleName):
self.bot.unloadmodule(moduleName)
return self.bot.loadmodule(moduleName)
def redoModule(self, moduleName):
return self.bot.redomodule(moduleName)
def getTraceback(self):
tb = str(traceback.format_exc())
print(tb)
return tb

42
pyircbot/main.py Normal file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
import os
import sys
import logging
import yaml
import asyncore
from optparse import OptionParser
from core.pyircbot import PyIRCBot
if __name__ == "__main__":
" logging level and facility "
logging.basicConfig(level=logging.DEBUG, format="%(asctime)-15s %(levelname)-8s %(message)s")
log = logging.getLogger('main')
" parse command line args "
parser = OptionParser()
parser.add_option("-c", "--config", action="store", type="string", dest="config", help="Path to core config file")
parser.add_option("-b", "--bot", action="store", type="string", dest="bot", help="Path to bot config file")
(options, args) = parser.parse_args()
log.debug(options)
if not options.config:
log.critical("No core config file specified (-c). Exiting.")
sys.exit(0)
if not options.bot:
log.critical("No bot config file specified (-b). Exiting.")
sys.exit(0)
coreconfig = yaml.load(open(options.config, 'r'))
botconfig = yaml.load(open(options.bot, 'r'))
log.debug(coreconfig)
log.debug(botconfig)
bot = PyIRCBot(coreconfig, botconfig)
try:
asyncore.loop()
except KeyboardInterrupt:
bot.kill()

View File

@ -0,0 +1,139 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
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")
if len(serviceProviders)==0:
self.log.error("AttributeStorage: Could not find a valid mysql service provider")
else:
self.log.info("AttributeStorage: Selecting mysql service provider: %s" % serviceProviders[0])
self.db = serviceProviders[0]
if not self.db.connection.tableExists("attribute"):
self.log.info("AttributeStorage: Creating table: attribute")
c = self.db.connection.query("""CREATE TABLE IF NOT EXISTS `attribute` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`attribute` varchar(128) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `attribute` (`attribute`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;""")
c.close()
if not self.db.connection.tableExists("items"):
self.log.info("AttributeStorage: Creating table: items")
c = self.db.connection.query("""CREATE TABLE IF NOT EXISTS `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item` varchar(512) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;""")
c.close()
if not self.db.connection.tableExists("values"):
self.log.info("AttributeStorage: Creating table: values")
c = self.db.connection.query("""CREATE TABLE IF NOT EXISTS `values` (
`itemid` int(11) NOT NULL,
`attributeid` int(11) NOT NULL,
`value` varchar(512) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`itemid`,`attributeid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;""")
c.close()
# self.getItem('xMopxShell', 'name')
# self.getAttribute('xMopxShell', 'name')
# self.setAttribute('xMopxShell', 'name', 'dave')
def getItem(self, name):
c = self.db.connection.query("""SELECT
`i`.`id`,
`i`.`item`,
`a`.`attribute`,
`v`.`value`
FROM
`items` `i`
INNER JOIN `values` `v`
on `v`.`itemid`=`i`.`id`
INNER JOIN `attribute` `a`
on `a`.`id`=`v`.`attributeid`
WHERE
`i`.`item`=%s;""",
(name,)
)
item = {}
while True:
row = c.fetchone()
if row == None:
break
item[row["attribute"]]=row["value"]
c.close()
if len(item)==0:
return {}
return item
def getAttribute(self, item, attribute):
c = self.db.connection.query("""SELECT
`i`.`id`,
`i`.`item`,
`a`.`attribute`,
`v`.`value`
FROM
`items` `i`
INNER JOIN `values` `v`
on `v`.`itemid`=`i`.`id`
INNER JOIN `attribute` `a`
on `a`.`id`=`v`.`attributeid`
WHERE
`i`.`item`=%s
AND
`a`.`attribute`=%s;""",
(item,attribute)
)
row = c.fetchone()
c.close()
if row == None:
return None
return row["value"]
def setAttribute(self, item, attribute, value):
item = item.lower()
attribute = attribute.lower()
# Check attribute exists
c = self.db.connection.query("SELECT `id` FROM `attribute` WHERE `attribute`=%s;", (attribute))
row = c.fetchone()
attributeId = -1
if row == None:
c = self.db.connection.query("INSERT INTO `attribute` (`attribute`) VALUES (%s);", (attribute))
attributeId = c.lastrowid
else:
attributeId = row["id"]
c.close()
# check item exists
c = self.db.connection.query("SELECT `id` FROM `items` WHERE `item`=%s;", (item))
row = c.fetchone()
itemId = -1
if row == None:
c = self.db.connection.query("INSERT INTO `items` (`item`) VALUES (%s);", (item))
itemId = c.lastrowid
else:
itemId = row["id"]
c.close()
if value == None:
# delete it
c = self.db.connection.query("DELETE FROM `values` WHERE `itemid`=%s AND `attributeid`=%s ;", (itemId, attributeId))
self.log.debug("AttributeStorage: Stored item %s attribute %s value: %s (Deleted)" % (itemId, attributeId, value))
else:
# add attribute
c = self.db.connection.query("REPLACE INTO `values` (`itemid`, `attributeid`, `value`) VALUES (%s, %s, %s);", (itemId, attributeId, value))
self.log.debug("AttributeStorage: Stored item %s attribute %s value: %s" % (itemId, attributeId, value))
c.close()

View File

@ -0,0 +1,315 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
import random
import yaml
import os
import time
import math
import hashlib
from threading import Timer
class DogeDice(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[ModuleHook("PRIVMSG", self.gotMsg)]
self.loadConfig()
# Load attribute storage
self.attr = self.bot.getBestModuleForService("attributes")
# Load doge RPC
self.doge = self.bot.getBestModuleForService("dogerpc")
# Dict of #channel -> game object
self.games = {}
def gotMsg(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
# Ignore messages from users not logged in
loggedinfrom = self.attr.getAttribute(prefixObj.nick, "loggedinfrom")
if loggedinfrom==None:
# Send them a hint?
return
elif prefixObj.hostname == loggedinfrom:
if args[0][0] == "#":
# create a blank game obj if there isn't one (and whitelisted ? )
if not args[0] in self.games and (not self.config["channelWhitelistOn"] or (self.config["channelWhitelistOn"] and args[0][1:] in self.config["channelWhitelist"]) ):
self.games[args[0]]=gameObj(self, args[0])
# Channel message
self.games[args[0]].gotMsg(args, prefix, trailing)
else:
# Private message
#self.games[args[0].gotPrivMsg(args, prefix, trailing)
pass
else:
# Ignore potential spoofing
pass
def removeGame(self, channel):
del self.games[channel]
def ondisable(self):
self.log.info("DogeDice: Unload requested, ending games...")
while len(self.games)>0:
first = list(self.games.keys())[0]
self.games[first].gameover()
class gameObj:
def __init__(self, master, channel):
self.master = master
self.channel = channel
# Game state
# 0 = waiting for players
# - advertise self?
# - players must be registered and have enough doge for current bet
# 1 = enough players, countdown
# - Last warning to pull out
# 2 = locked in / game setup
# - Move doge from player's wallets to table wallet. kick players if they can't afford
# 3 = start of a round
# - Each player's turn to roll
# 4 = determine winner, move doge
# - if > 10 doge, house fee?
self.step = 0
# Bet amount
self.bet = 0.0
# players list
self.players = []
# min players
self.minPlayers = 2
# max players
self.maxPlayers = 4
# Lobby countdown timer
self.startCountdownTimer = None
# pre-result timer
self.endgameResultTimer = None
# in-game timeout
self.playTimeout = None
# Wallet for this game
self.walletName = None
def getPlayer(self, nick):
for player in self.players:
if player.nick == nick:
return player
return None
def gotPrivMsg(self, args, prefix, trailing):
prefix = self.master.bot.decodePrefix(prefix)
pass
def gotMsg(self, args, prefix, trailing):
prefix = self.master.bot.decodePrefix(prefix)
if self.step == 0 or self.step == 1:
# Join game
cmd = self.master.bot.messageHasCommand(".join", trailing)
if cmd:
if len(self.players)-1 < self.maxPlayers:
if self.getPlayer(prefix.nick)==None:
userWallet = self.master.attr.getAttribute(prefix.nick, "dogeaccountname")
if userWallet == None:
self.master.bot.act_PRIVMSG(self.channel, "%s: You don't have enough DOGE!" % (prefix.nick))
return
balance = self.master.doge.getBal(userWallet)
# check if the room is 'opened' already:
if len(self.players)==0:
# require an amount
if len(cmd.args)==1:
# Check if they have enough coins
try:
bet = float(cmd.args[0])
except:
return
if bet < self.master.config["minBet"]:
self.master.bot.act_PRIVMSG(self.channel, "%s: Minimum bet is %s DOGE!" % (prefix.nick, self.master.config["minBet"]))
return
if balance>=bet:
newPlayer = playerObj(self, prefix.nick)
newPlayer.dogeWalletName = userWallet
self.players.append(newPlayer)
self.bet = bet
self.master.bot.act_PRIVMSG(self.channel, "%s: You have joined!" % (prefix.nick))
else:
self.master.bot.act_PRIVMSG(self.channel, "%s: You don't have enough DOGE!" % (prefix.nick))
else:
self.master.bot.act_PRIVMSG(self.channel, "%s: You need to specify a bet amount: .join 10" % (prefix.nick))
else:
# no amount required
if balance>=self.bet:
newPlayer = playerObj(self, prefix.nick)
newPlayer.dogeWalletName = userWallet
self.players.append(newPlayer)
self.master.bot.act_PRIVMSG(self.channel, "%s: You have joined!" % (prefix.nick))
if self.canStart() and self.startCountdownTimer == None:
self.initStartCountdown()
self.master.bot.act_PRIVMSG(self.channel, "The game will start in %s seconds! Bet is %s DOGE each!" % (self.master.config["lobbyIdleSeconds"], self.bet))
else:
self.master.bot.act_PRIVMSG(self.channel, "%s: You don't have enough DOGE!" % (prefix.nick))
else:
self.master.bot.act_PRIVMSG(self.channel, "%s: you're already in the game. Quit with .leave" % (prefix.nick))
else:
self.master.bot.act_PRIVMSG(self.channel, "%s: the game is full (%s/%)! Cannot join." % (prefix.nick, len(self.players), self.maxPlayers))
# Leave game
cmd = self.master.bot.messageHasCommand(".leave", trailing)
if cmd:
if self.getPlayer(prefix.nick)==None:
self.master.bot.act_PRIVMSG(self.channel, "%s: You're not in the game." % (prefix.nick))
else:
self.removePlayer(prefix.nick)
self.master.bot.act_PRIVMSG(self.channel, "%s: You have left the game!" % (prefix.nick))
if not self.canStart() and self.startCountdownTimer:
self.clearTimer(self.startCountdownTimer)
self.startCountdownTimer = None
self.master.bot.act_PRIVMSG(self.channel, "Game start aborted." )
self.step = 0
elif self.step == 2:
pass
elif self.step == 3:
# Ignore cmds from people outside the game
player = self.getPlayer(prefix.nick)
if not player:
return
# handle a .roll
cmd = self.master.bot.messageHasCommand(".roll", trailing)
if cmd and not player.hasRolled:
roll1 = random.randint(1,6)
roll2 = random.randint(1,6)
self.master.bot.act_PRIVMSG(self.channel, "%s rolls %s and %s!" % (prefix.nick, roll1, roll2))
player.hasRolled = True
player.rollValue = roll1+roll2
# Check if all players have rolled
for player in self.players:
if not player.hasRolled:
return
# start endgame timer
self.step = 4
self.endgameResultTimer = Timer(2, self.endgameResults)
self.endgameResultTimer.start()
elif self.step == 4:
pass
#senderIsOp = self.master.attr.getAttribute(prefix.nick, "op")=="yes"
def clearTimer(self, timer):
if timer:
timer.cancel()
timer = None
def removePlayer(self, playerNick):
pos = -1
for i in range(0, len(self.players)):
if self.players[i].nick == playerNick:
pos = i
break
if pos >= 0:
self.players.pop(pos)
def canStart(self):
# Return true if the step is 'lobby' mode and player count is OK
return self.step == 0 and len(self.players)>=self.minPlayers
def initStartCountdown(self):
# Start the game-start countdown
self.startCountdownTimer = Timer(self.master.config["lobbyIdleSeconds"], self.lobbyCountdownDone)
self.startCountdownTimer.start()
self.step = 1
def lobbyCountdownDone(self):
self.step = 2
self.master.bot.act_PRIVMSG(self.channel, "Collecting DOGE and starting game.. Type .roll !")
# Make a wallet for this game
self.walletName = "DogeDice-"+self.channel
# Generate an address to 'create' a wallet
self.master.doge.getAcctAddr(self.walletName)
# Verify and move funds from each player
for player in self.players:
playerBalance = self.master.doge.getAcctBal(player.dogeWalletName)
if playerBalance < self.bet:
self.master.bot.act_PRIVMSG(self.channel, "%s was dropped from the game!")
self.removePlayer(player.nick)
if len(self.players) <= 1:
self.master.bot.act_PRIVMSG(self.channel, "1 or players left - game over!")
self.resetGame()
return
# Take doges
for player in self.players:
self.master.doge.move(player.dogeWalletName, self.walletName, self.bet)
# Pre-game setup (nothing !)
# Accept game commands
self.step = 3
# Start play timeout
self.playTimeout = Timer(30, self.gamePlayTimeoutExpired)
self.playTimeout.start()
def gamePlayTimeoutExpired(self):
# Time out - return doges
self.master.bot.act_PRIVMSG(self.channel, "Time expired! Returning all doges.")
if self.step == 3:
# In game step. Refund doges
for player in self.players:
self.master.doge.move(self.walletName, player.dogeWalletName, self.bet)
self.resetGame()
def endgameResults(self):
maxRollNames = []
maxRollValue = 0
for player in self.players:
if player.rollValue > maxRollValue:
maxRollNames = []
maxRollNames.append(player.nick)
maxRollValue = player.rollValue
if player.rollValue == maxRollValue:
if not player.nick in maxRollNames:
maxRollNames.append(player.nick)
pot = self.master.doge.getAcctBal(self.walletName)
DOGEeachDec = pot/len(maxRollNames)
DOGEeach = math.floor(DOGEeachDec*100000000) / 100000000
if len(maxRollNames)==1:
self.master.bot.act_PRIVMSG(self.channel, "We have a winner - %s! Winnings are: %s DOGE" % (maxRollNames[0], DOGEeach))
else:
self.master.bot.act_PRIVMSG(self.channel, "We have a tie between %s - The take is %s DOGE each" % (' and '.join(maxRollNames), DOGEeach))
# Pay out
for nick in maxRollNames:
player = self.getPlayer(nick)
self.master.doge.move(self.walletName, player.dogeWalletName, DOGEeach)
# the end!
self.resetGame()
def resetGame(self):
self.clearTimer(self.startCountdownTimer)
self.startCountdownTimer = None
self.clearTimer(self.endgameResultTimer)
self.endgameResultTimer = None
self.clearTimer(self.playTimeout)
self.playTimeout = None
self.master.removeGame(self.channel)
def gameover(self):
self.gamePlayTimeoutExpired()
class playerObj:
def __init__(self, game, nick):
self.game = game
self.nick = nick
# Save the player's wallet name
self.dogeWalletName = None
# Set to true after they roll
self.hasRolled = False
# Sum of their dice
self.rollValue = None

View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
from bitcoinrpc.authproxy import AuthServiceProxy
class DogeRPC(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[]
self.services=["dogerpc"]
self.loadConfig()
self.rpc = DogeController(self)
def getBal(self, acct):
" get a balance of an address or an account "
return self.getAcctBal(acct)
def getAcctAddr(self, acct):
" returns the address for an account. creates if necessary "
self.rpc.ping()
addrs = self.rpc.con.getaddressesbyaccount(acct)
if len(addrs)==0:
return self.rpc.con.getnewaddress(acct)
return addrs[0]
def getAcctBal(self, acct):
" returns an account's balance"
self.rpc.ping()
return float(self.rpc.con.getbalance(acct))
def canMove(self, fromAcct, toAcct, amount):
" true or false if fromAcct can afford to give toAcct an amount of coins "
balfrom = self.getAcctBal(fromAcct)
return balfrom >= amount
def move(self, fromAcct, toAcct, amount):
" move coins from one account to another "
self.rpc.ping()
if self.canMove(fromAcct, toAcct, amount):
return self.rpc.con.move(fromAcct, toAcct, amount)
return False
def send(self, fromAcct, toAddr, amount):
" send coins to an external addr "
self.rpc.ping()
if self.canMove(fromAcct, toAddr, amount):
return self.rpc.con.sendfrom(fromAcct, toAddr, amount)
return False
class DogeController:
def __init__(self, master):
self.config = master.config
self.log = master.log
self.con = None
self.ping()
def ping(self):
try:
self.con.getinfo()
except:
self.connect()
def connect(self):
self.log.debug("DogeRPC: Connecting to dogecoind")
self.con = AuthServiceProxy("http://%s:%s@%s:%s" % (self.config["username"], self.config["password"], self.config["host"], self.config["port"]))
self.con.getinfo()
self.log.debug("DogeRPC: Connected to %s:%s" % (self.config["host"], self.config["port"]))

View File

@ -0,0 +1,258 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
import random
import yaml
import os
import time
from threading import Timer
class DogeScramble(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[ModuleHook("PRIVMSG", self.scramble)]
self.loadConfig()
# Load attribute storage
self.attr = None
serviceProviders = self.bot.getmodulesbyservice("attributes")
if len(serviceProviders)==0:
self.log.error("DogeScramble: Could not find a valid attributes service provider")
else:
self.log.info("DogeScramble: Selecting attributes service provider: %s" % serviceProviders[0])
self.attr = serviceProviders[0]
# Load doge RPC
self.doge = self.bot.getBestModuleForService("dogerpc")
# Per channel games
self.games = {}
def scramble(self, args, prefix, trailing):
channel = args[0]
if channel[0] == "#":
# Ignore messages from users without a dogewallet password
prefixObj = self.bot.decodePrefix(prefix)
if self.attr.getAttribute(prefixObj.nick, "password")==None:
return
if not channel in self.games:
self.games[channel]=scrambleGame(self, channel)
self.games[channel].scramble(args, prefix, trailing)
def ondisable(self):
self.log.info("DogeScramble: Unload requested, ending games...")
for game in self.games:
self.games[game].gameover()
class scrambleGame:
def __init__(self, master, channel):
self.master = master
self.channel = channel
# Running?
self.running = False
# Current word
self.currentWord = None
# Current word, scrambled
self.scrambled = None
# Online?
self.scrambleOn = False
# Count down to hints
self.hintTimer = None
# of hints given
self.hintsGiven = 0
# Cooldown between words
self.nextTimer = None
# How many guesses submitted this round
self.guesses = 0;
# How many games in a row where nobody guessed
self.gamesWithoutGuesses = 0;
# What file are we using
self.category_file = None;
# How many words in this category have been used?
self.category_count = 0
# How long between categories
self.change_category_after_words = self.master.config["categoryduration"]
# Should we change categories at the next pick?
self.should_change_category = True
# Holds the processed category name
self.category_name = None
# list of last picked words
self.lastwords = []
# name of last winner for decreasing return
self.lastwinner = None
self.lastwinvalue = 0
self.delayHint = self.master.config["hintDelay"];
self.delayNext = self.master.config["delayNext"];
self.maxHints = self.master.config["maxHints"];
self.abortAfterNoGuesses = self.master.config["abortAfterNoGuesses"];
def gameover(self):
self.clearTimers();
self.running = False
def clearTimers(self):
self.clearTimer(self.nextTimer)
self.clearTimer(self.hintTimer)
def clearTimer(self, timer):
if timer:
timer.cancel()
def scramble(self, args, prefix, trailing):
prefix = self.master.bot.decodePrefix(prefix)
sender = prefix.nick
senderIsOp = self.master.attr.getAttribute(prefix.nick, "op")=="yes"
cmd = self.master.bot.messageHasCommand(".scramble", trailing)
if cmd and not self.running:
#and senderIsOp
self.running = True
self.startScramble()
return
cmd = self.master.bot.messageHasCommand(".scrambleoff", trailing)
if cmd and senderIsOp and self.running:
self.gameover()
self.running = False
return
if self.currentWord and trailing.strip().lower() == self.currentWord:
# Get winner withdraw address
useraddr = self.master.attr.getAttribute(prefix.nick, "dogeaddr")
userwallet = self.master.attr.getAttribute(prefix.nick, "dogeaccountname")
self.master.bot.act_PRIVMSG(self.channel, "%s got the word - %s!" % (sender, self.currentWord))
if not useraddr:
self.master.bot.act_PRIVMSG(self.channel, "%s: to win DOGE, you must set an wallet address by PMing me \".setdogeaddr\". Next word in %s seconds." % (prefix.nick, self.delayNext))
else:
winamount = float(self.master.config["winAmount"])
if self.lastwinner == prefix.nick:
winamount = self.lastwinvalue * self.master.config["decreaseFactor"]
self.lastwinvalue = winamount
self.lastwinner = prefix.nick
self.master.bot.act_PRIVMSG(self.channel, "%s won %s DOGE! Next word in %s seconds." % (prefix.nick, round(winamount, 8), self.delayNext))
self.master.doge.move('', userwallet, winamount)
self.currentWord = None
self.clearTimers()
self.hintsGiven = 0
self.nextTimer = Timer(self.delayNext, self.startNewWord)
self.nextTimer.start()
self.guesses=0
self.category_count+=1
self.master.log.debug("DogeScramble: category_count is: %s" % (self.category_count))
if self.category_count >= self.change_category_after_words:
self.should_change_category = True
else:
self.guesses+=1
def startScramble(self):
self.clearTimer(self.nextTimer)
self.nextTimer = Timer(0, self.startNewWord)
self.nextTimer.start()
def startNewWord(self):
self.currentWord = self.pickWord()
self.scrambled = self.scrambleWord(self.currentWord)
self.master.bot.act_PRIVMSG(self.channel, "[Category: %s] Unscramble this: %s " % (self.category_name, self.scrambled))
self.clearTimer(self.hintTimer)
self.hintTimer = Timer(self.delayHint, self.giveHint)
self.hintTimer.start()
def giveHint(self):
self.hintsGiven+=1
if self.hintsGiven>=len(self.currentWord) or self.hintsGiven > self.maxHints:
self.abortWord()
return
blanks = ""
for letter in list(self.currentWord):
if letter == " ":
blanks+=" "
else:
blanks+="_"
partFromWord = self.currentWord[0:self.hintsGiven]
partFromBlanks = blanks[self.hintsGiven:]
hintstr = partFromWord+partFromBlanks
self.master.bot.act_PRIVMSG(self.channel, "Hint: - %s" % (hintstr))
self.clearTimer(self.hintTimer)
self.hintTimer = Timer(self.delayHint, self.giveHint)
self.hintTimer.start()
def abortWord(self):
cur = self.currentWord
self.currentWord = None
self.master.bot.act_PRIVMSG(self.channel, "Word expired - the answer was '%s'. Next word in %s seconds." % (cur, self.delayNext))
self.hintsGiven = 0
self.clearTimer(self.nextTimer)
if self.guesses==0:
self.gamesWithoutGuesses+=1
if self.gamesWithoutGuesses >= self.abortAfterNoGuesses:
self.master.bot.act_PRIVMSG(self.channel, "No one seems to be playing - type .scramble to start again.")
self.gameover()
return
else:
self.gamesWithoutGuesses=0
self.nextTimer = Timer(self.delayNext, self.startNewWord)
self.nextTimer.start()
def catFileNameToStr(self, s):
s=s.split(".")[0]
s=s.replace("_", " ")
return s.title()
def pickWord(self):
if self.should_change_category:
# clear flags
self.should_change_category = False
self.category_count = 0
# Get the path to word files dir
dirpath = self.master.getFilePath("")
# List dir
files = os.listdir(dirpath)
# choose a random file
random.shuffle(files)
self.category_file = files[0]
self.category_name = self.catFileNameToStr(self.category_file)
# Process the name & announce
self.master.bot.act_PRIVMSG(self.channel, "The category is now: %s " % self.category_name)
# count lines
f = open(self.master.getFilePath(self.category_file), "r")
lines = 0
while True:
lines+=1
if f.readline() == "":
break
f.close()
# change category
picked = ""
while picked == "" or picked in self.lastwords:
skip = random.randint(0, lines)
f = open(self.master.getFilePath(self.category_file), "r")
while skip>=0:
f.readline()
skip-=1
picked = f.readline().strip().lower()
f.close()
self.master.log.debug("DogeScramble: picked %s for %s" % (picked, self.channel))
self.lastwords.append(picked)
if len(self.lastwords) > 5:
self.lastwords.pop(0)
return picked
def scrambleWord(self, word):
scrambled = ""
for subword in word.split(" "):
scrambled+=self.scrambleIndividualWord(subword)+ " "
return scrambled.strip()
def scrambleIndividualWord(self, word):
scrambled = list(word)
random.shuffle(scrambled)
return ''.join(scrambled).lower()

View File

@ -0,0 +1,164 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
import time
import hashlib
class DogeWallet(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[ModuleHook("PRIVMSG", self.gotmsg)]
# Load attribute storage
self.attr = self.bot.getBestModuleForService("attributes")
# Load doge RPC
self.doge = self.bot.getBestModuleForService("dogerpc")
def gotmsg(self, args, prefix, trailing):
channel = args[0]
if channel[0] == "#":
# Ignore channel messages
pass
else:
self.handlePm(args, prefix, trailing)
def handlePm(self, args, prefix, trailing):
prefix = self.bot.decodePrefix(prefix)
cmd = self.bot.messageHasCommand(".setpass", trailing)
if cmd:
if len(cmd.args)==0:
self.bot.act_PRIVMSG(prefix.nick, ".setpass: usage: \".setpass newpass\" or \".setpass oldpass newpass\"")
else:
oldpass = self.attr.getAttribute(prefix.nick, "password")
if oldpass == None:
self.attr.setAttribute(prefix.nick, "password", cmd.args[0])
self.bot.act_PRIVMSG(prefix.nick, ".setpass: Your password has been set to \"%s\"." % cmd.args[0])
else:
if len(cmd.args)==2:
if cmd.args[0] == oldpass:
self.attr.setAttribute(prefix.nick, "password", cmd.args[1])
self.bot.act_PRIVMSG(prefix.nick, ".setpass: Your password has been set to \"%s\"." % cmd.args[1])
else:
self.bot.act_PRIVMSG(prefix.nick, ".setpass: Old password incorrect.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".setpass: You must provide the old password when setting a new one.")
cmd = self.bot.messageHasCommand(".setdogeaddr", trailing)
if cmd:
userpw = self.attr.getAttribute(prefix.nick, "password")
if userpw==None:
self.bot.act_PRIVMSG(prefix.nick, ".setdogeaddr: You must first set a password with .setpass")
else:
if len(cmd.args)==2:
if userpw == cmd.args[0]:
self.attr.setAttribute(prefix.nick, "dogeaddr", cmd.args[1])
self.bot.act_PRIVMSG(prefix.nick, ".setdogeaddr: Your doge address has been set to \"%s\"." % cmd.args[1])
# if they don't have a wallet name, we'll make one now
if self.attr.getAttribute(prefix.nick, "dogeaccountname")==None:
randName = self.md5(str(time.time()))[0:10]
self.attr.setAttribute(prefix.nick, "dogeaccountname", randName)
else:
self.bot.act_PRIVMSG(prefix.nick, ".setdogeaddr: incorrect password.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".setdogeaddr: usage: \".setdogeaddr password address\" or \".setdogeaddr mypassword D8VNy3zkMGspffcFSWWqsxx7GrtVsmF2up\"")
cmd = self.bot.messageHasCommand(".getdogebal", trailing)
if cmd:
userpw = self.attr.getAttribute(prefix.nick, "password")
if userpw==None:
self.bot.act_PRIVMSG(prefix.nick, ".getdogebal: You must first set a password with .setpass")
else:
if len(cmd.args)==1:
if userpw == cmd.args[0]:
#################
walletname = self.attr.getAttribute(prefix.nick, "dogeaccountname")
amount = 0.0
if walletname:
amount = self.doge.getBal(walletname)
self.bot.act_PRIVMSG(prefix.nick, ".getdogebal: Your balance is: %s DOGE" % amount)
#################
else:
self.bot.act_PRIVMSG(prefix.nick, ".getdogebal: incorrect password.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".getdogebal: usage: \".getdogebal password\"")
cmd = self.bot.messageHasCommand(".withdrawdoge", trailing)
if cmd:
userpw = self.attr.getAttribute(prefix.nick, "password")
useraddr = self.attr.getAttribute(prefix.nick, "dogeaddr")
if userpw==None:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: You must first set a password with .setpass")
elif useraddr==None:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: You must first set a withdraw address .setdogeaddr")
else:
if len(cmd.args)==2:
if userpw == cmd.args[0]:
#################
walletname = self.attr.getAttribute(prefix.nick, "dogeaccountname")
walletbal = self.doge.getBal(walletname)
desiredAmount = float(cmd.args[1])
if walletbal >= desiredAmount:
txn = self.doge.send(walletname, useraddr, desiredAmount)
if txn:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: %s DOGE sent to %s. Transaction ID: %s"% (desiredAmount, useraddr, txn))
else:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: Unable to create transaction. Please contact an Operator.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: You only have %s DOGE. You cannot withdraw %s DOGE." % (walletbal, desiredAmount))
#################
else:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: incorrect password.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".withdrawdoge: usage: \".withdrawdoge password amount\" - \".withdrawdoge mypassword 5.0\" - ")
cmd = self.bot.messageHasCommand(".getdepositaddr", trailing)
if cmd:
userpw = self.attr.getAttribute(prefix.nick, "password")
if userpw==None:
self.bot.act_PRIVMSG(prefix.nick, ".getdepositaddr: You must first set a password with .setpass")
else:
if len(cmd.args)==1:
if userpw == cmd.args[0]:
#################
walletname = self.attr.getAttribute(prefix.nick, "dogeaccountname")
addr = self.doge.getAcctAddr(walletname)
self.bot.act_PRIVMSG(prefix.nick, ".getdepositaddr: Your deposit address is: %s" % addr)
#################
else:
self.bot.act_PRIVMSG(prefix.nick, ".getdepositaddr: incorrect password.")
else:
self.bot.act_PRIVMSG(prefix.nick, ".getdepositaddr: usage: \".getdepositaddr password\"")
cmd = self.bot.messageHasCommand(".login", trailing)
if cmd:
userpw = self.attr.getAttribute(prefix.nick, "password")
if userpw==None:
self.bot.act_PRIVMSG(prefix.nick, ".login: You must first set a password with .setpass")
else:
if len(cmd.args)==1:
if userpw == cmd.args[0]:
#################
self.attr.setAttribute(prefix.nick, "loggedinfrom", prefix.hostname)
self.bot.act_PRIVMSG(prefix.nick, ".login: You have been logged in from: %s" % prefix.hostname)
#################
else:
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)
if cmd:
loggedin = self.attr.getAttribute(prefix.nick, "loggedinfrom")
if loggedin == None:
self.bot.act_PRIVMSG(prefix.nick, ".logout: You must first be logged in")
else:
self.attr.setAttribute(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

@ -0,0 +1,65 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
import random
import yaml
import os
import time
from threading import Timer
class GameBase(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[ModuleHook("PRIVMSG", self.gotMsg)]
self.loadConfig()
# Load attribute storage
self.attr = self.bot.getBestModuleForService("attributes")
# Load doge RPC
self.doge = self.bot.getBestModuleForService("dogerpc")
# Dict of #channel -> game object
self.games = {}
def gotMsg(self, args, prefix, trailing):
prefixObj = self.bot.decodePrefix(prefix)
# Ignore messages from users not logged in
if self.attr.getAttribute(prefixObj.nick, "loggedinfrom")==None:
# Send them a hint?
return
else:
if args[0][0] == "#":
# create a blank game obj if there isn't one (and whitelisted ? )
if not args[0] in self.games and (not self.config["channelWhitelistOn"] or (self.config["channelWhitelistOn"] and args[0][1:] in self.config["channelWhitelist"]) ):
self.games[args[0]]=gameObj(self, args[0])
# Channel message
self.games[args[0]].gotMsg(args, prefix, trailing)
else:
# Private message
#self.games[args[0]].gotPrivMsg(args, prefix, trailing)
pass
def ondisable(self):
self.log.info("GameBase: Unload requested, ending games...")
for game in self.games:
self.games[game].gameover()
class gameObj:
def __init__(self, master, channel):
self.master = master
self.channel = channel
def gotPrivMsg(self, args, prefix, trailing):
prefix = self.master.bot.decodePrefix(prefix)
pass
def gotMsg(self, args, prefix, trailing):
prefix = self.master.bot.decodePrefix(prefix)
pass
#senderIsOp = self.master.attr.getAttribute(prefix.nick, "op")=="yes"
def gameover(self):
pass
class playerObj:
def __init__(self, game, nick):
self.game = game
self.nick = nick

98
pyircbot/modules/MySQL.py Normal file
View File

@ -0,0 +1,98 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
import sys
try:
import MySQLdb #python 2.x
except:
import pymysql as MySQLdb #python 3.x
class MySQL(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[]
self.services=["mysql"]
self.loadConfig()
self.connection = self.getConnection()
def getConnection(self):
return Connection(self)
class Connection:
def __init__(self, master):
self.config = master.config
self.log = master.log
self._connect()
# Check if the table requested exists
def tableExists(self, tablename):
c = self.getCursor()
c.execute("SHOW TABLES;")
tables = c.fetchall()
if len(tables)==0:
return False;
key = list(tables[0].keys())[0]
for table in tables:
if table[key]==tablename:
return True;
return False
def query(self, queryText, args=()):
c = self.getCursor()
if len(args)==0:
c.execute(queryText)
else:
c.execute(queryText, args)
return c
# Returns a cusor object, after checking for connectivity
def getCursor(self):
self.ensureConnected()
if sys.version_info > (3,0):
c = self.connection.cursor(MySQLdb.cursors.DictCursor)
else:
c = self.connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
c.execute("USE `%s`;" % self.config["database"])
return c
def escape(self, s):
self.ensureConnected()
return self.connection.escape_string(s)
def ensureConnected(self):
try:
self.connection.ping()
except:
try:
self.connection.close()
except:
pass
del self.connection
self._connect()
def ondisable(self):
self.connection.close()
# Connects to the database server, and selects a database (Or attempts to create it if it doesn't exist yet)
def _connect(self):
self.log.info("MySQL: Connecting to db host at %s" % self.config["host"])
self.connection = MySQLdb.connect(host=self.config["host"],user=self.config["username"] ,passwd=self.config["password"])
self.log.info("MySQL: Connected.")
self.connection.autocommit(True)
c = None
if sys.version_info > (3,0):
c = self.connection.cursor(MySQLdb.cursors.DictCursor)
else:
c = self.connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
c.execute("SHOW DATABASES")
dblist = c.fetchall()
found = False
for row in dblist:
if row["Database"]==self.config["database"]:
found = True
if not found:
c.execute("CREATE DATABASE `%s`;" % self.config["database"])
c.execute("USE `%s`;" % self.config["database"])
c.close()

View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
class PingResponder(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[ModuleHook("PING", self.pingrespond)]
def pingrespond(self, args, prefix, trailing):
# got a ping? send it right back
self.bot.act_PONG(trailing)
self.log.info("Responded to a ping: %s" % trailing)

View File

@ -0,0 +1,232 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
import random
import yaml
import os
import time
from threading import Timer
from operator import itemgetter
class Scramble(ModuleBase):
def __init__(self, bot, moduleName):
# init the base module
ModuleBase.__init__(self, bot, moduleName);
self.hooks=[ModuleHook("PRIVMSG", self.scramble)]
self.loadConfig()
# Dictionary
self.wordsCount=0;
self.wordsFile = self.getFilePath("words.txt")
print(self.wordsFile)
wordsF = open(self.wordsFile, "r")
while True:
word = wordsF.readline()
if word=="":
break
self.wordsCount+=1
wordsF.close
self.log.info("Scramble: Loaded %s words" % str(self.wordsCount))
# Load scores
self.scoresFile = self.getFilePath("scores.yml")
if not os.path.exists(self.scoresFile):
yaml.dump({}, file(self.scoresFile, 'w'))
self.scores = yaml.load(file(self.scoresFile, 'r'))
# Per channel games
self.games = {}
# Hook in
self.hooks=[ModuleBase.ModuleHook("PRIVMSG", self.scramble)]
def scramble(self, args, prefix, trailing):
channel = args[0]
if channel[0] == "#":
if not channel in self.games:
self.games[channel]=scrambleGame(self, channel)
self.games[channel].scramble(args, prefix, trailing)
def saveScores(self):
yaml.dump(self.scores, file(self.scoresFile, 'w'))
def getScore(self, player, add=0):
player = player.lower()
if not player in self.scores:
self.scores[player] = 0
if not add == 0:
self.scores[player]+=add
self.saveScores()
return self.scores[player]
def getScoreNoWrite(self, player):
if not player.lower() in self.scores:
return 0
else:
return self.getScore(player)
def ondisable(self):
self.log.info("Scramble: Unload requested, ending games...")
for game in self.games:
self.games[game].gameover()
self.saveScores()
class scrambleGame:
def __init__(self, master, channel):
self.master = master
self.channel = channel
# Running?
self.running = False
# Current word
self.currentWord = None
# Current word, scrambled
self.scrambled = None
# Online?
self.scrambleOn = False
# Count down to hints
self.hintTimer = None
# of hints given
self.hintsGiven = 0
# Cooldown between words
self.nextTimer = None
# How many guesses submitted this round
self.guesses = 0;
# How many games in a row where nobody guessed
self.gamesWithoutGuesses = 0;
self.delayHint = self.master.config["hintDelay"];
self.delayNext = self.master.config["delayNext"];
self.maxHints = self.master.config["maxHints"];
self.abortAfterNoGuesses = self.master.config["abortAfterNoGuesses"];
def gameover(self):
self.clearTimers();
self.running = False
def clearTimers(self):
self.clearTimer(self.nextTimer)
self.clearTimer(self.hintTimer)
def clearTimer(self, timer):
if timer:
timer.cancel()
def scramble(self, args, prefix, trailing):
prefix = self.master.bot.decodePrefix(prefix)
sender = prefix.nick
cmd = self.master.bot.messageHasCommand(".scrambleon", trailing)
if cmd and not self.running:
self.running = True
self.startScramble()
return
cmd = self.master.bot.messageHasCommand(".scrambleoff", trailing)
if cmd and self.running:
self.gameover()
self.running = False
return
cmd = self.master.bot.messageHasCommand(".scramble top", trailing)
if cmd:
sortedscores = []
for player in self.master.scores:
sortedscores.append({'name':player, 'score':self.master.scores[player]})
sortedscores = sorted(sortedscores, key=itemgetter('score'))
sortedscores.reverse()
numScores = len(sortedscores)
if numScores>3:
numScores=3
resp = "Top %s: " % str(numScores)
which = 1
while which<=numScores:
resp+="%s: %s, " % (sortedscores[which-1]["name"], sortedscores[which-1]["score"])
which+=1
self.master.bot.act_PRIVMSG(self.channel, resp[:-2])
cmd = self.master.bot.messageHasCommand(".scramble score", trailing)
if cmd:
someone = cmd.args.strip()
if len(someone) > 0:
self.master.bot.act_PRIVMSG(self.channel, "%s: %s has a score of %s" % (sender, someone, self.master.getScoreNoWrite(someone)))
else:
self.master.bot.act_PRIVMSG(self.channel, "%s: %s" % (sender, self.master.getScore(sender)))
if self.currentWord and trailing.strip().lower() == self.currentWord:
playerScore = self.master.getScore(sender, 1)
self.master.bot.act_PRIVMSG(self.channel, "%s guessed the word - %s! %s now has %s points. Next word in %s seconds." % (sender, self.currentWord, sender, playerScore, self.delayNext))
self.currentWord = None
self.clearTimers()
self.hintsGiven = 0
self.nextTimer = Timer(self.delayNext, self.startNewWord)
self.nextTimer.start()
self.guesses=0
else:
self.guesses+=1
def startScramble(self):
self.clearTimer(self.nextTimer)
self.nextTimer = Timer(0, self.startNewWord)
self.nextTimer.start()
def startNewWord(self):
self.currentWord = self.pickWord()
self.master.log.info("Scramble: New word for %s: %s" % (self.channel, self.currentWord))
self.scrambled = self.scrambleWord(self.currentWord)
self.master.bot.act_PRIVMSG(self.channel, "New word - %s " % (self.scrambled))
self.clearTimer(self.hintTimer)
self.hintTimer = Timer(self.delayHint, self.giveHint)
self.hintTimer.start()
def giveHint(self):
self.hintsGiven+=1
if self.hintsGiven>=len(self.currentWord) or self.hintsGiven > self.maxHints:
self.abortWord()
return
blanks = ""
for letter in list(self.currentWord):
if letter == " ":
blanks+=" "
else:
blanks+="_"
partFromWord = self.currentWord[0:self.hintsGiven]
partFromBlanks = blanks[self.hintsGiven:]
hintstr = partFromWord+partFromBlanks
self.master.bot.act_PRIVMSG(self.channel, "Hint: - %s" % (hintstr))
self.clearTimer(self.hintTimer)
self.hintTimer = Timer(self.delayHint, self.giveHint)
self.hintTimer.start()
def abortWord(self):
cur = self.currentWord
self.currentWord = None
self.master.bot.act_PRIVMSG(self.channel, "Word expired - the answer was %s. Next word in %s seconds." % (cur, self.delayNext))
self.hintsGiven = 0
self.clearTimer(self.nextTimer)
if self.guesses==0:
self.gamesWithoutGuesses+=1
if self.gamesWithoutGuesses >= self.abortAfterNoGuesses:
self.master.bot.act_PRIVMSG(self.channel, "No one seems to be playing - type .scrambleon to start again.")
self.gameover()
return
else:
self.gamesWithoutGuesses=0
self.nextTimer = Timer(self.delayNext, self.startNewWord)
self.nextTimer.start()
def pickWord(self):
f = open(self.master.wordsFile, "r")
skip = random.randint(0, self.master.wordsCount)
while skip>=0:
f.readline()
skip-=1
picked = f.readline().strip().lower()
f.close()
return picked
def scrambleWord(self, word):
scrambled = ""
for subword in word.split(" "):
scrambled+=self.scrambleIndividualWord(subword)+ " "
return scrambled.strip()
def scrambleIndividualWord(self, word):
scrambled = list(word)
random.shuffle(scrambled)
return ''.join(scrambled).lower()

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python
from modulebase import ModuleBase,ModuleHook
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.loadConfig()
self.current_nick = 0
self.do_ghost = False
def doConnect(self, args, prefix, trailing):
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):
if self.config["ident"]["ghost"]:
self.do_ghost = True
self.current_nick+=1
if self.current_nick >= len(self.config["user"]["nick"]):
self.log.critical("Ran out of usernames while selecting backup username!")
return
self.bot.act_NICK(self.config["user"]["nick"][self.current_nick])
def initservices(self, args, prefix, trailing):
if self.do_ghost:
self.bot.act_PRIVMSG(self.config["ident"]["ghost_to"], self.config["ident"]["ghost_cmd"] % {"nick":self.config["user"]["nick"][0], "password":self.config["user"]["password"]})
sleep(2)
self.bot.act_NICK(self.config["user"]["nick"][0])
self.do_initservices()
def invited(self, args, prefix, trailing):
if trailing.lower() in self.config["privatechannels"]["list"]:
self.log.info("Invited to %s, joining" % trailing)
self.bot.act_JOIN(trailing)
def do_initservices(self):
" id to nickserv "
if self.config["ident"]["enable"]:
self.bot.act_PRIVMSG(self.config["ident"]["to"], self.config["ident"]["command"] % {"password":self.config["user"]["password"]})
" join plain channels "
for channel in self.config["channels"]:
self.log.info("Joining %s" % channel)
self.bot.act_JOIN(channel)
" request invite for private message channels "
for channel in self.config["privatechannels"]["list"]:
self.log.info("Requesting invite to %s" % channel)
self.bot.act_PRIVMSG(self.config["privatechannels"]["to"], self.config["privatechannels"]["command"] % {"channel":channel})

2
run-example.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/sh
./pyircbot/main.py -c config.main.yml -b pyircbot.yml