forked from sch/Slixfeed
Python : Add support for newer OMEMO (Thank you. Syndance);
Python : Fix Ad-Hoc Commands; SQLite : Fix tagging mechanism.
This commit is contained in:
parent
178f49cb86
commit
c050c765dd
8 changed files with 216 additions and 60 deletions
|
@ -47,7 +47,6 @@ dependencies = [
|
|||
"feedparser",
|
||||
"lxml",
|
||||
"python-dateutil",
|
||||
"requests",
|
||||
"slixmpp",
|
||||
"tomli", # Python 3.10
|
||||
"tomli_w",
|
||||
|
@ -59,14 +58,7 @@ Repository = "https://git.xmpp-it.net/sch/Slixfeed"
|
|||
Issues = "https://gitgud.io/sjehuda/slixfeed/issues"
|
||||
|
||||
[project.optional-dependencies]
|
||||
omemo = [
|
||||
"DoubleRatchet>=0.7.0,<0.8",
|
||||
"OMEMO>=0.13.0,<0.15",
|
||||
"protobuf==3.20.3",
|
||||
"slixmpp-omemo",
|
||||
"X3DH>=0.5.9,<0.6",
|
||||
"XEdDSA<0.5,>=0.4.7",
|
||||
]
|
||||
omemo = ["slixmpp-omemo"]
|
||||
proxy = ["pysocks"]
|
||||
|
||||
# [project.readme]
|
||||
|
|
|
@ -366,7 +366,7 @@ def create_tables(db_file):
|
|||
id INTEGER NOT NULL,
|
||||
feed_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
|
||||
FOREIGN KEY ("feed_id") REFERENCES "feeds_properties" ("id")
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
FOREIGN KEY ("tag_id") REFERENCES "tags" ("id")
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
__version__ = '0.1.95'
|
||||
__version_info__ = (0, 1, 95)
|
||||
__version__ = '0.1.96'
|
||||
__version_info__ = (0, 1, 96)
|
||||
|
|
|
@ -154,18 +154,23 @@ class XmppChat:
|
|||
# await compose.message(self, jid_bare, message)
|
||||
|
||||
if self.omemo_present and self['xep_0384'].is_encrypted(message):
|
||||
allow_untrusted=True # Temporary fix. This should be handled by "retry""
|
||||
command, omemo_decrypted, retry = await XmppOmemo.decrypt(
|
||||
self, message, allow_untrusted)
|
||||
if retry:
|
||||
command, omemo_decrypted, retry = await XmppOmemo.decrypt(
|
||||
self, message, allow_untrusted=True)
|
||||
command, omemo_decrypted = await XmppOmemo.decrypt(
|
||||
self, message)
|
||||
else:
|
||||
omemo_decrypted = None
|
||||
|
||||
if message_type == 'groupchat':
|
||||
command = command[1:]
|
||||
command_lowercase = command.lower()
|
||||
|
||||
if isinstance(command, str):
|
||||
command_lowercase = command.lower()
|
||||
elif isinstance(command, Message):
|
||||
command_lowercase = command['body'].lower()
|
||||
|
||||
# This is a work-around to empty messages that are caused by function
|
||||
# self.register_handler(CoroutineCallback( of module client.py.
|
||||
# The code was taken from the cho bot xample of slixmpp-omemo.
|
||||
#if not command_lowercase: return
|
||||
|
||||
logger.debug([message_from.full, ':', command])
|
||||
|
||||
|
@ -363,7 +368,7 @@ class XmppChat:
|
|||
chat_type = await XmppUtilities.get_chat_type(self, jid_bare)
|
||||
if self.omemo_present and encrypted:
|
||||
url_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
|
||||
self, message_from, url)
|
||||
self, message_from, 'chat', url)
|
||||
XmppMessage.send_omemo_oob(self, message_from, url_encrypted, chat_type)
|
||||
else:
|
||||
XmppMessage.send_oob(self, jid_bare, url, chat_type)
|
||||
|
@ -632,7 +637,7 @@ class XmppChat:
|
|||
encrypted = True if encrypt_omemo else False
|
||||
if self.omemo_present and encrypted and self['xep_0384'].is_encrypted(message):
|
||||
response_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
|
||||
self, message_from, response)
|
||||
self, message_from, 'chat', response)
|
||||
if omemo_decrypted and omemo_encrypted:
|
||||
# message_from = message['from']
|
||||
# message_type = message['type']
|
||||
|
@ -737,7 +742,7 @@ class XmppChatAction:
|
|||
if media_url and news_digest:
|
||||
if self.omemo_present and encrypt_omemo:
|
||||
news_digest_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
|
||||
self, jid, news_digest)
|
||||
self, jid, 'chat', news_digest)
|
||||
if self.omemo_present and encrypt_omemo and omemo_encrypted:
|
||||
XmppMessage.send_omemo(self, jid, chat_type, news_digest_encrypted)
|
||||
else:
|
||||
|
@ -777,7 +782,7 @@ class XmppChatAction:
|
|||
# media_url_new = await XmppUpload.start(
|
||||
# self, jid_bare, Path(pathname), filesize, encrypted=encrypted)
|
||||
media_url_new_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
|
||||
self, jid, media_url_new)
|
||||
self, jid, 'chat', media_url_new)
|
||||
if media_url_new_encrypted and omemo_encrypted:
|
||||
# NOTE Tested against Gajim.
|
||||
# FIXME This only works with aesgcm URLs, and it does
|
||||
|
@ -801,7 +806,7 @@ class XmppChatAction:
|
|||
if news_digest:
|
||||
if self.omemo_present and encrypt_omemo:
|
||||
news_digest_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
|
||||
self, jid, news_digest)
|
||||
self, jid, 'chat', news_digest)
|
||||
if self.omemo_present and encrypt_omemo and omemo_encrypted:
|
||||
XmppMessage.send_omemo(self, jid, chat_type, news_digest_encrypted)
|
||||
else:
|
||||
|
|
|
@ -145,22 +145,40 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
self.register_plugin('xep_0115') # Entity Capabilities
|
||||
self.register_plugin('xep_0122') # Data Forms Validation
|
||||
self.register_plugin('xep_0153') # vCard-Based Avatars
|
||||
self.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping
|
||||
self.register_plugin('xep_0199', # XMPP Ping
|
||||
{'keepalive': True})
|
||||
self.register_plugin('xep_0203') # Delayed Delivery
|
||||
self.register_plugin('xep_0249') # Direct MUC Invitations
|
||||
self.register_plugin('xep_0363') # HTTP File Upload
|
||||
self.register_plugin('xep_0380') # Explicit Message Encryption
|
||||
self.register_plugin('xep_0402') # PEP Native Bookmarks
|
||||
self.register_plugin('xep_0444') # Message Reactions
|
||||
|
||||
try:
|
||||
from slixfeed.xmpp.encryption import XmppOmemo
|
||||
import slixmpp_omemo
|
||||
from slixmpp_omemo import PluginCouldNotLoad
|
||||
self.omemo_present = True
|
||||
except Exception as e:
|
||||
print('Encryption of type OMEMO is not enabled. Reason: ' + str(e))
|
||||
self.omemo_present = False
|
||||
|
||||
if self.omemo_present:
|
||||
#from slixmpp.xmlstream.handler import CoroutineCallback
|
||||
#from slixmpp.xmlstream.matcher import MatchXPath
|
||||
#self.register_handler(CoroutineCallback(
|
||||
# 'Messages',
|
||||
# MatchXPath(f'{{{self.default_ns}}}message'),
|
||||
# self.on_message # type: ignore[arg-type]
|
||||
#))
|
||||
|
||||
from slixfeed.xmpp.encryption import XEP_0384Impl
|
||||
from slixfeed.xmpp.encryption import XmppOmemo
|
||||
import slixfeed.xmpp.encryption as slixfeed_xmpp_encryption
|
||||
from slixmpp.plugins import register_plugin
|
||||
register_plugin(XEP_0384Impl)
|
||||
self.register_plugin('xep_0384', # OMEMO Encryption
|
||||
module=XEP_0384Impl)
|
||||
|
||||
"""
|
||||
if self.omemo_present:
|
||||
try:
|
||||
self.register_plugin(
|
||||
|
@ -177,7 +195,7 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
except slixmpp.plugins.base.PluginNotFound:
|
||||
logger.error('Could not load xep_0454. Ensure you have '
|
||||
'\'cryptography\' from extras_require installed.')
|
||||
|
||||
"""
|
||||
# proxy_enabled = config.get_value('accounts', 'XMPP', 'proxy_enabled')
|
||||
# if proxy_enabled == '1':
|
||||
# values = config.get_value('accounts', 'XMPP', [
|
||||
|
@ -870,6 +888,7 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
# http://jabber.org/protocol/commands#actions
|
||||
|
||||
async def _handle_publish(self, iq, session):
|
||||
jid = session['from']
|
||||
jid_full = session['from'].full
|
||||
function_name = sys._getframe().f_code.co_name
|
||||
logger.debug('{}: jid_full: {}'
|
||||
|
@ -911,6 +930,7 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
return session
|
||||
|
||||
async def _handle_publish_action(self, payload, session):
|
||||
jid = session['from']
|
||||
jid_full = session['from'].full
|
||||
function_name = sys._getframe().f_code.co_name
|
||||
logger.debug('{}: jid_full: {}'
|
||||
|
@ -1340,7 +1360,7 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
form.add_field(label='Active',
|
||||
ftype='text-single',
|
||||
value=feeds_act)
|
||||
entries = sqlite.get_number_of_items(db_file, 'entries_properties')
|
||||
entries = str(sqlite.get_number_of_items(db_file, 'entries_properties'))
|
||||
form.add_field(label='Items',
|
||||
ftype='text-single',
|
||||
value=entries)
|
||||
|
@ -1350,44 +1370,35 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
value=unread)
|
||||
form.add_field(ftype='fixed',
|
||||
label='Options')
|
||||
key_archive = Config.get_setting_value(self, jid_bare, 'archive')
|
||||
key_archive = str(key_archive)
|
||||
key_archive = str(Config.get_setting_value(self, jid_bare, 'archive'))
|
||||
form.add_field(label='Archive',
|
||||
ftype='text-single',
|
||||
value=key_archive)
|
||||
key_enabled = Config.get_setting_value(self, jid_bare, 'enabled')
|
||||
key_enabled = str(key_enabled)
|
||||
key_enabled = str(Config.get_setting_value(self, jid_bare, 'enabled'))
|
||||
form.add_field(label='Enabled',
|
||||
ftype='text-single',
|
||||
value=key_enabled)
|
||||
key_interval = Config.get_setting_value(self, jid_bare, 'interval')
|
||||
key_interval = str(key_interval)
|
||||
key_interval = str(Config.get_setting_value(self, jid_bare, 'interval'))
|
||||
form.add_field(label='Interval',
|
||||
ftype='text-single',
|
||||
value=key_interval)
|
||||
key_length = Config.get_setting_value(self, jid_bare, 'length')
|
||||
key_length = str(key_length)
|
||||
key_length = str(Config.get_setting_value(self, jid_bare, 'length'))
|
||||
form.add_field(label='Length',
|
||||
ftype='text-single',
|
||||
value=key_length)
|
||||
key_media = Config.get_setting_value(self, jid_bare, 'media')
|
||||
key_media = str(key_media)
|
||||
key_media = str(Config.get_setting_value(self, jid_bare, 'media'))
|
||||
form.add_field(label='Media',
|
||||
ftype='text-single',
|
||||
value=key_media)
|
||||
key_old = Config.get_setting_value(self, jid_bare, 'old')
|
||||
key_old = str(key_old)
|
||||
key_old = str(Config.get_setting_value(self, jid_bare, 'old'))
|
||||
form.add_field(label='Old',
|
||||
ftype='text-single',
|
||||
value=key_old)
|
||||
key_quantum = Config.get_setting_value(self, jid_bare, 'quantum')
|
||||
key_quantum = str(key_quantum)
|
||||
key_quantum = str(Config.get_setting_value(self, jid_bare, 'quantum'))
|
||||
form.add_field(label='Quantum',
|
||||
ftype='text-single',
|
||||
value=key_quantum)
|
||||
update_interval = Config.get_setting_value(self, jid_bare, 'interval')
|
||||
update_interval = str(update_interval)
|
||||
update_interval = 60 * int(update_interval)
|
||||
update_interval = 60 * Config.get_setting_value(self, jid_bare, 'interval')
|
||||
last_update_time = sqlite.get_last_update_time(db_file)
|
||||
if last_update_time:
|
||||
last_update_time = float(last_update_time)
|
||||
|
@ -2930,7 +2941,7 @@ class XmppClient(slixmpp.ClientXMPP):
|
|||
chat_type = await XmppUtilities.get_chat_type(self, jid_bare)
|
||||
if encrypted:
|
||||
url_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
|
||||
self, JID(jid_bare), url)
|
||||
self, JID(jid_bare), 'chat', url)
|
||||
XmppMessage.send_omemo_oob(self, JID(jid_bare), url_encrypted, chat_type)
|
||||
else:
|
||||
XmppMessage.send_oob(self, jid_bare, url, chat_type)
|
||||
|
|
|
@ -23,18 +23,19 @@ TODO
|
|||
|
||||
"""
|
||||
|
||||
from omemo.exceptions import MissingBundleException
|
||||
import json
|
||||
from omemo.storage import Just, Maybe, Nothing, Storage
|
||||
from omemo.types import DeviceInformation, JSONType
|
||||
from slixfeed.log import Logger
|
||||
from slixmpp import JID
|
||||
from slixmpp.exceptions import IqTimeout, IqError
|
||||
#from slixmpp.plugins import register_plugin
|
||||
from slixmpp.stanza import Message
|
||||
from slixmpp_omemo import MissingOwnKey, EncryptionPrepareException
|
||||
from slixmpp_omemo import UndecidedException, UntrustedException, NoAvailableSession
|
||||
|
||||
from slixmpp_omemo import TrustLevel, XEP_0384
|
||||
from typing import Any, Dict, FrozenSet, Literal, Optional, Union
|
||||
|
||||
logger = Logger(__name__)
|
||||
|
||||
|
||||
# for task in main_task:
|
||||
# task.cancel()
|
||||
|
||||
|
@ -46,7 +47,59 @@ logger = Logger(__name__)
|
|||
class XmppOmemo:
|
||||
|
||||
|
||||
async def decrypt(self, message: Message, allow_untrusted: bool = False):
|
||||
async def decrypt(self, stanza: Message):
|
||||
|
||||
omemo_decrypted = None
|
||||
|
||||
mto = stanza["from"]
|
||||
mtype = stanza["type"]
|
||||
|
||||
namespace = self['xep_0384'].is_encrypted(stanza)
|
||||
if namespace is None:
|
||||
omemo_decrypted = False
|
||||
response = f"Unencrypted message or unsupported message encryption: {stanza['body']}"
|
||||
else:
|
||||
print(f'Message in namespace {namespace} received: {stanza}')
|
||||
try:
|
||||
response, device_information = await self['xep_0384'].decrypt_message(stanza)
|
||||
print(f'Information about sender: {device_information}')
|
||||
omemo_decrypted = True
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
response = f'Error {type(e).__name__}: {e}'
|
||||
|
||||
return response, omemo_decrypted
|
||||
|
||||
|
||||
async def encrypt(
|
||||
self,
|
||||
mto: JID,
|
||||
mtype: Literal['chat', 'normal'],
|
||||
mbody: str
|
||||
) -> None:
|
||||
|
||||
if isinstance(mbody, str):
|
||||
reply = self.make_message(mto=mto, mtype=mtype)
|
||||
reply['body'] = mbody
|
||||
|
||||
reply.set_to(mto)
|
||||
reply.set_from(self.boundjid)
|
||||
|
||||
# It might be a good idea to strip everything except for the body from the stanza,
|
||||
# since some things might break when echoed.
|
||||
message, encryption_errors = await self['xep_0384'].encrypt_message(reply, mto)
|
||||
|
||||
if len(encryption_errors) > 0:
|
||||
print(f'There were non-critical errors during encryption: {encryption_errors}')
|
||||
# log.info(f'There were non-critical errors during encryption: {encryption_errors}')
|
||||
|
||||
# for namespace, message in messages.items():
|
||||
# message['eme']['namespace'] = namespace
|
||||
# message['eme']['name'] = self['xep_0380'].mechanisms[namespace]
|
||||
|
||||
return message, True
|
||||
|
||||
|
||||
async def _decrypt(self, message: Message, allow_untrusted: bool = False):
|
||||
jid = message['from']
|
||||
try:
|
||||
print('XmppOmemo.decrypt')
|
||||
|
@ -124,7 +177,7 @@ class XmppOmemo:
|
|||
return response, omemo_decrypted, retry
|
||||
|
||||
|
||||
async def encrypt(self, jid: JID, message_body):
|
||||
async def _encrypt(self, jid: JID, message_body):
|
||||
print(jid)
|
||||
print(message_body)
|
||||
expect_problems = {} # type: Optional[Dict[JID, List[int]]]
|
||||
|
@ -192,3 +245,95 @@ class XmppOmemo:
|
|||
raise
|
||||
|
||||
return message_body, omemo_encrypted
|
||||
|
||||
|
||||
class StorageImpl(Storage):
|
||||
"""
|
||||
Example storage implementation that stores all data in a single JSON file.
|
||||
"""
|
||||
|
||||
JSON_FILE = "/home/admin/omemo-echo-client.json"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.__data: Dict[str, JSONType] = {}
|
||||
try:
|
||||
with open(self.JSON_FILE, encoding="utf8") as f:
|
||||
self.__data = json.load(f)
|
||||
except Exception: # pylint: disable=broad-exception-caught
|
||||
pass
|
||||
|
||||
async def _load(self, key: str) -> Maybe[JSONType]:
|
||||
if key in self.__data:
|
||||
return Just(self.__data[key])
|
||||
|
||||
return Nothing()
|
||||
|
||||
async def _store(self, key: str, value: JSONType) -> None:
|
||||
self.__data[key] = value
|
||||
with open(self.JSON_FILE, "w", encoding="utf8") as f:
|
||||
json.dump(self.__data, f)
|
||||
|
||||
async def _delete(self, key: str) -> None:
|
||||
self.__data.pop(key, None)
|
||||
with open(self.JSON_FILE, "w", encoding="utf8") as f:
|
||||
json.dump(self.__data, f)
|
||||
|
||||
|
||||
class XEP_0384Impl(XEP_0384): # pylint: disable=invalid-name
|
||||
"""
|
||||
Example implementation of the OMEMO plugin for Slixmpp.
|
||||
"""
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: # pylint: disable=redefined-outer-name
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Just the type definition here
|
||||
self.__storage: Storage
|
||||
|
||||
def plugin_init(self) -> None:
|
||||
self.__storage = StorageImpl()
|
||||
|
||||
super().plugin_init()
|
||||
|
||||
@property
|
||||
def storage(self) -> Storage:
|
||||
return self.__storage
|
||||
|
||||
@property
|
||||
def _btbv_enabled(self) -> bool:
|
||||
return True
|
||||
|
||||
async def _devices_blindly_trusted(
|
||||
self,
|
||||
blindly_trusted: FrozenSet[DeviceInformation],
|
||||
identifier: Optional[str]
|
||||
) -> None:
|
||||
log.info(f"[{identifier}] Devices trusted blindly: {blindly_trusted}")
|
||||
|
||||
async def _prompt_manual_trust(
|
||||
self,
|
||||
manually_trusted: FrozenSet[DeviceInformation],
|
||||
identifier: Optional[str]
|
||||
) -> None:
|
||||
# Since BTBV is enabled and we don't do any manual trust adjustments in the example, this method
|
||||
# should never be called. All devices should be automatically trusted blindly by BTBV.
|
||||
|
||||
# To show how a full implementation could look like, the following code will prompt for a trust
|
||||
# decision using `input`:
|
||||
session_mananger = await self.get_session_manager()
|
||||
|
||||
for device in manually_trusted:
|
||||
while True:
|
||||
answer = input(f"[{identifier}] Trust the following device? (yes/no) {device}")
|
||||
if answer in { "yes", "no" }:
|
||||
await session_mananger.set_trust(
|
||||
device.bare_jid,
|
||||
device.identity_key,
|
||||
TrustLevel.TRUSTED.value if answer == "yes" else TrustLevel.DISTRUSTED.value
|
||||
)
|
||||
break
|
||||
print("Please answer yes or no.")
|
||||
|
||||
#register_plugin(XEP_0384Impl)
|
||||
|
|
|
@ -41,14 +41,17 @@ class XmppMessage:
|
|||
|
||||
|
||||
def send_omemo(self, jid: JID, chat_type, response_encrypted):
|
||||
jid_from = str(self.boundjid) if self.is_component else None
|
||||
message = self.make_message(mto=jid, mfrom=jid_from, mtype=chat_type)
|
||||
eme_ns = 'eu.siacs.conversations.axolotl'
|
||||
# jid_from = str(self.boundjid) if self.is_component else None
|
||||
# message = self.make_message(mto=jid, mfrom=jid_from, mtype=chat_type)
|
||||
# eme_ns = 'eu.siacs.conversations.axolotl'
|
||||
# message['eme']['namespace'] = eme_ns
|
||||
# message['eme']['name'] = self['xep_0380'].mechanisms[eme_ns]
|
||||
message['eme'] = {'namespace': eme_ns}
|
||||
# message['eme'] = {'name': self['xep_0380'].mechanisms[eme_ns]}
|
||||
message.append(response_encrypted)
|
||||
# message['eme'] = {'namespace': eme_ns}
|
||||
# message.append(response_encrypted)
|
||||
for namespace, message in response_encrypted.items():
|
||||
message['eme']['namespace'] = namespace
|
||||
message['eme']['name'] = self['xep_0380'].mechanisms[namespace]
|
||||
message.send()
|
||||
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class XmppUtilities:
|
|||
access = True if XmppUtilities.is_moderator(self, room, alias) else False
|
||||
if access: print('Access granted to groupchat moderator ' + alias)
|
||||
else:
|
||||
print('Access granted to chat ' + jid_bare)
|
||||
print('Access granted to chat jid ' + jid_bare)
|
||||
access = True
|
||||
return access
|
||||
|
||||
|
|
Loading…
Reference in a new issue