# Twisted/Klein imports from twisted.logger import Logger from twisted.internet.task import LoopingCall # Other library imports from json import loads from forex_python.converter import CurrencyRates from agoradesk_py.agoradesk import AgoraDesk # Project imports from settings import settings class Agora(object): """ AgoraDesk API handler. """ def __init__(self): """ Initialise the AgoraDesk and CurrencyRates APIs. Initialise the last_dash storage for detecting new trades. """ self.log = Logger("agora") self.agora = AgoraDesk(settings.Agora.Token) self.cr = CurrencyRates() # Cache for detecting new trades self.last_dash = set() # Cache for detecting new messages self.last_messages = {} def set_irc(self, irc): self.irc = irc def set_tx(self, tx): self.tx = tx def setup_loop(self): """ Set up the LoopingCall to get all active trades and messages. """ self.lc_dash = LoopingCall(self.dashboard) self.lc_dash.start(int(settings.Agora.RefreshSec)) def dashboard(self, send_irc=True, formatted=False): """ Returns a dict for the dashboard. Calls hooks to parse dashboard info and get all contact messages. :return: dict of dashboard info :rtype: dict """ dash = self.agora.dashboard_seller() dash_tmp = {} if "data" not in dash["response"].keys(): self.log.error("Data not in dashboard response: {content}", content=dash) return False if dash["response"]["data"]["contact_count"] > 0: for contact in dash["response"]["data"]["contact_list"]: contact_id = contact["data"]["contact_id"] dash_tmp[contact_id] = contact fmt = self.dashboard_hook(dash_tmp) self.get_all_messages_hook(dash_tmp) if formatted: return fmt return dash_tmp def dashboard_hook(self, dash, send_irc=True): """ Get information about our open trades. Post new trades to IRC and cache trades for the future. :return: human readable list of strings about our trades or False :rtype: list or bool """ dash_tmp = [] current_trades = [] for contact_id, contact in dash.items(): current_trades.append(contact_id) buyer = contact["data"]["buyer"]["username"] amount = contact["data"]["amount"] amount_xmr = contact["data"]["amount_xmr"] currency = contact["data"]["currency"] if not contact["data"]["is_selling"]: continue reference = self.tx.tx_to_ref(contact_id) if not reference: reference = "not_set" if contact_id not in self.last_dash: self.tx.new_trade(contact_id, buyer, currency, amount, amount_xmr) if send_irc: self.irc.client.msg( self.irc.client.channel, f"AUTO {contact_id}: {buyer} {amount}{currency} {amount_xmr}XMR {reference}" ) dash_tmp.append(f"{contact_id}: {buyer} {amount}{currency} {amount_xmr}XMR {reference}") self.last_dash.add(contact_id) # Purge old trades from cache for trade in list(self.last_dash): # We're removing from the list on the fly if trade not in current_trades: self.last_dash.remove(trade) return dash_tmp def dashboard_release_urls(self): """ Get information about our open trades. Post new trades to IRC and cache trades for the future. :return: human readable list of strings about our trades or False :rtype: list or bool """ dash = self.agora.dashboard_seller() dash_tmp = [] if "data" not in dash["response"]: self.log.error("Data not in dashboard response: {content}", content=dash) return False if dash["response"]["data"]["contact_count"] > 0: for contact in dash["response"]["data"]["contact_list"]: contact_id = contact["data"]["contact_id"] buyer = contact["data"]["buyer"]["username"] amount = contact["data"]["amount"] amount_xmr = contact["data"]["amount_xmr"] currency = contact["data"]["currency"] release_url = contact["actions"]["release_url"] if not contact["data"]["is_selling"]: continue dash_tmp.append(f"{contact_id}: {buyer} {amount}{currency} {amount_xmr}XMR {release_url}") return dash_tmp def get_messages(self, contact_id, send_irc=True): """ Get messages for a certain trade ID. Post new messages to IRC and cache messages for the future. :param contact_id: trade/contact ID :return: list of messages :rtype: list """ messages = self.agora.contact_messages(contact_id) messages_tmp = [] for message in messages["response"]["data"]["message_list"]: messages_tmp.append(f"({message['sender']['username']}): {message['msg']}") if send_irc: if contact_id in self.last_messages: if not len(self.last_messages[contact_id]) == len(messages_tmp): difference = set(messages_tmp) ^ self.last_messages[contact_id] for x in difference: self.irc.client.msg(self.irc.client.channel, f"AUTO {contact_id}: {x}") else: self.last_messages[contact_id] = messages_tmp for x in messages_tmp: self.irc.client.msg(self.irc.client.channel, f"NEW {contact_id}: {x}") return messages_tmp def get_all_messages(self, send_irc=True): """ Get all messages for all open trades. Wrapper for passing in dashboard. :return: dict of lists keyed by trade/contact ID :rtype: dict with lists """ dash = self.dashboard() if dash is None: return False return self.get_all_messages_hook(dash, send_irc=send_irc) def get_all_messages_hook(self, dash, send_irc=True): """ Get all messages for all open trades. :return: dict of lists keyed by trade/contact ID :rtype: dict with lists """ messages_tmp = {} for contact_id in dash: messages = self.get_messages(contact_id, send_irc=send_irc) messages_tmp[contact_id] = messages # Purge old trades from cache for trade in list(self.last_messages): # We're removing from the list on the fly if trade not in messages_tmp: del self.last_messages[trade] return messages_tmp def get_ads(self): """ Get information about our ads. :return: data about our open ads :rtype: dict """ ads = self.agora.ads() return ads def get_rates_all(self): """ Get all rates that pair with USD. :return: dictionary of USD/XXX rates :rtype: dict """ rates = self.cr.get_rates("USD") return rates def get_acceptable_margins(self, currency, amount): """ Get the minimum and maximum amounts we would accept a trade for. :param currency: currency code :param amount: amount :return: (min, max) :rtype: tuple """ rates = self.get_rates_all() if currency == "USD": min_amount = amount - float(settings.Agora.AcceptableUSDMargin) max_amount = amount + float(settings.Agora.AcceptableUSDMargin) return (min_amount, max_amount) amount_usd = amount / rates[currency] min_usd = amount_usd - float(settings.Agora.AcceptableUSDMargin) max_usd = amount_usd + float(settings.Agora.AcceptableUSDMargin) min_local = min_usd * rates[currency] max_local = max_usd * rates[currency] return (min_local, max_local) def create_ad(self, countrycode, currency): """ Post an ad in a country with a given currency. Convert the min and max amounts from settings to the given currency with CurrencyRates. :param countrycode: country code :param currency: currency code :type countrycode: string :type currency: string :return: data about created object or error :rtype: dict """ rates = self.get_rates_all() if currency == "USD": min_amount = float(settings.Agora.MinUSD) max_amount = float(settings.Agora.MaxUSD) else: min_amount = rates[currency] * float(settings.Agora.MinUSD) max_amount = rates[currency] * float(settings.Agora.MaxUSD) price_formula = f"coingeckoxmrusd*usd{currency.lower()}*{settings.Agora.Margin}" # price_formula = f"coingeckoxmrusd*{settings.Agora.Margin}" ad = settings.Agora.Ad ad = ad.replace("\\t", "\t") ad = self.agora.ad_create( country_code=countrycode, currency=currency, trade_type="ONLINE_SELL", asset="XMR", price_equation=price_formula, track_max_amount=False, require_trusted_by_advertiser=False, # verified_email_required = False, online_provider="REVOLUT", msg=settings.Agora.Ad, min_amount=min_amount, max_amount=max_amount, payment_method_details=settings.Agora.PaymentMethodDetails, # require_feedback_score = 0, account_info=settings.Agora.PaymentDetails, ) return ad def dist_countries(self): """ Distribute our advert into all countries listed in the config. :return: True or False :rtype: bool """ for currency, countrycode in loads(settings.Agora.DistList): rtrn = self.create_ad(countrycode, currency) if not rtrn: return False return True