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<int> _blipStarts = new List<int>();
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<float> 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<float> PitchPerSyllable(string text)
{
List<float> list = new List<float>();
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<AudioClip> 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<AudioClip> CollectClips(Action_AskBingBong action)
{
List<AudioClip> list = new List<AudioClip>();
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<AudioClip> 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<float> list = new List<float>();
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<BingBongVoice> Active = new List<BingBongVoice>();
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<Action_AskBingBong>();
_mouth = ((Component)this).GetComponentInChildren<BingBongMouth>(true);
_item = ((Component)this).GetComponent<Item>();
if ((Object)(object)_item == (Object)null)
{
_item = ((Component)this).GetComponentInParent<Item>();
}
if ((Object)(object)_item == (Object)null)
{
_item = ((Component)this).GetComponentInChildren<Item>(true);
}
_view = ((Component)this).GetComponent<PhotonView>();
if ((Object)(object)_view == (Object)null)
{
_view = ((Component)this).GetComponentInParent<PhotonView>();
}
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<Camera>(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("<size=10>So o nome, sem o https://twitch.tv/</size>", (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("<size=10>Tagarelice feita com os sons do proprio Bing Bong. De graca, sem internet, instantaneo. A mensagem aparece na legenda.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
}
else if (Modes[_modeIndex] == VoiceMode.ElevenLabs)
{
GUILayout.Label("<size=10>So ElevenLabs. Se falhar ou acabarem os creditos, sai apenas a legenda.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
}
else
{
GUILayout.Label("<size=10>Tenta o ElevenLabs; quando ele falha, o Bing Bong assume com a tagarelice. Nunca fica mudo.</size>", (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("<size=10>Fica so neste PC. Gasta creditos da sua conta.</size>", (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) ? "<size=10>Fala portugues e ingles bem. Um pouco mais lento.</size>" : "<size=10>Responde mais rapido e gasta menos creditos.</size>", (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("<size=10>Esta voz e da biblioteca. No plano gratuito a API recusa ela e o Bing Bong nao fala.</size>", (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<AudioClip> 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<string> 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<VoiceInfo> found = new List<VoiceInfo>();
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<BingBongVoice>() != (Object)null))
{
((Component)val).gameObject.AddComponent<BingBongVoice>();
PhotonView component = ((Component)val).gameObject.GetComponent<PhotonView>();
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<BingBongVoice>();
if ((Object)(object)bingBongVoice == (Object)null)
{
Plugin.Log.LogWarning((object)"BingBongVoice nao estava anexado -> anexando agora.");
bingBongVoice = ((Component)__instance).gameObject.AddComponent<BingBongVoice>();
}
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<string> CfgChannel;
public static ConfigEntry<bool> CfgEnabled;
public static ConfigEntry<SpeakMode> CfgMode;
public static ConfigEntry<string> CfgCommandPrefix;
public static ConfigEntry<bool> CfgOnlySubs;
public static ConfigEntry<float> CfgAutoCooldown;
public static ConfigEntry<int> CfgMaxLength;
public static ConfigEntry<bool> CfgShowAuthor;
public static ConfigEntry<bool> CfgSyncToOthers;
public static ConfigEntry<float> CfgVolume;
public static ConfigEntry<float> CfgPitchMin;
public static ConfigEntry<float> CfgPitchMax;
public static ConfigEntry<float> CfgSyllableSeconds;
public static ConfigEntry<VoiceMode> CfgVoiceMode;
public static ConfigEntry<string> CfgElevenApiKey;
public static ConfigEntry<string> CfgElevenVoiceId;
public static ConfigEntry<string> CfgElevenModel;
public static ConfigEntry<float> CfgElevenPitch;
public static ConfigEntry<float> CfgElevenMinGap;
public static ConfigEntry<float> CfgElevenTimeout;
public static ConfigEntry<float> CfgSecondsPerChar;
public static ConfigEntry<float> CfgMinSeconds;
public static ConfigEntry<float> CfgMaxSeconds;
public static ChatMessage LastMessage;
public static ConfigEntry<bool> CfgSetupShown;
public static ConfigEntry<bool> CfgVerbose;
public static ConfigEntry<bool> 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<ConfigWindow>();
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<bool>("1. Twitch", "Enabled", true, "Liga o mod.");
CfgChannel = ((BaseUnityPlugin)this).Config.Bind<string>("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<SpeakMode>("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<string>("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<bool>("2. Comportamento", "OnlySubsAndMods", false, "So aceita mensagens de subs, VIPs, mods e do dono do canal.");
CfgAutoCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("2. Comportamento", "AutoCooldownSeconds", 6f, new ConfigDescription("No modo Auto, tempo minimo entre uma fala e outra.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), new object[0]));
CfgMaxLength = ((BaseUnityPlugin)this).Config.Bind<int>("2. Comportamento", "MaxLength", 140, new ConfigDescription("Mensagens maiores que isso sao cortadas.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 400), new object[0]));
CfgShowAuthor = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Comportamento", "ShowAuthor", true, "Mostra o nome de quem escreveu antes da mensagem.");
CfgSyncToOthers = ((BaseUnityPlugin)this).Config.Bind<bool>("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<float>("4. Voz", "Volume", 1f, new ConfigDescription("Volume da voz do Bing Bong.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), new object[0]));
CfgPitchMin = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "PitchMin", 0.85f, new ConfigDescription("Pitch mais grave da tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), new object[0]));
CfgPitchMax = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "PitchMax", 1.35f, new ConfigDescription("Pitch mais agudo da tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), new object[0]));
CfgSyllableSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "SyllableSeconds", 0.085f, new ConfigDescription("Duracao de cada silaba. Menor = ele fala mais rapido.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.03f, 0.3f), new object[0]));
CfgVoiceMode = ((BaseUnityPlugin)this).Config.Bind<VoiceMode>("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<string>("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<string>("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<string>("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<float>("7. ElevenLabs", "Pitch", 1.35f, new ConfigDescription("Acelera a voz para soar agudo tipo Bing Bong. 1 = voz normal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2.5f), new object[0]));
CfgElevenMinGap = ((BaseUnityPlugin)this).Config.Bind<float>("7. ElevenLabs", "MinSecondsBetweenRequests", 5f, new ConfigDescription("Protecao de creditos: tempo minimo entre duas chamadas na API.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), new object[0]));
CfgElevenTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("7. ElevenLabs", "TimeoutSeconds", 15f, new ConfigDescription("Desiste da chamada depois desse tempo e usa a tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f), new object[0]));
CfgSecondsPerChar = ((BaseUnityPlugin)this).Config.Bind<float>("5. Legenda", "SecondsPerChar", 0.075f, new ConfigDescription("Quanto tempo de fala cada caractere vale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.3f), new object[0]));
CfgMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("5. Legenda", "MinSeconds", 1.8f, new ConfigDescription("Duracao minima da fala.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), new object[0]));
CfgMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("5. Legenda", "MaxSeconds", 9f, new ConfigDescription("Duracao maxima da fala.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), new object[0]));
CfgSetupShown = ((BaseUnityPlugin)this).Config.Bind<bool>("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<bool>("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<bool>("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<ChatMessage> _queue = new ConcurrentQueue<ChatMessage>();
private readonly Action<string> _log;
private readonly Action<string> _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<string> log, Action<string> 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;
}
}