mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2025-02-22 17:29:52 +01:00
Added markdown rendering and message composing
This commit is contained in:
parent
a90a451865
commit
84b214cb90
@ -12,7 +12,7 @@ version.regex = __version__ = ['"](.*)['"]
|
|||||||
version.filename = %(source.dir)s/main.py
|
version.filename = %(source.dir)s/main.py
|
||||||
android.numeric_version = 20250120
|
android.numeric_version = 20250120
|
||||||
|
|
||||||
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions
|
requirements = kivy==2.3.0,libbz2,pillow==10.2.0,qrcode==7.3.1,usb4a,usbserial4a,able_recipe,libwebp,libogg,libopus,opusfile,numpy,cryptography,ffpyplayer,codec2,pycodec2,sh,pynacl,typing-extensions,mistune>=3.0.2,beautifulsoup4
|
||||||
|
|
||||||
android.gradle_dependencies = com.android.support:support-compat:28.0.0
|
android.gradle_dependencies = com.android.support:support-compat:28.0.0
|
||||||
#android.enable_androidx = True
|
#android.enable_androidx = True
|
||||||
|
@ -19,6 +19,7 @@ import RNS
|
|||||||
import LXMF
|
import LXMF
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import pathlib
|
import pathlib
|
||||||
import base64
|
import base64
|
||||||
import threading
|
import threading
|
||||||
@ -1523,6 +1524,50 @@ class SidebandApp(MDApp):
|
|||||||
|
|
||||||
### Messages (conversation) screen
|
### Messages (conversation) screen
|
||||||
######################################
|
######################################
|
||||||
|
|
||||||
|
def md_to_bbcode(self, text):
|
||||||
|
if not hasattr(self, "mdconv"):
|
||||||
|
from md2bbcode.main import process_readme as mdconv
|
||||||
|
self.mdconv = mdconv
|
||||||
|
converted = self.mdconv(text)
|
||||||
|
while converted.endswith("\n"):
|
||||||
|
converted = converted[:-1]
|
||||||
|
|
||||||
|
return converted
|
||||||
|
|
||||||
|
def process_bb_markup(self, text):
|
||||||
|
st = time.time()
|
||||||
|
ms = int(sp(14))
|
||||||
|
h1s = int(sp(20))
|
||||||
|
h2s = int(sp(18))
|
||||||
|
h3s = int(sp(16))
|
||||||
|
|
||||||
|
if not hasattr(self, "pres"):
|
||||||
|
self.pres = []
|
||||||
|
res = [ [r"\[(?:code|icode).*?\]", f"[font=mono][size={ms}]"],
|
||||||
|
[r"\[\/(?:code|icode).*?\]", "[/size][/font]"],
|
||||||
|
[r"\[(?:heading)\]", f"[b][size={h1s}]"],
|
||||||
|
[r"\[(?:heading=1)*?\]", f"[b][size={h1s}]"],
|
||||||
|
[r"\[(?:heading=2)*?\]", f"[b][size={h2s}]"],
|
||||||
|
[r"\[(?:heading=3)*?\]", f"[b][size={h3s}]"],
|
||||||
|
[r"\[(?:heading=).*?\]", f"[b][size={h3s}]"], # Match all remaining lower-level headings
|
||||||
|
[r"\[\/(?:heading).*?\]", "[/size][/b]"],
|
||||||
|
[r"\[(?:list).*?\]", ""],
|
||||||
|
[r"\[\/(?:list).*?\]", ""],
|
||||||
|
[r"\n\[(?:\*).*?\]", "\n - "],
|
||||||
|
[r"\[(?:url).*?\]", ""], # Strip URLs for now
|
||||||
|
[r"\[\/(?:url).*?\]", ""],
|
||||||
|
[r"\[(?:img).*?\].*\[\/(?:img).*?\]", ""] # Strip images for now
|
||||||
|
]
|
||||||
|
|
||||||
|
for r in res:
|
||||||
|
self.pres.append([re.compile(r[0], re.IGNORECASE | re.MULTILINE ), r[1]])
|
||||||
|
|
||||||
|
for pr in self.pres:
|
||||||
|
text = pr[0].sub(pr[1], text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
def conversation_from_announce_action(self, context_dest):
|
def conversation_from_announce_action(self, context_dest):
|
||||||
if self.sideband.has_conversation(context_dest):
|
if self.sideband.has_conversation(context_dest):
|
||||||
pass
|
pass
|
||||||
@ -2758,7 +2803,7 @@ class SidebandApp(MDApp):
|
|||||||
|
|
||||||
str_comps = " - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)"
|
str_comps = " - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)"
|
||||||
str_comps += "\n - [b]Kivy[/b] (MIT License)\n - [b]Codec2[/b] (LGPL License)\n - [b]PyCodec2[/b] (BSD-3 License)"
|
str_comps += "\n - [b]Kivy[/b] (MIT License)\n - [b]Codec2[/b] (LGPL License)\n - [b]PyCodec2[/b] (BSD-3 License)"
|
||||||
str_comps += "\n - [b]PyDub[/b] (MIT License)\n - [b]PyOgg[/b] (Public Domain)"
|
str_comps += "\n - [b]PyDub[/b] (MIT License)\n - [b]PyOgg[/b] (Public Domain)\n - [b]MD2bbcode[/b] (GPL3 License)"
|
||||||
str_comps += "\n - [b]GeoidHeight[/b] (LGPL License)\n - [b]Python[/b] (PSF License)"
|
str_comps += "\n - [b]GeoidHeight[/b] (LGPL License)\n - [b]Python[/b] (PSF License)"
|
||||||
str_comps += "\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright © 2025 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of "+self.root.ids.app_version_info.text+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - SIDEBAND COMES WITH ABSOLUTELY NO WARRANTY - USE AT YOUR OWN RISK AND RESPONSIBILITY"
|
str_comps += "\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright © 2025 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of "+self.root.ids.app_version_info.text+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - SIDEBAND COMES WITH ABSOLUTELY NO WARRANTY - USE AT YOUR OWN RISK AND RESPONSIBILITY"
|
||||||
info = "This is "+self.root.ids.app_version_info.text+", on RNS v"+RNS.__version__+" and LXMF v"+LXMF.__version__+".\n\nHumbly build using the following open components:\n\n"+str_comps
|
info = "This is "+self.root.ids.app_version_info.text+", on RNS v"+RNS.__version__+" and LXMF v"+LXMF.__version__+".\n\nHumbly build using the following open components:\n\n"+str_comps
|
||||||
@ -3041,6 +3086,10 @@ class SidebandApp(MDApp):
|
|||||||
self.sideband.config["trusted_markup_only"] = self.settings_screen.ids.settings_trusted_markup_only.active
|
self.sideband.config["trusted_markup_only"] = self.settings_screen.ids.settings_trusted_markup_only.active
|
||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
|
|
||||||
|
def save_compose_in_markdown(sender=None, event=None):
|
||||||
|
self.sideband.config["compose_in_markdown"] = self.settings_screen.ids.settings_compose_in_markdown.active
|
||||||
|
self.sideband.save_configuration()
|
||||||
|
|
||||||
def save_advanced_stats(sender=None, event=None):
|
def save_advanced_stats(sender=None, event=None):
|
||||||
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
self.sideband.config["advanced_stats"] = self.settings_screen.ids.settings_advanced_statistics.active
|
||||||
self.sideband.save_configuration()
|
self.sideband.save_configuration()
|
||||||
@ -3219,6 +3268,9 @@ class SidebandApp(MDApp):
|
|||||||
self.settings_screen.ids.settings_trusted_markup_only.active = self.sideband.config["trusted_markup_only"]
|
self.settings_screen.ids.settings_trusted_markup_only.active = self.sideband.config["trusted_markup_only"]
|
||||||
self.settings_screen.ids.settings_trusted_markup_only.bind(active=save_trusted_markup_only)
|
self.settings_screen.ids.settings_trusted_markup_only.bind(active=save_trusted_markup_only)
|
||||||
|
|
||||||
|
self.settings_screen.ids.settings_compose_in_markdown.active = self.sideband.config["compose_in_markdown"]
|
||||||
|
self.settings_screen.ids.settings_compose_in_markdown.bind(active=save_compose_in_markdown)
|
||||||
|
|
||||||
self.settings_screen.ids.settings_ignore_invalid_stamps.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
|
self.settings_screen.ids.settings_ignore_invalid_stamps.active = self.sideband.config["lxmf_ignore_invalid_stamps"]
|
||||||
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_lxmf_ignore_invalid_stamps)
|
self.settings_screen.ids.settings_ignore_invalid_stamps.bind(active=save_lxmf_ignore_invalid_stamps)
|
||||||
|
|
||||||
|
@ -457,6 +457,7 @@ class SidebandCore():
|
|||||||
self.config["eink_mode"] = True
|
self.config["eink_mode"] = True
|
||||||
self.config["lxm_limit_1mb"] = True
|
self.config["lxm_limit_1mb"] = True
|
||||||
self.config["trusted_markup_only"] = False
|
self.config["trusted_markup_only"] = False
|
||||||
|
self.config["compose_in_markdown"] = False
|
||||||
|
|
||||||
# Connectivity
|
# Connectivity
|
||||||
self.config["connect_transport"] = False
|
self.config["connect_transport"] = False
|
||||||
@ -601,6 +602,8 @@ class SidebandCore():
|
|||||||
self.config["hq_ptt"] = False
|
self.config["hq_ptt"] = False
|
||||||
if not "trusted_markup_only" in self.config:
|
if not "trusted_markup_only" in self.config:
|
||||||
self.config["trusted_markup_only"] = False
|
self.config["trusted_markup_only"] = False
|
||||||
|
if not "compose_in_markdown" in self.config:
|
||||||
|
self.config["compose_in_markdown"] = False
|
||||||
|
|
||||||
if not "input_language" in self.config:
|
if not "input_language" in self.config:
|
||||||
self.config["input_language"] = None
|
self.config["input_language"] = None
|
||||||
@ -4396,7 +4399,13 @@ class SidebandCore():
|
|||||||
fields[LXMF.FIELD_IMAGE] = image
|
fields[LXMF.FIELD_IMAGE] = image
|
||||||
if audio != None:
|
if audio != None:
|
||||||
fields[LXMF.FIELD_AUDIO] = audio
|
fields[LXMF.FIELD_AUDIO] = audio
|
||||||
if self.has_bb_markup(content):
|
md_sig = "#!md\n"
|
||||||
|
if content.startswith(md_sig):
|
||||||
|
content = content[len(md_sig):]
|
||||||
|
fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_MARKDOWN
|
||||||
|
elif self.config["compose_in_markdown"]:
|
||||||
|
fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_MARKDOWN
|
||||||
|
elif self.has_bb_markup(content):
|
||||||
fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_BBCODE
|
fields[LXMF.FIELD_RENDERER] = LXMF.RENDERER_BBCODE
|
||||||
|
|
||||||
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields, include_ticket=self.is_trusted(destination_hash))
|
lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields, include_ticket=self.is_trusted(destination_hash))
|
||||||
@ -4538,12 +4547,12 @@ class SidebandCore():
|
|||||||
|
|
||||||
def strip_bb_markup(self, text):
|
def strip_bb_markup(self, text):
|
||||||
if not hasattr(self, "smr") or self.smr == None:
|
if not hasattr(self, "smr") or self.smr == None:
|
||||||
self.smr = re.compile(r'\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]',re.IGNORECASE | re.MULTILINE )
|
self.smr = re.compile(r"\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]",re.IGNORECASE | re.MULTILINE )
|
||||||
return self.smr.sub("", text)
|
return self.smr.sub("", text)
|
||||||
|
|
||||||
def has_bb_markup(self, text):
|
def has_bb_markup(self, text):
|
||||||
if not hasattr(self, "smr") or self.smr == None:
|
if not hasattr(self, "smr") or self.smr == None:
|
||||||
self.smr = re.compile(r'\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]',re.IGNORECASE | re.MULTILINE )
|
self.smr = re.compile(r"\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]",re.IGNORECASE | re.MULTILINE )
|
||||||
if self.smr.match(text):
|
if self.smr.match(text):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -1655,6 +1655,22 @@ MDScreen:
|
|||||||
disabled: False
|
disabled: False
|
||||||
active: False
|
active: False
|
||||||
|
|
||||||
|
MDBoxLayout:
|
||||||
|
orientation: "horizontal"
|
||||||
|
size_hint_y: None
|
||||||
|
padding: [0,0,dp(24),dp(0)]
|
||||||
|
height: dp(48)
|
||||||
|
|
||||||
|
MDLabel:
|
||||||
|
text: "Compose messages in markdown"
|
||||||
|
font_style: "H6"
|
||||||
|
|
||||||
|
MDSwitch:
|
||||||
|
id: settings_compose_in_markdown
|
||||||
|
pos_hint: {"center_y": 0.3}
|
||||||
|
disabled: False
|
||||||
|
active: False
|
||||||
|
|
||||||
MDBoxLayout:
|
MDBoxLayout:
|
||||||
orientation: "horizontal"
|
orientation: "horizontal"
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
|
@ -110,8 +110,6 @@ class Messages():
|
|||||||
msg = self.app.sideband.message(lxm_hash)
|
msg = self.app.sideband.message(lxm_hash)
|
||||||
if msg:
|
if msg:
|
||||||
close_button = MDRectangleFlatButton(text="Close", font_size=dp(18))
|
close_button = MDRectangleFlatButton(text="Close", font_size=dp(18))
|
||||||
# d_items = [ ]
|
|
||||||
# d_items.append(DialogItem(IconLeftWidget(icon="postage-stamp"), text="[size="+str(ss)+"]Stamp[/size]"))
|
|
||||||
|
|
||||||
d_text = ""
|
d_text = ""
|
||||||
|
|
||||||
@ -492,11 +490,24 @@ class Messages():
|
|||||||
|
|
||||||
for m in self.new_messages:
|
for m in self.new_messages:
|
||||||
if not m["hash"] in self.added_item_hashes:
|
if not m["hash"] in self.added_item_hashes:
|
||||||
|
renderer = None
|
||||||
|
message_source = m["content"]
|
||||||
|
if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_RENDERER in m["lxm"].fields:
|
||||||
|
renderer = m["lxm"].fields[LXMF.FIELD_RENDERER]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.app.sideband.config["trusted_markup_only"] and not self.is_trusted:
|
if self.app.sideband.config["trusted_markup_only"] and not self.is_trusted:
|
||||||
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
|
message_input = str( escape_markup(m["content"].decode("utf-8")) ).encode("utf-8")
|
||||||
else:
|
else:
|
||||||
message_input = m["content"]
|
message_input = m["content"]
|
||||||
|
if renderer == LXMF.RENDERER_MARKDOWN:
|
||||||
|
message_input = self.app.md_to_bbcode(message_input.decode("utf-8")).encode("utf-8")
|
||||||
|
message_input = self.app.process_bb_markup(message_input.decode("utf-8")).encode("utf-8")
|
||||||
|
elif renderer == LXMF.RENDERER_BBCODE:
|
||||||
|
message_input = self.app.process_bb_markup(message_input.decode("utf-8")).encode("utf-8")
|
||||||
|
else:
|
||||||
|
message_input = str(escape_markup(m["content"].decode("utf-8"))).encode("utf-8")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"Message content could not be decoded: {e}", RNS.LOG_DEBUG)
|
RNS.log(f"Message content could not be decoded: {e}", RNS.LOG_DEBUG)
|
||||||
message_input = b""
|
message_input = b""
|
||||||
@ -1144,7 +1155,7 @@ class Messages():
|
|||||||
"viewclass": "OneLineListItem",
|
"viewclass": "OneLineListItem",
|
||||||
"text": "Copy message text",
|
"text": "Copy message text",
|
||||||
"height": dp(40),
|
"height": dp(40),
|
||||||
"on_release": gen_copy(message_input.decode("utf-8"), item)
|
"on_release": gen_copy(message_source.decode("utf-8"), item)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Delete",
|
"text": "Delete",
|
||||||
@ -1178,7 +1189,7 @@ class Messages():
|
|||||||
"viewclass": "OneLineListItem",
|
"viewclass": "OneLineListItem",
|
||||||
"text": "Copy message text",
|
"text": "Copy message text",
|
||||||
"height": dp(40),
|
"height": dp(40),
|
||||||
"on_release": gen_copy(message_input.decode("utf-8"), item)
|
"on_release": gen_copy(message_source.decode("utf-8"), item)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Delete",
|
"text": "Delete",
|
||||||
@ -1196,7 +1207,7 @@ class Messages():
|
|||||||
"viewclass": "OneLineListItem",
|
"viewclass": "OneLineListItem",
|
||||||
"text": "Copy",
|
"text": "Copy",
|
||||||
"height": dp(40),
|
"height": dp(40),
|
||||||
"on_release": gen_copy(message_input.decode("utf-8"), item)
|
"on_release": gen_copy(message_source.decode("utf-8"), item)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Delete",
|
"text": "Delete",
|
||||||
@ -1213,7 +1224,7 @@ class Messages():
|
|||||||
"viewclass": "OneLineListItem",
|
"viewclass": "OneLineListItem",
|
||||||
"text": "Copy",
|
"text": "Copy",
|
||||||
"height": dp(40),
|
"height": dp(40),
|
||||||
"on_release": gen_copy(message_input.decode("utf-8"), item)
|
"on_release": gen_copy(message_source.decode("utf-8"), item)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"viewclass": "OneLineListItem",
|
"viewclass": "OneLineListItem",
|
||||||
@ -1236,7 +1247,7 @@ class Messages():
|
|||||||
"viewclass": "OneLineListItem",
|
"viewclass": "OneLineListItem",
|
||||||
"text": "Copy",
|
"text": "Copy",
|
||||||
"height": dp(40),
|
"height": dp(40),
|
||||||
"on_release": gen_copy(message_input.decode("utf-8"), item)
|
"on_release": gen_copy(message_source.decode("utf-8"), item)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Delete",
|
"text": "Delete",
|
||||||
|
2
setup.py
2
setup.py
@ -123,6 +123,8 @@ setuptools.setup(
|
|||||||
"ffpyplayer",
|
"ffpyplayer",
|
||||||
"sh",
|
"sh",
|
||||||
"numpy<=1.26.4",
|
"numpy<=1.26.4",
|
||||||
|
"mistune>=3.0.2",
|
||||||
|
"beautifulsoup4",
|
||||||
"pycodec2;sys.platform!='Windows' and sys.platform!='win32' and sys.platform!='darwin'",
|
"pycodec2;sys.platform!='Windows' and sys.platform!='win32' and sys.platform!='darwin'",
|
||||||
"pyaudio;sys.platform=='linux'",
|
"pyaudio;sys.platform=='linux'",
|
||||||
"pyobjus;sys.platform=='darwin'",
|
"pyobjus;sys.platform=='darwin'",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user