2014-12-09 10:00:19 +00:00
#!/bin/python
2014-12-21 18:50:24 +00:00
#
2014-12-22 21:52:37 +00:00
# Cryptonote tipbot
2015-01-01 17:33:07 +00:00
# Copyright 2014,2015 moneromooo
2014-12-21 18:50:24 +00:00
# Inspired by "Simple Python IRC bot" by berend
#
2014-12-23 10:47:56 +00:00
# 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.
#
2014-12-09 10:00:19 +00:00
2014-12-28 10:23:17 +00:00
import sys
2014-12-30 13:12:13 +00:00
import os
2014-12-09 10:00:19 +00:00
import socket
2014-12-21 18:50:24 +00:00
import select
import random
2014-12-09 10:00:19 +00:00
import redis
import hashlib
import json
import httplib
2014-12-21 18:50:24 +00:00
import time
2014-12-23 09:42:55 +00:00
import string
2014-12-29 17:08:13 +00:00
import importlib
import tipbot . coinspecs as coinspecs
import tipbot . config as config
from tipbot . log import log_error , log_warn , log_info , log_log
2015-01-13 12:28:05 +00:00
from tipbot . link import *
from tipbot . user import *
from tipbot . group import *
2014-12-29 17:08:13 +00:00
from tipbot . utils import *
from tipbot . redisdb import *
2014-12-31 10:30:07 +00:00
from tipbot . command_manager import *
2014-12-29 17:08:13 +00:00
2015-01-17 13:19:50 +00:00
disabled = False
2014-12-29 17:08:13 +00:00
selected_coin = None
modulenames = [ ]
argc = 1
while argc < len ( sys . argv ) :
arg = sys . argv [ argc ]
if arg == " -c " or arg == " --coin " :
if argc + 1 == len ( sys . argv ) :
log_error ( ' Usage: tipbot.py [-h|--help] [-m|--module modulename]* -c|--coin <coinname> ' )
exit ( 1 )
argc = argc + 1
selected_coin = sys . argv [ argc ]
try :
log_info ( ' Importing %s coin setup ' % selected_coin )
if not selected_coin in coinspecs . coinspecs :
log_error ( ' Unknown coin: %s ' % selected_coin )
exit ( 1 )
for field in coinspecs . coinspecs [ selected_coin ] :
setattr ( coinspecs , field , coinspecs . coinspecs [ selected_coin ] [ field ] )
except Exception , e :
log_error ( ' Failed to load coin setup for %s : %s ' % ( selected_coin , str ( e ) ) )
exit ( 1 )
elif arg == " -m " or arg == " --module " :
if argc + 1 == len ( sys . argv ) :
log_error ( ' Usage: tipbot.py [-m|--module modulename]* -c|--coin <coinname> ' )
exit ( 1 )
argc = argc + 1
modulenames . append ( sys . argv [ argc ] )
elif arg == " -h " or arg == " --help " :
log_info ( ' Usage: tipbot.py [-m|--module modulename]* -c|--coin <coinname> ' )
exit ( 0 )
else :
log_error ( ' Usage: tipbot.py [-m|--module modulename]* -c|--coin <coinname> ' )
exit ( 1 )
argc = argc + 1
2014-12-09 10:00:19 +00:00
2014-12-29 17:08:13 +00:00
if not selected_coin :
log_error ( ' Coin setup needs to be specified with -c. See --help ' )
2014-12-28 10:23:17 +00:00
exit ( 1 )
2014-12-21 18:50:24 +00:00
2014-12-30 13:12:13 +00:00
sys . path . append ( os . path . join ( ' tipbot ' , ' modules ' ) )
2014-12-29 17:08:13 +00:00
for modulename in modulenames :
2015-01-13 12:28:05 +00:00
if modulename in sys . modules :
log_error ( ' A %s module already exists ' % modulename )
exit ( 1 )
2014-12-29 17:08:13 +00:00
log_info ( ' Importing %s module ' % modulename )
2014-12-21 18:50:24 +00:00
try :
2014-12-30 13:12:13 +00:00
__import__ ( modulename )
2014-12-21 18:50:24 +00:00
except Exception , e :
2014-12-29 17:08:13 +00:00
log_error ( ' Failed to load module " %s " : %s ' % ( modulename , str ( e ) ) )
exit ( 1 )
2014-12-09 10:00:19 +00:00
2015-01-13 12:28:05 +00:00
def GetBalance ( link , cmd ) :
nick = link . user . nick
log_log ( " GetBalance: checking %s ( %s ) " % ( link . identity ( ) , str ( link ) ) )
2014-12-09 10:00:19 +00:00
try :
2015-01-13 12:28:05 +00:00
balance = redis_hget ( " balances " , link . identity ( ) )
2014-12-09 10:00:19 +00:00
if balance == None :
balance = 0
2015-01-11 18:53:00 +00:00
balance = long ( balance )
2014-12-09 10:00:19 +00:00
sbalance = AmountToString ( balance )
2015-01-11 18:53:00 +00:00
if balance < coinspecs . atomic_units :
2015-01-13 23:32:11 +00:00
if balance == 0 :
2015-01-13 12:28:05 +00:00
link . send ( " %s ' s balance is %s " % ( nick , sbalance ) )
2015-01-13 23:32:11 +00:00
else :
2015-01-13 12:28:05 +00:00
link . send ( " %s ' s balance is %s ( %.16g %s ) " % ( nick , sbalance , float ( balance ) / coinspecs . atomic_units , coinspecs . name ) )
2015-01-11 18:53:00 +00:00
else :
2015-01-13 12:28:05 +00:00
link . send ( " %s ' s balance is %s " % ( nick , sbalance ) )
2014-12-09 10:00:19 +00:00
except Exception , e :
2014-12-21 18:50:24 +00:00
log_error ( ' GetBalance: exception: %s ' % str ( e ) )
2015-01-13 12:28:05 +00:00
link . send ( " An error has occured " )
2014-12-09 10:00:19 +00:00
2015-01-13 12:28:05 +00:00
def AddBalance ( link , cmd ) :
nick = link . user . nick
2015-01-10 13:53:22 +00:00
if GetParam ( cmd , 2 ) :
anick = GetParam ( cmd , 1 )
amount = GetParam ( cmd , 2 )
else :
anick = nick
amount = GetParam ( cmd , 1 )
if not amount :
2015-01-13 12:28:05 +00:00
link . send ( ' usage: !addbalance [<nick>] <amount> ' )
2015-01-10 13:53:22 +00:00
return
try :
2015-01-18 11:52:01 +00:00
units = long ( float ( amount ) * coinspecs . atomic_units )
2015-01-10 13:53:22 +00:00
except Exception , e :
2015-01-13 12:28:05 +00:00
log_error ( ' AddBalance: error converting amount: %s ' % str ( e ) )
link . send ( ' usage: !addbalance [<nick>] <amount> ' )
2015-01-10 13:53:22 +00:00
return
2015-01-13 12:28:05 +00:00
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 ) )
2014-12-09 10:00:19 +00:00
try :
2015-01-13 12:28:05 +00:00
balance = redis_hincrby ( " balances " , aidentity , units )
2014-12-09 10:00:19 +00:00
except Exception , e :
2014-12-21 18:50:24 +00:00
log_error ( ' AddBalance: exception: %s ' % str ( e ) )
2015-01-13 12:28:05 +00:00
link . send ( " An error has occured " )
2015-01-20 19:28:46 +00:00
return
2015-01-13 12:28:05 +00:00
link . send ( " %s ' s balance is now %s " % ( aidentity , AmountToString ( balance ) ) )
2014-12-09 10:00:19 +00:00
2015-01-13 12:28:05 +00:00
def ScanWho ( link , cmd ) :
link . network . update_users_list ( link . group . name if link . group else None )
2014-12-21 18:50:24 +00:00
2015-01-13 12:28:05 +00:00
def GetHeight ( link , cmd ) :
log_info ( ' GetHeight: %s wants to know block height ' % str ( link ) )
2014-12-09 10:00:19 +00:00
try :
2014-12-28 10:27:31 +00:00
j = SendDaemonHTMLCommand ( " getheight " )
2014-12-09 10:00:19 +00:00
except Exception , e :
2014-12-21 18:50:24 +00:00
log_error ( ' GetHeight: error: %s ' % str ( e ) )
2015-01-13 12:28:05 +00:00
link . send ( " An error has occured " )
2014-12-09 10:00:19 +00:00
return
2014-12-21 18:50:24 +00:00
log_log ( ' GetHeight: Got reply: %s ' % str ( j ) )
2014-12-09 10:00:19 +00:00
if not " height " in j :
2014-12-21 18:50:24 +00:00
log_error ( ' GetHeight: Cannot see height in here ' )
2015-01-13 12:28:05 +00:00
link . send ( " Height not found " )
2014-12-09 10:00:19 +00:00
return
2014-12-21 18:50:24 +00:00
height = j [ " height " ]
2014-12-29 17:08:13 +00:00
log_info ( ' GetHeight: height is %s ' % str ( height ) )
2015-01-13 12:28:05 +00:00
link . send ( " Height: %s " % str ( height ) )
2014-12-09 10:00:19 +00:00
2015-01-13 12:28:05 +00:00
def GetTipbotBalance ( link , cmd ) :
log_info ( ' %s wants to know the tipbot balance ' % str ( link ) )
2014-12-09 10:00:19 +00:00
try :
2015-01-01 17:33:07 +00:00
balance , unlocked_balance = RetrieveTipbotBalance ( )
2014-12-09 10:00:19 +00:00
except Exception , e :
2015-01-13 12:28:05 +00:00
link . send ( " An error has occured " )
2014-12-09 10:00:19 +00:00
return
pending = long ( balance ) - long ( unlocked_balance )
if pending == 0 :
2014-12-21 18:50:24 +00:00
log_info ( " GetTipbotBalance: Tipbot balance: %s " % AmountToString ( balance ) )
2015-01-13 12:28:05 +00:00
link . send ( " Tipbot balance: %s " % AmountToString ( balance ) )
2014-12-09 10:00:19 +00:00
else :
2014-12-21 18:50:24 +00:00
log_info ( " GetTipbotBalance: Tipbot balance: %s ( %s pending) " % ( AmountToString ( unlocked_balance ) , AmountToString ( pending ) ) )
2015-01-13 12:28:05 +00:00
link . send ( " Tipbot balance: %s ( %s pending) " % ( AmountToString ( unlocked_balance ) , AmountToString ( pending ) ) )
2014-12-09 10:00:19 +00:00
2015-01-13 12:28:05 +00:00
def DumpUsers ( link , cmd ) :
for network in networks :
network . dump_users ( )
2014-12-22 13:30:59 +00:00
2015-01-13 12:28:05 +00:00
def Help ( link , cmd ) :
2015-01-03 18:32:09 +00:00
module = GetParam ( cmd , 1 )
if module :
2015-01-13 12:28:05 +00:00
RunModuleHelpFunction ( module , link )
2015-01-03 18:32:09 +00:00
return
2015-01-20 17:18:15 +00:00
link . send_private ( " See available commands with !commands or !commands <modulename> " )
link . send_private ( " Available modules: %s " % " , " . join ( GetModuleNameList ( IsAdmin ( link ) ) ) )
link . send_private ( " Get help on a particular module with !help <modulename> " )
2014-12-29 17:08:13 +00:00
if coinspecs . web_wallet_url :
2015-01-20 17:18:15 +00:00
link . send_private ( " No %s address ? You can use %s " % ( coinspecs . name , coinspecs . web_wallet_url ) )
2015-01-13 12:28:05 +00:00
def Info ( link , cmd ) :
2015-01-20 17:18:15 +00:00
link . send_private ( " Info for %s : " % config . tipbot_name )
link . send_private ( " Copyright 2014,2015 moneromooo - http://duckpool.mooo.com/tipbot/ " )
link . send_private ( " Type !help, or !commands for a list of commands " )
link . send_private ( " NO WARRANTY, YOU MAY LOSE YOUR COINS " )
link . send_private ( " By sending your %s to %s , you are giving up their control " % ( coinspecs . name , config . tipbot_name ) )
link . send_private ( " to whoever runs the tipbot. Any tip you make/receive using %s " % config . tipbot_name )
link . send_private ( " is obviously not anonymous. %s ' s wallet may end up corrupt, or be " % config . tipbot_name )
link . send_private ( " stolen, the server compromised, etc. While I hope this won ' t be the case, " )
link . send_private ( " I will not offer any warranty whatsoever for the use of %s or the " % config . tipbot_name )
link . send_private ( " return of any %s . Use at your own risk. " % coinspecs . name )
link . send_private ( " That being said, I hope you enjoy using it :) " )
2014-12-09 10:00:19 +00:00
2014-12-22 13:30:59 +00:00
def InitScanBlockHeight ( ) :
try :
2014-12-29 17:08:13 +00:00
scan_block_height = redis_get ( " scan_block_height " )
2014-12-22 13:30:59 +00:00
scan_block_height = long ( scan_block_height )
except Exception , e :
try :
2014-12-29 17:08:13 +00:00
redis_set ( " scan_block_height " , 0 )
2014-12-22 13:30:59 +00:00
except Exception , e :
log_error ( ' Failed to initialize scan_block_height: %s ' % str ( e ) )
2015-01-13 12:28:05 +00:00
def ShowActivity ( link , cmd ) :
anick = GetParam ( cmd , 1 )
achan = GetParam ( cmd , 2 )
if not anick or not achan :
link . send ( ' usage: !show_activity <nick> <chan> ' )
return
if anick . find ( ' : ' ) == - 1 :
network = link . network
2014-12-26 16:57:03 +00:00
else :
2015-01-13 12:28:05 +00:00
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 )
2014-12-26 16:57:03 +00:00
2015-01-13 12:28:05 +00:00
def SendToLink ( link , msg ) :
link . send ( msg )
2014-12-21 18:50:24 +00:00
2015-01-13 12:28:05 +00:00
def IsRegistered ( link , cmd ) :
RunRegisteredCommand ( link , SendToLink , " You are registered " , SendToLink , " You are not registered " )
2014-12-21 18:50:24 +00:00
2015-01-13 12:28:05 +00:00
def Reload ( link , cmd ) :
2015-01-01 10:06:09 +00:00
modulename = GetParam ( cmd , 1 )
if not modulename :
2015-01-13 12:28:05 +00:00
link . send ( " Usage: reload <modulename> " )
2015-01-01 10:06:09 +00:00
return
if modulename == " builtin " :
2015-01-13 12:28:05 +00:00
link . send ( " Cannot reload builtin module " )
return
if not modulename in sys . modules :
link . send ( " %s is not a dynamic module " % modulename )
2015-01-01 10:06:09 +00:00
return
log_info ( ' Unloading %s module ' % modulename )
2015-01-03 18:32:09 +00:00
UnregisterModule ( modulename )
2015-01-01 10:06:09 +00:00
log_info ( ' Reloading %s module ' % modulename )
try :
reload ( sys . modules [ modulename ] )
2015-01-13 12:28:05 +00:00
link . send ( ' %s reloaded ' % modulename )
2015-01-01 10:06:09 +00:00
except Exception , e :
log_error ( ' Failed to load module " %s " : %s ' % ( modulename , str ( e ) ) )
2015-01-13 12:28:05 +00:00
link . send ( ' An error occured ' )
2015-01-01 10:06:09 +00:00
2015-01-13 12:28:05 +00:00
def Disable ( link , cmd ) :
2015-01-17 13:19:50 +00:00
global disabled
disabled = True
2015-01-13 12:28:05 +00:00
link . send ( ' %s disabled, will require restart ' % config . tipbot_name )
2015-01-17 13:19:50 +00:00
2014-12-29 17:08:13 +00:00
def OnIdle ( ) :
2015-01-17 13:19:50 +00:00
if disabled :
return
2014-12-29 17:08:13 +00:00
RunIdleFunctions ( [ irc , redisdb ] )
2014-12-21 18:50:24 +00:00
2015-01-13 12:28:05 +00:00
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 ) :
2015-01-17 13:19:50 +00:00
if disabled :
2015-01-13 12:28:05 +00:00
log_info ( ' Ignoring identified notification for %s while disabled ' % str ( link . identity ( ) ) )
2015-01-17 13:19:50 +00:00
return
2015-01-13 12:28:05 +00:00
RunNextCommand ( link , identified )
2014-12-21 18:50:24 +00:00
2014-12-29 17:08:13 +00:00
def RegisterCommands ( ) :
2015-01-03 18:32:09 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' help ' , ' parms ' : ' [module] ' , ' function ' : Help , ' help ' : " Displays help about %s " % config . tipbot_name } )
2014-12-31 10:31:16 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' commands ' , ' parms ' : ' [module] ' , ' function ' : Commands , ' help ' : " Displays list of commands " } )
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' isregistered ' , ' function ' : IsRegistered , ' help ' : " show whether you are currently registered with freenode " } )
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' balance ' , ' function ' : GetBalance , ' registered ' : True , ' help ' : " show your current balance " } )
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' info ' , ' function ' : Info , ' help ' : " infornmation about %s " % config . tipbot_name } )
2014-12-22 13:30:59 +00:00
2014-12-31 10:31:16 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' height ' , ' function ' : GetHeight , ' admin ' : True , ' help ' : " Get current blockchain height " } )
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' tipbot_balance ' , ' function ' : GetTipbotBalance , ' admin ' : True , ' help ' : " Get current blockchain height " } )
2015-01-10 13:53:22 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' addbalance ' , ' parms ' : ' <nick> <amount> ' , ' function ' : AddBalance , ' admin ' : True , ' help ' : " Add balance to your account " } )
2014-12-31 10:31:16 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' scanwho ' , ' function ' : ScanWho , ' admin ' : True , ' help ' : " Refresh users list in a channel " } )
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' dump_users ' , ' function ' : DumpUsers , ' admin ' : True , ' help ' : " Dump users table to log " } )
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' show_activity ' , ' function ' : ShowActivity , ' admin ' : True , ' help ' : " Show time since a user was last active " } )
2015-01-01 10:06:09 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' reload ' , ' function ' : Reload , ' admin ' : True , ' help ' : " Reload a module " } )
2015-01-17 13:19:50 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' disable ' , ' function ' : Disable , ' admin ' : True , ' help ' : " Disable %s " % config . tipbot_name } )
2015-01-13 12:28:05 +00:00
RegisterCommand ( { ' module ' : ' builtin ' , ' name ' : ' quit ' , ' function ' : Quit , ' admin ' : True , ' help ' : " Quit " } )
2014-12-21 18:50:24 +00:00
2015-01-13 12:28:05 +00:00
def OnCommandProxy ( link , cmd ) :
2015-01-17 13:19:50 +00:00
if disabled :
2015-01-13 12:28:05 +00:00
log_info ( ' Ignoring command from %s while disabled: %s ' % ( str ( link . identity ( ) ) , str ( cmd ) ) )
2015-01-17 13:19:50 +00:00
return
2015-01-13 12:28:05 +00:00
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 ( )
2014-12-21 18:50:24 +00:00
2015-01-19 08:54:07 +00:00
def MigrateRedis ( ) :
2015-01-13 12:28:05 +00:00
balances = redis_hgetall ( ' balances ' )
for balance in balances :
if balance . find ( ' : ' ) == - 1 :
redis_hset ( ' balances ' , ' freenode: ' + balance , balances [ balance ] )
redis_hdel ( ' balances ' , balance )
2014-12-21 18:50:24 +00:00
2015-01-19 08:54:07 +00:00
keys = redisdb . keys ( ' * ' )
for key in keys :
if key . startswith ( ' dice:stats: ' ) and key . find ( ' freenode: ' ) == - 1 :
if key != " dice:stats: " and key != " dice:stats:* " :
parts = key . split ( ' : ' )
if len ( parts ) == 3 or ( len ( parts ) == 4 and parts [ 2 ] == " reset " ) :
parts . insert ( len ( parts ) - 1 , " freenode " )
newkey = " : " . join ( parts )
log_info ( ' renaming %s to %s ' % ( key , newkey ) )
redisdb . rename ( key , newkey )
for recordtype in [ ' playerseed ' , ' serverseed ' , ' rolls ' ] :
hname = ' dice: %s ' % recordtype
keys = redisdb . hgetall ( hname )
for key in keys :
if key . find ( ' : ' ) == - 1 :
newkey = ' freenode: ' + key
log_info ( ' renaming field %s to %s in %s ' % ( key , newkey , hname ) )
redisdb . hset ( hname , newkey , redisdb . hget ( hname , key ) )
redisdb . hdel ( hname , key )
2015-01-13 12:28:05 +00:00
RegisterCommands ( )
2014-12-29 17:08:13 +00:00
redisdb = connect_to_redis ( config . redis_host , config . redis_port )
2015-01-19 08:54:07 +00:00
MigrateRedis ( )
2014-12-29 17:08:13 +00:00
InitScanBlockHeight ( )
2014-12-09 10:00:19 +00:00
2015-01-13 12:28:05 +00:00
# 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 ( )
2015-01-10 13:53:42 +00:00
log_info ( ' shutting down redis ' )
redisdb . shutdown
log_info ( ' exiting ' )