diff --git a/tipbot/coinspecs.py b/tipbot/coinspecs.py index 9d5170b..a6c0b76 100644 --- a/tipbot/coinspecs.py +++ b/tipbot/coinspecs.py @@ -15,7 +15,7 @@ coinspecs = { "symbol": "XMR", "atomic_units": 1e12, "denominations": [[1000000, 1, "piconero"], [1000000000, 1e6, "micronero"], [1000000000000, 1e9, "millinero"]], - "address_length": [95, 95], # min/max size of addresses + "address_length": [[95, 95], [106, 106]], # 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 diff --git a/tipbot/config.py.example b/tipbot/config.py.example index 5efbc8c..0d8cf2e 100644 --- a/tipbot/config.py.example +++ b/tipbot/config.py.example @@ -28,6 +28,7 @@ payment_confirmations = 6 tipbot_balance_cache_time = 35 # seconds site_game_salt = '' openalias_address = None +rpc_timeout = 180 admins = ["freenode:moneromooo", "freenode:moneromoo"] @@ -116,3 +117,4 @@ kitsune_max_loss = 35 # how much are we prepared to lose as a ratio of our current pot kitsune_max_loss_ratio = 0.1 +spammer_allowed = ['_Slack', 'i2p-relay'] diff --git a/tipbot/modules/announcements.py b/tipbot/modules/announcements.py index 5a24804..9d46dfc 100644 --- a/tipbot/modules/announcements.py +++ b/tipbot/modules/announcements.py @@ -29,7 +29,8 @@ def Announce(link,cmd): return nextid=redis_get('cryptokingdom:announcements:nextid') if nextid==None: - nextid=0 + nextid=1 + nextid=long(nextid) text = " ".join(cmd[1:]) redis_hset('cryptokingdom:announcements',nextid,'From %s: %s'%(link.user.nick,text)) nextid+=1 @@ -54,8 +55,8 @@ def Cancel(link,cmd): redis_hdel('cryptokingdom:announcements',which) def Help(link): - link.send(link,'Announce anything that you want others to know') - link.send(link,'Offers, auctions, other information') + link.send_private('Announce anything that you want others to know') + link.send_private('Offers, auctions, other information') diff --git a/tipbot/modules/irc.py b/tipbot/modules/irc.py index 4a45655..7ce4fe1 100644 --- a/tipbot/modules/irc.py +++ b/tipbot/modules/irc.py @@ -314,6 +314,8 @@ class IRCNetwork(Network): self.update_last_active_time(chan,GetNick(who)) # resplit to avoid splitting text that contains ':' text = data.split(' :',1)[1] + if self.on_event: + self.on_event('message',link=Link(self,User(self,GetNick(who),who),Group(self,chan)),message=text) exidx = text.find('!') if exidx != -1 and len(text)>exidx+1 and text[exidx+1] in string.ascii_letters and self.is_acceptable_command_prefix(text[:exidx]): cmd = text.split('!')[1] @@ -338,7 +340,7 @@ class IRCNetwork(Network): self.userstable[chan][nick] = None log_log("New list of users in %s: %s" % (chan, str(self.userstable[chan].keys()))) if self.on_event: - self.on_event('user-joined',link=Link(self,User(self,nick),Group(self,chan))) + self.on_event('user-joined',link=Link(self,User(self,nick,who),Group(self,chan))) elif action == 'PART': nick = GetNick(who) @@ -443,6 +445,8 @@ class IRCNetwork(Network): (r,w,x)=select.select([self.irc.fileno()],[],[],1) if self.irc.fileno() in r: newdata=self._irc_recv(4096,socket.MSG_DONTWAIT) + if len(newdata) == 0: + raise RuntimeError('0 bytes received, EOF') else: newdata = None if self.irc.fileno() in x: diff --git a/tipbot/modules/payment.py b/tipbot/modules/payment.py index f6d91e8..3e4c208 100644 --- a/tipbot/modules/payment.py +++ b/tipbot/modules/payment.py @@ -161,14 +161,18 @@ def UpdateCoin(data): def Deposit(link,cmd): Help(link) +def RandomPaymentID(link,cmd): + link.send_private(" New payment ID: %s" % GetRandomPaymentID(link)) + def Help(link): GetAccount(link.identity()) - link.send_private("You can send %s to your account:" % coinspecs.name); + link.send_private("You can send %s to your account using this address AND payment ID:" % coinspecs.name); address=GetTipbotAddress() or 'ERROR' link.send_private(" Address: %s" % address) if config.openalias_address != None: link.send_private(" (or %s when using OpenAlias)" % config.openalias_address) - link.send_private(" Payment ID: %s" % GetPaymentID(link)) + link.send_private(" Use your primary payment ID: %s" % GetPaymentID(link)) + link.send_private(" OR generate random payment ids at will with: !randompid") link.send_private("Incoming transactions are credited after %d confirmations" % config.payment_confirmations) RegisterModule({ @@ -182,4 +186,11 @@ RegisterCommand({ 'function': Deposit, 'help': "Show instructions about depositing %s" % coinspecs.name }) +RegisterCommand({ + 'module': __name__, + 'name': 'randompid', + 'function': RandomPaymentID, + 'registered': True, + 'help': "Generate a new random payment ID" +}) diff --git a/tipbot/modules/reddit.py b/tipbot/modules/reddit.py index 38c75c1..f9b7a75 100644 --- a/tipbot/modules/reddit.py +++ b/tipbot/modules/reddit.py @@ -15,6 +15,7 @@ import time import threading import re import praw +import logging import tipbot.config as config from tipbot.log import log_error, log_warn, log_info, log_log from tipbot.user import User @@ -44,7 +45,7 @@ class RedditNetwork(Network): try: cfg=config.network_config[self.name] self.login=cfg['login'] - password=GetPassword(self.name) + password=GetPassword(self.name+'/password') self.subreddits=cfg['subreddits'] user_agent=cfg['user_agent'] self.update_period=cfg['update_period'] @@ -52,9 +53,19 @@ class RedditNetwork(Network): self.keyword=cfg['keyword'] self.use_unread_api=cfg['use_unread_api'] self.cache_timeout=cfg['cache_timeout'] + client_id=GetPassword(self.name+'/client_id') + client_secret=GetPassword(self.name+'/client_secret') + username=GetPassword(self.name+'/username') - self.reddit=praw.Reddit(user_agent=user_agent,cache_timeout=self.cache_timeout) - self.reddit.login(self.login,password) + if False: + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + logger = logging.getLogger('prawcore') + logger.setLevel(logging.DEBUG) + logger.addHandler(handler) + + self.reddit=praw.Reddit(client_id=client_id,client_secret=client_secret,password=password,user_agent=user_agent,username=username) + log_info("Logged in reddit as " + str(self.reddit.user.me())) self.items_cache=dict() self.stop = False @@ -107,18 +118,23 @@ class RedditNetwork(Network): return if not hasattr(item.author,'name'): log_warn('author of %s has no name field, ignored' % str(item.id)) - try: - item.mark_as_read() - except Exception,e: - log_warning('Failed to mark %s as read: %s' % (item.id,str(e))) + if True: + try: + item.mark_read() + except Exception,e: + log_warn('Failed to mark %s as read: %s' % (item.id,str(e))) return author=self.canonicalize(item.author.name) - if author==self.canonicalize(self.login): + if author and author==self.canonicalize(self.login): return if item.id in self.last_seen_ids: - #log_log('Already seen %s %.1f hours ago by %s: %s (%s), skipping' % (item.id,age/3600,str(author),repr(title),repr(item.body))) + log_log('Already seen %s %.1f hours ago by %s: %s (%s), skipping' % (item.id,age/3600,str(author),repr(title),repr(item.body))) + try: + item.mark_read() + except Exception,e: + log_warn('Failed to mark %s as read: %s' % (item.id,str(e))) return age=time.time()-item.created_utc @@ -155,7 +171,7 @@ class RedditNetwork(Network): line=line.replace(self.keyword,'').strip() if self.on_command: try: - parent_item=self.reddit.get_info(thing_id=item.parent_id) + parent_item=next(self.reddit.info([item.parent_id])) if not hasattr(parent_item,'author'): raise RuntimeError('Parent item has no author') author=parent_item.author.name @@ -167,10 +183,11 @@ class RedditNetwork(Network): self.on_command(link,synthetic_cmd) except Exception,e: log_error('Failed to tip %s\'s parent: %s' % (item.id,str(e))) - try: - item.mark_as_read() - except Exception,e: - log_warning('Failed to mark %s as read: %s' % (item.id,str(e))) + if True: + try: + item.mark_read() + except Exception,e: + log_warn('Failed to mark %s as read: %s' % (item.id,str(e))) def _schedule_reply(self,item,recipient,text): log_log('scheduling reply to %s:%s: %s' % (item.id if item else '""',recipient or '""',text)) @@ -214,7 +231,10 @@ class RedditNetwork(Network): if fullname in self.items_cache: item=self.items_cache[fullname] if not item: - item=self.reddit.get_info(thing_id=fullname) + item = self.reddit.mesage(fullname) + if not item: + gen=self.reddit.info([fullname]) + item=next(gen, None) if not item: log_error('Failed to find item %s to post %s' % (fullname,text)) redis_lpop('reddit:replies') @@ -226,9 +246,6 @@ class RedditNetwork(Network): redis_lpop('reddit:replies') - except praw.errors.RateLimitExceeded,e: - log_info('Rate limited trying to send %s, will retry: %s' % (data,str(e))) - return False except Exception,e: log_error('Error sending %s, will retry: %s' % (data,str(e))) return False @@ -256,15 +273,15 @@ class RedditNetwork(Network): self._parse(message,not message.was_comment) else: - messages=self.reddit.get_inbox() - for message in messages: - if not message.was_comment: + for message in self.reddit.inbox.unread(limit=self.load_limit): + #if not message.was_comment: self._parse(message,True) - sr=self.reddit.get_subreddit("+".join(self.subreddits)) - comments=sr.get_comments(limit=self.load_limit) - for comment in comments: - self._parse(comment,False) + #print "Submissions from %s" % ("+".join(self.subreddits)) + #sr=self.reddit.subreddit("+".join(self.subreddits)) + #for s in sr.new(limit=self.load_limit): + # for comment in s.comments: + # self._parse(comment,False) while self._post_next_reply(): pass diff --git a/tipbot/modules/spammer.py b/tipbot/modules/spammer.py new file mode 100644 index 0000000..237f364 --- /dev/null +++ b/tipbot/modules/spammer.py @@ -0,0 +1,170 @@ +#!/bin/python +# +# Cryptonote tipbot - matylda 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 redis +import string +import re +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.redisdb import * +from tipbot.command_manager import * + +def BanUser(link): + log_info('Banning %s (%s)' % (link.user.nick, link.user.ident)) + if not link.group: + return + chan=link.group.name + log_info("chan: " + chan) + net=link.network + try: + cmd="MODE " + chan + " +b " + link.user.ident + net._irc_sendmsg(cmd) + cmd="KICK " + chan + " " + link.user.nick + net._irc_sendmsg(cmd) + except: + pass + +def MuteUser(link): + log_info('Muting %s (%s)' % (link.user.nick, link.user.ident)) + if not link.group: + return + chan=link.group.name + log_info("chan: " + chan) + net=link.network + try: + cmd="MODE " + chan + " +q " + link.user.ident + net._irc_sendmsg(cmd) + except: + pass + +def OnUserJoined(event,*args,**kwargs): + link=kwargs['link'] + + nick=link.user.nick.lower() + if nick=="lbft" or nick=="lbft_": + BanUser(link) + +triggers=[l.lower() for l in [ + "triple your btc", "pm me to begin", "hatt uu", + "accelerate the blockchain", "u stappid", "me a message to begin", + "the ops have confirmed", "expanding technology", "exploding technology", + "allah is doing", "pm me to get going", "defragment the blockchain to grow" +]] + +def OnMessage(event,*args,**kwargs): + line=kwargs['message'] + if not line: + return + link=kwargs['link'] + if IsAdmin(link): + return + if link.nick in config.allowed: + return + + line=re.sub(r'\x03[0-9]?[0-9]?','',line) + line=re.sub(r'\x0f','',line) + line=line.lower().strip() + + log_info("Testing: " + line) + for expr in triggers: + if re.match(".*"+expr+".*",line): + MuteUser(link) + return + +def AddTrigger(link,cmd): + triggers.append(" ".join(cmd[1:])) + +def ShowTriggers(link,cmd): + link.send(", ".join(triggers)) + +def Ban(link,cmd): + link.send("disabled") # need to ban by ident + return + + try: + who=cmd[1] + except Exception,e: + link.send("usage: ban ") + return + group=link.group + if not group: + link.send("Not in a channel") + return + l=Link(link.network,User(link.network,who),group) + BanUser(l) + +def Mute(link,cmd): + link.send("disabled") # need to mute by ident + return + + try: + who=cmd[1] + except Exception,e: + link.send("usage: mute ") + return + group=link.group + if not group: + link.send("Not in a channel") + return + l=Link(link.network,User(link.network,who),group) + MuteUser(l) + +def Help(link): + link.send_private('Ban assholes') + + +RegisterModule({ + 'name': __name__, + 'help': Help, +}) +RegisterEventHandler({ + 'module': __name__, + 'event': 'user-joined', + 'function': OnUserJoined, +}) +RegisterEventHandler({ + 'module': __name__, + 'event': 'message', + 'function': OnMessage, +}) +RegisterCommand({ + 'module': __name__, + 'name': 'add_trigger', + 'function': AddTrigger, + 'admin': True, + 'help': "add keyword trigger to spammer trap" +}) +RegisterCommand({ + 'module': __name__, + 'name': 'show_triggers', + 'function': ShowTriggers, + 'admin': True, + 'help': "list keyword triggers" +}) +RegisterCommand({ + 'module': __name__, + 'name': 'ban', + 'function': Ban, + 'admin': True, + 'help': "ban a user" +}) +RegisterCommand({ + 'module': __name__, + 'name': 'mute', + 'function': Mute, + 'admin': True, + 'help': "mute a user" +}) diff --git a/tipbot/user.py b/tipbot/user.py index 9f1881f..d846607 100644 --- a/tipbot/user.py +++ b/tipbot/user.py @@ -10,9 +10,10 @@ # class User: - def __init__(self,network,nick): + def __init__(self,network,nick,ident=None): self.network=network self.nick=nick + self.ident=ident def check_registered(self): pass diff --git a/tipbot/utils.py b/tipbot/utils.py index 2a6015b..30300a2 100644 --- a/tipbot/utils.py +++ b/tipbot/utils.py @@ -17,6 +17,8 @@ import time import threading import math import string +import random +from Crypto.Random.random import getrandbits from decimal import * import tipbot.config as config import tipbot.coinspecs as coinspecs @@ -55,8 +57,10 @@ def GetParam(parms,idx): return parms[idx] return None -def GetPaymentID(link): +def GetPaymentID(link,random=False): salt="2u3g55bkwrui32fi3g4bGR$j5g4ugnujb-"+coinspecs.name+"-"; + if random: + salt = salt + "-" + str(time.time()) + "-" + str(getrandbits(128)) p = hashlib.sha256(salt+link.identity()).hexdigest(); try: redis_hset("paymentid",p,link.identity()) @@ -64,6 +68,9 @@ def GetPaymentID(link): log_error('GetPaymentID: failed to set payment ID for %s to redis: %s' % (link.identity(),str(e))) return p +def GetRandomPaymentID(link): + return GetPaymentID(link, True) + def GetIdentityFromPaymentID(p): if not redis_hexists("paymentid",p): log_log('PaymentID %s not found' % p) @@ -76,8 +83,18 @@ def GetIdentityFromPaymentID(p): identity = "freenode:"+identity return identity +def IsAddressLengthValid(address): + if type(coinspecs.address_length[0]) == list: + for allist in coinspecs.address_length: + if len(address) >= allist[0] and len(address) <= allist[1]: + return True + else: + if len(address) >= coinspecs.address_length[0] and len(address) <= coinspecs.address_length[1]: + return True + return False + def IsValidAddress(address): - if len(address) < coinspecs.address_length[0] or len(address) > coinspecs.address_length[1]: + if not IsAddressLengthValid(address): return False for prefix in coinspecs.address_prefix: if address.startswith(prefix): @@ -198,7 +215,7 @@ def StringToUnits(s): def SendJSONRPCCommand(host,port,method,params): try: - http = httplib.HTTPConnection(host,port,timeout=20) + http = httplib.HTTPConnection(host,port,timeout=config.rpc_timeout) except Exception,e: log_error('SendJSONRPCCommand: Error connecting to %s:%u: %s' % (host, port, str(e))) raise @@ -235,7 +252,7 @@ def SendJSONRPCCommand(host,port,method,params): def SendHTMLCommand(host,port,method): try: - http = httplib.HTTPConnection(host,port,timeout=20) + http = httplib.HTTPConnection(host,port,timeout=config.rpc_timeout) except Exception,e: log_error('SendHTMLCommand: Error connecting to %s:%u: %s' % (host, port, str(e))) raise