using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Alpha; using Alpha.Core.Command; using Alpha.Core.Util; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Fomo; using Fomo.Core; using FomoTelegram.Commands; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AndrewLin")] [assembly: AssemblyConfiguration("Publish")] [assembly: AssemblyDescription("FomoTelegram: A Fomo submod that relays On-Together chat messages to a Telegram chat via a Telegram Bot. Use /fomohelp and look for /fomotelegram commands")] [assembly: AssemblyFileVersion("1.2.9.0")] [assembly: AssemblyInformationalVersion("1.2.9+93bf6c254192ca3bde9494f062dab68782362cda")] [assembly: AssemblyProduct("AndrewLin.FomoTelegram")] [assembly: AssemblyTitle("AndrewLin.FomoTelegram")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")] [assembly: AssemblyVersion("1.2.9.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace FomoTelegram { public static class BuildInfo { public const string Version = "1.2.9"; } public sealed class FomoTelegramManager : IDisposable { public const string PlaceholderApiKey = "YOUR_BOT_TOKEN_HERE"; public const string PlaceholderChatId = "YOUR_CHAT_ID_HERE"; private static readonly TimeSpan MinSendInterval = TimeSpan.FromMilliseconds(50.0); private readonly ManualLogSource _log = Logger.CreateLogSource("FomoTelegram.Manager"); private readonly HttpClient _http = new HttpClient { Timeout = TimeSpan.FromSeconds(40.0) }; private readonly string _apiBase; private readonly string _chatId; private readonly long _chatIdLong; private readonly ConcurrentQueue _sendQueue = new ConcurrentQueue(); private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private readonly Task _workerTask; public bool IsReady { get; private set; } public FomoTelegramManager(string apiKey, string chatId) { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; _apiBase = "https://api.telegram.org/bot" + apiKey; _chatId = chatId; long.TryParse(chatId, out _chatIdLong); _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); ValidateAndStartReceiverAsync(); } public void Enqueue(string text) { if (IsReady) { _sendQueue.Enqueue(text); } } public async Task SendDirectAsync(string text) { try { string json = JsonConvert.SerializeObject((object)new { chat_id = _chatId, text = text }); using StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); HttpResponseMessage resp = await _http.PostAsync(_apiBase + "/sendMessage", content); if (!resp.IsSuccessStatusCode) { string respBody = await resp.Content.ReadAsStringAsync(); _log.LogWarning((object)$"Telegram send failed ({resp.StatusCode}): {respBody}"); } } catch (Exception ex2) { Exception ex = ex2; _log.LogWarning((object)("Telegram send failed: " + ex.Message)); } } private async Task ValidateAndStartReceiverAsync() { try { _log.LogInfo((object)("Validating Telegram credentials against " + _apiBase + "/getMe ...")); HttpResponseMessage resp = await _http.GetAsync(_apiBase + "/getMe"); string raw = await resp.Content.ReadAsStringAsync(); _log.LogDebug((object)$"getMe HTTP {(int)resp.StatusCode}: {raw}"); JObject json = JObject.Parse(raw); JToken obj = json["ok"]; if (obj == null || !Extensions.Value((IEnumerable)obj)) { JToken obj2 = json["description"]; string description = ((obj2 != null) ? Extensions.Value((IEnumerable)obj2) : null) ?? raw; JToken obj3 = json["error_code"]; int errorCode = ((obj3 != null) ? Extensions.Value((IEnumerable)obj3) : 0); _log.LogError((object)$"Telegram validation failed (HTTP {(int)resp.StatusCode}, error_code={errorCode}): {description}. Check your API key in the config."); return; } JToken obj4 = json["result"]; object obj5; if (obj4 == null) { obj5 = null; } else { JToken obj6 = obj4[(object)"username"]; obj5 = ((obj6 != null) ? Extensions.Value((IEnumerable)obj6) : null); } if (obj5 == null) { obj5 = "unknown"; } string username = (string)obj5; JToken obj7 = json["result"]; long? obj8; if (obj7 == null) { obj8 = null; } else { JToken obj9 = obj7[(object)"id"]; obj8 = ((obj9 != null) ? new long?(Extensions.Value((IEnumerable)obj9)) : null); } long? num = obj8; long id = num.GetValueOrDefault(); _log.LogInfo((object)$"Connected to Telegram as @{username} (id={id})."); IsReady = true; } catch (HttpRequestException ex3) { HttpRequestException ex2 = ex3; _log.LogError((object)"Telegram validation failed - network error reaching api.telegram.org. This is NOT an API key issue."); _log.LogError((object)(" HttpRequestException: " + ex2.Message)); if (ex2.InnerException != null) { _log.LogError((object)(" Caused by (" + ex2.InnerException.GetType().Name + "): " + ex2.InnerException.Message)); } if (ex2.InnerException?.InnerException != null) { _log.LogError((object)(" Root cause (" + ex2.InnerException.InnerException.GetType().Name + "): " + ex2.InnerException.InnerException.Message)); } _log.LogError((object)" Possible causes: no internet, firewall blocking api.telegram.org, TLS negotiation failure on Mono, or missing root certificates."); return; } catch (Exception ex4) { Exception ex = ex4; _log.LogError((object)("Telegram validation failed - unexpected " + ex.GetType().Name + ": " + ex.Message)); if (ex.InnerException != null) { _log.LogError((object)(" Caused by (" + ex.InnerException.GetType().Name + "): " + ex.InnerException.Message)); } return; } Task.Run(() => ReceiveLoopAsync(_cts.Token)); _log.LogInfo((object)"Telegram inbound receiver started."); } private async Task ReceiveLoopAsync(CancellationToken ct) { long offset = await DropPendingUpdatesAsync(ct); while (!ct.IsCancellationRequested) { try { string url = $"{_apiBase}/getUpdates?offset={offset}&timeout=30&allowed_updates=%5B%22message%22%5D"; JObject json = JObject.Parse(await (await _http.GetAsync(url, ct)).Content.ReadAsStringAsync()); JToken obj = json["ok"]; if (obj == null || !Extensions.Value((IEnumerable)obj)) { continue; } JToken obj2 = json["result"]; JArray updates = (JArray)(object)((obj2 is JArray) ? obj2 : null); if (updates == null) { continue; } foreach (JToken update in updates) { JToken obj3 = update[(object)"update_id"]; long updateId = ((obj3 != null) ? Extensions.Value((IEnumerable)obj3) : 0); offset = updateId + 1; JToken msg = update[(object)"message"]; if (msg == null) { continue; } JToken obj4 = msg[(object)"text"]; string text = ((obj4 != null) ? Extensions.Value((IEnumerable)obj4) : null); if (string.IsNullOrEmpty(text)) { continue; } JToken obj5 = msg[(object)"chat"]; long? obj6; if (obj5 == null) { obj6 = null; } else { JToken obj7 = obj5[(object)"id"]; obj6 = ((obj7 != null) ? new long?(Extensions.Value((IEnumerable)obj7)) : null); } long? num = obj6; long chatId = num.GetValueOrDefault(); if (_chatIdLong == 0L || chatId == _chatIdLong) { JToken obj8 = msg[(object)"from"]; bool? obj9; if (obj8 == null) { obj9 = null; } else { JToken obj10 = obj8[(object)"is_bot"]; obj9 = ((obj10 != null) ? new bool?(Extensions.Value((IEnumerable)obj10)) : null); } bool? flag = obj9; if (!flag.GetValueOrDefault()) { FomoPlugin.DispatchIncomingText(text); } } } } catch (OperationCanceledException) { break; } catch (Exception ex) { _log.LogWarning((object)("Telegram polling error: " + ex.Message)); try { await Task.Delay(5000, ct); } catch (OperationCanceledException) { break; } } } } private async Task DropPendingUpdatesAsync(CancellationToken ct) { try { string url = _apiBase + "/getUpdates?timeout=0"; JObject json = JObject.Parse(await (await _http.GetAsync(url, ct)).Content.ReadAsStringAsync()); JToken val = json["result"]; JArray updates = (JArray)(object)((val is JArray) ? val : null); if (updates != null && ((JContainer)updates).Count > 0) { JToken obj = updates[((JContainer)updates).Count - 1][(object)"update_id"]; long lastId = ((obj != null) ? Extensions.Value((IEnumerable)obj) : 0); return lastId + 1; } } catch { } return 0L; } private async Task WorkerLoopAsync(CancellationToken ct) { _log.LogInfo((object)"Telegram send-worker started."); while (!ct.IsCancellationRequested) { try { if (_sendQueue.TryDequeue(out string text)) { await SendDirectAsync(text); try { await Task.Delay(MinSendInterval, ct); } catch (OperationCanceledException) { goto end_IL_005f; } } else { try { await Task.Delay(100, ct); } catch (OperationCanceledException) { goto end_IL_005f; } } text = null; continue; end_IL_005f:; } catch (OperationCanceledException) { } catch (Exception ex) { _log.LogError((object)$"Unexpected error in Telegram send-worker: {ex}"); try { await Task.Delay(1000, ct); } catch (OperationCanceledException) { goto end_IL_0226; } continue; end_IL_0226:; } break; } _log.LogInfo((object)"Telegram send-worker stopped."); } public void Dispose() { _cts.Cancel(); try { _workerTask.Wait(TimeSpan.FromSeconds(2.0)); } catch { } _cts.Dispose(); _http.Dispose(); } } [BepInPlugin("com.andrewlin.ontogether.fomotelegram", "FomoTelegram", "1.2.9")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class FomoTelegramPlugin : BaseUnityPlugin { public const string ModGUID = "com.andrewlin.ontogether.fomotelegram"; public const string ModName = "FomoTelegram"; public const string ModVersion = "1.2.9"; private static TelegramChatSink? _sink; internal static ManualLogSource Log = null; public static ConfigEntry? EnableFeature { get; private set; } public static ConfigEntry? TelegramBotApiKey { get; private set; } public static ConfigEntry? TelegramChatId { get; private set; } public static ConfigEntry? RelayGlobalChat { get; private set; } public static ConfigEntry? RelayLocalChat { get; private set; } public static ConfigEntry? RelayNotifications { get; private set; } public static ConfigEntry? MessageFormat { get; private set; } public static ConfigEntry? NotificationFormat { get; private set; } public static FomoTelegramManager? TelegramManager { get; private set; } public static string ConfigPath { get; private set; } = "BepInEx/config/com.andrewlin.ontogether.fomotelegram.cfg"; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"FomoTelegram v1.2.9 loading…"); InitConfig(); ConfigPath = ((BaseUnityPlugin)this).Config.ConfigFilePath; Log.LogInfo((object)("Config file path: " + ConfigPath)); ChatCommandManager commandManager = AlphaPlugin.CommandManager; if (commandManager != null) { commandManager.Register((IChatCommand)(object)new FomoTelegramToggleCommand()); } ChatCommandManager commandManager2 = AlphaPlugin.CommandManager; if (commandManager2 != null) { commandManager2.Register((IChatCommand)(object)new FomoTelegramMessageFormatCommand()); } ChatCommandManager commandManager3 = AlphaPlugin.CommandManager; if (commandManager3 != null) { commandManager3.Register((IChatCommand)(object)new FomoTelegramNotificationFormatCommand()); } ChatCommandManager commandManager4 = AlphaPlugin.CommandManager; if (commandManager4 != null) { commandManager4.Register((IChatCommand)(object)new FomoTelegramSetupInfoCommand()); } ChatCommandManager commandManager5 = AlphaPlugin.CommandManager; if (commandManager5 != null) { commandManager5.Register((IChatCommand)(object)new FomoTelegramReloadConfigCommand()); } ChatCommandManager commandManager6 = AlphaPlugin.CommandManager; if (commandManager6 != null) { commandManager6.Register((IChatCommand)(object)new FomoTelegramRestartCommand()); } string text = TelegramBotApiKey?.Value ?? string.Empty; string text2 = TelegramChatId?.Value ?? string.Empty; if (string.IsNullOrWhiteSpace(text) || text == "YOUR_BOT_TOKEN_HERE") { Log.LogWarning((object)("TelegramBotApiKey is not set. Edit config and restart: " + ConfigPath)); } else if (string.IsNullOrWhiteSpace(text2) || text2 == "YOUR_CHAT_ID_HERE") { Log.LogWarning((object)("TelegramChatId is not set. Edit config and restart: " + ConfigPath)); } else { TelegramManager = new FomoTelegramManager(text, text2); _sink = new TelegramChatSink(TelegramManager); ChatSinkManager sinkManager = FomoPlugin.SinkManager; if (sinkManager != null) { sinkManager.Register((IChatSink)(object)_sink); } Log.LogInfo((object)"TelegramChatSink registered with Fomo SinkManager."); } Log.LogInfo((object)"FomoTelegram loaded."); } private void OnDestroy() { TelegramManager?.Dispose(); } internal static string ReloadConfig() { ConfigEntry? telegramBotApiKey = TelegramBotApiKey; if (telegramBotApiKey != null) { ConfigFile configFile = ((ConfigEntryBase)telegramBotApiKey).ConfigFile; if (configFile != null) { configFile.Reload(); } } string apiKey = TelegramBotApiKey?.Value ?? string.Empty; string text = TelegramChatId?.Value ?? string.Empty; string text2 = MaskApiKey(apiKey); return "ApiKey = " + text2 + "\nChatId = " + text; } internal static string RestartTelegram() { ConfigEntry? telegramBotApiKey = TelegramBotApiKey; if (telegramBotApiKey != null) { ConfigFile configFile = ((ConfigEntryBase)telegramBotApiKey).ConfigFile; if (configFile != null) { configFile.Reload(); } } string text = TelegramBotApiKey?.Value ?? string.Empty; string text2 = TelegramChatId?.Value ?? string.Empty; if (string.IsNullOrWhiteSpace(text) || text == "YOUR_BOT_TOKEN_HERE") { return "Cannot restart: TelegramBotApiKey is not set."; } if (string.IsNullOrWhiteSpace(text2) || text2 == "YOUR_CHAT_ID_HERE") { return "Cannot restart: TelegramChatId is not set."; } TelegramManager?.Dispose(); TelegramManager = new FomoTelegramManager(text, text2); if (_sink != null) { _sink.SwapManager(TelegramManager); } else { _sink = new TelegramChatSink(TelegramManager); ChatSinkManager sinkManager = FomoPlugin.SinkManager; if (sinkManager != null) { sinkManager.Register((IChatSink)(object)_sink); } } string text3 = MaskApiKey(text); return "Telegram restarted.\nApiKey = " + text3 + "\nChatId = " + text2; } internal static string MaskApiKey(string apiKey) { if (string.IsNullOrWhiteSpace(apiKey) || apiKey == "YOUR_BOT_TOKEN_HERE") { return apiKey; } return (apiKey.Length > 8) ? (apiKey.Substring(0, 4) + "..." + apiKey.Substring(apiKey.Length - 4)) : "****"; } private void InitConfig() { EnableFeature = ((BaseUnityPlugin)this).Config.Bind("General", "EnableFeature", true, "Master switch - disable to stop all Telegram forwarding without removing the plugin."); TelegramBotApiKey = ((BaseUnityPlugin)this).Config.Bind("Telegram", "TelegramBotApiKey", "YOUR_BOT_TOKEN_HERE", "Your Telegram Bot API token from @BotFather (e.g. 123456:ABC-DEF…)."); TelegramChatId = ((BaseUnityPlugin)this).Config.Bind("Telegram", "TelegramChatId", "YOUR_CHAT_ID_HERE", "Telegram chat / group / channel ID to forward messages to. Use @userinfobot or the Telegram API to find your chat ID. e.g. 1234567890"); RelayGlobalChat = ((BaseUnityPlugin)this).Config.Bind("Filters", "RelayGlobalChat", true, "Forward global chat messages to Telegram."); RelayLocalChat = ((BaseUnityPlugin)this).Config.Bind("Filters", "RelayLocalChat", true, "Forward local chat messages to Telegram."); RelayNotifications = ((BaseUnityPlugin)this).Config.Bind("Filters", "RelayNotifications", true, "Forward system notifications (joins, leaves, etc.) to Telegram."); MessageFormat = ((BaseUnityPlugin)this).Config.Bind("Formatting", "MessageFormat", "[{channel:short}{distance}] {username}: {message}", "Format string for chat messages sent to Telegram.\nPlaceholders: {timestamp[:fmt]}, {channel[:short]}, {username}, {message}, {distance}, {source}, {playerid}"); NotificationFormat = ((BaseUnityPlugin)this).Config.Bind("Formatting", "NotificationFormat", "{message}", "Format string for system notifications sent to Telegram.\nPlaceholders: {timestamp[:fmt]}, {channel[:short]}, {username}, {message}, {distance}, {source}, {playerid}"); } } public class TelegramChatSink : IChatSink { private static readonly ManualLogSource _log = Logger.CreateLogSource("FomoTelegram.TCS"); private FomoTelegramManager _manager; public TelegramChatSink(FomoTelegramManager manager) { _manager = manager; } internal void SwapManager(FomoTelegramManager manager) { _manager = manager; } public Task SendAsync(ChatEntry entry) { ConfigEntry? enableFeature = FomoTelegramPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { return Task.CompletedTask; } if (!_manager.IsReady) { return Task.CompletedTask; } if (entry.Channel == null || entry.UserName == null) { ConfigEntry? relayNotifications = FomoTelegramPlugin.RelayNotifications; if (relayNotifications == null || !relayNotifications.Value) { return Task.CompletedTask; } } else if (entry.Channel != null && entry.Channel.StartsWith("Local")) { ConfigEntry? relayLocalChat = FomoTelegramPlugin.RelayLocalChat; if (relayLocalChat == null || !relayLocalChat.Value) { return Task.CompletedTask; } } else { ConfigEntry? relayGlobalChat = FomoTelegramPlugin.RelayGlobalChat; if (relayGlobalChat == null || !relayGlobalChat.Value) { return Task.CompletedTask; } } if (entry.IsNotification) { string text = ChatEntryFormatter.Format(entry, FomoTelegramPlugin.NotificationFormat?.Value); _manager.Enqueue(text); } else { string text2 = ChatEntryFormatter.Format(entry, FomoTelegramPlugin.MessageFormat?.Value); _manager.Enqueue(text2); } return Task.CompletedTask; } } public static class MyPluginInfo { public const string PLUGIN_GUID = "AndrewLin.FomoTelegram"; public const string PLUGIN_NAME = "AndrewLin.FomoTelegram"; public const string PLUGIN_VERSION = "1.2.9"; } } namespace FomoTelegram.Commands { public class FomoTelegramMessageFormatCommand : IChatCommand, IComparable { public const string CMD = "fomotelegrammessageformat"; public string Name => "fomotelegrammessageformat"; public string ShortName => "ftmf"; public string Description => "Set or get the Telegram message format."; public string Namespace => "fomo"; public void Execute(string[] args) { ConfigEntry? enableFeature = FomoTelegramPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { ChatUtils.AddGlobalNotification("Telegram feature is disabled."); } else if (FomoTelegramPlugin.MessageFormat == null) { ChatUtils.AddGlobalNotification("Fomo Telegram Mod is not initialized yet."); } else if (args.Length == 0) { ChatUtils.AddGlobalNotification("Telegram message format: " + FomoTelegramPlugin.MessageFormat?.Value); } else if (args.Length != 0) { string text = string.Join(" ", args); if (string.IsNullOrWhiteSpace(text)) { ChatUtils.AddGlobalNotification("Please enter a valid Telegram message format."); return; } FomoTelegramPlugin.MessageFormat.Value = text; ChatUtils.AddGlobalNotification("Telegram message format is now set to: " + text + "."); } } } public class FomoTelegramNotificationFormatCommand : IChatCommand, IComparable { public const string CMD = "fomotelegramnotificationformat"; public string Name => "fomotelegramnotificationformat"; public string ShortName => "ftnf"; public string Description => "Set or get the Telegram notification format."; public string Namespace => "fomo"; public void Execute(string[] args) { ConfigEntry? enableFeature = FomoTelegramPlugin.EnableFeature; if (enableFeature == null || !enableFeature.Value) { ChatUtils.AddGlobalNotification("Telegram feature is disabled."); } else if (FomoTelegramPlugin.NotificationFormat == null) { ChatUtils.AddGlobalNotification("Fomo Telegram Mod is not initialized yet."); } else if (args.Length == 0) { ChatUtils.AddGlobalNotification("Telegram notification format: " + FomoTelegramPlugin.NotificationFormat?.Value); } else if (args.Length != 0) { string text = string.Join(" ", args); if (string.IsNullOrWhiteSpace(text)) { ChatUtils.AddGlobalNotification("Please enter a valid Telegram notification format."); return; } FomoTelegramPlugin.NotificationFormat.Value = text; ChatUtils.AddGlobalNotification("Telegram notification format is now set to: " + text + "."); } } } public class FomoTelegramReloadConfigCommand : IChatCommand, IComparable { public string Name => "fomotelegramreloadconfig"; public string ShortName => "ftrc"; public string Description => "Reload Telegram config from disk and show current API key and chat ID."; public string Namespace => "fomo"; public void Execute(string[] args) { string text = FomoTelegramPlugin.ReloadConfig(); ChatUtils.AddGlobalNotification("Config reloaded.\n" + text); } } public class FomoTelegramRestartCommand : IChatCommand, IComparable { public string Name => "fomotelegramrestart"; public string ShortName => "ftr"; public string Description => "Reload config and restart the Telegram connection."; public string Namespace => "fomo"; public void Execute(string[] args) { string text = FomoTelegramPlugin.RestartTelegram(); ChatUtils.AddGlobalNotification(text); } } public class FomoTelegramSetupInfoCommand : IChatCommand, IComparable { public const string CMD = "fomotelegramsetupinfo"; public string Name => "fomotelegramsetupinfo"; public string ShortName => "ftsinfo"; public string Description => "Instruction to set up the Telegram integration."; public string Namespace => "fomo"; public void Execute(string[] args) { ChatUtils.AddGlobalNotification("To set up Telegram integration you need:\n1. A Telegram Bot API key from @BotFather.\n2. Your Telegram chat ID from @userinfobot.\n3. Update the API key and chat ID in the mod configuration.\nConfig path: " + FomoTelegramPlugin.ConfigPath + "\n4. Reload config with /ftrc.\n5. Restart telegram manager with /ftr."); } } public class FomoTelegramToggleCommand : IChatCommand, IComparable { public const string CMD = "fomotelegramtoggle"; public string Name => "fomotelegramtoggle"; public string ShortName => "ftt"; public string Description => "Toggle Telegram chat feature on/off."; public string Namespace => "fomo"; public void Execute(string[] args) { if (FomoTelegramPlugin.EnableFeature != null) { FomoTelegramPlugin.EnableFeature.Value = !FomoTelegramPlugin.EnableFeature.Value; ChatUtils.AddGlobalNotification("Fomo Telegram is now " + (FomoTelegramPlugin.EnableFeature.Value ? "enabled" : "disabled") + "."); } } } }