mirror of
https://git.wownero.com/wownero/tippero.git
synced 2024-08-15 00:33:14 +00:00
Structural overhaul
Split the tipbot in modules: - main modules to contain base functions by theme - coin specs, to define a coin's specifics - optional modules, defining commands/behaviors
This commit is contained in:
parent
7cfc14faf6
commit
6b6a1a67e7
17 changed files with 1460 additions and 1127 deletions
14
README
14
README
|
@ -9,13 +9,15 @@ Installation requirements:
|
|||
The daemon needs a running redis, daemon and simplewallet. Set the connection parameters
|
||||
for these in tipbot.py.
|
||||
|
||||
Before starting, read the configuration parameters at the top of tipbot.py and change
|
||||
Before starting, read the configuration parameters in tipbot/config.py and change
|
||||
as appropriate.
|
||||
|
||||
Start the bot with the coin name as parameter (eg, python tipbot.py monero). Coin specs
|
||||
are defined in a file called tipbot_<coin-name>.py. If you want to add a coin that the
|
||||
tipbot does not support yet, simply copy an existing spec module and adapt to that coin's
|
||||
particular specs.
|
||||
Start the bot with the coin name as parameter to -c (eg, python tipbot.py -c monero).
|
||||
Coin specs are defined in a file called tipbot/coinspecs.py. If you want to add a coin
|
||||
that the tipbot does not support yet, simply copy and adapt an existing spec.
|
||||
|
||||
Modules are loaded with -m (eg, python tipbot.py -m payment). Available modules are
|
||||
in the tipbot/modules directory.
|
||||
|
||||
The tipbot will need a wallet. Any wallet can do, but it is recommended to use a separate
|
||||
wallet. This wallet should be loaded in the simplewallet the tipbot connects to.
|
||||
|
@ -24,7 +26,7 @@ A file called tipbot-password.txt shall be created where the tpibot runs, contai
|
|||
Freenode account password for the tipbot. This is so the tipbot can identify, to avoid
|
||||
others passing off for the tipbot.
|
||||
|
||||
Tipbot commands are prefix with "!". Try !help to get a list of available commands.
|
||||
Tipbot commands are prefix with "!". Try !commands to get a list of available commands.
|
||||
|
||||
The withdrawal fee is currently set to the default network fee. For coins with per kB fees,
|
||||
if a withdraw transaction happens to be larger than 1 kB, more will be charged by
|
||||
|
|
1
tipbot/__init__.py
Normal file
1
tipbot/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
__all__ = [ 'config', 'coins', 'utils', 'log', 'irc', 'commands' ]
|
41
tipbot/coinspecs.py
Normal file
41
tipbot/coinspecs.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - coins specifications
|
||||
# Copyright 2014 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.
|
||||
#
|
||||
|
||||
coinspecs = {
|
||||
"monero": {
|
||||
"name": "Monero",
|
||||
"atomic_units": 1e12,
|
||||
"denominations": [[1000000, 1, "piconero"], [1000000000, 1e6, "micronero"], [1000000000000, 1e9, "millinero"]],
|
||||
"address_length": [95, 95], # min/max size of addresses
|
||||
"address_prefix": ['4', '9'], # allowed prefixes of addresses
|
||||
"min_withdrawal_fee": 10000000000,
|
||||
"web_wallet_url": "https://mymonero.com/", # None is there's none
|
||||
},
|
||||
"ducknote": {
|
||||
"name": "Darknote",
|
||||
"atomic_units": 1e8,
|
||||
"denominations": [],
|
||||
"address_length": [95, 98], # min/max size of addresses
|
||||
"address_prefix": ['dd'], # allowed prefixes of addresses
|
||||
"min_withdrawal_fee": 1000000,
|
||||
"web_wallet_url": None,
|
||||
},
|
||||
"dashcoin": {
|
||||
"name": "Dashcoin",
|
||||
"atomic_units": 1e8,
|
||||
"denominations": [],
|
||||
"address_length": [96], # min/max size of addresses
|
||||
"address_prefix": ['D'], # allowed prefixes of addresses
|
||||
"min_withdrawal_fee": 1000000,
|
||||
"web_wallet_url": None,
|
||||
}
|
||||
}
|
||||
|
96
tipbot/command_manager.py
Normal file
96
tipbot/command_manager.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - commands
|
||||
# Copyright 2014 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 tipbot.config as config
|
||||
from tipbot.irc import *
|
||||
|
||||
commands = dict()
|
||||
calltable=dict()
|
||||
idles = []
|
||||
|
||||
def RunRegisteredCommand(nick,chan,ifyes,yesdata,ifno,nodata):
|
||||
if nick not in calltable:
|
||||
calltable[nick] = []
|
||||
calltable[nick].append([chan,ifyes,yesdata,ifno,nodata])
|
||||
if nick in registered_users:
|
||||
RunNextCommand(nick,True)
|
||||
else:
|
||||
SendTo('nickserv', "ACC " + nick)
|
||||
|
||||
def IsAdmin(nick):
|
||||
return nick in config.admins
|
||||
|
||||
def RunAdminCommand(nick,chan,ifyes,yesdata,ifno,nodata):
|
||||
if not IsAdmin(nick):
|
||||
log_warn('RunAdminCommand: nick %s is not admin, cannot call %s with %s' % (str(nick),str(ifyes),str(yesdata)))
|
||||
SendTo(nick, "Access denied")
|
||||
return
|
||||
RunRegisteredCommand(nick,chan,ifyes,yesdata,ifno,nodata)
|
||||
|
||||
def RunNextCommand(nick,registered):
|
||||
if registered:
|
||||
registered_users.add(nick)
|
||||
else:
|
||||
registered_users.discard(nick)
|
||||
if nick not in calltable:
|
||||
log_error( 'Nothing in queue for %s' % nick)
|
||||
return
|
||||
try:
|
||||
if registered:
|
||||
calltable[nick][0][1](nick,calltable[nick][0][0],calltable[nick][0][2])
|
||||
else:
|
||||
calltable[nick][0][3](nick,calltable[nick][0][0],calltable[nick][0][4])
|
||||
del calltable[nick][0]
|
||||
except Exception, e:
|
||||
log_error('RunNextCommand: Exception in action, continuing: %s' % str(e))
|
||||
del calltable[nick][0]
|
||||
|
||||
def Commands(nick,chan,cmd):
|
||||
if IsAdmin(nick):
|
||||
all = True
|
||||
else:
|
||||
all = False
|
||||
SendTo(nick, "Commands for %s:" % config.tipbot_name)
|
||||
for command_name in commands:
|
||||
c = commands[command_name]
|
||||
if 'admin' in c and c['admin'] and not all:
|
||||
continue
|
||||
synopsis = c['name']
|
||||
if 'parms' in c:
|
||||
synopsis = synopsis + " " + c['parms']
|
||||
SendTo(nick, "%s - %s" % (synopsis, c['help']))
|
||||
|
||||
def RegisterCommand(command):
|
||||
commands[command['name']] = command
|
||||
|
||||
def RegisterIdleFunction(function):
|
||||
idles.append(function)
|
||||
|
||||
def OnCommand(cmd,chan,who,check_admin,check_registered):
|
||||
if cmd[0] in commands:
|
||||
c = commands[cmd[0]]
|
||||
if 'admin' in c and c['admin']:
|
||||
check_admin(GetNick(who),chan,c['function'],cmd,SendTo,"You must be admin")
|
||||
elif 'registered' in c and c['registered']:
|
||||
check_registered(GetNick(who),chan,c['function'],cmd,SendTo,"You must be registered with Freenode")
|
||||
else:
|
||||
c['function'](GetNick(who),chan,cmd)
|
||||
else:
|
||||
SendTo(GetNick(who), "Invalid command, try !help")
|
||||
|
||||
def RunIdleFunctions(param):
|
||||
for f in idles:
|
||||
try:
|
||||
f(param)
|
||||
except Exception,e:
|
||||
log_error("Exception running idle function %s: %s" % (str(f),str(e)))
|
||||
|
||||
|
35
tipbot/config.py
Normal file
35
tipbot/config.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - configuration
|
||||
# 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.
|
||||
#
|
||||
|
||||
tipbot_name = "monero-testnet-tipbot"
|
||||
irc_network = 'irc.freenode.net'
|
||||
irc_port = 6667
|
||||
irc_send_delay = 0.4
|
||||
|
||||
redis_host="127.0.0.1"
|
||||
redis_port=7777
|
||||
|
||||
daemon_host = 'testfull.monero.cc' # '127.0.0.1'
|
||||
daemon_port = 28081 # 6060
|
||||
wallet_host = '127.0.0.1'
|
||||
wallet_port = 6061
|
||||
wallet_update_time = 30 # seconds
|
||||
withdrawal_fee=None # None defaults to the network default fee
|
||||
min_withdraw_amount = None # None defaults to the withdrawal fee
|
||||
disable_withdraw_on_error = True
|
||||
|
||||
admins = ["moneromooo", "moneromoo"]
|
||||
|
||||
# list of nicks to ignore for rains - bots, trolls, etc
|
||||
no_rain_to_nicks = []
|
||||
|
||||
|
399
tipbot/irc.py
Normal file
399
tipbot/irc.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
#!/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 select
|
||||
import time
|
||||
import string
|
||||
from tipbot.log import log_error, log_warn, log_info, log_log, log_IRCSEND, log_IRCRECV
|
||||
|
||||
irc_line_delay = 0
|
||||
irc = None
|
||||
irc_password = ""
|
||||
|
||||
irc_welcome_line = 'Welcome to the freenode Internet Relay Chat Network'
|
||||
irc_homechan = '#txtptest000'
|
||||
irc_timeout_seconds = 600
|
||||
last_ping_time = time.time()
|
||||
irc_network = None
|
||||
irc_port = None
|
||||
irc_name = None
|
||||
|
||||
userstable=dict()
|
||||
registered_users=set()
|
||||
|
||||
def SendIRC(msg):
|
||||
log_IRCSEND(msg)
|
||||
irc.send(msg + '\r\n')
|
||||
time.sleep(irc_line_delay)
|
||||
|
||||
def connect_to_irc(network,port,name,password,delay):
|
||||
global irc
|
||||
global irc_line_delay
|
||||
global irc_network
|
||||
global irc_port
|
||||
global irc_line_delay
|
||||
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 )
|
||||
irc.connect ( ( network, port ) )
|
||||
except Exception, e:
|
||||
log_error( 'Error initializing IRC: %s' % str(e))
|
||||
exit()
|
||||
log_IRCRECV(irc.recv ( 4096 ))
|
||||
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 Send(msg):
|
||||
SendIRC ('PRIVMSG ' + irc_homechan + ' : ' + msg)
|
||||
|
||||
def SendTo(where,msg):
|
||||
SendIRC ('PRIVMSG ' + where + ' : ' + msg)
|
||||
|
||||
def Join(chan):
|
||||
SendIRC ( 'JOIN ' + chan)
|
||||
|
||||
def Part(chan):
|
||||
SendIRC ( 'PART ' + chan)
|
||||
|
||||
def Who(chan):
|
||||
userstable[chan] = dict()
|
||||
SendIRC ( 'WHO ' + chan)
|
||||
|
||||
def GetHost(host): # Return Host
|
||||
host = host.split('@')[1]
|
||||
host = host.split(' ')[0]
|
||||
return host
|
||||
|
||||
def GetChannel(data): # Return Channel
|
||||
channel = data.split('#')[1]
|
||||
channel = channel.split(':')[0]
|
||||
channel = '#' + channel
|
||||
channel = channel.strip(' \t\n\r')
|
||||
return channel
|
||||
|
||||
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 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 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(s):
|
||||
global buffered_data
|
||||
idx = buffered_data.find("\n")
|
||||
if idx == -1:
|
||||
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))
|
||||
# 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
|
||||
|
||||
while True:
|
||||
action = None
|
||||
try:
|
||||
data = GetIRCLine(irc)
|
||||
except Exception,e:
|
||||
log_warn('Exception fron GetIRCLine, we were probably disconnected, reconnecting in 5 seconds')
|
||||
time.sleep(5)
|
||||
last_ping_time = time.time()
|
||||
reconnect_to_irc(irc_network,irc_port)
|
||||
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 > irc_timeout_seconds:
|
||||
log_warn('%s seconds without PING, reconnecting in 5 seconds' % irc_timeout_seconds)
|
||||
time.sleep(5)
|
||||
last_ping_time = time.time()
|
||||
reconnect_to_irc(irc_network,irc_port)
|
||||
continue
|
||||
|
||||
data = data.strip("\r\n")
|
||||
log_IRCRECV(data)
|
||||
|
||||
# consider any IRC data as a ping
|
||||
last_ping_time = time.time()
|
||||
|
||||
if data.find ( irc_welcome_line ) != -1:
|
||||
userstable = dict()
|
||||
registered_users.clear()
|
||||
SendTo("nickserv", "IDENTIFY %s" % irc_password)
|
||||
Join(irc_homechan)
|
||||
#ScanWho(None,[irc_homechan])
|
||||
|
||||
if data.find ( 'PING' ) == 0:
|
||||
log_log('Got PING, replying PONG')
|
||||
last_ping_time = time.time()
|
||||
SendIRC ( 'PONG ' + data.split() [ 1 ])
|
||||
continue
|
||||
|
||||
if data.find('ERROR :Closing Link:') == 0:
|
||||
log_warn('We were kicked from IRC, reconnecting in 5 seconds')
|
||||
time.sleep(5)
|
||||
last_ping_time = time.time()
|
||||
reconnect_to_irc(irc_network,irc_port)
|
||||
continue
|
||||
|
||||
#--------------------------- Action check --------------------------------#
|
||||
if data.find(':') == -1:
|
||||
continue
|
||||
|
||||
try:
|
||||
cparts = data.split(':')
|
||||
if len(cparts) < 2:
|
||||
continue
|
||||
if len(cparts) >= 3:
|
||||
text = cparts[2]
|
||||
else:
|
||||
text = ""
|
||||
parts = cparts[1].split(' ')
|
||||
who = parts[0]
|
||||
action = parts[1]
|
||||
chan = parts[2]
|
||||
except Exception, 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
|
||||
|
||||
# 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 == '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(' 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 == '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 "who" line: %s: %s' % (data, str(e)))
|
||||
|
||||
elif action == '353':
|
||||
try:
|
||||
who_chan = parts[4]
|
||||
who_chan_users = cparts[2].split(" ")
|
||||
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 "who" line: %s: %s' % (data, str(e)))
|
||||
|
||||
elif action == 'PRIVMSG':
|
||||
UpdateLastActiveTime(chan,GetNick(who))
|
||||
exidx = text.find('!')
|
||||
if exidx != -1 and len(text)>exidx+1 and text[exidx+1] in string.ascii_letters:
|
||||
cmd = text.split('!')[1]
|
||||
cmd = cmd.split(' ')
|
||||
cmd[0] = cmd[0].strip(' \t\n\r')
|
||||
|
||||
log_log('Found command: "%s" in channel "%s"' % (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 == '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]:
|
||||
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))
|
||||
|
35
tipbot/log.py
Normal file
35
tipbot/log.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - logging
|
||||
# Copyright 2014 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 time
|
||||
|
||||
def log(stype,msg):
|
||||
header = "%s\t%s\t" % (time.ctime(time.time()),stype)
|
||||
print "%s%s" % (header, str(msg).replace("\n","\n"+header))
|
||||
|
||||
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 log_IRCSEND(msg):
|
||||
log("IRCSEND",msg)
|
||||
|
1
tipbot/modules/__init__.py
Normal file
1
tipbot/modules/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
__all__ = ['tipping', 'withdraw', 'payment']
|
92
tipbot/modules/payment.py
Normal file
92
tipbot/modules/payment.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - payment
|
||||
# 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 redis
|
||||
import time
|
||||
import tipbot.config as config
|
||||
from tipbot.log import log_error, log_warn, log_info, log_log
|
||||
from tipbot.utils import *
|
||||
from tipbot.redisdb import *
|
||||
|
||||
last_wallet_update_time = None
|
||||
|
||||
def UpdateCoin(param):
|
||||
irc = param[0]
|
||||
redisdb = param[1]
|
||||
|
||||
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 < config.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()
|
||||
|
||||
RegisterIdleFunction(UpdateCoin)
|
||||
|
269
tipbot/modules/tipping.py
Normal file
269
tipbot/modules/tipping.py
Normal file
|
@ -0,0 +1,269 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - tipping commands
|
||||
# Copyright 2014 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 socket
|
||||
import select
|
||||
import random
|
||||
import redis
|
||||
import hashlib
|
||||
import json
|
||||
import httplib
|
||||
import time
|
||||
import string
|
||||
import tipbot.config as config
|
||||
from tipbot.log import log_error, log_warn, log_info, log_log
|
||||
import tipbot.coinspecs as coinspecs
|
||||
from tipbot.utils import *
|
||||
from tipbot.irc import *
|
||||
from tipbot.command_manager import *
|
||||
from tipbot.redisdb import *
|
||||
|
||||
|
||||
def Tip(nick,chan,cmd):
|
||||
sendto=GetSendTo(nick,chan)
|
||||
try:
|
||||
who=cmd[1]
|
||||
amount=float(cmd[2])
|
||||
except Exception,e:
|
||||
SendTo(sendto, "Usage: tip nick amount")
|
||||
return
|
||||
units=long(amount*coinspecs.atomic_units)
|
||||
if units <= 0:
|
||||
SendTo(sendto, "Invalid amount")
|
||||
return
|
||||
|
||||
log_info("Tip: %s wants to tip %s %s" % (nick, who, AmountToString(units)))
|
||||
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
|
||||
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(sendto,"%s has tipped %s %s" % (nick, who, AmountToString(units)))
|
||||
except Exception, e:
|
||||
SendTo(sendto, "An error occured")
|
||||
return
|
||||
except Exception, e:
|
||||
log_error('Tip: exception: %s' % str(e))
|
||||
SendTo(sendto, "An error has occured")
|
||||
|
||||
def Rain(nick,chan,cmd):
|
||||
userstable = GetUsersTable()
|
||||
|
||||
if chan[0] != '#':
|
||||
SendTo(nick, "Raining can only be done in a channel")
|
||||
return
|
||||
|
||||
try:
|
||||
amount=float(cmd[1])
|
||||
except Exception,e:
|
||||
SendTo(chan, "Usage: rain amount [users]")
|
||||
return
|
||||
users = GetParam(cmd,2)
|
||||
if users:
|
||||
try:
|
||||
users=long(users)
|
||||
except Exception,e:
|
||||
SendTo(chan, "Usage: rain amount [users]")
|
||||
return
|
||||
|
||||
if amount <= 0:
|
||||
SendTo(chan, "Usage: rain amount [users]")
|
||||
return
|
||||
if users != None and users <= 0:
|
||||
SendTo(chan, "Usage: rain amount [users]")
|
||||
return
|
||||
units = long(amount * coinspecs.atomic_units)
|
||||
|
||||
try:
|
||||
balance = redis_hget("balances",nick)
|
||||
if balance == None:
|
||||
balance = 0
|
||||
balance=long(balance)
|
||||
if units > balance:
|
||||
SendTo(chan, "You only have %s" % (AmountToString(balance)))
|
||||
return
|
||||
|
||||
log_log("userstable: %s" % str(userstable))
|
||||
userlist = userstable[chan].keys()
|
||||
userlist.remove(nick)
|
||||
for n in config.no_rain_to_nicks:
|
||||
userlist.remove(n)
|
||||
if users == None or users > len(userlist):
|
||||
users = len(userlist)
|
||||
everyone = True
|
||||
else:
|
||||
everyone = False
|
||||
if users == 0:
|
||||
SendTo(chan, "Nobody eligible for rain")
|
||||
return
|
||||
if units < users:
|
||||
SendTo(chan, "This would mean not even an atomic unit 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(chan, "%s" % msg)
|
||||
|
||||
except Exception,e:
|
||||
log_error('Rain: exception: %s' % str(e))
|
||||
SendTo(chan, "An error has occured")
|
||||
return
|
||||
|
||||
def RainActive(nick,chan,cmd):
|
||||
userstable = GetUsersTable()
|
||||
|
||||
amount=GetParam(cmd,1)
|
||||
hours=GetParam(cmd,2)
|
||||
minfrac=GetParam(cmd,3)
|
||||
|
||||
if chan[0] != '#':
|
||||
SendTo(nick, "Raining can only be done in a channel")
|
||||
return
|
||||
try:
|
||||
amount=float(amount)
|
||||
if amount <= 0:
|
||||
raise RuntimeError("")
|
||||
except Exception,e:
|
||||
SendTo(chan, "Invalid amount")
|
||||
return
|
||||
try:
|
||||
hours=float(hours)
|
||||
if hours <= 0:
|
||||
raise RuntimeError("")
|
||||
except Exception,e:
|
||||
SendTo(chan, "Invalid hours")
|
||||
return
|
||||
if minfrac:
|
||||
try:
|
||||
minfrac=float(minfrac)
|
||||
if minfrac < 0 or minfrac > 1:
|
||||
raise RuntimeError("")
|
||||
except Exception,e:
|
||||
SendTo(chan, "minfrac must be a number between 0 and 1")
|
||||
return
|
||||
else:
|
||||
minfrac = 0
|
||||
|
||||
units = long(amount * coinspecs.atomic_units)
|
||||
|
||||
try:
|
||||
balance = redis_hget("balances",nick)
|
||||
if balance == None:
|
||||
balance = 0
|
||||
balance=long(balance)
|
||||
if units > balance:
|
||||
SendTo(chan, "You only have %s" % (AmountToString(balance)))
|
||||
return
|
||||
|
||||
now = time.time()
|
||||
userlist = userstable[chan].keys()
|
||||
userlist.remove(nick)
|
||||
for n in config.no_rain_to_nicks:
|
||||
userlist.remove(n)
|
||||
weights=dict()
|
||||
weight=0
|
||||
for n in userlist:
|
||||
t = userstable[chan][n]
|
||||
if t == None:
|
||||
continue
|
||||
dt = now - t
|
||||
if dt <= hours * 3600:
|
||||
w = (1 * (hours * 3600 - dt) + minfrac * (1 - (hours * 3600 - dt))) / (hours * 3600)
|
||||
weights[n] = w
|
||||
weight += w
|
||||
|
||||
if len(weights) == 0:
|
||||
SendTo(chan, "Nobody eligible for rain")
|
||||
return
|
||||
|
||||
# if units < users:
|
||||
# SendTo(chan, "This would mean not even an atomic unit per nick")
|
||||
# return
|
||||
|
||||
pipe = redis_pipeline()
|
||||
pipe.hincrby("balances",nick,-units)
|
||||
rained_units = 0
|
||||
nnicks = 0
|
||||
minu=None
|
||||
maxu=None
|
||||
for n in weights:
|
||||
user_units = long(units * weights[n] / weight)
|
||||
if user_units <= 0:
|
||||
continue
|
||||
log_info("%s rained %s on %s (last active %f hours ago)" % (nick, AmountToString(user_units),n,GetTimeSinceActive(chan,n)/3600))
|
||||
pipe.hincrby("balances",n,user_units)
|
||||
rained_units += user_units
|
||||
if not minu or user_units < minu:
|
||||
minu = user_units
|
||||
if not maxu or user_units > maxu:
|
||||
maxu = user_units
|
||||
nnicks = nnicks+1
|
||||
|
||||
if maxu == None:
|
||||
SendTo(chan, "This would mean not even an atomic unit per nick")
|
||||
return
|
||||
|
||||
pipe.execute()
|
||||
log_info("%s rained %s - %s (total %s, acc %s) on the %d nicks active in the last %f hours" % (nick, AmountToString(minu), AmountToString(maxu), AmountToString(units), AmountToString(rained_units), nnicks, hours))
|
||||
SendTo(chan, "%s rained %s - %s on the %d nicks active in the last %f hours" % (nick, AmountToString(minu), AmountToString(maxu), nnicks, hours))
|
||||
|
||||
except Exception,e:
|
||||
log_error('Rain: exception: %s' % str(e))
|
||||
SendTo(chan, "An error has occured")
|
||||
return
|
||||
|
||||
|
||||
RegisterCommand({
|
||||
'name': 'tip',
|
||||
'parms': '<nick> <amount>',
|
||||
'function': Tip,
|
||||
'registered': True,
|
||||
'help': "tip another user"
|
||||
})
|
||||
RegisterCommand({
|
||||
'name': 'rain',
|
||||
'parms': '<amount> [<users>]',
|
||||
'function': Rain,
|
||||
'registered': True,
|
||||
'help': "rain some coins on everyone (or just a few)"
|
||||
})
|
||||
RegisterCommand({
|
||||
'name': 'rainactive',
|
||||
'parms': '<amount> [<hours>]',
|
||||
'function': RainActive,
|
||||
'registered': True,
|
||||
'help': "rain some coins on whoever was active recently"
|
||||
})
|
167
tipbot/modules/withdraw.py
Normal file
167
tipbot/modules/withdraw.py
Normal file
|
@ -0,0 +1,167 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - withdrawal commands
|
||||
# Copyright 2014 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 redis
|
||||
import json
|
||||
import string
|
||||
from tipbot.log import log_error, log_warn, log_info, log_log
|
||||
import tipbot.coinspecs as coinspecs
|
||||
from tipbot.utils import *
|
||||
from tipbot.irc import *
|
||||
from tipbot.redisdb import *
|
||||
from tipbot.command_manager import *
|
||||
|
||||
withdraw_disabled = False
|
||||
|
||||
def DisableWithdraw(nick,chan,cmd):
|
||||
global withdraw_disabled
|
||||
if nick:
|
||||
log_warn('DisableWithdraw: disabled by %s' % nick)
|
||||
else:
|
||||
log_warn('DisableWithdraw: disabled')
|
||||
withdraw_disabled = True
|
||||
|
||||
def EnableWithdraw(nick,chan,cmd):
|
||||
global withdraw_disabled
|
||||
log_info('EnableWithdraw: enabled by %s' % nick)
|
||||
withdraw_disabled = False
|
||||
|
||||
def CheckDisableWithdraw():
|
||||
if config.disable_withdraw_on_error:
|
||||
DisableWithdraw(None,None,None)
|
||||
|
||||
def IsValidAddress(address):
|
||||
if len(address) < coinspecs.address_length[0] or len(address) > coinspecs.address_length[1]:
|
||||
return False
|
||||
for prefix in coinspecs.address_prefix:
|
||||
if address.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
def Withdraw(nick,chan,cmd):
|
||||
local_withdraw_fee = config.withdrawal_fee or coinspecs.min_withdrawal_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:
|
||||
log_error('Withdraw: Inconsistent withdrawal settings')
|
||||
SendTo(nick, "An error has occured")
|
||||
return
|
||||
|
||||
try:
|
||||
address=cmd[1]
|
||||
except Exception,e:
|
||||
SendTo(sendto, "Usage: withdraw address [amount]")
|
||||
return
|
||||
|
||||
if not IsValidAddress(address):
|
||||
SendTo(nick, "Invalid address")
|
||||
return
|
||||
amount = GetParam(cmd,2)
|
||||
if amount:
|
||||
try:
|
||||
famount=float(amount)
|
||||
if (famount < 0):
|
||||
raise RuntimeError("")
|
||||
amount = long(famount * coinspecs.atomic_units)
|
||||
amount += local_withdraw_fee
|
||||
except Exception,e:
|
||||
SendTo(nick, "Invalid amount")
|
||||
return
|
||||
|
||||
log_info("Withdraw: %s wants to withdraw %s to %s" % (nick, AmountToString(amount) if amount else "all", 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 amount:
|
||||
if amount > balance:
|
||||
log_info("Withdraw: %s trying to withdraw %s, but only has %s" % (nick,AmountToString(amount),AmountToString(balance)))
|
||||
SendTo(nick, "You only have %s" % AmountToString(balance))
|
||||
return
|
||||
else:
|
||||
amount = balance
|
||||
|
||||
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)))
|
||||
SendTo(nick, "Minimum withdrawal balance: %s, cannot withdraw %s" % (AmountToString(config.min_withdraw_amount),AmountToString(amount)))
|
||||
return
|
||||
try:
|
||||
fee = long(local_withdraw_fee)
|
||||
topay = long(amount - 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))
|
||||
CheckDisableWithdraw()
|
||||
SendTo(nick,"An error has occured")
|
||||
return
|
||||
if not "result" in j:
|
||||
log_error('Withdraw: No result in transfer reply')
|
||||
CheckDisableWithdraw()
|
||||
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')
|
||||
CheckDisableWithdraw()
|
||||
SendTo(nick,"An error has occured")
|
||||
return
|
||||
tx_hash = result["tx_hash"]
|
||||
log_info('%s has withdrawn %s, tx hash %s' % (nick, amount, str(tx_hash)))
|
||||
SendTo(nick, "Tx sent: %s" % tx_hash)
|
||||
|
||||
try:
|
||||
redis_hincrby("balances",nick,-amount)
|
||||
except Exception, e:
|
||||
log_error('Withdraw: FAILED TO SUBTRACT BALANCE: exception: %s' % str(e))
|
||||
CheckDisableWithdraw()
|
||||
|
||||
|
||||
|
||||
RegisterCommand({
|
||||
'name': 'withdraw',
|
||||
'parms': '<address> [<amount>]',
|
||||
'function': Withdraw,
|
||||
'registered': True,
|
||||
'help': "withdraw part or all of your balance"
|
||||
})
|
||||
RegisterCommand({
|
||||
'name': 'enable_withdraw',
|
||||
'function': EnableWithdraw,
|
||||
'admin': True,
|
||||
'help': "Enable withdrawals"
|
||||
})
|
||||
RegisterCommand({
|
||||
'name': 'disable_withdraw',
|
||||
'function': DisableWithdraw,
|
||||
'admin': True,
|
||||
'help': "Disable withdrawals"
|
||||
})
|
49
tipbot/redisdb.py
Normal file
49
tipbot/redisdb.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot
|
||||
# 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 redis
|
||||
from tipbot.log import log_error, log_warn, log_info, log_log
|
||||
|
||||
redisdb = None
|
||||
|
||||
def connect_to_redis(host,port):
|
||||
log_info('Connecting to Redis at %s:%u' % (host, port))
|
||||
try:
|
||||
global redisdb
|
||||
redisdb = redis.Redis(host=host,port=port)
|
||||
return redisdb
|
||||
except Exception, e:
|
||||
log_error( 'Error initializing redis: %s' % str(e))
|
||||
exit()
|
||||
|
||||
def redis_pipeline():
|
||||
return redisdb.pipeline()
|
||||
|
||||
def redis_get(k):
|
||||
return redisdb.get(k)
|
||||
|
||||
def redis_set(k,v):
|
||||
return redisdb.set(k,v)
|
||||
|
||||
def redis_hget(t,k):
|
||||
return redisdb.hget(t,k)
|
||||
|
||||
def redis_hgetall(t):
|
||||
return redisdb.hgetall(t)
|
||||
|
||||
def redis_hset(t,k,v):
|
||||
return redisdb.hset(t,k,v)
|
||||
|
||||
def redis_hincrby(t,k,v):
|
||||
return redisdb.hincrby(t,k,v)
|
||||
|
||||
|
151
tipbot/utils.py
Normal file
151
tipbot/utils.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - utility functions
|
||||
# Copyright 2014 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 redis
|
||||
import hashlib
|
||||
import json
|
||||
import httplib
|
||||
import tipbot.config as config
|
||||
import tipbot.coinspecs as coinspecs
|
||||
from tipbot.log import log_error, log_warn, log_info, log_log
|
||||
from tipbot.irc import *
|
||||
from tipbot.redisdb import *
|
||||
from tipbot.command_manager import *
|
||||
|
||||
|
||||
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 IsParamPresent(parms,idx):
|
||||
return len(parms) > idx
|
||||
|
||||
def GetParam(parms,idx):
|
||||
if IsParamPresent(parms,idx):
|
||||
return parms[idx]
|
||||
return None
|
||||
|
||||
def GetPaymentID(nick):
|
||||
salt="2u3g55bkwrui32fi3g4bGR$j5g4ugnujb-"+coinspecs.name+"-";
|
||||
p = hashlib.sha256(salt+nick).hexdigest();
|
||||
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 GetNickFromPaymendID(p):
|
||||
nick = redis_hget("paymentid",p)
|
||||
log_log('PaymendID %s => %s' % (p, str(nick)))
|
||||
return nick
|
||||
|
||||
def AmountToString(amount):
|
||||
if amount == None:
|
||||
amount = 0
|
||||
lamount=long(amount)
|
||||
samount = None
|
||||
if lamount == 0:
|
||||
samount = "0 %s" % coinspecs.name
|
||||
else:
|
||||
for den in coinspecs.denominations:
|
||||
if lamount < den[0]:
|
||||
samount = "%.16g %s" % (float(lamount) / den[1], den[2])
|
||||
break
|
||||
if not samount:
|
||||
samount = "%.16g %s" % (float(lamount) / coinspecs.atomic_units, coinspecs.name)
|
||||
log_log("AmountToString: %s -> %s" % (str(amount),samount))
|
||||
return samount
|
||||
|
||||
def SendJSONRPCCommand(host,port,method,params):
|
||||
try:
|
||||
http = httplib.HTTPConnection(host,port)
|
||||
except Exception,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:
|
||||
log_error('SendJSONRPCCommand: Failed to encode JSON: %s' % str(e))
|
||||
http.close()
|
||||
raise
|
||||
log_log('SendJSONRPCCommand: Sending json as body: %s' % j)
|
||||
headers = None
|
||||
try:
|
||||
http.request("POST","/json_rpc",body=j)
|
||||
except Exception,e:
|
||||
log_error('SendJSONRPCCommand: Failed to post request: %s' % str(e))
|
||||
http.close()
|
||||
raise
|
||||
response = http.getresponse()
|
||||
log_log('SendJSONRPCCommand: Received reply status: %s' % response.status)
|
||||
if response.status != 200:
|
||||
log_error('SendJSONRPCCommand: Error, not 200: %s' % str(response.status))
|
||||
http.close()
|
||||
raise RuntimeError("Error "+response.status)
|
||||
s = response.read()
|
||||
log_log('SendJSONRPCCommand: Received reply: %s' % str(s))
|
||||
try:
|
||||
j = json.loads(s)
|
||||
except Exception,e:
|
||||
log_error('SendJSONRPCCommand: Failed to decode JSON: %s' % str(e))
|
||||
http.close()
|
||||
raise
|
||||
http.close()
|
||||
return j
|
||||
|
||||
def SendHTMLCommand(host,port,method):
|
||||
try:
|
||||
http = httplib.HTTPConnection(host,port)
|
||||
except Exception,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:
|
||||
log_error('SendHTMLCommand: Failed to post request: %s' % str(e))
|
||||
http.close()
|
||||
raise
|
||||
response = http.getresponse()
|
||||
log_log('SendHTMLCommand: Received reply status: %s' % response.status)
|
||||
if response.status != 200:
|
||||
log_error('SendHTMLCommand: Error, not 200: %s' % str(response.status))
|
||||
http.close()
|
||||
raise RuntimeError("Error "+response.status)
|
||||
s = response.read()
|
||||
log_log('SendHTMLCommand: Received reply: %s' % s)
|
||||
try:
|
||||
j = json.loads(s)
|
||||
except Exception,e:
|
||||
log_error('SendHTMLCommand: Failed to decode JSON: %s' % str(e))
|
||||
http.close()
|
||||
raise
|
||||
http.close()
|
||||
return j
|
||||
|
||||
def SendWalletJSONRPCCommand(method,params):
|
||||
return SendJSONRPCCommand(config.wallet_host,config.wallet_port,method,params)
|
||||
|
||||
def SendDaemonJSONRPCCommand(method,params):
|
||||
return SendJSONRPCCommand(config.daemon_host,config.daemon_port,method,params)
|
||||
|
||||
def SendDaemonHTMLCommand(method):
|
||||
return SendHTMLCommand(config.daemon_host,config.daemon_port,method)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - dashcoin setup
|
||||
# Copyright 2014 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.
|
||||
#
|
||||
|
||||
coin_name="Dashcoin"
|
||||
coin=1e8
|
||||
coin_denominations = []
|
||||
address_length = [96] # min/max size of addresses
|
||||
address_prefix = ['D'] # allowed prefixes of addresses
|
||||
min_withdrawal_fee = 0.01
|
||||
web_wallet_url = None
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - ducknote setup
|
||||
# Copyright 2014 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.
|
||||
#
|
||||
|
||||
coin_name="Darknote"
|
||||
coin=1e8
|
||||
coin_denominations = []
|
||||
address_length = [95, 98] # min/max size of addresses
|
||||
address_prefix = ['dd'] # allowed prefixes of addresses
|
||||
min_withdrawal_fee = 0.1
|
||||
web_wallet_url = None
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/python
|
||||
#
|
||||
# Cryptonote tipbot - monero setup
|
||||
# Copyright 2014 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.
|
||||
#
|
||||
|
||||
coin_name="Monero"
|
||||
coin=1e12
|
||||
coin_denominations = [[1000000, 1, "piconero"], [1000000000, 1e6, "micronero"], [1000000000000, 1e9, "millinero"]]
|
||||
address_length = [95, 95] # min/max size of addresses
|
||||
address_prefix = ['4', '9'] # allowed prefixes of addresses
|
||||
min_withdrawal_fee = 10000000000
|
||||
web_wallet_url = "https://mymonero.com/" # None is there's none
|
Loading…
Reference in a new issue