diff --git a/handler/app.py b/handler/app.py index a30038e..73e7f02 100755 --- a/handler/app.py +++ b/handler/app.py @@ -12,7 +12,6 @@ from signal import signal, SIGINT # Project imports from settings import settings import util -from revolut import Revolut from agora import Agora from transactions import Transactions from irc import bot @@ -99,7 +98,6 @@ if __name__ == "__main__": "irc": bot(), "agora": Agora(), "markets": Markets(), - "revolut": Revolut(), "nordigen": Nordigen(), "truelayer": TrueLayer(), "fidor": Fidor(), @@ -110,21 +108,6 @@ if __name__ == "__main__": # Merge all classes into each other util.xmerge_attrs(init_map) - # Setup the authcode -> refresh token and refresh_token -> auth_token stuff - # util.setup_call_loops( - # token_setting=settings.Revolut.SetupToken, - # function_init=init_map["revolut"].setup_auth, - # function_continuous=init_map["revolut"].get_new_token, - # delay=int(settings.Revolut.RefreshSec), - # function_post_start=init_map["revolut"].setup_webhook, - # ) - # util.setup_call_loops( - # token_setting=settings.TrueLayer.SetupToken, - # function_init=init_map["truelayer"].setup_auth, - # function_continuous=init_map["truelayer"].get_new_token, - # delay=int(settings.TrueLayer.RefreshSec), - # ) - # Set up the loops to put data in ES init_map["tx"].setup_loops() diff --git a/handler/commands.py b/handler/commands.py index 6fe2989..b9fd666 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, notify): + def run(cmd, spl, length, authed, msg, agora, 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, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): """ Post an ad on AgoraDesk with the given country and currency code. """ @@ -64,7 +64,7 @@ class IRCCommands(object): helptext = "Get messages. Usage: messages []" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): """ Get all messages for all open trades or a given trade. """ @@ -98,7 +98,7 @@ class IRCCommands(object): helptext = "Distribute all our chosen currency and country ad pairs. Usage: dist []" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): # Distribute out our ad to all countries in the config if length == 2: asset = spl[1] @@ -123,7 +123,7 @@ class IRCCommands(object): helptext = "Update all ads with details." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): for x in agora.redist_countries(): if x[0]["success"]: msg(f"{x[0]['response']['data']['message']}: {x[1]}") @@ -136,51 +136,17 @@ class IRCCommands(object): helptext = "Remove all duplicate adverts." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): rtrn = agora.strip_duplicate_ads() msg(dumps(rtrn)) - class accounts(object): - name = "accounts" - authed = True - helptext = "Get all account information from Revolut." - - @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): - accounts = revolut.accounts() - accounts_posted = 0 - if accounts is None: - msg("Error getting accounts.") - for account in accounts: - if account["balance"] > 0: - if "name" in account: - name = account["name"] - else: - name = "not_set" - msg(f"{name} {account['currency']}: {account['balance']}") - accounts_posted += 1 - if accounts_posted == 0: - msg("No accounts with balances.") - - class balance(object): - name = "balance" - authed = True - helptext = "Get total account balance from Revolut in USD." - - @staticmethod - 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.") - msg(f"Total: {round(total_usd, 2)}USD") - class total(object): name = "total" authed = True - helptext = "Get total account balance from Revolut and Agora." + helptext = "Get total account balance from Sinks and Agora." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): totals_all = tx.get_total() totals = totals_all[0] wallets = totals_all[1] @@ -202,7 +168,7 @@ class IRCCommands(object): helptext = "Summon all operators." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): notify.sendmsg("You have been summoned!") class message(object): @@ -211,7 +177,7 @@ class IRCCommands(object): helptext = "Send a message on a trade. Usage: msg " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length > 2: full_msg = " ".join(spl[2:]) reference = tx.ref_to_tx(spl[1]) @@ -227,7 +193,7 @@ class IRCCommands(object): helptext = "List all references" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): msg(f"References: {', '.join(tx.get_refs())}") class ref(object): @@ -236,7 +202,7 @@ class IRCCommands(object): helptext = "Get more information about a reference. Usage: ref " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length == 2: ref_data = tx.get_ref(spl[1]) if not ref_data: @@ -250,7 +216,7 @@ class IRCCommands(object): helptext = "Delete a reference. Usage: del " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length == 2: ref_data = tx.get_ref(spl[1]) if not ref_data: @@ -265,7 +231,7 @@ class IRCCommands(object): helptext = "Release funds for a trade. Usage: release " @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length == 2: tx = tx.ref_to_tx(spl[1]) if not tx: @@ -282,7 +248,7 @@ class IRCCommands(object): helptext = "Delete all our adverts." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): rtrn = agora.nuke_ads() msg(dumps(rtrn)) @@ -292,7 +258,7 @@ class IRCCommands(object): helptext = "Get Agora wallet balances." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): rtrn_xmr = agora.agora.wallet_balance_xmr() if not rtrn_xmr["success"]: msg("Error getting XMR wallet details.") @@ -312,7 +278,7 @@ class IRCCommands(object): helptext = "View public adverts. Usage: pubads []" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length == 3: asset = spl[1] if asset not in loads(settings.Agora.AssetList): @@ -345,7 +311,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, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length == 1: agora.run_cheat_in_thread() msg("Running cheat in thread.") @@ -363,7 +329,7 @@ class IRCCommands(object): helptext = "Run the next currency for cheat." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): if length == 1: asset = agora.run_cheat_in_thread() msg(f"Running next asset for cheat in thread: {asset}") @@ -374,7 +340,7 @@ class IRCCommands(object): helptext = "Get all our ad regions" @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): ads = agora.enum_ads() for ad in ads: msg(f"({ad[0]}) {ad[1]} {ad[2]} {ad[3]} {ad[4]}") @@ -385,7 +351,7 @@ class IRCCommands(object): helptext = "Get current XMR price." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, 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"] @@ -398,7 +364,7 @@ class IRCCommands(object): helptext = "Get current BTC price." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, 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"] @@ -411,28 +377,16 @@ class IRCCommands(object): helptext = "Take profit." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): agora.withdraw_funds() - class shuffle(object): - name = "shuffle" - authed = True - helptext = "Convert all currencies in Revolut to supplied one. Usage: shuffle " - - @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): - if length == 2: - currency = spl[1] - rtrn = revolut.shuffle(currency) - msg(dumps(rtrn)) - class remaining(object): name = "r" authed = True helptext = "Show how much is left before we are able to withdraw funds." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): remaining = tx.get_remaining() msg(f"Remaining: {remaining}USD") @@ -442,7 +396,7 @@ class IRCCommands(object): helptext = "Show how much is left before we are able to withdraw funds (including open trades)." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): remaining = tx.get_total_remaining() msg(f"Total remaining: {remaining}USD") @@ -452,7 +406,7 @@ class IRCCommands(object): helptext = "Get total value of all open trades in USD." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): total = tx.get_open_trades_usd() msg(f"Total trades: {total}USD") @@ -462,7 +416,7 @@ class IRCCommands(object): helptext = "Get total value of everything, including open trades." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): total = tx.get_total_with_trades() msg(f"${total}") @@ -472,7 +426,7 @@ class IRCCommands(object): helptext = "Get total profit." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): total = tx.money.get_profit() msg(f"Profit: {total}USD") @@ -482,7 +436,7 @@ class IRCCommands(object): helptext = "Get total profit with open trades." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): total = tx.money.get_profit(True) msg(f"Profit: {total}USD") @@ -492,6 +446,6 @@ class IRCCommands(object): helptext = "Generate a TrueLayer signin URL." @staticmethod - def run(cmd, spl, length, authed, msg, agora, revolut, tx, notify): + def run(cmd, spl, length, authed, msg, agora, tx, notify): auth_url = agora.truelayer.create_auth_url() msg(f"Auth URL: {auth_url}") diff --git a/handler/irc.py b/handler/irc.py index 83d9bb5..4ab9ca3 100644 --- a/handler/irc.py +++ b/handler/irc.py @@ -88,7 +88,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, self.notify) + obj.run(cmd, spl, length, authed, msgl, self.agora, self.tx, self.notify) else: # Handle authentication here instead of in the command module for security self.msg(channel, "Access denied.") @@ -192,7 +192,8 @@ class IRCBotFactory(protocol.ClientFactory): prcol = IRCBot(self.log) self.client = prcol setattr(self.client, "agora", self.agora) - setattr(self.client, "revolut", self.revolut) + setattr(self.client, "truelayer", self.truelayer) + setattr(self.client, "nordigen", self.nordigen) setattr(self.client, "tx", self.tx) setattr(self.client, "notify", self.notify) return prcol diff --git a/handler/revolut.py b/handler/revolut.py deleted file mode 100644 index 436b76d..0000000 --- a/handler/revolut.py +++ /dev/null @@ -1,247 +0,0 @@ -# Twisted/Klein imports -from twisted.logger import Logger - -# Other library imports -from json import dumps -from simplejson.errors import JSONDecodeError -import requests -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.backends import default_backend -import jwt -from random import choices -from string import ascii_uppercase - -# Project imports -from settings import settings - - -class Revolut(object): - """ - Class to handle Revolut API calls. - """ - - def __init__(self): - """ - Initialise the Revolut object. - Set the logger and token. - """ - self.log = Logger("revolut") - self.token = None - - def setup_auth(self): - """ - Function to create a new Java Web Token and use it to get a refresh/access token. - """ - self.create_new_jwt() - self.get_access_token() - - def create_new_jwt(self): - """ - Create a new Java Web Token. - """ - payload = { - "iss": settings.Revolut.Domain, - "sub": settings.Revolut.ClientID, - "aud": "https://revolut.com", - "exp": int(settings.Revolut.Expiry), - } - with open(settings.Revolut.PrivateKey, "rb") as f: - pem_bytes = f.read() - - # payload = {jwt_header, jwt_body} - private_key = serialization.load_pem_private_key(pem_bytes, password=None, backend=default_backend()) - encoded = jwt.encode(payload, private_key, algorithm="RS256") - settings.Revolut.JWT = encoded - settings.write() - - def get_access_token(self): - """ - Get an access token with our JWT. - :return: True or False - :rtype: bool - """ - headers = {"Content-Type": "application/x-www-form-urlencoded"} - data = { - "grant_type": "authorization_code", - "code": settings.Revolut.AuthCode, - "client_id": settings.Revolut.ClientID, - "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - "client_assertion": settings.Revolut.JWT, - } - r = requests.post(f"{settings.Revolut.Base}/auth/token", data=data, headers=headers) - try: - parsed = r.json() - except JSONDecodeError: - self.log.error("Error parsing access token response: {content}", content=r.content) - return False - if r.status_code == 200: - try: - settings.Revolut.RefreshToken = parsed["refresh_token"] - settings.Revolut.SetupToken = "0" - settings.write() - self.log.info("Refreshed refresh token - Revolut") - self.token = parsed["access_token"] - self.log.info("Refreshed access token - Revolut") - except KeyError: - self.log.error(f"Token authorization didn't contain refresh or access token: {parsed}", parsed=parsed) - return False - else: - self.log.error(f"Cannot refresh token: {parsed}", parsed=parsed) - return False - - def get_new_token(self, fail=False): - """ - Get a new access token with the refresh token. - :param fail: whether to exit() if this fails - :type fail: bool - :return: True or False - :rtype: bool - """ - headers = {"Content-Type": "application/x-www-form-urlencoded"} - data = { - "grant_type": "refresh_token", - "refresh_token": settings.Revolut.RefreshToken, - "client_id": settings.Revolut.ClientID, - "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - "client_assertion": settings.Revolut.JWT, - } - r = requests.post(f"{settings.Revolut.Base}/auth/token", data=data, headers=headers) - try: - parsed = r.json() - except JSONDecodeError: - if fail: - exit() - return False - if r.status_code == 200: - if "access_token" in parsed.keys(): - self.token = parsed["access_token"] - self.log.info("Refreshed access token - Revolut") - return True - else: - self.log.error(f"Token refresh didn't contain access token: {parsed}", parsed=parsed) - if fail: - exit() - return False - else: - self.log.error(f"Cannot refresh token: {parsed}", parsed=parsed) - if fail: - exit() - return False - - def setup_webhook(self): - """ - Check the webhook we have set up in Revolut. - Set up configured webhook if not already set up. - :return: True or False - :rtype: bool - """ - webhook = self.get_webhook() - if "url" in webhook.keys(): - if webhook["url"] == settings.Revolut.WebhookURL: - self.log.info("Webhook exists - skipping setup: {url}", url=webhook["url"]) - return True # Webhook already exists - self.log.info("Setting up webhook: {url}", url=settings.Revolut.WebhookURL) - headers = {"Authorization": f"Bearer {self.token}"} - data = {"url": settings.Revolut.WebhookURL} - r = requests.post(f"{settings.Revolut.Base}/webhook", data=dumps(data), headers=headers) - if r.status_code == 204: - self.log.info("Set up webhook: {url}", url=settings.Revolut.WebhookURL) - return True - else: - parsed = r.json() - self.log.info("Failed setting up webhook: {parsed}", parsed=parsed) - return False - - def get_webhook(self): - """ - Get the webhook address active in Revolut. - :return: dict of hook with key url or False - :rtype: dict or bool - """ - headers = {"Authorization": f"Bearer {self.token}"} - r = requests.get(f"{settings.Revolut.Base}/webhook", headers=headers) - if r.status_code == 200: - parsed = r.json() - return parsed - elif r.status_code == 404: - return {} - else: - self.log.error("Cannot get webhook: {content}", r.content) - return False - - def accounts(self): - """ - Get the details and balances of all Revolut accounts. - :return: account details - :rtype: dict - """ - headers = {"Authorization": f"Bearer {self.token}"} - r = requests.get(f"{settings.Revolut.Base}/accounts", headers=headers) - if r.status_code == 200: - return r.json() - else: - self.log.error("Error getting accounts: {content}", content=r.content) - return False - - def get_total_usd(self): - rates = self.money.get_rates_all() - accounts = self.accounts() - if not accounts: - return False - total_usd = 0 - for account in accounts: - if account["currency"] == "USD": - total_usd += account["balance"] - else: - total_usd += account["balance"] / rates[account["currency"]] - return total_usd - - def convert(self, from_account_id, from_currency, to_account_id, to_currency, sell_amount): - """ - Convert currency. - :param sell_currency: currency to sell - :param buy_currency: currency to buy - :param sell_amount: amount of currency to sell - """ - reference = "".join(choices(ascii_uppercase, k=5)) - headers = {"Authorization": f"Bearer {self.token}"} - data = { - "from": { - "account_id": from_account_id, - "currency": from_currency, - "amount": sell_amount, - }, - "to": { - "account_id": to_account_id, - "currency": to_currency, - }, - "request_id": reference, - } - r = requests.post(f"{settings.Revolut.Base}/exchange", headers=headers, data=dumps(data)) - if r.status_code == 200: - return r.json() - else: - self.log.error("Error converting balance: {content}", content=r.content) - return False - - def shuffle(self, currency): - """ - Exchange money in all accounts to the given currency. - :param currency: the currency to convert all our funds to - """ - accounts = self.accounts() - - # Find given currency account - for account in accounts: - if account["currency"] == currency: - if account["state"] == "active" and account["public"] is True: - dest_account = account - # Remove this account - accounts.remove(dest_account) - break - - for account in accounts: - if account["balance"] > 0: - self.convert(account["id"], account["currency"], dest_account["id"], dest_account["currency"], account["balance"]) - - return True