From 04ef03f543a0b52f447066aa85c8d51b4bd56b23 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Thu, 27 Jan 2022 19:12:15 +0000 Subject: [PATCH] Add the notify module --- handler/agora.py | 32 +++++++++++++++++---- handler/app.py | 14 +++++++++ handler/commands.py | 63 +++++++++++++++++++++++------------------ handler/irc.py | 9 +++++- handler/notify.py | 45 +++++++++++++++++++++++++++++ handler/transactions.py | 5 ++++ 6 files changed, 134 insertions(+), 34 deletions(-) create mode 100644 handler/notify.py diff --git a/handler/agora.py b/handler/agora.py index 3e3a013..86c2a9e 100644 --- a/handler/agora.py +++ b/handler/agora.py @@ -65,6 +65,9 @@ class Agora(object): def set_tx(self, tx): self.tx = tx + def set_notify(self, notify): + self.notify = notify + def setup_loop(self): """ Set up the LoopingCall to get all active trades and messages. @@ -470,12 +473,13 @@ class Agora(object): # Don't waste API rate limits on setting the same margin as before if new_margin != our_margin: # rtrn = self.agora.ad_equation(ad_id, new_formula) - to_update.append([ad_id, new_formula, asset, False]) + to_update.append([ad_id, new_formula, asset, currency, False]) # if not rtrn["success"]: # self.log.error("Error updating ad {ad_id}: {response}", ad_id=ad_id, response=rtrn["response"]) self.log.info("Rate for {currency}: {margin}", currency=currency, margin=new_margin) else: self.log.info("Not changed rate for {currency}, keeping old margin of {margin}", currency=currency, margin=our_margin) + print("TO UPDATE", to_update) self.slow_ad_update(to_update) def slow_ad_update(self, ads): @@ -487,15 +491,18 @@ class Agora(object): iterations = 0 throttled = 0 assets = set() - while not all([x[2] for x in ads]) or iterations == 1000: + currencies = set() + while not all([x[4] for x in ads]) or iterations == 1000: for ad_index in range(len(ads)): - ad_id, new_formula, asset, actioned = ads[ad_index] + ad_id, new_formula, asset, currency, actioned = ads[ad_index] + print("SLOW ITER", ad_id, new_formula, asset, currency, actioned) assets.add(asset) + currencies.add(currency) self.log.error("ASSET {a}", a=asset) if not actioned: rtrn = self.agora.ad_equation(ad_id, new_formula) if rtrn["success"]: - ads[ad_index][2] = True + ads[ad_index][4] = True throttled = 0 self.log.info("Successfully updated ad: {id}", id=ad_id) continue @@ -518,8 +525,13 @@ class Agora(object): self.log.info("Slow ad update finished, no ads to update") self.irc.sendmsg("Slow ad update finished, no ads to update") else: - self.log.info("Slow ad update completed with {iterations} iterations: [{assets}]", iterations=iterations, assets=assets) - self.irc.sendmsg(f"Slow ad update completed with {iterations} iterations: [{assets}]") + self.log.info( + "Slow ad update completed with {iterations} iterations: [{assets}] | [{currencies}]", + iterations=iterations, + assets=", ".join(assets), + currencies=", ".join(currencies), + ) + self.irc.sendmsg(f"Slow ad update completed with {iterations} iterations: [{', '.join(assets)}] | [{', '.join(currencies)}]") def autoprice(self, ads, currency): """ @@ -858,3 +870,11 @@ class Agora(object): rtrn2 = self.agora.wallet_send_xmr(**send_cast) self.irc.sendmsg(f"Withdrawal: {rtrn1['success']} | {rtrn2['success']}") + self.notify.notify_withdrawal(profit_usd / 2) + + def to_usd(self, amount, currency): + if currency == "USD": + return float(amount) + else: + rates = self.get_rates_all() + return float(amount) / rates[currency] diff --git a/handler/app.py b/handler/app.py index 1759796..cbb643e 100755 --- a/handler/app.py +++ b/handler/app.py @@ -15,6 +15,7 @@ from revolut import Revolut from agora import Agora from transactions import Transactions from irc import bot +from notify import Notify def convert(data): @@ -54,10 +55,20 @@ class WebApp(object): if __name__ == "__main__": + # Define Notify + notify = Notify() + # Define IRC and Agora irc = bot() agora = Agora() + # Pass Notify to IRC and Agora + irc.set_notify(notify) + agora.set_notify(notify) + + # Pass Agora to Notify + notify.set_agora(agora) + # Pass IRC to Agora and Agora to IRC # This is to prevent recursive dependencies agora.set_irc(irc) @@ -73,6 +84,9 @@ if __name__ == "__main__": # Define Transactions tx = Transactions() + # Pass Notify to Transactions + tx.set_notify(notify) + # Pass Agora and IRC to Transactions and Transactions to IRC tx.set_agora(agora) tx.set_irc(irc) diff --git a/handler/commands.py b/handler/commands.py index b024da0..fd30fbb 100644 --- a/handler/commands.py +++ b/handler/commands.py @@ -12,7 +12,7 @@ class IRCCommands(object): helptext = "Get all open trades." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): """ Get details of open trades and post on IRC. """ @@ -32,7 +32,7 @@ class IRCCommands(object): helptext = "Create an ad. Usage: create " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): """ Post an ad on AgoraDesk with the given country and currency code. """ @@ -52,7 +52,7 @@ class IRCCommands(object): helptext = "Get messages. Usage: messages []" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): """ Get all messages for all open trades or a given trade. """ @@ -86,7 +86,7 @@ class IRCCommands(object): helptext = "Distribute all our chosen currency and country ad pairs." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): # Distribute out our ad to all countries in the config for x in agora.dist_countries(): if x["success"]: @@ -100,7 +100,7 @@ class IRCCommands(object): # helptext = "Use a bruteforce algorithm to create all possible currency and country pairs." # # @staticmethod - # def run(cmd, spl, length, authed, msg, agora, revolut, tx): + # def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): # for x in agora.dist_bruteforce(): # if x["success"]: # msg(f"{x['response']['data']['message']}: {x['response']['data']['ad_id']}") @@ -113,7 +113,7 @@ class IRCCommands(object): # helptext = "Resume a run of brute by getting all our adverts then filling the blanks." # # @staticmethod - # def run(cmd, spl, length, authed, msg, agora, revolut, tx): + # def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): # for x in agora.bruteforce_fill_blanks(): # if x["success"]: # msg(f"{x['response']['data']['message']}: {x['response']['data']['ad_id']}") @@ -126,7 +126,7 @@ class IRCCommands(object): helptext = "Remove all duplicate adverts." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): rtrn = agora.strip_duplicate_ads() msg(dumps(rtrn)) @@ -136,7 +136,7 @@ class IRCCommands(object): helptext = "Find a transaction. Usage: find " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): """ Find a transaction received by Revolut with the given reference and amount. """ @@ -158,7 +158,7 @@ class IRCCommands(object): helptext = "Get all account information from Revolut." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): accounts = revolut.accounts() accounts_posted = 0 if accounts is None: @@ -180,7 +180,7 @@ class IRCCommands(object): helptext = "Get total account balance from Revolut in USD." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): total_usd = revolut.get_total_usd() if total_usd is False: msg("Error getting total balance.") @@ -192,7 +192,7 @@ class IRCCommands(object): helptext = "Get total account balance from Revolut and Agora." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): totals_all = tx.get_total() totals = totals_all[0] wallets = totals_all[1] @@ -208,13 +208,22 @@ class IRCCommands(object): def run(cmd, spl, length, authed, msg): msg("Pong!") + class summon(object): + name = "summon" + authed = True + helptext = "Summon all operators." + + @staticmethod + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + notify.sendmsg("You have been summoned!") + class release_url(object): name = "release_url" authed = True helptext = "Get release URL for all open trades." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): trades = agora.dashboard_release_urls() if not trades: msg("No trades.") @@ -227,7 +236,7 @@ class IRCCommands(object): helptext = "Send a message on a trade. Usage: msg " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length > 2: full_msg = " ".join(spl[2:]) reference = tx.ref_to_tx(spl[1]) @@ -243,7 +252,7 @@ class IRCCommands(object): helptext = "List all references" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): msg(f"References: {', '.join(tx.get_refs())}") class ref(object): @@ -252,7 +261,7 @@ class IRCCommands(object): helptext = "Get more information about a reference. Usage: ref " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 2: ref_data = tx.get_ref(spl[1]) if not ref_data: @@ -266,7 +275,7 @@ class IRCCommands(object): helptext = "Delete a reference. Usage: del " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 2: ref_data = tx.get_ref(spl[1]) if not ref_data: @@ -281,7 +290,7 @@ class IRCCommands(object): helptext = "Release funds for a trade. Usage: release " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 2: tx = tx.ref_to_tx(spl[1]) if not tx: @@ -298,7 +307,7 @@ class IRCCommands(object): helptext = "Delete all our adverts." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): rtrn = agora.nuke_ads() msg(dumps(rtrn)) @@ -308,7 +317,7 @@ class IRCCommands(object): helptext = "Get Agora wallet balances." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): rtrn_xmr = agora.agora.wallet_balance_xmr() if not rtrn_xmr["success"]: msg("Error getting XMR wallet details.") @@ -328,7 +337,7 @@ class IRCCommands(object): helptext = "View public adverts. Usage: pubads " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 3: asset = spl[1] if asset not in loads(settings.Agora.AssetList): @@ -345,7 +354,7 @@ class IRCCommands(object): helptext = "Cheat the markets by manipulating our prices to exploit people. Usage: cheat []" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 1: agora._update_prices() msg("Running cheat in thread.") @@ -369,7 +378,7 @@ class IRCCommands(object): helptext = "Run the next currency for cheat." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 1: asset = agora._update_prices(None, None) msg(f"Running next asset for cheat in thread: {asset}") @@ -380,7 +389,7 @@ class IRCCommands(object): helptext = "Get all our ad regions" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): ads = agora.enum_ads() for ad in ads: msg(f"({ad[0]}) {ad[1]} {ad[2]} {ad[3]}") @@ -391,7 +400,7 @@ class IRCCommands(object): helptext = "Get current XMR price." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): xmr_prices = agora.cg.get_price(ids="monero", vs_currencies=["sek", "usd", "gbp"]) price_sek = xmr_prices["monero"]["sek"] price_usd = xmr_prices["monero"]["usd"] @@ -404,7 +413,7 @@ class IRCCommands(object): helptext = "Get current BTC price." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): xmr_prices = agora.cg.get_price(ids="bitcoin", vs_currencies=["sek", "usd", "gbp"]) price_sek = xmr_prices["bitcoin"]["sek"] price_usd = xmr_prices["bitcoin"]["usd"] @@ -417,7 +426,7 @@ class IRCCommands(object): helptext = "Take profit." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): agora.withdraw_funds() class shuffle(object): @@ -426,7 +435,7 @@ class IRCCommands(object): helptext = "Convert all currencies in Revolut to supplied one. Usage: shuffle " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx): + def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): if length == 2: currency = spl[1] rtrn = revolut.shuffle(currency) diff --git a/handler/irc.py b/handler/irc.py index 97c362d..8cd3dd6 100644 --- a/handler/irc.py +++ b/handler/irc.py @@ -47,6 +47,9 @@ class IRCBot(irc.IRCClient): def set_tx(self, tx): self.tx = tx + def set_notify(self, notify): + self.notify = notify + def parse(self, user, host, channel, msg): """ Simple handler for IRC commands. @@ -97,7 +100,7 @@ class IRCBot(irc.IRCClient): # Check if the command required authentication if obj.authed: if host in self.admins: - obj.run(cmd, spl, length, authed, msgl, self.agora, self.revolut, self.tx) + obj.run(cmd, spl, length, authed, msgl, self.agora, self.revolut, self.tx, self.notify) else: # Handle authentication here instead of in the command module for security self.msg(channel, "Access denied.") @@ -189,6 +192,9 @@ class IRCBotFactory(protocol.ClientFactory): def set_tx(self, tx): self.tx = tx + def set_notify(self, notify): + self.notify = notify + def sendmsg(self, msg): """ Passthrough function to send a message to the channel. @@ -210,6 +216,7 @@ class IRCBotFactory(protocol.ClientFactory): self.client.set_agora(self.agora) self.client.set_revolut(self.revolut) self.client.set_tx(self.tx) + self.client.set_notify(self.notify) return prcol def clientConnectionLost(self, connector, reason): diff --git a/handler/notify.py b/handler/notify.py new file mode 100644 index 0000000..c2dda75 --- /dev/null +++ b/handler/notify.py @@ -0,0 +1,45 @@ +# Twisted/Klein imports +from twisted.logger import Logger + +# Other library imports +import requests + +# Project imports +from settings import settings + + +class Notify(object): + """ + Class to handle more robust notifications. + """ + + def __init__(self): + self.log = Logger("notify") + + def set_agora(self, agora): + self.agora = agora + + def sendmsg(self, msg, title=None, priority=None, tags=None): + headers = {"Title": "Bot"} + if title: + headers["Title"] = title + if priority: + headers["Priority"] = priority + if tags: + headers["Tags"] = tags + requests.post( + f"{settings.Notify.Host}/{settings.Notify.Topic}", + data=msg, + headers=headers, + ) + + def notify_new_trade(self, amount, currency): + amount_usd = self.agora.to_usd(amount, currency) + self.sendmsg(f"Total: {amount_usd}", title="New trade", tags="trades") + + def notify_complete_trade(self, amount, currency): + amount_usd = self.agora.to_usd(amount, currency) + self.sendmsg(f"Total: {amount_usd}", title="Trade complete", tags="trades,profit") + + def notify_withdrawal(self, amount_usd): + self.sendmsg(f"Total: {amount_usd}", title="Withdrawal", tags="profit") diff --git a/handler/transactions.py b/handler/transactions.py index 2e8c2d7..5d76566 100644 --- a/handler/transactions.py +++ b/handler/transactions.py @@ -33,6 +33,9 @@ class Transactions(object): def set_revolut(self, revolut): self.revolut = revolut + def set_notify(self, notify): + self.notify = notify + def transaction(self, data): """ Store details of transaction and post notifications to IRC. @@ -232,6 +235,7 @@ class Transactions(object): r.hmset(f"tx.{txid}", to_store) self.release_funds(stored_trade["id"], stored_trade["reference"]) + self.notify.notify_complete_trade(amount, currency) def release_funds(self, trade_id, reference): self.log.info("All checks passed, releasing funds for {trade_id} {reference}", trade_id=trade_id, reference=reference) @@ -266,6 +270,7 @@ class Transactions(object): self.log.info("Storing trade information: {info}", info=str(to_store)) r.hmset(f"trade.{reference}", to_store) self.irc.sendmsg(f"Generated reference for {trade_id}: {reference}") + self.notify.notify_new_trade(amount, currency) if settings.Agora.Send == "1": self.agora.agora.contact_message_post(trade_id, f"Hi! When sending the payment please use reference code: {reference}") if existing_ref: