Compare commits
5 Commits
v2.40.0
...
deepsource
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73f677d319 | ||
|
4770c21499
|
|||
|
720bef90c7
|
|||
|
1c98a231fd
|
|||
|
f6a1be5e80
|
26
.github/workflows/build.yml
vendored
26
.github/workflows/build.yml
vendored
@@ -4,10 +4,33 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
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:
|
jobs:
|
||||||
build_windows:
|
build_windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_windows == 'true')
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
@@ -49,6 +72,7 @@ jobs:
|
|||||||
|
|
||||||
build_mac:
|
build_mac:
|
||||||
runs-on: macos-13
|
runs-on: macos-13
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_mac == 'true')
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
@@ -90,6 +114,7 @@ jobs:
|
|||||||
|
|
||||||
build_linux:
|
build_linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_linux == 'true')
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
@@ -134,6 +159,7 @@ jobs:
|
|||||||
|
|
||||||
build_docker:
|
build_docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_docker == 'true')
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
45
.github/workflows/manual-docker-build.yml
vendored
45
.github/workflows/manual-docker-build.yml
vendored
@@ -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/
|
|
||||||
|
|
||||||
89
meshchat.py
89
meshchat.py
@@ -221,7 +221,8 @@ class ReticulumMeshChat:
|
|||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# gets app version from package.json
|
# 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:
|
with open(get_file_path("package.json")) as f:
|
||||||
package_json = json.load(f)
|
package_json = json.load(f)
|
||||||
return package_json["version"]
|
return package_json["version"]
|
||||||
@@ -447,7 +448,8 @@ class ReticulumMeshChat:
|
|||||||
)
|
)
|
||||||
return query.exists()
|
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:
|
if not fields_json:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -491,7 +493,8 @@ class ReticulumMeshChat:
|
|||||||
|
|
||||||
return matches
|
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:
|
if value is None:
|
||||||
return False
|
return False
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
@@ -1986,14 +1989,14 @@ class ReticulumMeshChat:
|
|||||||
nomadnetwork_node_announce is not None
|
nomadnetwork_node_announce is not None
|
||||||
and nomadnetwork_node_announce.app_data 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,
|
nomadnetwork_node_announce.app_data, None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# parse app_data so we can see if propagation is enabled or disabled for this node
|
# parse app_data so we can see if propagation is enabled or disabled for this node
|
||||||
is_propagation_enabled = None
|
is_propagation_enabled = None
|
||||||
per_transfer_limit = 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,
|
announce.app_data,
|
||||||
)
|
)
|
||||||
if propagation_node_data is not None:
|
if propagation_node_data is not None:
|
||||||
@@ -2753,8 +2756,8 @@ class ReticulumMeshChat:
|
|||||||
other_user_hash,
|
other_user_hash,
|
||||||
),
|
),
|
||||||
"destination_hash": other_user_hash,
|
"destination_hash": other_user_hash,
|
||||||
"is_unread": self.is_lxmf_conversation_unread(other_user_hash),
|
"is_unread": ReticulumMeshChat.is_lxmf_conversation_unread(other_user_hash),
|
||||||
"failed_messages_count": self.lxmf_conversation_failed_messages_count(
|
"failed_messages_count": ReticulumMeshChat.lxmf_conversation_failed_messages_count(
|
||||||
other_user_hash,
|
other_user_hash,
|
||||||
),
|
),
|
||||||
"has_attachments": has_attachments,
|
"has_attachments": has_attachments,
|
||||||
@@ -3167,7 +3170,8 @@ class ReticulumMeshChat:
|
|||||||
# to the following map:
|
# to the following map:
|
||||||
# - var_field1: 123
|
# - var_field1: 123
|
||||||
# - var_field2: 456
|
# - 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 = {}
|
data = {}
|
||||||
if path_data is not None:
|
if path_data is not None:
|
||||||
for field in path_data.split("|"):
|
for field in path_data.split("|"):
|
||||||
@@ -3178,7 +3182,8 @@ class ReticulumMeshChat:
|
|||||||
print(f"unhandled field: {field}")
|
print(f"unhandled field: {field}")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def convert_nomadnet_field_data_to_map(self, field_data):
|
@staticmethod
|
||||||
|
def convert_nomadnet_field_data_to_map(field_data):
|
||||||
data = {}
|
data = {}
|
||||||
if field_data is not None or "{}":
|
if field_data is not None or "{}":
|
||||||
try:
|
try:
|
||||||
@@ -3681,7 +3686,8 @@ class ReticulumMeshChat:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# convert lxmf state to a human friendly string
|
# 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
|
# convert state to string
|
||||||
lxmf_message_state = "unknown"
|
lxmf_message_state = "unknown"
|
||||||
if lxmf_message.state == LXMF.LXMessage.GENERATING:
|
if lxmf_message.state == LXMF.LXMessage.GENERATING:
|
||||||
@@ -3704,7 +3710,8 @@ class ReticulumMeshChat:
|
|||||||
return lxmf_message_state
|
return lxmf_message_state
|
||||||
|
|
||||||
# convert lxmf method to a human friendly string
|
# 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
|
# convert method to string
|
||||||
lxmf_message_method = "unknown"
|
lxmf_message_method = "unknown"
|
||||||
if lxmf_message.method == LXMF.LXMessage.OPPORTUNISTIC:
|
if lxmf_message.method == LXMF.LXMessage.OPPORTUNISTIC:
|
||||||
@@ -3718,7 +3725,8 @@ class ReticulumMeshChat:
|
|||||||
|
|
||||||
return lxmf_message_method
|
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
|
# map states to strings
|
||||||
state_map = {
|
state_map = {
|
||||||
LXMRouter.PR_IDLE: "idle",
|
LXMRouter.PR_IDLE: "idle",
|
||||||
@@ -3749,7 +3757,7 @@ class ReticulumMeshChat:
|
|||||||
if announce.aspect == "lxmf.delivery":
|
if announce.aspect == "lxmf.delivery":
|
||||||
display_name = self.parse_lxmf_display_name(announce.app_data)
|
display_name = self.parse_lxmf_display_name(announce.app_data)
|
||||||
elif announce.aspect == "nomadnetwork.node":
|
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
|
# find lxmf user icon from database
|
||||||
lxmf_user_icon = None
|
lxmf_user_icon = None
|
||||||
@@ -3787,7 +3795,8 @@ class ReticulumMeshChat:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# convert database favourite to a dictionary
|
# 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 {
|
return {
|
||||||
"id": favourite.id,
|
"id": favourite.id,
|
||||||
"destination_hash": favourite.destination_hash,
|
"destination_hash": favourite.destination_hash,
|
||||||
@@ -3798,7 +3807,8 @@ class ReticulumMeshChat:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# convert database lxmf message to a dictionary
|
# 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 {
|
return {
|
||||||
"id": db_lxmf_message.id,
|
"id": db_lxmf_message.id,
|
||||||
"hash": db_lxmf_message.hash,
|
"hash": db_lxmf_message.hash,
|
||||||
@@ -3823,8 +3833,8 @@ class ReticulumMeshChat:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# updates the lxmf user icon for the provided destination hash
|
# updates the lxmf user icon for the provided destination hash
|
||||||
|
@staticmethod
|
||||||
def update_lxmf_user_icon(
|
def update_lxmf_user_icon(
|
||||||
self,
|
|
||||||
destination_hash: str,
|
destination_hash: str,
|
||||||
icon_name: str,
|
icon_name: str,
|
||||||
foreground_colour: str,
|
foreground_colour: str,
|
||||||
@@ -3852,7 +3862,8 @@ class ReticulumMeshChat:
|
|||||||
query.execute()
|
query.execute()
|
||||||
|
|
||||||
# check if a destination is blocked
|
# 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:
|
try:
|
||||||
blocked = database.BlockedDestination.get_or_none(
|
blocked = database.BlockedDestination.get_or_none(
|
||||||
database.BlockedDestination.destination_hash == destination_hash,
|
database.BlockedDestination.destination_hash == destination_hash,
|
||||||
@@ -3862,7 +3873,8 @@ class ReticulumMeshChat:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# check if message content matches spam keywords
|
# 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:
|
try:
|
||||||
spam_keywords = database.SpamKeyword.select()
|
spam_keywords = database.SpamKeyword.select()
|
||||||
search_text = (title + " " + content).lower()
|
search_text = (title + " " + content).lower()
|
||||||
@@ -3874,7 +3886,8 @@ class ReticulumMeshChat:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# check if message has attachments and should be rejected
|
# 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:
|
try:
|
||||||
if LXMF.FIELD_FILE_ATTACHMENTS in lxmf_fields:
|
if LXMF.FIELD_FILE_ATTACHMENTS in lxmf_fields:
|
||||||
return len(lxmf_fields[LXMF.FIELD_FILE_ATTACHMENTS]) > 0
|
return len(lxmf_fields[LXMF.FIELD_FILE_ATTACHMENTS]) > 0
|
||||||
@@ -4108,8 +4121,9 @@ class ReticulumMeshChat:
|
|||||||
query.execute()
|
query.execute()
|
||||||
|
|
||||||
# upserts a custom destination display name to the database
|
# upserts a custom destination display name to the database
|
||||||
|
@staticmethod
|
||||||
def db_upsert_custom_destination_display_name(
|
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
|
# prepare data to insert or update
|
||||||
data = {
|
data = {
|
||||||
@@ -4127,8 +4141,9 @@ class ReticulumMeshChat:
|
|||||||
query.execute()
|
query.execute()
|
||||||
|
|
||||||
# upserts a custom destination display name to the database
|
# upserts a custom destination display name to the database
|
||||||
|
@staticmethod
|
||||||
def db_upsert_favourite(
|
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
|
# prepare data to insert or update
|
||||||
data = {
|
data = {
|
||||||
@@ -4147,7 +4162,8 @@ class ReticulumMeshChat:
|
|||||||
query.execute()
|
query.execute()
|
||||||
|
|
||||||
# upserts lxmf conversation read state to the database
|
# 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
|
# prepare data to insert or update
|
||||||
data = {
|
data = {
|
||||||
"destination_hash": destination_hash,
|
"destination_hash": destination_hash,
|
||||||
@@ -4633,7 +4649,8 @@ class ReticulumMeshChat:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# gets the custom display name a user has set for the provided destination hash
|
# 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
|
# get display name from database
|
||||||
db_destination_display_name = database.CustomDestinationDisplayName.get_or_none(
|
db_destination_display_name = database.CustomDestinationDisplayName.get_or_none(
|
||||||
database.CustomDestinationDisplayName.destination_hash == destination_hash,
|
database.CustomDestinationDisplayName.destination_hash == destination_hash,
|
||||||
@@ -4646,7 +4663,8 @@ class ReticulumMeshChat:
|
|||||||
# get name to show for an lxmf conversation
|
# get name to show for an lxmf conversation
|
||||||
# currently, this will use the app data from the most recent announce
|
# 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
|
# 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
|
# get lxmf.delivery announce from database for the provided destination hash
|
||||||
lxmf_announce = (
|
lxmf_announce = (
|
||||||
database.Announce.select()
|
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
|
# 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
|
# 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:
|
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
|
# announce did not have app data, so provide a fallback name
|
||||||
return "Anonymous Peer"
|
return "Anonymous Peer"
|
||||||
|
|
||||||
# reads the lxmf display name from the provided base64 app data
|
# reads the lxmf display name from the provided base64 app data
|
||||||
|
@staticmethod
|
||||||
def parse_lxmf_display_name(
|
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:
|
try:
|
||||||
app_data_bytes = base64.b64decode(app_data_base64)
|
app_data_bytes = base64.b64decode(app_data_base64)
|
||||||
@@ -4678,7 +4697,8 @@ class ReticulumMeshChat:
|
|||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
# reads the lxmf stamp cost from the provided base64 app data
|
# 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:
|
try:
|
||||||
app_data_bytes = base64.b64decode(app_data_base64)
|
app_data_bytes = base64.b64decode(app_data_base64)
|
||||||
return LXMF.stamp_cost_from_app_data(app_data_bytes)
|
return LXMF.stamp_cost_from_app_data(app_data_bytes)
|
||||||
@@ -4686,8 +4706,9 @@ class ReticulumMeshChat:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# reads the nomadnetwork node display name from the provided base64 app data
|
# reads the nomadnetwork node display name from the provided base64 app data
|
||||||
|
@staticmethod
|
||||||
def parse_nomadnetwork_node_display_name(
|
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:
|
try:
|
||||||
app_data_bytes = base64.b64decode(app_data_base64)
|
app_data_bytes = base64.b64decode(app_data_base64)
|
||||||
@@ -4696,7 +4717,8 @@ class ReticulumMeshChat:
|
|||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
# parses lxmf propagation node app data
|
# 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:
|
try:
|
||||||
app_data_bytes = base64.b64decode(app_data_base64)
|
app_data_bytes = base64.b64decode(app_data_base64)
|
||||||
data = msgpack.unpackb(app_data_bytes)
|
data = msgpack.unpackb(app_data_bytes)
|
||||||
@@ -4709,7 +4731,8 @@ class ReticulumMeshChat:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# returns true if the conversation has messages newer than the last read at timestamp
|
# 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
|
# get lxmf conversation read state from database for the provided destination hash
|
||||||
lxmf_conversation_read_state = (
|
lxmf_conversation_read_state = (
|
||||||
database.LxmfConversationReadState.select()
|
database.LxmfConversationReadState.select()
|
||||||
@@ -4745,7 +4768,8 @@ class ReticulumMeshChat:
|
|||||||
return conversation_last_read_at < conversation_latest_message_at
|
return conversation_last_read_at < conversation_latest_message_at
|
||||||
|
|
||||||
# returns number of messages that failed to send in a conversation
|
# 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 (
|
return (
|
||||||
database.LxmfMessage.select()
|
database.LxmfMessage.select()
|
||||||
.where(database.LxmfMessage.state == "failed")
|
.where(database.LxmfMessage.state == "failed")
|
||||||
@@ -4754,7 +4778,8 @@ class ReticulumMeshChat:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# find an interface by name
|
# 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:
|
for interface in RNS.Transport.interfaces:
|
||||||
interface_name = str(interface)
|
interface_name = str(interface)
|
||||||
if name == interface_name:
|
if name == interface_name:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "npm run build-frontend -- --watch",
|
"watch": "npm run build-frontend -- --watch",
|
||||||
"build-frontend": "vite build",
|
"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",
|
"build": "npm run build-frontend && npm run build-backend",
|
||||||
"electron-postinstall": "electron-builder install-app-deps",
|
"electron-postinstall": "electron-builder install-app-deps",
|
||||||
"electron": "npm run electron-postinstall && npm run build && electron .",
|
"electron": "npm run electron-postinstall && npm run build && electron .",
|
||||||
|
|||||||
18
scripts/build-backend.js
Executable file
18
scripts/build-backend.js
Executable 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);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user