mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-17 14:54:26 +01:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e9dd2a481 | ||
|
|
ed3596b89a | ||
|
|
84ea13acbd | ||
|
|
bd631f6f5f | ||
|
|
5bcb4a3cec | ||
|
|
1913dff3fd | ||
|
|
0a5663b985 | ||
|
|
0ebdb9c826 | ||
|
|
52dfebcfa2 | ||
|
|
29feb1cab1 | ||
|
|
73fffe519a | ||
|
|
22f18324fc | ||
|
|
0affe9f283 | ||
|
|
dfd87a2119 | ||
|
|
c2f0f969fb | ||
|
|
9244f00e21 | ||
|
|
b1b2a2a302 | ||
|
|
b08ae0cf02 | ||
|
|
730c17c981 | ||
|
|
15a4ec2af9 | ||
|
|
08a9225cc9 | ||
|
|
6a8a146d6a | ||
|
|
70d0b0a32a | ||
|
|
fe0437a2fd | ||
|
|
4fcf37ac86 | ||
|
|
fe257a63c0 | ||
|
|
72cfab3bd2 | ||
|
|
c276c29cd0 | ||
|
|
a7ffd2101b | ||
|
|
d5e3809ba5 | ||
|
|
0cbdb2c395 | ||
|
|
29269de3ab | ||
|
|
cb672565e4 | ||
|
|
10501f6712 | ||
|
|
094a604dd6 | ||
|
|
583b4307fc |
41
README.md
41
README.md
@@ -1,5 +1,4 @@
|
|||||||
Nomad Network - Communicate Freely
|
# Nomad Network - Communicate Freely
|
||||||
==========
|
|
||||||
|
|
||||||
Off-grid, resilient mesh communication with strong encryption, forward secrecy and extreme privacy.
|
Off-grid, resilient mesh communication with strong encryption, forward secrecy and extreme privacy.
|
||||||
|
|
||||||
@@ -26,11 +25,6 @@ If you'd rather want to use an LXMF client with a graphical user interface, you
|
|||||||
## Current Status
|
## Current Status
|
||||||
The current version of the program should be considered a beta release. The program works well, but there will most probably be bugs and possibly sub-optimal performance in some scenarios. On the other hand, this is the ideal time to have an influence on the direction of the development of Nomad Network. To do so, join the discussion, report bugs and request features here on the GitHub project.
|
The current version of the program should be considered a beta release. The program works well, but there will most probably be bugs and possibly sub-optimal performance in some scenarios. On the other hand, this is the ideal time to have an influence on the direction of the development of Nomad Network. To do so, join the discussion, report bugs and request features here on the GitHub project.
|
||||||
|
|
||||||
### Feature roadmap
|
|
||||||
- Network-wide propagated bulletins and discussion threads
|
|
||||||
- Collaborative maps and geospatial information sharing
|
|
||||||
- Facilitation of trade and barter
|
|
||||||
|
|
||||||
## How do I get started?
|
## How do I get started?
|
||||||
The easiest way to install Nomad Network is via pip:
|
The easiest way to install Nomad Network is via pip:
|
||||||
|
|
||||||
@@ -48,23 +42,23 @@ nomadnet --daemon
|
|||||||
nomadnet --help
|
nomadnet --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Please Note**: If this is the very first time you use pip to install a program on your system, you might need to reboot your system for the program to become available. If you get a "command not found" error or similar when running the program, reboot your system and try again.
|
||||||
|
|
||||||
The first time the program is running, you will be presented with the **Guide section**, which contains all the information you need to start using Nomad Network.
|
The first time the program is running, you will be presented with the **Guide section**, which contains all the information you need to start using Nomad Network.
|
||||||
|
|
||||||
To use Nomad Network on packet radio or LoRa, you will need to configure your Reticulum installation to use any relevant packet radio TNCs or LoRa devices on your system. See the [Reticulum documentation](https://markqvist.github.io/Reticulum/manual/interfaces.html) for info. For a general introduction on how to set up such a system, take a look at [this post](https://unsigned.io/private-messaging-over-lora/).
|
To use Nomad Network on packet radio or LoRa, you will need to configure your Reticulum installation to use any relevant packet radio TNCs or LoRa devices on your system. See the [Reticulum documentation](https://markqvist.github.io/Reticulum/manual/interfaces.html) for info. For a general introduction on how to set up such a system, take a look at [this post](https://unsigned.io/private-messaging-over-lora/).
|
||||||
|
|
||||||
If you want to try Nomad Network without building your own physical network, you can connect to the [Unsigned.io RNS Testnet](https://github.com/markqvist/Reticulum#public-testnet) over the Internet, where there is already some Nomad Network and LXMF activity. If you connect to the testnet, you can leave nomadnet running for a while and wait for it to receive announces from other nodes on the network that host pages or services, or you can try connecting directly to some nodes listed here:
|
If you want to try Nomad Network without building your own physical network, you can connect to the [Unsigned.io RNS Testnet](https://github.com/markqvist/Reticulum#public-testnet) over the Internet, where there is already some Nomad Network and LXMF activity. If you connect to the testnet, you can leave nomadnet running for a while and wait for it to receive announces from other nodes on the network that host pages or services, or you can try connecting directly to some nodes listed here:
|
||||||
|
|
||||||
- `464ddc8cd323648ef919a638923b4916` Dublin Hub Testnet Node
|
- `abb3ebcd03cb2388a838e70c001291f9` Dublin Hub Testnet Node
|
||||||
- `5cda089fc42675bdd904c6d06db87d49` Frankfurt Hub Testnet Node
|
- `ea6a715f814bdc37e56f80c34da6ad51` Frankfurt Hub Testnet Node
|
||||||
|
|
||||||
To browse pages on a node that is not currently known, open the URL dialog in the `Network` section of the program by pressing `Ctrl+U`, paste or enter the address and select `Go` or press enter. Nomadnet will attempt to discover and connect to the requested node.
|
To browse pages on a node that is not currently known, open the URL dialog in the `Network` section of the program by pressing `Ctrl+U`, paste or enter the address and select `Go` or press enter. Nomadnet will attempt to discover and connect to the requested node.
|
||||||
|
|
||||||
**Please Note**: If this is the very first time you use pip to install a program on your system, you might need to reboot your system for the program to become available. If you get a "command not found" error or similar when running the program, reboot your system and try again.
|
|
||||||
|
|
||||||
### Install on Android
|
### Install on Android
|
||||||
You can install Nomad Network on Android using Termux, but there's a few more commands involved than the above one-liner. The process is documented in the [Android Installation](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html#reticulum-on-android) section of the Reticulum Manual. Once the Reticulum has been installed according to the linked documentation, Nomad Network can be installed as usual with pip.
|
You can install Nomad Network on Android using Termux, but there's a few more commands involved than the above one-liner. The process is documented in the [Android Installation](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html#reticulum-on-android) section of the Reticulum Manual. Once the Reticulum has been installed according to the linked documentation, Nomad Network can be installed as usual with pip.
|
||||||
|
|
||||||
For a native Android application with a graphical user interface, have a look at [Sideband](https://unsigned.io/sideband).
|
For a native Android application with a graphical user interface, have a look at [Sideband](https://github.com/markqvist/Sideband).
|
||||||
|
|
||||||
### Docker Images
|
### Docker Images
|
||||||
|
|
||||||
@@ -119,6 +113,29 @@ You can help support the continued development of open, free and private communi
|
|||||||
```
|
```
|
||||||
- Ko-Fi: https://ko-fi.com/markqvist
|
- Ko-Fi: https://ko-fi.com/markqvist
|
||||||
|
|
||||||
|
## Development Roadmap
|
||||||
|
|
||||||
|
- New major features
|
||||||
|
- Network-wide propagated bulletins and discussion threads
|
||||||
|
- Collaborative maps and geospatial information sharing
|
||||||
|
- Facilitation of trade and barter
|
||||||
|
- Minor improvements and fixes
|
||||||
|
- Link status (RSSI and SNR) in conversation or conv list
|
||||||
|
- Ctrl-M shorcut for jumping to menu
|
||||||
|
- Share node with other users / send node info to user
|
||||||
|
- Fix internal editor failing on some OSes with no "editor" alias
|
||||||
|
- Possibly add a required-width header
|
||||||
|
- Improve browser handling of remote link close
|
||||||
|
- 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
|
||||||
|
- Possibly add via entry in node info box, next to distance
|
||||||
|
|
||||||
## Caveat Emptor
|
## Caveat Emptor
|
||||||
Nomad Network is beta software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy-breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
|
Nomad Network is beta software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy-breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,35 @@ class Conversation:
|
|||||||
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def paper_output(self, content="", title=""):
|
||||||
|
if self.send_destination:
|
||||||
|
try:
|
||||||
|
dest = self.send_destination
|
||||||
|
source = self.app.lxmf_destination
|
||||||
|
desired_method = LXMF.LXMessage.PAPER
|
||||||
|
|
||||||
|
lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method)
|
||||||
|
qr_code = lxm.as_qr()
|
||||||
|
qr_tmp_path = self.app.tmpfilespath+"/"+str(RNS.hexrep(lxm.hash, delimit=False))
|
||||||
|
qr_code.save(qr_tmp_path)
|
||||||
|
|
||||||
|
print_result = self.app.print_file(qr_tmp_path)
|
||||||
|
os.unlink(qr_tmp_path)
|
||||||
|
|
||||||
|
if print_result:
|
||||||
|
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
||||||
|
self.messages.append(ConversationMessage(message_path))
|
||||||
|
|
||||||
|
return print_result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("An error occurred while generating paper message, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
||||||
|
return False
|
||||||
|
|
||||||
def message_notification(self, message):
|
def message_notification(self, message):
|
||||||
if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail:
|
if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail:
|
||||||
RNS.log("Direct delivery of "+str(message)+" failed. Retrying as propagated message.", RNS.LOG_VERBOSE)
|
RNS.log("Direct delivery of "+str(message)+" failed. Retrying as propagated message.", RNS.LOG_VERBOSE)
|
||||||
|
|||||||
@@ -5,6 +5,29 @@ import time
|
|||||||
import nomadnet
|
import nomadnet
|
||||||
import RNS.vendor.umsgpack as msgpack
|
import RNS.vendor.umsgpack as msgpack
|
||||||
|
|
||||||
|
class PNAnnounceHandler:
|
||||||
|
def __init__(self, owner):
|
||||||
|
self.aspect_filter = "lxmf.propagation"
|
||||||
|
self.owner = owner
|
||||||
|
|
||||||
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
|
try:
|
||||||
|
if type(app_data) == bytes:
|
||||||
|
data = msgpack.unpackb(app_data)
|
||||||
|
|
||||||
|
if data[0] == 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)
|
||||||
|
associated_node = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", announced_identity)
|
||||||
|
|
||||||
|
self.owner.app.directory.pn_announce_received(destination_hash, app_data, associated_peer, associated_node)
|
||||||
|
self.owner.app.autoselect_propagation_node()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while evaluating propagation node announce, ignoring announce.", RNS.LOG_DEBUG)
|
||||||
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
||||||
|
|
||||||
class Directory:
|
class Directory:
|
||||||
ANNOUNCE_STREAM_MAXLENGTH = 64
|
ANNOUNCE_STREAM_MAXLENGTH = 64
|
||||||
|
|
||||||
@@ -14,8 +37,6 @@ class Directory:
|
|||||||
app = nomadnet.NomadNetworkApp.get_shared_instance()
|
app = nomadnet.NomadNetworkApp.get_shared_instance()
|
||||||
|
|
||||||
if not destination_hash in app.ignored_list:
|
if not destination_hash in app.ignored_list:
|
||||||
destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
|
|
||||||
|
|
||||||
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
|
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
|
||||||
|
|
||||||
app.directory.node_announce_received(destination_hash, app_data, associated_peer)
|
app.directory.node_announce_received(destination_hash, app_data, associated_peer)
|
||||||
@@ -31,6 +52,9 @@ class Directory:
|
|||||||
self.app = app
|
self.app = app
|
||||||
self.load_from_disk()
|
self.load_from_disk()
|
||||||
|
|
||||||
|
self.pn_announce_handler = PNAnnounceHandler(self)
|
||||||
|
RNS.Transport.register_announce_handler(self.pn_announce_handler)
|
||||||
|
|
||||||
|
|
||||||
def save_to_disk(self):
|
def save_to_disk(self):
|
||||||
try:
|
try:
|
||||||
@@ -90,7 +114,7 @@ class Directory:
|
|||||||
def lxmf_announce_received(self, source_hash, app_data):
|
def lxmf_announce_received(self, source_hash, app_data):
|
||||||
if app_data != None:
|
if app_data != None:
|
||||||
timestamp = time.time()
|
timestamp = time.time()
|
||||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, False))
|
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "peer"))
|
||||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||||
self.announce_stream.pop()
|
self.announce_stream.pop()
|
||||||
|
|
||||||
@@ -100,7 +124,7 @@ class Directory:
|
|||||||
def node_announce_received(self, source_hash, app_data, associated_peer):
|
def node_announce_received(self, source_hash, app_data, associated_peer):
|
||||||
if app_data != None:
|
if app_data != None:
|
||||||
timestamp = time.time()
|
timestamp = time.time()
|
||||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, True))
|
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "node"))
|
||||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||||
self.announce_stream.pop()
|
self.announce_stream.pop()
|
||||||
|
|
||||||
@@ -113,6 +137,27 @@ class Directory:
|
|||||||
if hasattr(self.app.ui, "main_display"):
|
if hasattr(self.app.ui, "main_display"):
|
||||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||||
|
|
||||||
|
def pn_announce_received(self, source_hash, app_data, associated_peer, associated_node):
|
||||||
|
found_node = None
|
||||||
|
for sh in self.directory_entries:
|
||||||
|
if sh == associated_node:
|
||||||
|
found_node = True
|
||||||
|
break
|
||||||
|
|
||||||
|
for e in self.announce_stream:
|
||||||
|
if e[1] == associated_node:
|
||||||
|
found_node = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found_node:
|
||||||
|
timestamp = time.time()
|
||||||
|
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "pn"))
|
||||||
|
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||||
|
self.announce_stream.pop()
|
||||||
|
|
||||||
|
if hasattr(self.app.ui, "main_display"):
|
||||||
|
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||||
|
|
||||||
def remove_announce_with_timestamp(self, timestamp):
|
def remove_announce_with_timestamp(self, timestamp):
|
||||||
selected_announce = None
|
selected_announce = None
|
||||||
for announce in self.announce_stream:
|
for announce in self.announce_stream:
|
||||||
@@ -250,11 +295,6 @@ class DirectoryEntry:
|
|||||||
def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False, preferred_delivery=None, identify_on_connect=False):
|
def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False, preferred_delivery=None, identify_on_connect=False):
|
||||||
if len(source_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
if len(source_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
||||||
self.source_hash = source_hash
|
self.source_hash = source_hash
|
||||||
|
|
||||||
# TODO: Clean
|
|
||||||
# if display_name == None:
|
|
||||||
# display_name = source_hash
|
|
||||||
|
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
|
|
||||||
if preferred_delivery == None:
|
if preferred_delivery == None:
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class NomadNetworkApp:
|
|||||||
self.ignoredpath = self.configdir+"/ignored"
|
self.ignoredpath = self.configdir+"/ignored"
|
||||||
self.logfilepath = self.configdir+"/logfile"
|
self.logfilepath = self.configdir+"/logfile"
|
||||||
self.errorfilepath = self.configdir+"/errors"
|
self.errorfilepath = self.configdir+"/errors"
|
||||||
|
self.pnannouncedpath = self.configdir+"/pnannounced"
|
||||||
self.storagepath = self.configdir+"/storage"
|
self.storagepath = self.configdir+"/storage"
|
||||||
self.identitypath = self.configdir+"/storage/identity"
|
self.identitypath = self.configdir+"/storage/identity"
|
||||||
self.cachepath = self.configdir+"/storage/cache"
|
self.cachepath = self.configdir+"/storage/cache"
|
||||||
@@ -105,6 +106,7 @@ class NomadNetworkApp:
|
|||||||
self.conversationpath = self.configdir+"/storage/conversations"
|
self.conversationpath = self.configdir+"/storage/conversations"
|
||||||
self.directorypath = self.configdir+"/storage/directory"
|
self.directorypath = self.configdir+"/storage/directory"
|
||||||
self.peersettingspath = self.configdir+"/storage/peersettings"
|
self.peersettingspath = self.configdir+"/storage/peersettings"
|
||||||
|
self.tmpfilespath = self.configdir+"/storage/tmp"
|
||||||
|
|
||||||
self.pagespath = self.configdir+"/storage/pages"
|
self.pagespath = self.configdir+"/storage/pages"
|
||||||
self.filespath = self.configdir+"/storage/files"
|
self.filespath = self.configdir+"/storage/files"
|
||||||
@@ -145,6 +147,11 @@ class NomadNetworkApp:
|
|||||||
if not os.path.isdir(self.cachepath):
|
if not os.path.isdir(self.cachepath):
|
||||||
os.makedirs(self.cachepath)
|
os.makedirs(self.cachepath)
|
||||||
|
|
||||||
|
if not os.path.isdir(self.tmpfilespath):
|
||||||
|
os.makedirs(self.tmpfilespath)
|
||||||
|
else:
|
||||||
|
self.clear_tmp_dir()
|
||||||
|
|
||||||
if os.path.isfile(self.configpath):
|
if os.path.isfile(self.configpath):
|
||||||
try:
|
try:
|
||||||
self.config = ConfigObj(self.configpath)
|
self.config = ConfigObj(self.configpath)
|
||||||
@@ -256,7 +263,7 @@ class NomadNetworkApp:
|
|||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while fetching loading list of ignored destinations: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while loading list of ignored destinations: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.directory = nomadnet.Directory(self)
|
self.directory = nomadnet.Directory(self)
|
||||||
|
|
||||||
@@ -290,11 +297,24 @@ class NomadNetworkApp:
|
|||||||
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
|
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.message_router.enable_propagation()
|
self.message_router.enable_propagation()
|
||||||
|
try:
|
||||||
|
with open(self.pnannouncedpath, "wb") as pnf:
|
||||||
|
pnf.write(msgpack.packb(time.time()))
|
||||||
|
pnf.close()
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("An error ocurred while writing Propagation Node announce timestamp. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
|
RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
|
||||||
self.node = nomadnet.Node(self)
|
self.node = nomadnet.Node(self)
|
||||||
else:
|
else:
|
||||||
self.node = None
|
self.node = None
|
||||||
|
if os.path.isfile(self.pnannouncedpath):
|
||||||
|
try:
|
||||||
|
RNS.log("Sending indication to peered LXMF Propagation Node that this node is no longer participating", RNS.LOG_DEBUG)
|
||||||
|
self.message_router.disable_propagation()
|
||||||
|
os.unlink(self.pnannouncedpath)
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("An error ocurred while indicating that this LXMF Propagation Node is no longer participating. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
RNS.Transport.register_announce_handler(nomadnet.Conversation)
|
RNS.Transport.register_announce_handler(nomadnet.Conversation)
|
||||||
RNS.Transport.register_announce_handler(nomadnet.Directory)
|
RNS.Transport.register_announce_handler(nomadnet.Directory)
|
||||||
@@ -436,8 +456,9 @@ class NomadNetworkApp:
|
|||||||
def autoselect_propagation_node(self):
|
def autoselect_propagation_node(self):
|
||||||
selected_node = None
|
selected_node = None
|
||||||
|
|
||||||
if "propagation_node" in self.peer_settings and self.directory.find(self.peer_settings["propagation_node"]):
|
if "propagation_node" in self.peer_settings:
|
||||||
selected_node = self.directory.find(self.peer_settings["propagation_node"])
|
selected_node = self.peer_settings["propagation_node"]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
nodes = self.directory.known_nodes()
|
nodes = self.directory.known_nodes()
|
||||||
trusted_nodes = []
|
trusted_nodes = []
|
||||||
@@ -450,19 +471,14 @@ class NomadNetworkApp:
|
|||||||
|
|
||||||
if hops < best_hops:
|
if hops < best_hops:
|
||||||
best_hops = hops
|
best_hops = hops
|
||||||
selected_node = node
|
selected_node = node.source_hash
|
||||||
|
|
||||||
if selected_node == None:
|
if selected_node == None:
|
||||||
RNS.log("Could not autoselect a propagation node! LXMF propagation will not be available until a trusted node announces on the network.", RNS.LOG_WARNING)
|
RNS.log("Could not autoselect a propagation node! LXMF propagation will not be available until a trusted node announces on the network, or a propagation node is manually selected.", RNS.LOG_WARNING)
|
||||||
else:
|
else:
|
||||||
node_identity = RNS.Identity.recall(selected_node.source_hash)
|
pn_name_str = ""
|
||||||
if node_identity != None:
|
RNS.log("Selecting "+RNS.prettyhexrep(selected_node)+pn_name_str+" as default LXMF propagation node", RNS.LOG_INFO)
|
||||||
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
|
self.message_router.set_outbound_propagation_node(selected_node)
|
||||||
RNS.log("Selecting "+selected_node.display_name+" "+RNS.prettyhexrep(propagation_hash)+" as default LXMF propagation node", RNS.LOG_INFO)
|
|
||||||
self.message_router.set_outbound_propagation_node(propagation_hash)
|
|
||||||
else:
|
|
||||||
RNS.log("Could not recall identity for autoselected LXMF propagation node "+RNS.prettyhexrep(selected_node.source_hash), RNS.LOG_WARNING)
|
|
||||||
RNS.log("LXMF propagation will not be available until a trusted node announces on the network.", RNS.LOG_WARNING)
|
|
||||||
|
|
||||||
def get_user_selected_propagation_node(self):
|
def get_user_selected_propagation_node(self):
|
||||||
if "propagation_node" in self.peer_settings:
|
if "propagation_node" in self.peer_settings:
|
||||||
@@ -518,6 +534,26 @@ class NomadNetworkApp:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def print_file(self, filename):
|
||||||
|
print_command = self.print_command+" "+filename
|
||||||
|
|
||||||
|
try:
|
||||||
|
return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("An error occurred while executing print command: "+str(print_command), RNS.LOG_ERROR)
|
||||||
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if return_code == 0:
|
||||||
|
RNS.log("Successfully printed "+str(filename)+" using print command: "+print_command, RNS.LOG_DEBUG)
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
RNS.log("Printing "+str(filename)+" failed using print command: "+print_command, RNS.LOG_DEBUG)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def print_message(self, message, received = None):
|
def print_message(self, message, received = None):
|
||||||
try:
|
try:
|
||||||
template = self.printing_template_msg
|
template = self.printing_template_msg
|
||||||
@@ -551,8 +587,7 @@ class NomadNetworkApp:
|
|||||||
f.write(output.encode("utf-8"))
|
f.write(output.encode("utf-8"))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
print_command = "lp -d thermal -o cpi=16 -o lpi=8 "+filename
|
self.print_file(filename)
|
||||||
return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
||||||
|
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
@@ -580,6 +615,12 @@ class NomadNetworkApp:
|
|||||||
if os.path.isfile(self.conversationpath + "/" + source_hash + "/unread"):
|
if os.path.isfile(self.conversationpath + "/" + source_hash + "/unread"):
|
||||||
os.unlink(self.conversationpath + "/" + source_hash + "/unread")
|
os.unlink(self.conversationpath + "/" + source_hash + "/unread")
|
||||||
|
|
||||||
|
def clear_tmp_dir(self):
|
||||||
|
if os.path.isdir(self.tmpfilespath):
|
||||||
|
for file in os.listdir(self.tmpfilespath):
|
||||||
|
fpath = self.tmpfilespath+"/"+file
|
||||||
|
os.unlink(fpath)
|
||||||
|
|
||||||
def createDefaultConfig(self):
|
def createDefaultConfig(self):
|
||||||
self.config = ConfigObj(__default_nomadnet_config__)
|
self.config = ConfigObj(__default_nomadnet_config__)
|
||||||
self.config.filename = self.configpath
|
self.config.filename = self.configpath
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.2.2"
|
__version__ = "0.3.2"
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ GLYPHS = {
|
|||||||
("decoration_menu", " +", " +", " \uf93a"),
|
("decoration_menu", " +", " +", " \uf93a"),
|
||||||
("unread_menu", " !", " \u2709", urm_char),
|
("unread_menu", " !", " \u2709", urm_char),
|
||||||
("globe", "", "", "\uf484"),
|
("globe", "", "", "\uf484"),
|
||||||
("sent", "/\\", "\u2191", "\ufbf4")
|
("sent", "/\\", "\u2191", "\ufbf4"),
|
||||||
|
("papermsg", "P", "\u25a4", "\uf719"),
|
||||||
|
("qrcode", "QR", "\u25a4", "\uf029"),
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextUI:
|
class TextUI:
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ class ConversationListDisplayShortcuts():
|
|||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info [C-x] Delete [C-r] Sync [C-n] New [C-g] Fullscreen"), "shortcutbar")
|
self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info [C-x] Delete [C-r] Sync [C-n] New [C-u] Ingest URI [C-g] Fullscreen"), "shortcutbar")
|
||||||
|
|
||||||
class ConversationDisplayShortcuts():
|
class ConversationDisplayShortcuts():
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-k] Clear [C-w] Close [C-t] Title [C-p] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar")
|
self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-p] Paper Msg [C-t] Title [C-k] Clear [C-w] Close [C-u] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar")
|
||||||
|
|
||||||
class ConversationsArea(urwid.LineBox):
|
class ConversationsArea(urwid.LineBox):
|
||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
@@ -30,6 +30,8 @@ class ConversationsArea(urwid.LineBox):
|
|||||||
self.delegate.delete_selected_conversation()
|
self.delegate.delete_selected_conversation()
|
||||||
elif key == "ctrl n":
|
elif key == "ctrl n":
|
||||||
self.delegate.new_conversation()
|
self.delegate.new_conversation()
|
||||||
|
elif key == "ctrl u":
|
||||||
|
self.delegate.ingest_lxm_uri()
|
||||||
elif key == "ctrl r":
|
elif key == "ctrl r":
|
||||||
self.delegate.sync_conversations()
|
self.delegate.sync_conversations()
|
||||||
elif key == "ctrl g":
|
elif key == "ctrl g":
|
||||||
@@ -330,6 +332,110 @@ class ConversationsDisplay():
|
|||||||
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||||
self.columns_widget.contents[0] = (overlay, options)
|
self.columns_widget.contents[0] = (overlay, options)
|
||||||
|
|
||||||
|
def ingest_lxm_uri(self):
|
||||||
|
self.dialog_open = True
|
||||||
|
lxm_uri = ""
|
||||||
|
e_uri = urwid.Edit(caption="URI : ",edit_text=lxm_uri)
|
||||||
|
|
||||||
|
def dismiss_dialog(sender):
|
||||||
|
self.update_conversation_list()
|
||||||
|
self.dialog_open = False
|
||||||
|
|
||||||
|
def confirmed(sender):
|
||||||
|
try:
|
||||||
|
local_delivery_signal = "local_delivery_occurred"
|
||||||
|
duplicate_signal = "duplicate_lxm"
|
||||||
|
lxm_uri = e_uri.get_edit_text()
|
||||||
|
|
||||||
|
ingest_result = self.app.message_router.ingest_lxm_uri(
|
||||||
|
lxm_uri,
|
||||||
|
signal_local_delivery=local_delivery_signal,
|
||||||
|
signal_duplicate=duplicate_signal
|
||||||
|
)
|
||||||
|
|
||||||
|
if ingest_result == False:
|
||||||
|
raise ValueError("The URI contained no decodable messages")
|
||||||
|
|
||||||
|
elif ingest_result == local_delivery_signal:
|
||||||
|
rdialog_pile = urwid.Pile([
|
||||||
|
urwid.Text("Message was decoded, decrypted successfully, and added to your conversation list."),
|
||||||
|
urwid.Text(""),
|
||||||
|
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||||
|
])
|
||||||
|
rdialog_pile.error_display = False
|
||||||
|
|
||||||
|
rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
|
||||||
|
rdialog.delegate = self
|
||||||
|
bottom = self.listbox
|
||||||
|
|
||||||
|
roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||||
|
self.columns_widget.contents[0] = (roverlay, options)
|
||||||
|
|
||||||
|
elif ingest_result == duplicate_signal:
|
||||||
|
rdialog_pile = urwid.Pile([
|
||||||
|
urwid.Text("The decoded message has already been processed by the LXMF Router, and will not be ingested again."),
|
||||||
|
urwid.Text(""),
|
||||||
|
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||||
|
])
|
||||||
|
rdialog_pile.error_display = False
|
||||||
|
|
||||||
|
rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
|
||||||
|
rdialog.delegate = self
|
||||||
|
bottom = self.listbox
|
||||||
|
|
||||||
|
roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||||
|
self.columns_widget.contents[0] = (roverlay, options)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.app.enable_node:
|
||||||
|
propagation_text = "The decoded message was not addressed to this LXMF address, but has been added to the propagation node queues, and will be distributed on the propagation network."
|
||||||
|
else:
|
||||||
|
propagation_text = "The decoded message was not addressed to this LXMF address, and has been discarded."
|
||||||
|
|
||||||
|
rdialog_pile = urwid.Pile([
|
||||||
|
urwid.Text(propagation_text),
|
||||||
|
urwid.Text(""),
|
||||||
|
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||||
|
])
|
||||||
|
rdialog_pile.error_display = False
|
||||||
|
|
||||||
|
rdialog = DialogLineBox(rdialog_pile, title="Ingest message URI")
|
||||||
|
rdialog.delegate = self
|
||||||
|
bottom = self.listbox
|
||||||
|
|
||||||
|
roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||||
|
self.columns_widget.contents[0] = (roverlay, options)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Could not ingest LXM URI. The contained exception was: "+str(e), RNS.LOG_VERBOSE)
|
||||||
|
if not dialog_pile.error_display:
|
||||||
|
dialog_pile.error_display = True
|
||||||
|
options = dialog_pile.options(height_type="pack")
|
||||||
|
dialog_pile.contents.append((urwid.Text(""), options))
|
||||||
|
dialog_pile.contents.append((urwid.Text(("error_text", "Could ingest LXM from URI data. Check your input."), align="center"), options))
|
||||||
|
|
||||||
|
dialog_pile = urwid.Pile([
|
||||||
|
e_uri,
|
||||||
|
urwid.Text(""),
|
||||||
|
urwid.Columns([("weight", 0.45, urwid.Button("Ingest", on_press=confirmed)), ("weight", 0.1, urwid.Text("")), ("weight", 0.45, urwid.Button("Back", on_press=dismiss_dialog))])
|
||||||
|
])
|
||||||
|
dialog_pile.error_display = False
|
||||||
|
|
||||||
|
dialog = DialogLineBox(dialog_pile, title="Ingest message URI")
|
||||||
|
dialog.delegate = self
|
||||||
|
bottom = self.listbox
|
||||||
|
|
||||||
|
overlay = urwid.Overlay(dialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
options = self.columns_widget.options("given", ConversationsDisplay.given_list_width)
|
||||||
|
self.columns_widget.contents[0] = (overlay, options)
|
||||||
|
|
||||||
def delete_conversation(self, source_hash):
|
def delete_conversation(self, source_hash):
|
||||||
if source_hash in ConversationsDisplay.cached_conversation_widgets:
|
if source_hash in ConversationsDisplay.cached_conversation_widgets:
|
||||||
conversation = ConversationsDisplay.cached_conversation_widgets[source_hash]
|
conversation = ConversationsDisplay.cached_conversation_widgets[source_hash]
|
||||||
@@ -398,10 +504,15 @@ class ConversationsDisplay():
|
|||||||
if pn_ident != None:
|
if pn_ident != None:
|
||||||
node_hash = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", pn_ident)
|
node_hash = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", pn_ident)
|
||||||
pn_entry = self.app.directory.find(node_hash)
|
pn_entry = self.app.directory.find(node_hash)
|
||||||
|
pn_display_str = " "
|
||||||
|
if pn_entry != None:
|
||||||
|
pn_display_str += " "+str(pn_entry.display_name)
|
||||||
|
else:
|
||||||
|
pn_display_str += " "+RNS.prettyhexrep(pn_hash)
|
||||||
|
|
||||||
dialog = DialogLineBox(
|
dialog = DialogLineBox(
|
||||||
urwid.Pile([
|
urwid.Pile([
|
||||||
urwid.Text(""+g["node"]+" "+str(pn_entry.display_name), align="center"),
|
urwid.Text(""+g["node"]+pn_display_str, align="center"),
|
||||||
urwid.Divider(g["divider1"]),
|
urwid.Divider(g["divider1"]),
|
||||||
sync_progress,
|
sync_progress,
|
||||||
urwid.Divider(g["divider1"]),
|
urwid.Divider(g["divider1"]),
|
||||||
@@ -417,7 +528,7 @@ class ConversationsDisplay():
|
|||||||
urwid.Pile([
|
urwid.Pile([
|
||||||
urwid.Text(""),
|
urwid.Text(""),
|
||||||
urwid.Text("No trusted nodes found, cannot sync!\n", align="center"),
|
urwid.Text("No trusted nodes found, cannot sync!\n", align="center"),
|
||||||
urwid.Text("To syncronise messages from the network, one or more nodes must be marked as trusted in the Known Nodes list. Nomad Network will then automatically sync from the nearest trusted node.", align="left"),
|
urwid.Text("To syncronise messages from the network, one or more nodes must be marked as trusted in the Known Nodes list, or a node must manually be selected as the default propagation node. Nomad Network will then automatically sync from the nearest trusted node, or the manually selected one.", align="left"),
|
||||||
urwid.Text(""),
|
urwid.Text(""),
|
||||||
button_columns
|
button_columns
|
||||||
]), title="Message Sync"
|
]), title="Message Sync"
|
||||||
@@ -631,6 +742,8 @@ class MessageEdit(urwid.Edit):
|
|||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
if key == "ctrl d":
|
if key == "ctrl d":
|
||||||
self.delegate.send_message()
|
self.delegate.send_message()
|
||||||
|
elif key == "ctrl p":
|
||||||
|
self.delegate.paper_message()
|
||||||
elif key == "ctrl k":
|
elif key == "ctrl k":
|
||||||
self.delegate.clear_editor()
|
self.delegate.clear_editor()
|
||||||
elif key == "up":
|
elif key == "up":
|
||||||
@@ -795,7 +908,7 @@ class ConversationWidget(urwid.WidgetWrap):
|
|||||||
self.toggle_focus_area()
|
self.toggle_focus_area()
|
||||||
elif key == "ctrl w":
|
elif key == "ctrl w":
|
||||||
self.close()
|
self.close()
|
||||||
elif key == "ctrl p":
|
elif key == "ctrl u":
|
||||||
self.conversation.purge_failed()
|
self.conversation.purge_failed()
|
||||||
self.conversation_changed(None)
|
self.conversation_changed(None)
|
||||||
elif key == "ctrl t":
|
elif key == "ctrl t":
|
||||||
@@ -855,6 +968,34 @@ class ConversationWidget(urwid.WidgetWrap):
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def paper_message(self):
|
||||||
|
content = self.content_editor.get_edit_text()
|
||||||
|
title = self.title_editor.get_edit_text()
|
||||||
|
if not content == "":
|
||||||
|
if self.conversation.paper_output(content, title):
|
||||||
|
self.clear_editor()
|
||||||
|
else:
|
||||||
|
self.paper_message_failed()
|
||||||
|
|
||||||
|
def paper_message_failed(self):
|
||||||
|
def dismiss_dialog(sender):
|
||||||
|
self.dialog_open = False
|
||||||
|
self.conversation_changed(None)
|
||||||
|
|
||||||
|
dialog = DialogLineBox(
|
||||||
|
urwid.Pile([
|
||||||
|
urwid.Text("Could not output paper message,\ncheck your settings. See the log\nfile for any error messages.\n", align="center"),
|
||||||
|
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||||
|
]), title="!"
|
||||||
|
)
|
||||||
|
dialog.delegate = self
|
||||||
|
bottom = self.messagelist
|
||||||
|
|
||||||
|
overlay = urwid.Overlay(dialog, bottom, align="center", width=34, valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
self.frame.contents["body"] = (overlay, self.frame.options())
|
||||||
|
self.frame.set_focus("body")
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.delegate.close_conversation(self)
|
self.delegate.close_conversation(self)
|
||||||
|
|
||||||
@@ -885,6 +1026,9 @@ class LXMessageWidget(urwid.WidgetWrap):
|
|||||||
elif message.lxm.method == LXMF.LXMessage.PROPAGATED and message.lxm.state == LXMF.LXMessage.SENT:
|
elif message.lxm.method == LXMF.LXMessage.PROPAGATED and message.lxm.state == LXMF.LXMessage.SENT:
|
||||||
header_style = "msg_header_propagated"
|
header_style = "msg_header_propagated"
|
||||||
title_string = g["sent"]+" "+title_string
|
title_string = g["sent"]+" "+title_string
|
||||||
|
elif message.lxm.method == LXMF.LXMessage.PAPER and message.lxm.state == LXMF.LXMessage.PAPER:
|
||||||
|
header_style = "msg_header_propagated"
|
||||||
|
title_string = g["papermsg"]+" "+title_string
|
||||||
elif message.lxm.state == LXMF.LXMessage.SENT:
|
elif message.lxm.state == LXMF.LXMessage.SENT:
|
||||||
header_style = "msg_header_sent"
|
header_style = "msg_header_sent"
|
||||||
title_string = g["sent"]+" "+title_string
|
title_string = g["sent"]+" "+title_string
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ In the `![ Conversations ]`! part of the program you can view and interact with
|
|||||||
|
|
||||||
By default, Nomad Network will attempt to deliver messages to a peer directly. This happens by first establishing an encrypted link directly to the peer, and then delivering the message over it.
|
By default, Nomad Network will attempt to deliver messages to a peer directly. This happens by first establishing an encrypted link directly to the peer, and then delivering the message over it.
|
||||||
|
|
||||||
If the desired peer is not available because it has disconnected from the network, this method will obviously fail. In this case, Nomad Network will attempt to deliver the message to a node, which will store and forward it over the network, for later retrieval by the destination peer. The message is encrypted with an ephemeral key before being transmitted to the network, and is only readable by the intended recipient.
|
If the desired peer is not available because it has disconnected from the network, this method will obviously fail. In this case, Nomad Network will attempt to deliver the message to a node, which will store and forward it over the network, for later retrieval by the destination peer. The message is encrypted before being transmitted to the network, and is only readable by the intended recipient.
|
||||||
|
|
||||||
For propagated delivery to work, one or more nodes must be available on the network. If one or more trusted nodes are available, Nomad Network will automatically select the most suitable node to send the message via, but you can also manually specify what node to use.
|
For propagated delivery to work, one or more nodes must be available on the network. If one or more trusted nodes are available, Nomad Network will automatically select the most suitable node to send the message via, but you can also manually specify what node to use.
|
||||||
|
|
||||||
@@ -670,25 +670,25 @@ For future reference, you can download the Reticulum Manual in PDF format here:
|
|||||||
|
|
||||||
It might be nice to keep that handy when you are not connected to the Internet, as it is full of information and examples that are also very relevant to Nomad Network.
|
It might be nice to keep that handy when you are not connected to the Internet, as it is full of information and examples that are also very relevant to Nomad Network.
|
||||||
|
|
||||||
>The Unsigned.io Testnet
|
>The Reticulum Testnet
|
||||||
|
|
||||||
If you have Internet access, and just want to get started experimenting, you are welcome to join the Unsigned.io RNS Testnet. The testnet is just that, an informal network for testing and experimenting. It will be up most of the time, and anyone can join, but it also means that there's no guarantees for service availability.
|
If you have Internet access, and just want to get started experimenting, you are welcome to join the Unsigned.io RNS Testnet. The testnet is just that, an informal network for testing and experimenting. It will be up most of the time, and anyone can join, but it also means that there's no guarantees for service availability.
|
||||||
|
|
||||||
The Testnet also runs the latest version of Reticulum, often even a short while before it is publicly released, which means strange behaviour might occur. If none of that scares you, add the following interface to your Reticulum configuration file to join:
|
The Testnet also runs the latest version of Reticulum, often even a short while before it is publicly released, which means strange behaviour might occur. If none of that scares you, add the following interface to your Reticulum configuration file to join:
|
||||||
|
|
||||||
>>
|
>>
|
||||||
[[RNS Testnet Frankfurt]]
|
[[RNS Testnet Zurich]]
|
||||||
type = TCPClientInterface
|
type = TCPClientInterface
|
||||||
interface_enabled = yes
|
interface_enabled = yes
|
||||||
outgoing = True
|
outgoing = True
|
||||||
target_host = frankfurt.rns.unsigned.io
|
target_host = zurich.connect.reticulum.network
|
||||||
target_port = 4965
|
target_port = 4242
|
||||||
<
|
<
|
||||||
|
|
||||||
If you connect to the testnet, you can leave nomadnet running for a while and wait for it to receive announces from other nodes on the network that host pages or services, or you can try connecting directly to some nodes listed here:
|
If you connect to the testnet, you can leave nomadnet running for a while and wait for it to receive announces from other nodes on the network that host pages or services, or you can try connecting directly to some nodes listed here:
|
||||||
|
|
||||||
- Dublin Hub Testnet Node : `!`[464ddc8cd323648ef919a638923b4916]`!
|
- Dublin Hub Testnet Node : `!`[abb3ebcd03cb2388a838e70c001291f9]`!
|
||||||
- Frankfurt Hub Testnet Node : `!`[5cda089fc42675bdd904c6d06db87d49]`!
|
- Frankfurt Hub Testnet Node : `!`[ea6a715f814bdc37e56f80c34da6ad51]`!
|
||||||
|
|
||||||
To browse pages on a node that is not currently known, open the URL dialog in the `![ Network ]`! section of the program by pressing `!Ctrl+U`!, paste or enter the address and select `!< Go >`! or press enter. Nomadnet will attempt to discover and connect to the requested node. You can save the currently connected node by pressing `!Ctrl+S`!.
|
To browse pages on a node that is not currently known, open the URL dialog in the `![ Network ]`! section of the program by pressing `!Ctrl+U`!, paste or enter the address and select `!< Go >`! or press enter. Nomadnet will attempt to discover and connect to the requested node. You can save the currently connected node by pressing `!Ctrl+S`!.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -480,7 +480,11 @@ def make_output(state, line, url_delegate):
|
|||||||
|
|
||||||
elif mode == "text":
|
elif mode == "text":
|
||||||
if c == "\\":
|
if c == "\\":
|
||||||
escape = True
|
if escape:
|
||||||
|
part += c
|
||||||
|
escape = False
|
||||||
|
else:
|
||||||
|
escape = True
|
||||||
elif c == "`":
|
elif c == "`":
|
||||||
if escape:
|
if escape:
|
||||||
part += c
|
part += c
|
||||||
|
|||||||
@@ -71,11 +71,17 @@ class AnnounceInfo(urwid.WidgetWrap):
|
|||||||
trust_str = ""
|
trust_str = ""
|
||||||
display_str = self.app.directory.simplest_display_str(source_hash)
|
display_str = self.app.directory.simplest_display_str(source_hash)
|
||||||
addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||||
is_node = announce[3]
|
info_type = announce[3]
|
||||||
|
|
||||||
if is_node:
|
is_node = False
|
||||||
type_string = "Node " + g["node"]
|
is_pn = False
|
||||||
else:
|
if info_type == "node" or info_type == True:
|
||||||
|
type_string = "Nomad Network Node " + g["node"]
|
||||||
|
is_node = True
|
||||||
|
elif info_type == "pn":
|
||||||
|
type_string = "LXMF Propagation Node " + g["sent"]
|
||||||
|
is_pn = True
|
||||||
|
elif info_type == "peer" or info_type == False:
|
||||||
type_string = "Peer " + g["peer"]
|
type_string = "Peer " + g["peer"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -174,10 +180,20 @@ class AnnounceInfo(urwid.WidgetWrap):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while starting conversation from announce. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while starting conversation from announce. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
def use_pn(sender):
|
||||||
|
show_announce_stream(None)
|
||||||
|
try:
|
||||||
|
self.app.set_user_selected_propagation_node(source_hash)
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while setting active propagation node from announce. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
if is_node:
|
if is_node:
|
||||||
type_button = ("weight", 0.45, urwid.Button("Connect", on_press=connect))
|
type_button = ("weight", 0.45, urwid.Button("Connect", on_press=connect))
|
||||||
msg_button = ("weight", 0.45, urwid.Button("Msg Op", on_press=msg_op))
|
msg_button = ("weight", 0.45, urwid.Button("Msg Op", on_press=msg_op))
|
||||||
save_button = ("weight", 0.45, urwid.Button("Save", on_press=save_node))
|
save_button = ("weight", 0.45, urwid.Button("Save", on_press=save_node))
|
||||||
|
elif is_pn:
|
||||||
|
type_button = ("weight", 0.45, urwid.Button("Use as default", on_press=use_pn))
|
||||||
|
save_button = None
|
||||||
else:
|
else:
|
||||||
type_button = ("weight", 0.45, urwid.Button("Converse", on_press=converse))
|
type_button = ("weight", 0.45, urwid.Button("Converse", on_press=converse))
|
||||||
save_button = None
|
save_button = None
|
||||||
@@ -187,21 +203,33 @@ class AnnounceInfo(urwid.WidgetWrap):
|
|||||||
else:
|
else:
|
||||||
button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_announce_stream)), ("weight", 0.1, urwid.Text("")), type_button])
|
button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_announce_stream)), ("weight", 0.1, urwid.Text("")), type_button])
|
||||||
|
|
||||||
pile_widgets = [
|
pile_widgets = []
|
||||||
urwid.Text("Time : "+ts_string, align="left"),
|
|
||||||
urwid.Text("Addr : "+addr_str, align="left"),
|
|
||||||
urwid.Text("Type : "+type_string, align="left"),
|
|
||||||
urwid.Text("Name : "+display_str, align="left"),
|
|
||||||
urwid.Text(["Trust : ", (style, trust_str)], align="left"),
|
|
||||||
urwid.Divider(g["divider1"]),
|
|
||||||
urwid.Text(["Announce Data: \n", (data_style, data_str)], align="left"),
|
|
||||||
urwid.Divider(g["divider1"]),
|
|
||||||
button_columns
|
|
||||||
]
|
|
||||||
|
|
||||||
if is_node:
|
if is_pn:
|
||||||
operator_entry = urwid.Text("Oprtr : "+op_str, align="left")
|
pile_widgets = [
|
||||||
pile_widgets.insert(4, operator_entry)
|
urwid.Text("Time : "+ts_string, align="left"),
|
||||||
|
urwid.Text("Addr : "+addr_str, align="left"),
|
||||||
|
urwid.Text("Type : "+type_string, align="left"),
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
button_columns
|
||||||
|
]
|
||||||
|
|
||||||
|
else:
|
||||||
|
pile_widgets = [
|
||||||
|
urwid.Text("Time : "+ts_string, align="left"),
|
||||||
|
urwid.Text("Addr : "+addr_str, align="left"),
|
||||||
|
urwid.Text("Type : "+type_string, align="left"),
|
||||||
|
urwid.Text("Name : "+display_str, align="left"),
|
||||||
|
urwid.Text(["Trust : ", (style, trust_str)], align="left"),
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
urwid.Text(["Announce Data: \n", (data_style, data_str)], align="left"),
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
button_columns
|
||||||
|
]
|
||||||
|
|
||||||
|
if is_node:
|
||||||
|
operator_entry = urwid.Text("Oprtr : "+op_str, align="left")
|
||||||
|
pile_widgets.insert(4, operator_entry)
|
||||||
|
|
||||||
pile = urwid.Pile(pile_widgets)
|
pile = urwid.Pile(pile_widgets)
|
||||||
|
|
||||||
@@ -220,7 +248,7 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
|
|||||||
|
|
||||||
timestamp = announce[0]
|
timestamp = announce[0]
|
||||||
source_hash = announce[1]
|
source_hash = announce[1]
|
||||||
is_node = announce[3]
|
announce_type = announce[3]
|
||||||
self.app = app
|
self.app = app
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
time_format = app.time_format
|
time_format = app.time_format
|
||||||
@@ -257,10 +285,12 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
|
|||||||
style = "list_untrusted"
|
style = "list_untrusted"
|
||||||
focus_style = "list_focus_untrusted"
|
focus_style = "list_focus_untrusted"
|
||||||
|
|
||||||
if is_node:
|
if announce_type == "node" or announce_type == True:
|
||||||
type_symbol = g["node"]
|
type_symbol = g["node"]
|
||||||
else:
|
elif announce_type == "peer" or announce_type == False:
|
||||||
type_symbol = g["peer"]
|
type_symbol = g["peer"]
|
||||||
|
elif announce_type == "pn":
|
||||||
|
type_symbol = g["sent"]
|
||||||
|
|
||||||
widget = ListEntry(ts_string+" "+type_symbol+" "+display_str)
|
widget = ListEntry(ts_string+" "+type_symbol+" "+display_str)
|
||||||
urwid.connect_signal(widget, "click", self.display_announce, announce)
|
urwid.connect_signal(widget, "click", self.display_announce, announce)
|
||||||
@@ -425,13 +455,15 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
if display_str == None:
|
if display_str == None:
|
||||||
display_str = addr_str
|
display_str = addr_str
|
||||||
|
|
||||||
|
pn_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_ident)
|
||||||
|
|
||||||
if node_ident != None:
|
if node_ident != None:
|
||||||
lxmf_addr_str = g["sent"]+" LXMF Propagation Node Address is "+RNS.prettyhexrep(RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_ident))
|
lxmf_addr_str = g["sent"]+" LXMF Propagation Node Address is "+RNS.prettyhexrep(pn_hash)
|
||||||
else:
|
else:
|
||||||
lxmf_addr_str = "No associated Propagation Node known"
|
lxmf_addr_str = "No associated Propagation Node known"
|
||||||
|
|
||||||
|
|
||||||
type_string = "Node " + g["node"]
|
type_string = "Nomad Network Node " + g["node"]
|
||||||
|
|
||||||
if trust_level == DirectoryEntry.UNTRUSTED:
|
if trust_level == DirectoryEntry.UNTRUSTED:
|
||||||
trust_str = "Untrusted"
|
trust_str = "Untrusted"
|
||||||
@@ -525,7 +557,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
def save_node(sender):
|
def save_node(sender):
|
||||||
if self.pn_changed:
|
if self.pn_changed:
|
||||||
if propagation_node_checkbox.get_state():
|
if propagation_node_checkbox.get_state():
|
||||||
self.app.set_user_selected_propagation_node(source_hash)
|
self.app.set_user_selected_propagation_node(pn_hash)
|
||||||
else:
|
else:
|
||||||
self.app.set_user_selected_propagation_node(None)
|
self.app.set_user_selected_propagation_node(None)
|
||||||
|
|
||||||
@@ -862,7 +894,7 @@ class NodeActiveConnections(urwid.WidgetWrap):
|
|||||||
if self.app.node != None:
|
if self.app.node != None:
|
||||||
self.stat_string = str(len(self.app.node.destination.links))
|
self.stat_string = str(len(self.app.node.destination.links))
|
||||||
|
|
||||||
self.display_widget.set_text("Conneced Now : "+self.stat_string)
|
self.display_widget.set_text("Connected Now : "+self.stat_string)
|
||||||
|
|
||||||
def update_stat_callback(self, loop=None, user_data=None):
|
def update_stat_callback(self, loop=None, user_data=None):
|
||||||
self.update_stat()
|
self.update_stat()
|
||||||
@@ -1572,7 +1604,14 @@ class LXMFPeerEntry(urwid.WidgetWrap):
|
|||||||
style = "list_unknown"
|
style = "list_unknown"
|
||||||
focus_style = "list_focus"
|
focus_style = "list_focus"
|
||||||
|
|
||||||
widget = ListEntry(sym+" "+display_str+"\n "+str(len(peer.unhandled_messages))+" unhandled LXMs - "+"Last heard "+pretty_date(int(peer.last_heard)))
|
alive_string = "Unknown"
|
||||||
|
if hasattr(peer, "alive"):
|
||||||
|
if peer.alive:
|
||||||
|
alive_string = "Available"
|
||||||
|
else:
|
||||||
|
alive_string = "Unresponsive"
|
||||||
|
|
||||||
|
widget = ListEntry(sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard))+"\n "+str(len(peer.unhandled_messages))+" unhandled LXMs")
|
||||||
# urwid.connect_signal(widget, "click", delegate.connect_node, node)
|
# urwid.connect_signal(widget, "click", delegate.connect_node, node)
|
||||||
|
|
||||||
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -23,6 +23,6 @@ setuptools.setup(
|
|||||||
entry_points= {
|
entry_points= {
|
||||||
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
||||||
},
|
},
|
||||||
install_requires=['rns>=0.3.12', 'lxmf>=0.1.8', 'urwid>=2.1.2'],
|
install_requires=["rns>=0.4.7", "lxmf>=0.2.9", "urwid>=2.1.2", "qrcode"],
|
||||||
python_requires='>=3.6',
|
python_requires=">=3.6",
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user