From f8cf06c19738409e780cec1f7003d306196fd47d Mon Sep 17 00:00:00 2001 From: moneromooo Date: Sun, 21 Dec 2014 18:50:24 +0000 Subject: [PATCH] Rain, withdraw, wallet scanning, logs, lots of cleanup I should commit more often. --- tipbot.py | 702 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 552 insertions(+), 150 deletions(-) diff --git a/tipbot.py b/tipbot.py index b78739d..50b7859 100644 --- a/tipbot.py +++ b/tipbot.py @@ -1,56 +1,81 @@ #!/bin/python +# +# Monero tipbot +# Copyright 2014 moneromooo +# Inspired by "Simple Python IRC bot" by berend +# import socket +import select import sys -from random import randint +import random import re import redis import hashlib import json import httplib +import time -#----------------------------------- Settings --------------------------------------# -network = 'irc.freenode.net' -port = 6667 -homechan = '#txtptest000' -try: - irc = socket.socket ( socket.AF_INET, socket.SOCK_STREAM ) - irc.connect ( ( network, port ) ) -except Exception, e: - print 'Error initializing IRC: ' + str(e) - exit() -print irc.recv ( 4096 ) -irc.send ( 'PASS *********\r\n') -irc.send ( 'NICK txtptest\r\n' ) -irc.send ( 'USER txtptest txtptest txtptest :txtptest\r\n' ) -#----------------------------------------------------------------------------------# - -calltable=dict() +irc_network = 'irc.freenode.net' +irc_port = 6667 +irc_homechan = '#txtptest000' redis_host="127.0.0.1" redis_port=7777 -try: - redis = redis.Redis(host=redis_host,port=redis_port) -except Exception, e: - print 'Error initializing redis: ' + str(e) - exit() -#----------------------------------------------------------------------------------# -tipbot_address="TODO" bitmonerod_host = '127.0.0.1' bitmonerod_port = 6060 wallet_host = '127.0.0.1' wallet_port = 6061 -#---------------------------------- Functions -------------------------------------# -def readAdmin(host): # Return status 0/1 - bestand = open('admins.txt', 'r') - for line in bestand: - if host in line: - status = 1 - return status - else: - status = 0 - return status +wallet_update_time = 30 # seconds +withdrawal_fee = 10000000000 +min_withdraw_amount = 2*withdrawal_fee +withdraw_disabled = False + +userstable=dict() +calltable=dict() +last_wallet_update_time = None +last_ping_time = time.time() + + + +def log(stype,msg): + print '%s\t%s\t%s' % (time.ctime(time.time()),stype,str(msg)) + +def log_error(msg): + log("ERROR",msg) + +def log_warn(msg): + log("WARNING",msg) + +def log_info(msg): + log("INFO",msg) + +def log_log(msg): + log("LOG",msg) + +def log_IRCRECV(msg): + log("IRCRECV",msg) + +def connect_to_irc(network,port): + global irc + try: + irc = socket.socket ( socket.AF_INET, socket.SOCK_STREAM ) + irc.connect ( ( network, port ) ) + except Exception, e: + log_error( 'Error initializing IRC: %s' % str(e)) + exit() + log_IRCRECV(irc.recv ( 4096 )) + irc.send ( 'PASS *********\r\n') + irc.send ( 'NICK monero-tipbot\r\n' ) + irc.send ( 'USER monero-tipbot monero-tipbot monero-tipbot :monero-tipbot\r\n' ) + +def connect_to_redis(host,port): + try: + redis = redis.Redis(host=host,port=port) + except Exception, e: + log_error( 'Error initializing redis: %s' % str(e)) + exit() def GetHost(host): # Return Host host = host.split('@')[1] @@ -71,8 +96,20 @@ def GetNick(data): # Return Nickname nick = nick.strip(' \t\n\r') return nick +def GetPassword(): + try: + f = open('tipbot-password.txt', 'r') + for p in f: + p = p.strip("\r\n") + f.close() + return p + except Exception,e: + log_error('could not fetch password: %s' % str(e)) + raise + return "xxx" + def Send(msg): - irc.send('PRIVMSG ' + homechan + ' : ' + msg + '\r\n') + irc.send('PRIVMSG ' + irc_homechan + ' : ' + msg + '\r\n') def SendTo(where,msg): irc.send('PRIVMSG ' + where + ' : ' + msg + '\r\n') @@ -83,21 +120,32 @@ def Join(chan): def Part(chan): irc.send ( 'PART ' + chan + '\r\n' ) +def Who(chan): + irc.send ( 'WHO ' + chan + '\r\n' ) + def CheckRegistered(nick,ifyes,yesdata,ifno,nodata): if nick not in calltable: calltable[nick] = [] calltable[nick].append([ifyes,yesdata,ifno,nodata]) - SendTo('nickserv', "INFO " + nick) + SendTo('nickserv', "ACC " + nick) + +def IsAdmin(nick): + if nick == "moneromooo": + return True + if nick == "moneromoo": + return True + return False def CheckAdmin(nick,ifyes,yesdata,ifno,nodata): - if nick != "moneromooo" and nick != "moneromoo": + if not IsAdmin(nick): + log_warn('CheckAdmin: nick %s is not admin, cannot call %s with %s' % (str(nick),str(ifyes),str(yesdata))) SendTo(nick, "Access denied") return CheckRegistered(nick,ifyes,yesdata,ifno,nodata) def PerformNextAction(nick,registered): if nick not in calltable: - print 'Nothing in queue for ', nick + log_error( 'Nothing in queue for %s' % nick) return try: if registered: @@ -106,18 +154,36 @@ def PerformNextAction(nick,registered): calltable[nick][0][2](nick,calltable[nick][0][3]) del calltable[nick][0] except Exception, e: - print 'Exception in action, continuing: ' + str(e) + log_error('PerformNextAction: Exception in action, continuing: %s' % str(e)) del calltable[nick][0] def GetPaymentID(nick): salt="2u3g55bkwrui32fi3g4bGR$j5g4ugnujb"; p = hashlib.sha256(salt+nick).hexdigest(); - redis.hset("paymentid",p,nick) + try: + redis.hset("paymentid",p,nick) + except Exception,e: + log_error('GetPaymentID: failed to set payment ID for %s to redis: %s' % (nick,str(e))) return p +def GetTipbotAddress(): + try: + j = SendWalletJSONRPCCommand("getaddress",None) + if not "result" in j: + log_error('GetTipbotAddress: No result found in getaddress reply') + return ERROR + result = j["result"] + if not "address" in result: + log_error('GetTipbotAddress: No address found in getaddress reply') + return ERROR + return result["address"] + except Exception,e: + log_error("GetTipbotAddress: Error retrieving tipbot address: %s" % str(e)) + return "ERROR" + def GetNickFromPaymendID(p): nick = redis.hget("paymentid",p) - print 'PaymendID %s => %s' % (p, str(nick)) + log_log('PaymendID %s => %s' % (p, str(nick))) return nick def AmountToString(amount): @@ -127,43 +193,45 @@ def AmountToString(amount): if lamount < 1000000: samount = "%u tacoshi" % lamount elif lamount < 1000000000: - samount = " %f micromonero" % (float(lamount) / 1e6) - elif lamount < 1: - samount = " %f millimonero" % (float(lamount) / 1e9) + samount = " %.16g micromonero" % (float(lamount) / 1e6) + elif lamount < 1000000000000: + samount = " %.16g millimonero" % (float(lamount) / 1e9) else: - samount = "%f monero" % (float(lamount) / 1e12) + samount = "%.16g monero" % (float(lamount) / 1e12) return samount def GetBalance(nick,data): - print "GetBalance: checking", nick + log_log("GetBalance: checking %s" % nick) + sendto=data[0] try: balance = redis.hget("balances",nick) if balance == None: balance = 0 sbalance = AmountToString(balance) - SendTo(nick, "%s's balance is %s" % (nick, sbalance)) + SendTo(sendto, "%s's balance is %s" % (nick, sbalance)) except Exception, e: - print 'GetBalance: exception: ' + str(e) - SendTo(nick, "An error has occured") + log_error('GetBalance: exception: %s' % str(e)) + SendTo(sendto, "An error has occured") def AddBalance(nick,data): amount=data - print 'Adding ' + amount + " to " + nick + "'s balance" + log_info("AddBalance: Adding %s to %s's balance" % (AmountToString(amount),nick)) try: balance = redis.hincrby("balances",nick,amount) except Exception, e: - print 'AddBalance: exception: ' + str(e) + log_error('AddBalance: exception: %s' % str(e)) SendTo(nick, "An error has occured") def Tip(nick,data): - who=data[0] + sendto=data[0] + who=data[1] try: - amount=float(data[1]) + amount=float(data[2]) except Exception,e: - SendTo(nick, "Usage: tip nick amount") + SendTo(sendto, "Usage: tip nick amount") return - print "%s wants to tip %s %.12f monero" % (nick, who, amount) + log_info("Tip: %s wants to tip %s %.16g monero" % (nick, who, amount)) try: balance = redis.hget("balances",nick) if balance == None: @@ -171,58 +239,210 @@ def Tip(nick,data): balance=long(balance) units=long(amount*1e12) if units <= 0: - SendTo(nick, "Invalid amount") + SendTo(sendto, "Invalid amount") return if units > balance: - SendTo(nick, "You only have %.12f" % (balance / 1e12)) + SendTo(sendto, "You only have %.16g" % (balance / 1e12)) return - print '%s tipping %s %u units, with balance %u' % (nick, who, units, balance) + log_info('Tip: %s tipping %s %u units, with balance %u' % (nick, who, units, balance)) try: p = redis.pipeline() p.hincrby("balances",nick,-units); p.hincrby("balances",who,units) p.execute() - SendTo(nick,"%s has tipped %s %.12f monero" % (nick, who, amount)) + SendTo(sendto,"%s has tipped %s %.16g monero" % (nick, who, amount)) except Exception, e: - SendTo(nick, "An error occured") + SendTo(sendto, "An error occured") return except Exception, e: - print 'Tip: exception: ' + str(e) + log_error('Tip: exception: %s' % str(e)) + SendTo(sendto, "An error has occured") + +def ScanWho(nick,data): + chan=data[0] + userstable[chan] = [] + Who(chan) + +def Rain(nick,data): + chan=data[0] + try: + amount=float(data[1]) + except Exception,e: + SendTo(sendto, "Usage: rain amount [users]") + return + try: + if data[2] == None: + users = None + else: + users=long(data[2]) + except Exception,e: + SendTo(sendto, "Usage: rain amount [users]") + return + + if amount <= 0: + SendTo(sendto, "Usage: rain amount [users]") + return + if users != None and users <= 0: + SendTo(sendto, "Usage: rain amount [users]") + return + units = long(amount * 1e12) + + try: + balance = redis.hget("balances",nick) + if balance == None: + balance = 0 + balance=long(balance) + if units > balance: + SendTo(sendto, "You only have %s" % (AmountToString(balance))) + return + + userlist = userstable[chan][:] + userlist.remove(nick) + if users == None or users > len(userlist): + users = len(userlist) + everyone = True + else: + everyone = False + if units < users: + SendTo(sendto, "This would mean not even a tacoshi per nick") + return + log_info("%s wants to rain %s on %s users in %s" % (nick, AmountToString(units), users, chan)) + log_log("users in %s: %s" % (chan, str(userlist))) + random.shuffle(userlist) + userlist = userlist[0:users] + log_log("selected users in %s: %s" % (chan, userlist)) + user_units = long(units / users) + + if everyone: + msg = "%s rained %s on everyone in the channel" % (nick, AmountToString(user_units)) + else: + msg = "%s rained %s on:" % (nick, AmountToString(user_units)) + pipe = redis.pipeline() + pipe.hincrby("balances",nick,-units) + for user in userlist: + pipe.hincrby("balances",user,user_units) + if not everyone: + msg = msg + " " + user + pipe.execute() + SendTo(sendto, "%s" % msg) + + except Exception,e: + log_error('Rain: exception: %s' % str(e)) + SendTo(sendto, "An error has occured") + return + +def DisableWithdraw(): + log_warn('DisableWithdraw: disabled') + withdraw_disabled = True + +def Withdraw(nick,data): + address=data[0] + if len(address) != 95: + SendTo(nick, "Invalid address") + return + if address[0] != '4' and address[0] != '9': + SendTo(nick, "Invalid address") + return + + if min_withdraw_amount <= 0 or withdrawal_fee <= 0 or min_withdraw_amount < withdrawal_fee: + log_error('Withdraw: Inconsistent withdrawal settings') SendTo(nick, "An error has occured") + return + + log_info("Withdraw: %s wants to withdraw to %s" % (nick, address)) + + if withdraw_disabled: + log_error('Withdraw: disabled') + SendTo(nick, "Sorry, withdrawal is disabled due to a wallet error which requires admin assistance") + return + + try: + balance = redis.hget("balances",nick) + if balance == None: + balance = 0 + balance=long(balance) + except Exception, e: + log_error('Withdraw: exception: %s' % str(e)) + SendTo(nick, "An error has occured") + return + if balance <= 0 or balance < min_withdraw_amount: + log_info("Withdraw: Minimum withdrawal balance: %s, %s only has %s" % (AmountToString(min_withdraw_amount),nick,AmountToString(balance))) + SendTo(nick, "Minimum withdrawal balance: %s, you only have %s" % (AmountToString(min_withdraw_amount),AmountToString(balance))) + return + try: + fee = long(withdrawal_fee) + topay = long(balance - fee) + log_info('Withdraw: Raw: fee: %s, to pay: %s' % (str(fee), str(topay))) + log_info('Withdraw: fee: %s, to pay: %s' % (AmountToString(fee), AmountToString(topay))) + params = { + 'destinations': [{'address': address, 'amount': topay}], + 'payment_id': GetPaymentID(nick), + 'fee': fee, + 'mixin': 0, + 'unlock_time': 0, + } + j = SendWalletJSONRPCCommand("transfer",params) + except Exception,e: + log_error('Withdraw: Error in transfer: %s' % str(e)) + DisableWithdraw() + SendTo(nick,"An error has occured") + return + if not "result" in j: + log_error('Withdraw: No result in transfer reply') + DisableWithdraw() + SendTo(nick,"An error has occured") + return + result = j["result"] + if not "tx_hash" in result: + log_error('Withdraw: No tx_hash in transfer reply') + DisableWithdraw() + SendTo(nick,"An error has occured") + return + tx_hash = result["tx_hash"] + log_info('%s has withdrawn %s, tx hash %s' % (nick, balance, str(tx_hash))) + + try: + redis.hincrby("balances",nick,-balance) + except Exception, e: + log_error('Withdraw: FAILED TO SUBTRACT BALANCE: exception: %s' % str(e)) + DisableWithdraw() + + log_info('%s has withdrawn %s, tx hash %s' % (nick, balance, str(tx_hash))) + SendTo(nick, "Tx sent: %s" % tx_hash) def SendJSONRPCCommand(host,port,method,params): try: http = httplib.HTTPConnection(host,port) except Exception,e: - print 'Error connecting to %s:%u: %s' % (host, port, str(e)) + log_error('SendJSONRPCCommand: Error connecting to %s:%u: %s' % (host, port, str(e))) raise d = dict(id="0",jsonrpc="2.0",method=method,params=params) try: j = json.dumps(d).encode() except Exception,e: - print 'Failed to encode JSON: ' + str(e) + log_error('SendJSONRPCCommand: Failed to encode JSON: %s' % str(e)) http.close() raise - print 'Sending json as body: ', j + log_log('SendJSONRPCCommand: Sending json as body: %s' % j) headers = None try: http.request("POST","/json_rpc",body=j) except Exception,e: - print 'Failed to post request: ' + str(e) + log_error('SendJSONRPCCommand: Failed to post request: %s' % str(e)) http.close() raise response = http.getresponse() - print 'Received reply status: ', response.status + log_log('SendJSONRPCCommand: Received reply status: %s' % response.status) if response.status != 200: - print 'Error, not 200' + log_error('SendJSONRPCCommand: Error, not 200: %s' % str(response.status)) http.close() raise RuntimeError("Error "+response.status) s = response.read() - print 'Received reply: ', s + log_log('SendJSONRPCCommand: Received reply: %s' % str(s)) try: j = json.loads(s) except Exception,e: - print 'Failed to decode JSON: ' + str(e) + log_error('SendJSONRPCCommand: Failed to decode JSON: %s' % str(e)) http.close() raise http.close() @@ -232,27 +452,27 @@ def SendHTMLCommand(host,port,method): try: http = httplib.HTTPConnection(host,port) except Exception,e: - print 'Error connecting to %s:%u: %s' % (host, port, str(e)) + log_error('SendHTMLCommand: Error connecting to %s:%u: %s' % (host, port, str(e))) raise headers = None try: http.request("POST","/"+method) except Exception,e: - print 'Failed to post request: ' + str(e) + log_error('SendHTMLCommand: Failed to post request: %s' % str(e)) http.close() raise response = http.getresponse() - print 'Received reply status: ', response.status + log_log('SendHTMLCommand: Received reply status: %s' % response.status) if response.status != 200: - print 'Error, not 200' + log_error('SendHTMLCommand: Error, not 200: %s' % str(response.status)) http.close() raise RuntimeError("Error "+response.status) s = response.read() - print 'Received reply: ', s + log_log('SendHTMLCommand: Received reply: %s' % s) try: j = json.loads(s) except Exception,e: - print 'Failed to decode JSON: ' + str(e) + log_error('SendHTMLCommand: Failed to decode JSON: %s' % str(e)) http.close() raise http.close() @@ -268,64 +488,77 @@ def SendBitmonerodHTMLCommand(method): return SendHTMLCommand(bitmonerod_host,bitmonerod_port,method) def GetHeight(nick,data): - print '%s wants to know block height' % nick + log_info('GetHeight: %s wants to know block height' % nick) try: j = SendBitmonerodHTMLCommand("getheight") except Exception,e: + log_error('GetHeight: error: %s' % str(e)) SendTo(nick,"An error has occured") return - print 'Got reply: ' + str(j) + log_log('GetHeight: Got reply: %s' % str(j)) if not "height" in j: - print 'Cannot see height in here' + log_error('GetHeight: Cannot see height in here') SendTo(nick, "Height not found") return - SendTo(nick, "Height: %s" % str(j["height"])) + height=j["height"] + log_info('GetHeight: geight is %s' % str(height)) + SendTo(nick, "Height: %s" % str(height)) def GetTipbotBalance(nick,data): - print '%s wants to know the tipbot balance' % nick + log_info('%s wants to know the tipbot balance' % nick) try: j = SendWalletJSONRPCCommand("getbalance",None) except Exception,e: SendTo(nick,"An error has occured") return if not "result" in j: - print 'result not found in reply' + log_error('GetTipbotBalance: result not found in reply') SendTo(nick, "An error has occured") return result = j["result"] if not "balance" in result: - print 'balance not found in result' + log_error('GetTipbotBalance: balance not found in result') SendTo(nick, "An error has occured") return if not "unlocked_balance" in result: - print 'unlocked_balance not found in result' + log_error('GetTipbotBalance: unlocked_balance not found in result') SendTo(nick, "An error has occured") return balance = result["balance"] unlocked_balance = result["unlocked_balance"] - print 'balance: %s' % str(balance) - print 'unlocked_balance: %s' % str(unlocked_balance) + log_log('GetTipbotBalance: balance: %s' % str(balance)) + log_log('GetTipbotBalance: unlocked_balance: %s' % str(unlocked_balance)) pending = long(balance)-long(unlocked_balance) if pending < 0: - print 'Negative pending balance! balance %s, unlocked %s' + log_error('GetTipbotBalance: Negative pending balance! balance %s, unlocked %s' % (str(balance),str(unlocked))) SendTo(nick, "An error has occured") return if pending == 0: + log_info("GetTipbotBalance: Tipbot balance: %s" % AmountToString(balance)) SendTo(nick,"Tipbot balance: %s" % AmountToString(balance)) else: + 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))) +def EnableWithdraw(nick,data): + log_info('EnableWithdraw: enabled by %s' % nick) + withdraw_disabled = False + def Help(nick): SendTo(nick, "Help for the monero tipbot:") SendTo(nick, "!isregistered - show whether you are currently registered with freenode") SendTo(nick, "!balance - show your current balance") SendTo(nick, "!tip - tip another user") + SendTo(nick, "!rain [] - rain some monero on everyone (or just a few)") + SendTo(nick, "!withdraw
- withdraw your balance") SendTo(nick, "!info - information about the tipbot") SendTo(nick, "You can send monero to your tipbot account:"); - SendTo(nick, " Address: %s" % tipbot_address) + SendTo(nick, " Address: %s" % GetTipbotAddress()) SendTo(nick, " Payment ID: %s" % GetPaymentID(nick)) SendTo(nick, "NO WARRANTY, YOU MAY LOSE YOUR COINS") - SendTo(nick, "Minimum withdrawal: 0.1 monero") + SendTo(nick, "Minimum withdrawal: %s" % AmountToString(min_withdraw_amount)) + SendTo(nick, "Withdrawal fee: %s" % AmountToString(withdrawal_fee)) + SendTo(nick, "No Monero address ? You can use https://mymonero.com/") def Info(nick): SendTo(nick, "Info for the monero tipbot:") @@ -339,6 +572,72 @@ def Info(nick): SendTo(nick, "return of any monero. Use at your own risk.") SendTo(nick, "That being said, I hope you enjoy using it :)") +def UpdateCoin(): + global last_wallet_update_time + if last_wallet_update_time == None: + last_wallet_update_time = 0 + t=time.time() + dt = t - last_wallet_update_time + if dt < wallet_update_time: + return + try: + try: + scan_block_height = redis.get("scan_block_height") + scan_block_height = long(scan_block_height) + except Exception,e: + log_error('Failed to get scan_block_height: %s' % str(e)) + last_wallet_update_time = time.time() + return + + full_payment_ids = redis.hgetall("paymentid") + #print 'Got full payment ids: %s' % str(full_payment_ids) + payment_ids = [] + for pid in full_payment_ids: + payment_ids.append(pid) + #print 'Got payment ids: %s' % str(payment_ids) + params = { + "payment_ids": payment_ids, + "min_block_height": scan_block_height + } + j = SendWalletJSONRPCCommand("get_bulk_payments",params) + #print 'Got j: %s' % str(j) + if "result" in j: + result = j["result"] + if "payments" in result: + payments = result["payments"] + log_info('UpdateCoin: Got %d payments' % len(payments)) + for p in payments: + log_log('UpdateCoin: Looking at payment %s' % str(p)) + bh = p["block_height"] + if bh > scan_block_height: + scan_block_height = bh + log_log('UpdateCoin: seen payments up to block %d' % scan_block_height) + try: + pipe = redis.pipeline() + pipe.set("scan_block_height", scan_block_height) + log_log('UpdateCoin: processing payments') + for p in payments: + payment_id=p["payment_id"] + tx_hash=p["tx_hash"] + amount=p["amount"] + try: + recipient = GetNickFromPaymendID(payment_id) + log_info('UpdateCoin: Found payment %s to %s for %s' % (tx_hash,recipient, AmountToString(amount))) + pipe.hincrby("balances",recipient,amount); + except Exception,e: + log_error('UpdateCoin: No nick found for payment id %s, tx hash %s, amount %s' % (payment_id, tx_hash, amount)) + log_log('UpdateCoin: Executing received payments pipeline') + pipe.execute() + except Exception,e: + log_error('UpdateCoin: failed to set scan_block_height: %s' % str(e)) + else: + log_log('UpdateCoin: No payments in get_bulk_payments reply') + else: + log_error('UpdateCoin: No results in get_bulk_payments reply') + except Exception,e: + log_error('UpdateCoin: Failed to get bulk payments: %s' % str(e)) + last_wallet_update_time = time.time() + #def Op(to_op, chan): # irc.send( 'MODE ' + chan + ' +o: ' + to_op + '\r\n') # @@ -357,7 +656,21 @@ def getline(s): global buffered_data idx = buffered_data.find("\n") if idx == -1: - buffered_data+=s.recv(4096) + try: + (r,w,x)=select.select([s.fileno()],[],[],1) + if s.fileno() in r: + newdata=s.recv(4096,socket.MSG_DONTWAIT) + else: + newdata = None + if s.fileno() in x: + log_error('getline: IRC socket in exception set') + newdata = None + except Exception,e: + log_error('getline: Exception: %s' % str(e)) + newdata = None + if newdata == None: + return None + buffered_data+=newdata idx = buffered_data.find("\n") if idx == -1: ret = buffered_data @@ -367,42 +680,66 @@ def getline(s): buffered_data = buffered_data[idx+1:] return ret + + +connect_to_irc(irc_network,irc_port) +connect_to_redis(irc_network,irc_port) + while True: action = None data = getline(irc) + + # All that must be done even when nothing from IRC - data may be None here + UpdateCoin() + + if data == None: + if time.time() - last_ping_time > 60: + log_warn('60 seconds without PING, reconnecting') + last_ping_time = time.time() + connect_to_irc(irc_network,irc_port) + continue + data = data.strip("\r\n") - print data + log_IRCRECV(data) if data.find ( 'Welcome to the freenode Internet Relay Chat Network' ) != -1: - Join(homechan) + SendTo("nickserv", "IDENTIFY %s" % GetPassword()) + Join(irc_homechan) + #ScanWho(None,[irc_homechan]) - if data.find ( 'PING' ) != -1: - irc.send ( 'PONG ' + data.split() [ 1 ] + '\r\n' ) + if data.find ( 'PING' ) == 0: + log_log('Got PING, replying PONG') + last_ping_time = time.time() + irc.send ( 'PONG ' + data.split() [ 1 ] + '\r\n' ) + continue #--------------------------- Action check --------------------------------# if data.find(':') == -1: - continue + continue try: parts = data.split(':') - if len(parts) < 3: + if len(parts) < 2: continue - text = parts[2] + if len(parts) >= 3: + text = parts[2] + else: + text = "" parts = parts[1].split(' ') who = parts[0] action = parts[1] chan = parts[2] except Exception, e: - print 'Exception, continuing: ' + str(e) + log_error('main parser: Exception, continuing: %s' % str(e)) continue if action == None: continue - print 'text: ', text - print 'who: ', who - print 'action: ', action - print 'chan: ', chan + #print 'text: ', text + #print 'who: ', who + #print 'action: ', action + #print 'chan: ', chan # if data.find('#') != -1: # action = data.split('#')[0] @@ -413,70 +750,135 @@ while True: # action = 'NICK' #----------------------------- Actions -----------------------------------# - if action == 'NOTICE': + try: + if action == 'NOTICE': 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('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) + PerformNextAction(ns_nick, True) + else: + log_info('NickServ says %s is not identified' % ns_nick) + PerformNextAction(ns_nick, False) + else: + log_error('ACC line not as expected...') - if action == 'PRIVMSG': + 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].append(who_chan_user) + log_log("New list of users in %s: %s" % (who_chan, str(userstable[who_chan]))) + except Exception,e: + log_error('Failed to parse "who" line: %s: %s' % (data, str(e))) + + elif action == 'PRIVMSG': if text.find('!') != -1: cmd = text.split('!')[1] cmd = cmd.split(' ') cmd[0] = cmd[0].strip(' \t\n\r') - #print 'XXX found command: "%s"' % str(cmd) - if cmd[0] == 'join': - Join('#' + cmd[1]) - elif cmd[0] == 'part': - Part('#' + cmd[1]) - elif cmd[0] == 'version': - SendTo(chan, 'I am a monero tipbot, more info if you type !help') + if chan[0] == '#': + sendto=chan + else: + sendto=GetNick(who) + log_log('Found command: "%s" in channel "%s", replying to %s' % (str(cmd), str(chan), sendto)) + + #if cmd[0] == 'join': + # Join('#' + cmd[1]) + #elif cmd[0] == 'part': + # Part('#' + cmd[1]) + if cmd[0] == 'help': + Help(GetNick(who)) elif cmd[0] == 'isregistered': CheckRegistered(GetNick(who),SendTo,"You are registered",SendTo,"You are not registered") elif cmd[0] == 'balance': - CheckRegistered(GetNick(who),GetBalance,None,SendTo,"You must be registered with Freenode to query balance") + CheckRegistered(GetNick(who),GetBalance,[sendto],SendTo,"You must be registered with Freenode to query balance") elif cmd[0] == 'tip': if len(cmd) == 3: - CheckRegistered(GetNick(who),Tip,[cmd[1],cmd[2]],SendTo,"You must be registered with Freenode to tip") + CheckRegistered(GetNick(who),Tip,[sendto,cmd[1],cmd[2]],SendTo,"You must be registered with Freenode to tip") else: SendTo(GetNick(who), "Usage: !tip nick amount"); + elif cmd[0] == 'withdraw': + if len(cmd) == 2: + CheckRegistered(GetNick(who),Withdraw,[cmd[1]],SendTo,"You must be registered with Freenode to withdraw") + else: + SendTo(GetNick(who), "Usage: !withdraw address"); + elif cmd[0] == 'info': + Info(GetNick(who)) + elif cmd[0] == 'rain': + if chan[0] == '#': + if len(cmd) == 2 or len(cmd) == 3: + users = None + if len(cmd) == 3: + users = cmd[2] + CheckRegistered(GetNick(who),Rain,[chan,cmd[1],users],SendTo,"You must be registered with Freenode to rain") + else: + SendTo(sendto, "Usage: !rain amount [users]"); + else: + SendTo(sendto, "Raining can only be done in a channel") + # admin commands elif cmd[0] == 'height': CheckAdmin(GetNick(who),GetHeight,None,SendTo,"You must be admin") elif cmd[0] == 'tipbot_balance': CheckAdmin(GetNick(who),GetTipbotBalance,None,SendTo,"You must be admin") - #elif cmd[0] == 'addbalance': - # CheckRegistered(GetNick(who),AddBalance,cmd[1],SendTo,"You must be registered with Freenode to add balance") - elif cmd[0] == 'info': - Info(GetNick(who)) - elif cmd[0] == 'help': - Help(GetNick(who)) + elif cmd[0] == 'addbalance': + CheckAdmin(GetNick(who),AddBalance,cmd[1],SendTo,"You must be admin") + elif cmd[0] == 'scanwho': + CheckAdmin(GetNick(who),ScanWho,[chan],SendTo,"You must be admin") + elif cmd[0] == 'enable_withdraw': + CheckAdmin(GetNick(who),EnableWithdraw,None,SendTo,"You must be admin") + else: + SendTo(GetNick(who), "Invalid command, try !help") + + elif action == 'JOIN': + nick = GetNick(who) + log_info('%s joined the channel' % nick) + if not chan in userstable: + userstable[chan] = [] + if nick in userstable[chan]: + log_warn('%s joined, but already in %s' % (nick, chan)) + else: + userstable[chan].append(nick) + log_log("New list of users in %s: %s" % (chan, str(userstable[chan]))) + + elif action == 'PART': + nick = GetNick(who) + log_info('%s joined the channel' % nick) + if not nick in userstable[chan]: + log_warn('%s parted, but was not in %s' % (nick, chan)) + else: + userstable[chan].remove(nick) + log_log("New list of users in %s: %s" % (chan, str(userstable[chan]))) + + elif action == 'NICK': + nick = GetNick(who) + new_nick = text + log_info('%s renamed to %s' % (nick, new_nick)) + for c in userstable: + log_log('checking %s' % c) + if nick in userstable[c]: + userstable[c].remove(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].append(new_nick) + log_log("New list of users in %s: %s" % (c, str(userstable[c]))) + + except Exception,e: + log_error('Exception in top level action processing: %s' % str(e)) -# if action == 'xxx_______MODE': -# Host = GetHost(data) -# status = readAdmin(Host) -# if status == 0: -# if data.find('-o') != -1: -# to_op = data.split('-o')[1] -# chan = GetChannel(data) -# chan = chan.split('-o')[0] -# Op(to_op, chan) -# -# if data.find('+o') != -1: -# to_deop = data.split('+o')[1] -# chan = GetChannel(data) -# chan = chan.split('+o')[0] -# DeOp(to_deop, chan) -# -# if action == 'xxx_______JOIN': -# Host = GetHost(data) -# status = readAdmin(Host) -# if status == 1: -# chan = GetChannel(data) -# nick = GetNick(data) -# Op(nick, chan)