Decompiled source of FomoTelegram v0.2.3

AndrewLin.FomoTelegram.dll

Decompiled 2 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Fomo;
using Fomo.Core;
using Fomo.Core.Util;
using FomoTelegram.Commands;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AndrewLin.FomoTelegram")]
[assembly: AssemblyConfiguration("Release")]
[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("0.2.3.0")]
[assembly: AssemblyInformationalVersion("0.2.3+dc290028f2302c7953f8cdfe5b05908e8e411cbb")]
[assembly: AssemblyProduct("AndrewLin.FomoTelegram")]
[assembly: AssemblyTitle("AndrewLin.FomoTelegram")]
[assembly: AssemblyVersion("0.2.3.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 = "0.2.3";
	}
	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<string> _sendQueue = new ConcurrentQueue<string>();

		private readonly CancellationTokenSource _cts = new CancellationTokenSource();

		private readonly Task _workerTask;

		public bool IsReady { get; private set; }

		public FomoTelegramManager(string apiKey, string chatId)
		{
			_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)
		{
			_ = 1;
			try
			{
				string content2 = JsonConvert.SerializeObject((object)new
				{
					chat_id = _chatId,
					text = text
				});
				using StringContent content = new StringContent(content2, Encoding.UTF8, "application/json");
				HttpResponseMessage resp = await _http.PostAsync(_apiBase + "/sendMessage", content);
				if (!resp.IsSuccessStatusCode)
				{
					string arg = await resp.Content.ReadAsStringAsync();
					_log.LogWarning((object)$"Telegram send failed ({resp.StatusCode}): {arg}");
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Telegram send failed: " + ex.Message));
			}
		}

		private async Task ValidateAndStartReceiverAsync()
		{
			_ = 1;
			try
			{
				string text = await (await _http.GetAsync(_apiBase + "/getMe")).Content.ReadAsStringAsync();
				JObject val = JObject.Parse(text);
				JToken obj = val["ok"];
				if (obj == null || !Extensions.Value<bool>((IEnumerable<JToken>)obj))
				{
					_log.LogError((object)("Telegram validation failed - check your API key. Response: " + text));
					return;
				}
				JToken obj2 = val["result"];
				object obj3;
				if (obj2 == null)
				{
					obj3 = null;
				}
				else
				{
					JToken obj4 = obj2[(object)"username"];
					obj3 = ((obj4 != null) ? Extensions.Value<string>((IEnumerable<JToken>)obj4) : null);
				}
				if (obj3 == null)
				{
					obj3 = "unknown";
				}
				string arg = (string)obj3;
				JToken obj5 = val["result"];
				long? obj6;
				if (obj5 == null)
				{
					obj6 = null;
				}
				else
				{
					JToken obj7 = obj5[(object)"id"];
					obj6 = ((obj7 != null) ? new long?(Extensions.Value<long>((IEnumerable<JToken>)obj7)) : null);
				}
				long? num = obj6;
				long valueOrDefault = num.GetValueOrDefault();
				_log.LogInfo((object)$"Connected to Telegram as @{arg} (id={valueOrDefault}).");
				IsReady = true;
			}
			catch (Exception ex)
			{
				_log.LogError((object)("Telegram validation failed - check your API key. Error: " + ex.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 requestUri = $"{_apiBase}/getUpdates?offset={offset}&timeout=30&allowed_updates=%5B%22message%22%5D";
					JObject val = JObject.Parse(await (await _http.GetAsync(requestUri, ct)).Content.ReadAsStringAsync());
					JToken obj = val["ok"];
					if (obj == null || !Extensions.Value<bool>((IEnumerable<JToken>)obj))
					{
						continue;
					}
					JToken obj2 = val["result"];
					JArray val2 = (JArray)(object)((obj2 is JArray) ? obj2 : null);
					if (val2 == null)
					{
						continue;
					}
					foreach (JToken item in val2)
					{
						JToken obj3 = item[(object)"update_id"];
						long num = ((obj3 != null) ? Extensions.Value<long>((IEnumerable<JToken>)obj3) : 0);
						offset = num + 1;
						JToken val3 = item[(object)"message"];
						if (val3 == null)
						{
							continue;
						}
						JToken obj4 = val3[(object)"text"];
						string text = ((obj4 != null) ? Extensions.Value<string>((IEnumerable<JToken>)obj4) : null);
						if (string.IsNullOrEmpty(text))
						{
							continue;
						}
						JToken obj5 = val3[(object)"chat"];
						long? obj6;
						if (obj5 == null)
						{
							obj6 = null;
						}
						else
						{
							JToken obj7 = obj5[(object)"id"];
							obj6 = ((obj7 != null) ? new long?(Extensions.Value<long>((IEnumerable<JToken>)obj7)) : null);
						}
						long? num2 = obj6;
						long valueOrDefault = num2.GetValueOrDefault();
						if (_chatIdLong == 0L || valueOrDefault == _chatIdLong)
						{
							JToken obj8 = val3[(object)"from"];
							bool? obj9;
							if (obj8 == null)
							{
								obj9 = null;
							}
							else
							{
								JToken obj10 = obj8[(object)"is_bot"];
								obj9 = ((obj10 != null) ? new bool?(Extensions.Value<bool>((IEnumerable<JToken>)obj10)) : null);
							}
							bool? flag = obj9;
							if (!flag.GetValueOrDefault())
							{
								ChatUtils.DispatchIncomingText(text);
							}
						}
					}
				}
				catch (OperationCanceledException)
				{
					break;
				}
				catch (Exception ex2)
				{
					_log.LogWarning((object)("Telegram polling error: " + ex2.Message));
					try
					{
						await Task.Delay(5000, ct);
					}
					catch (OperationCanceledException)
					{
						break;
					}
				}
			}
		}

		private async Task<long> DropPendingUpdatesAsync(CancellationToken ct)
		{
			_ = 1;
			try
			{
				string requestUri = _apiBase + "/getUpdates?timeout=0";
				JToken obj = JObject.Parse(await (await _http.GetAsync(requestUri, ct)).Content.ReadAsStringAsync())["result"];
				JArray val = (JArray)(object)((obj is JArray) ? obj : null);
				if (val != null && ((JContainer)val).Count > 0)
				{
					JToken obj2 = val[((JContainer)val).Count - 1][(object)"update_id"];
					return ((obj2 != null) ? Extensions.Value<long>((IEnumerable<JToken>)obj2) : 0) + 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 result))
					{
						await SendDirectAsync(result);
						try
						{
							await Task.Delay(MinSendInterval, ct);
						}
						catch (OperationCanceledException)
						{
							break;
						}
					}
					else
					{
						try
						{
							await Task.Delay(100, ct);
						}
						catch (OperationCanceledException)
						{
							break;
						}
					}
				}
				catch (OperationCanceledException)
				{
					break;
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"Unexpected error in Telegram send-worker: {arg}");
					try
					{
						await Task.Delay(1000, ct);
						goto end_IL_01c2;
					}
					catch (OperationCanceledException)
					{
					}
					break;
					end_IL_01c2:;
				}
			}
			_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", "0.2.3")]
	[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 = "0.2.3";

		internal static ManualLogSource Log = null;

		public static ConfigEntry<bool>? EnableFeature { get; private set; }

		public static ConfigEntry<string>? TelegramBotApiKey { get; private set; }

		public static ConfigEntry<string>? TelegramChatId { get; private set; }

		public static ConfigEntry<bool>? RelayGlobalChat { get; private set; }

		public static ConfigEntry<bool>? RelayLocalChat { get; private set; }

		public static ConfigEntry<bool>? RelayNotifications { get; private set; }

		public static ConfigEntry<string>? MessageFormat { get; private set; }

		public static ConfigEntry<string>? 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 v0.2.3 loading…");
			InitConfig();
			ConfigPath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			Log.LogInfo((object)("Config file path: " + ConfigPath));
			ChatCommandManager commandManager = FomoPlugin.CommandManager;
			if (commandManager != null)
			{
				commandManager.Register((IChatCommand)(object)new FomoTelegramToggleCommand());
			}
			ChatCommandManager commandManager2 = FomoPlugin.CommandManager;
			if (commandManager2 != null)
			{
				commandManager2.Register((IChatCommand)(object)new FomoTelegramMessageFormatCommand());
			}
			ChatCommandManager commandManager3 = FomoPlugin.CommandManager;
			if (commandManager3 != null)
			{
				commandManager3.Register((IChatCommand)(object)new FomoTelegramNotificationFormatCommand());
			}
			ChatCommandManager commandManager4 = FomoPlugin.CommandManager;
			if (commandManager4 != null)
			{
				commandManager4.Register((IChatCommand)(object)new FomoTelegramSetupInfoCommand());
			}
			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);
				TelegramChatSink telegramChatSink = new TelegramChatSink(TelegramManager);
				ChatSinkManager sinkManager = FomoPlugin.SinkManager;
				if (sinkManager != null)
				{
					sinkManager.Register((IChatSink)(object)telegramChatSink);
				}
				Log.LogInfo((object)"TelegramChatSink registered with Fomo SinkManager.");
			}
			Log.LogInfo((object)"FomoTelegram loaded.");
		}

		private void OnDestroy()
		{
			TelegramManager?.Dispose();
		}

		private void InitConfig()
		{
			EnableFeature = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableFeature", true, "Master switch - disable to stop all Telegram forwarding without removing the plugin.");
			TelegramBotApiKey = ((BaseUnityPlugin)this).Config.Bind<string>("Telegram", "TelegramBotApiKey", "YOUR_BOT_TOKEN_HERE", "Your Telegram Bot API token from @BotFather (e.g. 123456:ABC-DEF…).");
			TelegramChatId = ((BaseUnityPlugin)this).Config.Bind<string>("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.");
			RelayGlobalChat = ((BaseUnityPlugin)this).Config.Bind<bool>("Filters", "RelayGlobalChat", true, "Forward global chat messages to Telegram.");
			RelayLocalChat = ((BaseUnityPlugin)this).Config.Bind<bool>("Filters", "RelayLocalChat", true, "Forward local chat messages to Telegram.");
			RelayNotifications = ((BaseUnityPlugin)this).Config.Bind<bool>("Filters", "RelayNotifications", true, "Forward system notifications (joins, leaves, etc.) to Telegram.");
			MessageFormat = ((BaseUnityPlugin)this).Config.Bind<string>("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<string>("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 readonly FomoTelegramManager _manager;

		public TelegramChatSink(FomoTelegramManager manager)
		{
			_manager = manager;
		}

		public Task SendAsync(ChatEntry entry)
		{
			ConfigEntry<bool>? 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<bool>? relayNotifications = FomoTelegramPlugin.RelayNotifications;
				if (relayNotifications == null || !relayNotifications.Value)
				{
					return Task.CompletedTask;
				}
			}
			else if (entry.Channel != null && entry.Channel.StartsWith("Local"))
			{
				ConfigEntry<bool>? relayLocalChat = FomoTelegramPlugin.RelayLocalChat;
				if (relayLocalChat == null || !relayLocalChat.Value)
				{
					return Task.CompletedTask;
				}
			}
			else
			{
				ConfigEntry<bool>? 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 = "0.2.3";
	}
}
namespace FomoTelegram.Commands
{
	public class FomoTelegramMessageFormatCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomotelegrammessageformat";

		public string Name => "fomotelegrammessageformat";

		public string ShortName => "ftmf";

		public string Description => "Set or get the Telegram message format.";

		public void Execute(string[] args)
		{
			ConfigEntry<bool>? 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<IChatCommand>
	{
		public const string CMD = "fomotelegramnotificationformat";

		public string Name => "fomotelegramnotificationformat";

		public string ShortName => "ftnf";

		public string Description => "Set or get the Telegram notification format.";

		public void Execute(string[] args)
		{
			ConfigEntry<bool>? 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 FomoTelegramSetupInfoCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomotelegramsetupinfo";

		public string Name => "fomotelegramsetupinfo";

		public string ShortName => "ftsinfo";

		public string Description => "Instruction to set up the Telegram integration.";

		public void Execute(string[] args)
		{
			ChatUtils.AddGlobalNotification("To set up Telegram integration you need:");
			ChatUtils.AddGlobalNotification("1. A Telegram Bot API key from @BotFather.");
			ChatUtils.AddGlobalNotification("2. Your Telegram chat ID from @userinfobot.");
			ChatUtils.AddGlobalNotification("3. Update the API key and chat ID in the mod configuration.");
			ChatUtils.AddGlobalNotification("Config path: " + FomoTelegramPlugin.ConfigPath);
			ChatUtils.AddGlobalNotification("4. Then restart the game.");
			ChatUtils.AddGlobalNotification("Inputting API key and chat ID via chat commands is not supported for security reasons. Please update the config file directly.");
		}
	}
	public class FomoTelegramToggleCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomotelegramtoggle";

		public string Name => "fomotelegramtoggle";

		public string ShortName => "ftt";

		public string Description => "Toggle Telegram chat feature on/off.";

		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") + ".");
			}
		}
	}
}