mirror of
https://github.com/markqvist/LXMF-Tools.git
synced 2024-10-01 03:15:38 -04:00
Merge branch 'SebastianObi:main' into main
This commit is contained in:
commit
a36d8f9a9d
4
lxmf_bridge_matrix/Examples/README.md
Normal file
4
lxmf_bridge_matrix/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
4
lxmf_bridge_meshtastic/Examples/README.md
Normal file
4
lxmf_bridge_meshtastic/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
4
lxmf_bridge_mqtt/Examples/README.md
Normal file
4
lxmf_bridge_mqtt/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -48,6 +48,9 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
#### Process ####
|
#### Process ####
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
@ -97,9 +100,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -112,6 +116,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -120,6 +125,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -134,6 +141,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -188,10 +199,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -218,6 +237,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -261,7 +284,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -276,8 +299,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -346,7 +374,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -363,26 +391,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -424,24 +455,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -529,6 +586,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -772,6 +866,9 @@ def mqtt_message_received_callback_send(client, userdata, message):
|
|||||||
if "title" not in message_data:
|
if "title" not in message_data:
|
||||||
message_data["title"] = ""
|
message_data["title"] = ""
|
||||||
|
|
||||||
|
if "fields" not in message_data:
|
||||||
|
message_data["fields"] = None
|
||||||
|
|
||||||
timestamp = None
|
timestamp = None
|
||||||
if "timestamp" in message_data and timestamp is None:
|
if "timestamp" in message_data and timestamp is None:
|
||||||
message_data["timestamp"] = message_data["timestamp"].strip()
|
message_data["timestamp"] = message_data["timestamp"].strip()
|
||||||
@ -783,7 +880,7 @@ def mqtt_message_received_callback_send(client, userdata, message):
|
|||||||
if message_data["date_time"] != "":
|
if message_data["date_time"] != "":
|
||||||
timestamp = time.mktime(datetime.datetime.strptime(message_data["date_time"], '%Y-%m-%d %H:%M:%S').timetuple())
|
timestamp = time.mktime(datetime.datetime.strptime(message_data["date_time"], '%Y-%m-%d %H:%M:%S').timetuple())
|
||||||
|
|
||||||
LXMF_CONNECTION.send(message_data["destination"].strip(), content, message_data["title"].strip(), None, timestamp)
|
LXMF_CONNECTION.send(message_data["destination"].strip(), content, message_data["title"].strip(), message_data["fields"], timestamp)
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -843,6 +940,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
@ -936,15 +1063,15 @@ def config_default(file=None, file_override=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -1110,6 +1237,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -1118,9 +1250,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -1134,6 +1269,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -1244,7 +1380,13 @@ display_name =
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
propagation_node = ca2762fe5283873719aececfb9e18835
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1260,6 +1402,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = No
|
announce_periodic = No
|
||||||
announce_periodic_interval = 360 #Minutes
|
announce_periodic_interval = 360 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
|
4
lxmf_bridge_telegram/Examples/README.md
Normal file
4
lxmf_bridge_telegram/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
4
lxmf_chatbot/Examples/README.md
Normal file
4
lxmf_chatbot/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -47,6 +47,9 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
#### Process ####
|
#### Process ####
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
@ -95,9 +98,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -110,6 +114,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -118,6 +123,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -132,6 +139,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -186,10 +197,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -216,6 +235,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -259,7 +282,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -274,8 +297,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -344,7 +372,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -361,26 +389,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -422,24 +453,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -527,6 +584,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -666,7 +760,37 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config
|
||||||
|
- Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
|
|
||||||
@ -759,15 +883,15 @@ def config_default(file=None, file_override=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -933,6 +1057,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -941,9 +1070,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -957,6 +1089,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -1061,7 +1194,13 @@ display_name = Chatbot
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
#propagation_node =
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1077,6 +1216,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = No
|
announce_periodic = No
|
||||||
announce_periodic_interval = 360 #Minutes
|
announce_periodic_interval = 360 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
|
4
lxmf_cmd/Examples/README.md
Normal file
4
lxmf_cmd/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -47,6 +47,9 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
#### Process ####
|
#### Process ####
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
@ -93,9 +96,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -108,6 +112,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -116,6 +121,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -130,6 +137,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -184,10 +195,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -214,6 +233,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -257,7 +280,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -272,8 +295,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -342,7 +370,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -359,26 +387,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -420,24 +451,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -525,6 +582,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -701,6 +795,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
@ -794,15 +918,15 @@ def config_default(file=None, file_override=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -967,6 +1091,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -975,9 +1104,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -991,6 +1123,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -1085,7 +1218,13 @@ display_name = CMD
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
#propagation_node =
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1101,6 +1240,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = No
|
announce_periodic = No
|
||||||
announce_periodic_interval = 360 #Minutes
|
announce_periodic_interval = 360 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
#### Main program settings ####
|
||||||
|
[main]
|
||||||
|
lng = de # en/de
|
||||||
|
|
||||||
|
fields_announce = True
|
||||||
|
fields_message = True
|
||||||
|
|
||||||
|
|
||||||
|
#### LXMF connection settings ####
|
||||||
|
[lxmf]
|
||||||
|
destination_type_conv = 6
|
||||||
|
|
||||||
|
display_name = Test Channel
|
||||||
|
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
try_propagation_on_fail = Yes
|
||||||
|
|
||||||
|
announce_startup = Yes
|
||||||
|
announce_periodic = Yes
|
||||||
|
announce_periodic_interval = 30 #Minutes
|
||||||
|
|
||||||
|
sync_startup = Yes
|
||||||
|
sync_periodic = Yes
|
||||||
|
sync_periodic_interval = 5 #Minutes
|
||||||
|
sync_limit = 0
|
||||||
|
|
||||||
|
|
||||||
|
#### Cluster settings ####
|
||||||
|
[cluster]
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
#### Router settings ####
|
||||||
|
[router]
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
#### High availability settings ####
|
||||||
|
[high_availability]
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
#### Message settings ####
|
||||||
|
[message]
|
||||||
|
send_title_prefix =
|
||||||
|
send_prefix =
|
||||||
|
|
||||||
|
cluster_receive_title_prefix =
|
||||||
|
cluster_receive_prefix = @!cluster_source!->
|
||||||
|
|
||||||
|
cluster_send_title_prefix = !source_name! <!source_address!>
|
||||||
|
cluster_send_prefix = @!cluster_destination!!n!
|
||||||
|
|
||||||
|
fields_remove = gps
|
||||||
|
fields_remove_anonymous = src,gps
|
||||||
|
|
||||||
|
|
||||||
|
#### Statistic/Counter settings ####
|
||||||
|
[statistic]
|
||||||
|
enabled = True
|
||||||
|
|
||||||
|
|
||||||
|
#### User rights assignment ####
|
||||||
|
[rights]
|
||||||
|
admin = receive_local,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,send_local,anonymous,join
|
||||||
|
mod = receive_local,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,send_local,anonymous,join
|
||||||
|
user = receive_local,join
|
||||||
|
guest = receive_local,join
|
||||||
|
wait = join
|
||||||
|
|
||||||
|
|
||||||
|
#### User cmd assignment ####
|
||||||
|
[cmds]
|
||||||
|
admin = update,leave,invite,kick,block,unblock,allow,deny
|
||||||
|
mod = update,leave,invite,kick,block,unblock,allow,deny
|
||||||
|
user = update,leave
|
||||||
|
guest = update,leave
|
||||||
|
wait = update,leave
|
||||||
|
|
||||||
|
|
||||||
|
#### User config assignment ####
|
||||||
|
[configs]
|
||||||
|
admin = tx_enabled=True,group_enabled=True
|
||||||
|
mod = tx_enabled=True,group_enabled=True
|
||||||
|
user =
|
||||||
|
guest =
|
||||||
|
wait =
|
||||||
|
|
||||||
|
|
||||||
|
#### Interface settings - Messages ####
|
||||||
|
[interface_messages]
|
||||||
|
auto_add_admin = Welcome to the channel "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all channel members.
|
||||||
|
auto_add_admin-de = Willkommen in dem Kanal "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Kanalmitglieder verteilt.
|
||||||
|
auto_add_mod = Welcome to the channel "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all channel members.
|
||||||
|
auto_add_mod-de = Willkommen in dem Kanal "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Kanalmitglieder verteilt.
|
||||||
|
auto_add_user = Welcome to the channel "!display_name!"!!n!!n!!description!
|
||||||
|
auto_add_user-de = Willkommen in dem Kanal "!display_name!"!!n!!n!!description!
|
||||||
|
auto_add_guest = Welcome to the channel "!display_name!"!!n!!n!!description!
|
||||||
|
auto_add_guest-de = Willkommen in dem Kanal "!display_name!"!!n!!n!!description!
|
||||||
|
auto_add_wait = Welcome to the channel "!display_name!"!!n!!n!You still need to be allowed to join. You will be notified automatically.
|
||||||
|
auto_add_wait-de = Willkommen in dem Kanal "!display_name!"!!n!!n!Der Beitritt muss ihnen noch erlaubt werden. Sie werden darüber automatisch benachrichtigt.
|
||||||
|
|
||||||
|
invite_admin = You have been invited to the channel "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all channel members.
|
||||||
|
invite_admin-de = Sie wurden in den Kanal "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Kanalmitglieder verteilt.
|
||||||
|
invite_mod = You have been invited to the channel "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all channel members.
|
||||||
|
invite_mod-de = Sie wurden in den Kanal "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Kanalmitglieder verteilt.
|
||||||
|
invite_user = You have been invited to the channel "!display_name!"!!n!!n!!description!
|
||||||
|
invite_user-de = Sie wurden in den Kanal "!display_name!" eingeladen!!n!!n!!description!
|
||||||
|
invite_guest = You have been invited to the channel "!display_name!"!!n!!n!!description!
|
||||||
|
invite_guest-de = Sie wurden in den Kanal "!display_name!" eingeladen!!n!!n!!description!
|
||||||
|
invite_wait = You have been invited to the channel "!display_name!"!!n!!n!You still need to be allowed to join. You will be notified automatically.
|
||||||
|
invite_wait-de = Sie wurden in den Kanal "!display_name!" eingeladen!!n!!n!Der Beitritt muss ihnen noch erlaubt werden. Sie werden darüber automatisch benachrichtigt.
|
||||||
|
|
||||||
|
allow_admin = You have been allowed to join the channel "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all channel members.
|
||||||
|
allow_admin-de = Sie wurden erlaubt dem Kanal "!display_name!" beizutreten!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Kanalmitglieder verteilt.
|
||||||
|
allow_mod = You have been allowed to join the channel "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all channel members.
|
||||||
|
allow_mod-de = Sie wurden erlaubt dem Kanal "!display_name!" beizutreten!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Kanalmitglieder verteilt.
|
||||||
|
allow_user = You have been allowed to join the channel "!display_name!"!!n!!n!!description!
|
||||||
|
allow_user-de = Sie wurden erlaubt dem Kanal "!display_name!" beizutreten!!n!!n!!description!
|
||||||
|
allow_guest = You have been allowed to join the channel "!display_name!"!!n!!n!!description!
|
||||||
|
allow_guest-de = Sie wurden erlaubt dem Kanal "!display_name!" beizutreten!!n!!n!!description!
|
||||||
|
allow_wait =
|
||||||
|
allow_wait-de =
|
||||||
|
|
||||||
|
member_join = Joins the channel.
|
||||||
|
member_join-de = Tritt dem Kanal bei.
|
||||||
|
member_leave = Leave the channel.
|
||||||
|
member_leave-de = Verlässt den Kanal.
|
||||||
|
member_invite = Was invited to the channel by !source_name!
|
||||||
|
member_invite-de = Wurde in den Kanal eingeladen von !source_name!
|
||||||
|
member_kick = Was kicked out of the channel by !source_name!
|
||||||
|
member_kick-de = Wurde aus dem Kanal geworfen von !source_name!
|
||||||
|
member_block = Was blocked by !source_name!
|
||||||
|
member_block-de = Wurde geblockt von !source_name!
|
||||||
|
member_unblock = Was unblocked by !source_name!
|
||||||
|
member_unblock-de = Wurde entsperrt von !source_name!
|
||||||
|
member_allow = Was allowed by !source_name!
|
||||||
|
member_allow-de = Wurde erlaubt von !source_name!
|
||||||
|
member_deny = Was denied by !source_name!
|
||||||
|
member_deny-de = Wurde abgelehnt von !source_name!
|
||||||
|
member_name_def = Name defined
|
||||||
|
member_name_def-de = Name definiert
|
||||||
|
member_name_change = Name changed
|
||||||
|
member_name_change-de = Namen geändert
|
||||||
|
|
||||||
|
|
||||||
|
#### Interface settings - Menu/command ####
|
||||||
|
[interface_menu]
|
||||||
|
cmd_unknown = ERROR: Unknown command.
|
||||||
|
cmd_unknown-de = FEHLER: Unbekannter Befehl.
|
@ -0,0 +1,52 @@
|
|||||||
|
[high_availability]
|
||||||
|
role = master
|
||||||
|
last_heartbeat = 0000-00-00 00:00:00
|
||||||
|
|
||||||
|
[main]
|
||||||
|
enabled_local = True
|
||||||
|
enabled_cluster = True
|
||||||
|
auto_add_user = True
|
||||||
|
auto_add_user_type = user
|
||||||
|
auto_add_cluster = True
|
||||||
|
auto_add_router = True
|
||||||
|
invite_user = True
|
||||||
|
invite_user_type = user
|
||||||
|
allow_user = True
|
||||||
|
allow_user_type = user
|
||||||
|
deny_user = True
|
||||||
|
deny_user_type = block_wait
|
||||||
|
description =
|
||||||
|
description-de =
|
||||||
|
rules =
|
||||||
|
rules-de =
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
|
||||||
|
[mod]
|
||||||
|
|
||||||
|
[user]
|
||||||
|
|
||||||
|
[guest]
|
||||||
|
|
||||||
|
[wait]
|
||||||
|
|
||||||
|
[block_admin]
|
||||||
|
|
||||||
|
[block_mod]
|
||||||
|
|
||||||
|
[block_user]
|
||||||
|
|
||||||
|
[block_guest]
|
||||||
|
|
||||||
|
[block_wait]
|
||||||
|
|
||||||
|
[cluster]
|
||||||
|
|
||||||
|
[block_cluster]
|
||||||
|
|
||||||
|
[router]
|
||||||
|
|
||||||
|
[block_router]
|
||||||
|
|
||||||
|
[pin]
|
||||||
|
|
@ -0,0 +1,151 @@
|
|||||||
|
#### Main program settings ####
|
||||||
|
[main]
|
||||||
|
lng = de # en/de
|
||||||
|
|
||||||
|
fields_announce = True
|
||||||
|
fields_message = True
|
||||||
|
|
||||||
|
|
||||||
|
#### LXMF connection settings ####
|
||||||
|
[lxmf]
|
||||||
|
destination_type_conv = 4
|
||||||
|
|
||||||
|
display_name = Test Group
|
||||||
|
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
try_propagation_on_fail = Yes
|
||||||
|
|
||||||
|
announce_startup = Yes
|
||||||
|
announce_periodic = Yes
|
||||||
|
announce_periodic_interval = 30 #Minutes
|
||||||
|
|
||||||
|
sync_startup = Yes
|
||||||
|
sync_periodic = Yes
|
||||||
|
sync_periodic_interval = 5 #Minutes
|
||||||
|
sync_limit = 0
|
||||||
|
|
||||||
|
|
||||||
|
#### Cluster settings ####
|
||||||
|
[cluster]
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
#### Router settings ####
|
||||||
|
[router]
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
#### High availability settings ####
|
||||||
|
[high_availability]
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
#### Message settings ####
|
||||||
|
[message]
|
||||||
|
send_title_prefix = !source_name! <!source_address!>
|
||||||
|
send_prefix =
|
||||||
|
|
||||||
|
cluster_receive_title_prefix =
|
||||||
|
cluster_receive_prefix = @!cluster_source!->
|
||||||
|
|
||||||
|
cluster_send_title_prefix = !source_name! <!source_address!>
|
||||||
|
cluster_send_prefix = @!cluster_destination!!n!
|
||||||
|
|
||||||
|
fields_remove =
|
||||||
|
fields_remove_anonymous = src,gps
|
||||||
|
|
||||||
|
|
||||||
|
#### Statistic/Counter settings ####
|
||||||
|
[statistic]
|
||||||
|
enabled = True
|
||||||
|
|
||||||
|
|
||||||
|
#### User rights assignment ####
|
||||||
|
[rights]
|
||||||
|
admin = receive_local,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,send_local,join
|
||||||
|
mod = receive_local,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,send_local,join
|
||||||
|
user = receive_local,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,send_local,join
|
||||||
|
guest = receive_local,join
|
||||||
|
wait = join,leave
|
||||||
|
|
||||||
|
|
||||||
|
#### User cmd assignment ####
|
||||||
|
[cmds]
|
||||||
|
admin = update,leave,invite,kick,block,unblock,allow,deny
|
||||||
|
mod = update,leave,invite,kick,block,unblock,allow,deny
|
||||||
|
user = update,leave
|
||||||
|
guest = update,leave
|
||||||
|
wait = update,leave
|
||||||
|
|
||||||
|
|
||||||
|
#### User config assignment ####
|
||||||
|
[configs]
|
||||||
|
admin =
|
||||||
|
mod =
|
||||||
|
user =
|
||||||
|
guest =
|
||||||
|
wait =
|
||||||
|
|
||||||
|
|
||||||
|
#### Interface settings - Messages ####
|
||||||
|
[interface_messages]
|
||||||
|
auto_add_admin = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
auto_add_admin-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
auto_add_mod = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
auto_add_mod-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
auto_add_user = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
auto_add_user-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
auto_add_guest = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!You can only receive messages.
|
||||||
|
auto_add_guest-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Sie können nur Nachrichten empfangen.
|
||||||
|
auto_add_wait = Welcome to the group "!display_name!"!!n!!n!You still need to be allowed to join. You will be notified automatically.
|
||||||
|
auto_add_wait-de = Willkommen in der Gruppe "!display_name!"!!n!!n!Der Beitritt muss ihnen noch erlaubt werden. Sie werden darüber automatisch benachrichtigt.
|
||||||
|
|
||||||
|
invite_admin = You have been invited to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
invite_admin-de = Sie wurden in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
invite_mod = You have been invited to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
invite_mod-de = Sie wurden in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
invite_user = You have been invited to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
invite_user-de = Sie wurden in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
invite_guest = You have been invited to the group "!display_name!"!!n!!n!!description!!n!!n!You can only receive messages.
|
||||||
|
invite_guest-de = Sie wurden in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Sie können nur Nachrichten empfangen.
|
||||||
|
invite_wait = You have been invited to the group "!display_name!"!!n!!n!You still need to be allowed to join. You will be notified automatically.
|
||||||
|
invite_wait-de = Sie wurden in die Gruppe "!display_name!" eingeladen!!n!!n!Der Beitritt muss ihnen noch erlaubt werden. Sie werden darüber automatisch benachrichtigt.
|
||||||
|
|
||||||
|
allow_admin = You have been allowed to join the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
allow_admin-de = Sie wurden erlaubt der Gruppe "!display_name!" beizutreten!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
allow_mod = You have been allowed to join the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
allow_mod-de = Sie wurden erlaubt der Gruppe "!display_name!" beizutreten!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
allow_user = You have been allowed to join the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.
|
||||||
|
allow_user-de = Sie wurden erlaubt der Gruppe "!display_name!" beizutreten!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.
|
||||||
|
allow_guest = You have been allowed to join the group "!display_name!"!!n!!n!!description!!n!!n!You can only receive messages.
|
||||||
|
allow_guest-de = Sie wurden erlaubt der Gruppe "!display_name!" beizutreten!!n!!n!!description!!n!!n!Sie können nur Nachrichten empfangen.
|
||||||
|
allow_wait =
|
||||||
|
allow_wait-de =
|
||||||
|
|
||||||
|
member_join = Joins the group.
|
||||||
|
member_join-de = Tritt der Gruppe bei.
|
||||||
|
member_leave = Leave the group.
|
||||||
|
member_leave-de = Verlässt die Gruppe.
|
||||||
|
member_invite = Was invited to the group by !source_name!
|
||||||
|
member_invite-de = Wurde in die Gruppe eingeladen von !source_name!
|
||||||
|
member_kick = Was kicked out of the group by !source_name!
|
||||||
|
member_kick-de = Wurde aus der Gruppe geworfen von !source_name!
|
||||||
|
member_block = Was blocked by !source_name!
|
||||||
|
member_block-de = Wurde geblockt von !source_name!
|
||||||
|
member_unblock = Was unblocked by !source_name!
|
||||||
|
member_unblock-de = Wurde entsperrt von !source_name!
|
||||||
|
member_allow = Was allowed by !source_name!
|
||||||
|
member_allow-de = Wurde erlaubt von !source_name!
|
||||||
|
member_deny = Was denied by !source_name!
|
||||||
|
member_deny-de = Wurde abgelehnt von !source_name!
|
||||||
|
member_name_def = Name defined
|
||||||
|
member_name_def-de = Name definiert
|
||||||
|
member_name_change = Name changed
|
||||||
|
member_name_change-de = Namen geändert
|
||||||
|
|
||||||
|
|
||||||
|
#### Interface settings - Menu/command ####
|
||||||
|
[interface_menu]
|
||||||
|
cmd_unknown = ERROR: Unknown command.
|
||||||
|
cmd_unknown-de = FEHLER: Unbekannter Befehl.
|
@ -0,0 +1,52 @@
|
|||||||
|
[high_availability]
|
||||||
|
role = master
|
||||||
|
last_heartbeat = 0000-00-00 00:00:00
|
||||||
|
|
||||||
|
[main]
|
||||||
|
enabled_local = True
|
||||||
|
enabled_cluster = True
|
||||||
|
auto_add_user = True
|
||||||
|
auto_add_user_type = user
|
||||||
|
auto_add_cluster = True
|
||||||
|
auto_add_router = True
|
||||||
|
invite_user = True
|
||||||
|
invite_user_type = user
|
||||||
|
allow_user = True
|
||||||
|
allow_user_type = user
|
||||||
|
deny_user = True
|
||||||
|
deny_user_type = block_wait
|
||||||
|
description = This group is for a first test of functionality.
|
||||||
|
description-de = Diese Gruppe dient einem ersten Test der Funktionalität.
|
||||||
|
rules = Please follow the general rules of etiquette which should be taken for granted!!n!Prohibited are:!n!Spam, insults, violence, sex, illegal topics
|
||||||
|
rules-de = Bitte befolgen Sie die allgemeinen benimm-dich-Regeln welche als selbstverständlich gelten sollten!!n!Verboten sind:!n!Spam, Beleidigungen, Gewalt, Sex, illegale Themen
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
|
||||||
|
[mod]
|
||||||
|
|
||||||
|
[user]
|
||||||
|
|
||||||
|
[guest]
|
||||||
|
|
||||||
|
[wait]
|
||||||
|
|
||||||
|
[block_admin]
|
||||||
|
|
||||||
|
[block_mod]
|
||||||
|
|
||||||
|
[block_user]
|
||||||
|
|
||||||
|
[block_guest]
|
||||||
|
|
||||||
|
[block_wait]
|
||||||
|
|
||||||
|
[cluster]
|
||||||
|
|
||||||
|
[block_cluster]
|
||||||
|
|
||||||
|
[router]
|
||||||
|
|
||||||
|
[block_router]
|
||||||
|
|
||||||
|
[pin]
|
||||||
|
|
4
lxmf_distribution_group/Examples/README.md
Normal file
4
lxmf_distribution_group/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
File diff suppressed because it is too large
Load Diff
4
lxmf_distribution_group_minimal/Examples/README.md
Normal file
4
lxmf_distribution_group_minimal/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -48,6 +48,9 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
#### Process ####
|
#### Process ####
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
@ -91,9 +94,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -106,6 +110,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -114,6 +119,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -128,6 +135,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -182,10 +193,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -212,6 +231,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -255,7 +278,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -270,8 +293,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -340,7 +368,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -357,26 +385,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -418,24 +449,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -523,6 +580,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -704,6 +798,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
@ -887,15 +1011,15 @@ def data_default(file=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -1065,6 +1189,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -1073,9 +1202,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -1089,6 +1221,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -1162,8 +1295,8 @@ DEFAULT_CONFIG_OVERRIDE = '''# This is the user configuration file to override t
|
|||||||
# It is also used in the group description/info.
|
# It is also used in the group description/info.
|
||||||
display_name = Distribution Group
|
display_name = Distribution Group
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Set propagation node automatically.
|
||||||
propagation_node = ca2762fe5283873719aececfb9e18835
|
propagation_node_auto = True
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1205,7 +1338,13 @@ display_name = Distribution Group
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
propagation_node = ca2762fe5283873719aececfb9e18835
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1221,6 +1360,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = Yes
|
announce_periodic = Yes
|
||||||
announce_periodic_interval = 120 #Minutes
|
announce_periodic_interval = 120 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
|
4
lxmf_echo/Examples/README.md
Normal file
4
lxmf_echo/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -47,6 +47,9 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
#### Process ####
|
#### Process ####
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
@ -89,9 +92,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -104,6 +108,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -112,6 +117,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -126,6 +133,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -180,10 +191,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -210,6 +229,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -253,7 +276,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -268,8 +291,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -338,7 +366,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -355,26 +383,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -416,24 +447,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -521,6 +578,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -669,6 +763,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
@ -762,15 +886,15 @@ def config_default(file=None, file_override=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -935,6 +1059,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -943,9 +1072,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -959,6 +1091,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -1053,7 +1186,13 @@ display_name = Echo Test
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
#propagation_node =
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1069,6 +1208,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = Yes
|
announce_periodic = Yes
|
||||||
announce_periodic_interval = 360 #Minutes
|
announce_periodic_interval = 360 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
|
4
lxmf_ping/Examples/README.md
Normal file
4
lxmf_ping/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -44,6 +44,9 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
#### Other ####
|
#### Other ####
|
||||||
import random
|
import random
|
||||||
import secrets
|
import secrets
|
||||||
@ -90,9 +93,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -105,6 +109,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -113,6 +118,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -127,6 +134,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -181,10 +192,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -211,6 +230,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -254,7 +277,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -269,8 +292,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -339,7 +367,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -356,26 +384,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -417,24 +448,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -522,6 +579,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -575,15 +669,15 @@ def lxmf_failed(message):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
|
4
lxmf_provisioning/Examples/README.md
Normal file
4
lxmf_provisioning/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -47,6 +47,15 @@ import pickle
|
|||||||
#### String ####
|
#### String ####
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
#### Regex ####
|
||||||
|
import re
|
||||||
|
|
||||||
|
#### UID ####
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
#### ####
|
||||||
|
import base64
|
||||||
|
|
||||||
#### Process ####
|
#### Process ####
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
@ -81,7 +90,10 @@ PATH_RNS = None
|
|||||||
|
|
||||||
|
|
||||||
#### Global Variables - System (Not changeable) ####
|
#### Global Variables - System (Not changeable) ####
|
||||||
CACHE = []
|
CACHE = {}
|
||||||
|
CACHE["in"] = {}
|
||||||
|
CACHE["out"] = {}
|
||||||
|
CACHE_CHANGE = False
|
||||||
CONFIG = None
|
CONFIG = None
|
||||||
RNS_CONNECTION = None
|
RNS_CONNECTION = None
|
||||||
LXMF_CONNECTION = None
|
LXMF_CONNECTION = None
|
||||||
@ -97,9 +109,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -112,6 +125,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -120,6 +134,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -134,6 +150,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -188,10 +208,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -218,6 +246,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -261,7 +293,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -276,8 +308,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -346,7 +383,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -363,26 +400,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -424,24 +464,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -529,6 +595,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -549,7 +652,7 @@ class lxmf_announce_callback:
|
|||||||
|
|
||||||
#### LXMF - Message ####
|
#### LXMF - Message ####
|
||||||
def lxmf_message_received_callback(message):
|
def lxmf_message_received_callback(message):
|
||||||
global CACHE
|
global CACHE, CACHE_CHANGE
|
||||||
|
|
||||||
if CONFIG["lxmf"].getboolean("signature_validated") and not message.signature_validated:
|
if CONFIG["lxmf"].getboolean("signature_validated") and not message.signature_validated:
|
||||||
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " have no valid signature", LOG_DEBUG)
|
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " have no valid signature", LOG_DEBUG)
|
||||||
@ -558,34 +661,341 @@ def lxmf_message_received_callback(message):
|
|||||||
if not message.fields:
|
if not message.fields:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not "registration_request" in message.fields and not "telemetry" in message.fields:
|
hash_destination = RNS.hexrep(message.source_hash, delimit=False)
|
||||||
return
|
hash_identity = ""
|
||||||
|
#hash_identity = RNS.Identity.recall(message.source_hash)
|
||||||
|
#if hash_identity != None:
|
||||||
|
# hash_identity = RNS.hexrep(hash_identity, delimit=False)
|
||||||
|
#else:
|
||||||
|
# hash_identity = ""
|
||||||
|
|
||||||
|
for key in message.fields:
|
||||||
|
try:
|
||||||
|
data = message.fields[key]
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
continue
|
||||||
|
if "type" not in data:
|
||||||
|
continue
|
||||||
|
if data["type"] == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
data["hash_destination"] = hash_destination
|
||||||
|
data["hash_identity"] = hash_identity
|
||||||
|
data["timestamp_client"] = message.timestamp
|
||||||
|
data["timestamp_server"] = time.time()
|
||||||
|
|
||||||
|
if "password" in data:
|
||||||
|
data["password"] = str(base64.b32encode(data["password"]))
|
||||||
|
|
||||||
|
CACHE["in"][str(uuid.uuid4())] = data
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### LXMF - Notification ####
|
||||||
|
def lxmf_message_notification_success_callback(message):
|
||||||
|
global CACHE, CACHE_CHANGE
|
||||||
|
|
||||||
|
key = message.app_data
|
||||||
|
if key in CACHE["out"]:
|
||||||
|
del CACHE["out"][key]
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Jobs ####
|
||||||
|
def jobs_in():
|
||||||
|
global CACHE, CACHE_CHANGE
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(CONFIG["processing"].getint("interval_in"))
|
||||||
|
log("Jobs - Loop/Execute", LOG_DEBUG)
|
||||||
|
|
||||||
|
if len(CACHE["in"]) > 0:
|
||||||
|
log("Cache - Available -> Execute", LOG_DEBUG)
|
||||||
|
|
||||||
|
CACHE_DEL = []
|
||||||
db = None
|
db = None
|
||||||
try:
|
try:
|
||||||
db = psycopg2.connect(user=CONFIG["database"]["user"], password=CONFIG["database"]["password"], host=CONFIG["database"]["host"], port=CONFIG["database"]["port"], database=CONFIG["database"]["database"])
|
db = psycopg2.connect(user=CONFIG["database"]["user"], password=CONFIG["database"]["password"], host=CONFIG["database"]["host"], port=CONFIG["database"]["port"], database=CONFIG["database"]["database"])
|
||||||
dbc = db.cursor()
|
dbc = db.cursor()
|
||||||
|
|
||||||
if CONFIG["features"].getboolean("registration") and "registration_request" in message.fields:
|
for key in CACHE["in"]:
|
||||||
dbc.execute("INSERT INTO "+CONFIG["database"]["table_registration"]+" (hash, data) VALUES(%s, %s)", (
|
try:
|
||||||
RNS.hexrep(message.source_hash, delimit=False),
|
log("-> Execute", LOG_EXTREME)
|
||||||
umsgpack.packb(message.fields["registration_request"]))
|
log(CACHE["in"][key], LOG_EXTREME)
|
||||||
)
|
|
||||||
|
|
||||||
if CONFIG["features"].getboolean("telemetry") and "telemetry" in message.fields:
|
data = CACHE["in"][key]
|
||||||
dbc.execute("INSERT INTO "+CONFIG["database"]["table_telemetry"]+" (hash, data) VALUES(%s, %s)", (
|
|
||||||
RNS.hexrep(message.source_hash, delimit=False),
|
if data["type"] == "account_add" and CONFIG["features"].getboolean("account_add"):
|
||||||
umsgpack.packb(message.fields["telemetry"]))
|
# members
|
||||||
|
dbc.execute("SELECT member_user_id FROM members WHERE member_email = %s AND member_password = %s", (data["email"], data["password"]))
|
||||||
|
result = dbc.fetchall()
|
||||||
|
if len(result) == 0:
|
||||||
|
user_id = str(uuid.uuid4())
|
||||||
|
dbc.execute("INSERT INTO members (member_user_id, member_email, member_password, member_dob, member_sex, member_introduction, member_country, member_state, member_city, member_occupation, member_skills, member_tasks, member_wallet_address, member_accept_rules, member_language, member_locale, member_status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, '0')", (
|
||||||
|
user_id,
|
||||||
|
data["email"],
|
||||||
|
data["password"],
|
||||||
|
data["dob"],
|
||||||
|
data["sex"],
|
||||||
|
data["introduction"],
|
||||||
|
data["country"],
|
||||||
|
data["state"],
|
||||||
|
data["city"],
|
||||||
|
data["occupation"],
|
||||||
|
data["skills"],
|
||||||
|
data["tasks"],
|
||||||
|
data["wallet_address"],
|
||||||
|
data["accept_rules"],
|
||||||
|
data["language"],
|
||||||
|
data["language"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if CONFIG["features"].getboolean("account_add_auth"):
|
||||||
|
fields = {}
|
||||||
|
if CONFIG["lxmf"]["destination_type_conv"] != "":
|
||||||
|
fields["type"] = CONFIG["lxmf"].getint("destination_type_conv")
|
||||||
|
fields["prov"] = {}
|
||||||
|
fields["prov"]["auth_state"] = CONFIG["features"].getint("account_add_auth_state")
|
||||||
|
fields["prov"]["auth_role"] = CONFIG["features"].getint("account_add_auth_role")
|
||||||
|
CACHE["out"][str(uuid.uuid4())] = {"hash_destination": data["hash_destination"], "content": "", "title": "", "fields": fields}
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
elif len(result) == 1:
|
||||||
|
user_id = result[0][0]
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# devices
|
||||||
|
dbc.execute("DELETE FROM devices WHERE device_id = %s OR device_rns_id = %s", (data["device_id"], data["hash_destination"]))
|
||||||
|
dbc.execute("INSERT INTO devices (device_id, device_user_id, device_name, device_display_name, device_rns_id) VALUES (%s, %s, %s, %s, %s)", (
|
||||||
|
data["device_id"],
|
||||||
|
user_id,
|
||||||
|
data["device_name"],
|
||||||
|
data["device_display_name"],
|
||||||
|
data["hash_destination"]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
CACHE_DEL.append(key)
|
||||||
|
|
||||||
|
if data["type"] == "account_edit" and CONFIG["features"].getboolean("account_edit"):
|
||||||
|
# members
|
||||||
|
dbc.execute("SELECT member_user_id FROM members WHERE member_email = %s AND member_password = %s", (data["email"], data["password"]))
|
||||||
|
result = dbc.fetchall()
|
||||||
|
if len(result) == 0:
|
||||||
|
user_id = str(uuid.uuid4())
|
||||||
|
dbc.execute("INSERT INTO members (member_user_id, member_email, member_password, member_dob, member_sex, member_introduction, member_country, member_state, member_city, member_occupation, member_skills, member_tasks, member_wallet_address, member_accept_rules, member_language, member_locale, member_status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, '0')", (
|
||||||
|
user_id,
|
||||||
|
data["email"],
|
||||||
|
data["password"],
|
||||||
|
data["dob"],
|
||||||
|
data["sex"],
|
||||||
|
data["introduction"],
|
||||||
|
data["country"],
|
||||||
|
data["state"],
|
||||||
|
data["city"],
|
||||||
|
data["occupation"],
|
||||||
|
data["skills"],
|
||||||
|
data["tasks"],
|
||||||
|
data["wallet_address"],
|
||||||
|
data["accept_rules"],
|
||||||
|
data["language"],
|
||||||
|
data["language"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if CONFIG["features"].getboolean("account_add_auth"):
|
||||||
|
fields = {}
|
||||||
|
if CONFIG["lxmf"]["destination_type_conv"] != "":
|
||||||
|
fields["type"] = CONFIG["lxmf"].getint("destination_type_conv")
|
||||||
|
fields["prov"] = {}
|
||||||
|
fields["prov"]["auth_state"] = CONFIG["features"].getint("account_add_auth_state")
|
||||||
|
fields["prov"]["auth_role"] = CONFIG["features"].getint("account_add_auth_role")
|
||||||
|
CACHE["out"][str(uuid.uuid4())] = {"hash_destination": data["hash_destination"], "content": "", "title": "", "fields": fields}
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
elif len(result) == 1:
|
||||||
|
user_id = result[0][0]
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# devices
|
||||||
|
dbc.execute("DELETE FROM devices WHERE device_id = %s OR device_rns_id = %s", (data["device_id"], data["hash_destination"]))
|
||||||
|
dbc.execute("INSERT INTO devices (device_id, device_user_id, device_name, device_display_name, device_rns_id) VALUES (%s, %s, %s, %s, %s)", (
|
||||||
|
data["device_id"],
|
||||||
|
user_id,
|
||||||
|
data["device_name"],
|
||||||
|
data["device_display_name"],
|
||||||
|
data["hash_destination"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
CACHE_DEL.append(key)
|
||||||
|
|
||||||
|
if data["type"] == "account_prove" and CONFIG["features"].getboolean("account_prove"):
|
||||||
|
dbc.execute("SELECT device_user_id FROM devices LEFT JOIN members ON members.member_user_id = devices.device_user_id WHERE devices.device_rns_id = %s and members.member_status = '1'", (data["hash_destination"], ))
|
||||||
|
result = dbc.fetchall()
|
||||||
|
if len(result) == 1:
|
||||||
|
source_user_id = result[0][0]
|
||||||
|
dbc.execute("SELECT device_user_id FROM devices WHERE device_rns_id = %s", (data["prove"], ))
|
||||||
|
result = dbc.fetchall()
|
||||||
|
if len(result) == 1:
|
||||||
|
destination_user_id = result[0][0]
|
||||||
|
dbc.execute("INSERT INTO proves (prove_source_user_id, prove_destination_user_id) VALUES (%s, %s)", (source_user_id, destination_user_id))
|
||||||
|
dbc.execute("SELECT member_status FROM members WHERE member_user_id = %s AND member_status = '0'", (destination_user_id, ))
|
||||||
|
result = dbc.fetchall()
|
||||||
|
if len(result) == 1:
|
||||||
|
dbc.execute("SELECT * FROM proves WHERE prove_destination_user_id = %s", (destination_user_id,))
|
||||||
|
result = dbc.fetchall()
|
||||||
|
if len(result) >= 2:
|
||||||
|
dbc.execute("UPDATE members SET member_status = '1' WHERE member_user_id = %s AND member_status = '0'", (destination_user_id,))
|
||||||
|
if CONFIG["features"].getboolean("account_prove_auth"):
|
||||||
|
fields = {}
|
||||||
|
if CONFIG["lxmf"]["destination_type_conv"] != "":
|
||||||
|
fields["type"] = CONFIG["lxmf"].getint("destination_type_conv")
|
||||||
|
fields["prov"] = {}
|
||||||
|
fields["prov"]["auth_state"] = CONFIG["features"].getint("account_prove_auth_state")
|
||||||
|
fields["prov"]["auth_role"] = CONFIG["features"].getint("account_prove_auth_role")
|
||||||
|
CACHE["out"][str(uuid.uuid4())] = {"hash_destination": data["prove"], "content": "", "title": "", "fields": fields}
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
CACHE_DEL.append(key)
|
||||||
|
|
||||||
|
except psycopg2.DatabaseError as e:
|
||||||
|
log("Loop - DB - Error: "+str(e), LOG_ERROR)
|
||||||
|
db.rollback()
|
||||||
|
|
||||||
except psycopg2.DatabaseError as e:
|
except psycopg2.DatabaseError as e:
|
||||||
log("DB - Error: "+str(e), LOG_ERROR)
|
log("DB - Error: "+str(e), LOG_ERROR)
|
||||||
|
db.rollback()
|
||||||
|
|
||||||
|
if len(CACHE_DEL) > 0:
|
||||||
|
for key in CACHE_DEL:
|
||||||
|
del CACHE["in"][key]
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
|
||||||
if db:
|
if db:
|
||||||
dbc.close()
|
dbc.close()
|
||||||
db.close()
|
db.close()
|
||||||
db = None
|
db = None
|
||||||
|
|
||||||
|
if CACHE_CHANGE:
|
||||||
|
if cache_save(PATH + "/cache.data"):
|
||||||
|
CACHE_CHANGE = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Jobs ####
|
||||||
|
def jobs_out():
|
||||||
|
global CACHE, CACHE_CHANGE
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(CONFIG["processing"].getint("interval_out"))
|
||||||
|
log("Jobs Out - Loop/Execute", LOG_DEBUG)
|
||||||
|
|
||||||
|
if len(CACHE["out"]) > 0:
|
||||||
|
log("Cache - Available -> Execute", LOG_DEBUG)
|
||||||
|
|
||||||
|
CACHE_DEL = []
|
||||||
|
for key in CACHE["out"]:
|
||||||
|
try:
|
||||||
|
log("-> Execute", LOG_EXTREME)
|
||||||
|
log(CACHE["out"][key], LOG_EXTREME)
|
||||||
|
|
||||||
|
data = CACHE["out"][key]
|
||||||
|
LXMF_CONNECTION.send(data["hash_destination"], data["content"], data["title"], data["fields"], app_data=key, destination_name="lxmf", destination_type="delivery")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(CACHE_DEL) > 0:
|
||||||
|
for key in CACHE_DEL:
|
||||||
|
del CACHE["out"][key]
|
||||||
|
CACHE_CHANGE = True
|
||||||
|
|
||||||
|
if CACHE_CHANGE:
|
||||||
|
if cache_save(PATH + "/cache.data"):
|
||||||
|
CACHE_CHANGE = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################################################################
|
||||||
|
# Cache
|
||||||
|
|
||||||
|
|
||||||
|
#### Cache - Read #####
|
||||||
|
def cache_read(file=None):
|
||||||
|
log("Cache - Read", LOG_DEBUG)
|
||||||
|
global CACHE
|
||||||
|
|
||||||
|
if file is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if os.path.isfile(file):
|
||||||
|
try:
|
||||||
|
fh = open(file, "rb")
|
||||||
|
CACHE = umsgpack.unpackb(fh.read())
|
||||||
|
fh.close()
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if not cache_default(file=file):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Cache - Save #####
|
||||||
|
def cache_save(file=None):
|
||||||
|
log("Cache - Save", LOG_DEBUG)
|
||||||
|
global CACHE
|
||||||
|
|
||||||
|
if file is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if os.path.isfile(file):
|
||||||
|
try:
|
||||||
|
fh = open(file, "wb")
|
||||||
|
fh.write(umsgpack.packb(CACHE))
|
||||||
|
fh.close()
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Cache - Default #####
|
||||||
|
def cache_default(file=None):
|
||||||
|
log("Cache - Default", LOG_DEBUG)
|
||||||
|
global CACHE
|
||||||
|
|
||||||
|
if file is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if not os.path.isdir(os.path.dirname(file)):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(file))
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
fh = open(file, "wb")
|
||||||
|
fh.write(umsgpack.packb(CACHE))
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# Config
|
# Config
|
||||||
@ -644,6 +1054,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
@ -737,15 +1177,15 @@ def config_default(file=None, file_override=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -881,6 +1321,10 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
print("Config - Error reading config file " + PATH + "/config.cfg")
|
print("Config - Error reading config file " + PATH + "/config.cfg")
|
||||||
panic()
|
panic()
|
||||||
|
|
||||||
|
if not cache_read(PATH + "/cache.data"):
|
||||||
|
print("Cache - Error reading cache file " + PATH + "/cache.data")
|
||||||
|
panic()
|
||||||
|
|
||||||
if CONFIG["main"].getboolean("default_config"):
|
if CONFIG["main"].getboolean("default_config"):
|
||||||
print("Exit!")
|
print("Exit!")
|
||||||
print("First start with the default config!")
|
print("First start with the default config!")
|
||||||
@ -910,6 +1354,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -925,10 +1374,13 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
announce_data = umsgpack.packb(announce_data),
|
announce_data = umsgpack.packb(announce_data),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -942,6 +1394,8 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_message_notification_success_callback(lxmf_message_notification_success_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -949,6 +1403,15 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
log("LXMF - Address: " + RNS.prettyhexrep(LXMF_CONNECTION.destination_hash()), LOG_FORCE)
|
log("LXMF - Address: " + RNS.prettyhexrep(LXMF_CONNECTION.destination_hash()), LOG_FORCE)
|
||||||
log("...............................................................................", LOG_FORCE)
|
log("...............................................................................", LOG_FORCE)
|
||||||
|
|
||||||
|
log("...............................................................................", LOG_EXTREME)
|
||||||
|
log(CACHE, LOG_EXTREME)
|
||||||
|
log("...............................................................................", LOG_EXTREME)
|
||||||
|
|
||||||
|
jobs_in_thread = threading.Thread(target=jobs_in, daemon=True)
|
||||||
|
jobs_in_thread.start()
|
||||||
|
|
||||||
|
jobs_out_thread = threading.Thread(target=jobs_out, daemon=True)
|
||||||
|
jobs_out_thread.start()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@ -1007,9 +1470,16 @@ announce_periodic_interval = 15 #Minutes
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
announce_versions = True
|
announce_versions = True
|
||||||
registration = True
|
account_add = True
|
||||||
|
account_edit = True
|
||||||
|
account_del = True
|
||||||
|
account_prove = True
|
||||||
telemetry = False
|
telemetry = False
|
||||||
|
|
||||||
|
[processing]
|
||||||
|
interval_in = 5 #Seconds
|
||||||
|
interval_out = 60 #Seconds
|
||||||
|
|
||||||
[data]
|
[data]
|
||||||
v_s = 0.0.0 #Version software
|
v_s = 0.0.0 #Version software
|
||||||
v_c = 2022-01-01 00:00 #Version config
|
v_c = 2022-01-01 00:00 #Version config
|
||||||
@ -1017,6 +1487,7 @@ v_d = 2022-01-01 00:00 #Version data
|
|||||||
v_a = 2022-01-01 00:00 #Version auth
|
v_a = 2022-01-01 00:00 #Version auth
|
||||||
u_s = #URL Software
|
u_s = #URL Software
|
||||||
i_s = #Info Software
|
i_s = #Info Software
|
||||||
|
cmd = #CMD
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@ -1045,6 +1516,7 @@ name = LXMF Provisioning Server
|
|||||||
# to be compatibel with other LXMF programs.
|
# to be compatibel with other LXMF programs.
|
||||||
destination_name = lxmf
|
destination_name = lxmf
|
||||||
destination_type = provisioning
|
destination_type = provisioning
|
||||||
|
destination_type_conv = 11
|
||||||
|
|
||||||
# The name will be visible to other peers
|
# The name will be visible to other peers
|
||||||
# on the network, and included in announces.
|
# on the network, and included in announces.
|
||||||
@ -1054,11 +1526,17 @@ display_name = LXMF Provisioning Server
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
#propagation_node =
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
try_propagation_on_fail = No
|
try_propagation_on_fail = Yes
|
||||||
|
|
||||||
# The peer is announced at startup
|
# The peer is announced at startup
|
||||||
# to let other peers reach it immediately.
|
# to let other peers reach it immediately.
|
||||||
@ -1070,6 +1548,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = Yes
|
announce_periodic = Yes
|
||||||
announce_periodic_interval = 360 #Minutes
|
announce_periodic_interval = 360 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
@ -1091,7 +1573,7 @@ sync_periodic_interval = 360 #Minutes
|
|||||||
sync_limit = 8
|
sync_limit = 8
|
||||||
|
|
||||||
# Allow only messages with valid signature.
|
# Allow only messages with valid signature.
|
||||||
signature_validated = Yes
|
signature_validated = No
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1104,8 +1586,6 @@ port = 5432
|
|||||||
user = postgres
|
user = postgres
|
||||||
password = password
|
password = password
|
||||||
database = database
|
database = database
|
||||||
table_registration = tbl_account
|
|
||||||
table_telemetry = tbl_telemetry
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1114,12 +1594,37 @@ table_telemetry = tbl_telemetry
|
|||||||
[features]
|
[features]
|
||||||
|
|
||||||
announce_versions = True
|
announce_versions = True
|
||||||
registration = True
|
|
||||||
|
account_add = True
|
||||||
|
account_add_auth = False
|
||||||
|
account_add_auth_state = 1
|
||||||
|
account_add_auth_role = 3
|
||||||
|
|
||||||
|
account_edit = True
|
||||||
|
account_edit_auth = False
|
||||||
|
account_edit_auth_state = 1
|
||||||
|
account_edit_auth_role = 3
|
||||||
|
|
||||||
|
account_del = True
|
||||||
|
|
||||||
|
account_prove = True
|
||||||
|
account_prove_auth = True
|
||||||
|
account_prove_auth_state = 1
|
||||||
|
account_prove_auth_role = 3
|
||||||
|
|
||||||
telemetry = False
|
telemetry = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Processing ####
|
||||||
|
[processing]
|
||||||
|
interval_in = 5 #Seconds
|
||||||
|
interval_out = 60 #Seconds
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Data settings ####
|
#### Data settings ####
|
||||||
[data]
|
[data]
|
||||||
|
|
||||||
@ -1129,6 +1634,7 @@ v_d = 2022-01-01 00:00 #Version data
|
|||||||
v_a = 2022-01-01 00:00 #Version auth
|
v_a = 2022-01-01 00:00 #Version auth
|
||||||
u_s = #URL Software
|
u_s = #URL Software
|
||||||
i_s = #Info Software
|
i_s = #Info Software
|
||||||
|
cmd = #CMD
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
4
lxmf_terminal/Examples/README.md
Normal file
4
lxmf_terminal/Examples/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Examples
|
||||||
|
This folder contains sample configurations for different application environments or scenarios.
|
||||||
|
|
||||||
|
Copy these files accordingly to the appropriate configuration folder. Then customize the content of these files to your needs.
|
@ -193,9 +193,10 @@ class lxmf_connection:
|
|||||||
message_notification_callback = None
|
message_notification_callback = None
|
||||||
message_notification_success_callback = None
|
message_notification_success_callback = None
|
||||||
message_notification_failed_callback = None
|
message_notification_failed_callback = None
|
||||||
|
config_set_callback = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
|
||||||
self.storage_path = storage_path
|
self.storage_path = storage_path
|
||||||
|
|
||||||
self.identity_file = identity_file
|
self.identity_file = identity_file
|
||||||
@ -208,6 +209,7 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.announce_data = announce_data
|
self.announce_data = announce_data
|
||||||
|
self.announce_hidden = announce_hidden
|
||||||
|
|
||||||
self.send_delay = int(send_delay)
|
self.send_delay = int(send_delay)
|
||||||
|
|
||||||
@ -216,6 +218,8 @@ class lxmf_connection:
|
|||||||
else:
|
else:
|
||||||
self.desired_method_direct = True
|
self.desired_method_direct = True
|
||||||
self.propagation_node = propagation_node
|
self.propagation_node = propagation_node
|
||||||
|
self.propagation_node_auto = propagation_node_auto
|
||||||
|
self.propagation_node_active = propagation_node_active
|
||||||
self.try_propagation_on_fail = try_propagation_on_fail
|
self.try_propagation_on_fail = try_propagation_on_fail
|
||||||
|
|
||||||
self.announce_startup = announce_startup
|
self.announce_startup = announce_startup
|
||||||
@ -230,6 +234,10 @@ class lxmf_connection:
|
|||||||
self.sync_periodic = sync_periodic
|
self.sync_periodic = sync_periodic
|
||||||
self.sync_periodic_interval = int(sync_periodic_interval)
|
self.sync_periodic_interval = int(sync_periodic_interval)
|
||||||
|
|
||||||
|
if not self.storage_path:
|
||||||
|
log("LXMF - No storage_path parameter", LOG_ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
if not os.path.isdir(self.storage_path):
|
if not os.path.isdir(self.storage_path):
|
||||||
os.makedirs(self.storage_path)
|
os.makedirs(self.storage_path)
|
||||||
log("LXMF - Storage path was created", LOG_NOTICE)
|
log("LXMF - Storage path was created", LOG_NOTICE)
|
||||||
@ -284,10 +292,18 @@ class lxmf_connection:
|
|||||||
|
|
||||||
self.destination.set_link_established_callback(self.client_connected)
|
self.destination.set_link_established_callback(self.client_connected)
|
||||||
|
|
||||||
self.autoselect_propagation_node()
|
if self.propagation_node_auto:
|
||||||
|
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
|
||||||
|
RNS.Transport.register_announce_handler(self.propagation_callback)
|
||||||
|
if self.propagation_node_active:
|
||||||
|
self.propagation_node_set(self.propagation_node_active)
|
||||||
|
elif self.propagation_node:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
else:
|
||||||
|
self.propagation_node_set(self.propagation_node)
|
||||||
|
|
||||||
if self.announce_startup or self.announce_periodic:
|
if self.announce_startup or self.announce_periodic:
|
||||||
self.announce(True)
|
self.announce(initial=True)
|
||||||
|
|
||||||
if self.sync_startup or self.sync_periodic:
|
if self.sync_startup or self.sync_periodic:
|
||||||
self.sync(True)
|
self.sync(True)
|
||||||
@ -314,6 +330,10 @@ class lxmf_connection:
|
|||||||
self.message_notification_failed_callback = handler_function
|
self.message_notification_failed_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
|
def register_config_set_callback(self, handler_function):
|
||||||
|
self.config_set_callback = handler_function
|
||||||
|
|
||||||
|
|
||||||
def destination_hash(self):
|
def destination_hash(self):
|
||||||
return self.destination.hash
|
return self.destination.hash
|
||||||
|
|
||||||
@ -357,7 +377,7 @@ class lxmf_connection:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
|
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
|
||||||
if type(destination) is not bytes:
|
if type(destination) is not bytes:
|
||||||
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
|
||||||
destination = destination[1:-1]
|
destination = destination[1:-1]
|
||||||
@ -372,8 +392,13 @@ class lxmf_connection:
|
|||||||
log("LXMF - Destination is invalid", LOG_ERROR)
|
log("LXMF - Destination is invalid", LOG_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if destination_name == None:
|
||||||
|
destination_name = self.destination_name
|
||||||
|
if destination_type == None:
|
||||||
|
destination_type = self.destination_type
|
||||||
|
|
||||||
destination_identity = RNS.Identity.recall(destination)
|
destination_identity = RNS.Identity.recall(destination)
|
||||||
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
|
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
|
||||||
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
|
||||||
|
|
||||||
|
|
||||||
@ -442,7 +467,7 @@ class lxmf_connection:
|
|||||||
message.desired_method_str = "propagated"
|
message.desired_method_str = "propagated"
|
||||||
|
|
||||||
|
|
||||||
def announce(self, initial=False):
|
def announce(self, app_data=None, attached_interface=None, initial=False):
|
||||||
announce_timer = None
|
announce_timer = None
|
||||||
|
|
||||||
if self.announce_periodic and self.announce_periodic_interval > 0:
|
if self.announce_periodic and self.announce_periodic_interval > 0:
|
||||||
@ -459,26 +484,29 @@ class lxmf_connection:
|
|||||||
announce_timer.daemon = True
|
announce_timer.daemon = True
|
||||||
announce_timer.start()
|
announce_timer.start()
|
||||||
else:
|
else:
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.announce_now()
|
self.announce_now(app_data=app_data, attached_interface=attached_interface)
|
||||||
|
|
||||||
|
|
||||||
def announce_now(self, app_data=None):
|
def announce_now(self, app_data=None, attached_interface=None):
|
||||||
if app_data:
|
if self.announce_hidden:
|
||||||
|
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
|
||||||
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
|
||||||
|
elif app_data != None:
|
||||||
if isinstance(app_data, str):
|
if isinstance(app_data, str):
|
||||||
self.destination.announce(app_data.encode("utf-8"))
|
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(app_data)
|
self.destination.announce(app_data, attached_interface=attached_interface)
|
||||||
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
elif self.announce_data:
|
elif self.announce_data:
|
||||||
if isinstance(self.announce_data, str):
|
if isinstance(self.announce_data, str):
|
||||||
self.destination.announce(self.announce_data.encode("utf-8"))
|
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce(self.announce_data)
|
self.destination.announce(self.announce_data, attached_interface=attached_interface)
|
||||||
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
|
||||||
else:
|
else:
|
||||||
self.destination.announce()
|
self.destination.announce()
|
||||||
@ -520,24 +548,50 @@ class lxmf_connection:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autoselect_propagation_node(self):
|
def propagation_node_set(self, dest_str):
|
||||||
if self.propagation_node is not None:
|
if not dest_str:
|
||||||
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
return False
|
||||||
|
|
||||||
|
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
|
||||||
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
log("LXMF - Propagation node length is invalid", LOG_ERROR)
|
||||||
else:
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propagation_hash = bytes.fromhex(self.propagation_node)
|
dest_hash = bytes.fromhex(dest_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
log("LXMF - Propagation node is invalid", LOG_ERROR)
|
||||||
return
|
return False
|
||||||
|
|
||||||
node_identity = RNS.Identity.recall(propagation_hash)
|
node_identity = RNS.Identity.recall(dest_hash)
|
||||||
if node_identity != None:
|
if node_identity != None:
|
||||||
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
|
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
self.message_router.set_outbound_propagation_node(dest_hash)
|
||||||
|
self.propagation_node_active = dest_str
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
log("LXMF - Propagation node identity not known", LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_update(self, dest_str):
|
||||||
|
if self.propagation_node_hash_str() != dest_str:
|
||||||
|
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
|
||||||
|
self.config_set_callback("propagation_node_active", dest_str)
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash(self):
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(self.propagation_node_active)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def propagation_node_hash_str(self):
|
||||||
|
if self.propagation_node_active:
|
||||||
|
return self.propagation_node_active
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def client_connected(self, link):
|
def client_connected(self, link):
|
||||||
@ -625,6 +679,43 @@ class lxmf_connection:
|
|||||||
log("- App Data: " + message.app_data, LOG_DEBUG)
|
log("- App Data: " + message.app_data, LOG_DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class lxmf_connection_propagation():
|
||||||
|
def __init__(self, owner, aspect_filter=None):
|
||||||
|
self.owner = owner
|
||||||
|
self.aspect_filter = aspect_filter
|
||||||
|
|
||||||
|
EMITTED_DELTA_GRACE = 300
|
||||||
|
EMITTED_DELTA_IGNORE = 10
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
if app_data == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(app_data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unpacked = umsgpack.unpackb(app_data)
|
||||||
|
node_active = unpacked[0]
|
||||||
|
emitted = unpacked[1]
|
||||||
|
hop_count = RNS.Transport.hops_to(destination_hash)
|
||||||
|
age = time.time() - emitted
|
||||||
|
if age < 0:
|
||||||
|
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
|
||||||
|
return
|
||||||
|
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
|
||||||
|
if self.owner.propagation_node_active == None:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
else:
|
||||||
|
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
|
||||||
|
if hop_count <= prev_hop_count:
|
||||||
|
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
# LXMF Functions
|
# LXMF Functions
|
||||||
|
|
||||||
@ -839,6 +930,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Config - Set #####
|
||||||
|
def config_set(key=None, value=""):
|
||||||
|
global PATH
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = PATH + "/config.cfg.owr"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
file = PATH + "/config.cfg"
|
||||||
|
if os.path.isfile(file):
|
||||||
|
fh = open(file,'r')
|
||||||
|
data = fh.read()
|
||||||
|
fh.close()
|
||||||
|
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
|
||||||
|
fh = open(file,'w')
|
||||||
|
fh.write(data)
|
||||||
|
fh.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Config - Read #####
|
#### Config - Read #####
|
||||||
def config_read(file=None, file_override=None):
|
def config_read(file=None, file_override=None):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
@ -932,15 +1053,15 @@ def config_default(file=None, file_override=None):
|
|||||||
# Value convert
|
# Value convert
|
||||||
|
|
||||||
|
|
||||||
def val_to_bool(val):
|
def val_to_bool(val, fallback_true=True, fallback_false=False):
|
||||||
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
|
||||||
return True
|
return True
|
||||||
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
|
||||||
return False
|
return False
|
||||||
elif val != "":
|
elif val != "":
|
||||||
return True
|
return fallback_true
|
||||||
else:
|
else:
|
||||||
return False
|
return fallback_false
|
||||||
|
|
||||||
|
|
||||||
##############################################################################################################
|
##############################################################################################################
|
||||||
@ -1106,6 +1227,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
else:
|
else:
|
||||||
config_propagation_node = None
|
config_propagation_node = None
|
||||||
|
|
||||||
|
if CONFIG.has_option("lxmf", "propagation_node_active"):
|
||||||
|
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
|
||||||
|
else:
|
||||||
|
config_propagation_node_active = None
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
path = PATH
|
path = PATH
|
||||||
|
|
||||||
@ -1114,9 +1240,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
destination_name=CONFIG["lxmf"]["destination_name"],
|
destination_name=CONFIG["lxmf"]["destination_name"],
|
||||||
destination_type=CONFIG["lxmf"]["destination_type"],
|
destination_type=CONFIG["lxmf"]["destination_type"],
|
||||||
display_name=CONFIG["lxmf"]["display_name"],
|
display_name=CONFIG["lxmf"]["display_name"],
|
||||||
|
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
|
||||||
send_delay=CONFIG["lxmf"]["send_delay"],
|
send_delay=CONFIG["lxmf"]["send_delay"],
|
||||||
desired_method=CONFIG["lxmf"]["desired_method"],
|
desired_method=CONFIG["lxmf"]["desired_method"],
|
||||||
propagation_node=config_propagation_node,
|
propagation_node=config_propagation_node,
|
||||||
|
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
|
||||||
|
propagation_node_active=config_propagation_node_active,
|
||||||
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
|
||||||
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
|
||||||
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
|
||||||
@ -1130,6 +1259,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
|
|||||||
|
|
||||||
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
|
||||||
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
|
||||||
|
LXMF_CONNECTION.register_config_set_callback(config_set)
|
||||||
|
|
||||||
log("LXMF - Connected", LOG_DEBUG)
|
log("LXMF - Connected", LOG_DEBUG)
|
||||||
|
|
||||||
@ -1228,7 +1358,13 @@ display_name = CMD
|
|||||||
desired_method = direct #direct/propagated
|
desired_method = direct #direct/propagated
|
||||||
|
|
||||||
# Propagation node address/hash.
|
# Propagation node address/hash.
|
||||||
#propagation_node =
|
propagation_node =
|
||||||
|
|
||||||
|
# Set propagation node automatically.
|
||||||
|
propagation_node_auto = True
|
||||||
|
|
||||||
|
# Current propagation node (Automatically set by the software).
|
||||||
|
propagation_node_active =
|
||||||
|
|
||||||
# Try to deliver a message via the LXMF propagation network,
|
# Try to deliver a message via the LXMF propagation network,
|
||||||
# if a direct delivery to the recipient is not possible.
|
# if a direct delivery to the recipient is not possible.
|
||||||
@ -1244,6 +1380,10 @@ announce_startup_delay = 0 #Seconds
|
|||||||
announce_periodic = No
|
announce_periodic = No
|
||||||
announce_periodic_interval = 360 #Minutes
|
announce_periodic_interval = 360 #Minutes
|
||||||
|
|
||||||
|
# The announce is hidden for client applications
|
||||||
|
# but is still used for the routing tables.
|
||||||
|
announce_hidden = No
|
||||||
|
|
||||||
# Some waiting time after message send
|
# Some waiting time after message send
|
||||||
# for LXMF/Reticulum processing.
|
# for LXMF/Reticulum processing.
|
||||||
send_delay = 0 #Seconds
|
send_delay = 0 #Seconds
|
||||||
|
Loading…
Reference in New Issue
Block a user