raspiblitz/home.admin/config.scripts/blitz.ip2tor.py

644 lines
21 KiB
Python
Raw Normal View History

2020-05-22 19:24:46 +02:00
#!/usr/bin/python3
import sys
import locale
2020-05-22 20:06:59 +02:00
import requests
import json
2020-05-23 02:44:08 +02:00
import math
2020-05-24 13:11:31 +02:00
import time
import datetime, time
2020-05-23 13:49:13 +02:00
import codecs, grpc, os
2020-05-24 13:11:31 +02:00
from pathlib import Path
from blitzpy import RaspiBlitzConfig
from lndlibs import rpc_pb2 as lnrpc
from lndlibs import rpc_pb2_grpc as rpcstub
2020-05-23 13:49:13 +02:00
2020-05-25 00:19:32 +02:00
####### SCRIPT INFO #########
2020-05-22 19:24:46 +02:00
# display config script info
if len(sys.argv) <= 1 or sys.argv[1] == "-h" or sys.argv[1] == "help":
2020-05-23 02:44:08 +02:00
print("# manage ip2tor subscriptions for raspiblitz")
print("# blitz.ip2tor.py menu")
2020-05-25 00:19:32 +02:00
print("# blitz.ip2tor.py shop-list [shopurl]")
print("# blitz.ip2tor.py shop-order [shopurl] [hostid] [toraddress:port] [duration] [msats]")
print("# blitz.ip2tor.py subscriptions-list")
print("# blitz.ip2tor.py subscriptions-renew [secondsBeforeSuspend]")
2020-05-22 19:24:46 +02:00
sys.exit(1)
2020-05-25 00:19:32 +02:00
####### BASIC SETTINGS #########
2020-05-24 13:11:31 +02:00
cfg = RaspiBlitzConfig()
2020-05-24 18:04:56 +02:00
session = requests.session()
2020-05-24 13:11:31 +02:00
if Path("/mnt/hdd/raspiblitz.conf").is_file():
print("# blitz.ip2tor.py")
cfg.reload()
2020-05-24 18:38:09 +02:00
#DEFAULT_SHOPURL="shopdeu2vdhazvmllyfagdcvlpflzdyt5gwftmn4hjj3zw2oyelksaid.onion"
DEFAULT_SHOPURL="shop.ip2t.org"
2020-05-24 13:11:31 +02:00
LND_IP="127.0.0.1"
2020-05-24 17:48:33 +02:00
LND_ADMIN_MACAROON_PATH="/mnt/hdd/app-data/lnd/data/chain/{0}/{1}net/admin.macaroon".format(cfg.network,cfg.chain)
2020-05-24 13:11:31 +02:00
LND_TLS_PATH="/mnt/hdd/app-data/lnd/tls.cert"
2020-05-24 18:38:09 +02:00
# make sure to make requests thru TOR 127.0.0.1:9050
session.proxies = {'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050'}
2020-05-24 13:11:31 +02:00
else:
print("# blitz.ip2tor.py (development env)")
DEFAULT_SHOPURL="shop.ip2t.org"
LND_IP="192.168.178.95"
LND_ADMIN_MACAROON_PATH="/Users/rotzoll/Downloads/RaspiBlitzCredentials/admin.macaroon"
LND_TLS_PATH="/Users/rotzoll/Downloads/RaspiBlitzCredentials/tls.cert"
2020-05-25 00:19:32 +02:00
####### HELPER FUNCTIONS #########
2020-05-24 13:11:31 +02:00
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def parseDate(datestr):
return datetime.datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%S.%fZ")
def secondsLeft(dateObj):
2020-05-24 13:46:47 +02:00
return round((dateObj - datetime.datetime.utcnow()).total_seconds())
2020-05-24 13:11:31 +02:00
# takes a shopurl from user input and turns it into the format needed
# also makes sure that .onion addresses run just with http not https
def normalizeShopUrl(shopurlUserInput):
# basic checks and formats
if len(shopurlUserInput) < 3: return
shopurlUserInput = shopurlUserInput.lower()
shopurlUserInput = shopurlUserInput.replace(" ", "")
shopurlUserInput = shopurlUserInput.replace("\n", "")
shopurlUserInput = shopurlUserInput.replace("\r", "")
# remove protocol from the beginning (if needed)
if shopurlUserInput.find("://") > 0:
shopurlUserInput = shopurlUserInput[shopurlUserInput.find("://")+3:]
# remove all path after domain
if shopurlUserInput.find("/") > 0:
shopurlUserInput = shopurlUserInput[:shopurlUserInput.find("/")]
# add correct protocol again
if ( not shopurlUserInput.startswith("http://") and not shopurlUserInput.startswith("https://") ):
if shopurlUserInput.endswith(".onion"):
shopurlUserInput = "http://{0}".format(shopurlUserInput)
else:
shopurlUserInput = "https://{0}".format(shopurlUserInput)
return shopurlUserInput
2020-05-22 19:24:46 +02:00
2020-05-25 00:19:32 +02:00
####### IP2TOR API CALLS #########
2020-05-23 13:49:13 +02:00
def apiGetHosts(session, shopurl):
2020-05-23 02:44:08 +02:00
print("# apiGetHosts")
hosts=[]
# make HTTP request
try:
2020-05-24 18:00:48 +02:00
url="{0}/api/v1/public/hosts/".format(shopurl)
response = session.get(url)
2020-05-23 02:44:08 +02:00
except Exception as e:
2020-05-24 18:00:48 +02:00
eprint(url)
2020-05-24 13:11:31 +02:00
eprint(e)
2020-05-23 02:44:08 +02:00
print("error='FAILED HTTP REQUEST'")
return
if response.status_code != 200:
2020-05-24 18:00:48 +02:00
eprint(url)
2020-05-24 13:11:31 +02:00
eprint(response.content)
2020-05-23 02:44:08 +02:00
print("error='FAILED HTTP CODE ({0})'".format(response.status_code))
return
# parse & validate data
try:
jData = json.loads(response.content)
except Exception as e:
2020-05-24 13:11:31 +02:00
eprint(e)
2020-05-23 02:44:08 +02:00
print("error='FAILED JSON PARSING'")
return
if not isinstance(jData, list):
print("error='NOT A JSON LIST'")
return
for idx, hostEntry in enumerate(jData):
try:
# ignore if not offering tor bridge
if not hostEntry['offers_tor_bridges']: continue
# ignore if duration is less than an hour
if hostEntry['tor_bridge_duration'] < 3600: continue
# add duration per hour value
hostEntry['tor_bridge_duration_hours'] = math.floor(hostEntry['tor_bridge_duration']/3600)
# ignore if prices are negative or below one sat (maybe msats later)
if hostEntry['tor_bridge_price_initial'] < 1000: continue
if hostEntry['tor_bridge_price_extension'] < 1000: continue
# add price in sats
hostEntry['tor_bridge_price_initial_sats'] = math.ceil(hostEntry['tor_bridge_price_initial']/1000)
hostEntry['tor_bridge_price_extension_sats'] = math.ceil(hostEntry['tor_bridge_price_extension']/1000)
# ignore name is less then 3 chars
if len(hostEntry['name']) < 3: continue
# ignore id with zero value
if len(hostEntry['id']) < 1: continue
# shorten names to 20 chars max
hostEntry['name'] = hostEntry['name'][:20]
except Exception as e:
2020-05-24 13:11:31 +02:00
eprint(e)
print("error='PARSING HOST ENTRY'")
return
2020-05-23 02:44:08 +02:00
hosts.append(hostEntry)
print("# found {0} valid torbridge hosts".format(len(hosts)))
return hosts
2020-05-24 13:11:31 +02:00
def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
2020-05-23 02:44:08 +02:00
2020-05-24 13:11:31 +02:00
print("# apiPlaceOrderNew")
2020-05-23 02:44:08 +02:00
try:
2020-05-24 13:11:31 +02:00
postData={
'product': "tor_bridge",
'host_id': hostid,
'tos_accepted': True,
'comment': 'test',
'target': toraddressWithPort,
'public_key': ''
}
response = session.post("{0}/api/v1/public/order/".format(shopurl), data=postData)
2020-05-23 02:44:08 +02:00
except Exception as e:
2020-05-24 13:11:31 +02:00
eprint(e)
2020-05-23 02:44:08 +02:00
print("error='FAILED HTTP REQUEST'")
return
if response.status_code != 201:
2020-05-24 13:11:31 +02:00
eprint(response.content)
2020-05-23 13:49:13 +02:00
print("error='FAILED HTTP CODE ({0}) != 201'".format(response.status_code))
2020-05-23 02:44:08 +02:00
return
# parse & validate data
try:
jData = json.loads(response.content)
2020-05-23 13:49:13 +02:00
if len(jData['id']) == 0:
print("error='MISSING ID'")
return
2020-05-23 02:44:08 +02:00
except Exception as e:
2020-05-24 13:11:31 +02:00
eprint(e)
2020-05-23 02:44:08 +02:00
print("error='FAILED JSON PARSING'")
return
2020-05-23 13:49:13 +02:00
return jData['id']
2020-05-24 13:11:31 +02:00
def apiPlaceOrderExtension(session, shopurl, bridgeid):
print("# apiPlaceOrderExtension")
try:
url="{0}/api/v1/public/tor_bridges/{1}/extend/".format(shopurl, bridgeid)
response = session.post(url)
except Exception as e:
eprint(url)
eprint(e)
print("error='FAILED HTTP REQUEST'")
return
if response.status_code != 200 and response.status_code != 201:
eprint(response.content)
print("error='FAILED HTTP CODE ({0}) != 201'".format(response.status_code))
return
# parse & validate data
try:
jData = json.loads(response.content)
if len(jData['po_id']) == 0:
print("error='MISSING ID'")
return
except Exception as e:
eprint(e)
print("error='FAILED JSON PARSING'")
return
return jData['po_id']
2020-05-23 13:49:13 +02:00
def apiGetOrder(session, shopurl, orderid):
print("# apiGetOrder")
# make HTTP request
try:
2020-05-24 13:11:31 +02:00
response = session.get("{0}/api/v1/public/pos/{1}/".format(shopurl,orderid))
2020-05-23 13:49:13 +02:00
except Exception as e:
2020-05-24 13:11:31 +02:00
eprint(e)
2020-05-23 13:49:13 +02:00
print("error='FAILED HTTP REQUEST'")
return
if response.status_code != 200:
2020-05-24 13:11:31 +02:00
eprint(response.content)
2020-05-23 13:49:13 +02:00
print("error='FAILED HTTP CODE ({0})'".format(response.status_code))
return
# parse & validate data
try:
jData = json.loads(response.content)
if len(jData['item_details']) == 0:
print("error='MISSING ITEM'")
return
if len(jData['ln_invoices']) > 1:
print("error='MORE THEN ONE INVOICE'")
return
except Exception as e:
2020-05-24 13:11:31 +02:00
eprint(e)
print("error='FAILED JSON PARSING'")
return
return jData
def apiGetBridgeStatus(session, shopurl, bridgeid):
print("# apiGetBridgeStatus")
# make HTTP request
try:
response = session.get("{0}/api/v1/public/tor_bridges/{1}/".format(shopurl,bridgeid))
except Exception as e:
eprint(e)
print("error='FAILED HTTP REQUEST'")
return
if response.status_code != 200:
eprint(response.content)
print("error='FAILED HTTP CODE ({0})'".format(response.status_code))
return
# parse & validate data
try:
jData = json.loads(response.content)
if len(jData['id']) == 0:
print("error='ID IS MISSING'")
return
except Exception as e:
eprint(e)
2020-05-23 13:49:13 +02:00
print("error='FAILED JSON PARSING'")
return
2020-05-23 02:44:08 +02:00
2020-05-23 13:49:13 +02:00
return jData
2020-05-25 00:19:32 +02:00
####### LND API CALLS #########
2020-05-23 13:49:13 +02:00
def lndDecodeInvoice(lnInvoiceString):
2020-05-24 13:11:31 +02:00
try:
# call LND GRPC API
macaroon = codecs.encode(open(LND_ADMIN_MACAROON_PATH, 'rb').read(), 'hex')
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
cert = open(LND_TLS_PATH, 'rb').read()
ssl_creds = grpc.ssl_channel_credentials(cert)
channel = grpc.secure_channel("{0}:10009".format(LND_IP), ssl_creds)
stub = rpcstub.LightningStub(channel)
request = lnrpc.PayReqString(
pay_req=lnInvoiceString,
)
response = stub.DecodePayReq(request, metadata=[('macaroon', macaroon)])
# validate results
if response.num_msat <= 0:
print("error='ZERO INVOICES NOT ALLOWED'")
return
except Exception as e:
2020-05-24 17:48:33 +02:00
eprint(e)
2020-05-24 13:11:31 +02:00
print("error='FAILED LND INVOICE DECODING'")
return
return response
def lndPayInvoice(lnInvoiceString):
try:
# call LND GRPC API
macaroon = codecs.encode(open(LND_ADMIN_MACAROON_PATH, 'rb').read(), 'hex')
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
cert = open(LND_TLS_PATH, 'rb').read()
ssl_creds = grpc.ssl_channel_credentials(cert)
channel = grpc.secure_channel("{0}:10009".format(LND_IP), ssl_creds)
stub = rpcstub.LightningStub(channel)
request = lnrpc.SendRequest(
payment_request=lnInvoiceString,
)
response = stub.SendPaymentSync(request, metadata=[('macaroon', macaroon)])
# validate results
if len(response.payment_error) > 0:
print("error='PAYMENT FAILED'")
print("error_detail='{}'".format(response.payment_error))
return
except Exception as e:
print("error='FAILED LND INVOICE PAYMENT'")
return
2020-05-23 13:49:13 +02:00
2020-05-24 13:11:31 +02:00
return response
2020-05-23 13:49:13 +02:00
2020-05-25 00:19:32 +02:00
####### PROCESS FUNCTIONS #########
def shopList(shopUrl):
print("#### GET HOSTS")
shopUrl=normalizeShopUrl(shopUrl)
return apiGetHosts(session, shopUrl)
def shopOrder(shopurl, hostid, toraddress, duration, msatsFirst):
print("#### PLACE ORDER")
shopUrl=normalizeShopUrl(shopUrl)
orderid = apiPlaceOrderNew(session, shopUrl, hostid, torTarget)
if orderid is None: sys.exit()
print("#### WAIT UNTIL INVOICE IS AVAILABLE")
loopCount=0
while True:
loopCount+=1
print("# Loop {0}".format(loopCount))
order = apiGetOrder(session, shopUrl, orderid)
if order is None: sys.exit()
if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None: break
if loopCount > 30:
eprint("# server is not able to deliver a invoice within timeout")
print("error='timeout on getting invoice'")
sys.exit()
time.sleep(2)
# get data from now complete order
paymentRequestStr = order['ln_invoices'][0]['payment_request']
bridge_id = order['item_details'][0]['product']['id']
bridge_ip = order['item_details'][0]['product']['host']['ip']
bridge_port = order['item_details'][0]['product']['port']
print("#### DECODE INVOICE & CHECK)")
print("# invoice: {0}".format(paymentRequestStr))
paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr)
if paymentRequestDecoded is None: sys.exit()
print("# amount as advertised: {0}".format(msatsFirst))
print("# amount in invoice is: {0}".format(paymentRequestDecoded.num_msat))
if msatsFirst < paymentRequestDecoded.num_msat:
eprint("# invoice wants more the advertised before -> EXIT")
print("error='invoice other amount than advertised'")
sys.exit(1)
print("#### PAY INVOICE")
payedInvoice = lndPayInvoice(paymentRequestStr)
if payedInvoice is None: sys.exit()
print('# OK PAYMENT SENT')
print("#### CHECK IF BRIDGE IS READY")
loopCount=0
while True:
loopCount+=1
print("## Loop {0}".format(loopCount))
bridge = apiGetBridgeStatus(session, shopUrl, bridge_id)
if bridge is None: sys.exit()
if bridge['status'] == "A": break
if loopCount > 60:
eprint("# timeout bridge not getting ready")
print("error='timeout on waiting for active bridge'")
sys.exit()
time.sleep(3)
# get data from ready bride
bridge_suspendafter = bridge['suspend_after']
bridge_port = bridge['port']
print("#### CHECK IF DURATION DELIVERED AS PROMISED")
secondsDelivered=secondsLeft(parseDate(bridge_suspendafter))
print("# delivered({0}) promised({1})".format(secondsDelivered, duration))
if (secondsDelivered + 600) < duration:
print("warning='delivered duration shorter than advertised'")
print("# OK - BRIDGE READY: {0}:{1} -> {2}".format(bridge_ip, bridge_port, torTarget))
def subscriptionExtend(shopUrl, bridgeid, duration, msatsFirst):
print("#### PLACE EXTENSION ORDER")
shopUrl=normalizeShopUrl(shopUrl)
orderid = apiPlaceOrderExtension(session, shopUrl, bridgeid)
if orderid is None: sys.exit()
print("#### WAIT UNTIL INVOICE IS AVAILABLE")
loopCount=0
while True:
loopCount+=1
print("## Loop {0}".format(loopCount))
order = apiGetOrder(session, shopUrl, orderid)
if order is None: sys.exit()
if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None: break
if loopCount > 30:
eprint("# server is not able to deliver a invoice within timeout")
print("error='timeout on getting invoice'")
sys.exit()
time.sleep(2)
paymentRequestStr = order['ln_invoices'][0]['payment_request']
print("#### DECODE INVOICE & CHECK AMOUNT")
print("# invoice: {0}".format(paymentRequestStr))
paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr)
if paymentRequestDecoded is None: sys.exit()
print("# amount as advertised: {0}".format(msatsNext))
print("# amount in invoice is: {0}".format(paymentRequestDecoded.num_msat))
if msatsNext < paymentRequestDecoded.num_msat:
eprint("# invoice wants more the advertised before -> EXIT")
print("error='invoice other amount than advertised'")
sys.exit(1)
print("#### PAY INVOICE")
payedInvoice = lndPayInvoice(paymentRequestStr)
if payedInvoice is None: sys.exit()
print("#### CHECK IF BRIDGE GOT EXTENDED")
loopCount=0
while True:
loopCount+=1
print("## Loop {0}".format(loopCount))
bridge = apiGetBridgeStatus(session, shopUrl, bridgeid)
if bridge['suspend_after'] != bridge_suspendafter: break
if loopCount > 60:
eprint("# timeout bridge not getting ready")
print("error='timeout on waiting for extending bridge'")
sys.exit()
time.sleep(3)
print("#### CHECK IF DURATION DELIVERED AS PROMISED")
secondsLeftOld = secondsLeft(parseDate(bridge_suspendafter))
secondsLeftNew = secondsLeft(parseDate(bridge['suspend_after']))
secondsExtended = secondsLeftNew - secondsLeftOld
print("# secondsExtended({0}) promised({1})".format(secondsExtended, duration))
if secondsExtended < duration:
print("warning='delivered duration shorter than advertised'")
print("# BRIDGE GOT EXTENDED: {0} -> {1}".format(bridge_suspendafter, bridge['suspend_after']))
2020-05-23 13:49:13 +02:00
2020-05-25 00:19:32 +02:00
####### COMMANDS #########
###############
# MENU
# use for ssh shell menu
###############
if sys.argv[1] == "menu":
# late imports - so that rest of script can run also if dependency is not available
from dialog import Dialog
shopurl = DEFAULT_SHOPURL
2020-05-25 01:10:03 +02:00
while True:
# input shop url
d = Dialog(dialog="dialog",autowidgetsize=True)
2020-05-25 01:17:57 +02:00
d.set_background_title("Select ip2tor Bridge Shop (communication secured thru TOR)")
code, text = d.inputbox("Enter Domain of a IP2TOR shop (PRESS ENTER FOR DEFAULT):", height=10, width=60, init=shopurl)
2020-05-25 01:10:03 +02:00
# if user canceled
if code != d.OK: sys.exit(0)
# get host list from shop
2020-05-25 01:17:57 +02:00
shopurl = text
2020-05-25 01:10:03 +02:00
hosts = shopList(shopurl)
if hosts is None:
# shopurl not working
print("NONE")
time.sleep(3)
elif len(hosts) == 0:
# shopurl not working
print("NO HOSTS")
time.sleep(3)
else:
# ok we got hosts - continue
break
2020-05-25 00:19:32 +02:00
2020-05-25 01:10:03 +02:00
# TODO: User entry of shopurl - loop until working or cancel
2020-05-25 00:19:32 +02:00
# create menu to select shop - TODO: also while loop list & detail until cancel or subscription
2020-05-25 01:10:03 +02:00
host=None
2020-05-25 00:19:32 +02:00
choices = []
for idx, hostEntry in enumerate(hosts):
2020-05-25 00:28:04 +02:00
choices.append( ("{0}".format(idx), "{0} ({1} hours, first: {2} sats, next: {3} sats)".format(hostEntry['name'].ljust(20), hostEntry['tor_bridge_duration_hours'], hostEntry['tor_bridge_price_initial_sats'], hostEntry['tor_bridge_price_extension_sats'])) )
2020-05-25 00:19:32 +02:00
2020-05-25 01:10:03 +02:00
while True:
2020-05-25 00:19:32 +02:00
2020-05-25 01:10:03 +02:00
# show menu with options
d = Dialog(dialog="dialog",autowidgetsize=True)
d.set_background_title("TOR Bridge Shop: {0}".format(shopurl))
code, tag = d.menu("Following TOR bridge hosts are available. Select for details:", choices=choices)
if code != d.OK:
host=None
break
# get data of selected
seletedIndex = int(tag)
hostid = hosts[seletedIndex]['id']
msatsFirst=hosts[seletedIndex]['tor_bridge_price_initial']
msatsNext=hosts[seletedIndex]['tor_bridge_price_extension']
duration=hosts[seletedIndex]['tor_bridge_duration']
# show details of selected
2020-05-25 00:19:32 +02:00
2020-05-25 01:10:03 +02:00
# if user has canceled
if host is None:
print("cancel")
sys.exit(0)
# TODO: try to subscribe to host
print(hostid)
2020-05-24 13:11:31 +02:00
sys.exit()
2020-05-25 00:19:32 +02:00
###############
# SHOP LIST
# call from web interface
###############
if sys.argv[1] == "shop-list":
# check parameters
try:
shopurl = sys.argv[2]
if len(shopurl) == 0:
print("error='invalid parameter'")
sys.exit(1)
except Exception as e:
print("error='invalid parameters'")
sys.exit(1)
# get data
hosts = shopList(shopurl)
if hosts is None: sys.exit(1)
# output is json list of hosts
print(hosts)
sys.exit(0)
###############
# SHOP ORDER
# call from web interface
###############
if sys.argv[1] == "shop-order":
shopurl = sys.argv[2]
hostid = sys.argv[3]
toraddress = sys.argv[4]
duration = sys.argv[5]
msats = sys.argv[6]
# TODO: basic data input check
shopOrder(shopurl, hostid, toraddress, duration, msats)
# TODO: print out result data
2020-05-24 13:11:31 +02:00
2020-05-24 13:46:47 +02:00
sys.exit()
2020-05-25 00:19:32 +02:00
#######################
# SUBSCRIPTIONS RENEW
# call in intervalls from background process
#######################
if sys.argv[1] == "subscriptions-renew":
# secondsBeforeSuspend
secondsBeforeSuspend = sys.argv[2]
if secondsBeforeSuspend < 0:
print("error='invalid parameter'")
sys.exit()
# TODO: check if any active subscrpitions are below the secondsBeforeSuspend - if yes extend
# subscriptionExtend(shopurl, hostid, toraddress, duration, msatsFirst)
# unkown command
print("error='unkown command'")
sys.exit()
2020-05-23 02:44:08 +02:00
if False: '''
2020-05-22 19:24:46 +02:00
###############
# MENU
###############
if sys.argv[1] == "menu":
2020-05-23 02:44:08 +02:00
from dialog import Dialog
2020-05-22 19:24:46 +02:00
d = Dialog(dialog="dialog",autowidgetsize=True)
d.set_background_title("IP2TOR Subscription Service")
code, tag = d.menu("OK, then you have two options:",
2020-05-22 20:06:59 +02:00
choices=[("(1)", "Test HTTP REQUEST thru TOR PROXY"),
2020-05-22 20:28:27 +02:00
("(2)", "Make REST API - JSON request"),
("(3)", "TOML test"),
2020-05-23 02:44:08 +02:00
("(4)", "Working with .conf files")])
2020-05-22 20:28:27 +02:00
if tag == "(3)":
2020-05-22 20:32:17 +02:00
print ("Needs: pip3 install toml")
2020-05-22 20:28:27 +02:00
import toml
toml_string = """
"""
if tag == "(4)":
with open('/mnt/hdd/raspiblitz.conf', 'r') as myfile:
2020-05-22 20:29:43 +02:00
data=myfile.read()
2020-05-22 20:32:17 +02:00
print(data)
2020-05-22 20:33:04 +02:00
import toml
2020-05-22 20:32:17 +02:00
parsed_toml = toml.loads(data)
print(parsed_toml)
2020-05-22 20:28:27 +02:00
2020-05-22 19:24:46 +02:00
else:
2020-05-23 02:44:08 +02:00
print("Cancel")
'''