Example #1
0
def main():
    helper.testnet = args.testnet
    helper.password = args.password
    if helper.testnet:
        helper.ports.ports_to_testnet()

    try:
        while True:
            for i in sorted(
                    os.listdir("wallets/" +
                               ("testnet/" if args.testnet else "mainnet/"))):
                if not "." in i:
                    start = int(round(time.time() * 1000))
                    print("Opening " + i + "'s wallet")
                    with HiddenPrints():
                        get_info(wallet_name=i,
                                 private_info=False,
                                 password=args.password,
                                 port=helper.ports.wallet_sync_port,
                                 timeout=sync_time)
                    if int(round(time.time() *
                                 1000)) - start > sync_time * 1000 - 10000:
                        print("Warn: " + i + "'s wallet is likely unsynced")
                    print("Taking a break...")
                    time.sleep(sync_time)  # Poor CPU

    except Exception as e:
        tipper_logger.log("walletsyncer error: " + str(e))
        traceback.print_exc()
        main()
Example #2
0
    def open_rpc(self,
                 port,
                 wallet_name,
                 password=wallet_password,
                 timeout=timeout,
                 tries=5):
        if tries == 0:
            tipper_logger.log(
                f"WARNING: FAILED to open {wallet_name}'s wallet!!")
            return
        self.rpc = RPC(port=port,
                       wallet_name=wallet_name,
                       password=password,
                       load_timeout=timeout)

        if not os.path.isfile("aborted-" + wallet_name
                              ):  # Check if wallet was emergency aborted
            self.wallet = Wallet(
                JSONRPCWallet(port=self.rpc.port,
                              password=self.rpc.password,
                              timeout=self.rpc.load_timeout))
        else:
            tipper_logger.log(
                f"WARNING: {wallet_name} had their RPC aborted!!! Trying {tries} more times"
            )
            os.remove("aborted-" + wallet_name)
            self.open_rpc(port=port,
                          wallet_name=wallet_name,
                          password=password,
                          timeout=timeout,
                          tries=tries - 1)
Example #3
0
def handle_tip_request(author, body, comment):
    """
    Handles the tipping interaction, called by a Redditor's comment
    Replies to the user if the response is not None
    Sends user a message if message is not None

    :param body: The contents of a comment that called the bot
    :param author: The username of the entity that created the comment
    :param comment: The comment itself that called the bot
    """

    recipient = get_tip_recipient(comment)
    amount = helper.parse_amount(f'/u/{helper.botname.lower()} (tip )?', body)

    if recipient is None or amount is None:
        reply = "Nothing interesting happens.\n\n*In case you were trying to tip, I didn't understand you.*"
    elif Decimal(amount) < Decimal(0.0001):
        reply = helper.get_below_threshold_message()
    else:
        tipper_logger.log(f'{author} is sending {recipient} {amount} XMR.')
        generate_wallet_if_doesnt_exist(recipient.lower())

        res = tip(sender=author, recipient=recipient, amount=amount)

        reply = f'{res["response"]}'
        tipper_logger.log("The response is: " + reply)

        if res["message"] is not None:
            helper.praw.redditor(author).message(
                subject="Your tip",
                message=f"Regarding your tip here: {comment.context}\n\n" +
                res["message"] + get_signature())

    helper.praw.comment(str(comment)).reply(reply + get_signature())
Example #4
0
def handle_anonymous_tip(author, subject, contents):
    """
    Allows people to send anonymous tips

    :param author: Reddit account to withdraw from
    :param subject: Subject line of the message, telling who to tip and how much
    :param contents: Message body (ignored)
    """

    recipient = parse_anon_tip_recipient(subject)
    amount = parse_anon_tip_amount(subject)

    if recipient is None or amount is None:
        helper.praw.redditor(author).message(subject="Your anonymous tip", message="Nothing interesting happens.\n\n*Your recipient or amount wasn't clear to me*" + get_signature())
        return
    if Decimal(amount) < (0.0001):  # Less than amount displayed in balance page
        helper.praw.redditor(author).message(subject="Your anonymous tip", message=helper.get_below_threshold_message() + get_signature())
        return

    generate_wallet_if_doesnt_exist(recipient)

    tipper_logger.log(author + " is trying to send " + parse_anon_tip_amount(subject) + " XMR to " + parse_anon_tip_recipient(subject))

    res = tip(sender=author, recipient=recipient, amount=amount)

    if res["message"] is not None:
        helper.praw.redditor(author).message(subject="Your anonymous tip", message=res["message"] + get_signature())
    else:
        helper.praw.redditor(author).message(subject="Anonymous tip successful", message=res["response"] + get_signature())
        helper.praw.redditor(recipient).message(f"You have received an anonymous tip of {amount} XMR! ({helper.get_dollar_val(amount)} USD)",
                                                message=(get_signature() if contents == helper.no_message_anon_tip_string else "The tipper attached the following message:\n\n" + contents + get_signature()))
def generate_wallet(name, password=None):
    """
    Generates a new user wallet
    Stores the blockheight in a file named user_blockheight

    :param name: Name of user generating the wallet
    :param password: Password to give the new wallet
    :return True on successful wallet generation, False otherwise
    """
    if password is None:
        password = helper.password

    name = str(name)
    rpc = RPC(port=helper.ports.generate_wallet_port)

    rpc_url = f"http://127.0.0.1:{helper.ports.generate_wallet_port}/json_rpc"
    function_url = "http://127.0.0.1:" + str(helper.ports.monerod_port) + "/get_height"
    headers = {'Content-Type': 'application/json'}

    payload = {
        "jsonrpc" : "2.0",
        "id" : "0",
        "method" : "create_wallet",
        "params": {
            "filename" : name,
            "password" : password,
            "language" : "English",
        }
    }

    tipper_logger.log(f"Generating wallet for {name}.")

    try:
        requests.post(rpc_url, data=json.dumps(payload), headers=headers).json()
    except Exception as e:
        tipper_logger.log(str(e))

    try:
        blockheight_response = requests.post(function_url, headers=headers).json()
        print(blockheight_response["height"] - 10, file=open('wallets/' + ("testnet/" if helper.testnet else "mainnet/") + name + ".height", 'w')) # DON'T CHANGE THIS DUMDUM
    except Exception as e:
        tipper_logger.log(str(e))

    rpc.kill()

    # Create .address.txt (probably a better way since we already had a wallet open)
    wallet = SafeWallet(port=helper.ports.create_address_txt_port, wallet_name=name, wallet_password=password)
    address = wallet.rpc.run_rpc_request(
        '{"jsonrpc":"2.0","id":"0","method":"get_address","params":{"account_index":0,"address_index":[0]}}').json()[
        "result"]["address"]
    print(address, file=open('wallets/' + ("testnet/" if helper.testnet else "mainnet/") + name + ".address.txt", 'w'))
    wallet.kill_rpc()

    if wallet_exists(name):
        tipper_logger.log("Generated a wallet for " + name)
        return True
    tipper_logger.log("Failed to generate a wallet for " + name)
    return False
Example #6
0
 def wait_for_rpc_to_load(self):
     """
     Waits for RPC to confirm it's ready for commands
     """
     rpc_read_process = multiprocessing.Process(
         target=self.check_rpc_loaded)
     rpc_read_process.start()
     rpc_read_process.join(timeout=self.load_timeout)
     rpc_read_process.kill()
     tipper_logger.log("Got final status of rpc reader")
Example #7
0
def main():
    while True:
        tipper_logger.log("Searching for new messages")
        start_time = datetime.datetime.now().timestamp()

        author = None
        try:
            for message in helper.praw.inbox.stream():
                if not message.author:
                    helper.praw.inbox.mark_read(
                        [message]
                    )  # Gets rid of messages that otherwise crash service (i.e. sub bans)
                else:
                    author = message.author.name
                    if message.created_utc > start_time:
                        process_message(author=author,
                                        comment=message,
                                        subject=message.subject,
                                        body=message.body)
        except Exception as e:
            try:
                if "read timeout" not in str(e).lower() \
                        and "reddit.com timed out" not in str(e) \
                        and "503" not in str(e):
                    tipper_logger.log("Main error: " + str(e))
                    tipper_logger.log("Blame " + author)
                    traceback.print_exc()
                    helper.praw.redditor("OsrsNeedsF2P").message(
                        subject=f"Something broke for /u/{author}!!",
                        message=f"{str(e)}" + helper.get_signature())
            except Exception as e:
                tipper_logger.log("Just wow." + str(e))
Example #8
0
def get_tip_recipient(comment):
    """
    Determines the recipient of the tip, based on the comment requesting the tip

    :param comment: The PRAW comment that notified the bot
    :return: String representing Username of the parent of the comment
    """

    author = None
    try:
        author = comment.parent().author
    except Exception:
        tipper_logger.log("Somehow there's no parent at all?")

    return fix_automoderator_recipient(author.name)
Example #9
0
def generate_transaction(sender_wallet,
                         recipient_address,
                         amount,
                         split_size=6,
                         timeout=50):
    """
    Generates a transaction with multiple outputs instead of 2
    This allows for the recipient to spend more easily.

    Each output is worth approx. amount/splitSize XMR

    :param sender_wallet: Wallet to send Monero from
    :param recipient_address: Address to receive Monero
    :param amount: The amount to send, in XMR
    :param split_size: The amount of outputs to generate
    :param timeout: Time (in seconds) to try and broadcast a tx before returning failure
    :return: TXID on success, the string "FAILURE" otherwise
    """

    sum = 0
    transactions = []
    decimalamount = Decimal(amount)
    senderwalletbalance = sender_wallet.balance()
    if Decimal(amount) > sender_wallet.balance() - Decimal(0.001) and Decimal(
            amount
    ) < Decimal(0.1) + sender_wallet.balance(
    ):  # If you're sending more than your balance, but not much more --
        tipper_logger.log("Sending sweep_all transaction...")

        sweep_res = timeout_function(target=send_sweep_all,
                                     args=(sender_wallet, recipient_address),
                                     timeout=timeout)

        tipper_logger.log("Sweep res is: " + str(sweep_res))
        if not is_txid(sweep_res):
            raise ValueError(
                sweep_res
            )  #It'll get caught by the calling function which will handle it
        return sweep_res

    # Make multiple of the same output, but in smaller chunks
    for i in range(0, split_size - 1):
        sum += float(amount) / split_size
        transactions.append(
            (recipient_address, Decimal(float(amount) / split_size)))

    # Add the remainder
    transactions.append((recipient_address, Decimal(float(amount) - sum)))

    tipper_logger.log("About to broadcast transaction..")

    broadcast_res = timeout_function(target=broadcast_transaction,
                                     args=(sender_wallet, transactions),
                                     timeout=timeout)
    tipper_logger.log("Broadcast res is: " + str(broadcast_res))
    if not is_txid(broadcast_res):
        raise ValueError(broadcast_res)
    return broadcast_res
Example #10
0
def handle_info_request(author, private_info=False):
    """
    Allows Reddit users to see their wallet address, balance, and optionally their private key.

    :param author: Username of the entity requesting their info
    :param private_info: Whether or not to send the private key (mnemonic) along with the message
    :return:
    """
    helper.praw.redditor(author).message(
        subject="Your " + ("private address and info"
                           if private_info else "public address and balance"),
        message=get_info_as_string(wallet_name=author.lower(),
                                   private_info=private_info) +
        get_signature())
    tipper_logger.log(
        f'Told {author} their {("private" if private_info else "public")} info.'
    )
Example #11
0
 def kill_existing_rpc(self, port):
     for proc in psutil.process_iter():
         try:
             for conns in proc.connections(kind='inet'):
                 if conns.laddr.port == port:
                     proc.send_signal(SIGTERM)
                     print("MURDERING THE SIGNAL")
                     # TODO: Sleep until it's dead, timeout 20 seconds?
         except psutil.AccessDenied:
             # This is fine, because this issue was not incurred when trying to kill the signal.
             pass
         except Exception as e:
             # This could be bad, so let's log it just in case.
             tipper_logger.log(
                 "RPC BAD: Something bad happened with trying to kill the RPC?"
             )
             tipper_logger.log(e)
Example #12
0
def handle_donation(author, subject):
    """
    Allows Reddit users to donate a portion of their balance directly to the CCS
    CCS can be seen at: https://ccs.getmonero.org/

    :param author: Reddit account to withdraw from
    :param subject: Subject line of the message, telling how much to withdraw
    """

    sender_rpc_n_wallet = SafeWallet(port=helper.ports.donation_sender_port,
                                     wallet_name=author.lower(),
                                     wallet_password=helper.password)

    amount = Decimal(
        helper.parse_amount('donate ',
                            subject,
                            balance=sender_rpc_n_wallet.wallet.balance()))

    try:
        generate_transaction(
            sender_wallet=sender_rpc_n_wallet.wallet,
            recipient_address=helper.get_general_fund_address(),
            amount=amount,
            split_size=1)
        helper.praw.redditor(author).message(
            subject="Your donation to the General Dev Fund",
            message=
            f'Thank you for donating {format_decimal(amount)} of your XMR balance to the CCS!\n\nYou will soon have your total donations broadcasted to the wiki :) {get_signature()}'
        )
        helper.praw.redditor("OsrsNeedsF2P").message(
            subject=f'{author} donated {amount} to the CCS!',
            message=
            f"Update table here: https://old.reddit.com/r/{helper.botname}/wiki/index#wiki_donating_to_the_ccs"
        )
        tipper_logger.log(
            f'{author} donated {format_decimal(amount)} to the CCS.')
    except Exception as e:
        helper.praw.redditor(author).message(
            subject="Your donation to the CCS failed",
            message=f'Please send the following to /u/OsrsNeedsF2P:\n\n' +
            str(e) + get_signature())
        tipper_logger.log("Caught an error during a donation to CCS: " +
                          str(e))

    sender_rpc_n_wallet.kill_rpc()
Example #13
0
def handle_withdraw(sender_wallet, sender_name, recipient_address, amount):
    """
    Withdraws Monero from sender_name's wallet

    :param sender_wallet: sender_name's wallet
    :param sender_name: User who wishes to withdraw
    :param recipient_address: Address to send funds to
    :param amount: Amount to send in XMR
    :return: Response message regarding status of send
    """

    tipper_logger.log(f'{sender_name} is trying to send {recipient_address} {amount} XMR')
    try:
        res = "Withdrawal success! [Txid](" \
              f"{helper.get_xmrchain(generate_transaction(sender_wallet=sender_wallet, recipient_address=recipient_address, amount=Decimal(amount)))})"
    except Exception as e:
        tipper_logger.log(e)
        res = get_error_response(e)

    return res
Example #14
0
    def check_rpc_loaded(self):
        """
        Loops through RPC output until it detects it's ready/failed
        """
        rpc_output = self.parse_rpc_output()
        while rpc_output == "LOADING":
            time.sleep(0.01)
            rpc_output = self.parse_rpc_output()

        if rpc_output == "FAIL":
            tipper_logger.log("RPC Failed!!! Aborting!")
            self.kill()
            self.kill_existing_rpc(
                self.port
            )  # Any wallet attempted to be created with this RPC will now fail
            open("aborted-" + self.wallet_name, "w").close()
            time.sleep(0.01)  # Just in case - time to write

        if rpc_output == "SUCCESS":
            tipper_logger.log("Wallet loaded in time")
Example #15
0
    def __init__(self,
                 port,
                 wallet_name=None,
                 rpc_location="monero_tools/extras/monero-wallet-rpc",
                 password=None,
                 disable_rpc_login=True,
                 load_timeout=300):
        if password is None:
            password = helper.password
        self.port = port
        self.wallet_name = wallet_name
        self.rpc_location = rpc_location
        self.password = password
        self.disable_rpc_login = disable_rpc_login
        self.load_timeout = load_timeout

        if wallet_name is not None:  # Open wallet
            rpc_command = f'{rpc_location} --wallet-file ./wallets/{"testnet/" if helper.testnet else "mainnet/"}{wallet_name} --password {password} --rpc-bind-port {port} {"--testnet" if helper.testnet else ""} {"--disable-rpc-login" if disable_rpc_login else ""}'
        else:  # Create new wallet
            rpc_command = f'{rpc_location} --wallet-dir ./wallets/{"testnet/" if helper.testnet else "mainnet/"} --rpc-bind-port {port}{" --testnet" if helper.testnet else ""}{" --disable-rpc-login" if disable_rpc_login else ""}'

        tipper_logger.log(rpc_command)
        rpc_command_shelled = shlex.split(rpc_command)

        self.kill_existing_rpc(
            port)  # Prevents an old RPC from accidentally being reused
        self.rpc_process = subprocess.Popen(rpc_command_shelled,
                                            stdout=subprocess.PIPE)

        self.wait_for_rpc_to_load()
        if os.path.isfile(
                "locked"
        ):  # Check if we were syncing it with walletsyncer.py in another program
            print("Wallet locked - waiting 90 sec and trying again")
            os.remove("locked")
            self.kill()
            time.sleep(90)
            self.rpc_process = subprocess.Popen(rpc_command_shelled,
                                                stdout=subprocess.PIPE)
            self.wait_for_rpc_to_load()
Example #16
0
def handle_withdraw_request(author, subject, contents):
    """
    Handles the withdrawal request, setting up RPC and calling the withdraw function

    :param author: Wallet to withdraw from
    :param subject: The withdrawl request string
    :param contents: The address to withdraw to
    :return: Response message about withdrawl request
    """

    amount = helper.parse_amount("withdraw ", subject)
    if amount is None:
        helper.praw.redditor(author).message(subject="I didn't understand your withdrawal!", message=f'You sent: "{subject}", but I couldn\'t figure out how much you wanted to send. See [this](https://www.reddit.com/r/{helper.botname}/wiki/index#wiki_withdrawing) guide if you need help, or click "Report a Bug" under "Get Started"  if you think there\'s a bug!' + get_signature())
        return None

    sender_rpc_n_wallet = SafeWallet(port=helper.ports.withdraw_sender_port, wallet_name=author.lower(), wallet_password=helper.password)

    res = str(handle_withdraw(sender_rpc_n_wallet.wallet, author, contents, amount))

    sender_rpc_n_wallet.kill_rpc()

    helper.praw.redditor(author).message(subject="Your withdrawl", message=res + get_signature())
    tipper_logger.log("Told " + author + " their withdrawl status (" + res + ")")
Example #17
0
def process_message(author, comment, subject, body):
    """
    Handles the comment command a user tried to execute

    :param subject: Subject line of private message
    :param body: Body of private message
    :param author: Username of author
    :param comment: comment to parse for the command
    """

    tipper_logger.log("Got message " + body)

    tipper_logger.log(f'Received message: {subject} from {author}: {body}')

    generate_wallet_if_doesnt_exist(name=author.lower(), password=helper.password)

    if comment_requests_tip(body):
        handle_tip_request(author=author, body=body, comment=comment)
        return
    if subject_requests_info(subject):
        handle_info_request(author=author, private_info=False)
        return
    if subject_requests_private_info(subject):
        handle_info_request(author=author, private_info=True)
        return
    if subject_requests_withdraw(subject):
        handle_withdraw_request(author=author, subject=subject, contents=body)
        return
    if subject_requests_donate(subject):
        handle_donation(author=author, subject=subject)
        return
    if subject_requests_anonymous_tip(subject):
        handle_anonymous_tip(author=author, subject=subject, contents=body)
        return

    helper.praw.redditor(author).message(subject="I didn't understand your command", message=f'I didn\'t understand what you meant last time you tagged me. You said: \n\n{body}\n\nIf you didn\'t mean to summon me, you\'re all good! If you\'re confused, please let my owner know by clicking Report a Bug!{helper.get_signature()}')
Example #18
0
    def parse_rpc_output(self):
        """
        Reads 1 line from RPC output
        :return: Status of RPC - LOADING if still unknown, FAIL if an error occurred, SUCCESS otherwise
        """
        rpc_out = self.rpc_process.stdout.readline()
        tipper_logger.log("RPC:" + str(rpc_out))

        if "error" in str(rpc_out).lower() or "failed to initialize" in str(
                rpc_out).lower():
            if "locking fd" in str(rpc_out.lower()):
                tipper_logger.log(
                    "The wallet is already open (that's likely fine..)")
                open("locked", "w").close()
                time.sleep(0.01)  # Just in case
            else:
                tipper_logger.log("Found out the RPC has an error (FAIL)")
            return "FAIL"
        if "starting wallet rpc server" in str(rpc_out.lower()):
            tipper_logger.log("Found out the RPC has started (SUCCESS)")
            return "SUCCESS"

        return "LOADING"
Example #19
0
def tip(sender, recipient, amount):
    """
    Sends Monero from sender to recipient
    If the sender and the recipient are the same, it creates only 1 rpc
    Always closes RPCs, even on failure

    :param sender: name of wallet sending Monero
    :param recipient: name of wallet receiving
    :param amount: amount to send in XMR
    :return info: dictionary containing txid, a private message and a public response
    """

    info = {
        "txid": "None",
        "response": "None",  #Comment reply
        "message": None  #Error message
    }

    tipper_logger.log(sender + " is trying to send " + recipient + " " +
                      amount + " XMR")

    sender = str(sender)
    recipient = str(recipient)
    sender_rpc_n_wallet = None

    try:
        sender_rpc_n_wallet = SafeWallet(port=helper.ports.tip_sender_port,
                                         wallet_password=helper.password,
                                         wallet_name=sender.lower())
        tipper_logger.log("Sender wallet loaded!!")
    except Exception as e:
        sender_rpc_n_wallet.kill_rpc()
        tipper_logger.log("Failed to open wallets for " + sender + " and " +
                          recipient + ". Message: ")
        tipper_logger.log(e)
        info[
            "response"] = "Could not open wallets properly! Perhaps my node is out of sync? (Try again shortly).\n\n^/u/OsrsNeedsF2P!!"
        info["message"] = str(e)
        return info

    tipper_logger.log("Successfully initialized wallets..")

    try:
        recipient_address = helper.get_address_txt(recipient.lower())
        txs = generate_transaction(sender_wallet=sender_rpc_n_wallet.wallet,
                                   recipient_address=recipient_address,
                                   amount=amount)

        info["txid"] = str(txs)
        info[
            "response"] = f"Successfully tipped /u/{recipient} {amount} XMR! [^(txid)]({helper.get_xmrchain(txs)})"
        tipper_logger.log("Successfully sent tip")
    except Exception as e:
        tipper_logger.log(e)
        traceback.print_exc()
        info["message"] = get_error_response(e)
        info[
            "response"] = "Didn't tip - Check your private message to see why :)"

    sender_rpc_n_wallet.kill_rpc()

    tipper_logger.log("Tip function completed without crashing")

    return info
Example #20
0
def fix_automoderator_recipient(recipient):
    if recipient.lower() == "automoderator":
        tipper_logger.log(
            f"Changing recipient to {helper.botname} to prevent abuse")
        return helper.botname
    return recipient