Browse Source

Initial commit

dave/xdcc
Dave Pedu 9 years ago
commit
83d30ceab6
  1. 2
      config.main.yml
  2. 5
      data/config/DogeDice.yml
  3. 4
      data/config/DogeRPC.yml
  4. 7
      data/config/DogeScramble.yml
  5. 3
      data/config/GameBase.yml
  6. 4
      data/config/MySQL.yml
  7. 5
      data/config/Scramble.yml
  8. 23
      data/config/Services.yml
  9. 50
      data/data/DogeScramble/us_states.txt
  10. 313
      data/data/Scramble/words.txt
  11. 13
      pyircbot.yml
  12. 0
      pyircbot/core/__init__.py
  13. 1113
      pyircbot/core/jsonrpc.py
  14. 43
      pyircbot/core/modulebase.py
  15. 454
      pyircbot/core/pyircbot.py
  16. 52
      pyircbot/core/rpc.py
  17. 42
      pyircbot/main.py
  18. 139
      pyircbot/modules/AttributeStorage.py
  19. 315
      pyircbot/modules/DogeDice.py
  20. 66
      pyircbot/modules/DogeRPC.py
  21. 258
      pyircbot/modules/DogeScramble.py
  22. 164
      pyircbot/modules/DogeWallet.py
  23. 65
      pyircbot/modules/GameBase.py
  24. 98
      pyircbot/modules/MySQL.py
  25. 11
      pyircbot/modules/PingResponder.py
  26. 232
      pyircbot/modules/Scramble.py
  27. 51
      pyircbot/modules/Services.py
  28. 2
      run-example.sh

2
config.main.yml

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

5
data/config/DogeDice.yml

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

4
data/config/DogeRPC.yml

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

7
data/config/DogeScramble.yml

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

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

4
data/config/MySQL.yml

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

5
data/config/Scramble.yml

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

23
data/config/Services.yml

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

50
data/data/DogeScramble/us_states.txt

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

313
data/data/Scramble/words.txt

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

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

0
pyircbot/core/__init__.py

1113
pyircbot/core/jsonrpc.py

File diff suppressed because it is too large Load Diff

43
pyircbot/core/modulebase.py

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

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

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

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

139
pyircbot/modules/AttributeStorage.py

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

315
pyircbot/modules/DogeDice.py

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