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
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"
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue