#!/bin/python # # Cryptonote tipbot - tipping commands # Copyright 2014, 2015 moneromooo # # The Cryptonote tipbot is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as published # by the Free Software Foundation; either version 2, or (at your option) # any later version. # import sys import 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.user import User from tipbot.link import Link from tipbot.command_manager import * from tipbot.redisdb import * pending_confirmations=dict() def PerformTip(link,whoid,units): identity=link.identity() try: account = GetAccount(identity) who_account = GetAccount(whoid) balance = redis_hget("balances",account) if balance == None: balance = 0 balance=long(balance) if units > balance: link.send("You only have %s" % (AmountToString(balance))) return log_info('Tip: %s tipping %s %u units, with balance %u' % (identity, whoid, units, balance)) try: p = redis_pipeline() p.incrby("tips_total_count",1); p.incrby("tips_total_amount",units); p.hincrby("tips_count",identity,1); p.hincrby("tips_amount",identity,units); p.hincrby("balances",account,-units); p.hincrby("balances",who_account,units) p.execute() if units < coinspecs.atomic_units: link.send("%s has tipped %s %s (%.16g %s)" % (NickFromIdentity(identity), NickFromIdentity(whoid), AmountToString(units), float(units) / coinspecs.atomic_units, coinspecs.name)) else: link.send("%s has tipped %s %s" % (NickFromIdentity(identity), NickFromIdentity(whoid), AmountToString(units))) except Exception, e: log_error("Tip: Error updating redis: %s" % str(e)) link.send("An error occured") return except Exception, e: log_error('Tip: exception: %s' % str(e)) link.send("An error has occured") def Tip(link,cmd): identity=link.identity() try: who=cmd[1] units=StringToUnits(cmd[2]) except Exception,e: link.send("Usage: tip nick amount") return if units <= 0: link.send("Invalid amount") return whoid = IdentityFromString(link,who) log_info("Tip: %s wants to tip %s %s" % (identity, whoid, AmountToString(units))) if link.group: userlist=[user.identity() for user in link.network.get_users(link.group.name)] log_log('users: %s' % str(userlist)) if not whoid in userlist: link.send("%s is not in %s: if you really intend to tip %s, type !confirmtip before tipping again" % (who, link.group.name, who)) pending_confirmations[identity]={'who': whoid, 'units': units} return pending_confirmations.pop(identity,None) PerformTip(link,whoid,units) def ConfirmTip(link,cmd): identity=link.identity() if not identity in pending_confirmations: link.send("%s has no tip waiting confirmation" % NickFromIdentity(identity)) return whoid=pending_confirmations[identity]['who'] units=pending_confirmations[identity]['units'] pending_confirmations.pop(identity,None) PerformTip(link,whoid,units) def Rain(link,cmd): identity=link.identity() group=link.group if not group: link.send("Raining can only be done in a group") return try: amount=float(cmd[1]) units = StringToUnits(cmd[1]) except Exception,e: link.send("Usage: rain amount [users]") return users = GetParam(cmd,2) if users: try: users=long(users) except Exception,e: link.send("Usage: rain amount [users]") return if amount <= 0: link.send("Usage: rain amount [users]") return if users != None and users <= 0: link.send("Usage: rain amount [users]") return try: account = GetAccount(identity) balance = redis_hget("balances",account) if balance == None: balance = 0 balance=long(balance) if units > balance: link.send("You only have %s" % (AmountToString(balance))) return userlist=link.network.get_users(group.name) log_log("users in %s: %s" % (group.name,str([user.identity() for user in userlist]))) userlist.remove(link) for n in config.no_rain_to_nicks: i=IdentityFromString(link,n) l=Link(link.network,User(link.network,NickFromIdentity(i)),group) if l in userlist: userlist.remove(l) if users == None or users > len(userlist): users = len(userlist) everyone = True else: everyone = False if users == 0: link.send("Nobody eligible for rain") return if units < users: link.send("This would mean not even an atomic unit per nick") return log_info("%s wants to rain %s on %s users in %s" % (identity, AmountToString(units), users, group.name)) log_log("eligible users in %s: %s" % (group.name, str([user.identity() for user in userlist]))) random.shuffle(userlist) userlist = userlist[0:users] log_log("selected users in %s: %s" % (group.name, [user.identity() for user in userlist])) user_units = long(units / users) enumerate_users = False if everyone: msg = "%s rained %s on everyone in the channel" % (link.user.nick, AmountToString(user_units)) elif len(userlist) > 16: msg = "%s rained %s on %d nicks" % (link.user.nick, AmountToString(user_units), len(userlist)) else: msg = "%s rained %s on:" % (link.user.nick, AmountToString(user_units)) enumerate_users = True pipe = redis_pipeline() pipe.hincrby("balances",account,-units) pipe.incrby("rain_total_count",1) pipe.incrby("rain_total_amount",units) pipe.hincrby("rain_count",identity,1) pipe.hincrby("rain_amount",identity,units) for user in userlist: a = GetAccount(user) pipe.hincrby("balances",a,user_units) if enumerate_users: msg = msg + " " + NickFromIdentity(user.identity()) pipe.execute() link.send("%s" % msg) except Exception,e: log_error('Rain: exception: %s' % str(e)) link.send("An error has occured") return def RainActive(link,cmd): identity=link.identity() amount=GetParam(cmd,1) hours=GetParam(cmd,2) minfrac=GetParam(cmd,3) group=link.group if not group: link.send("Raining can only be done in a channel") return if not amount or not hours: link.send("usage: !rainactive []") return try: units=StringToUnits(amount) amount=float(amount) if amount <= 0: raise RuntimeError("") except Exception,e: link.send("Invalid amount") return try: hours=float(hours) if hours <= 0: raise RuntimeError("") seconds = hours * 3600 except Exception,e: link.send("Invalid hours") return if minfrac: try: minfrac=float(minfrac) if minfrac < 0 or minfrac > 1: raise RuntimeError("") except Exception,e: link.send("minfrac must be a number between 0 and 1") return else: minfrac = 0 try: account = GetAccount(link) balance = redis_hget("balances",account) if balance == None: balance = 0 balance=long(balance) if units > balance: link.send("You only have %s" % (AmountToString(balance))) return now = time.time() userlist = link.network.get_active_users(seconds,group.name) log_log('userlist: %s' % str([user.identity() for user in userlist])) userlist.remove(link) for n in config.no_rain_to_nicks: i=IdentityFromString(link,n) l=Link(link.network,User(link.network,NickFromIdentity(i)),group) if l in userlist: userlist.remove(l) weights=[] weight=0 log_log('userlist to loop: %s' % str([user.identity() for user in userlist])) for n in userlist: log_log('user to check: %s' % n.user.nick) t = link.network.get_last_active_time(n.user.nick,group.name) if t == None: continue dt = now - t if dt <= seconds: w = (1 * (seconds - dt) + minfrac * dt) / (seconds) weights.append((n, w)) weight += w if len(weights) == 0: link.send("Nobody eligible for rain") return pipe = redis_pipeline() pipe.hincrby("balances",account,-units) pipe.incrby("arain_total_count",1); pipe.incrby("arain_total_amount",units); pipe.hincrby("arain_count",identity,1); pipe.hincrby("arain_amount",identity,units); rained_units = 0 nnicks = 0 minu=None maxu=None for n,w in weights: user_units = long(units * w / weight) if user_units <= 0: continue act = now - link.network.get_last_active_time(n.user.nick,link.group.name) log_info("%s rained %s on %s (last active %f hours ago)" % (identity, AmountToString(user_units),n.identity(),act/3600)) a=GetAccount(n) pipe.hincrby("balances",a,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: link.send("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 %s" % (identity, AmountToString(minu), AmountToString(maxu), AmountToString(units), AmountToString(rained_units), nnicks, TimeToString(seconds))) link.send("%s rained %s - %s on the %d nicks active in the last %s" % (link.user.nick, AmountToString(minu), AmountToString(maxu), nnicks, TimeToString(seconds))) except Exception,e: log_error('Rain: exception: %s' % str(e)) link.send("An error has occured") return def Help(link): link.send_private('You can tip other people, or rain %s on them' % coinspecs.name) link.send_private('!tip tips a single person, while !rain shares equally between people in the channel') link.send_private('!rainactive tips all within the last N hours, with more recently active people') link.send_private('getting a larger share.') RegisterModule({ 'name': __name__, 'help': Help, }) RegisterCommand({ 'module': __name__, 'name': 'tip', 'parms': ' ', 'function': Tip, 'registered': True, 'help': "tip another user" }) RegisterCommand({ 'module': __name__, 'name': 'confirmtip', 'function': ConfirmTip, 'registered': True, 'help': "confirm a tip to another user who is not in the channel" }) RegisterCommand({ 'module': __name__, 'name': 'rain', 'parms': ' []', 'function': Rain, 'registered': True, 'help': "rain some coins on everyone (or just a few)" }) RegisterCommand({ 'module': __name__, 'name': 'rainactive', 'parms': ' []', 'function': RainActive, 'registered': True, 'help': "rain some coins on whoever was active recently" })