Decompiled source of TwitchChatAPI v2.0.0

plugins/TwitchChatAPI.dll

Decompiled 2 months 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.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TwitchChatAPI.Enums;
using TwitchChatAPI.Helpers;
using TwitchChatAPI.MonoBehaviours;
using TwitchChatAPI.Objects;
using UnityEngine;

[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("Zehs")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2025 Zehs")]
[assembly: AssemblyDescription("Add Twitch chat integration to your Unity game mods! Subscribe to events like Messages, Cheers, Subs, and Raids. No Twitch authentication or connections required.")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: AssemblyInformationalVersion("2.0.0+8d20404d44337407c8c2c06658bf8873112aeb86")]
[assembly: AssemblyProduct("TwitchChatAPI")]
[assembly: AssemblyTitle("TwitchChatAPI")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ZehsTeam/TwitchChatAPI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.0.0.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.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;
		}
	}
	[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 TwitchChatAPI
{
	public static class API
	{
		public static string Channel => TwitchChat.Channel;

		public static ConnectionState ConnectionState => TwitchChat.ConnectionState;

		public static IReadOnlyCollection<TwitchUser> Users => UserHelper.Users.Values;

		public static event Action<ConnectionState> OnConnectionStateChanged;

		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;

		public static void Connect()
		{
			TwitchChat.Connect();
		}

		public static void Connect(string channel)
		{
			TwitchChat.Connect(channel);
		}

		public static void Disconnect()
		{
			TwitchChat.Disconnect();
		}

		public static bool TryGetUserByUsername(string username, out TwitchUser twitchUser)
		{
			return UserHelper.TryGetUserByUsername(username, out twitchUser);
		}

		public static bool TryGetUserByUserId(string userId, out TwitchUser twitchUser)
		{
			return UserHelper.TryGetUserByUserId(userId, out twitchUser);
		}

		public static TwitchUser[] GetUsersSeenWithin(TimeSpan timeSpan)
		{
			return UserHelper.GetUsersSeenWithin(timeSpan);
		}

		internal static void InvokeOnConnectionStateChanged(ConnectionState state)
		{
			MainThreadDispatcher.Enqueue(delegate
			{
				API.OnConnectionStateChanged?.Invoke(state);
			});
		}

		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 static class ConfigManager
	{
		public static ConfigFile ConfigFile { get; private set; }

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

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

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

		public static void Initialize(ConfigFile configFile)
		{
			ConfigFile = configFile;
			BindConfigs();
		}

		private static void BindConfigs()
		{
			ExtendedLogging = ConfigFile.Bind<bool>("General", "ExtendedLogging", false, "Enable extended logging.");
			TwitchChat_Enabled = ConfigFile.Bind<bool>("Twitch Chat", "Enabled", true, "Enable/Disable the connection to Twitch chat.");
			TwitchChat_Channel = ConfigFile.Bind<string>("Twitch Chat", "Channel", "", "Your Twitch channel username.");
			TwitchChat_Enabled.SettingChanged += delegate
			{
				TwitchChat.HandleEnabledChanged();
			};
			TwitchChat_Channel.SettingChanged += delegate
			{
				TwitchChat.HandleChannelChanged();
			};
		}
	}
	internal static class Logger
	{
		public static ManualLogSource ManualLogSource { get; private set; }

		public static void Initialize(ManualLogSource manualLogSource)
		{
			ManualLogSource = manualLogSource;
		}

		public static void LogDebug(object data)
		{
			Log((LogLevel)32, data);
		}

		public static void LogInfo(object data, bool extended = false)
		{
			Log((LogLevel)16, data, extended);
		}

		public static void LogWarning(object data, bool extended = false)
		{
			Log((LogLevel)4, data, extended);
		}

		public static void LogError(object data, bool extended = false)
		{
			Log((LogLevel)2, data, extended);
		}

		public static void LogFatal(object data, bool extended = false)
		{
			Log((LogLevel)1, data, extended);
		}

		public static void Log(LogLevel logLevel, object data, bool extended = false)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (!extended || IsExtendedLoggingEnabled())
			{
				ManualLogSource manualLogSource = ManualLogSource;
				if (manualLogSource != null)
				{
					manualLogSource.Log(logLevel, data);
				}
			}
		}

		public static bool IsExtendedLoggingEnabled()
		{
			if (ConfigManager.ExtendedLogging == null)
			{
				return false;
			}
			return ConfigManager.ExtendedLogging.Value;
		}
	}
	[BepInPlugin("TwitchChatAPI", "TwitchChatAPI", "2.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public static Plugin Instance { get; private set; }

		internal static ConfigFile Config { get; private set; }

		internal static JsonSave GlobalSave { get; private set; }

		private void Awake()
		{
			Instance = this;
			Logger.Initialize(Logger.CreateLogSource("TwitchChatAPI"));
			Logger.LogInfo("TwitchChatAPI has awoken!");
			Config = Utils.CreateGlobalConfigFile((BaseUnityPlugin)(object)this);
			GlobalSave = new JsonSave(Utils.GetPluginPersistentDataPath(), "GlobalSave");
			ConfigManager.Initialize(Config);
			MainThreadDispatcher.Initialize();
			TwitchChat.Initialize();
		}
	}
	internal static class TwitchChat
	{
		public const string ServerIP = "irc.chat.twitch.tv";

		public const int ServerPort = 6667;

		private static ConnectionState _connectionState = ConnectionState.None;

		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 = 5000;

		private static bool _explicitDisconnect;

		private static readonly object _connectionLock = new object();

		private static readonly SemaphoreSlim _readLock = new SemaphoreSlim(1, 1);

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

		public static string Channel => ConfigManager.TwitchChat_Channel.Value.Trim();

		public static ConnectionState ConnectionState
		{
			get
			{
				return _connectionState;
			}
			private set
			{
				if (_connectionState != value)
				{
					_connectionState = value;
					API.InvokeOnConnectionStateChanged(_connectionState);
				}
			}
		}

		public static void Initialize()
		{
			Application.quitting += OnApplicationQuit;
			if (Enabled)
			{
				Connect();
			}
		}

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

		public static void Connect(string channel)
		{
			ConfigManager.TwitchChat_Channel.Value = channel;
			Connect();
		}

		public static async Task ConnectAsync()
		{
			lock (_connectionLock)
			{
				if (_isReconnecting)
				{
					CancelReconnect();
				}
				_explicitDisconnect = false;
				_isReconnecting = false;
			}
			if (!Enabled)
			{
				Logger.LogError("Failed to connect to Twitch chat. Twitch chat has been disabled in the config settings.");
				return;
			}
			if (ConnectionState == ConnectionState.Connecting)
			{
				Logger.LogWarning("Twitch chat is already connecting.");
				return;
			}
			if (ConnectionState == ConnectionState.Connected)
			{
				Disconnect();
			}
			if (!UserHelper.IsValidUsername(Channel))
			{
				Logger.LogWarning("Failed to start Twitch chat connection: Invalid or empty channel name.");
				return;
			}
			Logger.LogInfo("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;
				Logger.LogInfo("Successfully connected to Twitch chat " + Channel + ".");
				API.InvokeOnConnect();
				await Task.Run((Func<Task?>)ListenAsync, _cts.Token);
			}
			catch (Exception arg)
			{
				ConnectionState = ConnectionState.Disconnected;
				_explicitDisconnect = false;
				Logger.LogError($"Failed to connect to Twitch chat {Channel}. {arg}");
				ScheduleReconnect();
			}
		}

		public static void Disconnect()
		{
			lock (_connectionLock)
			{
				_explicitDisconnect = true;
				CancelReconnect();
				if (ConnectionState != ConnectionState.Connected && ConnectionState != ConnectionState.Connecting)
				{
					Logger.LogInfo("Twitch chat is not connected or already disconnecting.");
					return;
				}
				ConnectionState = ConnectionState.Disconnecting;
				_cts?.Cancel();
				DisconnectStreams();
				ConnectionState = ConnectionState.Disconnected;
				Logger.LogInfo("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;
				}
				Logger.LogInfo($"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)
						{
							Logger.LogInfo("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 text = await SafeReadLineAsync(_cts.Token);
					if (text != null)
					{
						if (text.StartsWith("PING"))
						{
							Logger.LogInfo("Received PING, sending PONG...", extended: true);
							await (_writer?.WriteLineAsync("PONG :tmi.twitch.tv") ?? Task.CompletedTask).ConfigureAwait(continueOnCapturedContext: false);
						}
						else
						{
							MessageHelper.ProcessMessage(text);
						}
					}
				}
			}
			catch (TaskCanceledException)
			{
				Logger.LogInfo("Twitch chat listen task canceled.");
			}
			catch (OperationCanceledException)
			{
				Logger.LogInfo("Twitch chat listen task canceled.");
			}
			catch (Exception arg)
			{
				Logger.LogError($"Twitch chat listen task failed. {arg}");
				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()
		{
			Logger.LogInfo("Application is quitting. Disconnecting Twitch chat...");
			Disconnect();
		}

		public static void HandleEnabledChanged()
		{
			if (Enabled)
			{
				Connect();
			}
			else
			{
				Disconnect();
			}
		}

		public static void HandleChannelChanged()
		{
			if (Enabled)
			{
				Connect();
			}
		}
	}
	internal static class Utils
	{
		public static string GetPluginPersistentDataPath()
		{
			return Path.Combine(Application.persistentDataPath, "TwitchChatAPI");
		}

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

		public static ConfigFile CreateLocalConfigFile(BaseUnityPlugin plugin, string name = null, bool saveOnInit = false)
		{
			return CreateConfigFile(plugin, Paths.ConfigPath, name, saveOnInit);
		}

		public static ConfigFile CreateGlobalConfigFile(BaseUnityPlugin plugin, string name = null, bool saveOnInit = false)
		{
			string pluginPersistentDataPath = GetPluginPersistentDataPath();
			if (name == null)
			{
				name = "global";
			}
			return CreateConfigFile(plugin, pluginPersistentDataPath, name, saveOnInit);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "TwitchChatAPI";

		public const string PLUGIN_NAME = "TwitchChatAPI";

		public const string PLUGIN_VERSION = "2.0.0";
	}
}
namespace 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)
			{
				Logger.LogError("KeyExists: Data is null. Ensure the save file is properly loaded.");
				return false;
			}
			return _data.ContainsKey(key);
		}

		public T Load<T>(string key, T defaultValue = default(T), bool readFile = false)
		{
			if (TryLoad<T>(key, out var value, readFile))
			{
				return value;
			}
			return defaultValue;
		}

		public bool TryLoad<T>(string key, out T value, bool readFile = false)
		{
			//IL_0057: Expected O, but got Unknown
			value = default(T);
			if (readFile)
			{
				_data = ReadFile();
			}
			if (_data == null)
			{
				Logger.LogError("Load: 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;
					Logger.LogError("Load: JSON Conversion Error for key: " + key + ". " + ((Exception)(object)val3).Message);
				}
				catch (ArgumentNullException ex)
				{
					Logger.LogError("Load: Argument Null Error for key: " + key + ". " + ex.Message);
				}
				catch (Exception ex2)
				{
					Logger.LogError("Load: Unexpected Error for key: " + key + ". " + ex2.Message);
				}
				return false;
			}
			Logger.LogWarning("Load: Key '" + key + "' does not exist. Returning default value.", extended: true);
			return false;
		}

		public bool Save<T>(string key, T value)
		{
			if (_data == null)
			{
				Logger.LogError("Save: 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)
			{
				Logger.LogError("Save: Error saving key: " + key + ". " + ex.Message);
				return false;
			}
		}

		private JObject ReadFile()
		{
			//IL_0070: Expected O, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			try
			{
				if (!File.Exists(FilePath))
				{
					Logger.LogWarning("ReadFile: Save file does not exist at \"" + FilePath + "\". Initializing with an empty file.", extended: true);
					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;
				Logger.LogError("ReadFile: JSON Parsing Error for file: \"" + FilePath + "\". " + ((Exception)(object)val2).Message);
			}
			catch (Exception ex)
			{
				Logger.LogError("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)
			{
				Logger.LogError("WriteFile: Unexpected Error for file: \"" + FilePath + "\". " + ex.Message);
			}
			return false;
		}
	}
	internal class JsonSaveValue<T> : ObservableValue<T>
	{
		public JsonSave JsonSave { get; private set; }

		public string Key { get; private set; }

		public T DefaultValue { get; private set; }

		public bool ReadFile { get; private set; }

		public bool HasValue
		{
			get
			{
				T value;
				return TryLoad(out value);
			}
		}

		public JsonSaveValue(JsonSave jsonSave, string key, T defaultValue = default(T), bool readFile = false)
			: base(default(T))
		{
			JsonSave = jsonSave;
			Key = key;
			DefaultValue = defaultValue;
			ReadFile = readFile;
			CustomValueGetter = Load;
			CustomValueSetter = Save;
		}

		private T Load()
		{
			return JsonSave.Load(Key, DefaultValue, ReadFile);
		}

		private bool TryLoad(out T value)
		{
			return JsonSave.TryLoad<T>(Key, out value, ReadFile);
		}

		private void Save(T value)
		{
			if (!object.Equals(value, base.Value))
			{
				JsonSave.Save(Key, value);
			}
		}
	}
	internal class ObservableValue<T>
	{
		protected T _value;

		protected Func<T> CustomValueGetter;

		protected Action<T> CustomValueSetter;

		public T Value
		{
			get
			{
				return GetValue();
			}
			set
			{
				SetValue(value);
			}
		}

		public event Action<T> OnValueChanged;

		public ObservableValue(T initialValue = default(T))
		{
			_value = initialValue;
		}

		private T GetValue()
		{
			if (CustomValueGetter != null)
			{
				_value = CustomValueGetter();
			}
			return _value;
		}

		private void SetValue(T value)
		{
			if (!object.Equals(_value, value))
			{
				_value = value;
				CustomValueSetter?.Invoke(value);
				this.OnValueChanged?.Invoke(value);
			}
		}
	}
	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 TwitchEvent RemoveTags()
		{
			Tags = new Dictionary<string, string>();
			return this;
		}
	}
	public class TwitchSubEvent : TwitchEvent
	{
		public SubType Type { get; set; }

		public SubTier Tier { get; set; }

		public int CumulativeMonths { get; set; }

		public string RecipientUser { get; set; }

		public int GiftCount { get; set; }

		[Obsolete("Use Type instead.", true)]
		public SubType SubType => Type;

		[Obsolete("Use CumulativeMonths instead.", true)]
		public int Months => CumulativeMonths;

		[Obsolete("Use SubTier.Prime instead.", true)]
		public bool IsPrime => Tier == SubTier.Prime;
	}
	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 TwitchMessage RemoveTags()
		{
			TwitchMessage result = this;
			result.Tags = new Dictionary<string, string>();
			return result;
		}
	}
	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 TwitchRoomState RemoveTags()
		{
			TwitchRoomState result = this;
			result.Tags = new Dictionary<string, string>();
			return result;
		}
	}
	public struct TwitchUser : IEquatable<TwitchUser>
	{
		public string UserId { get; set; }

		public string Username { 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; }

		public override bool Equals(object obj)
		{
			if (obj is TwitchUser other)
			{
				return Equals(other);
			}
			return false;
		}

		public bool Equals(TwitchUser other)
		{
			return string.Equals(UserId, other.UserId, StringComparison.Ordinal);
		}

		public override int GetHashCode()
		{
			return UserId?.GetHashCode() ?? 0;
		}

		public static bool operator ==(TwitchUser left, TwitchUser right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(TwitchUser left, TwitchUser right)
		{
			return !left.Equals(right);
		}
	}
}
namespace TwitchChatAPI.MonoBehaviours
{
	public class MainThreadDispatcher : MonoBehaviour
	{
		private static readonly Queue<Action> _actions = new Queue<Action>();

		public static MainThreadDispatcher Instance { get; private set; }

		public static void Initialize()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			if (!((Object)(object)Instance != (Object)null))
			{
				GameObject val = new GameObject("TwitchChatAPI MainThreadDispatcher")
				{
					hideFlags = (HideFlags)61
				};
				Object.DontDestroyOnLoad((Object)(object)val);
				val.AddComponent<MainThreadDispatcher>();
			}
		}

		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;
			Logger.LogInfo("Spawned \"" + ((Object)((Component)this).gameObject).name + "\"", extended: true);
		}

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

		public static void Enqueue(Action action)
		{
			lock (_actions)
			{
				_actions.Enqueue(action);
			}
		}
	}
}
namespace TwitchChatAPI.Helpers
{
	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
				{
					Logger.LogInfo("Unhandled RAW message: " + message, extended: true);
				}
			}
			catch (Exception arg)
			{
				Logger.LogError($"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)
				{
					if (!tag.Contains('='))
					{
						return string.Empty;
					}
					int num3 = tag.IndexOf('=') + 1;
					return tag.Substring(num3, tag.Length - num3);
				});
				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(default(TwitchUser)))
				{
					UserHelper.UpdateUser(twitchUser);
					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)
			{
				Logger.LogError($"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"))
				};
				Logger.LogInfo("RAW cheer message: " + message, extended: true);
				Logger.LogInfo($"[!] Cheer event: {twitchCheerEvent.User.DisplayName} cheered {twitchCheerEvent.CheerAmount} bits!\n{JsonConvert.SerializeObject((object)twitchCheerEvent, (Formatting)1)}", extended: true);
				API.InvokeOnCheer(twitchCheerEvent);
			}
			catch (Exception arg)
			{
				Logger.LogError($"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)
				{
					if (!tag.Contains('='))
					{
						return string.Empty;
					}
					int num3 = tag.IndexOf('=') + 1;
					return tag.Substring(num3, tag.Length - num3);
				});
				string valueOrDefault = dictionary.GetValueOrDefault("msg-id", string.Empty);
				if (string.IsNullOrEmpty(valueOrDefault))
				{
					Logger.LogError("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(default(TwitchUser)))
				{
					UserHelper.UpdateUser(twitchUser);
					switch (valueOrDefault)
					{
					case "sub":
					case "resub":
					case "subgift":
					case "submysterygift":
						ProcessMessage_USERNOTICE_Sub(message, channel, twitchUser, chatMessage, dictionary);
						break;
					case "raid":
						ProcessMessage_USERNOTICE_Raid(message, channel, twitchUser, dictionary);
						break;
					default:
						Logger.LogInfo("Unhandled USERNOTICE message: " + message, extended: true);
						break;
					}
				}
			}
			catch (Exception arg)
			{
				Logger.LogError($"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))
				{
					Logger.LogError("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"))
				{
					Logger.LogInfo("Skipping subgift since it originates from a submysterygift. Message: " + message, extended: true);
					return;
				}
				SubTier tier = SubTier.One;
				if (tags.TryGetValue("msg-param-sub-plan", out var value) && !string.IsNullOrEmpty(value))
				{
					switch (value)
					{
					case "Prime":
						tier = SubTier.Prime;
						break;
					case "1000":
						tier = SubTier.One;
						break;
					case "2000":
						tier = SubTier.Two;
						break;
					case "3000":
						tier = SubTier.Three;
						break;
					}
				}
				TwitchSubEvent twitchSubEvent = new TwitchSubEvent
				{
					Channel = channel,
					User = twitchUser,
					Message = chatMessage,
					Tags = tags,
					Type = subType,
					Tier = tier,
					CumulativeMonths = 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"))
				};
				Logger.LogInfo("RAW subscription message: " + message, extended: true);
				Logger.LogInfo("[!] Subscription event: \n" + JsonConvert.SerializeObject((object)twitchSubEvent, (Formatting)1), extended: true);
				API.InvokeOnSub(twitchSubEvent);
			}
			catch (Exception arg)
			{
				Logger.LogError($"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"))
				};
				Logger.LogInfo("RAW raid message: " + message, extended: true);
				Logger.LogInfo($"[!] Raid detected: {twitchRaidEvent.User.DisplayName} is raiding with {twitchRaidEvent.ViewerCount} viewers!\n{JsonConvert.SerializeObject((object)twitchRaidEvent, (Formatting)1)}", extended: true);
				API.InvokeOnRaid(twitchRaidEvent);
			}
			catch (Exception arg)
			{
				Logger.LogError($"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;
				Logger.LogInfo("RAW roomstate message: " + message, extended: true);
				Logger.LogInfo("[!] Room state change detected: \n" + JsonConvert.SerializeObject((object)twitchRoomState2, (Formatting)1), extended: true);
				API.InvokeOnRoomStateUpdate(twitchRoomState2);
			}
			catch (Exception arg)
			{
				Logger.LogError($"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");
				string text = tags.GetValueOrDefault("color", "#FFFFFF");
				if (string.IsNullOrEmpty(text))
				{
					text = "#FFFFFF";
				}
				TwitchUser result = default(TwitchUser);
				result.UserId = tags.GetValueOrDefault("user-id", "0");
				result.Username = valueOrDefault.ToLower();
				result.DisplayName = valueOrDefault;
				result.Color = text;
				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)
			{
				Logger.LogError($"Failed to get TwitchUser: {arg}");
				return default(TwitchUser);
			}
		}
	}
	internal static class UserHelper
	{
		public static Dictionary<string, TwitchUser> Users { get; private set; } = new Dictionary<string, TwitchUser>();


		public static Dictionary<string, string> UsernameToUserId { get; private set; } = new Dictionary<string, string>();


		public static Dictionary<string, float> TimeLastSeen { get; private set; } = new Dictionary<string, float>();


		public static bool TryGetUserByUsername(string username, out TwitchUser twitchUser)
		{
			if (UsernameToUserId.TryGetValue(username.ToLower(), out var value) && Users.TryGetValue(value, out twitchUser))
			{
				return true;
			}
			twitchUser = default(TwitchUser);
			return false;
		}

		public static bool TryGetUserByUserId(string userId, out TwitchUser twitchUser)
		{
			return Users.TryGetValue(userId, out twitchUser);
		}

		public static TwitchUser[] GetUsersSeenWithin(TimeSpan timeSpan)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			float minTime = realtimeSinceStartup - (float)timeSpan.TotalSeconds;
			TwitchUser value;
			return (from entry in TimeLastSeen
				where entry.Value >= minTime
				select (!Users.TryGetValue(entry.Key, out value)) ? default(TwitchUser) : value into user
				where true
				select user).ToArray();
		}

		public static void UpdateUser(TwitchUser twitchUser)
		{
			if (!twitchUser.Equals(default(TwitchUser)))
			{
				Users[twitchUser.UserId] = twitchUser;
				string key = twitchUser.Username.ToLower();
				if (UsernameToUserId.TryGetValue(key, out var value) && value != twitchUser.UserId)
				{
					UsernameToUserId.Remove(key);
				}
				UsernameToUserId[key] = twitchUser.UserId;
				TimeLastSeen[twitchUser.UserId] = Time.realtimeSinceStartup;
			}
		}

		public static bool IsValidUsername(string username)
		{
			if (string.IsNullOrWhiteSpace(username))
			{
				return false;
			}
			Regex regex = new Regex("^[A-Za-z0-9][A-Za-z0-9_]{3,24}$");
			if (regex.IsMatch(username))
			{
				return !username.StartsWith("_");
			}
			return false;
		}
	}
}
namespace TwitchChatAPI.Extensions
{
	public static class TwitchUserExtensions
	{
		public static string GetDisplayNameWithColor(this TwitchUser twitchUser)
		{
			return "<color=" + twitchUser.Color + ">" + twitchUser.DisplayName + "</color>";
		}

		public static string GetDisplayNameWithColor(this TwitchUser twitchUser, Func<string, string> colorParser)
		{
			string text = ((colorParser != null) ? colorParser(twitchUser.Color) : twitchUser.Color);
			return "<color=" + text + ">" + twitchUser.DisplayName + "</color>";
		}
	}
}
namespace TwitchChatAPI.Enums
{
	public enum ConnectionState
	{
		None,
		Connecting,
		Connected,
		Disconnecting,
		Disconnected
	}
	public enum SubTier
	{
		Prime,
		One,
		Two,
		Three
	}
	public enum SubType
	{
		Sub,
		Resub,
		SubGift,
		SubMysteryGift
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace System.Diagnostics.CodeAnalysis
{
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	[DebuggerNonUserCode]
	internal sealed class MemberNotNullAttribute : Attribute
	{
		public string[] Members { get; }

		public MemberNotNullAttribute(string member)
		{
			Members = new string[1] { member };
		}

		public MemberNotNullAttribute(params string[] members)
		{
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	[DebuggerNonUserCode]
	internal sealed class MemberNotNullWhenAttribute : Attribute
	{
		public bool ReturnValue { get; }

		public string[] Members { get; }

		public MemberNotNullWhenAttribute(bool returnValue, string member)
		{
			ReturnValue = returnValue;
			Members = new string[1] { member };
		}

		public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
		{
			ReturnValue = returnValue;
			Members = members;
		}
	}
}

plugins/com.github.zehsteam.TwitchChatAPI.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using TwitchChatAPI;
using TwitchChatAPI.Objects;
using UnityEngine;
using com.github.zehsteam.TwitchChatAPI.Enums;
using com.github.zehsteam.TwitchChatAPI.Helpers;
using com.github.zehsteam.TwitchChatAPI.Objects;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("TwitchChatAPI")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Zehs")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2025 Zehs")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: AssemblyInformationalVersion("2.0.0+8d20404d44337407c8c2c06658bf8873112aeb86")]
[assembly: AssemblyProduct("TwitchChatAPI.Deprecated")]
[assembly: AssemblyTitle("com.github.zehsteam.TwitchChatAPI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.0.0.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.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;
		}
	}
	[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
{
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public static class API
	{
		internal const string ObsoleteMessage = "You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.";

		public static ConnectionState ConnectionState => Converter.Convert(API.ConnectionState, ConnectionState.None);

		public static IReadOnlyCollection<TwitchUser> Users => (IReadOnlyCollection<TwitchUser>)Converter.ConvertList<TwitchUser, TwitchUser>(API.Users);

		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 Initialize()
		{
			API.OnConnect += InvokeOnConnect;
			API.OnDisconnect += InvokeOnDisconnect;
			API.OnMessage += InvokeOnMessage;
			API.OnCheer += InvokeOnCheer;
			API.OnSub += InvokeOnSub;
			API.OnRaid += InvokeOnRaid;
			API.OnRoomStateUpdate += InvokeOnRoomStateUpdate;
			Application.quitting += delegate
			{
				API.OnConnect -= InvokeOnConnect;
				API.OnDisconnect -= InvokeOnDisconnect;
				API.OnMessage -= InvokeOnMessage;
				API.OnCheer -= InvokeOnCheer;
				API.OnSub -= InvokeOnSub;
				API.OnRaid -= InvokeOnRaid;
				API.OnRoomStateUpdate -= InvokeOnRoomStateUpdate;
			};
		}

		public static bool TryGetUserByUsername(string username, out TwitchUser twitchUser)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			TwitchUser val = default(TwitchUser);
			if (API.TryGetUserByUsername(username, ref val))
			{
				if (Converter.TryConvert<TwitchUser>(val, out twitchUser))
				{
					return true;
				}
				Logger.LogWarning("[Deprecated] API: Failed to get TwitchUser by Username. Could not convert TwitchUser.", extended: true);
				twitchUser = default(TwitchUser);
				return true;
			}
			twitchUser = default(TwitchUser);
			return false;
		}

		public static bool TryGetUserByUserId(string userId, out TwitchUser twitchUser)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			TwitchUser val = default(TwitchUser);
			if (API.TryGetUserByUserId(userId, ref val))
			{
				if (Converter.TryConvert<TwitchUser>(val, out twitchUser))
				{
					return true;
				}
				Logger.LogWarning("[Deprecated] API: Failed to get TwitchUser by UserId. Could not convert TwitchUser.", extended: true);
				twitchUser = default(TwitchUser);
				return true;
			}
			twitchUser = default(TwitchUser);
			return false;
		}

		public static TwitchUser[] GetUsersSeenWithin(TimeSpan timeSpan)
		{
			return Converter.ConvertList<TwitchUser, TwitchUser>(API.GetUsersSeenWithin(timeSpan)).ToArray();
		}

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

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

		internal static void InvokeOnMessage(TwitchMessage newMessage)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (API.OnMessage != null)
			{
				if (Converter.TryConvert<TwitchMessage>(newMessage, out var result))
				{
					API.OnMessage?.Invoke(result);
				}
				else
				{
					Logger.LogWarning("[Deprecated] API: Failed to invoke OnMessage. Could not convert TwitchMessage.", extended: true);
				}
			}
		}

		internal static void InvokeOnCheer(TwitchCheerEvent newCheerEvent)
		{
			if (API.OnCheer != null)
			{
				if (Converter.TryConvert<TwitchCheerEvent>(newCheerEvent, out var result))
				{
					API.OnCheer?.Invoke(result);
				}
				else
				{
					Logger.LogWarning("[Deprecated] API: Failed to invoke OnCheer. Could not convert TwitchCheerEvent.", extended: true);
				}
			}
		}

		internal static void InvokeOnSub(TwitchSubEvent newSubEvent)
		{
			if (API.OnSub != null)
			{
				if (Converter.TryConvert<TwitchSubEvent>(newSubEvent, out var result))
				{
					API.OnSub?.Invoke(result);
				}
				else
				{
					Logger.LogWarning("[Deprecated] API: Failed to invoke OnSub. Could not convert TwitchSubEvent.", extended: true);
				}
			}
		}

		internal static void InvokeOnRaid(TwitchRaidEvent newRaidEvent)
		{
			if (API.OnRaid != null)
			{
				if (Converter.TryConvert<TwitchRaidEvent>(newRaidEvent, out var result))
				{
					API.OnRaid?.Invoke(result);
				}
				else
				{
					Logger.LogWarning("[Deprecated] API: Failed to invoke OnRaid. Could not convert TwitchRaidEvent.", extended: true);
				}
			}
		}

		internal static void InvokeOnRoomStateUpdate(TwitchRoomState newRoomState)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (API.OnRoomStateUpdate != null)
			{
				if (Converter.TryConvert<TwitchRoomState>(newRoomState, out var result))
				{
					API.OnRoomStateUpdate?.Invoke(result);
				}
				else
				{
					Logger.LogWarning("[Deprecated] API: Failed to invoke OnRoomStateUpdate. Could not convert TwitchRoomState.", extended: true);
				}
			}
		}
	}
	internal static class Logger
	{
		public static ManualLogSource ManualLogSource { get; private set; }

		public static void Initialize(ManualLogSource manualLogSource)
		{
			ManualLogSource = manualLogSource;
		}

		public static void LogDebug(object data)
		{
			Log((LogLevel)32, data);
		}

		public static void LogInfo(object data, bool extended = false)
		{
			Log((LogLevel)16, data, extended);
		}

		public static void LogWarning(object data, bool extended = false)
		{
			Log((LogLevel)4, data, extended);
		}

		public static void LogError(object data, bool extended = false)
		{
			Log((LogLevel)2, data, extended);
		}

		public static void LogFatal(object data, bool extended = false)
		{
			Log((LogLevel)1, data, extended);
		}

		public static void Log(LogLevel logLevel, object data, bool extended = false)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (!extended || IsExtendedLoggingEnabled())
			{
				ManualLogSource manualLogSource = ManualLogSource;
				if (manualLogSource != null)
				{
					manualLogSource.Log(logLevel, data);
				}
			}
		}

		public static bool IsExtendedLoggingEnabled()
		{
			return Logger.IsExtendedLoggingEnabled();
		}
	}
	[BepInPlugin("com.github.zehsteam.TwitchChatAPI", "TwitchChatAPI.Deprecated", "2.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	internal class Plugin : BaseUnityPlugin
	{
		internal static Plugin Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			Logger.Initialize(Logger.CreateLogSource("com.github.zehsteam.TwitchChatAPI"));
			Logger.LogInfo("TwitchChatAPI.Deprecated has awoken!");
			InitializeAPI();
		}

		[Obsolete]
		private void InitializeAPI()
		{
			API.Initialize();
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "com.github.zehsteam.TwitchChatAPI";

		public const string PLUGIN_NAME = "TwitchChatAPI.Deprecated";

		public const string PLUGIN_VERSION = "2.0.0";
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Objects
{
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	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; }
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public class TwitchSubEvent : TwitchEvent
	{
		public SubType SubType { get; set; }

		public bool IsPrime { get; set; }

		public SubTier Tier { get; set; }

		public int CumulativeMonths { get; set; }

		public string RecipientUser { get; set; }

		public int GiftCount { get; set; }

		[Obsolete("Use CumulativeMonths instead.", true)]
		public int Months => CumulativeMonths;
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public class TwitchCheerEvent : TwitchEvent
	{
		public int CheerAmount { get; set; }
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public class TwitchRaidEvent : TwitchEvent
	{
		public int ViewerCount { get; set; }
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	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; }
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	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; }
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public struct TwitchUser : IEquatable<TwitchUser>
	{
		public string UserId { get; set; }

		public string Username { 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; }

		public override bool Equals(object obj)
		{
			if (obj is TwitchUser other)
			{
				return Equals(other);
			}
			return false;
		}

		public bool Equals(TwitchUser other)
		{
			return string.Equals(UserId, other.UserId, StringComparison.Ordinal);
		}

		public override int GetHashCode()
		{
			return UserId?.GetHashCode() ?? 0;
		}

		public static bool operator ==(TwitchUser left, TwitchUser right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(TwitchUser left, TwitchUser right)
		{
			return !left.Equals(right);
		}
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Helpers
{
	internal static class Converter
	{
		public static T Convert<T>(object source, T defaultValue) where T : new()
		{
			if (TryConvert<T>(source, out var result))
			{
				return result;
			}
			return defaultValue;
		}

		public static bool TryConvert<T>(object source, out T result) where T : new()
		{
			result = default(T);
			if (source == null)
			{
				return false;
			}
			try
			{
				string text = JsonConvert.SerializeObject(source);
				result = JsonConvert.DeserializeObject<T>(text);
				return result != null;
			}
			catch
			{
				return false;
			}
		}

		public static IEnumerable<T> ConvertList<T>(IEnumerable<object> items) where T : new()
		{
			return ConvertList<object, T>(items);
		}

		public static IEnumerable<TTarget> ConvertList<TSource, TTarget>(IEnumerable<TSource> items) where TTarget : new()
		{
			List<TTarget> list = new List<TTarget>();
			foreach (TSource item in items)
			{
				if (TryConvert<TTarget>(item, out var result))
				{
					list.Add(result);
				}
			}
			return list;
		}
	}
}
namespace com.github.zehsteam.TwitchChatAPI.Enums
{
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public enum ConnectionState
	{
		None,
		Connecting,
		Connected,
		Disconnecting,
		Disconnected
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public enum SubTier
	{
		One = 1,
		Two,
		Three
	}
	[Obsolete("You are using the deprecated version of TwitchChatAPI. You need to reference the new TwitchChatAPI.dll, update your BepInDependency to use the new GUID, and use the new namespaces.", true)]
	public enum SubType
	{
		Sub,
		Resub,
		SubGift,
		SubMysteryGift
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace System.Diagnostics.CodeAnalysis
{
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	[DebuggerNonUserCode]
	internal sealed class MemberNotNullAttribute : Attribute
	{
		public string[] Members { get; }

		public MemberNotNullAttribute(string member)
		{
			Members = new string[1] { member };
		}

		public MemberNotNullAttribute(params string[] members)
		{
			Members = members;
		}
	}
	[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
	[ExcludeFromCodeCoverage]
	[DebuggerNonUserCode]
	internal sealed class MemberNotNullWhenAttribute : Attribute
	{
		public bool ReturnValue { get; }

		public string[] Members { get; }

		public MemberNotNullWhenAttribute(bool returnValue, string member)
		{
			ReturnValue = returnValue;
			Members = new string[1] { member };
		}

		public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
		{
			ReturnValue = returnValue;
			Members = members;
		}
	}
}