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
示例#2
0
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
示例#4
0
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,
    }
示例#6
0
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)
示例#7
0
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
示例#9
0
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.")
示例#10
0
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)
示例#11
0
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)
示例#12
0
def list_users(u):
    sql = "SELECT username FROM accounts"
    MYCURSOR.execute(sql)
    results = MYCURSOR.fetchall()
    for result in results:
        LOGGER.info(result)
示例#13
0
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
示例#14
0
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)
示例#15
0
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)
示例#16
0
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
示例#17
0
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
示例#18
0
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.")