Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
415c62c
Implement StreamChatClient.SearchMessagesAsync
sierpinskid May 20, 2026
2eefbca
Add tests for search feature
sierpinskid May 20, 2026
4f47976
Fix test to not use both Query and MessageFilter (disallowed by API)
sierpinskid May 21, 2026
dde9038
Fix date serialization. The API was returning "Search failed with err…
sierpinskid May 22, 2026
5958cfa
Fix "TearDown : System.ArgumentException : 'async void' methods are n…
sierpinskid May 22, 2026
c95e348
Fix tests deadlock
sierpinskid May 22, 2026
af2a824
Make test more resilient to changes not being immediately available o…
sierpinskid May 22, 2026
6f6d61f
Apply API response to cache when soft delete is used so that the loca…
sierpinskid May 22, 2026
ed22ad0
Fix inconsistent datetime serialization expected by the API
sierpinskid May 27, 2026
60f2bef
rewrite test for clarity + extend timeout
sierpinskid May 27, 2026
cb38489
Fix flaky When_thread_reply_with_show_in_channel_received_expect_adde…
sierpinskid May 28, 2026
5cfa9f6
Don't serialize MessageFilterConditions if null or empty -> we add th…
sierpinskid May 28, 2026
610ef6f
Refactor WatchedChannels to only contain watched channels. Before sea…
sierpinskid May 28, 2026
08ce9cb
watch search results by default + add StreamMessage.IsWatched flag + …
sierpinskid May 29, 2026
1b15bd6
Revise comments + optimize watching search result channels
sierpinskid May 29, 2026
4daa0dd
Fix compiler error
sierpinskid May 29, 2026
c42e115
Fix thread object custom data
sierpinskid May 29, 2026
48b4d85
Add freeing space before the docker image is pulled. Unity image can …
sierpinskid May 29, 2026
1974563
Improve comments on the public interface
sierpinskid May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Assets/Plugins/StreamChat/Core/IStreamChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,31 @@ Task<IStreamThread> GetThreadAsync(string parentMessageId,
/// <param name="request">Query request</param>
Task<StreamQueryThreadsResponse> QueryThreadsAsync(StreamQueryThreadsRequest request);

/// <summary>
/// Search messages across the channels the local user can access.
///
/// Unlike the low-level <c>Client.LowLevelClient.MessageApi.SearchMessagesAsync</c>, results
/// are returned as cached, stateful <see cref="IStreamMessage"/> (and accompanying
/// <see cref="IStreamChannel"/>) instances - the same objects already in the cache are
/// reused, and they continue to react to realtime WebSocket events.
///
/// <para>
/// The <paramref name="request"/> requires a channel-level filter (e.g.
/// <c>ChannelFilter.Members.In(localUser)</c>). Additional message-level filters can be
/// expressed with <c>MessageFilter.*</c> builders, and a free-text phrase can be supplied
/// via <see cref="StreamSearchMessagesRequest.Query"/>. See <see cref="StreamSearchMessagesRequest"/>
/// for pagination and sorting options.
/// </para>
/// </summary>
/// <param name="request">Search parameters - channel filter, message filter, query phrase,
/// sort, and pagination.</param>
/// <param name="cancellationToken">[Optional] Cancellation token for the request.</param>
/// <returns>Stateful results plus pagination cursors.</returns>
/// <remarks>https://getstream.io/chat/docs/unity/search/?language=unity</remarks>
Task<StreamSearchMessagesResponse> SearchMessagesAsync(
StreamSearchMessagesRequest request,
CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Upsert users. Upsert means update this user or create if not found
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,8 @@ protected FieldFilterRule InternalAutocomplete(string value)

protected FieldFilterRule InternalContains(string value)
=> new FieldFilterRule(FieldName, QueryOperatorType.Contains, value);

protected FieldFilterRule InternalExists(bool exists)
=> new FieldFilterRule(FieldName, QueryOperatorType.Exists, exists);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StreamChat.Libs.Utils;

namespace StreamChat.Core.QueryBuilders.Filters
{
Expand Down Expand Up @@ -36,14 +36,14 @@ public FieldFilterRule(string field, QueryOperatorType operatorType, DateTime va
{
Field = field;
OperatorType = operatorType;
Value = ToRfc3339String(value);
Value = value.ToStreamDateString();
}

public FieldFilterRule(string field, QueryOperatorType operatorType, DateTimeOffset value)
{
Field = field;
OperatorType = operatorType;
Value = ToRfc3339String(value);
Value = value.ToStreamDateString();
}

public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable<string> value)
Expand All @@ -57,14 +57,14 @@ public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable
{
Field = field;
OperatorType = operatorType;
Value = value.ToArray();
Value = value.Select(StreamDateFormatter.ToStreamDateString).ToArray();
}

public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable<DateTimeOffset> value)
{
Field = field;
OperatorType = operatorType;
Value = value.ToArray();
Value = value.Select(StreamDateFormatter.ToStreamDateString).ToArray();
}

//StreamTodo: research how to reduce allocation here
Expand All @@ -79,10 +79,5 @@ public KeyValuePair<string, object> GenerateFilterEntry()
}
);

private static string ToRfc3339String(DateTime dateTime)
=> dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);

private static string ToRfc3339String(DateTimeOffset dateTimeOffset)
=> dateTimeOffset.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.Generic;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by the type of an attachment on the message (<c>image</c>, <c>video</c>,
/// <c>file</c>, <c>audio</c>, <c>giphy</c>, <c>location</c>, or any custom type).
/// </summary>
public sealed class MessageFieldAttachmentType : BaseFieldToFilter
{
public override string FieldName => "attachments.type";

public FieldFilterRule EqualsTo(string attachmentType) => InternalEqualsTo(attachmentType);

public FieldFilterRule Contains(string attachmentType) => InternalContains(attachmentType);

public FieldFilterRule In(IEnumerable<string> attachmentTypes) => InternalIn(attachmentTypes);

public FieldFilterRule In(params string[] attachmentTypes) => InternalIn(attachmentTypes);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using StreamChat.Core.StatefulModels;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by message <see cref="IStreamMessage.CreatedAt"/> timestamp.
/// </summary>
public sealed class MessageFieldCreatedAt : BaseFieldToFilter
{
public override string FieldName => "created_at";

public FieldFilterRule EqualsTo(DateTime date) => InternalEqualsTo(date);
public FieldFilterRule EqualsTo(DateTimeOffset date) => InternalEqualsTo(date);

public FieldFilterRule GreaterThan(DateTime date) => InternalGreaterThan(date);
public FieldFilterRule GreaterThan(DateTimeOffset date) => InternalGreaterThan(date);

public FieldFilterRule GreaterThanOrEquals(DateTime date) => InternalGreaterThanOrEquals(date);
public FieldFilterRule GreaterThanOrEquals(DateTimeOffset date) => InternalGreaterThanOrEquals(date);

public FieldFilterRule LessThan(DateTime date) => InternalLessThan(date);
public FieldFilterRule LessThan(DateTimeOffset date) => InternalLessThan(date);

public FieldFilterRule LessThanOrEquals(DateTime date) => InternalLessThanOrEquals(date);
public FieldFilterRule LessThanOrEquals(DateTimeOffset date) => InternalLessThanOrEquals(date);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using StreamChat.Core.State;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by an arbitrary custom message field (any top-level key the customer attached to the message).
/// </summary>
public sealed class MessageFieldCustom : BaseFieldToFilter
{
public override string FieldName { get; }

public MessageFieldCustom(string customFieldName)
{
StreamAsserts.AssertNotNullOrEmpty(customFieldName, nameof(customFieldName));
FieldName = customFieldName;
}

public FieldFilterRule EqualsTo(string value) => InternalEqualsTo(value);
public FieldFilterRule EqualsTo(bool value) => InternalEqualsTo(value);
public FieldFilterRule EqualsTo(int value) => InternalEqualsTo(value);
public FieldFilterRule EqualsTo(DateTime value) => InternalEqualsTo(value);
public FieldFilterRule EqualsTo(DateTimeOffset value) => InternalEqualsTo(value);

public FieldFilterRule In(IEnumerable<string> values) => InternalIn(values);
public FieldFilterRule In(params string[] values) => InternalIn(values);

public FieldFilterRule GreaterThan(int value) => InternalGreaterThan(value);
public FieldFilterRule GreaterThan(string value) => InternalGreaterThan(value);
public FieldFilterRule GreaterThan(DateTime value) => InternalGreaterThan(value);
public FieldFilterRule GreaterThan(DateTimeOffset value) => InternalGreaterThan(value);

public FieldFilterRule GreaterThanOrEquals(int value) => InternalGreaterThanOrEquals(value);
public FieldFilterRule GreaterThanOrEquals(string value) => InternalGreaterThanOrEquals(value);
public FieldFilterRule GreaterThanOrEquals(DateTime value) => InternalGreaterThanOrEquals(value);
public FieldFilterRule GreaterThanOrEquals(DateTimeOffset value) => InternalGreaterThanOrEquals(value);

public FieldFilterRule LessThan(int value) => InternalLessThan(value);
public FieldFilterRule LessThan(string value) => InternalLessThan(value);
public FieldFilterRule LessThan(DateTime value) => InternalLessThan(value);
public FieldFilterRule LessThan(DateTimeOffset value) => InternalLessThan(value);

public FieldFilterRule LessThanOrEquals(int value) => InternalLessThanOrEquals(value);
public FieldFilterRule LessThanOrEquals(string value) => InternalLessThanOrEquals(value);
public FieldFilterRule LessThanOrEquals(DateTime value) => InternalLessThanOrEquals(value);
public FieldFilterRule LessThanOrEquals(DateTimeOffset value) => InternalLessThanOrEquals(value);

public FieldFilterRule Contains(string value) => InternalContains(value);

public FieldFilterRule Exists(bool exists) => InternalExists(exists);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
using StreamChat.Core.StatefulModels;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by the id of a user mentioned in the message
/// (<see cref="IStreamMessage.MentionedUsers"/>).
/// </summary>
public sealed class MessageFieldMentionedUserId : BaseFieldToFilter
{
public override string FieldName => "mentioned_users.id";

public FieldFilterRule EqualsTo(string userId) => InternalEqualsTo(userId);

public FieldFilterRule EqualsTo(IStreamUser user) => InternalEqualsTo(user.Id);

public FieldFilterRule Contains(string userId) => InternalContains(userId);

public FieldFilterRule Contains(IStreamUser user) => InternalContains(user.Id);

public FieldFilterRule In(IEnumerable<string> userIds) => InternalIn(userIds);

public FieldFilterRule In(params string[] userIds) => InternalIn(userIds);

public FieldFilterRule In(IEnumerable<IStreamUser> users) => InternalIn(users.Select(_ => _.Id));

public FieldFilterRule In(params IStreamUser[] users) => InternalIn(users.Select(_ => _.Id));
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using StreamChat.Core.StatefulModels;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by <see cref="IStreamMessage.ParentId"/>.
///
/// Typical usage:
/// <list type="bullet">
/// <item><c>Exists(true)</c> - only thread replies.</item>
/// <item><c>Exists(false)</c> - only top-level messages.</item>
/// <item><c>EqualsTo(parentId)</c> - replies to a specific parent message.</item>
/// </list>
/// </summary>
public sealed class MessageFieldParentId : BaseFieldToFilter
{
public override string FieldName => "parent_id";

public FieldFilterRule EqualsTo(string parentMessageId) => InternalEqualsTo(parentMessageId);

public FieldFilterRule EqualsTo(IStreamMessage parentMessage) => InternalEqualsTo(parentMessage.Id);

public FieldFilterRule In(IEnumerable<string> parentMessageIds) => InternalIn(parentMessageIds);

public FieldFilterRule In(params string[] parentMessageIds) => InternalIn(parentMessageIds);

/// <summary>
/// When <c>true</c>, returns only replies (messages whose <c>parent_id</c> is set).
/// When <c>false</c>, returns only top-level (non-reply) messages.
/// </summary>
public FieldFilterRule Exists(bool exists) => InternalExists(exists);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using StreamChat.Core.StatefulModels;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by <see cref="IStreamMessage.Pinned"/>.
/// Useful for cross-channel pinned-message searches.
/// </summary>
public sealed class MessageFieldPinned : BaseFieldToFilter
{
public override string FieldName => "pinned";

public FieldFilterRule EqualsTo(bool pinned) => InternalEqualsTo(pinned);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;

namespace StreamChat.Core.QueryBuilders.Filters.Messages
{
/// <summary>
/// Filter by the id of a poll attached to the message.
///
/// Use <see cref="Exists"/> with <c>true</c> to find any message that has a poll attached,
/// or pass a specific poll id to find the message that hosts a known poll.
/// </summary>
public sealed class MessageFieldPollId : BaseFieldToFilter
{
public override string FieldName => "poll_id";

public FieldFilterRule EqualsTo(string pollId) => InternalEqualsTo(pollId);

public FieldFilterRule In(IEnumerable<string> pollIds) => InternalIn(pollIds);

public FieldFilterRule In(params string[] pollIds) => InternalIn(pollIds);

/// <summary>
/// When <c>true</c>, returns only messages that have a poll attached.
/// When <c>false</c>, returns only messages without a poll.
/// </summary>
public FieldFilterRule Exists(bool exists) => InternalExists(exists);
}
}
Loading
Loading