Decompiled source of TwitchChatAPI v1.0.1

com.github.zehsteam.TwitchChatAPI.dll

Decompiled 16 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LethalConfig;
using LethalConfig.ConfigItems;
using LethalConfig.ConfigItems.Options;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using com.github.zehsteam.TwitchChatAPI.Dependencies;
using com.github.zehsteam.TwitchChatAPI.Enums;
using com.github.zehsteam.TwitchChatAPI.Helpers;
using com.github.zehsteam.TwitchChatAPI.MonoBehaviours;
using com.github.zehsteam.TwitchChatAPI.Objects;
using com.github.zehsteam.TwitchChatAPI.Patches;

[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("com.github.zehsteam.TwitchChatAPI")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+5b3ef0b580c03fd46b25c5fbcea30e90ea6d03bb")]
[assembly: AssemblyProduct("TwitchChatAPI")]
[assembly: AssemblyTitle("com.github.zehsteam.TwitchChatAPI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace com.github.zehsteam.TwitchChatAPI
{
	public static class API
	{
		public static ConnectionState ConnectionState => TwitchChat.ConnectionState;

		public static event Action OnConnect;

		public static event Action OnDisconnect;

		public static event Action<TwitchMessage> OnMessage;

		public static event Action<TwitchCheerEvent> OnCheer;

		public static event Action<TwitchSubEvent> OnSub;

		public static event Action<TwitchRaidEvent> OnRaid;

		public static event Action<TwitchRoomState> OnRoomStateUpdate;

		internal static void InvokeOnConnect()
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnConnect?.Invoke();
			});
		}

		internal static void InvokeOnDisconnect()
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnDisconnect?.Invoke();
			});
		}

		internal static void InvokeOnMessage(TwitchMessage message)
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnMessage?.Invoke(message);
			});
		}

		internal static void InvokeOnSub(TwitchSubEvent subEvent)
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnSub?.Invoke(subEvent);
			});
		}

		internal static void InvokeOnCheer(TwitchCheerEvent cheerEvent)
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnCheer?.Invoke(cheerEvent);
			});
		}

		internal static void InvokeOnRaid(TwitchRaidEvent raidEvent)
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnRaid?.Invoke(raidEvent);
			});
		}

		internal static void InvokeOnRoomStateUpdate(TwitchRoomState roomState)
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnRoomStateUpdate?.Invoke(roomState);
			});
		}
	}
	internal class ConfigManager
	{
		public ConfigEntry<bool> ExtendedLogging { get; private set; }

		public ConfigEntry<bool> TwitchChat_Enabled { get; private set; }

		public ConfigEntry<string> TwitchChat_Channel { get; private set; }

		public ConfigManager()
		{
			BindConfigs();
			ConfigHelper.ClearUnusedEntries();
		}

		private void BindConfigs()
		{
			ConfigHelper.SkipAutoGen();
			ExtendedLogging = ConfigHelper.Bind("General", "ExtendedLogging", defaultValue: false, "Enable extended logging.");
			TwitchChat_Enabled = ConfigHelper.Bind("Twitch Chat", "Enabled", defaultValue: true, "Enable/Disable the connection to Twitch chat.");
			TwitchChat_Channel = ConfigHelper.Bind("Twitch Chat", "Channel", "", "Your Twitch channel username.");
			ConfigHelper.AddButton("Twitch Chat", "Refresh Connection", "Refresh the connection to Twitch chat.", "Refresh", TwitchChat_Refresh_Clicked);
			TwitchChat_Enabled.SettingChanged += delegate
			{
				TwitchChat_Enabled_SettingChanged();
			};
			TwitchChat_Channel.SettingChanged += delegate
			{
				TwitchChat_Channel_SettingChanged();
			};
		}

		private void TwitchChat_Enabled_SettingChanged()
		{
			if (TwitchChat_Enabled.Value)
			{
				TwitchChat.Connect();
			}
			else
			{
				TwitchChat.Disconnect();
			}
		}

		private void TwitchChat_Channel_SettingChanged()
		{
			if (TwitchChat_Enabled.Value)
			{
				TwitchChat.Connect();
			}
		}

		private void TwitchChat_Refresh_Clicked()
		{
			if (TwitchChat_Enabled.Value)
			{
				TwitchChat.Connect();
			}
		}
	}
	internal static class Content
	{
		public static GameObject PluginCanvasPrefab { get; private set; }

		public static GameObject MainThreadDispatcherPrefab { get; private set; }

		public static void Load()
		{
			LoadAssetsFromAssetBundle();
		}

		private static void LoadAssetsFromAssetBundle()
		{
			AssetBundle val = LoadAssetBundle("twitchchatapi_assets");
			if (!((Object)(object)val == (Object)null))
			{
				PluginCanvasPrefab = LoadAssetFromAssetBundle<GameObject>("TwitchChatAPICanvas", val);
				MainThreadDispatcherPrefab = LoadAssetFromAssetBundle<GameObject>("MainThreadDispatcher", val);
				Plugin.Logger.LogInfo((object)"Successfully loaded assets from AssetBundle!");
			}
		}

		private static AssetBundle LoadAssetBundle(string fileName)
		{
			try
			{
				string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)Plugin.Instance).Info.Location);
				string text = Path.Combine(directoryName, fileName);
				return AssetBundle.LoadFromFile(text);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to load AssetBundle \"{fileName}\". {arg}");
			}
			return null;
		}

		private static T LoadAssetFromAssetBundle<T>(string name, AssetBundle assetBundle) where T : Object
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				Plugin.Logger.LogError((object)("Failed to load asset of type \"" + typeof(T).Name + "\" from AssetBundle. Name is null or whitespace."));
				return default(T);
			}
			if ((Object)(object)assetBundle == (Object)null)
			{
				Plugin.Logger.LogError((object)("Failed to load asset of type \"" + typeof(T).Name + "\" with name \"" + name + "\" from AssetBundle. AssetBundle is null."));
				return default(T);
			}
			T val = assetBundle.LoadAsset<T>(name);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Logger.LogError((object)("Failed to load asset of type \"" + typeof(T).Name + "\" with name \"" + name + "\" from AssetBundle. No asset found with that type and name."));
				return default(T);
			}
			return val;
		}
	}
	[BepInPlugin("com.github.zehsteam.TwitchChatAPI", "TwitchChatAPI", "1.0.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	internal class Plugin : BaseUnityPlugin
	{
		private readonly Harmony _harmony = new Harmony("com.github.zehsteam.TwitchChatAPI");

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Logger { get; private set; }

		internal static ConfigFile Config { get; private set; }

		internal static ConfigManager ConfigManager { get; private set; }

		private void Awake()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
			}
			Logger = Logger.CreateLogSource("com.github.zehsteam.TwitchChatAPI");
			Logger.LogInfo((object)"TwitchChatAPI has awoken!");
			Config = Utils.CreateGlobalConfigFile();
			_harmony.PatchAll(typeof(MenuManagerPatch));
			_harmony.PatchAll(typeof(QuickMenuManagerPatch));
			Content.Load();
			ConfigManager = new ConfigManager();
			if (ConfigManager.TwitchChat_Enabled.Value)
			{
				TwitchChat.Connect();
			}
		}

		public void SpawnPluginCanvas()
		{
			if (!((Object)(object)PluginCanvas.Instance != (Object)null))
			{
				Object.Instantiate<GameObject>(Content.PluginCanvasPrefab);
				Logger.LogInfo((object)"Spawned PluginCanvas");
			}
		}

		public void SpawnMainThreadDispatcher()
		{
			if (!((Object)(object)MainThreadDispatcher.Instance != (Object)null))
			{
				Object.Instantiate<GameObject>(Content.MainThreadDispatcherPrefab);
				Logger.LogInfo((object)"Spawned MainThreadDispatcher");
			}
		}

		public void LogInfoExtended(object data)
		{
			LogExtended((LogLevel)16, data);
		}

		public void LogWarningExtended(object data)
		{
			LogExtended((LogLevel)4, data);
		}

		public void LogExtended(LogLevel level, object data)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (ConfigManager == null || ConfigManager.ExtendedLogging == null)
			{
				Logger.Log(level, data);
			}
			else if (ConfigManager.ExtendedLogging.Value)
			{
				Logger.Log(level, data);
			}
		}
	}
	internal static class TwitchChat
	{
		public const string ServerIP = "irc.chat.twitch.tv";

		public const int ServerPort = 6667;

		private static ConnectionState _connectionState;

		private static TcpClient _client;

		private static NetworkStream _stream;

		private static StreamReader _reader;

		private static StreamWriter _writer;

		private static CancellationTokenSource _cts;

		private static bool _isReconnecting;

		private static Task _reconnectTask;

		private static CancellationTokenSource _reconnectCts;

		private static int _reconnectDelay;

		private static bool _explicitDisconnect;

		private static readonly object _connectionLock;

		private static readonly SemaphoreSlim _readLock;

		public static bool Enabled => Plugin.ConfigManager.TwitchChat_Enabled.Value;

		public static string Channel => ("#" + Plugin.ConfigManager.TwitchChat_Channel.Value).Trim();

		public static ConnectionState ConnectionState
		{
			get
			{
				return _connectionState;
			}
			private set
			{
				_connectionState = value;
				PluginCanvas.Instance?.UpdateSettingsWindowConnectionStatus();
			}
		}

		static TwitchChat()
		{
			_connectionState = ConnectionState.None;
			_reconnectDelay = 5000;
			_connectionLock = new object();
			_readLock = new SemaphoreSlim(1, 1);
			Application.quitting += OnApplicationQuit;
		}

		public static void Connect()
		{
			Task.Run((Func<Task?>)ConnectAsync);
		}

		public static async Task ConnectAsync()
		{
			lock (_connectionLock)
			{
				if (_isReconnecting)
				{
					CancelReconnect();
				}
				_explicitDisconnect = false;
				_isReconnecting = false;
			}
			if (!Enabled)
			{
				Plugin.Logger.LogError((object)"Failed to connect to Twitch chat. Twitch chat has been disabled in the config settings.");
				return;
			}
			if (ConnectionState == ConnectionState.Connecting)
			{
				Plugin.Logger.LogWarning((object)"Twitch chat is already connecting.");
				return;
			}
			if (ConnectionState == ConnectionState.Connected)
			{
				Disconnect();
			}
			if (string.IsNullOrWhiteSpace(Channel) || Channel == "#")
			{
				Plugin.Logger.LogWarning((object)"Failed to start Twitch chat connection: Invalid or empty channel name.");
				return;
			}
			Plugin.Logger.LogInfo((object)"Establishing connection to Twitch chat...");
			ConnectionState = ConnectionState.Connecting;
			try
			{
				_cts = new CancellationTokenSource();
				_client = new TcpClient();
				await _client.ConnectAsync("irc.chat.twitch.tv", 6667);
				_stream = _client.GetStream();
				_reader = new StreamReader(_stream);
				_writer = new StreamWriter(_stream)
				{
					AutoFlush = true
				};
				await _writer.WriteLineAsync("NICK justinfan123");
				await _writer.WriteLineAsync("CAP REQ :twitch.tv/tags");
				await _writer.WriteLineAsync("CAP REQ :twitch.tv/commands");
				await _writer.WriteLineAsync("JOIN " + Channel);
				ConnectionState = ConnectionState.Connected;
				_explicitDisconnect = false;
				Plugin.Logger.LogInfo((object)("Successfully connected to Twitch chat " + Channel + "."));
				API.InvokeOnConnect();
				await Task.Run((Func<Task?>)ListenAsync, _cts.Token);
			}
			catch (Exception ex)
			{
				ConnectionState = ConnectionState.Disconnected;
				_explicitDisconnect = false;
				Plugin.Logger.LogError((object)$"Failed to connect to Twitch chat {Channel}. {ex}");
				ScheduleReconnect();
			}
		}

		public static void Disconnect()
		{
			lock (_connectionLock)
			{
				_explicitDisconnect = true;
				CancelReconnect();
				if (ConnectionState != ConnectionState.Connected && ConnectionState != ConnectionState.Connecting)
				{
					Plugin.Logger.LogInfo((object)"Twitch chat is not connected or already disconnecting.");
					return;
				}
				ConnectionState = ConnectionState.Disconnecting;
				_cts?.Cancel();
				DisconnectStreams();
				ConnectionState = ConnectionState.Disconnected;
				Plugin.Logger.LogInfo((object)"Twitch chat connection stopped.");
				API.InvokeOnDisconnect();
			}
		}

		private static void DisconnectStreams()
		{
			_writer?.Dispose();
			_reader?.Dispose();
			_stream?.Dispose();
			_client?.Close();
			_writer = null;
			_reader = null;
			_stream = null;
			_client = null;
		}

		private static void ScheduleReconnect()
		{
			lock (_connectionLock)
			{
				if (!Enabled || _explicitDisconnect || _isReconnecting)
				{
					return;
				}
				Plugin.Logger.LogInfo((object)$"Reconnection to Twitch chat will be attempted in {_reconnectDelay / 1000} seconds.");
				_isReconnecting = true;
				_reconnectCts = new CancellationTokenSource();
				_reconnectTask = Task.Delay(_reconnectDelay, _reconnectCts.Token).ContinueWith((Func<Task, Task>)async delegate(Task task)
				{
					if (!task.IsCanceled)
					{
						lock (_connectionLock)
						{
							_isReconnecting = false;
						}
						if (Enabled)
						{
							Plugin.Logger.LogInfo((object)"Attempting to reconnect to Twitch chat...");
							await ConnectAsync();
						}
					}
				});
			}
		}

		private static void CancelReconnect()
		{
			lock (_connectionLock)
			{
				if (_reconnectTask != null && !_reconnectTask.IsCompleted)
				{
					_reconnectCts?.Cancel();
					_reconnectTask = null;
				}
				_isReconnecting = false;
			}
		}

		private static async Task ListenAsync()
		{
			if (ConnectionState != ConnectionState.Connected || _reader == null)
			{
				return;
			}
			try
			{
				while (_cts != null && !_cts.Token.IsCancellationRequested)
				{
					lock (_connectionLock)
					{
						if (_reader == null)
						{
							break;
						}
					}
					string message = await SafeReadLineAsync(_cts.Token);
					if (message != null)
					{
						if (message.StartsWith("PING"))
						{
							Plugin.Instance.LogInfoExtended("Received PING, sending PONG...");
							await (_writer?.WriteLineAsync("PONG :tmi.twitch.tv") ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext: false);
						}
						else
						{
							MessageHelper.ProcessMessage(message);
						}
					}
				}
			}
			catch (TaskCanceledException)
			{
				Plugin.Logger.LogInfo((object)"Twitch chat listen task canceled.");
			}
			catch (OperationCanceledException)
			{
				Plugin.Logger.LogInfo((object)"Twitch chat listen task canceled.");
			}
			catch (Exception ex4)
			{
				Exception ex = ex4;
				Plugin.Logger.LogError((object)$"Twitch chat listen task failed. {ex}");
				ScheduleReconnect();
			}
			finally
			{
				lock (_connectionLock)
				{
					ConnectionState = ConnectionState.Disconnected;
				}
			}
		}

		private static async Task<string> SafeReadLineAsync(CancellationToken cancellationToken)
		{
			await _readLock.WaitAsync(cancellationToken);
			try
			{
				Task<string> readTask = _reader.ReadLineAsync();
				if (await Task.WhenAny(new Task[2]
				{
					readTask,
					Task.Delay(-1, cancellationToken)
				}).ConfigureAwait(continueOnCapturedContext: false) == readTask)
				{
					return await readTask.ConfigureAwait(continueOnCapturedContext: false);
				}
				throw new OperationCanceledException(cancellationToken);
			}
			finally
			{
				_readLock.Release();
			}
		}

		private static void OnApplicationQuit()
		{
			Plugin.Logger.LogInfo((object)"Application is quitting. Disconnecting Twitch chat...");
			Disconnect();
		}
	}
	internal static class Utils
	{
		public static string GetEnumName<T>(T e) where T : Enum
		{
			return Enum.GetName(typeof(T), e) ?? string.Empty;
		}

		public static string GetPluginDirectoryPath()
		{
			return Path.GetDirectoryName(((BaseUnityPlugin)Plugin.Instance).Info.Location);
		}

		public static string GetConfigDirectoryPath()
		{
			return Paths.ConfigPath;
		}

		public static string GetGlobalConfigDirectoryPath()
		{
			return Path.Combine(Application.persistentDataPath, "TwitchChatAPI");
		}

		public static ConfigFile CreateConfigFile(string directoryPath, string name = null, bool saveOnInit = false)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			BepInPlugin metadata = MetadataHelper.GetMetadata((object)Plugin.Instance);
			if (name == null)
			{
				name = metadata.GUID;
			}
			name += ".cfg";
			return new ConfigFile(Path.Combine(directoryPath, name), saveOnInit, metadata);
		}

		public static ConfigFile CreateLocalConfigFile(string name = null, bool saveOnInit = false)
		{
			if (name == null)
			{
				name = "com.github.zehsteam.TwitchChatAPI-" + name;
			}
			return CreateConfigFile(Paths.ConfigPath, name, saveOnInit);
		}

		public static ConfigFile CreateGlobalConfigFile(string name = null, bool saveOnInit = false)
		{
			if (name == null)
			{
				name = "global";
			}
			return CreateConfigFile(GetGlobalConfigDirectoryPath(), name, saveOnInit);
		}

		public static void LogStackTrace()
		{
			StackTrace stackTrace = new StackTrace();
			for (int i = 1; i < stackTrace.FrameCount; i++)
			{
				StackFrame frame = stackTrace.GetFrame(i);
				MethodBase method = frame.GetMethod();
				Type declaringType = method.DeclaringType;
				string name = method.Name;
				Plugin.Instance.LogInfoExtended($"Call stack depth {i}: {declaringType}.{name}");
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "com.github.zehsteam.TwitchChatAPI";

		public const string PLUGIN_NAME = "TwitchChatAPI";

		public const string PLUGIN_VERSION = "1.0.1";
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Patches
{
	[HarmonyPatch(typeof(MenuManager))]
	internal static class MenuManagerPatch
	{
		[HarmonyPatch("Awake")]
		[HarmonyPrefix]
		private static void AwakePatch(MenuManager __instance)
		{
			MenuManagerHelper.SetInstance(__instance);
		}

		[HarmonyPatch("Start")]
		[HarmonyPostfix]
		private static void StartPatch(MenuManager __instance)
		{
			if (!__instance.isInitScene)
			{
				Plugin.Instance.SpawnPluginCanvas();
				Plugin.Instance.SpawnMainThreadDispatcher();
			}
		}
	}
	[HarmonyPatch(typeof(QuickMenuManager))]
	internal static class QuickMenuManagerPatch
	{
		[HarmonyPatch("Start")]
		[HarmonyPrefix]
		private static void StartPatch(QuickMenuManager __instance)
		{
			QuickMenuManagerHelper.SetInstance(__instance);
			Plugin.Instance.SpawnPluginCanvas();
		}

		[HarmonyPatch("OpenQuickMenu")]
		[HarmonyPostfix]
		private static void OpenQuickMenuPatch()
		{
			PluginCanvas.Instance?.UpdateSettingsButton();
		}

		[HarmonyPatch("CloseQuickMenu")]
		[HarmonyPostfix]
		private static void CloseQuickMenuPatch()
		{
			PluginCanvas.Instance?.CloseSettingsWindow();
		}
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Objects
{
	internal class JsonSave
	{
		private JObject _data;

		public string DirectoryPath { get; private set; }

		public string FileName { get; private set; }

		public string FilePath => Path.Combine(DirectoryPath, FileName);

		public JsonSave(string directoryPath, string fileName)
		{
			DirectoryPath = directoryPath;
			FileName = fileName;
			_data = ReadFile();
		}

		public bool KeyExists(string key)
		{
			if (_data == null)
			{
				Plugin.Logger.LogError((object)"KeyExists: Data is null. Ensure the save file is properly loaded.");
				return false;
			}
			return _data.ContainsKey(key);
		}

		public T LoadValue<T>(string key, T defaultValue = default(T))
		{
			if (TryLoadValue<T>(key, out var value))
			{
				return value;
			}
			return defaultValue;
		}

		public bool TryLoadValue<T>(string key, out T value)
		{
			//IL_0064: Expected O, but got Unknown
			value = default(T);
			if (_data == null)
			{
				Plugin.Logger.LogError((object)("LoadValue: Data is null. Returning default value for key: " + key + "."));
				return false;
			}
			JToken val = default(JToken);
			if (_data.TryGetValue(key, ref val))
			{
				try
				{
					value = val.ToObject<T>();
					return true;
				}
				catch (JsonException val2)
				{
					JsonException val3 = val2;
					Plugin.Logger.LogError((object)("LoadValue: JSON Conversion Error for key: " + key + ". " + ((Exception)(object)val3).Message));
				}
				catch (ArgumentNullException ex)
				{
					Plugin.Logger.LogError((object)("LoadValue: Argument Null Error for key: " + key + ". " + ex.Message));
				}
				catch (Exception ex2)
				{
					Plugin.Logger.LogError((object)("LoadValue: Unexpected Error for key: " + key + ". " + ex2.Message));
				}
				return false;
			}
			Plugin.Instance.LogWarningExtended("LoadValue: Key '" + key + "' does not exist. Returning default value.");
			return false;
		}

		public bool SaveValue<T>(string key, T value)
		{
			if (_data == null)
			{
				Plugin.Logger.LogError((object)("SaveValue: Data is null. Cannot save key: " + key + "."));
				return false;
			}
			try
			{
				JToken val = JToken.FromObject((object)value);
				if (_data.ContainsKey(key))
				{
					_data[key] = val;
				}
				else
				{
					_data.Add(key, val);
				}
				return WriteFile(_data);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("SaveValue: Error saving key: " + key + ". " + ex.Message));
				return false;
			}
		}

		private JObject ReadFile()
		{
			//IL_0080: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Expected O, but got Unknown
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			try
			{
				if (!File.Exists(FilePath))
				{
					Plugin.Logger.LogWarning((object)("ReadFile: Save file does not exist at \"" + FilePath + "\". Initializing with an empty file."));
					return new JObject();
				}
				using FileStream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
				using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
				return JObject.Parse(streamReader.ReadToEnd());
			}
			catch (JsonException val)
			{
				JsonException val2 = val;
				Plugin.Logger.LogError((object)("ReadFile: JSON Parsing Error for file: \"" + FilePath + "\". " + ((Exception)(object)val2).Message));
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("ReadFile: Unexpected Error for file: \"" + FilePath + "\". " + ex.Message));
			}
			return new JObject();
		}

		private bool WriteFile(JObject data)
		{
			try
			{
				if (!Directory.Exists(DirectoryPath))
				{
					Directory.CreateDirectory(DirectoryPath);
				}
				File.WriteAllText(FilePath, ((object)data).ToString(), Encoding.UTF8);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("WriteFile: Unexpected Error for file: \"" + FilePath + "\". " + ex.Message));
			}
			return false;
		}
	}
	public abstract class TwitchEvent
	{
		public string Channel { get; set; }

		public TwitchUser User { get; set; }

		public string Message { get; set; }

		public Dictionary<string, string> Tags { get; set; }
	}
	public class TwitchSubEvent : TwitchEvent
	{
		public SubType SubType { get; set; }

		public bool IsPrime { get; set; }

		public int Tier { get; set; }

		public int Months { get; set; }

		public string RecipientUser { get; set; }

		public int GiftCount { get; set; }
	}
	public class TwitchCheerEvent : TwitchEvent
	{
		public int CheerAmount { get; set; }
	}
	public class TwitchRaidEvent : TwitchEvent
	{
		public int ViewerCount { get; set; }
	}
	public struct TwitchMessage
	{
		public string Channel { get; set; }

		public TwitchUser User { get; set; }

		public string Message { get; set; }

		public Dictionary<string, string> Tags { get; set; }
	}
	public struct TwitchRoomState
	{
		public string Channel { get; set; }

		public bool IsEmoteOnly { get; set; }

		public bool IsFollowersOnly { get; set; }

		public bool IsR9K { get; set; }

		public bool IsSlowMode { get; set; }

		public bool IsSubsOnly { get; set; }

		public Dictionary<string, string> Tags { get; set; }
	}
	public struct TwitchUser
	{
		public string UserId { get; set; }

		public string DisplayName { get; set; }

		public string Color { get; set; }

		public bool IsVIP { get; set; }

		public bool IsSubscriber { get; set; }

		public bool IsModerator { get; set; }

		public bool IsBroadcaster { get; set; }
	}
}
namespace com.github.zehsteam.TwitchChatAPI.MonoBehaviours
{
	public class MainThreadDispatcher : MonoBehaviour
	{
		private static readonly Queue<Action> _actions = new Queue<Action>();

		public static MainThreadDispatcher Instance { get; private set; }

		private void Awake()
		{
			if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			Instance = this;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}

		private void Update()
		{
			lock (_actions)
			{
				while (_actions.Count > 0)
				{
					_actions.Dequeue()?.Invoke();
				}
			}
		}

		public static void Enqueue(Action action)
		{
			lock (_actions)
			{
				_actions.Enqueue(action);
			}
		}
	}
	public class PluginCanvas : MonoBehaviour
	{
		[Header("Windows")]
		[Space(5f)]
		public GameObject SettingsWindowObject;

		public GameObject MainMenuObject;

		public GameObject QuickMenuObject;

		[Header("Settings Window Properties")]
		[Space(5f)]
		public TextMeshProUGUI ConnectionStatusText;

		public Toggle EnabledToggle;

		public TMP_InputField ChannelInputField;

		[Header("MainMenu Properties")]
		[Space(5f)]
		public RectTransform MainMenuSettingsButtonTransform;

		[Header("QuickMenu Properties")]
		[Space(5f)]
		public RectTransform QuickMenuSettingsButtonTransform;

		public static PluginCanvas Instance { get; private set; }

		public bool IsSettingsWindowOpen { get; private set; }

		private void Awake()
		{
			if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
			else
			{
				Instance = this;
			}
		}

		private void Start()
		{
			CloseSettingsWindow();
			OpenSettingsWindowFirstTimeOnly();
			UpdateSettingsButton();
		}

		private void OpenSettingsWindowFirstTimeOnly()
		{
			if (!SaveHelper.LoadValue("OpenedSettingsWindowFirstTime", SaveLocation.Global, defaultValue: false))
			{
				SaveHelper.SaveValue("OpenedSettingsWindowFirstTime", value: true, SaveLocation.Global);
				OpenSettingsWindow();
			}
		}

		public void OpenSettingsWindow()
		{
			if (!((Object)(object)SettingsWindowObject == (Object)null) && !IsSettingsWindowOpen && (!((Object)(object)QuickMenuManagerHelper.Instance != (Object)null) || QuickMenuManagerHelper.IsMenuOpen))
			{
				IsSettingsWindowOpen = true;
				SettingsWindowObject.SetActive(true);
				UpdateSettingsWindowUI();
				UpdateSettingsButton();
			}
		}

		public void CloseSettingsWindow()
		{
			if (!((Object)(object)SettingsWindowObject == (Object)null))
			{
				IsSettingsWindowOpen = false;
				SettingsWindowObject.SetActive(false);
				UpdateSettingsButton();
			}
		}

		public void OnApplyButtonClicked()
		{
			if ((Object)(object)EnabledToggle != (Object)null)
			{
				Plugin.ConfigManager.TwitchChat_Enabled.Value = EnabledToggle.isOn;
			}
			if ((Object)(object)ChannelInputField != (Object)null)
			{
				Plugin.ConfigManager.TwitchChat_Channel.Value = ChannelInputField.text;
			}
		}

		public void OnCloseButtonClicked()
		{
			CloseSettingsWindow();
		}

		private void UpdateSettingsWindowUI()
		{
			if ((Object)(object)EnabledToggle != (Object)null)
			{
				EnabledToggle.isOn = Plugin.ConfigManager.TwitchChat_Enabled.Value;
			}
			if ((Object)(object)ChannelInputField != (Object)null)
			{
				ChannelInputField.text = Plugin.ConfigManager.TwitchChat_Channel.Value;
			}
			UpdateSettingsWindowConnectionStatus();
		}

		public void UpdateSettingsWindowConnectionStatus()
		{
			if (!((Object)(object)ConnectionStatusText == (Object)null))
			{
				ConnectionState connectionState = TwitchChat.ConnectionState;
				if (1 == 0)
				{
				}
				string text = connectionState switch
				{
					ConnectionState.Connecting => "#00FF00", 
					ConnectionState.Connected => "#00FF00", 
					ConnectionState.Disconnecting => "#FF0000", 
					ConnectionState.Disconnected => "#FF0000", 
					_ => string.Empty, 
				};
				if (1 == 0)
				{
				}
				string text2 = text;
				string text3 = (string.IsNullOrEmpty(text2) ? Utils.GetEnumName(TwitchChat.ConnectionState) : ("<color=" + text2 + ">" + Utils.GetEnumName(TwitchChat.ConnectionState) + "</color>"));
				((TMP_Text)ConnectionStatusText).text = "Connection Status: " + text3;
			}
		}

		public void OnSettingsButtonClicked()
		{
			OpenSettingsWindow();
		}

		public void UpdateSettingsButton()
		{
			UpdateMainMenuUI();
			UpdateQuickMenuUI();
		}

		private void UpdateMainMenuUI()
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)MainMenuObject == (Object)null)
			{
				return;
			}
			MainMenuObject.SetActive((Object)(object)MenuManagerHelper.Instance != (Object)null);
			if (!((Object)(object)MainMenuSettingsButtonTransform == (Object)null))
			{
				((Component)MainMenuSettingsButtonTransform).gameObject.SetActive(!IsSettingsWindowOpen);
				if (MoreCompanyProxy.Enabled)
				{
					MainMenuSettingsButtonTransform.anchoredPosition = new Vector2(-145f, 48f);
				}
				else
				{
					MainMenuSettingsButtonTransform.anchoredPosition = new Vector2(-24f, 48f);
				}
			}
		}

		private void UpdateQuickMenuUI()
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)QuickMenuObject == (Object)null)
			{
				return;
			}
			QuickMenuObject.SetActive(QuickMenuManagerHelper.IsMenuOpen);
			if (!((Object)(object)QuickMenuSettingsButtonTransform == (Object)null))
			{
				((Component)QuickMenuSettingsButtonTransform).gameObject.SetActive(!IsSettingsWindowOpen);
				if (MoreCompanyProxy.Enabled)
				{
					QuickMenuSettingsButtonTransform.anchoredPosition = new Vector2(-169f, 63f);
				}
				else
				{
					QuickMenuSettingsButtonTransform.anchoredPosition = new Vector2(-52f, 63f);
				}
			}
		}
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Helpers
{
	internal static class ConfigHelper
	{
		public static void SkipAutoGen()
		{
			if (LethalConfigProxy.Enabled)
			{
				LethalConfigProxy.SkipAutoGen();
			}
		}

		public static void AddButton(string section, string name, string description, string buttonText, Action callback)
		{
			if (LethalConfigProxy.Enabled)
			{
				LethalConfigProxy.AddButton(section, name, description, buttonText, callback);
			}
		}

		public static ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, bool requiresRestart = false, AcceptableValueBase acceptableValues = null, Action<T> settingChanged = null, ConfigFile configFile = null)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			if (configFile == null)
			{
				configFile = Plugin.Config;
			}
			ConfigEntry<T> configEntry = ((acceptableValues == null) ? configFile.Bind<T>(section, key, defaultValue, description) : configFile.Bind<T>(section, key, defaultValue, new ConfigDescription(description, acceptableValues, Array.Empty<object>())));
			if (settingChanged != null)
			{
				configEntry.SettingChanged += delegate
				{
					settingChanged?.Invoke(configEntry.Value);
				};
			}
			if (LethalConfigProxy.Enabled)
			{
				LethalConfigProxy.AddConfig<T>(configEntry, requiresRestart);
			}
			return configEntry;
		}

		public static Dictionary<ConfigDefinition, string> GetOrphanedConfigEntries(ConfigFile configFile = null)
		{
			if (configFile == null)
			{
				configFile = Plugin.Config;
			}
			PropertyInfo property = ((object)configFile).GetType().GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic);
			return (Dictionary<ConfigDefinition, string>)property.GetValue(configFile, null);
		}

		public static void SetConfigEntryValue<T>(ConfigEntry<T> configEntry, string value)
		{
			if (typeof(T) == typeof(int) && int.TryParse(value, out var result))
			{
				configEntry.Value = (T)(object)result;
				return;
			}
			if (typeof(T) == typeof(float) && float.TryParse(value, out var result2))
			{
				configEntry.Value = (T)(object)result2;
				return;
			}
			if (typeof(T) == typeof(double) && double.TryParse(value, out var result3))
			{
				configEntry.Value = (T)(object)result3;
				return;
			}
			if (typeof(T) == typeof(bool) && bool.TryParse(value, out var result4))
			{
				configEntry.Value = (T)(object)result4;
				return;
			}
			if (typeof(T) == typeof(string))
			{
				configEntry.Value = (T)(object)value;
				return;
			}
			throw new InvalidOperationException($"Unsupported type: {typeof(T)}");
		}

		public static void ClearUnusedEntries(ConfigFile configFile = null)
		{
			if (configFile == null)
			{
				configFile = Plugin.Config;
			}
			Dictionary<ConfigDefinition, string> orphanedConfigEntries = GetOrphanedConfigEntries(configFile);
			if (orphanedConfigEntries != null)
			{
				orphanedConfigEntries.Clear();
				configFile.Save();
			}
		}
	}
	internal static class MenuManagerHelper
	{
		public static MenuManager Instance { get; private set; }

		public static void SetInstance(MenuManager menuManager)
		{
			Instance = menuManager;
		}
	}
	internal static class MessageHelper
	{
		public static void ProcessMessage(string message)
		{
			try
			{
				if (message.StartsWith("@") && message.Contains("PRIVMSG"))
				{
					ProcessMessage_PRIVMSG(message);
				}
				else if (message.StartsWith("@") && message.Contains("USERNOTICE"))
				{
					ProcessMessage_USERNOTICE(message);
				}
				else if (message.StartsWith("@") && message.Contains("ROOMSTATE"))
				{
					ProcessMessage_ROOMSTATE(message);
				}
				else
				{
					Plugin.Instance.LogInfoExtended("Unhandled RAW message: " + message);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static void ProcessMessage_PRIVMSG(string message)
		{
			try
			{
				string text = message.Split(' ')[0].Substring(1);
				Dictionary<string, string> dictionary = text.Split(';').ToDictionary((string tag) => tag.Split('=')[0], delegate(string tag)
				{
					string result;
					if (!tag.Contains('='))
					{
						result = string.Empty;
					}
					else
					{
						int num3 = tag.IndexOf('=') + 1;
						result = tag.Substring(num3, tag.Length - num3);
					}
					return result;
				});
				string text2 = message.Split("PRIVMSG")[1];
				string channel = text2.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0].Trim().TrimStart('#');
				string text3 = string.Empty;
				int num = text2.IndexOf(':');
				if (num != -1)
				{
					string text4 = text2;
					int num2 = num + 1;
					text3 = text4.Substring(num2, text4.Length - num2).Trim();
				}
				TwitchUser twitchUser = GetTwitchUser(channel, dictionary);
				if (!twitchUser.Equals(null))
				{
					if (dictionary.ContainsKey("bits"))
					{
						ProcessMessage_PRIVMSG_Cheer(message, channel, twitchUser, text3, dictionary);
						return;
					}
					TwitchMessage twitchMessage = default(TwitchMessage);
					twitchMessage.Channel = channel;
					twitchMessage.User = twitchUser;
					twitchMessage.Message = text3;
					twitchMessage.Tags = dictionary;
					TwitchMessage message2 = twitchMessage;
					API.InvokeOnMessage(message2);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process PRIVMSG message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static void ProcessMessage_PRIVMSG_Cheer(string message, string channel, TwitchUser twitchUser, string chatMessage, Dictionary<string, string> tags)
		{
			try
			{
				string[] value = new string[25]
				{
					"Cheer", "cheerwhal", "Corgo", "uni", "ShowLove", "Party", "SeemsGood", "Pride", "Kappa", "FrankerZ",
					"HeyGuys", "DansGame", "EleGiggle", "TriHard", "Kreygasm", "4Head", "SwiftRage", "NotLikeThis", "FailFish", "VoHiYo",
					"PJSalt", "MrDestructoid", "bday", "RIPCheer", "Shamrock"
				};
				string pattern = "\\b(" + string.Join("|", value) + ")\\d+\\b";
				chatMessage = Regex.Replace(chatMessage, pattern, string.Empty, RegexOptions.IgnoreCase).Trim();
				TwitchCheerEvent twitchCheerEvent = new TwitchCheerEvent
				{
					Channel = channel,
					User = twitchUser,
					Message = chatMessage,
					Tags = tags,
					CheerAmount = int.Parse(tags.GetValueOrDefault("bits", "0"))
				};
				Plugin.Instance.LogInfoExtended("RAW cheer message: " + message);
				Plugin.Instance.LogInfoExtended($"[!] Cheer event: {twitchCheerEvent.User.DisplayName} cheered {twitchCheerEvent.CheerAmount} bits!\n{JsonConvert.SerializeObject((object)twitchCheerEvent, (Formatting)1)}");
				API.InvokeOnCheer(twitchCheerEvent);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process PRIVMSG message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static void ProcessMessage_USERNOTICE(string message)
		{
			try
			{
				string text = message.Split(' ')[0].Substring(1);
				Dictionary<string, string> dictionary = text.Split(';').ToDictionary((string tag) => tag.Split('=')[0], delegate(string tag)
				{
					string result;
					if (!tag.Contains('='))
					{
						result = string.Empty;
					}
					else
					{
						int num3 = tag.IndexOf('=') + 1;
						result = tag.Substring(num3, tag.Length - num3);
					}
					return result;
				});
				string valueOrDefault = dictionary.GetValueOrDefault("msg-id", string.Empty);
				if (string.IsNullOrEmpty(valueOrDefault))
				{
					Plugin.Logger.LogError((object)("Failed to process USERNOTICE message:\n\n" + message));
					return;
				}
				string text2 = message.Split("USERNOTICE")[1];
				string channel = text2.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0].Trim().TrimStart('#');
				string chatMessage = string.Empty;
				int num = text2.IndexOf(':');
				if (num != -1)
				{
					string text3 = text2;
					int num2 = num + 1;
					chatMessage = text3.Substring(num2, text3.Length - num2).Trim();
				}
				TwitchUser twitchUser = GetTwitchUser(channel, dictionary);
				if (twitchUser.Equals(null))
				{
					return;
				}
				switch (valueOrDefault)
				{
				default:
					if (!(valueOrDefault == "submysterygift"))
					{
						if (valueOrDefault == "raid")
						{
							ProcessMessage_USERNOTICE_Raid(message, channel, twitchUser, dictionary);
						}
						else
						{
							Plugin.Instance.LogInfoExtended("Unhandled USERNOTICE message: " + message);
						}
						break;
					}
					goto case "sub";
				case "sub":
				case "resub":
				case "subgift":
					ProcessMessage_USERNOTICE_Sub(message, channel, twitchUser, chatMessage, dictionary);
					break;
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process USERNOTICE message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static void ProcessMessage_USERNOTICE_Sub(string message, string channel, TwitchUser twitchUser, string chatMessage, Dictionary<string, string> tags)
		{
			try
			{
				string valueOrDefault = tags.GetValueOrDefault("msg-id", string.Empty);
				if (string.IsNullOrEmpty(valueOrDefault))
				{
					Plugin.Logger.LogError((object)("Failed to process USERNOTICE message: " + message));
					return;
				}
				SubType subType = SubType.Sub;
				switch (valueOrDefault)
				{
				case "resub":
					subType = SubType.Resub;
					break;
				case "subgift":
					subType = SubType.SubGift;
					break;
				case "submysterygift":
					subType = SubType.SubMysteryGift;
					break;
				}
				if (subType == SubType.SubGift && tags.ContainsKey("msg-param-community-gift-id"))
				{
					Plugin.Instance.LogInfoExtended("Skipping subgift since it originates from a submysterygift. Message: " + message);
					return;
				}
				bool isPrime = false;
				int tier = 1;
				if (tags.TryGetValue("msg-param-sub-plan", out var value) && !string.IsNullOrEmpty(value))
				{
					switch (value)
					{
					case "Prime":
						isPrime = true;
						break;
					case "2000":
						tier = 2;
						break;
					case "3000":
						tier = 3;
						break;
					}
				}
				TwitchSubEvent twitchSubEvent = new TwitchSubEvent
				{
					Channel = channel,
					User = twitchUser,
					Message = chatMessage,
					Tags = tags,
					SubType = subType,
					IsPrime = isPrime,
					Tier = tier,
					Months = int.Parse(tags.GetValueOrDefault("msg-param-cumulative-months", "0")),
					RecipientUser = tags.GetValueOrDefault("msg-param-recipient-display-name", string.Empty),
					GiftCount = int.Parse(tags.GetValueOrDefault("msg-param-mass-gift-count", "0"))
				};
				Plugin.Instance.LogInfoExtended("RAW subscription message: " + message);
				Plugin.Instance.LogInfoExtended("[!] Subscription event: \n" + JsonConvert.SerializeObject((object)twitchSubEvent, (Formatting)1));
				API.InvokeOnSub(twitchSubEvent);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process USERNOTICE message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static void ProcessMessage_USERNOTICE_Raid(string message, string channel, TwitchUser twitchUser, Dictionary<string, string> tags)
		{
			try
			{
				TwitchRaidEvent twitchRaidEvent = new TwitchRaidEvent
				{
					Channel = channel,
					User = twitchUser,
					Message = string.Empty,
					Tags = tags,
					ViewerCount = int.Parse(tags.GetValueOrDefault("msg-param-viewerCount", "0"))
				};
				Plugin.Instance.LogInfoExtended("RAW raid message: " + message);
				Plugin.Instance.LogInfoExtended($"[!] Raid detected: {twitchRaidEvent.User.DisplayName} is raiding with {twitchRaidEvent.ViewerCount} viewers!\n{JsonConvert.SerializeObject((object)twitchRaidEvent, (Formatting)1)}");
				API.InvokeOnRaid(twitchRaidEvent);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process USERNOTICE message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static void ProcessMessage_ROOMSTATE(string message)
		{
			try
			{
				string text = message.Split(' ')[0].Substring(1);
				Dictionary<string, string> dictionary = text.Split(';').ToDictionary((string tag) => tag.Split('=')[0], (string tag) => tag.Contains('=') ? tag.Split('=')[1] : "");
				string channel = message.Split("ROOMSTATE")[1].Trim().TrimStart('#');
				TwitchRoomState twitchRoomState = default(TwitchRoomState);
				twitchRoomState.Channel = channel;
				twitchRoomState.IsEmoteOnly = dictionary.ContainsKey("emote-only") && dictionary["emote-only"] == "1";
				twitchRoomState.IsFollowersOnly = dictionary.ContainsKey("followers-only") && dictionary["followers-only"] != "-1";
				twitchRoomState.IsR9K = dictionary.ContainsKey("r9k") && dictionary["r9k"] == "1";
				twitchRoomState.IsSlowMode = dictionary.ContainsKey("slow") && dictionary["slow"] != "0";
				twitchRoomState.IsSubsOnly = dictionary.ContainsKey("subs-only") && dictionary["subs-only"] == "1";
				twitchRoomState.Tags = dictionary;
				TwitchRoomState twitchRoomState2 = twitchRoomState;
				Plugin.Instance.LogInfoExtended("RAW roomstate message: " + message);
				Plugin.Instance.LogInfoExtended("[!] Room state change detected: \n" + JsonConvert.SerializeObject((object)twitchRoomState2, (Formatting)1));
				API.InvokeOnRoomStateUpdate(twitchRoomState2);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to process ROOMSTATE message:\n\n{message}\n\nError: {arg}");
			}
		}

		private static TwitchUser GetTwitchUser(string channel, Dictionary<string, string> tags)
		{
			try
			{
				string valueOrDefault = tags.GetValueOrDefault("display-name", "Anonymous");
				TwitchUser result = default(TwitchUser);
				result.UserId = tags.GetValueOrDefault("user-id", "0");
				result.DisplayName = valueOrDefault;
				result.Color = tags.GetValueOrDefault("color", "#FFFFFF");
				result.IsVIP = tags.TryGetValue("vip", out var value) && value == "1";
				result.IsSubscriber = tags.TryGetValue("subscriber", out var value2) && value2 == "1";
				result.IsModerator = tags.TryGetValue("mod", out var value3) && value3 == "1";
				result.IsBroadcaster = valueOrDefault.Equals(channel, StringComparison.OrdinalIgnoreCase);
				return result;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Failed to get TwitchUser: {arg}");
				return default(TwitchUser);
			}
		}
	}
	internal static class QuickMenuManagerHelper
	{
		public static QuickMenuManager Instance { get; private set; }

		public static bool IsMenuOpen => (Object)(object)Instance != (Object)null && Instance.isMenuOpen;

		public static void SetInstance(QuickMenuManager quickMenuManager)
		{
			Instance = quickMenuManager;
		}
	}
	internal static class SaveHelper
	{
		private static JsonSave _modpackSave;

		private static JsonSave _globalSave;

		static SaveHelper()
		{
			_modpackSave = new JsonSave(Utils.GetConfigDirectoryPath(), "TwitchChatAPI_Save");
			_globalSave = new JsonSave(Utils.GetGlobalConfigDirectoryPath(), "GlobalSave");
		}

		public static bool KeyExists(string key, SaveLocation saveLocation)
		{
			try
			{
				string key2 = GetKey(key, saveLocation);
				switch (saveLocation)
				{
				case SaveLocation.CurrentSave:
					return ES3.KeyExists(key2, GetCurrentSaveFilePath());
				case SaveLocation.GeneralSave:
					return ES3.KeyExists(key2, GetGeneralSaveFilePath());
				case SaveLocation.Modpack:
					return _modpackSave.KeyExists(key2);
				case SaveLocation.Global:
					return _globalSave.KeyExists(key2);
				default:
					Plugin.Logger.LogWarning((object)$"KeyExists: Unknown SaveLocation: {saveLocation}");
					return false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)$"KeyExists Error: Key: \"{key}\", SaveLocation: {saveLocation}. Exception: {ex.Message}");
				return false;
			}
		}

		public static T LoadValue<T>(string key, SaveLocation saveLocation, T defaultValue = default(T))
		{
			if (TryLoadValue<T>(key, saveLocation, out var value))
			{
				return value;
			}
			return defaultValue;
		}

		public static bool TryLoadValue<T>(string key, SaveLocation saveLocation, out T value)
		{
			value = default(T);
			if (!KeyExists(key, saveLocation))
			{
				return false;
			}
			try
			{
				string key2 = GetKey(key, saveLocation);
				switch (saveLocation)
				{
				case SaveLocation.CurrentSave:
					value = ES3.Load<T>(key2, GetCurrentSaveFilePath(), default(T));
					return true;
				case SaveLocation.GeneralSave:
					value = ES3.Load<T>(key2, GetGeneralSaveFilePath(), default(T));
					return true;
				case SaveLocation.Modpack:
					return _modpackSave.TryLoadValue<T>(key2, out value);
				case SaveLocation.Global:
					return _globalSave.TryLoadValue<T>(key2, out value);
				default:
					Plugin.Logger.LogWarning((object)$"LoadValue: Unknown SaveLocation: {saveLocation}");
					return false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)$"LoadValue Error: Key: \"{key}\", SaveLocation: {saveLocation}. Exception: {ex.Message}");
				return false;
			}
		}

		public static bool SaveValue<T>(string key, T value, SaveLocation saveLocation)
		{
			if (string.IsNullOrWhiteSpace(key) || value == null)
			{
				Plugin.Logger.LogError((object)"SaveValue: Invalid key or value.");
				return false;
			}
			try
			{
				string key2 = GetKey(key, saveLocation);
				switch (saveLocation)
				{
				case SaveLocation.CurrentSave:
					ES3.Save<T>(key2, value, GetCurrentSaveFilePath());
					return true;
				case SaveLocation.GeneralSave:
					ES3.Save<T>(key2, value, GetGeneralSaveFilePath());
					return true;
				case SaveLocation.Modpack:
					return _modpackSave.SaveValue(key2, value);
				case SaveLocation.Global:
					return _globalSave.SaveValue(key2, value);
				default:
					Plugin.Logger.LogWarning((object)$"SaveValue: Unknown SaveLocation: {saveLocation}");
					return false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)$"SaveValue Error: Key: \"{key}\", SaveLocation: {saveLocation}. Exception: {ex.Message}");
				return false;
			}
		}

		private static string GetKey(string key, SaveLocation saveLocation)
		{
			if (1 == 0)
			{
			}
			string result = (((uint)saveLocation > 1u) ? key : ("com.github.zehsteam.TwitchChatAPI." + key));
			if (1 == 0)
			{
			}
			return result;
		}

		private static string GetCurrentSaveFilePath()
		{
			if ((Object)(object)GameNetworkManager.Instance == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"GetCurrentSaveFilePath: GameNetworkManager instance is null. Returning an empty string.");
				return string.Empty;
			}
			return GameNetworkManager.Instance.currentSaveFileName ?? string.Empty;
		}

		private static string GetGeneralSaveFilePath()
		{
			return "LCGeneralSaveData";
		}
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Enums
{
	public enum ConnectionState
	{
		None,
		Connecting,
		Connected,
		Disconnecting,
		Disconnected
	}
	internal enum SaveLocation
	{
		CurrentSave,
		GeneralSave,
		Modpack,
		Global
	}
	public enum SubType
	{
		Sub,
		Resub,
		SubGift,
		SubMysteryGift
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Dependencies
{
	internal static class LethalConfigProxy
	{
		public const string PLUGIN_GUID = "ainavt.lc.lethalconfig";

		private static bool? _enabled;

		public static bool Enabled
		{
			get
			{
				bool valueOrDefault = _enabled.GetValueOrDefault();
				if (!_enabled.HasValue)
				{
					valueOrDefault = Chainloader.PluginInfos.ContainsKey("ainavt.lc.lethalconfig");
					_enabled = valueOrDefault;
				}
				return _enabled.Value;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
		public static void SkipAutoGen()
		{
			LethalConfigManager.SkipAutoGen();
		}

		[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
		public static void AddConfig<T>(ConfigEntry<T> configEntry, bool requiresRestart = false)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			AcceptableValueBase acceptableValues = ((ConfigEntryBase)configEntry).Description.AcceptableValues;
			if (acceptableValues != null)
			{
				if (acceptableValues is AcceptableValueRange<float> || acceptableValues is AcceptableValueRange<int>)
				{
					AddConfigSlider<T>(configEntry, requiresRestart);
					return;
				}
				if (acceptableValues is AcceptableValueList<string>)
				{
					AddConfigDropdown<T>(configEntry, requiresRestart);
					return;
				}
			}
			if (!(configEntry is ConfigEntry<string> val))
			{
				if (!(configEntry is ConfigEntry<bool> val2))
				{
					if (!(configEntry is ConfigEntry<float> val3))
					{
						if (!(configEntry is ConfigEntry<int> val4))
						{
							throw new NotSupportedException($"Unsupported type: {typeof(T)}");
						}
						LethalConfigManager.AddConfigItem((BaseConfigItem)new IntInputFieldConfigItem(val4, requiresRestart));
					}
					else
					{
						LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatInputFieldConfigItem(val3, requiresRestart));
					}
				}
				else
				{
					LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(val2, requiresRestart));
				}
			}
			else
			{
				LethalConfigManager.AddConfigItem((BaseConfigItem)new TextInputFieldConfigItem(val, requiresRestart));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
		public static void AddConfigSlider<T>(ConfigEntry<T> configEntry, bool requiresRestart = false)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			if (!(configEntry is ConfigEntry<float> val))
			{
				if (!(configEntry is ConfigEntry<int> val2))
				{
					throw new NotSupportedException($"Slider not supported for type: {typeof(T)}");
				}
				LethalConfigManager.AddConfigItem((BaseConfigItem)new IntSliderConfigItem(val2, requiresRestart));
			}
			else
			{
				LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(val, requiresRestart));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
		public static void AddConfigDropdown<T>(ConfigEntry<T> configEntry, bool requiresRestart = false)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			if (configEntry is ConfigEntry<string> val)
			{
				LethalConfigManager.AddConfigItem((BaseConfigItem)new TextDropDownConfigItem(val, requiresRestart));
				return;
			}
			throw new NotSupportedException($"Dropdown not supported for type: {typeof(T)}");
		}

		[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
		public static void AddButton(string section, string name, string description, string buttonText, Action callback)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			LethalConfigManager.AddConfigItem((BaseConfigItem)new GenericButtonConfigItem(section, name, description, buttonText, (GenericButtonHandler)delegate
			{
				callback?.Invoke();
			}));
		}
	}
	internal static class MoreCompanyProxy
	{
		public const string PLUGIN_GUID = "me.swipez.melonloader.morecompany";

		private static bool? _enabled;

		public static bool Enabled
		{
			get
			{
				bool valueOrDefault = _enabled.GetValueOrDefault();
				if (!_enabled.HasValue)
				{
					valueOrDefault = Chainloader.PluginInfos.ContainsKey("me.swipez.melonloader.morecompany");
					_enabled = valueOrDefault;
				}
				return _enabled.Value;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}