637 lines
21 KiB
C#
637 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Td = TdLib;
|
|
using static TdLib.TdApi;
|
|
using static tgcli.Util;
|
|
using static tgcli.CommandManager;
|
|
|
|
// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault
|
|
|
|
namespace tgcli;
|
|
|
|
/*
|
|
* TODO:
|
|
* fix newlines with input nav...
|
|
* unreads are unreliable in secret chats!
|
|
* mute,unmute chats
|
|
* photo & document download & show externally
|
|
* refactor everything
|
|
* re-evaluate ClearCurrentConsoleLine function
|
|
* When TDLib 1.6 is released: implement contacts
|
|
*/
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
public static class tgcli {
|
|
public const string version = "0.4a";
|
|
public static volatile bool lockInputToBottom;
|
|
public static volatile Td.TdClient client = new();
|
|
public static string dbdir = "";
|
|
public static volatile bool authorized;
|
|
public static volatile string connectionState = "Connecting";
|
|
public static long currentChatId = 0;
|
|
public static long currentChatUserId = 0;
|
|
public static volatile bool currentUserRead;
|
|
public static volatile Message lastMessage;
|
|
public static volatile bool quitting;
|
|
public static volatile string currentInputLine = "";
|
|
public static volatile int currentInputPos;
|
|
public static volatile List<string> messageQueue = new();
|
|
public static volatile List<string> missedMessages = new();
|
|
public static volatile string prefix = "[tgcli";
|
|
public static volatile bool silent;
|
|
|
|
public static volatile object @lock = new();
|
|
|
|
private static void Main(string[] args) {
|
|
if (args.Contains("-s"))
|
|
silent = true;
|
|
|
|
if (args.Contains("-l"))
|
|
lockInputToBottom = true;
|
|
|
|
if (args.Contains("-h") || args.Contains("-?") || args.Contains("--help")) {
|
|
Console.WriteLine($"""
|
|
tgcli v{version}
|
|
Laura Hausmann <laura@hausmann.dev>
|
|
Source Code: https://git.ztn.sh/zotan/tgcli
|
|
|
|
Arguments:
|
|
-s Silent mode. Disables terminal bell on new message.
|
|
-l Locks input line to bottom, like in an IRC client.
|
|
""");
|
|
return;
|
|
}
|
|
|
|
dbdir = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
|
? $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.tgcli"
|
|
: $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.local{Path.DirectorySeparatorChar}share{Path.DirectorySeparatorChar}tgcli";
|
|
|
|
if (!Directory.Exists(dbdir))
|
|
Directory.CreateDirectory(dbdir);
|
|
|
|
client.Send(new SetLogStream { LogStream = new LogStream.LogStreamFile { Path = Path.Combine(dbdir, "tdlib.log"), MaxFileSize = 10000000 } });
|
|
|
|
client.Send(new SetLogVerbosityLevel { NewVerbosityLevel = 2 });
|
|
|
|
Console.Clear();
|
|
ClearCurrentConsoleLine();
|
|
|
|
client.UpdateReceived += HandleUpdate;
|
|
|
|
OnAuthUpdate(new Update.UpdateAuthorizationState { AuthorizationState = new AuthorizationState.AuthorizationStateWaitTdlibParameters() });
|
|
|
|
while (!authorized) {
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
if (lockInputToBottom)
|
|
Console.SetCursorPosition(0, Console.LargestWindowHeight);
|
|
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 HandleUpdate(object sender, Update e) {
|
|
switch (e) {
|
|
case Update.UpdateAuthorizationState state:
|
|
OnAuthUpdate(state);
|
|
break;
|
|
case Update.UpdateNewMessage message: {
|
|
Task.Run(() => AddMessageToQueue(message.Message));
|
|
break;
|
|
}
|
|
case 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 Update.UpdateMessageUnreadReactions message:
|
|
Task.Run(() => AddMessageToQueue(message));
|
|
break;
|
|
case Update.UpdateMessageSendSucceeded sentMsg:
|
|
lastMessage = sentMsg.Message;
|
|
break;
|
|
case Update.UpdateChatReadOutbox update:
|
|
if (lastMessage != null && lastMessage.ChatId == update.ChatId) {
|
|
currentUserRead = true;
|
|
ScreenUpdate();
|
|
}
|
|
|
|
break;
|
|
case Update.UpdateConnectionState state:
|
|
switch (state.State) {
|
|
case ConnectionState.ConnectionStateConnecting _:
|
|
connectionState = "Connecting";
|
|
if (!authorized)
|
|
return;
|
|
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Connecting to Telegram servers...");
|
|
ScreenUpdate();
|
|
break;
|
|
case ConnectionState.ConnectionStateConnectingToProxy _:
|
|
connectionState = "Connecting";
|
|
if (!authorized)
|
|
return;
|
|
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Connecting to Proxy...");
|
|
ScreenUpdate();
|
|
break;
|
|
case ConnectionState.ConnectionStateReady _:
|
|
if (!authorized)
|
|
return;
|
|
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Connected.");
|
|
Task.Run(() => {
|
|
HandleCommand("u");
|
|
connectionState = "Ready";
|
|
ScreenUpdate();
|
|
});
|
|
ScreenUpdate();
|
|
break;
|
|
case ConnectionState.ConnectionStateUpdating _:
|
|
connectionState = "Updating";
|
|
if (!authorized)
|
|
return;
|
|
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Updating message cache...");
|
|
ScreenUpdate();
|
|
break;
|
|
case ConnectionState.ConnectionStateWaitingForNetwork _:
|
|
connectionState = "Waiting for Network";
|
|
if (!authorized)
|
|
return;
|
|
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Lost connection. Waiting for network...");
|
|
ScreenUpdate();
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case Update.UpdateSecretChat update:
|
|
var chat = update.SecretChat;
|
|
switch (chat.State) {
|
|
case SecretChatState.SecretChatStateClosed _:
|
|
lock (@lock)
|
|
messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat with {chat.Id} was closed.");
|
|
ScreenUpdate();
|
|
break;
|
|
case SecretChatState.SecretChatStatePending _: break;
|
|
case SecretChatState.SecretChatStateReady _:
|
|
lock (@lock)
|
|
messageQueue.Add($"{Ansi.Green}[tgcli] Secret chat {chat.Id} connected.");
|
|
ScreenUpdate();
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void ScreenUpdate() {
|
|
lock (@lock) {
|
|
var status = GetFormattedStatus(currentUserRead);
|
|
var output = prefix;
|
|
if (connectionState != "Ready")
|
|
output += $" | {connectionState}";
|
|
if (currentChatUserId != 0)
|
|
output += status;
|
|
else
|
|
output += "]";
|
|
output += " > ";
|
|
var prefixlen = GetActualStringWidth(output);
|
|
var inputLine = GetPagedMessageInputLine(currentInputLine, currentInputPos, Console.LargestWindowWidth - prefixlen);
|
|
output += inputLine.messageBuffer;
|
|
|
|
ClearCurrentConsoleLine();
|
|
messageQueue.ForEach(p => Console.WriteLine(p + Ansi.ResetAll));
|
|
if (messageQueue.Count > 0 && !silent)
|
|
Console.Write("\a"); //ring terminal bell
|
|
messageQueue.Clear();
|
|
Console.Write(output);
|
|
Console.Write($"\u001b[{inputLine.relCursorPos + prefixlen + 1}G");
|
|
}
|
|
}
|
|
|
|
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[1..];
|
|
SetInputLine("");
|
|
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 <query>");
|
|
ScreenUpdate();
|
|
return;
|
|
}
|
|
case ConsoleKey.Enter:
|
|
SendMessage(currentInputLine, currentChatId);
|
|
SetInputLine("");
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.Backspace when currentInputLine.Length >= 1 && currentInputPos >= 1:
|
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) {
|
|
RemoveFromInputLine(true);
|
|
ScreenUpdate();
|
|
return;
|
|
}
|
|
|
|
RemoveFromInputLine();
|
|
//if (currentInputLine.EndsWith("⏎"))
|
|
// currentInputLine = currentInputLine.Remove(currentInputLine.Length - 1);
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.Delete when currentInputLine.Length >= 1 && currentInputPos < currentInputLine.Length:
|
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) {
|
|
ScreenUpdate();
|
|
return;
|
|
}
|
|
|
|
RemoveFromInputLineForward();
|
|
//if (currentInputLine.EndsWith("⏎"))
|
|
// currentInputLine = currentInputLine.Remove(currentInputLine.Length - 1);
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.B when key.Modifiers.HasFlag(ConsoleModifiers.Alt):
|
|
case ConsoleKey.LeftArrow when key.Modifiers.HasFlag(ConsoleModifiers.Alt):
|
|
if (currentInputPos == 0)
|
|
break;
|
|
|
|
var part1 = currentInputLine[..currentInputPos];
|
|
var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal);
|
|
if (lastIndex > 0)
|
|
lastIndex++;
|
|
if (lastIndex < 0)
|
|
lastIndex = 0;
|
|
currentInputPos = lastIndex;
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.F when key.Modifiers.HasFlag(ConsoleModifiers.Alt):
|
|
case ConsoleKey.RightArrow when key.Modifiers.HasFlag(ConsoleModifiers.Alt):
|
|
if (currentInputPos >= currentInputLine.Length)
|
|
break;
|
|
|
|
var index = currentInputLine.IndexOf(" ", currentInputPos + 1, StringComparison.Ordinal);
|
|
currentInputPos = index;
|
|
if (index < 0)
|
|
currentInputPos = currentInputLine.Length;
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.LeftArrow:
|
|
if (currentInputPos > 0)
|
|
currentInputPos--;
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.RightArrow:
|
|
if (currentInputPos < currentInputLine.Length)
|
|
currentInputPos++;
|
|
ScreenUpdate();
|
|
break;
|
|
case ConsoleKey.UpArrow: break;
|
|
case ConsoleKey.DownArrow: break;
|
|
default: {
|
|
switch (key.Key) {
|
|
//case ConsoleKey.N when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
// InsertToInputLine("⏎ ");
|
|
// ScreenUpdate();
|
|
// return;
|
|
|
|
case ConsoleKey.Q when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
HandleCommand("q");
|
|
ScreenUpdate();
|
|
return;
|
|
case ConsoleKey.O when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
SetInputLine(currentInputLine switch {
|
|
"/o " => "/os ",
|
|
"/os " => "/o ",
|
|
"" => "/o ",
|
|
_ => currentInputLine
|
|
});
|
|
ScreenUpdate();
|
|
return;
|
|
|
|
// standard terminal commands
|
|
case ConsoleKey.L when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
HandleCommand("cl");
|
|
ScreenUpdate();
|
|
return;
|
|
case ConsoleKey.D when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
HandleCommand(currentChatId == 0 ? "q" : "c");
|
|
ScreenUpdate();
|
|
return;
|
|
|
|
// input navigation
|
|
case ConsoleKey.U when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
SetInputLine("");
|
|
ScreenUpdate();
|
|
return;
|
|
case ConsoleKey.A when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
currentInputPos = 0;
|
|
ScreenUpdate();
|
|
return;
|
|
case ConsoleKey.E when key.Modifiers.HasFlag(ConsoleModifiers.Control):
|
|
currentInputPos = currentInputLine.Length;
|
|
ScreenUpdate();
|
|
return;
|
|
}
|
|
|
|
if (!SpecialKeys.Contains(key.Key)) {
|
|
InsertToInputLine(key.KeyChar.ToString());
|
|
ScreenUpdate();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void OnAuthUpdate(Update.UpdateAuthorizationState state) {
|
|
switch (state.AuthorizationState) {
|
|
case AuthorizationState.AuthorizationStateWaitTdlibParameters _:
|
|
client.Send(new SetTdlibParameters {
|
|
ApiId = 600606,
|
|
ApiHash = "c973f46778be4b35481ce45e93271e82",
|
|
DatabaseDirectory = dbdir,
|
|
UseMessageDatabase = true,
|
|
SystemLanguageCode = "en_US",
|
|
DeviceModel = Environment.MachineName,
|
|
SystemVersion = ".NET Core CLR " + Environment.Version,
|
|
ApplicationVersion = version,
|
|
EnableStorageOptimizer = true,
|
|
UseSecretChats = true
|
|
});
|
|
break;
|
|
// case AuthorizationState.AuthorizationStateWaitEncryptionKey _:
|
|
// client.Send(new Td.TdApi.CheckDatabaseEncryptionKey());
|
|
// break;
|
|
case AuthorizationState.AuthorizationStateWaitPhoneNumber _: {
|
|
Console.Write("[tgcli] login> ");
|
|
var phone = Console.ReadLine();
|
|
client.Send(new SetAuthenticationPhoneNumber { PhoneNumber = phone });
|
|
break;
|
|
}
|
|
case AuthorizationState.AuthorizationStateWaitCode _: {
|
|
Console.Write("[tgcli] code> ");
|
|
var code = Console.ReadLine();
|
|
client.Send(new CheckAuthenticationCode { Code = code });
|
|
break;
|
|
}
|
|
case AuthorizationState.AuthorizationStateWaitPassword _: {
|
|
Console.Write("[tgcli] 2fa password> ");
|
|
var pass = ReadConsolePassword();
|
|
client.Send(new CheckAuthenticationPassword { Password = pass });
|
|
break;
|
|
}
|
|
case AuthorizationState.AuthorizationStateReady _:
|
|
Console.WriteLine("[tgcli] logged in.");
|
|
authorized = true;
|
|
connectionState = "Ready";
|
|
break;
|
|
case AuthorizationState.AuthorizationStateClosed _:
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Logged out successfully. All local data has been deleted.");
|
|
ScreenUpdate();
|
|
Environment.Exit(0);
|
|
break;
|
|
case AuthorizationState.AuthorizationStateClosing _:
|
|
messageQueue.Add($"{Ansi.Yellow}[tgcli] Logging out...");
|
|
ScreenUpdate();
|
|
break;
|
|
case 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 string FormatMessage(Message msg) {
|
|
string text;
|
|
if (msg.Content is MessageContent.MessageText messageText)
|
|
text = messageText.Text.Text;
|
|
else if (msg.Content is 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 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 chat = GetChat(msg.ChatId);
|
|
var username = TruncateString(GetFormattedUsername(msg.SenderId), 10);
|
|
var time = FormatTime(msg.Date);
|
|
var isChannel = msg.IsChannelPost;
|
|
var isPrivate = chat.Type is ChatType.ChatTypePrivate || chat.Type is ChatType.ChatTypeSecret;
|
|
var isSecret = chat.Type is ChatType.ChatTypeSecret;
|
|
var isReply = msg.ReplyToMessageId != 0;
|
|
|
|
chat.Title = TruncateString(chat.Title, 20);
|
|
|
|
Message replyMessage;
|
|
|
|
var msgPrefix = $"{Ansi.Bold}{Ansi.Green}[{time}] {(isSecret ? $"{Ansi.Red}[sec] " : "")}{Ansi.Cyan}{chat.Title} "
|
|
+ $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}";
|
|
var finalOutput = msgPrefix;
|
|
|
|
var indent = new string(' ', GetActualStringWidth(msgPrefix));
|
|
var arrows = $"{(msg.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} ";
|
|
|
|
if (isReply) {
|
|
try {
|
|
replyMessage = GetMessage(chat.Id, msg.ReplyToMessageId);
|
|
finalOutput = $"{FormatMessageReply(replyMessage, msgPrefix)}";
|
|
}
|
|
catch {
|
|
//ignored; reply to deleted msg
|
|
}
|
|
}
|
|
|
|
var rest = $"{text}{(msg.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}";
|
|
|
|
if (msg.InteractionInfo != null && msg.InteractionInfo.Reactions.Any(p => p.Type is ReactionType.ReactionTypeEmoji)) {
|
|
rest = $"{rest} {Ansi.Cyan}<--";
|
|
foreach (var reaction in msg.InteractionInfo.Reactions)
|
|
if (reaction.Type is ReactionType.ReactionTypeEmoji emoji)
|
|
rest += $" {reaction.TotalCount} {emoji.Emoji}";
|
|
}
|
|
|
|
var lines = rest.Split("\n").ToList();
|
|
if (!isReply) {
|
|
finalOutput += arrows + lines.First();
|
|
lines.RemoveAt(0);
|
|
}
|
|
|
|
lines.ForEach(l => finalOutput += "\n" + indent + arrows + l);
|
|
|
|
return finalOutput;
|
|
}
|
|
|
|
public static string FormatMessageReply(Message msg, string origPrefix) {
|
|
string text;
|
|
if (msg.Content is MessageContent.MessageText messageText)
|
|
text = messageText.Text.Text;
|
|
else
|
|
text = $"[unsupported {msg.Content.DataType}]";
|
|
var chat = GetChat(msg.ChatId);
|
|
var username = TruncateString(GetFormattedUsername(msg.SenderId), 10);
|
|
var time = FormatTime(msg.Date);
|
|
var isChannel = msg.IsChannelPost;
|
|
var isPrivate = chat.Type is ChatType.ChatTypePrivate or ChatType.ChatTypeSecret;
|
|
var isSecret = chat.Type is ChatType.ChatTypeSecret;
|
|
|
|
chat.Title = TruncateString(chat.Title, 20);
|
|
|
|
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} ")}";
|
|
|
|
var indent = new string(' ', GetActualStringWidth(replyPrefix));
|
|
var arrows = $"{(msg.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} ";
|
|
|
|
var rest = $"{text}{(msg.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}";
|
|
|
|
finalOutput += replyPrefix;
|
|
|
|
var lines = rest.Split("\n").ToList();
|
|
finalOutput += arrows + lines.First();
|
|
lines.RemoveAt(0);
|
|
lines.ForEach(l => finalOutput += "\n" + indent + arrows + l);
|
|
|
|
return finalOutput;
|
|
}
|
|
|
|
private static string FormatMessage(Update.UpdateMessageContent msg) {
|
|
string text;
|
|
if (msg.NewContent is MessageContent.MessageText messageText)
|
|
text = messageText.Text.Text;
|
|
else
|
|
text = $"[unsupported {msg.NewContent.DataType}]";
|
|
var message = GetMessage(msg.ChatId, msg.MessageId);
|
|
var chat = GetChat(msg.ChatId);
|
|
var username = TruncateString(GetFormattedUsername(message.SenderId), 10);
|
|
var time = FormatTime(message.EditDate);
|
|
var isChannel = message.IsChannelPost;
|
|
var isPrivate = chat.Type is ChatType.ChatTypePrivate;
|
|
chat.Title = TruncateString(chat.Title, 20);
|
|
|
|
return $"{Ansi.Bold}{Ansi.Green}[{time}] {Ansi.Cyan}{chat.Title} "
|
|
+ $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"
|
|
+ $"{(message.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} "
|
|
+ $"{text}"
|
|
+ $"{Ansi.Yellow}*";
|
|
}
|
|
|
|
private static string FormatMessage(Update.UpdateMessageUnreadReactions msg) {
|
|
string text;
|
|
|
|
var message = GetMessage(msg.ChatId, msg.MessageId);
|
|
if (message.Content is MessageContent.MessageText messageText)
|
|
text = messageText.Text.Text;
|
|
else
|
|
text = $"[unsupported {message.Content.DataType}]";
|
|
var chat = GetChat(msg.ChatId);
|
|
var username = TruncateString(GetFormattedUsername(message.SenderId), 10);
|
|
var time = FormatTime(message.Date);
|
|
var isChannel = message.IsChannelPost;
|
|
var isPrivate = chat.Type is ChatType.ChatTypePrivate;
|
|
chat.Title = TruncateString(chat.Title, 20);
|
|
|
|
text = $"{text}{Ansi.Yellow} <-- ";
|
|
foreach (var reaction in msg.UnreadReactions)
|
|
if (reaction.Type is ReactionType.ReactionTypeEmoji emoji)
|
|
text += $"{emoji.Emoji} ({GetFormattedUsername(reaction.SenderId)})";
|
|
|
|
return $"{Ansi.Bold}{Ansi.Green}[{time}] {Ansi.Cyan}{chat.Title} "
|
|
+ $"{(isPrivate || isChannel ? "" : $"{Ansi.Yellow}{username} ")}"
|
|
+ $"{(message.IsOutgoing ? $"{Ansi.Blue}»»»" : $"{Ansi.Magenta}«««")} "
|
|
+ $"{(message.EditDate == 0 ? "" : $"{Ansi.Yellow}*")}"
|
|
+ $"{text}";
|
|
}
|
|
|
|
public static void AddMessageToQueue(Message msg) {
|
|
//handle muted
|
|
if (IsMuted(GetChat(msg.ChatId)) && currentChatId != msg.ChatId)
|
|
return;
|
|
|
|
//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(Update.UpdateMessageContent msg) {
|
|
//handle muted
|
|
if (IsMuted(GetChat(msg.ChatId)) && 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();
|
|
}
|
|
|
|
public static void AddMessageToQueue(Update.UpdateMessageUnreadReactions msg) {
|
|
//handle muted
|
|
if (IsMuted(GetChat(msg.ChatId)) && currentChatId != msg.ChatId)
|
|
return;
|
|
|
|
if (!msg.UnreadReactions.Any(p => p.Type is ReactionType.ReactionTypeEmoji))
|
|
return;
|
|
|
|
var formattedMessage = FormatMessage(msg);
|
|
|
|
if (currentChatId != 0 && msg.ChatId != currentChatId)
|
|
lock (@lock)
|
|
missedMessages.Add(formattedMessage);
|
|
else
|
|
lock (@lock)
|
|
messageQueue.Add(formattedMessage);
|
|
ScreenUpdate();
|
|
}
|
|
}
|