LXMF-Tools/lxmf_distribution_group/lxmf_distribution_group.py
2023-11-17 19:37:31 +01:00

5338 lines
248 KiB
Python
Executable File

#!/usr/bin/env python3
##############################################################################################################
#
# Copyright (c) 2022 Sebastian Obele / obele.eu
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# This software uses the following software-parts:
# Reticulum, LXMF, NomadNet / Copyright (c) 2016-2022 Mark Qvist / unsigned.io / MIT License
#
##############################################################################################################
##############################################################################################################
# Include
#### System ####
import sys
import os
import time
import datetime
import argparse
#### Config ####
import configparser
#### Variables ####
from collections import defaultdict
#### JSON ####
import json
import pickle
#### String ####
import string
#### Regex ####
import re
#### Search ####
import fnmatch
#### Process ####
import signal
import threading
#### Reticulum, LXMF ####
# Install: pip3 install rns lxmf
# Source: https://markqvist.github.io
import RNS
import LXMF
import RNS.vendor.umsgpack as umsgpack
##############################################################################################################
# Globals
#### Global Variables - Configuration ####
NAME = "LXMF Distribution Group"
DESCRIPTION = "Server-Side group functions for LXMF based apps"
VERSION = "0.0.1 (2022-10-21)"
COPYRIGHT = "(c) 2022 Sebastian Obele / obele.eu"
PATH = os.path.expanduser("~") + "/." + os.path.splitext(os.path.basename(__file__))[0]
PATH_RNS = None
#### Global Variables - System (Not changeable) ####
DATA = None
CONFIG = None
STATISTIC = None
RNS_MAIN_CONNECTION = None
LXMF_CONNECTION = None
RNS_CONNECTION = None
CONV_P2P = 0x01
CONV_GROUP = 0x02
CONV_BROADCAST = 0x03
CONV_DISTRIBUTION_GROUP = 0x04
##############################################################################################################
# LXMF Class
class lxmf_connection:
message_received_callback = None
message_notification_callback = None
message_notification_success_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, 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.identity_file = identity_file
self.identity = identity
self.destination_name = destination_name
self.destination_type = destination_type
self.aspect_filter = self.destination_name + "." + self.destination_type
self.display_name = display_name
self.announce_data = announce_data
self.announce_hidden = announce_hidden
self.send_delay = int(send_delay)
if desired_method == "propagated" or desired_method == "PROPAGATED":
self.desired_method_direct = False
else:
self.desired_method_direct = True
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.announce_startup = announce_startup
self.announce_startup_delay = int(announce_startup_delay)
self.announce_periodic = announce_periodic
self.announce_periodic_interval = int(announce_periodic_interval)
self.sync_startup = sync_startup
self.sync_startup_delay = int(sync_startup_delay)
self.sync_limit = int(sync_limit)
self.sync_periodic = sync_periodic
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):
os.makedirs(self.storage_path)
log("LXMF - Storage path was created", LOG_NOTICE)
log("LXMF - Storage path: " + self.storage_path, LOG_INFO)
if self.identity:
log("LXMF - Using existing Primary Identity %s" % (str(self.identity)))
else:
if not self.identity_file:
self.identity_file = "identity"
self.identity_path = self.storage_path + "/" + self.identity_file
if os.path.isfile(self.identity_path):
try:
self.identity = RNS.Identity.from_file(self.identity_path)
if self.identity != None:
log("LXMF - Loaded Primary Identity %s from %s" % (str(self.identity), self.identity_path))
else:
log("LXMF - Could not load the Primary Identity from "+self.identity_path, LOG_ERROR)
except Exception as e:
log("LXMF - Could not load the Primary Identity from "+self.identity_path, LOG_ERROR)
log("LXMF - The contained exception was: %s" % (str(e)), LOG_ERROR)
else:
try:
log("LXMF - No Primary Identity file found, creating new...")
self.identity = RNS.Identity()
self.identity.to_file(self.identity_path)
log("LXMF - Created new Primary Identity %s" % (str(self.identity)))
except Exception as e:
log("LXMF - Could not create and save a new Primary Identity", LOG_ERROR)
log("LXMF - The contained exception was: %s" % (str(e)), LOG_ERROR)
self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path)
if self.destination_name == "lxmf" and self.destination_type == "delivery":
self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name)
self.message_router.register_delivery_callback(self.process_lxmf_message_propagated)
else:
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
if self.display_name == "":
self.display_name = RNS.prettyhexrep(self.destination_hash())
self.destination.set_default_app_data(self.display_name.encode("utf-8"))
self.destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
RNS.Identity.remember(packet_hash=None, destination_hash=self.destination.hash, public_key=self.identity.get_public_key(), app_data=None)
log("LXMF - Identity: " + str(self.identity), LOG_INFO)
log("LXMF - Destination: " + str(self.destination), LOG_INFO)
log("LXMF - Hash: " + RNS.prettyhexrep(self.destination_hash()), LOG_INFO)
self.destination.set_link_established_callback(self.client_connected)
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:
self.announce(initial=True)
if self.sync_startup or self.sync_periodic:
self.sync(True)
def register_announce_callback(self, handler_function):
self.announce_callback = handler_function(self.aspect_filter)
RNS.Transport.register_announce_handler(self.announce_callback)
def register_message_received_callback(self, handler_function):
self.message_received_callback = handler_function
def register_message_notification_callback(self, handler_function):
self.message_notification_callback = handler_function
def register_message_notification_success_callback(self, handler_function):
self.message_notification_success_callback = handler_function
def register_message_notification_failed_callback(self, 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):
return self.destination.hash
def destination_hash_str(self):
return RNS.hexrep(self.destination.hash, False)
def destination_check(self, destination):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
log("LXMF - Destination length is invalid", LOG_ERROR)
return False
try:
destination = bytes.fromhex(destination)
except Exception as e:
log("LXMF - Destination is invalid", LOG_ERROR)
return False
return True
def destination_correct(self, destination):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
return ""
try:
destination_bytes = bytes.fromhex(destination)
return destination
except Exception as e:
return ""
return ""
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 len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
log("LXMF - Destination length is invalid", LOG_ERROR)
return None
try:
destination = bytes.fromhex(destination)
except Exception as e:
log("LXMF - Destination is invalid", LOG_ERROR)
return None
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 = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
return self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
def send_message(self, destination, source, content="", title="", fields=None, timestamp=None, app_data=""):
if self.desired_method_direct:
desired_method = LXMF.LXMessage.DIRECT
else:
desired_method = LXMF.LXMessage.PROPAGATED
message = LXMF.LXMessage(destination, source, content, title=title, desired_method=desired_method)
if fields is not None:
message.fields = fields
if timestamp is not None:
message.timestamp = timestamp
message.app_data = app_data
self.message_method(message)
self.log_message(message, "LXMF - Message send")
message.register_delivery_callback(self.message_notification)
message.register_failed_callback(self.message_notification)
if self.message_router.get_outbound_propagation_node() != None:
message.try_propagation_on_fail = self.try_propagation_on_fail
try:
self.message_router.handle_outbound(message)
time.sleep(self.send_delay)
return message.hash
except Exception as e:
log("LXMF - Could not send message " + str(message), LOG_ERROR)
log("LXMF - The contained exception was: " + str(e), LOG_ERROR)
return None
def message_notification(self, message):
self.message_method(message)
if self.message_notification_callback is not None:
self.message_notification_callback(message)
if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail:
self.log_message(message, "LXMF - Delivery receipt (failed) Retrying as propagated message")
message.try_propagation_on_fail = None
message.delivery_attempts = 0
del message.next_delivery_attempt
message.packed = None
message.desired_method = LXMF.LXMessage.PROPAGATED
self.message_router.handle_outbound(message)
elif message.state == LXMF.LXMessage.FAILED:
self.log_message(message, "LXMF - Delivery receipt (failed)")
if self.message_notification_failed_callback is not None:
self.message_notification_failed_callback(message)
else:
self.log_message(message, "LXMF - Delivery receipt (success)")
if self.message_notification_success_callback is not None:
self.message_notification_success_callback(message)
def message_method(self, message):
if message.desired_method == LXMF.LXMessage.DIRECT:
message.desired_method_str = "direct"
elif message.desired_method == LXMF.LXMessage.PROPAGATED:
message.desired_method_str = "propagated"
def announce(self, app_data=None, attached_interface=None, initial=False):
announce_timer = None
if self.announce_periodic and self.announce_periodic_interval > 0:
announce_timer = threading.Timer(self.announce_periodic_interval*60, self.announce)
announce_timer.daemon = True
announce_timer.start()
if initial:
if self.announce_startup:
if self.announce_startup_delay > 0:
if announce_timer is not None:
announce_timer.cancel()
announce_timer = threading.Timer(self.announce_startup_delay, self.announce)
announce_timer.daemon = True
announce_timer.start()
else:
self.announce_now(app_data=app_data, attached_interface=attached_interface)
return
self.announce_now(app_data=app_data, attached_interface=attached_interface)
def announce_now(self, app_data=None, attached_interface=None):
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):
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
else:
self.destination.announce(app_data, attached_interface=attached_interface)
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
elif self.announce_data:
if isinstance(self.announce_data, str):
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)
else:
self.destination.announce(self.announce_data, attached_interface=attached_interface)
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
else:
self.destination.announce()
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG)
def sync(self, initial=False):
sync_timer = None
if self.sync_periodic and self.sync_periodic_interval > 0:
sync_timer = threading.Timer(self.sync_periodic_interval*60, self.sync)
sync_timer.daemon = True
sync_timer.start()
if initial:
if self.sync_startup:
if self.sync_startup_delay > 0:
if sync_timer is not None:
sync_timer.cancel()
sync_timer = threading.Timer(self.sync_startup_delay, self.sync)
sync_timer.daemon = True
sync_timer.start()
else:
self.sync_now(self.sync_limit)
return
self.sync_now(self.sync_limit)
def sync_now(self, limit=None):
if self.message_router.get_outbound_propagation_node() is not None:
if self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_IDLE or self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
log("LXMF - Message sync requested from propagation node " + RNS.prettyhexrep(self.message_router.get_outbound_propagation_node()) + " for " + str(self.identity), LOG_DEBUG)
self.message_router.request_messages_from_propagation_node(self.identity, max_messages = limit)
return True
else:
return False
else:
return False
def propagation_node_set(self, dest_str):
if not dest_str:
return False
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
log("LXMF - Propagation node length is invalid", LOG_ERROR)
return False
try:
dest_hash = bytes.fromhex(dest_str)
except Exception as e:
log("LXMF - Propagation node is invalid", LOG_ERROR)
return False
node_identity = RNS.Identity.recall(dest_hash)
if node_identity != None:
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
self.message_router.set_outbound_propagation_node(dest_hash)
self.propagation_node_active = dest_str
return True
else:
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):
log("LXMF - Client connected " + str(link), LOG_EXTREME)
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
link.set_resource_concluded_callback(self.resource_concluded)
link.set_packet_callback(self.packet_received)
def packet_received(self, lxmf_bytes, packet):
log("LXMF - Single packet delivered " + str(packet), LOG_EXTREME)
self.process_lxmf_message_bytes(lxmf_bytes)
def resource_concluded(self, resource):
log("LXMF - Resource data transfer (multi packet) delivered " + str(resource.file), LOG_EXTREME)
if resource.status == RNS.Resource.COMPLETE:
lxmf_bytes = resource.data.read()
self.process_lxmf_message_bytes(lxmf_bytes)
else:
log("LXMF - Received resource message is not complete", LOG_EXTREME)
def process_lxmf_message_bytes(self, lxmf_bytes):
try:
message = LXMF.LXMessage.unpack_from_bytes(lxmf_bytes)
except Exception as e:
log("LXMF - Could not assemble LXMF message from received data", LOG_ERROR)
log("LXMF - The contained exception was: " + str(e), LOG_ERROR)
return
message.desired_method = LXMF.LXMessage.DIRECT
self.message_method(message)
self.log_message(message, "LXMF - Message received")
if self.message_received_callback is not None:
log("LXMF - Call to registered message received callback", LOG_DEBUG)
self.message_received_callback(message)
else:
log("LXMF - No message received callback registered", LOG_DEBUG)
def process_lxmf_message_propagated(self, message):
message.desired_method = LXMF.LXMessage.PROPAGATED
self.message_method(message)
self.log_message(message, "LXMF - Message received")
if self.message_received_callback is not None:
log("LXMF - Call to registered message received callback", LOG_DEBUG)
self.message_received_callback(message)
else:
log("LXMF - No message received callback registered", LOG_DEBUG)
def log_message(self, message, message_tag="LXMF - Message log"):
if message.signature_validated:
signature_string = "Validated"
else:
if message.unverified_reason == LXMF.LXMessage.SIGNATURE_INVALID:
signature_string = "Invalid signature"
elif message.unverified_reason == LXMF.LXMessage.SOURCE_UNKNOWN:
signature_string = "Cannot verify, source is unknown"
else:
signature_string = "Signature is invalid, reason undetermined"
title = message.title.decode('utf-8')
content = message.content.decode('utf-8')
fields = message.fields
log(message_tag + ":", LOG_DEBUG)
log("- Date/Time: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp)), LOG_DEBUG)
log("- Title: " + title, LOG_DEBUG)
log("- Content: " + content, LOG_DEBUG)
log("- Fields: " + str(fields), LOG_DEBUG)
log("- Size: " + str(len(title) + len(content) + len(title) + len(pickle.dumps(fields))) + " bytes", LOG_DEBUG)
log("- Source: " + RNS.prettyhexrep(message.source_hash), LOG_DEBUG)
log("- Destination: " + RNS.prettyhexrep(message.destination_hash), LOG_DEBUG)
log("- Signature: " + signature_string, LOG_DEBUG)
log("- Attempts: " + str(message.delivery_attempts), LOG_DEBUG)
if hasattr(message, "desired_method_str"):
log("- Method: " + message.desired_method_str + " (" + str(message.desired_method) + ")", LOG_DEBUG)
else:
log("- Method: " + str(message.desired_method), LOG_DEBUG)
if hasattr(message, "app_data"):
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
##############################################################################################################
# RNS Class
class rns_connection:
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="rns", destination_type="connect", announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, announce_data="", announce_hidden=False):
self.storage_path = storage_path
self.identity_file = identity_file
self.identity = identity
self.destination_name = destination_name
self.destination_type = destination_type
self.aspect_filter = self.destination_name + "." + self.destination_type
self.announce_startup = announce_startup
self.announce_startup_delay = int(announce_startup_delay)
self.announce_periodic = announce_periodic
self.announce_periodic_interval = int(announce_periodic_interval)
self.announce_data = announce_data
self.announce_hidden = announce_hidden
if not self.storage_path:
log("RNS - No storage_path parameter", LOG_ERROR)
return
if not os.path.isdir(self.storage_path):
os.makedirs(self.storage_path)
log("RNS - Storage path was created", LOG_NOTICE)
log("RNS - Storage path: " + self.storage_path, LOG_INFO)
if self.identity:
log("RNS - Using existing Primary Identity %s" % (str(self.identity)))
else:
if not self.identity_file:
self.identity_file = "identity"
self.identity_path = self.storage_path + "/" + self.identity_file
if os.path.isfile(self.identity_path):
try:
self.identity = RNS.Identity.from_file(self.identity_path)
if self.identity != None:
log("RNS - Loaded Primary Identity %s from %s" % (str(self.identity), self.identity_path))
else:
log("RNS - Could not load the Primary Identity from "+self.identity_path, LOG_ERROR)
except Exception as e:
log("RNS - Could not load the Primary Identity from "+self.identity_path, LOG_ERROR)
log("RNS - The contained exception was: %s" % (str(e)), LOG_ERROR)
else:
try:
log("RNS - No Primary Identity file found, creating new...")
self.identity = RNS.Identity()
self.identity.to_file(self.identity_path)
log("RNS - Created new Primary Identity %s" % (str(self.identity)))
except Exception as e:
log("RNS - Could not create and save a new Primary Identity", LOG_ERROR)
log("RNS - The contained exception was: %s" % (str(e)), LOG_ERROR)
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
self.destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
if self.announce_startup or self.announce_periodic:
self.announce(initial=True)
def register_announce_callback(self, handler_function):
self.announce_callback = handler_function(self.aspect_filter)
RNS.Transport.register_announce_handler(self.announce_callback)
def destination_hash(self):
return self.destination.hash
def destination_hash_str(self):
return RNS.hexrep(self.destination.hash, False)
def destination_check(self, destination):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
log("RNS - Destination length is invalid", LOG_ERROR)
return False
try:
destination = bytes.fromhex(destination)
except Exception as e:
log("RNS - Destination is invalid", LOG_ERROR)
return False
return True
def destination_correct(self, destination):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
return ""
try:
destination_bytes = bytes.fromhex(destination)
return destination
except Exception as e:
return ""
return ""
def announce(self, app_data=None, attached_interface=None, initial=False):
announce_timer = None
if self.announce_periodic and self.announce_periodic_interval > 0:
announce_timer = threading.Timer(self.announce_periodic_interval*60, self.announce)
announce_timer.daemon = True
announce_timer.start()
if initial:
if self.announce_startup:
if self.announce_startup_delay > 0:
if announce_timer is not None:
announce_timer.cancel()
announce_timer = threading.Timer(self.announce_startup_delay, self.announce)
announce_timer.daemon = True
announce_timer.start()
else:
self.announce_now(app_data=app_data, attached_interface=attached_interface)
return
self.announce_now(app_data=app_data, attached_interface=attached_interface)
def announce_now(self, app_data=None, attached_interface=None):
if self.announce_hidden:
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
elif app_data != None:
if isinstance(app_data, str):
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
else:
self.destination.announce(app_data, attached_interface=attached_interface)
log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
else:
if isinstance(self.announce_data, str):
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
else:
self.destination.announce(self.announce_data, attached_interface=attached_interface)
log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
##############################################################################################################
# LXMF Functions
#### LXMF - Announce ####
class lxmf_announce_callback:
def __init__(self, aspect_filter=None):
self.aspect_filter = aspect_filter
@staticmethod
def received_announce(destination_hash, announced_identity, app_data):
if app_data == None:
return
if len(app_data) == 0:
return
try:
app_data_dict = umsgpack.unpackb(app_data)
if isinstance(app_data_dict, dict) and "c" in app_data_dict:
app_data = app_data_dict["c"]
except:
pass
try:
app_data = app_data.decode("utf-8").strip()
except:
return
log("LXMF - Received an announce from " + RNS.prettyhexrep(destination_hash) + ": " + app_data, LOG_INFO)
global DATA
lng_key = "-" + CONFIG["main"]["lng"]
sections = []
for (key, val) in CONFIG.items("rights"):
if DATA.has_section(key):
sections.append(key)
if CONFIG["main"].getboolean("auto_name_def") or CONFIG["main"].getboolean("auto_name_change"):
source_hash = RNS.hexrep(destination_hash, False)
for section in DATA.sections():
for (key, val) in DATA.items(section):
if key == source_hash:
if (val == "" and CONFIG["main"].getboolean("auto_name_def")) or (val != "" and CONFIG["main"].getboolean("auto_name_change")):
value = app_data
if value != DATA[section][key]:
if DATA[section][key] == "":
content_type = "name_def"
content_add = " " + value
else:
content_type = "name_change"
content_add = " " + DATA[section][key] + " -> " + value
DATA[section][key] = value
content_group = config_get(CONFIG, "interface_messages", "member_"+content_type, "", lng_key)
if content_group != "":
fields = fields_generate(lng_key, h=destination_hash ,n=value, tpl=content_type)
content_group = replace(content_group, source_hash, value, "", lng_key)
content_group = content_group + content_add
for section in sections:
if "receive_auto_"+content_type in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
#### LXMF - Message ####
def lxmf_message_received_callback(message):
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)
return
title = message.title.decode('utf-8').strip()
denys = config_getarray(CONFIG, "message", "deny_title")
if len(denys) > 0:
if "*" in denys:
return
for deny in denys:
if deny in title:
return
content = message.content.decode('utf-8').strip()
denys = config_getarray(CONFIG, "message", "deny_content")
if len(denys) > 0:
if "*" in denys:
return
for deny in denys:
if deny in title:
return
if message.fields:
denys = config_getarray(CONFIG, "message", "deny_fields")
if len(denys) > 0:
if "*" in denys:
return
for deny in denys:
if deny in message.fields:
return
if not CONFIG["message"].getboolean("title"):
title = ""
if CONFIG["message"].getboolean("fields") and message.fields:
pass
elif content == "":
return
fields = message.fields
lng_key = "-" + CONFIG["main"]["lng"]
sections = []
for (key, val) in CONFIG.items("rights"):
if DATA.has_section(key):
sections.append(key)
destination_hash = RNS.hexrep(message.destination_hash, False)
source_hash = RNS.hexrep(message.source_hash, False)
source_name = ""
source_right = ""
for section in DATA.sections():
if section.startswith("block"):
if DATA.has_option(section, source_hash):
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " blocked", LOG_DEBUG)
source_right = section.replace("block_", "")
source_rights = config_get(CONFIG, "rights", source_right)
source_rights = source_rights.split(",")
if "reply_block" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_block", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
source_rights = []
for section in DATA.sections():
for (key, val) in DATA.items(section):
if key == source_hash:
if source_name == "":
source_name = val
source_right = section
source_rights.append(section)
if fields:
if "c_n" in fields and "c_t" in fields and "m_t" in fields:
if fields["c_n"] == CONFIG["cluster"]["name"] and fields["c_t"] == CONFIG["cluster"]["type"] and "cluster" in source_rights and config_getboolean(CONFIG, "cluster", "enabled"):
title_prefix = config_get(CONFIG, "message", "cluster_receive_title_prefix", "", lng_key)
content_prefix = config_get(CONFIG, "message", "cluster_receive_prefix", "", lng_key)
content_suffix = config_get(CONFIG, "message", "cluster_receive_suffix", "", lng_key)
title_prefix = replace(title_prefix, source_hash, source_name, source_right, lng_key)
content_prefix = replace(content_prefix, source_hash, source_name, source_right, lng_key)
content_suffix = replace(content_suffix, source_hash, source_name, source_right, lng_key)
source = source_name.rsplit('/', 1)[-1]
destination = config_get(CONFIG, "cluster", "display_name", "", lng_key).rsplit('/', 1)[-1]
title_prefix = title_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_source"+CONFIG["interface"]["delimiter_output"], source)
title_prefix = title_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_destination"+CONFIG["interface"]["delimiter_output"], destination)
content_prefix = content_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_source"+CONFIG["interface"]["delimiter_output"], source)
content_prefix = content_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_destination"+CONFIG["interface"]["delimiter_output"], destination)
content_suffix = content_suffix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_source"+CONFIG["interface"]["delimiter_output"], source)
content_suffix = content_suffix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_destination"+CONFIG["interface"]["delimiter_output"], destination)
search = config_get(CONFIG, "message", "cluster_receive_search")
if search != "":
content = content.replace(search, config_get(CONFIG, "message", "cluster_receive_replace"))
search = config_get(CONFIG, "message", "cluster_receive_regex_search")
if search != "":
content = re.sub(search, config_get(CONFIG, "message", "cluster_receive_regex_replace"), content)
title = title_prefix + title
content = content_prefix + content + content_suffix
if config_get(CONFIG, "message", "timestamp", "", lng_key) == "client":
timestamp = message.timestamp
else:
timestamp = time.time()
if CONFIG["message"].getboolean("fields"):
if message.fields:
fields = fields_remove(message.fields, "fields_remove_anonymous" if "anonymous" in source_rights else "fields_remove")
else:
fields = {}
else:
fields = {}
fields = fields(fields)
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("cluster"):
statistic("add", "cluster_in_" + message.desired_method_str)
if fields["m_t"] == "message":
for section in sections:
if "receive_cluster" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content, title, fields, timestamp, "cluster_send")
elif fields["m_t"] == "pin":
delimiter = CONFIG["interface"]["delimiter_output"]
value_new = config_get(CONFIG, "interface_menu", "cluster_pin", "", lng_key)
value_new = replace(value_new, source_hash, source_name, source_right, lng_key)
value_new = value_new.replace(delimiter+"value"+delimiter, content)
key = time.strftime(config_get(CONFIG, "message", "pin_id", "%y%m%d-%H%M%S", lng_key), time.localtime(time.time()))
if DATA.has_option("pin", key):
key = key + "-"
key_int = 0
while DATA.has_option("pin", key+str(key_int)):
key_int += 1
key = key+str(key_int)
DATA["pin"][key] = value_new
content_group = config_get(CONFIG, "interface_messages", "cluster_pin_add", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"key"+delimiter, key)
content_group = content_group.replace(delimiter+"value"+delimiter, value_new)
if content_group != "":
for section in sections:
if "receive_cluster_pin_add" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields, None, "cluster_send")
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
return
if source_right == "" and DATA["main"].getboolean("auto_add_user"):
if CONFIG["lxmf"].getboolean("signature_validated_new") and not message.signature_validated:
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " have no valid signature 'new'", LOG_DEBUG)
return
source_right = DATA["main"]["auto_add_user_type"]
if DATA.has_section(source_right) and source_right != "main":
if CONFIG["main"].getboolean("auto_name_add"):
app_data = RNS.Identity.recall_app_data(message.source_hash)
if app_data != None and len(app_data) > 0:
try:
app_data_dict = umsgpack.unpackb(app_data)
if isinstance(app_data_dict, dict) and "c" in app_data_dict:
app_data = app_data_dict["c"]
except:
pass
source_name = app_data.decode('utf-8')
DATA[source_right][source_hash] = source_name
DATA.remove_option("main", "unsaved")
content = config_get(CONFIG, "interface_messages", "auto_add_"+source_right, "", lng_key)
content_group = config_get(CONFIG, "interface_messages", "member_join", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
if content_group != "":
fields = fields_generate(lng_key, h=message.source_hash ,n=source_name, m=True, tpl="join")
for section in sections:
if "receive_join" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, title, fields, None, "interface_send")
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
content = replace(content, source_hash, source_name, source_right, lng_key)
if content != "":
LXMF_CONNECTION.send(source_hash, content, title, fields_generate(lng_key, m=True, d=True, r=True, cmd=source_right, config=source_right, tpl="info"), None, "interface_send")
return
elif source_right == "":
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " not exist (auto add disabled)", LOG_DEBUG)
return
source_rights = config_get(CONFIG, "rights", source_right)
if source_rights == "":
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " have no right", LOG_DEBUG)
return
source_rights = source_rights.split(",")
if CONFIG["lxmf"].getboolean("signature_validated_known") and not message.signature_validated:
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " have no valid signature 'known'", LOG_DEBUG)
if "reply_signature" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_signature", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
length = config_getint(CONFIG, "message", "receive_length_min", 0, lng_key)
if length> 0:
if len(content) < length:
if "reply_length_min" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_length_min", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
length = config_getint(CONFIG, "message", "receive_length_max", 0, lng_key)
if length > 0:
if len(content) > length:
if "reply_length_max" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_length_max", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
title_prefix = config_get(CONFIG, "message", "receive_title_prefix", "", lng_key)
content_prefix = config_get(CONFIG, "message", "receive_prefix", "", lng_key)
content_suffix = config_get(CONFIG, "message", "receive_suffix", "", lng_key)
search = config_get(CONFIG, "message", "receive_search")
if search != "":
content = content.replace(search, config_get(CONFIG, "message", "receive_replace"))
search = config_get(CONFIG, "message", "receive_regex_search")
if search != "":
content = re.sub(search, config_get(CONFIG, "message", "receive_regex_replace"), content)
title = title_prefix + title
content = content_prefix + content + content_suffix
# Interface
if content.startswith(CONFIG["interface"]["delimiter_input"]):
if not config_getboolean(CONFIG, "interface", "enabled"):
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'interface' disabled", LOG_DEBUG)
if "reply_interface_enabled" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_interface_enabled", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
if "interface" not in source_rights:
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'interface' not allowed", LOG_DEBUG)
if "reply_interface_right" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_interface_right", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
content = interface(content[len(CONFIG["interface"]["delimiter_input"]):], source_hash, source_name, source_right, source_rights, lng_key, message)
if content == "":
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'interface' not allowed (empty response)", LOG_DEBUG)
return
if CONFIG["statistic"].getboolean("enabled"):
if CONFIG["statistic"].getboolean("interface"):
statistic("add", "interface_received_" + message.desired_method_str)
if CONFIG["statistic"].getboolean("user"):
statistic("value_set", source_hash, "activity", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
statistic("value_set", source_hash, "activity_receive", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
LXMF_CONNECTION.send(source_hash, content, "", fields_generate(lng_key), None, "interface_send")
return
# Message - Cluster
if content.startswith(CONFIG["cluster"]["delimiter_input"]):
if not config_getboolean(CONFIG, "cluster", "enabled") or not DATA["main"].getboolean("enabled_cluster"):
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'cluster' disabled", LOG_DEBUG)
if "reply_cluster_enabled" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_cluster_enabled", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
if "send_cluster" not in source_rights:
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'cluster' not allowed", LOG_DEBUG)
if "reply_cluster_right" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_cluster_right", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
try:
content = content[len(CONFIG["cluster"]["delimiter_input"]):]
destination, content = content.split(" ", 1)
except:
LXMF_CONNECTION.send(source_hash, config_get(CONFIG, "interface_menu", "cluster_format_error", "", lng_key) , "", fields_generate(lng_key), None, "interface_send")
return
destinations = []
for (key, val) in DATA.items("cluster"):
if key != destination_hash and destination in val.split("/"):
destinations.append(key)
if len(destinations) == 0:
LXMF_CONNECTION.send(source_hash, config_get(CONFIG, "interface_menu", "cluster_found_error", "", lng_key) , "", fields_generate(lng_key), None, "interface_send")
return
length = config_getint(CONFIG, "message", "cluster_send_length_min", 0, lng_key)
if length> 0:
if len(content) < length:
if "reply_length_min" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_length_min", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
length = config_getint(CONFIG, "message", "cluster_send_length_max", 0, lng_key)
if length > 0:
if len(content) > length:
if "reply_length_max" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_length_max", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
title_prefix = config_get(CONFIG, "message", "cluster_send_title_prefix", "", lng_key)
content_prefix = config_get(CONFIG, "message", "cluster_send_prefix", "", lng_key)
content_suffix = config_get(CONFIG, "message", "cluster_send_suffix", "", lng_key)
if "anonymous" in source_rights:
title_prefix = replace(title_prefix, "", "", source_right, lng_key)
content_prefix = replace(content_prefix, "", "", source_right, lng_key)
content_suffix = replace(content_suffix, "", "", source_right, lng_key)
else:
title_prefix = replace(title_prefix, source_hash, source_name, source_right, lng_key)
content_prefix = replace(content_prefix, source_hash, source_name, source_right, lng_key)
content_suffix = replace(content_suffix, source_hash, source_name, source_right, lng_key)
source = config_get(CONFIG, "cluster", "display_name", "", lng_key).rsplit('/', 1)[-1]
title_prefix = title_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_source"+CONFIG["interface"]["delimiter_output"], source)
title_prefix = title_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_destination"+CONFIG["interface"]["delimiter_output"], destination)
content_prefix = content_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_source"+CONFIG["interface"]["delimiter_output"], source)
content_prefix = content_prefix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_destination"+CONFIG["interface"]["delimiter_output"], destination)
content_suffix = content_suffix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_source"+CONFIG["interface"]["delimiter_output"], source)
content_suffix = content_suffix.replace(CONFIG["interface"]["delimiter_output"]+"cluster_destination"+CONFIG["interface"]["delimiter_output"], destination)
search = config_get(CONFIG, "message", "cluster_send_search")
if search != "":
content = content.replace(search, config_get(CONFIG, "message", "cluster_send_replace"))
search = config_get(CONFIG, "message", "cluster_send_regex_search")
if search != "":
content = re.sub(search, config_get(CONFIG, "message", "cluster_send_regex_replace"), content)
if CONFIG["message"].getboolean("fields"):
if message.fields:
fields = fields_remove(message.fields, "fields_remove_anonymous" if "anonymous" in source_rights else "fields_remove")
else:
fields = {}
else:
fields = {}
if CONFIG["main"].getboolean("fields_message"):
if not 0xA7 in fields:
fields[0xA7] = message.hash
if not "anonymous" in source_rights and 0xAF not in fields:
fields[0xAF] = {}
fields[0xAF]["h"] = message.source_hash
fields[0xAF]["n"] = source_name
fields["c_n"] = CONFIG["cluster"]["name"]
fields["c_t"] = CONFIG["cluster"]["type"]
delimiter_input = CONFIG["interface"]["delimiter_input"]
if (content.startswith(delimiter_input+"pin ") or content.startswith(delimiter_input+"pins ")) and "cluster_pin_add" in source_rights:
content = content.lstrip(delimiter_input+"pin ")
content = content.lstrip(delimiter_input+"pins ")
fields["m_t"] = "pin"
else:
fields["m_t"] = "message"
title = title_prefix + title
content = content_prefix + content + content_suffix
if config_get(CONFIG, "message", "timestamp", "", lng_key) == "client":
timestamp = message.timestamp
else:
timestamp = time.time()
if CONFIG["statistic"].getboolean("enabled"):
if CONFIG["statistic"].getboolean("cluster"):
statistic("add", "cluster_received_" + message.desired_method_str)
if CONFIG["statistic"].getboolean("user"):
statistic("add", source_hash)
statistic("value_set", source_hash, "activity", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
statistic("value_set", source_hash, "activity_receive", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
for val in destinations:
LXMF_CONNECTION.send(key, content, title, fields, timestamp, "cluster_out")
cluster_loop = False
if destination in config_get(CONFIG, "cluster", "display_name", "", lng_key).split("/"):
cluster_loop = True
if CONFIG["message"].getboolean("fields"):
if message.fields:
fields = fields_remove(message.fields, "fields_remove_anonymous" if "anonymous" in source_rights else "fields_remove")
else:
fields = {}
else:
fields = {}
if CONFIG["main"].getboolean("fields_message"):
if CONFIG["lxmf"]["destination_type_conv"] != "":
fields[0xB3] = CONFIG["lxmf"].getint("destination_type_conv")
if not 0xA7 in fields:
fields[0xA7] = message.hash
if not "anonymous" in source_rights and 0xAF not in fields:
fields[0xAF] = {}
fields[0xAF]["h"] = message.source_hash
fields[0xAF]["n"] = source_name
for section in sections:
if "receive_cluster_send" in config_get(CONFIG, "rights", section).split(",") or (cluster_loop and "receive_cluster_loop" in config_get(CONFIG, "rights", section).split(",")):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content, title, fields, timestamp, "local_send")
return
# Message - Local
if DATA["main"].getboolean("enabled_local"):
if "send_local" in source_rights:
length = config_getint(CONFIG, "message", "send_length_min", 0, lng_key)
if length> 0:
if len(content) < length:
if "reply_length_min" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_length_min", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
length = config_getint(CONFIG, "message", "send_length_max", 0, lng_key)
if length > 0:
if len(content) > length:
if "reply_length_max" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_length_max", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
title_prefix = config_get(CONFIG, "message", "send_title_prefix", "", lng_key)
content_prefix = config_get(CONFIG, "message", "send_prefix", "", lng_key)
content_suffix = config_get(CONFIG, "message", "send_suffix", "", lng_key)
if "anonymous" in source_rights:
title_prefix = replace(title_prefix, "", "", source_right, lng_key)
content_prefix = replace(content_prefix, "", "", source_right, lng_key)
content_suffix = replace(content_suffix, "", "", source_right, lng_key)
else:
title_prefix = replace(title_prefix, source_hash, source_name, source_right, lng_key)
content_prefix = replace(content_prefix, source_hash, source_name, source_right, lng_key)
content_suffix = replace(content_suffix, source_hash, source_name, source_right, lng_key)
search = config_get(CONFIG, "message", "send_search")
if search != "":
content = content.replace(search, config_get(CONFIG, "message", "send_replace"))
search = config_get(CONFIG, "message", "send_regex_search")
if search != "":
content = re.sub(search, config_get(CONFIG, "message", "send_regex_replace"), content)
title = title_prefix + title
content = content_prefix + content + content_suffix
if CONFIG["message"].getboolean("fields"):
if message.fields:
fields = fields_remove(message.fields, "fields_remove_anonymous" if "anonymous" in source_rights else "fields_remove")
else:
fields = {}
else:
fields = {}
if CONFIG["main"].getboolean("fields_message"):
if CONFIG["lxmf"]["destination_type_conv"] != "":
fields[0xB3] = CONFIG["lxmf"].getint("destination_type_conv")
if not 0xA7 in fields:
fields[0xA7] = message.hash
if not "anonymous" in source_rights and 0xAF not in fields:
fields[0xAF] = {}
fields[0xAF]["h"] = message.source_hash
fields[0xAF]["n"] = source_name
if config_get(CONFIG, "message", "timestamp", "", lng_key) == "client":
timestamp = message.timestamp
else:
timestamp = time.time()
if CONFIG["statistic"].getboolean("enabled"):
if CONFIG["statistic"].getboolean("local"):
statistic("add", "local_received_" + message.desired_method_str)
if CONFIG["statistic"].getboolean("user"):
statistic("add", source_hash)
statistic("value_set", source_hash, "activity", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
statistic("value_set", source_hash, "activity_receive", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
for section in sections:
if "receive_local" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content, title, fields, timestamp, "local_send")
return
else:
log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'send' not allowed", LOG_DEBUG)
if "reply_local_right" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_local_right", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
else:
if "reply_local_enabled" in source_rights:
content_user = config_get(CONFIG, "interface_messages", "reply_local_enabled", "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(source_hash, content_user, "", fields_generate(lng_key), None, "interface_send")
return
#### LXMF - Notification ####
def lxmf_message_notification_success_callback(message):
if CONFIG["statistic"].getboolean("enabled"):
if message.app_data.startswith("cluster") and CONFIG["statistic"].getboolean("cluster"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_success")
elif message.app_data.startswith("router") and CONFIG["statistic"].getboolean("router"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_success")
elif message.app_data.startswith("local") and CONFIG["statistic"].getboolean("local"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_success")
elif message.app_data.startswith("interface") and CONFIG["statistic"].getboolean("interface"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_success")
if CONFIG["statistic"].getboolean("user"):
if message.desired_method_str == "direct":
destination_hash = RNS.hexrep(message.destination_hash, False)
statistic("value_set", destination_hash, "activity", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
statistic("value_set", destination_hash, "activity_send", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())))
return
#### LXMF - Notification ####
def lxmf_message_notification_failed_callback(message):
if CONFIG["statistic"].getboolean("enabled"):
if message.app_data.startswith("cluster") and CONFIG["statistic"].getboolean("cluster"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_failed")
elif message.app_data.startswith("router") and CONFIG["statistic"].getboolean("router"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_failed")
elif message.app_data.startswith("local") and CONFIG["statistic"].getboolean("local"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_failed")
elif message.app_data.startswith("interface") and CONFIG["statistic"].getboolean("interface"):
statistic("add", message.app_data + "_" + message.desired_method_str + "_failed")
return
##############################################################################################################
# RNS Functions
class rns_announce_callback:
def __init__(self, aspect_filter=None):
self.aspect_filter = aspect_filter
@staticmethod
def received_announce(destination_hash, announced_identity, app_data):
if app_data != None:
log("Cluster - Received an announce from " + RNS.prettyhexrep(destination_hash) + ": " + app_data.decode("utf-8"), LOG_INFO)
global DATA
lng_key = "-" + CONFIG["main"]["lng"]
sections = []
for (key, val) in CONFIG.items("rights"):
if DATA.has_section(key):
sections.append(key)
receive = app_data.decode("utf-8")
if receive != "":
receive = json.loads(receive)
executed = False
if "h" in receive and "c" in receive and "c_n" in receive and CONFIG["cluster"].getboolean("enabled") and DATA["main"].getboolean("auto_add_cluster"):
if receive["c"] == "1":
if not DATA.has_option("cluster", receive["h"]):
content_group = config_get(CONFIG, "interface_messages", "cluster_join", "", lng_key)
content_group = replace(content_group, receive["h"], receive["c_n"], "", lng_key)
if content_group != "":
for section in sections:
if "receive_cluster_join" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields_generate(lng_key), None, "interface_send")
DATA["cluster"][receive["h"]] = receive["c_n"]
executed = True
if "h" in receive and "r" in receive and "r_n" in receive and CONFIG["router"].getboolean("enabled") and DATA["main"].getboolean("auto_add_router"):
if receive["r"] == "1":
DATA["router"][receive["h"]] = receive["r_n"]
executed = True
if executed:
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
##############################################################################################################
# Interface
#### Interface #####
def interface(cmd, source_hash, source_name, source_right, source_rights, lng_key, message):
cmd = cmd.strip()
content = ""
delimiter = CONFIG["interface"]["delimiter_output"]
sections = []
for (key, val) in CONFIG.items("rights"):
if DATA.has_section(key):
sections.append(key)
# "/help" command.
if (cmd == "help" or cmd == "?") and "help" in source_rights:
content = config_get(CONFIG, "interface_menu", "help_"+source_right, "", lng_key)
interface_help = ""
interface_help_command = ""
for value in source_rights:
interface_help = interface_help + config_get(CONFIG, "interface_help", value, "", lng_key)
interface_help_command = interface_help_command + config_get(CONFIG, "interface_help_command", value, "", lng_key)
content = content.replace(delimiter+"interface_help"+delimiter, interface_help)
content = content.replace(delimiter+"interface_help_command"+delimiter, interface_help_command)
content = replace(content, source_hash, source_name, source_right, lng_key)
# "/update" command.
elif (cmd == "update") and "update" in source_rights:
try:
content = config_get(CONFIG, "interface_menu", "update_ok", "", lng_key)
LXMF_CONNECTION.send(source_hash, content, "", fields_generate(lng_key, m=True, d=True, r=True, cmd=source_right, config=source_right, tpl="update"), None, "interface_send")
content = ""
except:
content = config_get(CONFIG, "interface_menu", "update_error", "", lng_key)
# "/update_all" command.
elif (cmd == "update_all") and "update_all" in source_rights:
try:
content = config_get(CONFIG, "interface_menu", "update_all_ok", "", lng_key)
for section in sections:
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content, "", fields_generate(lng_key, m=True, d=True, r=True, cmd=section, config=section, tpl="update"), None, "interface_send")
content = ""
except:
content = config_get(CONFIG, "interface_menu", "update_all_error", "", lng_key)
# "/join" command.
elif (cmd == "join" or cmd == "subscribe") and "join" in source_rights:
try:
content = config_get(CONFIG, "interface_messages", "auto_add_"+source_right, "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
if content != "":
LXMF_CONNECTION.send(source_hash, content, "", fields_generate(lng_key, m=True, d=True, r=True, cmd=source_right, config=source_right, tpl="info"), None, "interface_send")
content = ""
except:
content = config_get(CONFIG, "interface_menu", "join_error", "", lng_key)
# "/leave" command.
elif (cmd == "leave" or cmd == "unsubscribe" or cmd == "part") and "leave" in source_rights:
try:
for section in sections:
for (key, val) in DATA.items(section):
if key == source_hash:
DATA.remove_option(section, key)
if CONFIG["statistic"].getboolean("enabled"):
statistic("del", key)
content_group = config_get(CONFIG, "interface_messages", "member_leave", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
if content_group != "":
fields = fields_generate(lng_key, h=message.source_hash ,n=source_name, m=True, tpl="leave")
for section in sections:
if "receive_leave" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "leave_ok", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
if content != "":
LXMF_CONNECTION.send(source_hash, content, "", {0xA3: None, 0xB1: "info"}, None, "interface_send")
content = ""
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "leave_error", "", lng_key)
# "/name" command.
elif (cmd == "name" or cmd == "nick") and "name" in source_rights:
content = config_get(CONFIG, "interface_menu", "name", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
elif (cmd.startswith("name ") or cmd.startswith("nick ") or cmd.startswith("setname ")) and "name" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
for section in sections:
for (key, val) in DATA.items(section):
if key == source_hash:
DATA[section][key] = value
if source_name == "":
content_type = "name_def"
content_add = " " + value
else:
content_type = "name_change"
content_add = " " + source_name + " -> " + value
content_group = config_get(CONFIG, "interface_messages", "member_"+content_type, "", lng_key)
if content_group != "":
fields = fields_generate(lng_key, h=message.source_hash ,n=source_name, tpl=content_type)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group + content_add
for section in sections:
if "receive_"+content_type in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "name_ok", "", lng_key) + " " + value
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "name_error", "", lng_key)
# "/address" command.
elif cmd == "address" and "address" in source_rights:
content = config_get(CONFIG, "interface_menu", "address_"+source_right, "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
# "/info" command.
elif cmd == "info" and "info" in source_rights:
content = config_get(CONFIG, "interface_menu", "info_"+source_right, "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
# "/pin" command.
elif (cmd == "pin" or cmd == "pins") and "pin" in source_rights:
count = 0
content = config_get(CONFIG, "interface_menu", "pin_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
for (key, val) in DATA.items("pin"):
count += 1
content = content + "#" + key + "\n" + val + "\n\n"
content = content.replace(delimiter+"count"+delimiter, str(count))
elif (cmd.startswith("pin ") or cmd.startswith("pins ")) and "pin_add" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
value_new = config_get(CONFIG, "interface_menu", "pin", "", lng_key)
value_new = replace(value_new, source_hash, source_name, source_right, lng_key)
value_new = value_new.replace(delimiter+"value"+delimiter, value)
key = time.strftime(config_get(CONFIG, "message", "pin_id", "%y%m%d-%H%M%S", lng_key), time.localtime(time.time()))
if DATA.has_option("pin", key):
key = key + "-"
key_int = 0
while DATA.has_option("pin", key+str(key_int)):
key_int += 1
key = key+str(key_int)
DATA["pin"][key] = value_new
content_group = config_get(CONFIG, "interface_messages", "pin_add", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"key"+delimiter, key)
content_group = content_group.replace(delimiter+"value"+delimiter, value_new)
if content_group != "":
for section in sections:
if "receive_pin_add" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields_generate(lng_key, h=message.source_hash ,n=source_name), None, "interface_send")
content = config_get(CONFIG, "interface_menu", "pin_add_ok", "", lng_key)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
elif (cmd.startswith("unpin ") or cmd.startswith("unpins ")) and "pin_remove" in source_rights:
try:
cmd, key = cmd.split(" ", 1)
if key.startswith("#"):
key = key[1:]
if DATA.has_option("pin", key):
value = DATA["pin"][key]
DATA.remove_option("pin", key)
content_group = config_get(CONFIG, "interface_messages", "pin_remove", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"key"+delimiter, key)
content_group = content_group.replace(delimiter+"value"+delimiter, value)
if content_group != "":
for section in sections:
if "receive_pin_add" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields_generate(lng_key, h=message.source_hash ,n=source_name), None, "interface_send")
content = config_get(CONFIG, "interface_menu", "pin_remove_ok", "", lng_key)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "pin_found_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/version" command.
elif cmd == "version" and "version" in source_rights:
content = config_get(CONFIG, "interface_menu", "version_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content = content + NAME + "\n" + DESCRIPTION + "\nV" + VERSION
# "/groups" command.
elif (cmd == "groups" or cmd == "group" or cmd == "cluster") and "groups" in source_rights:
count = 0
content = config_get(CONFIG, "interface_menu", "groups_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content_member = config_get(CONFIG, "interface_menu", "groups_member", "", lng_key)
data_dict = defaultdict(dict)
section = "cluster"
for (key, val) in DATA.items(section):
data_dict[val] = key
for key in sorted(data_dict):
count += 1
content = content + replace(content_member, data_dict[key], key, section, lng_key)
content = content.replace(delimiter+"count"+delimiter, str(count))
elif (cmd.startswith("groups ") or cmd.startswith("group ") or cmd.startswith("cluster ")) and "groups" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
executed = False
count = 0
content = config_get(CONFIG, "interface_menu", "groups_search_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content_member = config_get(CONFIG, "interface_menu", "groups_search_member", "", lng_key)
data_dict = defaultdict(dict)
section = "cluster"
for (key, val) in DATA.items(section):
if value in val:
executed = True
data_dict[val] = key
for key in sorted(data_dict):
count += 1
content = content + replace(content_member, data_dict[key], key, section, lng_key)
content = content.replace(delimiter+"count"+delimiter, str(count))
if not executed:
content = config_get(CONFIG, "interface_menu", "groups_search_found_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/members" command.
elif (cmd == "members" or cmd == "member" or cmd == "names" or cmd == "who") and "members" in source_rights:
count = 0
content = config_get(CONFIG, "interface_menu", "members_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content_member = config_get(CONFIG, "interface_menu", "members_member", "", lng_key)
for section in sections:
for (key, val) in DATA.items(section):
count += 1
content = content + replace(content_member, key, val, section, lng_key)
content = content.replace(delimiter+"count"+delimiter, str(count))
# "/search" command.
elif (cmd.startswith("search ") or cmd.startswith("whois ") or cmd.startswith("w ")) and "search" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
executed = False
count = 0
content = config_get(CONFIG, "interface_menu", "search_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content_member = config_get(CONFIG, "interface_menu", "search_member", "", lng_key)
for section in sections:
for (key, val) in DATA.items(section):
if fnmatch.fnmatch(key, value) or fnmatch.fnmatch(val, value):
executed = True
count += 1
content = content + replace(content_member, key, val, section, lng_key).replace(delimiter+"activity_receive"+delimiter, statistic_value_get(key, "activity_receive")).replace(delimiter+"activity_send"+delimiter, statistic_value_get(key, "activity_send"))
content = content.replace(delimiter+"count"+delimiter, str(count))
if not executed:
content = config_get(CONFIG, "interface_menu", "search_found_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/activitys" command.
elif (cmd == "activitys" or cmd == "activity") and "activitys" in source_rights:
count = 0
content = config_get(CONFIG, "interface_menu", "activitys_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content_member = config_get(CONFIG, "interface_menu", "activitys_member", "", lng_key)
for section in sections:
for (key, val) in DATA.items(section):
count += 1
content = content + replace(content_member, key, val, section, lng_key).replace(delimiter+"activity_receive"+delimiter, statistic_value_get(key, "activity_receive")).replace(delimiter+"activity_send"+delimiter, statistic_value_get(key, "activity_send"))
content = content.replace(delimiter+"count"+delimiter, str(count))
# "/statistic" command.
elif (cmd == "statistic" or cmd == "stat" or cmd == "stats" or cmd.startswith("statistic ") or cmd.startswith("stat ") or cmd.startswith("stats ")) and "statistic" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
except:
value = "day"
values = ["day", "last_day", "week", "last_week", "month", "last_month", "year", "last_year", "all", "max"]
if value in values:
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("cluster") and "statistic_cluster" in source_rights and ("statistic_min" in source_rights or "statistic_full" in source_rights):
content = content + replace(config_get(CONFIG, "interface_menu", "statistic_header_cluster", "", lng_key), source_hash, source_name, source_right, lng_key).replace(delimiter+"value"+delimiter, value)
if "statistic_min" in source_rights:
statistic_recalculate("cluster_received_direct")
statistic_recalculate("cluster_received_propagated")
statistic_recalculate("cluster_send_direct_success")
statistic_recalculate("cluster_send_propagated_success")
statistic_recalculate("cluster_send_direct_failed")
statistic_recalculate("cluster_send_propagated_failed")
statistic_recalculate("cluster_in_direct")
statistic_recalculate("cluster_in_propagated")
statistic_recalculate("cluster_out_direct_success")
statistic_recalculate("cluster_out_propagated_success")
statistic_recalculate("cluster_out_direct_failed")
statistic_recalculate("cluster_out_propagated_failed")
content = content + "#Received: " + statistic_value_get("cluster_received_direct", value+"_value", "0") + "d/" + statistic_value_get("cluster_received_propagated", value+"_value", "0") + "p\n"
content = content + "#Send OK: " + statistic_value_get("cluster_send_direct_success", value+"_value", "0") + "d/" + statistic_value_get("cluster_send_propagated_success", value+"_value", "0") + "p\n"
content = content + "#Send Failed: " + statistic_value_get("cluster_send_direct_failed", value+"_value", "0") + "d/" + statistic_value_get("cluster_send_propagated_failed", value+"_value", "0") + "p\n"
content = content + "#In: " + statistic_value_get("cluster_in_direct", value+"_value", "0") + "d/" + statistic_value_get("cluster_in_propagated", value+"_value", "0") + "p\n"
content = content + "#Out OK: " + statistic_value_get("cluster_out_direct_success", value+"_value", "0") + "d/" + statistic_value_get("cluster_out_propagated_success", value+"_value", "0") + "p\n"
content = content + "#Out Failed: " + statistic_value_get("cluster_out_direct_failed", value+"_value", "0") + "d/" + statistic_value_get("cluster_out_propagated_failed", value+"_value", "0") + "p\n\n"
if "statistic_full" in source_rights:
content = content + "#Received - Direct:\n" + statistic_get("cluster_received_direct") + "\n\n"
content = content + "#Received - Propagated:\n" + statistic_get("cluster_received_propagated") + "\n\n"
content = content + "#Send - Direct - Success:\n" + statistic_get("cluster_send_direct_success") + "\n\n"
content = content + "#Send - Propagated - Success:\n" + statistic_get("cluster_send_propagated_success") + "\n\n"
content = content + "#Send - Direct - Failed:\n" + statistic_get("cluster_send_direct_failed") + "\n\n"
content = content + "#Send - Propagated - Failed:\n" + statistic_get("cluster_send_propagated_failed") + "\n\n"
content = content + "#In - Direct:\n" + statistic_get("cluster_in_direct") + "\n\n"
content = content + "#In - Propagated:\n" + statistic_get("cluster_in_propagated") + "\n\n"
content = content + "#Out - Direct - Success:\n" + statistic_get("cluster_out_direct_success") + "\n\n"
content = content + "#Out - Propagated - Success:\n" + statistic_get("cluster_out_propagated_success") + "\n\n"
content = content + "#Out - Direct - Failed:\n" + statistic_get("cluster_out_direct_failed") + "\n\n"
content = content + "#Out - Propagated - Failed:\n" + statistic_get("cluster_out_propagated_failed") + "\n\n"
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("router") and "statistic_router" in source_rights and ("statistic_min" in source_rights or "statistic_full" in source_rights):
content = content + replace(config_get(CONFIG, "interface_menu", "statistic_header_router", "", lng_key), source_hash, source_name, source_right, lng_key).replace(delimiter+"value"+delimiter, value)
if "statistic_min" in source_rights:
statistic_recalculate("router_in_direct")
statistic_recalculate("router_in_propagated")
statistic_recalculate("router_out_direct_success")
statistic_recalculate("router_out_propagated_success")
statistic_recalculate("router_out_direct_failed")
statistic_recalculate("router_out_propagated_failed")
content = content + "#In: " + statistic_value_get("router_in_direct", value+"_value", "0") + "d/" + statistic_value_get("router_in_propagated", value+"_value", "0") + "p\n"
content = content + "#Out OK: " + statistic_value_get("router_out_direct_success", value+"_value", "0") + "d/" + statistic_value_get("router_out_propagated_success", value+"_value", "0") + "p\n"
content = content + "#Out Failed: " + statistic_value_get("router_out_direct_failed", value+"_value", "0") + "d/" + statistic_value_get("router_out_propagated_failed", value+"_value", "0") + "p\n\n"
if "statistic_full" in source_rights:
content = content + "#In - Direct:\n" + statistic_get("router_in_direct") + "\n\n"
content = content + "#In - Propagated:\n" + statistic_get("router_in_propagated") + "\n\n"
content = content + "#Out - Direct - Success:\n" + statistic_get("router_out_direct_success") + "\n\n"
content = content + "#Out - Propagated - Success:\n" + statistic_get("router_out_propagated_success") + "\n\n"
content = content + "#Out - Direct - Failed:\n" + statistic_get("router_out_direct_failed") + "\n\n"
content = content + "#Out - Propagated - Failed:\n" + statistic_get("router_out_propagated_failed") + "\n\n"
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("local") and "statistic_local" in source_rights and ("statistic_min" in source_rights or "statistic_full" in source_rights):
content = content + replace(config_get(CONFIG, "interface_menu", "statistic_header_local", "", lng_key), source_hash, source_name, source_right, lng_key).replace(delimiter+"value"+delimiter, value)
if "statistic_min" in source_rights:
statistic_recalculate("local_received_direct")
statistic_recalculate("local_received_propagated")
statistic_recalculate("local_send_direct_success")
statistic_recalculate("local_send_propagated_success")
statistic_recalculate("local_send_direct_failed")
statistic_recalculate("local_send_propagated_failed")
content = content + "#Received: " + statistic_value_get("local_received_direct", value+"_value", "0") + "d/" + statistic_value_get("local_received_propagated", value+"_value", "0") + "p\n"
content = content + "#Send OK: " + statistic_value_get("local_send_direct_success", value+"_value", "0") + "d/" + statistic_value_get("local_send_propagated_success", value+"_value", "0") + "p\n"
content = content + "#Send Failed: " + statistic_value_get("local_send_direct_failed", value+"_value", "0") + "d/" + statistic_value_get("local_send_propagated_failed", value+"_value", "0") + "p\n\n"
if "statistic_full" in source_rights:
content = content + "#Received - Direct:\n" + statistic_get("local_received_direct") + "\n\n"
content = content + "#Received - Propagated:\n" + statistic_get("local_received_propagated") + "\n\n"
content = content + "#Send - Direct - Success:\n" + statistic_get("local_send_direct_success") + "\n\n"
content = content + "#Send - Propagated - Success:\n" + statistic_get("local_send_propagated_success") + "\n\n"
content = content + "#Send - Direct - Failed:\n" + statistic_get("local_send_direct_failed") + "\n\n"
content = content + "#Send - Propagated - Failed:\n" + statistic_get("local_send_propagated_failed") + "\n\n"
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("interface") and "statistic_interface" in source_rights and ("statistic_min" in source_rights or "statistic_full" in source_rights):
content = content + replace(config_get(CONFIG, "interface_menu", "statistic_header_interface", "", lng_key), source_hash, source_name, source_right, lng_key).replace(delimiter+"value"+delimiter, value)
if "statistic_min" in source_rights:
statistic_recalculate("interface_received_direct")
statistic_recalculate("interface_received_propagated")
statistic_recalculate("interface_send_direct_success")
statistic_recalculate("interface_send_propagated_success")
statistic_recalculate("interface_send_direct_failed")
statistic_recalculate("interface_send_propagated_failed")
content = content + "#Received: " + statistic_value_get("local_received_direct", value+"_value", "0") + "d/" + statistic_value_get("local_received_propagated", value+"_value", "0") + "p\n"
content = content + "#Send OK: " + statistic_value_get("interface_send_direct_success", value+"_value", "0") + "d/" + statistic_value_get("interface_send_propagated_success", value+"_value", "0") + "p\n"
content = content + "#Send Failed: " + statistic_value_get("interface_send_direct_failed", value+"_value", "0") + "d/" + statistic_value_get("interface_send_propagated_failed", value+"_value", "0") + "p\n\n"
if "statistic_full" in source_rights:
content = content + "#Received - Direct:\n" + statistic_get("interface_received_direct") + "\n\n"
content = content + "#Received - Propagated:\n" + statistic_get("interface_received_propagated") + "\n\n"
content = content + "#Send - Direct - Success:\n" + statistic_get("interface_send_direct_success") + "\n\n"
content = content + "#Send - Propagated - Success:\n" + statistic_get("interface_send_propagated_success") + "\n\n"
content = content + "#Send - Direct - Failed:\n" + statistic_get("interface_send_direct_failed") + "\n\n"
content = content + "#Send - Propagated - Failed:\n" + statistic_get("interface_send_propagated_failed") + "\n\n"
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("user") and "statistic_self" in source_rights and ("statistic_min" in source_rights or "statistic_full" in source_rights):
content = content + replace(config_get(CONFIG, "interface_menu", "statistic_header_self", "", lng_key), source_hash, source_name, source_right, lng_key).replace(delimiter+"value"+delimiter, value)
if "statistic_min" in source_rights or "statistic_full" in source_rights:
content = content + statistic_get(source_hash) + "\n\n"
if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("user") and "statistic_user" in source_rights and ("statistic_min" in source_rights or "statistic_full" in source_rights):
content = content + replace(config_get(CONFIG, "interface_menu", "statistic_header_user", "", lng_key), source_hash, source_name, source_right, lng_key).replace(delimiter+"value"+delimiter, value)
for section in STATISTIC.sections():
if section != "main" and not section.startswith("cluster") and not section.startswith("local") and not section.startswith("interface"):
if "statistic_min" in source_rights:
statistic_recalculate(section)
content = "<" + section + ">: " + statistic_value_get(section, value+"_value") + "\n"
if "statistic_full" in source_rights:
content = "<" + section + ">:\n" + statistic_get(section) + "\n\n"
else:
content = config_get(CONFIG, "interface_menu", "statistic_found_error", "", lng_key)
# "/status" command.
elif cmd == "status" and "status" in source_rights:
content = config_get(CONFIG, "interface_menu", "status_"+source_right, "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content = content.replace(delimiter+"enabled_local"+delimiter, DATA["main"]["enabled_local"])
content = content.replace(delimiter+"enabled_cluster"+delimiter, DATA["main"]["enabled_cluster"])
# "/delivery" command.
#elif cmd == "delivery" and "delivery" in source_rights:
# TODO
# "/enable_local" command.
elif cmd == "enable_local" and "enable_local" in source_rights:
if DATA["main"].getboolean("enabled_local"):
content = config_get(CONFIG, "interface_menu", "enable_local_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "enable_local_false", "", lng_key)
elif cmd.startswith("enable_local ") and "enable_local" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["enabled_local"] = "True"
content = config_get(CONFIG, "interface_menu", "enable_local_true", "", lng_key)
DATA["main"]["unsaved_local"] = "True"
else:
DATA["main"]["enabled_local"] = "False"
content = config_get(CONFIG, "interface_menu", "enable_local_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "enable_local_error", "", lng_key)
# "/enable_cluster" command.
elif cmd == "enable_cluster" and "enable_cluster" in source_rights:
if DATA["main"].getboolean("enabled_cluster"):
content = config_get(CONFIG, "interface_menu", "enable_cluster_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "enable_cluster_false", "", lng_key)
elif cmd.startswith("enable_cluster ") and "enable_cluster" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["enabled_cluster"] = "True"
content = config_get(CONFIG, "interface_menu", "enable_cluster_true", "", lng_key)
DATA["main"]["unsaved_cluster"] = "True"
else:
DATA["main"]["enabled_cluster"] = "False"
content = config_get(CONFIG, "interface_menu", "enable_cluster_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "enable_cluster_error", "", lng_key)
# "/auto_add_user" command.
elif cmd == "auto_add_user" and "auto_add_user" in source_rights:
if DATA["main"].getboolean("auto_add_user"):
content = config_get(CONFIG, "interface_menu", "auto_add_user_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "auto_add_user_false", "", lng_key)
elif cmd.startswith("auto_add_user ") and "auto_add_user" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["auto_add_user"] = "True"
content = config_get(CONFIG, "interface_menu", "auto_add_user_true", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["auto_add_user"] = "False"
content = config_get(CONFIG, "interface_menu", "auto_add_user_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "auto_add_user_error", "", lng_key)
# "/auto_add_user_type" command.
elif cmd == "auto_add_user_type" and "auto_add_user_type" in source_rights:
content = config_get(DATA, "main", "auto_add_user_type", "", lng_key)
elif cmd.startswith("auto_add_user_type ") and "auto_add_user_type" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
DATA["main"]["auto_add_user_type"] = value
content = config_get(CONFIG, "interface_menu", "auto_add_user_type", "", lng_key) + " " + value
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "auto_add_user_type_error", "", lng_key)
# "/auto_add_cluster" command.
elif cmd == "auto_add_cluster" and "auto_add_cluster" in source_rights:
if DATA["main"].getboolean("auto_add_cluster"):
content = config_get(CONFIG, "interface_menu", "auto_add_cluster_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "auto_add_cluster_false", "", lng_key)
elif cmd.startswith("auto_add_cluster ") and "auto_add_cluster" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["auto_add_cluster"] = "True"
content = config_get(CONFIG, "interface_menu", "auto_add_cluster_true", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["auto_add_cluster"] = "False"
content = config_get(CONFIG, "interface_menu", "auto_add_cluster_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "auto_add_cluster_error", "", lng_key)
# "/auto_add_router" command.
elif cmd == "auto_add_router" and "auto_add_router" in source_rights:
if DATA["main"].getboolean("auto_add_router"):
content = config_get(CONFIG, "interface_menu", "auto_add_router_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "auto_add_router_false", "", lng_key)
elif cmd.startswith("auto_add_router ") and "auto_add_router" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["auto_add_router"] = "True"
content = config_get(CONFIG, "interface_menu", "auto_add_router_true", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["auto_add_router"] = "False"
content = config_get(CONFIG, "interface_menu", "auto_add_router_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "auto_add_router_error", "", lng_key)
# "/invite_user" command.
elif cmd == "invite_user" and "invite_user" in source_rights:
if DATA["main"].getboolean("invite_user"):
content = config_get(CONFIG, "interface_menu", "invite_user_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "invite_user_false", "", lng_key)
elif cmd.startswith("invite_user ") and "invite_user" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["invite_user"] = "True"
content = config_get(CONFIG, "interface_menu", "invite_user_true", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["invite_user"] = "False"
content = config_get(CONFIG, "interface_menu", "invite_user_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "invite_user_error", "", lng_key)
# "/invite_user_type" command.
elif cmd == "invite_user_type" and "invite_user_type" in source_rights:
content = config_get(DATA, "main", "invite_user_type", "", lng_key)
elif cmd.startswith("invite_user_type ") and "invite_user_type" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
DATA["main"]["invite_user_type"] = value
content = config_get(CONFIG, "interface_menu", "invite_user_type", "", lng_key) + " " + value
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "invite_user_type_error", "", lng_key)
# "/allow_user" command.
elif cmd == "allow_user" and "allow_user" in source_rights:
if DATA["main"].getboolean("allow_user"):
content = config_get(CONFIG, "interface_menu", "allow_user_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "allow_user_false", "", lng_key)
elif cmd.startswith("allow_user ") and "allow_user" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["allow_user"] = "True"
content = config_get(CONFIG, "interface_menu", "allow_user_true", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["allow_user"] = "False"
content = config_get(CONFIG, "interface_menu", "allow_user_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "allow_user_error", "", lng_key)
# "/allow_user_type" command.
elif cmd == "allow_user_type" and "allow_user_type" in source_rights:
content = config_get(DATA, "main", "allow_user_type", "", lng_key)
elif cmd.startswith("allow_user_type ") and "allow_user_type" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
DATA["main"]["allow_user_type"] = value
content = config_get(CONFIG, "interface_menu", "allow_user_type", "", lng_key) + " " + value
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "allow_user_type_error", "", lng_key)
# "/deny_user" command.
elif cmd == "deny_user" and "deny_user" in source_rights:
if DATA["main"].getboolean("deny_user"):
content = config_get(CONFIG, "interface_menu", "deny_user_true", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "deny_user_false", "", lng_key)
elif cmd.startswith("deny_user ") and "deny_user" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
if val_to_bool(value):
DATA["main"]["deny_user"] = "True"
content = config_get(CONFIG, "interface_menu", "deny_user_true", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["deny_user"] = "False"
content = config_get(CONFIG, "interface_menu", "deny_user_false", "", lng_key)
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "deny_user_error", "", lng_key)
# "/deny_user_type" command.
elif cmd == "deny_user_type" and "deny_user_type" in source_rights:
content = config_get(DATA, "main", "deny_user_type", "", lng_key)
elif cmd.startswith("deny_user_type ") and "deny_user_type" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
DATA["main"]["deny_user_type"] = value
content = config_get(CONFIG, "interface_menu", "deny_user_type", "", lng_key) + " " + value
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "deny_user_type_error", "", lng_key)
# "/description" command.
elif cmd == "description" and "description" in source_rights:
content = config_get(DATA, "main", "description", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
elif cmd.startswith("description ") and "description_set" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
DATA["main"]["description"+lng_key] = value
content_group = config_get(CONFIG, "interface_messages", "description", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
if content_group != "":
for section in sections:
if "receive_description" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields_generate(lng_key, h=message.source_hash ,n=source_name, tpl="description"), None, "interface_send")
content = config_get(CONFIG, "interface_menu", "description", "", lng_key) + " " + value
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "description_error", "", lng_key)
# "/rules" command.
elif cmd == "rules" and "rules" in source_rights:
content = config_get(DATA, "main", "rules", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
elif cmd.startswith("rules ") and "rules_set" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
DATA["main"]["rules"+lng_key] = value
content_group = config_get(CONFIG, "interface_messages", "rules", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
if content_group != "":
for section in sections:
if "receive_rules" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields_generate(lng_key, h=message.source_hash ,n=source_name, tpl="rules"), None, "interface_send")
content = config_get(CONFIG, "interface_menu", "rules", "", lng_key) + " " + value
DATA["main"]["unsaved"] = "True"
except:
content = config_get(CONFIG, "interface_menu", "rules_error", "", lng_key)
# "/readme" command.
elif cmd == "readme" and "readme" in source_rights:
content = config_get(CONFIG, "interface_menu", "readme", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
# "/time" command.
elif cmd == "time" and "time" in source_rights:
content = config_get(CONFIG, "interface_menu", "time", "", lng_key)
content = time.strftime(content, time.localtime(time.time()))
content = replace(content, source_hash, source_name, source_right, lng_key)
# "/announce" command.
elif cmd == "announce" and "announce" in source_rights:
content = config_get(CONFIG, "interface_menu", "announce", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
LXMF_CONNECTION.announce_now()
if CONFIG["cluster"].getboolean("enabled"):
RNS_CONNECTION.announce_now()
# "/sync" command.
elif cmd == "sync" and "sync" in source_rights:
content = config_get(CONFIG, "interface_menu", "sync", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
LXMF_CONNECTION.sync_now()
# "/show run" command.
elif (cmd == "show run" or cmd == "sh run") and "show_run" in source_rights:
content = config_get(CONFIG, "interface_menu", "show_run_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
for (key, val) in DATA.items("main"):
content = content + key + " = " + val + "\n"
# "/show" command.
elif (cmd.startswith("show") or cmd.startswith("list") or cmd.startswith("sh")) and "show" in source_rights:
try:
cmd, key = cmd.split(" ", 1)
if DATA.has_section(key) and key != "main":
content = config_get(CONFIG, "interface_menu", "show_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content = content + "[" + key + "]\n"
for (section_key, section_val) in DATA.items(key):
content = content + section_key + " = " + section_val + "\n"
else:
content = config_get(CONFIG, "interface_menu", "user_type_error", "", lng_key) + " " + key
except:
content = config_get(CONFIG, "interface_menu", "show_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
for section in DATA.sections():
if section in sections or section.replace("block_", "") in sections:
content = content + "[" + section + "]\n"
for (key, val) in DATA.items(section):
content = content + key + " = " + val + "\n"
content = content + "\n"
# "/user" command.
elif cmd.startswith("add ") and "add" in source_rights:
try:
cmd, key, value, name = cmd.split(" ", 3)
if DATA.has_section(key) and key != "main":
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
for section in DATA.sections():
if section != "main":
for (key, val) in DATA.items(section):
if key == value:
DATA.remove_option(section, key)
DATA[key][value] = name
content = config_get(CONFIG, "interface_menu", "user_add", "", lng_key) + " " + value + " -> " + key
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "user_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "user_type_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/user" command.
elif (cmd.startswith("del ") or cmd.startswith("rm ") or cmd.startswith("delete ")) and "del" in source_rights:
try:
cmd, key, value = cmd.split(" ", 2)
if DATA.has_section(key) and key != "main":
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
if DATA.has_option(key, value):
DATA.remove_option(key, value)
content = config_get(CONFIG, "interface_menu", "user_del", "", lng_key) + " " + value + " -> " + key
DATA["main"]["unsaved"] = "True"
if CONFIG["statistic"].getboolean("enabled"):
statistic("del", value)
else:
content = config_get(CONFIG, "interface_menu", "user_error", "", lng_key) + " " + value + " -> " + key
else:
content = config_get(CONFIG, "interface_menu", "user_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "user_type_error", "", lng_key)
except:
try:
cmd, value = cmd.split(" ", 1)
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
for section in DATA.sections():
if section != "main":
for (key, val) in DATA.items(section):
if key == value:
DATA.remove_option(section, key)
if CONFIG["statistic"].getboolean("enabled"):
statistic("del", value)
content = "OK: Removed user '" + value + "' from all types"
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "user_format_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/user" command.
elif (cmd.startswith("move ") or cmd.startswith("mv ")) and "move" in source_rights:
try:
cmd, key, value = cmd.split(" ", 2)
if DATA.has_section(key) and key != "main":
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
for section in DATA.sections():
if section != "main":
for (key_old, val_old) in DATA.items(section):
if key_old == value:
DATA.remove_option(section, key_old)
DATA[key][value] = val_old
content = config_get(CONFIG, "interface_menu", "user_move", "", lng_key) + " " + value + " -> " + key
DATA["main"]["unsaved"] = "True"
if content == "":
content = config_get(CONFIG, "interface_menu", "user_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "user_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "user_type_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/user" command.
elif cmd.startswith("rename ") and "rename" in source_rights:
try:
cmd, key, value = cmd.split(" ", 2)
key = LXMF_CONNECTION.destination_correct(key)
if key != "":
executed = False
for section in sections:
if DATA.has_option(section, key):
content = config_get(CONFIG, "interface_menu", "user_rename", "", lng_key) + " " + DATA[section][key] + " -> " + value
DATA[section][key] = value
executed = True
if executed:
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "user_found_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "user_format_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/invite" command.
elif cmd.startswith("invite ") and "invite" in source_rights:
if DATA["main"].getboolean("invite_user"):
try:
cmd, value = cmd.split(" ", 1)
key = DATA["main"]["invite_user_type"]
if DATA.has_section(key) and key != "main":
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
user_name = ""
if CONFIG["main"].getboolean("auto_name_add"):
app_data = RNS.Identity.recall_app_data(bytes.fromhex(value))
if app_data != None:
user_name = app_data.decode('utf-8')
DATA[key][value] = user_name
content_user = config_get(CONFIG, "interface_messages", "invite_"+key, "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
content_user = content_user.replace(delimiter+"user_name"+delimiter, user_name)
if content_user != "":
LXMF_CONNECTION.send(value, content_user, "", fields_generate(lng_key, m=True, d=True, r=True, cmd=key, config=key), None, "interface_send")
content_group = config_get(CONFIG, "interface_messages", "member_invite", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"user_address"+delimiter, value)
content_group = content_group.replace(delimiter+"user_name"+delimiter, user_name)
if content_group != "":
fields = fields_generate(lng_key, h=bytes.fromhex(value) ,n=user_name, m=True, tpl="invite")
for section in sections:
if "receive_invite" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
if key != source_hash:
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "invite_ok", "", lng_key) + " <" + value + ">"
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "invite_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "invite_type_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/kick" command.
elif cmd.startswith("kick ") and "kick" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
executed = False
for section in sections:
for (key, val) in DATA.items(section):
if key == value:
user_section = section
user_name = val
executed = True
DATA.remove_option(section, key)
if executed:
if CONFIG["statistic"].getboolean("enabled"):
statistic("del", value)
content_user = config_get(CONFIG, "interface_messages", "kick_"+user_section, "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(value, content_user, "", fields_generate(lng_key), None, "interface_send")
content_group = config_get(CONFIG, "interface_messages", "member_kick", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"user_address"+delimiter, value)
content_group = content_group.replace(delimiter+"user_name"+delimiter, user_name)
if content_group != "":
fields = fields_generate(lng_key, h=bytes.fromhex(value) ,n=user_name, m=True, tpl="kick")
for section in sections:
if "receive_kick" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "kick_ok", "", lng_key)
content = content.replace(delimiter+"user_address"+delimiter, value)
content = content.replace(delimiter+"user_name"+delimiter, user_name)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "kick_found_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "kick_format_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/block" command.
elif (cmd.startswith("block ") or cmd.startswith("ban ")) and "block" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
executed = False
for section in sections:
for (key, val) in DATA.items(section):
if key == value:
user_section = section
user_name = val
executed = True
if not DATA.has_section("block_"+section):
DATA.add_section("block_"+section)
DATA["block_"+section][key] = val
DATA.remove_option(section, key)
if executed:
content_user = config_get(CONFIG, "interface_messages", "block_"+user_section, "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(value, content_user, "", fields_generate(lng_key), None, "interface_send")
content_group = config_get(CONFIG, "interface_messages", "member_block", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"user_address"+delimiter, value)
content_group = content_group.replace(delimiter+"user_name"+delimiter, user_name)
if content_group != "":
fields = fields_generate(lng_key, h=bytes.fromhex(value) ,n=user_name, m=True, tpl="block")
for section in sections:
if "receive_block" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "block_ok", "", lng_key)
content = content.replace(delimiter+"user_address"+delimiter, value)
content = content.replace(delimiter+"user_name"+delimiter, user_name)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "block_found_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "block_format_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/unblock" command.
elif (cmd.startswith("unblock ") or cmd.startswith("unban ")) and "unblock" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
executed = False
for section in DATA.sections():
if section.startswith("block"):
for (key, val) in DATA.items(section):
if key == value:
user_section = section.replace("block_", "")
user_name = val
executed = True
if not DATA.has_section(user_section):
DATA.add_section(user_section)
DATA[user_section][key] = val
DATA.remove_option(section, key)
if executed:
content_user = config_get(CONFIG, "interface_messages", "unblock_"+user_section, "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(value, content_user, "", fields_generate(lng_key, m=True, d=True, r=True, cmd=user_section, config=user_section), None, "interface_send")
content_group = config_get(CONFIG, "interface_messages", "member_unblock", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"user_address"+delimiter, value)
content_group = content_group.replace(delimiter+"user_name"+delimiter, user_name)
if content_group != "":
fields = fields_generate(lng_key, h=bytes.fromhex(value) ,n=user_name, m=True, tpl="unblock")
for section in sections:
if "receive_block" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "unblock_ok", "", lng_key)
content = content.replace(delimiter+"user_address"+delimiter, value)
content = content.replace(delimiter+"user_name"+delimiter, user_name)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "unblock_found_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "unblock_format_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/allow" command.
elif cmd.startswith("allow ") and "allow" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
user_section = DATA["main"]["allow_user_type"]
if DATA.has_section(user_section) and user_section != "main":
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
executed = False
section = "wait"
if DATA.has_section(section):
for (key, val) in DATA.items(section):
if key == value:
user_name = val
executed = True
DATA[user_section][key] = val
DATA.remove_option(section, key)
if executed:
content_user = config_get(CONFIG, "interface_messages", "allow_"+user_section, "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(value, content_user, "", fields_generate(lng_key, m=True, d=True, r=True, cmd=user_section, config=user_section), None, "interface_send")
content_group = config_get(CONFIG, "interface_messages", "member_allow", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"user_address"+delimiter, value)
content_group = content_group.replace(delimiter+"user_name"+delimiter, user_name)
if content_group != "":
fields = fields_generate(lng_key, h=bytes.fromhex(value) ,n=user_name, m=True, tpl="allow")
for section in sections:
if "receive_block" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "allow_ok", "", lng_key)
content = content.replace(delimiter+"user_address"+delimiter, value)
content = content.replace(delimiter+"user_name"+delimiter, user_name)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "allow_found_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "allow_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "allow_type_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/deny" command.
elif cmd.startswith("deny ") and "deny" in source_rights:
try:
cmd, value = cmd.split(" ", 1)
user_section = DATA["main"]["deny_user_type"]
if DATA.has_section(user_section) and user_section != "main":
value = LXMF_CONNECTION.destination_correct(value)
if value != "":
executed = False
for section in sections:
for (key, val) in DATA.items(section):
if key == value:
user_name = val
executed = True
DATA[user_section][key] = val
DATA.remove_option(section, key)
if executed:
content_user = config_get(CONFIG, "interface_messages", "deny_"+user_section, "", lng_key)
content_user = replace(content_user, source_hash, source_name, source_right, lng_key)
if content_user != "":
LXMF_CONNECTION.send(value, content_user, "", fields_generate(lng_key), None, "interface_send")
content_group = config_get(CONFIG, "interface_messages", "member_deny", "", lng_key)
content_group = replace(content_group, source_hash, source_name, source_right, lng_key)
content_group = content_group.replace(delimiter+"user_address"+delimiter, value)
content_group = content_group.replace(delimiter+"user_name"+delimiter, user_name)
if content_group != "":
fields = fields_generate(lng_key, h=bytes.fromhex(value) ,n=user_name, m=True, tpl="deny")
for section in sections:
if "receive_block" in config_get(CONFIG, "rights", section).split(","):
for (key, val) in DATA.items(section):
LXMF_CONNECTION.send(key, content_group, "", fields, None, "interface_send")
content = config_get(CONFIG, "interface_menu", "deny_ok", "", lng_key)
content = content.replace(delimiter+"user_address"+delimiter, value)
content = content.replace(delimiter+"user_name"+delimiter, user_name)
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
else:
DATA["main"]["unsaved"] = "True"
else:
content = config_get(CONFIG, "interface_menu", "deny_found_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "deny_format_error", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "deny_type_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
# "/load" command.
elif (cmd == "load" or cmd == "read") and "load" in source_rights:
if data_read(PATH + "/data.cfg"):
content = config_get(CONFIG, "interface_menu", "load_ok", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "load_error", "", lng_key)
# "/save" command.
elif (cmd == "save" or cmd == "wr") and "save" in source_rights:
DATA.remove_option("main", "unsaved")
if data_save(PATH + "/data.cfg"):
content = config_get(CONFIG, "interface_menu", "save_ok", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "save_error", "", lng_key)
DATA["main"]["unsaved"] = "True"
if CONFIG["statistic"].getboolean("enabled"):
statistic_save(PATH + "/statistic.cfg")
# "/reload" command.
elif cmd == "reload" and "reload" in source_rights:
content = config_get(CONFIG, "interface_menu", "reload_error", "", lng_key)
DATA.remove_option("main", "unsaved")
if data_save(PATH + "/data.cfg"):
if data_read(PATH + "/data.cfg"):
content = config_get(CONFIG, "interface_menu", "reload_ok", "", lng_key)
else:
DATA["main"]["unsaved"] = "True"
# "/reset" command.
elif cmd.startswith("reset statistic ") and "reset" in source_rights:
try:
cmd, key, value = cmd.split(" ", 2)
if value == "all":
for section in STATISTIC.sections():
statistic_reset(section)
if CONFIG["main"].getboolean("auto_save_statistic"):
statistic_save(PATH + "/statistic.cfg")
else:
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
content = config_get(CONFIG, "interface_menu", "reset_statistic_ok", "", lng_key)
elif value == "cluster":
for section in STATISTIC.sections():
if section.startswith("cluster"):
statistic_reset(section)
if CONFIG["main"].getboolean("auto_save_statistic"):
statistic_save(PATH + "/statistic.cfg")
else:
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
content = config_get(CONFIG, "interface_menu", "reset_statistic_ok", "", lng_key)
elif value == "local":
for section in STATISTIC.sections():
if section.startswith("local"):
statistic_reset(section)
if CONFIG["main"].getboolean("auto_save_statistic"):
statistic_save(PATH + "/statistic.cfg")
else:
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
content = config_get(CONFIG, "interface_menu", "reset_statistic_ok", "", lng_key)
elif value == "interface":
for section in STATISTIC.sections():
if section.startswith("interface"):
statistic_reset(section)
if CONFIG["main"].getboolean("auto_save_statistic"):
statistic_save(PATH + "/statistic.cfg")
else:
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
content = config_get(CONFIG, "interface_menu", "reset_statistic_ok", "", lng_key)
elif value == "user":
for section in STATISTIC.sections():
if not section.startswith("cluster") and not section.startswith("local") and not section.startswith("interface"):
statistic_reset(section)
if CONFIG["main"].getboolean("auto_save_statistic"):
statistic_save(PATH + "/statistic.cfg")
else:
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
content = config_get(CONFIG, "interface_menu", "reset_statistic_ok", "", lng_key)
else:
content = config_get(CONFIG, "interface_menu", "reset_statistic_error", "", lng_key)
except:
content = config_get(CONFIG, "interface_menu", "cmd_error", "", lng_key)
else:
# "/admins" command.
# "/moderators" command.
# "/users" command.
# "/guests" command.
executed = False
for section in sections:
if (cmd == section or cmd == section+"s") and section+"s" in source_rights:
count = 0
content = config_get(CONFIG, "interface_menu", section+"s_header", "", lng_key)
content = replace(content, source_hash, source_name, source_right, lng_key)
content_member = config_get(CONFIG, "interface_menu", section+"s_member", "", lng_key)
for (key, val) in DATA.items(section):
count += 1
content = content + replace(content_member, key, val, section, lng_key)
content = content.replace(delimiter+"count"+delimiter, str(count))
executed = True
break
# cmd_unknown
if not executed:
content = config_get(CONFIG, "interface_menu", "cmd_unknown", "", lng_key)
# unsaved
if DATA["main"].getboolean("unsaved") and "unsaved" in source_rights:
if CONFIG["main"].getboolean("auto_save_data"):
DATA.remove_option("main", "unsaved")
if data_save(PATH + "/data.cfg"):
content = content + "\n" + config_get(CONFIG, "interface_menu", "save_ok", "", lng_key)
else:
content = content + "\n" + config_get(CONFIG, "interface_menu", "save_error", "", lng_key)
DATA["main"]["unsaved"] = "True"
else:
content = content + "\n" + config_get(CONFIG, "interface_menu", "save_info", "", lng_key)
return content
#### Fields #####
def fields_remove(fields=None, key="fields_remove"):
search = config_getarray(CONFIG, "message", key)
delete = []
for field in fields:
if field in search:
delete.append(field)
for field in delete:
del fields[field]
return fields
#### Fields #####
def fields_generate(lng_key, fields=None, h=None, n=None, m=False, d=False, r=False, cmd=None, config=None, tpl=None):
if not CONFIG["main"].getboolean("fields_message"):
return fields
if not fields:
fields = {}
if CONFIG["lxmf"]["destination_type_conv"] != "":
fields[0xB3] = CONFIG["lxmf"].getint("destination_type_conv")
if h:
fields[0xAF] = {}
fields[0xAF]["h"] = h
if n:
fields[0xAF]["n"] = n
else:
fields[0xAF]["n"] = ""
if m or d or r or cmd or config:
fields[0xA3] = {}
if m:
fields[0xA3]["m"] = {}
for (key, val) in CONFIG.items("rights"):
if DATA.has_section(key):
fields[0xA3]["m"][key] = {}
for (section_key, section_val) in DATA.items(key):
try:
h = bytes.fromhex(LXMF_CONNECTION.destination_correct(section_key))
fields[0xA3]["m"][key][h] = section_val
except:
pass
if d:
fields[0xA3]["d"] = config_get(DATA, "main", "description", "", lng_key).replace(CONFIG["interface"]["delimiter_output"]+"n"+CONFIG["interface"]["delimiter_output"], "\n")
if r:
fields[0xA3]["r"] = config_get(DATA, "main", "rules", "", lng_key).replace(CONFIG["interface"]["delimiter_output"]+"n"+CONFIG["interface"]["delimiter_output"], "\n")
if cmd:
fields[0xA3]["cmd"] = []
if CONFIG.has_option("cmds", cmd):
cmds = config_get(CONFIG, "cmds", cmd).split(",")
for cmd in cmds:
fields[0xA3]["cmd"].append({"c": "/"+cmd})
if config:
fields[0xA3]["config"] = {}
if CONFIG.has_option("configs", config):
configs = config_get(CONFIG, "configs", config).split(",")
for config in configs:
if config != "":
key, value = config.split("=", 1)
fields[0xA3]["config"][key] = val_to_val(value)
if cmd or config:
if DATA.has_section("topics"):
fields[0xA3]["topics"] = {}
for (key, val) in DATA.items("topics"):
try:
fields[0xA3]["topics"][int(key)] = val
except:
pass
if tpl:
fields[0xB1] = tpl
return fields
#### Replace #####
def replace(text, source_hash, source_name, source_right, lng_key):
delimiter = CONFIG["interface"]["delimiter_output"]
text = text.replace(delimiter+"source_address"+delimiter, source_hash)
text = text.replace(delimiter+"source_name"+delimiter, source_name)
text = text.replace(delimiter+"source_right"+delimiter, source_right)
text = text.replace(delimiter+"name"+delimiter, config_get(CONFIG, "main", "name", "", lng_key))
text = text.replace(delimiter+"display_name"+delimiter, config_get(CONFIG, "lxmf", "display_name", "", lng_key))
text = text.replace(delimiter+"description"+delimiter, config_get(DATA, "main", "description", "", lng_key))
text = text.replace(delimiter+"rules"+delimiter, config_get(DATA, "main", "rules", "", lng_key))
text = text.replace(delimiter+"destination_address"+delimiter, LXMF_CONNECTION.destination_hash_str())
text = text.replace(delimiter+"propagation_node"+delimiter, config_get(CONFIG, "lxmf", "propagation_node", "", lng_key))
text = text.replace(delimiter+"cluster_name"+delimiter, config_get(CONFIG, "cluster", "display_name", "", lng_key).rsplit('/', 1)[-1])
text = text.replace(delimiter+"n"+delimiter, "\n")
if delimiter+"count_members"+delimiter in text:
count = 0
for (section, section_val) in CONFIG.items("rights"):
if DATA.has_section(section):
for (key, val) in DATA.items(section):
count += 1
text = text.replace(delimiter+"count_members"+delimiter, str(count))
if delimiter+"count_pin"+delimiter in text:
count = 0
if DATA.has_section("pin"):
for (key, val) in DATA.items("pin"):
count += 1
text = text.replace(delimiter+"count_pin"+delimiter, str(count))
return text
##############################################################################################################
# Config
#### Config - Get #####
def config_get(config, section, key, default="", lng_key=""):
if not config or section == "" or key == "": return default
if not config.has_section(section): return default
if config.has_option(section, key+lng_key):
return config[section][key+lng_key]
elif config.has_option(section, key):
return config[section][key]
return default
def config_getarray(config, section, key, default=[], lng_key=""):
if not config or section == "" or key == "": return default
if not config.has_section(section): return default
value = ""
if config.has_option(section, key+lng_key):
value = config[section][key+lng_key]
elif config.has_option(section, key):
value = config[section][key]
if value != "":
values_return = []
values = value.split(",")
for value in values:
values_return.append(val_to_val(value.strip()))
return values_return
return default
def config_getint(config, section, key, default=0, lng_key=""):
if not config or section == "" or key == "": return default
if not config.has_section(section): return default
if config.has_option(section, key+lng_key):
return config.getint(section, key+lng_key)
elif config.has_option(section, key):
return config.getint(section, key)
return default
def config_getboolean(config, section, key, default=False, lng_key=""):
if not config or section == "" or key == "": return default
if not config.has_section(section): return default
if config.has_option(section, key+lng_key):
return config[section].getboolean(key+lng_key)
elif config.has_option(section, key):
return config[section].getboolean(key)
return default
def config_getsection(config, section, default="", lng_key=""):
if not config or section == "": return default
if not config.has_section(section): return default
if config.has_section(section+lng_key):
return key+lng_key
elif config.has_section(section):
return key
return default
def config_getoption(config, section, key, default=False, lng_key=""):
if not config or section == "" or key == "": return default
if not config.has_section(section): return default
if config.has_option(section, key+lng_key):
return key+lng_key
elif config.has_option(section, key):
return key
return default
#### 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):
global CONFIG
if file is None:
return False
else:
CONFIG = configparser.ConfigParser(allow_no_value=True, inline_comment_prefixes="#")
CONFIG.sections()
if os.path.isfile(file):
try:
if file_override is None:
CONFIG.read(file, encoding='utf-8')
elif os.path.isfile(file_override):
CONFIG.read([file, file_override], encoding='utf-8')
else:
CONFIG.read(file, encoding='utf-8')
except Exception as e:
return False
else:
if not config_default(file=file, file_override=file_override):
return False
return True
#### Config - Save #####
def config_save(file=None):
global CONFIG
if file is None:
return False
else:
if os.path.isfile(file):
try:
with open(file,"w") as file:
CONFIG.write(file)
except Exception as e:
return False
else:
return False
return True
#### Config - Default #####
def config_default(file=None, file_override=None):
global CONFIG
if file is None:
return False
elif DEFAULT_CONFIG != "":
if file_override and DEFAULT_CONFIG_OVERRIDE != "":
if not os.path.isdir(os.path.dirname(file_override)):
try:
os.makedirs(os.path.dirname(file_override))
except Exception:
return False
if not os.path.exists(file_override):
try:
config_file = open(file_override, "w")
config_file.write(DEFAULT_CONFIG_OVERRIDE)
config_file.close()
except:
return False
if not os.path.isdir(os.path.dirname(file)):
try:
os.makedirs(os.path.dirname(file))
except Exception:
return False
try:
config_file = open(file, "w")
config_file.write(DEFAULT_CONFIG)
config_file.close()
if not config_read(file=file, file_override=file_override):
return False
except:
return False
else:
return False
if not CONFIG.has_section("main"): CONFIG.add_section("main")
CONFIG["main"]["default_config"] = "True"
return True
##############################################################################################################
# Data
#### Data - Read #####
def data_read(file=None):
global DATA
if file is None:
return False
else:
DATA = configparser.ConfigParser(allow_no_value=True, inline_comment_prefixes="#")
DATA.sections()
if os.path.isfile(file):
try:
DATA.read(file)
except Exception as e:
return False
else:
if not data_default(file=file):
return False
return True
#### Data - Save #####
def data_save(file=None):
global DATA
if file is None:
return False
else:
if os.path.isfile(file):
try:
with open(file,"w") as file:
DATA.write(file)
except Exception as e:
return False
else:
return False
return True
#### Data - Save #####
def data_save_periodic(initial=False):
data_timer = threading.Timer(CONFIG.getint("main", "periodic_save_data_interval")*60, data_save_periodic)
data_timer.daemon = True
data_timer.start()
if initial:
return
global DATA
if DATA.has_section("main"):
if DATA["main"].getboolean("unsaved"):
DATA.remove_option("main", "unsaved")
if not data_save(PATH + "/data.cfg"):
DATA["main"]["unsaved"] = "True"
#### Data - Default #####
def data_default(file=None):
global DATA
if file is None:
return False
elif DEFAULT_DATA != "":
if not os.path.isdir(os.path.dirname(file)):
try:
os.makedirs(os.path.dirname(file))
except Exception:
return False
try:
data_file = open(file, "w")
data_file.write(DEFAULT_DATA)
data_file.close()
if not data_read(file=file):
return False
except:
return False
else:
return False
return True
##############################################################################################################
# Statistic/Counter
#### Statistic #####
def statistic(cmd="add", section="global", key="", value=1):
global STATISTIC
changed = False
if cmd == "add":
statistic_add(section, value)
changed = True
elif cmd == "del":
statistic_del(section)
changed = True
elif cmd == "reset":
statistic_reset(section)
changed = True
elif cmd == "get":
return statistic_get(section)
elif cmd == "value_set":
statistic_value_set(section, key, value)
changed = True
elif cmd == "value_get":
return statistic_value_get(section, key)
elif cmd == "read":
return statistic_read(PATH + "/statistic.cfg")
elif cmd == "save":
return statistic_save(PATH + "/statistic.cfg")
if changed:
if CONFIG["main"].getboolean("auto_save_statistic"):
statistic_save(PATH + "/statistic.cfg")
else:
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
#### Statistic - Add #####
def statistic_add(section="global", value=1):
global STATISTIC
if not STATISTIC.has_section(section):
statistic_default(section)
statistic_recalculate(section)
date = datetime.date.today()
day = date.timetuple().tm_yday
month = date.timetuple().tm_mon
year = date.timetuple().tm_year
week = date.isocalendar()[1]
#day
if STATISTIC[section]["day_index"] == str(day):
STATISTIC[section]["day_value"] = str(STATISTIC.getint(section, "day_value")+value)
#week
if STATISTIC[section]["week_index"] == str(week):
STATISTIC[section]["week_value"] = str(STATISTIC.getint(section, "week_value")+value)
#month
if STATISTIC[section]["month_index"] == str(month):
STATISTIC[section]["month_value"] = str(STATISTIC.getint(section, "month_value")+value)
#year
if STATISTIC[section]["year_index"] == str(year):
STATISTIC[section]["year_value"] = str(STATISTIC.getint(section, "year_value")+value)
#all
STATISTIC[section]["all_value"] = str(STATISTIC.getint(section, "all_value")+value)
#max
if STATISTIC.getint(section, "day_value") > STATISTIC.getint(section, "max_value"):
STATISTIC[section]["max_value"] = STATISTIC[section]["day_value"]
STATISTIC[section]["max_index"] = time.strftime("%Y-%m-%d", time.localtime(time.time()))
return
#### Statistic - Recalculate #####
def statistic_recalculate(section="global"):
global STATISTIC
if not STATISTIC.has_section(section):
return
date = datetime.date.today()
day = date.timetuple().tm_yday
month = date.timetuple().tm_mon
year = date.timetuple().tm_year
week = date.isocalendar()[1]
#day
if STATISTIC[section]["day_index"] != str(day):
if STATISTIC[section]["day_index"] == str(day-1):
STATISTIC[section]["last_day_value"] = STATISTIC[section]["day_value"]
STATISTIC[section]["last_day_index"] = str(day-1)
else:
STATISTIC[section]["last_day_value"] = "0"
STATISTIC[section]["last_day_index"] = str(day-1)
STATISTIC[section]["day_value"] = "0"
STATISTIC[section]["day_index"] = str(day)
#week
if STATISTIC[section]["week_index"] != str(week):
if STATISTIC[section]["week_index"] == str(week-1):
STATISTIC[section]["last_week_value"] = STATISTIC[section]["week_value"]
STATISTIC[section]["last_week_index"] = str(week-1)
else:
STATISTIC[section]["last_week_value"] = "0"
STATISTIC[section]["last_week_index"] = str(week-1)
STATISTIC[section]["week_value"] = "0"
STATISTIC[section]["week_index"] = str(week)
#month
if STATISTIC[section]["month_index"] != str(month):
if STATISTIC[section]["month_index"] == str(month-1):
STATISTIC[section]["last_month_value"] = STATISTIC[section]["month_value"]
STATISTIC[section]["last_month_index"] = str(month-1)
else:
STATISTIC[section]["last_month_value"] = "0"
STATISTIC[section]["last_month_index"] = str(month-1)
STATISTIC[section]["month_value"] = "0"
STATISTIC[section]["month_index"] = str(month)
#year
if STATISTIC[section]["year_index"] != str(year):
if STATISTIC[section]["year_index"] == str(year-1):
STATISTIC[section]["last_year_value"] = STATISTIC[section]["year_value"]
STATISTIC[section]["last_year_index"] = str(year-1)
else:
STATISTIC[section]["last_year_value"] = "0"
STATISTIC[section]["last_year_index"] = str(year-1)
STATISTIC[section]["year_value"] = "0"
STATISTIC[section]["year_index"] = str(year)
return
#### Statistic - Del #####
def statistic_del(section="global"):
global STATISTIC
if STATISTIC.has_section(section):
STATISTIC.remove_section(section)
#### Statistic - Reset #####
def statistic_reset(section="global"):
statistic_del(section)
statistic_add(section, 0)
#### Statistic - Get #####
def statistic_get(section="global"):
global STATISTIC
text = ""
if STATISTIC.has_section(section):
statistic_recalculate(section)
for (key, val) in STATISTIC.items(section):
if key.endswith("_value"):
text = text + key.capitalize() + ": " + val + "\n"
text = text.replace("_value", "")
text = text.replace("_", " ")
text = text.strip()
return text
#### Statistic - Value set #####
def statistic_value_set(section, key, value):
global STATISTIC
if not STATISTIC.has_section(section):
statistic_default(section)
STATISTIC[section][key] = value
#### Statistic - Value get #####
def statistic_value_get(section, key, default=""):
global STATISTIC
if STATISTIC.has_section(section):
if STATISTIC.has_option(section, key):
return STATISTIC[section][key]
return default
#### Statistic - Read #####
def statistic_read(file=None):
global STATISTIC
if file is None:
return False
else:
STATISTIC = configparser.ConfigParser(allow_no_value=True, inline_comment_prefixes="#")
STATISTIC.sections()
if os.path.isfile(file):
try:
STATISTIC.read(file)
except Exception as e:
return False
return True
#### Statistic - Save #####
def statistic_save(file=None):
global STATISTIC
if file is None:
return False
else:
try:
with open(file,"w") as file:
if STATISTIC.has_section("main"):
STATISTIC.remove_section("main")
STATISTIC.write(file)
except Exception as e:
return False
return True
#### Statistic - Save #####
def statistic_save_periodic(initial=False):
statistic_timer = threading.Timer(CONFIG.getint("main", "periodic_save_statistic_interval")*60, statistic_save_periodic)
statistic_timer.daemon = True
statistic_timer.start()
if initial:
return
global STATISTIC
if STATISTIC.has_section("main"):
if STATISTIC["main"].getboolean("unsaved"):
STATISTIC.remove_section("main")
if not statistic_save(PATH + "/statistic.cfg"):
if not STATISTIC.has_section("main"):
STATISTIC.add_section("main")
STATISTIC["main"]["unsaved"] = "True"
#### Statistic - Default #####
def statistic_default(section="global"):
global STATISTIC
date = datetime.date.today()
day = date.timetuple().tm_yday
month = date.timetuple().tm_mon
year = date.timetuple().tm_year
week = date.isocalendar()[1]
STATISTIC.add_section(section)
STATISTIC[section]["day_value"] = "0"
STATISTIC[section]["day_index"] = str(day)
STATISTIC[section]["last_day_value"] = "0"
STATISTIC[section]["last_day_index"] = str(day-1)
STATISTIC[section]["week_value"] = "0"
STATISTIC[section]["week_index"] = str(week)
STATISTIC[section]["last_week_value"] = "0"
STATISTIC[section]["last_week_index"] = str(week-1)
STATISTIC[section]["month_value"] = "0"
STATISTIC[section]["month_index"] = str(month)
STATISTIC[section]["last_month_value"] = "0"
STATISTIC[section]["last_month_index"] = str(month-1)
STATISTIC[section]["year_value"] = "0"
STATISTIC[section]["year_index"] = str(year)
STATISTIC[section]["last_year_value"] = "0"
STATISTIC[section]["last_year_index"] = str(year-1)
STATISTIC[section]["all_value"] = "0"
STATISTIC[section]["max_value"] = "0"
STATISTIC[section]["max_index"] = time.strftime("%Y-%m-%d", time.localtime(time.time()))
##############################################################################################################
# Value convert
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":
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":
return False
elif val != "":
return fallback_true
else:
return fallback_false
def val_to_val(val):
if val.isdigit():
return int(val)
elif val.isnumeric():
return float(val)
elif val.lower() == "true":
return True
elif val.lower() == "false":
return False
elif val.startswith("0x") or val.startswith("0X"):
try:
val_int = int(val, 16)
return val_int
except:
pass
return val
##############################################################################################################
# Log
LOG_FORCE = -1
LOG_CRITICAL = 0
LOG_ERROR = 1
LOG_WARNING = 2
LOG_NOTICE = 3
LOG_INFO = 4
LOG_VERBOSE = 5
LOG_DEBUG = 6
LOG_EXTREME = 7
LOG_LEVEL = LOG_NOTICE
LOG_LEVEL_SERVICE = LOG_NOTICE
LOG_TIMEFMT = "%Y-%m-%d %H:%M:%S"
LOG_MAXSIZE = 5*1024*1024
LOG_PREFIX = ""
LOG_SUFFIX = ""
LOG_FILE = ""
def log(text, level=3, file=None):
if not LOG_LEVEL:
return
if LOG_LEVEL >= level:
name = "Unknown"
if (level == LOG_FORCE):
name = ""
if (level == LOG_CRITICAL):
name = "Critical"
if (level == LOG_ERROR):
name = "Error"
if (level == LOG_WARNING):
name = "Warning"
if (level == LOG_NOTICE):
name = "Notice"
if (level == LOG_INFO):
name = "Info"
if (level == LOG_VERBOSE):
name = "Verbose"
if (level == LOG_DEBUG):
name = "Debug"
if (level == LOG_EXTREME):
name = "Extra"
if not isinstance(text, str):
text = str(text)
text = "[" + time.strftime(LOG_TIMEFMT, time.localtime(time.time())) +"] [" + name + "] " + LOG_PREFIX + text + LOG_SUFFIX
if file == None and LOG_FILE != "":
file = LOG_FILE
if file == None:
print(text)
else:
try:
file_handle = open(file, "a")
file_handle.write(text + "\n")
file_handle.close()
if os.path.getsize(file) > LOG_MAXSIZE:
file_prev = file + ".1"
if os.path.isfile(file_prev):
os.unlink(file_prev)
os.rename(file, file_prev)
except:
return
##############################################################################################################
# System
#### Panic #####
def panic():
sys.exit(255)
#### Exit #####
def exit():
sys.exit(0)
##############################################################################################################
# Setup/Start
#### Setup #####
def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False):
global PATH
global PATH_RNS
global LOG_LEVEL
global LOG_FILE
global RNS_MAIN_CONNECTION
global LXMF_CONNECTION
global RNS_CONNECTION
if path is not None:
if path.endswith("/"):
path = path[:-1]
PATH = path
if path_rns is not None:
if path_rns.endswith("/"):
path_rns = path_rns[:-1]
PATH_RNS = path_rns
if loglevel is not None:
LOG_LEVEL = loglevel
rns_loglevel = loglevel
else:
rns_loglevel = None
if service:
LOG_LEVEL = LOG_LEVEL_SERVICE
if path_log is not None:
if path_log.endswith("/"):
path_log = path_log[:-1]
LOG_FILE = path_log
else:
LOG_FILE = PATH
LOG_FILE = LOG_FILE + "/" + NAME + ".log"
rns_loglevel = None
if not config_read(PATH + "/config.cfg", PATH + "/config.cfg.owr"):
print("Config - Error reading config file " + PATH + "/config.cfg")
panic()
if not data_read(PATH + "/data.cfg"):
print("Data - Error reading data file " + PATH + "/data.cfg")
panic()
if CONFIG["main"].getboolean("default_config"):
print("Exit!")
print("First start with the default config!")
print("You should probably edit the config file \"" + PATH + "/config.cfg\" to suit your needs and use-case!")
print("You should make all your changes at the user configuration file \"" + PATH + "/config.cfg.owr\" to override the default configuration file!")
print("Then restart this program again!")
exit()
if not CONFIG["main"].getboolean("enabled"):
print("Disabled in config file. Exit!")
exit()
if CONFIG["statistic"].getboolean("enabled"):
if not statistic_read(PATH + "/statistic.cfg"):
print("Statistic - Error reading statistic file " + PATH + "/statistic.cfg")
panic()
if CONFIG.has_section("cmds") and CONFIG.has_section("rights"):
for (key, val) in CONFIG.items("cmds"):
if val != "" and CONFIG.has_option("rights", key):
CONFIG["rights"][key] += ",interface,"+val
RNS_MAIN_CONNECTION = RNS.Reticulum(configdir=PATH_RNS, loglevel=rns_loglevel)
log("...............................................................................", LOG_INFO)
log(" Name: " + CONFIG["main"]["name"], LOG_INFO)
log("Program File: " + __file__, LOG_INFO)
log(" Config File: " + PATH + "/config", LOG_INFO)
log(" Data File: " + PATH + "/data.cfg", LOG_INFO)
log(" Version: " + VERSION, LOG_INFO)
log(" Copyright: " + COPYRIGHT, LOG_INFO)
log("...............................................................................", LOG_INFO)
log("LXMF - Connecting ...", LOG_DEBUG)
if CONFIG.has_option("lxmf", "propagation_node"):
config_propagation_node = CONFIG["lxmf"]["propagation_node"]
else:
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:
path = PATH
display_name = CONFIG["lxmf"]["display_name"]
announce_data = None
if CONFIG["lxmf"]["destination_type_conv"] != "":
try:
if CONFIG["main"].getboolean("fields_announce"):
announce_data = umsgpack.packb({"c": CONFIG["lxmf"]["display_name"].encode("utf-8"), "t": None, "f": {0xB3: CONFIG["lxmf"].getint("destination_type_conv")}})
else:
display_name += chr(CONFIG["lxmf"].getint("destination_type_conv"))
except:
pass
LXMF_CONNECTION = lxmf_connection(
storage_path=path,
identity_file="identity",
identity=None,
destination_name=CONFIG["lxmf"]["destination_name"],
destination_type=CONFIG["lxmf"]["destination_type"],
display_name=display_name,
announce_data = announce_data,
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
send_delay=CONFIG["lxmf"]["send_delay"],
desired_method=CONFIG["lxmf"]["desired_method"],
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"),
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
announce_periodic=CONFIG["lxmf"].getboolean("announce_periodic"),
announce_periodic_interval=CONFIG["lxmf"]["announce_periodic_interval"],
sync_startup=CONFIG["lxmf"].getboolean("sync_startup"),
sync_startup_delay=CONFIG["lxmf"]["sync_startup_delay"],
sync_limit=CONFIG["lxmf"]["sync_limit"],
sync_periodic=CONFIG["lxmf"].getboolean("sync_periodic"),
sync_periodic_interval=CONFIG["lxmf"]["sync_periodic_interval"])
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
LXMF_CONNECTION.register_config_set_callback(config_set)
if CONFIG["statistic"].getboolean("enabled"):
LXMF_CONNECTION.register_message_notification_success_callback(lxmf_message_notification_success_callback)
LXMF_CONNECTION.register_message_notification_failed_callback(lxmf_message_notification_failed_callback)
log("LXMF - Connected", LOG_DEBUG)
log("...............................................................................", LOG_FORCE)
log("LXMF - Address: " + RNS.prettyhexrep(LXMF_CONNECTION.destination_hash()), LOG_FORCE)
log("...............................................................................", LOG_FORCE)
if CONFIG["cluster"].getboolean("enabled") or CONFIG["router"].getboolean("enabled") or CONFIG["high_availability"].getboolean("enabled"):
announce_data = defaultdict(dict)
announce_data["h"] = LXMF_CONNECTION.destination_hash_str()
if CONFIG["high_availability"].getboolean("enabled"):
announce_data["ha"] = "1"
announce_data["ha_r"] = CONFIG["high_availability"]["role"]
else:
announce_data["ha"] = "0"
if CONFIG["cluster"].getboolean("enabled"):
announce_data["c"] = "1"
announce_data["c_n"] = CONFIG["cluster"]["display_name"].replace(" ", "")
else:
announce_data["c"] = "0"
if CONFIG["router"].getboolean("enabled"):
announce_data["r"] = "1"
announce_data["r_n"] = CONFIG["router"]["display_name"].replace(" ", "")
else:
announce_data["r"] = "0"
log("RNS - Connecting ...", LOG_DEBUG)
RNS_CONNECTION = rns_connection(
storage_path=path,
identity_file="identity",
identity=LXMF_CONNECTION.identity,
destination_name=CONFIG["cluster"]["name"],
destination_type=CONFIG["cluster"]["type"],
announce_startup=CONFIG["rns"].getboolean("announce_startup"),
announce_startup_delay=CONFIG["rns"]["announce_startup_delay"],
announce_periodic=CONFIG["rns"].getboolean("announce_periodic"),
announce_periodic_interval=CONFIG["rns"]["announce_periodic_interval"],
announce_data = json.dumps(announce_data, separators=(',', ':')),
announce_hidden=CONFIG["rns"].getboolean("announce_hidden")
)
RNS_CONNECTION.register_announce_callback(rns_announce_callback)
log("RNS - Connected", LOG_DEBUG)
if CONFIG["main"].getboolean("periodic_save_data"):
data_save_periodic(True)
if CONFIG["main"].getboolean("periodic_save_statistic"):
statistic_save_periodic(True)
while True:
time.sleep(1)
#### Start ####
def main():
try:
description = NAME + " - " + DESCRIPTION
parser = argparse.ArgumentParser(description=description)
parser.add_argument("-p", "--path", action="store", type=str, default=None, help="Path to alternative config directory")
parser.add_argument("-pr", "--path_rns", action="store", type=str, default=None, help="Path to alternative Reticulum config directory")
parser.add_argument("-pl", "--path_log", action="store", type=str, default=None, help="Path to alternative log directory")
parser.add_argument("-l", "--loglevel", action="store", type=int, default=LOG_LEVEL)
parser.add_argument("-s", "--service", action="store_true", default=False, help="Running as a service and should log to file")
parser.add_argument("--exampleconfig", action="store_true", default=False, help="Print verbose configuration example to stdout and exit")
parser.add_argument("--exampleconfigoverride", action="store_true", default=False, help="Print verbose configuration example to stdout and exit")
parser.add_argument("--exampledata", action="store_true", default=False, help="Print verbose configuration example to stdout and exit")
params = parser.parse_args()
if params.exampleconfig:
print("Config File: " + PATH + "/config.cfg")
print("Content:")
print(DEFAULT_CONFIG)
exit()
if params.exampleconfigoverride:
print("Config Override File: " + PATH + "/config.cfg.owr")
print("Content:")
print(DEFAULT_CONFIG_OVERRIDE)
exit()
if params.exampledata:
print("Data File: " + PATH + "/data.cfg")
print("Content:")
print(DEFAULT_DATA)
exit()
setup(path=params.path, path_rns=params.path_rns, path_log=params.path_log, loglevel=params.loglevel, service=params.service)
except KeyboardInterrupt:
print("Terminated by CTRL-C")
exit()
##############################################################################################################
# Files
#### Default configuration override file ####
DEFAULT_CONFIG_OVERRIDE = '''# This is the user configuration file to override the default configuration file.
# All settings made here have precedence.
# This file can be used to clearly summarize all settings that deviate from the default.
# This also has the advantage that all changed settings can be kept when updating the program.
#### Main program settings ####
[main]
# Default language.
lng = en # en/de
#### LXMF connection settings ####
[lxmf]
# The name will be visible to other peers
# on the network, and included in announces.
# It is also used in the group description/info.
display_name = Distribution Group
# Set propagation node automatically.
propagation_node_auto = True
# Try to deliver a message via the LXMF propagation network,
# if a direct delivery to the recipient is not possible.
try_propagation_on_fail = Yes
#### Cluster settings ####
[cluster]
# Enable/Disable this functionality.
enabled = True
# To use several completely separate clusters/groups,
# an individual name and type can be assigned here.
name = grp
type = cluster
# Slash-separated list with the names of this cluster.
# This feature can be used to build multi level group structures.
# All send messages that match the name (all levels) will be received.
# The last name is the main name of this group and is used as source for send messages.
# No spaces are allowed in the name.
display_name = County/Region/City
#### Router settings ####
[router]
# Enable/Disable router functionality.
enabled = True
# Comma-separated list with the names for which the messages are to be routed/repeated.
# The names and levels must match the used display_name of the cluster accordingly.
# No spaces are allowed in the name.
display_name = Country,Country/Region
#### High availability settings ####
[high_availability]
# Enable/Disable this functionality.
enabled = False
# Role of this node (master/slave)
role = master
# Peer address
peer =
#### Statistic/Counter settings ####
[statistic]
# Enable/Disable this functionality.
enabled = True
'''
#### Default configuration file ####
DEFAULT_CONFIG = '''# This is the default config file.
# You should probably edit it to suit your needs and use-case.
#### Main program settings ####
[main]
# Enable/Disable this functionality.
enabled = True
# Name of the program. Only for display in the log or program startup.
name = Distribution Group
# Default language.
# The following languages are available. Other languages can be defined below in the "interface" settings.
# You have to add the language key to the settings to be used. For example "-de".
# en/de
lng = en
# Auto save changes.
# If there are changes in the data or statistics, they can be saved directly in the files.
# Attention: This can lead to very high write cycles.
# If you want to prevent frequent writing, please set this to 'False' and use the peridodic save function.
auto_save_data = True
auto_save_statistic = False
# Periodic actions - Save changes periodically.
periodic_save_data = True
periodic_save_data_interval = 30 #Minutes
periodic_save_statistic = True
periodic_save_statistic_interval = 30 #Minutes
# Auto apply name from announces.
# As an alternative to defining the nickname manually, it can be used automatically from the announce.
auto_name_add = True
auto_name_def = True
auto_name_change = True
# Transport extended data in the announce and fields variable.
# This is needed for the integration of advanced client apps.
fields_announce = False
fields_message = False
#### LXMF connection settings ####
[lxmf]
# Destination name & type need to fits the LXMF protocoll
# to be compatibel with other LXMF programs.
destination_name = lxmf
destination_type = delivery
destination_type_conv = #4=Group, 6=Channel (Only for use with Communicator-Software.)
# The name will be visible to other peers
# on the network, and included in announces.
# It is also used in the group description/info.
display_name = Distribution Group
# Default send method.
desired_method = direct #direct/propagated
# Propagation node address/hash.
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,
# if a direct delivery to the recipient is not possible.
try_propagation_on_fail = Yes
# The peer is announced at startup
# to let other peers reach it immediately.
announce_startup = Yes
announce_startup_delay = 0 #Seconds
# The peer is announced periodically
# to let other peers reach it.
announce_periodic = Yes
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
# for LXMF/Reticulum processing.
send_delay = 0 #Seconds
# Sync LXMF messages at startup.
sync_startup = No
sync_startup_delay = 0 #Seconds
# Sync LXMF messages periodically.
sync_periodic = No
# The sync interval in minutes.
sync_periodic_interval = 360 #Minutes
# Automatic LXMF syncs will only
# download x messages at a time. You can change
# this number, or set the option to 0 to disable
# the limit, and download everything every time.
sync_limit = 0
# Allow only messages with valid signature.
signature_validated = No
signature_validated_new = No
signature_validated_known = No
#### RNS connection settings ####
[rns]
# Destination name & type need to fits the RNS protocoll
# to be compatibel with other RNS programs.
destination_name = grp
destination_type = cluster
# The peer is announced at startup
# to let other peers reach it immediately.
announce_startup = Yes
announce_startup_delay = 0 #Seconds
# The peer is announced periodically
# to let other peers reach it.
announce_periodic = Yes
announce_periodic_interval = 120 #Minutes
# The announce is hidden for client applications
# but is still used for the routing tables.
announce_hidden = No
#### Cluster settings ####
[cluster]
# Enable/Disable this functionality.
enabled = False
# To use several completely separate clusters/groups,
# an individual name and type can be assigned here.
name = grp
type = cluster
# Slash-separated list with the names of this cluster.
# This feature can be used to build multi level group structures.
# All send messages that match the name (all levels) will be received.
# The last name is the main name of this group and is used as source for send messages.
# No spaces are allowed in the name.
display_name = County/Region/City
# Define the delimiters for cluster input.
delimiter_input = @
#### Router settings ####
[router]
# Enable/Disable router functionality.
enabled = False
# Comma-separated list with the names for which the messages are to be routed/repeated.
# The names and levels must match the used display_name of the cluster accordingly.
# No spaces are allowed in the name.
display_name = Country,Country/Region
#### High availability settings ####
[high_availability]
# Enable/Disable this functionality.
enabled = False
# Role of this node (master/slave)
role = master
# Peer address
peer =
# Sync
sync_periodic_interval = 30 #Minutes
# Sync at startup
sync_startup = No
sync_startup_delay = 0 #Seconds
# Heartbeat
heartbeat_interval = 1 #Minutes
heartbeat_timeout = 15 #Minutes
#### Message settings ####
[message]
## Each message received (message and command) ##
# Deny message if the title/content/fields contains the following content.
# Comma-separated list with text or field keys.
# *=any
deny_title =
deny_content =
deny_fields =
# Text is added.
receive_title_prefix =
receive_prefix =
receive_suffix =
# Text is replaced.
receive_search =
receive_replace =
# Text is replaced by regular expression.
receive_regex_search =
receive_regex_replace =
# Length limitation.
receive_length_min = 0 #0=any length
receive_length_max = 0 #0=any length
## Each message send (message) ##
# Text is added.
send_title_prefix = #!source_name!!n!<!source_address!>!n!
send_prefix = !source_name!!n!<!source_address!>!n!
send_suffix =
# Text is replaced.
send_search =
send_replace =
# Text is replaced by regular expression.
send_regex_search =
send_regex_replace =
# Length limitation.
send_length_min = 0 #0=any length
send_length_max = 0 #0=any length
## Each cluster message received (message and command) ##
# Text is added.
cluster_receive_title_prefix = #@!cluster_source!->
cluster_receive_prefix = @!cluster_source!->
cluster_receive_suffix =
# Text is replaced.
cluster_receive_search =
cluster_receive_replace =
# Text is replaced by regular expression.
cluster_receive_regex_search =
cluster_receive_regex_replace =
# Length limitation.
cluster_receive_length_min = 0 #0=any length
cluster_receive_length_max = 0 #0=any length
## Each cluster message send (message) ##
# Text is added.
cluster_send_title_prefix = #@!cluster_destination!!n!!source_name!!n!<!source_address!>!n!
cluster_send_prefix = @!cluster_destination!!n!!source_name!!n!<!source_address!>!n!
cluster_send_suffix =
# Text is replaced.
cluster_send_search =
cluster_send_replace =
# Text is replaced by regular expression.
cluster_send_regex_search =
cluster_send_regex_replace =
# Length limitation.
cluster_send_length_min = 0 #0=any length
cluster_send_length_max = 0 #0=any length
## Each pinned message ##
pin_id = %%y%%m%%d-%%H%%M%%S
# Define which message timestamp should be used.
timestamp = client #client/server
# Use title/fields.
title = Yes
fields = Yes
# Comma-separated list with fields which will be removed.
fields_remove =
fields_remove_anonymous =
#### Statistic/Counter settings ####
[statistic]
# Enable/Disable this functionality.
enabled = True
# Create cluster statistics.
cluster = True
# Create router statistics.
router = True
# Create local/group statistics.
local = True
# Create interface statistics.
interface = True
# Create user statistics.
user = True
#### User rights assignment ####
# Define the individual rights for the different user types.
# Delimiter for different rights: ,
[rights]
admin = interface,receive_local,receive_cluster,receive_cluster_pin_add,receive_cluster_loop,receive_cluster_join,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,receive_deny,receive_description,receive_rules,receive_pin_add,receive_pin_remove,receive_name_def,receive_name_change,receive_auto_name_def,receive_auto_name_change,reply_signature,reply_cluster_enabled,reply_cluster_right,reply_interface_enabled,reply_interface_right,reply_local_enabled,reply_local_right,reply_block,reply_length_min,reply_length_max,send_local,send_cluster,help,update,update_all,join,leave,name,address,info,pin,pin_add,pin_remove,cluster_pin_add,description,rules,readme,time,version,groups,members,admins,moderators,users,guests,search,activitys,statistic,statistic_min,statistic_full,statistic_cluster,statistic_router,statistic_local,statistic_interface,statistic_self,statistic_user,status,delivery,enable_local,enable_cluster,auto_add_user,auto_add_user_type,auto_add_cluster,auto_add_router,invite_user,invite_user_type,allow_user,allow_user_type,deny_user,deny_user_type,description_set,rules_set,announce,sync,show_run,show,add,del,move,rename,invite,kick,block,unblock,allow,deny,load,save,reload,reset,unsaved
mod = interface,receive_local,receive_cluster,receive_cluster_pin_add,receive_cluster_loop,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,receive_deny,receive_description,receive_rules,receive_pin_add,reply_signature,reply_cluster_enabled,reply_cluster_right,reply_interface_enabled,reply_interface_right,reply_local_enabled,reply_local_right,reply_block,reply_length_min,reply_length_max,send_local,send_cluster,help,update,update_all,join,leave,name,address,info,pin,pin_add,pin_remove,cluster_pin_add,description,rules,readme,time,version,groups,members,admins,moderators,users,guests,search,activitys,statistic,statistic_min,statistic_cluster,statistic_router,statistic_local,statistic_self,delivery,show,add,del,move,rename,invite,kick,block,unblock,allow,deny
user = interface,receive_local,receive_cluster,receive_cluster_pin_add,receive_cluster_loop,receive_join,receive_leave,receive_invite,receive_kick,receive_block,receive_unblock,receive_allow,receive_description,receive_rules,receive_pin_add,reply_signature,reply_cluster_enabled,reply_cluster_right,reply_interface_enabled,reply_interface_right,reply_local_enabled,reply_local_right,reply_block,reply_length_min,reply_length_max,send_local,send_cluster,help,update,join,leave,name,address,info,pin,pin_add,pin_remove,cluster_pin_add,description,rules,readme,time,version,groups,members,admins,moderators,users,guests,search,activitys,statistic,statistic_min,statistic_cluster,statistic_router,statistic_local,statistic_self,delivery,invite
guest = interface,receive_local,receive_cluster,receive_cluster_loop,update,join,leave
wait = interface,update,join,leave
#### User cmd assignment ####
# Define the individual cmds for the different user types.
# Delimiter for different cmds: ,
[cmds]
admin = update,update_all,leave,invite,kick,block,unblock,allow,deny
mod = update,update_all,leave,invite,kick,block,unblock,allow,deny
user = leave,invite
guest = leave
wait = leave
#### User config assignment ####
# Define the individual configs for the different user types.
# Delimiter for different configs: ,
[configs]
admin = #file_tx_enabled=True,audio_tx_enabled=True
mod =
user =
guest =
wait =
#### User rights/cmds options ####
# The following rights/cmds can be assigned:
# anonymous = Hide source identity.
# interface = General function of the command interface.
# receive_local = Receive local (own group) messages.
# receive_cluster = Receive cluster (foreign group) messages.
# receive_cluster_pin_add = Receive cluster (foreign group) pinned messages.
# receive_cluster_send = Receive a copy of the message sent to another cluster.
# receive_cluster_loop = Receive message which is sent to a higher hierarchy in the cluster and includes the own cluster.
# receive_cluster_join = Receive an info message when a new cluster joins.
# receive_join = Receive an info message when a new user joins.
# receive_leave = Receive an info message when a user leaves.
# receive_invite = Receive an info message when a user is invited.
# receive_kick = Receive an info message when a user is kicked.
# receive_block = Receive an info message when a user is blocked.
# receive_unblock = Receive an info message when a user is unblocked.
# receive_allow = Receive an info message when a user has been allowed.
# receive_deny = Receive an info message when a user has been denied.
# receive_description = Receive an info message when the group description is changed.
# receive_pin_add = Receive an info message when a message is pinned.
# receive_pin_remove = Receive an info message when a pinned message is removed.
# receive_rules = Receive an info message when the group rules are changed.
# receive_name_def = Receive an info message when a user assigns a name.
# receive_name_change = Receive an info message when a user changes his name.
# receive_auto_name_def = Receive an info message when a user assigns a name.
# receive_auto_name_change = Receive an info message when a user changes his name.
# reply_signature = Receive an error message if the signature is invalid.
# reply_cluster_enabled = Receive an error message when the cluster is disabled.
# reply_cluster_right = Receive an error message when you do not have permission to send in the cluster.
# reply_interface_enabled = Receive an error message when the interface is disabled.
# reply_interface_right = Receive an error message if you do not have permission to use the interface.
# reply_local_enabled = Receive an error message when sending a local message is disabled.
# reply_local_right = Receive an error message when you do not have permission to send locally.
# reply_block = Receive an error message when you are blocked.
# reply_length_min = Receive an error message if the message length is too short.
# reply_length_max = Receive an error message if the message length is too long.
# send_local = Allows you to send loacally in your own group.
# send_cluster = Allows sending to another cluster/group.
# help = Use of the "/help" command allowed.
# update = Use of the "/update" command allowed.
# join = Use of the "/join" command allowed.
# leave = Use of the "/leave" command allowed.
# name = Use of the "/name" command allowed.
# address = Use of the "/address" command allowed.
# info = Use of the "/info" command allowed.
# pin = Use of the "/pin" command allowed.
# pin_add = Use of the "/pin" command allowed.
# pin_remove = Use of the "/pin" command allowed.
# cluster_pin_add = Use of the "/pin" command allowed.
# version = Use of the "/version" command allowed.
# groups = Use of the "/groups" command allowed.
# members = Use of the "/members" command allowed.
# admins = Use of the "/admins" command allowed.
# moderators = Use of the "/moderators" command allowed.
# users = Use of the "/users" command allowed.
# guests = Use of the "/guests" command allowed.
# search = Use of the "/search" command allowed.
# activitys = Use of the "/activitys" command allowed.
# statistic = Use of the "/statistic" command allowed.
# statistic_min = Minimal statistics output.
# statistic_full = Full/Maximal statistics output.
# statistic_cluster = Displays the cluster statistics on the statistics text.
# statistic_router = Displays the cluster statistics on the statistics text.
# statistic_local = Displays the local statistics on the statistics text.
# statistic_interface = Displays the interface statistics on the statistics text.
# statistic_self = Displays the own statistics on the statistics text.
# statistic_user = Displays the user statistics on the statistics text.
# status = Use of the "/status" command allowed.
# delivery = Use of the "/delivery" command allowed.
# enable_local = Use of the "/enable_local" command allowed.
# enable_cluster = Use of the "/enable_cluster" command allowed.
# auto_add_user = Use of the "/auto_add_user" command allowed.
# auto_add_user_type = Use of the "/auto_add_user_type" command allowed.
# auto_add_cluster = Use of the "/auto_add_cluster" command allowed.
# auto_add_router = Use of the "/auto_add_router" command allowed.
# invite_user = Use of the "/invite_user" command allowed.
# invite_user_type = Use of the "/invite_user_type" command allowed.
# allow_user = Use of the "/allow_user" command allowed.
# allow_user_type = Use of the "/allow_user_type" command allowed.
# deny_user = Use of the "/deny_user" command allowed.
# deny_user_type = Use of the "/deny_user_type" command allowed.
# description = Use of the "/description" command allowed.
# description_set = Use of the "/description" command allowed.
# rules = Use of the "/rules" command allowed.
# rules_set = Use of the "/rules" command allowed.
# readme = Use of the "/readme" command allowed.
# time = Use of the "/time" command allowed.
# announce = Use of the "/announce" command allowed.
# sync = Use of the "/sync" command allowed.
# show_run = Use of the "/show_run" command allowed.
# show = Use of the "/show" command allowed.
# add = Use of the "/add" command allowed.
# del = Use of the "/del" command allowed.
# move = Use of the "/move" command allowed.
# rename = Use of the "/rename" command allowed.
# invite = Use of the "/invite" command allowed.
# kick = Use of the "/kick" command allowed.
# block = Use of the "/block" command allowed.
# unblock = Use of the "/unblock" command allowed.
# allow = Use of the "/allow" command allowed.
# deny = Use of the "/deny" command allowed.
# load = Use of the "/load" command allowed.
# save = Use of the "/save" command allowed.
# reload = Use of the "/reload" command allowed.
# reset = Use of the "/reset" command allowed.
# unsaved = Displays the status of the data file when using any action/command.
#### Interface settings - General ####
[interface]
# Enable/Disable the whole interface/commands.
enabled = True
# Define the delimiters for command input/output.
delimiter_input = /
delimiter_output = !
#### Interface settings - Messages ####
# Define messages for user or automatic actions.
# These messages are sent automatically when a corresponding action is triggered.
# If a message is to be deactivated simply comment it out.
[interface_messages]
# Auto user add. (Single message to the user.)
auto_error = ERROR: Joining the group is not possible.
auto_error-de = FEHLER: Beitritt zur Gruppe ist nicht möglich.
auto_add_admin = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.!n!!n!Your current nickname is "!source_name!". Please change your nickname with the command /name
auto_add_admin-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.!n!!n!Dein aktueller Nickname ist "!source_name!" Bitte ändern Sie ihren Nickname mit dem Befehl /name
auto_add_mod = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.!n!!n!Your current nickname is "!source_name!". Please change your nickname with the command /name
auto_add_mod-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.!n!!n!Dein aktueller Nickname ist "!source_name!" Bitte ändern Sie ihren Nickname mit dem Befehl /name
auto_add_user = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.!n!!n!Your current nickname is "!source_name!". Please change your nickname with the command /name
auto_add_user-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.!n!!n!Dein aktueller Nickname ist "!source_name!" Bitte ändern Sie ihren Nickname mit dem Befehl /name
auto_add_guest = Welcome to the group "!display_name!"!!n!!n!!description!!n!!n!You can only receive messages.!n!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.!n!!n!To leave the group use the following command: /leave
auto_add_guest-de = Willkommen in der Gruppe "!display_name!"!!n!!n!!description!!n!!n!Sie können nur Nachrichten empfangen.!n!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.!n!!n!Um die Gruppe zu verlassen verwenden Sie folgenden Befehl: /leave
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.
# Manual/Admin user add. (Single message to the user.)
add_admin =
add_admin-de =
add_mod =
add_mod-de =
add_user =
add_user-de =
add_guest =
add_guest-de =
add_wait =
add_wait-de =
# Invite user. (Single message to the user.)
invite_admin = You have been invited by !source_name! <!source_address!> to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Your current nickname is "!user_name!". Please change your nickname with the command /name
invite_admin-de = Sie wurden von !source_name! <!source_address!> in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Dein aktueller Nickname ist "!user_name!" Bitte ändern Sie ihren Nickname mit dem Befehl /name
invite_mod = You have been invited by !source_name! <!source_address!> to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Your current nickname is "!user_name!". Please change your nickname with the command /name
invite_mod-de = Sie wurden von !source_name! <!source_address!> in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Dein aktueller Nickname ist "!user_name!" Bitte ändern Sie ihren Nickname mit dem Befehl /name
invite_user = You have been invited by !source_name! <!source_address!> to the group "!display_name!"!!n!!n!!description!!n!!n!The messages sent here are distributed to all group members.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Your current nickname is "!user_name!". Please change your nickname with the command /name
invite_user-de = Sie wurden von !source_name! <!source_address!> in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Die hier gesendeten Nachrichten werden an alle Gruppenmitglieder verteilt.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Dein aktueller Nickname ist "!user_name!" Bitte ändern Sie ihren Nickname mit dem Befehl /name
invite_guest = You have been invited by !source_name! <!source_address!> to the group "!display_name!"!!n!!n!!description!!n!!n!You can only receive messages.!n!!n!To leave the group use the following command: /leave
invite_guest-de = Sie wurden von !source_name! <!source_address!> in die Gruppe "!display_name!" eingeladen!!n!!n!!description!!n!!n!Sie können nur Nachrichten empfangen.!n!!n!Um die Gruppe zu verlassen verwenden Sie folgenden Befehl: /leave
invite_wait = You have been invited by !source_name! <!source_address!> 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 von !source_name! <!source_address!> in die Gruppe "!display_name!" eingeladen!!n!!n!Der Beitritt muss ihnen noch erlaubt werden. Sie werden darüber automatisch benachrichtigt.
# Kick user. (Single message to the user.)
kick_admin = You have been kicked out of the group!
kick_admin-de = Sie wurden aus der Gruppe geworfen!
kick_mod = You have been kicked out of the group!
kick_mod-de = Sie wurden aus der Gruppe geworfen!
kick_user = You have been kicked out of the group!
kick_user-de = Sie wurden aus der Gruppe geworfen!
kick_guest = You have been kicked out of the group!
kick_guest-de = Sie wurden aus der Gruppe geworfen!
kick_wait = You have been kicked out of the group!
kick_wait-de = Sie wurden aus der Gruppe geworfen!
# Block user. (Single message to the user.)
block_admin =
block_admin-de =
block_mod =
block_mod-de =
block_user =
block_user-de =
block_guest =
block_guest-de =
block_wait =
block_wait-de =
# Unblock user. (Single message to the user.)
unblock_admin =
unblock_admin-de =
unblock_mod =
unblock_mod-de =
unblock_user =
unblock_user-de =
unblock_guest =
unblock_guest-de =
unblock_wait =
unblock_wait-de =
# Allow user. (Single message to the user.)
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.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Please assign a nickname with the command /name
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.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Bitte vergeben Sie einen Nickname mit dem Befehl /name
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.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Please assign a nickname with the command /name
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.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Bitte vergeben Sie einen Nickname mit dem Befehl /name
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.!n!!n!For help enter /?!n!!n!To read the group rules use the command /rules!n!!n!Please assign a nickname with the command /name
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.!n!!n!Für Hilfe geben Sie /? ein.!n!!n!Um die Gruppenregeln zu lesen verwenden Sie den Befehl /rules!n!!n!Bitte vergeben Sie einen Nickname mit dem Befehl /name
allow_guest = You have been allowed to join the group "!display_name!"!!n!!n!!description!!n!!n!You can only receive messages.!n!!n!To leave the group use the following command: /leave
allow_guest-de = Sie wurden erlaubt der Gruppe "!display_name!" beizutreten!!n!!n!!description!!n!!n!Sie können nur Nachrichten empfangen.!n!!n!Um die Gruppe zu verlassen verwenden Sie folgenden Befehl: /leave
allow_wait =
allow_wait-de =
# Deny user. (Single message to the user.)
deny_admin = You have been denied to join the group "!display_name!"!
deny_admin-de = Ihnen wurde der Beitritt in die Gruppe "!display_name!" abgelehnt!
deny_mod = You have been denied to join the group "!display_name!"!
deny_mod-de = Ihnen wurde der Beitritt in die Gruppe "!display_name!" abgelehnt!
deny_user = You have been denied to join the group "!display_name!"!
deny_user-de = Ihnen wurde der Beitritt in die Gruppe "!display_name!" abgelehnt!
deny_guest = You have been denied to join the group "!display_name!"!
deny_guest-de = Ihnen wurde der Beitritt in die Gruppe "!display_name!" abgelehnt!
deny_wait =
deny_wait-de =
# General user/member messages. (Group message to all group members.)
member_join = !source_name! <!source_address!> joins the group.
member_join-de = !source_name! <!source_address!> tritt der Gruppe bei.
member_leave = !source_name! <!source_address!> leave the group.
member_leave-de = !source_name! <!source_address!> verlässt die Gruppe.
member_invite = !user_name! <!user_address!> was invited to the group by !source_name! <!source_address!>
member_invite-de = !user_name! <!user_address!> wurde in die Gruppe eingeladen von !source_name! <!source_address!>
member_kick = !user_name! <!user_address!> was kicked out of the group by !source_name! <!source_address!>
member_kick-de = !user_name! <!user_address!> wurde aus der Gruppe geworfen von !source_name! <!source_address!>
member_block = !user_name! <!user_address!> was blocked by !source_name! <!source_address!>
member_block-de = !user_name! <!user_address!> wurde geblockt von !source_name! <!source_address!>
member_unblock = !user_name! <!user_address!> was unblocked by !source_name! <!source_address!>
member_unblock-de = !user_name! <!user_address!> wurde entsperrt von !source_name! <!source_address!>
member_allow = !user_name! <!user_address!> was allowed by !source_name! <!source_address!>
member_allow-de = !user_name! <!user_address!> wurde erlaubt von !source_name! <!source_address!>
member_deny = !user_name! <!user_address!> was denied by !source_name! <!source_address!>
member_deny-de = !user_name! <!user_address!> wurde abgelehnt von !source_name! <!source_address!>
member_name_def = <!source_address!> defined the name:
member_name_def-de = <!source_address!> hat den Namen definiert:
member_name_change = <!source_address!> changed the name:
member_name_change-de = <!source_address!> hat den Namen geändert:
description = !source_name! <!source_address!> has changed the group description:!n!!n!!description!
description-de = !source_name! <!source_address!> hat die Gruppenbeschreibung geändert:!n!!n!!description!
rules = !source_name! <!source_address!> has changed the group rules:!n!!n!!rules!
rules-de = !source_name! <!source_address!> hat die Gruppenregeln geändert:!n!!n!!rules!
cluster_join = New cluster/group connected: !source_name! <!source_address!>
cluster_join-de = Neue Cluster/Gruppe verbunden: !source_name! <!source_address!>
pin_add = New pinned message:!n!#!key!!n!!value!
pin_add-de = Neue angeheftete Nachricht:!n!#!key!!n!!value!
pin_remove = Removed pinned message:!n!#!key!!n!!value!
pin_remove-de = Angeheftete Nachricht entfernt:!n!#!key!!n!!value!
cluster_pin_add = New pinned message:!n!#!key!!n!!value!
cluster_pin_add-de = Neue angeheftete Nachricht:!n!#!key!!n!!value!
# Reply messages. (Single message to the user.)
reply_signature = Info: Signature invalid!
reply_signature-de = Info: Signatur ungültig!
reply_cluster_enabled = Info: Cluster disabled!
reply_cluster_enabled-de = Info: Cluster deaktiviert!
reply_cluster_right = Info: No authorization for cluster messages!
reply_cluster_right-de = Keine Berechtigung für Clusternachrichten!
reply_interface_enabled = Info: Commands disabled!
reply_interface_enabled-de = Info: Befehle deaktiviert!
reply_interface_right = Info: No authorization for commands!
reply_interface_right-de = Info: Keine Berechtigung für Befehle!
reply_local_enabled = Info: Group deactivated!
reply_local_enabled-de = Info: Gruppe deaktiviert!
reply_local_right = Info: No authorization to send messages!
reply_local_right-de = Info: Keine Berechtigung zum senden von Nachrichten!
reply_block = Info: You are blocked!
reply_block-de = Info: Sie sind geblockt!
reply_length_min = Info: Minimum message length not reached!
reply_length_min-de = Info: Minimale Nachrichtenlänge unterschritten!
reply_length_max = Info: Maximum message length exceeded!
reply_length_max-de = Info: Maximale Nachrichtenlänge überschritten!
#### Interface settings - Menu/command ####
# Define menu/command texts.
# These texts are used within the menu the user has to start.
[interface_menu]
# "/help" command.
help_admin = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Nickname: !source_name!!n!User right/type: !source_right!!n!!n!!interface_help!!n!Commands:!n!!interface_help_command!
help_admin-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Nickname: !source_name!!n!Benutzer Recht/Typ: !source_right!!n!!n!!interface_help!!n!Befehle:!n!!interface_help_command!
help_mod = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Nickname: !source_name!!n!User right/type: !source_right!!n!!n!!interface_help!!n!Commands:!n!!interface_help_command!
help_mod-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Nickname: !source_name!!n!Benutzer Recht/Typ: !source_right!!n!!n!!interface_help!!n!Befehle:!n!!interface_help_command!
help_user = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Nickname: !source_name!!n!User right/type: !source_right!!n!!n!!interface_help!!n!Commands:!n!!interface_help_command!
help_user-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Nickname: !source_name!!n!Benutzer Recht/Typ: !source_right!!n!!n!!interface_help!!n!Befehle:!n!!interface_help_command!
help_guest =
help_guest-de =
# "/update" command.
update_ok = OK: Data updated.
update_ok-de = OK: Daten aktualisiert.
update_error = ERROR: Updating data.
update_error-de = FEHLER: Daten aktualisieren.
# "/update_all" command.
update_all_ok = OK: Data updated.
update_all_ok-de = OK: Daten aktualisiert.
update_all_error = ERROR: Updating data.
update_all_error-de = FEHLER: Daten aktualisieren.
# "/join" command.
join_error = ERROR: While joining group.
join_error-de = FEHLER: Beim Beitritt in die Gruppe.
# "/leave" command.
leave_ok = OK: You leaved the group.
leave_ok-de = OK: Sie haben die Gruppe verlassen.
leave_error = ERROR: While leaving group.
leave_error-de = FEHLER: Beim Verlassen der Gruppe.
# "/name" command.
name = Current nickname: !source_name!
name-de = Aktueller Nickname: !source_name!
name_ok = OK: Changed name:
name_ok-de = OK: Name geändert:
name_error = ERROR: Changing name:
name_error-de = FEHLER: Name ändern:
# "/address" command.
address_admin = Group address:!n!<!destination_address!>!n!!n!Propagation node:!n!<!propagation_node!>
address_admin-de = Gruppenadresse:!n!<!destination_address!>!n!!n!Propagation Node:!n!<!propagation_node!>
address_mod = Group address:!n!<!destination_address!>!n!!n!Propagation node:!n!<!propagation_node!>
address_mod-de = Gruppenadresse:!n!<!destination_address!>!n!!n!Propagation Node:!n!<!propagation_node!>
address_user = Group address:!n!<!destination_address!>!n!!n!Propagation node:!n!<!propagation_node!>
address_user-de = Gruppenadresse:!n!<!destination_address!>!n!!n!Propagation Node:!n!<!propagation_node!>
address_guest = Group address:!n!<!destination_address!>!n!!n!Propagation node:!n!<!propagation_node!>
address_guest-de = Gruppenadresse:!n!<!destination_address!>!n!!n!Propagation Node:!n!<!propagation_node!>
# "/info" command.
info_admin = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.
info_admin-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.
info_mod = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.
info_mod-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.
info_user = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.
info_user-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.
info_guest = Group:!n!!display_name!!n!!n!Description:!n!!description!!n!!n!Number of members: !count_members!!n!Pinned messages: !count_pin!!n!Use the command /pin to display them.
info_guest-de = Gruppe:!n!!display_name!!n!!n!Beschreibung:!n!!description!!n!!n!Anzahl Mitglieder: !count_members!!n!Angepinnte Nachrichten: !count_pin!!n!Verwenden Sie den Befehl /pin um sie anzuzeigen.
# "/pin" command.
pin_header = Pinned messages (!count!):!n!!n!
pin_header-de = Angeheftete Nachrichten (!count!):!n!!n!
pin = !source_name!!n!<!source_address!>!n!!value!
pin-de = !source_name!!n!<!source_address!>!n!!value!
pin_add_ok = OK: Message pinned
pin_add_ok-de = OK: Nachricht angeheftet
pin_remove_ok = OK: Message removed
pin_remove_ok-de = OK: Nachricht entfernt
pin_found_error = ERROR: Message ID not found
pin_found_error-de = FEHLER: Nachrichten ID nicht gefunden
cluster_pin = !value!
cluster_pin-de = !value!
# "/version" command.
version_header = Version info:!n!!n!
version_header-de = Versionsinformationen:!n!!n!
# "/groups" command.
groups_header = Groups/Cluster (!count!):!n!!n!
groups_header-de = Gruppen/Cluster (!count!):!n!!n!
groups_member = !source_name!!n!
groups_member-de = !source_name!!n!
groups_search_header = Found groups/cluster (!count!):!n!!n!
groups_search_header-de = Gefundene Gruppen/Cluster (!count!):!n!!n!
groups_search_member = !source_name!!n!
groups_search_member-de = !source_name!!n!
groups_search_found_error = ERROR: Group/Cluster not found
groups_search_found_error-de = FEHLER: Gruppe/Cluster nicht gefunden
# "/members" command.
members_header = Group members (!count!):!n!!n!
members_header-de = Gruppenmitglieder (!count!):!n!!n!
members_member = !source_name!!n!<!source_address!>!n!!n!
members_member-de = !source_name!!n!<!source_address!>!n!!n!
# "/admins" command.
admins_header = Group admins (!count!):!n!!n!
admins_header-de = Gruppenadmins (!count!):!n!!n!
admins_member = !source_name!!n!<!source_address!>!n!!n!
admins_member-de = !source_name!!n!<!source_address!>!n!!n!
# "/moderators" command.
moderators_header = Group moderators (!count!):!n!!n!
moderators_header-de = Gruppenmoderatoren (!count!):!n!!n!
moderators_member = !source_name!!n!<!source_address!>!n!!n!
moderators_member-de = !source_name!!n!<!source_address!>!n!!n!
# "/users" command.
users_header = Group users (!count!):!n!!n!
users_header-de = Gruppenbenutzer (!count!):!n!!n!
users_member = !source_name!!n!<!source_address!>!n!!n!
users_member-de = !source_name!!n!<!source_address!>!n!!n!
# "/guests" command.
guests_header = Group guests (!count!):!n!!n!
guests_header-de = Gruppengäste (!count!):!n!!n!
guests_member = !source_name!!n!<!source_address!>!n!!n!
guests_member-de = !source_name!!n!<!source_address!>!n!!n!
# "/search" command.
search_header = Found members (!count!):!n!!n!
search_header-de = Gefundene Mitglieder (!count!):!n!!n!
search_member = !source_name!!n!<!source_address!>!n!!activity_receive! / !activity_send!!n!!n!
search_member-de = !source_name!!n!<!source_address!>!n!!activity_receive! / !activity_send!!n!!n!
search_found_error = ERROR: Nickname or address not found
search_found_error-de = FEHLER: Benutzername oder Adresse nicht gefunden
# "/activitys" command.
activitys_header = User activitys (!count!):!n!(receive / send)!n!!n!
activitys_header-de = Benutzeraktivitäten (!count!):!n!(empf. / gesendet)!n!!n!
activitys_member = !source_name!!n!<!source_address!>!n!!activity_receive! / !activity_send!!n!!n!
activitys_member-de = !source_name!!n!<!source_address!>!n!!activity_receive! / !activity_send!!n!!n!
# "/statistic" command.
statistic_header_cluster = -- Cluster statistics - !value! --!n!
statistic_header_cluster-de = -- Cluster-Statistik - !value! --!n!
statistic_header_router = -- Router statistics - !value! --!n!
statistic_header_router-de = -- Router-Statistik - !value! --!n!
statistic_header_local = -- Group statistics - !value! --!n!
statistic_header_local-de = -- Gruppen-Statistik - !value! --!n!
statistic_header_interface = -- Interface statistics - !value! --!n!
statistic_header_interface-de = -- Interface-Statistik - !value! --!n!
statistic_header_user = -- User statistics - !value! --!n!
statistic_header_user-de = -- Benutzer-Statistik - !value! --!n!
statistic_header_self = -- Own statistics --!n!
statistic_header_self-de = -- Eigene-Statistik --!n!
statistic_found_error = ERROR: Statistic type not found
statistic_found_error-de = FEHLER: Statistik typ nicht vorhanden
# "/status" command.
status_admin = Status:!n!!n!Local message routing:!enabled_local!!n!Cluster message routing:!enabled_cluster!!n!
status_admin-de = Status:!n!!n!Lokales Nachrichten Routing:!enabled_local!!n!Cluster Nachrichten Routing:!enabled_cluster!!n!
status_mod = Status:!n!!n!Local message routing:!enabled_local!!n!Cluster message routing:!enabled_cluster!!n!
status_mod-de = Status:!n!!n!Lokales Nachrichten Routing:!enabled_local!!n!Cluster Nachrichten Routing:!enabled_cluster!!n!
status_user = Status:!n!!n!Local message routing:!enabled_local!!n!Cluster message routing:!enabled_cluster!!n!
status_user-de = Status:!n!!n!Lokales Nachrichten Routing:!enabled_local!!n!Cluster Nachrichten Routing:!enabled_cluster!!n!
status_guest = Status:!n!!n!Local message routing:!enabled_local!!n!Cluster message routing:!enabled_cluster!!n!
status_guest-de = Status:!n!!n!Lokales Nachrichten Routing:!enabled_local!!n!Cluster Nachrichten Routing:!enabled_cluster!!n!
# "/delivery" command.
# todo
# "/enable_local" command.
enable_local_true = OK: Local message routing enabled.
enable_local_true-de = OK: Lokales Nachrichten Routing aktiviert.
enable_local_false = OK: Local message routing disabled.
enable_local_false-de = OK: Lokales Nachrichten Routing deaktiviert.
enable_local_error = ERROR: Local message routing change.
enable_local_error-de = FEHLER: Änderung Lokales Nachrichten Routing.
# "/enable_cluster" command.
enable_cluster_true = OK: Cluster message routing enabled.
enable_cluster_true-de = OK: Cluster Nachrichten Routing aktiviert.
enable_cluster_false = OK: Cluster message routing disabled.
enable_cluster_false-de = OK: Cluster Nachrichten Routing deaktiviert.
enable_cluster_error = ERROR: Cluster message routing change.
enable_cluster_error-de = FEHLER: Änderung Cluster Nachrichten Routing.
# "/auto_add_user" command.
auto_add_user_true = OK: Auto add user enabled.
auto_add_user_true-de = OK: Benutzer automatisch hinzufügen aktiviert.
auto_add_user_false = OK: Auto add user disabled.
auto_add_user_false-de = OK: Benutzer automatisch hinzufügen deaktiviert.
auto_add_user_error = ERROR: Auto add user change.
auto_add_user_error-de = FEHLER: Änderung Benutzer automatisch hinzufügen.
# "/auto_add_user_type" command.
auto_add_user_type = OK: User type changed to:
auto_add_user_type-de = OK: Benutzertyp geändert in:
auto_add_user_type_error = ERROR: User type change.
auto_add_user_type_error-de = FEHLER: Änderung des Benutzertyps.
# "/auto_add_cluster" command.
auto_add_cluster_true = OK: Auto add cluster enabled.
auto_add_cluster_true-de = OK: Cluster/Gruppen automatisch hinzufügen aktiviert.
auto_add_cluster_false = OK: Auto add cluster disabled.
auto_add_cluster_false-de = OK: Cluster/Gruppen automatisch hinzufügen deaktiviert.
auto_add_cluster_error = ERROR: Auto add cluster change.
auto_add_cluster_error-de = FEHLER: Änderung Cluster/Gruppen automatisch hinzufügen.
# "/auto_add_router" command.
auto_add_router_true = OK: Auto add router enabled.
auto_add_router_true-de = OK: Router automatisch hinzufügen aktiviert.
auto_add_router_false = OK: Auto add router disabled.
auto_add_router_false-de = OK: Router automatisch hinzufügen deaktiviert.
auto_add_router_error = ERROR: Auto add router change.
auto_add_router_error-de = FEHLER: Änderung Router automatisch hinzufügen.
# "/invite_user" command.
invite_user_true = OK: Invite user enabled.
invite_user_true-de = OK: Benutzer einladen aktiviert.
invite_user_false = OK: Invite user disabled.
invite_user_false-de = OK: Benutzer einladen deaktiviert.
invite_user_error = ERROR: Invite user change.
invite_user_error-de = FEHLER: Änderung Benutzer einladen.
# "/invite_user_type" command.
invite_user_type = OK: User type changed to:
invite_user_type-de = OK: Benutzertyp geändert in:
invite_user_type_error = ERROR: User type change.
invite_user_type_error-de = FEHLER: Änderung des Benutzertyps.
# "/allow_user" command.
allow_user_true = OK: Allow user enabled.
allow_user_true-de = OK: Benutzer erlauben aktiviert.
allow_user_false = OK: Allow user disabled.
allow_user_false-de = OK: Benutzer erlauben deaktiviert.
allow_user_error = ERROR: Allow user change.
allow_user_error-de = FEHLER: Änderung Benutzer erlauben.
# "/allow_user_type" command.
allow_user_type = OK: User type changed to:
allow_user_type-de = OK: Benutzertyp geändert in:
allow_user_type_error = ERROR: User type change.
allow_user_type_error-de = FEHLER: Änderung des Benutzertyps.
# "/deny_user" command.
deny_user_true = OK: Deny user enabled.
deny_user_true-de = OK: Benutzer ablehnen aktiviert.
deny_user_false = OK: Deny user disabled.
deny_user_false-de = OK: Benutzer ablehnen deaktiviert.
deny_user_error = ERROR: Deny user change.
deny_user_error-de = FEHLER: Änderung Benutzer ablehnen.
# "/deny_user_type" command.
deny_user_type = OK: User type changed to:
deny_user_type-de = OK: Benutzertyp geändert in:
deny_user_type_error = ERROR: User type change.
deny_user_type_error-de = FEHLER: Änderung des Benutzertyps.
# "/description" command.
description = OK: Description changed to:
description-de = OK: Beschreibung geändert in:
description_error = ERROR: Description change.
description_error-de = FEHLER: Beschreibung ändern.
# "/rules" command.
rules = OK: Rules changed to:
rules-de = OK: Regeln geändert in:
rules_error = ERROR: Rules change.
rules_error-de = FEHLER: Regeln ändern.
# "/readme" command.
readme =
readme-de =
# "/time" command.
time = Current server time: %%Y-%%m-%%d %%H:%%M:%%S
time-de = Aktuelle Server-Zeit: %%Y-%%m-%%d %%H:%%M:%%S
# "/announce" command.
announce = Announce send.
announce-de = Announce gesendet.
# "/sync" command.
sync = Synchronize messages with propagation node <!propagation_node!>.
sync-de = Synchronisiere Nachrichten mit Propagation node <!propagation_node!>.
# "/show run" command.
show_run_header = Current settings/data:!n!!n!
show_run_header-de = Aktuelle Konfiguration/Daten:!n!!n!
# "/show" command.
show_header =
show_header-de =
# "/user" command.
user_add = OK: Added user -> group:
user_add-de = OK: Benutzer -> Gruppe hinzugefügt:
user_del = OK: Removed user -> group:
user_del-de = OK: Entfernter Benutzer -> Gruppe:
user_move = OK: Moved user -> group:
user_move-de = OK: Benutzer -> Gruppe verschoben:
user_rename = OK: Renamed user:
user_rename-de = OK: Umbenannter Benutzer:
user_error = ERROR: Unknown user -> group:
user_error-de = FEHLER: Unbekannter Benutzer -> Gruppe:
user_found_error = ERROR: User not found
user_found_error-de = FEHLER: Benutzer nicht gefunden
user_format_error = ERROR: Wrong user format
user_format_error-de = FEHLER: Falsches Benutzerformat
user_type_error = ERROR: Unknown user type
user_type_error-de = FEHLER: Unbekannter Benutzertyp
# "/invite" command.
invite_ok = OK: Invited user:
invite_ok-de = OK: Benutzer eingeladen:
invite_error = ERROR: Inviting user:
invite_error-de = FEHLER: Benutzer einladen:
invite_format_error = ERROR: Wrong user format
invite_format_error-de = FEHLER: Falsches Benutzerformat
invite_type_error = ERROR: Unknown user type
invite_type_error-de = FEHLER: Unbekannter Benutzertyp
# "/kick" command.
kick_ok = OK: User kicked out: !user_name! <!user_address!>
kick_ok-de = OK: Benutzer rausgeworfen: !user_name! <!user_address!>
kick_found_error = ERROR: User address not found
kick_found_error-de = FEHLER: Benutzer Adresse nicht gefunden
kick_format_error = ERROR: Wrong user format
kick_format_error-de = FEHLER: Falsches Benutzerformat
# "/block" command.
block_ok = OK: User blocked: !user_name! <!user_address!>
block_ok-de = OK: Benutzer blockiert: !user_name! <!user_address!>
block_found_error = ERROR: User address not found
block_found_error-de = FEHLER: Benutzer Adresse nicht gefunden
block_format_error = ERROR: Wrong user format
block_format_error-de = FEHLER: Falsches Benutzerformat
# "/unblock" command.
unblock_ok = OK: User unblocked: !user_name! <!user_address!>
unblock_ok-de = OK: Blockerierung des Benutzers aufgehoben: !user_name! <!user_address!>
unblock_found_error = ERROR: User address not found
unblock_found_error-de = FEHLER: Benutzer Adresse nicht gefunden
unblock_format_error = ERROR: Wrong user format
unblock_format_error-de = FEHLER: Falsches Benutzerformat
# "/allow" command.
allow_ok = OK: User allowed: !user_name! <!user_address!>
allow_ok-de = OK: Benutzer erlaubt: !user_name! <!user_address!>
allow_found_error = ERROR: User address not found
allow_found_error-de = FEHLER: Benutzer Adresse nicht gefunden
allow_format_error = ERROR: Wrong user format
allow_format_error-de = FEHLER: Falsches Benutzerformat
allow_type_error = ERROR: Unknown user type
allow_type_error-de = FEHLER: Unbekannter Benutzertyp
# "/deny" command.
deny_ok = OK: User denied: !user_name! <!user_address!>
deny_ok-de = OK: Benutzer abgelehnt: !user_name! <!user_address!>
deny_found_error = ERROR: User address not found
deny_found_error-de = FEHLER: Benutzer Adresse nicht gefunden
deny_format_error = ERROR: Wrong user format
deny_format_error-de = FEHLER: Falsches Benutzerformat
deny_type_error = ERROR: Unknown user type
deny_type_error-de = FEHLER: Unbekannter Benutzertyp
# "/load" command.
load_ok = OK: Loading configuration/data.
load_ok-de = OK: Konfiguration/Daten werden geladen.
load_error = ERROR: Loading configuration/data.
load_error-de = FEHLER: Konfiguration/Daten werden geladen.
# "/save" command.
save_ok = OK: Saved configuration/data.
save_ok-de = OK: Konfiguration/Daten gespeichert.
save_error = ERROR: Saving configuration/data.
save_error-de = FEHLER: Speichern der Konfiguration/Daten.
save_info = INFO: Unsaved changes! Please run the command '/save' to save these changes permanently!
save_info-de = INFO: Nicht gespeicherte Änderungen! Bitte führen Sie den Befehl '/save' aus, um diese Änderungen dauerhaft zu speichern!
# "/reload" command.
reload_ok = OK: Reloaded configuration/data.
reload_ok-de = OK: Konfiguration/Daten neu geladen.
reload_error = ERROR: Reload configuration/data.
reload_error-de = FEHLER: Neu laden der Konfiguration/Daten.
# "/reset" command.
reset_statistic_ok = OK: Reset statistic.
reset_statistic_ok-de = OK: Statistik zurückgesetzt.
reset_statistic_error = ERROR: Reset statistic.
reset_statistic_error-de = FEHLER: Statistik zurücksetzen.
# Cluster messages.
cluster_found_error = ERROR: Cluster name not found
cluster_found_error-de = FEHLER: Clustername nicht gefunden
cluster_format_error = ERROR: Wrong cluster format
cluster_format_error-de = FEHLER: Falsches Clusterformat
# General messages.
cmd_error = ERROR: Processing command.
cmd_error-de = FEHLER: Verarbeitung des Befehls.
cmd_unknown = ERROR: Unknown command. Type /? for help.
cmd_unknown-de = FEHLER: Unbekannter Befehl. Geben Sie /? für Hilfe ein.
#### Interface settings - Help ####
# Define help texts.
# These texts are used within the help-menu.
# Only the commands defined in the user rights are displayed.
# If a message is to be deactivated simply comment it out.
[interface_help]
send_local = To send a message simply enter any text.!n!!n!
send_local-de = Um eine Nachricht zu senden, geben Sie einfach einen beliebigen Text ein.!n!!n!
send_cluster = To send a message to another group enter the destination group with the following command followed by the message: @destination message.!n!!n!
send_cluster-de = Um eine Nachricht an eine andere Gruppe zu senden, geben Sie die Zielgruppe mit dem folgenden Befehl gefolgt von der Nachricht ein: @Zielname Nachricht.!n!!n!
interface = If the sent message is displayed as delivered and no error message is received, everything has worked fine.!n!!n!
interface-de = Wenn die gesendete Nachricht als zugestellt angezeigt wird und keine Fehlermeldung eingeht, hat alles fehlerfrei funktioniert.!n!!n!
# Define help texts.
# These texts are used within the help-menu.
# Only the commands defined in the user rights are displayed.
# If a message is to be deactivated simply comment it out.
[interface_help_command]
help = /help or /? = Shows this help!n!
help-de = /help oder /? = Zeigt diese Hilfe an!n!
leave = /leave or /part = Leave group!n!
leave-de = /leave oder /part = Gruppe verlassen!n!
name = /name = Show current nickname!n!/nick = Show current nickname!n!/name <your nickname> = Change/Define nickname!n!/nick <your nickname> = Change/Define nickname!n!
name-de = /name = Aktueller Nickname anzeigen!n!/nick = Aktueller Nickname anzeigen!n!/name <dein Nichname> = Ändern/Definieren des Nickname!n!/nick <dein Nichname> = Ändern/Definieren des Nickname!n!
address = /address = Dislay address info!n!
address-de = /address = Adressinfos anzeigen!n!
info = /info = Show group info!n!
info-de = /info = Gruppeninfos anzeigen!n!
pin = /pin = Show pinned messages!n!
pin-de = /pin = Angeheftete Nachrichten anzeigen!n!
pin_add = /pin <message> = Pin a message!n!
pin_add-de = /pin <Nachricht> = Nachricht anheften!n!
pin_remove = /unpin <#id> = Removes a pinned message!n!
pin_remove-de = /unpin <#id> = Angeheftete Nachricht entfernen!n!
version = /version = Show version info!n!
version-de = /version = Versionsinformationen anzeigen!n!
groups = /groups or /cluster = Show all groups/clusters!n!/groups <name> = Searches for a group/cluster by name!n!
groups-de = /groups oder /cluster = Alle Gruppen/Cluster anzeigen!n!/groups <name> = Sucht nach einer Gruppe/Cluster nach Namen!n!
members = /members or /names or /who = Show all group members!n!
members-de = /members oder /names oder /who = Alle Gruppenmitglieder anzeigen!n!
admins = /admins = Show group admins!n!
admins-de = /admins = Gruppenadmins anzeigen!n!
moderators = /moderators or /mods = Show group moderators!n!
moderators-de = /moderators oder /mods = Gruppenmoderatoren anzeigen!n!
users = /users = Show group users!n!
users-de = /users = Gruppenbenutzer anzeigen!n!
guests = /guests = Show group guests!n!
guests-de = /guests = Gruppengäste anzeigen!n!
search = /search <nickname/user_address> = Searches for a user by nickname or address!n!/whois <nickname/user_address> = Searches for a user by nickname or address!n!
search-de = /search <nickname/user_address> = Sucht einen Benutzer anhand des Nicknamens oder Adresse!n!/whois <nickname/user_address> = Sucht einen Benutzer anhand des Nicknamens oder Adresse!n!
activitys = /activitys = Show user activitys!n!
activitys-de = /activitys = Benutzeraktivitäten anzeigen!n!
statistic = /statistic or /stat = Show group statistic!n!/statistic <day/week/month/year/all> or /stat <day/week/month/year/all> = Show group statistic!n!
statistic-de = /statistic oder /stat = Gruppenstatistik anzeigen!n!/statistic <day/week/month/year/all> oder /stat <day/week/month/year/all> = Gruppenstatistik anzeigen!n!
status = /status = Show status!n!
status-de = /status = Status anzeigen!n!
delivery = /delivery or /message = Show delivery status of last message!n!
delivery-de = /delivery oder /message = Lieferstatus der letzten Nachricht anzeigen!n!
enable_local = /enable_local <true/false> = Local message routing!n!
enable_local-de = /enable_local <true/false> Lokales Nachrichten Routing!n!
enable_cluster = /enable_cluster <true/false> = Cluster message routing!n!
enable_cluster-de = /enable_cluster <true/false> Cluster Nachrichten Routing!n!
auto_add_user = /auto_add_user <true/false> = Add unknown user functionality!n!
auto_add_user-de = /auto_add_user <true/false> = Unbekannten Benutzer hinzufügen Funktionalität!n!
auto_add_user_type = /auto_add_user_type <admin/mod/user/guest>!n!
auto_add_user_type-de = /auto_add_user_type <admin/mod/user/guest>!n!
auto_add_cluster = /auto_add_cluster <true/false> = Add unknown cluster functionality!n!
auto_add_cluster-de = /auto_add_cluster <true/false> = Unbekannten Cluster/Gruppen hinzufügen Funktionalität!n!
auto_add_router = /auto_add_router <true/false> = Add unknown router functionality!n!
auto_add_router-de = /auto_add_router <true/false> = Unbekannten Router hinzufügen Funktionalität!n!
invite_user = /invite_user <true/false> = Invite functionality!n!
invite_user-de = /invite_user <true/false> = Einladung Funktionalität!n!
invite_user_type = /invite_user_type <admin/mod/user/guest>!n!
invite_user_type-de = /invite_user_type <admin/mod/user/guest>!n!
allow_user = /allow_user <true/false> = Allow user functionality!n!
allow_user-de = /allow_user <true/false> = Benutzer erlauben Funktionalität!n!
allow_user_type = /allow_user_type <admin/mod/user/guest>!n!
allow_user_type-de = /allow_user_type <admin/mod/user/guest>!n!
deny_user = /deny_user <true/false> = Deny user functionality!n!
deny_user-de = /deny_user <true/false> = Benutzer ablehnen Funktionalität!n!
deny_user_type = /deny_user_type <admin/mod/user/guest>!n!
deny_user_type-de = /deny_user_type <admin/mod/user/guest>!n!
description = /description = Show current description!n!
description-de = /description = Aktuelle Beschreibung anzeigen!n!
description_set = /description <description> = Change description!n!
description_set-de = /description <description>!n! = Beschreibung ändern
rules = /rules = Show current rules!n!
rules-de = /rules = Aktuelle Regeln anzeigen!n!
rules_set = /rules <description> = Change rules!n!
rules_set-de = /rules <description>!n! = Regeln ändern
readme = /readme = Show readme!n!
readme-de = /readme = Liesmich anzeigen!n!
time = /time = Show date/time!n!
time-de = /time = Datum/Uhrzeit anzeigen!n!
announce = /announce = Send announce!n!
announce-de = /announce = Announce senden!n!
sync = /sync = Synchronize messages with propagation node!n!
sync-de = /sync = Nachrichten mit Propagation Node synchronisieren!n!
show_run = /show run = Show current configuration!n!
show_run-de = /show run = Aktuelle Konfiguration anzeigen!n!
show = /show or /list!n!/show or /list <admin/mod/user/guest>!n!
show-de = /show oder /list!n!/show oder /list <admin/mod/user/guest>!n!
add = /add <admin/mod/user/guest> <user_address> <user_name>!n!
add-de = /add <admin/mod/user/guest> <user_address> <user_name>!n!
del = /del or /rm <admin/mod/user/guest> <user_address>!n!/del or /rm <user_address>!n!
del-de = /del oder /rm <admin/mod/user/guest> <user_address>!n!/del oder /rm <user_address>!n!
move = /move <admin/mod/user/guest> <user_address>!n!
move-de = /move <admin/mod/user/guest> <user_address>!n!
rename = /rename <user_address> <new nickname> = Change nickname!n!
rename-de = /rename <user_address> <new nickname> = Ändern des Nickname!n!
invite = /invite <user_address> = Invites user to group!n!
invite-de = /invite <user_address> = Lädt Benutzer zur Gruppe ein!n!
kick = /kick <user_address> = Kicks user out of group!n!
kick-de = /kick <user_address> = Wirft den Benutzer aus der Gruppe!n!
block = /block <user_address> = Block user!n!/ban <user_address> = Block user!n!
block-de = /block <user_address> = Blockiert Benutzer!n!/ban <user_address> = Blockiert Benutzer!n!
unblock = /unblock <user_address> = Unblock user!n!/unban <user_address> = Unblock user!n!
unblock-de = /unblock <user_address> = Blockierung des Benutzers aufheben!n!/unban <user_address> = Blockierung des Benutzers aufheben!n!
allow = /allow <user_address> = Allow user!n!
allow-de = /allow <user_address> = Benutzer erlauben!n!
deny = /deny <user_address> = Deny user!n!
deny-de = /deny <user_address> = Benutzer ablehnen!n!
load = /load or /read = Read the configuration/data!n!
load-de = /load oder /read = Lesen der Konfiguration/Daten!n!
save = /save or /wr = Saves the current configuration/data!n!
save-de = /save oder /wr = Speichert die aktuelle Konfiguration/Daten!n!
reload = /reload = Reload the current configuration/data!n!
reload-de = /reload = Neu laden der aktuelle Konfiguration/Daten!n!
reset = /reset statistic <all/cluster/local/interface/user> = Reset statistic!n!
reset-de = /reset statistic <all/cluster/local/interface/user> = Statisktik zurücksetzenn!n!
'''
#### Default data file ####
DEFAULT_DATA = '''# This is the data file. It is automatically created and saved/overwritten.
# It contains data managed by the software itself.
# If manual adjustments are made here, the program must be shut down first!
#### High availability settings ####
[high_availability]
role = master
last_heartbeat = 0000-00-00 00:00:00
#### Main program settings ####
[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 = 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
#### Topics ####
[topics]
#### Admin user ####
[admin]
#### Mod/Moderator user ####
[mod]
#### User ####
[user]
#### Guest user ####
[guest]
#### Wait user ####
[wait]
#### Blocked user ####
[block_admin]
[block_mod]
[block_user]
[block_guest]
[block_wait]
#### Cluster (Automatically or manual created) ####
[cluster]
[block_cluster]
#### Router (Automatically or manual created) ####
[router]
[block_router]
#### Pinned messages (Automatically created) ####
[pin]
'''
##############################################################################################################
# Init
if __name__ == "__main__":
main()