Add chat command for omemo;

Fix NameError: name 'XmppChat' is not defined of function XmppOmemo.decrypt.
This commit is contained in:
Schimon Jehudah, Adv. 2024-07-07 11:16:00 +03:00
parent f9a1c683cd
commit 51943b5b0c
7 changed files with 119 additions and 38 deletions

View file

@ -36,6 +36,7 @@ NOTE
""" """
import aiofiles
from aiohttp import ClientError, ClientSession, ClientTimeout from aiohttp import ClientError, ClientSession, ClientTimeout
from asyncio import TimeoutError from asyncio import TimeoutError
# from asyncio.exceptions import IncompleteReadError # from asyncio.exceptions import IncompleteReadError
@ -135,6 +136,9 @@ class Http:
) as response: ) as response:
status = response.status status = response.status
if status in (200, 201): if status in (200, 201):
f = await aiofiles.open(pathname, mode='wb')
await f.write(await response.read())
await f.close()
try: try:
result = {'charset': response.charset, result = {'charset': response.charset,
'content_length': response.content_length, 'content_length': response.content_length,

View file

@ -327,6 +327,7 @@ class Html:
'//img[not(' '//img[not('
'contains(@src, "avatar") or ' 'contains(@src, "avatar") or '
'contains(@src, "cc-by-sa") or ' 'contains(@src, "cc-by-sa") or '
'contains(@src, "data:image/") or '
'contains(@src, "emoji") or ' 'contains(@src, "emoji") or '
'contains(@src, "icon") or ' 'contains(@src, "icon") or '
'contains(@src, "logo") or ' 'contains(@src, "logo") or '

View file

@ -1,2 +1,2 @@
__version__ = '0.1.87' __version__ = '0.1.88'
__version_info__ = (0, 1, 87) __version_info__ = (0, 1, 88)

View file

@ -46,6 +46,7 @@ from slixmpp import JID
from slixmpp.stanza import Message from slixmpp.stanza import Message
import sys import sys
import time import time
from typing import Optional
logger = Logger(__name__) logger = Logger(__name__)
@ -150,8 +151,11 @@ class XmppChat:
# await compose.message(self, jid_bare, message) # await compose.message(self, jid_bare, message)
if self['xep_0384'].is_encrypted(message): if self['xep_0384'].is_encrypted(message):
command, omemo_decrypted = await XmppOmemo.decrypt( command, omemo_decrypted, retry = await XmppOmemo.decrypt(
self, message, allow_untrusted) self, message, allow_untrusted)
if retry:
command, omemo_decrypted, retry = await XmppOmemo.decrypt(
self, message, allow_untrusted=True)
else: else:
omemo_decrypted = None omemo_decrypted = None
@ -353,7 +357,12 @@ class XmppChat:
# XmppMessage.send_oob_reply_message(message, url, response) # XmppMessage.send_oob_reply_message(message, url, response)
if url: if url:
chat_type = await XmppUtilities.get_chat_type(self, jid_bare) chat_type = await XmppUtilities.get_chat_type(self, jid_bare)
XmppMessage.send_oob(self, jid_bare, url, chat_type) if encrypted:
url_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
self, JID(jid_bare), url)
XmppMessage.send_omemo_oob(self, JID(jid_bare), url_encrypted, chat_type)
else:
XmppMessage.send_oob(self, jid_bare, url, chat_type)
else: else:
response = 'OPML file export has been failed.' response = 'OPML file export has been failed.'
del self.pending_tasks[jid_bare][pending_tasks_num] del self.pending_tasks[jid_bare][pending_tasks_num]
@ -473,6 +482,13 @@ class XmppChat:
self, jid_bare, db_file) self, jid_bare, db_file)
case _ if command_lowercase.startswith('next'): case _ if command_lowercase.startswith('next'):
num = command[5:] num = command[5:]
if num:
try:
int(num)
except:
# NOTE Show this text as a status message
# response = 'Argument for command "next" must be an integer.'
num = None
await XmppChatAction.send_unread_items(self, jid_bare, num) await XmppChatAction.send_unread_items(self, jid_bare, num)
XmppStatusTask.restart_task(self, jid_bare) XmppStatusTask.restart_task(self, jid_bare)
case _ if command_lowercase.startswith('node delete'): case _ if command_lowercase.startswith('node delete'):
@ -494,6 +510,12 @@ class XmppChat:
case 'old': case 'old':
response = await XmppCommands.set_old_on( response = await XmppCommands.set_old_on(
self, jid_bare, db_file) self, jid_bare, db_file)
case 'omemo off':
response = await XmppCommands.set_omemo_off(
self, jid_bare, db_file)
case 'omemo on':
response = await XmppCommands.set_omemo_on(
self, jid_bare, db_file)
case 'options': case 'options':
response = 'Options:\n```' response = 'Options:\n```'
response += XmppCommands.print_options(self, jid_bare) response += XmppCommands.print_options(self, jid_bare)
@ -572,9 +594,9 @@ class XmppChat:
XmppPresence.send(self, jid_bare, status_message, XmppPresence.send(self, jid_bare, status_message,
status_type=status_type) status_type=status_type)
await asyncio.sleep(5) await asyncio.sleep(5)
tasks = (FeedTask, XmppChatTask, XmppStatusTask) callbacks = (FeedTask, XmppChatTask, XmppStatusTask)
response = await XmppCommands.scheduler_start( response = await XmppCommands.scheduler_start(
self, db_file, jid_bare, tasks) self, db_file, jid_bare, callbacks)
case 'stats': case 'stats':
response = XmppCommands.print_statistics(db_file) response = XmppCommands.print_statistics(db_file)
case 'stop': case 'stop':
@ -639,7 +661,7 @@ class XmppChat:
class XmppChatAction: class XmppChatAction:
async def send_unread_items(self, jid_bare, num=None): async def send_unread_items(self, jid_bare, num: Optional[int] = None):
""" """
Send news items as messages. Send news items as messages.
@ -693,6 +715,17 @@ class XmppChatAction:
media_url = enclosure media_url = enclosure
else: else:
media_url = await Html.extract_image_from_html(url) media_url = await Html.extract_image_from_html(url)
try:
http_headers = await Http.fetch_headers(media_url)
if ('Content-Length' in http_headers):
if int(http_headers['Content-Length']) < 100000:
media_url = None
else:
media_url = None
except Exception as e:
print(media_url)
logger.error(e)
media_url = None
if media_url and news_digest: if media_url and news_digest:
if encrypt_omemo: if encrypt_omemo:
@ -707,34 +740,55 @@ class XmppChatAction:
# Send media # Send media
if encrypt_omemo: if encrypt_omemo:
cache_dir = config.get_default_cache_directory() cache_dir = config.get_default_cache_directory()
# if not media_url.startswith('data:'):
filename = media_url.split('/').pop().split('?')[0] filename = media_url.split('/').pop().split('?')[0]
if not filename: breakpoint()
pathname = os.path.join(cache_dir, filename) pathname = os.path.join(cache_dir, filename)
# http_response = await Http.response(media_url) # http_response = await Http.response(media_url)
http_headers = await Http.fetch_headers(media_url)
# http_headers = await Http.fetch_headers(media_url) if ('Content-Length' in http_headers and
# breakpoint() int(http_headers['Content-Length']) < 3000000):
# status = Http.fetch_media(media_url, pathname) status = await Http.fetch_media(media_url, pathname)
# if status: if status:
filesize = os.path.getsize(pathname)
media_url_new = await XmppUpload.start(
self, jid_bare, Path(pathname), filesize, encrypted=encrypted)
else:
media_url_new = media_url
else:
media_url_new = media_url
# else:
# import io, base64
# from PIL import Image
# file_content = media_url.split(',').pop()
# file_extension = media_url.split(';')[0].split(':').pop().split('/').pop()
# img = Image.open(io.BytesIO(base64.decodebytes(bytes(file_content, "utf-8"))))
# filename = 'image.' + file_extension
# pathname = os.path.join(cache_dir, filename)
# img.save(pathname)
# filesize = os.path.getsize(pathname) # filesize = os.path.getsize(pathname)
# media_url_new = await XmppUpload.start( # media_url_new = await XmppUpload.start(
# self, jid_bare, Path(pathname), filesize, encrypted=encrypted) # self, jid_bare, Path(pathname), filesize, encrypted=encrypted)
# else:
# media_url_new = media_url
media_url_new = media_url
media_url_new_encrypted, omemo_encrypted = await XmppOmemo.encrypt( media_url_new_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
self, jid, media_url_new) self, jid, media_url_new)
if media_url_new_encrypted and omemo_encrypted:
# NOTE Temporary line! # NOTE Tested against Gajim.
XmppMessage.send_omemo_oob(self, jid_bare, media_url_new_encrypted, chat_type) # FIXME This only works with aesgcm URLs, and it does
# not work with http URLs.
# url = saxutils.escape(url)
# if media_url_new_encrypted and omemo_encrypted: # AttributeError: 'Encrypted' object has no attribute 'replace'
# XmppMessage.send_omemo_oob(self, jid, media_url_new_encrypted, chat_type) XmppMessage.send_omemo_oob(self, jid, media_url_new_encrypted, chat_type)
# elif media_url:
# XmppMessage.send_oob(self, jid_bare, media_url_new_encrypted, chat_type)
else: else:
XmppMessage.send_oob(self, jid_bare, media_url, chat_type) # NOTE Tested against Gajim.
# FIXME Jandle data: URIs.
if not media_url.startswith('data:'):
http_headers = await Http.fetch_headers(media_url)
if ('Content-Length' in http_headers and
int(http_headers['Content-Length']) > 100000):
print(http_headers['Content-Length'])
XmppMessage.send_oob(self, jid_bare, media_url, chat_type)
else:
XmppMessage.send_oob(self, jid_bare, media_url, chat_type)
media_url = None media_url = None
if news_digest: if news_digest:

View file

@ -43,7 +43,6 @@ import slixmpp
# import xml.etree.ElementTree as ET # import xml.etree.ElementTree as ET
# from lxml import etree # from lxml import etree
from omemo.exceptions import MissingBundleException
import slixfeed.config as config import slixfeed.config as config
from slixfeed.config import Config, Data from slixfeed.config import Config, Data
import slixfeed.fetch as fetch import slixfeed.fetch as fetch
@ -55,11 +54,12 @@ from slixfeed.version import __version__
from slixfeed.xmpp.bookmark import XmppBookmark from slixfeed.xmpp.bookmark import XmppBookmark
from slixfeed.xmpp.chat import XmppChat, XmppChatTask from slixfeed.xmpp.chat import XmppChat, XmppChatTask
from slixfeed.xmpp.connect import XmppConnect, XmppConnectTask from slixfeed.xmpp.connect import XmppConnect, XmppConnectTask
from slixfeed.xmpp.encryption import XmppOmemo
from slixfeed.xmpp.groupchat import XmppGroupchat
from slixfeed.xmpp.ipc import XmppIpcServer from slixfeed.xmpp.ipc import XmppIpcServer
from slixfeed.xmpp.iq import XmppIQ from slixfeed.xmpp.iq import XmppIQ
from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.message import XmppMessage
from slixfeed.xmpp.muc import XmppMuc from slixfeed.xmpp.muc import XmppMuc
from slixfeed.xmpp.groupchat import XmppGroupchat
from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.presence import XmppPresence
import slixfeed.xmpp.profile as profile import slixfeed.xmpp.profile as profile
from slixfeed.xmpp.publish import XmppPubsub, XmppPubsubAction, XmppPubsubTask from slixfeed.xmpp.publish import XmppPubsub, XmppPubsubAction, XmppPubsubTask
@ -68,9 +68,9 @@ from slixfeed.xmpp.roster import XmppRoster
from slixfeed.xmpp.status import XmppStatusTask from slixfeed.xmpp.status import XmppStatusTask
from slixfeed.xmpp.upload import XmppUpload from slixfeed.xmpp.upload import XmppUpload
from slixfeed.xmpp.utilities import XmppUtilities from slixfeed.xmpp.utilities import XmppUtilities
from slixmpp import JID
import slixmpp_omemo import slixmpp_omemo
from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, EncryptionPrepareException from slixmpp_omemo import PluginCouldNotLoad
from slixmpp_omemo import UndecidedException, UntrustedException, NoAvailableSession
import sys import sys
import time import time
@ -2906,7 +2906,12 @@ class XmppClient(slixmpp.ClientXMPP):
if url: if url:
form['instructions'] = 'Export has been completed successfully!' form['instructions'] = 'Export has been completed successfully!'
chat_type = await XmppUtilities.get_chat_type(self, jid_bare) chat_type = await XmppUtilities.get_chat_type(self, jid_bare)
XmppMessage.send_oob(self, jid_bare, url, chat_type) if encrypted:
url_encrypted, omemo_encrypted = await XmppOmemo.encrypt(
self, JID(jid_bare), url)
XmppMessage.send_omemo_oob(self, JID(jid_bare), url_encrypted, chat_type)
else:
XmppMessage.send_oob(self, jid_bare, url, chat_type)
url_field = form.add_field(var=ext.upper(), url_field = form.add_field(var=ext.upper(),
ftype='text-single', ftype='text-single',
label=ext, label=ext,

View file

@ -648,6 +648,18 @@ class XmppCommands:
return message return message
async def set_omemo_off(self, jid_bare, db_file):
await Config.set_setting_value(self.settings, jid_bare, db_file, 'omemo', 0)
message = 'OMEMO is disabled.'
return message
async def set_omemo_on(self, jid_bare, db_file):
await Config.set_setting_value(self.settings, jid_bare, db_file, 'omemo', 1)
message = 'OMEMO is enabled.'
return message
def node_delete(self, info): def node_delete(self, info):
info = info.split(' ') info = info.split(' ')
if len(info) > 2: if len(info) > 2:
@ -958,10 +970,10 @@ class XmppCommands:
# Tasks are classes which are passed to this function # Tasks are classes which are passed to this function
# On an occasion in which they would have returned, variable "tasks" might be called "callback" # On an occasion in which they would have returned, variable "tasks" might be called "callback"
async def scheduler_start(self, db_file, jid_bare, tasks): async def scheduler_start(self, db_file, jid_bare, callbacks):
await Config.set_setting_value(self.settings, jid_bare, db_file, 'enabled', 1) await Config.set_setting_value(self.settings, jid_bare, db_file, 'enabled', 1)
for task in tasks: for callback in callbacks:
task.restart_task(self, jid_bare) callback.restart_task(self, jid_bare)
message = 'Updates are enabled.' message = 'Updates are enabled.'
return message return message

View file

@ -23,13 +23,13 @@ TODO
""" """
from omemo.exceptions import MissingBundleException
from slixfeed.log import Logger from slixfeed.log import Logger
from slixmpp import JID from slixmpp import JID
from slixmpp.exceptions import IqTimeout, IqError from slixmpp.exceptions import IqTimeout, IqError
from slixmpp.stanza import Message from slixmpp.stanza import Message
from slixmpp_omemo import PluginCouldNotLoad, MissingOwnKey, EncryptionPrepareException from slixmpp_omemo import MissingOwnKey, EncryptionPrepareException
from slixmpp_omemo import UndecidedException, UntrustedException, NoAvailableSession from slixmpp_omemo import UndecidedException, UntrustedException, NoAvailableSession
from omemo.exceptions import MissingBundleException
logger = Logger(__name__) logger = Logger(__name__)
@ -66,6 +66,7 @@ class XmppOmemo:
response = ('Error: Your message has not been encrypted for ' response = ('Error: Your message has not been encrypted for '
'Slixfeed (MissingOwnKey).') 'Slixfeed (MissingOwnKey).')
omemo_decrypted = False omemo_decrypted = False
retry = False
logger.error(exn) logger.error(exn)
except (NoAvailableSession,) as exn: except (NoAvailableSession,) as exn:
# We received a message from that contained a session that we # We received a message from that contained a session that we
@ -77,6 +78,7 @@ class XmppOmemo:
response = ('Error: Your message has not been encrypted for ' response = ('Error: Your message has not been encrypted for '
'Slixfeed (NoAvailableSession).') 'Slixfeed (NoAvailableSession).')
omemo_decrypted = False omemo_decrypted = False
retry = False
logger.error(exn) logger.error(exn)
except (UndecidedException, UntrustedException) as exn: except (UndecidedException, UntrustedException) as exn:
# We received a message from an untrusted device. We can # We received a message from an untrusted device. We can
@ -90,9 +92,10 @@ class XmppOmemo:
response = (f'Error: Device "{exn.device}" is not present in the ' response = (f'Error: Device "{exn.device}" is not present in the '
'trusted devices of Slixfeed.') 'trusted devices of Slixfeed.')
omemo_decrypted = False omemo_decrypted = False
retry = True
logger.error(exn) logger.error(exn)
# We resend, setting the `allow_untrusted` parameter to True. # We resend, setting the `allow_untrusted` parameter to True.
await XmppChat.process_message(self, message, allow_untrusted=True) # await XmppChat.process_message(self, message, allow_untrusted=True)
except (EncryptionPrepareException,) as exn: except (EncryptionPrepareException,) as exn:
# Slixmpp tried its best, but there were errors it couldn't # Slixmpp tried its best, but there were errors it couldn't
# resolve. At this point you should have seen other exceptions # resolve. At this point you should have seen other exceptions
@ -100,15 +103,17 @@ class XmppOmemo:
response = ('Error: Your message has not been encrypted for ' response = ('Error: Your message has not been encrypted for '
'Slixfeed (EncryptionPrepareException).') 'Slixfeed (EncryptionPrepareException).')
omemo_decrypted = False omemo_decrypted = False
retry = False
logger.error(exn) logger.error(exn)
except (Exception,) as exn: except (Exception,) as exn:
response = ('Error: Your message has not been encrypted for ' response = ('Error: Your message has not been encrypted for '
'Slixfeed (Unknown).') 'Slixfeed (Unknown).')
omemo_decrypted = False omemo_decrypted = False
retry = False
logger.error(exn) logger.error(exn)
raise raise
return response, omemo_decrypted return response, omemo_decrypted, retry
async def encrypt(self, jid: JID, message_body): async def encrypt(self, jid: JID, message_body):