Compare commits

..

12 Commits

Author SHA1 Message Date
ba47e16b75 Rename package from reticulum-meshchat to reticulum-meshchatx in package-lock.json 2025-11-30 21:30:32 -06:00
578e80023f remove 2025-11-30 21:29:04 -06:00
b7dcee4c06 Update 2025-11-30 21:28:59 -06:00
e44ec59b6e Rename reticulum-meshchat service to reticulum-meshchatx and update image reference in docker-compose.yml 2025-11-30 21:28:46 -06:00
45379e6df1 update version and name 2025-11-30 21:28:39 -06:00
308f1f6459 update 2025-11-30 21:28:31 -06:00
424ff116d1 Merge pull request #20 from Sudo-Ivan/deepsource-autofix-29fa619a
refactor: change methods not using its bound instance to staticmethods
2025-11-30 21:23:46 -06:00
deepsource-autofix[bot]
73f677d319 refactor: change methods not using its bound instance to staticmethods
The method doesn't use its bound instance. Decorate this method with `@staticmethod` decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods [here](https://docs.python.org/3/library/functions.html#staticmethod).
2025-12-01 03:22:30 +00:00
4770c21499 update to add manual trigger 2025-11-30 21:18:49 -06:00
720bef90c7 remove old workflow 2025-11-30 21:18:42 -06:00
1c98a231fd Refactor ReticulumMeshChat methods to static
- Updated several instance methods in ReticulumMeshChat to static methods for improved clarity and usability.
- Adjusted method calls to reflect the new static context, enhancing code organization.
2025-11-30 21:17:09 -06:00
f6a1be5e80 Replace backend build script in package.json with a Node.js script for improved compatibility and maintainability. Added new build-backend.js script to handle the backend build process using Python. 2025-11-30 21:16:49 -06:00
9 changed files with 118 additions and 125 deletions

View File

@@ -4,10 +4,33 @@ on:
push:
tags:
- "*"
workflow_dispatch:
inputs:
build_windows:
description: 'Build Windows'
required: false
default: 'true'
type: boolean
build_mac:
description: 'Build macOS'
required: false
default: 'true'
type: boolean
build_linux:
description: 'Build Linux'
required: false
default: 'true'
type: boolean
build_docker:
description: 'Build Docker'
required: false
default: 'true'
type: boolean
jobs:
build_windows:
runs-on: windows-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_windows == 'true')
permissions:
contents: write
steps:
@@ -49,6 +72,7 @@ jobs:
build_mac:
runs-on: macos-13
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_mac == 'true')
permissions:
contents: write
steps:
@@ -90,6 +114,7 @@ jobs:
build_linux:
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_linux == 'true')
permissions:
contents: write
steps:
@@ -134,6 +159,7 @@ jobs:
build_docker:
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_docker == 'true')
permissions:
packages: write
contents: read
@@ -164,9 +190,9 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: >-
ghcr.io/${{ env.REPO_OWNER_LC }}/reticulum-meshchat:latest,
ghcr.io/${{ env.REPO_OWNER_LC }}/reticulum-meshchat:${{ github.ref_name }}
ghcr.io/${{ env.REPO_OWNER_LC }}/reticulum-meshchatx:latest,
ghcr.io/${{ env.REPO_OWNER_LC }}/reticulum-meshchatx:${{ github.ref_name }}
labels: >-
org.opencontainers.image.title=Reticulum MeshChat,
org.opencontainers.image.description=Docker image for Reticulum MeshChat,
org.opencontainers.image.url=https://github.com/${{ github.repository }}/pkgs/container/reticulum-meshchat/
org.opencontainers.image.title=Reticulum MeshChatX,
org.opencontainers.image.description=Docker image for Reticulum MeshChatX,
org.opencontainers.image.url=https://github.com/${{ github.repository }}/pkgs/container/reticulum-meshchatx/

View File

@@ -1,45 +0,0 @@
name: Temporary manual trigger for Docker build
on:
workflow_dispatch:
jobs:
build_docker:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Clone Repo
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Set lowercase repository owner
run: echo "REPO_OWNER_LC=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
- name: Log in to the GitHub Container registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker images
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: >-
ghcr.io/${{ env.REPO_OWNER_LC }}/reticulum-meshchat:latest,
ghcr.io/${{ env.REPO_OWNER_LC }}/reticulum-meshchat:${{ github.ref_name }}
labels: >-
org.opencontainers.image.title=Reticulum MeshChat,
org.opencontainers.image.description=Docker image for Reticulum MeshChat,
org.opencontainers.image.url=https://github.com/${{ github.repository }}/pkgs/container/reticulum-meshchat/

View File

@@ -16,7 +16,8 @@ A heavily customized fork of [Reticulum MeshChat](https://github.com/liamcottle/
- [x] Docker images are smaller and use SHA256 hashes for the images.
- [x] Electron improvements.
- [x] Latest updates for NPM and Python dependencies (bleeding edge)
- [x] Ruff linting, CodeQL Advanced and Bearer SAST fixes.
- [x] Numerous Ruff, Deepsource, CodeQL Advanced and Bearer Linting/SAST fixes.
- [x] Some performance improvements.
## Usage

31
TODO.md
View File

@@ -1,31 +0,0 @@
1. for messages fix:
convo goes off edge, near edge should be ... 3 dots
long names push the last message/announced seconds/time to right and nearly off the side, fix please
2. interfaces:
3 dots background circle is a oval, fix to be circle
on 3 dots clicked there is still white background the buttons have dark backgrounds though but main dropdown window is white fix depdning on theme
also on 3 dots drop down it still makes me scroll down in that interfaces window, we can expand that interfaces box os something so this crap doesnt hapen or if dropdown is above it
rework propagation nodes page with new UI/UX please like rest of app.
1. the attachment dropups/popups are white on dark mode, they need a ui/ux rework.
2. for settings add ability to set inbound stamp, ref lxmf via python -c if needed.
3. add multi-identity / account suport and a switcher at bottom with ability to create, delete or import/export identies from other apps.
for all this you will likely need to look at my ren chat app for stamps, multi-identity, /mnt/projects/ren-messenger/
its pretty simple.
translator tool
reticulum documentation tool
lxmfy bot tool
page downloader tool
page snapshots

View File

@@ -1,7 +1,7 @@
services:
reticulum-meshchat:
container_name: reticulum-meshchat
image: ghcr.io/sudo-ivan/reticulum-meshchat:latest
reticulum-meshchatx:
container_name: reticulum-meshchatx
image: ghcr.io/sudo-ivan/reticulum-meshchatx:latest
pull_policy: always
restart: unless-stopped
# Make the meshchat web interface accessible from the host on port 8000

View File

@@ -221,7 +221,8 @@ class ReticulumMeshChat:
thread.start()
# gets app version from package.json
def get_app_version(self) -> str:
@staticmethod
def get_app_version() -> str:
with open(get_file_path("package.json")) as f:
package_json = json.load(f)
return package_json["version"]
@@ -447,7 +448,8 @@ class ReticulumMeshChat:
)
return query.exists()
def message_fields_have_attachments(self, fields_json: str | None):
@staticmethod
def message_fields_have_attachments(fields_json: str | None):
if not fields_json:
return False
try:
@@ -491,7 +493,8 @@ class ReticulumMeshChat:
return matches
def parse_bool_query_param(self, value: str | None) -> bool:
@staticmethod
def parse_bool_query_param(value: str | None) -> bool:
if value is None:
return False
value = value.lower()
@@ -1986,14 +1989,14 @@ class ReticulumMeshChat:
nomadnetwork_node_announce is not None
and nomadnetwork_node_announce.app_data is not None
):
operator_display_name = self.parse_nomadnetwork_node_display_name(
operator_display_name = ReticulumMeshChat.parse_nomadnetwork_node_display_name(
nomadnetwork_node_announce.app_data, None,
)
# parse app_data so we can see if propagation is enabled or disabled for this node
is_propagation_enabled = None
per_transfer_limit = None
propagation_node_data = self.parse_lxmf_propagation_node_app_data(
propagation_node_data = ReticulumMeshChat.parse_lxmf_propagation_node_app_data(
announce.app_data,
)
if propagation_node_data is not None:
@@ -2753,8 +2756,8 @@ class ReticulumMeshChat:
other_user_hash,
),
"destination_hash": other_user_hash,
"is_unread": self.is_lxmf_conversation_unread(other_user_hash),
"failed_messages_count": self.lxmf_conversation_failed_messages_count(
"is_unread": ReticulumMeshChat.is_lxmf_conversation_unread(other_user_hash),
"failed_messages_count": ReticulumMeshChat.lxmf_conversation_failed_messages_count(
other_user_hash,
),
"has_attachments": has_attachments,
@@ -3167,7 +3170,8 @@ class ReticulumMeshChat:
# to the following map:
# - var_field1: 123
# - var_field2: 456
def convert_nomadnet_string_data_to_map(self, path_data: str | None):
@staticmethod
def convert_nomadnet_string_data_to_map(path_data: str | None):
data = {}
if path_data is not None:
for field in path_data.split("|"):
@@ -3178,7 +3182,8 @@ class ReticulumMeshChat:
print(f"unhandled field: {field}")
return data
def convert_nomadnet_field_data_to_map(self, field_data):
@staticmethod
def convert_nomadnet_field_data_to_map(field_data):
data = {}
if field_data is not None or "{}":
try:
@@ -3681,7 +3686,8 @@ class ReticulumMeshChat:
}
# convert lxmf state to a human friendly string
def convert_lxmf_state_to_string(self, lxmf_message: LXMF.LXMessage):
@staticmethod
def convert_lxmf_state_to_string(lxmf_message: LXMF.LXMessage):
# convert state to string
lxmf_message_state = "unknown"
if lxmf_message.state == LXMF.LXMessage.GENERATING:
@@ -3704,7 +3710,8 @@ class ReticulumMeshChat:
return lxmf_message_state
# convert lxmf method to a human friendly string
def convert_lxmf_method_to_string(self, lxmf_message: LXMF.LXMessage):
@staticmethod
def convert_lxmf_method_to_string(lxmf_message: LXMF.LXMessage):
# convert method to string
lxmf_message_method = "unknown"
if lxmf_message.method == LXMF.LXMessage.OPPORTUNISTIC:
@@ -3718,7 +3725,8 @@ class ReticulumMeshChat:
return lxmf_message_method
def convert_propagation_node_state_to_string(self, state):
@staticmethod
def convert_propagation_node_state_to_string(state):
# map states to strings
state_map = {
LXMRouter.PR_IDLE: "idle",
@@ -3749,7 +3757,7 @@ class ReticulumMeshChat:
if announce.aspect == "lxmf.delivery":
display_name = self.parse_lxmf_display_name(announce.app_data)
elif announce.aspect == "nomadnetwork.node":
display_name = self.parse_nomadnetwork_node_display_name(announce.app_data)
display_name = ReticulumMeshChat.parse_nomadnetwork_node_display_name(announce.app_data)
# find lxmf user icon from database
lxmf_user_icon = None
@@ -3787,7 +3795,8 @@ class ReticulumMeshChat:
}
# convert database favourite to a dictionary
def convert_db_favourite_to_dict(self, favourite: database.FavouriteDestination):
@staticmethod
def convert_db_favourite_to_dict(favourite: database.FavouriteDestination):
return {
"id": favourite.id,
"destination_hash": favourite.destination_hash,
@@ -3798,7 +3807,8 @@ class ReticulumMeshChat:
}
# convert database lxmf message to a dictionary
def convert_db_lxmf_message_to_dict(self, db_lxmf_message: database.LxmfMessage):
@staticmethod
def convert_db_lxmf_message_to_dict(db_lxmf_message: database.LxmfMessage):
return {
"id": db_lxmf_message.id,
"hash": db_lxmf_message.hash,
@@ -3823,8 +3833,8 @@ class ReticulumMeshChat:
}
# updates the lxmf user icon for the provided destination hash
@staticmethod
def update_lxmf_user_icon(
self,
destination_hash: str,
icon_name: str,
foreground_colour: str,
@@ -3852,7 +3862,8 @@ class ReticulumMeshChat:
query.execute()
# check if a destination is blocked
def is_destination_blocked(self, destination_hash: str) -> bool:
@staticmethod
def is_destination_blocked(destination_hash: str) -> bool:
try:
blocked = database.BlockedDestination.get_or_none(
database.BlockedDestination.destination_hash == destination_hash,
@@ -3862,7 +3873,8 @@ class ReticulumMeshChat:
return False
# check if message content matches spam keywords
def check_spam_keywords(self, title: str, content: str) -> bool:
@staticmethod
def check_spam_keywords(title: str, content: str) -> bool:
try:
spam_keywords = database.SpamKeyword.select()
search_text = (title + " " + content).lower()
@@ -3874,7 +3886,8 @@ class ReticulumMeshChat:
return False
# check if message has attachments and should be rejected
def has_attachments(self, lxmf_fields: dict) -> bool:
@staticmethod
def has_attachments(lxmf_fields: dict) -> bool:
try:
if LXMF.FIELD_FILE_ATTACHMENTS in lxmf_fields:
return len(lxmf_fields[LXMF.FIELD_FILE_ATTACHMENTS]) > 0
@@ -4108,8 +4121,9 @@ class ReticulumMeshChat:
query.execute()
# upserts a custom destination display name to the database
@staticmethod
def db_upsert_custom_destination_display_name(
self, destination_hash: str, display_name: str,
destination_hash: str, display_name: str,
):
# prepare data to insert or update
data = {
@@ -4127,8 +4141,9 @@ class ReticulumMeshChat:
query.execute()
# upserts a custom destination display name to the database
@staticmethod
def db_upsert_favourite(
self, destination_hash: str, display_name: str, aspect: str,
destination_hash: str, display_name: str, aspect: str,
):
# prepare data to insert or update
data = {
@@ -4147,7 +4162,8 @@ class ReticulumMeshChat:
query.execute()
# upserts lxmf conversation read state to the database
def db_mark_lxmf_conversation_as_read(self, destination_hash: str):
@staticmethod
def db_mark_lxmf_conversation_as_read(destination_hash: str):
# prepare data to insert or update
data = {
"destination_hash": destination_hash,
@@ -4633,7 +4649,8 @@ class ReticulumMeshChat:
)
# gets the custom display name a user has set for the provided destination hash
def get_custom_destination_display_name(self, destination_hash: str):
@staticmethod
def get_custom_destination_display_name(destination_hash: str):
# get display name from database
db_destination_display_name = database.CustomDestinationDisplayName.get_or_none(
database.CustomDestinationDisplayName.destination_hash == destination_hash,
@@ -4646,7 +4663,8 @@ class ReticulumMeshChat:
# get name to show for an lxmf conversation
# currently, this will use the app data from the most recent announce
# TODO: we should fetch this from our contacts database, when it gets implemented, and if not found, fallback to app data
def get_lxmf_conversation_name(self, destination_hash):
@staticmethod
def get_lxmf_conversation_name(destination_hash):
# get lxmf.delivery announce from database for the provided destination hash
lxmf_announce = (
database.Announce.select()
@@ -4658,14 +4676,15 @@ class ReticulumMeshChat:
# if app data is available in database, it should be base64 encoded text that was announced
# we will return the parsed lxmf display name as the conversation name
if lxmf_announce is not None and lxmf_announce.app_data is not None:
return self.parse_lxmf_display_name(app_data_base64=lxmf_announce.app_data)
return ReticulumMeshChat.parse_lxmf_display_name(app_data_base64=lxmf_announce.app_data)
# announce did not have app data, so provide a fallback name
return "Anonymous Peer"
# reads the lxmf display name from the provided base64 app data
@staticmethod
def parse_lxmf_display_name(
self, app_data_base64: str, default_value: str | None = "Anonymous Peer",
app_data_base64: str, default_value: str | None = "Anonymous Peer",
):
try:
app_data_bytes = base64.b64decode(app_data_base64)
@@ -4678,7 +4697,8 @@ class ReticulumMeshChat:
return default_value
# reads the lxmf stamp cost from the provided base64 app data
def parse_lxmf_stamp_cost(self, app_data_base64: str):
@staticmethod
def parse_lxmf_stamp_cost(app_data_base64: str):
try:
app_data_bytes = base64.b64decode(app_data_base64)
return LXMF.stamp_cost_from_app_data(app_data_bytes)
@@ -4686,8 +4706,9 @@ class ReticulumMeshChat:
return None
# reads the nomadnetwork node display name from the provided base64 app data
@staticmethod
def parse_nomadnetwork_node_display_name(
self, app_data_base64: str, default_value: str | None = "Anonymous Node",
app_data_base64: str, default_value: str | None = "Anonymous Node",
):
try:
app_data_bytes = base64.b64decode(app_data_base64)
@@ -4696,7 +4717,8 @@ class ReticulumMeshChat:
return default_value
# parses lxmf propagation node app data
def parse_lxmf_propagation_node_app_data(self, app_data_base64: str):
@staticmethod
def parse_lxmf_propagation_node_app_data(app_data_base64: str):
try:
app_data_bytes = base64.b64decode(app_data_base64)
data = msgpack.unpackb(app_data_bytes)
@@ -4709,7 +4731,8 @@ class ReticulumMeshChat:
return None
# returns true if the conversation has messages newer than the last read at timestamp
def is_lxmf_conversation_unread(self, destination_hash):
@staticmethod
def is_lxmf_conversation_unread(destination_hash):
# get lxmf conversation read state from database for the provided destination hash
lxmf_conversation_read_state = (
database.LxmfConversationReadState.select()
@@ -4745,7 +4768,8 @@ class ReticulumMeshChat:
return conversation_last_read_at < conversation_latest_message_at
# returns number of messages that failed to send in a conversation
def lxmf_conversation_failed_messages_count(self, destination_hash: str):
@staticmethod
def lxmf_conversation_failed_messages_count(destination_hash: str):
return (
database.LxmfMessage.select()
.where(database.LxmfMessage.state == "failed")
@@ -4754,7 +4778,8 @@ class ReticulumMeshChat:
)
# find an interface by name
def find_interface_by_name(self, name: str):
@staticmethod
def find_interface_by_name(name: str):
for interface in RNS.Transport.interfaces:
interface_name = str(interface)
if name == interface_name:

9
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "reticulum-meshchat",
"version": "2.32.3",
"name": "reticulum-meshchatx",
"version": "2.41.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "reticulum-meshchat",
"version": "2.32.3",
"name": "reticulum-meshchatx",
"version": "2.41.0",
"license": "MIT",
"dependencies": {
"@mdi/js": "^7.4.47",
@@ -3025,7 +3025,6 @@
"integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"app-builder-lib": "24.13.3",
"builder-util": "24.13.1",

View File

@@ -1,13 +1,13 @@
{
"name": "reticulum-meshchat",
"version": "2.32.3",
"name": "reticulum-meshchatx",
"version": "2.41.0",
"description": "A simple mesh network communications app powered by the Reticulum Network Stack",
"author": "Sudo-Ivan",
"main": "electron/main.js",
"scripts": {
"watch": "npm run build-frontend -- --watch",
"build-frontend": "vite build",
"build-backend": "venv/bin/python setup.py build",
"build-backend": "node scripts/build-backend.js",
"build": "npm run build-frontend && npm run build-backend",
"electron-postinstall": "electron-builder install-app-deps",
"electron": "npm run electron-postinstall && npm run build && electron .",

18
scripts/build-backend.js Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
const os = require('os');
const platform = os.platform();
const venvPython = platform === 'win32'
? path.join('venv', 'Scripts', 'python.exe')
: path.join('venv', 'bin', 'python');
try {
execSync(`${venvPython} setup.py build`, { stdio: 'inherit' });
} catch (error) {
console.error('Build failed:', error.message);
process.exit(1);
}