Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TwitchChatAPI v2.0.0
plugins/TwitchChatAPI.dll
Decompiled 11 months agousing 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 11 months agousing 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; } } }