Skip to content

Commit

Permalink
Refactor code and extend update types
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasweimann committed Nov 8, 2023
1 parent 728b792 commit 5ffc5f4
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 46 deletions.
36 changes: 35 additions & 1 deletion src/RxTelegram.Bot/Api/IUpdateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ public interface IUpdateManager
/// </summary>
IObservable<PollAnswer> PollAnswer { get; }

/// <summary>
/// The bot's chat member status was updated in a chat. For private chats,
/// this update is received only when the bot is blocked or unblocked by the user.
/// </summary>
IObservable<ChatMemberUpdated> MyChatMember { get; }

/// <summary>
/// A chat member's status was updated in a chat.
/// The bot must be an administrator in the chat and must explicitly specify "chat_member" in the list of allowed_updates to receive these updates.
/// </summary>
IObservable<ChatMemberUpdated> ChatMember { get; }

/// <summary>
/// A request to join the chat has been sent. The bot must have the can_invite_users administrator right in the chat to receive these updates.
/// </summary>
IObservable<ChatJoinRequest> ChatJoinRequest { get; }

#if NETSTANDARD2_1
/// <summary>
/// Updates of all Types.
Expand Down Expand Up @@ -134,5 +151,22 @@ public interface IUpdateManager
/// A user changed their answer in a non-anonymous poll. Bots receive new votes only in polls that were sent by the bot itself.
/// </summary>
IAsyncEnumerable<PollAnswer> PollAnswerEnumerable();

/// <summary>
/// The bot's chat member status was updated in a chat. For private chats,
/// this update is received only when the bot is blocked or unblocked by the user.
/// </summary>
IAsyncEnumerable<ChatMemberUpdated> MyChatMemberEnumerable();

/// <summary>
/// A chat member's status was updated in a chat.
/// The bot must be an administrator in the chat and must explicitly specify "chat_member" in the list of allowed_updates to receive these updates.
/// </summary>
IAsyncEnumerable<ChatMemberUpdated> ChatMemberEnumerable();

/// <summary>
/// A request to join the chat has been sent. The bot must have the can_invite_users administrator right in the chat to receive these updates.
/// </summary>
IAsyncEnumerable<ChatJoinRequest> ChatJoinRequestEnumerable();
#endif
}
}
139 changes: 121 additions & 18 deletions src/RxTelegram.Bot/Api/UpdateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,13 @@ public class UpdateManager : IUpdateManager

public IObservable<PollAnswer> PollAnswer => _pollAnswer;

private readonly IDictionary<UpdateType, List<object>> _observerDictionary;
private readonly List<object> _updateObservers;
public IObservable<ChatMemberUpdated> MyChatMember => _myChatMember;

public IObservable<ChatMemberUpdated> ChatMember => _chatMember;

public IObservable<ChatJoinRequest> ChatJoinRequest => _chatJoinRequest;

private readonly IDictionary<UpdateTypeWrapper<UpdateType?>, List<object>> _observerDictionary;
private readonly Observable<Update> _update;
private readonly Observable<Message> _message;
private readonly Observable<Message> _editedMessage;
Expand All @@ -54,26 +59,33 @@ public class UpdateManager : IUpdateManager
private readonly Observable<Message> _editedChannelPost;
private readonly Observable<ShippingQuery> _shippingQuery;
private readonly Observable<PreCheckoutQuery> _preCheckoutQuery;
private readonly Observable<ChatMemberUpdated> _myChatMember;
private readonly Observable<ChatMemberUpdated> _chatMember;
private readonly Observable<ChatJoinRequest> _chatJoinRequest;

private readonly ITelegramBot _telegramBot;
private const int NotRunning = 0;
private const int Running = 1;
private int _isRunning = NotRunning;
private CancellationTokenSource _cancellationTokenSource;

private bool AnyObserver => _updateObservers.Any() || _observerDictionary.Any(x => x.Value.Any());
private bool AnyObserver => _observerDictionary.Any(x => x.Value.Any());

internal IEnumerable<UpdateType> UpdateTypes => _updateObservers.Any()
? _observerDictionary.Select(x => x.Key)
: _observerDictionary.Where(x => x.Value.Any())
.Select(x => x.Key);
internal IEnumerable<UpdateType?> UpdateTypes => _observerDictionary.Where(x => x.Value.Any())
.Select(x => x.Key.Value);

public UpdateManager(ITelegramBot telegramBot)
{
_telegramBot = telegramBot;
_updateObservers = new List<object>();
_observerDictionary = Enum.GetValues(typeof(UpdateType))
.Cast<UpdateType>()
.ToDictionary(updateType => updateType, updateType => new List<object>());
.Select(x => new UpdateTypeWrapper<UpdateType?>(x))
// add any update type
.Append(UpdateTypeWrapper<UpdateType?>.Any())
.ToDictionary(updateType => new UpdateTypeWrapper<UpdateType?>(updateType), _ => new List<object>());
_chatJoinRequest = new Observable<ChatJoinRequest>(UpdateType.ChatJoinRequest, this);
_chatMember = new Observable<ChatMemberUpdated>(UpdateType.ChatMember, this);
_myChatMember = new Observable<ChatMemberUpdated>(UpdateType.MyChatMember, this);
_preCheckoutQuery = new Observable<PreCheckoutQuery>(UpdateType.PreCheckoutQuery, this);
_shippingQuery = new Observable<ShippingQuery>(UpdateType.ShippingQuery, this);
_editedChannelPost = new Observable<Message>(UpdateType.EditedChannelPost, this);
Expand Down Expand Up @@ -124,7 +136,12 @@ internal async Task RunUpdate()
{
Offset = offset,
Timeout = 60,
AllowedUpdates = UpdateTypes

// if there is a null value in the list, it means that all updates are allowed
AllowedUpdates = UpdateTypes.Contains(null!)
? null
: UpdateTypes.Where(x => x.HasValue)
.Select(x => x.Value)
};
var result = await _telegramBot.GetUpdate(getUpdate, _cancellationTokenSource.Token);
if (!result.Any())
Expand Down Expand Up @@ -222,10 +239,8 @@ internal void OnException(Exception exception)
return;
}

var observers = _observerDictionary.Values.SelectMany(x => x).ToList();
observers.AddRange(_updateObservers);

foreach (var observer in observers)
foreach (var observer in _observerDictionary.Values.SelectMany(x => x)
.ToList())
{
var observerType = observer.GetType();
if (!observerType.GetInterfaces()
Expand All @@ -244,7 +259,7 @@ internal void OnException(Exception exception)

try
{
methodInfo.Invoke(observer, new object[] {exception});
methodInfo.Invoke(observer, new object[] { exception });
}
catch (Exception)
{
Expand All @@ -253,8 +268,21 @@ internal void OnException(Exception exception)
}
}

internal List<object> GetObservers(UpdateType? updateType) =>
!updateType.HasValue ? _updateObservers : _observerDictionary[updateType.Value];
internal List<object> GetObservers(UpdateType? updateType)
{
UpdateTypeWrapper<UpdateType?> c;
if (updateType == null)
{
c = UpdateTypeWrapper<UpdateType?>.Any();
}
else
{
c = updateType;
}

_observerDictionary.TryGetValue(c, out var list);
return list;
}

internal IDisposable Subscribe<T>(UpdateType? updateType, IObserver<T> observer)
{
Expand Down Expand Up @@ -297,7 +325,8 @@ internal void Remove<T>(UpdateType? updateType, IObserver<T> observer)
observers.Remove(observer);
}

if (!AnyObserver && Volatile.Read(ref _isRunning) == Running)
if (!AnyObserver &&
Volatile.Read(ref _isRunning) == Running)
{
_cancellationTokenSource?.Cancel();
}
Expand Down Expand Up @@ -350,5 +379,79 @@ private sealed class Unsubscriber : IDisposable
public IAsyncEnumerable<Poll> PollEnumerable() => _poll.ToAsyncEnumerable();

public IAsyncEnumerable<PollAnswer> PollAnswerEnumerable() => _pollAnswer.ToAsyncEnumerable();

public IAsyncEnumerable<ChatMemberUpdated> MyChatMemberEnumerable() => _myChatMember.ToAsyncEnumerable();

public IAsyncEnumerable<ChatMemberUpdated> ChatMemberEnumerable() => _chatMember.ToAsyncEnumerable();

public IAsyncEnumerable<ChatJoinRequest> ChatJoinRequestEnumerable() => _chatJoinRequest.ToAsyncEnumerable();
#endif

private struct UpdateTypeWrapper<T>
{
private readonly bool _anyType = true;

private UpdateTypeWrapper(T value, bool anyType) : this()
{
_anyType = anyType;
Value = value;
}

public UpdateTypeWrapper(T value) : this(value, value == null)
{
}

private UpdateTypeWrapper(bool anyType) : this(default, anyType)
{
}

public static UpdateTypeWrapper<T> Any() => new(true);

public T Value { get; }

public bool IsAny() => _anyType;

public static implicit operator T(UpdateTypeWrapper<T> updateTypeWrapper) => updateTypeWrapper.Value;

public static implicit operator UpdateTypeWrapper<T>(T item) => new(item);

public override string ToString() => Value != null ? Value.ToString() : "Any";

public override bool Equals(object obj)
{
if (obj == null)
{
return IsAny();
}

if (obj is not UpdateTypeWrapper<T> no)
{
return false;
}

if (IsAny())
{
return no.IsAny();
}

return !no.IsAny() && Value.Equals(no.Value);
}

public override int GetHashCode()
{
if (_anyType)
{
return 0;
}

var result = Value.GetHashCode();

if (result >= 0)
{
result++;
}

return result;
}
}
}
6 changes: 6 additions & 0 deletions src/RxTelegram.Bot/Interface/BaseTypes/Enums/UpdateType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ public enum UpdateType
Poll,

PollAnswer,

MyChatMember,

ChatMember,

ChatJoinRequest
}
2 changes: 1 addition & 1 deletion src/RxTelegram.Bot/Interface/BaseTypes/ForceReply.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ public class ForceReply : IReplyMarkup
/// sender of the original message.
/// </summary>
public bool Selective { get; set; }
}
}
1 change: 0 additions & 1 deletion src/RxTelegram.Bot/Interface/BaseTypes/Message.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Security.Authentication.ExtendedProtection;
using RxTelegram.Bot.Interface.Games;
using RxTelegram.Bot.Interface.Passport;
using RxTelegram.Bot.Interface.Payments;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using RxTelegram.Bot.Interface.BaseTypes.Requests.Base;
using RxTelegram.Bot.Interface.Validation;
using RxTelegram.Bot.Validation;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using RxTelegram.Bot.Interface.BaseTypes;
using RxTelegram.Bot.Interface.BaseTypes.Enums;
using RxTelegram.Bot.Validation;
Expand Down Expand Up @@ -34,4 +33,4 @@ public class InputTextMessageContent: InputMessageContent
public bool DisableWebPagePreview { get; set; }

protected override IValidationResult Validate() => this.CreateValidation();
}
}
30 changes: 29 additions & 1 deletion src/RxTelegram.Bot/Interface/Setup/GetUpdate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,43 @@

namespace RxTelegram.Bot.Interface.Setup;

/// <summary>
/// Use this method to receive incoming updates using long polling. Returns an Array of Update objects.
/// </summary>
public class GetUpdate : BaseValidation
{
/// <summary>
/// Identifier of the first update to be returned.
/// Must be greater by one than the highest among the identifiers of previously received updates.
/// By default, updates starting with the earliest unconfirmed update are returned.
/// An update is considered confirmed as soon as getUpdates is called with an offset higher than its update_id.
/// The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue.
/// All previous updates will be forgotten.
/// </summary>
public int? Offset { get; set; }

/// <summary>
/// Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100.
/// </summary>
public int? Limit { get; set; }

/// <summary>
/// Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling.
/// Should be positive, short polling should be used for testing purposes only.
/// </summary>
public int? Timeout { get; set; }

/// <summary>
/// A JSON-serialized list of the update types you want your bot to receive.
/// For example, specify ["message", "edited_channel_post", "callback_query"] to only receive updates of these types.
/// See Update for a complete list of available update types.
/// Specify an empty list to receive all update types except chat_member (default).
/// If not specified, the previous setting will be used.
///
/// Please note that this parameter doesn't affect updates created before the call to the getUpdates,
/// so unwanted updates may be received for a short period of time.
/// </summary>
public IEnumerable<UpdateType> AllowedUpdates { get; set; }

protected override IValidationResult Validate() => new ValidationResult<GetUpdate>(this);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using RxTelegram.Bot.Interface.BaseTypes.Requests.Attachments;
using RxTelegram.Bot.Interface.Validation;
using RxTelegram.Bot.Interface.Validation;
using RxTelegram.Bot.Validation;

namespace RxTelegram.Bot.Interface.Stickers.Requests;
Expand Down Expand Up @@ -33,4 +32,4 @@ public class AddStickerToSet : BaseValidation
public InputSticker Sticker { get; set; }

protected override IValidationResult Validate() => this.CreateValidation();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using RxTelegram.Bot.Interface.BaseTypes.Requests.Attachments;
using System.Collections.Generic;
using RxTelegram.Bot.Interface.Stickers.Enums;
using RxTelegram.Bot.Interface.Validation;
using RxTelegram.Bot.Validation;
Expand Down Expand Up @@ -58,4 +56,4 @@ public class CreateNewStickerSet : BaseValidation
public bool? NeedsRepainting { get; set; }

protected override IValidationResult Validate() => this.CreateValidation();
}
}
2 changes: 1 addition & 1 deletion src/UnitTests/BotInfoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ public void ValidToken()
[TestCase("123456:?????")]
public void InvalidToken(string token) =>
Assert.Throws<InvalidTokenException>(() => { new BotInfo(token); });
}
}
1 change: 0 additions & 1 deletion src/UnitTests/ChatIdTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Reflection;
using NUnit.Framework;
using RxTelegram.Bot.Interface.BaseTypes;
using RxTelegram.Bot.Interface.BaseTypes.Requests.Chats;

namespace RxTelegram.Bot.UnitTests;

Expand Down
Loading

0 comments on commit 5ffc5f4

Please sign in to comment.