Skip to content

Commit

Permalink
Merge pull request #8 from deploy-f/fix_photomsg_editing
Browse files Browse the repository at this point in the history
Fix for photo message updating
  • Loading branch information
wellon committed May 8, 2023
2 parents e25bf78 + 0175aff commit 59457a9
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 14 deletions.
61 changes: 53 additions & 8 deletions Deployf.Botf/BotController.cs
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using Deployf.Botf.System.UpdateMessageStrategies;
using Telegram.Bot;
using Telegram.Bot.Framework.Abstractions;
using Telegram.Bot.Types;
Expand All @@ -19,6 +20,7 @@ public abstract class BotController
protected ITelegramBotClient Client { get; set; } = null!;
protected MessageBuilder Message { get; set; } = new MessageBuilder();
public IKeyValueStorage? Store { get; set; }
public IUpdateMessageStrategyFactory UpdateMessageStrategyFactory { get; set; }

Check warning on line 23 in Deployf.Botf/BotController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'UpdateMessageStrategyFactory' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in Deployf.Botf/BotController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'UpdateMessageStrategyFactory' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in Deployf.Botf/BotController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'UpdateMessageStrategyFactory' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in Deployf.Botf/BotController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'UpdateMessageStrategyFactory' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public int? MessageId { get; set; }

protected bool IsDirty
Expand All @@ -35,6 +37,7 @@ public virtual void Init(IUpdateContext context, CancellationToken cancellationT
FromId = Context.GetSafeUserId().GetValueOrDefault();
Client = Context.Bot.Client;
Store = Context.Services.GetService<IKeyValueStorage>(); // todo: move outside
UpdateMessageStrategyFactory = Context.Services.GetRequiredService<IUpdateMessageStrategyFactory>();
Message = new MessageBuilder();
}

Expand Down Expand Up @@ -180,14 +183,46 @@ public async Task<Message> Update(InlineKeyboardMarkup? markup = null, string? t
{
var markupValue = markup ?? Message.Markup as InlineKeyboardMarkup;
IsDirty = false;
var message = await Client.EditMessageTextAsync(
ChatId == 0 ? Context!.GetSafeChatId()! : ChatId,
MessageId ?? Context!.GetSafeMessageId().GetValueOrDefault(),
text ?? Message.Message,
parseMode: mode,
replyMarkup: markupValue,
cancellationToken: CancelToken
);

var chatId = Context!.GetSafeChatId()!.Value;
var messageId = MessageId ?? Context!.GetSafeMessageId().GetValueOrDefault();
var messageText = text ?? Message.Message;
var previousMessage = Context.Update.CallbackQuery!.Message;
var nextMessagePhotoUrl = Message.PhotoUrl;

var ctx = new UpdateMessageContext(
Context,
chatId,
messageId,
messageText,
previousMessage!,
nextMessagePhotoUrl,
markupValue,
mode,
Message.ReplyToMessageId,
CancelToken);

Message message;
var strategy = UpdateMessageStrategyFactory.GetStrategy(ctx);
if (strategy == null)
{
var logger = Context.Services.GetRequiredService<ILogger<BotController>>();
logger.LogDebug("Not found a suitable strategy, using default instead");

message = await Client.EditMessageTextAsync(
ChatId == 0 ? Context!.GetSafeChatId()! : ChatId,
MessageId ?? Context!.GetSafeMessageId().GetValueOrDefault(),
text ?? Message.Message,
parseMode: mode,
replyMarkup: markupValue,
cancellationToken: CancelToken
);
}
else
{
message = await strategy.UpdateMessage(ctx);
}

await TrySaveLastMessageId(markupValue, message);
ClearMessage();
return message;
Expand Down Expand Up @@ -384,6 +419,16 @@ public void Reply(int? messageId = default)
}
}

/// <summary>
/// Sets photo for message
/// </summary>
/// <remarks>Attention! Telegram limit is 0-1024 characters for text messages with images</remarks>
/// <param name="url">
/// Photo url to send. Pass a FileId as String to send a photo that exists on
/// the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from
/// the Internet. The photo must be at most 10 MB in size.
/// The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20
/// </param>
public void Photo(string url)
{
Message.SetPhotoUrl(url);
Expand Down
9 changes: 8 additions & 1 deletion Deployf.Botf/StartupExtensions.cs
@@ -1,4 +1,5 @@
using Telegram.Bot;
using Deployf.Botf.System.UpdateMessageStrategies;
using Telegram.Bot;
using Telegram.Bot.Framework;
using Telegram.Bot.Framework.Abstractions;
using Telegram.Bot.Requests;
Expand Down Expand Up @@ -133,6 +134,12 @@ public static IServiceCollection AddBotf(this IServiceCollection services, BotfO
services.AddSingleton<IArgumentBind, ArgumentBindBridge>();
services.AddSingleton<ArgumentBinder>();

services.AddSingleton<IUpdateMessageStrategy, MediaToPlainTextStrategy>();
services.AddSingleton<IUpdateMessageStrategy, PlainTextToMediaStrategy>();
services.AddSingleton<IUpdateMessageStrategy, MediaToMediaFileStrategy>();
services.AddSingleton<IUpdateMessageStrategy, EditTextMessageStrategy>();
services.AddSingleton<IUpdateMessageStrategyFactory, UpdateMessageStrategyFactory>();

return services;
}

Expand Down
12 changes: 7 additions & 5 deletions Deployf.Botf/System/BotfException.cs
@@ -1,12 +1,14 @@
using System.Runtime.Serialization;

namespace Deployf.Botf;

[System.Serializable]
public class BotfException : System.Exception
[Serializable]
public class BotfException : Exception
{
public BotfException() { }
public BotfException(string message) : base(message) { }
public BotfException(string message, System.Exception inner) : base(message, inner) { }
public BotfException(string message, Exception inner) : base(message, inner) { }
protected BotfException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
SerializationInfo info,
StreamingContext context) : base(info, context) { }
}
22 changes: 22 additions & 0 deletions Deployf.Botf/System/UpdateContextExtensions.cs
Expand Up @@ -5,6 +5,7 @@ namespace Deployf.Botf;

public static class UpdateContextExtensions
{
private const string UPDATE_MESSAGE_POLICY_KEY = "$_UpdateMessagePolicy";
private const string STOP_HANDLING_KEY = "$_StopHandling";
private const string CURRENT_HANDLER_KEY = "$_CurrentHandler";
private const string FILTER_PARAMETER_KEY = "$_FilterParameter";
Expand Down Expand Up @@ -200,4 +201,25 @@ public static void SetFilterParameter(this IUpdateContext context, object? param

return null;
}

public static void SetUpdateMsgPolicy(this IUpdateContext context, UpdateMessagePolicy policy)
{
context.Items[UPDATE_MESSAGE_POLICY_KEY] = policy;
}

public static UpdateMessagePolicy? GetCurrentUpdateMsgPolicy(this IUpdateContext context)
{
if(context.Items.TryGetValue(UPDATE_MESSAGE_POLICY_KEY, out var policy) && policy is UpdateMessagePolicy result)
{
return result;
}

return null;
}
}

public enum UpdateMessagePolicy
{
UpdateContent = 0, // default
DeleteAndSend = 1
}
@@ -0,0 +1,37 @@
using Telegram.Bot;
using Telegram.Bot.Types;

namespace Deployf.Botf.System.UpdateMessageStrategies;

/// <summary>
/// Situation: previous message has no media file and a new message does not have.
/// </summary>
public class EditTextMessageStrategy : IUpdateMessageStrategy
{
private readonly BotfBot _bot;

public EditTextMessageStrategy(BotfBot bot)
{
_bot = bot;
}

public bool CanHandle(IUpdateMessageContext context)
{
var newMessageFileIsEmpty = string.IsNullOrEmpty(context.MediaFile?.FileId) &&
string.IsNullOrEmpty(context.MediaFile?.Url);

return context.PreviousMessage.Photo == null && newMessageFileIsEmpty;
}

public async Task<Message> UpdateMessage(IUpdateMessageContext context)
{
return await _bot.Client.EditMessageTextAsync(
context.ChatId,
context.MessageId,
context.MessageText,
parseMode: context.ParseMode,
replyMarkup: context.KeyboardMarkup,
cancellationToken: context.CancelToken
);
}
}
@@ -0,0 +1,9 @@
using Telegram.Bot.Types;

namespace Deployf.Botf.System.UpdateMessageStrategies;

public interface IUpdateMessageStrategy
{
public bool CanHandle(IUpdateMessageContext context);
public Task<Message> UpdateMessage(IUpdateMessageContext context);
}
@@ -0,0 +1,21 @@
namespace Deployf.Botf.System.UpdateMessageStrategies;

public interface IUpdateMessageStrategyFactory
{
IUpdateMessageStrategy? GetStrategy(IUpdateMessageContext context);
}

public class UpdateMessageStrategyFactory : IUpdateMessageStrategyFactory
{
private readonly IEnumerable<IUpdateMessageStrategy> _strategies;

public UpdateMessageStrategyFactory(IEnumerable<IUpdateMessageStrategy> strategies)
{
_strategies = strategies;
}

public IUpdateMessageStrategy? GetStrategy(IUpdateMessageContext context)
{
return _strategies.FirstOrDefault(s => s.CanHandle(context));
}
}
@@ -0,0 +1,55 @@
using Telegram.Bot;
using Telegram.Bot.Types;

namespace Deployf.Botf.System.UpdateMessageStrategies;

/// <summary>
/// Situation: previous message has a media file and a new message has one.
/// </summary>
public class MediaToMediaFileStrategy : IUpdateMessageStrategy
{
private readonly BotfBot _bot;

public MediaToMediaFileStrategy(BotfBot bot)
{
_bot = bot;
}

public bool CanHandle(IUpdateMessageContext context)
{
var newMessageHasFile = !string.IsNullOrEmpty(context.MediaFile?.FileId) ||
!string.IsNullOrEmpty(context.MediaFile?.Url);

return context.PreviousMessage.Photo != null && newMessageHasFile;
}

public async Task<Message> UpdateMessage(IUpdateMessageContext context)
{
var updateMessagePolicy = context.UpdateContext.GetCurrentUpdateMsgPolicy();
if (updateMessagePolicy is UpdateMessagePolicy.DeleteAndSend)
{
await _bot.Client.DeleteMessageAsync(context.ChatId, context.PreviousMessage.MessageId, context.CancelToken);
return await _bot.Client.SendPhotoAsync(
context.ChatId,
context.MediaFile!,
context.MessageText,
context.ParseMode,
replyMarkup: context.KeyboardMarkup,
cancellationToken: context.CancelToken,
replyToMessageId: context.ReplyToMessageId);
}
else
{
return await _bot.Client.EditMessageMediaAsync(
context.ChatId,
context.MessageId,
new InputMediaPhoto(context.MediaFile!)
{
Caption = context.MessageText
},
replyMarkup: context.KeyboardMarkup,
cancellationToken: context.CancelToken
);
}
}
}
@@ -0,0 +1,37 @@
using Telegram.Bot;
using Telegram.Bot.Types;

namespace Deployf.Botf.System.UpdateMessageStrategies;

/// <summary>
/// Situation: previous message has media file, but a new message does not have.
/// </summary>
public class MediaToPlainTextStrategy : IUpdateMessageStrategy
{
private readonly BotfBot _bot;

public MediaToPlainTextStrategy(BotfBot bot)
{
_bot = bot;
}

public bool CanHandle(IUpdateMessageContext context)
{
var newMessageFileIsEmpty = string.IsNullOrEmpty(context.MediaFile?.FileId) &&
string.IsNullOrEmpty(context.MediaFile?.Url);

return context.PreviousMessage.Photo != null && newMessageFileIsEmpty;
}

public async Task<Message> UpdateMessage(IUpdateMessageContext context)
{
await _bot.Client.DeleteMessageAsync(context.ChatId, context.PreviousMessage.MessageId, context.CancelToken);
return await _bot.Client.SendTextMessageAsync(
context.ChatId,
context.MessageText,
context.ParseMode,
replyMarkup: context.KeyboardMarkup,
cancellationToken: context.CancelToken,
replyToMessageId: context.ReplyToMessageId);
}
}
@@ -0,0 +1,38 @@
using Telegram.Bot;
using Telegram.Bot.Types;

namespace Deployf.Botf.System.UpdateMessageStrategies;

/// <summary>
/// Situation: previous message has no media file, but a new message has one.
/// </summary>
public class PlainTextToMediaStrategy : IUpdateMessageStrategy
{
private readonly BotfBot _bot;

public PlainTextToMediaStrategy(BotfBot bot)
{
_bot = bot;
}

public bool CanHandle(IUpdateMessageContext context)
{
var newMessageHasFile = !string.IsNullOrEmpty(context.MediaFile?.FileId) ||
!string.IsNullOrEmpty(context.MediaFile?.Url);

return context.PreviousMessage.Photo == null && newMessageHasFile;
}

public async Task<Message> UpdateMessage(IUpdateMessageContext context)
{
await _bot.Client.DeleteMessageAsync(context.ChatId, context.PreviousMessage.MessageId, context.CancelToken);
return await _bot.Client.SendPhotoAsync(
context.ChatId,
context.MediaFile!,
context.MessageText,
context.ParseMode,
replyMarkup: context.KeyboardMarkup,
cancellationToken: context.CancelToken,
replyToMessageId: context.ReplyToMessageId);
}
}

0 comments on commit 59457a9

Please sign in to comment.