From d4aee295a6139f2ef670296bc91e41733849d17e Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Fri, 13 Dec 2019 13:11:06 +0100 Subject: [PATCH] add secret chat support --- telegram/Command.cs | 227 ++++++++++++++++++++++++++++++++++++++------ telegram/Util.cs | 103 +++++++++++++++++--- telegram/tgcli.cs | 35 +++++-- 3 files changed, 318 insertions(+), 47 deletions(-) diff --git a/telegram/Command.cs b/telegram/Command.cs index c98ecbd..4a564a8 100644 --- a/telegram/Command.cs +++ b/telegram/Command.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NeoSmart.Unicode; using TdLib; using static telegram.tgcli; using static telegram.Util; @@ -9,15 +10,15 @@ namespace telegram { public abstract class Command { - public string ShortTrigger; + public string Trigger; public string Shortcut; public string Description; public int ParamCount; public abstract void Handler(List inputParams); - protected Command(string shortTrigger, string shortcut, string description, int paramCount) + protected Command(string trigger, string shortcut, string description, int paramCount) { - ShortTrigger = shortTrigger; + Trigger = trigger; Shortcut = shortcut; Description = description; ParamCount = paramCount; @@ -35,6 +36,11 @@ namespace telegram new OpenCommand(), new UnreadsCommand(), new CloseUnreadCommand(), + new ListSecretChatsCommand(), + new OpenSecretCommand(), + new NewSecretChatCommand(), + new CloseSecretChatCommand(), + new QuitCommand(), new HelpCommand() }; @@ -42,7 +48,7 @@ namespace telegram { var split = input.Split(" ").ToList(); var trigger = split.First(); - var command = Commands.Find(p => p.ShortTrigger == trigger || p.Shortcut == trigger); + var command = Commands.Find(p => p.Trigger == trigger || p.Shortcut == trigger); if (command == null) { //TODO Console.WriteLine("do something"); @@ -94,11 +100,7 @@ namespace telegram messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}"); messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}"); - while (GetHistory(chatId, 50).Count == 1) - { - GetHistory(chatId, 10); - } - + if (chat.UnreadCount >= 5) { var capped = chat.UnreadCount > 50; @@ -136,6 +138,131 @@ namespace telegram } } + public class CloseSecretChatCommand : Command + { + public CloseSecretChatCommand() : base("cs", "", "closes a secret chat (permanently)", 0) + { + } + + public override void Handler(List _) + { + if (currentChatId == 0) + return; //TODO do something + if (GetChat(currentChatId).Type is TdApi.ChatType.ChatTypeSecret type) + { + CloseSecretChat(type.SecretChatId); + DeleteChatHistory(currentChatId); + CommandManager.HandleCommand("c"); + } + else + { + //TODO do something + } + } + } + + public class NewSecretChatCommand : Command + { + public NewSecretChatCommand() : base("ns", "", "creates a new secret chat", -1) + { + } + + public override void Handler(List inputParams) + { + var query = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + var userId = SearchContacts(query); + + if (userId == 0) + return; + + var chat = CreateSecretChat(userId); + CommandManager.HandleCommand("os " + chat.Id); + } + } + + public class OpenSecretCommand : Command + { + public OpenSecretCommand() : base("os", "", "opens a secret chat", 1) + { + } + + public override void Handler(List inputParams) + { + var id = inputParams[0]; + if (!long.TryParse(id, out var chatId)) + { + return; //todo do something + } + + var chat = GetChat(chatId); + if (chat == null) + return; //TODO do something + + TdApi.SecretChat secChat; + if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) + { + currentChatUserId = secretChat.UserId; + currentChatId = chat.Id; + currentUserRead = false; + secChat = GetSecretChat(secretChat.SecretChatId); + } + else + { + return; //TODO do something + } + + 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 CloseUnreadCommand : Command { public CloseUnreadCommand() : base("cu", "", "closes a chat, marking it as unread", 0) @@ -239,6 +366,8 @@ namespace telegram 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); @@ -246,6 +375,26 @@ namespace telegram } } } + + 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 { @@ -262,16 +411,28 @@ namespace telegram { if (string.IsNullOrWhiteSpace(command.Shortcut)) { - messageQueue.Add($"{Ansi.Yellow}/{command.ShortTrigger}: {command.Description}"); + messageQueue.Add($"{Ansi.Yellow}/{command.Trigger}: {command.Description}"); return; } messageQueue.Add( - $"{Ansi.Yellow}/{command.ShortTrigger} [{command.Shortcut}]: {command.Description}"); + $"{Ansi.Yellow}/{command.Trigger} [{command.Shortcut}]: {command.Description}"); }); } } } + + 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 { @@ -281,28 +442,36 @@ namespace telegram public override void Handler(List inputParams) { - var message = inputParams.Aggregate((current, param) => current + " " + param).Trim(); - - if (currentChatId == 0) return; //TODO do something - - if (message.Length == 0) + try { - currentInputLine = "/e " + ((TdApi.MessageContent.MessageText) lastMessage?.Content)?.Text?.Text; - return; - } - if (lastMessage == null) + var message = inputParams.Aggregate((current, param) => current + " " + param).Trim(); + + if (currentChatId == 0) return; //TODO do something + + if (message.Length == 0) + { + currentInputLine = "/e " + ((TdApi.MessageContent.MessageText) lastMessage?.Content)?.Text?.Text; + return; + } + + if (lastMessage == null) + { + //try to find last message + var history = GetHistory(currentChatId, 50); + var last = history.LastOrDefault(p => p.IsOutgoing); + if (last == null) return; //TODO do something + lastMessage = last; + } + + if (string.IsNullOrWhiteSpace(message)) + return; + EditMessage(message, lastMessage); + } + catch { - //try to find last message - var history = GetHistory(currentChatId, 50); - var last = history.LastOrDefault(p => p.IsOutgoing); - if (last == null) return; //TODO do something - lastMessage = last; + //TODO do something } - - if (string.IsNullOrWhiteSpace(message)) - return; - EditMessage(message, lastMessage); } } } \ No newline at end of file diff --git a/telegram/Util.cs b/telegram/Util.cs index af26c7e..7bef3d1 100644 --- a/telegram/Util.cs +++ b/telegram/Util.cs @@ -44,10 +44,17 @@ namespace telegram public static Chat GetChat(long chatId) { - return client.ExecuteAsync(new GetChat + try { - ChatId = chatId - }).Result; + return client.ExecuteAsync(new GetChat + { + ChatId = chatId + }).Result; + } + catch + { + return null; + } } public static User GetMe() @@ -66,17 +73,24 @@ namespace telegram public static int GetTotalMessages(long chatId) { - var response = client.ExecuteAsync(new SearchChatMessages + try { - - ChatId = chatId, - Query = "+", - Limit = 1 - }); - return response.Result.TotalCount; + 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) + public static List GetHistory(long chatId, int limit = 5, long fromMessageId = 0, int offset = 0, bool isSecret = false) { var history = new List(); var total = GetTotalMessages(chatId); @@ -100,7 +114,7 @@ namespace telegram }) .Result; - if (response.Messages_.Length < limit && i > 1) + if (response.Messages_.Length < limit && i > 1 && !isSecret) { Thread.Sleep(100); continue; @@ -126,6 +140,51 @@ namespace telegram && c.NotificationSettings.MuteFor == 0) .ToList(); } + + 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 CloseSecretChat(int secretChatId) + { + client.ExecuteAsync(new CloseSecretChat() + { + SecretChatId = secretChatId + }).Wait(); + } + + public static Chat CreateSecretChat(int userId) + { + return client.ExecuteAsync(new CreateNewSecretChat + { + UserId = userId + }).Result; + } + + public static void DeleteChatHistory(long chatId) + { + client.ExecuteAsync(new DeleteChatHistory + { + ChatId = chatId, + RemoveFromChatList = true, + Revoke = true + }).Wait(); + } + + public static SecretChat GetSecretChat(int secretChatId) + { + var response = client.ExecuteAsync(new GetSecretChat + { + SecretChatId = secretChatId + }).Result; + return response; + } public static void ClearCurrentConsoleLine() { @@ -246,6 +305,26 @@ namespace telegram return 0; } } + + public static int SearchContacts(string query) + { + try + { + var results = client.ExecuteAsync(new SearchContacts + { + Query = query, + Limit = 5 + }).Result; + + return results.UserIds.First(); + } + catch + { + lock (@lock) + messageQueue.Add($"{Ansi.Red}[tgcli] No results found."); + return 0; + } + } public static object GetFormattedUsername(User sender) { diff --git a/telegram/tgcli.cs b/telegram/tgcli.cs index b8f5321..1043e84 100644 --- a/telegram/tgcli.cs +++ b/telegram/tgcli.cs @@ -14,8 +14,10 @@ namespace telegram { /* * TODO: - * commands: more shortcuts! * commands: usage! + * improve secret chat open: open by username, allow just one sec chat per userId + * global search: add contacts. + * commands: more shortcuts? * make commands & keybinds more consistent (maybe configurable?) * reply to x messages ago * cap length & truncate extremely long chat names! @@ -26,12 +28,12 @@ namespace telegram * make Util.getActualStringWidth better * refactor everything * add option to disable terminal bell - * command /s /search -> list matching chats, option 1-n, archived indicator + * command /s /search -> list matching chats, option 1-n, archived, muted indicator + * mute,unmute chats * split with newline if received message enters next line * fix issues when current_input message is longer than term width (only show as much as fits?) * photo/document/etc captions * photo download & show externally - * secret chats! * publish AUR package * maybe cursor input nav (cmd+del, left/right, up for last inputs, etc) */ @@ -174,6 +176,25 @@ namespace telegram break; } + break; + case Td.TdApi.Update.UpdateSecretChat update: + var chat = update.SecretChat; + switch (chat.State) + { + //TODO: send notifs here! + 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; } } @@ -304,7 +325,8 @@ namespace telegram DeviceModel = Environment.MachineName, SystemVersion = ".NET Core CLR " + Environment.Version, ApplicationVersion = "0.1a", - EnableStorageOptimizer = true + EnableStorageOptimizer = true, + UseSecretChats = true } }); break; @@ -364,11 +386,12 @@ namespace telegram var username = GetFormattedUsername(sender); var time = FormatTime(msg.Date); var isChannel = msg.IsChannelPost; - var isPrivate = chat.Type is Td.TdApi.ChatType.ChatTypePrivate; + 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; Td.TdApi.Message replyMessage; - var msgPrefix = $"{Ansi.Bold}{Ansi.Green}[{time}] {Ansi.Cyan}{chat.Title} " + + var msgPrefix = $"{Ansi.Bold}{Ansi.Green}[{time}] {(isSecret ? $"{Ansi.Red}[sec] " : "")}{Ansi.Cyan}{chat.Title} " + $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"; var finalOutput = msgPrefix;