Fix Matrix v3 attachments (#1373)

pull/1294/head
PrivacyFreak 2025-07-29 16:15:46 +00:00 committed by GitHub
parent 8929358803
commit f65f99cd5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 105 additions and 97 deletions

View File

@ -81,6 +81,9 @@ IS_ROOM_ID = re.compile(
re.I, re.I,
) )
# Matrix is_image check
IS_IMAGE = re.compile(r"^image/.*", re.I)
class MatrixMessageType: class MatrixMessageType:
"""The Matrix Message types.""" """The Matrix Message types."""
@ -692,6 +695,9 @@ class NotifyMatrix(NotifyBase):
# Get our room # Get our room
room = rooms.pop(0) room = rooms.pop(0)
# Set method according to MatrixVersion
method = "PUT" if self.version == MatrixVersion.V3 else "POST"
# Get our room_id from our response # Get our room_id from our response
room_id = self._room_join(room) room_id = self._room_join(room)
if not room_id: if not room_id:
@ -717,11 +723,7 @@ class NotifyMatrix(NotifyBase):
f"/rooms/{NotifyMatrix.quote(room_id)}/send/m.room.message" f"/rooms/{NotifyMatrix.quote(room_id)}/send/m.room.message"
) )
if self.version == MatrixVersion.V2: if image_url and self.version == MatrixVersion.V2:
#
# Attachments don't work beyond V2 at this time
#
if image_url:
# Define our payload # Define our payload
image_payload = { image_payload = {
"msgtype": "m.image", "msgtype": "m.image",
@ -731,8 +733,7 @@ class NotifyMatrix(NotifyBase):
# Post our content # Post our content
postokay, response = self._fetch( postokay, response = self._fetch(
path, payload=image_payload path, payload=image_payload)
)
if not postokay: if not postokay:
# Mark our failure # Mark our failure
has_error = True has_error = True
@ -744,8 +745,21 @@ class NotifyMatrix(NotifyBase):
attachment["type"] = "m.room.message" attachment["type"] = "m.room.message"
postokay, response = self._fetch( postokay, response = self._fetch(
path, payload=attachment path, payload=attachment, method=method)
# Increment the transaction ID to avoid future messages
# being recognized as retransmissions and ignored
if self.version == MatrixVersion.V3 \
and self.access_token != self.password:
self.transaction_id += 1
self.store.set(
"transaction_id", self.transaction_id,
expires=self.default_cache_expiry_sec)
path = "/rooms/{}/send/m.room.message/{}".format(
NotifyMatrix.quote(room_id),
self.transaction_id,
) )
if not postokay: if not postokay:
# Mark our failure # Mark our failure
has_error = True has_error = True
@ -790,7 +804,6 @@ class NotifyMatrix(NotifyBase):
}) })
# Post our content # Post our content
method = "PUT" if self.version == MatrixVersion.V3 else "POST"
postokay, response = self._fetch( postokay, response = self._fetch(
path, payload=payload, method=method path, payload=payload, method=method
) )
@ -824,18 +837,14 @@ class NotifyMatrix(NotifyBase):
"""Posts all of the provided attachments.""" """Posts all of the provided attachments."""
payloads = [] payloads = []
if self.version != MatrixVersion.V2:
self.logger.warning(
"Add ?v=2 to Apprise URL to support Attachments"
)
return next((False for a in attach if not a), [])
for attachment in attach: for attachment in attach:
if not attachment: if not attachment:
# invalid attachment (bad file) # invalid attachment (bad file)
return False return False
if not re.match(r"^image/", attachment.mimetype, re.I): if not IS_IMAGE.match(attachment.mimetype) \
and self.version == MatrixVersion.V2:
# unsuppored at this time # unsuppored at this time
continue continue
@ -849,29 +858,23 @@ class NotifyMatrix(NotifyBase):
# "content_uri": "mxc://example.com/a-unique-key" # "content_uri": "mxc://example.com/a-unique-key"
# } # }
# FUTURE if self.version == MatrixVersion.V3: if self.version == MatrixVersion.V3:
# FUTURE # Prepare our payload # Prepare our payload
# FUTURE payloads.append({ is_image = IS_IMAGE.match(attachment.mimetype)
# FUTURE "body": attachment.name, payloads.append({
# FUTURE "info": { "body": attachment.name,
# FUTURE "mimetype": attachment.mimetype, "info": {
# FUTURE "size": len(attachment), "mimetype": attachment.mimetype,
# FUTURE }, "size": len(attachment),
# FUTURE "msgtype": "m.image", },
# FUTURE "url": response.get('content_uri'), "msgtype": "m.image" if is_image else "m.file",
# FUTURE }) "url": response.get("content_uri"),
})
# FUTURE else: if not is_image:
# FUTURE # Prepare our payload # Setup `m.file'
# FUTURE payloads.append({ payloads[-1]["filename"] = attachment.name
# FUTURE "info": {
# FUTURE "mimetype": attachment.mimetype,
# FUTURE },
# FUTURE "msgtype": "m.image",
# FUTURE "body": "tta.webp",
# FUTURE "url": response.get('content_uri'),
# FUTURE })
else:
# Prepare our payload # Prepare our payload
payloads.append({ payloads.append({
"info": { "info": {
@ -1335,11 +1338,10 @@ class NotifyMatrix(NotifyBase):
status_code = requests.codes.internal_server_error status_code = requests.codes.internal_server_error
if path == "/upload": if path == "/upload":
# FUTURE if self.version == MatrixVersion.V3: if self.version == MatrixVersion.V3:
# FUTURE url += MATRIX_V3_MEDIA_PATH + path url += MATRIX_V3_MEDIA_PATH + path
# FUTURE else: else:
# FUTURE url += MATRIX_V2_MEDIA_PATH + path
url += MATRIX_V2_MEDIA_PATH + path url += MATRIX_V2_MEDIA_PATH + path
params.update({"filename": attachment.name}) params.update({"filename": attachment.name})

View File

@ -1002,9 +1002,8 @@ def test_plugin_matrix_image_errors(mock_post, mock_get, mock_put):
@mock.patch("requests.put") @mock.patch("requests.put")
@mock.patch("requests.get")
@mock.patch("requests.post") @mock.patch("requests.post")
def test_plugin_matrix_attachments_api_v3(mock_post, mock_get, mock_put): def test_plugin_matrix_attachments_api_v3(mock_post, mock_put):
"""NotifyMatrix() Attachment Checks (v3)""" """NotifyMatrix() Attachment Checks (v3)"""
# Prepare a good response # Prepare a good response
@ -1018,7 +1017,6 @@ def test_plugin_matrix_attachments_api_v3(mock_post, mock_get, mock_put):
# Prepare Mock return object # Prepare Mock return object
mock_post.return_value = response mock_post.return_value = response
mock_get.return_value = response
mock_put.return_value = response mock_put.return_value = response
# Instantiate our object # Instantiate our object
@ -1040,23 +1038,22 @@ def test_plugin_matrix_attachments_api_v3(mock_post, mock_get, mock_put):
attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, "apprise-test.gif")) attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, "apprise-test.gif"))
# Test our call count # Test our call count
assert mock_put.call_count == 1 assert mock_put.call_count == 2
assert mock_post.call_count == 2 assert mock_post.call_count == 3
assert ( assert mock_post.call_args_list[0][0][0] == \
mock_post.call_args_list[0][0][0] "http://localhost/_matrix/client/v3/login"
== "http://localhost/_matrix/client/v3/login" assert mock_post.call_args_list[1][0][0] == \
) "http://localhost/_matrix/media/v3/upload"
assert ( assert mock_post.call_args_list[2][0][0] == \
mock_post.call_args_list[1][0][0] "http://localhost/_matrix/client/v3/join/%23general%3Alocalhost"
== "http://localhost/_matrix/client/v3/join/%23general%3Alocalhost" assert mock_put.call_args_list[0][0][0] == \
) "http://localhost/_matrix/client/v3/rooms/%21abc123%3Alocalhost/" \
assert (
mock_put.call_args_list[0][0][0]
== "http://localhost/_matrix/client/v3/rooms/%21abc123%3Alocalhost/"
"send/m.room.message/0" "send/m.room.message/0"
) assert mock_put.call_args_list[1][0][0] == \
"http://localhost/_matrix/client/v3/rooms/%21abc123%3Alocalhost/" \
"send/m.room.message/1"
# Attach an unsupported file type (it's just skipped) # Attach a zip file type
attach = AppriseAttachment( attach = AppriseAttachment(
os.path.join(TEST_VAR_DIR, "apprise-archive.zip") os.path.join(TEST_VAR_DIR, "apprise-archive.zip")
) )
@ -1086,28 +1083,37 @@ def test_plugin_matrix_attachments_api_v3(mock_post, mock_get, mock_put):
# update our attachment to be valid # update our attachment to be valid
attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, "apprise-test.gif")) attach = AppriseAttachment(os.path.join(TEST_VAR_DIR, "apprise-test.gif"))
mock_put.return_value = None
mock_post.return_value = None mock_post.return_value = None
# Throw an exception on the first call to requests.post() # Throw an exception on the first call to requests.post()
for side_effect in (requests.RequestException(), OSError(), bad_response): for side_effect in (requests.RequestException(), OSError(), bad_response):
# Reset our value
mock_put.reset_mock()
mock_post.reset_mock()
mock_post.side_effect = [side_effect] mock_post.side_effect = [side_effect]
# We'll never fail because files are not attached assert obj.send(body="test", attach=attach) is False
assert obj.send(body="test", attach=attach) is True
# Throw an exception on the second call to requests.post() # Throw an exception on the second call to requests.post()
for side_effect in (requests.RequestException(), OSError(), bad_response): for side_effect in (requests.RequestException(), OSError(), bad_response):
mock_post.side_effect = [response, side_effect] # Reset our value
mock_put.reset_mock()
mock_post.reset_mock()
# Attachment support does not exist vor v3 at time, so this will mock_put.side_effect = [side_effect, response]
# work nicely mock_post.side_effect = [response, side_effect, response]
assert obj.send(body="test", attach=attach) is True
# We'll fail now because of our error handling
assert obj.send(body="test", attach=attach) is False
# handle a bad response # handle a bad response
mock_put.side_effect = [bad_response, response]
mock_post.side_effect = [response, bad_response, response] mock_post.side_effect = [response, bad_response, response]
# Attachment support does not exist vor v3 at time, so this will # We'll fail now because of an internal exception
# work nicely assert obj.send(body="test", attach=attach) is False
assert obj.send(body="test", attach=attach) is True
# Force a object removal (thus a logout call) # Force a object removal (thus a logout call)
del obj del obj