codebase restructure and organization.
@@ -3,24 +3,35 @@ README.md
|
|||||||
LICENSE
|
LICENSE
|
||||||
donate.md
|
donate.md
|
||||||
screenshots/
|
screenshots/
|
||||||
|
docs/
|
||||||
|
|
||||||
# Development files
|
# Development files
|
||||||
.github/
|
.github/
|
||||||
electron/
|
electron/
|
||||||
|
scripts/
|
||||||
|
Makefile
|
||||||
|
|
||||||
# Build artifacts and cache
|
# Build artifacts and cache
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
public/
|
public/
|
||||||
node_modules/
|
node_modules/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.py[cod]
|
||||||
*.pyo
|
*$py.class
|
||||||
*.pyd
|
*.so
|
||||||
.Python
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
*.egg
|
||||||
|
python-dist/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
.venv/
|
||||||
|
|
||||||
# IDE and editor files
|
# IDE and editor files
|
||||||
.vscode/
|
.vscode/
|
||||||
@@ -47,9 +58,19 @@ Dockerfile*
|
|||||||
docker-compose*.yml
|
docker-compose*.yml
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
|
||||||
|
# Local storage and runtime data
|
||||||
|
storage/
|
||||||
|
testing/
|
||||||
|
telemetry_test_lxmf/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
40
.github/workflows/build.yml
vendored
@@ -47,11 +47,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
run: python -m pip install --upgrade pip poetry
|
||||||
|
|
||||||
|
- name: Sync versions
|
||||||
|
run: python scripts/sync_version.py
|
||||||
|
|
||||||
- name: Install Python Deps
|
- name: Install Python Deps
|
||||||
run: |
|
run: python -m poetry install
|
||||||
python -m venv venv
|
|
||||||
venv\Scripts\pip install --upgrade pip
|
|
||||||
venv\Scripts\pip install -r requirements.txt
|
|
||||||
|
|
||||||
- name: Install NodeJS Deps
|
- name: Install NodeJS Deps
|
||||||
run: npm install
|
run: npm install
|
||||||
@@ -89,11 +92,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
run: python -m pip install --upgrade pip poetry
|
||||||
|
|
||||||
|
- name: Sync versions
|
||||||
|
run: python scripts/sync_version.py
|
||||||
|
|
||||||
- name: Install Python Deps
|
- name: Install Python Deps
|
||||||
run: |
|
run: python -m poetry install
|
||||||
python3 -m venv venv
|
|
||||||
venv/bin/pip install --upgrade pip
|
|
||||||
venv/bin/pip install -r requirements.txt
|
|
||||||
|
|
||||||
- name: Install NodeJS Deps
|
- name: Install NodeJS Deps
|
||||||
run: npm install
|
run: npm install
|
||||||
@@ -134,11 +140,21 @@ jobs:
|
|||||||
- name: Install patchelf
|
- name: Install patchelf
|
||||||
run: sudo apt-get update && sudo apt-get install -y patchelf
|
run: sudo apt-get update && sudo apt-get install -y patchelf
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
run: python -m pip install --upgrade pip poetry
|
||||||
|
|
||||||
|
- name: Sync versions
|
||||||
|
run: python scripts/sync_version.py
|
||||||
|
|
||||||
- name: Install Python Deps
|
- name: Install Python Deps
|
||||||
|
run: python -m poetry install
|
||||||
|
|
||||||
|
- name: Build Python wheel
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv venv
|
python -m poetry build -f wheel
|
||||||
venv/bin/pip install --upgrade pip
|
mkdir -p python-dist
|
||||||
venv/bin/pip install -r requirements.txt
|
mv dist/*.whl python-dist/
|
||||||
|
rm -rf dist
|
||||||
|
|
||||||
- name: Install NodeJS Deps
|
- name: Install NodeJS Deps
|
||||||
run: npm install
|
run: npm install
|
||||||
@@ -155,7 +171,7 @@ jobs:
|
|||||||
replacesArtifacts: true
|
replacesArtifacts: true
|
||||||
omitDraftDuringUpdate: true
|
omitDraftDuringUpdate: true
|
||||||
omitNameDuringUpdate: true
|
omitNameDuringUpdate: true
|
||||||
artifacts: "dist/*-linux.AppImage,dist/*-linux.deb"
|
artifacts: "dist/*-linux.AppImage,dist/*-linux.deb,python-dist/*.whl"
|
||||||
|
|
||||||
build_docker:
|
build_docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
53
.gitignore
vendored
@@ -1,13 +1,56 @@
|
|||||||
|
# IDE and editor files
|
||||||
.idea
|
.idea
|
||||||
node_modules
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
# build files
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Build files
|
||||||
/build/
|
/build/
|
||||||
/dist/
|
/dist/
|
||||||
/public/
|
/meshchatx/public/
|
||||||
/electron/build/exe/
|
/electron/build/exe/
|
||||||
|
python-dist/
|
||||||
|
|
||||||
# local storage
|
# Local storage and runtime data
|
||||||
storage/
|
storage/
|
||||||
|
testing/
|
||||||
|
telemetry_test_lxmf/
|
||||||
|
|
||||||
*.pyc
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
31
Makefile
@@ -1,26 +1,29 @@
|
|||||||
.PHONY: install run clean build build-appimage build-exe dist
|
.PHONY: install run develop clean build build-appimage build-exe dist sync-version wheel node_modules python
|
||||||
|
|
||||||
VENV = venv
|
PYTHON ?= python
|
||||||
PYTHON = $(VENV)/bin/python
|
POETRY = $(PYTHON) -m poetry
|
||||||
PIP = $(VENV)/bin/pip
|
|
||||||
NPM = npm
|
NPM = npm
|
||||||
|
|
||||||
install: $(VENV) node_modules
|
install: sync-version node_modules python
|
||||||
|
|
||||||
$(VENV):
|
|
||||||
python3 -m venv $(VENV)
|
|
||||||
$(PIP) install --upgrade pip
|
|
||||||
$(PIP) install -r requirements.txt
|
|
||||||
|
|
||||||
node_modules:
|
node_modules:
|
||||||
$(NPM) install
|
$(NPM) install
|
||||||
|
|
||||||
|
python:
|
||||||
|
$(POETRY) install
|
||||||
|
|
||||||
run: install
|
run: install
|
||||||
$(PYTHON) meshchat.py
|
$(POETRY) run meshchat
|
||||||
|
|
||||||
|
develop: run
|
||||||
|
|
||||||
build: install
|
build: install
|
||||||
$(NPM) run build
|
$(NPM) run build
|
||||||
|
|
||||||
|
wheel: install
|
||||||
|
$(POETRY) build -f wheel
|
||||||
|
$(PYTHON) scripts/move_wheels.py
|
||||||
|
|
||||||
build-appimage: build
|
build-appimage: build
|
||||||
$(NPM) run electron-postinstall
|
$(NPM) run electron-postinstall
|
||||||
$(NPM) run dist -- --linux AppImage
|
$(NPM) run dist -- --linux AppImage
|
||||||
@@ -32,10 +35,10 @@ build-exe: build
|
|||||||
dist: build-appimage
|
dist: build-appimage
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(VENV)
|
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
rm -rf build
|
rm -rf build
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
|
rm -rf python-dist
|
||||||
|
|
||||||
|
sync-version:
|
||||||
|
$(PYTHON) scripts/sync_version.py
|
||||||
|
|||||||
27
README.md
@@ -14,12 +14,12 @@ A heavily customized fork of [Reticulum MeshChat](https://github.com/liamcottle/
|
|||||||
- [ ] Multi-language support
|
- [ ] Multi-language support
|
||||||
- [ ] Offline Reticulum documentation tool
|
- [ ] Offline Reticulum documentation tool
|
||||||
- [ ] More tools (translate, LoRa calculator, LXMFy bots, etc)
|
- [ ] More tools (translate, LoRa calculator, LXMFy bots, etc)
|
||||||
- [ ] Codebase reorginization and cleanup.
|
- [x] Codebase reorganization and cleanup.
|
||||||
- [ ] Tests and proper CI/CD pipeline.
|
- [ ] Tests and proper CI/CD pipeline.
|
||||||
- [ ] RNS hot reload
|
- [ ] RNS hot reload
|
||||||
- [ ] Backup/Import identities, messages and interfaces.
|
- [ ] Backup/Import identities, messages and interfaces.
|
||||||
- [ ] Full LXST support.
|
- [ ] Full LXST support.
|
||||||
- [ ] Move to Poetry and pyproject.toml for Python packaging.
|
- [x] Move to Poetry and pyproject.toml for Python packaging.
|
||||||
- [x] More stats on about page.
|
- [x] More stats on about page.
|
||||||
- [x] Actions are pinned to full-length SHA hashes.
|
- [x] Actions are pinned to full-length SHA hashes.
|
||||||
- [x] Docker images are smaller and use SHA256 hashes for the images.
|
- [x] Docker images are smaller and use SHA256 hashes for the images.
|
||||||
@@ -35,23 +35,30 @@ Check [releases](https://github.com/Sudo-Ivan/reticulum-meshchatX/releases) for
|
|||||||
## Building
|
## Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make install
|
make install # installs Python deps via Poetry and Node deps via npm
|
||||||
make build
|
make build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can run `make run` or `make develop` (a thin alias) to start the backend + frontend loop locally through `poetry run meshchat`.
|
||||||
|
|
||||||
|
### Python packaging
|
||||||
|
|
||||||
|
The Python build is driven entirely by Poetry now. Run `python scripts/sync_version.py` or `make sync-version` before packaging so `pyproject.toml` and `src/version.py` match `package.json`. After that:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m poetry install
|
||||||
|
make wheel # produces a wheel in python-dist/ that bundles the public assets
|
||||||
|
```
|
||||||
|
|
||||||
|
The wheel includes the frontend `public/` assets, `logo/`, and the CLI entry point, and `python-dist/` keeps the artifact separate from the Electron `dist/` output.
|
||||||
|
|
||||||
### Building in Docker
|
### Building in Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make docker-build
|
make docker-build
|
||||||
```
|
```
|
||||||
|
|
||||||
The build will be in the `dist` directory.
|
The Electron build artifacts will still live under `dist/` for releases.
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make develop
|
|
||||||
```
|
|
||||||
|
|
||||||
## Python packaging
|
## Python packaging
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,14 @@ app.whenReady().then(async () => {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
// used to inject logging over ipc
|
// used to inject logging over ipc
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
// Security: disable node integration in renderer
|
||||||
|
nodeIntegration: false,
|
||||||
|
// Security: enable context isolation (default in Electron 12+)
|
||||||
|
contextIsolation: true,
|
||||||
|
// Security: enable sandbox for additional protection
|
||||||
|
sandbox: true,
|
||||||
|
// Security: disable remote module (deprecated but explicit)
|
||||||
|
enableRemoteModule: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
3
meshchatx/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""Reticulum MeshChatX - A mesh network communications app."""
|
||||||
|
|
||||||
|
__version__ = "2.41.0"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from peewee import * # noqa: F403
|
from peewee import * # noqa: F403
|
||||||
from playhouse.migrate import SqliteMigrator
|
from playhouse.migrate import SqliteMigrator
|
||||||
@@ -68,8 +68,8 @@ class Config(BaseModel):
|
|||||||
id = BigAutoField() # noqa: F405
|
id = BigAutoField() # noqa: F405
|
||||||
key = CharField(unique=True) # noqa: F405
|
key = CharField(unique=True) # noqa: F405
|
||||||
value = TextField() # noqa: F405
|
value = TextField() # noqa: F405
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -95,8 +95,8 @@ class Announce(BaseModel):
|
|||||||
snr = FloatField(null=True) # noqa: F405
|
snr = FloatField(null=True) # noqa: F405
|
||||||
quality = FloatField(null=True) # noqa: F405
|
quality = FloatField(null=True) # noqa: F405
|
||||||
|
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -108,8 +108,8 @@ class CustomDestinationDisplayName(BaseModel):
|
|||||||
destination_hash = CharField(unique=True) # noqa: F405 # unique destination hash
|
destination_hash = CharField(unique=True) # noqa: F405 # unique destination hash
|
||||||
display_name = CharField() # noqa: F405 # custom display name for the destination hash
|
display_name = CharField() # noqa: F405 # custom display name for the destination hash
|
||||||
|
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -122,8 +122,8 @@ class FavouriteDestination(BaseModel):
|
|||||||
display_name = CharField() # noqa: F405 # custom display name for the destination hash
|
display_name = CharField() # noqa: F405 # custom display name for the destination hash
|
||||||
aspect = CharField() # noqa: F405 # e.g: nomadnetwork.node
|
aspect = CharField() # noqa: F405 # e.g: nomadnetwork.node
|
||||||
|
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -159,8 +159,8 @@ class LxmfMessage(BaseModel):
|
|||||||
snr = FloatField(null=True) # noqa: F405
|
snr = FloatField(null=True) # noqa: F405
|
||||||
quality = FloatField(null=True) # noqa: F405
|
quality = FloatField(null=True) # noqa: F405
|
||||||
is_spam = BooleanField(default=False) # noqa: F405 # if true, message is marked as spam
|
is_spam = BooleanField(default=False) # noqa: F405 # if true, message is marked as spam
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -172,8 +172,8 @@ class LxmfConversationReadState(BaseModel):
|
|||||||
destination_hash = CharField(unique=True) # noqa: F405 # unique destination hash
|
destination_hash = CharField(unique=True) # noqa: F405 # unique destination hash
|
||||||
last_read_at = DateTimeField() # noqa: F405
|
last_read_at = DateTimeField() # noqa: F405
|
||||||
|
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -189,8 +189,8 @@ class LxmfUserIcon(BaseModel):
|
|||||||
CharField() # noqa: F405
|
CharField() # noqa: F405
|
||||||
) # hex colour to use for background (background colour)
|
) # hex colour to use for background (background colour)
|
||||||
|
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -203,8 +203,8 @@ class BlockedDestination(BaseModel):
|
|||||||
unique=True,
|
unique=True,
|
||||||
index=True,
|
index=True,
|
||||||
) # unique destination hash that is blocked
|
) # unique destination hash that is blocked
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -217,8 +217,8 @@ class SpamKeyword(BaseModel):
|
|||||||
unique=True,
|
unique=True,
|
||||||
index=True,
|
index=True,
|
||||||
) # keyword to match against message content
|
) # keyword to match against message content
|
||||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
created_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc)) # noqa: F405
|
updated_at = DateTimeField(default=lambda: datetime.now(UTC)) # noqa: F405
|
||||||
|
|
||||||
# define table name
|
# define table name
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -13,7 +13,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import LXMF
|
import LXMF
|
||||||
import psutil
|
import psutil
|
||||||
@@ -24,20 +24,21 @@ from LXMF import LXMRouter
|
|||||||
from peewee import SqliteDatabase
|
from peewee import SqliteDatabase
|
||||||
from serial.tools import list_ports
|
from serial.tools import list_ports
|
||||||
|
|
||||||
import database
|
from meshchatx import database
|
||||||
from src.backend.announce_handler import AnnounceHandler
|
from meshchatx.src.backend.announce_handler import AnnounceHandler
|
||||||
from src.backend.async_utils import AsyncUtils
|
from meshchatx.src.backend.async_utils import AsyncUtils
|
||||||
from src.backend.audio_call_manager import AudioCall, AudioCallManager
|
from meshchatx.src.backend.audio_call_manager import AudioCall, AudioCallManager
|
||||||
from src.backend.colour_utils import ColourUtils
|
from meshchatx.src.backend.colour_utils import ColourUtils
|
||||||
from src.backend.interface_config_parser import InterfaceConfigParser
|
from meshchatx.src.backend.interface_config_parser import InterfaceConfigParser
|
||||||
from src.backend.interface_editor import InterfaceEditor
|
from meshchatx.src.backend.interface_editor import InterfaceEditor
|
||||||
from src.backend.lxmf_message_fields import (
|
from meshchatx.src.backend.lxmf_message_fields import (
|
||||||
LxmfAudioField,
|
LxmfAudioField,
|
||||||
LxmfFileAttachment,
|
LxmfFileAttachment,
|
||||||
LxmfFileAttachmentsField,
|
LxmfFileAttachmentsField,
|
||||||
LxmfImageField,
|
LxmfImageField,
|
||||||
)
|
)
|
||||||
from src.backend.sideband_commands import SidebandCommands
|
from meshchatx.src.backend.sideband_commands import SidebandCommands
|
||||||
|
from meshchatx.src.version import __version__ as app_version
|
||||||
|
|
||||||
|
|
||||||
# NOTE: this is required to be able to pack our app with cxfreeze as an exe, otherwise it can't access bundled assets
|
# NOTE: this is required to be able to pack our app with cxfreeze as an exe, otherwise it can't access bundled assets
|
||||||
@@ -46,9 +47,22 @@ from src.backend.sideband_commands import SidebandCommands
|
|||||||
def get_file_path(filename):
|
def get_file_path(filename):
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
datadir = os.path.dirname(sys.executable)
|
datadir = os.path.dirname(sys.executable)
|
||||||
else:
|
return os.path.join(datadir, filename)
|
||||||
datadir = os.path.dirname(__file__)
|
|
||||||
return os.path.join(datadir, filename)
|
# Running from source or an installed wheel: assets live inside the meshchatx package
|
||||||
|
package_dir = os.path.dirname(__file__)
|
||||||
|
test_path = os.path.join(package_dir, filename)
|
||||||
|
if os.path.exists(test_path):
|
||||||
|
return test_path
|
||||||
|
|
||||||
|
# Fall back to repo root when running directly from the source tree
|
||||||
|
repo_root = os.path.dirname(package_dir)
|
||||||
|
repo_path = os.path.join(repo_root, filename)
|
||||||
|
if os.path.exists(repo_path):
|
||||||
|
return repo_path
|
||||||
|
|
||||||
|
# Return the package path even if it does not exist so callers raise a clear error
|
||||||
|
return test_path
|
||||||
|
|
||||||
|
|
||||||
class ReticulumMeshChat:
|
class ReticulumMeshChat:
|
||||||
@@ -225,12 +239,10 @@ class ReticulumMeshChat:
|
|||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# gets app version from package.json
|
# gets app version from the synchronized Python version helper
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_app_version() -> str:
|
def get_app_version() -> str:
|
||||||
with open(get_file_path("package.json")) as f:
|
return app_version
|
||||||
package_json = json.load(f)
|
|
||||||
return package_json["version"]
|
|
||||||
|
|
||||||
# automatically announces based on user config
|
# automatically announces based on user config
|
||||||
async def announce_loop(self):
|
async def announce_loop(self):
|
||||||
@@ -3011,13 +3023,38 @@ class ReticulumMeshChat:
|
|||||||
)
|
)
|
||||||
if message:
|
if message:
|
||||||
message.is_spam = is_spam
|
message.is_spam = is_spam
|
||||||
message.updated_at = datetime.now(timezone.utc)
|
message.updated_at = datetime.now(UTC)
|
||||||
message.save()
|
message.save()
|
||||||
return web.json_response({"message": "ok"})
|
return web.json_response({"message": "ok"})
|
||||||
return web.json_response({"error": "Message not found"}, status=404)
|
return web.json_response({"error": "Message not found"}, status=404)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return web.json_response({"error": str(e)}, status=500)
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
# security headers middleware
|
||||||
|
@web.middleware
|
||||||
|
async def security_middleware(request, handler):
|
||||||
|
response = await handler(request)
|
||||||
|
# Add security headers to all responses
|
||||||
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
|
response.headers["X-Frame-Options"] = "DENY"
|
||||||
|
response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||||
|
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
||||||
|
# CSP: allow localhost for development and Electron, websockets, and blob URLs
|
||||||
|
csp = (
|
||||||
|
"default-src 'self'; "
|
||||||
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
|
||||||
|
"style-src 'self' 'unsafe-inline'; "
|
||||||
|
"img-src 'self' data: blob:; "
|
||||||
|
"font-src 'self' data:; "
|
||||||
|
"connect-src 'self' ws://localhost:* wss://localhost:* blob:; "
|
||||||
|
"media-src 'self' blob:; "
|
||||||
|
"worker-src 'self' blob:; "
|
||||||
|
"object-src 'none'; "
|
||||||
|
"base-uri 'self';"
|
||||||
|
)
|
||||||
|
response.headers["Content-Security-Policy"] = csp
|
||||||
|
return response
|
||||||
|
|
||||||
# called when web app has started
|
# called when web app has started
|
||||||
async def on_startup(app):
|
async def on_startup(app):
|
||||||
# remember main event loop
|
# remember main event loop
|
||||||
@@ -3033,6 +3070,7 @@ class ReticulumMeshChat:
|
|||||||
# create and run web app
|
# create and run web app
|
||||||
app = web.Application(
|
app = web.Application(
|
||||||
client_max_size=1024 * 1024 * 50,
|
client_max_size=1024 * 1024 * 50,
|
||||||
|
middlewares=[security_middleware],
|
||||||
) # allow uploading files up to 50mb
|
) # allow uploading files up to 50mb
|
||||||
app.add_routes(routes)
|
app.add_routes(routes)
|
||||||
app.add_routes(
|
app.add_routes(
|
||||||
@@ -3886,7 +3924,7 @@ class ReticulumMeshChat:
|
|||||||
"icon_name": icon_name,
|
"icon_name": icon_name,
|
||||||
"foreground_colour": foreground_colour,
|
"foreground_colour": foreground_colour,
|
||||||
"background_colour": background_colour,
|
"background_colour": background_colour,
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# upsert to database
|
# upsert to database
|
||||||
@@ -4108,7 +4146,7 @@ class ReticulumMeshChat:
|
|||||||
"snr": lxmf_message_dict["snr"],
|
"snr": lxmf_message_dict["snr"],
|
||||||
"quality": lxmf_message_dict["quality"],
|
"quality": lxmf_message_dict["quality"],
|
||||||
"is_spam": is_spam,
|
"is_spam": is_spam,
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# upsert to database
|
# upsert to database
|
||||||
@@ -4144,7 +4182,7 @@ class ReticulumMeshChat:
|
|||||||
"rssi": rssi,
|
"rssi": rssi,
|
||||||
"snr": snr,
|
"snr": snr,
|
||||||
"quality": quality,
|
"quality": quality,
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# only set app data if provided, as we don't want to wipe existing data when we request keys from the network
|
# only set app data if provided, as we don't want to wipe existing data when we request keys from the network
|
||||||
@@ -4170,7 +4208,7 @@ class ReticulumMeshChat:
|
|||||||
data = {
|
data = {
|
||||||
"destination_hash": destination_hash,
|
"destination_hash": destination_hash,
|
||||||
"display_name": display_name,
|
"display_name": display_name,
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# upsert to database
|
# upsert to database
|
||||||
@@ -4193,7 +4231,7 @@ class ReticulumMeshChat:
|
|||||||
"destination_hash": destination_hash,
|
"destination_hash": destination_hash,
|
||||||
"display_name": display_name,
|
"display_name": display_name,
|
||||||
"aspect": aspect,
|
"aspect": aspect,
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# upsert to database
|
# upsert to database
|
||||||
@@ -4210,8 +4248,8 @@ class ReticulumMeshChat:
|
|||||||
# prepare data to insert or update
|
# prepare data to insert or update
|
||||||
data = {
|
data = {
|
||||||
"destination_hash": destination_hash,
|
"destination_hash": destination_hash,
|
||||||
"last_read_at": datetime.now(timezone.utc),
|
"last_read_at": datetime.now(UTC),
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# upsert to database
|
# upsert to database
|
||||||
@@ -4878,7 +4916,7 @@ class Config:
|
|||||||
data = {
|
data = {
|
||||||
"key": key,
|
"key": key,
|
||||||
"value": value,
|
"value": value,
|
||||||
"updated_at": datetime.now(timezone.utc),
|
"updated_at": datetime.now(UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
# upsert to database
|
# upsert to database
|
||||||
1
meshchatx/src/backend/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Backend utilities shared by the Reticulum MeshChatX CLI."""
|
||||||
@@ -3,9 +3,8 @@ import time
|
|||||||
|
|
||||||
import RNS
|
import RNS
|
||||||
from RNS.Interfaces.Interface import Interface
|
from RNS.Interfaces.Interface import Interface
|
||||||
from websockets.sync.server import Server, ServerConnection, serve
|
|
||||||
|
|
||||||
from src.backend.interfaces.WebsocketClientInterface import WebsocketClientInterface
|
from src.backend.interfaces.WebsocketClientInterface import WebsocketClientInterface
|
||||||
|
from websockets.sync.server import Server, ServerConnection, serve
|
||||||
|
|
||||||
|
|
||||||
class WebsocketServerInterface(Interface):
|
class WebsocketServerInterface(Interface):
|
||||||
1
meshchatx/src/backend/interfaces/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Shared transport interfaces for MeshChatX."""
|
||||||
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |