using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.InputFiles; using Telegram.Bot.Types.ReplyMarkups; using TelegramBotBase.Args; using TelegramBotBase.Base; using TelegramBotBase.Exceptions; using TelegramBotBase.Form; using TelegramBotBase.Interfaces; using TelegramBotBase.Markdown; namespace TelegramBotBase.Sessions; /// /// Base class for a device/chat session /// public class DeviceSession : IDeviceSession { private static readonly object EvMessageSent = new(); private static readonly object EvMessageReceived = new(); private static readonly object EvMessageDeleted = new(); private readonly EventHandlerList _events = new(); public DeviceSession() { } public DeviceSession(long deviceId) { DeviceId = deviceId; } public DeviceSession(long deviceId, FormBase startForm) { DeviceId = deviceId; ActiveForm = startForm; ActiveForm.Device = this; } /// /// Returns the ID of the last received message. /// public int LastMessageId => LastMessage?.MessageId ?? -1; /// /// Returns the last received message. /// public Message LastMessage { get; set; } public MessageClient Client => ActiveForm.Client; /// /// Returns if the messages is posted within a group. /// public bool IsGroup => LastMessage != null && (LastMessage.Chat.Type == ChatType.Group) | (LastMessage.Chat.Type == ChatType.Supergroup); /// /// Returns if the messages is posted within a channel. /// public bool IsChannel => LastMessage != null && LastMessage.Chat.Type == ChatType.Channel; #region "Static" /// /// Indicates the maximum number of times a request that received error /// 429 will be sent again after a timeout until it receives code 200 or an error code not equal to 429. /// public static uint MaxNumberOfRetries { get; set; } #endregion "Static" /// /// Device or chat id /// public long DeviceId { get; set; } /// /// Username of user or group /// public string ChatTitle { get; set; } /// /// When did any last action happend (message received or button clicked) /// public DateTime LastAction { get; set; } /// /// Returns the form where the user/group is at the moment. /// public FormBase ActiveForm { get; set; } /// /// Returns the previous shown form /// public FormBase PreviousForm { get; set; } /// /// contains if the form has been switched (navigated) /// public bool FormSwitched { get; set; } = false; /// /// Returns the ChatTitle depending on groups/channels or users /// /// public string GetChatTitle() { return LastMessage?.Chat.Title ?? LastMessage?.Chat.Username ?? LastMessage?.Chat.FirstName ?? ChatTitle; } /// /// Confirm incomming action (i.e. Button click) /// /// /// public async Task ConfirmAction(string callbackQueryId, string message = "", bool showAlert = false, string urlToOpen = null) { try { await Client.TelegramClient.AnswerCallbackQueryAsync(callbackQueryId, message, showAlert, urlToOpen); } catch { } } /// /// Edits the text message /// /// /// /// /// public async Task Edit(int messageId, string text, ButtonForm buttons = null, ParseMode parseMode = ParseMode.Markdown) { InlineKeyboardMarkup markup = buttons; if (text.Length > Constants.Telegram.MaxMessageLength) { throw new MaxLengthException(text.Length); } try { return await Api(a => a.EditMessageTextAsync(DeviceId, messageId, text, parseMode, replyMarkup: markup)); } catch { } return null; } /// /// Edits the text message /// /// /// /// /// public async Task Edit(int messageId, string text, InlineKeyboardMarkup markup, ParseMode parseMode = ParseMode.Markdown) { if (text.Length > Constants.Telegram.MaxMessageLength) { throw new MaxLengthException(text.Length); } try { return await Api(a => a.EditMessageTextAsync(DeviceId, messageId, text, parseMode, replyMarkup: markup)); } catch { } return null; } /// /// Edits the text message /// /// /// /// /// public async Task Edit(Message message, ButtonForm buttons = null, ParseMode parseMode = ParseMode.Markdown) { InlineKeyboardMarkup markup = buttons; if (message.Text.Length > Constants.Telegram.MaxMessageLength) { throw new MaxLengthException(message.Text.Length); } try { return await Api(a => a.EditMessageTextAsync(DeviceId, message.MessageId, message.Text, parseMode, replyMarkup: markup)); } catch { } return null; } /// /// Edits the reply keyboard markup (buttons) /// /// /// /// public async Task EditReplyMarkup(int messageId, ButtonForm bf) { try { return await Api(a => a.EditMessageReplyMarkupAsync(DeviceId, messageId, bf)); } catch { } return null; } /// /// Sends a simple text message /// /// /// /// /// /// public async Task Send(long deviceId, string text, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown, bool markdownV2AutoEscape = true) { if (ActiveForm == null) { return null; } InlineKeyboardMarkup markup = buttons; if (text.Length > Constants.Telegram.MaxMessageLength) { throw new MaxLengthException(text.Length); } if (parseMode == ParseMode.MarkdownV2 && markdownV2AutoEscape) { text = text.MarkdownV2Escape(); } try { var t = Api(a => a.SendTextMessageAsync(deviceId, text, parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends a simple text message /// /// /// /// /// /// public async Task Send(string text, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown, bool markdownV2AutoEscape = true) { return await Send(DeviceId, text, buttons, replyTo, disableNotification, parseMode, markdownV2AutoEscape); } /// /// Sends a simple text message /// /// /// /// /// /// public async Task Send(string text, InlineKeyboardMarkup markup, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown, bool markdownV2AutoEscape = true) { if (ActiveForm == null) { return null; } if (text.Length > Constants.Telegram.MaxMessageLength) { throw new MaxLengthException(text.Length); } if (parseMode == ParseMode.MarkdownV2 && markdownV2AutoEscape) { text = text.MarkdownV2Escape(); } try { var t = Api(a => a.SendTextMessageAsync(DeviceId, text, parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends a simple text message /// /// /// /// /// /// public async Task Send(string text, ReplyMarkupBase markup, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown, bool markdownV2AutoEscape = true) { if (ActiveForm == null) { return null; } if (text.Length > Constants.Telegram.MaxMessageLength) { throw new MaxLengthException(text.Length); } if (parseMode == ParseMode.MarkdownV2 && markdownV2AutoEscape) { text = text.MarkdownV2Escape(); } try { var t = Api(a => a.SendTextMessageAsync(DeviceId, text, parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends an image /// /// /// /// /// /// public async Task SendPhoto(InputOnlineFile file, string caption = null, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown) { if (ActiveForm == null) { return null; } InlineKeyboardMarkup markup = buttons; try { var t = Api(a => a.SendPhotoAsync(DeviceId, file, caption, parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends an video /// /// /// /// /// /// public async Task SendVideo(InputOnlineFile file, string caption = null, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown) { if (ActiveForm == null) { return null; } InlineKeyboardMarkup markup = buttons; try { var t = Api(a => a.SendVideoAsync(DeviceId, file, caption: caption, parseMode: parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends an video /// /// /// /// /// /// public async Task SendVideo(string url, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown) { if (ActiveForm == null) { return null; } InlineKeyboardMarkup markup = buttons; try { var t = Api(a => a.SendVideoAsync(DeviceId, new InputOnlineFile(url), parseMode: parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends an video /// /// /// /// /// /// /// public async Task SendVideo(string filename, byte[] video, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown) { if (ActiveForm == null) { return null; } InlineKeyboardMarkup markup = buttons; try { var ms = new MemoryStream(video); var fts = new InputOnlineFile(ms, filename); var t = Api(a => a.SendVideoAsync(DeviceId, fts, parseMode: parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends an local file as video /// /// /// /// /// /// /// public async Task SendLocalVideo(string filepath, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown) { if (ActiveForm == null) { return null; } InlineKeyboardMarkup markup = buttons; try { var fs = new FileStream(filepath, FileMode.Open); var filename = Path.GetFileName(filepath); var fts = new InputOnlineFile(fs, filename); var t = Api(a => a.SendVideoAsync(DeviceId, fts, parseMode: parseMode, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Sends an document /// /// /// /// /// /// /// /// public async Task SendDocument(string filename, byte[] document, string caption = "", ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false) { var ms = new MemoryStream(document); var fts = new InputOnlineFile(ms, filename); return await SendDocument(fts, caption, buttons, replyTo, disableNotification); } /// /// Generates a Textfile from scratch with the specified encoding. (Default is UTF8) /// /// /// /// Default is UTF8 /// /// /// /// /// public async Task SendTextFile(string filename, string textcontent, Encoding encoding = null, string caption = "", ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false) { encoding = encoding ?? Encoding.UTF8; var ms = new MemoryStream(); var sw = new StreamWriter(ms, encoding); sw.Write(textcontent); sw.Flush(); var content = ms.ToArray(); return await SendDocument(filename, content, caption, buttons, replyTo, disableNotification); } /// /// Sends an document /// /// /// /// /// /// /// public async Task SendDocument(InputOnlineFile document, string caption = "", ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false) { InlineKeyboardMarkup markup = null; if (buttons != null) { markup = buttons; } try { var t = Api(a => a.SendDocumentAsync(DeviceId, document, caption, replyMarkup: markup, disableNotification: disableNotification, replyToMessageId: replyTo)); var o = GetOrigin(new StackTrace()); await OnMessageSent(new MessageSentEventArgs(await t, o)); return await t; } catch { return null; } } /// /// Set a chat action (showed to the user) /// /// /// public async Task SetAction(ChatAction action) { await Api(a => a.SendChatActionAsync(DeviceId, action)); } /// /// Requests the contact from the user. /// /// /// /// /// public async Task RequestContact(string buttonText = "Send your contact", string requestMessage = "Give me your phone number!", bool oneTimeOnly = true) { var rck = new ReplyKeyboardMarkup(KeyboardButton.WithRequestContact(buttonText)) { OneTimeKeyboard = oneTimeOnly }; return await Api(a => a.SendTextMessageAsync(DeviceId, requestMessage, replyMarkup: rck)); } /// /// Requests the location from the user. /// /// /// /// /// public async Task RequestLocation(string buttonText = "Send your location", string requestMessage = "Give me your location!", bool oneTimeOnly = true) { var rcl = new ReplyKeyboardMarkup(KeyboardButton.WithRequestLocation(buttonText)) { OneTimeKeyboard = oneTimeOnly }; return await Api(a => a.SendTextMessageAsync(DeviceId, requestMessage, replyMarkup: rcl)); } public async Task HideReplyKeyboard(string closedMsg = "Closed", bool autoDeleteResponse = true) { try { var m = await Send(closedMsg, new ReplyKeyboardRemove()); if (autoDeleteResponse && m != null) { await DeleteMessage(m); } return m; } catch { } return null; } /// /// Deletes a message /// /// /// public virtual async Task DeleteMessage(int messageId = -1) { await Raw(a => a.DeleteMessageAsync(DeviceId, messageId)); OnMessageDeleted(new MessageDeletedEventArgs(messageId)); return true; } /// /// Deletes the given message /// /// /// public virtual async Task DeleteMessage(Message message) { return await DeleteMessage(message.MessageId); } public virtual async Task ChangeChatPermissions(ChatPermissions permissions) { try { await Api(a => a.SetChatPermissionsAsync(DeviceId, permissions)); } catch { } } private Type GetOrigin(StackTrace stackTrace) { for (var i = 0; i < stackTrace.FrameCount; i++) { var methodBase = stackTrace.GetFrame(i).GetMethod(); //Debug.WriteLine(methodBase.Name); if (methodBase.DeclaringType.IsSubclassOf(typeof(FormBase)) | methodBase.DeclaringType.IsSubclassOf(typeof(ControlBase))) { return methodBase.DeclaringType; } } return null; } /// /// Gives access to the original TelegramClient without any Exception catchings. /// /// /// /// public T Raw(Func call) { return call(Client.TelegramClient); } /// /// This will call a function on the TelegramClient and automatically Retry if an limit has been exceeded. /// /// /// /// public async Task Api(Func> call) { var numberOfTries = 0; while (numberOfTries < MaxNumberOfRetries) { try { return await call(Client.TelegramClient); } catch (ApiRequestException ex) { if (ex.ErrorCode != 429) { throw; } if (ex.Parameters != null && ex.Parameters.RetryAfter != null) { await Task.Delay(ex.Parameters.RetryAfter.Value * 1000); } numberOfTries++; } } return default; } /// /// This will call a function on the TelegramClient and automatically Retry if an limit has been exceeded. /// /// /// public async Task Api(Func call) { var numberOfTries = 0; while (numberOfTries < MaxNumberOfRetries) { try { await call(Client.TelegramClient); return; } catch (ApiRequestException ex) { if (ex.ErrorCode != 429) { throw; } if (ex.Parameters != null && ex.Parameters.RetryAfter != null) { await Task.Delay(ex.Parameters.RetryAfter.Value * 1000); } numberOfTries++; } } } #region "Users" public virtual async Task RestrictUser(long userId, ChatPermissions permissions, DateTime until = default) { try { await Api(a => a.RestrictChatMemberAsync(DeviceId, userId, permissions, until)); } catch { } } public virtual async Task GetChatUser(long userId) { try { return await Api(a => a.GetChatMemberAsync(DeviceId, userId)); } catch { } return null; } [Obsolete("User BanUser instead.")] public virtual async Task KickUser(long userId, DateTime until = default) { try { await Api(a => a.BanChatMemberAsync(DeviceId, userId, until)); } catch { } } public virtual async Task BanUser(long userId, DateTime until = default) { try { await Api(a => a.BanChatMemberAsync(DeviceId, userId, until)); } catch { } } public virtual async Task UnbanUser(long userId) { try { await Api(a => a.UnbanChatMemberAsync(DeviceId, userId)); } catch { } } #endregion #region "Events" /// /// Eventhandler for sent messages /// public event Async.AsyncEventHandler MessageSent { add => _events.AddHandler(EvMessageSent, value); remove => _events.RemoveHandler(EvMessageSent, value); } public async Task OnMessageSent(MessageSentEventArgs e) { if (e.Message == null) { return; } var handler = _events[EvMessageSent]?.GetInvocationList() .Cast>(); if (handler == null) { return; } foreach (var h in handler) { await h.InvokeAllAsync(this, e); } //(this.__Events[__evMessageSent] as EventHandler)?.Invoke(this, e); } /// /// Eventhandler for received messages /// public event EventHandler MessageReceived { add => _events.AddHandler(EvMessageReceived, value); remove => _events.RemoveHandler(EvMessageReceived, value); } public void OnMessageReceived(MessageReceivedEventArgs e) { (_events[EvMessageReceived] as EventHandler)?.Invoke(this, e); } /// /// Eventhandler for deleting messages /// public event EventHandler MessageDeleted { add => _events.AddHandler(EvMessageDeleted, value); remove => _events.RemoveHandler(EvMessageDeleted, value); } public void OnMessageDeleted(MessageDeletedEventArgs e) { (_events[EvMessageDeleted] as EventHandler)?.Invoke(this, e); } #endregion }