diff --git a/apprise/plugins/matrix.py b/apprise/plugins/matrix.py index c1401f23..70a40987 100644 --- a/apprise/plugins/matrix.py +++ b/apprise/plugins/matrix.py @@ -293,6 +293,9 @@ class NotifyMatrix(NotifyBase): # This gets initialized after a login/registration self.access_token = None + # This gets incremented for each request made against the v3 API + self.transaction_id = 0 + # Place an image inline with the message body self.include_image = include_image @@ -612,8 +615,10 @@ class NotifyMatrix(NotifyBase): # Build our path if self.version == MatrixVersion.V3: - path = '/rooms/{}/send/m.room.message/0'.format( - NotifyMatrix.quote(room_id)) + path = '/rooms/{}/send/m.room.message/{}'.format( + NotifyMatrix.quote(room_id), + self.transaction_id, + ) else: path = '/rooms/{}/send/m.room.message'.format( @@ -685,6 +690,12 @@ class NotifyMatrix(NotifyBase): method = 'PUT' if self.version == MatrixVersion.V3 else 'POST' postokay, response = self._fetch( path, payload=payload, method=method) + + # Increment the transaction ID to avoid future messages being + # recognized as retransmissions and ignored + if self.version == MatrixVersion.V3: + self.transaction_id += 1 + if not postokay: # Notify our user self.logger.warning( diff --git a/test/test_plugin_matrix.py b/test/test_plugin_matrix.py index df7e4dca..a9916add 100644 --- a/test/test_plugin_matrix.py +++ b/test/test_plugin_matrix.py @@ -1137,3 +1137,58 @@ def test_plugin_matrix_attachments_api_v2(mock_post, mock_get): # Force __del__() call del obj + + +@mock.patch('requests.put') +@mock.patch('requests.get') +@mock.patch('requests.post') +def test_plugin_matrix_transaction_ids_api_v3(mock_post, mock_get, mock_put): + """ + NotifyMatrix() Transaction ID Checks (v3) + + """ + + # Prepare a good response + response = mock.Mock() + response.status_code = requests.codes.ok + response.content = MATRIX_GOOD_RESPONSE.encode('utf-8') + + # Prepare a bad response + bad_response = mock.Mock() + bad_response.status_code = requests.codes.internal_server_error + + # Prepare Mock return object + mock_post.return_value = response + mock_get.return_value = response + mock_put.return_value = response + + notifications_to_send = [10, 1] + mock_post_offset = mock_put_counter = 0 + + for logins, notifications in enumerate(notifications_to_send, start=1): + # Instantiate our object + obj = Apprise.instantiate('matrix://user:pass@localhost/#general?v=3') + + for txnId in range(notifications): + assert obj.notify( + body='body', title='title', notify_type=NotifyType.INFO + ) is True + + # Test our call count + assert mock_put.call_count == mock_put_counter + 1 + # Login & join must happen only once per session + assert mock_post.call_count == mock_post_offset + (2 * logins) + assert mock_post.call_args_list[0][0][0] == \ + 'http://localhost/_matrix/client/v3/login' + assert mock_post.call_args_list[1][0][0] == \ + 'http://localhost/_matrix/client/v3/join/' + \ + '%23general%3Alocalhost' + assert mock_put.call_args_list[txnId][0][0] == \ + 'http://localhost/_matrix/client/v3/rooms/' + \ + f'%21abc123%3Alocalhost/send/m.room.message/{txnId}' + + mock_put_counter = mock_put.call_count + + # Force a object removal (thus a logout call) + del obj + mock_post_offset += 1