Use the Python logger instead of the Twisted one

This commit is contained in:
Mark Veidemanis 2022-03-05 21:52:31 +00:00
parent 80c696ef73
commit 539d6f1fbb
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
15 changed files with 224 additions and 151 deletions

View File

@ -1,5 +1,4 @@
# Twisted/Klein imports
from twisted.logger import Logger
from twisted.internet.task import LoopingCall
from twisted.internet.threads import deferToThread
@ -16,10 +15,8 @@ from datetime import datetime
from settings import settings
import util
log = Logger("agora.global")
class Agora(object):
class Agora(util.Base):
"""
AgoraDesk API handler.
"""
@ -29,7 +26,7 @@ class Agora(object):
Initialise the AgoraDesk and CurrencyRates APIs.
Initialise the last_dash storage for detecting new trades.
"""
self.log = Logger("agora")
super().__init__()
self.agora = AgoraDesk(settings.Agora.Token)
self.cr = CurrencyRates() # TODO: remove this and defer to money
self.cg = CoinGeckoAPI() # TODO: remove this and defer to money
@ -66,7 +63,7 @@ class Agora(object):
if not dash.items():
return False
if "data" not in dash["response"].keys():
self.log.error("Data not in dashboard response: {content}", content=dash)
self.log.error(f"Data not in dashboard response: {dash}")
return dash_tmp
if dash["response"]["data"]["contact_count"] > 0:
for contact in dash["response"]["data"]["contact_list"]:
@ -166,7 +163,7 @@ class Agora(object):
if not messages["success"]:
return False
if "data" not in messages["response"]:
self.log.error("Data not in messages response: {content}", content=messages["response"])
self.log.error(f"Data not in messages response: {messages['response']}")
return False
open_tx = self.tx.get_ref_map().keys()
for message in messages["response"]["data"]["message_list"]:
@ -429,20 +426,15 @@ class Agora(object):
continue
else:
if "error_code" not in rtrn["response"]["error"]:
self.log.error("Error code not in return for ad {ad_id}: {response}", ad_id=ad_id, response=rtrn["response"])
self.log.error(f"Error code not in return for ad {ad_id}: {rtrn['response']}")
return
if rtrn["response"]["error"]["error_code"] == 429:
throttled += 1
sleep_time = pow(throttled, float(settings.Agora.SleepExponent))
self.log.info(
"Throttled {x} times while updating {id}, sleeping for {sleep} seconds",
x=throttled,
id=ad_id,
sleep=sleep_time,
)
self.log.info(f"Throttled {throttled} times while updating {ad_id}, sleeping for {sleep_time} seconds")
# We're running in a thread, so this is fine
sleep(sleep_time)
self.log.error("Error updating ad {ad_id}: {response}", ad_id=ad_id, response=rtrn["response"])
self.log.error(f"Error updating ad {ad_id}: {rtrn['response']}")
continue
iterations += 1
@ -645,9 +637,7 @@ class Agora(object):
if not float(wallet_xmr) > profit_usd_in_xmr:
# Not enough funds to withdraw
self.log.error(
"Not enough funds to withdraw {profit}, as wallet only contains {wallet}", profit=profit_usd_in_xmr, wallet=wallet_xmr
)
self.log.error(f"Not enough funds to withdraw {profit_usd_in_xmr}, as wallet only contains {wallet_xmr}")
self.irc.sendmsg(f"Not enough funds to withdraw {profit_usd_in_xmr}, as wallet only contains {wallet_xmr}")
return

View File

@ -12,6 +12,9 @@ from typing import Union
import arrow
import httpx
# Project imports
import util
__author__ = "marvin8"
__copyright__ = "(C) 2021 https://codeberg.org/MarvinsCryptoTools/agoradesk_py"
__version__ = "0.1.0"
@ -20,7 +23,7 @@ __version__ = "0.1.0"
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logging.getLogger("requests.packages.urllib3").setLevel(logging.INFO)
logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO)
logger = logging.getLogger(__name__)
logger = util.get_logger(__name__)
URI_API = "https://agoradesk.com/api/v1/"

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3
# Twisted/Klein imports
from twisted.logger import Logger
from twisted.internet import reactor
from klein import Klein
@ -41,33 +40,20 @@ def cleanup(sig, frame):
signal(SIGINT, cleanup) # Handle Ctrl-C and run the cleanup routine
def convert(data):
if isinstance(data, bytes):
return data.decode("ascii")
if isinstance(data, dict):
return dict(map(convert, data.items()))
if isinstance(data, tuple):
return map(convert, data)
return data
class WebApp(object):
class WebApp(util.Base):
"""
Our Klein webapp.
"""
app = Klein()
def __init__(self):
self.log = Logger("webapp")
@app.route("/callback", methods=["POST"])
def callback(self, request):
content = request.content.read()
try:
parsed = loads(content)
except JSONDecodeError:
self.log.error("Failed to parse JSON callback: {content}", content=content)
self.log.error(f"Failed to parse JSON callback: {content}")
return dumps(False)
self.log.info("Callback received: {parsed}", parsed=parsed["data"]["id"])
# self.tx.transaction(parsed)
@ -76,19 +62,19 @@ class WebApp(object):
# set up another connection to a bank
@app.route("/signin", methods=["GET"])
def signin(self, request):
auth_url = self.truelayer.create_auth_url()
auth_url = self.sinks.truelayer.create_auth_url()
return f'Please sign in <a href="{auth_url}" target="_blank">here.</a>'
# endpoint called after we finish setting up a connection above
@app.route("/callback-truelayer", methods=["POST"])
def signin_callback(self, request):
code = request.args[b"code"]
self.truelayer.handle_authcode_received(code)
self.sinks.truelayer.handle_authcode_received(code)
return dumps(True)
@app.route("/accounts", methods=["GET"])
def balance(self, request):
accounts = self.truelayer.get_accounts()
accounts = self.sinks.truelayer.get_accounts()
return dumps(accounts, indent=2)

View File

@ -1,21 +1,16 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
from json import loads
# Project imports
from settings import settings
import util
class Markets(object):
class Markets(util.Base):
""" "
Markets handler for generic market functions.
"""
def __init__(self):
self.log = Logger("markets")
def get_all_assets(self):
assets = loads(settings.Agora.AssetList)
return assets

View File

@ -1,15 +1,13 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
from pycoingecko import CoinGeckoAPI
from forex_python.converter import CurrencyRates
# Project imports
from settings import settings
import util
class Money(object):
class Money(util.Base):
"""
Generic class for handling money-related matters that aren't Revolut or Agora.
"""
@ -20,7 +18,7 @@ class Money(object):
Set the logger.
Initialise the CoinGecko API.
"""
self.log = Logger("money")
super().__init__()
self.cr = CurrencyRates()
self.cg = CoinGeckoAPI()
@ -41,6 +39,7 @@ class Money(object):
price = float(ad[2])
rate = round(price / base_currency_price, 2)
ad.append(rate)
# TODO: sort?
return sorted(ads, key=lambda x: x[2])
def get_rates_all(self):

View File

@ -1,6 +1,3 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
# import requests
# from json import dumps
@ -10,15 +7,16 @@ from twisted.logger import Logger
import sinks.fidor
import sinks.nordigen
import sinks.truelayer
import util
class Sinks(object):
class Sinks(util.Base):
"""
Class to manage calls to various sinks.
"""
def __init__(self):
self.log = Logger("sinks")
super().__init__()
self.fidor = sinks.fidor.Fidor()
self.nordigen = sinks.nordigen.Nordigen()
self.truelayer = sinks.truelayer.TrueLayer()

View File

@ -1,5 +1,4 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
# import requests
@ -7,16 +6,14 @@ from twisted.logger import Logger
# Project imports
# from settings import settings
import util
class Fidor(object):
class Fidor(util.Base):
"""
Class to manage calls to the Fidor API.
"""
def __init__(self):
self.log = Logger("fidor")
def authorize(self):
"""
Perform initial authorization against Fidor API.

View File

@ -1,6 +1,3 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
import requests
from json import dumps
@ -8,15 +5,16 @@ from simplejson.errors import JSONDecodeError
# Project imports
from settings import settings
import util
class Nordigen(object):
class Nordigen(util.Base):
"""
Class to manage calls to Open Banking APIs through Nordigen.
"""
def __init__(self):
self.log = Logger("nordigen")
super().__init__()
self.token = None
self.get_access_token()
@ -36,11 +34,11 @@ class Nordigen(object):
try:
parsed = r.json()
except JSONDecodeError:
self.log.error("Error parsing access token response: {content}", content=r.content)
self.log.error(f"Error parsing access token response: {r.content}")
return False
if "access" in parsed:
self.token = parsed["access"]
self.log.info("Refreshed access token - Nordigen")
self.log.info("Refreshed access token")
def get_institutions(self, country, filter_name=None):
"""
@ -58,7 +56,7 @@ class Nordigen(object):
try:
parsed = r.json()
except JSONDecodeError:
self.log.error("Error parsing institutions response: {content}", content=r.content)
self.log.error(f"Error parsing institutions response: {r.content}")
return False
new_list = []
if filter_name:

View File

@ -1,5 +1,4 @@
# Twisted/Klein imports
from twisted.logger import Logger
from twisted.internet.task import LoopingCall
# Other library imports
@ -10,15 +9,16 @@ import urllib
# Project imports
from settings import settings
import util
class TrueLayer(object):
class TrueLayer(util.Base):
"""
Class to manage calls to Open Banking APIs through TrueLayer.
"""
def __init__(self):
self.log = Logger("truelayer")
super().__init__()
self.token = None
self.lc = LoopingCall(self.get_new_token)
self.lc.start(int(settings.TrueLayer.RefreshSec))
@ -58,7 +58,7 @@ class TrueLayer(object):
settings.TrueLayer.AuthCode = authcode
settings.write()
self.token = parsed["access_token"]
self.log.info("Retrieved access/refresh tokens - TrueLayer")
self.log.info("Retrieved access/refresh tokens")
def get_new_token(self):
"""
@ -81,7 +81,7 @@ class TrueLayer(object):
if r.status_code == 200:
if "access_token" in parsed.keys():
self.token = parsed["access_token"]
self.log.info("Refreshed access token - TrueLayer")
self.log.info("Refreshed access token")
return True
else:
self.log.error(f"Token refresh didn't contain access token: {parsed}", parsed=parsed)
@ -100,7 +100,25 @@ class TrueLayer(object):
try:
parsed = r.json()
except JSONDecodeError:
self.log.error("Error parsing institutions response: {content}", content=r.content)
self.log.error("Error parsing accounts response: {content}", content=r.content)
return False
return parsed
def get_transactions(self, account_id):
"""
Get a list of transactions from an account.
:param account_id: account to fetch transactions for
:return: list of transactions
:rtype: dict
"""
headers = {"Authorization": f"Bearer {self.token}"}
path = f"{settings.TrueLayer.DataBase}/accounts/{account_id}/transactions"
r = requests.get(path, headers=headers)
try:
parsed = r.json()
except JSONDecodeError:
self.log.error("Error parsing transactions response: {content}", content=r.content)
return False
return parsed["results"]

View File

@ -1,5 +1,4 @@
# Twisted/Klein imports
from twisted.logger import Logger
from twisted.internet.task import LoopingCall
from twisted.internet.threads import deferToThread
@ -15,7 +14,7 @@ import logging
# Project imports
from settings import settings
from db import r
from util import convert
import util
# TODO: secure ES traffic properly
urllib3.disable_warnings()
@ -26,7 +25,7 @@ tracer = logging.getLogger("elastic_transport.transport")
tracer.setLevel(logging.CRITICAL)
class Transactions(object):
class Transactions(util.Base):
"""
Handler class for incoming Revolut transactions.
"""
@ -36,7 +35,7 @@ class Transactions(object):
Initialise the Transaction object.
Set the logger.
"""
self.log = Logger("transactions")
super().__init__()
if settings.ES.Enabled == "1":
self.es = Elasticsearch(
f"https://{settings.ES.Host}:9200",
@ -89,10 +88,10 @@ class Transactions(object):
# stored_trade here is actually TX
stored_trade = r.hgetall(f"tx.{txid}")
if not stored_trade:
self.log.error("Could not find entry in DB for typeless transaction: {id}", id=txid)
self.log.error(f"Could not find entry in DB for typeless transaction: {txid}")
return
print("BEFORE CONVERT STORED TRADE", stored_trade)
stored_trade = convert(stored_trade)
stored_trade = util.convert(stored_trade)
if "old_state" in inside:
if "new_state" in inside:
# We don't care unless we're being told a transaction is now completed
@ -109,7 +108,7 @@ class Transactions(object):
r.hmset(f"tx.{txid}", stored_trade)
# Check it's all been previously validated
if "valid" not in stored_trade:
self.log.error("Valid not in stored trade for {txid}, aborting.", txid=txid)
self.log.error(f"Valid not in stored trade for {txid}, aborting.")
return
if stored_trade["valid"] == "1":
# Make it invalid immediately, as we're going to release now
@ -124,7 +123,7 @@ class Transactions(object):
else:
txtype = inside["type"]
if txtype == "card_payment":
self.log.info("Ignoring card payment: {id}", id=txid)
self.log.info(f"Ignoring card payment: {txid}")
return
state = inside["state"]
@ -142,7 +141,7 @@ class Transactions(object):
amount = leg["amount"]
if amount <= 0:
self.log.info("Ignoring transaction with negative/zero amount: {id}", id=txid)
self.log.info(f"Ignoring transaction with negative/zero amount: {txid}")
return
currency = leg["currency"]
description = leg["description"]
@ -161,7 +160,7 @@ class Transactions(object):
"description": description,
"valid": 0, # All checks passed and we can release escrow?
}
self.log.info("Transaction processed: {formatted}", formatted=dumps(to_store, indent=2))
self.log.info(f"Transaction processed: {dumps(to_store, indent=2)}")
self.irc.sendmsg(f"AUTO Incoming transaction: {amount}{currency} ({reference}) - {state} - {description}")
# Partial reference implementation
# Account for silly people not removing the default string
@ -172,7 +171,7 @@ class Transactions(object):
# Get all parts of the given reference split that match the existing references
stored_trade_reference = set(existing_refs).intersection(set(ref_split))
if len(stored_trade_reference) > 1:
self.log.error("Multiple references valid for TXID {txid}: {reference}", txid=txid, reference=reference)
self.log.error(f"Multiple references valid for TXID {txid}: {reference}")
self.irc.sendmsg(f"Multiple references valid for TXID {txid}: {reference}")
return
@ -181,21 +180,16 @@ class Transactions(object):
# Amount/currency lookup implementation
if not stored_trade_reference:
self.log.info(f"No reference in DB refs for {reference}", reference=reference)
self.log.info(f"No reference in DB refs for {reference}")
self.irc.sendmsg(f"No reference in DB refs for {reference}")
# Try checking just amount and currency, as some people (usually people buying small amounts)
# are unable to put in a reference properly.
self.log.info("Checking against amount and currency for TXID {txid}", txid=txid)
self.log.info(f"Checking against amount and currency for TXID {txid}")
self.irc.sendmsg(f"Checking against amount and currency for TXID {txid}")
stored_trade = self.find_trade(txid, currency, amount)
if not stored_trade:
self.log.info(
"Failed to get reference by amount and currency: {txid} {currency} {amount}",
txid=txid,
currency=currency,
amount=amount,
)
self.log.info(f"Failed to get reference by amount and currency: {txid} {currency} {amount}")
self.irc.sendmsg(f"Failed to get reference by amount and currency: {txid} {currency} {amount}")
return
if currency == "USD":
@ -215,7 +209,7 @@ class Transactions(object):
if not stored_trade:
stored_trade = self.get_ref(stored_trade_reference.pop())
if not stored_trade:
self.log.info("No reference in DB for {reference}", reference=reference)
self.log.info(f"No reference in DB for {reference}")
self.irc.sendmsg(f"No reference in DB for {reference}")
return
@ -224,11 +218,7 @@ class Transactions(object):
# Make sure it was sent in the expected currency
if not stored_trade["currency"] == currency:
self.log.info(
"Currency mismatch, Agora: {currency_agora} / Sink: {currency}",
currency_agora=stored_trade["currency"],
currency=currency,
)
self.log.info(f"Currency mismatch, Agora: {stored_trade['currency']} / Sink: {currency}")
self.irc.sendmsg(f"Currency mismatch, Agora: {stored_trade['currency']} / Sink: {currency}")
return
@ -238,19 +228,10 @@ class Transactions(object):
return
# If the amount does not match exactly, get the min and max values for our given acceptable margins for trades
min_amount, max_amount = self.money.get_acceptable_margins(currency, stored_trade["amount"])
self.log.info(
"Amount does not match exactly, trying with margins: min: {min_amount} / max: {max_amount}",
min_amount=min_amount,
max_amount=max_amount,
)
self.log.info(f"Amount does not match exactly, trying with margins: min: {min_amount} / max: {max_amount}")
self.irc.sendmsg(f"Amount does not match exactly, trying with margins: min: {min_amount} / max: {max_amount}")
if not min_amount < amount < max_amount:
self.log.info(
"Amount mismatch - not in margins: {amount} (min: {min_amount} / max: {max_amount}",
amount=stored_trade["amount"],
min_amount=min_amount,
max_amount=max_amount,
)
self.log.info("Amount mismatch - not in margins: {stored_trade['amount']} (min: {min_amount} / max: {max_amount}")
self.irc.sendmsg(f"Amount mismatch - not in margins: {stored_trade['amount']} (min: {min_amount} / max: {max_amount}")
return
@ -260,7 +241,7 @@ class Transactions(object):
# Store the trade ID so we can release it easily
to_store["trade_id"] = stored_trade["id"]
if not state == "completed":
self.log.info("Storing incomplete trade: {id}", id=txid)
self.log.info(f"Storing incomplete trade: {txid}")
r.hmset(f"tx.{txid}", to_store)
# Don't procees further if state is not "completed"
return
@ -270,7 +251,7 @@ class Transactions(object):
self.ux.notify.notify_complete_trade(amount, currency)
def release_funds(self, trade_id, reference):
self.log.info("All checks passed, releasing funds for {trade_id} {reference}", trade_id=trade_id, reference=reference)
self.log.info(f"All checks passed, releasing funds for {trade_id} {reference}")
self.irc.sendmsg(f"All checks passed, releasing funds for {trade_id} / {reference}")
rtrn = self.agora.release_funds(trade_id)
self.agora.agora.contact_message_post(trade_id, "Thanks! Releasing now :)")
@ -300,14 +281,14 @@ class Transactions(object):
"reference": reference,
"provider": provider,
}
self.log.info("Storing trade information: {info}", info=str(to_store))
self.log.info(f"Storing trade information: {str(to_store)}")
r.hmset(f"trade.{reference}", to_store)
self.irc.sendmsg(f"Generated reference for {trade_id}: {reference}")
self.ux.notify.notify_new_trade(amount, currency)
if settings.Agora.Send == "1":
self.agora.agora.contact_message_post(trade_id, f"Hi! When sending the payment please use reference code: {reference}")
if existing_ref:
return convert(existing_ref)
return util.convert(existing_ref)
else:
return reference
@ -332,7 +313,7 @@ class Transactions(object):
if stored_trade["currency"] == currency and float(stored_trade["amount"]) == float(amount):
matching_refs.append(stored_trade)
if len(matching_refs) != 1:
self.log.error("Find trade returned multiple results for TXID {txid}: {matching_refs}", txid=txid, matching_refs=matching_refs)
self.log.error(f"Find trade returned multiple results for TXID {txid}: {matching_refs}")
return False
return matching_refs[0]
@ -346,7 +327,7 @@ class Transactions(object):
ref_keys = r.keys("trade.*.reference")
for key in ref_keys:
references.append(r.get(key))
return convert(references)
return util.convert(references)
def get_ref_map(self):
"""
@ -357,9 +338,9 @@ class Transactions(object):
references = {}
ref_keys = r.keys("trade.*.reference")
for key in ref_keys:
tx = convert(key).split(".")[1]
tx = util.convert(key).split(".")[1]
references[tx] = r.get(key)
return convert(references)
return util.convert(references)
def get_ref(self, reference):
"""
@ -370,7 +351,7 @@ class Transactions(object):
:rtype: dict
"""
ref_data = r.hgetall(f"trade.{reference}")
ref_data = convert(ref_data)
ref_data = util.convert(ref_data)
if not ref_data:
return False
return ref_data
@ -394,7 +375,7 @@ class Transactions(object):
"""
for tx, reference in self.get_ref_map().items():
if reference not in references:
self.log.info("Archiving trade reference: {reference} / TX: {tx}", reference=reference, tx=tx)
self.log.info(f"Archiving trade reference: {reference} / TX: {tx}")
r.rename(f"trade.{tx}.reference", f"archive.trade.{tx}.reference")
r.rename(f"trade.{reference}", f"archive.trade.{reference}")
@ -408,7 +389,7 @@ class Transactions(object):
"""
refs = self.get_refs()
for reference in refs:
ref_data = convert(r.hgetall(f"trade.{reference}"))
ref_data = util.convert(r.hgetall(f"trade.{reference}"))
if not ref_data:
continue
if ref_data["id"] == tx:
@ -422,7 +403,7 @@ class Transactions(object):
:return: trade ID
:rtype: string
"""
ref_data = convert(r.hgetall(f"trade.{reference}"))
ref_data = util.convert(r.hgetall(f"trade.{reference}"))
if not ref_data:
return False
return ref_data["id"]
@ -448,12 +429,12 @@ class Transactions(object):
# Get the BTC -> USD exchange rate
btc_usd = self.agora.cg.get_price(ids="bitcoin", vs_currencies=["USD"])
# Convert the Agora XMR total to USD
total_usd_agora_xmr = float(total_xmr_agora) * xmr_usd["monero"]["usd"]
# Convert the Agora BTC total to USD
total_usd_agora_btc = float(total_btc_agora) * btc_usd["bitcoin"]["usd"]
# Convert the Agora XMR total to USD
total_usd_agora_xmr = float(total_xmr_agora) * xmr_usd["monero"]["usd"]
# Add it all up
total_usd_agora = total_usd_agora_xmr + total_usd_agora_btc
# total_usd = total_usd_agora + total_usd_revolut

View File

@ -1,11 +1,98 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
from httpx import ReadTimeout, ReadError, RemoteProtocolError
from datetime import datetime
import logging
log = Logger("util.global")
log = logging.getLogger("util")
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
# The background is set with 40 plus the number of the color, and the foreground with 30
# These are the sequences need to get colored ouput
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ = "\033[1m"
def formatter_message(message, use_color=True):
if use_color:
message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
else:
message = message.replace("$RESET", "").replace("$BOLD", "")
return message
COLORS = {"WARNING": YELLOW, "INFO": WHITE, "DEBUG": BLUE, "CRITICAL": YELLOW, "ERROR": RED}
class ColoredFormatter(logging.Formatter):
def __init__(self, msg, use_color=True):
logging.Formatter.__init__(self, msg)
self.use_color = use_color
def format(self, record):
levelname = record.levelname
if self.use_color and levelname in COLORS:
levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ
record.levelname = levelname_color
return logging.Formatter.format(self, record)
def get_logger(name):
# Define the logging format
FORMAT = "%(asctime)s %(levelname)s $BOLD%(name)13s$RESET - %(message)s"
COLOR_FORMAT = formatter_message(FORMAT, True)
color_formatter = ColoredFormatter(COLOR_FORMAT)
# formatter = logging.Formatter(
# Why is this so complicated?
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# ch.setFormatter(formatter)
ch.setFormatter(color_formatter)
# Define the logger on the base class
log = logging.getLogger(name)
# Add the handler and stop it being silly and printing everything twice
log.addHandler(ch)
log.propagate = False
return log
class ColoredLogger(logging.Logger):
FORMAT = "[$BOLD%(name)-20s$RESET][%(levelname)-18s] %(message)s ($BOLD%(filename)s$RESET:%(lineno)d)"
COLOR_FORMAT = formatter_message(FORMAT, True)
def __init__(self, name):
logging.Logger.__init__(self, name, logging.DEBUG)
color_formatter = ColoredFormatter(self.COLOR_FORMAT)
console = logging.StreamHandler()
console.setFormatter(color_formatter)
self.addHandler(console)
return
class Base(object):
def __init__(self):
name = self.__class__.__name__
# Set up all the logging stuff
self._setup_logger(name)
self.log.info("Class initialised")
def _setup_logger(self, name):
"""
Set up the logging handlers.
"""
self.log = get_logger(name)
def xmerge_attrs(init_map):
@ -78,10 +165,10 @@ def handle_exceptions(func):
if "error_code" in rtrn["response"]["error"]:
code = rtrn["response"]["error"]["error_code"]
if not code == 136:
log.error("API error: {code}", code=code)
log.error(f"API error: {code}")
return False
else:
log.error("API error: {code}", code=rtrn["response"]["error"])
log.error(f"API error: {rtrn['response']['error']}")
return False
return rtrn

View File

@ -1,6 +1,3 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
# import requests
# from json import dumps
@ -20,7 +17,7 @@ class UX(object):
"""
def __init__(self):
self.log = Logger("ux")
super().__init__()
self.irc = ux.irc.bot()
self.notify = ux.notify.Notify()

View File

@ -447,5 +447,32 @@ class IRCCommands(object):
@staticmethod
def run(cmd, spl, length, authed, msg, agora, tx, ux):
auth_url = agora.truelayer.create_auth_url()
auth_url = tx.truelayer.create_auth_url()
msg(f"Auth URL: {auth_url}")
class accounts(object):
name = "accounts"
authed = True
helptext = "Get a list of acccounts."
@staticmethod
def run(cmd, spl, length, authed, msg, agora, tx, ux):
accounts = tx.sinks.truelayer.get_accounts()
msg(dumps(accounts))
class transactions(object):
name = "transactions"
authed = True
helptext = "Get a list of transactions. Usage: transactions <account_id>"
@staticmethod
def run(cmd, spl, length, authed, msg, agora, tx, ux):
if length == 2:
account_id = spl[1]
transactions = tx.sinks.truelayer.get_transactions(account_id)
for transaction in transactions:
timestamp = transaction["timestamp"]
amount = transaction["amount"]
currency = transaction["currency"]
recipient = transaction["counter_party_preferred_name"]
msg(f"{timestamp} {amount}{currency} {recipient}")

View File

@ -1,5 +1,4 @@
# 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
@ -7,6 +6,7 @@ from twisted.internet.task import deferLater
# Project imports
from settings import settings
from ux.commands import IRCCommands
import util
class IRCBot(irc.IRCClient):
@ -106,7 +106,7 @@ class IRCBot(irc.IRCClient):
Called when we have signed on to IRC.
Join our channel.
"""
self.log.info("Signed on as %s" % (self.nickname))
self.log.info(f"Signed on as {self.nickname}")
deferLater(reactor, 2, self.join, self.channel)
def joined(self, channel):
@ -118,7 +118,7 @@ class IRCBot(irc.IRCClient):
:type channel: string
"""
self.agora.setup_loop()
self.log.info("Joined channel %s" % (channel))
self.log.info(f"Joined channel {channel}")
def privmsg(self, user, channel, msg):
"""
@ -141,7 +141,7 @@ class IRCBot(irc.IRCClient):
ident = user.split("!")[1]
ident = ident.split("@")[0]
self.log.info("(%s) %s: %s" % (channel, user, msg))
self.log.info(f"({channel}) {user}: {msg}")
if msg[0] == self.prefix:
if len(msg) > 1:
if msg.split()[0] != "!":
@ -171,7 +171,8 @@ class IRCBot(irc.IRCClient):
class IRCBotFactory(protocol.ClientFactory):
def __init__(self):
self.log = Logger("irc")
self.log = util.get_logger("IRC")
self.log.info("Class initialised")
def sendmsg(self, msg):
"""
@ -180,7 +181,7 @@ class IRCBotFactory(protocol.ClientFactory):
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)
self.log.error(f"Trying to send a message without connected client: {msg}")
return
def buildProtocol(self, addr):
@ -189,6 +190,7 @@ class IRCBotFactory(protocol.ClientFactory):
Passes through the Agora instance to IRC.
:return: IRCBot Protocol instance
"""
# Pass through the logger
prcol = IRCBot(self.log)
self.client = prcol
setattr(self.client, "agora", self.agora)
@ -205,7 +207,7 @@ class IRCBotFactory(protocol.ClientFactory):
:type connector: object
:type reason: string
"""
self.log.error("Lost connection: {reason}, reconnecting", reason=reason)
self.log.error(f"Lost connection: {reason}, reconnecting")
connector.connect()
def clientConnectionFailed(self, connector, reason):
@ -216,7 +218,7 @@ class IRCBotFactory(protocol.ClientFactory):
:type connector: object
:type reason: string
"""
self.log.error("Could not connect: {reason}", reason=reason)
self.log.error(f"Could not connect: {reason}")
connector.connect()

View File

@ -1,21 +1,16 @@
# Twisted/Klein imports
from twisted.logger import Logger
# Other library imports
import requests
# Project imports
from settings import settings
import util
class Notify(object):
class Notify(util.Base):
"""
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: