Abstract away communications networks

This commit is contained in:
moneromooo 2015-01-13 12:28:05 +00:00
parent 62c2adbaf6
commit ea25e9d41b
16 changed files with 1249 additions and 983 deletions

223
tipbot.py
View File

@ -25,8 +25,10 @@ import importlib
import tipbot.coinspecs as coinspecs import tipbot.coinspecs as coinspecs
import tipbot.config as config import tipbot.config as config
from tipbot.log import log_error, log_warn, log_info, log_log from tipbot.log import log_error, log_warn, log_info, log_log
from tipbot.link import *
from tipbot.user import *
from tipbot.group import *
from tipbot.utils import * from tipbot.utils import *
from tipbot.ircutils import *
from tipbot.redisdb import * from tipbot.redisdb import *
from tipbot.command_manager import * from tipbot.command_manager import *
@ -73,6 +75,9 @@ if not selected_coin:
sys.path.append(os.path.join('tipbot','modules')) sys.path.append(os.path.join('tipbot','modules'))
for modulename in modulenames: for modulename in modulenames:
if modulename in sys.modules:
log_error('A %s module already exists' % modulename)
exit(1)
log_info('Importing %s module' % modulename) log_info('Importing %s module' % modulename)
try: try:
__import__(modulename) __import__(modulename)
@ -82,28 +87,28 @@ for modulename in modulenames:
def GetBalance(nick,chan,cmd): def GetBalance(link,cmd):
sendto=GetSendTo(nick,chan) nick=link.user.nick
log_log("GetBalance: checking %s" % nick) log_log("GetBalance: checking %s (%s)" % (link.identity(),str(link)))
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",link.identity())
if balance == None: if balance == None:
balance = 0 balance = 0
balance = long(balance) balance = long(balance)
sbalance = AmountToString(balance) sbalance = AmountToString(balance)
if balance < coinspecs.atomic_units: if balance < coinspecs.atomic_units:
if balance == 0: if balance == 0:
SendTo(sendto, "%s's balance is %s" % (nick, sbalance)) link.send("%s's balance is %s" % (nick, sbalance))
else: else:
SendTo(sendto, "%s's balance is %s (%.16g %s)" % (nick, sbalance, float(balance) / coinspecs.atomic_units, coinspecs.name)) link.send("%s's balance is %s (%.16g %s)" % (nick, sbalance, float(balance) / coinspecs.atomic_units, coinspecs.name))
else: else:
SendTo(sendto, "%s's balance is %s" % (nick, sbalance)) link.send("%s's balance is %s" % (nick, sbalance))
except Exception, e: except Exception, e:
log_error('GetBalance: exception: %s' % str(e)) log_error('GetBalance: exception: %s' % str(e))
SendTo(sendto, "An error has occured") link.send("An error has occured")
def AddBalance(nick,chan,cmd): def AddBalance(link,cmd):
sendto = GetSendTo(nick,chan) nick=link.user.nick
if GetParam(cmd,2): if GetParam(cmd,2):
anick = GetParam(cmd,1) anick = GetParam(cmd,1)
amount = GetParam(cmd,2) amount = GetParam(cmd,2)
@ -111,85 +116,91 @@ def AddBalance(nick,chan,cmd):
anick = nick anick = nick
amount = GetParam(cmd,1) amount = GetParam(cmd,1)
if not amount: if not amount:
SendTo(sendto, 'usage: !addbalance [<nick>] <amount>') link.send('usage: !addbalance [<nick>] <amount>')
return return
try: try:
units = long(float(amount)*coinspecs.atomic_units) units = long(float(amount)*coinspecs.atomic_units)
except Exception,e: except Exception,e:
log_error('AddBalance: invalid amount: %s' % str(e)) log_error('AddBalance: error converting amount: %s' % str(e))
SendTo(sendto, 'usage: !addbalance [<nick>] <amount>') link.send('usage: !addbalance [<nick>] <amount>')
return return
log_info("AddBalance: Adding %s to %s's balance" % (AmountToString(units),anick)) if anick.find(':') == -1:
network=link.network
log_info('No network found in %s, using %s from command originator' % (anick,network.name))
aidentity=Link(network,User(network,anick)).identity()
else:
aidentity=anick
log_info("AddBalance: Adding %s to %s's balance" % (AmountToString(units),aidentity))
try: try:
balance = redis_hincrby("balances",anick,units) balance = redis_hincrby("balances",aidentity,units)
except Exception, e: except Exception, e:
log_error('AddBalance: exception: %s' % str(e)) log_error('AddBalance: exception: %s' % str(e))
SendTo(sendto, "An error has occured") link.send( "An error has occured")
SendTo(sendto,"%s's bvalance is now %s" % (anick,AmountToString(balance))) link.send("%s's balance is now %s" % (aidentity,AmountToString(balance)))
def ScanWho(nick,chan,cmd): def ScanWho(link,cmd):
Who(chan) link.network.update_users_list(link.group.name if link.group else None)
def GetHeight(nick,chan,cmd): def GetHeight(link,cmd):
log_info('GetHeight: %s wants to know block height' % nick) log_info('GetHeight: %s wants to know block height' % str(link))
try: try:
j = SendDaemonHTMLCommand("getheight") j = SendDaemonHTMLCommand("getheight")
except Exception,e: except Exception,e:
log_error('GetHeight: error: %s' % str(e)) log_error('GetHeight: error: %s' % str(e))
SendTo(nick,"An error has occured") link.send("An error has occured")
return return
log_log('GetHeight: Got reply: %s' % str(j)) log_log('GetHeight: Got reply: %s' % str(j))
if not "height" in j: if not "height" in j:
log_error('GetHeight: Cannot see height in here') log_error('GetHeight: Cannot see height in here')
SendTo(nick, "Height not found") link.send("Height not found")
return return
height=j["height"] height=j["height"]
log_info('GetHeight: height is %s' % str(height)) log_info('GetHeight: height is %s' % str(height))
SendTo(nick, "Height: %s" % str(height)) link.send("Height: %s" % str(height))
def GetTipbotBalance(nick,chan,cmd): def GetTipbotBalance(link,cmd):
log_info('%s wants to know the tipbot balance' % nick) log_info('%s wants to know the tipbot balance' % str(link))
try: try:
balance, unlocked_balance = RetrieveTipbotBalance() balance, unlocked_balance = RetrieveTipbotBalance()
except Exception,e: except Exception,e:
SendTo(nick,"An error has occured") link.send("An error has occured")
return return
pending = long(balance)-long(unlocked_balance) pending = long(balance)-long(unlocked_balance)
if pending == 0: if pending == 0:
log_info("GetTipbotBalance: Tipbot balance: %s" % AmountToString(balance)) log_info("GetTipbotBalance: Tipbot balance: %s" % AmountToString(balance))
SendTo(nick,"Tipbot balance: %s" % AmountToString(balance)) link.send("Tipbot balance: %s" % AmountToString(balance))
else: else:
log_info("GetTipbotBalance: Tipbot balance: %s (%s pending)" % (AmountToString(unlocked_balance), AmountToString(pending))) log_info("GetTipbotBalance: Tipbot balance: %s (%s pending)" % (AmountToString(unlocked_balance), AmountToString(pending)))
SendTo(nick,"Tipbot balance: %s (%s pending)" % (AmountToString(unlocked_balance), AmountToString(pending))) link.send("Tipbot balance: %s (%s pending)" % (AmountToString(unlocked_balance), AmountToString(pending)))
def DumpUsers(nick,chan,cmd): def DumpUsers(link,cmd):
userstable = GetUsersTable() for network in networks:
log_info(str(userstable)) network.dump_users()
def Help(nick,chan,cmd): def Help(link,cmd):
module = GetParam(cmd,1) module = GetParam(cmd,1)
if module: if module:
RunModuleHelpFunction(module,nick,chan) RunModuleHelpFunction(module,link)
return return
SendTo(nick, "See available commands with !commands or !commands <modulename>") link.send("See available commands with !commands or !commands <modulename>")
SendTo(nick, "Available modules: %s" % ", ".join(GetModuleNameList(IsAdmin(nick)))) link.send("Available modules: %s" % ", ".join(GetModuleNameList(IsAdmin(link))))
SendTo(nick, "Get help on a particular module with !help <modulename>") link.send("Get help on a particular module with !help <modulename>")
if coinspecs.web_wallet_url: if coinspecs.web_wallet_url:
SendTo(nick, "No %s address ? You can use %s" % (coinspecs.name, coinspecs.web_wallet_url)) link.send("No %s address ? You can use %s" % (coinspecs.name, coinspecs.web_wallet_url))
def Info(nick,chan,cmd): def Info(link,cmd):
SendTo(nick, "Info for %s:" % config.tipbot_name) link.send("Info for %s:" % config.tipbot_name)
SendTo(nick, "Copyright 2014,2015 moneromooo - http://duckpool.mooo.com/tipbot/") link.send("Copyright 2014,2015 moneromooo - http://duckpool.mooo.com/tipbot/")
SendTo(nick, "Type !help, or !commands for a list of commands") link.send("Type !help, or !commands for a list of commands")
SendTo(nick, "NO WARRANTY, YOU MAY LOSE YOUR COINS") link.send("NO WARRANTY, YOU MAY LOSE YOUR COINS")
SendTo(nick, "By sending your %s to %s, you are giving up their control" % (coinspecs.name, config.tipbot_name)) link.send("By sending your %s to %s, you are giving up their control" % (coinspecs.name, config.tipbot_name))
SendTo(nick, "to whoever runs the tipbot. Any tip you make/receive using %s" % config.tipbot_name) link.send("to whoever runs the tipbot. Any tip you make/receive using %s" % config.tipbot_name)
SendTo(nick, "is obviously not anonymous. %s's wallet may end up corrupt, or be" % config.tipbot_name) link.send("is obviously not anonymous. %s's wallet may end up corrupt, or be" % config.tipbot_name)
SendTo(nick, "stolen, the server compromised, etc. While I hope this won't be the case,") link.send("stolen, the server compromised, etc. While I hope this won't be the case,")
SendTo(nick, "I will not offer any warranty whatsoever for the use of %s or the" % config.tipbot_name) link.send("I will not offer any warranty whatsoever for the use of %s or the" % config.tipbot_name)
SendTo(nick, "return of any %s. Use at your own risk." % coinspecs.name) link.send("return of any %s. Use at your own risk." % coinspecs.name)
SendTo(nick, "That being said, I hope you enjoy using it :)") link.send("That being said, I hope you enjoy using it :)")
def InitScanBlockHeight(): def InitScanBlockHeight():
try: try:
@ -201,56 +212,83 @@ def InitScanBlockHeight():
except Exception,e: except Exception,e:
log_error('Failed to initialize scan_block_height: %s' % str(e)) log_error('Failed to initialize scan_block_height: %s' % str(e))
def ShowActivity(nick,chan,cmd): def ShowActivity(link,cmd):
achan=cmd[1] anick=GetParam(cmd,1)
anick=cmd[2] achan=GetParam(cmd,2)
activity = GetTimeSinceActive(achan,anick) if not anick or not achan:
if activity: link.send('usage: !show_activity <nick> <chan>')
SendTo(nick,"%s was active in %s %f seconds ago" % (anick,achan,activity)) return
if anick.find(':') == -1:
network = link.network
else: else:
SendTo(nick,"%s was never active in %s" % (anick,achan)) parts=anick.split(':')
network_name=parts[0]
anick=parts[1]
network = GetNetworkByName(network_name)
if network:
last_activity = network.get_last_active_time(anick,achan)
if last_activity:
link.send("%s was active in %s %f seconds ago" % (anick,achan,now-last_activity))
else:
link.send("%s was never active in %s" % (anick,achan))
else:
link.send("%s is not a valid network" % network)
def SendToNick(nick,chan,msg): def SendToLink(link,msg):
SendTo(nick,msg) link.send(msg)
def IsRegistered(nick,chan,cmd): def IsRegistered(link,cmd):
RunRegisteredCommand(nick,chan,SendToNick,"You are registered",SendToNick,"You are not registered") RunRegisteredCommand(link,SendToLink,"You are registered",SendToLink,"You are not registered")
def Reload(nick,chan,cmd): def Reload(link,cmd):
sendto=GetSendTo(nick,chan)
modulename=GetParam(cmd,1) modulename=GetParam(cmd,1)
if not modulename: if not modulename:
SendTo(sendto,"Usage: reload <modulename>") link.send("Usage: reload <modulename>")
return return
if modulename=="builtin": if modulename=="builtin":
SendTo(sendto,"Cannot reload builtin module") link.send("Cannot reload builtin module")
return
if not modulename in sys.modules:
link.send("%s is not a dynamic module" % modulename)
return return
log_info('Unloading %s module' % modulename) log_info('Unloading %s module' % modulename)
UnregisterModule(modulename) UnregisterModule(modulename)
log_info('Reloading %s module' % modulename) log_info('Reloading %s module' % modulename)
try: try:
reload(sys.modules[modulename]) reload(sys.modules[modulename])
SendTo(sendto,'%s reloaded' % modulename) link.send('%s reloaded' % modulename)
except Exception,e: except Exception,e:
log_error('Failed to load module "%s": %s' % (modulename, str(e))) log_error('Failed to load module "%s": %s' % (modulename, str(e)))
SendTo(sendto,'An error occured') link.send('An error occured')
def Disable(nick,chan,cmd): def Disable(link,cmd):
global disabled global disabled
sendto=GetSendTo(nick,chan)
disabled = True disabled = True
SendTo(sendto,'%s disabled, will require restart' % config.tipbot_name) link.send('%s disabled, will require restart' % config.tipbot_name)
def OnIdle(): def OnIdle():
if disabled: if disabled:
return return
RunIdleFunctions([irc,redisdb]) RunIdleFunctions([irc,redisdb])
def OnIdentified(nick, identified): def Quit(link,cmd):
global networks
msg = ""
for w in cmd[1:]:
msg = msg + " " + w
for network in networks:
log_info('Quitting %s network' % network.name)
network.quit()
networks = []
def OnIdle():
RunIdleFunctions()
def OnIdentified(link, identified):
if disabled: if disabled:
log_info('Ignoring identified notification for %s while disabled' % str(nick)) log_info('Ignoring identified notification for %s while disabled' % str(link.identity()))
return return
RunNextCommand(nick, identified) RunNextCommand(link, identified)
def RegisterCommands(): def RegisterCommands():
RegisterCommand({'module': 'builtin', 'name': 'help', 'parms': '[module]', 'function': Help, 'help': "Displays help about %s" % config.tipbot_name}) RegisterCommand({'module': 'builtin', 'name': 'help', 'parms': '[module]', 'function': Help, 'help': "Displays help about %s" % config.tipbot_name})
@ -267,20 +305,43 @@ def RegisterCommands():
RegisterCommand({'module': 'builtin', 'name': 'show_activity', 'function': ShowActivity, 'admin': True, 'help': "Show time since a user was last active"}) RegisterCommand({'module': 'builtin', 'name': 'show_activity', 'function': ShowActivity, 'admin': True, 'help': "Show time since a user was last active"})
RegisterCommand({'module': 'builtin', 'name': 'reload', 'function': Reload, 'admin': True, 'help': "Reload a module"}) RegisterCommand({'module': 'builtin', 'name': 'reload', 'function': Reload, 'admin': True, 'help': "Reload a module"})
RegisterCommand({'module': 'builtin', 'name': 'disable', 'function': Disable, 'admin': True, 'help': "Disable %s"%config.tipbot_name}) RegisterCommand({'module': 'builtin', 'name': 'disable', 'function': Disable, 'admin': True, 'help': "Disable %s"%config.tipbot_name})
RegisterCommand({'module': 'builtin', 'name': 'quit', 'function': Quit, 'admin': True, 'help': "Quit"})
def OnCommandProxy(cmd,chan,who): def OnCommandProxy(link,cmd):
if disabled: if disabled:
log_info('Ignoring command from %s while disabled: %s' % (str(who),str(cmd))) log_info('Ignoring command from %s while disabled: %s' % (str(link.identity()),str(cmd)))
return return
OnCommand(cmd,chan,who,RunAdminCommand,RunRegisteredCommand) link.batch_send_start()
try:
OnCommand(link,cmd,RunAdminCommand,RunRegisteredCommand)
except Exception,e:
log_error('Exception running command %s: %s' % (str(cmd),str(e)))
link.batch_send_done()
def MigrateBalances():
balances=redis_hgetall('balances')
for balance in balances:
if balance.find(':') == -1:
redis_hset('balances','freenode:'+balance,balances[balance])
redis_hdel('balances',balance)
redisdb = connect_to_redis(config.redis_host,config.redis_port)
irc = connect_to_irc(config.irc_network,config.irc_port,config.tipbot_name,GetPassword(),config.irc_send_delay)
InitScanBlockHeight()
RegisterCommands() RegisterCommands()
redisdb = connect_to_redis(config.redis_host,config.redis_port)
MigrateBalances()
InitScanBlockHeight()
# TODO: make this be created when the module is loaded
irc = sys.modules["freenode"].FreenodeNetwork()
irc.set_callbacks(OnCommandProxy,OnIdentified)
if irc.connect(config.irc_network,config.irc_port,config.tipbot_name,GetPassword(),config.irc_send_delay):
AddNetwork(irc)
while len(networks)>0:
for network in networks:
network.update()
OnIdle()
IRCLoop(OnIdle,OnIdentified,OnCommandProxy)
log_info('shutting down redis') log_info('shutting down redis')
redisdb.shutdown redisdb.shutdown

View File

@ -41,76 +41,82 @@ def IsBetAmountValid(amount,minbet,maxbet,potential_loss,max_loss,max_loss_ratio
return True, None return True, None
def IsPlayerBalanceAtLeast(nick,units): def IsPlayerBalanceAtLeast(link,units):
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",link.identity())
if balance == None: if balance == None:
balance = 0 balance = 0
balance=long(balance) balance=long(balance)
if units > balance: if units > balance:
log_error ('%s does not have enough balance' % nick) log_error ('%s does not have enough balance' % link.user.nick)
return False, "You only have %s" % (AmountToString(balance)) return False, "You only have %s" % (AmountToString(balance))
except Exception,e: except Exception,e:
log_error ('failed to query balance') log_error ('failed to query balance')
return False, "Failed to query balance" return False, "Failed to query balance"
return True, None return True, None
def SetServerSeed(nick,game,seed): def SetServerSeed(link,game,seed):
identity=link.identity()
try: try:
redis_hset('%s:serverseed' % game,nick,seed) redis_hset('%s:serverseed' % game,identity,seed)
log_info('%s\'s %s server seed set' % (nick, game)) log_info('%s\'s %s serverseed set' % (identity, game))
except Exception,e: except Exception,e:
log_error('Failed to set %s server seed for %s: %s' % (game, nick, str(e))) log_error('Failed to set %s server seed for %s: %s' % (game, identity, str(e)))
raise raise
def GetServerSeed(nick,game): def GetServerSeed(link,game):
EnsureServerSeed(nick,game) EnsureServerSeed(link,game)
identity=link.identity()
try: try:
return redis_hget('%s:serverseed' % game,nick) return redis_hget('%s:serverseed' % game,identity)
except Exception,e: except Exception,e:
log_error('Failed to get %s server seed for %s: %s' % (game, nick, str(e))) log_error('Failed to get %s server seed for %s: %s' % (game, identity, str(e)))
raise raise
def GenerateServerSeed(nick,game): def GenerateServerSeed(link,game):
identity=link.identity()
try: try:
salt="kfn3kjg4nkngvekjvn3u4vgb" + ":" + game salt="kfn3kjg4nkngvekjvn3u4vgb" + ":" + game
s=salt+":"+nick+":"+str(time.time())+":"+str(random.randint(0,1000000)) s=salt+":"+identity+":"+str(time.time())+":"+str(random.randint(0,1000000))
seed=hashlib.sha256(s).hexdigest() seed=hashlib.sha256(s).hexdigest()
SetServerSeed(nick,game,seed) SetServerSeed(link,game,seed)
except Exception,e: except Exception,e:
log_error('Failed to generate %s server seed for %s: %s' % (game,nick,str(e))) log_error('Failed to generate %s server seed for %s: %s' % (game,identity,str(e)))
raise raise
def EnsureServerSeed(nick,game): def EnsureServerSeed(link,game):
if not redis_hexists('%s:serverseed' % game,nick): if not redis_hexists('%s:serverseed' % game,link.identity()):
GenerateServerSeed(nick,game) GenerateServerSeed(link,game)
def SetPlayerSeed(nick,game,seed): def SetPlayerSeed(link,game,seed):
identity=link.identity()
try: try:
redis_hset('%s:playerseed' % game,nick,seed) redis_hset('%s:playerseed' % game,identity,seed)
log_info('%s\'s %s playerseed set to %s' % (nick, game, seed)) log_info('%s\'s %s playerseed set to %s' % (identity, game, seed))
except Exception,e: except Exception,e:
log_error('Failed to set %s player seed for %s: %s' % (game, nick, str(e))) log_error('Failed to set %s player seed for %s: %s' % (game, identity, str(e)))
raise raise
def GetPlayerSeed(nick,game): def GetPlayerSeed(link,game):
identity=link.identity()
try: try:
if not redis_hexists('%s:playerseed' % game,nick): if not redis_hexists('%s:playerseed' % game,identity):
return "" return ""
return redis_hget('%s:playerseed' % game,nick) return redis_hget('%s:playerseed' % game,identity)
except Exception,e: except Exception,e:
log_error('Failed to get %s player seed for %s: %s' % (game, nick, str(e))) log_error('Failed to get %s player seed for %s: %s' % (game, identity, str(e)))
raise raise
def GetServerSeedHash(nick,game): def GetServerSeedHash(link,game):
seed = GetServerSeed(nick,game) seed = GetServerSeed(link,game)
return hashlib.sha256(seed).hexdigest() return hashlib.sha256(seed).hexdigest()
def RecordGameResult(nick,chan,game,win,lose,units): def RecordGameResult(link,game,win,lose,units):
identity=link.identity()
try: try:
p = redis_pipeline() p = redis_pipeline()
tname="%s:stats:"%game+nick tname="%s:stats:"%game+identity
rtname="%s:stats:reset:"%game+nick rtname="%s:stats:reset:"%game+identity
alltname="%s:stats:"%game alltname="%s:stats:"%game
p.hincrby(tname,"bets",1) p.hincrby(tname,"bets",1)
p.hincrby(rtname,"bets",1) p.hincrby(rtname,"bets",1)
@ -119,7 +125,7 @@ def RecordGameResult(nick,chan,game,win,lose,units):
p.hincrby(rtname,"wagered",units) p.hincrby(rtname,"wagered",units)
p.hincrby(alltname,"wagered",units) p.hincrby(alltname,"wagered",units)
if win: if win:
p.hincrby("balances",nick,units) p.hincrby("balances",identity,units)
p.hincrby(tname,"won",units) p.hincrby(tname,"won",units)
p.hincrby(rtname,"won",units) p.hincrby(rtname,"won",units)
p.hincrby(alltname,"won",units) p.hincrby(alltname,"won",units)
@ -127,7 +133,7 @@ def RecordGameResult(nick,chan,game,win,lose,units):
p.hincrby(rtname,"nwon",1) p.hincrby(rtname,"nwon",1)
p.hincrby(alltname,"nwon",1) p.hincrby(alltname,"nwon",1)
if lose: if lose:
p.hincrby("balances",nick,-units) p.hincrby("balances",identity,-units)
p.hincrby(tname,"lost",units) p.hincrby(tname,"lost",units)
p.hincrby(rtname,"lost",units) p.hincrby(rtname,"lost",units)
p.hincrby(alltname,"lost",units) p.hincrby(alltname,"lost",units)
@ -139,8 +145,9 @@ def RecordGameResult(nick,chan,game,win,lose,units):
log_error('RecordGameResult: exception updating redis: %s' % str(e)) log_error('RecordGameResult: exception updating redis: %s' % str(e))
raise raise
def ShowGameStats(sendto,snick,title,game): def ShowGameStats(link,sidentity,title,game):
tname="%s:stats:"%game+snick identity=IdentityFromString(link,sidentity)
tname="%s:stats:"%game+sidentity
try: try:
bets=redis_hget(tname,"bets") bets=redis_hget(tname,"bets")
wagered=redis_hget(tname,"wagered") wagered=redis_hget(tname,"wagered")
@ -150,10 +157,10 @@ def ShowGameStats(sendto,snick,title,game):
nlost=redis_hget(tname,"nlost") nlost=redis_hget(tname,"nlost")
except Exception,e: except Exception,e:
log_error('Failed to retrieve %s stats for %s: %s' % (game, title, str(e))) log_error('Failed to retrieve %s stats for %s: %s' % (game, title, str(e)))
SendTo(sendto,"An error occured") link.send("An error occured")
return return
if not bets: if not bets:
SendTo(sendto,"No %s stats available for %s" % (game,title)) link.send("No %s stats available for %s" % (game,title))
return return
bets = long(bets) bets = long(bets)
@ -164,7 +171,7 @@ def ShowGameStats(sendto,snick,title,game):
nlost = long(nlost or 0) nlost = long(nlost or 0)
if bets==0: if bets==0:
SenTo(sendto,"No %s stats available for %s" % (game,title)) link.send("No %s stats available for %s" % (game,title))
return return
swagered = AmountToString(wagered) swagered = AmountToString(wagered)
@ -175,12 +182,13 @@ def ShowGameStats(sendto,snick,title,game):
sov = "+" + AmountToString(won-lost) sov = "+" + AmountToString(won-lost)
else: else:
sov = "-" + AmountToString(lost-won) sov = "-" + AmountToString(lost-won)
SendTo(sendto,"%s: %d games %d won, %d lost, %s wagered (average %s per game), %s won, %s lost, overall %s" % (title, bets, nwon, nlost, swagered, savg, swon, slost, sov)) link.send("%s: %d games %d won, %d lost, %s wagered (average %s per game), %s won, %s lost, overall %s" % (title, bets, nwon, nlost, swagered, savg, swon, slost, sov))
def ResetGameStats(sendto,snick,game): def ResetGameStats(link,sidentity,game):
identity=IdentityFromString(link,sidentity)
try: try:
p = redis_pipeline() p = redis_pipeline()
tname="%s:stats:reset:"%game+snick tname="%s:stats:reset:"%game+sidentity
bets=p.hset(tname,"bets",0) bets=p.hset(tname,"bets",0)
wagered=p.hset(tname,"wagered",0) wagered=p.hset(tname,"wagered",0)
won=p.hset(tname,"won",0) won=p.hset(tname,"won",0)
@ -188,31 +196,31 @@ def ResetGameStats(sendto,snick,game):
nwon=p.hset(tname,"nwon",0) nwon=p.hset(tname,"nwon",0)
nlost=p.hset(tname,"nlost",0) nlost=p.hset(tname,"nlost",0)
p.execute() p.execute()
SendTo(sendto,"%s stats reset for %s" % (game,snick)) link.send("%s stats reset for %s" % (game,sidentity))
except Exception,e: except Exception,e:
log_error('Error resetting %s stats for %s: %s' % (game,snick,str(e))) log_error('Error resetting %s stats for %s: %s' % (game,sidentity,str(e)))
raise raise
def RetrieveHouseBalance(): def RetrieveHouseBalance():
balance, unlocked_balance = RetrieveTipbotBalance() balance, unlocked_balance = RetrieveTipbotBalance()
nicks = redis_hgetall("balances") identities = redis_hgetall("balances")
for nick in nicks: for identity in identities:
nb = redis_hget("balances", nick) ib = redis_hget("balances", identity)
unlocked_balance = unlocked_balance - long(nb) unlocked_balance = unlocked_balance - long(ib)
log_log('RetrieveHouseBalance: subtracting %s from %s to give %s' % (AmountToString(nb), nick, AmountToString(unlocked_balance))) log_log('RetrieveHouseBalance: subtracting %s from %s to give %s' % (AmountToString(ib), identity, AmountToString(unlocked_balance)))
rbal=redis_get('reserve_balance') rbal=redis_get('reserve_balance')
if rbal: if rbal:
unlocked_balance = unlocked_balance - long(rbal) unlocked_balance = unlocked_balance - long(rbal)
log_log('RetrieveHouseBalance: subtracting %s reserve balance to give %s' % (AmountToString(rbal), AmountToString(unlocked_balance)))
if unlocked_balance < 0: if unlocked_balance < 0:
raise RuntimeError('Negative house balance') raise RuntimeError('Negative house balance')
return return
return unlocked_balance return unlocked_balance
def ReserveBalance(nick,chan,cmd): def ReserveBalance(link,cmd):
sendto=GetSendTo(nick,chan)
rbal=GetParam(cmd,1) rbal=GetParam(cmd,1)
if rbal: if rbal:
try: try:
@ -222,36 +230,36 @@ def ReserveBalance(nick,chan,cmd):
rbal = long(rbal * coinspecs.atomic_units) rbal = long(rbal * coinspecs.atomic_units)
except Exception,e: except Exception,e:
log_error('SetReserveBalance: invalid balance: %s' % str(e)) log_error('SetReserveBalance: invalid balance: %s' % str(e))
SendTo(sendto,"Invalid balance") link.send("Invalid balance")
return return
try: try:
current_rbal=long(redis_get('reserve_balance') or 0) current_rbal=long(redis_get('reserve_balance') or 0)
except Exception,e: except Exception,e:
log_error('Failed to get current reserve balance: %s' % str(e)) log_error('Failed to get current reserve balance: %s' % str(e))
SendTo(sendto,"Failed to get current reserve balance") link.send("Failed to get current reserve balance")
return return
if rbal == None: if rbal == None:
SendTo(sendto,"Reserve balance is %s" % AmountToString(current_rbal)) link.send("Reserve balance is %s" % AmountToString(current_rbal))
return return
try: try:
house_balance = RetrieveHouseBalance() house_balance = RetrieveHouseBalance()
except Exception,e: except Exception,e:
log_error('Failed to get house balance: %s' % str(e)) log_error('Failed to get house balance: %s' % str(e))
SendTo(sendto,"Failed to get house balance") link.send("Failed to get house balance")
return return
if rbal > house_balance + current_rbal: if rbal > house_balance + current_rbal:
log_error('Cannot set reserve balance higher than max house balance') log_error('Cannot set reserve balance higher than max house balance')
SendTo(sendto,'Cannot set reserve balance higher than max house balance') link.send('Cannot set reserve balance higher than max house balance')
return return
try: try:
redis_set('reserve_balance',rbal) redis_set('reserve_balance',rbal)
except Exception,e: except Exception,e:
log_error('Failed to set reserve balance: %s' % str(e)) log_error('Failed to set reserve balance: %s' % str(e))
SendTo(sendto,"Failed to set reserve balance") link.send("Failed to set reserve balance")
return return
SendTo(sendto,"Reserve balance set") link.send("Reserve balance set")
RegisterCommand({ RegisterCommand({
'module': 'betting', 'module': 'betting',

View File

@ -11,7 +11,6 @@
import tipbot.config as config import tipbot.config as config
from tipbot.utils import * from tipbot.utils import *
from tipbot.ircutils import *
modules = dict() modules = dict()
commands = dict() commands = dict()
@ -19,48 +18,46 @@ calltable=dict()
idles = [] idles = []
cleanup = dict() cleanup = dict()
def SendToProxy(nick,chan,msg): def SendToProxy(link,msg):
SendTo(GetSendTo(nick,chan),msg) link.send(msg)
def RunRegisteredCommand(nick,chan,ifyes,yesdata,ifno,nodata): def RunRegisteredCommand(link,ifyes,yesdata,ifno,nodata):
if nick not in calltable: if link.identity() not in calltable:
calltable[nick] = [] calltable[link.identity()] = []
calltable[nick].append([chan,ifyes,yesdata,ifno,nodata]) calltable[link.identity()].append([link,ifyes,yesdata,ifno,nodata])
if nick in registered_users: if link.network.is_identified(link):
RunNextCommand(nick,True) RunNextCommand(link,True)
else: else:
SendTo('nickserv', "ACC " + nick) link.network.identify(link)
def IsAdmin(nick): def IsAdmin(link):
return nick in config.admins return link.identity() in config.admins
def RunAdminCommand(nick,chan,ifyes,yesdata,ifno,nodata): def RunAdminCommand(link,ifyes,yesdata,ifno,nodata):
if not IsAdmin(nick): if not IsAdmin(link):
log_warn('RunAdminCommand: nick %s is not admin, cannot call %s with %s' % (str(nick),str(ifyes),str(yesdata))) log_warn('RunAdminCommand: %s is not admin, cannot call %s with %s' % (str(link.identity()),str(ifyes),str(yesdata)))
SendTo(nick, "Access denied") link.send("Access denied")
return return
RunRegisteredCommand(nick,chan,ifyes,yesdata,ifno,nodata) RunRegisteredCommand(link,ifyes,yesdata,ifno,nodata)
def RunNextCommand(nick,registered): def RunNextCommand(link,registered):
if registered: identity = link.identity()
registered_users.add(nick) if identity not in calltable:
else: log_error('Nothing in queue for %s' % identity)
registered_users.discard(nick)
if nick not in calltable:
log_error( 'Nothing in queue for %s' % nick)
return return
try: try:
link=calltable[identity][0][0]
if registered: if registered:
calltable[nick][0][1](nick,calltable[nick][0][0],calltable[nick][0][2]) calltable[identity][0][1](link,calltable[identity][0][2])
else: else:
calltable[nick][0][3](nick,calltable[nick][0][0],calltable[nick][0][4]) calltable[identity][0][3](link,calltable[identity][0][4])
del calltable[nick][0] del calltable[identity][0]
except Exception, e: except Exception, e:
log_error('RunNextCommand: Exception in action, continuing: %s' % str(e)) log_error('RunNextCommand: Exception in action, continuing: %s' % str(e))
del calltable[nick][0] del calltable[identity][0]
def Commands(nick,chan,cmd): def Commands(link,cmd):
if IsAdmin(nick): if IsAdmin(link):
all = True all = True
else: else:
all = False all = False
@ -68,9 +65,9 @@ def Commands(nick,chan,cmd):
module_name = GetParam(cmd,1) module_name = GetParam(cmd,1)
if module_name: if module_name:
SendTo(nick, "Commands for %s's %s module:" % (config.tipbot_name,module_name)) link.send_private("Commands for %s's %s module:" % (config.tipbot_name,module_name))
else: else:
SendTo(nick, "Commands for %s (use !commands <modulename> for help about the module's commands):" % config.tipbot_name) link.send_private("Commands for %s (use !commands <modulename> for help about the module's commands):" % config.tipbot_name)
msgs = dict() msgs = dict()
for command_name in commands: for command_name in commands:
@ -84,7 +81,7 @@ def Commands(nick,chan,cmd):
synopsis = c['name'] synopsis = c['name']
if 'parms' in c: if 'parms' in c:
synopsis = synopsis + " " + c['parms'] synopsis = synopsis + " " + c['parms']
SendTo(nick, "%s - %s" % (synopsis, c['help'])) link.send_private("%s - %s" % (synopsis, c['help']))
else: else:
if module in msgs: if module in msgs:
msgs[module] = msgs[module] +(", ") msgs[module] = msgs[module] +(", ")
@ -94,7 +91,7 @@ def Commands(nick,chan,cmd):
if not module_name: if not module_name:
for msg in msgs: for msg in msgs:
SendTo(nick, "%s" % msgs[msg]) link.send_private("%s" % msgs[msg])
def RegisterModule(module): def RegisterModule(module):
if module['name'] in modules: if module['name'] in modules:
@ -131,7 +128,7 @@ def RegisterIdleFunction(module,function):
def RegisterCleanupFunction(module,function): def RegisterCleanupFunction(module,function):
cleanup.append((module,function)) cleanup.append((module,function))
def OnCommand(cmd,chan,who,check_admin,check_registered): def OnCommand(link,cmd,check_admin,check_registered):
cmdparts = cmd[0].split(':') cmdparts = cmd[0].split(':')
log_log('cmdparts: %s' % str(cmdparts)) log_log('cmdparts: %s' % str(cmdparts))
if len(cmdparts) == 2: if len(cmdparts) == 2:
@ -141,7 +138,7 @@ def OnCommand(cmd,chan,who,check_admin,check_registered):
modulename = None modulename = None
cmdname = cmdparts[0] cmdname = cmdparts[0]
else: else:
SendTo(GetNick(who), "Invalid command, try !help") link.send("Invalid command, try !help")
return return
log_log('modulename: %s, cmdname: %s' % (str(modulename),str(cmdname))) log_log('modulename: %s, cmdname: %s' % (str(modulename),str(cmdname)))
if cmdname in commands: if cmdname in commands:
@ -153,7 +150,7 @@ def OnCommand(cmd,chan,who,check_admin,check_registered):
if msg != "": if msg != "":
msg = msg + ", " msg = msg + ", "
msg = msg + c['module'] + ":" + cmd[0] msg = msg + c['module'] + ":" + cmd[0]
SendTo(GetNick(who), "Ambiguous command, try one of: %s" % msg) link.send("Ambiguous command, try one of: %s" % msg)
return return
c = None c = None
for command in commands[cmdname]: for command in commands[cmdname]:
@ -161,34 +158,34 @@ def OnCommand(cmd,chan,who,check_admin,check_registered):
c = command c = command
break break
if not c: if not c:
SendTo(GetNick(who), "Invalid command, try !help") link.send("Invalid command, try !help")
return return
else: else:
c = commands[cmdname][0] c = commands[cmdname][0]
if 'admin' in c and c['admin']: if 'admin' in c and c['admin']:
check_admin(GetNick(who),chan,c['function'],cmd,SendToProxy,"You must be admin") check_admin(link,c['function'],cmd,SendToProxy,"You must be admin")
elif 'registered' in c and c['registered']: elif 'registered' in c and c['registered']:
check_registered(GetNick(who),chan,c['function'],cmd,SendToProxy,"You must be registered with Freenode") check_registered(link,c['function'],cmd,SendToProxy,"You must be registered with Freenode")
else: else:
c['function'](GetNick(who),chan,cmd) c['function'](link,cmd)
else: else:
SendTo(GetNick(who), "Invalid command, try !help") link.send("Invalid command, try !help")
def RunIdleFunctions(param): def RunIdleFunctions(param=None):
for f in idles: for f in idles:
try: try:
f[1](param) f[1](param)
except Exception,e: except Exception,e:
log_error("Exception running idle function %s from module %s: %s" % (str(f[1]),str(f[2]),str(e))) log_error("Exception running idle function %s from module %s: %s" % (str(f[1]),str(f[2]),str(e)))
def RunModuleHelpFunction(module,nick,chan): def RunModuleHelpFunction(module,link):
if module in modules: if module in modules:
try: try:
modules[module]['help'](nick,chan) modules[module]['help'](link)
except Exception,e: except Exception,e:
log_error("Exception running help function %s from module %s: %s" % (str(modules[module]['help']),str(module),str(e))) log_error("Exception running help function %s from module %s: %s" % (str(modules[module]['help']),str(module),str(e)))
else: else:
SendTo(nick,'No help found for module %s' % module) link.send_private('No help found for module %s' % module)
def UnregisterModule(module): def UnregisterModule(module):
global commands global commands

View File

@ -35,7 +35,7 @@ min_withdraw_amount = None # None defaults to the withdrawal fee
withdrawal_mixin=0 withdrawal_mixin=0
disable_withdraw_on_error = True disable_withdraw_on_error = True
admins = ["moneromooo", "moneromoo"] admins = ["freenode:moneromooo", "freenode:moneromoo"]
# list of nicks to ignore for rains - bots, trolls, etc # list of nicks to ignore for rains - bots, trolls, etc
no_rain_to_nicks = [] no_rain_to_nicks = []

19
tipbot/group.py Normal file
View File

@ -0,0 +1,19 @@
#!/bin/python
#
# Cryptonote tipbot - group
# Copyright 2015 moneromooo
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
class Group:
def __init__(self,network,name):
self.network=network
self.name=name
def send(self,msg):
self.network.send_group(self,msg)

View File

@ -1,515 +0,0 @@
#!/bin/python
#
# Cryptonote tipbot - IRC routines
# Copyright 2014 moneromooo
# Inspired by "Simple Python IRC bot" by berend
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
import sys
import socket
import ssl
import select
import time
import string
import base64
import re
import tipbot.config as config
from tipbot.log import log, log_error, log_warn, log_info, log_log
irc_line_delay = 0
irc = None
sslirc = None
irc_password = ""
irc_min_send_delay = 0.05 # seconds
irc_max_send_delay = 1.1 # seconds
last_ping_time = time.time()
last_send_time = 0
current_send_delay = irc_min_send_delay
irc_network = None
irc_port = None
irc_name = None
irc_quitting = False
userstable=dict()
registered_users=set()
def log_IRCRECV(msg):
log("IRCRECV",msg)
def log_IRCSEND(msg):
log("IRCSEND",msg)
def SendIRC(msg):
global last_send_time, current_send_delay
t = time.time()
dt = t - last_send_time
if dt < current_send_delay:
time.sleep (current_send_delay - dt)
current_send_delay = current_send_delay * 1.5
if current_send_delay > irc_max_send_delay:
current_send_delay = irc_max_send_delay
else:
while dt > current_send_delay * 1.5:
dt = dt - current_send_delay
current_send_delay = current_send_delay / 1.5
if current_send_delay < irc_min_send_delay:
current_send_delay = irc_min_send_delay
break
log_IRCSEND(msg)
irc_send(msg + '\r\n')
last_send_time = time.time()
def irc_recv(size,flags=None):
if config.irc_use_ssl:
return sslirc.read(size)
else:
return irc.recv(size,flags)
def irc_send(data):
if config.irc_use_ssl:
return sslirc.write(data)
else:
return irc.send(data)
def connect_to_irc(network,port,name,password,delay):
global irc
global sslirc
global irc_line_delay
global irc_network
global irc_port
global irc_line_delay
global irc_name
global irc_password
irc_network=network
irc_port=port
irc_name=name
irc_line_delay = delay
irc_password=password
log_info('Connecting to IRC at %s:%u' % (network, port))
try:
irc = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
if config.irc_use_ssl:
try:
raise RuntimeError('')
irc_ssl_context = ssl.create_default_context()
sslirc = irc_ssl_context.wrap_socket(irc, network)
sslirc.connect ( ( network, port ) )
except Exception,e:
log_warn('Failed to create SSL context, using fallback code')
irc.connect ( ( network, port ) )
sslirc = socket.ssl(irc)
except Exception, e:
log_error( 'Error initializing IRC: %s' % str(e))
exit()
log_IRCRECV(irc_recv(4096))
if config.irc_use_sasl:
SendIRC('CAP REQ :sasl')
else:
SendIRC ( 'PASS *********')
SendIRC ( 'NICK %s' % name)
SendIRC ( 'USER %s %s %s :%s' % (name, name, name, name))
return irc
def reconnect_to_irc():
connect_to_irc(irc_network,irc_port,irc_name,irc_password,irc_line_delay)
def SendTo(where,msg):
SendIRC ('PRIVMSG ' + where + ' : ' + msg)
def Join(chan):
SendIRC ( 'JOIN ' + chan)
def Part(chan):
SendIRC ( 'PART ' + chan)
def Quit(msg):
global irc_quitting
irc_quitting = True
SendIRC ( 'QUIT%s' % msg)
def Who(chan):
userstable[chan] = dict()
SendIRC ( 'WHO ' + chan)
def GetNick(data): # Return Nickname
nick = data.split('!')[0]
nick = nick.replace(':', ' ')
nick = nick.replace(' ', '')
nick = nick.strip(' \t\n\r')
return nick
def GetSendTo(nick,chan):
if chan[0] == '#':
return chan
return nick
def UpdateLastActiveTime(chan,nick):
if chan[0] != '#':
return
if not chan in userstable:
log_error("UpdateLastActiveTime: %s spoke in %s, but %s not found in users table" % (nick, chan, chan))
userstable[chan] = dict()
if not nick in userstable[chan]:
log_error("UpdateLastActiveTime: %s spoke in %s, but was not found in that channel's users table" % (nick, chan))
userstable[chan][nick] = None
userstable[chan][nick] = time.time()
def GetTimeSinceActive(chan,nick):
if not chan in userstable:
log_error("GetTimeSinceActive: channel %s not found in users table" % chan)
return None
if not nick in userstable[chan]:
log_error("GetTimeSinceActive: %s not found in channel %s's users table" % (nick, chan))
return None
t = userstable[chan][nick]
if t == None:
return None
dt = time.time() - t
if dt < 0:
log_error("GetTimeSinceActive: %s active in %s in the future" % (nick, chan))
return None
return dt
def GetActiveNicks(chan,seconds):
nicks = []
if not chan in userstable:
return []
now = time.time()
for nick in userstable[chan]:
t = userstable[chan][nick]
if t == None:
continue
dt = now - t
if dt < 0:
log_error("GetActiveNicks: %s active in %s in the future" % (nick, chan))
continue
if dt < seconds:
nicks.append(nick)
return nicks
def GetUsersTable():
return userstable
def IsAcceptableCommandPrefix(s):
s=s.strip()
log_log('checking whether %s is an acceptable command prefix' % s)
if s=="":
return True
if re.match("%s[\t ]*[:,]?$"%config.tipbot_name, s):
return True
return False
#def Op(to_op, chan):
# SendIRC( 'MODE ' + chan + ' +o: ' + to_op)
#
#def DeOp(to_deop, chan):
# SendIRC( 'MODE ' + chan + ' -o: ' + to_deop)
#
#def Voice(to_v, chan):
# SendIRC( 'MODE ' + chan + ' +v: ' + to_v)
#
#def DeVoice(to_dv, chan):
# SendIRC( 'MODE ' + chan + ' -v: ' + to_dv)
buffered_data = ""
def GetIRCLine():
global buffered_data
idx = buffered_data.find("\n")
if idx == -1:
try:
(r,w,x)=select.select([irc.fileno()],[],[],1)
if irc.fileno() in r:
newdata=irc_recv(4096,socket.MSG_DONTWAIT)
else:
newdata = None
if irc.fileno() in x:
log_error('getline: IRC socket in exception set')
newdata = None
except Exception,e:
log_error('getline: Exception: %s' % str(e))
# Broken pipe when we get kicked for spam
if str(e).find("Broken pipe") != -1:
raise
newdata = None
if newdata == None:
return None
buffered_data+=newdata
idx = buffered_data.find("\n")
if idx == -1:
ret = buffered_data
buffered_data = ""
return ret
ret = buffered_data[0:idx+1]
buffered_data = buffered_data[idx+1:]
return ret
def IRCLoop(on_idle,on_identified,on_command):
global userstable
global registered_users
global last_ping_time
while True:
action = None
try:
data = GetIRCLine()
except Exception,e:
log_warn('Exception from GetIRCLine, we were probably disconnected, reconnecting in 5 seconds')
time.sleep(5)
last_ping_time = time.time()
reconnect_to_irc()
continue
# All that must be done even when nothing from IRC - data may be None here
on_idle()
if data == None:
if time.time() - last_ping_time > config.irc_timeout_seconds:
log_warn('%s seconds without PING, reconnecting in 5 seconds' % config.irc_timeout_seconds)
time.sleep(5)
last_ping_time = time.time()
reconnect_to_irc()
continue
data = data.strip("\r\n")
log_IRCRECV(data)
# consider any IRC data as a ping
last_ping_time = time.time()
if data.find ( config.irc_welcome_line ) != -1:
userstable = dict()
registered_users.clear()
if not config.irc_use_sasl:
SendTo("nickserv", "IDENTIFY %s" % irc_password)
for chan in config.irc_channels:
Join(chan)
#ScanWho(None,[chan])
if data.find ( 'PING' ) == 0:
log_log('Got PING, replying PONG')
last_ping_time = time.time()
SendIRC ( 'PONG ' + data.split() [ 1 ])
continue
if data.startswith('AUTHENTICATE +'):
if config.irc_use_sasl:
authstring = config.irc_sasl_name + chr(0) + config.irc_sasl_name + chr(0) + irc_password
SendIRC('AUTHENTICATE %s' % base64.b64encode(authstring))
else:
log_warn('Got AUTHENTICATE while not using SASL')
if data.find('ERROR :Closing Link:') == 0:
if irc_quitting:
log_info('IRC stopped, bye')
break
log_warn('We were kicked from IRC, reconnecting in 5 seconds')
time.sleep(5)
last_ping_time = time.time()
reconnect_to_irc()
continue
#--------------------------- Action check --------------------------------#
if data.find(':') == -1:
continue
try:
cparts = data.lstrip(':').split(' :')
if len(cparts) == 0:
continue
#if len(cparts) >= 9:
# idx_colon = data.find(':',1)
# idx_space = data.find(' ')
# if idx_space and idx_colon < idx_space and re.search("@([0-9a-fA-F]+:){7}[0-9a-fA-F]+", data):
# log_info('Found IPv6 address in non-text, restructuring')
# idx = data.rfind(':')
# cparts = [ cparts[0], "".join(cparts[1:]) ]
if len(cparts) >= 2:
text = cparts[1]
else:
text = ""
parts = cparts[0].split(' ')
who = parts[0]
action = parts[1]
if len(parts) >= 3:
chan = parts[2]
else:
chan = None
except Exception, e:
log_error('main parser: Exception, continuing: %s' % str(e))
continue
if action == None:
continue
#print 'cparts: ', str(cparts)
#print 'parts: ', str(parts)
#print 'text: ', text
#print 'who: ', who
#print 'action: ', action
#print 'chan: ', chan
# if data.find('#') != -1:
# action = data.split('#')[0]
# action = action.split(' ')[1]
# if data.find('NICK') != -1:
# if data.find('#') == -1:
# action = 'NICK'
#----------------------------- Actions -----------------------------------#
try:
if action == 'CAP':
if parts[2] == '*' and parts[3] == 'ACK':
log_info('CAP ACK received from server')
SendIRC('AUTHENTICATE PLAIN')
elif parts[2] == '*' and parts[3] == 'NAK':
log_info('CAP NAK received from server')
log_error('Failed to negotiate SASL')
exit()
else:
log_warn('Unknown CAP line received from server: %s' % data)
if action == 'NOTICE':
if text.find ('throttled due to flooding') >= 0:
log_warn('Flood protection kicked in, outgoing messages lost')
if who == "NickServ!NickServ@services.":
#if text.find('Information on ') != -1:
# ns_nick = text.split(' ')[2].strip("\002")
# print 'NickServ says %s is registered' % ns_nick
# PerformNextAction(ns_nick, True)
#elif text.find(' is not registered') != -1:
# ns_nick = text.split(' ')[0].strip("\002")
# print 'NickServ says %s is not registered' % ns_nick
# PerformNextAction(ns_nick, False)
if text.find(' ACC ') != -1:
stext = text.split(' ')
ns_nick = stext[0]
ns_acc = stext[1]
ns_status = stext[2]
if ns_acc == "ACC":
if ns_status == "3":
log_info('NickServ says %s is identified' % ns_nick)
on_identified(ns_nick, True)
else:
log_info('NickServ says %s is not identified' % ns_nick)
on_identified(ns_nick, False)
else:
log_error('ACC line not as expected...')
elif action == '903':
log_info('SASL authentication success')
SendIRC('CAP END')
elif action in ['902', '904', '905', '906']:
log_error('SASL authentication failed (%s)' % action)
elif action == '352':
try:
who_chan = parts[3]
who_chan_user = parts[7]
if not who_chan_user in userstable[who_chan]:
userstable[who_chan][who_chan_user] = None
log_log("New list of users in %s: %s" % (who_chan, str(userstable[who_chan].keys())))
except Exception,e:
log_error('Failed to parse "352" line: %s: %s' % (data, str(e)))
elif action == '353':
try:
who_chan = parts[4]
who_chan_users = cparts[1].split(" ")
log_info('who_chan: %s' % str(who_chan))
log_info('who_chan_users: %s' % str(who_chan_users))
for who_chan_user in who_chan_users:
if not who_chan_user in userstable[who_chan]:
if who_chan_user[0] == "@":
who_chan_user = who_chan_user[1:]
userstable[who_chan][who_chan_user] = None
log_log("New list of users in %s: %s" % (who_chan, str(userstable[who_chan].keys())))
except Exception,e:
log_error('Failed to parse "353" line: %s: %s' % (data, str(e)))
elif action == 'PRIVMSG':
UpdateLastActiveTime(chan,GetNick(who))
# resplit to avoid splitting text that contains ':'
text = data.split(':',2)[2]
text=text.strip()
exidx = text.find('!')
if exidx != -1 and len(text)>exidx+1 and text[exidx+1] in string.ascii_letters and IsAcceptableCommandPrefix(text[:exidx]):
cmd = text.split('!')[1]
cmd = cmd.split(' ')
cmd[0] = cmd[0].strip(' \t\n\r')
log_log('Found command from %s: "%s" in channel "%s"' % (who, str(cmd), str(chan)))
#if cmd[0] == 'join':
# Join('#' + cmd[1])
#elif cmd[0] == 'part':
# Part('#' + cmd[1])
on_command(cmd,chan,who)
elif action == 'JOIN':
nick = GetNick(who)
log_info('%s joined the channel' % nick)
if not chan in userstable:
userstable[chan] = dict()
if nick in userstable[chan]:
log_warn('%s joined, but already in %s' % (nick, chan))
else:
userstable[chan][nick] = None
log_log("New list of users in %s: %s" % (chan, str(userstable[chan].keys())))
elif action == 'PART':
nick = GetNick(who)
log_info('%s left the channel' % nick)
if not nick in userstable[chan]:
log_warn('%s left, but was not in %s' % (nick, chan))
else:
del userstable[chan][nick]
log_log("New list of users in %s: %s" % (chan, str(userstable[chan].keys())))
elif action == 'QUIT':
nick = GetNick(who)
log_info('%s quit' % nick)
removed_list = ""
for chan in userstable:
log_log("Checking in %s" % chan)
if nick in userstable[chan]:
removed_list = removed_list + " " + chan
del userstable[chan][nick]
log_log("New list of users in %s: %s" % (chan, str(userstable[chan].keys())))
elif action == 'KICK':
nick = parts[3]
log_info('%s was kicked' % nick)
removed_list = ""
for chan in userstable:
log_log("Checking in %s" % chan)
if nick in userstable[chan]:
removed_list = removed_list + " " + chan
del userstable[chan][nick]
log_log("New list of users in %s: %s" % (chan, str(userstable[chan].keys())))
elif action == 'NICK':
nick = GetNick(who)
new_nick = cparts[len(cparts)-1]
log_info('%s renamed to %s' % (nick, new_nick))
for c in userstable:
log_log('checking %s' % c)
if nick in userstable[c]:
del userstable[c][nick]
if new_nick in userstable[c]:
log_warn('%s is the new name of %s, but was already in %s' % (new_nick, nick, c))
else:
userstable[c][new_nick] = None
log_log("New list of users in %s: %s" % (c, str(userstable[c].keys())))
except Exception,e:
log_error('Exception in top level action processing: %s' % str(e))

62
tipbot/link.py Normal file
View File

@ -0,0 +1,62 @@
#!/bin/python
#
# Cryptonote tipbot - link
# Copyright 2015 moneromooo
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
from tipbot.log import log_error, log_warn, log_info, log_log
class Link:
def __init__(self,network,user,group=None,data=None):
self.network=network
self.user=user
self.group=group
self.data=data
self.batch_message = None
self.batch_message_private = None
def __repr__(self):
return '<link: network %s, user %s, group %s, data %s>' % (str(self.network),str(self.user),str(self.group),str(self.data))
def identity(self):
return self.network.name+":"+self.user.nick
def send(self,msg):
if self.batch_message != None:
self.batch_message.append(msg)
else:
return self._send(msg)
def send_private(self,msg):
if self.batch_message_private != None:
self.batch_message_private.append(msg)
else:
return self._send_private(msg)
def _send(self,msg):
if self.group:
self.network.send_group(self.group,msg,self.data)
else:
self.network.send_user(self.user,msg,self.data)
def _send_private(self,msg):
self.network.send_user(self.user,msg,self.data)
def batch_send_start(self):
self.batch_message = []
self.batch_message_private = []
def batch_send_done(self):
if self.batch_message != None:
if len(self.batch_message)>0:
self._send("\n".join(self.batch_message))
self.batch_message = None
if self.batch_message_private != None:
if len(self.batch_message_private)>0:
self._send_private("\n".join(self.batch_message_private))
self.batch_message_private = None

View File

@ -20,95 +20,93 @@ import tipbot.config as config
from tipbot.log import log_error, log_warn, log_info, log_log from tipbot.log import log_error, log_warn, log_info, log_log
import tipbot.coinspecs as coinspecs import tipbot.coinspecs as coinspecs
from tipbot.utils import * from tipbot.utils import *
from tipbot.ircutils import *
from tipbot.command_manager import * from tipbot.command_manager import *
from tipbot.redisdb import * from tipbot.redisdb import *
from tipbot.betutils import * from tipbot.betutils import *
def GetHouseBalance(nick,chan,cmd): def GetHouseBalance(link,cmd):
sendto=GetSendTo(nick,chan)
try: try:
balance = RetrieveHouseBalance() balance = RetrieveHouseBalance()
except Exception,e: except Exception,e:
log_error('Failed to retrieve house balance: %s' % str(e)) log_error('Failed to retrieve house balance: %s' % str(e))
SendTo(sendto, 'An error occured') link.send('An error occured')
return return
SendTo(sendto, 'House balance: %s' % AmountToString(balance)) link.send('House balance: %s' % AmountToString(balance))
def Roll(nick): def Roll(link):
identity=link.identity()
try: try:
if redis_hexists('dice:rolls',nick): if redis_hexists('dice:rolls',identity):
rolls = redis_hget('dice:rolls',nick) rolls = redis_hget('dice:rolls',identity)
rolls = long(rolls) + 1 rolls = long(rolls) + 1
else: else:
rolls = 1 rolls = 1
except Exception,e: except Exception,e:
log_error('Failed to prepare roll for %s: %s' % (nick, str(e))) log_error('Failed to prepare roll for %s: %s' % (identity, str(e)))
raise raise
try: try:
s = GetServerSeed(nick,'dice') + ":" + GetPlayerSeed(nick,'dice') + ":" + str(rolls) s = GetServerSeed(link,'dice') + ":" + GetPlayerSeed(link,'dice') + ":" + str(rolls)
sh = hashlib.sha256(s).hexdigest() sh = hashlib.sha256(s).hexdigest()
roll = float(long(sh[0:8],base=16))/0x100000000 roll = float(long(sh[0:8],base=16))/0x100000000
return rolls, roll return rolls, roll
except Exception,e: except Exception,e:
log_error('Failed to roll for %s: %s' % (nick,str(e))) log_error('Failed to roll for %s: %s' % (identity,str(e)))
raise raise
def Dice(nick,chan,cmd): def Dice(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
try: try:
amount=float(cmd[1]) amount=float(cmd[1])
units=long(amount*coinspecs.atomic_units) units=long(amount*coinspecs.atomic_units)
multiplier = float(cmd[2]) multiplier = float(cmd[2])
overunder=GetParam(cmd,3) overunder=GetParam(cmd,3)
except Exception,e: except Exception,e:
SendTo(sendto, "Usage: dice amount multiplier [over|under]") link.send("Usage: dice amount multiplier [over|under]")
return return
if multiplier < 0.1 or multiplier > 10: if multiplier < 0.1 or multiplier > 10:
SendTo(sendto, "Invalid multiplier: should be between 0.1 and 10") link.send("Invalid multiplier: should be between 0.1 and 10")
return return
if overunder == "over": if overunder == "over":
under=False under=False
elif overunder == "under" or not overunder: elif overunder == "under" or not overunder:
under=True under=True
else: else:
SendTo(sendto, "Usage: dice amount multiplier [over|under]") link.send("Usage: dice amount multiplier [over|under]")
return return
log_info("Dice: %s wants to bet %s at x%f, %s target" % (nick, AmountToString(units), multiplier, "under" if under else "over")) log_info("Dice: %s wants to bet %s at x%f, %s target" % (identity, AmountToString(units), multiplier, "under" if under else "over"))
potential_loss = amount * multiplier potential_loss = amount * multiplier
valid,reason = IsBetAmountValid(amount,config.dice_min_bet,config.dice_max_bet,potential_loss,config.dice_max_loss,config.dice_max_loss_ratio) valid,reason = IsBetAmountValid(amount,config.dice_min_bet,config.dice_max_bet,potential_loss,config.dice_max_loss,config.dice_max_loss_ratio)
if not valid: if not valid:
log_info("Dice: %s's bet refused: %s" % (nick, reason)) log_info("Dice: %s's bet refused: %s" % (identity, reason))
SendTo(sendto, "%s: %s" % (nick, reason)) link.send("%s: %s" % (link.user.nick, reason))
return return
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",identity)
if balance == None: if balance == None:
balance = 0 balance = 0
balance=long(balance) balance=long(balance)
if units > balance: if units > balance:
log_error ('%s does not have enough balance' % nick) log_error ('%s does not have enough balance' % identity)
SendTo(sendto, "You only have %s" % (AmountToString(balance))) link.send("You only have %s" % (AmountToString(balance)))
return return
except Exception,e: except Exception,e:
log_error ('failed to query balance') log_error ('failed to query balance')
SendTo(sendto, "Failed to query balance") link.send("Failed to query balance")
return return
try: try:
rolls, roll = Roll(nick) rolls, roll = Roll(link)
except: except:
SendTo(sendto,"An error occured") link.send("An error occured")
return return
target = (1 - config.dice_edge) / (1+multiplier) target = (1 - config.dice_edge) / (1+multiplier)
if not under: if not under:
target = 1 - target target = 1 - target
log_info("Dice: %s's #%d roll: %.16g, target %s %.16g" % (nick, rolls, roll, "under" if under else "over", target)) log_info("Dice: %s's #%d roll: %.16g, target %s %.16g" % (identity, rolls, roll, "under" if under else "over", target))
lose_units = units lose_units = units
win_units = long(units * multiplier) win_units = long(units * multiplier)
@ -118,129 +116,140 @@ def Dice(nick,chan,cmd):
else: else:
win = roll >= target win = roll >= target
if win: if win:
msg = "%s wins %s on roll #%d! %.16g %s %.16g" % (nick, AmountToString(win_units), rolls, roll, "<=" if under else ">=", target) msg = "%s wins %s on roll #%d! %.16g %s %.16g" % (link.user.nick, AmountToString(win_units), rolls, roll, "<=" if under else ">=", target)
else: else:
msg = "%s loses %s on roll #%d. %.16g %s %.16g" % (nick, AmountToString(lose_units), rolls, roll, ">" if under else "<", target) msg = "%s loses %s on roll #%d. %.16g %s %.16g" % (link.user.nick, AmountToString(lose_units), rolls, roll, ">" if under else "<", target)
try: try:
RecordGameResult(nick,chan,"dice",win,not win,win_units if win else lose_units) RecordGameResult(link,"dice",win,not win,win_units if win else lose_units)
except: except:
return return
redis_hset("dice:rolls",nick,rolls) redis_hset("dice:rolls",identity,rolls)
SendTo(sendto, "%s" % msg) link.send("%s" % msg)
def ShowDiceStats(sendto,snick,title): def ShowDiceStats(link,sidentity,title):
return ShowGameStats(sendto,snick,title,"dice") return ShowGameStats(link,sidentity,title,"dice")
def GetDiceStats(nick,chan,cmd): def GetDiceStats(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
snick = GetParam(cmd,1) sidentity = GetParam(cmd,1)
if snick and snick != nick: if sidentity:
if not IsAdmin(nick): sidentity=IdentityFromString(link,sidentity)
log_error('%s is not admin, cannot see dice stats for %s' % (nick, snick)) if sidentity and sidentity != identity:
SendTo(sendto,'Access denied') if not IsAdmin(link):
log_error('%s is not admin, cannot see dice stats for %s' % (identity, sidentity))
link.send('Access denied')
return return
else: else:
snick=nick sidentity=identity
ShowDiceStats(sendto,snick,snick) ShowDiceStats(link,sidentity,sidentity)
ShowDiceStats(sendto,"reset:"+snick,'%s since reset' % snick) ShowDiceStats(link,"reset:"+sidentity,'%s since reset' % sidentity)
ShowDiceStats(sendto,'','overall') ShowDiceStats(link,'','overall')
def ResetDiceStats(nick,chan,cmd): def ResetDiceStats(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
snick = GetParam(cmd,1) sidentity = GetParam(cmd,1)
if snick and snick != nick: if sidentity:
if not IsAdmin(nick): sidentity=IdentityFromString(link,sidentity)
log_error('%s is not admin, cannot see dice stats for %s' % (nick, snick)) if sidentity and sidentity != identity:
SendTo(sendto,'Access denied') if not IsAdmin(link):
log_error('%s is not admin, cannot see dice stats for %s' % (identity, sidentity))
link.send('Access denied')
return return
else: else:
snick=nick sidentity=identity
try: try:
ResetGameStats(sendto,snick,"dice") ResetGameStats(link,sidentity,"dice")
except Exception,e: except Exception,e:
SendTo(sendto,"An error occured") link.send("An error occured")
def PlayerSeed(nick,chan,cmd): def PlayerSeed(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
fair_string = GetParam(cmd,1) fair_string = GetParam(cmd,1)
if not fair_string: if not fair_string:
SendTo(nick, "Usage: !playerseed <string>") link.send("Usage: !playerseed <string>")
return return
try: try:
SetPlayerSeed(nick,'dice',fair_string) SetPlayerSeed(link,'dice',fair_string)
except Exception,e: except Exception,e:
log_error('Failed to save player seed for %s: %s' % (nick, str(e))) log_error('Failed to save player seed for %s: %s' % (identity, str(e)))
SendTo(sendto, 'An error occured') link.send('An error occured')
try:
ps = GetPlayerSeed(link,'dice')
except Exception,e:
log_error('Failed to retrieve newly set player seed for %s: %s' % (identity, str(e)))
link.send('An error occured')
return
link.send('Your new player seed is: %s' % ps)
def FairCheck(nick,chan,cmd): def FairCheck(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
try: try:
seed = GetServerSeed(nick,'dice') seed = GetServerSeed(link,'dice')
except Exception,e: except Exception,e:
log_error('Failed to get server seed for %s: %s' % (nick,str(e))) log_error('Failed to get server seed for %s: %s' % (identity,str(e)))
SendTo(seed,'An error has occured') link.send('An error has occured')
return return
try: try:
GenerateServerSeed(nick,'dice') GenerateServerSeed(link,'dice')
except Exception,e: except Exception,e:
log_error('Failed to generate server seed for %s: %s' % (nick,str(e))) log_error('Failed to generate server seed for %s: %s' % (identity,str(e)))
SendTo(seed,'An error has occured') link.send('An error has occured')
return return
SendTo(sendto, 'Your server seed was %s - it has now been reset; see !fair for details' % str(seed)) link.send('Your server seed was %s - it has now been reset; see !fair for details' % str(seed))
def Seeds(nick,chan,cmd): def Seeds(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
try: try:
sh = GetServerSeedHash(nick,'dice') sh = GetServerSeedHash(link,'dice')
ps = GetPlayerSeed(nick,'dice') ps = GetPlayerSeed(link,'dice')
except Exception,e: except Exception,e:
log_error('Failed to get server seed for %s: %s' % (nick,str(e))) log_error('Failed to get server seed for %s: %s' % (identity,str(e)))
SendTo(seed,'An error has occured') link.send('An error has occured')
return return
SendTo(sendto, 'Your server seed hash is %s' % str(sh)) link.send('Your server seed hash is %s' % str(sh))
if ps == "": if ps == "":
SendTo(sendto, 'Your have not set a player seed') link.send('Your have not set a player seed')
else: else:
SendTo(sendto, 'Your player seed hash is %s' % str(ps)) link.send('Your player seed hash is %s' % str(ps))
def Fair(nick,chan,cmd): def Fair(link,cmd):
SendTo(nick,"%s's dice betting is provably fair" % config.tipbot_name) link.send("%s's dice betting is provably fair" % config.tipbot_name)
SendTo(nick,"Your rolls are determined by three pieces of information:") link.send("Your rolls are determined by three pieces of information:")
SendTo(nick," - your server seed. You can see its hash with !seeds") link.send(" - your server seed. You can see its hash with !seeds")
SendTo(nick," - your player seed. Empty by default, you can set it with !playerseed") link.send(" - your player seed. Empty by default, you can set it with !playerseed")
SendTo(nick," - the roll number, displayed with each bet you make") link.send(" - the roll number, displayed with each bet you make")
SendTo(nick,"To verify past rolls were fair, use !faircheck") link.send("To verify past rolls were fair, use !faircheck")
SendTo(nick,"You will be given your server seed, and a new one will be generated") link.send("You will be given your server seed, and a new one will be generated")
SendTo(nick,"for future rolls. Then follow these steps:") link.send("for future rolls. Then follow these steps:")
SendTo(nick,"Calculate the SHA-256 sum of serverseed:playerseed:rollnumber") link.send("Calculate the SHA-256 sum of serverseed:playerseed:rollnumber")
SendTo(nick,"Take the first 8 characters of this sum to make an hexadecimal") link.send("Take the first 8 characters of this sum to make an hexadecimal")
SendTo(nick,"number, and divide it by 0x100000000. You will end up with a number") link.send("number, and divide it by 0x100000000. You will end up with a number")
SendTo(nick,"between 0 and 1 which was your roll for that particular bet") link.send("between 0 and 1 which was your roll for that particular bet")
SendTo(nick,"See !faircode for Python code implementing this check") link.send("See !faircode for Python code implementing this check")
def FairCode(nick,chan,cmd): def FairCode(link,cmd):
SendTo(nick,"This Python 2 code takes the seeds and roll number and outputs the roll") link.send("This Python 2 code takes the seeds and roll number and outputs the roll")
SendTo(nick,"for the corresponding game. Run it with three arguments: server seed,") link.send("for the corresponding game. Run it with three arguments: server seed,")
SendTo(nick,"player seed (use '' if you did not set any), and roll number.") link.send("player seed (use '' if you did not set any), and roll number.")
SendTo(nick,"import sys,hashlib,random") link.send("import sys,hashlib,random")
SendTo(nick,"try:") link.send("try:")
SendTo(nick," s=hashlib.sha256(sys.argv[1]+':'+sys.argv[2]+':'+sys.argv[3]).hexdigest()") link.send(" s=hashlib.sha256(sys.argv[1]+':'+sys.argv[2]+':'+sys.argv[3]).hexdigest()")
SendTo(nick," roll = float(long(s[0:8],base=16))/0x100000000") link.send(" roll = float(long(s[0:8],base=16))/0x100000000")
SendTo(nick," print '%.16g' % roll") link.send(" print '%.16g' % roll")
SendTo(nick,"except:") link.send("except:")
SendTo(nick," print 'need serverseed, playerseed, and roll number'") link.send(" print 'need serverseed, playerseed, and roll number'")
def DiceHelp(nick,chan): def DiceHelp(link):
SendTo(nick,"The dice module is a provably fair %s dice betting game" % coinspecs.name) link.send("The dice module is a provably fair %s dice betting game" % coinspecs.name)
SendTo(nick,"Basic usage: !dice <amount> <multiplier> [over|under]") link.send("Basic usage: !dice <amount> <multiplier> [over|under]")
SendTo(nick,"The goal is to get a roll under (or over, at your option) a target that depends") link.send("The goal is to get a roll under (or over, at your option) a target that depends")
SendTo(nick,"on your chosen profit multiplier (1 for even money)") link.send("on your chosen profit multiplier (1 for even money)")
SendTo(nick,"See !fair and !faircode for a description of the provable fairness of the game") link.send("See !fair and !faircode for a description of the provable fairness of the game")
SendTo(nick,"See !faircheck to get the server seed to check past rolls were fair") link.send("See !faircheck to get the server seed to check past rolls were fair")

View File

@ -0,0 +1,54 @@
#!/bin/python
#
# Cryptonote tipbot - Freenode
# Copyright 2015 moneromooo
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
import sys
import string
import tipbot.config as config
from tipbot.log import log_error, log_warn, log_info, log_log
from tipbot.utils import *
from tipbot.command_manager import *
from irc import *
class FreenodeNetwork(IRCNetwork):
def __init__(self):
IRCNetwork.__init__(self,"freenode")
def login(self):
self.send_to("nickserv", "IDENTIFY %s" % self.password)
def identify(self,link):
nick = link.user.nick
log_info('Asking nickserv whether %s is identified' % nick)
self.send_to('nickserv', "ACC " + nick)
def on_notice(self,who,text):
if who == "NickServ!NickServ@services.":
if text.find(' ACC ') != -1:
stext = text.split(' ')
ns_nick = stext[0]
ns_acc = stext[1]
ns_status = stext[2]
if ns_acc == "ACC":
ns_link=Link(self,User(self,ns_nick),None)
if ns_status == "3":
log_info('NickServ says %s is identified' % ns_nick)
self.registered_users.add(ns_link.identity())
if self.on_identified:
self.on_identified(ns_link,True)
else:
log_info('NickServ says %s is not identified' % ns_nick)
self.registered_users.discard(ns_link.identity())
if self.on_identified:
self.on_identified(ns_link,False)
else:
log_error('ACC line not as expected...')
return True

View File

@ -1,7 +1,7 @@
#!/bin/python #!/bin/python
# #
# Cryptonote tipbot - IRC commands # Cryptonote tipbot - IRC commands
# Copyright 2014 moneromooo # Copyright 2015 moneromooo
# #
# The Cryptonote tipbot is free software; you can redistribute it and/or # The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published # modify it under the terms of the GNU General Public License as published
@ -10,40 +10,498 @@
# #
import sys import sys
import socket
import ssl
import select
import time
import string import string
import base64
import re
import tipbot.config as config import tipbot.config as config
from tipbot.log import log_error, log_warn, log_info, log_log from tipbot.log import log, log_error, log_warn, log_info, log_log
from tipbot.utils import * from tipbot.utils import *
from tipbot.ircutils import * from tipbot.network import *
from tipbot.command_manager import * from tipbot.command_manager import *
def JoinChannel(nick,chan,cmd): irc_min_send_delay = 0.05 # seconds
sendto=GetSendTo(nick,chan) irc_max_send_delay = 1.1 # seconds
def GetNick(data): # Return Nickname
nick = data.split('!')[0]
nick = nick.replace(':', ' ')
nick = nick.replace(' ', '')
nick = nick.strip(' \t\n\r')
return nick
class IRCNetwork(Network):
def __init__(self,name):
Network.__init__(self,name)
self.userstable = dict()
self.registered_users = set()
self.last_send_time=0
self.last_ping_time=0
self.current_send_delay = irc_min_send_delay
self.quitting = False
self.buffered_data = ""
def connect(self,host,port,login,password,delay):
return self._connect(host,port,login,password,delay)
def disconnect(self):
self._irc_sendmsg ('QUIT')
if self.sslirc:
self.sslirc.close()
self.sslirc = None
self.irc.close()
self.irc = None
def send_to(self,where,msg):
for line in msg.split("\n"):
line=line.strip('\r')
if len(line)>0:
self._irc_sendmsg('PRIVMSG '+where+' :'+line)
def send_group(self,group,msg,data=None):
self.send_to(group.name,msg)
def send_user(self,user,msg,data=None):
self.send_to(user.nick,msg)
def is_identified(self,link):
return link.identity() in self.registered_users
def join(self,chan):
self._irc_sendmsg('JOIN '+chan)
def part(self,chan):
self._irc_sendmsg('PART '+chan)
def quit(self,msg=None):
self.quitting = True
if msg:
self._irc_sendmsg('QUIT%s '%msg)
else:
self._irc_sendmsg('QUIT')
def dump_users(self):
log_info('users on %s: %s' % (self.name,str(self.userstable)))
def update_users_list(self,chan):
if chan:
self._irc_sendmsg('WHO '+chan)
def update_last_active_time(self,chan,nick):
if chan[0] != '#':
return
if not chan in self.userstable:
log_error("IRCNetwork:update_last_active_time: %s spoke in %s, but %s not found in users table" % (nick, chan, chan))
self.userstable[chan] = dict()
if not nick in self.userstable[chan]:
log_error("IRCNetwork:update_last_active_time: %s spoke in %s, but was not found in that channel's users table" % (nick, chan))
self.userstable[chan][nick] = None
self.userstable[chan][nick] = time.time()
def get_last_active_time(self,nick,chan):
if not chan in self.userstable:
log_error("IRCNetwork:get_last_active_time: channel %s not found in users table" % chan)
return None
if not nick in self.userstable[chan]:
log_error("IRCNetwork:get_last_active_time: %s not found in channel %s's users table" % (nick, chan))
return None
return self.userstable[chan][nick]
def get_active_users(self,seconds,chan):
nicks = []
if not chan in self.userstable:
return []
now = time.time()
for nick in self.userstable[chan]:
t = self.userstable[chan][nick]
if t == None:
continue
dt = now - t
if dt < 0:
log_error("IRCNetwork:get_active_users: %s active in %s in the future" % (nick, chan))
continue
if dt < seconds:
nicks.append(Link(self,User(self,nick),Group(self,chan)))
return nicks
def get_users(self,chan):
nicks = []
if not chan in self.userstable:
return []
for nick in self.userstable[chan]:
nicks.append(Link(self,User(self,nick),Group(self,chan)))
return nicks
def is_acceptable_command_prefix(self,s):
s=s.strip()
log_log('checking whether %s is an acceptable command prefix' % s)
if s=="":
return True
if re.match("%s[\t ]*[:,]?$"%config.tipbot_name, s):
return True
return False
def update(self):
try:
data=self._getline()
except Exception,e:
log_warn('Exception from IRCNetwork:_getline, we were probably disconnected, reconnecting in %s seconds' % config.irc_timeout_seconds)
time.sleep(5)
self.last_ping_time = time.time()
self._reconnect()
return True
if data == None:
if time.time() - self.last_ping_time > config.irc_timeout_seconds:
log_warn('%s seconds without PING, reconnecting in 5 seconds' % config.irc_timeout_seconds)
time.sleep(5)
self.last_ping_time = time.time()
self._reconnect()
return True
data = data.strip("\r\n")
self._log_IRCRECV(data)
# consider any IRC data as a ping
self.last_ping_time = time.time()
if data.find ( config.irc_welcome_line ) != -1:
self.userstable = dict()
self.registered_users.clear()
if not config.irc_use_sasl:
self.login()
for chan in config.irc_channels:
self.join(chan)
#ScanWho(None,[chan])
if data.find ( 'PING' ) == 0:
log_log('Got PING, replying PONG')
self.last_ping_time = time.time()
self._irc_sendmsg ( 'PONG ' + data.split() [ 1 ])
return True
if data.startswith('AUTHENTICATE +'):
if config.irc_use_sasl:
authstring = config.irc_sasl_name + chr(0) + config.irc_sasl_name + chr(0) + self.password
self._irc_sendmsg('AUTHENTICATE %s' % base64.b64encode(authstring))
else:
log_warn('Got AUTHENTICATE while not using SASL')
if data.find('ERROR :Closing Link:') == 0:
if self.quitting:
log_info('IRC stopped, bye')
return False
log_warn('We were kicked from IRC, reconnecting in 5 seconds')
time.sleep(5)
self.last_ping_time = time.time()
self._reconnect()
return True
if data.find(':') == -1:
return True
try:
cparts = data.lstrip(':').split(' :')
if len(cparts) == 0:
log_warn('No separator found, ignoring line')
return True
#if len(cparts) >= 9:
# idx_colon = data.find(':',1)
# idx_space = data.find(' ')
# if idx_space and idx_colon < idx_space and re.search("@([0-9a-fA-F]+:){7}[0-9a-fA-F]+", data):
# log_info('Found IPv6 address in non-text, restructuring')
# idx = data.rfind(':')
# cparts = [ cparts[0], "".join(cparts[1:]) ]
if len(cparts) >= 2:
text = cparts[1]
else:
text = ""
parts = cparts[0].split(' ')
who = parts[0]
action = parts[1]
if len(parts) >= 3:
chan = parts[2]
else:
chan = None
except Exception, e:
log_error('main parser: Exception, ignoring line: %s' % str(e))
return True
if action == None:
return True
#print 'cparts: ', str(cparts)
#print 'parts: ', str(parts)
#print 'text: ', text
#print 'who: ', who
#print 'action: ', action
#print 'chan: ', chan
try:
if action == 'CAP':
if parts[2] == '*' and parts[3] == 'ACK':
log_info('CAP ACK received from server')
self._irc_sendmsg('AUTHENTICATE PLAIN')
elif parts[2] == '*' and parts[3] == 'NAK':
log_info('CAP NAK received from server')
log_error('Failed to negotiate SASL')
exit()
else:
log_warn('Unknown CAP line received from server: %s' % data)
if action == 'NOTICE':
if text.find ('throttled due to flooding') >= 0:
log_warn('Flood protection kicked in, outgoing messages lost')
ret = self.on_notice(who,text)
if ret:
return ret
elif action == '903':
log_info('SASL authentication success')
self._irc_sendmsg('CAP END')
elif action in ['902', '904', '905', '906']:
log_error('SASL authentication failed (%s)' % action)
elif action == '352':
try:
who_chan = parts[3]
who_chan_user = parts[7]
if not who_chan_user in self.userstable[who_chan]:
self.userstable[who_chan][who_chan_user] = None
log_log("New list of users in %s: %s" % (who_chan, str(self.userstable[who_chan].keys())))
except Exception,e:
log_error('Failed to parse "352" line: %s: %s' % (data, str(e)))
elif action == '353':
try:
who_chan = parts[4]
who_chan_users = cparts[1].split(" ")
log_info('who_chan: %s' % str(who_chan))
log_info('who_chan_users: %s' % str(who_chan_users))
for who_chan_user in who_chan_users:
if not who_chan_user in self.userstable[who_chan]:
if who_chan_user[0] == "@":
who_chan_user = who_chan_user[1:]
self.userstable[who_chan][who_chan_user] = None
log_log("New list of users in %s: %s" % (who_chan, str(self.userstable[who_chan].keys())))
except Exception,e:
log_error('Failed to parse "353" line: %s: %s' % (data, str(e)))
elif action == 'PRIVMSG':
self.update_last_active_time(chan,GetNick(who))
# resplit to avoid splitting text that contains ':'
text = data.split(':',2)[2]
exidx = text.find('!')
if exidx != -1 and len(text)>exidx+1 and text[exidx+1] in string.ascii_letters and self.is_acceptable_command_prefix(text[:exidx]):
cmd = text.split('!')[1]
cmd = cmd.split(' ')
cmd[0] = cmd[0].strip(' \t\n\r')
log_log('Found command from %s: "%s" in channel "%s"' % (who, str(cmd), str(chan)))
if self.on_command:
self.on_command(Link(self,User(self,GetNick(who)),Group(self,chan) if chan[0]=='#' else None),cmd)
return True
elif action == 'JOIN':
nick = GetNick(who)
log_info('%s joined the channel' % nick)
if not chan in self.userstable:
self.userstable[chan] = dict()
if nick in self.userstable[chan]:
log_warn('%s joined, but already in %s' % (nick, chan))
else:
self.userstable[chan][nick] = None
log_log("New list of users in %s: %s" % (chan, str(self.userstable[chan].keys())))
elif action == 'PART':
nick = GetNick(who)
log_info('%s left the channel' % nick)
if not nick in self.userstable[chan]:
log_warn('%s left, but was not in %s' % (nick, chan))
else:
del self.userstable[chan][nick]
log_log("New list of users in %s: %s" % (chan, str(self.userstable[chan].keys())))
elif action == 'QUIT':
nick = GetNick(who)
log_info('%s quit' % nick)
removed_list = ""
for chan in self.userstable:
log_log("Checking in %s" % chan)
if nick in self.userstable[chan]:
removed_list = removed_list + " " + chan
del self.userstable[chan][nick]
log_log("New list of users in %s: %s" % (chan, str(self.userstable[chan].keys())))
elif action == 'KICK':
nick = parts[3]
log_info('%s was kicked' % nick)
removed_list = ""
for chan in self.userstable:
log_log("Checking in %s" % chan)
if nick in self.userstable[chan]:
removed_list = removed_list + " " + chan
del self.userstable[chan][nick]
log_log("New list of users in %s: %s" % (chan, str(self.userstable[chan].keys())))
elif action == 'NICK':
nick = GetNick(who)
new_nick = cparts[len(cparts)-1]
log_info('%s renamed to %s' % (nick, new_nick))
for c in self.userstable:
log_log('checking %s' % c)
if nick in self.userstable[c]:
del self.userstable[c][nick]
if new_nick in self.userstable[c]:
log_warn('%s is the new name of %s, but was already in %s' % (new_nick, nick, c))
else:
self.userstable[c][new_nick] = None
log_log("New list of users in %s: %s" % (c, str(self.userstable[c].keys())))
except Exception,e:
log_error('Exception in top level action processing: %s' % str(e))
return True
def _log_IRCRECV(self,msg):
log("IRCRECV",msg)
def _log_IRCSEND(self,msg):
log("IRCSEND",msg)
def _irc_recv(self,size,flags=None):
if config.irc_use_ssl:
return self.sslirc.read(size)
else:
return self.irc.recv(size,flags)
def _irc_send(self,data):
if config.irc_use_ssl:
return self.sslirc.write(data)
else:
return self.irc.send(data)
def _irc_sendmsg(self,msg):
t = time.time()
dt = t - self.last_send_time
if dt < self.current_send_delay:
time.sleep (self.current_send_delay - dt)
self.current_send_delay = self.current_send_delay * 1.5
if self.current_send_delay > irc_max_send_delay:
self.current_send_delay = irc_max_send_delay
else:
while dt > self.current_send_delay * 1.5:
dt = dt - self.current_send_delay
self.current_send_delay = self.current_send_delay / 1.5
if self.current_send_delay < irc_min_send_delay:
self.current_send_delay = irc_min_send_delay
break
self._log_IRCSEND(msg)
self._irc_send(msg + '\r\n')
self.last_send_time = time.time()
def _getline(self):
idx = self.buffered_data.find("\n")
if idx == -1:
try:
(r,w,x)=select.select([self.irc.fileno()],[],[],1)
if self.irc.fileno() in r:
newdata=self._irc_recv(4096,socket.MSG_DONTWAIT)
else:
newdata = None
if self.irc.fileno() in x:
log_error('getline: IRC socket in exception set')
newdata = None
except Exception,e:
log_error('getline: Exception: %s' % str(e))
# Broken pipe when we get kicked for spam
if str(e).find("Broken pipe") != -1:
raise
newdata = None
if newdata == None:
return None
self.buffered_data+=newdata
idx = self.buffered_data.find("\n")
if idx == -1:
ret = self.buffered_data
self.buffered_data = ""
return ret
ret = self.buffered_data[0:idx+1]
self.buffered_data = self.buffered_data[idx+1:]
return ret
def _connect(self,host,port,login,password,delay):
self.host=host
self.port=port
self.login=login
self.password=password
self.line_delay=delay
log_info('Connecting to IRC at %s:%u' % (host, port))
self.last_send_time=0
self.last_ping_time = time.time()
self.quitting = False
self.buffered_data = ""
self.userstable=dict()
self.registered_users=set()
try:
self.irc = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
if config.irc_use_ssl:
try:
raise RuntimeError('')
self.irc_ssl_context = ssl.create_default_context()
self.sslirc = self.irc_ssl_context.wrap_socket(self.irc, host)
self.sslirc.connect ( ( host, port ) )
except Exception,e:
log_warn('Failed to create SSL context, using fallback code: %s' % str(e))
self.irc.connect ( ( host, port ) )
self.sslirc = socket.ssl(self.irc)
except Exception, e:
log_error( 'Error initializing IRC: %s' % str(e))
return False
self._log_IRCRECV(self._irc_recv(4096))
if config.irc_use_sasl:
self._irc_sendmsg('CAP REQ :sasl')
else:
self._irc_sendmsg ( 'PASS *********')
self._irc_sendmsg ( 'NICK %s' % login)
self._irc_sendmsg ( 'USER %s %s %s :%s' % (login, login, login, login))
return True
def _reconnect(self):
return self._connect(self.host,self.port,self.login,self.password,self.line_delay)
def JoinChannel(link,cmd):
jchan = GetParam(cmd,1) jchan = GetParam(cmd,1)
if not jchan: if not jchan:
SendTo(sendto,'Usage: join <channel>') link.send('Usage: join <channel>')
rerurn
if jchan[0] != '#':
SendTo(sendto,'Channel name must start with #')
return return
Join(jchan) if jchan[0] != '#':
link.send('Channel name must start with #')
return
network=GetNetworkByType(IRCNetwork)
if not network:
link.send('No IRC network found')
return
network.join(jchan)
def PartChannel(nick,chan,cmd): def PartChannel(link,cmd):
sendto=GetSendTo(nick,chan)
pchan = GetParam(cmd,1) pchan = GetParam(cmd,1)
if pchan: if pchan:
if pchan[0] != '#': if pchan[0] != '#':
SendTo(sendto,'Channel name must start with #') link.send('Channel name must start with #')
return return
else: else:
pchan = chan pchan = chan
Part(pchan) network=GetNetworkByType(IRCNetwork)
if not network:
def QuitIRC(nick,chan,cmd): link.send('No IRC network found')
msg = "" return
for w in cmd[1:]: network.part(pchan)
msg = msg + " " + w
Quit(msg)
RegisterCommand({ RegisterCommand({
'module': __name__, 'module': __name__,
@ -61,10 +519,3 @@ RegisterCommand({
'admin': True, 'admin': True,
'help': "Makes %s part from a channel" % (config.tipbot_name) 'help': "Makes %s part from a channel" % (config.tipbot_name)
}) })
RegisterCommand({
'module': __name__,
'name': 'quit',
'function': QuitIRC,
'admin': True,
'help': "Makes %s quit IRC" % (config.tipbot_name)
})

View File

@ -35,10 +35,7 @@ def GetTipbotAddress():
log_error("GetTipbotAddress: Error retrieving %s's address: %s" % (config.tipbot_name, str(e))) log_error("GetTipbotAddress: Error retrieving %s's address: %s" % (config.tipbot_name, str(e)))
return "ERROR" return "ERROR"
def UpdateCoin(param): def UpdateCoin(data):
irc = param[0]
redisdb = param[1]
global last_wallet_update_time global last_wallet_update_time
if last_wallet_update_time == None: if last_wallet_update_time == None:
last_wallet_update_time = 0 last_wallet_update_time = 0
@ -87,11 +84,13 @@ def UpdateCoin(param):
tx_hash=p["tx_hash"] tx_hash=p["tx_hash"]
amount=p["amount"] amount=p["amount"]
try: try:
recipient = GetNickFromPaymentID(payment_id) recipient = GetIdentityFromPaymentID(payment_id)
if not recipient:
raise RuntimeError('Payment ID %s not found' % payment_id)
log_info('UpdateCoin: Found payment %s to %s for %s' % (tx_hash,recipient, AmountToString(amount))) log_info('UpdateCoin: Found payment %s to %s for %s' % (tx_hash,recipient, AmountToString(amount)))
pipe.hincrby("balances",recipient,amount); pipe.hincrby("balances",recipient,amount);
except Exception,e: except Exception,e:
log_error('UpdateCoin: No nick found for payment id %s, tx hash %s, amount %s' % (payment_id, tx_hash, amount)) log_error('UpdateCoin: No identity found for payment id %s, tx hash %s, amount %s: %s' % (payment_id, tx_hash, amount, str(e)))
log_log('UpdateCoin: Executing received payments pipeline') log_log('UpdateCoin: Executing received payments pipeline')
pipe.execute() pipe.execute()
except Exception,e: except Exception,e:
@ -104,13 +103,13 @@ def UpdateCoin(param):
log_error('UpdateCoin: Failed to get bulk payments: %s' % str(e)) log_error('UpdateCoin: Failed to get bulk payments: %s' % str(e))
last_wallet_update_time = time.time() last_wallet_update_time = time.time()
def Deposit(nick,chan,cmd): def Deposit(link,cmd):
Help(nick,chan) Help(link)
def Help(nick,chan): def Help(link):
SendTo(nick, "You can send %s to your account:" % coinspecs.name); link.send("You can send %s to your account:" % coinspecs.name);
SendTo(nick, " Address: %s" % GetTipbotAddress()) link.send(" Address: %s" % GetTipbotAddress())
SendTo(nick, " Payment ID: %s" % GetPaymentID(nick)) link.send(" Payment ID: %s" % GetPaymentID(link))
RegisterModule({ RegisterModule({
'name': __name__, 'name': __name__,

View File

@ -23,184 +23,183 @@ import tipbot.config as config
from tipbot.log import log_error, log_warn, log_info, log_log from tipbot.log import log_error, log_warn, log_info, log_log
import tipbot.coinspecs as coinspecs import tipbot.coinspecs as coinspecs
from tipbot.utils import * from tipbot.utils import *
from tipbot.ircutils import *
from tipbot.command_manager import * from tipbot.command_manager import *
from tipbot.redisdb import * from tipbot.redisdb import *
pending_confirmations=dict() pending_confirmations=dict()
def PerformTip(nick,chan,who,units): def PerformTip(link,whoid,units):
sendto=GetSendTo(nick,chan) identity=link.identity()
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",identity)
if balance == None: if balance == None:
balance = 0 balance = 0
balance=long(balance) balance=long(balance)
if units > balance: if units > balance:
SendTo(sendto, "You only have %s" % (AmountToString(balance))) link.send("You only have %s" % (AmountToString(balance)))
return return
log_info('Tip: %s tipping %s %u units, with balance %u' % (nick, who, units, balance)) log_info('Tip: %s tipping %s %u units, with balance %u' % (identity, whoid, units, balance))
try: try:
p = redis_pipeline() p = redis_pipeline()
p.incrby("tips_total_count",1); p.incrby("tips_total_count",1);
p.incrby("tips_total_amount",units); p.incrby("tips_total_amount",units);
p.hincrby("tips_count",nick,1); p.hincrby("tips_count",identity,1);
p.hincrby("tips_amount",nick,units); p.hincrby("tips_amount",identity,units);
p.hincrby("balances",nick,-units); p.hincrby("balances",identity,-units);
p.hincrby("balances",who,units) p.hincrby("balances",whoid,units)
p.execute() p.execute()
SendTo(sendto,"%s has tipped %s %s" % (nick, who, AmountToString(units))) link.send("%s has tipped %s %s" % (NickFromIdentity(identity), NickFromIdentity(whoid), AmountToString(units)))
except Exception, e: except Exception, e:
log_error("Tip: Error updating redis: %s" % str(e)) log_error("Tip: Error updating redis: %s" % str(e))
SendTo(sendto, "An error occured") link.send("An error occured")
return return
except Exception, e: except Exception, e:
log_error('Tip: exception: %s' % str(e)) log_error('Tip: exception: %s' % str(e))
SendTo(sendto, "An error has occured") link.send("An error has occured")
def Tip(nick,chan,cmd): def Tip(link,cmd):
userstable = GetUsersTable() identity=link.identity()
sendto=GetSendTo(nick,chan)
try: try:
who=cmd[1] who=cmd[1]
amount=float(cmd[2]) amount=float(cmd[2])
except Exception,e: except Exception,e:
SendTo(sendto, "Usage: tip nick amount") link.send("Usage: tip nick amount")
return return
units=long(amount*coinspecs.atomic_units) units=long(amount*coinspecs.atomic_units)
if units <= 0: if units <= 0:
SendTo(sendto, "Invalid amount") link.send("Invalid amount")
return return
log_info("Tip: %s wants to tip %s %s" % (nick, who, AmountToString(units))) whoid = IdentityFromString(link,who)
if chan in userstable:
log_info('getting keys') log_info("Tip: %s wants to tip %s %s" % (identity, whoid, AmountToString(units)))
userlist = userstable[chan].keys() if link.group:
log_info('testingwho') userlist=[user.identity() for user in link.network.get_users(link.group.name)]
if not who in userlist: log_log('users: %s' % str(userlist))
SendTo(sendto,"%s is not in %s: if you really intend to tip %s, type !confirmtip before tipping again" % (who, chan, who)) if not whoid in userlist:
pending_confirmations[nick]={'who': who, 'units': units} link.send("%s is not in %s: if you really intend to tip %s, type !confirmtip before tipping again" % (who, link.group.name, who))
pending_confirmations[identity]={'who': whoid, 'units': units}
return return
log_info('delk') pending_confirmations.pop(identity,None)
pending_confirmations.pop(nick,None) PerformTip(link,whoid,units)
PerformTip(nick,chan,who,units)
def ConfirmTip(nick,chan,cmd): def ConfirmTip(link,cmd):
sendto=GetSendTo(nick,chan) identity=link.identity()
if not nick in pending_confirmations: if not identity in pending_confirmations:
SendTo(sendto,"%s has no tip waiting confirmation" % nick) link.send("%s has no tip waiting confirmation" % NickFromIdentity(identity))
return return
who=pending_confirmations[nick]['who'] whoid=pending_confirmations[identity]['who']
units=pending_confirmations[nick]['units'] units=pending_confirmations[identity]['units']
pending_confirmations.pop(nick,None) pending_confirmations.pop(identity,None)
PerformTip(nick,chan,who,units) PerformTip(link,whoid,units)
def Rain(nick,chan,cmd): def Rain(link,cmd):
userstable = GetUsersTable() identity=link.identity()
if chan[0] != '#': group=link.group
SendTo(nick, "Raining can only be done in a channel") if not group:
link.send("Raining can only be done in a group")
return return
try: try:
amount=float(cmd[1]) amount=float(cmd[1])
except Exception,e: except Exception,e:
SendTo(chan, "Usage: rain amount [users]") link.send("Usage: rain amount [users]")
return return
users = GetParam(cmd,2) users = GetParam(cmd,2)
if users: if users:
try: try:
users=long(users) users=long(users)
except Exception,e: except Exception,e:
SendTo(chan, "Usage: rain amount [users]") link.send("Usage: rain amount [users]")
return return
if amount <= 0: if amount <= 0:
SendTo(chan, "Usage: rain amount [users]") link.send("Usage: rain amount [users]")
return return
if users != None and users <= 0: if users != None and users <= 0:
SendTo(chan, "Usage: rain amount [users]") link.send("Usage: rain amount [users]")
return return
units = long(amount * coinspecs.atomic_units) units = long(amount * coinspecs.atomic_units)
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",identity)
if balance == None: if balance == None:
balance = 0 balance = 0
balance=long(balance) balance=long(balance)
if units > balance: if units > balance:
SendTo(chan, "You only have %s" % (AmountToString(balance))) link.send("You only have %s" % (AmountToString(balance)))
return return
log_log("userstable: %s" % str(userstable)) userlist=[user.identity() for user in link.network.get_users(group.name)]
userlist = userstable[chan].keys() log_log("users in %s: %s" % (group.name,str(userlist)))
userlist.remove(nick) userlist.remove(identity)
for n in config.no_rain_to_nicks: for n in config.no_rain_to_nicks:
userlist.remove(n) userlist.remove(IdentityFromString(link,n))
if users == None or users > len(userlist): if users == None or users > len(userlist):
users = len(userlist) users = len(userlist)
everyone = True everyone = True
else: else:
everyone = False everyone = False
if users == 0: if users == 0:
SendTo(chan, "Nobody eligible for rain") link.send("Nobody eligible for rain")
return return
if units < users: if units < users:
SendTo(chan, "This would mean not even an atomic unit per nick") link.send("This would mean not even an atomic unit per nick")
return return
log_info("%s wants to rain %s on %s users in %s" % (nick, AmountToString(units), users, chan)) log_info("%s wants to rain %s on %s users in %s" % (identity, AmountToString(units), users, group.name))
log_log("users in %s: %s" % (chan, str(userlist))) log_log("eligible users in %s: %s" % (group.name, str(userlist)))
random.shuffle(userlist) random.shuffle(userlist)
userlist = userlist[0:users] userlist = userlist[0:users]
log_log("selected users in %s: %s" % (chan, userlist)) log_log("selected users in %s: %s" % (group.name, userlist))
user_units = long(units / users) user_units = long(units / users)
enumerate_users = False enumerate_users = False
if everyone: if everyone:
msg = "%s rained %s on everyone in the channel" % (nick, AmountToString(user_units)) msg = "%s rained %s on everyone in the channel" % (link.user.nick, AmountToString(user_units))
elif len(userlist) > 16: elif len(userlist) > 16:
msg = "%s rained %s on %d nicks" % (nick, AmountToString(user_units), len(userlist)) msg = "%s rained %s on %d nicks" % (link.user.nick, AmountToString(user_units), len(userlist))
else: else:
msg = "%s rained %s on:" % (nick, AmountToString(user_units)) msg = "%s rained %s on:" % (link.user.nick, AmountToString(user_units))
enumerate_users = True enumerate_users = True
pipe = redis_pipeline() pipe = redis_pipeline()
pipe.hincrby("balances",nick,-units) pipe.hincrby("balances",identity,-units)
pipe.incrby("rain_total_count",1); pipe.incrby("rain_total_count",1)
pipe.incrby("rain_total_amount",units); pipe.incrby("rain_total_amount",units)
pipe.hincrby("rain_count",nick,1); pipe.hincrby("rain_count",identity,1)
pipe.hincrby("rain_amount",nick,units); pipe.hincrby("rain_amount",identity,units)
for user in userlist: for user in userlist:
pipe.hincrby("balances",user,user_units) pipe.hincrby("balances",user,user_units)
if enumerate_users: if enumerate_users:
msg = msg + " " + user msg = msg + " " + NickFromIdentity(user)
pipe.execute() pipe.execute()
SendTo(chan, "%s" % msg) link.send("%s" % msg)
except Exception,e: except Exception,e:
log_error('Rain: exception: %s' % str(e)) log_error('Rain: exception: %s' % str(e))
SendTo(chan, "An error has occured") link.send("An error has occured")
return return
def RainActive(nick,chan,cmd): def RainActive(link,cmd):
userstable = GetUsersTable() identity=link.identity()
amount=GetParam(cmd,1) amount=GetParam(cmd,1)
hours=GetParam(cmd,2) hours=GetParam(cmd,2)
minfrac=GetParam(cmd,3) minfrac=GetParam(cmd,3)
if chan[0] != '#': group=link.group
SendTo(nick, "Raining can only be done in a channel") if not group:
link.send("Raining can only be done in a channel")
return return
if not amount or not hours: if not amount or not hours:
SendTo(chan, "usage: !rainactive <amount> <hours> [<minfrac>]") link.send("usage: !rainactive <amount> <hours> [<minfrac>]")
return return
try: try:
amount=float(amount) amount=float(amount)
if amount <= 0: if amount <= 0:
raise RuntimeError("") raise RuntimeError("")
except Exception,e: except Exception,e:
SendTo(chan, "Invalid amount") link.send("Invalid amount")
return return
try: try:
hours=float(hours) hours=float(hours)
@ -208,7 +207,7 @@ def RainActive(nick,chan,cmd):
raise RuntimeError("") raise RuntimeError("")
seconds = hours * 3600 seconds = hours * 3600
except Exception,e: except Exception,e:
SendTo(chan, "Invalid hours") link.send("Invalid hours")
return return
if minfrac: if minfrac:
try: try:
@ -216,7 +215,7 @@ def RainActive(nick,chan,cmd):
if minfrac < 0 or minfrac > 1: if minfrac < 0 or minfrac > 1:
raise RuntimeError("") raise RuntimeError("")
except Exception,e: except Exception,e:
SendTo(chan, "minfrac must be a number between 0 and 1") link.send("minfrac must be a number between 0 and 1")
return return
else: else:
minfrac = 0 minfrac = 0
@ -224,23 +223,26 @@ def RainActive(nick,chan,cmd):
units = long(amount * coinspecs.atomic_units) units = long(amount * coinspecs.atomic_units)
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",identity)
if balance == None: if balance == None:
balance = 0 balance = 0
balance=long(balance) balance=long(balance)
if units > balance: if units > balance:
SendTo(chan, "You only have %s" % (AmountToString(balance))) link.send("You only have %s" % (AmountToString(balance)))
return return
now = time.time() now = time.time()
userlist = userstable[chan].keys() userlist = [user.identity() for user in link.network.get_active_users(seconds,group.name)]
userlist.remove(nick) log_log('userlist: %s' % str(userlist))
userlist.remove(link.identity())
for n in config.no_rain_to_nicks: for n in config.no_rain_to_nicks:
userlist.remove(n) userlist.remove(IdentityFromString(link,n))
weights=dict() weights=dict()
weight=0 weight=0
log_log('userlist to loop: %s' % str(userlist))
for n in userlist: for n in userlist:
t = userstable[chan][n] log_log('user to check: %s' % NickFromIdentity(n))
t = link.network.get_last_active_time(NickFromIdentity(n),group.name)
if t == None: if t == None:
continue continue
dt = now - t dt = now - t
@ -250,15 +252,15 @@ def RainActive(nick,chan,cmd):
weight += w weight += w
if len(weights) == 0: if len(weights) == 0:
SendTo(chan, "Nobody eligible for rain") link.send("Nobody eligible for rain")
return return
pipe = redis_pipeline() pipe = redis_pipeline()
pipe.hincrby("balances",nick,-units) pipe.hincrby("balances",identity,-units)
pipe.incrby("arain_total_count",1); pipe.incrby("arain_total_count",1);
pipe.incrby("arain_total_amount",units); pipe.incrby("arain_total_amount",units);
pipe.hincrby("arain_count",nick,1); pipe.hincrby("arain_count",identity,1);
pipe.hincrby("arain_amount",nick,units); pipe.hincrby("arain_amount",identity,units);
rained_units = 0 rained_units = 0
nnicks = 0 nnicks = 0
minu=None minu=None
@ -267,7 +269,8 @@ def RainActive(nick,chan,cmd):
user_units = long(units * weights[n] / weight) user_units = long(units * weights[n] / weight)
if user_units <= 0: if user_units <= 0:
continue continue
log_info("%s rained %s on %s (last active %f hours ago)" % (nick, AmountToString(user_units),n,GetTimeSinceActive(chan,n)/3600)) act = now - link.network.get_last_active_time(NickFromIdentity(n),link.group.name)
log_info("%s rained %s on %s (last active %f hours ago)" % (identity, AmountToString(user_units),n,act/3600))
pipe.hincrby("balances",n,user_units) pipe.hincrby("balances",n,user_units)
rained_units += user_units rained_units += user_units
if not minu or user_units < minu: if not minu or user_units < minu:
@ -277,23 +280,23 @@ def RainActive(nick,chan,cmd):
nnicks = nnicks+1 nnicks = nnicks+1
if maxu == None: if maxu == None:
SendTo(chan, "This would mean not even an atomic unit per nick") link.send("This would mean not even an atomic unit per nick")
return return
pipe.execute() pipe.execute()
log_info("%s rained %s - %s (total %s, acc %s) on the %d nicks active in the last %s" % (nick, AmountToString(minu), AmountToString(maxu), AmountToString(units), AmountToString(rained_units), nnicks, TimeToString(seconds))) log_info("%s rained %s - %s (total %s, acc %s) on the %d nicks active in the last %f hours" % (identity, AmountToString(minu), AmountToString(maxu), AmountToString(units), AmountToString(rained_units), nnicks, TimeToString(seconds)))
SendTo(chan, "%s rained %s - %s on the %d nicks active in the last %s" % (nick, AmountToString(minu), AmountToString(maxu), nnicks, TimeToString(seconds))) link.send("%s rained %s - %s on the %d nicks active in the last %f hours" % (identity, AmountToString(minu), AmountToString(maxu), nnicks, TimeToString(seconds)))
except Exception,e: except Exception,e:
log_error('Rain: exception: %s' % str(e)) log_error('Rain: exception: %s' % str(e))
SendTo(chan, "An error has occured") link.send("An error has occured")
return return
def Help(nick,chan): def Help(link):
SendTo(nick,'You can tip other people, or rain %s on them' % coinspecs.name) link.send('You can tip other people, or rain %s on them' % coinspecs.name)
SendTo(nick,'!tip tips a single person, while !rain shares equally between people in the channel') link.send('!tip tips a single person, while !rain shares equally between people in the channel')
SendTo(nick,'!rainactive tips all within the last N hours, with more recently active people') link.send('!rainactive tips all within the last N hours, with more recently active people')
SendTo(nick,'getting a larger share.') link.send('getting a larger share.')
RegisterModule({ RegisterModule({

View File

@ -16,46 +16,47 @@ from tipbot.log import log_error, log_warn, log_info, log_log
import tipbot.coinspecs as coinspecs import tipbot.coinspecs as coinspecs
import tipbot.config as config import tipbot.config as config
from tipbot.utils import * from tipbot.utils import *
from tipbot.ircutils import *
from tipbot.redisdb import * from tipbot.redisdb import *
from tipbot.command_manager import * from tipbot.command_manager import *
withdraw_disabled = False withdraw_disabled = False
def DisableWithdraw(nick,chan,cmd): def DisableWithdraw(link,cmd):
global withdraw_disabled global withdraw_disabled
if nick: if link:
log_warn('DisableWithdraw: disabled by %s' % nick) log_warn('DisableWithdraw: disabled by %s' % link.identity())
else: else:
log_warn('DisableWithdraw: disabled') log_warn('DisableWithdraw: disabled')
withdraw_disabled = True withdraw_disabled = True
def EnableWithdraw(nick,chan,cmd): def EnableWithdraw(link,cmd):
global withdraw_disabled global withdraw_disabled
log_info('EnableWithdraw: enabled by %s' % nick) log_info('EnableWithdraw: enabled by %s' % link.identity())
withdraw_disabled = False withdraw_disabled = False
def CheckDisableWithdraw(): def CheckDisableWithdraw():
if config.disable_withdraw_on_error: if config.disable_withdraw_on_error:
DisableWithdraw(None,None,None) DisableWithdraw(None,None,None)
def Withdraw(nick,chan,cmd): def Withdraw(link,cmd):
identity=link.identity()
local_withdraw_fee = config.withdrawal_fee or coinspecs.min_withdrawal_fee local_withdraw_fee = config.withdrawal_fee or coinspecs.min_withdrawal_fee
local_min_withdraw_amount = config.min_withdraw_amount or local_withdraw_fee local_min_withdraw_amount = config.min_withdraw_amount or local_withdraw_fee
if local_min_withdraw_amount <= 0 or local_withdraw_fee <= 0 or local_min_withdraw_amount < local_withdraw_fee: if local_min_withdraw_amount <= 0 or local_withdraw_fee <= 0 or local_min_withdraw_amount < local_withdraw_fee:
log_error('Withdraw: Inconsistent withdrawal settings') log_error('Withdraw: Inconsistent withdrawal settings')
SendTo(nick, "An error has occured") link.send("An error has occured")
return return
try: try:
address=cmd[1] address=cmd[1]
except Exception,e: except Exception,e:
SendTo(nick, "Usage: withdraw address [amount]") link.send("Usage: withdraw address [amount]")
return return
if not IsValidAddress(address): if not IsValidAddress(address):
SendTo(nick, "Invalid address") link.send("Invalid address")
return return
amount = GetParam(cmd,2) amount = GetParam(cmd,2)
if amount: if amount:
@ -66,37 +67,37 @@ def Withdraw(nick,chan,cmd):
amount = long(famount * coinspecs.atomic_units) amount = long(famount * coinspecs.atomic_units)
amount += local_withdraw_fee amount += local_withdraw_fee
except Exception,e: except Exception,e:
SendTo(nick, "Invalid amount") link.send("Invalid amount")
return return
log_info("Withdraw: %s wants to withdraw %s to %s" % (nick, AmountToString(amount) if amount else "all", address)) log_info("Withdraw: %s wants to withdraw %s to %s" % (identity, AmountToString(amount) if amount else "all", address))
if withdraw_disabled: if withdraw_disabled:
log_error('Withdraw: disabled') log_error('Withdraw: disabled')
SendTo(nick, "Sorry, withdrawal is disabled due to a wallet error which requires admin assistance") link.send("Sorry, withdrawal is disabled due to a wallet error which requires admin assistance")
return return
try: try:
balance = redis_hget("balances",nick) balance = redis_hget("balances",identity)
if balance == None: if balance == None:
balance = 0 balance = 0
balance=long(balance) balance=long(balance)
except Exception, e: except Exception, e:
log_error('Withdraw: exception: %s' % str(e)) log_error('Withdraw: exception: %s' % str(e))
SendTo(nick, "An error has occured") link.send("An error has occured")
return return
if amount: if amount:
if amount > balance: if amount > balance:
log_info("Withdraw: %s trying to withdraw %s, but only has %s" % (nick,AmountToString(amount),AmountToString(balance))) log_info("Withdraw: %s trying to withdraw %s, but only has %s" % (identity,AmountToString(amount),AmountToString(balance)))
SendTo(nick, "You only have %s" % AmountToString(balance)) link.send("You only have %s" % AmountToString(balance))
return return
else: else:
amount = balance amount = balance
if amount <= 0 or amount < local_min_withdraw_amount: if amount <= 0 or amount < local_min_withdraw_amount:
log_info("Withdraw: Minimum withdrawal balance: %s, %s cannot withdraw %s" % (AmountToString(config.min_withdraw_amount),nick,AmountToString(amount))) log_info("Withdraw: Minimum withdrawal balance: %s, %s cannot withdraw %s" % (AmountToString(config.min_withdraw_amount),nick,AmountToString(amount)))
SendTo(nick, "Minimum withdrawal balance: %s, cannot withdraw %s" % (AmountToString(config.min_withdraw_amount),AmountToString(amount))) link.send("Minimum withdrawal balance: %s, cannot withdraw %s" % (AmountToString(config.min_withdraw_amount),AmountToString(amount)))
return return
try: try:
fee = long(local_withdraw_fee) fee = long(local_withdraw_fee)
@ -105,7 +106,7 @@ def Withdraw(nick,chan,cmd):
log_info('Withdraw: fee: %s, to pay: %s' % (AmountToString(fee), AmountToString(topay))) log_info('Withdraw: fee: %s, to pay: %s' % (AmountToString(fee), AmountToString(topay)))
params = { params = {
'destinations': [{'address': address, 'amount': topay}], 'destinations': [{'address': address, 'amount': topay}],
'payment_id': GetPaymentID(nick), 'payment_id': GetPaymentID(link),
'fee': fee, 'fee': fee,
'mixin': config.withdrawal_mixin, 'mixin': config.withdrawal_mixin,
'unlock_time': 0, 'unlock_time': 0,
@ -114,34 +115,34 @@ def Withdraw(nick,chan,cmd):
except Exception,e: except Exception,e:
log_error('Withdraw: Error in transfer: %s' % str(e)) log_error('Withdraw: Error in transfer: %s' % str(e))
CheckDisableWithdraw() CheckDisableWithdraw()
SendTo(nick,"An error has occured") link.send("An error has occured")
return return
if not "result" in j: if not "result" in j:
log_error('Withdraw: No result in transfer reply') log_error('Withdraw: No result in transfer reply')
CheckDisableWithdraw() CheckDisableWithdraw()
SendTo(nick,"An error has occured") link.send("An error has occured")
return return
result = j["result"] result = j["result"]
if not "tx_hash" in result: if not "tx_hash" in result:
log_error('Withdraw: No tx_hash in transfer reply') log_error('Withdraw: No tx_hash in transfer reply')
CheckDisableWithdraw() CheckDisableWithdraw()
SendTo(nick,"An error has occured") link.send("An error has occured")
return return
tx_hash = result["tx_hash"] tx_hash = result["tx_hash"]
log_info('%s has withdrawn %s, tx hash %s' % (nick, amount, str(tx_hash))) log_info('%s has withdrawn %s, tx hash %s' % (identity, amount, str(tx_hash)))
SendTo(nick, "Tx sent: %s" % tx_hash) link.send( "Tx sent: %s" % tx_hash)
try: try:
redis_hincrby("balances",nick,-amount) redis_hincrby("balances",identity,-amount)
except Exception, e: except Exception, e:
log_error('Withdraw: FAILED TO SUBTRACT BALANCE: exception: %s' % str(e)) log_error('Withdraw: FAILED TO SUBTRACT BALANCE: exception: %s' % str(e))
CheckDisableWithdraw() CheckDisableWithdraw()
def Help(nick,chan): def Help(link):
fee = config.withdrawal_fee or coinspecs.min_withdrawal_fee fee = config.withdrawal_fee or coinspecs.min_withdrawal_fee
min_amount = config.min_withdraw_amount or fee min_amount = config.min_withdraw_amount or fee
SendTo(nick, "Minimum withdrawal: %s" % AmountToString(min_amount)) link.send("Minimum withdrawal: %s" % AmountToString(min_amount))
SendTo(nick, "Withdrawal fee: %s" % AmountToString(fee)) link.send("Withdrawal fee: %s" % AmountToString(fee))

58
tipbot/network.py Normal file
View File

@ -0,0 +1,58 @@
#!/bin/python
#
# Cryptonote tipbot - network
# Copyright 2015 moneromooo
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
from link import Link
from user import User
from group import Group
class Network:
def __init__(self,name):
self.name=name
def connect(self):
pass
def disconnect(self):
pass
def send_group(self,group,msg,data=None):
pass
def send_user(self,user,msg,data=None):
pass
def identify(self,link):
pass
def dump_users(self):
pass
def set_callbacks(self,on_command,on_identified):
self.on_command=on_command
self.on_identified=on_identified
def get_last_active_time(user_name,group_name=None):
return None
def get_active_users(seconds,group_name=None):
return []
def get_users(group_name=None):
return []
def update_users_list(self,group_name=None):
pass
def update(self):
return True
def quit(self,msg=None):
pass

24
tipbot/user.py Normal file
View File

@ -0,0 +1,24 @@
#!/bin/python
#
# Cryptonote tipbot - user
# Copyright 2015 moneromooo
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
class User:
def __init__(self,network,nick):
self.network=network
self.nick=nick
def check_registered(self):
pass
def is_registered(self):
if not self.registered:
self.check_registered()
return self.registered

View File

@ -16,9 +16,9 @@ import httplib
import tipbot.config as config import tipbot.config as config
import tipbot.coinspecs as coinspecs import tipbot.coinspecs as coinspecs
from tipbot.log import log_error, log_warn, log_info, log_log from tipbot.log import log_error, log_warn, log_info, log_log
from tipbot.ircutils import *
from tipbot.redisdb import * from tipbot.redisdb import *
networks=[]
def GetPassword(): def GetPassword():
try: try:
@ -40,19 +40,26 @@ def GetParam(parms,idx):
return parms[idx] return parms[idx]
return None return None
def GetPaymentID(nick): def GetPaymentID(link):
salt="2u3g55bkwrui32fi3g4bGR$j5g4ugnujb-"+coinspecs.name+"-"; salt="2u3g55bkwrui32fi3g4bGR$j5g4ugnujb-"+coinspecs.name+"-";
p = hashlib.sha256(salt+nick).hexdigest(); p = hashlib.sha256(salt+link.identity()).hexdigest();
try: try:
redis_hset("paymentid",p,nick) redis_hset("paymentid",p,link.identity())
except Exception,e: except Exception,e:
log_error('GetPaymentID: failed to set payment ID for %s to redis: %s' % (nick,str(e))) log_error('GetPaymentID: failed to set payment ID for %s to redis: %s' % (link.identity(),str(e)))
return p return p
def GetNickFromPaymentID(p): def GetIdentityFromPaymentID(p):
nick = redis_hget("paymentid",p) if not redis_hexists("paymentid",p):
log_log('PaymentID %s => %s' % (p, str(nick))) log_log('PaymentID %s not found' % p)
return nick return None
identity = redis_hget("paymentid",p)
log_log('PaymentID %s => %s' % (p, str(identity)))
# HACK - grandfathering pre-network payment IDs
if identity.index(':') == -1:
log_warn('Pre-network payment ID found, assuming freenode')
identity = "freenode:"+identity
return identity
def IsValidAddress(address): def IsValidAddress(address):
if len(address) < coinspecs.address_length[0] or len(address) > coinspecs.address_length[1]: if len(address) < coinspecs.address_length[0] or len(address) > coinspecs.address_length[1]:
@ -205,3 +212,31 @@ def RetrieveTipbotBalance():
return return
return balance, unlocked_balance return balance, unlocked_balance
def IdentityFromString(link,s):
if s.find(':') == -1:
network = link.network.name
nick=s
else:
parts=s.split(':')
network=parts[0]
nick=parts[1]
return network+':'+nick
def NickFromIdentity(identity):
return identity.split(':')[1]
def AddNetwork(network):
networks.append(network)
def GetNetworkByName(name):
for network in networks:
if network.name==name:
return network
return None
def GetNetworkByType(type):
for network in networks:
if isinstance(network,type):
return network
return None