Move IRC, commands and notify classes into a UX class
This commit is contained in:
parent
2423e4a066
commit
50c71703fd
|
@ -685,4 +685,4 @@ class Agora(object):
|
||||||
rtrn2 = self.agora.wallet_send_xmr(**send_cast)
|
rtrn2 = self.agora.wallet_send_xmr(**send_cast)
|
||||||
|
|
||||||
self.irc.sendmsg(f"Withdrawal: {rtrn1['success']} | {rtrn2['success']}")
|
self.irc.sendmsg(f"Withdrawal: {rtrn1['success']} | {rtrn2['success']}")
|
||||||
self.notify.notify_withdrawal(half_rounded)
|
self.ux.notify.notify_withdrawal(half_rounded)
|
||||||
|
|
|
@ -12,17 +12,16 @@ from signal import signal, SIGINT
|
||||||
# Project imports
|
# Project imports
|
||||||
from settings import settings
|
from settings import settings
|
||||||
import util
|
import util
|
||||||
|
|
||||||
|
# Old style classes
|
||||||
from agora import Agora
|
from agora import Agora
|
||||||
from transactions import Transactions
|
from transactions import Transactions
|
||||||
from irc import bot
|
|
||||||
from notify import Notify
|
|
||||||
from markets import Markets
|
from markets import Markets
|
||||||
from money import Money
|
from money import Money
|
||||||
|
|
||||||
# from sinks.nordigen import Nordigen
|
# New style classes
|
||||||
# from sinks.truelayer import TrueLayer
|
|
||||||
# from sinks.fidor import Fidor
|
|
||||||
import sinks
|
import sinks
|
||||||
|
import ux
|
||||||
|
|
||||||
init_map = None
|
init_map = None
|
||||||
|
|
||||||
|
@ -95,8 +94,7 @@ class WebApp(object):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
init_map = {
|
init_map = {
|
||||||
"notify": Notify(),
|
"ux": ux.UX(),
|
||||||
"irc": bot(),
|
|
||||||
"agora": Agora(),
|
"agora": Agora(),
|
||||||
"markets": Markets(),
|
"markets": Markets(),
|
||||||
"sinks": sinks.Sinks(),
|
"sinks": sinks.Sinks(),
|
||||||
|
@ -107,6 +105,11 @@ if __name__ == "__main__":
|
||||||
# Merge all classes into each other
|
# Merge all classes into each other
|
||||||
util.xmerge_attrs(init_map)
|
util.xmerge_attrs(init_map)
|
||||||
|
|
||||||
|
# Let the classes know they have been merged
|
||||||
|
for class_name, class_instance in init_map.items():
|
||||||
|
if hasattr(class_instance, "__xmerged__"):
|
||||||
|
class_instance.__xmerged__()
|
||||||
|
|
||||||
# Set up the loops to put data in ES
|
# Set up the loops to put data in ES
|
||||||
init_map["tx"].setup_loops()
|
init_map["tx"].setup_loops()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Twisted/Klein imports
|
||||||
|
from twisted.logger import Logger
|
||||||
|
|
||||||
|
# Other library imports
|
||||||
|
# import requests
|
||||||
|
# from json import dumps
|
||||||
|
|
||||||
|
# Project imports
|
||||||
|
# from settings import settings
|
||||||
|
import sinks.fidor
|
||||||
|
import sinks.nordigen
|
||||||
|
import sinks.truelayer
|
||||||
|
|
||||||
|
|
||||||
|
class Sinks(object):
|
||||||
|
"""
|
||||||
|
Class to manage calls to various sinks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.log = Logger("sinks")
|
||||||
|
self.fidor = sinks.fidor.Fidor()
|
||||||
|
self.nordigen = sinks.nordigen.Nordigen()
|
||||||
|
self.truelayer = sinks.truelayer.TrueLayer()
|
|
@ -45,8 +45,9 @@ class TestTransactions(TestCase):
|
||||||
self.transactions.irc = MagicMock()
|
self.transactions.irc = MagicMock()
|
||||||
self.transactions.irc.sendmsg = MagicMock()
|
self.transactions.irc.sendmsg = MagicMock()
|
||||||
self.transactions.release_funds = MagicMock()
|
self.transactions.release_funds = MagicMock()
|
||||||
self.transactions.notify = MagicMock()
|
self.transactions.ux = MagicMock()
|
||||||
self.transactions.notify.notify_complete_trade = MagicMock()
|
self.transactions.ux.notify = MagicMock()
|
||||||
|
self.transactions.ux.notify.notify_complete_trade = MagicMock()
|
||||||
|
|
||||||
# Mock the rates
|
# Mock the rates
|
||||||
self.transactions.money = MagicMock()
|
self.transactions.money = MagicMock()
|
||||||
|
|
|
@ -117,7 +117,7 @@ class Transactions(object):
|
||||||
r.hmset(f"tx.{txid}", stored_trade)
|
r.hmset(f"tx.{txid}", stored_trade)
|
||||||
reference = self.tx_to_ref(stored_trade["trade_id"])
|
reference = self.tx_to_ref(stored_trade["trade_id"])
|
||||||
self.release_funds(stored_trade["trade_id"], reference)
|
self.release_funds(stored_trade["trade_id"], reference)
|
||||||
self.notify.notify_complete_trade(stored_trade["amount"], stored_trade["currency"])
|
self.ux.notify.notify_complete_trade(stored_trade["amount"], stored_trade["currency"])
|
||||||
return
|
return
|
||||||
# If type not in inside and we haven't hit any more returns
|
# If type not in inside and we haven't hit any more returns
|
||||||
return
|
return
|
||||||
|
@ -267,7 +267,7 @@ class Transactions(object):
|
||||||
|
|
||||||
r.hmset(f"tx.{txid}", to_store)
|
r.hmset(f"tx.{txid}", to_store)
|
||||||
self.release_funds(stored_trade["id"], stored_trade["reference"])
|
self.release_funds(stored_trade["id"], stored_trade["reference"])
|
||||||
self.notify.notify_complete_trade(amount, currency)
|
self.ux.notify.notify_complete_trade(amount, currency)
|
||||||
|
|
||||||
def release_funds(self, trade_id, reference):
|
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)
|
self.log.info("All checks passed, releasing funds for {trade_id} {reference}", trade_id=trade_id, reference=reference)
|
||||||
|
@ -303,7 +303,7 @@ class Transactions(object):
|
||||||
self.log.info("Storing trade information: {info}", info=str(to_store))
|
self.log.info("Storing trade information: {info}", info=str(to_store))
|
||||||
r.hmset(f"trade.{reference}", to_store)
|
r.hmset(f"trade.{reference}", to_store)
|
||||||
self.irc.sendmsg(f"Generated reference for {trade_id}: {reference}")
|
self.irc.sendmsg(f"Generated reference for {trade_id}: {reference}")
|
||||||
self.notify.notify_new_trade(amount, currency)
|
self.ux.notify.notify_new_trade(amount, currency)
|
||||||
if settings.Agora.Send == "1":
|
if settings.Agora.Send == "1":
|
||||||
self.agora.agora.contact_message_post(trade_id, f"Hi! When sending the payment please use reference code: {reference}")
|
self.agora.agora.contact_message_post(trade_id, f"Hi! When sending the payment please use reference code: {reference}")
|
||||||
if existing_ref:
|
if existing_ref:
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Twisted/Klein imports
|
||||||
|
from twisted.logger import Logger
|
||||||
|
|
||||||
|
# Other library imports
|
||||||
|
# import requests
|
||||||
|
# from json import dumps
|
||||||
|
|
||||||
|
# Project imports
|
||||||
|
# from settings import settings
|
||||||
|
import util
|
||||||
|
|
||||||
|
import ux.irc
|
||||||
|
import ux.commands
|
||||||
|
import ux.notify
|
||||||
|
|
||||||
|
|
||||||
|
class UX(object):
|
||||||
|
"""
|
||||||
|
Class to manage calls to various user interfaces.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.log = Logger("ux")
|
||||||
|
|
||||||
|
self.irc = ux.irc.bot()
|
||||||
|
self.notify = ux.notify.Notify()
|
||||||
|
|
||||||
|
def __xmerged__(self):
|
||||||
|
"""
|
||||||
|
Called when xmerge has been completed in the webapp.
|
||||||
|
Merge all instances into child classes.
|
||||||
|
"""
|
||||||
|
init_map = {
|
||||||
|
"ux": self,
|
||||||
|
"agora": self.agora,
|
||||||
|
"markets": self.markets,
|
||||||
|
"sinks": self.sinks,
|
||||||
|
"tx": self.tx,
|
||||||
|
"webapp": self.webapp,
|
||||||
|
"money": self.money,
|
||||||
|
"irc": self.irc,
|
||||||
|
"notify": self.notify,
|
||||||
|
}
|
||||||
|
util.xmerge_attrs(init_map)
|
|
@ -0,0 +1,451 @@
|
||||||
|
# Other library imports
|
||||||
|
from json import dumps, loads
|
||||||
|
|
||||||
|
# Project imports
|
||||||
|
from settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
class IRCCommands(object):
|
||||||
|
class trades(object):
|
||||||
|
name = "trades"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get all open trades."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
"""
|
||||||
|
Get details of open trades and post on IRC.
|
||||||
|
"""
|
||||||
|
# Send IRC - we don't want to automatically send messages on IRC, even though
|
||||||
|
# this variable seems counter-intuitive here, we are doing something with the result
|
||||||
|
# then calling msg() ourselves, and we don't want extra spam in the channel.
|
||||||
|
trades = agora.get_dashboard()
|
||||||
|
if not trades:
|
||||||
|
msg("No open trades.")
|
||||||
|
return
|
||||||
|
for trade_id in trades:
|
||||||
|
msg(trade_id)
|
||||||
|
|
||||||
|
class create(object):
|
||||||
|
name = "create"
|
||||||
|
authed = True
|
||||||
|
helptext = "Create an ad. Usage: create <XMR/BTC> <country> <currency> [<provider>]"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
"""
|
||||||
|
Post an ad on AgoraDesk with the given country and currency code.
|
||||||
|
"""
|
||||||
|
if length == 4:
|
||||||
|
if spl[1] not in loads(settings.Agora.AssetList):
|
||||||
|
msg(f"Not a valid asset: {spl[1]}")
|
||||||
|
return
|
||||||
|
posted = agora.create_ad(spl[1], spl[2], spl[3], "REVOLUT")
|
||||||
|
if posted["success"]:
|
||||||
|
msg(f"{posted['response']['data']['message']}: {posted['response']['data']['ad_id']}")
|
||||||
|
else:
|
||||||
|
msg(dumps(posted["response"]))
|
||||||
|
elif length == 5:
|
||||||
|
if spl[1] not in loads(settings.Agora.AssetList):
|
||||||
|
msg(f"Not a valid asset: {spl[1]}")
|
||||||
|
return
|
||||||
|
if spl[4] not in loads(settings.Agora.ProviderList):
|
||||||
|
msg(f"Not a valid provider: {spl[4]}")
|
||||||
|
return
|
||||||
|
posted = agora.create_ad(spl[1], spl[2], spl[3], spl[4])
|
||||||
|
if posted["success"]:
|
||||||
|
msg(f"{posted['response']['data']['message']}: {posted['response']['data']['ad_id']}")
|
||||||
|
else:
|
||||||
|
msg(dumps(posted["response"]))
|
||||||
|
|
||||||
|
class messages(object):
|
||||||
|
name = "messages"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get messages. Usage: messages [<reference>]"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
"""
|
||||||
|
Get all messages for all open trades or a given trade.
|
||||||
|
"""
|
||||||
|
if length == 1:
|
||||||
|
messages = agora.get_recent_messages()
|
||||||
|
if messages is False:
|
||||||
|
msg("Error getting messages.")
|
||||||
|
return
|
||||||
|
if not messages:
|
||||||
|
msg("No messages.")
|
||||||
|
return
|
||||||
|
for reference in messages:
|
||||||
|
for message in messages[reference]:
|
||||||
|
msg(f"{reference}: {message[0]} {message[1]}")
|
||||||
|
msg("---")
|
||||||
|
|
||||||
|
elif length == 2:
|
||||||
|
tx = tx.ref_to_tx(spl[1])
|
||||||
|
if not tx:
|
||||||
|
msg(f"No such reference: {spl[1]}")
|
||||||
|
return
|
||||||
|
messages = agora.get_messages(spl[1], send_irc=False)
|
||||||
|
if not messages:
|
||||||
|
msg("No messages.")
|
||||||
|
for message in messages:
|
||||||
|
msg(f"{spl[1]}: {message}")
|
||||||
|
|
||||||
|
class dist(object):
|
||||||
|
name = "dist"
|
||||||
|
authed = True
|
||||||
|
helptext = "Distribute all our chosen currency and country ad pairs. Usage: dist [<XMR/BTC>]"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
# Distribute out our ad to all countries in the config
|
||||||
|
if length == 2:
|
||||||
|
asset = spl[1]
|
||||||
|
if asset not in loads(settings.Agora.AssetList):
|
||||||
|
msg(f"Not a valid asset: {spl[1]}")
|
||||||
|
return
|
||||||
|
for x in agora.dist_countries(filter_asset=asset):
|
||||||
|
if x["success"]:
|
||||||
|
msg(f"{x['response']['data']['message']}: {x['response']['data']['ad_id']}")
|
||||||
|
else:
|
||||||
|
msg(dumps(x["response"]))
|
||||||
|
elif length == 1:
|
||||||
|
for x in agora.dist_countries():
|
||||||
|
if x["success"]:
|
||||||
|
msg(f"{x['response']['data']['message']}: {x['response']['data']['ad_id']}")
|
||||||
|
else:
|
||||||
|
msg(dumps(x["response"]))
|
||||||
|
|
||||||
|
class redist(object):
|
||||||
|
name = "redist"
|
||||||
|
authed = True
|
||||||
|
helptext = "Update all ads with details."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
for x in agora.redist_countries():
|
||||||
|
if x[0]["success"]:
|
||||||
|
msg(f"{x[0]['response']['data']['message']}: {x[1]}")
|
||||||
|
else:
|
||||||
|
msg(dumps(x[0]["response"]))
|
||||||
|
|
||||||
|
class stripdupes(object):
|
||||||
|
name = "stripdupes"
|
||||||
|
authed = True
|
||||||
|
helptext = "Remove all duplicate adverts."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
rtrn = agora.strip_duplicate_ads()
|
||||||
|
msg(dumps(rtrn))
|
||||||
|
|
||||||
|
class total(object):
|
||||||
|
name = "total"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get total account balance from Sinks and Agora."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
totals_all = tx.get_total()
|
||||||
|
totals = totals_all[0]
|
||||||
|
wallets = totals_all[1]
|
||||||
|
msg(f"Totals: SEK: {totals[0]} | USD: {totals[1]} | GBP: {totals[2]}")
|
||||||
|
msg(f"Wallets: XMR USD: {wallets[0]} | BTC USD: {wallets[1]}")
|
||||||
|
|
||||||
|
class ping(object):
|
||||||
|
name = "ping"
|
||||||
|
authed = False
|
||||||
|
helptext = "Pong!"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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, tx, ux):
|
||||||
|
ux.notify.sendmsg("You have been summoned!")
|
||||||
|
|
||||||
|
class message(object):
|
||||||
|
name = "msg"
|
||||||
|
authed = True
|
||||||
|
helptext = "Send a message on a trade. Usage: msg <reference> <message...>"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length > 2:
|
||||||
|
full_msg = " ".join(spl[2:])
|
||||||
|
reference = tx.ref_to_tx(spl[1])
|
||||||
|
if not reference:
|
||||||
|
msg(f"No such reference: {spl[1]}")
|
||||||
|
return
|
||||||
|
rtrn = agora.agora.contact_message_post(reference, full_msg)
|
||||||
|
msg(f"Sent {full_msg} to {reference}: {rtrn}")
|
||||||
|
|
||||||
|
class refs(object):
|
||||||
|
name = "refs"
|
||||||
|
authed = True
|
||||||
|
helptext = "List all references"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
msg(f"References: {', '.join(tx.get_refs())}")
|
||||||
|
|
||||||
|
class ref(object):
|
||||||
|
name = "ref"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get more information about a reference. Usage: ref <reference>"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length == 2:
|
||||||
|
ref_data = tx.get_ref(spl[1])
|
||||||
|
if not ref_data:
|
||||||
|
msg(f"No such reference: {spl[1]}")
|
||||||
|
return
|
||||||
|
msg(f"{spl[1]}: {dumps(ref_data)}")
|
||||||
|
|
||||||
|
class delete(object):
|
||||||
|
name = "del"
|
||||||
|
authed = True
|
||||||
|
helptext = "Delete a reference. Usage: del <reference>"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length == 2:
|
||||||
|
ref_data = tx.get_ref(spl[1])
|
||||||
|
if not ref_data:
|
||||||
|
msg(f"No such reference: {spl[1]}")
|
||||||
|
return
|
||||||
|
tx.del_ref(spl[1])
|
||||||
|
msg(f"Deleted reference: {spl[1]}")
|
||||||
|
|
||||||
|
class release(object):
|
||||||
|
name = "release"
|
||||||
|
authed = True
|
||||||
|
helptext = "Release funds for a trade. Usage: release <reference>"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length == 2:
|
||||||
|
tx = tx.ref_to_tx(spl[1])
|
||||||
|
if not tx:
|
||||||
|
msg(f"No such reference: {spl[1]}")
|
||||||
|
return
|
||||||
|
rtrn = agora.release_funds(tx)
|
||||||
|
message = rtrn["message"]
|
||||||
|
message_long = rtrn["response"]["data"]["message"]
|
||||||
|
msg(f"{message} - {message_long}")
|
||||||
|
|
||||||
|
class nuke(object):
|
||||||
|
name = "nuke"
|
||||||
|
authed = True
|
||||||
|
helptext = "Delete all our adverts."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
rtrn = agora.nuke_ads()
|
||||||
|
msg(dumps(rtrn))
|
||||||
|
|
||||||
|
class wallet(object):
|
||||||
|
name = "wallet"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get Agora wallet balances."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
rtrn_xmr = agora.agora.wallet_balance_xmr()
|
||||||
|
if not rtrn_xmr["success"]:
|
||||||
|
msg("Error getting XMR wallet details.")
|
||||||
|
return
|
||||||
|
rtrn_btc = agora.agora.wallet_balance()
|
||||||
|
if not rtrn_btc["success"]:
|
||||||
|
msg("Error getting BTC wallet details.")
|
||||||
|
return
|
||||||
|
balance_xmr = rtrn_xmr["response"]["data"]["total"]["balance"]
|
||||||
|
balance_btc = rtrn_btc["response"]["data"]["total"]["balance"]
|
||||||
|
msg(f"XMR wallet balance: {balance_xmr}")
|
||||||
|
msg(f"BTC wallet balance: {balance_btc}")
|
||||||
|
|
||||||
|
class pubads(object):
|
||||||
|
name = "pubads"
|
||||||
|
authed = True
|
||||||
|
helptext = "View public adverts. Usage: pubads <XMR/BTC> <currency> [<provider,...>]"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length == 3:
|
||||||
|
asset = spl[1]
|
||||||
|
if asset not in loads(settings.Agora.AssetList):
|
||||||
|
msg(f"Not a valid asset: {spl[1]}")
|
||||||
|
return
|
||||||
|
currency = spl[2]
|
||||||
|
rtrn = agora.get_all_public_ads(assets=[asset], currencies=[currency])
|
||||||
|
if not rtrn:
|
||||||
|
msg("No results.")
|
||||||
|
return
|
||||||
|
for ad in rtrn[currency]:
|
||||||
|
msg(f"({ad[0]}) {ad[1]} {ad[2]} {ad[3]} {ad[4]} {ad[5]} {ad[6]}")
|
||||||
|
elif length == 4:
|
||||||
|
asset = spl[1]
|
||||||
|
if asset not in loads(settings.Agora.AssetList):
|
||||||
|
msg(f"Not a valid asset: {spl[1]}")
|
||||||
|
return
|
||||||
|
providers = spl[3].split(",")
|
||||||
|
currency = spl[2]
|
||||||
|
rtrn = agora.get_all_public_ads(assets=[asset], currencies=[currency], providers=providers)
|
||||||
|
if not rtrn:
|
||||||
|
msg("No results.")
|
||||||
|
return
|
||||||
|
for ad in rtrn[currency]:
|
||||||
|
msg(f"({ad[0]}) {ad[1]} {ad[2]} {ad[3]} {ad[4]} {ad[5]} {ad[6]}")
|
||||||
|
|
||||||
|
class cheat(object):
|
||||||
|
name = "cheat"
|
||||||
|
authed = True
|
||||||
|
helptext = "Cheat the markets by manipulating our prices to exploit people. Usage: cheat [<XMR/BTC>]"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length == 1:
|
||||||
|
agora.run_cheat_in_thread()
|
||||||
|
msg("Running cheat in thread.")
|
||||||
|
elif length == 2:
|
||||||
|
asset = spl[1]
|
||||||
|
if asset not in loads(settings.Agora.AssetList):
|
||||||
|
msg(f"Not a valid asset: {spl[1]}")
|
||||||
|
return
|
||||||
|
agora.run_cheat_in_thread([asset])
|
||||||
|
msg(f"Running cheat in thread for {asset}.")
|
||||||
|
|
||||||
|
class cheatnext(object):
|
||||||
|
name = "cheatnext"
|
||||||
|
authed = True
|
||||||
|
helptext = "Run the next currency for cheat."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
if length == 1:
|
||||||
|
asset = agora.run_cheat_in_thread()
|
||||||
|
msg(f"Running next asset for cheat in thread: {asset}")
|
||||||
|
|
||||||
|
class ads(object):
|
||||||
|
name = "ads"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get all our ad regions"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
ads = agora.enum_ads()
|
||||||
|
for ad in ads:
|
||||||
|
msg(f"({ad[0]}) {ad[1]} {ad[2]} {ad[3]} {ad[4]}")
|
||||||
|
|
||||||
|
class xmr(object):
|
||||||
|
name = "xmr"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get current XMR price."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
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"]
|
||||||
|
price_gbp = xmr_prices["monero"]["gbp"]
|
||||||
|
msg(f"SEK: {price_sek} | USD: {price_usd} | GBP: {price_gbp}")
|
||||||
|
|
||||||
|
class btc(object):
|
||||||
|
name = "btc"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get current BTC price."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
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"]
|
||||||
|
price_gbp = xmr_prices["bitcoin"]["gbp"]
|
||||||
|
msg(f"SEK: {price_sek} | USD: {price_usd} | GBP: {price_gbp}")
|
||||||
|
|
||||||
|
class withdraw(object):
|
||||||
|
name = "withdraw"
|
||||||
|
authed = True
|
||||||
|
helptext = "Take profit."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
agora.withdraw_funds()
|
||||||
|
|
||||||
|
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, tx, ux):
|
||||||
|
remaining = tx.get_remaining()
|
||||||
|
msg(f"Remaining: {remaining}USD")
|
||||||
|
|
||||||
|
class total_remaining(object):
|
||||||
|
name = "tr"
|
||||||
|
authed = True
|
||||||
|
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, tx, ux):
|
||||||
|
remaining = tx.get_total_remaining()
|
||||||
|
msg(f"Total remaining: {remaining}USD")
|
||||||
|
|
||||||
|
class tradetotal(object):
|
||||||
|
name = "tradetotal"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get total value of all open trades in USD."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
total = tx.get_open_trades_usd()
|
||||||
|
msg(f"Total trades: {total}USD")
|
||||||
|
|
||||||
|
class dollar(object):
|
||||||
|
name = "$"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get total value of everything, including open trades."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
total = tx.get_total_with_trades()
|
||||||
|
msg(f"${total}")
|
||||||
|
|
||||||
|
class profit(object):
|
||||||
|
name = "profit"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get total profit."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
total = tx.money.get_profit()
|
||||||
|
msg(f"Profit: {total}USD")
|
||||||
|
|
||||||
|
class tprofit(object):
|
||||||
|
name = "tprofit"
|
||||||
|
authed = True
|
||||||
|
helptext = "Get total profit with open trades."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
total = tx.money.get_profit(True)
|
||||||
|
msg(f"Profit: {total}USD")
|
||||||
|
|
||||||
|
class signin(object):
|
||||||
|
name = "signin"
|
||||||
|
authed = True
|
||||||
|
helptext = "Generate a TrueLayer signin URL."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(cmd, spl, length, authed, msg, agora, tx, ux):
|
||||||
|
auth_url = agora.truelayer.create_auth_url()
|
||||||
|
msg(f"Auth URL: {auth_url}")
|
|
@ -0,0 +1,234 @@
|
||||||
|
# Twisted/Klein imports
|
||||||
|
from twisted.logger import Logger
|
||||||
|
from twisted.words.protocols import irc
|
||||||
|
from twisted.internet import protocol, reactor, ssl
|
||||||
|
from twisted.internet.task import deferLater
|
||||||
|
|
||||||
|
# Project imports
|
||||||
|
from settings import settings
|
||||||
|
from ux.commands import IRCCommands
|
||||||
|
|
||||||
|
|
||||||
|
class IRCBot(irc.IRCClient):
|
||||||
|
def __init__(self, log):
|
||||||
|
"""
|
||||||
|
Initialise IRC bot.
|
||||||
|
:param log: logger instance
|
||||||
|
:type log: Logger
|
||||||
|
"""
|
||||||
|
self.log = log
|
||||||
|
self.cmd = IRCCommands()
|
||||||
|
# Parse the commands into "commandname": "commandclass"
|
||||||
|
self.cmdhash = {getattr(self.cmd, x).name: x for x in dir(self.cmd) if not x.startswith("_")}
|
||||||
|
self.nickname = settings.IRC.Nick
|
||||||
|
self.password = settings.IRC.Pass
|
||||||
|
self.realname = self.nickname
|
||||||
|
self.username = self.nickname
|
||||||
|
|
||||||
|
# Don't give away information about our client
|
||||||
|
self.userinfo = None
|
||||||
|
self.fingerReply = None
|
||||||
|
self.versionName = None
|
||||||
|
self.sourceURL = None
|
||||||
|
self.lineRate = None # Don't throttle messages, we may need to send a lot
|
||||||
|
|
||||||
|
self.prefix = settings.IRC.Prefix
|
||||||
|
self.admins = (settings.IRC.Admins).split("\n")
|
||||||
|
self.highlight = (settings.IRC.Highlight).split("\n")
|
||||||
|
|
||||||
|
self.channel = settings.IRC.Channel
|
||||||
|
|
||||||
|
def parse(self, user, host, channel, msg):
|
||||||
|
"""
|
||||||
|
Simple handler for IRC commands.
|
||||||
|
:param user: full user string with host
|
||||||
|
:param host: user's hostname
|
||||||
|
:param channel: channel the message was received on
|
||||||
|
:param msg: the message
|
||||||
|
:type user: string
|
||||||
|
:type host: string
|
||||||
|
:type channel: string
|
||||||
|
:type msg: string
|
||||||
|
"""
|
||||||
|
spl = msg.split()
|
||||||
|
# nick = user.split("!")[0]
|
||||||
|
|
||||||
|
cmd = spl[0]
|
||||||
|
length = len(spl)
|
||||||
|
|
||||||
|
# Check if user is authenticated
|
||||||
|
authed = host in self.admins
|
||||||
|
if cmd == "help" and length == 2 and authed:
|
||||||
|
if spl[1] in self.cmdhash:
|
||||||
|
cmdname = self.cmdhash[spl[1]]
|
||||||
|
obj = getattr(self.cmd, cmdname)
|
||||||
|
helptext = getattr(obj, "helptext")
|
||||||
|
self.msg(channel, helptext)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.msg(channel, f"No such command: {spl[1]}")
|
||||||
|
return
|
||||||
|
if cmd == "helpall" and authed:
|
||||||
|
for command in self.cmdhash:
|
||||||
|
cmdname = self.cmdhash[command]
|
||||||
|
obj = getattr(self.cmd, cmdname)
|
||||||
|
helptext = getattr(obj, "helptext")
|
||||||
|
self.msg(channel, f"{cmdname}: {helptext}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd in self.cmdhash:
|
||||||
|
# Get the class name of the referenced command
|
||||||
|
cmdname = self.cmdhash[cmd]
|
||||||
|
# Get the class name
|
||||||
|
obj = getattr(self.cmd, cmdname)
|
||||||
|
|
||||||
|
def msgl(x):
|
||||||
|
self.msg(channel, x)
|
||||||
|
|
||||||
|
# Check if the command required authentication
|
||||||
|
if obj.authed:
|
||||||
|
if host in self.admins:
|
||||||
|
obj.run(cmd, spl, length, authed, msgl, self.agora, self.tx, self.ux)
|
||||||
|
else:
|
||||||
|
# Handle authentication here instead of in the command module for security
|
||||||
|
self.msg(channel, "Access denied.")
|
||||||
|
else:
|
||||||
|
# Run an unauthenticated command, without passing through secure library calls
|
||||||
|
obj.run(cmd, spl, len(spl), authed, msgl)
|
||||||
|
return
|
||||||
|
self.msg(channel, "Command not found.")
|
||||||
|
if authed:
|
||||||
|
# Give user command hints if they are authenticated
|
||||||
|
self.msg(channel, f"Commands loaded: {', '.join(self.cmdhash.keys())}")
|
||||||
|
|
||||||
|
def signedOn(self):
|
||||||
|
"""
|
||||||
|
Called when we have signed on to IRC.
|
||||||
|
Join our channel.
|
||||||
|
"""
|
||||||
|
self.log.info("Signed on as %s" % (self.nickname))
|
||||||
|
deferLater(reactor, 2, self.join, self.channel)
|
||||||
|
|
||||||
|
def joined(self, channel):
|
||||||
|
"""
|
||||||
|
Called when we have joined a channel.
|
||||||
|
Setup the Agora LoopingCall to get trades.
|
||||||
|
This is here to ensure the IRC client is initialised enough to send the trades.
|
||||||
|
:param channel: channel we joined
|
||||||
|
:type channel: string
|
||||||
|
"""
|
||||||
|
self.agora.setup_loop()
|
||||||
|
self.log.info("Joined channel %s" % (channel))
|
||||||
|
|
||||||
|
def privmsg(self, user, channel, msg):
|
||||||
|
"""
|
||||||
|
Called on received PRIVMSGs.
|
||||||
|
Pass through identified commands to the parse function.
|
||||||
|
:param user: full user string with host
|
||||||
|
:param channel: channel the message was received on
|
||||||
|
:param msg: the message
|
||||||
|
:type user: string
|
||||||
|
:type channel: string
|
||||||
|
:type msg: string
|
||||||
|
"""
|
||||||
|
nick = user.split("!")[0]
|
||||||
|
|
||||||
|
if channel == self.nickname:
|
||||||
|
channel = nick
|
||||||
|
|
||||||
|
host = user.split("!")[1]
|
||||||
|
host = host.split("@")[1]
|
||||||
|
|
||||||
|
ident = user.split("!")[1]
|
||||||
|
ident = ident.split("@")[0]
|
||||||
|
self.log.info("(%s) %s: %s" % (channel, user, msg))
|
||||||
|
if msg[0] == self.prefix:
|
||||||
|
if len(msg) > 1:
|
||||||
|
if msg.split()[0] != "!":
|
||||||
|
self.parse(user, host, channel, msg[1:])
|
||||||
|
elif host in self.admins and channel == nick:
|
||||||
|
if len(msg) > 0:
|
||||||
|
spl = msg.split()
|
||||||
|
if len(spl) > 0:
|
||||||
|
if spl[0] != "!":
|
||||||
|
self.parse(user, host, channel, msg)
|
||||||
|
|
||||||
|
def noticed(self, user, channel, msg):
|
||||||
|
"""
|
||||||
|
Called on received NOTICEs.
|
||||||
|
:param user: full user string with host
|
||||||
|
:param channel: channel the notice was received on
|
||||||
|
:param msg: the message
|
||||||
|
:type user: string
|
||||||
|
:type channel: string
|
||||||
|
:type msg: string
|
||||||
|
"""
|
||||||
|
nick = user.split("!")[0]
|
||||||
|
if channel == self.nickname:
|
||||||
|
channel = nick
|
||||||
|
# self.log.info("[%s] %s: %s" % (channel, user, msg))
|
||||||
|
|
||||||
|
|
||||||
|
class IRCBotFactory(protocol.ClientFactory):
|
||||||
|
def __init__(self):
|
||||||
|
self.log = Logger("irc")
|
||||||
|
|
||||||
|
def sendmsg(self, msg):
|
||||||
|
"""
|
||||||
|
Passthrough function to send a message to the channel.
|
||||||
|
"""
|
||||||
|
if self.client:
|
||||||
|
self.client.msg(self.client.channel, msg)
|
||||||
|
else:
|
||||||
|
self.log.error("Trying to send a message without connected client: {msg}", msg=msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
def buildProtocol(self, addr):
|
||||||
|
"""
|
||||||
|
Custom override for the Twisted buildProtocol so we can access the Protocol instance.
|
||||||
|
Passes through the Agora instance to IRC.
|
||||||
|
:return: IRCBot Protocol instance
|
||||||
|
"""
|
||||||
|
prcol = IRCBot(self.log)
|
||||||
|
self.client = prcol
|
||||||
|
setattr(self.client, "agora", self.agora)
|
||||||
|
setattr(self.client, "sinks", self.sinks)
|
||||||
|
setattr(self.client, "tx", self.tx)
|
||||||
|
setattr(self.client, "ux", self.ux)
|
||||||
|
return prcol
|
||||||
|
|
||||||
|
def clientConnectionLost(self, connector, reason):
|
||||||
|
"""
|
||||||
|
Called when connection to IRC server lost. Reconnect.
|
||||||
|
:param connector: connector object
|
||||||
|
:param reason: reason connection lost
|
||||||
|
:type connector: object
|
||||||
|
:type reason: string
|
||||||
|
"""
|
||||||
|
self.log.error("Lost connection: {reason}, reconnecting", reason=reason)
|
||||||
|
connector.connect()
|
||||||
|
|
||||||
|
def clientConnectionFailed(self, connector, reason):
|
||||||
|
"""
|
||||||
|
Called when connection to IRC server failed. Reconnect.
|
||||||
|
:param connector: connector object
|
||||||
|
:param reason: reason connection failed
|
||||||
|
:type connector: object
|
||||||
|
:type reason: string
|
||||||
|
"""
|
||||||
|
self.log.error("Could not connect: {reason}", reason=reason)
|
||||||
|
connector.connect()
|
||||||
|
|
||||||
|
|
||||||
|
def bot():
|
||||||
|
"""
|
||||||
|
Load the certificates, start the Bot Factory and connect it to the IRC server.
|
||||||
|
:return: Factory instance
|
||||||
|
:rtype: Factory
|
||||||
|
"""
|
||||||
|
# Load the certificates
|
||||||
|
context = ssl.DefaultOpenSSLContextFactory(settings.IRC.Cert, settings.IRC.Cert)
|
||||||
|
# Define the factory instance
|
||||||
|
factory = IRCBotFactory()
|
||||||
|
reactor.connectSSL(settings.IRC.Host, int(settings.IRC.Port), factory, context)
|
||||||
|
return factory
|
|
@ -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 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.money.to_usd(amount, currency)
|
||||||
|
self.sendmsg(f"Total: {amount_usd}", title="New trade", tags="trades", priority="2")
|
||||||
|
|
||||||
|
def notify_complete_trade(self, amount, currency):
|
||||||
|
amount_usd = self.money.to_usd(amount, currency)
|
||||||
|
self.sendmsg(f"Total: {amount_usd}", title="Trade complete", tags="trades,profit", priority="3")
|
||||||
|
|
||||||
|
def notify_withdrawal(self, amount_usd):
|
||||||
|
self.sendmsg(f"Total: {amount_usd}", title="Withdrawal", tags="profit", priority="4")
|
||||||
|
|
||||||
|
def notify_need_topup(self, amount_usd_xmr, amount_usd_btc):
|
||||||
|
self.sendmsg(f"XMR: {amount_usd_xmr} | BTC: {amount_usd_btc}", title="Topup needed", tags="admin", priority="5")
|
Loading…
Reference in New Issue