diff --git a/telegram/Command.cs b/telegram/Command.cs index 8a3c4f5..e861ea6 100644 --- a/telegram/Command.cs +++ b/telegram/Command.cs @@ -5,861 +5,706 @@ using TdLib; using static telegram.tgcli; using static telegram.Util; -namespace telegram -{ - public abstract class Command - { - public string trigger; - public string shortcut; - public string description; - public string syntax; - public int paramCount; - public abstract void Handler(List inputParams); - - protected Command(string trigger, string shortcut, string description, string syntax, int paramCount) - { - this.trigger = trigger; - this.shortcut = shortcut; - this.description = description; - this.paramCount = paramCount; - this.syntax = syntax; - } - } - - public static class CommandManager - { - public static readonly List Commands = new List - { - new ClearCommand(), - new CloseCommand(), - new EditCommand(), - new ReplyCommand(), - new HistoryCommand(), - new OpenCommand(), - new UnreadsCommand(), - new CloseUnreadCommand(), - new ListChatsCommand(), - new NewChatCommand(), - new ListSecretChatsCommand(), - new OpenSecretCommand(), - new OpenSecretDirectCommand(), - new NewSecretChatCommand(), - new CloseSecretChatCommand(), - new SearchUserCommand(), - //new AddContactCommand(), - new QuitCommand(), - new HelpCommand(), - new LogoutCommand(), - }; - - public static void HandleCommand(string input) - { - var split = input.Split(" ").ToList(); - var trigger = split.First(); - var command = Commands.Find(p => p.trigger == trigger || p.shortcut == trigger); - if (command == null) - { - lock(@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command. Check /help for all available commands."); - return; - } - - split.RemoveAt(0); - if (command.paramCount == -1) - { - command.Handler(split); - } - else if (split.Count == command.paramCount) - { - command.Handler(split); - } - else - { - lock(@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command syntax. Check /help for more information."); - } - } - } - - public class OpenCommand : Command - { - public OpenCommand() : base("o", "^O", "opens a chat. queries chat list", "", -1) - { - } - - public override void Handler(List inputParams) - { - if (inputParams.Count == 0){ - lock(@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command syntax. Check /help for more information."); - return; - } - var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - var chatId = SearchChatId(query); - if (chatId == 0) return; - - currentChatId = 0; - currentChatUserId = 0; - currentUserRead = false; - - var chat = GetChat(chatId); - if (chat.Type is TdApi.ChatType.ChatTypePrivate privChat) - { - currentChatUserId = privChat.UserId; - } - - currentChatId = chat.Id; - - chat.Title = TruncateString(chat.Title, 20); - - prefix = $"[{chat.Title}"; - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}"); - messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + - $"{(chat.UnreadCount == 1 ? "." : "s.")}"); - - if (chat.UnreadCount >= 5) - { - var capped = chat.UnreadCount > 50; - GetHistory(chatId, capped ? 50 : chat.UnreadCount).ForEach(AddMessageToQueue); - if (capped) - messageQueue.Add( - $"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); - } - else if (chat.UnreadCount > 0) - { - var unreads = GetHistory(chatId, chat.UnreadCount); - var rest = GetHistory(chatId, 5 - unreads.Count, unreads.First().Id); - rest.ForEach(AddMessageToQueue); - messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); - unreads.ForEach(AddMessageToQueue); - } - else - { - GetHistory(chatId).ForEach(AddMessageToQueue); - } - } - - var history = GetHistory(currentChatId, 50); - if (history.Count != 0) - MarkRead(chat.Id, history.First().Id); - var last = history.LastOrDefault(p => p.IsOutgoing); - if (last == null) - { - currentUserRead = true; - return; - } - - lastMessage = last; - currentUserRead = IsMessageRead(last.ChatId, last.Id); - } - } - - public class NewChatCommand : Command - { - public NewChatCommand() : base("n", "", "starts a new chat.", "", 1) - { - } - - public override void Handler(List inputParams) - { - var chat = GetChatByUsernameGlobal(inputParams[0]); - if (chat == null) - { - lock(@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] User not found. Try /s to find valid usernames."); - return; - } - - currentChatId = 0; - currentChatUserId = 0; - currentUserRead = false; - - if (chat.Type is TdApi.ChatType.ChatTypePrivate privChat) - { - currentChatUserId = privChat.UserId; - } - - currentChatId = chat.Id; - - chat.Title = TruncateString(chat.Title, 20); - - prefix = $"[{chat.Title}"; - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}"); - messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + - $"{(chat.UnreadCount == 1 ? "." : "s.")}"); - - if (chat.UnreadCount >= 5) - { - var capped = chat.UnreadCount > 50; - GetHistory(chat.Id, capped ? 50 : chat.UnreadCount).ForEach(AddMessageToQueue); - if (capped) - messageQueue.Add( - $"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); - } - else if (chat.UnreadCount > 0) - { - var unreads = GetHistory(chat.Id, chat.UnreadCount); - var rest = GetHistory(chat.Id, 5 - unreads.Count, unreads.First().Id); - rest.ForEach(AddMessageToQueue); - messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); - unreads.ForEach(AddMessageToQueue); - } - else - { - GetHistory(chat.Id).ForEach(AddMessageToQueue); - } - } - - var history = GetHistory(currentChatId, 50); - if (history.Count != 0) - MarkRead(chat.Id, history.First().Id); - var last = history.LastOrDefault(p => p.IsOutgoing); - if (last == null) - { - currentUserRead = true; - return; - } - - lastMessage = last; - currentUserRead = IsMessageRead(last.ChatId, last.Id); - } - } - - public class CloseSecretChatCommand : Command - { - public CloseSecretChatCommand() : base("cs", "", "closes a secret chat (permanently)", "", 0) - { - } - - public override void Handler(List _) - { - if (currentChatId != 0 && GetChat(currentChatId).Type is TdApi.ChatType.ChatTypeSecret type) - { - CloseSecretChat(type.SecretChatId); - DeleteChatHistory(currentChatId); - CommandManager.HandleCommand("c"); - } - else - { - lock(@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No secret chat selected, cannot continue."); - } - } - } - - public class NewSecretChatCommand : Command - { - public NewSecretChatCommand() : base("ns", "", "creates a new secret chat.", "", 1) - { - } - - public override void Handler(List inputParams) - { - var userId = GetUserIdByUsername(inputParams[0]); - - if (userId == 0) - { - lock(@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] User not found. Try /s to find valid usernames."); - return; - } - - if (GetSecretChats().Count(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId) > 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] You already have a secret chat with the specified user."); - return; - } - - var chat = CreateSecretChat(userId); - CommandManager.HandleCommand("osd " + chat.Id); - } - } - - public class OpenSecretDirectCommand : Command - { - public OpenSecretDirectCommand() : base("osd", "", "opens a secret chat by chat id", "", 1) - { - } - - public override void Handler(List inputParams) - { - var id = inputParams[0]; - if (!long.TryParse(id, out var chatId)) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid chat id."); - return; - } - - var chat = GetChat(chatId); - if (chat == null) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid chat id."); - return; - } - - TdApi.SecretChat secChat; - if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) - { - currentChatUserId = secretChat.UserId; - currentChatId = chat.Id; - currentUserRead = false; - secChat = GetSecretChat(secretChat.SecretChatId); - } - else - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] The specified chat isn't a secret chat."); - return; - } - - chat.Title = TruncateString(chat.Title, 20); - - prefix = $"[{Ansi.Red}sec {Ansi.ResetAll}{chat.Title}]"; - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening secret chat: {chat.Title}"); - messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + - $"{(chat.UnreadCount == 1 ? "." : "s.")}"); - if (secChat.State is TdApi.SecretChatState.SecretChatStateClosed) - { - messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat has ended. No messages can be sent."); - } - else if (secChat.State is TdApi.SecretChatState.SecretChatStatePending) - { - messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat is pending. No messages can be sent."); - } - - if (chat.UnreadCount >= 5) - { - var capped = chat.UnreadCount > 50; - GetHistory(chatId, capped ? 50 : chat.UnreadCount, isSecret: true).ForEach(AddMessageToQueue); - if (capped) - messageQueue.Add( - $"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); - } - else if (chat.UnreadCount > 0) - { - var unreads = GetHistory(chatId, chat.UnreadCount, isSecret: true); - var rest = GetHistory(chatId, 5 - unreads.Count, unreads.First().Id, isSecret: true); - rest.ForEach(AddMessageToQueue); - messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); - unreads.ForEach(AddMessageToQueue); - } - else - { - GetHistory(chatId, isSecret: true).ForEach(AddMessageToQueue); - } - } - - var history = GetHistory(currentChatId, 50, isSecret: true); - if (history.Count != 0) - MarkRead(chat.Id, history.First().Id); - var last = history.LastOrDefault(p => p.IsOutgoing); - if (last == null) - { - currentUserRead = true; - return; - } - - lastMessage = last; - currentUserRead = IsMessageRead(last.ChatId, last.Id); - } - } - - public class OpenSecretCommand : Command - { - public OpenSecretCommand() : base("os", "", "opens a secret chat. queries chat list.", "", -1) - { - } - - public override void Handler(List inputParams) - { - var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - var userId = SearchUserInChats(query); - if (userId == 0 || query.Length == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No matching chat found."); - return; - } - var chat = GetSecretChats().Find(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId); - if (chat == null) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No matching secret chat found."); - return; - } - - TdApi.SecretChat secChat; - if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) - { - currentChatUserId = secretChat.UserId; - currentChatId = chat.Id; - currentUserRead = false; - secChat = GetSecretChat(secretChat.SecretChatId); - } - else - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No matching secret chat found. (this error should be impossible to produce)"); - return; - } - - chat.Title = TruncateString(chat.Title, 20); - - prefix = $"[{Ansi.Red}sec {Ansi.ResetAll}{chat.Title}]"; - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening secret chat: {chat.Title}"); - messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + - $"{(chat.UnreadCount == 1 ? "." : "s.")}"); - if (secChat.State is TdApi.SecretChatState.SecretChatStateClosed) - { - messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat has ended. No messages can be sent."); - } - else if (secChat.State is TdApi.SecretChatState.SecretChatStatePending) - { - messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat is pending. No messages can be sent."); - } - - if (chat.UnreadCount >= 5) - { - var capped = chat.UnreadCount > 50; - GetHistory(chat.Id, capped ? 50 : chat.UnreadCount, isSecret: true).ForEach(AddMessageToQueue); - if (capped) - messageQueue.Add( - $"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); - } - else if (chat.UnreadCount > 0) - { - var unreads = GetHistory(chat.Id, chat.UnreadCount, isSecret: true); - var rest = GetHistory(chat.Id, 5 - unreads.Count, unreads.First().Id, isSecret: true); - rest.ForEach(AddMessageToQueue); - messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); - unreads.ForEach(AddMessageToQueue); - } - else - { - GetHistory(chat.Id, isSecret: true).ForEach(AddMessageToQueue); - } - } - - var history = GetHistory(currentChatId, 50, isSecret: true); - if (history.Count != 0) - MarkRead(chat.Id, history.First().Id); - var last = history.LastOrDefault(p => p.IsOutgoing); - if (last == null) - { - currentUserRead = true; - return; - } - - lastMessage = last; - currentUserRead = IsMessageRead(last.ChatId, last.Id); - } - } - - public class CloseUnreadCommand : Command - { - public CloseUnreadCommand() : base("cu", "", "closes a chat, marking it as unread", "", 0) - { - } - - public override void Handler(List inputParams) - { - if (currentChatId == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); - return; - } - MarkUnread(currentChatId); - CommandManager.HandleCommand("c"); - } - } - - public class CloseCommand : Command - { - public CloseCommand() : base("c", "^E", "closes a chat", "", 0) - { - } - - public override void Handler(List inputParams) - { - if (currentChatId == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); - return; - } - - currentChatId = 0; - currentChatUserId = 0; - currentUserRead = false; - lastMessage = null; - prefix = "[tgcli"; - - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Closing chat."); - var count = missedMessages.Count; - if (count == 0) return; - messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {count} missed message" + - $"{(count == 1 ? "." : "s.")}"); - messageQueue.AddRange(missedMessages); - missedMessages.Clear(); - } - } - } - - public class HistoryCommand : Command - { - public HistoryCommand() : base("h", "", "shows chat history. default limit is 5", "[1-50]", -1) - { - } - - public override void Handler(List inputParams) - { - if (currentChatId == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); - return; - } - - var history = inputParams.Count == 1 && int.TryParse(inputParams[0], out var limit) - ? GetHistory(currentChatId, limit) - : GetHistory(currentChatId); - - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Last {history.Count} messages in " + - $"{GetChat(currentChatId).Title}"); - } - - foreach (var msg in history) - { - AddMessageToQueue(msg); - } - } - } - - public class ClearCommand : Command - { - public ClearCommand() : base("cl", "^L", "clears console", "", 0) - { - } - - public override void Handler(List inputParams) - { - lock (@lock) - { - Console.Clear(); - } - } - } - - public class UnreadsCommand : Command - { - public UnreadsCommand() : base("u", "^U", "displays unread chat", "[all]", -1) - { - } - - public override void Handler(List inputParams) - { - var unreads = GetUnreadChats(inputParams.Count == 1 && inputParams[0].Equals("all")); - - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] You have {unreads.Count} unread chats."); - unreads.ForEach(chat => - { - string line; - if (chat.UnreadCount == 0) - line = $"{Ansi.Bold}{Ansi.Yellow}[M] {chat.Title}"; - else if (chat.Type is TdApi.ChatType.ChatTypeSecret) - line = $"{Ansi.Bold}{Ansi.Red}[{chat.UnreadCount}] [sec] {chat.Title}"; - else - line = $"{Ansi.Bold}{Ansi.Green}[{chat.UnreadCount}] {chat.Title}"; - messageQueue.Add(line); - }); - } - } - } - - public class ListChatsCommand : Command - { - public ListChatsCommand() : base("lc", "", "lists all chats, optionally filtered", "[query]", -1) - { - } - - public override void Handler(List inputParams) - { - var chats = GetChats(); - - lock (@lock) - { - if (inputParams.Count > 0) - { - var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - chats = chats.FindAll(p => p.Title.ToLower().Contains(query.ToLower())); - } - - messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {chats.Count} chats."); - chats.ForEach(chat => - { - string line; - if (chat.UnreadCount == 0) - line = $"{Ansi.Bold}{Ansi.Blue}[0] {chat.Title}"; - else - line = $"{Ansi.Bold}{Ansi.Green}[{chat.UnreadCount}] {chat.Title}"; - messageQueue.Add(line); - }); - } - } - } - - public class SearchUserCommand : Command - { - public SearchUserCommand() : base("s", "", "searches for users globally", "", -1) - { - } - - public override void Handler(List inputParams) - { - if (inputParams.Count == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information."); - return; - } - - var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - - var chats = SearchChatsGlobal(query); - chats = chats.FindAll(p => p.Type is TdApi.ChatType.ChatTypePrivate); - - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {chats.Count} chats."); - chats.ForEach(chat => - { - string line; - var type = (TdApi.ChatType.ChatTypePrivate) chat.Type; - var user = GetUser(type.UserId); - line = $"{Ansi.Bold}{Ansi.Yellow}@{user.Username} {Ansi.Magenta}{chat.Title}"; - messageQueue.Add(line); - }); - } - } - } - - public class AddContactCommand : Command - { - public AddContactCommand() : base("ac", "", "adds user to contact list", "", 1) - { - } - - public override void Handler(List inputParams) - { - /* - var query = inputParams[0]; - - var chat = GetChatByUsernameGlobal(query); - - if (chat.Type is TdApi.ChatType.ChatTypePrivate type) - { - //TODO implement when TDLib 1.6 is released - } - else - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Username does not refer to a user."); - } - */ - } - } - - public class ListSecretChatsCommand : Command - { - public ListSecretChatsCommand() : base("ls", "", "displays all open secret chats", "", 0) - { - } - - public override void Handler(List inputParams) - { - var secretChats = GetSecretChats(); - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {secretChats.Count} secret chats:"); - secretChats.ForEach(chat => - { - messageQueue.Add($"{Ansi.Bold}{Ansi.Red}[sec] {chat.Title} -> {chat.Id}"); - }); - } - } - } - - public class HelpCommand : Command - { - public HelpCommand() : base("help", "", "lists all commands", "", 0) - { - } - - public override void Handler(List inputParams) - { - lock (@lock) - { - messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {CommandManager.Commands.Count} commands:"); - CommandManager.Commands.ForEach(command => - { - var commandText = $"/{command.trigger}"; - if (!string.IsNullOrWhiteSpace(command.syntax)) - commandText += $" {command.syntax}"; - commandText += $": {command.description}"; - if (!string.IsNullOrWhiteSpace(command.shortcut)) - commandText += $" ({command.shortcut})"; - - messageQueue.Add($"{Ansi.Yellow}{commandText}"); - }); - } - } - } - - public class QuitCommand : Command - { - public QuitCommand() : base("q", "^D", "quits the program", "", 0) - { - } - - public override void Handler(List inputParams) - { - quitting = true; - } - } - - public class EditCommand : Command - { - public EditCommand() : base("e", "", "edits last message. param empty adds last message to inputline", - "[message]", -1) - { - } - - public override void Handler(List inputParams) - { - try - { - if (inputParams.Count == 0) - { - currentInputLine = "/e " + ((TdApi.MessageContent.MessageText) lastMessage?.Content)?.Text?.Text; - Emojis.ForEach(em => currentInputLine = currentInputLine.Replace(em.Item2, em.Item1)); - return; - } - - var message = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - - if (currentChatId == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); - return; - } - - if (lastMessage == null) - { - //try to find last message - var history = GetHistory(currentChatId, 50); - var last = history.LastOrDefault(p => p.IsOutgoing); - if (last == null) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No message to edit found, cannot continue."); - return; - } - lastMessage = last; - } - - if (string.IsNullOrWhiteSpace(message)) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No message specified, cannot continue."); - return; - } - EditMessage(message, lastMessage); - } - catch - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error editing message."); - } - } - } - - public class ReplyCommand : Command - { - public ReplyCommand() : base("r", "", "replies to message", " ", -1) - { - } - - public override void Handler(List inputParams) - { - try - { - if (inputParams.Count < 2) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information."); - return; - } - - var history = GetHistory(currentChatId, 50); - var parsed = int.TryParse(inputParams[0], out var offset); - inputParams.RemoveAt(0); - history.Reverse(); - var message = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - - if (!parsed || string.IsNullOrWhiteSpace(message) || history.Count < offset) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}Invalid syntax, check /help for more information."); - return; - } - - var replyMessage = history[offset - 1]; - - if (currentChatId == 0) - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); - return; - } - - SendMessage(message, currentChatId, replyMessage.Id); - } - catch - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error sending message."); - } - } - } - - public class LogoutCommand : Command - { - public LogoutCommand() : base("logout", "", "log out this session (destroys all local data)", "", 0) - { - } - - public override void Handler(List inputParams) - { - LogOut(); - } - } +namespace telegram { + public abstract class Command { + public string trigger; + public string shortcut; + public string description; + public string syntax; + public int paramCount; + public abstract void Handler(List inputParams); + + protected Command(string trigger, string shortcut, string description, string syntax, int paramCount) { + this.trigger = trigger; + this.shortcut = shortcut; + this.description = description; + this.paramCount = paramCount; + this.syntax = syntax; + } + } + + public static class CommandManager { + public static readonly List Commands = new List { + new ClearCommand(), + new CloseCommand(), + new EditCommand(), + new ReplyCommand(), + new HistoryCommand(), + new OpenCommand(), + new UnreadsCommand(), + new CloseUnreadCommand(), + new ListChatsCommand(), + new NewChatCommand(), + new ListSecretChatsCommand(), + new OpenSecretCommand(), + new OpenSecretDirectCommand(), + new NewSecretChatCommand(), + new CloseSecretChatCommand(), + new SearchUserCommand(), + //new AddContactCommand(), + new QuitCommand(), + new HelpCommand(), + new LogoutCommand(), + }; + + public static void HandleCommand(string input) { + var split = input.Split(" ").ToList(); + var trigger = split.First(); + var command = Commands.Find(p => p.trigger == trigger || p.shortcut == trigger); + if (command == null) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command. Check /help for all available commands."); + return; + } + + split.RemoveAt(0); + if (command.paramCount == -1) { + command.Handler(split); + } + else if (split.Count == command.paramCount) { + command.Handler(split); + } + else { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command syntax. Check /help for more information."); + } + } + } + + public class OpenCommand : Command { + public OpenCommand() : base("o", "^O", "opens a chat. queries chat list", "", -1) { } + + public override void Handler(List inputParams) { + if (inputParams.Count == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command syntax. Check /help for more information."); + return; + } + + var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + var chatId = SearchChatId(query); + if (chatId == 0) + return; + + currentChatId = 0; + currentChatUserId = 0; + currentUserRead = false; + + var chat = GetChat(chatId); + if (chat.Type is TdApi.ChatType.ChatTypePrivate privChat) { + currentChatUserId = privChat.UserId; + } + + currentChatId = chat.Id; + + chat.Title = TruncateString(chat.Title, 20); + + prefix = $"[{chat.Title}"; + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}"); + messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}"); + + if (chat.UnreadCount >= 5) { + var capped = chat.UnreadCount > 50; + GetHistory(chatId, capped ? 50 : chat.UnreadCount).ForEach(AddMessageToQueue); + if (capped) + messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); + } + else if (chat.UnreadCount > 0) { + var unreads = GetHistory(chatId, chat.UnreadCount); + var rest = GetHistory(chatId, 5 - unreads.Count, unreads.First().Id); + rest.ForEach(AddMessageToQueue); + messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); + unreads.ForEach(AddMessageToQueue); + } + else { + GetHistory(chatId).ForEach(AddMessageToQueue); + } + } + + var history = GetHistory(currentChatId, 50); + if (history.Count != 0) + MarkRead(chat.Id, history.First().Id); + var last = history.LastOrDefault(p => p.IsOutgoing); + if (last == null) { + currentUserRead = true; + return; + } + + lastMessage = last; + currentUserRead = IsMessageRead(last.ChatId, last.Id); + } + } + + public class NewChatCommand : Command { + public NewChatCommand() : base("n", "", "starts a new chat.", "", 1) { } + + public override void Handler(List inputParams) { + var chat = GetChatByUsernameGlobal(inputParams[0]); + if (chat == null) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] User not found. Try /s to find valid usernames."); + return; + } + + currentChatId = 0; + currentChatUserId = 0; + currentUserRead = false; + + if (chat.Type is TdApi.ChatType.ChatTypePrivate privChat) { + currentChatUserId = privChat.UserId; + } + + currentChatId = chat.Id; + + chat.Title = TruncateString(chat.Title, 20); + + prefix = $"[{chat.Title}"; + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}"); + messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}"); + + if (chat.UnreadCount >= 5) { + var capped = chat.UnreadCount > 50; + GetHistory(chat.Id, capped ? 50 : chat.UnreadCount).ForEach(AddMessageToQueue); + if (capped) + messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); + } + else if (chat.UnreadCount > 0) { + var unreads = GetHistory(chat.Id, chat.UnreadCount); + var rest = GetHistory(chat.Id, 5 - unreads.Count, unreads.First().Id); + rest.ForEach(AddMessageToQueue); + messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); + unreads.ForEach(AddMessageToQueue); + } + else { + GetHistory(chat.Id).ForEach(AddMessageToQueue); + } + } + + var history = GetHistory(currentChatId, 50); + if (history.Count != 0) + MarkRead(chat.Id, history.First().Id); + var last = history.LastOrDefault(p => p.IsOutgoing); + if (last == null) { + currentUserRead = true; + return; + } + + lastMessage = last; + currentUserRead = IsMessageRead(last.ChatId, last.Id); + } + } + + public class CloseSecretChatCommand : Command { + public CloseSecretChatCommand() : base("cs", "", "closes a secret chat (permanently)", "", 0) { } + + public override void Handler(List _) { + if (currentChatId != 0 && GetChat(currentChatId).Type is TdApi.ChatType.ChatTypeSecret type) { + CloseSecretChat(type.SecretChatId); + DeleteChatHistory(currentChatId); + CommandManager.HandleCommand("c"); + } + else { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No secret chat selected, cannot continue."); + } + } + } + + public class NewSecretChatCommand : Command { + public NewSecretChatCommand() : base("ns", "", "creates a new secret chat.", "", 1) { } + + public override void Handler(List inputParams) { + var userId = GetUserIdByUsername(inputParams[0]); + + if (userId == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] User not found. Try /s to find valid usernames."); + return; + } + + if (GetSecretChats().Count(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId) > 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] You already have a secret chat with the specified user."); + return; + } + + var chat = CreateSecretChat(userId); + CommandManager.HandleCommand("osd " + chat.Id); + } + } + + public class OpenSecretDirectCommand : Command { + public OpenSecretDirectCommand() : base("osd", "", "opens a secret chat by chat id", "", 1) { } + + public override void Handler(List inputParams) { + var id = inputParams[0]; + if (!long.TryParse(id, out var chatId)) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid chat id."); + return; + } + + var chat = GetChat(chatId); + if (chat == null) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid chat id."); + return; + } + + TdApi.SecretChat secChat; + if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) { + currentChatUserId = secretChat.UserId; + currentChatId = chat.Id; + currentUserRead = false; + secChat = GetSecretChat(secretChat.SecretChatId); + } + else { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] The specified chat isn't a secret chat."); + return; + } + + chat.Title = TruncateString(chat.Title, 20); + + prefix = $"[{Ansi.Red}sec {Ansi.ResetAll}{chat.Title}]"; + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening secret chat: {chat.Title}"); + messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}"); + if (secChat.State is TdApi.SecretChatState.SecretChatStateClosed) { + messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat has ended. No messages can be sent."); + } + else if (secChat.State is TdApi.SecretChatState.SecretChatStatePending) { + messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat is pending. No messages can be sent."); + } + + if (chat.UnreadCount >= 5) { + var capped = chat.UnreadCount > 50; + GetHistory(chatId, capped ? 50 : chat.UnreadCount, isSecret: true).ForEach(AddMessageToQueue); + if (capped) + messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); + } + else if (chat.UnreadCount > 0) { + var unreads = GetHistory(chatId, chat.UnreadCount, isSecret: true); + var rest = GetHistory(chatId, 5 - unreads.Count, unreads.First().Id, isSecret: true); + rest.ForEach(AddMessageToQueue); + messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); + unreads.ForEach(AddMessageToQueue); + } + else { + GetHistory(chatId, isSecret: true).ForEach(AddMessageToQueue); + } + } + + var history = GetHistory(currentChatId, 50, isSecret: true); + if (history.Count != 0) + MarkRead(chat.Id, history.First().Id); + var last = history.LastOrDefault(p => p.IsOutgoing); + if (last == null) { + currentUserRead = true; + return; + } + + lastMessage = last; + currentUserRead = IsMessageRead(last.ChatId, last.Id); + } + } + + public class OpenSecretCommand : Command { + public OpenSecretCommand() : base("os", "", "opens a secret chat. queries chat list.", "", -1) { } + + public override void Handler(List inputParams) { + var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + var userId = SearchUserInChats(query); + if (userId == 0 || query.Length == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No matching chat found."); + return; + } + + var chat = GetSecretChats().Find(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId); + if (chat == null) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No matching secret chat found."); + return; + } + + TdApi.SecretChat secChat; + if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) { + currentChatUserId = secretChat.UserId; + currentChatId = chat.Id; + currentUserRead = false; + secChat = GetSecretChat(secretChat.SecretChatId); + } + else { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No matching secret chat found. (this error should be impossible to produce)"); + return; + } + + chat.Title = TruncateString(chat.Title, 20); + + prefix = $"[{Ansi.Red}sec {Ansi.ResetAll}{chat.Title}]"; + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening secret chat: {chat.Title}"); + messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}"); + if (secChat.State is TdApi.SecretChatState.SecretChatStateClosed) { + messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat has ended. No messages can be sent."); + } + else if (secChat.State is TdApi.SecretChatState.SecretChatStatePending) { + messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat is pending. No messages can be sent."); + } + + if (chat.UnreadCount >= 5) { + var capped = chat.UnreadCount > 50; + GetHistory(chat.Id, capped ? 50 : chat.UnreadCount, isSecret: true).ForEach(AddMessageToQueue); + if (capped) + messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages."); + } + else if (chat.UnreadCount > 0) { + var unreads = GetHistory(chat.Id, chat.UnreadCount, isSecret: true); + var rest = GetHistory(chat.Id, 5 - unreads.Count, unreads.First().Id, isSecret: true); + rest.ForEach(AddMessageToQueue); + messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---"); + unreads.ForEach(AddMessageToQueue); + } + else { + GetHistory(chat.Id, isSecret: true).ForEach(AddMessageToQueue); + } + } + + var history = GetHistory(currentChatId, 50, isSecret: true); + if (history.Count != 0) + MarkRead(chat.Id, history.First().Id); + var last = history.LastOrDefault(p => p.IsOutgoing); + if (last == null) { + currentUserRead = true; + return; + } + + lastMessage = last; + currentUserRead = IsMessageRead(last.ChatId, last.Id); + } + } + + public class CloseUnreadCommand : Command { + public CloseUnreadCommand() : base("cu", "", "closes a chat, marking it as unread", "", 0) { } + + public override void Handler(List inputParams) { + if (currentChatId == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); + return; + } + + MarkUnread(currentChatId); + CommandManager.HandleCommand("c"); + } + } + + public class CloseCommand : Command { + public CloseCommand() : base("c", "^E", "closes a chat", "", 0) { } + + public override void Handler(List inputParams) { + if (currentChatId == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); + return; + } + + currentChatId = 0; + currentChatUserId = 0; + currentUserRead = false; + lastMessage = null; + prefix = "[tgcli"; + + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Closing chat."); + var count = missedMessages.Count; + if (count == 0) + return; + + messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {count} missed message" + $"{(count == 1 ? "." : "s.")}"); + messageQueue.AddRange(missedMessages); + missedMessages.Clear(); + } + } + } + + public class HistoryCommand : Command { + public HistoryCommand() : base("h", "", "shows chat history. default limit is 5", "[1-50]", -1) { } + + public override void Handler(List inputParams) { + if (currentChatId == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); + return; + } + + List history; + + if (inputParams.Count == 1 && int.TryParse(inputParams[0], out var limit)) { + history = GetHistory(currentChatId, Math.Min(limit, 50)); + while (limit > 50) { + limit -= 50; + history.InsertRange(0, GetHistory(currentChatId, Math.Min(limit, 50), history.First().Id)); + } + } + + else { + history = GetHistory(currentChatId); + } + + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Last {history.Count} messages in " + $"{GetChat(currentChatId).Title}"); + } + + foreach (var msg in history) { + AddMessageToQueue(msg); + } + } + } + + public class ClearCommand : Command { + public ClearCommand() : base("cl", "^L", "clears console", "", 0) { } + + public override void Handler(List inputParams) { + lock (@lock) { + Console.Clear(); + } + } + } + + public class UnreadsCommand : Command { + public UnreadsCommand() : base("u", "^U", "displays unread chat", "[all]", -1) { } + + public override void Handler(List inputParams) { + var unreads = GetUnreadChats(inputParams.Count == 1 && inputParams[0].Equals("all")); + + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] You have {unreads.Count} unread chats."); + unreads.ForEach(chat => { + string line; + if (chat.UnreadCount == 0) + line = $"{Ansi.Bold}{Ansi.Yellow}[M] {chat.Title}"; + else if (chat.Type is TdApi.ChatType.ChatTypeSecret) + line = $"{Ansi.Bold}{Ansi.Red}[{chat.UnreadCount}] [sec] {chat.Title}"; + else + line = $"{Ansi.Bold}{Ansi.Green}[{chat.UnreadCount}] {chat.Title}"; + messageQueue.Add(line); + }); + } + } + } + + public class ListChatsCommand : Command { + public ListChatsCommand() : base("lc", "", "lists all chats, optionally filtered", "[query]", -1) { } + + public override void Handler(List inputParams) { + var chats = GetChats(); + + lock (@lock) { + if (inputParams.Count > 0) { + var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + chats = chats.FindAll(p => p.Title.ToLower().Contains(query.ToLower())); + } + + messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {chats.Count} chats."); + chats.ForEach(chat => { + string line; + if (chat.UnreadCount == 0) + line = $"{Ansi.Bold}{Ansi.Blue}[0] {chat.Title}"; + else + line = $"{Ansi.Bold}{Ansi.Green}[{chat.UnreadCount}] {chat.Title}"; + messageQueue.Add(line); + }); + } + } + } + + public class SearchUserCommand : Command { + public SearchUserCommand() : base("s", "", "searches for users globally", "", -1) { } + + public override void Handler(List inputParams) { + if (inputParams.Count == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information."); + return; + } + + var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + + var chats = SearchChatsGlobal(query); + chats = chats.FindAll(p => p.Type is TdApi.ChatType.ChatTypePrivate); + + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {chats.Count} chats."); + chats.ForEach(chat => { + string line; + var type = (TdApi.ChatType.ChatTypePrivate) chat.Type; + var user = GetUser(type.UserId); + line = $"{Ansi.Bold}{Ansi.Yellow}@{user.Username} {Ansi.Magenta}{chat.Title}"; + messageQueue.Add(line); + }); + } + } + } + + public class AddContactCommand : Command { + public AddContactCommand() : base("ac", "", "adds user to contact list", "", 1) { } + + public override void Handler(List inputParams) { + /* + var query = inputParams[0]; + + var chat = GetChatByUsernameGlobal(query); + + if (chat.Type is TdApi.ChatType.ChatTypePrivate type) + { + //TODO implement when TDLib 1.6 is released + } + else + { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Username does not refer to a user."); + } + */ + } + } + + public class ListSecretChatsCommand : Command { + public ListSecretChatsCommand() : base("ls", "", "displays all open secret chats", "", 0) { } + + public override void Handler(List inputParams) { + var secretChats = GetSecretChats(); + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {secretChats.Count} secret chats:"); + secretChats.ForEach(chat => { messageQueue.Add($"{Ansi.Bold}{Ansi.Red}[sec] {chat.Title} -> {chat.Id}"); }); + } + } + } + + public class HelpCommand : Command { + public HelpCommand() : base("help", "", "lists all commands", "", 0) { } + + public override void Handler(List inputParams) { + lock (@lock) { + messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {CommandManager.Commands.Count} commands:"); + CommandManager.Commands.ForEach(command => { + var commandText = $"/{command.trigger}"; + if (!string.IsNullOrWhiteSpace(command.syntax)) + commandText += $" {command.syntax}"; + commandText += $": {command.description}"; + if (!string.IsNullOrWhiteSpace(command.shortcut)) + commandText += $" ({command.shortcut})"; + + messageQueue.Add($"{Ansi.Yellow}{commandText}"); + }); + } + } + } + + public class QuitCommand : Command { + public QuitCommand() : base("q", "^D", "quits the program", "", 0) { } + + public override void Handler(List inputParams) { + quitting = true; + } + } + + public class EditCommand : Command { + public EditCommand() : base("e", "", "edits last message. param empty adds last message to inputline", "[message]", -1) { } + + public override void Handler(List inputParams) { + try { + if (inputParams.Count == 0) { + currentInputLine = "/e " + ((TdApi.MessageContent.MessageText) lastMessage?.Content)?.Text?.Text; + Emojis.ForEach(em => currentInputLine = currentInputLine.Replace(em.Item2, em.Item1)); + return; + } + + var message = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + + if (currentChatId == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); + return; + } + + if (lastMessage == null) { + //try to find last message + var history = GetHistory(currentChatId, 50); + var last = history.LastOrDefault(p => p.IsOutgoing); + if (last == null) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No message to edit found, cannot continue."); + return; + } + + lastMessage = last; + } + + if (string.IsNullOrWhiteSpace(message)) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No message specified, cannot continue."); + return; + } + + EditMessage(message, lastMessage); + } + catch { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error editing message."); + } + } + } + + public class ReplyCommand : Command { + public ReplyCommand() : base("r", "", "replies to message", " ", -1) { } + + public override void Handler(List inputParams) { + try { + if (inputParams.Count < 2) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information."); + return; + } + + var history = GetHistory(currentChatId, 50); + var parsed = int.TryParse(inputParams[0], out var offset); + inputParams.RemoveAt(0); + history.Reverse(); + var message = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + + if (!parsed || string.IsNullOrWhiteSpace(message) || history.Count < offset) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}Invalid syntax, check /help for more information."); + return; + } + + var replyMessage = history[offset - 1]; + + if (currentChatId == 0) { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue."); + return; + } + + SendMessage(message, currentChatId, replyMessage.Id); + } + catch { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error sending message."); + } + } + } + + public class LogoutCommand : Command { + public LogoutCommand() : base("logout", "", "log out this session (destroys all local data)", "", 0) { } + + public override void Handler(List inputParams) { + LogOut(); + } + } } \ No newline at end of file diff --git a/telegram/Util.cs b/telegram/Util.cs index c2afe57..c8d8220 100644 --- a/telegram/Util.cs +++ b/telegram/Util.cs @@ -6,604 +6,448 @@ using NeoSmart.Unicode; using static TdLib.TdApi; using static telegram.tgcli; -namespace telegram -{ - public class Util - { - public static class Ansi - { - public const string ResetAll = "\x1B[0m"; - public const string Red = "\x1b[31m"; - public const string Green = "\x1b[32m"; - public const string Yellow = "\x1b[33m"; - public const string Blue = "\x1b[34m"; - public const string Magenta = "\x1b[35m"; - public const string Cyan = "\x1b[36m"; - public const string Bold = "\x1b[1m"; - public const string BoldOff = "\x1b[22m"; - } +namespace telegram { + public class Util { + public static class Ansi { + public const string ResetAll = "\x1B[0m"; + public const string Red = "\x1b[31m"; + public const string Green = "\x1b[32m"; + public const string Yellow = "\x1b[33m"; + public const string Blue = "\x1b[34m"; + public const string Magenta = "\x1b[35m"; + public const string Cyan = "\x1b[36m"; + public const string Bold = "\x1b[1m"; + public const string BoldOff = "\x1b[22m"; + } - public static User GetUser(int uid) - { - try - { - var uinfo = client.ExecuteAsync(new GetUser - { - UserId = uid - }).Result; - return uinfo; - } - catch - { - var user = new User(); - user.FirstName = "null"; - user.LastName = "null"; - return user; - } - } + public static User GetUser(int uid) { + try { + var uinfo = client.ExecuteAsync(new GetUser {UserId = uid}).Result; + return uinfo; + } + catch { + var user = new User(); + user.FirstName = "null"; + user.LastName = "null"; + return user; + } + } - public static Chat GetChat(long chatId) - { - try - { - return client.ExecuteAsync(new GetChat - { - ChatId = chatId - }).Result; - } - catch - { - return null; - } - } + public static Chat GetChat(long chatId) { + try { + return client.ExecuteAsync(new GetChat {ChatId = chatId}).Result; + } + catch { + return null; + } + } - public static User GetMe() - { - return client.ExecuteAsync(new GetMe()).Result; - } + public static User GetMe() { + return client.ExecuteAsync(new GetMe()).Result; + } - public static Message GetMessage(long chatId, long messageId) - { - return client.ExecuteAsync(new GetMessage - { - ChatId = chatId, - MessageId = messageId - }).Result; - } + public static Message GetMessage(long chatId, long messageId) { + return client.ExecuteAsync(new GetMessage {ChatId = chatId, MessageId = messageId}).Result; + } - public static int GetTotalMessages(long chatId) - { - try - { - var response = client.ExecuteAsync(new SearchChatMessages - { - ChatId = chatId, - Query = "+", - Limit = 1 - }); - return response.Result.TotalCount; - } - catch - { - return 9999; - } - } + public static int GetTotalMessages(long chatId) { + try { + var response = client.ExecuteAsync(new SearchChatMessages {ChatId = chatId, Query = "+", Limit = 1}); + return response.Result.TotalCount; + } + catch { + return 9999; + } + } - public static List GetHistory(long chatId, int limit = 5, long fromMessageId = 0, int offset = 0, - bool isSecret = false, bool skipTotal = false) - { - var history = new List(); - var total = GetTotalMessages(chatId); - var chat = GetChat(chatId); - if (chat.Type is ChatType.ChatTypeSupergroup || isSecret) - skipTotal = true; - if (limit > total && !skipTotal) limit = total; + public static List GetHistory(long chatId, int limit = 5, long fromMessageId = 0, int offset = 0, bool isSecret = false, + bool skipTotal = false) { + var history = new List(); + var total = GetTotalMessages(chatId); + var chat = GetChat(chatId); + if (chat.Type is ChatType.ChatTypeSupergroup || isSecret) + skipTotal = true; + if (limit > total && !skipTotal) + limit = total; - for (var i = 5; i > 0; i--) - { - if (limit <= 0) - { - if (total == 0) return history; - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] " + - $"Limit cannot be less than one. Usage: /history "); - return history; - } + for (var i = 5; i > 0; i--) { + if (limit <= 0) { + if (total == 0) + return history; - var response = client.ExecuteAsync(new GetChatHistory - { - ChatId = chatId, - FromMessageId = fromMessageId, - Limit = limit, - Offset = offset, - OnlyLocal = false - }) - .Result; + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] " + $"Limit cannot be less than one. Usage: /history "); + return history; + } - if (response.Messages_.Length < limit && i > 1 && !isSecret) - { - Thread.Sleep(100); - continue; - } + var response = client.ExecuteAsync(new GetChatHistory { + ChatId = chatId, + FromMessageId = fromMessageId, + Limit = limit, + Offset = offset, + OnlyLocal = false + }) + .Result; - history.AddRange(response.Messages_); - history.Reverse(); - return history; - } + if (response.Messages_.Length < limit && i > 1 && !isSecret) { + Thread.Sleep(100); + continue; + } - return history; - } + history.AddRange(response.Messages_); + history.Reverse(); + return history; + } - public static List GetUnreadChats(bool all = false) - { - var response = client.ExecuteAsync(new GetChats - { - OffsetOrder = long.MaxValue, - Limit = int.MaxValue - }).Result; - return all - ? response.ChatIds.Select(GetChat).Where(c => c.UnreadCount > 0 || c.IsMarkedAsUnread).ToList() - : response.ChatIds.Select(GetChat).Where(c => (c.UnreadCount > 0 || c.IsMarkedAsUnread) - && c.NotificationSettings.MuteFor == 0) - .ToList(); - } + return history; + } - public static List GetChats() - { - var response = client.ExecuteAsync(new GetChats - { - OffsetOrder = long.MaxValue, - Limit = int.MaxValue - }).Result; - return response.ChatIds.Select(GetChat).ToList(); - } + public static List GetUnreadChats(bool all = false) { + var response = client.ExecuteAsync(new GetChats {OffsetOrder = long.MaxValue, Limit = int.MaxValue}).Result; + return all + ? response.ChatIds.Select(GetChat).Where(c => c.UnreadCount > 0 || c.IsMarkedAsUnread).ToList() + : response.ChatIds.Select(GetChat).Where(c => (c.UnreadCount > 0 || c.IsMarkedAsUnread) && c.NotificationSettings.MuteFor == 0).ToList(); + } - public static List SearchChatsGlobal(string query) - { - if (query.TrimStart('@').Length < 5) - { - return new List(); - } + public static List GetChats() { + var response = client.ExecuteAsync(new GetChats {OffsetOrder = long.MaxValue, Limit = int.MaxValue}).Result; + return response.ChatIds.Select(GetChat).ToList(); + } - var response = client.ExecuteAsync(new SearchPublicChats - { - Query = query - }).Result; + public static List SearchChatsGlobal(string query) { + if (query.TrimStart('@').Length < 5) { + return new List(); + } - var chats = response.ChatIds.Select(GetChat).ToList(); + var response = client.ExecuteAsync(new SearchPublicChats {Query = query}).Result; - chats.AddRange(client.ExecuteAsync(new SearchChats - { - Query = query, - Limit = int.MaxValue - }).Result.ChatIds.Select(GetChat)); + var chats = response.ChatIds.Select(GetChat).ToList(); - return chats; - } + chats.AddRange(client.ExecuteAsync(new SearchChats {Query = query, Limit = int.MaxValue}).Result.ChatIds.Select(GetChat)); - public static Chat GetChatByUsernameGlobal(string username) - { - try - { - var response = client.ExecuteAsync(new SearchPublicChat - { - Username = username - }).Result; - return response; - } - catch - { - return null; - } - } + return chats; + } - public static int GetUserIdByUsername(string username) - { - try - { - var response = client.ExecuteAsync(new SearchPublicChat - { - Username = username - }).Result; + public static Chat GetChatByUsernameGlobal(string username) { + try { + var response = client.ExecuteAsync(new SearchPublicChat {Username = username}).Result; + return response; + } + catch { + return null; + } + } - if (response.Type is ChatType.ChatTypePrivate priv) - return priv.UserId; - return 0; - } - catch - { - return 0; - } - } + public static int GetUserIdByUsername(string username) { + try { + var response = client.ExecuteAsync(new SearchPublicChat {Username = username}).Result; - public static void AddUserToContacts(int userId, string name) - { - //TODO implement when TDLib 1.6 is released - } + if (response.Type is ChatType.ChatTypePrivate priv) + return priv.UserId; - public static List GetSecretChats() - { - var response = client.ExecuteAsync(new GetChats - { - OffsetOrder = long.MaxValue, - Limit = int.MaxValue - }).Result; - return response.ChatIds.Select(GetChat).Where(c => c.Type is ChatType.ChatTypeSecret).ToList(); - } + return 0; + } + catch { + return 0; + } + } - public static void CloseSecretChat(int secretChatId) - { - client.ExecuteAsync(new CloseSecretChat() - { - SecretChatId = secretChatId - }).Wait(); - } + public static void AddUserToContacts(int userId, string name) { + //TODO implement when TDLib 1.6 is released + } - public static Chat CreateSecretChat(int userId) - { - return client.ExecuteAsync(new CreateNewSecretChat - { - UserId = userId - }).Result; - } + public static List GetSecretChats() { + var response = client.ExecuteAsync(new GetChats {OffsetOrder = long.MaxValue, Limit = int.MaxValue}).Result; + return response.ChatIds.Select(GetChat).Where(c => c.Type is ChatType.ChatTypeSecret).ToList(); + } - public static void DeleteChatHistory(long chatId) - { - client.ExecuteAsync(new DeleteChatHistory - { - ChatId = chatId, - RemoveFromChatList = true, - Revoke = true - }).Wait(); - } + public static void CloseSecretChat(int secretChatId) { + client.ExecuteAsync(new CloseSecretChat() {SecretChatId = secretChatId}).Wait(); + } - public static SecretChat GetSecretChat(int secretChatId) - { - var response = client.ExecuteAsync(new GetSecretChat - { - SecretChatId = secretChatId - }).Result; - return response; - } + public static Chat CreateSecretChat(int userId) { + return client.ExecuteAsync(new CreateNewSecretChat {UserId = userId}).Result; + } - public static void ClearCurrentConsoleLine() - { - Console.Write("\u001b[2K\r"); + public static void DeleteChatHistory(long chatId) { + client.ExecuteAsync(new DeleteChatHistory {ChatId = chatId, RemoveFromChatList = true, Revoke = true}).Wait(); + } - //Console.SetCursorPosition(0, Console.WindowHeight); - //Console.Write(new string(' ', Console.WindowWidth)); - //Console.SetCursorPosition(0, Console.WindowHeight); - } + public static SecretChat GetSecretChat(int secretChatId) { + var response = client.ExecuteAsync(new GetSecretChat {SecretChatId = secretChatId}).Result; + return response; + } - public static string ReadConsolePassword() - { - string pass = ""; - do - { - ConsoleKeyInfo key = Console.ReadKey(true); - if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) - { - pass += key.KeyChar; - Console.Write("*"); - } - else - { - if (key.Key == ConsoleKey.Backspace && pass.Length > 0) - { - pass = pass.Substring(0, (pass.Length - 1)); - Console.Write("\b \b"); - } - else if (key.Key == ConsoleKey.Enter) - { - break; - } - } - } while (true); + public static void ClearCurrentConsoleLine() { + Console.Write("\u001b[2K\r"); - Console.WriteLine(); - return pass; - } + //Console.SetCursorPosition(0, Console.WindowHeight); + //Console.Write(new string(' ', Console.WindowWidth)); + //Console.SetCursorPosition(0, Console.WindowHeight); + } - public static void SendMessage(string message, long chatId, long replyTo = 0) - { - if (string.IsNullOrWhiteSpace(message)) - return; - Emojis.ForEach(em => message = message.Replace(em.Item1, em.Item2)); - client.ExecuteAsync(new SendMessage - { - ChatId = chatId, - InputMessageContent = new InputMessageContent.InputMessageText - { - Text = new FormattedText() - { - Text = message - } - }, - ReplyToMessageId = replyTo, - }); - currentUserRead = false; - } + public static string ReadConsolePassword() { + string pass = ""; + do { + ConsoleKeyInfo key = Console.ReadKey(true); + if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) { + pass += key.KeyChar; + Console.Write("*"); + } + else { + if (key.Key == ConsoleKey.Backspace && pass.Length > 0) { + pass = pass.Substring(0, (pass.Length - 1)); + Console.Write("\b \b"); + } + else if (key.Key == ConsoleKey.Enter) { + break; + } + } + } while (true); - public static Message EditMessage(string newText, Message message) - { - Emojis.ForEach(em => newText = newText.Replace(em.Item1, em.Item2)); + Console.WriteLine(); + return pass; + } - var msg = client.ExecuteAsync(new EditMessageText - { - ChatId = message.ChatId, - MessageId = message.Id, - InputMessageContent = new InputMessageContent.InputMessageText - { - Text = new FormattedText() - { - Text = newText - } - } - }).Result; + public static void SendMessage(string message, long chatId, long replyTo = 0) { + if (string.IsNullOrWhiteSpace(message)) + return; - return msg; - } + Emojis.ForEach(em => message = message.Replace(em.Item1, em.Item2)); + client.ExecuteAsync(new SendMessage { + ChatId = chatId, + InputMessageContent = new InputMessageContent.InputMessageText {Text = new FormattedText() {Text = message}}, + ReplyToMessageId = replyTo, + }); + currentUserRead = false; + } - public static void MarkRead(long chatId, long messageId) - { - client.ExecuteAsync(new ViewMessages - { - ChatId = chatId, - MessageIds = new[] - { - messageId - }, - ForceRead = true - }); - } + public static Message EditMessage(string newText, Message message) { + Emojis.ForEach(em => newText = newText.Replace(em.Item1, em.Item2)); - public static void MarkUnread(long chatId) - { - client.ExecuteAsync(new ToggleChatIsMarkedAsUnread - { - ChatId = chatId, - IsMarkedAsUnread = true, - }); - } + var msg = client.ExecuteAsync(new EditMessageText { + ChatId = message.ChatId, + MessageId = message.Id, + InputMessageContent = new InputMessageContent.InputMessageText {Text = new FormattedText() {Text = newText}} + }) + .Result; - public static long SearchChatId(string query) - { - try - { - var results = client.ExecuteAsync(new SearchChats - { - Query = query, - Limit = 5 - }).Result; + return msg; + } - if (query.StartsWith("@")) - return results.ChatIds.First(p => - GetChat(p).Type is ChatType.ChatTypePrivate type && - GetUser(type.UserId).Username == query.Substring(1)); - return results.ChatIds.First(p => !(GetChat(p).Type is ChatType.ChatTypeSecret)); - } - catch - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No results found."); - return 0; - } - } + public static void MarkRead(long chatId, long messageId) { + client.ExecuteAsync(new ViewMessages {ChatId = chatId, MessageIds = new[] {messageId}, ForceRead = true}); + } - public static int SearchUserInChats(string query) - { - var results = client.ExecuteAsync(new SearchChatsOnServer - { - Query = query, - Limit = 5 - }).Result; - if (results.ChatIds.Length == 0) - return 0; - return results.ChatIds - .Select(GetChat) - .Where(p => p.Type is ChatType.ChatTypePrivate) - .Select(p => ((ChatType.ChatTypePrivate) p.Type).UserId) - .First(); - } + public static void MarkUnread(long chatId) { + client.ExecuteAsync(new ToggleChatIsMarkedAsUnread {ChatId = chatId, IsMarkedAsUnread = true,}); + } - public static int SearchContacts(string query) - { - //TODO implement when TDLib 1.6 is released - try - { - var results = client.ExecuteAsync(new SearchContacts - { - Query = query, - Limit = 5 - }).Result; + public static long SearchChatId(string query) { + try { + var results = client.ExecuteAsync(new SearchChats {Query = query, Limit = 5}).Result; - return query.StartsWith("@") - ? results.UserIds.First(p => GetUser(p).Username == query.Substring(1)) - : results.UserIds.First(); - } - catch - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] No results found."); - return 0; - } - } + if (query.StartsWith("@")) + return results.ChatIds.First(p => GetChat(p).Type is ChatType.ChatTypePrivate type && GetUser(type.UserId).Username == query.Substring(1)); - public static void LogOut() - { - lock (@lock) - messageQueue.Add($"{Ansi.Yellow}[tgcli] Logging out..."); - client.ExecuteAsync(new LogOut()).Wait(); - } + return results.ChatIds.First(p => !(GetChat(p).Type is ChatType.ChatTypeSecret)); + } + catch { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No results found."); + return 0; + } + } - public static string GetFormattedUsername(User sender) - { - var username = sender.Username; - if (string.IsNullOrWhiteSpace(username)) - username = sender.FirstName + " " + - sender.LastName; - else - username = "@" + username; + public static int SearchUserInChats(string query) { + var results = client.ExecuteAsync(new SearchChatsOnServer {Query = query, Limit = 5}).Result; + if (results.ChatIds.Length == 0) + return 0; - return username; - } + return results.ChatIds.Select(GetChat) + .Where(p => p.Type is ChatType.ChatTypePrivate) + .Select(p => ((ChatType.ChatTypePrivate) p.Type).UserId) + .First(); + } - public static string FormatTime(long unix) - { - var time = DateTimeOffset.FromUnixTimeSeconds(unix).DateTime.ToLocalTime(); - var currentTime = DateTime.Now.ToLocalTime(); - return time.ToString(time.Date.Ticks == currentTime.Date.Ticks ? "HH:mm" : "yyyy-MM-dd HH:mm"); - } + public static int SearchContacts(string query) { + //TODO implement when TDLib 1.6 is released + try { + var results = client.ExecuteAsync(new SearchContacts {Query = query, Limit = 5}).Result; - public static bool IsMessageRead(long chatId, long messageId) - { - var chat = GetChat(chatId); - return chat.LastReadOutboxMessageId >= messageId; - } + return query.StartsWith("@") ? results.UserIds.First(p => GetUser(p).Username == query.Substring(1)) : results.UserIds.First(); + } + catch { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No results found."); + return 0; + } + } - public static int GetActualStringWidth(string input) - { - input = input.Replace(Ansi.Blue, ""); - input = input.Replace(Ansi.Bold, ""); - input = input.Replace(Ansi.Cyan, ""); - input = input.Replace(Ansi.Green, ""); - input = input.Replace(Ansi.Magenta, ""); - input = input.Replace(Ansi.Red, ""); - input = input.Replace(Ansi.Yellow, ""); - input = input.Replace(Ansi.BoldOff, ""); - input = input.Replace(Ansi.ResetAll, ""); - return input.Length; - } + public static void LogOut() { + lock (@lock) + messageQueue.Add($"{Ansi.Yellow}[tgcli] Logging out..."); + client.ExecuteAsync(new LogOut()).Wait(); + } - public static string GetFormattedStatus(bool isRead) - { - var output = " "; - output += (isRead ? Ansi.Green : Ansi.Red) + "r"; - return output + $"{Ansi.ResetAll}]"; - } + public static string GetFormattedUsername(User sender) { + var username = sender.Username; + if (string.IsNullOrWhiteSpace(username)) + username = sender.FirstName + " " + sender.LastName; + else + username = "@" + username; - public static string TruncateString(string input, int maxLen) - { - if (maxLen < 2) - maxLen = 2; - return input.Length <= maxLen ? input : input.Substring(0, maxLen - 1) + "~"; - } + return username; + } - public static string TruncateMessageStart(string input, int maxLen) - { - if (maxLen < 2) - maxLen = 2; - if (input.Contains("⏎")) - input = "⏎" + input.Split("⏎").Last(); - return input.Length < maxLen ? input : "<" + input.Substring(input.Length - maxLen + 2); - } + public static string FormatTime(long unix) { + var time = DateTimeOffset.FromUnixTimeSeconds(unix).DateTime.ToLocalTime(); + var currentTime = DateTime.Now.ToLocalTime(); + return time.ToString(time.Date.Ticks == currentTime.Date.Ticks ? "HH:mm" : "yyyy-MM-dd HH:mm"); + } - public static readonly List> Emojis = new List> - { - new Tuple("⏎", "\n"), - new Tuple(":xd:", Emoji.FaceWithTearsOfJoy.Sequence.AsString), - new Tuple(":check:", Emoji.WhiteHeavyCheckMark.Sequence.AsString), - new Tuple(":thinking:", Emoji.ThinkingFace.Sequence.AsString), - new Tuple(":eyes:", Emoji.Eyes.Sequence.AsString), - new Tuple(":heart:", Emoji.RedHeart.Sequence.AsString), - new Tuple(":shrug:", Emoji.PersonShrugging.Sequence.AsString), - new Tuple(":shrugf:", Emoji.WomanShrugging.Sequence.AsString), - new Tuple(":shrugm:", Emoji.ManShrugging.Sequence.AsString) - }; + public static bool IsMessageRead(long chatId, long messageId) { + var chat = GetChat(chatId); + return chat.LastReadOutboxMessageId >= messageId; + } - public static readonly List SpecialKeys = new List - { - ConsoleKey.Backspace, - ConsoleKey.Tab, - ConsoleKey.Clear, - ConsoleKey.Enter, - ConsoleKey.Pause, - ConsoleKey.Escape, - ConsoleKey.PageUp, - ConsoleKey.PageDown, - ConsoleKey.End, - ConsoleKey.Home, - ConsoleKey.LeftArrow, - ConsoleKey.UpArrow, - ConsoleKey.RightArrow, - ConsoleKey.DownArrow, - ConsoleKey.Select, - ConsoleKey.Print, - ConsoleKey.Execute, - ConsoleKey.PrintScreen, - ConsoleKey.Insert, - ConsoleKey.Delete, - ConsoleKey.Help, - ConsoleKey.LeftWindows, - ConsoleKey.RightWindows, - ConsoleKey.Applications, - ConsoleKey.Sleep, - ConsoleKey.F1, - ConsoleKey.F2, - ConsoleKey.F3, - ConsoleKey.F4, - ConsoleKey.F5, - ConsoleKey.F6, - ConsoleKey.F7, - ConsoleKey.F8, - ConsoleKey.F9, - ConsoleKey.F10, - ConsoleKey.F11, - ConsoleKey.F12, - ConsoleKey.F13, - ConsoleKey.F14, - ConsoleKey.F15, - ConsoleKey.F16, - ConsoleKey.F17, - ConsoleKey.F18, - ConsoleKey.F19, - ConsoleKey.F20, - ConsoleKey.F21, - ConsoleKey.F22, - ConsoleKey.F23, - ConsoleKey.F24, - ConsoleKey.BrowserBack, - ConsoleKey.BrowserForward, - ConsoleKey.BrowserRefresh, - ConsoleKey.BrowserStop, - ConsoleKey.BrowserSearch, - ConsoleKey.BrowserFavorites, - ConsoleKey.BrowserHome, - ConsoleKey.VolumeMute, - ConsoleKey.VolumeDown, - ConsoleKey.VolumeUp, - ConsoleKey.MediaNext, - ConsoleKey.MediaPrevious, - ConsoleKey.MediaStop, - ConsoleKey.MediaPlay, - ConsoleKey.LaunchMail, - ConsoleKey.LaunchMediaSelect, - ConsoleKey.LaunchApp1, - ConsoleKey.LaunchApp2, - ConsoleKey.Oem1, - ConsoleKey.OemPlus, - ConsoleKey.OemComma, - ConsoleKey.OemMinus, - ConsoleKey.OemPeriod, - ConsoleKey.Oem2, - ConsoleKey.Oem3, - ConsoleKey.Oem4, - ConsoleKey.Oem5, - ConsoleKey.Oem6, - ConsoleKey.Oem7, - ConsoleKey.Oem8, - ConsoleKey.Oem102, - ConsoleKey.Process, - ConsoleKey.Packet, - ConsoleKey.Attention, - ConsoleKey.CrSel, - ConsoleKey.ExSel, - ConsoleKey.EraseEndOfFile, - ConsoleKey.Play, - ConsoleKey.Zoom, - ConsoleKey.NoName, - ConsoleKey.Pa1, - ConsoleKey.OemClear - }; - } + public static int GetActualStringWidth(string input) { + input = input.Replace(Ansi.Blue, ""); + input = input.Replace(Ansi.Bold, ""); + input = input.Replace(Ansi.Cyan, ""); + input = input.Replace(Ansi.Green, ""); + input = input.Replace(Ansi.Magenta, ""); + input = input.Replace(Ansi.Red, ""); + input = input.Replace(Ansi.Yellow, ""); + input = input.Replace(Ansi.BoldOff, ""); + input = input.Replace(Ansi.ResetAll, ""); + return input.Length; + } + + public static string GetFormattedStatus(bool isRead) { + var output = " "; + output += (isRead ? Ansi.Green : Ansi.Red) + "r"; + return output + $"{Ansi.ResetAll}]"; + } + + public static string TruncateString(string input, int maxLen) { + if (maxLen < 2) + maxLen = 2; + return input.Length <= maxLen ? input : input.Substring(0, maxLen - 1) + "~"; + } + + public static string TruncateMessageStart(string input, int maxLen) { + if (maxLen < 2) + maxLen = 2; + if (input.Contains("⏎ ")) + input = "⏎ " + input.Split("⏎ ").Last(); + return input.Length < maxLen ? input : "<" + input.Substring(input.Length - maxLen + 2); + } + + public static readonly List> Emojis = new List> { + new Tuple("⏎ ", "\n"), + new Tuple(":xd:", Emoji.FaceWithTearsOfJoy.Sequence.AsString), + new Tuple(":check:", Emoji.WhiteHeavyCheckMark.Sequence.AsString), + new Tuple(":thinking:", Emoji.ThinkingFace.Sequence.AsString), + new Tuple(":eyes:", Emoji.Eyes.Sequence.AsString), + new Tuple(":heart:", Emoji.RedHeart.Sequence.AsString), + new Tuple(":shrug:", Emoji.PersonShrugging.Sequence.AsString), + new Tuple(":shrugf:", Emoji.WomanShrugging.Sequence.AsString), + new Tuple(":shrugm:", Emoji.ManShrugging.Sequence.AsString) + }; + + public static readonly List SpecialKeys = new List { + ConsoleKey.Backspace, + ConsoleKey.Tab, + ConsoleKey.Clear, + ConsoleKey.Enter, + ConsoleKey.Pause, + ConsoleKey.Escape, + ConsoleKey.PageUp, + ConsoleKey.PageDown, + ConsoleKey.End, + ConsoleKey.Home, + ConsoleKey.LeftArrow, + ConsoleKey.UpArrow, + ConsoleKey.RightArrow, + ConsoleKey.DownArrow, + ConsoleKey.Select, + ConsoleKey.Print, + ConsoleKey.Execute, + ConsoleKey.PrintScreen, + ConsoleKey.Insert, + ConsoleKey.Delete, + ConsoleKey.Help, + ConsoleKey.LeftWindows, + ConsoleKey.RightWindows, + ConsoleKey.Applications, + ConsoleKey.Sleep, + ConsoleKey.F1, + ConsoleKey.F2, + ConsoleKey.F3, + ConsoleKey.F4, + ConsoleKey.F5, + ConsoleKey.F6, + ConsoleKey.F7, + ConsoleKey.F8, + ConsoleKey.F9, + ConsoleKey.F10, + ConsoleKey.F11, + ConsoleKey.F12, + ConsoleKey.F13, + ConsoleKey.F14, + ConsoleKey.F15, + ConsoleKey.F16, + ConsoleKey.F17, + ConsoleKey.F18, + ConsoleKey.F19, + ConsoleKey.F20, + ConsoleKey.F21, + ConsoleKey.F22, + ConsoleKey.F23, + ConsoleKey.F24, + ConsoleKey.BrowserBack, + ConsoleKey.BrowserForward, + ConsoleKey.BrowserRefresh, + ConsoleKey.BrowserStop, + ConsoleKey.BrowserSearch, + ConsoleKey.BrowserFavorites, + ConsoleKey.BrowserHome, + ConsoleKey.VolumeMute, + ConsoleKey.VolumeDown, + ConsoleKey.VolumeUp, + ConsoleKey.MediaNext, + ConsoleKey.MediaPrevious, + ConsoleKey.MediaStop, + ConsoleKey.MediaPlay, + ConsoleKey.LaunchMail, + ConsoleKey.LaunchMediaSelect, + ConsoleKey.LaunchApp1, + ConsoleKey.LaunchApp2, + ConsoleKey.Oem1, + ConsoleKey.OemPlus, + ConsoleKey.OemComma, + ConsoleKey.OemMinus, + ConsoleKey.OemPeriod, + ConsoleKey.Oem2, + ConsoleKey.Oem3, + ConsoleKey.Oem4, + ConsoleKey.Oem5, + ConsoleKey.Oem6, + ConsoleKey.Oem7, + ConsoleKey.Oem8, + ConsoleKey.Oem102, + ConsoleKey.Process, + ConsoleKey.Packet, + ConsoleKey.Attention, + ConsoleKey.CrSel, + ConsoleKey.ExSel, + ConsoleKey.EraseEndOfFile, + ConsoleKey.Play, + ConsoleKey.Zoom, + ConsoleKey.NoName, + ConsoleKey.Pa1, + ConsoleKey.OemClear + }; + } } \ No newline at end of file diff --git a/telegram/tgcli.cs b/telegram/tgcli.cs index 14d8ed9..74d02d9 100644 --- a/telegram/tgcli.cs +++ b/telegram/tgcli.cs @@ -10,553 +10,498 @@ using static telegram.CommandManager; // ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault -namespace telegram -{ - /* - * TODO: - * fuzzy matching for replies? - * unreads are unreliable in secret chats! - * mute,unmute chats - * photo & document download & show externally - * publish AUR package - * cursor input nav (up/down history, (alt +) left/right) - * ref: http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#cursor-navigation - * ref: https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences - * refactor everything - * re-evaluate ClearCurrentConsoleLine function - * When TDLib 1.6 is released: implement contacts - */ +namespace telegram { + /* + * TODO: + * fuzzy matching for replies? + * unreads are unreliable in secret chats! + * mute,unmute chats + * photo & document download & show externally + * publish AUR package + * cursor input nav (up/down history, (alt +) left/right) + * ref: http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#cursor-navigation + * ref: https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences + * refactor everything + * re-evaluate ClearCurrentConsoleLine function + * When TDLib 1.6 is released: implement contacts + */ - // ReSharper disable once InconsistentNaming - public static class tgcli - { - public static volatile Td.TdClient client = new Td.TdClient(); - public static string dbdir = ""; - public static volatile bool authorized; - public static volatile string connectionState = "Connecting"; - public static long currentChatId = 0; - public static volatile int currentChatUserId = 0; - public static volatile bool currentUserRead; - public static volatile Td.TdApi.Message lastMessage; - public static volatile bool quitting; - public static volatile string currentInputLine = ""; - public static volatile List messageQueue = new List(); - public static volatile List missedMessages = new List(); - public static volatile string prefix = "[tgcli"; - public static volatile bool silent; + // ReSharper disable once InconsistentNaming + public static class tgcli { + public static volatile Td.TdClient client = new Td.TdClient(); + public static string dbdir = ""; + public static volatile bool authorized; + public static volatile string connectionState = "Connecting"; + public static long currentChatId = 0; + public static volatile int currentChatUserId = 0; + public static volatile bool currentUserRead; + public static volatile Td.TdApi.Message lastMessage; + public static volatile bool quitting; + public static volatile string currentInputLine = ""; + public static volatile List messageQueue = new List(); + public static volatile List missedMessages = new List(); + public static volatile string prefix = "[tgcli"; + public static volatile bool silent; - public static volatile object @lock = new object(); + public static volatile object @lock = new object(); - private static void Main(string[] args) - { - if (args.Length == 1 && args[0] == "-s") - silent = true; + private static void Main(string[] args) { + if (args.Length == 1 && args[0] == "-s") + silent = true; - dbdir = - $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.tgcli"; - if (!Directory.Exists(dbdir)) - Directory.CreateDirectory(dbdir); + dbdir = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.tgcli"; + if (!Directory.Exists(dbdir)) + Directory.CreateDirectory(dbdir); - client.Send(new Td.TdApi.SetLogStream - { - LogStream = new Td.TdApi.LogStream.LogStreamFile - { - Path = Path.Combine(dbdir, "tdlib.log"), - MaxFileSize = 10000000 - } - }); + client.Send(new Td.TdApi.SetLogStream { + LogStream = new Td.TdApi.LogStream.LogStreamFile {Path = Path.Combine(dbdir, "tdlib.log"), MaxFileSize = 10000000} + }); - client.Send(new Td.TdApi.SetLogVerbosityLevel - { - NewVerbosityLevel = 2 - }); + client.Send(new Td.TdApi.SetLogVerbosityLevel {NewVerbosityLevel = 2}); - Console.Clear(); - ClearCurrentConsoleLine(); + Console.Clear(); + ClearCurrentConsoleLine(); - client.UpdateReceived += HandleUpdate; + client.UpdateReceived += HandleUpdate; - OnAuthUpdate(new Td.TdApi.Update.UpdateAuthorizationState() - { - AuthorizationState = new Td.TdApi.AuthorizationState.AuthorizationStateWaitTdlibParameters() - }); + OnAuthUpdate(new Td.TdApi.Update.UpdateAuthorizationState() { + AuthorizationState = new Td.TdApi.AuthorizationState.AuthorizationStateWaitTdlibParameters() + }); - while (!authorized) - { - Thread.Sleep(1); - } + while (!authorized) { + Thread.Sleep(1); + } - ScreenUpdate(); - while (!quitting) - MainLoop(); - ClearCurrentConsoleLine(); - Console.WriteLine($"{Ansi.Yellow}[tgcli] Shutting down...{Ansi.ResetAll}"); - } + ScreenUpdate(); + while (!quitting) + MainLoop(); + ClearCurrentConsoleLine(); + Console.WriteLine($"{Ansi.Yellow}[tgcli] Shutting down...{Ansi.ResetAll}"); + } - private static void MainLoop() - { - var key = Console.ReadKey(true); - OnKeyPressed(key); - } + private static void MainLoop() { + var key = Console.ReadKey(true); + OnKeyPressed(key); + } - private static void HandleUpdate(object sender, Td.TdApi.Update e) - { - switch (e) - { - case Td.TdApi.Update.UpdateAuthorizationState state: - OnAuthUpdate(state); - break; - case Td.TdApi.Update.UpdateNewMessage message: - { - Task.Run(() => AddMessageToQueue(message.Message)); - break; - } - case Td.TdApi.Update.UpdateMessageContent message: - Task.Run(() => AddMessageToQueue(message)); - Task.Run(() => - { - var msg = GetMessage(message.ChatId, message.MessageId); - if (msg.IsOutgoing && currentChatId == msg.ChatId) - { - lastMessage = msg; - } - }); - break; - case Td.TdApi.Update.UpdateMessageSendSucceeded sentMsg: - lastMessage = sentMsg.Message; - break; - case Td.TdApi.Update.UpdateChatReadOutbox update: - if (lastMessage != null && lastMessage.ChatId == update.ChatId) - { - currentUserRead = true; - ScreenUpdate(); - } + private static void HandleUpdate(object sender, Td.TdApi.Update e) { + switch (e) { + case Td.TdApi.Update.UpdateAuthorizationState state: + OnAuthUpdate(state); + break; + case Td.TdApi.Update.UpdateNewMessage message: { + Task.Run(() => AddMessageToQueue(message.Message)); + break; + } + case Td.TdApi.Update.UpdateMessageContent message: + Task.Run(() => AddMessageToQueue(message)); + Task.Run(() => { + var msg = GetMessage(message.ChatId, message.MessageId); + if (msg.IsOutgoing && currentChatId == msg.ChatId) { + lastMessage = msg; + } + }); + break; + case Td.TdApi.Update.UpdateMessageSendSucceeded sentMsg: + lastMessage = sentMsg.Message; + break; + case Td.TdApi.Update.UpdateChatReadOutbox update: + if (lastMessage != null && lastMessage.ChatId == update.ChatId) { + currentUserRead = true; + ScreenUpdate(); + } - break; - case Td.TdApi.Update.UpdateConnectionState state: - switch (state.State) - { - case Td.TdApi.ConnectionState.ConnectionStateConnecting _: - connectionState = "Connecting"; - if (!authorized) return; - messageQueue.Add($"{Ansi.Yellow}[tgcli] Connecting to Telegram servers..."); - ScreenUpdate(); - break; - case Td.TdApi.ConnectionState.ConnectionStateConnectingToProxy _: - connectionState = "Connecting"; - if (!authorized) return; - messageQueue.Add($"{Ansi.Yellow}[tgcli] Connecting to Proxy..."); - ScreenUpdate(); - break; - case Td.TdApi.ConnectionState.ConnectionStateReady _: - if (!authorized) return; - messageQueue.Add($"{Ansi.Yellow}[tgcli] Connected."); - Task.Run(() => - { - HandleCommand("u"); - connectionState = "Ready"; - ScreenUpdate(); - }); - ScreenUpdate(); - break; - case Td.TdApi.ConnectionState.ConnectionStateUpdating _: - connectionState = "Updating"; - if (!authorized) return; - messageQueue.Add($"{Ansi.Yellow}[tgcli] Updating message cache..."); - ScreenUpdate(); - break; - case Td.TdApi.ConnectionState.ConnectionStateWaitingForNetwork _: - connectionState = "Waiting for Network"; - if (!authorized) return; - messageQueue.Add($"{Ansi.Yellow}[tgcli] Lost connection. Waiting for network..."); - ScreenUpdate(); - break; - } + break; + case Td.TdApi.Update.UpdateConnectionState state: + switch (state.State) { + case Td.TdApi.ConnectionState.ConnectionStateConnecting _: + connectionState = "Connecting"; + if (!authorized) + return; - break; - case Td.TdApi.Update.UpdateSecretChat update: - var chat = update.SecretChat; - switch (chat.State) - { - case Td.TdApi.SecretChatState.SecretChatStateClosed _: - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat with {chat.Id} was closed."); - ScreenUpdate(); - break; - case Td.TdApi.SecretChatState.SecretChatStatePending _: - break; - case Td.TdApi.SecretChatState.SecretChatStateReady _: - lock (@lock) - messageQueue.Add($"{Ansi.Green}[tgcli] Secret chat {chat.Id} connected."); - ScreenUpdate(); - break; - } + messageQueue.Add($"{Ansi.Yellow}[tgcli] Connecting to Telegram servers..."); + ScreenUpdate(); + break; + case Td.TdApi.ConnectionState.ConnectionStateConnectingToProxy _: + connectionState = "Connecting"; + if (!authorized) + return; - break; - } - } + messageQueue.Add($"{Ansi.Yellow}[tgcli] Connecting to Proxy..."); + ScreenUpdate(); + break; + case Td.TdApi.ConnectionState.ConnectionStateReady _: + if (!authorized) + return; - public static void ScreenUpdate() - { - lock (@lock) - { - ClearCurrentConsoleLine(); - messageQueue.ForEach(p => Console.WriteLine(p + Ansi.ResetAll)); - if (messageQueue.Count > 0 && !silent) - Console.Write("\a"); //ring terminal bell - messageQueue.Clear(); - var status = GetFormattedStatus(currentUserRead); - var output = prefix; - if (connectionState != "Ready") - output += $" | {connectionState}"; - if (currentChatUserId != 0) - output += status; - else - output += "]"; - output += " > "; - output += TruncateMessageStart(currentInputLine, Console.LargestWindowWidth - GetActualStringWidth(output)); - Console.Write(output); - } - } + messageQueue.Add($"{Ansi.Yellow}[tgcli] Connected."); + Task.Run(() => { + HandleCommand("u"); + connectionState = "Ready"; + ScreenUpdate(); + }); + ScreenUpdate(); + break; + case Td.TdApi.ConnectionState.ConnectionStateUpdating _: + connectionState = "Updating"; + if (!authorized) + return; - private static void OnKeyPressed(ConsoleKeyInfo key) - { - switch (key.Key) - { - case ConsoleKey.Enter when connectionState != "Ready": - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] " + - "Connection unstable. Check your network connection and try again."); - ScreenUpdate(); - break; - case ConsoleKey.Enter when currentInputLine.StartsWith("/"): - { - var command = currentInputLine.Substring(1); - currentInputLine = ""; - HandleCommand(command); - ScreenUpdate(); - return; - } - case ConsoleKey.Enter when currentChatId == 0: - { - lock (@lock) - messageQueue.Add($"{Ansi.Red}[tgcli] " + - "No chat selected. Select a chat with /open "); - ScreenUpdate(); - return; - } - case ConsoleKey.Enter: - SendMessage(currentInputLine, currentChatId); - currentInputLine = ""; - ScreenUpdate(); - break; - case ConsoleKey.Backspace when currentInputLine.Length >= 1: - if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) - { - var lastIndex = currentInputLine.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal); - if (lastIndex < 0) - lastIndex = 0; - currentInputLine = currentInputLine.Substring(0, lastIndex); - if (lastIndex != 0) - currentInputLine += " "; - ScreenUpdate(); - return; - } + messageQueue.Add($"{Ansi.Yellow}[tgcli] Updating message cache..."); + ScreenUpdate(); + break; + case Td.TdApi.ConnectionState.ConnectionStateWaitingForNetwork _: + connectionState = "Waiting for Network"; + if (!authorized) + return; - currentInputLine = currentInputLine.Substring(0, currentInputLine.Length - 1); - ScreenUpdate(); - break; - default: - { - switch (key.Key) - { - case ConsoleKey.N when key.Modifiers.HasFlag(ConsoleModifiers.Control): - currentInputLine += "⏎"; - ScreenUpdate(); - return; - case ConsoleKey.D when key.Modifiers.HasFlag(ConsoleModifiers.Control): - HandleCommand("q"); - ScreenUpdate(); - return; - case ConsoleKey.Q when key.Modifiers.HasFlag(ConsoleModifiers.Control): - HandleCommand("q"); - ScreenUpdate(); - return; - case ConsoleKey.E when key.Modifiers.HasFlag(ConsoleModifiers.Control): - HandleCommand("c"); - ScreenUpdate(); - return; - case ConsoleKey.U when key.Modifiers.HasFlag(ConsoleModifiers.Control): - HandleCommand("u"); - ScreenUpdate(); - return; - case ConsoleKey.O when key.Modifiers.HasFlag(ConsoleModifiers.Control): - if (string.IsNullOrWhiteSpace(currentInputLine)) - currentInputLine = "/o "; - ScreenUpdate(); - return; - case ConsoleKey.L when key.Modifiers.HasFlag(ConsoleModifiers.Control): - HandleCommand("cl"); - ScreenUpdate(); - return; - } + messageQueue.Add($"{Ansi.Yellow}[tgcli] Lost connection. Waiting for network..."); + ScreenUpdate(); + break; + } - if (!SpecialKeys.Contains(key.Key)) - { - currentInputLine += key.KeyChar; - ScreenUpdate(); - } + break; + case Td.TdApi.Update.UpdateSecretChat update: + var chat = update.SecretChat; + switch (chat.State) { + case Td.TdApi.SecretChatState.SecretChatStateClosed _: + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat with {chat.Id} was closed."); + ScreenUpdate(); + break; + case Td.TdApi.SecretChatState.SecretChatStatePending _: break; + case Td.TdApi.SecretChatState.SecretChatStateReady _: + lock (@lock) + messageQueue.Add($"{Ansi.Green}[tgcli] Secret chat {chat.Id} connected."); + ScreenUpdate(); + break; + } - break; - } - } - } + break; + } + } - private static void OnAuthUpdate(Td.TdApi.Update.UpdateAuthorizationState state) - { - switch (state.AuthorizationState) - { - case Td.TdApi.AuthorizationState.AuthorizationStateWaitTdlibParameters _: - client.Send(new Td.TdApi.SetTdlibParameters - { - Parameters = new Td.TdApi.TdlibParameters - { - ApiId = 600606, - ApiHash = "c973f46778be4b35481ce45e93271e82", - DatabaseDirectory = dbdir, - UseMessageDatabase = true, - SystemLanguageCode = "en_US", - DeviceModel = Environment.MachineName, - SystemVersion = ".NET Core CLR " + Environment.Version, - ApplicationVersion = "0.1a", - EnableStorageOptimizer = true, - UseSecretChats = true - } - }); - break; - case Td.TdApi.AuthorizationState.AuthorizationStateWaitEncryptionKey _: - client.Send(new Td.TdApi.CheckDatabaseEncryptionKey()); - break; - case Td.TdApi.AuthorizationState.AuthorizationStateWaitPhoneNumber _: - { - Console.Write("[tgcli] login> "); - var phone = Console.ReadLine(); - client.Send(new Td.TdApi.SetAuthenticationPhoneNumber - { - PhoneNumber = phone - }); - break; - } - case Td.TdApi.AuthorizationState.AuthorizationStateWaitCode _: - { - Console.Write("[tgcli] code> "); - var code = Console.ReadLine(); - client.Send(new Td.TdApi.CheckAuthenticationCode - { - Code = code - }); - break; - } - case Td.TdApi.AuthorizationState.AuthorizationStateWaitPassword _: - { - Console.Write("[tgcli] 2fa password> "); - var pass = ReadConsolePassword(); - client.Send(new Td.TdApi.CheckAuthenticationPassword - { - Password = pass - }); - break; - } - case Td.TdApi.AuthorizationState.AuthorizationStateReady _: - Console.WriteLine("[tgcli] logged in."); - authorized = true; - connectionState = "Ready"; - break; - case Td.TdApi.AuthorizationState.AuthorizationStateClosed _: - messageQueue.Add($"{Ansi.Yellow}[tgcli] Logged out successfully. All local data has been deleted."); - ScreenUpdate(); - Environment.Exit(0); - break; - case Td.TdApi.AuthorizationState.AuthorizationStateClosing _: - messageQueue.Add($"{Ansi.Yellow}[tgcli] Logging out..."); - ScreenUpdate(); - break; - case Td.TdApi.AuthorizationState.AuthorizationStateLoggingOut _: - if (authorized) return; - Console.WriteLine( - "[tgcli] This session has been destroyed externally, to fix this delete ~/.tgcli"); - Environment.Exit(1); - break; - default: - Console.WriteLine($"unknown state: {state.AuthorizationState.DataType}"); - Environment.Exit(1); - break; - } - } + public static void ScreenUpdate() { + lock (@lock) { + ClearCurrentConsoleLine(); + messageQueue.ForEach(p => Console.WriteLine(p + Ansi.ResetAll)); + if (messageQueue.Count > 0 && !silent) + Console.Write("\a"); //ring terminal bell + messageQueue.Clear(); + var status = GetFormattedStatus(currentUserRead); + var output = prefix; + if (connectionState != "Ready") + output += $" | {connectionState}"; + if (currentChatUserId != 0) + output += status; + else + output += "]"; + output += " > "; + output += TruncateMessageStart(currentInputLine, Console.LargestWindowWidth - GetActualStringWidth(output)); + Console.Write(output); + } + } - public static string FormatMessage(Td.TdApi.Message msg) - { - string text; - if (msg.Content is Td.TdApi.MessageContent.MessageText messageText) - text = messageText.Text.Text; - else if (msg.Content is Td.TdApi.MessageContent.MessagePhoto photo) - text = !string.IsNullOrWhiteSpace(photo.Caption.Text) - ? $"[unsupported {msg.Content.DataType}] {photo.Caption.Text}" - : $"[unsupported {msg.Content.DataType}]"; - else if (msg.Content is Td.TdApi.MessageContent.MessageDocument document) - text = !string.IsNullOrWhiteSpace(document.Caption.Text) - ? $"[unsupported {msg.Content.DataType}] {document.Caption.Text}" - : $"[unsupported {msg.Content.DataType}]"; - else - text = $"[unsupported {msg.Content.DataType}]"; - var sender = GetUser(msg.SenderUserId); - var chat = GetChat(msg.ChatId); - var username = TruncateString(GetFormattedUsername(sender), 10); - var time = FormatTime(msg.Date); - var isChannel = msg.IsChannelPost; - var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate || - chat.Type is Td.TdApi.ChatType.ChatTypeSecret; - var isSecret = chat.Type is Td.TdApi.ChatType.ChatTypeSecret; - var isReply = msg.ReplyToMessageId != 0; + private static void OnKeyPressed(ConsoleKeyInfo key) { + switch (key.Key) { + case ConsoleKey.Enter when connectionState != "Ready": + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] " + "Connection unstable. Check your network connection and try again."); + ScreenUpdate(); + break; + case ConsoleKey.Enter when currentInputLine.StartsWith("/"): { + var command = currentInputLine.Substring(1); + currentInputLine = ""; + HandleCommand(command); + ScreenUpdate(); + return; + } + case ConsoleKey.Enter when currentChatId == 0: { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] " + "No chat selected. Select a chat with /open "); + ScreenUpdate(); + return; + } + case ConsoleKey.Enter: + SendMessage(currentInputLine, currentChatId); + currentInputLine = ""; + ScreenUpdate(); + break; + case ConsoleKey.Backspace when currentInputLine.Length >= 1: + if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) { + var lastIndex = currentInputLine.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal); + if (lastIndex < 0) + lastIndex = 0; + currentInputLine = currentInputLine.Substring(0, lastIndex); + if (lastIndex != 0) + currentInputLine += " "; + ScreenUpdate(); + return; + } - chat.Title = TruncateString(chat.Title, 20); + currentInputLine = currentInputLine.Substring(0, currentInputLine.Length - 1); + ScreenUpdate(); + break; + default: { + switch (key.Key) { + case ConsoleKey.N when key.Modifiers.HasFlag(ConsoleModifiers.Control): + currentInputLine += "⏎ "; + ScreenUpdate(); + return; + case ConsoleKey.D when key.Modifiers.HasFlag(ConsoleModifiers.Control): + HandleCommand("q"); + ScreenUpdate(); + return; + case ConsoleKey.Q when key.Modifiers.HasFlag(ConsoleModifiers.Control): + HandleCommand("q"); + ScreenUpdate(); + return; + case ConsoleKey.E when key.Modifiers.HasFlag(ConsoleModifiers.Control): + HandleCommand("c"); + ScreenUpdate(); + return; + case ConsoleKey.U when key.Modifiers.HasFlag(ConsoleModifiers.Control): + HandleCommand("u"); + ScreenUpdate(); + return; + case ConsoleKey.O when key.Modifiers.HasFlag(ConsoleModifiers.Control): + if (string.IsNullOrWhiteSpace(currentInputLine)) + currentInputLine = "/o "; + ScreenUpdate(); + return; + case ConsoleKey.L when key.Modifiers.HasFlag(ConsoleModifiers.Control): + HandleCommand("cl"); + ScreenUpdate(); + return; + } - Td.TdApi.Message replyMessage; + if (!SpecialKeys.Contains(key.Key)) { + currentInputLine += key.KeyChar; + ScreenUpdate(); + } - var msgPrefix = - $"{Ansi.Bold}{Ansi.Green}[{time}] {(isSecret ? $"{Ansi.Red}[sec] " : "")}{Ansi.Cyan}{chat.Title} " + - $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"; - var finalOutput = msgPrefix; + break; + } + } + } - var indent = new string(' ', GetActualStringWidth(msgPrefix)); - var arrows = $"{(msg.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} "; + private static void OnAuthUpdate(Td.TdApi.Update.UpdateAuthorizationState state) { + switch (state.AuthorizationState) { + case Td.TdApi.AuthorizationState.AuthorizationStateWaitTdlibParameters _: + client.Send(new Td.TdApi.SetTdlibParameters { + Parameters = new Td.TdApi.TdlibParameters { + ApiId = 600606, + ApiHash = "c973f46778be4b35481ce45e93271e82", + DatabaseDirectory = dbdir, + UseMessageDatabase = true, + SystemLanguageCode = "en_US", + DeviceModel = Environment.MachineName, + SystemVersion = ".NET Core CLR " + Environment.Version, + ApplicationVersion = "0.1a", + EnableStorageOptimizer = true, + UseSecretChats = true + } + }); + break; + case Td.TdApi.AuthorizationState.AuthorizationStateWaitEncryptionKey _: + client.Send(new Td.TdApi.CheckDatabaseEncryptionKey()); + break; + case Td.TdApi.AuthorizationState.AuthorizationStateWaitPhoneNumber _: { + Console.Write("[tgcli] login> "); + var phone = Console.ReadLine(); + client.Send(new Td.TdApi.SetAuthenticationPhoneNumber {PhoneNumber = phone}); + break; + } + case Td.TdApi.AuthorizationState.AuthorizationStateWaitCode _: { + Console.Write("[tgcli] code> "); + var code = Console.ReadLine(); + client.Send(new Td.TdApi.CheckAuthenticationCode {Code = code}); + break; + } + case Td.TdApi.AuthorizationState.AuthorizationStateWaitPassword _: { + Console.Write("[tgcli] 2fa password> "); + var pass = ReadConsolePassword(); + client.Send(new Td.TdApi.CheckAuthenticationPassword {Password = pass}); + break; + } + case Td.TdApi.AuthorizationState.AuthorizationStateReady _: + Console.WriteLine("[tgcli] logged in."); + authorized = true; + connectionState = "Ready"; + break; + case Td.TdApi.AuthorizationState.AuthorizationStateClosed _: + messageQueue.Add($"{Ansi.Yellow}[tgcli] Logged out successfully. All local data has been deleted."); + ScreenUpdate(); + Environment.Exit(0); + break; + case Td.TdApi.AuthorizationState.AuthorizationStateClosing _: + messageQueue.Add($"{Ansi.Yellow}[tgcli] Logging out..."); + ScreenUpdate(); + break; + case Td.TdApi.AuthorizationState.AuthorizationStateLoggingOut _: + if (authorized) + return; - if (isReply) - { - try - { - replyMessage = GetMessage(chat.Id, msg.ReplyToMessageId); - finalOutput = $"{FormatMessageReply(replyMessage, msgPrefix)}"; - } - catch - { - //ignored; reply to deleted msg - } - } + Console.WriteLine("[tgcli] This session has been destroyed externally, to fix this delete ~/.tgcli"); + Environment.Exit(1); + break; + default: + Console.WriteLine($"unknown state: {state.AuthorizationState.DataType}"); + Environment.Exit(1); + break; + } + } - var rest = $"{text}{(msg.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}"; - var lines = rest.Split("\n").ToList(); - if (!isReply) - { - finalOutput += arrows + lines.First(); - lines.RemoveAt(0); - } + public static string FormatMessage(Td.TdApi.Message msg) { + string text; + if (msg.Content is Td.TdApi.MessageContent.MessageText messageText) + text = messageText.Text.Text; + else if (msg.Content is Td.TdApi.MessageContent.MessagePhoto photo) + text = !string.IsNullOrWhiteSpace(photo.Caption.Text) + ? $"[unsupported {msg.Content.DataType}] {photo.Caption.Text}" + : $"[unsupported {msg.Content.DataType}]"; + else if (msg.Content is Td.TdApi.MessageContent.MessageDocument document) + text = !string.IsNullOrWhiteSpace(document.Caption.Text) + ? $"[unsupported {msg.Content.DataType}] {document.Caption.Text}" + : $"[unsupported {msg.Content.DataType}]"; + else + text = $"[unsupported {msg.Content.DataType}]"; + var sender = GetUser(msg.SenderUserId); + var chat = GetChat(msg.ChatId); + var username = TruncateString(GetFormattedUsername(sender), 10); + var time = FormatTime(msg.Date); + var isChannel = msg.IsChannelPost; + var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate || chat.Type is Td.TdApi.ChatType.ChatTypeSecret; + var isSecret = chat.Type is Td.TdApi.ChatType.ChatTypeSecret; + var isReply = msg.ReplyToMessageId != 0; - lines.ForEach(l => finalOutput += "\n" + indent + arrows + l); + chat.Title = TruncateString(chat.Title, 20); - return finalOutput; - } + Td.TdApi.Message replyMessage; - public static string FormatMessageReply(Td.TdApi.Message msg, string origPrefix) - { - string text; - if (msg.Content is Td.TdApi.MessageContent.MessageText messageText) - text = messageText.Text.Text; - else - text = $"[unsupported {msg.Content.DataType}]"; - var sender = GetUser(msg.SenderUserId); - var chat = GetChat(msg.ChatId); - var username = GetFormattedUsername(sender); - var time = FormatTime(msg.Date); - var isChannel = msg.IsChannelPost; - var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate || - chat.Type is Td.TdApi.ChatType.ChatTypeSecret; - var isSecret = chat.Type is Td.TdApi.ChatType.ChatTypeSecret; + var msgPrefix = $"{Ansi.Bold}{Ansi.Green}[{time}] {(isSecret ? $"{Ansi.Red}[sec] " : "")}{Ansi.Cyan}{chat.Title} " + + $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"; + var finalOutput = msgPrefix; - chat.Title = TruncateString(chat.Title, 20); + var indent = new string(' ', GetActualStringWidth(msgPrefix)); + var arrows = $"{(msg.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} "; - var finalOutput = ""; - var replyPrefix = $"{origPrefix}{Ansi.Yellow}Re: {Ansi.Bold}{Ansi.Green}[{time}] " + - $"{(isSecret ? $"{Ansi.Red}[sec] " : "")}{Ansi.Cyan}{chat.Title} " + - $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"; + if (isReply) { + try { + replyMessage = GetMessage(chat.Id, msg.ReplyToMessageId); + finalOutput = $"{FormatMessageReply(replyMessage, msgPrefix)}"; + } + catch { + //ignored; reply to deleted msg + } + } - var indent = new string(' ', GetActualStringWidth(replyPrefix)); - var arrows = $"{(msg.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} "; + var rest = $"{text}{(msg.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}"; + var lines = rest.Split("\n").ToList(); + if (!isReply) { + finalOutput += arrows + lines.First(); + lines.RemoveAt(0); + } - var rest = $"{text}{(msg.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}"; + lines.ForEach(l => finalOutput += "\n" + indent + arrows + l); - finalOutput += replyPrefix; + return finalOutput; + } - var lines = rest.Split("\n").ToList(); - finalOutput += arrows + lines.First(); - lines.RemoveAt(0); - lines.ForEach(l => finalOutput += "\n" + indent + arrows + l); + public static string FormatMessageReply(Td.TdApi.Message msg, string origPrefix) { + string text; + if (msg.Content is Td.TdApi.MessageContent.MessageText messageText) + text = messageText.Text.Text; + else + text = $"[unsupported {msg.Content.DataType}]"; + var sender = GetUser(msg.SenderUserId); + var chat = GetChat(msg.ChatId); + var username = GetFormattedUsername(sender); + var time = FormatTime(msg.Date); + var isChannel = msg.IsChannelPost; + var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate || chat.Type is Td.TdApi.ChatType.ChatTypeSecret; + var isSecret = chat.Type is Td.TdApi.ChatType.ChatTypeSecret; - return finalOutput; - } + chat.Title = TruncateString(chat.Title, 20); - private static string FormatMessage(Td.TdApi.Update.UpdateMessageContent msg) - { - string text; - if (msg.NewContent is Td.TdApi.MessageContent.MessageText messageText) - text = messageText.Text.Text; - else - text = $"[unsupported {msg.NewContent.DataType}]"; - var message = GetMessage(msg.ChatId, msg.MessageId); - var sender = GetUser(message.SenderUserId); - var chat = GetChat(msg.ChatId); - var username = GetFormattedUsername(sender); - var time = FormatTime(message.EditDate); - var isChannel = message.IsChannelPost; - var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate; + var finalOutput = ""; + var replyPrefix = $"{origPrefix}{Ansi.Yellow}Re: {Ansi.Bold}{Ansi.Green}[{time}] " + + $"{(isSecret ? $"{Ansi.Red}[sec] " : "")}{Ansi.Cyan}{chat.Title} " + + $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"; - return $"{Ansi.Bold}{Ansi.Green}[{time}] {Ansi.Cyan}{chat.Title} " + - $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}" + - $"{(message.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} " + - $"{text}" + - $"{Ansi.Yellow}*"; - } + var indent = new string(' ', GetActualStringWidth(replyPrefix)); + var arrows = $"{(msg.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} "; - public static void AddMessageToQueue(Td.TdApi.Message msg) - { - //handle muted - if (GetChat(msg.ChatId).NotificationSettings.MuteFor > 0 && currentChatId != msg.ChatId) - return; + var rest = $"{text}{(msg.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}"; - //we aren't interested in backlog - if (connectionState != "Ready") - return; + finalOutput += replyPrefix; - var formattedMessage = FormatMessage(msg); + var lines = rest.Split("\n").ToList(); + finalOutput += arrows + lines.First(); + lines.RemoveAt(0); + lines.ForEach(l => finalOutput += "\n" + indent + arrows + l); - if (currentChatId != 0 && msg.ChatId != currentChatId) - lock (@lock) - missedMessages.Add(formattedMessage); - else - lock (@lock) - messageQueue.Add(formattedMessage); + return finalOutput; + } - if (msg.ChatId == currentChatId) - MarkRead(msg.ChatId, msg.Id); - ScreenUpdate(); - } + private static string FormatMessage(Td.TdApi.Update.UpdateMessageContent msg) { + string text; + if (msg.NewContent is Td.TdApi.MessageContent.MessageText messageText) + text = messageText.Text.Text; + else + text = $"[unsupported {msg.NewContent.DataType}]"; + var message = GetMessage(msg.ChatId, msg.MessageId); + var sender = GetUser(message.SenderUserId); + var chat = GetChat(msg.ChatId); + var username = GetFormattedUsername(sender); + var time = FormatTime(message.EditDate); + var isChannel = message.IsChannelPost; + var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate; - public static void AddMessageToQueue(Td.TdApi.Update.UpdateMessageContent msg) - { - //handle muted - if (GetChat(msg.ChatId).NotificationSettings.MuteFor > 0 && currentChatId != msg.ChatId - || GetMessage(msg.ChatId, msg.MessageId).EditDate == 0) - return; + return $"{Ansi.Bold}{Ansi.Green}[{time}] {Ansi.Cyan}{chat.Title} " + + $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}" + + $"{(message.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} " + + $"{text}" + + $"{Ansi.Yellow}*"; + } - var formattedMessage = FormatMessage(msg); + public static void AddMessageToQueue(Td.TdApi.Message msg) { + //handle muted + if (GetChat(msg.ChatId).NotificationSettings.MuteFor > 0 && currentChatId != msg.ChatId) + return; - if (currentChatId != 0 && msg.ChatId != currentChatId) - lock (@lock) - missedMessages.Add(formattedMessage); - else - lock (@lock) - messageQueue.Add(formattedMessage); - ScreenUpdate(); - } - } + //we aren't interested in backlog + if (connectionState != "Ready") + return; + + var formattedMessage = FormatMessage(msg); + + if (currentChatId != 0 && msg.ChatId != currentChatId) + lock (@lock) + missedMessages.Add(formattedMessage); + else + lock (@lock) + messageQueue.Add(formattedMessage); + + if (msg.ChatId == currentChatId) + MarkRead(msg.ChatId, msg.Id); + ScreenUpdate(); + } + + public static void AddMessageToQueue(Td.TdApi.Update.UpdateMessageContent msg) { + //handle muted + if (GetChat(msg.ChatId).NotificationSettings.MuteFor > 0 && currentChatId != msg.ChatId || GetMessage(msg.ChatId, msg.MessageId).EditDate == 0) + return; + + var formattedMessage = FormatMessage(msg); + + if (currentChatId != 0 && msg.ChatId != currentChatId) + lock (@lock) + missedMessages.Add(formattedMessage); + else + lock (@lock) + messageQueue.Add(formattedMessage); + ScreenUpdate(); + } + } } \ No newline at end of file