Ver Fonte

Track chatto v0.3.4: fix broken mutation queries against source SDL

Cross-checked the lib against chatto's GraphQL SDL in
cli/internal/graph/*.graphqls and found real bugs in 0.1.0b3:

Mutation selection sets that the server now rejects at parse time:
- updateMessage returns Boolean!, was selecting `{ id }`
- archiveRoom/unarchiveRoom return Room!, had no sub-selection
- setServerNotificationLevel/setRoomNotificationLevel return
  ViewerNotificationPreference!, had no sub-selection
- reorderRoomGroups/moveRoomToGroup/reorderRoomsInGroup had no
  sub-selection on their object/list return types
- updateServerConfig returns ServerProfile! directly (not Server),
  so the `profile { ... }` wrapper was nonsense

Server query / pagination:
- enabledAuthProviders → authProviders { id type label }
- Room.events(before/after) uses String cursors, not Time

Subscription union conflicts (fields with mismatched nullability across
EventType members must be aliased): threadRootEventId on
MessagePostedEvent/UserTypingEvent, messageEventId on Asset* events,
reason on MessageRetractedEvent, roomId on Asset* and
NotificationLevelChanged. Added CallStartedEvent and CallEndedEvent.

Client surface adjustments to match: post_message gains
mention_confirmation_token, update_message gains also_send_to_channel
and returns bool, archive_room/unarchive_room return Room,
update_server_config returns ServerProfile.

Versioning: chattolib now tracks chatto's version directly, so this
release is 0.3.4 to match the chatto server it targets (jumping from
0.1.0b3 which incorrectly labeled the chatto beta).
TheCodemancer há 4 meses atrás
pai
commit
7c22c55a37
4 ficheiros alterados com 106 adições e 38 exclusões
  1. 1 1
      pyproject.toml
  2. 21 12
      src/chattolib/client.py
  3. 74 21
      src/chattolib/queries.py
  4. 10 4
      tests/test_client.py

+ 1 - 1
pyproject.toml

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "chattolib"
-version = "0.1.0b3"
+version = "0.3.4"
 description = "Async Python client library for the Chatto webchat GraphQL API"
 requires-python = ">=3.11"
 license = "MIT"

+ 21 - 12
src/chattolib/client.py

@@ -421,6 +421,7 @@ class ChattoClient:
         in_reply_to: str | None = None,
         also_send_to_channel: bool | None = None,
         link_preview: dict[str, Any] | None = None,
+        mention_confirmation_token: str | None = None,
     ) -> dict[str, Any]:
         input_data: dict[str, Any] = {"roomId": room_id}
         if body is not None:
@@ -433,6 +434,8 @@ class ChattoClient:
             input_data["alsoSendToChannel"] = also_send_to_channel
         if link_preview is not None:
             input_data["linkPreview"] = link_preview
+        if mention_confirmation_token is not None:
+            input_data["mentionConfirmationToken"] = mention_confirmation_token
         data = await self._execute(Q.MUTATION_POST_MESSAGE, {"input": input_data})
         return data["postMessage"]
 
@@ -441,12 +444,18 @@ class ChattoClient:
         room_id: str,
         event_id: str,
         body: str,
-    ) -> dict[str, Any]:
-        data = await self._execute(
-            Q.MUTATION_UPDATE_MESSAGE,
-            {"input": {"roomId": room_id, "eventId": event_id, "body": body}},
-        )
-        return data["updateMessage"]
+        *,
+        also_send_to_channel: bool | None = None,
+    ) -> bool:
+        input_data: dict[str, Any] = {
+            "roomId": room_id,
+            "eventId": event_id,
+            "body": body,
+        }
+        if also_send_to_channel is not None:
+            input_data["alsoSendToChannel"] = also_send_to_channel
+        data = await self._execute(Q.MUTATION_UPDATE_MESSAGE, {"input": input_data})
+        return bool(data["updateMessage"])
 
     edit_message = update_message
 
@@ -519,13 +528,13 @@ class ChattoClient:
         data = await self._execute(Q.MUTATION_UPDATE_ROOM, {"input": input_data})
         return _parse_room(data["updateRoom"])
 
-    async def archive_room(self, room_id: str) -> Any:
+    async def archive_room(self, room_id: str) -> Room:
         data = await self._execute(Q.MUTATION_ARCHIVE_ROOM, {"input": {"roomId": room_id}})
-        return data["archiveRoom"]
+        return _parse_room(data["archiveRoom"])
 
-    async def unarchive_room(self, room_id: str) -> Any:
+    async def unarchive_room(self, room_id: str) -> Room:
         data = await self._execute(Q.MUTATION_UNARCHIVE_ROOM, {"input": {"roomId": room_id}})
-        return data["unarchiveRoom"]
+        return _parse_room(data["unarchiveRoom"])
 
     async def join_room(self, room_id: str) -> dict[str, Any]:
         data = await self._execute(Q.MUTATION_JOIN_ROOM, {"input": {"roomId": room_id}})
@@ -806,7 +815,7 @@ class ChattoClient:
         welcome_message: str | None = None,
         motd: str | None = None,
         description: str | None = None,
-    ) -> dict[str, Any]:
+    ) -> ServerProfile:
         input_data: dict[str, Any] = {}
         if server_name is not None:
             input_data["serverName"] = server_name
@@ -817,4 +826,4 @@ class ChattoClient:
         if description is not None:
             input_data["description"] = description
         data = await self._execute(Q.MUTATION_UPDATE_SERVER_CONFIG, {"input": input_data})
-        return data["updateServerConfig"]
+        return _parse_server_profile(data["updateServerConfig"])

+ 74 - 21
src/chattolib/queries.py

@@ -22,7 +22,7 @@ QUERY_SERVER = """
 query Server {
     server {
         version
-        enabledAuthProviders
+        authProviders { id type label }
         pushNotificationsEnabled
         vapidPublicKey
         livekitUrl
@@ -56,7 +56,7 @@ query Room($roomId: ID!) {
 """
 
 QUERY_ROOM_EVENTS = """
-query RoomEvents($roomId: ID!, $limit: Int, $before: Time, $after: Time) {
+query RoomEvents($roomId: ID!, $limit: Int, $before: String, $after: String) {
     room(roomId: $roomId) {
         events(limit: $limit, before: $before, after: $after) {
             events {
@@ -263,9 +263,7 @@ mutation PostMessage($input: PostMessageInput!) {
 
 MUTATION_UPDATE_MESSAGE = """
 mutation UpdateMessage($input: UpdateMessageInput!) {
-    updateMessage(input: $input) {
-        id
-    }
+    updateMessage(input: $input)
 }
 """
 
@@ -323,13 +321,21 @@ mutation UpdateRoom($input: UpdateRoomInput!) {
 
 MUTATION_ARCHIVE_ROOM = """
 mutation ArchiveRoom($input: ArchiveRoomInput!) {
-    archiveRoom(input: $input)
+    archiveRoom(input: $input) {
+        id
+        name
+        archived
+    }
 }
 """
 
 MUTATION_UNARCHIVE_ROOM = """
 mutation UnarchiveRoom($input: UnarchiveRoomInput!) {
-    unarchiveRoom(input: $input)
+    unarchiveRoom(input: $input) {
+        id
+        name
+        archived
+    }
 }
 """
 
@@ -456,13 +462,19 @@ mutation UpdateSettings($input: UpdateSettingsInput!) {
 
 MUTATION_SET_SERVER_NOTIFICATION_LEVEL = """
 mutation SetServerNotificationLevel($input: SetServerNotificationLevelInput!) {
-    setServerNotificationLevel(input: $input)
+    setServerNotificationLevel(input: $input) {
+        level
+        effectiveLevel
+    }
 }
 """
 
 MUTATION_SET_ROOM_NOTIFICATION_LEVEL = """
 mutation SetRoomNotificationLevel($input: SetRoomNotificationLevelInput!) {
-    setRoomNotificationLevel(input: $input)
+    setRoomNotificationLevel(input: $input) {
+        level
+        effectiveLevel
+    }
 }
 """
 
@@ -530,19 +542,28 @@ mutation DeleteRoomGroup($input: DeleteRoomGroupInput!) {
 
 MUTATION_REORDER_ROOM_GROUPS = """
 mutation ReorderRoomGroups($input: ReorderRoomGroupsInput!) {
-    reorderRoomGroups(input: $input)
+    reorderRoomGroups(input: $input) {
+        id
+        name
+    }
 }
 """
 
 MUTATION_MOVE_ROOM_TO_GROUP = """
 mutation MoveRoomToGroup($input: MoveRoomToGroupInput!) {
-    moveRoomToGroup(input: $input)
+    moveRoomToGroup(input: $input) {
+        id
+        groupId
+    }
 }
 """
 
 MUTATION_REORDER_ROOMS_IN_GROUP = """
 mutation ReorderRoomsInGroup($input: ReorderRoomsInGroupInput!) {
-    reorderRoomsInGroup(input: $input)
+    reorderRoomsInGroup(input: $input) {
+        id
+        name
+    }
 }
 """
 
@@ -581,7 +602,10 @@ mutation DeleteServerBanner {
 MUTATION_UPDATE_SERVER_CONFIG = """
 mutation UpdateServerConfig($input: UpdateServerConfigInput!) {
     updateServerConfig(input: $input) {
-        profile { name welcomeMessage motd description }
+        name
+        welcomeMessage
+        motd
+        description
     }
 }
 """
@@ -600,7 +624,7 @@ subscription MyEvents {
                 roomId
                 body
                 inReplyTo
-                threadRootEventId
+                messageThreadRootEventId: threadRootEventId
                 updatedAt
                 attachments { id filename contentType size width height url thumbnailUrl }
                 linkPreview { url title description imageUrl siteName }
@@ -613,10 +637,17 @@ subscription MyEvents {
                 attachments { id filename contentType size width height url thumbnailUrl }
                 linkPreview { url title description imageUrl siteName }
             }
-            ... on MessageRetractedEvent { roomId messageEventId reason }
+            ... on MessageRetractedEvent {
+                roomId
+                messageEventId
+                retractionReason: reason
+            }
             ... on ReactionAddedEvent { roomId messageEventId emoji }
             ... on ReactionRemovedEvent { roomId messageEventId emoji }
-            ... on UserTypingEvent { roomId threadRootEventId }
+            ... on UserTypingEvent {
+                roomId
+                typingThreadRootEventId: threadRootEventId
+            }
             ... on RoomCreatedEvent { roomId name description }
             ... on RoomUpdatedEvent { roomId name description }
             ... on RoomDeletedEvent { roomId }
@@ -628,10 +659,28 @@ subscription MyEvents {
             ... on RoomMemberUnbannedEvent { roomId userId }
             ... on ServerMemberDeletedEvent { userId }
             ... on ThreadCreatedEvent { roomId threadRootEventId }
-            ... on AssetProcessingStartedEvent { roomId assetId messageEventId }
-            ... on AssetProcessingSucceededEvent { roomId assetId messageEventId }
-            ... on AssetProcessingFailedEvent { roomId assetId messageEventId reasonCode }
-            ... on AssetDeletedEvent { roomId assetId }
+            ... on AssetProcessingStartedEvent {
+                assetRoomId: roomId
+                assetId
+                assetMessageEventId: messageEventId
+            }
+            ... on AssetProcessingSucceededEvent {
+                assetRoomId: roomId
+                assetId
+                assetMessageEventId: messageEventId
+            }
+            ... on AssetProcessingFailedEvent {
+                assetRoomId: roomId
+                assetId
+                assetMessageEventId: messageEventId
+                reasonCode
+            }
+            ... on AssetDeletedEvent {
+                assetRoomId: roomId
+                assetId
+            }
+            ... on CallStartedEvent { roomId callId }
+            ... on CallEndedEvent { roomId callId }
             ... on CallParticipantJoinedEvent { roomId }
             ... on CallParticipantLeftEvent { roomId }
             ... on PresenceChangedEvent { status }
@@ -639,7 +688,11 @@ subscription MyEvents {
             ... on UserDeletedEvent { userId }
             ... on UserProfileUpdatedEvent { userId displayName avatarUrl login }
             ... on ServerUserPreferencesUpdatedEvent { timezone timeFormat }
-            ... on NotificationLevelChangedEvent { roomId level effectiveLevel }
+            ... on NotificationLevelChangedEvent {
+                notifRoomId: roomId
+                level
+                effectiveLevel
+            }
             ... on ServerUpdatedEvent { name description logoUrl bannerUrl }
             ... on MentionNotificationEvent { roomId }
             ... on MentionStatusClearedEvent { roomId }

+ 10 - 4
tests/test_client.py

@@ -187,10 +187,10 @@ async def test_login_invalid():
 
 
 async def test_update_message(mock_api, client):
-    mock_api.post("/api/graphql").mock(return_value=_gql_response({"updateMessage": {"id": "e1"}}))
+    mock_api.post("/api/graphql").mock(return_value=_gql_response({"updateMessage": True}))
     async with client:
         result = await client.update_message("r1", "e1", "edited body")
-    assert result["id"] == "e1"
+    assert result is True
 
 
 async def test_delete_message(mock_api, client):
@@ -403,9 +403,15 @@ async def test_notifications(mock_api, client):
 
 
 async def test_archive_unarchive_room(mock_api, client):
-    mock_api.post("/api/graphql").mock(return_value=_gql_response({"archiveRoom": True}))
+    mock_api.post("/api/graphql").mock(
+        return_value=_gql_response(
+            {"archiveRoom": {"id": "r1", "name": "general", "archived": True}}
+        )
+    )
     async with client:
-        assert await client.archive_room("r1") is True
+        room = await client.archive_room("r1")
+    assert room.id == "r1"
+    assert room.archived is True
 
 
 async def test_create_room_group(mock_api, client):