def add_history_record( username=None, action="unknown", address=None, comment_or_message=None, recipient_username=None, recipient_address=None, amount=None, hash=None, comment_id=None, notes=None, reddit_time=None, comment_text=None, ): if action is None: action = "unknown" if reddit_time is None: reddit_time = datetime.utcnow() history = History(username=username, action=action, address=address, comment_or_message=comment_or_message, recipient_username=recipient_username, recipient_address=recipient_address, amount=amount, hash=hash, comment_id=comment_id, notes=notes, reddit_time=reddit_time, comment_text=comment_text) if history.save() < 1: LOGGER.error(f"Failed saving history item {username}") return history.id
def work_generate(hash, dpow=False): """ Generates PoW for a hash. If dpow is set to false, does it on the local node. If dpow is set to true, will attempt to use remote dpow server. If there is no response in one second, the function will recursively call itself with dpow=false and do it locally. :param hash: :param dpow: :return: dict with 'work' as a key """ # if dpow is globaly disable, disable here if not USE_DPOW: dpow = False if dpow: # API call try: # api token will be in a separate text file data = {"api_key": DPOW_TOKEN, "user": DPOW_USERNAME, "hash": hash} results = requests.post(DPOW_ENDPOINT, json.dumps(data), timeout=10) results = json.loads(results.text) except requests.exceptions.Timeout: LOGGER.info("Falling back to local POW...") return work_generate(hash) return results else: data = {"action": "work_generate", "hash": hash} results = perform_curl(data) return results
def message_in_database(message): query = History.select().where(History.comment_id == message.name) results = [r for r in query] if len(results) > 0: LOGGER.info("Found previous messages for %s: " % message.name) return True return False
def message_in_database(message): sql = "SELECT * FROM history WHERE comment_id = %s" val = (message.name,) MYCURSOR.execute(sql, val) results = MYCURSOR.fetchall() if len(results) > 0: LOGGER.info("Found previous messages for %s: " % message.name) for result in results: LOGGER.info(result) return True return False
def add_new_account(username): address, pk = generate_account() if address is None: LOGGER.error("Failed to create account!") return None acct = Account(username=username, private_key=pk, address=address, silence=False, active=False, opt_in=True) acct.save(force_insert=True) return { "username": username, "address": address, "private_key": pk, "silence": False, "balance": 0, "account_exists": True, }
def pull_history(u, n): """ Pulls n number of records for username u :param u: :param n: :return: """ sql = "SELECT id, username, sql_time, action, amount, comment_id, notes, recipient_username, recipient_address, return_status, comment_text, subreddit FROM history WHERE username=%s ORDER BY id DESC limit %s" val = (u, n) if u is None: sql = "SELECT id, username, sql_time, action, amount, comment_id, notes, recipient_username, recipient_address, return_status, comment_text, subreddit FROM history ORDER BY id DESC limit %s" val = (n,) MYCURSOR.execute(sql, val) results = MYCURSOR.fetchall() if len(results) == 0: LOGGER.info("Username %s not found." % u) MYDB.commit() LOGGER.info( "Printing results: Username, Datetime, action, amount, comment_id, notes_recipient_username, recipient_address, subreddit" ) for result in results: LOGGER.info(result)
def parse_action(action_item): if action_item is not None: parsed_text = parse_text(str(action_item.body)) else: return None if message_in_database(action_item): return "replay" elif not allowed_request(action_item.author, 30, 5): return "ignore" # check if it's a non-username post and if it has a tip or donate command elif action_item.name.startswith("t1_") and bool( {parsed_text[0], parsed_text[-2], parsed_text[-3]} & ( set(TIP_COMMANDS + DONATE_COMMANDS).union( {"/u/%s" % TIP_BOT_USERNAME, "u/%s" % TIP_BOT_USERNAME} ) ) ): LOGGER.info(f"Comment: {action_item.author} - " f"{action_item.body[:20]}") return "comment" # otherwise, lets parse the message. t4 means either a message or username mention elif action_item.name.startswith("t4_"): # check if it is a message from the bot. if action_item.author == TIP_BOT_USERNAME: # check if its a send, otherwise ignore if action_item.body.startswith("send 0.001 "): LOGGER.info( f"Faucet Tip: {action_item.author} - {action_item.body[:20]}" ) return "faucet_tip" else: return "ignore" # otherwise, it's a normal message else: LOGGER.info(f"Comment: {action_item.author} - " f"{action_item.body[:20]}") return "message" return None
def send_from_comment(message): """ Error codes: Success 10 - sent to existing user 20 - sent to new user 30 - sent to address 40 - donated to nanocenter project Tip not sent 100 - sender account does not exist 110 - Amount and/or recipient not specified 120 - could not parse send amount 130 - below program minimum 140 - currency code issue 150 - below 1 nano for untracked sub 160 - insufficient funds 170 - invalid address / recipient 180 - below recipient minimum 200 - No Nanocenter Project specified 210 - Nanocenter Project does not exist Extracts send command information from a PM command :param message: :return: response string """ parsed_text = parse_text(str(message.body)) response = {"username": str(message.author)} message_time = datetime.datetime.utcfromtimestamp( message.created_utc) # time the reddit message was created entry_id = add_history_record( username=response["username"], action="send", comment_or_message="comment", comment_id=message.name, reddit_time=message_time.strftime("%Y-%m-%d %H:%M:%S"), comment_text=str(message.body)[:255], ) # check if it's a donate command at the end if parsed_text[-3] in DONATE_COMMANDS: parsed_text = parsed_text[-3:] # don't do anything if the first word is a tip command or username elif (parsed_text[0] in [ f"/u/{TIP_BOT_USERNAME}", f"u/{TIP_BOT_USERNAME}" ]) or (parsed_text[0] in TIP_COMMANDS): pass # if the second to last is a username or tip command, redifine parsed text elif (parsed_text[-2] in [ f"/u/{TIP_BOT_USERNAME}", f"u/{TIP_BOT_USERNAME}" ]) or (parsed_text[-2] in TIP_COMMANDS): parsed_text = parsed_text[-2:] # before we can do anything, check the subreddit status for generating the response # check if amount is above subreddit minimum. response["subreddit"] = str(message.subreddit).lower() sql = "SELECT status FROM subreddits WHERE subreddit=%s" val = (response["subreddit"], ) results = tipper_functions.query_sql(sql, val) if len(results) == 0: response["subreddit_minimum"] = 1 results = [["untracked"]] elif results[0][0] in ["full", "friendly", "minimal", "silent"]: response["subreddit_minimum"] = 0 else: response["subreddit_minimum"] = 1 response["subreddit_status"] = results[0][0] # check that it wasn't a mistyped currency code or something if parsed_text[2] in EXCLUDED_REDDITORS: response["status"] = 140 return response if parsed_text[0] in TIP_COMMANDS and len(parsed_text) <= 1: update_history_notes(entry_id, "no recipient or amount specified") response["status"] = 110 return response if parsed_text[0] in DONATE_COMMANDS and len(parsed_text) <= 2: response["status"] = 110 update_history_notes(entry_id, "no recipient or amount specified") return response # pull sender account info sender_info = tipper_functions.account_info(response["username"]) if not sender_info: update_history_notes(entry_id, "user does not exist") response["status"] = 100 return response # parse the amount try: response["amount"] = parse_raw_amount(parsed_text, response["username"]) except TipError as err: response["status"] = 120 response["amount"] = parsed_text[1] update_history_notes(entry_id, err.sql_text) return response # check if it's above the program minimum if response["amount"] < nano_to_raw(PROGRAM_MINIMUM): update_history_notes(entry_id, "amount below program limit") response["status"] = 130 return response # check the user's balance if response["amount"] > sender_info["balance"]: update_history_notes(entry_id, "insufficient funds") response["status"] = 160 return response if response["amount"] < nano_to_raw(response["subreddit_minimum"]): update_history_notes(entry_id, "amount below subreddit minimum") response["status"] = 150 return response # if it's a normal send, pull the account author # we will distinguish users from donations by the presence of a private key if parsed_text[0] in (TIP_COMMANDS + [f"/u/{TIP_BOT_USERNAME}", f"u/{TIP_BOT_USERNAME}"]): response["status"] = 10 response["recipient"] = str(message.parent().author) recipient_info = tipper_functions.account_info(response["recipient"]) if not recipient_info: response["status"] = 20 recipient_info = tipper_functions.add_new_account( response["recipient"]) elif recipient_info["silence"]: response["status"] = 11 elif not recipient_info["opt_in"]: response["status"] = 190 return response elif parsed_text[0] in DONATE_COMMANDS: response["recipient"] = parsed_text[2] results = tipper_functions.query_sql( "FROM projects SELECT address WHERE project = %s", (parsed_text[2], )) if len(results) <= 0: response["status"] = 210 return response recipient_info = { "username": parsed_text[2], "address": results[0][0], "minimum": -1, } response["status"] = 40 else: response["status"] = 999 return response # check the send amount is above the user minimum, if a username is provided # if it was just an address, this would be -1 if response["amount"] < recipient_info["minimum"]: update_history_notes(entry_id, "below user minimum") response["status"] = 180 response["minimum"] = recipient_info["minimum"] return response # send the nanos!! response["hash"] = send( sender_info["address"], sender_info["private_key"], response["amount"], recipient_info["address"], )["hash"] # Update the sql and send the PMs sql = ( "UPDATE history SET notes = %s, address = %s, username = %s, recipient_username = %s, " "recipient_address = %s, amount = %s, hash = %s, return_status = %s WHERE id = %s" ) val = ( "sent to user", sender_info["address"], sender_info["username"], recipient_info["username"], recipient_info["address"], str(response["amount"]), response["hash"], "cleared", entry_id, ) tipper_functions.exec_sql(sql, val) LOGGER.info( f"Sending Nano: {sender_info['address']} {sender_info['private_key']} {response['amount']} {recipient_info['address']} {recipient_info['username']}" ) # Update the sql and send the PMs if needed # if there is no private key, it's a donation. No PMs to send if "private_key" not in recipient_info.keys(): sql = "UPDATE history SET notes = %s, address = %s, username = %s, recipient_address = %s, amount = %s WHERE id = %s" val = ( "sent to nanocenter address", sender_info["address"], sender_info["username"], recipient_info["address"], str(response["amount"]), entry_id, ) tipper_functions.exec_sql(sql, val) response["status"] = 40 return response # update the sql database and send sql = ( "UPDATE history SET notes = %s, address = %s, username = %s, recipient_username = %s, " "recipient_address = %s, amount = %s, return_status = %s WHERE id = %s" ) val = ( "sent to user", sender_info["address"], sender_info["username"], recipient_info["username"], recipient_info["address"], str(response["amount"]), "cleared", entry_id, ) tipper_functions.exec_sql(sql, val) if response["status"] == 20: subject = "Congrats on receiving your first Nano Tip!" message_text = (text.WELCOME_TIP % ( response["amount"] / 10**30, recipient_info["address"], recipient_info["address"], ) + text.COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response else: if not recipient_info["silence"]: receiving_new_balance = check_balance(recipient_info["address"]) subject = "You just received a new Nano tip!" message_text = (text.NEW_TIP % ( response["amount"] / 10**30, recipient_info["address"], receiving_new_balance[0] / 10**30, receiving_new_balance[1] / 10**30, response["hash"], ) + text.COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response
def return_transactions(): LOGGER.info("Running inactive script") myresults = query_sql("SELECT username FROM accounts WHERE active IS NOT TRUE") inactivated_accounts = {item[0] for item in myresults} results = query_sql( "SELECT recipient_username FROM history WHERE action = 'send' " "AND hash IS NOT NULL " "AND `sql_time` <= SUBDATE( CURRENT_DATE, INTERVAL 31 DAY) " "AND (" "return_status = 'cleared' " "OR return_status = 'warned'" ")" ) tipped_accounts = {item[0] for item in results} tipped_inactivated_accounts = inactivated_accounts.intersection(tipped_accounts) LOGGER.info(f"Accounts on warning: {sorted(tipped_inactivated_accounts)}") returns = {} # scrolls through our inactive members and check if they have unclaimed tips for i, recipient in enumerate(tipped_inactivated_accounts): # send warning messages on day 31 sql = ( "SELECT id, username, amount FROM history WHERE action = 'send' " "AND hash IS NOT NULL " "AND recipient_username = %s " "AND `sql_time` <= SUBDATE( CURRENT_DATE, INTERVAL 31 DAY) " "AND return_status = 'cleared'" ) txns = query_sql(sql, (recipient,)) if len(txns) >= 1: LOGGER.info(f"Warning Message to {recipient}") send_pm(recipient, SUBJECTS["RETURN_WARNING"], RETURN_WARNING + HELP) for txn in txns: sql = "UPDATE history SET return_status = 'warned' WHERE id = %s" val = (txn[0],) exec_sql(sql, val) # return transactions over 35 days old sql = ( "SELECT id, username, amount FROM history WHERE action = 'send' " "AND hash IS NOT NULL " "AND recipient_username = %s " "AND `sql_time` <= SUBDATE( CURRENT_DATE, INTERVAL 35 DAY) " "AND return_status = 'warned'" ) val = (recipient,) txns = query_sql(sql, val) if len(txns) >= 1: sql = "SELECT address, private_key FROM accounts WHERE username = %s" inactive_results = query_sql(sql, (recipient,)) address = inactive_results[0][0] private_key = inactive_results[0][1] for txn in txns: # set the pre-update message to 'return failed'. This will be changed # to 'returned' upon success sql = "UPDATE history SET return_status = 'return failed' WHERE id = %s" val = (txn[0],) exec_sql(sql, val) # get the transaction information and find out to whom we are returning # the tip sql = "SELECT address, percentage FROM accounts WHERE username = %s" val = (txn[1],) returned_results = query_sql(sql, val) recipient_address = returned_results[0][0] percentage = returned_results[0][1] percentage = float(percentage) / 100 # send it back donation_amount = from_raw(int(txn[2])) donation_amount = donation_amount * percentage donation_amount = to_raw(donation_amount) return_amount = int(txn[2]) - donation_amount if (return_amount > 0) and (return_amount <= int(txn[2])): hash = send(address, private_key, return_amount, recipient_address)[ "hash" ] add_history_record( action="return", hash=hash, amount=return_amount, notes="Returned transaction from history record %s" % txn[0], ) if (donation_amount > 0) and (donation_amount <= int(txn[2])): hash2 = send( address, private_key, donation_amount, TIPBOT_DONATION_ADDRESS, )["hash"] add_history_record( action="donate", hash=hash2, amount=donation_amount, notes="Donation from returned tip %s" % txn[0], ) # update database if everything goes through sql = "UPDATE history SET return_status = 'returned' WHERE id = %s" val = (txn[0],) exec_sql(sql, val) # add transactions to the messaging queue to build a single message message_recipient = txn[1] if message_recipient not in returns.keys(): returns[message_recipient] = { "percent": round(percentage * 100, 2), "transactions": [], } returns[message_recipient]["transactions"].append( [ recipient, from_raw(int(txn[2])), from_raw(return_amount), from_raw(donation_amount), ] ) # send out our return messages for user in returns: message = text.make_return_message(returns[user]) send_pm(user, SUBJECTS["RETURN_MESSAGE"], message) LOGGER.info("Inactivated script complete.")
def handle_message(message): response = "not activated" parsed_text = parse_text(str(message.body)) command = parsed_text[0].lower() # only activate if it's not an opt-out command if command != "opt-out": activate(message.author) # normal commands if command in ["help", "!help"]: LOGGER.info("Helping") subject = text.SUBJECTS["help"] response = handle_help(message) elif command in ["balance", "address"]: LOGGER.info("balance") subject = text.SUBJECTS["balance"] response = handle_balance(message) elif command in ["create", "register"]: LOGGER.info("Creating") subject = text.SUBJECTS["create"] response = handle_create(message) elif command in ["send", "withdraw"]: subject = text.SUBJECTS["send"] LOGGER.info("send via PM") response = handle_send(message) response = text.make_response_text(message, response) elif command == "history": LOGGER.info("history") subject = text.SUBJECTS["history"] response = handle_history(message) elif command == "silence": LOGGER.info("silencing") subject = text.SUBJECTS["silence"] response = handle_silence(message) elif command == "subreddit": LOGGER.info("subredditing") subject = text.SUBJECTS["subreddit"] response = handle_subreddit(message) elif command == "opt-out": LOGGER.info("opting out") response = handle_opt_out(message) subject = text.SUBJECTS["opt-out"] elif command == "opt-in": LOGGER.info("opting in") subject = text.SUBJECTS["opt-in"] response = handle_opt_in(message) # a few administrative tasks elif command in ["restart", "stop", "disable", "deactivate"]: if str(message.author).lower() in [ TIPBOT_OWNER, ]: # "joohansson"]: add_history_record( username=str(message.author), action="restart", comment_text=str(message.body)[:255], comment_or_message="message", comment_id=message.name, ) sys.exit() else: add_history_record( username=str(message.author), comment_text=str(message.body)[:255], comment_or_message="message", comment_id=message.name, ) return None message_recipient = str(message.author) message_text = response + COMMENT_FOOTER send_pm(message_recipient, subject, message_text, bypass_opt_out=True)
from time import sleep from shared import MYDB, MYCURSOR, REDDIT, LOGGER while True: sql = "SELECT * FROM messages" MYCURSOR.execute(sql) results = MYCURSOR.fetchall() MYDB.commit() for result in results: LOGGER.info("%s %s %s %s" % (result[1], result[2], result[4], repr(result[3])[:50])) try: # find the message to reply to it. if result[4] is not None: msg = REDDIT.inbox.message(result[4].replace("t4_", "")) msg.reply(str(result[3])) # if it was a comment, create a new message else: REDDIT.redditor(str(result[1])).message( str(result[2]), str(result[3])) except: pass sql = "DELETE FROM messages WHERE id = %s" val = (result[0], ) MYCURSOR.execute(sql, val) MYDB.commit() sleep(6)
def list_users(u): sql = "SELECT username FROM accounts" MYCURSOR.execute(sql) results = MYCURSOR.fetchall() for result in results: LOGGER.info(result)
def handle_send(message): """ Extracts send command information from a PM command :param message: :return: """ parsed_text = parse_text(str(message.body)) username = str(message.author) message_time = datetime.utcfromtimestamp( message.created_utc) # time the reddit message was created entry_id = add_history_record( username=username, action="send", comment_or_message="message", comment_id=message.name, reddit_time=message_time, comment_text=str(message.body)[:255], ) response = {"username": username} # check that there are enough fields (i.e. a username) if len(parsed_text) <= 2: update_history_notes(entry_id, "no recipient or amount specified") response["status"] = 110 return response # pull sender account info sender_info = tipper_functions.account_info(response["username"]) if not sender_info: update_history_notes(entry_id, "user does not exist") response["status"] = 100 return response # parse the amount try: response["amount"] = parse_raw_amount(parsed_text, response["username"]) except TipError as err: response["status"] = 120 response["amount"] = parsed_text[1] update_history_notes(entry_id, err.sql_text) return response # check if it's above the program minimum if response["amount"] < to_raw(PROGRAM_MINIMUM): update_history_notes(entry_id, "amount below program limit") response["status"] = 130 return response # check the user's balance if response["amount"] > sender_info["balance"]: update_history_notes(entry_id, "insufficient funds") response["status"] = 160 return response recipient_text = parsed_text[2] # catch invalid redditor AND address try: recipient_info = parse_recipient_username(recipient_text) except TipError as err: update_history_notes(entry_id, err.sql_text) response["recipient"] = recipient_text response["status"] = 170 return response # if we have a username, pull their info if "username" in recipient_info.keys(): response["recipient"] = recipient_info["username"] recipient_name = recipient_info["username"] recipient_info = tipper_functions.account_info(recipient_name) response["status"] = 10 if recipient_info is None: recipient_info = tipper_functions.add_new_account( response["recipient"]) if recipient_info is None: return text.TIP_CREATE_ACCT_ERROR response["status"] = 20 elif not recipient_info["opt_in"]: response["status"] = 190 return response # check if it's an address else: # otherwise, just use the address. Everything is None except address response["recipient"] = recipient_info["address"] response["status"] = 30 if sender_info["address"] == recipient_info["address"]: # Don't allow sends to yourself response["status"] = 200 return response response["hash"] = send( sender_info["address"], response["amount"], recipient_info["address"], )["block"] # if it was an address, just send to the address if "username" not in recipient_info.keys(): History.update( notes="send to address", address=sender_info["address"], username=sender_info["username"], recipient_username=None, recipient_address=recipient_info["address"], amount=str(response["amount"]), return_status="cleared").where(History.id == entry_id).execute() LOGGER.info( f"Sending Banano: {sender_info['address']} {sender_info['private_key']} {response['amount']} {recipient_info['address']}" ) return response # Update the sql and send the PMs History.update( notes="send to address", address=sender_info["address"], username=sender_info["username"], recipient_username=recipient_info["username"], recipient_address=recipient_info["address"], amount=str(response["amount"]), return_status="cleared").where(History.id == entry_id).execute() LOGGER.info( f"Sending Banano: {sender_info['address']} {sender_info['private_key']} {response['amount']} {recipient_info['address']} {recipient_info['username']}" ) if response["status"] == 20: subject = text.SUBJECTS["first_tip"] message_text = (WELCOME_TIP % ( NumberUtil.format_float(from_raw(response["amount"])), recipient_info["address"], recipient_info["address"], ) + COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response else: if not recipient_info["silence"]: receiving_new_balance = check_balance(recipient_info["address"]) subject = text.SUBJECTS["new_tip"] message_text = (NEW_TIP % ( NumberUtil.format_float(from_raw(response["amount"])), recipient_info["address"], from_raw(receiving_new_balance), response["hash"], ) + COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response
def handle_message(message): response = "not activated" parsed_text = parse_text(str(message.body)) command = parsed_text[0].lower() # only activate if it's not an opt-out command if command != "opt-out": activate(message.author) # normal commands if command in ["help", "!help"]: LOGGER.info("Helping") subject = "Nano Tipper - Help" response = handle_help(message) elif command in ["balance", "address"]: LOGGER.info("balance") subject = "Nano Tipper - Account Balance" response = handle_balance(message) elif command == "minimum": LOGGER.info("Setting Minimum") subject = "Nano Tipper - Tip Minimum" response = handle_minimum(message) elif command in ["percentage", "percent"]: LOGGER.info("Setting Percentage") subject = "Nano Tipper - Returned Tip Percentage for Donation" response = handle_percentage(message) elif command in ["create", "register"]: LOGGER.info("Creating") subject = "Nano Tipper - Create" response = handle_create(message) elif command in ["send", "withdraw"]: subject = "Nano Tipper - Send" LOGGER.info("send via PM") response = handle_send(message) response = text.make_response_text(message, response) elif command == "history": LOGGER.info("history") subject = "Nano Tipper - History" response = handle_history(message) elif command == "silence": LOGGER.info("silencing") subject = "Nano Tipper - Silence" response = handle_silence(message) elif command == "subreddit": LOGGER.info("subredditing") subject = "Nano Tipper - Subreddit" response = handle_subreddit(message) elif command == "opt-out": LOGGER.info("opting out") response = handle_opt_out(message) subject = "Nano Tipper - Opt Out" elif command == "opt-in": LOGGER.info("opting in") subject = "Nano Tipper - Opt In" response = handle_opt_in(message) # nanocenter donation commands elif command in ("project", "projects"): if (str(message.author).lower() in DONATION_ADMINS + TIPBOT_OWNER) and len(parsed_text) > 2: sql = "INSERT INTO projects (project, address) VALUES(%s, %s) ON DUPLICATE KEY UPDATE address=%s" val = (parsed_text[1], parsed_text[2], parsed_text[2]) MYCURSOR.execute(sql, val) MYDB.commit() add_history_record( username=str(message.author), action="project", comment_text=str(message.body)[:255], comment_or_message="message", comment_id=message.name, ) response = "Current NanoCenter Donation Projects: \n\n" subject = "Nanocenter Projects" sql = "SELECT project, address FROM projects" MYCURSOR.execute(sql) results = MYCURSOR.fetchall() for result in results: response += "%s %s \n" % (result[0], result[1]) elif command == "delete_project": if ((str(message.author) == TIPBOT_OWNER) or (str(message.author).lower() == "rockmsockmjesus")) and len(parsed_text) > 1: sql = "DELETE FROM projects WHERE project=%s" val = (parsed_text[1], ) MYCURSOR.execute(sql, val) MYDB.commit() response = "Current NanoCenter Donation Projects: \n\n" subject = "Nanocenter Projects" sql = "SELECT project, address FROM projects" MYCURSOR.execute(sql) results = MYCURSOR.fetchall() for result in results: response += "%s %s \n" % (result[0], result[1]) # a few administrative tasks elif command in ["restart", "stop", "disable", "deactivate"]: if str(message.author).lower() in [ TIPBOT_OWNER, "rockmsockmjesus", ]: # "joohansson"]: add_history_record( username=str(message.author), action="restart", comment_text=str(message.body)[:255], comment_or_message="message", comment_id=message.name, ) sys.exit() elif command == "test_welcome_tipped": subject = "Nano Tipper - Welcome By Tip" response = WELCOME_TIP % ( 0.01, "xrb_3jy9954gncxbhuieujc3pg5t1h36e7tyqfapw1y6zukn9y1g6dj5xr7r6pij", "xrb_3jy9954gncxbhuieujc3pg5t1h36e7tyqfapw1y6zukn9y1g6dj5xr7r6pij", ) elif command == "test_welcome_create": subject = "Nano Tipper - Create" response = WELCOME_CREATE % ( "xrb_3jy9954gncxbhuieujc3pg5t1h36e7tyqfapw1y6zukn9y1g6dj5xr7r6pij", "xrb_3jy9954gncxbhuieujc3pg5t1h36e7tyqfapw1y6zukn9y1g6dj5xr7r6pij", ) else: add_history_record( username=str(message.author), comment_text=str(message.body)[:255], comment_or_message="message", comment_id=message.name, ) return None message_recipient = str(message.author) message_text = response + COMMENT_FOOTER send_pm(message_recipient, subject, message_text, bypass_opt_out=True)
import time from time import sleep import shared from shared import REDDIT, PROGRAM_MINIMUM, SUBREDDITS, to_raw, LOGGER from message_functions import handle_message from tipper_functions import parse_action from comment_functions import handle_comment # how often we poll for new transactions CYCLE_TIME = 6 LOGGER.info("Starting tipbot") def stream_comments_messages(): """ # generator to stream comments and messages to the main loop at the bottom, and contains the auto_receive functionality. # Maybe this wasn't necessary, but I never get to use generators. # To check for new messages and comments, the function scans the subreddits and inbox every 6 seconds and builds a # set of current message. I compare the old set with the new set. :return: """ previous_time = time.time() previous_comments = {comment for comment in SUBREDDITS.comments()} previous_messages = {message for message in REDDIT.inbox.all(limit=25)} previous_all = previous_comments.union(previous_messages)
def handle_send(message): """ Extracts send command information from a PM command :param message: :return: """ parsed_text = parse_text(str(message.body)) username = str(message.author) message_time = datetime.utcfromtimestamp( message.created_utc) # time the reddit message was created entry_id = add_history_record( username=username, action="send", comment_or_message="message", comment_id=message.name, reddit_time=message_time.strftime("%Y-%m-%d %H:%M:%S"), comment_text=str(message.body)[:255], ) response = {"username": username} # check that there are enough fields (i.e. a username) if len(parsed_text) <= 2: update_history_notes(entry_id, "no recipient or amount specified") response["status"] = 110 return response # check that it wasn't a mistyped currency code or something if parsed_text[2] in EXCLUDED_REDDITORS: response["status"] = 140 return response # pull sender account info sender_info = tipper_functions.account_info(response["username"]) if not sender_info: update_history_notes(entry_id, "user does not exist") response["status"] = 100 return response # parse the amount try: response["amount"] = parse_raw_amount(parsed_text, response["username"]) except TipError as err: response["status"] = 120 response["amount"] = parsed_text[1] update_history_notes(entry_id, err.sql_text) return response # check if it's above the program minimum if response["amount"] < nano_to_raw(PROGRAM_MINIMUM): update_history_notes(entry_id, "amount below program limit") response["status"] = 130 return response # check the user's balance if response["amount"] > sender_info["balance"]: update_history_notes(entry_id, "insufficient funds") response["status"] = 160 return response recipient_text = parsed_text[2] # catch invalid redditor AND address try: recipient_info = parse_recipient_username(recipient_text) except TipError as err: update_history_notes(entry_id, err.sql_text) response["recipient"] = recipient_text response["status"] = 170 return response # if we have a username, pull their info if "username" in recipient_info.keys(): response["recipient"] = recipient_info["username"] recipient_name = recipient_info["username"] recipient_info = tipper_functions.account_info(recipient_name) response["status"] = 10 if recipient_info is None: recipient_info = tipper_functions.add_new_account( response["recipient"]) response["status"] = 20 elif not recipient_info["opt_in"]: response["status"] = 190 return response # check if it's an address else: # otherwise, just use the address. Everything is None except address recipient_info["minimum"] = 0 response["recipient"] = recipient_info["address"] response["status"] = 30 # check the send amount is above the user minimum, if a username is provided # if it was just an address, this would be -1 if response["amount"] < recipient_info["minimum"]: update_history_notes(entry_id, "below user minimum") response["status"] = 180 response["minimum"] = recipient_info["minimum"] return response response["hash"] = send( sender_info["address"], sender_info["private_key"], response["amount"], recipient_info["address"], )["hash"] # if it was an address, just send to the address if "username" not in recipient_info.keys(): sql = ( "UPDATE history SET notes = %s, address = %s, username = %s, recipient_username = %s, " "recipient_address = %s, amount = %s, return_status = %s WHERE id = %s" ) val = ( "send to address", sender_info["address"], sender_info["username"], None, recipient_info["address"], str(response["amount"]), "cleared", entry_id, ) tipper_functions.exec_sql(sql, val) LOGGER.info( f"Sending Nano: {sender_info['address']} {sender_info['private_key']} {response['amount']} {recipient_info['address']}" ) return response # Update the sql and send the PMs sql = ( "UPDATE history SET notes = %s, address = %s, username = %s, recipient_username = %s, " "recipient_address = %s, amount = %s, hash = %s, return_status = %s WHERE id = %s" ) val = ( "sent to user", sender_info["address"], sender_info["username"], recipient_info["username"], recipient_info["address"], str(response["amount"]), response["hash"], "cleared", entry_id, ) tipper_functions.exec_sql(sql, val) LOGGER.info( f"Sending Nano: {sender_info['address']} {sender_info['private_key']} {response['amount']} {recipient_info['address']} {recipient_info['username']}" ) if response["status"] == 20: subject = "Congrats on receiving your first Nano Tip!" message_text = (WELCOME_TIP % ( response["amount"] / 10**30, recipient_info["address"], recipient_info["address"], ) + COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response else: if not recipient_info["silence"]: receiving_new_balance = check_balance(recipient_info["address"]) subject = "You just received a new Nano tip!" message_text = (NEW_TIP % ( response["amount"] / 10**30, recipient_info["address"], receiving_new_balance[0] / 10**30, receiving_new_balance[1] / 10**30, response["hash"], ) + COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response
def send_from_comment(message): """ Error codes: Success 10 - sent to existing user 20 - sent to new user 30 - sent to address 40 - donated to nanocenter project Tip not sent 100 - sender account does not exist 110 - Amount and/or recipient not specified 120 - could not parse send amount 130 - below program minimum 150 - below 1 nano for untracked sub 160 - insufficient funds 170 - invalid address / recipient 180 - below recipient minimum 200 - No Nanocenter Project specified 210 - Nanocenter Project does not exist Extracts send command information from a PM command :param message: :return: response string """ parsed_text = parse_text(str(message.body)) response = {"username": str(message.author)} message_time = datetime.datetime.utcfromtimestamp( message.created_utc) # time the reddit message was created entry_id = add_history_record( username=response["username"], action="send", comment_or_message="comment", comment_id=message.name, reddit_time=message_time, comment_text=str(message.body)[:255], ) # don't do anything if the first word is a tip command or username if (parsed_text[0] in [f"/u/{TIP_BOT_USERNAME}", f"u/{TIP_BOT_USERNAME}" ]) or (parsed_text[0] in TIP_COMMANDS): pass # if the second to last is a username or tip command, redifine parsed text elif (parsed_text[-2] in [ f"/u/{TIP_BOT_USERNAME}", f"u/{TIP_BOT_USERNAME}" ]) or (parsed_text[-2] in TIP_COMMANDS): parsed_text = parsed_text[-2:] # before we can do anything, check the subreddit status for generating the response response["subreddit"] = str(message.subreddit).lower() try: sr = Subreddit.select(Subreddit.status, Subreddit.minimum).where( Subreddit.subreddit == response["subreddit"]).get() response["subreddit_status"] = sr.status response["subreddit_minimum"] = sr.minimum except Subreddit.DoesNotExist: response["subreddit_status"] = "untracked" response["subreddit_minimum"] = "1" if parsed_text[0] in TIP_COMMANDS and len(parsed_text) <= 1: update_history_notes(entry_id, "no recipient or amount specified") response["status"] = 110 return response # pull sender account info sender_info = tipper_functions.account_info(response["username"]) if not sender_info: update_history_notes(entry_id, "user does not exist") response["status"] = 100 return response # parse the amount try: response["amount"] = parse_raw_amount(parsed_text, response["username"]) except TipError as err: response["status"] = 120 response["amount"] = parsed_text[1] update_history_notes(entry_id, err.sql_text) return response # check if it's above the program minimum if response["amount"] < to_raw(PROGRAM_MINIMUM): update_history_notes(entry_id, "amount below program limit") response["status"] = 130 return response # check the user's balance if response["amount"] > sender_info["balance"]: update_history_notes(entry_id, "insufficient funds") response["status"] = 160 return response # check that it's above the subreddit minimum if response["amount"] < to_raw(response["subreddit_minimum"]): update_history_notes(entry_id, "amount below subreddit minimum") response["status"] = 150 return response # if it's a normal send, pull the account author # we will distinguish users from donations by the presence of a private key if parsed_text[0] in (TIP_COMMANDS + [f"/u/{TIP_BOT_USERNAME}", f"u/{TIP_BOT_USERNAME}"]): response["status"] = 10 response["recipient"] = str(message.parent().author) recipient_info = tipper_functions.account_info(response["recipient"]) if not recipient_info: response["status"] = 20 recipient_info = tipper_functions.add_new_account( response["recipient"]) if recipient_info is None: return text.TIP_CREATE_ACCT_ERROR elif recipient_info["silence"]: response["status"] = 11 elif not recipient_info["opt_in"]: response["status"] = 190 return response else: response["status"] = 999 return response if sender_info["address"] == recipient_info["address"]: # Don't allow sends to yourself response["status"] = 200 return response # send the bans!! response["hash"] = send( sender_info["address"], response["amount"], recipient_info["address"], )["block"] # Update the sql and send the PMs History.update( notes="sent to user", address=sender_info["address"], username=sender_info["username"], recipient_username=recipient_info["username"], recipient_address=recipient_info["address"], amount=str(response["amount"]), hash=response["hash"], return_status="cleared").where(History.id == entry_id).execute() LOGGER.info( f"Sending Banano: {sender_info['address']} {sender_info['private_key']} {response['amount']} {recipient_info['address']} {recipient_info['username']}" ) if response["status"] == 20: subject = text.SUBJECTS["first_tip"] message_text = (text.WELCOME_TIP % ( NumberUtil.format_float(from_raw(response["amount"])), recipient_info["address"], recipient_info["address"], ) + text.COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response else: if not recipient_info["silence"]: receiving_new_balance = check_balance(recipient_info["address"]) subject = text.SUBJECTS["new_tip"] message_text = (text.NEW_TIP % ( NumberUtil.format_float(from_raw(response["amount"])), recipient_info["address"], from_raw(receiving_new_balance), response["hash"], ) + text.COMMENT_FOOTER) send_pm(recipient_info["username"], subject, message_text) return response
from time import sleep from shared import REDDIT, LOGGER, Message LOGGER.info("Starting messenger") while True: results = Message.select() for result in results: LOGGER.info( "%s %s %s" % (result.username, result.subject, repr(result.message)[:50])) try: REDDIT.redditor(str(result.username)).message( str(result.subject), str(result.message)) except: pass Message.delete().where(Message.id == result.id).execute() sleep(6)
def return_transactions_new(): # remove all activated recipients LOGGER.info("Running inactive script") myresults = query_sql( "SELECT username FROM accounts WHERE active IS NOT TRUE") inactivated_accounts = {item[0] for item in myresults} myresults = query_sql("SELECT recipient_username FROM returns") return_accounts = {item[0] for item in myresults} # accounts which have been activated and should be removed from the returns accounts_to_remove = return_accounts - inactivated_accounts for account in accounts_to_remove: exec_sql("DELETE FROM returns WHERE recipient_username = %s", val=(account, )) # return accounts myresults = query_sql("SELECT recipient_username FROM returns") return_accounts = {item[0] for item in myresults} returns = {} # warn recipients for return_from in return_accounts: # return transactions over 35 days old sql = ( "SELECT id, username, amount, history_id FROM returns WHERE" " recipient_username = %s" " AND `sql_time` <= SUBDATE( CURRENT_TIMESTAMP, INTERVAL 35 DAY)" " AND return_status = 'warned'") val = (return_from, ) txns = query_sql(sql, val) # if there are transactions, do them if len(txns) > 0: sql = "SELECT address, private_key FROM accounts WHERE username = %s" inactive_results = query_sql(sql, (return_from, )) from_address = inactive_results[0][0] private_key = inactive_results[0][1] for txn in txns: # set the pre-update message to 'failed'. This will be # deleted upon success sql = "UPDATE returns SET return_status = 'failed' WHERE id = %s" val = (txn[0], ) exec_sql(sql, val) # get the transaction information and find out to whom we are returning sql = "SELECT address, percentage FROM accounts WHERE username = %s" val = (txn[1], ) returned_results = query_sql(sql, val) recipient_address = returned_results[0][0] percentage = returned_results[0][1] percentage = float(percentage) / 100 # send it back donation_amount = from_raw(int(txn[2])) donation_amount = donation_amount * percentage donation_amount = to_raw(donation_amount) return_amount = int(txn[2]) - donation_amount new_entry = 0 if (return_amount > 0) and (return_amount <= int(txn[2])): hash = send(from_address, private_key, return_amount, recipient_address)["hash"] new_entry = add_history_record( action="return", hash=hash, amount=return_amount, notes="Returned transaction from history record %s" % txn[3], username=txn[1], recipient_username=return_from, ) if (donation_amount > 0) and (donation_amount <= int(txn[2])): hash2 = send( from_address, private_key, donation_amount, TIPBOT_DONATION_ADDRESS, )["hash"] new_entry = add_history_record( action="donate", hash=hash2, amount=donation_amount, notes="Donation from returned tip %s" % txn[3], username=txn[1], recipient_username=return_from, ) # remove transactions from the return sql = "DELETE FROM returns WHERE id = %s" val = (txn[0], ) exec_sql(sql, val) # update database if everything goes through sql = f"UPDATE history SET return_status = 'returned by {new_entry}' WHERE id = %s" val = (txn[3], ) exec_sql(sql, val) # add transactions to the messaging queue to build a single message message_recipient = txn[1] if message_recipient not in returns.keys(): returns[message_recipient] = { "percent": round(percentage * 100, 2), "transactions": [], } returns[message_recipient]["transactions"].append([ return_from, from_raw(int(txn[2])), from_raw(return_amount), from_raw(donation_amount), ]) # send warning messages on day 31 sql = ( "SELECT id FROM returns WHERE" " recipient_username = %s" " AND `sql_time` <= SUBDATE( CURRENT_TIMESTAMP, INTERVAL 31 DAY)" " AND return_status = 'returnable'") txns = query_sql(sql, (return_from, )) if len(txns) >= 1: LOGGER.info(f"Warning Message to {return_from}") send_pm(return_from, SUBJECTS["RETURN_WARNING"], RETURN_WARNING + HELP) for txn in txns: sql = "UPDATE returns SET return_status = 'warned' WHERE id = %s" val = (txn[0], ) exec_sql(sql, val) # send out our return messages for user in returns: message = text.make_return_message(returns[user]) send_pm(user, SUBJECTS["RETURN_MESSAGE"], message) LOGGER.info("Inactivated script complete.")