def add_new_account(username):
    address = generate_account()
    private = address["private"]
    address = address["account"]
    sql = "INSERT INTO accounts (username, private_key, address, minimum, auto_receive, silence, active, percentage, opt_in) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
    val = (
        username,
        private,
        address,
        to_raw(RECIPIENT_MINIMUM),
        True,
        False,
        False,
        10,
        True,
    )
    MYCURSOR.execute(sql, val)
    MYDB.commit()
    return {
        "username": username,
        "address": address,
        "private_key": private,
        "minimum": to_raw(RECIPIENT_MINIMUM),
        "silence": False,
        "balance": 0,
        "account_exists": True,
    }
Beispiel #2
0
def auto_receive():
    count = 0
    MYCURSOR.execute("SELECT username, address, private_key FROM accounts")
    myresult = MYCURSOR.fetchall()

    addresses = [str(result[1]) for result in myresult]
    private_keys = [str(result[2]) for result in myresult]
    MYDB.commit()
    pendings = get_pendings(addresses, threshold=to_raw(PROGRAM_MINIMUM))
    # get any pending blocks from our address
    for address, private_key in zip(addresses, private_keys):
        # allow 5 transactions to be received per cycle. If the bot gets transaction spammed, at least it won't be
        # locked up receiving.
        if count >= 5:
            break
        try:
            if pendings["blocks"][address]:
                for sent_hash in pendings["blocks"][address]:
                    # address, private_key, dictionary where the blocks are the keys
                    open_or_receive_block(address, private_key, sent_hash)
                    count += 1
                    if count >= 2:
                        break

        except KeyError:
            pass
        except Exception as e:
            print(e)
Beispiel #3
0
def all_pendings(threshold):
    threshold = float(threshold)
    MYCURSOR.execute("SELECT username, address FROM accounts")
    myresult = MYCURSOR.fetchall()
    usernames = [str(result[0]) for result in myresult]
    addresses = [str(result[1]) for result in myresult]

    MYDB.commit()
    pendings = tipper_rpc.get_pendings(addresses, threshold=to_raw(threshold))
    for username, address in zip(usernames, addresses):
        if pendings["blocks"][address]:
            print(username, address, pendings["blocks"][address])
def parse_raw_amount(parsed_text, username=None):
    """
    Given some parsed command text, converts the units to Raw nano
    :param parsed_text:
    :param username: required if amount is 'all'
    :return:
    """
    conversion = 1
    # check if the amount is 'all'. This will convert it to the proper int
    if parsed_text[1].lower() == "all":
        try:
            acct = Account.select(
                Account.address).where(Account.username == username).get()
            address = acct.address
            balance = check_balance(address)
            return balance
        except Account.DoesNotExist:
            raise (TipError(None, text.NOT_OPEN))

    amount = parsed_text[1].lower()

    # before converting to a number, make sure the amount doesn't have nan or inf in it
    if amount == "nan" or ("inf" in amount):
        raise TipError(
            None,
            f"Could not read your tip or send amount. Is '{parsed_text[1]}' a number?",
        )
    else:
        try:
            amount = to_raw(float(amount) / conversion)
        except:
            raise TipError(
                None,
                f"Could not read your tip or send amount. Is '{amount}' a number, or is the "
                "currency code valid? If you are trying to send Nano directly, omit "
                "'Nano' from the amount (I will fix this in a future update).",
            )
    return amount
Beispiel #5
0
def mock_account_info(key, by_address=False):
    balances = {
        "rich": {
            "username": "******",
            "address": "private",
            "private_key": "one",
            "minimum": 0,
            "silence": False,
            "balance": to_raw(100),
            "account_exists": True,
            "opt_in": True,
            "active": True,
        },
        "poor": {
            "username": "******",
            "address": "private",
            "private_key": "one",
            "minimum": 0,
            "silence": False,
            "balance": 0,
            "account_exists": True,
            "opt_in": True,
            "active": True,
        },
        "high_min": {
            "username": "******",
            "address": "private",
            "private_key": "one",
            "minimum": to_raw(100),
            "silence": False,
            "balance": 0,
            "account_exists": True,
            "opt_in": True,
            "active": True,
        },
        "nano_valid": {
            "username": None,
            "address": "valid",
            "private_key": None,
            "minimum": -1,
            "silence": False,
            "balance": None,
            "account_exists": False,
            "opt_in": True,
            "active": True,
        },
        "silent": {
            "username": "******",
            "address": "private",
            "private_key": "one",
            "minimum": to_raw(100),
            "silence": True,
            "balance": 0,
            "account_exists": True,
            "opt_in": True,
            "active": True,
        },
        "out": {
            "username": "******",
            "address": "private",
            "private_key": "one",
            "minimum": 0,
            "silence": False,
            "balance": to_raw(100),
            "account_exists": True,
            "opt_in": False,
            "active": True,
        },
    }
    if not by_address:
        try:
            return balances[key]
        except KeyError:
            return None
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 parse_raw_amount(parsed_text, username=None):
    """
    Given some parsed command text, converts the units to Raw nano
    :param parsed_text:
    :param username: required if amount is 'all'
    :return:
    """
    conversion = 1
    # check if the amount is 'all'. This will convert it to the proper int
    if parsed_text[1].lower() == "all":
        sql = "SELECT address FROM accounts WHERE username = %s"
        val = (username,)
        MYCURSOR.execute(sql, val)
        result = MYCURSOR.fetchall()
        if len(result) > 0:
            address = result[0][0]
            balance = check_balance(address)
            return balance[0]
        else:
            raise (TipError(None, text.NOT_OPEN))

    # check if there is a currency code in the amount; if so, get the conversion
    if parsed_text[1][-3:].lower() in EXCLUDED_REDDITORS:
        currency = parsed_text[1][-3:].upper()
        url = "https://min-api.cryptocompare.com/data/price?fsym={}&tsyms={}".format(
            shared.CURRENCY, currency
        )
        try:
            results = requests.get(url, timeout=1)
            results = json.loads(results.text)
            conversion = float(results[currency])
            amount = parsed_text[1][:-3].lower()
        except requests.exceptions.Timeout:
            raise TipError(
                "Could not reach conversion server.",
                "Could not reach conversion server. Tip not sent.",
            )
        except:
            raise TipError(
                "Could not reach conversion server.",
                f"Currency {currency.upper()} not supported. Tip not sent.",
            )
    else:
        amount = parsed_text[1].lower()

    # before converting to a number, make sure the amount doesn't have nan or inf in it
    if amount == "nan" or ("inf" in amount):
        raise TipError(
            None,
            f"Could not read your tip or send amount. Is '{parsed_text[1]}' a number?",
        )
    else:
        try:
            amount = to_raw(float(amount) / conversion)
        except:
            raise TipError(
                None,
                f"Could not read your tip or send amount. Is '{amount}' a number, or is the "
                "currency code valid? If you are trying to send Nano directly, omit "
                "'Nano' from the amount (I will fix this in a future update).",
            )
    return amount
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],
        subreddit=str(message.subreddit).lower(),
    )

    # 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
    response["subreddit"] = str(message.subreddit).lower()
    sql = "SELECT status, minimum FROM subreddits WHERE subreddit=%s"
    val = (response["subreddit"], )
    results = tipper_functions.query_sql(sql, val)
    if len(results) == 0:
        results = [["untracked", "1"]]
    response["subreddit_status"] = results[0][0]
    response["subreddit_minimum"] = float(results[0][1])

    # 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"] < 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["subreddit_status"] != "untracked":
        if response["amount"] < to_raw(response["subreddit_minimum"]):
            update_history_notes(entry_id, "amount below subreddit minimum")
            response["status"] = 150
            return response
    else:
        if from_raw(response["amount"] * shared.USD_VALUE) < 0.9:
            update_history_notes(entry_id, "amount below untracked 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"]

    # if the recipient is not active, add it to our return table.
    # also, nanocenter projects won't have "active"
    if "active" in recipient_info.keys() and not recipient_info["active"]:
        add_return_record(
            username=sender_info["username"],
            reddit_time=message_time.strftime("%Y-%m-%d %H:%M:%S"),
            recipient_username=recipient_info["username"],
            recipient_address=recipient_info["address"],
            amount=str(response["amount"]),
            hash=response["hash"],
            comment_id=message.name,
            return_status="returnable",
            history_id=entry_id,
        )

    # 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 WHERE id = %s")
    val = (
        "sent to user",
        sender_info["address"],
        sender_info["username"],
        recipient_info["username"],
        recipient_info["address"],
        str(response["amount"]),
        response["hash"],
        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 WHERE id = %s")
    val = (
        "sent to user",
        sender_info["address"],
        sender_info["username"],
        recipient_info["username"],
        recipient_info["address"],
        str(response["amount"]),
        entry_id,
    )
    tipper_functions.exec_sql(sql, val)

    if response["status"] == 20:
        subject = text.SUBJECTS["first_tip"]
        message_text = (text.WELCOME_TIP % (
            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 % (
                from_raw(response["amount"]),
                recipient_info["address"],
                from_raw(receiving_new_balance[0]),
                from_raw(receiving_new_balance[1]),
                response["hash"],
            ) + text.COMMENT_FOOTER)
            send_pm(recipient_info["username"], subject, message_text)
        return response
Beispiel #9
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
Beispiel #10
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
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"] < 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 = text.SUBJECTS["first_tip"]
        message_text = (WELCOME_TIP % (
            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 % (
                from_raw(response["amount"]),
                recipient_info["address"],
                from_raw(receiving_new_balance[0]),
                from_raw(receiving_new_balance[1]),
                response["hash"],
            ) + COMMENT_FOOTER)
            send_pm(recipient_info["username"], subject, message_text)
        return response
def handle_minimum(message):
    message_time = datetime.utcfromtimestamp(
        message.created_utc)  # time the reddit message was created
    # user may select a minimum tip amount to avoid spamming. Tipbot minimum is 0.001
    username = str(message.author)
    # find any accounts associated with the redditor
    parsed_text = parse_text(str(message.body))

    # there should be at least 2 words, a minimum and an amount.
    if len(parsed_text) < 2:
        response = text.MINIMUM["parse_error"]
        return response
    # check that the minimum is a number

    if parsed_text[1].lower() == "nan" or ("inf" in parsed_text[1].lower()):
        response = text.NAN
        return response
    try:
        amount = float(parsed_text[1])
    except:
        response = text.NAN % parsed_text[1]
        return response

    # check that it's greater than 0.01
    if to_raw(amount) < to_raw(PROGRAM_MINIMUM):
        response = text.MINIMUM["below_program"] % PROGRAM_MINIMUM
        return response

    # check if the user is in the database
    sql = "SELECT address FROM accounts WHERE username=%s"
    val = (username, )
    MYCURSOR.execute(sql, val)
    result = MYCURSOR.fetchall()
    if len(result) > 0:
        # open_or_receive(result[0][0], result[0][1])
        # balance = check_balance(result[0][0])
        add_history_record(
            username=username,
            action="minimum",
            amount=to_raw(amount),
            address=result[0][0],
            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],
        )
        sql = "UPDATE accounts SET minimum = %s WHERE username = %s"
        val = (str(to_raw(amount)), username)
        MYCURSOR.execute(sql, val)
        MYDB.commit()
        response = text.MINIMUM["set_min"] % amount
        return response
    else:
        add_history_record(
            username=username,
            action="minimum",
            reddit_time=message_time.strftime("%Y-%m-%d %H:%M:%S"),
            amount=to_raw(amount),
            comment_id=message.name,
            comment_text=str(message.body)[:255],
        )
        response = text.NOT_OPEN
        return response
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.")