Implement ad management

This commit is contained in:
Mark Veidemanis 2023-03-10 02:21:36 +00:00
parent de559f8c40
commit 3d43107586
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
12 changed files with 500 additions and 199 deletions

View File

@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView
from django.urls import include, path from django.urls import include, path
from two_factor.urls import urlpatterns as tf_urls from two_factor.urls import urlpatterns as tf_urls
from core.views import aggregators, banks, base, notifications, platforms from core.views import ads, aggregators, banks, base, notifications, platforms
# from core.views.stripe_callbacks import Callback # from core.views.stripe_callbacks import Callback
@ -149,4 +149,35 @@ urlpatterns = [
platforms.PlatformTrades.as_view(), platforms.PlatformTrades.as_view(),
name="trades", name="trades",
), ),
# Ads
path(
"ads/<str:type>/",
ads.AdList.as_view(),
name="ads",
),
path(
"ads/<str:type>/create/",
ads.AdCreate.as_view(),
name="ad_create",
),
path(
"ads/<str:type>/update/<str:pk>/",
ads.AdUpdate.as_view(),
name="ad_update",
),
path(
"ads/<str:type>/delete/<str:pk>/",
ads.AdDelete.as_view(),
name="ad_delete",
),
path(
"ops/ads/dist/",
ads.AdDist.as_view(),
name="ad_dist",
),
path(
"ops/ads/redist/",
ads.AdRedist.as_view(),
name="ad_redist",
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -74,7 +74,6 @@ class LocalPlatformClient(ABC):
# dash = await self.api.dashboard() # dash = await self.api.dashboard()
dash = await self.call("dashboard") dash = await self.call("dashboard")
print("DASH22", dash)
# if dash["response"] is None: # if dash["response"] is None:
# return False # return False
dash_tmp = {} dash_tmp = {}
@ -110,9 +109,9 @@ class LocalPlatformClient(ABC):
# reference = db.tx_to_ref(contact_id) # reference = db.tx_to_ref(contact_id)
# buyer = contact["data"]["buyer"]["username"] # buyer = contact["data"]["buyer"]["username"]
# amount = contact["data"]["amount"] # amount = contact["data"]["amount"]
# if self.platform == "agora": # if self.name == "agora":
# asset = contact["data"]["advertisement"]["asset"] # asset = contact["data"]["advertisement"]["asset"]
# elif self.platform == "lbtc": # elif self.name == "lbtc":
# asset = "BTC" # asset = "BTC"
# if asset == "XMR": # if asset == "XMR":
# amount_crypto = contact["data"]["amount_xmr"] # amount_crypto = contact["data"]["amount_xmr"]
@ -124,7 +123,7 @@ class LocalPlatformClient(ABC):
# continue # continue
# rtrn.append( # rtrn.append(
# ( # (
# f"[#] [{reference}] ({self.platform}) <{buyer}>" # f"[#] [{reference}] ({self.name}) <{buyer}>"
# f" {amount}{currency} {provider} {amount_crypto}{asset}" # f" {amount}{currency} {provider} {amount_crypto}{asset}"
# ) # )
# ) # )
@ -146,9 +145,9 @@ class LocalPlatformClient(ABC):
current_trades.append(reference) current_trades.append(reference)
buyer = contact["data"]["buyer"]["username"] buyer = contact["data"]["buyer"]["username"]
amount = contact["data"]["amount"] amount = contact["data"]["amount"]
if self.platform == "agora": if self.name == "agora":
asset = contact["data"]["advertisement"]["asset"] asset = contact["data"]["advertisement"]["asset"]
elif self.platform == "lbtc": elif self.name == "lbtc":
asset = "BTC" asset = "BTC"
provider = contact["data"]["advertisement"]["payment_method"] provider = contact["data"]["advertisement"]["payment_method"]
if asset == "XMR": if asset == "XMR":
@ -160,7 +159,7 @@ class LocalPlatformClient(ABC):
continue continue
if reference not in self.last_dash: if reference not in self.last_dash:
reference = await self.new_trade( reference = await self.new_trade(
self.platform, self.name,
asset, asset,
contact_id, contact_id,
buyer, buyer,
@ -175,7 +174,7 @@ class LocalPlatformClient(ABC):
# Let us know there is a new trade # Let us know there is a new trade
title = "New trade" title = "New trade"
message = ( message = (
f"[#] [{reference}] ({self.platform}) <{buyer}>" f"[#] [{reference}] ({self.name}) <{buyer}>"
f" {amount}{currency} {provider} {amount_crypto}{asset}" f" {amount}{currency} {provider} {amount_crypto}{asset}"
) )
await notify.sendmsg(self.instance.user, message, title=title) await notify.sendmsg(self.instance.user, message, title=title)
@ -188,7 +187,7 @@ class LocalPlatformClient(ABC):
self.last_dash.remove(ref) self.last_dash.remove(ref)
if reference and reference not in current_trades: if reference and reference not in current_trades:
current_trades.append(reference) current_trades.append(reference)
messages = await db.cleanup(self.platform, current_trades) messages = await db.cleanup(self.name, current_trades)
for message in messages: for message in messages:
await notify.sendmsg(self.instance.user, message, title="Cleanup") await notify.sendmsg(self.instance.user, message, title="Cleanup")
@ -225,7 +224,7 @@ class LocalPlatformClient(ABC):
if reference in self.last_messages: if reference in self.last_messages:
if not [user, message] in self.last_messages[reference]: if not [user, message] in self.last_messages[reference]:
self.irc.sendmsg( self.irc.sendmsg(
f"[{reference}] ({self.platform}) <{user}> {message}" f"[{reference}] ({self.name}) <{user}> {message}"
) )
# Append sent messages to last_messages so we don't send # Append sent messages to last_messages so we don't send
# them again # them again
@ -234,7 +233,7 @@ class LocalPlatformClient(ABC):
self.last_messages[reference] = [[user, message]] self.last_messages[reference] = [[user, message]]
for x in messages_tmp[reference]: for x in messages_tmp[reference]:
self.irc.sendmsg( self.irc.sendmsg(
f"[{reference}] ({self.platform}) <{user}> {message}" f"[{reference}] ({self.name}) <{user}> {message}"
) )
# Purge old trades from cache # Purge old trades from cache
@ -245,7 +244,7 @@ class LocalPlatformClient(ABC):
return messages_tmp return messages_tmp
async def enum_ad_ids(self, page=0): async def enum_ad_ids(self, page=0):
if self.platform == "lbtc" and page == 0: if self.name == "lbtc" and page == 0:
page = 1 page = 1
ads = await self.api.ads(page=page) ads = await self.api.ads(page=page)
# ads = await self.api._api_call(api_method="ads", query_values={"page": page}) # ads = await self.api._api_call(api_method="ads", query_values={"page": page})
@ -269,7 +268,7 @@ class LocalPlatformClient(ABC):
return ads_total return ads_total
async def enum_ads(self, requested_asset=None, page=0): async def enum_ads(self, requested_asset=None, page=0):
if self.platform == "lbtc" and page == 0: if self.name == "lbtc" and page == 0:
page = 1 page = 1
query_values = {"page": page} query_values = {"page": page}
if requested_asset: if requested_asset:
@ -284,9 +283,9 @@ class LocalPlatformClient(ABC):
if not ads["response"]: if not ads["response"]:
return False return False
for ad in ads["response"]["data"]["ad_list"]: for ad in ads["response"]["data"]["ad_list"]:
if self.platform == "agora": if self.name == "agora":
asset = ad["data"]["asset"] asset = ad["data"]["asset"]
elif self.platform == "lbtc": elif self.name == "lbtc":
asset = "BTC" asset = "BTC"
ad_id = ad["data"]["ad_id"] ad_id = ad["data"]["ad_id"]
country = ad["data"]["countrycode"] country = ad["data"]["countrycode"]
@ -330,7 +329,7 @@ class LocalPlatformClient(ABC):
return sec_ago_date < 172800 return sec_ago_date < 172800
async def enum_public_ads(self, asset, currency, providers=None, page=0): async def enum_public_ads(self, asset, currency, providers=None, page=0):
if self.platform == "lbtc" and page == 0: if self.name == "lbtc" and page == 0:
page = 1 page = 1
to_return = [] to_return = []
# if asset == "XMR": # if asset == "XMR":
@ -363,7 +362,7 @@ class LocalPlatformClient(ABC):
return False return False
for ad in ads["response"]["data"]["ad_list"]: for ad in ads["response"]["data"]["ad_list"]:
provider = ad["data"]["online_provider"] provider = ad["data"]["online_provider"]
if self.platform == "lbtc": if self.name == "lbtc":
provider_test = self.map_provider(provider) provider_test = self.map_provider(provider)
else: else:
provider_test = provider provider_test = provider
@ -423,7 +422,7 @@ class LocalPlatformClient(ABC):
return False return False
# Get the ads to update # Get the ads to update
to_update = self.get_new_ad_equations(self.platform, public_ads, assets) to_update = self.get_new_ad_equations(self.name, public_ads, assets)
await self.slow_ad_update(to_update) await self.slow_ad_update(to_update)
async def get_all_public_ads(self, assets=None, currencies=None, providers=None): async def get_all_public_ads(self, assets=None, currencies=None, providers=None):
@ -438,18 +437,11 @@ class LocalPlatformClient(ABC):
"BTC": "bitcoin", "BTC": "bitcoin",
} }
if not assets: assets = assets or self.instance.ads_assets
assets = self.get_all_assets(self.platform)
# Get all currencies we have ads for, deduplicated # Get all currencies we have ads for, deduplicated
if not currencies:
currencies = self.get_all_currencies(self.platform) providers = providers or self.instance.ads_providers
if not providers: currencies = currencies or self.instance.currencies
providers = self.get_all_providers(self.platform)
sinks_currencies = self.currencies
supported_currencies = [
currency for currency in currencies if currency in sinks_currencies
]
currencies = supported_currencies
# We want to get the ads for each of these currencies and return the result # We want to get the ads for each of these currencies and return the result
rates = await money.cg.get_price( rates = await money.cg.get_price(
ids=["monero", "bitcoin"], vs_currencies=currencies ids=["monero", "bitcoin"], vs_currencies=currencies
@ -466,7 +458,7 @@ class LocalPlatformClient(ABC):
if not ads_list: if not ads_list:
log.debug("Error getting ads list.") log.debug("Error getting ads list.")
continue continue
ads = await money.lookup_rates(self.platform, ads_list, rates=rates) ads = await money.lookup_rates(self.name, ads_list, rates=rates)
if not ads: if not ads:
log.debug("Error lookup up rates.") log.debug("Error lookup up rates.")
continue continue
@ -493,7 +485,7 @@ class LocalPlatformClient(ABC):
"margin": ad[6], "margin": ad[6],
"ts": str(datetime.now().isoformat()), "ts": str(datetime.now().isoformat()),
"xtype": msgtype, "xtype": msgtype,
"market": self.platform, "market": self.name,
"type": "platform", "type": "platform",
"user_id": self.instance.user.id, "user_id": self.instance.user.id,
"platform_id": self.instance.id, "platform_id": self.instance.id,
@ -568,6 +560,7 @@ class LocalPlatformClient(ABC):
currency, currency,
provider, provider,
payment_details, payment_details,
ad,
visible=None, visible=None,
edit=False, edit=False,
ad_id=None, ad_id=None,
@ -590,19 +583,26 @@ class LocalPlatformClient(ABC):
if payment_details: if payment_details:
payment_details_text = self.format_payment_details( payment_details_text = self.format_payment_details(
currency, payment_details currency,
payment_details,
ad,
) )
ad_text = self.format_ad(asset, currency, payment_details_text) ad_text = self.format_ad(asset, currency, payment_details_text, ad)
min_amount, max_amount = money.get_minmax(self.platform, asset, currency) min_amount, max_amount = await money.get_minmax(
if self.platform == "lbtc": self.instance.min_trade_size_usd,
self.instance.max_trade_size_usd,
asset,
currency,
)
if self.name == "lbtc":
bank_name = payment_details["bank"] bank_name = payment_details["bank"]
# if self.platform == "agora": # if self.name == "agora":
price_formula = ( price_formula = (
f"coingecko{asset.lower()}usd*" f"coingecko{asset.lower()}usd*"
f"usd{currency.lower()}*{self.instance.margin}" f"usd{currency.lower()}*{self.instance.margin}"
) )
# elif self.platform == "lbtc": # elif self.name == "lbtc":
# price_formula = f"btc_in_usd*{self.instance.margin}*USD_in_{currency}" # price_formula = f"btc_in_usd*{self.instance.margin}*USD_in_{currency}"
form = { form = {
@ -616,13 +616,12 @@ class LocalPlatformClient(ABC):
"online_provider": provider, "online_provider": provider,
"require_feedback_score": 0, "require_feedback_score": 0,
} }
if self.platform == "agora": if self.name == "agora":
form["asset"] = asset form["asset"] = asset
form["payment_method_details"] = "Revolut" form["payment_method_details"] = ad.payment_method_details
form["online_provider"] = provider form["online_provider"] = provider
elif self.platform == "lbtc": elif self.name == "lbtc":
form["online_provider"] = self.map_provider(provider, reverse=True) form["online_provider"] = self.map_provider(provider, reverse=True)
if visible is False: if visible is False:
form["visible"] = False form["visible"] = False
elif visible is True: elif visible is True:
@ -632,27 +631,28 @@ class LocalPlatformClient(ABC):
form["msg"] = ad_text form["msg"] = ad_text
form["min_amount"] = round(min_amount, 2) form["min_amount"] = round(min_amount, 2)
form["max_amount"] = round(max_amount, 2) form["max_amount"] = round(max_amount, 2)
if self.platform == "lbtc": if self.name == "lbtc":
form["bank_name"] = bank_name form["bank_name"] = bank_name
if edit: if edit:
ad = await self.api.ad(ad_id=ad_id, **form) ad = await self.api.ad(ad_id=ad_id, **form)
else: else:
ad = await self.api.ad_create(**form) ad = await self.api.ad_create(**form)
return ad return ad
async def dist_countries(self, filter_asset=None): async def dist_countries(self, ad, filter_asset=None):
""" """
Distribute our advert into all countries and providers listed in the config. Distribute our advert into all countries and providers listed in the config.
Exits on errors. Exits on errors.
:return: False or dict with response :return: False or dict with response
:rtype: bool or dict :rtype: bool or dict
""" """
dist_list = list(self.create_distribution_list(self.platform, filter_asset)) dist_list = list(self.create_distribution_list(self.name, ad, filter_asset))
our_ads = await self.enum_ads() our_ads = await self.enum_ads()
( (
supported_currencies, supported_currencies,
account_info, account_info,
) = self.get_valid_account_details(self.platform) ) = self.get_valid_account_details(self.name)
# Let's get rid of the ad IDs and make it a tuple like dist_list # Let's get rid of the ad IDs and make it a tuple like dist_list
our_ads = [(x[0], x[2], x[3], x[4]) for x in our_ads] our_ads = [(x[0], x[2], x[3], x[4]) for x in our_ads]
if not our_ads: if not our_ads:
@ -669,6 +669,8 @@ class LocalPlatformClient(ABC):
currency, currency,
provider, provider,
payment_details=account_info[currency], payment_details=account_info[currency],
ad=ad,
visible=ad.visible,
) )
# Bail on first error, let's not continue # Bail on first error, let's not continue
if rtrn is False: if rtrn is False:
@ -676,7 +678,7 @@ class LocalPlatformClient(ABC):
to_return.append(rtrn) to_return.append(rtrn)
return to_return return to_return
async def redist_countries(self): async def redist_countries(self, ad):
""" """
Redistribute our advert details into all our listed adverts. Redistribute our advert details into all our listed adverts.
This will edit all ads and update the details. Only works if we have already This will edit all ads and update the details. Only works if we have already
@ -690,12 +692,16 @@ class LocalPlatformClient(ABC):
( (
supported_currencies, supported_currencies,
account_info, account_info,
) = self.get_valid_account_details(self.platform) ) = self.get_valid_account_details(self.name)
if not our_ads: if not our_ads:
log.error("Could not get our ads.") log.error("Could not get our ads.")
return False return False
to_return = [] to_return = []
for asset, ad_id, countrycode, currency, provider in our_ads: for asset, ad_id, countrycode, currency, provider in our_ads:
if asset not in self.instance.ads_assets:
continue
if provider not in self.instance.ads_providers:
continue
if currency in supported_currencies: if currency in supported_currencies:
rtrn = await self.create_ad( rtrn = await self.create_ad(
asset, asset,
@ -703,6 +709,8 @@ class LocalPlatformClient(ABC):
currency, currency,
provider, provider,
payment_details=account_info[currency], payment_details=account_info[currency],
ad=ad,
visible=ad.visible,
edit=True, edit=True,
ad_id=ad_id, ad_id=ad_id,
) )
@ -907,7 +915,7 @@ class LocalPlatformClient(ABC):
f"When sending the payment please use reference code: {reference}", f"When sending the payment please use reference code: {reference}",
) )
async def send_bank_details(self, platform, currency, trade_id): async def send_bank_details(self, platform, currency, trade_id, ad):
""" """
Send the bank details to a trade. Send the bank details to a trade.
""" """
@ -916,7 +924,7 @@ class LocalPlatformClient(ABC):
if send_setting == "1": if send_setting == "1":
account_info = self.get_matching_account_details(platform, currency) account_info = self.get_matching_account_details(platform, currency)
formatted_account_info = self.format_payment_details( formatted_account_info = self.format_payment_details(
currency, account_info, real=True currency, account_info, ad, real=True
) )
if not formatted_account_info: if not formatted_account_info:
log.error(f"Payment info invalid: {formatted_account_info}") log.error(f"Payment info invalid: {formatted_account_info}")
@ -927,10 +935,10 @@ class LocalPlatformClient(ABC):
) )
def get_all_assets(self, platform): def get_all_assets(self, platform):
return ["XMR"] # TODO raise Exception
def get_all_providers(self, platform): def get_all_providers(self, platform):
return ["REVOLUT"] # TODO raise Exception
def get_all_currencies(self, platform): def get_all_currencies(self, platform):
raise Exception raise Exception
@ -957,10 +965,9 @@ class LocalPlatformClient(ABC):
# call slow update. # call slow update.
# (asset, currency, provider) # (asset, currency, provider)
if not assets: assets = self.instance.ads_assets
assets = self.get_all_assets(platform)
currencies = self.currencies currencies = self.currencies
providers = self.get_all_providers(platform) providers = self.instance.ads_providers
# if platform == "lbtc": # if platform == "lbtc":
# providers = [ # providers = [
# self.sources.lbtc.map_provider(x, reverse=True) for x in providers # self.sources.lbtc.map_provider(x, reverse=True) for x in providers
@ -1092,48 +1099,25 @@ class LocalPlatformClient(ABC):
# log.debug("Cheapest ad above our min that is not us: {x}", x=cheapest_ad) # log.debug("Cheapest ad above our min that is not us: {x}", x=cheapest_ad)
return cheapest_ad_margin return cheapest_ad_margin
def create_distribution_list(self, platform, filter_asset=None): def create_distribution_list(self, platform, ad, filter_asset=None):
""" """
Create a list for distribution of ads. Create a list for distribution of ads.
:return: generator of asset, countrycode, currency, provider :return: generator of asset, countrycode, currency, provider
:rtype: generator of tuples :rtype: generator of tuples
""" """
distlist = [ dist_list_raw = ad.dist_list
["AUD", "AU"], dist_list_lines = dist_list_raw.splitlines()
["BGN", "BG"],
["CAD", "CA"], distlist = []
["CHF", "CH"], for line in dist_list_lines:
["CZK", "CZ"], l_currency, l_country = line.split()
["DKK", "DK"], distlist.append([l_currency, l_country])
["GBP", "GB"],
["USD", "GB"],
["EUR", "GB"],
["USD", "US"],
["GBP", "US"],
["EUR", "US"],
["HKD", "HK"],
["HRK", "HR"],
["HUF", "HU"],
["ISK", "IS"],
["JPY", "JP"],
["MXN", "MX"],
["NOK", "NO"],
["NZD", "NZ"],
["PLN", "PL"],
["RON", "RO"],
["RUB", "RU"],
["SEK", "SE"],
["EUR", "SE"],
["SGD", "SG"],
["THB", "TH"],
["TRY", "TR"],
["ZAR", "ZA"],
]
# Iterate providers like REVOLUT, NATIONAL_BANK # Iterate providers like REVOLUT, NATIONAL_BANK
for provider in self.get_all_providers(platform): for provider in self.instance.ads_providers:
# Iterate assets like XMR, BTC # Iterate assets like XMR, BTC
for asset in self.get_all_assets(platform): for asset in self.instance.ads_assets:
# Iterate pairs of currency and country like EUR, GB # Iterate pairs of currency and country like EUR, GB
for currency, countrycode in distlist: for currency, countrycode in distlist:
if filter_asset: if filter_asset:
@ -1144,7 +1128,7 @@ class LocalPlatformClient(ABC):
def get_valid_account_details(self, platform): def get_valid_account_details(self, platform):
currencies = self.instance.currencies currencies = self.instance.currencies
account_info = self.sinks.account_info account_info = self.instance.account_info
currency_account_info_map = {} currency_account_info_map = {}
for currency in currencies: for currency in currencies:
for bank, accounts in account_info.items(): for bank, accounts in account_info.items():
@ -1153,7 +1137,7 @@ class LocalPlatformClient(ABC):
currency_account_info_map[currency] = account["account_number"] currency_account_info_map[currency] = account["account_number"]
currency_account_info_map[currency]["bank"] = bank.split("_")[0] currency_account_info_map[currency]["bank"] = bank.split("_")[0]
currency_account_info_map[currency]["recipient"] = account[ currency_account_info_map[currency]["recipient"] = account[
"recipient" "ownerName"
] ]
return (currencies, currency_account_info_map) return (currencies, currency_account_info_map)
@ -1166,104 +1150,104 @@ class LocalPlatformClient(ABC):
return False return False
return currency_account_info_map[currency] return currency_account_info_map[currency]
def _distribute_account_details(self, platform, currencies=None, account_info=None): # def _distribute_account_details(self, platform, currencies=None, account_info=None):
""" # """
Distribute account details for ads. # Distribute account details for ads.
We will disable ads we can't support. # We will disable ads we can't support.
""" # """
if not currencies: # if not currencies:
currencies = self.instance.currencies # currencies = self.instance.currencies
if not account_info: # if not account_info:
account_info = self.instance.account_info # account_info = self.instance.account_info
( # (
supported_currencies, # supported_currencies,
currency_account_info_map, # currency_account_info_map,
) = self.get_valid_account_details(platform) # ) = self.get_valid_account_details(platform)
# not_supported = [currency for currency in all_currencies if # # not_supported = [currency for currency in all_currencies if
# currency not in supported_currencies] # # currency not in supported_currencies]
our_ads = self.enum_ads() # our_ads = self.enum_ads()
supported_ads = [ad for ad in our_ads if ad[3] in supported_currencies] # supported_ads = [ad for ad in our_ads if ad[3] in supported_currencies]
not_supported_ads = [ad for ad in our_ads if ad[3] not in supported_currencies] # not_supported_ads = [ad for ad in our_ads if ad[3] not in supported_currencies]
for ad in supported_ads: # for ad in supported_ads:
asset = ad[0] # asset = ad[0]
countrycode = ad[2] # countrycode = ad[2]
currency = ad[3] # currency = ad[3]
provider = ad[4] # provider = ad[4]
payment_details = currency_account_info_map[currency] # payment_details = currency_account_info_map[currency]
ad_id = ad[1] # ad_id = ad[1]
self.create_ad( # self.create_ad(
asset, # asset,
countrycode, # countrycode,
currency, # currency,
provider, # provider,
payment_details, # payment_details,
visible=True, # visible=True,
edit=True, # edit=True,
ad_id=ad_id, # ad_id=ad_id,
) # )
for ad in not_supported_ads: # for ad in not_supported_ads:
asset = ad[0] # asset = ad[0]
countrycode = ad[2] # countrycode = ad[2]
currency = ad[3] # currency = ad[3]
provider = ad[4] # provider = ad[4]
ad_id = ad[1] # ad_id = ad[1]
self.create_ad( # self.create_ad(
asset, # asset,
countrycode, # countrycode,
currency, # currency,
provider, # provider,
payment_details=False, # payment_details=False,
visible=False, # visible=False,
edit=True, # edit=True,
ad_id=ad_id, # ad_id=ad_id,
) # )
def distribute_account_details(self, currencies=None, account_info=None): # def distribute_account_details(self, currencies=None, account_info=None):
""" # """
Helper to distribute the account details for all platforms. # Helper to distribute the account details for all platforms.
""" # """
platforms = "agora" # platforms = "agora"
for platform in platforms: # for platform in platforms:
self._distribute_account_details( # self._distribute_account_details(
platform, currencies=currencies, account_info=account_info # platform, currencies=currencies, account_info=account_info
) # )
def format_ad(self, asset, currency, payment_details_text): def format_ad(self, asset, currency, payment_details_text, ad):
""" """
Format the ad. Format the ad.
""" """
ad = settings.Platform.Ad # TODO ad_text = ad.text
# Substitute the currency # Substitute the currency
ad = ad.replace("$CURRENCY$", currency) ad_text = ad_text.replace("$CURRENCY$", currency)
# Substitute the asset # Substitute the asset
ad = ad.replace("$ASSET$", asset) ad_text = ad_text.replace("$ASSET$", asset)
# Substitute the payment details # Substitute the payment details
ad = ad.replace("$PAYMENT$", payment_details_text) ad_text = ad_text.replace("$PAYMENT$", payment_details_text)
# Strip extra tabs # Strip extra tabs
ad = ad.replace("\\t", "\t") ad_text = ad_text.replace("\\t", "\t")
return ad return ad_text
def format_payment_details(self, currency, payment_details, real=False): def format_payment_details(self, currency, payment_details, ad, real=False):
""" """
Format the payment details. Format the payment details.
""" """
if not payment_details: if not payment_details:
return False return False
if real: if real:
payment = settings.Platform.PaymentDetailsReal payment = ad.payment_details_real
else: else:
payment = settings.Platform.PaymentDetails payment = ad.payment_details
payment_text = "" payment_text = ""
for field, value in payment_details.items(): for field, value in payment_details.items():

View File

@ -1,12 +1,12 @@
"""See https://agoradesk.com/api-docs/v1.""" """See https://agoradesk.com/api-docs/v1."""
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
# Large API. Lots of lines can't be avoided. # Large API. Lots of lines can't be avoided.
import json
import logging import logging
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
import aiohttp import aiohttp
import arrow import arrow
import orjson
from core.util import logs from core.util import logs
@ -71,7 +71,7 @@ class AgoraDesk:
logger.debug("Headers : %s", headers) logger.debug("Headers : %s", headers)
logger.debug("HTTP Method : %s", http_method) logger.debug("HTTP Method : %s", http_method)
logger.debug("Query Values: %s", query_values) logger.debug("Query Values: %s", query_values)
logger.debug("Query Values as Json:\n%s", json.dumps(query_values)) logger.debug("Query Values as Json:\n%s", orjson.dumps(query_values))
result: Dict[str, Any] = { result: Dict[str, Any] = {
"success": False, "success": False,
@ -83,7 +83,7 @@ class AgoraDesk:
response = None response = None
if http_method == "POST": if http_method == "POST":
if query_values: if query_values:
cast["data"] = query_values cast["data"] = orjson.dumps(query_values)
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(api_call_url, **cast) as response_raw: async with session.post(api_call_url, **cast) as response_raw:
response = await response_raw.json() response = await response_raw.json()
@ -98,7 +98,6 @@ class AgoraDesk:
response = await response_raw.json() response = await response_raw.json()
status_code = response_raw.status status_code = response_raw.status
if response: if response:
print("YES RESPONSE", response)
logger.debug(response) logger.debug(response)
result["status"] = status_code result["status"] = status_code
if status_code == 200: if status_code == 200:
@ -501,7 +500,7 @@ class AgoraDesk:
params["lat"] = lat params["lat"] = lat
if lon: if lon:
params["lon"] = lon params["lon"] = lon
if visible: if visible is not None:
params["visible"] = True if visible else False params["visible"] = True if visible else False
return await self._api_call( return await self._api_call(

View File

@ -4,7 +4,15 @@ from django.core.exceptions import FieldDoesNotExist
from django.forms import ModelForm from django.forms import ModelForm
from mixins.restrictions import RestrictedFormMixin from mixins.restrictions import RestrictedFormMixin
from .models import Aggregator, NotificationSettings, Platform, User from .models import (
Ad,
Aggregator,
Asset,
NotificationSettings,
Platform,
Provider,
User,
)
# flake8: noqa: E501 # flake8: noqa: E501
@ -128,3 +136,64 @@ class PlatformForm(RestrictedFormMixin, ModelForm):
"no_reference_amount_check_max_usd": "When ticked, when no reference was found and a trade is higher than this amount, we will not accept payment even if it is the only one with this amount.", "no_reference_amount_check_max_usd": "When ticked, when no reference was found and a trade is higher than this amount, we will not accept payment even if it is the only one with this amount.",
"enabled": "Whether or not the platform connection is enabled.", "enabled": "Whether or not the platform connection is enabled.",
} }
class AdForm(RestrictedFormMixin, ModelForm):
def __init__(self, *args, **kwargs):
super(AdForm, self).__init__(*args, **kwargs)
class Meta:
model = Ad
fields = (
"name",
"text",
"payment_details",
"payment_details_real",
"payment_method_details",
"dist_list",
"asset_list",
"provider_list",
"platforms",
"aggregators",
"visible",
"enabled",
)
help_texts = {
"name": "The name of the aggregator connection.",
"text": "The content of the ad.",
"payment_details": "Shown before a user opens a trade.",
"payment_details_real": "Shown after a user opens a trade.",
"payment_method_details": "Shown in the list",
"dist_list": "Currency and country, space separated, one pair per line.",
"asset_list": "List of assets to distribute ads for.",
"provider_list": "List of providers to distribute ads for.",
"platforms": "Enabled platforms for this ad",
"aggregators": "Enabled aggregators for this ad",
"visible": "Whether or not this ad is visible.",
"enabled": "Whether or not this ad is enabled.",
}
asset_list = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(),
widget=forms.CheckboxSelectMultiple,
help_text=Meta.help_texts["asset_list"],
required=True,
)
provider_list = forms.ModelMultipleChoiceField(
queryset=Provider.objects.all(),
widget=forms.CheckboxSelectMultiple,
help_text=Meta.help_texts["provider_list"],
required=True,
)
platforms = forms.ModelMultipleChoiceField(
queryset=Platform.objects.all(),
widget=forms.CheckboxSelectMultiple,
help_text=Meta.help_texts["platforms"],
required=True,
)
aggregators = forms.ModelMultipleChoiceField(
queryset=Aggregator.objects.all(),
widget=forms.CheckboxSelectMultiple,
help_text=Meta.help_texts["aggregators"],
required=True,
)

View File

@ -30,7 +30,6 @@ class Money(object):
Set the logger. Set the logger.
Initialise the CoinGecko API. Initialise the CoinGecko API.
""" """
print("MONEY INIT")
self.cr = CurrencyRates() self.cr = CurrencyRates()
self.cg = AsyncCoinGeckoAPISession() self.cg = AsyncCoinGeckoAPISession()
auth = (settings.ELASTICSEARCH_USERNAME, settings.ELASTICSEARCH_PASSWORD) auth = (settings.ELASTICSEARCH_USERNAME, settings.ELASTICSEARCH_PASSWORD)
@ -111,7 +110,7 @@ class Money(object):
:return: dictionary of USD/XXX rates :return: dictionary of USD/XXX rates
:rtype: dict :rtype: dict
""" """
rates = await self.cr.get_rates("USD") rates = self.cr.get_rates("USD")
return rates return rates
async def get_acceptable_margins(self, platform, currency, amount): async def get_acceptable_margins(self, platform, currency, amount):
@ -135,18 +134,11 @@ class Money(object):
max_local = max_usd * rates[currency] max_local = max_usd * rates[currency]
return (min_local, max_local) return (min_local, max_local)
async def get_minmax(self, platform, asset, currency): async def get_minmax(self, min_usd, max_usd, asset, currency):
sets = util.get_settings(platform)
rates = await self.get_rates_all() rates = await self.get_rates_all()
if currency not in rates and not currency == "USD": if currency not in rates and not currency == "USD":
self.log.error(f"Can't create ad without rates: {currency}") self.log.error(f"Can't create ad without rates: {currency}")
return return
if asset == "XMR":
min_usd = float(sets.MinUSDXMR)
max_usd = float(sets.MaxUSDXMR)
elif asset == "BTC":
min_usd = float(sets.MinUSDBTC)
max_usd = float(sets.MaxUSDBTC)
if currency == "USD": if currency == "USD":
min_amount = min_usd min_amount = min_usd
max_amount = max_usd max_amount = max_usd

View File

@ -0,0 +1,33 @@
# Generated by Django 4.1.7 on 2023-03-10 00:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_asset_provider_ad'),
]
operations = [
migrations.AddField(
model_name='ad',
name='account_map',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='ad',
name='aggregators',
field=models.ManyToManyField(to='core.aggregator'),
),
migrations.AddField(
model_name='ad',
name='enabled',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='ad',
name='platforms',
field=models.ManyToManyField(to='core.platform'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-03-10 02:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0010_ad_account_map_ad_aggregators_ad_enabled_and_more'),
]
operations = [
migrations.AddField(
model_name='ad',
name='visible',
field=models.BooleanField(default=True),
),
]

View File

@ -55,7 +55,7 @@ class Aggregator(models.Model):
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
def __str__(self): def __str__(self):
return f"Aggregator ({self.service}) for {self.user}" return f"{self.name} ({self.get_service_display()})"
@classmethod @classmethod
def get_by_id(cls, obj_id, user): def get_by_id(cls, obj_id, user):
@ -122,6 +122,9 @@ class Platform(models.Model):
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
def __str__(self):
return self.name
@classmethod @classmethod
def get_for_user(cls, user): def get_for_user(cls, user):
return cls.objects.filter(user=user, enabled=True) return cls.objects.filter(user=user, enabled=True)
@ -134,16 +137,55 @@ class Platform(models.Model):
def account_info(self): def account_info(self):
return Aggregator.get_account_info_for_platform(self) return Aggregator.get_account_info_for_platform(self)
@property
def ads(self):
"""
Get all ads linked to this platform.
"""
return Ad.objects.filter(user=self.user, enabled=True, platforms=self)
@property
def ads_assets(self):
"""
Get all the assets of all the ads.
"""
assets = set()
for ad in self.ads:
for asset in ad.asset_list.all():
assets.add(asset.code)
return list(assets)
@property
def ads_providers(self):
"""
Get all the providers of all the ads.
"""
providers = set()
for ad in self.ads:
for provider in ad.provider_list.all():
providers.add(provider.code)
return list(providers)
class Asset(models.Model): class Asset(models.Model):
code = models.CharField(max_length=64) code = models.CharField(max_length=64)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
def __str__(self):
return f"{self.name} ({self.code})"
class Provider(models.Model): class Provider(models.Model):
code = models.CharField(max_length=64) code = models.CharField(max_length=64)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
def __str__(self):
return f"{self.name} ({self.code})"
class Ad(models.Model): class Ad(models.Model):
""" """
@ -169,19 +211,27 @@ class Ad(models.Model):
asset_list = models.ManyToManyField(Asset) asset_list = models.ManyToManyField(Asset)
provider_list = models.ManyToManyField(Provider) provider_list = models.ManyToManyField(Provider)
platforms = models.ManyToManyField(Platform)
aggregators = models.ManyToManyField(Aggregator)
# assets = { account_map = models.JSONField(default=dict)
# "XMR": "Monero",
# "BTC": "Bitcoin",
# }
# providers = {
# "REVOLUT": "Revolut",
# "NATIONAL_BANK": "Bank transfer",
# }
# for code, name in assets.items():
# if not Asset.objects.filter(code=code).exists():
# Asset.objects.create(code=code, name=name)
# for code, name in providers.items(): visible = models.BooleanField(default=True)
# if not Provider.objects.filter(code=code).exists(): enabled = models.BooleanField(default=True)
# Provider.objects.create(code=code, name=name)
assets = {
"XMR": "Monero",
"BTC": "Bitcoin",
}
providers = {
"REVOLUT": "Revolut",
"NATIONAL_BANK": "Bank transfer",
}
for code, name in assets.items():
if not Asset.objects.filter(code=code).exists():
Asset.objects.create(code=code, name=name)
for code, name in providers.items():
if not Provider.objects.filter(code=code).exists():
Provider.objects.create(code=code, name=name)

View File

@ -248,11 +248,11 @@
<a class="navbar-item" href="#"> <a class="navbar-item" href="#">
Wallets Wallets
</a> </a>
<a class="navbar-item" href="#"> <a class="navbar-item" href="{% url 'ads' type='page' %}">
Advert Configuration Ads
</a> </a>
<a class="navbar-item" href="#"> <a class="navbar-item" href="{% url 'ads' type='page' %}">
Advert Management Posted Ads
</a> </a>
<a class="navbar-item" href="#"> <a class="navbar-item" href="#">
Market Research Market Research

View File

@ -0,0 +1,79 @@
{% load cache %}
{% load cachalot cache %}
{% get_last_invalidation 'core.Ad' as last %}
{% include 'mixins/partials/notify.html' %}
{# cache 600 objects_ads request.user.id object_list type last #}
<table
class="table is-fullwidth is-hoverable"
hx-target="#{{ context_object_name }}-table"
id="{{ context_object_name }}-table"
hx-swap="outerHTML"
hx-trigger="{{ context_object_name_singular }}Event from:body"
hx-get="{{ list_url }}">
<thead>
<th>id</th>
<th>user</th>
<th>name</th>
<th>enabled</th>
<th>actions</th>
</thead>
{% for item in object_list %}
<tr>
<td>
<a
class="has-text-grey"
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ item.id }}/');">
<span class="icon" data-tooltip="Copy to clipboard">
<i class="fa-solid fa-copy" aria-hidden="true"></i>
</span>
</a>
</td>
<td>{{ item.user }}</td>
<td>{{ item.name }}</td>
<td>
{% if item.enabled %}
<span class="icon">
<i class="fa-solid fa-check"></i>
</span>
{% else %}
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
{% endif %}
</td>
<td>
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'ad_update' type=type pk=item.id %}"
hx-trigger="click"
hx-target="#{{ type }}s-here"
hx-swap="innerHTML"
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-pencil"></i>
</span>
</span>
</button>
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'ad_delete' type=type pk=item.id %}"
hx-trigger="click"
hx-target="#modals-here"
hx-swap="innerHTML"
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-xmark"></i>
</span>
</span>
</button>
</div>
</td>
</tr>
{% endfor %}
</table>
{# endcache #}

View File

@ -1,5 +1,4 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.views import View from django.views import View
@ -19,16 +18,64 @@ from core.util import logs
from core.views.helpers import synchronize_async_helper from core.views.helpers import synchronize_async_helper
class AdDist(LoginRequiredMixin, OTPRequiredMixin, View):
template_name = "mixins/partials/notify.html"
def get(self, request):
ads = Ad.objects.filter(user=request.user, enabled=True)
for ad in ads:
for platform in ad.platforms.all():
run = synchronize_async_helper(AgoraClient(platform))
synchronize_async_helper(run.dist_countries(ad))
context = {"class": "success", "message": "Distributing ads"}
return render(request, self.template_name, context)
class AdRedist(LoginRequiredMixin, OTPRequiredMixin, View):
template_name = "mixins/partials/notify.html"
def get(self, request):
ads = Ad.objects.filter(user=request.user, enabled=True)
for ad in ads:
for platform in ad.platforms.all():
run = synchronize_async_helper(AgoraClient(platform))
synchronize_async_helper(run.redist_countries(ad))
context = {"class": "success", "message": "Updating ads"}
return render(request, self.template_name, context)
class AdList(LoginRequiredMixin, OTPRequiredMixin, ObjectList): class AdList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
list_template = "partials/ad-list.html" list_template = "partials/ad-list.html"
model = Ad model = Ad
page_title = "List of ad connections" page_title = "List of ads"
list_url_name = "ads" list_url_name = "ads"
list_url_args = ["type"] list_url_args = ["type"]
submit_url_name = "ad_create" submit_url_name = "ad_create"
def get_context_data(self):
context = super().get_context_data()
self.extra_buttons = [
{
"url": reverse("ad_dist"),
"action": "distribute",
"method": "get",
"label": "Distribute ads",
"icon": "fa-solid fa-upload",
},
{
"url": reverse("ad_redist"),
"action": "redistribute",
"method": "get",
"label": "Update ads",
"icon": "fa-solid fa-refresh",
},
]
return context
class AdCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate): class AdCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate):
model = Ad model = Ad

View File

@ -36,7 +36,6 @@ class PlatformTrades(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
for platform in platforms: for platform in platforms:
run = synchronize_async_helper(AgoraClient(platform)) run = synchronize_async_helper(AgoraClient(platform))
dash = synchronize_async_helper(run.wrap_dashboard()) dash = synchronize_async_helper(run.wrap_dashboard())
print("DASH", dash)
total_trades[platform.name] = dash total_trades[platform.name] = dash
return total_trades return total_trades