using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Photon.Pun; using TMPro; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace BingBongTwitch; public static class Babble { private const int OutputFrequency = 22050; private const float BlipOverlap = 1.45f; private const float EdgeFade = 0.004f; private static float[] _sourceSamples; private static int _sourceFrequency; private static readonly List _blipStarts = new List(); private static bool _preparing; public static bool Ready { get { if (_sourceSamples != null) { return _blipStarts.Count > 0; } return false; } } public static void Reset() { _sourceSamples = null; _blipStarts.Clear(); } public static AudioClip Build(string text, Action_AskBingBong action, float maxSeconds) { if (!Ready) { return null; } float num = Mathf.Max(0.02f, Plugin.CfgSyllableSeconds.Value); List list = PitchPerSyllable(text); if (list.Count == 0) { return null; } int num2 = Mathf.Max(1, Mathf.FloorToInt(maxSeconds / num)); if (list.Count > num2) { list.RemoveRange(num2, list.Count - num2); } float num3 = num * 1.45f; int num4 = Mathf.CeilToInt(num3 * 22050f); int num5 = Mathf.CeilToInt(num * 22050f); int num6 = num5 * list.Count + num4; float[] array = new float[num6]; for (int i = 0; i < list.Count; i++) { float num7 = list[i]; if (!(num7 <= 0f)) { RenderBlip(array, i * num5, num4, num7); } } Normalize(array); AudioClip val = AudioClip.Create("BingBongBabble", num6, 1, 22050, false); val.SetData(array, 0); return val; } private static void RenderBlip(float[] output, int outStart, int blipSamples, float pitch) { if (_blipStarts.Count == 0) { return; } int num = _blipStarts[Random.Range(0, _blipStarts.Count)]; float num2 = pitch * (float)_sourceFrequency / 22050f; int num3 = Mathf.Max(1, Mathf.CeilToInt(88.200005f)); float num4 = num; for (int i = 0; i < blipSamples; i++) { int num5 = outStart + i; if (num5 >= output.Length) { break; } int num6 = (int)num4; if (num6 + 1 >= _sourceSamples.Length) { break; } float num7 = num4 - (float)num6; float num8 = _sourceSamples[num6] * (1f - num7) + _sourceSamples[num6 + 1] * num7; float num9 = 1f; if (i < num3) { num9 = (float)i / (float)num3; } else if (i > blipSamples - num3) { num9 = (float)(blipSamples - i) / (float)num3; } output[num5] += num8 * num9; num4 += num2; } } private static List PitchPerSyllable(string text) { List list = new List(); float num = Plugin.CfgPitchMin.Value; float num2 = Plugin.CfgPitchMax.Value; if (num2 < num) { float num3 = num; num = num2; num2 = num3; } bool flag = false; for (int i = 0; i < text.Length; i++) { char c = char.ToLowerInvariant(text[i]); switch (c) { case ' ': flag = false; continue; case '!': case ',': case '.': case ':': case ';': case '?': list.Add(0f); flag = false; continue; } if (char.IsLetterOrDigit(c)) { bool flag2 = IsVowel(c); bool flag3 = (flag2 ? (!flag) : (list.Count == 0 || i == text.Length - 1)); flag = flag2; if (flag3) { float num4 = (float)(c * 7 % 11) / 10f; float item = Mathf.Lerp(num, num2, num4) * Random.Range(0.97f, 1.03f); list.Add(item); } } } if (list.Count == 0) { list.Add(Mathf.Lerp(num, num2, 0.5f)); } return list; } private static bool IsVowel(char c) { switch (c) { case 'a': case 'e': case 'i': case 'o': case 'u': case 'y': case 'à': case 'á': case 'â': case 'ã': case 'é': case 'ê': case 'í': case 'ó': case 'ô': case 'õ': case 'ú': return true; default: return false; } } public static IEnumerator Prepare(Action_AskBingBong action) { if (Ready || _preparing || (Object)(object)action == (Object)null || action.responses == null) { yield break; } _preparing = true; List clips = CollectClips(action); if (clips.Count == 0) { Plugin.Log.LogWarning((object)"O Bing Bong nao tem nenhum audio nas respostas."); _preparing = false; yield break; } for (int i = 0; i < clips.Count; i++) { if ((int)clips[i].loadState != 2) { clips[i].LoadAudioData(); } } float waited = 0f; while (waited < 8f) { bool stillLoading = false; for (int j = 0; j < clips.Count; j++) { if ((int)clips[j].loadState == 1) { stillLoading = true; break; } } if (!stillLoading) { break; } waited += Time.deltaTime; yield return null; } BuildBank(clips); _preparing = false; } private static List CollectClips(Action_AskBingBong action) { List list = new List(); for (int i = 0; i < action.responses.Length; i++) { BingBongResponse val = action.responses[i]; if (val == null || (Object)(object)val.sfx == (Object)null || val.sfx.clips == null) { continue; } for (int j = 0; j < val.sfx.clips.Length; j++) { AudioClip val2 = val.sfx.clips[j]; if ((Object)(object)val2 != (Object)null && !list.Contains(val2)) { list.Add(val2); } } } return list; } private static void BuildBank(List clips) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) List list = new List(); int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < clips.Count; i++) { AudioClip val = clips[i]; float[] array; try { array = new float[val.samples * val.channels]; if (!val.GetData(array, 0)) { num3++; continue; } } catch (Exception ex) { num3++; if (num3 == 1) { Plugin.Log.LogWarning((object)string.Concat("Falha lendo '", ((Object)val).name, "' (", val.loadType, ", estado ", val.loadState, "): ", ex.Message)); } continue; } if (num == 0) { num = val.frequency; } num2++; int num4 = Mathf.Max(1, val.channels); for (int j = 0; j + num4 <= array.Length; j += num4) { float num5 = 0f; for (int k = 0; k < num4; k++) { num5 += array[j + k]; } list.Add(num5 / (float)num4); } } if (list.Count == 0 || num == 0) { Plugin.Log.LogWarning((object)("Nao consegui ler os audios do Bing Bong: " + clips.Count + " clipes encontrados, " + num3 + " falharam.")); return; } _sourceSamples = list.ToArray(); _sourceFrequency = num; IndexLoudRegions(); Plugin.Log.LogInfo((object)("Banco de voz montado: " + num2 + "/" + clips.Count + " clipes, " + _sourceSamples.Length + " amostras, " + _blipStarts.Count + " trechos usaveis a " + _sourceFrequency + "Hz.")); } private static void IndexLoudRegions() { _blipStarts.Clear(); int num = Mathf.CeilToInt(0.12f * (float)_sourceFrequency); if (num <= 0 || _sourceSamples.Length <= num) { return; } double num2 = 0.0; for (int i = 0; i < _sourceSamples.Length; i++) { num2 += (double)Mathf.Abs(_sourceSamples[i]); } float num3 = (float)(num2 / (double)_sourceSamples.Length); float num4 = num3 * 1.3f; int num5 = Mathf.Max(1, num / 2); for (int j = 0; j + num < _sourceSamples.Length; j += num5) { float num6 = 0f; for (int k = 0; k < num; k += 4) { num6 += Mathf.Abs(_sourceSamples[j + k]); } float num7 = num6 / ((float)num / 4f); if (num7 >= num4) { _blipStarts.Add(j); } } if (_blipStarts.Count == 0) { for (int l = 0; l + num < _sourceSamples.Length; l += num) { _blipStarts.Add(l); } } } private static void Normalize(float[] buffer) { float num = 0f; for (int i = 0; i < buffer.Length; i++) { float num2 = Mathf.Abs(buffer[i]); if (num2 > num) { num = num2; } } if (!(num <= 0.0001f)) { float num3 = 0.85f / num; for (int j = 0; j < buffer.Length; j++) { buffer[j] *= num3; } } } } public class BingBongVoice : MonoBehaviour { public static readonly List Active = new List(); private Action_AskBingBong _action; private BingBongMouth _mouth; private Item _item; private PhotonView _view; private Coroutine _subtitleRoutine; private static Camera _cachedCamera; public Item Item => _item; private void Awake() { _action = ((Component)this).GetComponent(); _mouth = ((Component)this).GetComponentInChildren(true); _item = ((Component)this).GetComponent(); if ((Object)(object)_item == (Object)null) { _item = ((Component)this).GetComponentInParent(); } if ((Object)(object)_item == (Object)null) { _item = ((Component)this).GetComponentInChildren(true); } _view = ((Component)this).GetComponent(); if ((Object)(object)_view == (Object)null) { _view = ((Component)this).GetComponentInParent(); } if ((Object)(object)_action != (Object)null && !Babble.Ready) { ((MonoBehaviour)this).StartCoroutine(Babble.Prepare(_action)); } Plugin.Log.LogInfo((object)("Bing Bong encontrado -> action=" + ((Object)(object)_action != (Object)null) + " item=" + ((Object)(object)_item != (Object)null) + " mouth=" + ((Object)(object)_mouth != (Object)null) + " view=" + ((Object)(object)_view != (Object)null) + " responses=" + (((Object)(object)_action != (Object)null && _action.responses != null) ? _action.responses.Length : 0))); } private void OnEnable() { if (!Active.Contains(this)) { Active.Add(this); } } private void OnDisable() { Active.Remove(this); } public bool HeldByLocalPlayer() { if ((Object)(object)_item == (Object)null) { return false; } Character holderCharacter = _item.holderCharacter; if ((Object)(object)holderCharacter == (Object)null) { return false; } return (Object)(object)holderCharacter == (Object)(object)Character.localCharacter; } public string DescribeHolder() { if ((Object)(object)_item == (Object)null) { return "(item == null)"; } Character holderCharacter = _item.holderCharacter; string text = (((Object)(object)holderCharacter == (Object)null) ? "ninguem" : ((Object)holderCharacter).name); string text2 = (((Object)(object)Character.localCharacter == (Object)null) ? "null" : ((Object)Character.localCharacter).name); return "segurando=" + text + " local=" + text2; } public void Say(string author, string text) { if (Plugin.CfgSyncToOthers.Value && (Object)(object)_view != (Object)null && PhotonNetwork.InRoom) { Plugin.Log.LogInfo((object)("Enviando fala para a sala (" + PhotonNetwork.CurrentRoom.PlayerCount + " jogadores) via RPC.")); _view.RPC("BBTwitchSay", (RpcTarget)0, new object[2] { author, text }); return; } Plugin.Log.LogInfo((object)("Falando so aqui (sync=" + Plugin.CfgSyncToOthers.Value + " view=" + ((Object)(object)_view != (Object)null) + " naSala=" + PhotonNetwork.InRoom + ").")); SayLocal(author, text); } [PunRPC] public void BBTwitchSay(string author, string text) { SayLocal(author, text); } private void SayLocal(string author, string text) { if ((Object)(object)_action == (Object)null || string.IsNullOrEmpty(text)) { return; } string text2 = Sanitize(text, Plugin.CfgMaxLength.Value); if (!string.IsNullOrEmpty(text2)) { string text3 = text2; if (Plugin.CfgShowAuthor.Value && !string.IsNullOrEmpty(author)) { text3 = Sanitize(author, 25) + ": " + text2; } Plugin.Log.LogInfo((object)("Bing Bong falando: " + text3)); if (_subtitleRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_subtitleRoutine); } if ((Object)(object)_action.source != (Object)null) { _action.source.Stop(); } if ((Object)(object)_action.squishAnim != (Object)null) { _action.squishAnim.SetTrigger("Squish"); } VoiceMode value = Plugin.CfgVoiceMode.Value; if ((value == VoiceMode.ElevenLabs || value == VoiceMode.Ambos) && ElevenLabs.Configured) { ((MonoBehaviour)this).StartCoroutine(SpeakWithTts(text2, text3, value == VoiceMode.Ambos)); } else if (value == VoiceMode.ElevenLabs) { Plugin.Log.LogWarning((object)"Modo ElevenLabs sem chave configurada (F10 para colocar). Mostrando so a legenda."); _subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(text3, EstimateDuration(text2))); } else { SpeakBabble(text2, text3); } } } private void SpeakBabble(string body, string display) { if (!Babble.Ready) { ((MonoBehaviour)this).StartCoroutine(Babble.Prepare(_action)); } AudioClip val = Babble.Build(body, _action, Plugin.CfgMaxSeconds.Value); float duration; if ((Object)(object)val != (Object)null) { duration = Mathf.Max(val.length, Plugin.CfgMinSeconds.Value); if ((Object)(object)_action.source != (Object)null) { _action.source.pitch = 1f; _action.source.PlayOneShot(val, Plugin.CfgVolume.Value); } if ((Object)(object)_mouth != (Object)null) { _mouth.SampleAudioClip(val); } } else { Plugin.Log.LogWarning((object)"Nao consegui gerar a voz; mostrando so a legenda."); duration = EstimateDuration(body); } if (_subtitleRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_subtitleRoutine); } _subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(display, duration)); } private IEnumerator SpeakWithTts(string body, string display, bool babbleOnFailure) { _subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(display, EstimateDuration(body))); AudioClip clip = null; yield return ElevenLabs.Synthesize(body, delegate(AudioClip c) { clip = c; }); if ((Object)(object)clip == (Object)null) { if (babbleOnFailure) { Plugin.Log.LogInfo((object)"ElevenLabs falhou; o Bing Bong assume com a tagarelice."); SpeakBabble(body, display); } else { Plugin.Log.LogWarning((object)"ElevenLabs falhou. Sem tagarelice porque o modo e 'ElevenLabs' puro -- use 'Ambos' para ele assumir nesses casos."); } yield break; } float pitch = Plugin.CfgElevenPitch.Value; if ((Object)(object)_action.source != (Object)null) { _action.source.pitch = pitch; _action.source.PlayOneShot(clip, Plugin.CfgVolume.Value); } if ((Object)(object)_mouth != (Object)null) { _mouth.SampleAudioClip(clip); } float spoken = clip.length / Mathf.Max(0.1f, pitch); if (_subtitleRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_subtitleRoutine); } _subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(display, spoken)); } private static float EstimateDuration(string body) { return Mathf.Clamp((float)body.Length * Plugin.CfgSecondsPerChar.Value, Plugin.CfgMinSeconds.Value, Plugin.CfgMaxSeconds.Value); } private IEnumerator SubtitleRoutine(string display, float duration) { TextMeshPro subtitles = _action.subtitles; if ((Object)(object)subtitles == (Object)null) { yield break; } ((Component)subtitles).gameObject.SetActive(true); ((TMP_Text)subtitles).text = display; float t = 0f; while (t < duration) { t += Time.deltaTime; float fadeIn = t * 12f; float fadeOut = (duration - t) * 6f; ((TMP_Text)subtitles).alpha = Mathf.Clamp01(Mathf.Min(fadeIn, fadeOut)); Camera cam = GetCamera(); if ((Object)(object)cam != (Object)null) { Transform transform = subtitles.transform; float num = Vector3.Distance(transform.position, ((Component)cam).transform.position); if (_action.scaleCurve != null) { transform.localScale = Vector3.one * _action.scaleCurve.Evaluate(num); } transform.forward = ((Component)cam).transform.forward; } yield return null; } ((Component)subtitles).gameObject.SetActive(false); _subtitleRoutine = null; } private static Camera GetCamera() { if ((Object)(object)_cachedCamera != (Object)null) { return _cachedCamera; } if ((Object)(object)MainCamera.instance != (Object)null) { _cachedCamera = ((Component)MainCamera.instance).GetComponentInChildren(true); } if ((Object)(object)_cachedCamera == (Object)null) { _cachedCamera = Camera.main; } return _cachedCamera; } public static string Sanitize(string input, int maxLength) { if (string.IsNullOrEmpty(input)) { return ""; } StringBuilder stringBuilder = new StringBuilder(input.Length); foreach (char c in input) { switch (c) { case '<': stringBuilder.Append('‹'); continue; case '>': stringBuilder.Append('›'); continue; case '\t': case '\n': case '\r': stringBuilder.Append(' '); continue; } if (!char.IsControl(c)) { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (maxLength > 0 && text.Length > maxLength) { text = text.Substring(0, maxLength).TrimEnd() + "..."; } return text; } } public class ConfigWindow : MonoBehaviour { private const int WindowId = 731204; private bool _open; private Rect _window = new Rect(0f, 0f, 470f, 300f); private bool _centred; private static readonly string[] ModeLabels = new string[3] { "Bing Bong", "ElevenLabs", "Os dois" }; private static readonly VoiceMode[] Modes = new VoiceMode[3] { VoiceMode.Babble, VoiceMode.ElevenLabs, VoiceMode.Ambos }; private static readonly string[] ModelLabels = new string[2] { "Multi-idioma", "Turbo (rapido)" }; private static readonly string[] ModelIds = new string[2] { "eleven_multilingual_v2", "eleven_turbo_v2_5" }; private string _channel = ""; private string _apiKey = ""; private bool _showKey; private int _modeIndex; private int _modelIndex; private int _voiceIndex = -1; private bool _loadingVoices; private bool _onlyFreeVoices = true; private bool _voiceListOpen; private Vector2 _voiceScroll; private string _status = ""; private float _statusUntil; private CursorLockMode _previousLock; private bool _previousVisible; private float _autoOpenAt; private bool _firstRun; private void Start() { LoadFromConfig(); if (!Plugin.CfgSetupShown.Value) { _autoOpenAt = Time.realtimeSinceStartup + 5f; } } private void LoadFromConfig() { _channel = Plugin.CfgChannel.Value; _apiKey = Plugin.CfgElevenApiKey.Value; _modeIndex = 0; for (int i = 0; i < Modes.Length; i++) { if (Modes[i] == Plugin.CfgVoiceMode.Value) { _modeIndex = i; } } _modelIndex = 0; for (int j = 0; j < ModelIds.Length; j++) { if (ModelIds[j] == Plugin.CfgElevenModel.Value) { _modelIndex = j; } } _voiceIndex = -1; string text = Plugin.CfgElevenVoiceId.Value.Trim(); for (int k = 0; k < ElevenLabs.Voices.Length; k++) { if (ElevenLabs.Voices[k].Id == text) { _voiceIndex = k; } } } private void Update() { if (_autoOpenAt > 0f && Time.realtimeSinceStartup >= _autoOpenAt) { _autoOpenAt = 0f; _firstRun = true; if (!_open) { Open(); } Plugin.Log.LogInfo((object)"Primeira vez: abrindo a janela de configuracao. Depois disso, use F10."); } if (ToggleKeyPressed()) { if (_open) { Close(); } else { Open(); } } } private void ResetListState() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) _voiceListOpen = false; _voiceScroll = Vector2.zero; } private void Open() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) LoadFromConfig(); _open = true; _previousLock = Cursor.lockState; _previousVisible = Cursor.visible; ForceCursor(); if (ElevenLabs.Voices.Length == 0 && ElevenLabs.Configured && !_loadingVoices) { FetchVoices(); } } private void ForceCursor() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } if (!Cursor.visible) { Cursor.visible = true; } } private void LateUpdate() { if (_open) { ForceCursor(); } } private void Close() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) _open = false; ResetListState(); Cursor.lockState = _previousLock; Cursor.visible = _previousVisible; if (!Plugin.CfgSetupShown.Value) { Plugin.CfgSetupShown.Value = true; } _firstRun = false; } private static bool ToggleKeyPressed() { try { Keyboard current = Keyboard.current; if (current != null) { return ((ButtonControl)current[(Key)103]).wasPressedThisFrame; } } catch { } try { return Input.GetKeyDown((KeyCode)291); } catch { } return false; } private void OnGUI() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) if (_open) { ForceCursor(); if (!_centred) { ((Rect)(ref _window)).x = ((float)Screen.width - ((Rect)(ref _window)).width) / 2f; ((Rect)(ref _window)).y = ((float)Screen.height - ((Rect)(ref _window)).height) / 2f; _centred = true; } GUI.depth = 0; _window = GUILayout.Window(731204, _window, new WindowFunction(DrawWindow), "BingBongTwitch (F10 abre e fecha)", (GUILayoutOption[])(object)new GUILayoutOption[0]); } } private void DrawWindow(int id) { //IL_02b5: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(4f); if (_firstRun) { GUILayout.Label("Bem-vindo! Configure aqui e o Bing Bong passa a falar o seu chat.\nDepois, o F10 abre esta janela quando quiser.", (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Space(8f); } GUILayout.Label("Canal da Twitch", (GUILayoutOption[])(object)new GUILayoutOption[0]); _channel = GUILayout.TextField((_channel == null) ? "" : _channel, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("So o nome, sem o https://twitch.tv/", (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Space(10f); GUILayout.Label("Voz", (GUILayoutOption[])(object)new GUILayoutOption[0]); _modeIndex = GUILayout.Toolbar(_modeIndex, ModeLabels, (GUILayoutOption[])(object)new GUILayoutOption[0]); if (Modes[_modeIndex] == VoiceMode.Babble) { GUILayout.Label("Tagarelice feita com os sons do proprio Bing Bong. De graca, sem internet, instantaneo. A mensagem aparece na legenda.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } else if (Modes[_modeIndex] == VoiceMode.ElevenLabs) { GUILayout.Label("So ElevenLabs. Se falhar ou acabarem os creditos, sai apenas a legenda.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } else { GUILayout.Label("Tenta o ElevenLabs; quando ele falha, o Bing Bong assume com a tagarelice. Nunca fica mudo.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } if (Modes[_modeIndex] != VoiceMode.Babble) { GUILayout.Space(6f); GUILayout.Label("Chave do ElevenLabs", (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]); if (_showKey) { _apiKey = GUILayout.TextField((_apiKey == null) ? "" : _apiKey, (GUILayoutOption[])(object)new GUILayoutOption[0]); } else { _apiKey = GUILayout.PasswordField((_apiKey == null) ? "" : _apiKey, '*', (GUILayoutOption[])(object)new GUILayoutOption[0]); } _showKey = GUILayout.Toggle(_showKey, " ver", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) }); GUILayout.EndHorizontal(); GUILayout.Label("Fica so neste PC. Gasta creditos da sua conta.", (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Space(8f); DrawVoicePicker(); GUILayout.Space(6f); GUILayout.Label("Modelo", (GUILayoutOption[])(object)new GUILayoutOption[0]); _modelIndex = GUILayout.Toolbar(_modelIndex, ModelLabels, (GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label((_modelIndex == 0) ? "Fala portugues e ingles bem. Um pouco mais lento." : "Responde mais rapido e gasta menos creditos.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.Space(10f); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]); if (GUILayout.Button("Salvar", (GUILayoutOption[])(object)new GUILayoutOption[0])) { Save(); } if (GUILayout.Button("Fechar", (GUILayoutOption[])(object)new GUILayoutOption[0])) { Close(); } GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(_status) && Time.realtimeSinceStartup < _statusUntil) { GUILayout.Label(_status, (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f)); } private void DrawVoicePicker() { //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]); GUILayout.Label("Voz:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(35f) }); if (ElevenLabs.Voices.Length == 0) { GUILayout.Label(_loadingVoices ? "buscando..." : "(clique em Buscar)", (GUILayoutOption[])(object)new GUILayoutOption[0]); } else { string text = ((_voiceIndex < 0) ? "(primeira da conta)" : ElevenLabs.Voices[_voiceIndex].ToString()); if (GUILayout.Button(text + " " + (_voiceListOpen ? "▲" : "▼"), (GUILayoutOption[])(object)new GUILayoutOption[0])) { _voiceListOpen = !_voiceListOpen; } } GUI.enabled = !_loadingVoices; if (GUILayout.Button("Buscar", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { FetchVoices(); } GUI.enabled = true; GUILayout.EndHorizontal(); if (_voiceListOpen && ElevenLabs.Voices.Length > 0) { DrawVoiceList(); } if (ElevenLabs.Voices.Length > 0) { bool onlyFreeVoices = _onlyFreeVoices; _onlyFreeVoices = GUILayout.Toggle(_onlyFreeVoices, " Esconder vozes que exigem plano pago", (GUILayoutOption[])(object)new GUILayoutOption[0]); if (onlyFreeVoices != _onlyFreeVoices) { _voiceScroll = Vector2.zero; } if (_voiceIndex >= 0 && !ElevenLabs.Voices[_voiceIndex].FreePlanFriendly) { GUILayout.Label("Esta voz e da biblioteca. No plano gratuito a API recusa ela e o Bing Bong nao fala.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } } } private void DrawVoiceList() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) _voiceScroll = GUILayout.BeginScrollView(_voiceScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(160f) }); int num = 0; for (int i = 0; i < ElevenLabs.Voices.Length; i++) { ElevenLabs.VoiceInfo voiceInfo = ElevenLabs.Voices[i]; if (!_onlyFreeVoices || voiceInfo.FreePlanFriendly) { num++; string text = ((i == _voiceIndex) ? "• " : " "); if (GUILayout.Button(text + voiceInfo.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[0])) { _voiceIndex = i; _voiceListOpen = false; } } } if (num == 0) { GUILayout.Label("Nenhuma voz do plano gratuito nesta conta.\nDesmarque a opcao abaixo para ver as outras.", (GUILayoutOption[])(object)new GUILayoutOption[0]); } GUILayout.EndScrollView(); } private void FetchVoices() { Plugin.CfgElevenApiKey.Value = ((_apiKey == null) ? "" : _apiKey).Trim(); _loadingVoices = true; ((MonoBehaviour)this).StartCoroutine(ElevenLabs.FetchVoices(OnVoicesLoaded)); } private void OnVoicesLoaded(string error) { _loadingVoices = false; if (!string.IsNullOrEmpty(error)) { SetStatus(error); return; } string text = Plugin.CfgElevenVoiceId.Value.Trim(); _voiceIndex = -1; for (int i = 0; i < ElevenLabs.Voices.Length; i++) { if (ElevenLabs.Voices[i].Id == text) { _voiceIndex = i; } } if (_voiceIndex < 0) { ElevenLabs.VoiceInfo voiceInfo = ElevenLabs.PickDefaultVoice(); for (int j = 0; j < ElevenLabs.Voices.Length; j++) { if (ElevenLabs.Voices[j] == voiceInfo) { _voiceIndex = j; } } } int num = 0; for (int k = 0; k < ElevenLabs.Voices.Length; k++) { if (ElevenLabs.Voices[k].FreePlanFriendly) { num++; } } SetStatus(ElevenLabs.Voices.Length + " vozes (" + num + " funcionam no plano gratuito). Use < e > para escolher."); } private void Save() { string text = ((_channel == null) ? "" : _channel).Trim(); string value = ((_apiKey == null) ? "" : _apiKey).Trim(); VoiceMode voiceMode = Modes[_modeIndex]; if (voiceMode != VoiceMode.Babble && string.IsNullOrEmpty(value)) { SetStatus("Escreva a chave, ou escolha 'Bing Bong'."); return; } Plugin.CfgElevenApiKey.Value = value; Plugin.CfgVoiceMode.Value = voiceMode; Plugin.CfgElevenModel.Value = ModelIds[_modelIndex]; if (_voiceIndex >= 0 && _voiceIndex < ElevenLabs.Voices.Length) { Plugin.CfgElevenVoiceId.Value = ElevenLabs.Voices[_voiceIndex].Id; } Plugin.CfgChannel.Value = text; ElevenLabs.Reset(); if (!Plugin.CfgSetupShown.Value) { Plugin.CfgSetupShown.Value = true; } _firstRun = false; Plugin.Log.LogInfo((object)("Configuracao salva pela janela do jogo. Canal: " + (string.IsNullOrEmpty(text) ? "(vazio)" : text) + " | voz: " + Plugin.CfgVoiceMode.Value)); SetStatus("Salvo! Conectando em #" + text + "..."); } private void SetStatus(string message) { _status = message; _statusUntil = Time.realtimeSinceStartup + 6f; } } public static class ElevenLabs { public class VoiceInfo { public string Id; public string Name; public string Description; public bool FreePlanFriendly; public override string ToString() { string text = Name; if (!string.IsNullOrEmpty(Description)) { text = text + " - " + Description; } if (!FreePlanFriendly) { text += " [plano pago]"; } return text; } } private const string BaseUrl = "https://api.elevenlabs.io/v1"; private static string _resolvedVoiceId; private static bool _voiceLookupDone; private static float _lastRequestTime = -999f; private static int _fileCounter; public static VoiceInfo[] Voices = new VoiceInfo[0]; public static bool Configured { get { if (Plugin.CfgElevenApiKey != null) { return !string.IsNullOrEmpty(Plugin.CfgElevenApiKey.Value.Trim()); } return false; } } public static void Reset() { _resolvedVoiceId = null; _voiceLookupDone = false; } public static IEnumerator Synthesize(string text, Action onReady) { if (!Configured) { onReady(null); yield break; } float since = Time.realtimeSinceStartup - _lastRequestTime; float minGap = Plugin.CfgElevenMinGap.Value; if (since < minGap) { Plugin.Log.LogInfo((object)("ElevenLabs: ignorando (faltam " + (minGap - since).ToString("0.0") + "s para a proxima chamada permitida).")); onReady(null); yield break; } _lastRequestTime = Time.realtimeSinceStartup; if (!_voiceLookupDone) { yield return ResolveVoiceId(); _voiceLookupDone = true; } string voiceId = _resolvedVoiceId; if (string.IsNullOrEmpty(voiceId)) { Plugin.Log.LogWarning((object)"ElevenLabs: nenhuma voz disponivel. Preencha VoiceId no config."); onReady(null); yield break; } JObject body = new JObject(); body["text"] = JToken.op_Implicit(text); body["model_id"] = JToken.op_Implicit(Plugin.CfgElevenModel.Value); string url = "https://api.elevenlabs.io/v1/text-to-speech/" + voiceId + "?output_format=mp3_44100_128"; byte[] payload = Encoding.UTF8.GetBytes(((JToken)body).ToString((Formatting)0, (JsonConverter[])(object)new JsonConverter[0])); byte[] audio = null; UnityWebRequest request = new UnityWebRequest(url, "POST"); try { request.uploadHandler = (UploadHandler)new UploadHandlerRaw(payload); request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("xi-api-key", Plugin.CfgElevenApiKey.Value.Trim()); request.timeout = Mathf.CeilToInt(Plugin.CfgElevenTimeout.Value); float started = Time.realtimeSinceStartup; yield return request.SendWebRequest(); if ((int)request.result != 1) { string text2 = Describe(request); if (request.responseCode == 402 && text2.IndexOf("library voices", StringComparison.OrdinalIgnoreCase) >= 0) { Plugin.Log.LogWarning((object)"ElevenLabs: essa voz e da biblioteca e o plano gratuito nao deixa usa-la pela API. Aperte F10 e escolha uma voz SEM a marca [plano pago]."); } else if (request.responseCode == 401) { Plugin.Log.LogWarning((object)"ElevenLabs: chave invalida. Aperte F10 e confira."); } else if (request.responseCode == 429) { Plugin.Log.LogWarning((object)"ElevenLabs: muitas chamadas seguidas ou creditos esgotados."); } else { Plugin.Log.LogWarning((object)("ElevenLabs falhou (" + request.responseCode + "): " + request.error + " " + text2)); } onReady(null); yield break; } audio = request.downloadHandler.data; Plugin.Log.LogInfo((object)("ElevenLabs respondeu em " + (Time.realtimeSinceStartup - started).ToString("0.00") + "s (" + ((audio != null) ? audio.Length : 0) + " bytes).")); } finally { ((IDisposable)request)?.Dispose(); } if (audio == null || audio.Length == 0) { onReady(null); yield break; } string path = Path.Combine(Application.temporaryCachePath, "bbtwitch_" + _fileCounter++ % 4 + ".mp3"); bool written = false; try { File.WriteAllBytes(path, audio); written = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("ElevenLabs: nao consegui salvar o audio: " + ex.Message)); } if (!written) { onReady(null); yield break; } UnityWebRequest load = UnityWebRequestMultimedia.GetAudioClip("file://" + path.Replace("\\", "/"), (AudioType)13); try { yield return load.SendWebRequest(); if ((int)load.result != 1) { Plugin.Log.LogWarning((object)("ElevenLabs: nao consegui decodificar o MP3: " + load.error)); onReady(null); } else { AudioClip clip = DownloadHandlerAudioClip.GetContent(load); onReady(clip); } } finally { ((IDisposable)load)?.Dispose(); } } private static IEnumerator ResolveVoiceId() { string configured = Plugin.CfgElevenVoiceId.Value.Trim(); if (!string.IsNullOrEmpty(configured)) { _resolvedVoiceId = configured; yield break; } yield return FetchVoices(null); VoiceInfo pick = PickDefaultVoice(); if (pick != null) { _resolvedVoiceId = pick.Id; Plugin.Log.LogInfo((object)("ElevenLabs: usando a voz '" + pick.Name + "'. Aperte F10 para escolher outra.")); } } public static VoiceInfo PickDefaultVoice() { VoiceInfo voiceInfo = null; for (int i = 0; i < Voices.Length; i++) { VoiceInfo voiceInfo2 = Voices[i]; if (voiceInfo2.FreePlanFriendly) { if (voiceInfo == null) { voiceInfo = voiceInfo2; } string text = (voiceInfo2.Name + " " + voiceInfo2.Description).ToLowerInvariant(); if (text.Contains("portug") || text.Contains("brazil") || text.Contains("brasil")) { return voiceInfo2; } } } if (voiceInfo != null) { return voiceInfo; } if (Voices.Length <= 0) { return null; } return Voices[0]; } public static IEnumerator FetchVoices(Action onDone) { if (!Configured) { onDone?.Invoke("Escreva a chave primeiro."); yield break; } UnityWebRequest request = UnityWebRequest.Get("https://api.elevenlabs.io/v1/voices"); try { request.SetRequestHeader("xi-api-key", Plugin.CfgElevenApiKey.Value.Trim()); request.timeout = 20; yield return request.SendWebRequest(); if ((int)request.result != 1) { string text = "Nao consegui buscar as vozes (" + request.responseCode + ")"; if (request.responseCode == 401) { text = "Chave invalida."; } Plugin.Log.LogWarning((object)("ElevenLabs: " + text + " " + request.error)); onDone?.Invoke(text); yield break; } List found = new List(); string failure = null; try { JObject val = JObject.Parse(request.downloadHandler.text); JToken obj = val["voices"]; JArray val2 = (JArray)(object)((obj is JArray) ? obj : null); if (val2 != null) { for (int i = 0; i < ((JContainer)val2).Count; i++) { VoiceInfo voiceInfo = new VoiceInfo(); voiceInfo.Id = (string)val2[i][(object)"voice_id"]; voiceInfo.Name = (string)val2[i][(object)"name"]; JToken val3 = val2[i][(object)"labels"]; if (val3 != null) { string text2 = (string)val3[(object)"accent"]; string text3 = (string)val3[(object)"gender"]; string text4 = (string)val3[(object)"language"]; voiceInfo.Description = string.Join(" ", text3, text2, text4).Trim(); } string text5 = (string)val2[i][(object)"category"]; voiceInfo.FreePlanFriendly = text5 == "premade"; if (!string.IsNullOrEmpty(voiceInfo.Id)) { found.Add(voiceInfo); } } } } catch (Exception ex) { failure = "Resposta ilegivel: " + ex.Message; Plugin.Log.LogWarning((object)("ElevenLabs: " + failure)); } if (failure != null) { onDone?.Invoke(failure); yield break; } Voices = found.ToArray(); Plugin.Log.LogInfo((object)("ElevenLabs: " + Voices.Length + " vozes na conta.")); onDone?.Invoke((Voices.Length == 0) ? "A conta nao tem nenhuma voz." : null); } finally { ((IDisposable)request)?.Dispose(); } } private static string Describe(UnityWebRequest request) { if (request.downloadHandler == null) { return ""; } string text = request.downloadHandler.text; if (string.IsNullOrEmpty(text)) { return ""; } if (text.Length > 300) { text = text.Substring(0, 300); } return text; } } public static class Patches { [HarmonyPostfix] [HarmonyPatch(typeof(ItemActionBase), "Start")] public static void AttachVoice(ItemActionBase __instance) { Action_AskBingBong val = (Action_AskBingBong)(object)((__instance is Action_AskBingBong) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject.GetComponent() != (Object)null)) { ((Component)val).gameObject.AddComponent(); PhotonView component = ((Component)val).gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { component.RefreshRpcMonoBehaviourCache(); } } } [HarmonyPrefix] [HarmonyPatch(typeof(Action_AskBingBong), "RunAction")] public static bool SpeakChatOnUse(Action_AskBingBong __instance) { if (!Plugin.CfgEnabled.Value) { return true; } if (Plugin.CfgMode.Value == SpeakMode.Auto) { return true; } ChatMessage lastMessage = Plugin.LastMessage; if (lastMessage == null) { Plugin.Log.LogWarning((object)"Usou o Bing Bong, mas nenhuma mensagem do chat chegou ainda -> fala normal."); return true; } BingBongVoice bingBongVoice = ((Component)__instance).gameObject.GetComponent(); if ((Object)(object)bingBongVoice == (Object)null) { Plugin.Log.LogWarning((object)"BingBongVoice nao estava anexado -> anexando agora."); bingBongVoice = ((Component)__instance).gameObject.AddComponent(); } if (!bingBongVoice.HeldByLocalPlayer()) { Plugin.Log.LogWarning((object)("Bing Bong nao esta na mao do jogador local -> fala normal. " + bingBongVoice.DescribeHolder())); return true; } bingBongVoice.Say(lastMessage.Author, lastMessage.Text); return false; } } public enum VoiceMode { Babble, ElevenLabs, Ambos } public enum SpeakMode { OnUse, Auto, Both } [BepInPlugin("rodr313.bingbongtwitch", "BingBongTwitch", "1.0.3")] public class Plugin : BaseUnityPlugin { public const string Guid = "rodr313.bingbongtwitch"; public const string Name = "BingBongTwitch"; public const string Version = "1.0.3"; public static Plugin Instance; public static ManualLogSource Log; public static ConfigEntry CfgChannel; public static ConfigEntry CfgEnabled; public static ConfigEntry CfgMode; public static ConfigEntry CfgCommandPrefix; public static ConfigEntry CfgOnlySubs; public static ConfigEntry CfgAutoCooldown; public static ConfigEntry CfgMaxLength; public static ConfigEntry CfgShowAuthor; public static ConfigEntry CfgSyncToOthers; public static ConfigEntry CfgVolume; public static ConfigEntry CfgPitchMin; public static ConfigEntry CfgPitchMax; public static ConfigEntry CfgSyllableSeconds; public static ConfigEntry CfgVoiceMode; public static ConfigEntry CfgElevenApiKey; public static ConfigEntry CfgElevenVoiceId; public static ConfigEntry CfgElevenModel; public static ConfigEntry CfgElevenPitch; public static ConfigEntry CfgElevenMinGap; public static ConfigEntry CfgElevenTimeout; public static ConfigEntry CfgSecondsPerChar; public static ConfigEntry CfgMinSeconds; public static ConfigEntry CfgMaxSeconds; public static ChatMessage LastMessage; public static ConfigEntry CfgSetupShown; public static ConfigEntry CfgVerbose; public static ConfigEntry CfgTestOnConnect; private TwitchIrc _irc; private Harmony _harmony; private float _nextAutoSpeak; private bool _announcedConnection; private void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfig(); if (!CfgEnabled.Value) { Log.LogInfo((object)"Desligado no config (Enabled = false). Nenhum patch aplicado."); return; } _harmony = new Harmony("rodr313.bingbongtwitch"); _harmony.PatchAll(typeof(Patches)); ((Component)this).gameObject.AddComponent(); Log.LogInfo((object)"Aperte F10 dentro do jogo para escolher o canal da Twitch e a voz."); Connect(); } private void BindConfig() { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Expected O, but got Unknown //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Expected O, but got Unknown //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Expected O, but got Unknown //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Expected O, but got Unknown //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d8: Expected O, but got Unknown //IL_040c: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Expected O, but got Unknown //IL_044a: Unknown result type (might be due to invalid IL or missing references) //IL_0454: Expected O, but got Unknown CfgEnabled = ((BaseUnityPlugin)this).Config.Bind("1. Twitch", "Enabled", true, "Liga o mod."); CfgChannel = ((BaseUnityPlugin)this).Config.Bind("1. Twitch", "Channel", "mrfalll", "Nome do seu canal da Twitch (so o nome, sem o https://twitch.tv/). Vem preenchido com mrfalll -- troque pelo seu canal. A leitura do chat e anonima: nao precisa de token, senha nem API."); CfgMode = ((BaseUnityPlugin)this).Config.Bind("2. Comportamento", "Mode", SpeakMode.OnUse, "OnUse = ele fala a mensagem mais recente quando voce usa o Bing Bong. Auto = ele fala sozinho cada mensagem nova. Both = os dois."); CfgCommandPrefix = ((BaseUnityPlugin)this).Config.Bind("2. Comportamento", "CommandPrefix", "", "Se preenchido (ex: !bb), so mensagens que comecam com isso contam, e o prefixo e removido antes de falar. Vazio = qualquer mensagem."); CfgOnlySubs = ((BaseUnityPlugin)this).Config.Bind("2. Comportamento", "OnlySubsAndMods", false, "So aceita mensagens de subs, VIPs, mods e do dono do canal."); CfgAutoCooldown = ((BaseUnityPlugin)this).Config.Bind("2. Comportamento", "AutoCooldownSeconds", 6f, new ConfigDescription("No modo Auto, tempo minimo entre uma fala e outra.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), new object[0])); CfgMaxLength = ((BaseUnityPlugin)this).Config.Bind("2. Comportamento", "MaxLength", 140, new ConfigDescription("Mensagens maiores que isso sao cortadas.", (AcceptableValueBase)(object)new AcceptableValueRange(20, 400), new object[0])); CfgShowAuthor = ((BaseUnityPlugin)this).Config.Bind("2. Comportamento", "ShowAuthor", true, "Mostra o nome de quem escreveu antes da mensagem."); CfgSyncToOthers = ((BaseUnityPlugin)this).Config.Bind("3. Multiplayer", "SyncToOthers", true, "Manda a fala pra todo mundo na sala via Photon. Quem nao tiver o mod instalado nao vai ver (e vai logar um aviso no console dele)."); CfgVolume = ((BaseUnityPlugin)this).Config.Bind("4. Voz", "Volume", 1f, new ConfigDescription("Volume da voz do Bing Bong.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), new object[0])); CfgPitchMin = ((BaseUnityPlugin)this).Config.Bind("4. Voz", "PitchMin", 0.85f, new ConfigDescription("Pitch mais grave da tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2f), new object[0])); CfgPitchMax = ((BaseUnityPlugin)this).Config.Bind("4. Voz", "PitchMax", 1.35f, new ConfigDescription("Pitch mais agudo da tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2f), new object[0])); CfgSyllableSeconds = ((BaseUnityPlugin)this).Config.Bind("4. Voz", "SyllableSeconds", 0.085f, new ConfigDescription("Duracao de cada silaba. Menor = ele fala mais rapido.", (AcceptableValueBase)(object)new AcceptableValueRange(0.03f, 0.3f), new object[0])); CfgVoiceMode = ((BaseUnityPlugin)this).Config.Bind("4. Voz", "VoiceMode", VoiceMode.Ambos, "Babble = tagarelice feita com os sons do proprio Bing Bong (offline e de graca). ElevenLabs = fala de verdade, lendo a mensagem (precisa da ApiKey e gasta creditos); se falhar, sai so a legenda. Ambos = tenta o ElevenLabs e, quando ele falha, o Babble assume."); CfgElevenApiKey = ((BaseUnityPlugin)this).Config.Bind("7. ElevenLabs", "ApiKey", "", "Sua chave da API do ElevenLabs. Fica so nesta maquina. NUNCA mande este arquivo para outra pessoa: quem tiver a chave gasta os seus creditos."); CfgElevenVoiceId = ((BaseUnityPlugin)this).Config.Bind("7. ElevenLabs", "VoiceId", "", "Id da voz. Deixe vazio para usar a primeira voz da sua conta (o log mostra a lista completa com os ids para voce escolher)."); CfgElevenModel = ((BaseUnityPlugin)this).Config.Bind("7. ElevenLabs", "ModelId", "eleven_multilingual_v2", "Modelo usado. 'eleven_multilingual_v2' fala portugues bem. 'eleven_turbo_v2_5' responde mais rapido e custa menos."); CfgElevenPitch = ((BaseUnityPlugin)this).Config.Bind("7. ElevenLabs", "Pitch", 1.35f, new ConfigDescription("Acelera a voz para soar agudo tipo Bing Bong. 1 = voz normal.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2.5f), new object[0])); CfgElevenMinGap = ((BaseUnityPlugin)this).Config.Bind("7. ElevenLabs", "MinSecondsBetweenRequests", 5f, new ConfigDescription("Protecao de creditos: tempo minimo entre duas chamadas na API.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 120f), new object[0])); CfgElevenTimeout = ((BaseUnityPlugin)this).Config.Bind("7. ElevenLabs", "TimeoutSeconds", 15f, new ConfigDescription("Desiste da chamada depois desse tempo e usa a tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange(3f, 60f), new object[0])); CfgSecondsPerChar = ((BaseUnityPlugin)this).Config.Bind("5. Legenda", "SecondsPerChar", 0.075f, new ConfigDescription("Quanto tempo de fala cada caractere vale.", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.3f), new object[0])); CfgMinSeconds = ((BaseUnityPlugin)this).Config.Bind("5. Legenda", "MinSeconds", 1.8f, new ConfigDescription("Duracao minima da fala.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 10f), new object[0])); CfgMaxSeconds = ((BaseUnityPlugin)this).Config.Bind("5. Legenda", "MaxSeconds", 9f, new ConfigDescription("Duracao maxima da fala.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), new object[0])); CfgSetupShown = ((BaseUnityPlugin)this).Config.Bind("1. Twitch", "SetupShown", false, "Controla a janela de configuracao que abre sozinha na primeira vez. Volte para false se quiser que ela apareca de novo."); CfgVerbose = ((BaseUnityPlugin)this).Config.Bind("6. Diagnostico", "Verbose", true, "Escreve no log cada mensagem do chat que chega, e o motivo quando o Bing Bong solta uma fala normal em vez da mensagem."); CfgTestOnConnect = ((BaseUnityPlugin)this).Config.Bind("6. Diagnostico", "TestMessageOnConnect", true, "Ao conectar no chat, ja deixa uma mensagem de teste engatilhada, pra voce conseguir testar o mod sem esperar alguem escrever."); TwitchIrc.Verbose = CfgVerbose.Value; CfgVerbose.SettingChanged += delegate { TwitchIrc.Verbose = CfgVerbose.Value; }; CfgChannel.SettingChanged += delegate { Reconnect(); }; CfgEnabled.SettingChanged += delegate { Reconnect(); }; } private void Connect() { if (string.IsNullOrEmpty(CfgChannel.Value)) { Log.LogWarning((object)"Nenhum canal escolhido. APERTE F10 dentro do jogo e escreva o nome do seu canal da Twitch."); return; } _irc = new TwitchIrc(CfgChannel.Value, delegate(string m) { Log.LogInfo((object)m); }, delegate(string m) { Log.LogWarning((object)m); }); _irc.Start(); } private void Reconnect() { if (_irc != null) { _irc.Stop(); _irc = null; } if (CfgEnabled.Value) { Connect(); } } private void Update() { if (_irc == null) { return; } if (_irc.Connected && !_announcedConnection) { _announcedConnection = true; if (CfgTestOnConnect.Value && LastMessage == null) { ChatMessage chatMessage = new ChatMessage(); chatMessage.Author = "BingBongTwitch"; chatMessage.Text = "mod conectado! use o Bing Bong pra me ouvir"; LastMessage = chatMessage; Log.LogInfo((object)"Mensagem de teste engatilhada. Pegue o Bing Bong e use."); } } ChatMessage message; while (_irc.TryDequeue(out message)) { ChatMessage chatMessage2 = Filter(message); if (chatMessage2 == null) { if (CfgVerbose.Value) { Log.LogInfo((object)("Mensagem descartada pelos filtros: " + message.Author + ": " + message.Text)); } continue; } LastMessage = chatMessage2; if (CfgVerbose.Value) { Log.LogInfo((object)("Proxima fala do Bing Bong -> " + chatMessage2.Author + ": " + chatMessage2.Text)); } if (CfgMode.Value == SpeakMode.Auto || CfgMode.Value == SpeakMode.Both) { TryAutoSpeak(chatMessage2); } } } private static ChatMessage Filter(ChatMessage msg) { if (msg == null || string.IsNullOrEmpty(msg.Text)) { return null; } if (CfgOnlySubs.Value && !msg.IsSubscriber && !msg.IsModerator && !msg.IsVip && !msg.IsBroadcaster) { return null; } string value = CfgCommandPrefix.Value; if (!string.IsNullOrEmpty(value)) { value = value.Trim(); if (!msg.Text.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { return null; } msg.Text = msg.Text.Substring(value.Length).Trim(); if (msg.Text.Length == 0) { return null; } } return msg; } private void TryAutoSpeak(ChatMessage msg) { if (Time.time < _nextAutoSpeak) { return; } BingBongVoice bingBongVoice = FindSpeaker(); if ((Object)(object)bingBongVoice == (Object)null) { if (CfgVerbose.Value) { Log.LogInfo((object)("Modo Auto: ninguem esta segurando o Bing Bong (" + BingBongVoice.Active.Count + " encontrados na cena).")); } } else { _nextAutoSpeak = Time.time + CfgAutoCooldown.Value; bingBongVoice.Say(msg.Author, msg.Text); } } public static BingBongVoice FindSpeaker() { for (int i = 0; i < BingBongVoice.Active.Count; i++) { BingBongVoice bingBongVoice = BingBongVoice.Active[i]; if ((Object)(object)bingBongVoice != (Object)null && bingBongVoice.HeldByLocalPlayer()) { return bingBongVoice; } } return null; } private void OnDestroy() { if (_irc != null) { _irc.Stop(); } if (_harmony != null) { _harmony.UnpatchSelf(); } Babble.Reset(); } } public class ChatMessage { public string Author; public string Text; public bool IsBroadcaster; public bool IsModerator; public bool IsSubscriber; public bool IsVip; } public class TwitchIrc { private const string Host = "irc.chat.twitch.tv"; private const int Port = 6667; private readonly string _channel; private readonly ConcurrentQueue _queue = new ConcurrentQueue(); private readonly Action _log; private readonly Action _logError; private Thread _thread; private volatile bool _running; private TcpClient _client; public volatile bool Connected; public static bool Verbose; public string Channel => _channel; public TwitchIrc(string channel, Action log, Action logError) { _channel = ((channel == null) ? "" : channel).Trim().TrimStart('#').ToLowerInvariant(); _log = log; _logError = logError; } public bool TryDequeue(out ChatMessage message) { return _queue.TryDequeue(out message); } public void Start() { if (!_running) { if (string.IsNullOrEmpty(_channel)) { _logError("Nenhum canal configurado. Preencha Channel no arquivo de config."); return; } _running = true; _thread = new Thread(Loop); _thread.IsBackground = true; _thread.Name = "BingBongTwitchIRC"; _thread.Start(); } } public void Stop() { _running = false; Connected = false; try { if (_client != null) { _client.Close(); } } catch { } _client = null; } private void Loop() { int num = 2; while (_running) { try { RunSession(); num = 2; } catch (Exception ex) { if (_running) { _logError("Conexao com a Twitch caiu: " + ex.Message); } } Connected = false; if (!_running) { break; } for (int i = 0; i < num * 10; i++) { if (!_running) { break; } Thread.Sleep(100); } num = Math.Min(num * 2, 60); } } private void RunSession() { using TcpClient tcpClient = new TcpClient(); _client = tcpClient; tcpClient.Connect("irc.chat.twitch.tv", 6667); using NetworkStream stream = tcpClient.GetStream(); using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.NewLine = "\r\n"; streamWriter.AutoFlush = true; streamWriter.WriteLine("PASS SCHMOOPIIE"); streamWriter.WriteLine("NICK justinfan" + new Random().Next(10000, 99999)); streamWriter.WriteLine("CAP REQ :twitch.tv/tags"); streamWriter.WriteLine("JOIN #" + _channel); _log("Conectando ao chat de #" + _channel + " (anonimo, sem token)..."); string text; while (_running && (text = streamReader.ReadLine()) != null) { if (text.StartsWith("PING", StringComparison.Ordinal)) { streamWriter.WriteLine("PONG :tmi.twitch.tv"); continue; } if (!Connected && text.IndexOf(" 001 ", StringComparison.Ordinal) >= 0) { Connected = true; _log("Conectado ao chat de #" + _channel + "."); } ChatMessage chatMessage = Parse(text); if (chatMessage != null) { _queue.Enqueue(chatMessage); if (Verbose) { _log("IRC recebeu de " + chatMessage.Author + ": " + chatMessage.Text); } } else if (Verbose && text.IndexOf("PRIVMSG", StringComparison.Ordinal) >= 0) { _log("IRC nao entendeu esta linha: " + text); } } } private static ChatMessage Parse(string line) { string text = null; string text2 = line; if (text2.Length > 0 && text2[0] == '@') { int num = text2.IndexOf(' '); if (num < 0) { return null; } text = text2.Substring(1, num - 1); text2 = text2.Substring(num + 1); } string author = null; if (text2.Length > 0 && text2[0] == ':') { int num2 = text2.IndexOf(' '); if (num2 < 0) { return null; } string text3 = text2.Substring(1, num2 - 1); int num3 = text3.IndexOf('!'); author = ((num3 > 0) ? text3.Substring(0, num3) : text3); text2 = text2.Substring(num2 + 1); } if (!text2.StartsWith("PRIVMSG ", StringComparison.Ordinal)) { return null; } int num4 = text2.IndexOf(" :", StringComparison.Ordinal); if (num4 < 0) { return null; } string text4 = text2.Substring(num4 + 2); ChatMessage chatMessage = new ChatMessage(); chatMessage.Author = author; chatMessage.Text = text4; if (text != null) { string tag = GetTag(text, "display-name"); if (!string.IsNullOrEmpty(tag)) { chatMessage.Author = tag; } string text5 = GetTag(text, "badges"); if (text5 == null) { text5 = ""; } chatMessage.IsBroadcaster = text5.IndexOf("broadcaster/", StringComparison.Ordinal) >= 0; chatMessage.IsVip = text5.IndexOf("vip/", StringComparison.Ordinal) >= 0; chatMessage.IsModerator = GetTag(text, "mod") == "1" || chatMessage.IsBroadcaster; chatMessage.IsSubscriber = GetTag(text, "subscriber") == "1"; } if (string.IsNullOrEmpty(chatMessage.Author)) { chatMessage.Author = "chat"; } return chatMessage; } private static string GetTag(string tags, string key) { string[] array = tags.Split(';'); for (int i = 0; i < array.Length; i++) { int num = array[i].IndexOf('='); if (num > 0 && string.Equals(array[i].Substring(0, num), key, StringComparison.Ordinal)) { return array[i].Substring(num + 1); } } return null; } }