Skip to content

Commit 200b049

Browse files
committed
+ chain message update flow
1 parent bcc7083 commit 200b049

File tree

11 files changed

+245
-27
lines changed

11 files changed

+245
-27
lines changed

Deployf.Botf.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deployf.Botf.CalendarKeyboa
2424
EndProject
2525
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deployf.Botf.ReminderBot", "Examples\Deployf.Botf.ReminderBot\Deployf.Botf.ReminderBot.csproj", "{DF8A99F9-EF40-4319-BEDE-EAC5447FD62C}"
2626
EndProject
27-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deployf.Botf.GlobalStateMachineExample", "Examples\Deployf.Botf.GlobalStateMachineExample\Deployf.Botf.GlobalStateMachineExample.csproj", "{D3552144-80A0-4C3B-A0ED-A4A03691466B}"
27+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deployf.Botf.GlobalStateMachineExample", "Examples\Deployf.Botf.GlobalStateMachineExample\Deployf.Botf.GlobalStateMachineExample.csproj", "{D3552144-80A0-4C3B-A0ED-A4A03691466B}"
28+
EndProject
29+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deployf.Botf.ChainedExample", "Examples\Deployf.Botf.ChainedExample\Deployf.Botf.ChainedExample.csproj", "{7E6BE982-6DCF-4CCB-AF7E-80CD5F90ED00}"
2830
EndProject
2931
Global
3032
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -64,6 +66,10 @@ Global
6466
{D3552144-80A0-4C3B-A0ED-A4A03691466B}.Debug|Any CPU.Build.0 = Debug|Any CPU
6567
{D3552144-80A0-4C3B-A0ED-A4A03691466B}.Release|Any CPU.ActiveCfg = Release|Any CPU
6668
{D3552144-80A0-4C3B-A0ED-A4A03691466B}.Release|Any CPU.Build.0 = Release|Any CPU
69+
{7E6BE982-6DCF-4CCB-AF7E-80CD5F90ED00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70+
{7E6BE982-6DCF-4CCB-AF7E-80CD5F90ED00}.Debug|Any CPU.Build.0 = Debug|Any CPU
71+
{7E6BE982-6DCF-4CCB-AF7E-80CD5F90ED00}.Release|Any CPU.ActiveCfg = Release|Any CPU
72+
{7E6BE982-6DCF-4CCB-AF7E-80CD5F90ED00}.Release|Any CPU.Build.0 = Release|Any CPU
6773
EndGlobalSection
6874
GlobalSection(SolutionProperties) = preSolution
6975
HideSolutionNode = FALSE

Deployf.Botf/BotController.cs

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ public virtual void Init(IUpdateContext context, CancellationToken cancellationT
3434
Message = new MessageBuilder();
3535
}
3636

37+
#region state management
3738
protected ValueTask State(object state)
3839
{
3940
return Store!.Set(FromId, BotControllersFSMMiddleware.STATE_KEY, state);
4041
}
4142

43+
protected ValueTask ClearState()
44+
{
45+
return Store!.Remove(FromId, BotControllersFSMMiddleware.STATE_KEY);
46+
}
47+
4248
protected ValueTask AState<T>(T state, string? name = null) where T : notnull
4349
{
4450
if (name == null)
@@ -118,7 +124,9 @@ async ValueTask CallClear()
118124
}
119125
}
120126
}
127+
#endregion
121128

129+
#region misc
122130
public async Task Call<T>(Func<T, Task> method) where T : BotController
123131
{
124132
var controller = Context!.Services.GetRequiredService<T>();
@@ -160,6 +168,7 @@ public virtual async Task OnAfterCall()
160168
await SendOrUpdate();
161169
}
162170
}
171+
#endregion
163172

164173
#region sending
165174
public async Task SendOrUpdate()
@@ -184,6 +193,7 @@ protected async Task Send(string text, ParseMode mode)
184193
replyMarkup: Message.Markup,
185194
cancellationToken: CancelToken,
186195
replyToMessageId: Message.ReplyToMessageId);
196+
ClearMessage();
187197
}
188198

189199
public async Task UpdateMarkup(InlineKeyboardMarkup markup)
@@ -207,11 +217,7 @@ public async Task Update(InlineKeyboardMarkup? markup = null, string? text = nul
207217
replyMarkup: markup ?? Message.Markup as InlineKeyboardMarkup,
208218
cancellationToken: CancelToken
209219
);
210-
}
211-
212-
protected async Task SendHtml(string text)
213-
{
214-
await Send(text, ParseMode.Html);
220+
ClearMessage();
215221
}
216222

217223
protected async Task Send(string text)
@@ -234,16 +240,6 @@ public async Task Send()
234240
await Send(text);
235241
}
236242
}
237-
238-
public async Task SendHtml()
239-
{
240-
Message.SetParseMode(ParseMode.Html);
241-
var text = Message.Message;
242-
if (text != null)
243-
{
244-
await SendHtml(text);
245-
}
246-
}
247243
#endregion
248244

249245
#region formatting
@@ -334,6 +330,11 @@ public void Reply(int? messageId = default)
334330
Message.ReplyTo(messageId.GetValueOrDefault());
335331
}
336332
}
333+
334+
public void ClearMessage()
335+
{
336+
Message = new MessageBuilder();
337+
}
337338
#endregion
338339

339340
#region querying
@@ -431,4 +432,44 @@ public string FPath(string controller, string action, params object[] args)
431432
return $"{hit.template}{splitter}{part2}".TrimEnd('/').TrimEnd();
432433
}
433434
#endregion
435+
436+
#region chaining
437+
438+
public async Task<IUpdateContext> AwaitNextUpdate()
439+
{
440+
var store = Context.Services.GetRequiredService<ChainStorage>();
441+
var tcs = new TaskCompletionSource<IUpdateContext>();
442+
store.Set(ChatId, new (tcs));
443+
return await tcs.Task;
444+
}
445+
446+
public async Task<string> AwaitText()
447+
{
448+
while (true)
449+
{
450+
var update = await AwaitNextUpdate();
451+
if (update.Update.Type != UpdateType.Message)
452+
{
453+
continue;
454+
}
455+
456+
return update.GetSafeTextPayload()!;
457+
}
458+
}
459+
460+
public async Task<string> AwaitQuery()
461+
{
462+
while (true)
463+
{
464+
var update = await AwaitNextUpdate();
465+
if (update.Update.Type != UpdateType.CallbackQuery)
466+
{
467+
continue;
468+
}
469+
470+
return update.GetSafeTextPayload()!;
471+
}
472+
}
473+
474+
#endregion
434475
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Telegram.Bot.Framework.Abstractions;
2+
3+
namespace Deployf.Botf;
4+
5+
public class BotControllersChainMiddleware : IUpdateHandler
6+
{
7+
readonly ILogger<BotControllersChainMiddleware> _log;
8+
readonly ChainStorage _chainStorage;
9+
10+
public BotControllersChainMiddleware(ILogger<BotControllersChainMiddleware> log, ChainStorage chainStorage)
11+
{
12+
_log = log;
13+
_chainStorage = chainStorage;
14+
}
15+
16+
public async Task HandleAsync(IUpdateContext context, UpdateDelegate next, CancellationToken cancellationToken)
17+
{
18+
var id = context.GetSafeChatId();
19+
if (id != null)
20+
{
21+
var chain = _chainStorage.Get(id.Value);
22+
if(chain != null)
23+
{
24+
_log.LogTrace("Found chain for user {userId}, triggered continue execution of chain", id.Value);
25+
_chainStorage.Clear(id.Value);
26+
if (chain.Synchronizator != null)
27+
{
28+
chain.Synchronizator.SetResult(context);
29+
}
30+
}
31+
else
32+
{
33+
await next(context, cancellationToken);
34+
}
35+
}
36+
else
37+
{
38+
await next(context, cancellationToken);
39+
}
40+
}
41+
}

Deployf.Botf/ChainStorage.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Telegram.Bot.Framework.Abstractions;
2+
3+
namespace Deployf.Botf;
4+
5+
public class ChainStorage
6+
{
7+
readonly IDictionary<long, ChainItem?> _chains;
8+
9+
public ChainStorage()
10+
{
11+
_chains = new Dictionary<long, ChainItem?>();
12+
}
13+
14+
public ChainItem? Get(long id)
15+
{
16+
_chains.TryGetValue(id, out var result);
17+
return result;
18+
}
19+
20+
public void Clear(long id)
21+
{
22+
_chains[id] = null;
23+
}
24+
25+
public void Set(long id, ChainItem item)
26+
{
27+
_chains[id] = item;
28+
}
29+
30+
public record ChainItem(TaskCompletionSource<IUpdateContext> Synchronizator);
31+
}

Deployf.Botf/MessageBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public MessageBuilder KButton(KeyboardButton button)
157157
}
158158

159159
Keyboard!.Last().Add(button);
160-
Markup = new ReplyKeyboardMarkup(Keyboard!.Where(c => c.Count > 0));
160+
Markup = new ReplyKeyboardMarkup(Keyboard!.Where(c => c.Count > 0)) { ResizeKeyboard = true };
161161
IsDirty = true;
162162
return this;
163163
}

Deployf.Botf/StartupExtensions.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public static IApplicationBuilder UseBotf(this IApplicationBuilder app, Action<B
1818

1919
builder
2020
.Use<BotControllersExceptionMiddleware>()
21+
.Use<BotControllersChainMiddleware>()
2122
.Use<BotControllersBeforeAllMiddleware>()
2223
.Use<BotControllersMiddleware>()
2324
.Use<BotControllersFSMMiddleware>()
@@ -77,7 +78,9 @@ public static IServiceCollection AddBotf(this IServiceCollection services, BotfO
7778
services.AddSingleton(handlers);
7879
services.AddSingleton<PagingService>();
7980
services.AddSingleton<IKeyValueStorage, InMemoryKeyValueStorage>();
81+
services.AddSingleton<ChainStorage>();
8082
services.AddScoped<BotControllersMiddleware>();
83+
services.AddScoped<BotControllersChainMiddleware>();
8184
services.AddScoped<BotControllersFSMMiddleware>();
8285
services.AddScoped<BotControllersAuthMiddleware>();
8386
services.AddScoped<BotControllersInvokeMiddleware>();
@@ -122,12 +125,7 @@ internal static IApplicationBuilder UseBotfLongPolling<TBot>(this IApplicationBu
122125
{
123126
Offset = 0,
124127
Timeout = 100,
125-
AllowedUpdates = new[]
126-
{
127-
UpdateType.Message,
128-
UpdateType.CallbackQuery,
129-
UpdateType.EditedMessage
130-
}
128+
AllowedUpdates = new UpdateType[0]
131129
};
132130

133131
Task.Run(LoongPooling, cancellationToken)

Deployf.Botf/UpdateContextExtensions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,24 @@ public static long GetUserId(this IUpdateContext context)
1919
{
2020
return context.Update.Message?.Chat.Id
2121
?? context.Update.EditedMessage?.Chat.Id
22-
?? context.Update.CallbackQuery?.Message?.Chat.Id;
22+
?? context.Update.CallbackQuery?.Message?.Chat.Id
23+
?? context.Update.InlineQuery?.From.Id;
2324
}
2425

2526
public static long? GetSafeUserId(this IUpdateContext context)
2627
{
2728
return context.Update.Message?.From?.Id
2829
?? context.Update.EditedMessage?.From?.Id
29-
?? context.Update.CallbackQuery?.From?.Id;
30+
?? context.Update.CallbackQuery?.From?.Id
31+
?? context.Update.InlineQuery?.From.Id;
3032
}
3133

3234
public static long UserId(this IUpdateContext context)
3335
{
3436
var value = context.Update.Message?.From?.Id
3537
?? context.Update.EditedMessage?.From?.Id
36-
?? context.Update.CallbackQuery?.From?.Id;
38+
?? context.Update.CallbackQuery?.From?.Id
39+
?? context.Update.InlineQuery?.From?.Id;
3740

3841
return value!.Value;
3942
}
@@ -58,7 +61,8 @@ public static long UserId(this IUpdateContext context)
5861
public static string? GetSafeTextPayload(this IUpdateContext context)
5962
{
6063
return context.Update.Message?.Text
61-
?? context.Update.CallbackQuery?.Data;
64+
?? context.Update.CallbackQuery?.Data
65+
?? context.Update.InlineQuery?.Query;
6266
}
6367

6468
public static CallbackQuery GetCallbackQuery(this IUpdateContext context)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\Deployf.Botf\Deployf.Botf.csproj" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
global using Deployf.Botf;
2+
using Telegram.Bot.Types.Enums;
3+
4+
class Program : BotfProgram
5+
{
6+
public static void Main(string[] args) => StartBot(args);
7+
8+
readonly ILogger<Program> _logger;
9+
10+
public Program(ILogger<Program> logger)
11+
{
12+
_logger = logger;
13+
}
14+
15+
[Action("Start")]
16+
[Action("/start", "start the bot")]
17+
public async Task Start()
18+
{
19+
await Send($"Hi! What is your name?");
20+
21+
var name = await AwaitText();
22+
await Send($"Hi, {name}! Where are you from?");
23+
24+
var place = await AwaitText();
25+
26+
Button("Like");
27+
Button("Don't like");
28+
await Send($"Hi {name} from {place}! Nice to meet you!\nDo you like this place?");
29+
30+
var likeStatus = await AwaitQuery();
31+
if(likeStatus == "Like")
32+
{
33+
await Send("I'm glad to heat it!\nSend /start to try it again.");
34+
}
35+
else
36+
{
37+
await Send("It's bad(\nSend /start to try it again.");
38+
}
39+
}
40+
41+
[On(Handle.Unknown)]
42+
public async Task Unknown()
43+
{
44+
PushL("unknown");
45+
await Send();
46+
}
47+
48+
[On(Handle.Exception)]
49+
public async Task Ex(Exception e)
50+
{
51+
_logger.LogCritical(e, "Unhandled exception");
52+
53+
if (Context.Update.Type == UpdateType.CallbackQuery)
54+
{
55+
await AnswerCallback("Error");
56+
}
57+
else if (Context.Update.Type == UpdateType.Message)
58+
{
59+
Push("Error");
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)