diff --git a/data/nodes.json b/data/nodes.json index 3ddad5e..c812a24 100644 --- a/data/nodes.json +++ b/data/nodes.json @@ -1,41 +1,61 @@ { - "mainnet": { - "tor": [ - "fdlnlt5mr5o7lmhg.onion:18081", - "xmkwypann4ly64gh.onion:18081", - "xmrtolujkxnlinre.onion:18081", - "xmrag4hf5xlabmob.onion:18081", - "monero26mmldsallmxok2kwamne4ve3mybvvn2yijsvss7ey63hc4yyd.onion:18081", - "nrw57zxw5zyevn3i.onion:18081", - "monero5sjoz5xmjn.onion:18081", - "56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089", - "mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081", - "moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089", - "moneroptqodufzxj.onion:18081", - "3hvpnd4xejtzcuowvru2wfjum5wjf7synigm44rrizr3k4v5vzam2bad.onion:18081" - ], - "clearnet": [ - "eu-west.node.xmr.pm:18089", - "eu-west-2.node.xmr.pm:18089", - "usa-east-va.node.xmr.pm:18089", - "canada.node.xmr.pm:18089", - "singapore.node.xmr.pm:18089", - "192.110.160.146:18089", - "nodes.hashvault.pro:18081", - "node.supportxmr.com:18081", - "node.imonero.org:18081", - "xmr-node-eu.cakewallet.com:18081", - "xmr-node-usa-east.cakewallet.com:18081", - "node.xmr.pt:18081", - "node.xmr.ru:18081", - "xmr-peer-070.cypherpunklabs.com:18081" - ] + "xmr": { + "mainnet": { + "tor": [ + "fdlnlt5mr5o7lmhg.onion:18081", + "xmkwypann4ly64gh.onion:18081", + "xmrtolujkxnlinre.onion:18081", + "xmrag4hf5xlabmob.onion:18081", + "monero26mmldsallmxok2kwamne4ve3mybvvn2yijsvss7ey63hc4yyd.onion:18081", + "nrw57zxw5zyevn3i.onion:18081", + "monero5sjoz5xmjn.onion:18081", + "56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089", + "mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081", + "moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089", + "moneroptqodufzxj.onion:18081", + "3hvpnd4xejtzcuowvru2wfjum5wjf7synigm44rrizr3k4v5vzam2bad.onion:18081" + ], + "clearnet": [ + "eu-west.node.xmr.pm:18089", + "eu-west-2.node.xmr.pm:18089", + "usa-east-va.node.xmr.pm:18089", + "canada.node.xmr.pm:18089", + "singapore.node.xmr.pm:18089", + "192.110.160.146:18089", + "nodes.hashvault.pro:18081", + "node.supportxmr.com:18081", + "node.imonero.org:18081", + "xmr-node-eu.cakewallet.com:18081", + "xmr-node-usa-east.cakewallet.com:18081", + "node.xmr.pt:18081", + "node.xmr.ru:18081", + "xmr-peer-070.cypherpunklabs.com:18081" + ] + }, + "stagenet": { + "tor": [], + "clearnet": [ + "run.your.own.node.xmr.pm:38089", + "super.fast.node.xmr.pm:38089" + ] + } }, - "stagenet": { - "tor": [], - "clearnet": [ - "run.your.own.node.xmr.pm:38089", - "super.fast.node.xmr.pm:38089" - ] + "wow": { + "mainnet": { + "tor": [ + "wowbuxx535x4exuexja2xfezpwcyznxkofui4ndjiectj4yuh2xheiid.onion:34568" + ], + "clearnet": [ + "wow.pwned.systems:34568", + "global.wownodes.com:34568", + "node.suchwow.xyz:34568", + "super.fast.node.xmr.pm:34568", + "wowbux.org:34568" + ] + }, + "stagenet": { + "tor": [], + "clearnet": [] + } } -} +} \ No newline at end of file diff --git a/fapi/factory.py b/fapi/factory.py index 93ccd1d..4aa37d9 100644 --- a/fapi/factory.py +++ b/fapi/factory.py @@ -19,7 +19,7 @@ nodes = {} user_agents = None txfiatdb = None -print("""\033[91m +print(f"""\033[91m █████▒▓█████ ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ██▀███ ▓██ ▒ ▓█ ▀▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ▓██ ▒ ██▒ ▒████ ░ ▒███ ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ░▄█ ▒ @@ -28,7 +28,7 @@ print("""\033[91m ▒ ░ ░░ ▒░ ░▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒▓ ░▒▓░ ░ ░ ░ ░ ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░ ░▒ ░ ▒░ ░ ░ ░ ░ ▒ ░ ░ ░░ ░ ░ ░░ ░ - ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ \033[0m + ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ {settings.crypto_symbol}\033[0m """.strip()) @@ -58,7 +58,7 @@ def create_app(): loop = asyncio.get_event_loop() f = open("data/nodes.json", "r") - nodes = json.loads(f.read()) + nodes = json.loads(f.read())[settings.crypto_symbol] f.close() f = open("data/user_agents.txt", "r") @@ -67,13 +67,26 @@ def create_app(): from fapi.fapi import FeatherApi from fapi.utils import loopyloop, TxFiatDb, XmrRig - txfiatdb = TxFiatDb(settings.crypto_name, settings.crypto_block_date_start) - loop.create_task(loopyloop(20, FeatherApi.xmrto_rates, FeatherApi.after_xmrto)) + txfiatdb = TxFiatDb(settings.crypto_block_date_start) + + if settings.crypto_symbol == "xmr": + loop.create_task(loopyloop(20, FeatherApi.xmrto_rates, FeatherApi.after_xmrto)) + loop.create_task(loopyloop(120, FeatherApi.crypto_rates, FeatherApi.after_crypto)) loop.create_task(loopyloop(600, FeatherApi.fiat_rates, FeatherApi.after_fiat)) - loop.create_task(loopyloop(300, FeatherApi.ccs, FeatherApi.after_ccs)) + + if settings.crypto_symbol == "xmr": + loop.create_task(loopyloop(300, FeatherApi.ccs, FeatherApi.after_ccs)) + elif settings.crypto_symbol == "wow": + loop.create_task(loopyloop(300, FeatherApi.wfs, FeatherApi.after_wfs)) + loop.create_task(loopyloop(900, FeatherApi.reddit, FeatherApi.after_reddit)) - loop.create_task(loopyloop(60, FeatherApi.blockheight, FeatherApi.after_blockheight)) + + if settings.crypto_symbol == "xmr": + loop.create_task(loopyloop(60, FeatherApi.blockheight, FeatherApi.after_blockheight)) + elif settings.crypto_symbol == "wow": + loop.create_task(loopyloop(60, FeatherApi.wowheight, FeatherApi.after_blockheight)) + loop.create_task(loopyloop(60, FeatherApi.check_nodes, FeatherApi.after_check_nodes)) loop.create_task(loopyloop(43200, txfiatdb.update)) loop.create_task(loopyloop(43200, XmrRig.releases, XmrRig.after_releases)) diff --git a/fapi/fapi.py b/fapi/fapi.py index 4b5f016..55209f4 100644 --- a/fapi/fapi.py +++ b/fapi/fapi.py @@ -3,25 +3,34 @@ # Copyright (c) 2020, dsc@xmr.pm import json +from typing import Union import aiohttp from bs4 import BeautifulSoup from aiohttp_socks import ProxyType, ProxyConnector, ChainProxyConnector -from fapi.utils import broadcast_blockheight, broadcast_nodes, httpget, BlockHeight +from fapi.utils import broadcast_nodes, httpget, BlockHeight import settings class FeatherApi: @staticmethod - async def redis_get(key): + async def redis_get(key: str) -> dict: from fapi.factory import app, cache try: - data = await cache.get(key) + data = await cache.get(f"{settings.crypto_symbol}_{key}") if data: return json.loads(data) except Exception as ex: - app.logger.error(f"Redis error: {ex}") + app.logger.error(f"Redis SET error: {ex}") + + @staticmethod + async def redis_set(key: str, val: Union[list, dict]) -> None: + from fapi.factory import app, cache + try: + await cache.set(f"{settings.crypto_symbol}_{key}", json.dumps(val)) + except Exception as ex: + app.logger.error(f"Redis GET error: {ex}") @staticmethod async def xmrto_rates(): @@ -75,7 +84,6 @@ class FeatherApi: return crypto_rates # grab WOW price while we're at it... - try: _result = await httpget(settings.urls["crypto_wow_rates"]) if not _result: @@ -92,7 +100,7 @@ class FeatherApi: "price_change_percentage_24h": 0.0 }) - await cache.set("crypto_rates", json.dumps(crypto_rates)) + await FeatherApi.redis_set("crypto_rates", crypto_rates) return crypto_rates @staticmethod @@ -137,12 +145,11 @@ class FeatherApi: result = await httpget(settings.urls["fiat_rates"], json=True) if not result: raise Exception("empty response") - await cache.set("fiat_rates", json.dumps(result)) + await FeatherApi.redis_set("fiat_rates", result) return result except Exception as ex: app.logger.error(f"error parsing fiat_rates blob: {ex}") - # old cache app.logger.warning("USING OLD CACHE FOR FIAT RATES") return fiat_rates @@ -166,6 +173,62 @@ class FeatherApi: } }) + @staticmethod + async def wfs(): + # wownero-funding-system + from fapi.factory import app, cache + wfs = await FeatherApi.redis_get("wfs") + if wfs and app.config["DEBUG"]: + return wfs + + try: + blob = await httpget(f"{settings.urls['wfs']}?offset=0&limit=1&status=2") + if "data" not in blob: + raise Exception("invalid json response") + await FeatherApi.redis_set("wfs", blob) + return blob + except Exception as ex: + app.logger.error(f"error fetching wfs JSON: {ex}") + + # old cache + app.logger.warning("USING OLD CACHE FOR WFS") + return wfs + + @staticmethod + async def after_wfs(data): + from fapi.factory import app, cache, api_data, connected_websockets + if not data: + return + + proposals = [] + for p in data['data']: + item = { + "address": p["addr_donation"], + "url": f"https://funding.wownero.com/proposal/{p['id']}", + "state": "FUNDING-REQUIRED", + "date": p['date_posted'], + "title": p['headline'], + 'target_amount': p['funds_target'], + 'raised_amount': round(p['funds_target'] / 100 * p['funded_pct'], 2), + 'contributors': 0, + 'percentage_funded': round(p['funded_pct'], 2), + 'author': p['user'] + } + proposals.append(item) + data = proposals + + _data = api_data.get("wfs", {}) + _data = json.dumps(_data, sort_keys=True, indent=4) + if json.dumps(data, sort_keys=True, indent=4) == _data: + return + + api_data["wfs"] = data + for queue in connected_websockets: + await queue.put({ + "cmd": "wfs", + "data": api_data["wfs"] + }) + @staticmethod async def ccs(): # CCS JSON api is broken ;x https://hackerone.com/reports/934231 @@ -235,7 +298,7 @@ class FeatherApi: except Exception as ex: app.logger.error(f"error parsing a ccs item: {ex}") - await cache.set("ccs", json.dumps(data)) + await FeatherApi.redis_set("ccs", data) return data @staticmethod @@ -264,7 +327,7 @@ class FeatherApi: return reddit try: - blob = await httpget(settings.urls["reddit"]) + blob = await httpget(settings.urls[f"reddit_{settings.crypto_symbol}"]) if not blob: raise Exception("no data from url") blob = [{ @@ -276,7 +339,7 @@ class FeatherApi: # success if blob: - await cache.set("reddit", json.dumps(blob)) + await FeatherApi.redis_set("reddit", blob) return blob except Exception as ex: app.logger.error(f"error parsing reddit blob: {ex}") @@ -302,6 +365,16 @@ class FeatherApi: "data": api_data["reddit"] }) + @staticmethod + async def wowheight(): + from fapi.factory import app, cache + data = {"mainnet": 0, "stagenet": 0} + try: + data['mainnet'] = await BlockHeight.wowheight() + except Exception as ex: + app.logger.error(f"Could not fetch blockheight for wownero: {ex}") + return data + @staticmethod async def blockheight(): from fapi.factory import app, cache @@ -334,7 +407,14 @@ class FeatherApi: changed = True if changed: - await broadcast_blockheight() + from fapi.factory import connected_websockets, api_data + for queue in connected_websockets: + await queue.put({ + "cmd": "blockheights", + "data": { + "height": api_data.get("blockheights", {}) + } + }) @staticmethod async def check_nodes(): diff --git a/fapi/utils.py b/fapi/utils.py index dc3fb4c..a851c00 100644 --- a/fapi/utils.py +++ b/fapi/utils.py @@ -16,6 +16,15 @@ import settings class BlockHeight: + @staticmethod + async def wowheight(stagenet: bool = False): + re_blockheight = r"block\/(\d+)\"\>" + url = "https://explore.wownero.com/" + content = await httpget(url, json=False) + xmrchain = re.findall(re_blockheight, content) + current = max(map(int, xmrchain)) + return current + @staticmethod async def xmrchain(stagenet: bool = False): re_blockheight = r"block\/(\d+)\"\>" @@ -66,17 +75,6 @@ def collect_websocket(func): return wrapper -async def broadcast_blockheight(): - from fapi.factory import connected_websockets, api_data - for queue in connected_websockets: - await queue.put({ - "cmd": "blockheights", - "data": { - "height": api_data.get("blockheights", {}) - } - }) - - async def broadcast_nodes(): from fapi.factory import connected_websockets, api_data for queue in connected_websockets: @@ -100,12 +98,12 @@ def random_agent(): class TxFiatDb: - # historical fiat price db for given symbol - def __init__(self, symbol, block_date_start): - self.fn = "data/fiatdb" - self.symbol = symbol + # class for keeping the historical fiat price db up-to-date + def __init__(self, block_date_start): + self.fn = f"data/fiatdb_{settings.crypto_symbol}" + self.symbol = settings.crypto_name self.block_start = block_date_start - self._url = "https://www.coingecko.com/price_charts/69/usd/max.json" + self._url = settings.urls_fiathistory[settings.crypto_symbol] self.data = {} self.load() @@ -125,9 +123,9 @@ class TxFiatDb: return rtn def load(self): - if not os.path.exists("fiatdb"): + if not os.path.exists(self.fn): return {} - f = open("fiatdb", "r") + f = open(self.fn, "r") data = f.read() f.close() data = json.loads(data) @@ -136,14 +134,14 @@ class TxFiatDb: self.data = {int(k): {int(_k): {int(__k): __v for __k, __v in _v.items()} for _k, _v in v.items()} for k, v in data.items()} def write(self): - f = open("fiatdb", "w") + f = open(self.fn, "w") f.write(json.dumps(self.data)) f.close() async def update(self): try: content = await httpget(self._url, json=True) - if not "stats" in content: + if "stats" not in content: raise Exception() except Exception as ex: return