Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 All @@ -24,7 +24,7 @@ public FieldFilterRule(string field, QueryOperatorType operatorType, string valu
OperatorType = operatorType;
Value = value;
}

public FieldFilterRule(string field, QueryOperatorType operatorType, int value)
{
Field = field;
Expand All @@ -36,14 +36,17 @@ public FieldFilterRule(string field, QueryOperatorType operatorType, DateTime va
{
Field = field;
OperatorType = operatorType;
Value = ToRfc3339String(value);
// Store the raw DateTime so callers can pick the wire format at serialization time
// (different Stream endpoints accept different RFC 3339 sub-forms - see StreamDateFormat).
Value = value;
}

public FieldFilterRule(string field, QueryOperatorType operatorType, DateTimeOffset value)
{
Field = field;
OperatorType = operatorType;
Value = ToRfc3339String(value);
// See note above about deferred date formatting.
Value = value;
}

public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable<string> value)
Expand All @@ -52,37 +55,100 @@ public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable
OperatorType = operatorType;
Value = value.ToArray();
}

public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable<DateTime> value)
{
Field = field;
OperatorType = operatorType;
// See note above about deferred date formatting.
Value = value.ToArray();
}

public FieldFilterRule(string field, QueryOperatorType operatorType, IEnumerable<DateTimeOffset> value)
{
Field = field;
OperatorType = operatorType;
// See note above about deferred date formatting.
Value = value.ToArray();
}

/// <summary>
/// Returns the filter entry using the default endpoint-portable date form
/// (<see cref="StreamDateFormat.UtcOffset"/>). Callers targeting <c>POST /search</c>'s
/// <c>message_filter_conditions</c> must use the format-aware overload
/// (<see cref="GenerateFilterEntry(StreamDateFormat)"/>) with
/// <see cref="StreamDateFormat.Utc"/>.
/// </summary>
//StreamTodo: research how to reduce allocation here
public KeyValuePair<string, object> GenerateFilterEntry()
=> GenerateFilterEntry(StreamDateFormat.UtcOffset);

/// <summary>
/// Returns the filter entry, formatting any date values using <paramref name="dateFormat"/>.
/// Non-date values are passed through untouched.
/// </summary>
internal KeyValuePair<string, object> GenerateFilterEntry(StreamDateFormat dateFormat)
=> new KeyValuePair<string, object>
(
Field, new Dictionary<string, object>
{
{
OperatorType.ToOperatorKeyword(), Value
OperatorType.ToOperatorKeyword(), FormatValueForWire(Value, dateFormat)
}
}
);

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);

private static object FormatValueForWire(object value, StreamDateFormat dateFormat)
{
if (value is DateTime dt)
{
return dt.ToStreamDateString(dateFormat);
}

if (value is DateTimeOffset dto)
{
return dto.ToStreamDateString(dateFormat);
}

if (value is DateTime[] dts)
{
return dts.Select(d => d.ToStreamDateString(dateFormat)).ToArray();
}

if (value is DateTimeOffset[] dtos)
{
return dtos.Select(d => d.ToStreamDateString(dateFormat)).ToArray();
}

return value;
}
}

/// <summary>
/// Internal helpers for serializing <see cref="IFieldFilterRule"/> instances to the wire
/// dictionary with an explicit <see cref="StreamDateFormat"/>.
///
/// <para>
/// The public <see cref="IFieldFilterRule.GenerateFilterEntry"/> contract is intentionally
/// parameterless to avoid breaking external implementations. SDK-internal call sites that
/// need the <see cref="StreamDateFormat.Utc"/> (Z) form - currently only
/// <c>POST /search</c>'s <c>message_filter_conditions</c> / <c>filter_conditions</c> - go
/// through this helper. Anything implementing <see cref="IFieldFilterRule"/> that isn't the
/// SDK's own <see cref="FieldFilterRule"/> transparently falls back to the parameterless
/// path (i.e. <see cref="StreamDateFormat.UtcOffset"/>).
/// </para>
/// </summary>
internal static class FieldFilterRuleExtensions
{
internal static KeyValuePair<string, object> GenerateFilterEntry(this IFieldFilterRule rule,
StreamDateFormat dateFormat)
{
if (rule is FieldFilterRule concrete)
{
return concrete.GenerateFilterEntry(dateFormat);
}

return rule.GenerateFilterEntry();
}
}
}
}

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.

Loading
Loading