mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-28 12:14:26 +01:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ed85ad398 | ||
|
|
5ceb0c671e | ||
|
|
3e7e55a9ca | ||
|
|
a7a88e6a3e | ||
|
|
5de1b93fd9 | ||
|
|
c9ca0a2fd1 | ||
|
|
cb87148ec3 | ||
|
|
9596361a6b | ||
|
|
d4a3a91e04 | ||
|
|
dbd1d87adb | ||
|
|
db3642ee05 | ||
|
|
6b74e49b0f | ||
|
|
5253cccfa7 | ||
|
|
a2e6a06a35 | ||
|
|
9c79496504 | ||
|
|
eafe77718f | ||
|
|
abde448e00 | ||
|
|
6d2bf21f0d | ||
|
|
eac9021c75 | ||
|
|
e6688b157e | ||
|
|
ad2cefa329 | ||
|
|
373315423e | ||
|
|
025cae6ebf | ||
|
|
0baebe5a3c | ||
|
|
3fedd0af30 | ||
|
|
34e0bde0b4 | ||
|
|
334559a4b6 | ||
|
|
cbca0d67b7 | ||
|
|
5b0146325e | ||
|
|
b2f48d5853 | ||
|
|
f0c3b898ae | ||
|
|
bf4b9e55ae | ||
|
|
2d87214cd0 | ||
|
|
a20b4c9bc3 |
3
FUNDING.yml
Normal file
3
FUNDING.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
liberapay: Reticulum
|
||||
ko_fi: markqvist
|
||||
custom: "https://unsigned.io/donate"
|
||||
@@ -148,14 +148,17 @@ You can help support the continued development of open, free and private communi
|
||||
```
|
||||
- Bitcoin
|
||||
```
|
||||
bc1p4a6axuvl7n9hpapfj8sv5reqj8kz6uxa67d5en70vzrttj0fmcusgxsfk5
|
||||
bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6
|
||||
```
|
||||
- Ethereum
|
||||
```
|
||||
0xae89F3B94fC4AD6563F0864a55F9a697a90261ff
|
||||
0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E
|
||||
```
|
||||
- Liberapay: https://liberapay.com/Reticulum/
|
||||
|
||||
- Ko-Fi: https://ko-fi.com/markqvist
|
||||
|
||||
|
||||
## Development Roadmap
|
||||
|
||||
- New major features
|
||||
@@ -171,8 +174,6 @@ You can help support the continued development of open, free and private communi
|
||||
- Better navigation handling when requests fail (also because of closed links)
|
||||
- Retry failed messages mechanism
|
||||
- Re-arrange buttons to be more consistent
|
||||
- Input field for pages
|
||||
- Post mechanism
|
||||
- Term compatibility notice in readme
|
||||
- Selected icon in conversation list
|
||||
- Possibly a Search Local Nodes function
|
||||
|
||||
@@ -6,6 +6,9 @@ import nomadnet
|
||||
import threading
|
||||
import RNS.vendor.umsgpack as msgpack
|
||||
|
||||
from LXMF import pn_announce_data_is_valid
|
||||
from nomadnet.util import strip_modifiers
|
||||
|
||||
class PNAnnounceHandler:
|
||||
def __init__(self, owner):
|
||||
self.aspect_filter = "lxmf.propagation"
|
||||
@@ -13,10 +16,10 @@ class PNAnnounceHandler:
|
||||
|
||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||
try:
|
||||
if type(app_data) == bytes:
|
||||
if pn_announce_data_is_valid(app_data):
|
||||
data = msgpack.unpackb(app_data)
|
||||
|
||||
if data[0] == True:
|
||||
if data[2] == True:
|
||||
RNS.log("Received active propagation node announce from "+RNS.prettyhexrep(destination_hash))
|
||||
|
||||
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
|
||||
@@ -193,7 +196,8 @@ class Directory:
|
||||
found_node = True
|
||||
break
|
||||
|
||||
if not found_node:
|
||||
# TODO: Remove debug and rethink this (needs way to set PN when node is saved)
|
||||
if True or not found_node:
|
||||
if self.app.compact_stream:
|
||||
try:
|
||||
remove_announces = []
|
||||
@@ -226,7 +230,7 @@ class Directory:
|
||||
|
||||
def display_name(self, source_hash):
|
||||
if source_hash in self.directory_entries:
|
||||
return self.directory_entries[source_hash].display_name
|
||||
return strip_modifiers(self.directory_entries[source_hash].display_name)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -238,7 +242,7 @@ class Directory:
|
||||
if dn == None:
|
||||
return RNS.prettyhexrep(source_hash)
|
||||
else:
|
||||
return dn+" <"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||
return strip_modifiers(dn)+" <"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||
else:
|
||||
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||
else:
|
||||
@@ -247,13 +251,13 @@ class Directory:
|
||||
if dn == None:
|
||||
return RNS.prettyhexrep(source_hash)
|
||||
else:
|
||||
return dn
|
||||
return strip_modifiers(dn)
|
||||
else:
|
||||
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||
|
||||
def alleged_display_str(self, source_hash):
|
||||
if source_hash in self.directory_entries:
|
||||
return self.directory_entries[source_hash].display_name
|
||||
return strip_modifiers(self.directory_entries[source_hash].display_name)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@@ -122,13 +122,16 @@ class NomadNetworkApp:
|
||||
self.page_refresh_interval = 0
|
||||
self.file_refresh_interval = 0
|
||||
|
||||
self.static_peers = []
|
||||
self.peer_announce_at_start = True
|
||||
self.try_propagation_on_fail = True
|
||||
self.disable_propagation = False
|
||||
self.disable_propagation = True
|
||||
self.notify_on_new_message = True
|
||||
|
||||
self.lxmf_max_propagation_size = None
|
||||
self.lxmf_max_sync_size = None
|
||||
self.lxmf_max_incoming_size = None
|
||||
self.node_propagation_cost = LXMF.LXMRouter.PROPAGATION_COST
|
||||
|
||||
self.periodic_lxmf_sync = True
|
||||
self.lxmf_sync_interval = 360*60
|
||||
@@ -242,8 +245,11 @@ class NomadNetworkApp:
|
||||
self.peer_settings["served_file_requests"] = 0
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not load local peer settings from "+self.peersettingspath, RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
|
||||
RNS.logdest = RNS.LOG_STDOUT
|
||||
RNS.log(f"Could not load local peer settings from {self.peersettingspath}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"This likely means that the peer settings file has become corrupt.", RNS.LOG_ERROR)
|
||||
RNS.log(f"You can try deleting the file at {self.peersettingspath} and restarting nomadnet.", RNS.LOG_ERROR)
|
||||
nomadnet.panic()
|
||||
else:
|
||||
try:
|
||||
@@ -302,8 +308,8 @@ class NomadNetworkApp:
|
||||
|
||||
self.message_router = LXMF.LXMRouter(
|
||||
identity = self.identity, storagepath = self.storagepath, autopeer = True,
|
||||
propagation_limit = self.lxmf_max_propagation_size, delivery_limit = self.lxmf_max_incoming_size,
|
||||
max_peers = self.max_peers, static_peers = static_peers,
|
||||
propagation_limit = self.lxmf_max_propagation_size, sync_limit = self.lxmf_max_sync_size, delivery_limit = self.lxmf_max_incoming_size,
|
||||
max_peers = self.max_peers, static_peers = static_peers, propagation_cost=self.node_propagation_cost
|
||||
)
|
||||
|
||||
self.message_router.register_delivery_callback(self.lxmf_delivery)
|
||||
@@ -555,9 +561,9 @@ class NomadNetworkApp:
|
||||
return self.message_router.get_outbound_propagation_node()
|
||||
|
||||
def save_peer_settings(self):
|
||||
file = open(self.peersettingspath, "wb")
|
||||
file.write(msgpack.packb(self.peer_settings))
|
||||
file.close()
|
||||
tmp_path = f"{self.peersettingspath}.tmp"
|
||||
with open(tmp_path, "wb") as file: file.write(msgpack.packb(self.peer_settings))
|
||||
os.replace(tmp_path, self.peersettingspath)
|
||||
|
||||
def lxmf_delivery(self, message):
|
||||
time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp))
|
||||
@@ -802,7 +808,7 @@ class NomadNetworkApp:
|
||||
if not "intro_time" in self.config["textui"]:
|
||||
self.config["textui"]["intro_time"] = 1
|
||||
else:
|
||||
self.config["textui"]["intro_time"] = self.config["textui"].as_int("intro_time")
|
||||
self.config["textui"]["intro_time"] = self.config["textui"].as_float("intro_time")
|
||||
|
||||
if not "intro_text" in self.config["textui"]:
|
||||
self.config["textui"]["intro_text"] = "Nomad Network"
|
||||
@@ -876,7 +882,7 @@ class NomadNetworkApp:
|
||||
self.node_name = self.config["node"]["node_name"]
|
||||
|
||||
if not "disable_propagation" in self.config["node"]:
|
||||
self.disable_propagation = False
|
||||
self.disable_propagation = True
|
||||
else:
|
||||
self.disable_propagation = self.config["node"].as_bool("disable_propagation")
|
||||
|
||||
@@ -888,6 +894,14 @@ class NomadNetworkApp:
|
||||
value = 1
|
||||
self.lxmf_max_propagation_size = value
|
||||
|
||||
if not "max_sync_size" in self.config["node"]:
|
||||
self.lxmf_max_sync_size = 256*40
|
||||
else:
|
||||
value = self.config["node"].as_float("max_sync_size")
|
||||
if value < self.lxmf_max_propagation_size:
|
||||
value = self.lxmf_max_propagation_size
|
||||
self.lxmf_max_sync_size = value
|
||||
|
||||
if not "announce_at_start" in self.config["node"]:
|
||||
self.node_announce_at_start = False
|
||||
else:
|
||||
@@ -901,6 +915,13 @@ class NomadNetworkApp:
|
||||
if value < 1:
|
||||
value = 1
|
||||
self.node_announce_interval = value
|
||||
|
||||
if not "propagation_cost" in self.config["node"]:
|
||||
self.node_propagation_cost = 16
|
||||
else:
|
||||
value = self.config["node"].as_int("propagation_cost")
|
||||
if value < 13: value = 13
|
||||
self.node_propagation_cost = value
|
||||
|
||||
if "pages_path" in self.config["node"]:
|
||||
self.pagespath = self.config["node"]["pages_path"]
|
||||
@@ -1162,14 +1183,55 @@ announce_at_start = Yes
|
||||
|
||||
# When Nomad Network is hosting a page-serving
|
||||
# node, it can also act as an LXMF propagation
|
||||
# node. If there is already a large amount of
|
||||
# node. This is a convenient feature that lets
|
||||
# you easily set up and run a propagation node
|
||||
# on the network, but it is not as fully
|
||||
# featured as using the lxmd program to host a
|
||||
# propagation node. For complete control and
|
||||
# flexibility, use lxmd to run a PN. For a
|
||||
# small local system or network, the built-in
|
||||
# PN functionality will suffice for most cases.
|
||||
#
|
||||
# If there is already a large amount of
|
||||
# propagation nodes on the network, or you
|
||||
# simply want to run a pageserving-only node,
|
||||
# you can disable running a propagation node.
|
||||
# you should disable running a propagation node.
|
||||
# Due to lots of propagation nodes being
|
||||
# available, this is currently the default.
|
||||
|
||||
disable_propagation = Yes
|
||||
|
||||
# For clients and other propagation nodes
|
||||
# delivering messages via this node, you can
|
||||
# configure the minimum required propagation
|
||||
# stamp costs. All messages delivered to the
|
||||
# propagation node network must have a valid
|
||||
# propagation stamp, or they will be rejected.
|
||||
# Clients automatically detect the stamp cost
|
||||
# for the node they are delivering to, and
|
||||
# compute a corresponding stamp before trying
|
||||
# to deliver the message to the propagation
|
||||
# node.
|
||||
#
|
||||
# Propagation stamps are easier to verify in
|
||||
# large batches, and therefore also somewhat
|
||||
# easier to compute for the senders. As such,
|
||||
# a reasonable propagation stamp cost should
|
||||
# be a bit higher than the normal peer-to-peer
|
||||
# stamp costs.
|
||||
#
|
||||
# Propagation stamps does not incur any extra
|
||||
# load for propagation nodes processing them,
|
||||
# since they are only required to verify that
|
||||
# they are correct, and only the generation
|
||||
# is computationally costly. Setting a sensible
|
||||
# propagation stamp cost (and periodically
|
||||
# checking the average network consensus) helps
|
||||
# keep spam and misuse out of the propagation
|
||||
# node network.
|
||||
|
||||
propagation_cost = 16
|
||||
|
||||
# The maximum amount of storage to use for
|
||||
# the LXMF Propagation Node message store,
|
||||
# specified in megabytes. When this limit
|
||||
@@ -1179,19 +1241,26 @@ disable_propagation = Yes
|
||||
# new and small. Large and old messages will
|
||||
# be removed first. This setting is optional
|
||||
# and defaults to 2 gigabytes.
|
||||
|
||||
# message_storage_limit = 2000
|
||||
|
||||
# The maximum accepted transfer size per in-
|
||||
# coming propagation transfer, in kilobytes.
|
||||
# This also sets the upper limit for the size
|
||||
# of single messages accepted onto this node.
|
||||
# coming propagation message, in kilobytes.
|
||||
# This sets the upper limit for the size of
|
||||
# single messages accepted onto this node.
|
||||
|
||||
max_transfer_size = 256
|
||||
|
||||
# The maximum accepted transfer size per in-
|
||||
# coming propagation node sync.
|
||||
#
|
||||
# If a node wants to propagate a larger number
|
||||
# of messages to this node, than what can fit
|
||||
# within this limit, it will prioritise sending
|
||||
# the smallest, newest messages first, and try
|
||||
# the smallest messages first, and try again
|
||||
# with any remaining messages at a later point.
|
||||
max_transfer_size = 256
|
||||
|
||||
max_sync_size = 10240
|
||||
|
||||
# You can tell the LXMF message router to
|
||||
# prioritise storage for one or more
|
||||
@@ -1200,29 +1269,34 @@ max_transfer_size = 256
|
||||
# keeping messages for destinations specified
|
||||
# with this option. This setting is optional,
|
||||
# and generally you do not need to use it.
|
||||
|
||||
# prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf
|
||||
|
||||
# You can configure the maximum number of other
|
||||
# propagation nodes that this node will peer
|
||||
# with automatically. The default is 50.
|
||||
# max_peers = 25
|
||||
# with automatically. The default is 20.
|
||||
|
||||
# max_peers = 20
|
||||
|
||||
# You can configure a list of static propagation
|
||||
# node peers, that this node will always be
|
||||
# peered with, by specifying a list of
|
||||
# destination hashes.
|
||||
|
||||
# static_peers = e17f833c4ddf8890dd3a79a6fea8161d, 5a2d0029b6e5ec87020abaea0d746da4
|
||||
|
||||
# You can specify the interval in minutes for
|
||||
# rescanning the hosted pages path. By default,
|
||||
# this option is disabled, and the pages path
|
||||
# will only be scanned on startup.
|
||||
|
||||
# page_refresh_interval = 0
|
||||
|
||||
# You can specify the interval in minutes for
|
||||
# rescanning the hosted files path. By default,
|
||||
# this option is disabled, and the files path
|
||||
# will only be scanned on startup.
|
||||
|
||||
# file_refresh_interval = 0
|
||||
|
||||
[printing]
|
||||
@@ -1231,6 +1305,7 @@ max_transfer_size = 256
|
||||
# various kinds of information and messages.
|
||||
|
||||
# Printing messages is disabled by default
|
||||
|
||||
print_messages = No
|
||||
|
||||
# You can configure a custom template for
|
||||
@@ -1238,37 +1313,64 @@ print_messages = No
|
||||
# option, set a path to the template and
|
||||
# restart Nomad Network, a default template
|
||||
# will be created that you can edit.
|
||||
|
||||
# message_template = ~/.nomadnetwork/print_template_msg.txt
|
||||
|
||||
# You can configure Nomad Network to only
|
||||
# print messages from trusted destinations.
|
||||
|
||||
# print_from = trusted
|
||||
|
||||
# Or specify the source LXMF addresses that
|
||||
# will automatically have messages printed
|
||||
# on arrival.
|
||||
|
||||
# print_from = 76fe5751a56067d1e84eef3e88eab85b, 0e70b5848eb57c13154154feaeeb89b7
|
||||
|
||||
# Or allow printing from anywhere, if you
|
||||
# are feeling brave and adventurous.
|
||||
|
||||
# print_from = everywhere
|
||||
|
||||
# You can configure the printing command.
|
||||
# This will use the default CUPS printer on
|
||||
# your system.
|
||||
|
||||
print_command = lp
|
||||
|
||||
# You can specify what printer to use
|
||||
# print_command = lp -d PRINTER_NAME
|
||||
# print_command = lp -d [PRINTER_NAME]
|
||||
|
||||
# Or specify more advanced options. This
|
||||
# example works well for small thermal-
|
||||
# roll printers.
|
||||
# print_command = lp -d PRINTER_NAME -o cpi=16 -o lpi=8
|
||||
# roll printers:
|
||||
# print_command = lp -d [PRINTER_NAME] -o cpi=16 -o lpi=8
|
||||
|
||||
# This one is more suitable for full-sheet
|
||||
# printers.
|
||||
# print_command = lp -d PRINTER_NAME -o page-left=36 -o page-top=36 -o page-right=36 -o page-bottom=36
|
||||
# printers. It will print a QR code at the center of any media
|
||||
# your printer will accept, print in portrait mode, and move the message to
|
||||
# the top of the print queue:
|
||||
# print_command = lp -d [PRINTER_NAME] -o job-priority=100 -o media=Custom.75x75mm -o orientation-requested=3
|
||||
|
||||
# But you can modify the size to fit your needs.
|
||||
# The custom media option accepts millimeters, centimeters, and
|
||||
# inches in a width by length format like so:
|
||||
# -o media=Custom.[WIDTH]x[LENGTH][mm,cm,in]
|
||||
#
|
||||
# The job priority option accepts 1-100, though you can remove it
|
||||
# entirely if you aren't concerned with a print queue:
|
||||
# -o job-priority=[1-100]
|
||||
#
|
||||
# Finally, the orientation option allows for 90 degree rotations beginning with 3, so:
|
||||
# -o orientation-requested=4 (landscape, 90 degrees)
|
||||
# -o orientation-requested=5 (reverse portrait, 180 degrees)
|
||||
#
|
||||
# Here is the full command with the recommended customizable variables:
|
||||
# print_command = lp -d [PRINTER_NAME] -o job-priority=[N] -o media=[MEDIA_SIZE] -o orientation-requested=[N] -o sides=one-sided
|
||||
|
||||
# For example, here's a configuration for USB thermal printer that uses the POS-58 PPD driver
|
||||
# with rolls 47.98x209.9mm in size:
|
||||
# print_command = lp -d [PRINTER_NAME] -o job-priority=100 -o media=custom_47.98x209.9mm_47.98x209.9mm -o sides=one-sided
|
||||
|
||||
'''.splitlines()
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ from .Node import Node
|
||||
from .ui import *
|
||||
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
|
||||
def panic():
|
||||
os._exit(255)
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.7.0"
|
||||
__version__ = "0.9.3"
|
||||
|
||||
@@ -3,8 +3,10 @@ import glob
|
||||
import RNS
|
||||
import nomadnet
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
|
||||
|
||||
UI_NONE = 0x00
|
||||
|
||||
@@ -11,6 +11,7 @@ import threading
|
||||
from .MicronParser import markup_to_attrmaps, make_style, default_state
|
||||
from nomadnet.Directory import DirectoryEntry
|
||||
from nomadnet.vendor.Scrollable import *
|
||||
from nomadnet.util import strip_modifiers
|
||||
|
||||
class BrowserFrame(urwid.Frame):
|
||||
def keypress(self, size, key):
|
||||
@@ -697,7 +698,7 @@ class Browser:
|
||||
|
||||
def confirmed(sender):
|
||||
try:
|
||||
self.retrieve_url(e_url.get_edit_text())
|
||||
self.retrieve_url(e_url.get_edit_text().strip())
|
||||
except Exception as e:
|
||||
self.browser_footer = urwid.Text("Could not open link: "+str(e))
|
||||
self.frame.contents["footer"] = (self.browser_footer, self.frame.options())
|
||||
@@ -799,7 +800,7 @@ class Browser:
|
||||
|
||||
self.page_background_color = None
|
||||
bgpos = self.markup.find("#!bg=")
|
||||
if bgpos:
|
||||
if bgpos >= 0:
|
||||
endpos = self.markup.find("\n", bgpos)
|
||||
if endpos-(bgpos+5) == 3:
|
||||
bg = self.markup[bgpos+5:endpos]
|
||||
@@ -807,13 +808,13 @@ class Browser:
|
||||
|
||||
self.page_foreground_color = None
|
||||
fgpos = self.markup.find("#!fg=")
|
||||
if fgpos:
|
||||
if fgpos >= 0:
|
||||
endpos = self.markup.find("\n", fgpos)
|
||||
if endpos-(fgpos+5) == 3:
|
||||
fg = self.markup[fgpos+5:endpos]
|
||||
self.page_foreground_color = fg
|
||||
|
||||
self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
|
||||
self.attr_maps = markup_to_attrmaps(strip_modifiers(self.markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
|
||||
|
||||
self.response_progress = 0
|
||||
self.response_speed = None
|
||||
@@ -866,7 +867,7 @@ class Browser:
|
||||
|
||||
self.page_background_color = None
|
||||
bgpos = self.markup.find("#!bg=")
|
||||
if bgpos:
|
||||
if bgpos >= 0:
|
||||
endpos = self.markup.find("\n", bgpos)
|
||||
if endpos-(bgpos+5) == 3:
|
||||
bg = self.markup[bgpos+5:endpos]
|
||||
@@ -874,13 +875,13 @@ class Browser:
|
||||
|
||||
self.page_foreground_color = None
|
||||
fgpos = self.markup.find("#!fg=")
|
||||
if fgpos:
|
||||
if fgpos >= 0:
|
||||
endpos = self.markup.find("\n", fgpos)
|
||||
if endpos-(fgpos+5) == 3:
|
||||
fg = self.markup[fgpos+5:endpos]
|
||||
self.page_foreground_color = fg
|
||||
|
||||
self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
|
||||
self.attr_maps = markup_to_attrmaps(strip_modifiers(self.markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
|
||||
|
||||
self.response_progress = 0
|
||||
self.response_speed = None
|
||||
@@ -1018,7 +1019,7 @@ class Browser:
|
||||
|
||||
self.page_background_color = None
|
||||
bgpos = self.markup.find("#!bg=")
|
||||
if bgpos:
|
||||
if bgpos >= 0:
|
||||
endpos = self.markup.find("\n", bgpos)
|
||||
if endpos-(bgpos+5) == 3:
|
||||
bg = self.markup[bgpos+5:endpos]
|
||||
@@ -1026,13 +1027,13 @@ class Browser:
|
||||
|
||||
self.page_foreground_color = None
|
||||
fgpos = self.markup.find("#!fg=")
|
||||
if fgpos:
|
||||
if fgpos >= 0:
|
||||
endpos = self.markup.find("\n", fgpos)
|
||||
if endpos-(fgpos+5) == 3:
|
||||
fg = self.markup[fgpos+5:endpos]
|
||||
self.page_foreground_color = fg
|
||||
|
||||
self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
|
||||
self.attr_maps = markup_to_attrmaps(strip_modifiers(self.markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
|
||||
self.response_progress = 0
|
||||
self.response_speed = None
|
||||
self.progress_updated_at = None
|
||||
|
||||
@@ -331,7 +331,7 @@ class ConversationsDisplay():
|
||||
existing_conversations = nomadnet.Conversation.conversation_list(self.app)
|
||||
|
||||
display_name = e_name.get_edit_text()
|
||||
source_hash_text = e_id.get_edit_text()
|
||||
source_hash_text = e_id.get_edit_text().strip()
|
||||
source_hash = bytes.fromhex(source_hash_text)
|
||||
trust_level = DirectoryEntry.UNTRUSTED
|
||||
if r_unknown.state == True:
|
||||
@@ -412,7 +412,7 @@ class ConversationsDisplay():
|
||||
try:
|
||||
local_delivery_signal = "local_delivery_occurred"
|
||||
duplicate_signal = "duplicate_lxm"
|
||||
lxm_uri = e_uri.get_edit_text()
|
||||
lxm_uri = e_uri.get_edit_text().strip()
|
||||
|
||||
ingest_result = self.app.message_router.ingest_lxm_uri(
|
||||
lxm_uri,
|
||||
|
||||
@@ -345,7 +345,7 @@ By default, you can find the examples in `!~/.nomadnetwork/examples`!. If you bu
|
||||
|
||||
Sometimes, you don't want everyone to be able to view certain pages or execute certain scripts. In such cases, you can use `*authentication`* to control who gets to run certain requests.
|
||||
|
||||
To enable authentication for any page, simply add a new file to your pages directory with ".allowed" added to the file-name of the page. If your page is named "secret_page.mu", just add a file named "secret_page.allowed".
|
||||
To enable authentication for any page, simply add a new file to your pages directory with ".allowed" added to the file-name of the page. If your page is named "secret_page.mu", just add a file named "secret_page.mu.allowed".
|
||||
|
||||
For each user allowed to access the page, add a line to this file, containing the hash of that users primary identity. Users can find their own identity hash in the `![ Network ]`! part of the program, under `!Local Peer Info`!. If you want to allow access for three different users, your file would look like this:
|
||||
|
||||
|
||||
0
nomadnet/ui/textui/Helpers.py
Normal file
0
nomadnet/ui/textui/Helpers.py
Normal file
@@ -148,6 +148,9 @@ def parse_line(line, state, url_delegate):
|
||||
elif first_char == "-":
|
||||
if len(line) == 2:
|
||||
divider_char = line[1]
|
||||
# Control characters don't make sense here and otherwise crash nomadnet
|
||||
if ord(divider_char) < 32:
|
||||
divider_char = "\u2500"
|
||||
else:
|
||||
divider_char = "\u2500"
|
||||
if state["depth"] == 0:
|
||||
@@ -484,20 +487,14 @@ def make_output(state, line, url_delegate, pre_escape=False):
|
||||
state["bg_color"] = state["default_bg"]
|
||||
state["align"] = state["default_align"]
|
||||
elif c == "c":
|
||||
if state["align"] != "center":
|
||||
state["align"] = "center"
|
||||
else:
|
||||
state["align"] = state["default_align"]
|
||||
if state["align"] != "center": state["align"] = "center"
|
||||
# else: state["align"] = state["default_align"]
|
||||
elif c == "l":
|
||||
if state["align"] != "left":
|
||||
state["align"] = "left"
|
||||
else:
|
||||
state["align"] = state["default_align"]
|
||||
if state["align"] != "left": state["align"] = "left"
|
||||
# else: state["align"] = state["default_align"]
|
||||
elif c == "r":
|
||||
if state["align"] != "right":
|
||||
state["align"] = "right"
|
||||
else:
|
||||
state["align"] = state["default_align"]
|
||||
if state["align"] != "right": state["align"] = "right"
|
||||
# else: state["align"] = state["default_align"]
|
||||
elif c == "a":
|
||||
state["align"] = state["default_align"]
|
||||
|
||||
@@ -646,7 +643,7 @@ def make_output(state, line, url_delegate, pre_escape=False):
|
||||
orig_spec = speclist[4]
|
||||
|
||||
if url_delegate != None:
|
||||
linkspec = LinkSpec(link_url, orig_spec)
|
||||
linkspec = LinkSpec(link_url, orig_spec, cm=cm)
|
||||
if link_fields != "":
|
||||
lf = link_fields.split("|")
|
||||
if len(lf) > 0:
|
||||
@@ -693,11 +690,11 @@ def make_output(state, line, url_delegate, pre_escape=False):
|
||||
|
||||
|
||||
class LinkSpec(urwid.AttrSpec):
|
||||
def __init__(self, link_target, orig_spec):
|
||||
def __init__(self, link_target, orig_spec, cm=256):
|
||||
self.link_target = link_target
|
||||
self.link_fields = None
|
||||
|
||||
super().__init__(orig_spec.foreground, orig_spec.background)
|
||||
super().__init__(orig_spec.foreground, orig_spec.background, colors=cm)
|
||||
|
||||
|
||||
class LinkableText(urwid.Text):
|
||||
|
||||
@@ -6,6 +6,7 @@ import threading
|
||||
from datetime import datetime
|
||||
from nomadnet.Directory import DirectoryEntry
|
||||
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY
|
||||
from nomadnet.util import strip_modifiers
|
||||
|
||||
from .Browser import Browser
|
||||
|
||||
@@ -250,7 +251,7 @@ class AnnounceInfo(urwid.WidgetWrap):
|
||||
|
||||
|
||||
class AnnounceStreamEntry(urwid.WidgetWrap):
|
||||
def __init__(self, app, announce, delegate):
|
||||
def __init__(self, app, announce, delegate, show_destination=False):
|
||||
full_time_format = "%Y-%m-%d %H:%M:%S"
|
||||
date_time_format = "%Y-%m-%d"
|
||||
time_time_format = "%H:%M:%S"
|
||||
@@ -274,7 +275,16 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
|
||||
ts_string = dt.strftime(date_only_format)
|
||||
|
||||
trust_level = self.app.directory.trust_level(source_hash)
|
||||
display_str = self.app.directory.simplest_display_str(source_hash)
|
||||
|
||||
if show_destination:
|
||||
display_str = RNS.hexrep(source_hash, delimit=False)
|
||||
else:
|
||||
try:
|
||||
display_str = strip_modifiers(announce[2].decode("utf-8"))
|
||||
if len(display_str) > 32:
|
||||
display_str = display_str[:32] + "..."
|
||||
except:
|
||||
display_str = self.app.directory.simplest_display_str(source_hash)
|
||||
|
||||
if trust_level == DirectoryEntry.UNTRUSTED:
|
||||
symbol = g["cross"]
|
||||
@@ -381,22 +391,33 @@ class AnnounceStream(urwid.WidgetWrap):
|
||||
self.ilb = None
|
||||
self.no_content = True
|
||||
self.current_tab = "nodes"
|
||||
self.show_destination = False
|
||||
self.search_text = ""
|
||||
|
||||
self.added_entries = []
|
||||
self.widget_list = []
|
||||
self.update_widget_list()
|
||||
|
||||
# Create tab buttons
|
||||
self.tab_nodes = TabButton("Nodes", on_press=self.show_nodes_tab)
|
||||
self.tab_peers = TabButton("Peers", on_press=self.show_peers_tab)
|
||||
self.tab_pn = TabButton("Propagation Nodes", on_press=self.show_pn_tab)
|
||||
self.tab_nodes = TabButton("Nodes (0)", on_press=self.show_nodes_tab)
|
||||
self.tab_peers = TabButton("Peers (0)", on_press=self.show_peers_tab)
|
||||
self.tab_pn = TabButton("Propagation Nodes (0)", on_press=self.show_pn_tab)
|
||||
|
||||
# Create tab bar with proportional widths
|
||||
self.tab_bar = urwid.Columns([
|
||||
('weight', 1, self.tab_nodes),
|
||||
('weight', 1, self.tab_peers),
|
||||
('weight', 3, self.tab_pn),
|
||||
], dividechars=1) # Add 1 character spacing between tabs
|
||||
], dividechars=1)
|
||||
|
||||
self.search_edit = urwid.Edit(caption="Search: ")
|
||||
urwid.connect_signal(self.search_edit, 'change', self.on_search_change)
|
||||
|
||||
self.display_toggle = TabButton("Show: Name", on_press=self.toggle_display_mode)
|
||||
|
||||
self.filter_bar = urwid.Columns([
|
||||
('weight', 2, self.search_edit),
|
||||
('weight', 1, self.display_toggle),
|
||||
], dividechars=1)
|
||||
|
||||
self.update_widget_list()
|
||||
|
||||
self.ilb = ExceptionHandlingListBox(
|
||||
self.widget_list,
|
||||
@@ -406,9 +427,9 @@ class AnnounceStream(urwid.WidgetWrap):
|
||||
#highlight_offFocus="list_off_focus"
|
||||
)
|
||||
|
||||
# Combine tab bar and list box
|
||||
self.pile = urwid.Pile([
|
||||
('pack', self.tab_bar),
|
||||
('pack', self.filter_bar),
|
||||
('weight', 1, self.ilb),
|
||||
])
|
||||
|
||||
@@ -416,13 +437,25 @@ class AnnounceStream(urwid.WidgetWrap):
|
||||
super().__init__(urwid.LineBox(self.display_widget, title="Announce Stream"))
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == "up" and (self.no_content or self.ilb.first_item_is_selected()):
|
||||
if key == "up" and self.pile.focus == self.tab_bar:
|
||||
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
|
||||
elif key == "ctrl x":
|
||||
self.delete_selected_entry()
|
||||
|
||||
return super(AnnounceStream, self).keypress(size, key)
|
||||
|
||||
def on_search_change(self, widget, text):
|
||||
self.search_text = text.lower()
|
||||
self.update_widget_list()
|
||||
|
||||
def toggle_display_mode(self, button):
|
||||
self.show_destination = not self.show_destination
|
||||
if self.show_destination:
|
||||
self.display_toggle.set_label("Show: Dest")
|
||||
else:
|
||||
self.display_toggle.set_label("Show: Name")
|
||||
self.update_widget_list()
|
||||
|
||||
def delete_selected_entry(self):
|
||||
if self.ilb.get_selected_item() != None:
|
||||
self.app.directory.remove_announce_with_timestamp(self.ilb.get_selected_item().original_widget.timestamp)
|
||||
@@ -438,19 +471,36 @@ class AnnounceStream(urwid.WidgetWrap):
|
||||
self.widget_list = []
|
||||
new_entries = []
|
||||
|
||||
node_count = 0
|
||||
peer_count = 0
|
||||
pn_count = 0
|
||||
|
||||
for e in self.app.directory.announce_stream:
|
||||
announce_type = e[3]
|
||||
|
||||
# Filter based on current tab
|
||||
if self.current_tab == "nodes" and (announce_type == "node" or announce_type == True):
|
||||
new_entries.append(e)
|
||||
elif self.current_tab == "peers" and (announce_type == "peer" or announce_type == False):
|
||||
new_entries.append(e)
|
||||
elif self.current_tab == "pn" and announce_type == "pn":
|
||||
new_entries.append(e)
|
||||
if self.search_text:
|
||||
try:
|
||||
announce_data = e[2].decode("utf-8").lower()
|
||||
except:
|
||||
announce_data = ""
|
||||
if self.search_text not in announce_data:
|
||||
continue
|
||||
|
||||
if announce_type == "node" or announce_type == True:
|
||||
node_count += 1
|
||||
if self.current_tab == "nodes":
|
||||
new_entries.append(e)
|
||||
elif announce_type == "peer" or announce_type == False:
|
||||
peer_count += 1
|
||||
if self.current_tab == "peers":
|
||||
new_entries.append(e)
|
||||
elif announce_type == "pn":
|
||||
pn_count += 1
|
||||
if self.current_tab == "pn":
|
||||
new_entries.append(e)
|
||||
|
||||
for e in new_entries:
|
||||
nw = AnnounceStreamEntry(self.app, e, self)
|
||||
nw = AnnounceStreamEntry(self.app, e, self, show_destination=self.show_destination)
|
||||
nw.timestamp = e[0]
|
||||
self.widget_list.append(nw)
|
||||
|
||||
@@ -460,6 +510,10 @@ class AnnounceStream(urwid.WidgetWrap):
|
||||
self.no_content = True
|
||||
self.widget_list = [urwid.Text(f"No {self.current_tab} announces", align='center')]
|
||||
|
||||
self.tab_nodes.set_label(f"Nodes ({node_count})")
|
||||
self.tab_peers.set_label(f"Peers ({peer_count})")
|
||||
self.tab_pn.set_label(f"Propagation Nodes ({pn_count})")
|
||||
|
||||
if self.ilb:
|
||||
self.ilb.set_body(self.widget_list)
|
||||
|
||||
@@ -555,7 +609,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
||||
if node_entry == None:
|
||||
display_str = self.app.directory.simplest_display_str(source_hash)
|
||||
else:
|
||||
display_str = node_entry.display_name
|
||||
display_str = strip_modifiers(node_entry.display_name)
|
||||
|
||||
addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||
|
||||
@@ -1648,7 +1702,6 @@ class NetworkDisplay():
|
||||
def reinit_known_nodes(self):
|
||||
self.known_nodes_display = KnownNodes(self.app)
|
||||
self.known_nodes_display.delegate = self
|
||||
self.close_list_dialogs()
|
||||
self.announce_stream_display.rebuild_widget_list()
|
||||
|
||||
def reinit_lxmf_peers(self):
|
||||
@@ -1843,15 +1896,24 @@ class LXMFPeerEntry(urwid.WidgetWrap):
|
||||
style = "list_unresponsive"
|
||||
focus_style = "list_focus_unresponsive"
|
||||
|
||||
if peer.propagation_transfer_limit:
|
||||
txfer_limit = RNS.prettysize(peer.propagation_transfer_limit*1000)
|
||||
else:
|
||||
txfer_limit = "No"
|
||||
if peer.propagation_transfer_limit: txfer_limit = RNS.prettysize(peer.propagation_transfer_limit*1000)
|
||||
else: txfer_limit = "No"
|
||||
|
||||
if peer.propagation_sync_limit: sync_limit = RNS.prettysize(peer.propagation_sync_limit*1000)
|
||||
else: sync_limit = "Unknown"
|
||||
|
||||
if peer.propagation_stamp_cost: sct = peer.propagation_stamp_cost
|
||||
else: sct = "Unknown"
|
||||
|
||||
if peer.propagation_stamp_cost_flexibility: scf = f" (flex {peer.propagation_stamp_cost_flexibility})"
|
||||
else: scf = ""
|
||||
|
||||
ar = round(peer.acceptance_rate*100, 2)
|
||||
peer_info_str = sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard))
|
||||
peer_info_str += "\n "+str(peer.unhandled_message_count)+f" unhandled LXMs, {txfer_limit} sync limit\n"
|
||||
peer_info_str += f" {RNS.prettyspeed(peer.sync_transfer_rate)} STR, "
|
||||
peer_info_str += f"{RNS.prettyspeed(peer.link_establishment_rate)} LER, {ar}% AR\n"
|
||||
peer_info_str += f"\n {sync_limit} sync limit, {txfer_limit} msg limit"
|
||||
peer_info_str += f"\n {RNS.prettyspeed(peer.sync_transfer_rate)} STR, {RNS.prettyspeed(peer.link_establishment_rate)} LER"
|
||||
peer_info_str += f"\n Propagation cost {sct}{scf}"
|
||||
peer_info_str += "\n "+str(peer.unhandled_message_count)+f" unhandled LXMs, {ar}% AR"
|
||||
widget = ListEntry(peer_info_str)
|
||||
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
||||
self.display_widget.destination_hash = destination_hash
|
||||
@@ -1899,4 +1961,4 @@ def pretty_date(time=False):
|
||||
return str(int(day_diff / 7)) + " weeks ago"
|
||||
if day_diff < 365:
|
||||
return str(int(day_diff / 30)) + " months ago"
|
||||
return str(int(day_diff / 365)) + " years ago"
|
||||
return str(int(day_diff / 365)) + " years ago"
|
||||
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
|
||||
30
nomadnet/util.py
Normal file
30
nomadnet/util.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import re
|
||||
import unicodedata
|
||||
import RNS
|
||||
|
||||
def strip_modifiers(text):
|
||||
def process_characters(text):
|
||||
result = []
|
||||
i = 0
|
||||
while i < len(text):
|
||||
char = text[i]
|
||||
category = unicodedata.category(char)
|
||||
|
||||
if category.startswith(('L', 'N', 'P', 'S')):
|
||||
result.append(char)
|
||||
i += 1
|
||||
elif category.startswith(('M', 'Sk', 'Cf')) or char in '\u200d\u200c':
|
||||
i += 1
|
||||
else:
|
||||
result.append(char)
|
||||
i += 1
|
||||
|
||||
return ''.join(result)
|
||||
|
||||
stripped = process_characters(text)
|
||||
stripped = re.sub(r'[\uFE00-\uFE0F]', '', stripped)
|
||||
stripped = re.sub(r'[\U000E0100-\U000E01EF]', '', stripped, flags=re.UNICODE)
|
||||
stripped = re.sub(r'[\U0001F3FB-\U0001F3FF]', '', stripped, flags=re.UNICODE)
|
||||
stripped = re.sub(r'[\u200D\u200C]', '', stripped)
|
||||
|
||||
return stripped
|
||||
6
nomadnet/vendor/__init__.py
vendored
6
nomadnet/vendor/__init__.py
vendored
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
|
||||
2
setup.py
2
setup.py
@@ -30,6 +30,6 @@ setuptools.setup(
|
||||
entry_points= {
|
||||
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
||||
},
|
||||
install_requires=["rns>=0.9.6", "lxmf>=0.7.1", "urwid>=2.6.16", "qrcode"],
|
||||
install_requires=["rns>=1.0.4", "lxmf>=0.9.3", "urwid>=2.6.16", "qrcode"],
|
||||
python_requires=">=3.7",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user