|
|
@@ -0,0 +1,187 @@
|
|
|
+syntax = "proto3";
|
|
|
+
|
|
|
+package chatto.api.v1;
|
|
|
+
|
|
|
+import "buf/validate/validate.proto";
|
|
|
+import "chatto/api/v1/message_types.proto";
|
|
|
+import "chatto/api/v1/room_timeline.proto";
|
|
|
+import "chatto/api/v1/rooms.proto";
|
|
|
+import "chatto/api/v1/threads.proto";
|
|
|
+import "google/protobuf/duration.proto";
|
|
|
+import "google/protobuf/timestamp.proto";
|
|
|
+
|
|
|
+// Ordering for message or grouped-thread search results.
|
|
|
+enum MessageSearchOrder {
|
|
|
+ // Use relevance order.
|
|
|
+ MESSAGE_SEARCH_ORDER_UNSPECIFIED = 0;
|
|
|
+ // Highest message score first; grouped results use their best matching message.
|
|
|
+ MESSAGE_SEARCH_ORDER_RELEVANCE = 1;
|
|
|
+ // Newest matching message first, also when results are grouped.
|
|
|
+ MESSAGE_SEARCH_ORDER_NEWEST = 2;
|
|
|
+ // Return threads with the most recent activity first. Requires thread grouping.
|
|
|
+ MESSAGE_SEARCH_ORDER_THREAD_ACTIVITY = 3;
|
|
|
+ // Newest root-message creation first, with unknown dates last. Requires thread
|
|
|
+ // grouping. Older servers can reject this order.
|
|
|
+ MESSAGE_SEARCH_ORDER_THREAD_CREATED = 4;
|
|
|
+}
|
|
|
+
|
|
|
+// Candidate scope, intersected with the caller's current access and query filters.
|
|
|
+enum MessageSearchScope {
|
|
|
+ // Search all accessible messages. This is the existing default.
|
|
|
+ MESSAGE_SEARCH_SCOPE_UNSPECIFIED = 0;
|
|
|
+ // Search roots and replies in followed threads, including direct messages.
|
|
|
+ MESSAGE_SEARCH_SCOPE_FOLLOWED_THREADS = 1;
|
|
|
+}
|
|
|
+
|
|
|
+// Determines the result type and pagination unit.
|
|
|
+enum MessageSearchGroupBy {
|
|
|
+ // Return individual matching messages.
|
|
|
+ MESSAGE_SEARCH_GROUP_BY_UNSPECIFIED = 0;
|
|
|
+ // Return distinct thread rows. A root without replies is also a thread.
|
|
|
+ MESSAGE_SEARCH_GROUP_BY_THREAD = 1;
|
|
|
+}
|
|
|
+
|
|
|
+// Current availability of message search on this server.
|
|
|
+enum MessageSearchState {
|
|
|
+ // The server did not report a meaningful state.
|
|
|
+ MESSAGE_SEARCH_STATE_UNSPECIFIED = 0;
|
|
|
+ // Search is disabled by the server operator.
|
|
|
+ MESSAGE_SEARCH_STATE_DISABLED = 1;
|
|
|
+ // Search is enabled but its provider has not established index state yet.
|
|
|
+ MESSAGE_SEARCH_STATE_STARTING = 2;
|
|
|
+ // The provider is rebuilding or catching up and cannot answer queries yet.
|
|
|
+ MESSAGE_SEARCH_STATE_INDEXING = 3;
|
|
|
+ // Search is ready to answer queries.
|
|
|
+ MESSAGE_SEARCH_STATE_READY = 4;
|
|
|
+ // Search can answer queries but has a known non-fatal limitation.
|
|
|
+ MESSAGE_SEARCH_STATE_DEGRADED = 5;
|
|
|
+ // Search is enabled but no provider can currently answer.
|
|
|
+ MESSAGE_SEARCH_STATE_UNAVAILABLE = 6;
|
|
|
+}
|
|
|
+
|
|
|
+// Request to search current message bodies visible to the authenticated user.
|
|
|
+message SearchMessagesRequest {
|
|
|
+ // Required query expression. Words are required terms, quoted text is an
|
|
|
+ // exact phrase, and `AND` may separate terms. The query also accepts `in:`,
|
|
|
+ // `from:`, `before:`, `after:`, and `has:attachment` filters; a recognized
|
|
|
+ // filter may be used without a word or phrase.
|
|
|
+ string query = 1 [(buf.validate.field).string = {
|
|
|
+ min_len: 1
|
|
|
+ max_bytes: 1024
|
|
|
+ }];
|
|
|
+ // Optional room-ID scope. The server intersects this room with rooms the
|
|
|
+ // current user may read and with any `in:` filters in query.
|
|
|
+ optional string room_id = 2 [(buf.validate.field).string = {
|
|
|
+ min_len: 1
|
|
|
+ max_bytes: 128
|
|
|
+ }];
|
|
|
+ // Optional author-ID scope. The server intersects this author with any
|
|
|
+ // `from:` filters in query.
|
|
|
+ optional string author_id = 3 [(buf.validate.field).string = {
|
|
|
+ min_len: 1
|
|
|
+ max_bytes: 128
|
|
|
+ }];
|
|
|
+ // Exclusive lower bound on message creation time. The stricter bound wins
|
|
|
+ // when query also contains `after:`.
|
|
|
+ google.protobuf.Timestamp created_after = 4;
|
|
|
+ // Exclusive upper bound on message creation time. The stricter bound wins
|
|
|
+ // when query also contains `before:`.
|
|
|
+ google.protobuf.Timestamp created_before = 5;
|
|
|
+ // When true, return only messages that currently have attachments.
|
|
|
+ bool has_attachments = 6;
|
|
|
+ // Defaults to relevance in both result modes. Thread activity order requires
|
|
|
+ // thread grouping; newest order uses matching-message creation time.
|
|
|
+ MessageSearchOrder order = 7 [(buf.validate.field).enum.defined_only = true];
|
|
|
+ // Maximum results to return (messages or distinct threads). Zero uses 50; the
|
|
|
+ // maximum is 100. Stale or no-longer-visible provider hits are omitted, so a
|
|
|
+ // page may contain fewer messages than requested even when next_cursor is
|
|
|
+ // present.
|
|
|
+ uint32 page_size = 8 [(buf.validate.field).uint32.lte = 100];
|
|
|
+ // Opaque cursor returned by the preceding response. It is bound to the
|
|
|
+ // authenticated user and every other query field.
|
|
|
+ string cursor = 9 [(buf.validate.field).string.max_bytes = 8192];
|
|
|
+ // Candidate scope. Followed threads are selected before result pagination.
|
|
|
+ MessageSearchScope scope = 10 [(buf.validate.field).enum.defined_only = true];
|
|
|
+ // Result grouping. The server deduplicates threads before pagination.
|
|
|
+ MessageSearchGroupBy group_by = 11 [(buf.validate.field).enum.defined_only = true];
|
|
|
+ // Select root authors within FOLLOWED_THREADS before search and pagination.
|
|
|
+ // Other scopes require UNSPECIFIED. This does not restrict reply authors;
|
|
|
+ // author_id and from: still select the authors of matching messages.
|
|
|
+ ThreadRootAuthorFilter root_author_filter = 12 [(buf.validate.field).enum.defined_only = true];
|
|
|
+}
|
|
|
+
|
|
|
+// One current, authorized match. With thread grouping, each result represents
|
|
|
+// one thread and message is its best match in the selected order.
|
|
|
+message MessageSearchResult {
|
|
|
+ // Current renderable message that matched the query.
|
|
|
+ Message message = 1;
|
|
|
+ // Search-provider relevance score. Higher values are more relevant for the
|
|
|
+ // same query. Clients may merge results from servers using compatible
|
|
|
+ // search implementations by this value.
|
|
|
+ double relevance_score = 2;
|
|
|
+ // Present only with THREAD grouping. The matching message can be a reply;
|
|
|
+ // the root and latest reply in this context need not match the query.
|
|
|
+ ThreadSearchContext thread_context = 3;
|
|
|
+}
|
|
|
+
|
|
|
+// One ordered page of current, authorized message or thread results. Pagination reads a live
|
|
|
+// search index rather than a pinned snapshot, so results may move, repeat, or
|
|
|
+// disappear between page requests while the index advances.
|
|
|
+message SearchMessagesResponse {
|
|
|
+ // Ordered matches. With THREAD grouping, one result per matching thread.
|
|
|
+ // Otherwise each result is one message, without thread_context.
|
|
|
+ repeated MessageSearchResult results = 1;
|
|
|
+ // Opaque cursor for the next page in the selected result mode.
|
|
|
+ // Empty means no more matches. Live activity can reorder thread pages.
|
|
|
+ string next_cursor = 2;
|
|
|
+ // Total distinct matching threads. Present only when group_by is THREAD.
|
|
|
+ optional uint64 thread_total_count = 4;
|
|
|
+ // Related entities for grouped results, avoiding per-row reads.
|
|
|
+ RoomTimelineIncludes includes = 5;
|
|
|
+}
|
|
|
+
|
|
|
+// Current context for a grouped match. The viewer need not follow this thread.
|
|
|
+// Search evidence stays on the containing MessageSearchResult.
|
|
|
+message ThreadSearchContext {
|
|
|
+ // Renderable root message, when still visible.
|
|
|
+ Message root_message = 1;
|
|
|
+ // Room containing this thread.
|
|
|
+ RoomSummary room = 2;
|
|
|
+ // Current thread metadata and viewer follow/read state.
|
|
|
+ ThreadSummary thread = 3;
|
|
|
+ // Most recent visible reply, when present; it need not match the query.
|
|
|
+ Message latest_reply = 4;
|
|
|
+ // Conversation-label participants for direct messages; empty for channels.
|
|
|
+ repeated string direct_message_participant_user_ids = 5;
|
|
|
+}
|
|
|
+
|
|
|
+// Request for current message-search availability.
|
|
|
+message GetStatusRequest {}
|
|
|
+
|
|
|
+// Current message-search availability.
|
|
|
+message GetStatusResponse {
|
|
|
+ reserved 2, 3;
|
|
|
+ reserved "indexed_event_count", "target_event_count";
|
|
|
+
|
|
|
+ // Current feature/provider state.
|
|
|
+ MessageSearchState state = 1;
|
|
|
+ // Suggested delay before checking again while search is not ready.
|
|
|
+ google.protobuf.Duration retry_after = 4;
|
|
|
+}
|
|
|
+
|
|
|
+// Searches current message content visible to an authenticated user.
|
|
|
+service MessageSearchService {
|
|
|
+ // Reports whether the operator enabled Search and whether its provider can
|
|
|
+ // currently answer. This method remains available when Search is disabled.
|
|
|
+ rpc GetStatus(GetStatusRequest) returns (GetStatusResponse);
|
|
|
+ // Searches current message bodies in rooms where the caller is a member.
|
|
|
+ // Scope and grouping are independent, including for direct messages. THREAD
|
|
|
+ // grouping returns one row per matching root and its replies. Scope, grouping,
|
|
|
+ // and ordering are part of the cursor scope. Unsupported combinations return
|
|
|
+ // INVALID_ARGUMENT. Existing requests return individual messages by default.
|
|
|
+ // Results also require message.read or a matching thread relationship with
|
|
|
+ // message.read-interactions. Results are authorized and hydrated from current Chatto state
|
|
|
+ // after provider matching. Returns FAILED_PRECONDITION when Search is
|
|
|
+ // disabled and UNAVAILABLE when its provider is not ready.
|
|
|
+ rpc SearchMessages(SearchMessagesRequest) returns (SearchMessagesResponse);
|
|
|
+}
|