Prechádzať zdrojové kódy

Track Chatto v0.5.0-beta.10

Wire delta vs 0.5.0-beta.9 is comment-only: RoomWithViewerState.member_user_ids
on DM rooms now documents that it also includes participants whose accounts
were deleted (clients render those as deleted users; channel rooms still empty).
No new fields, RPCs, renames, or realtime oneof changes, so the client code
needed no adaptation (the regenerated _pb bindings are byte-identical, as
protoc strips comments from serialized descriptors). Added a regression test
asserting list_rooms surfaces the full DM participant list and a Gotcha entry
in AGENTS.md.

Verification: ruff format+check clean (27 files); mypy identical to the
pre-change baseline (6 pre-existing import-untyped/no-any-return, zero new);
pytest 92 passed / 2 skipped (2 skips pre-existing, require CHATTO_BOT_KEY);
live GetServer against https://chat.chatto.run reports version
0.5.0-beta.10; live bot integration test passed with CHATTO_BOT_TOKEN.

Bumped version 0.5.0b9 -> 0.5.0b10.
the-codemancer 1 mesiac pred
rodič
commit
65f0bbd5d3

+ 1 - 0
AGENTS.md

@@ -139,6 +139,7 @@ to its grants.
 - **0.5.0b7 replaced the room ban triad with a *suspension* triad (breaking).** `BanMember`/`UnbanMember`/`ListBans` and the `RoomBan` message are gone; they are now `RemoveUser` (remove a member with a required reason; the optional `oneof suspension` is either `suspension_expires_at` or `suspend_indefinitely` — at most one, and Universal rooms require one; DM rooms are excluded), `LiftSuspension` (no-op success when no active suspension), and `ListSuspensions` (returns `repeated RoomSuspension suspensions`, page newest-first). The client methods are `remove_user(...)` / `lift_suspension(...)` / `list_suspensions(...)` and the dataclass is `RoomSuspension` (the ban's `expires_at` field name is kept). Both `RemoveUser` and `LiftSuspension` require `room.remove-member` at server scope.
 - **0.5.0b8 added the public `ServerDiscoveryService.ListNeighborhoodServers` RPC** (no session needed; the server caches background discovery, refreshed at least hourly). `NeighborhoodServer.profile` is a `ServerPublicProfile` **without** the welcome message, and its logo/banner URLs are server-relative paths of copies on the *called* server — resolve them against that server's origin, not the neighbor's. `ListFollowedThreadsRequest.unread_only` filters to followed threads with unread replies (older servers ignore the field and return everything; the page total then counts only the filtered set).
 - **0.5.0b9 consolidated all public-profile edits into a new `chatto.api.v1.UserService.UpdateUserProfile` RPC (breaking for both edit paths).** `MyAccountService.UpdateProfile` was removed and `chatto.admin.v1.AdminUserService.UpdateUser` was removed; the new RPC takes a required `user_id` plus `display_name`/`login`/`bio` (an inferred `update_mask`) and returns just the updated `User` — no admin member row. Self-updates need no extra authority; editing another human requires `user.manage-accounts` (and, for a bot target, ownership of the bot, `user.manage-accounts`, or `bot.manage`), and a `user.manage-accounts` caller bypasses the username-change cooldown without starting one. Client: new `update_user_profile(user_id, ...)` plus a self-service `update_profile(...)` that resolves the caller's id via the viewer snapshot and delegates (same public signature as before, plus `bio`); `admin_update_user(...)` keeps its name/shape but now delegates to the same RPC and its second return value is always `None` (the removed response no longer carries an `AdminMember`) — re-fetch with `admin_get_member()` when the row is needed. `User.bio` is now documented as Markdown. The vendored `user_service.proto` also newly carries `ListUsers`/`GetUser`/`BatchGetUsers` (directory) and `UploadAvatar`/`DeleteAvatar`, for which chattolib still ships no wrappers.
+- **0.5.0b10: DM rooms keep deleted participants in `member_user_ids`.** Since 0.5.0-beta.10, `RoomWithViewerState.member_user_ids` on a DM includes participants whose accounts were deleted (the directory no longer prunes them); clients render those as deleted users. Channel rooms still carry an empty list. The wire shape is unchanged (same field, same number) and `RoomWithViewerState.parse` already copies the full list — so no code change was needed, but code that resolves every `member_user_ids` entry to a `User` must tolerate users that `get_user` no longer returns.
 - **Never regenerate protos from `chattocorp/chatto@main`.** `scripts/generate_pb.sh` defaults to a tag derived from `pyproject.toml`: `v<base>` for finals, and `v<base>-<kind>.<n>` for PEP 440 pre-releases (`0.5.0b6` → `v0.5.0-beta.6`), probing candidates with `git ls-remote` (exact ref match; the REST `/tags/{name}` endpoint 404s on lightweight tags). Override with `CHATTO_REF=<tag>` only when you know the deployed server has caught up. chattolib 0.4.19 shipped once from `main` and broke realtime for every downstream client because Chatto's main had a protocol-v2 rewrite that the deployed 0.4.19 server did not speak.
 
 ### Known bugs

+ 3 - 1
proto/chatto/api/v1/room_directory.proto

@@ -56,7 +56,9 @@ message RoomWithViewerState {
   Room room = 1;
   // State and permission decisions resolved for the current user.
   RoomViewerState viewer_state = 14;
-  // Complete participant user IDs for a DM. Empty for channel rooms.
+  // Complete participant user IDs for a DM, including participants whose
+  // accounts were deleted. Clients show those as deleted users. Empty for
+  // channel rooms.
   repeated string member_user_ids = 15;
   // Whether this DM has received a root message. Absent for channel rooms.
   optional bool has_message_history = 16;

+ 1 - 1
pyproject.toml

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "chattolib"
-version = "0.5.0b9"
+version = "0.5.0b10"
 description = "Async Python client library for the Chatto webchat Connect API"
 requires-python = ">=3.11"
 license = "MPL-2.0 AND Apache-2.0"

+ 22 - 0
tests/test_client.py

@@ -166,6 +166,28 @@ async def test_list_rooms(client):
     assert rooms[0].viewer_state.is_member is True
 
 
+async def test_list_rooms_dm_keeps_deleted_participants(client):
+    # Since Chatto 0.5.0-beta.10, a DM's member_user_ids keeps participants
+    # whose accounts were deleted; the client must surface the full list
+    # (resolution to a User then legitimately fails for the deleted one).
+    resp = room_directory_pb2.ListRoomsResponse()
+    entry = resp.rooms.add()
+    entry.room.id = "r-dm"
+    entry.room.kind = "ROOM_KIND_DM"
+    entry.room.name = "me & u2"
+    entry.viewer_state.is_member = True
+    entry.member_user_ids.extend(["u1", "u-deleted"])
+    _mock_method(client, "room_directory", "list_rooms", resp)
+
+    async with client:
+        rooms = await client.list_rooms()
+
+    assert len(rooms) == 1
+    assert rooms[0].room is not None
+    assert rooms[0].room.kind is RoomKind.DM
+    assert rooms[0].member_user_ids == ["u1", "u-deleted"]
+
+
 # --- Messages ----------------------------------------------------------