First commit with progress so far

This commit is contained in:
Tom 2023-12-30 09:27:31 +00:00
commit 72be20594d
575 changed files with 23496 additions and 0 deletions

22
Projects.sln Normal file
View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwitchChatTTS", "TwitchChatTTS\TwitchChatTTS.csproj", "{7A371F54-F9D5-49C9-BE2D-819C60A0D621}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

2
TwitchChatTTS/.redeems Normal file
View File

@ -0,0 +1,2 @@
hydration
test

1
TwitchChatTTS/.token Normal file
View File

@ -0,0 +1 @@
1C2920l0uhZfl4uaNSquFRec6cCiHCLS

View File

@ -0,0 +1 @@
freezeflame99

View File

@ -0,0 +1,9 @@
using System.Diagnostics.CodeAnalysis;
[Serializable]
public class Account {
[AllowNull]
public string id { get; set; }
[AllowNull]
public string username { get; set; }
}

View File

@ -0,0 +1,37 @@
using System;
public class HermesClient {
private Account account;
private string key;
public string Id { get => account?.id; }
public string Username { get => account?.username; }
public HermesClient() {
// Read API Key from file.
if (!File.Exists(".token")) {
throw new Exception("Ensure you have written your API key in \".token\" file, in the same folder as this application.");
}
key = File.ReadAllText(".token")?.Trim();
WebHelper.AddHeader("x-api-key", key);
}
public async Task UpdateHermesAccount() {
account = await WebHelper.GetJson<Account>("https://hermes.goblincaves.com/api/account");
}
public async Task<TwitchBotToken> FetchTwitchBotToken() {
if (string.IsNullOrWhiteSpace(key)) {
throw new InvalidOperationException("Hermes API key not provided.");
}
var token = await WebHelper.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
if (token == null) {
throw new Exception("Failed to fetch Twitch API token from Hermes.");
}
return token;
}
}

View File

@ -0,0 +1,8 @@
[Serializable]
public class TwitchBotToken {
public string client_id { get; set; }
public string client_secret { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string broadcaster_id { get; set; }
}

View File

@ -0,0 +1,8 @@
[Serializable]
public class TwitchConnection {
public string id { get; set; }
public string secret { get; set; }
public string broadcasterId { get; set; }
public string username { get; set; }
public string userId { get; set; }
}

282
TwitchChatTTS/Program.cs Normal file
View File

@ -0,0 +1,282 @@
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using NAudio.Wave;
using TwitchLib.Api;
using TwitchLib.Client;
using TwitchLib.Client.Events;
using TwitchLib.Client.Models;
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Events;
using TwitchLib.PubSub;
using TwitchLib.PubSub.Events;
using NAudio.Wave.SampleProviders;
/**
Future handshake/connection procedure:
- GET all tts config data
- Continuous connection to server to receive commands from tom & send logs/errors (med priority, though tough task)
Ideas:
- Filter messages by badges, username, ..., etc.
- Filter messages by content.
- Speed up TTS based on message queue size?
- Cut TTS off shortly after raid (based on size of raid)?
- Limit duration of TTS
- Voice selection for channel and per user.
**/
// dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
// dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true
// SE voices: https://api.streamelements.com/kappa/v2/speech?voice=brian&text=hello
// Read redeems from file.
var redeems = File.Exists(".redeems") ? await File.ReadAllLinesAsync(".redeems") : new string[0];
// Fetch id and username based on api key given.
HermesClient hermes = new HermesClient();
Console.WriteLine("Fetching Hermes account details...");
await hermes.UpdateHermesAccount();
Console.WriteLine("ID: " + hermes.Id);
Console.WriteLine("Username: " + hermes.Username);
Console.WriteLine();
Console.WriteLine("Fetching Twitch API details from Hermes...");
TwitchApiClient twitchapiclient = new TwitchApiClient(await hermes.FetchTwitchBotToken());
await twitchapiclient.Authorize();
var sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)");
var voiceRegex = new Regex(@"\b(Filiz|Astrid|Tatyana|Maxim|Carmen|Ines|Cristiano|Vitoria|Ricardo|Maja|Jan|Jacek|Ewa|Ruben|Lotte|Liv|Seoyeon|Takumi|Mizuki|Giorgio|Carla|Bianca|Karl|Dora|Mathieu|Celine|Chantal|Penelope|Miguel|Mia|Enrique|Conchita|Geraint|Salli|Matthew|Kimberly|Kendra|Justin|Joey|Joanna|Ivy|Raveena|Aditi|Emma|Brian|Amy|Russell|Nicole|Vicki|Marlene|Hans|Naja|Mads|Gwyneth|Zhiyu|Tracy|Danny|Huihui|Yaoyao|Kangkang|HanHan|Zhiwei|Asaf|An|Stefanos|Filip|Ivan|Heidi|Herena|Kalpana|Hemant|Matej|Andika|Rizwan|Lado|Valluvar|Linda|Heather|Sean|Michael|Karsten|Guillaume|Pattara|Jakub|Szabolcs|Hoda|Naayf)\:(.*?)(?=\Z|\b(?:Filiz|Astrid|Tatyana|Maxim|Carmen|Ines|Cristiano|Vitoria|Ricardo|Maja|Jan|Jacek|Ewa|Ruben|Lotte|Liv|Seoyeon|Takumi|Mizuki|Giorgio|Carla|Bianca|Karl|Dora|Mathieu|Celine|Chantal|Penelope|Miguel|Mia|Enrique|Conchita|Geraint|Salli|Matthew|Kimberly|Kendra|Justin|Joey|Joanna|Ivy|Raveena|Aditi|Emma|Brian|Amy|Russell|Nicole|Vicki|Marlene|Hans|Naja|Mads|Gwyneth|Zhiyu|Tracy|Danny|Huihui|Yaoyao|Kangkang|HanHan|Zhiwei|Asaf|An|Stefanos|Filip|Ivan|Heidi|Herena|Kalpana|Hemant|Matej|Andika|Rizwan|Lado|Valluvar|Linda|Heather|Sean|Michael|Karsten|Guillaume|Pattara|Jakub|Szabolcs|Hoda|Naayf)\:)", RegexOptions.IgnoreCase);
TTSPlayer player = new TTSPlayer();
ISampleProvider playing = null;
var channels = File.Exists(".twitchchannels") ? File.ReadAllLines(".twitchchannels") : new string[] { hermes.Username };
twitchapiclient.InitializeClient(hermes, channels);
twitchapiclient.InitializePublisher(player, redeems);
void HandleMessage(int priority, string voice, string message, OnMessageReceivedArgs e, bool bot) {
var m = e.ChatMessage;
var parts = sfxRegex.Split(message);
var sfxMatches = sfxRegex.Matches(message);
var sfxStart = sfxMatches.FirstOrDefault()?.Index ?? message.Length;
var alphanumeric = new Regex(@"[^a-zA-Z0-9!@#$%&\^*+\-_(),+':;?.,\[\]\s\\/~`]");
message = alphanumeric.Replace(message, " ");
if (string.IsNullOrWhiteSpace(message)) {
return;
}
if (parts.Length == 1) {
Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}");
player.Add(new TTSMessage() {
Voice = voice,
Bot = bot,
Message = message,
Moderator = m.IsModerator,
Timestamp = DateTime.UtcNow,
Username = m.Username,
Bits = m.Bits,
Badges = m.Badges,
Priority = priority
});
return;
}
for (var i = 0; i < sfxMatches.Count; i++) {
var sfxMatch = sfxMatches[i];
var sfxName = sfxMatch.Groups[1]?.ToString()?.ToLower();
if (!File.Exists("sfx/" + sfxName + ".mp3")) {
parts[i * 2 + 2] = parts[i * 2] + " (" + parts[i * 2 + 1] + ")" + parts[i * 2 + 2];
continue;
}
if (!string.IsNullOrWhiteSpace(parts[i * 2])) {
Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {parts[i * 2]}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}");
player.Add(new TTSMessage() {
Voice = voice,
Bot = bot,
Message = parts[i * 2],
Moderator = m.IsModerator,
Timestamp = DateTime.UtcNow,
Username = m.Username,
Bits = m.Bits,
Badges = m.Badges,
Priority = priority
});
}
Console.WriteLine($"Voice: {voice}; Priority: {priority}; SFX: {sfxName}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}");
player.Add(new TTSMessage() {
Voice = voice,
Bot = bot,
Message = sfxName,
File = $"sfx/{sfxName}.mp3",
Moderator = m.IsModerator,
Timestamp = DateTime.UtcNow,
Username = m.Username,
Bits = m.Bits,
Badges = m.Badges,
Priority = priority
});
}
if (!string.IsNullOrWhiteSpace(parts.Last())) {
Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {parts.Last()}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}");
player.Add(new TTSMessage() {
Voice = voice,
Bot = bot,
Message = parts.Last(),
Moderator = m.IsModerator,
Timestamp = DateTime.UtcNow,
Username = m.Username,
Bits = m.Bits,
Badges = m.Badges,
Priority = priority
});
}
}
twitchapiclient.AddOnNewMessageReceived(async Task (object? s, OnMessageReceivedArgs e) => {
var m = e.ChatMessage;
var msg = e.ChatMessage.Message;
if ((m.IsVip || m.IsModerator || m.IsBroadcaster) && (msg.ToLower().StartsWith("!skip ") || msg.ToLower() == "!skip")) {
AudioPlaybackEngine.Instance.RemoveMixerInput(playing);
playing = null;
return;
}
string[] bots = new string[] { "nightbot", "streamelements", "own3d", "streamlabs", "soundalerts", "pokemoncommunitygame" };
bool bot = bots.Any(b => b == m.Username);
if (bot || m.IsBroadcaster || msg.StartsWith('!')) {
return;
}
string[] bad = new string[] { "incel", "simp", "virgin", "faggot", "fagg", "fag", "nigger", "nigga", "nigg", "nig", "whore", "retard", "cock", "fuck", "bastard", "wanker", "bollocks", "motherfucker", "bitch", "bish", "bich", "asshole", "ass", "dick", "dickhead", "frigger", "shit", "slut", "turd", "twat", "nigra", "penis" };
foreach (var b in bad) {
msg = new Regex($@"\b{b}\b", RegexOptions.IgnoreCase).Replace(msg, "");
}
msg = new Regex(@"%").Replace(msg, " percent ");
msg = new Regex(@"https?\:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)").Replace(msg, "");
msg = new Regex(@"\bfreeze153").Replace(msg, "");
// Filter highly repetitive words (like emotes) from message.
var words = msg.Split(" ");
var wordCounter = new Dictionary<string, int>();
string filteredMsg = string.Empty;
foreach (var w in words) {
if (wordCounter.ContainsKey(w)) {
wordCounter[w]++;
} else {
wordCounter.Add(w, 1);
}
if (wordCounter[w] < 5) {
filteredMsg += w + " ";
}
}
msg = filteredMsg;
foreach (var w in words) {
if (wordCounter.ContainsKey(w)) {
wordCounter[w]++;
} else {
wordCounter.Add(w, 1);
}
}
int priority = 0;
if (m.IsStaff) {
priority = int.MinValue;
} else if (m.IsModerator) {
priority = -100;
} else if (m.IsVip) {
priority = -10;
} else if (m.IsPartner) {
priority = -5;
} else if (m.IsHighlighted) {
priority = -1;
}
priority = (int) Math.Round(Math.Min(priority, -m.SubscribedMonthCount * (m.Badges.Any(b => b.Key == "subscriber" && b.Value == "1") ? 1.2 : 1)));
var matches = voiceRegex.Matches(msg);
int defaultEnd = matches.FirstOrDefault()?.Index ?? msg.Length;
if (defaultEnd > 0) {
HandleMessage(priority, "Brian", msg.Substring(0, defaultEnd), e, bot);
}
foreach (Match match in matches) {
var message = match.Groups[2].ToString();
if (string.IsNullOrWhiteSpace(message)) {
continue;
}
var voice = match.Groups[1].ToString();
voice = voice[0].ToString().ToUpper() + voice.Substring(1).ToLower();
HandleMessage(priority, voice, message, e, bot);
}
});
AudioPlaybackEngine.Instance.AddOnMixerInputEnded((object? s, SampleProviderEventArgs e) => {
if (e.SampleProvider == playing) {
playing = null;
}
});
Task.Run(async () => {
while (true) {
try {
var m = player.ReceiveBuffer();
if (m == null) {
await Task.Delay(200);
continue;
}
string url = $"https://api.streamelements.com/kappa/v2/speech?voice={m.Voice}&text={m.Message}";
var sound = new NetworkWavSound(url);
var provider = new CachedWavProvider(sound);
var data = AudioPlaybackEngine.Instance.ConvertSound(provider);
m.Audio = data;
player.Ready(m);
} catch (COMException e) {
Console.WriteLine(e.GetType().Name + ": " + e.Message + " (HResult: " + e.HResult + ")");
} catch (Exception e) {
Console.WriteLine(e.GetType().Name + ": " + e.Message);
}
}
});
Task.Run(async () => {
while (true) {
try {
while (player.IsEmpty() || playing != null) {
await Task.Delay(200);
}
var m = player.ReceiveReady();
if (m == null) {
continue;
}
if (!string.IsNullOrWhiteSpace(m.File) && File.Exists(m.File)) {
Console.WriteLine("Playing sfx: " + m.File);
AudioPlaybackEngine.Instance.PlaySound(m.File);
continue;
}
Console.WriteLine("Playing message: " + m.Message);
playing = m.Audio;
AudioPlaybackEngine.Instance.AddMixerInput(m.Audio);
} catch (Exception e) {
Console.WriteLine(e.GetType().Name + ": " + e.Message);
}
}
});
Console.WriteLine("Twitch API client connecting...");
twitchapiclient.Connect();
Console.ReadLine();
Console.ReadLine();

View File

@ -0,0 +1,90 @@
using NAudio.Wave;
using NAudio.Extras;
using NAudio.Wave.SampleProviders;
public class AudioPlaybackEngine : IDisposable
{
public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(22050, 1);
private readonly IWavePlayer outputDevice;
private readonly MixingSampleProvider mixer;
private AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2)
{
outputDevice = new WaveOutEvent();
mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount));
mixer.ReadFully = true;
outputDevice.Init(mixer);
outputDevice.Play();
}
private ISampleProvider ConvertToRightChannelCount(ISampleProvider input)
{
if (input.WaveFormat.Channels == mixer.WaveFormat.Channels)
{
return input;
}
if (input.WaveFormat.Channels == 1 && mixer.WaveFormat.Channels == 2)
{
return new MonoToStereoSampleProvider(input);
}
throw new NotImplementedException("Not yet implemented this channel count conversion");
}
public void PlaySound(string fileName)
{
var input = new AudioFileReader(fileName);
AddMixerInput(new AutoDisposeFileReader(input));
}
public void PlaySound(NetworkWavSound sound)
{
AddMixerInput(new CachedWavProvider(sound));
}
public ISampleProvider ConvertSound(IWaveProvider provider) {
ISampleProvider converted = null;
if (provider.WaveFormat.Encoding == WaveFormatEncoding.Pcm) {
if (provider.WaveFormat.BitsPerSample == 8) {
converted = new Pcm8BitToSampleProvider(provider);
} else if (provider.WaveFormat.BitsPerSample == 16) {
converted = new Pcm16BitToSampleProvider(provider);
} else if (provider.WaveFormat.BitsPerSample == 24) {
converted = new Pcm24BitToSampleProvider(provider);
} else if (provider.WaveFormat.BitsPerSample == 32) {
converted = new Pcm32BitToSampleProvider(provider);
}
} else if (provider.WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) {
if (provider.WaveFormat.BitsPerSample == 64) {
converted = new WaveToSampleProvider64(provider);
} else {
converted = new WaveToSampleProvider(provider);
}
} else {
throw new ArgumentException("Unsupported source encoding while adding to mixer.");
}
return converted;
}
public void AddMixerInput(ISampleProvider input)
{
mixer.AddMixerInput(input);
}
public void AddMixerInput(IWaveProvider input)
{
mixer.AddMixerInput(input);
}
public void RemoveMixerInput(ISampleProvider sound) {
mixer.RemoveMixerInput(sound);
}
public void AddOnMixerInputEnded(EventHandler<SampleProviderEventArgs> e) {
mixer.MixerInputEnded += e;
}
public void Dispose() {
outputDevice.Dispose();
}
}

View File

@ -0,0 +1,45 @@
using NAudio.Wave;
using System;
public class NetworkWavSound
{
public byte[] AudioData { get; private set; }
public WaveFormat WaveFormat { get; private set; }
public NetworkWavSound(string uri)
{
using (var mfr = new MediaFoundationReader(uri)) {
WaveFormat = mfr.WaveFormat;
//Console.WriteLine("W: " + WaveFormat.SampleRate + " C: " + WaveFormat.Channels + " B: " + WaveFormat.BitsPerSample + " E: " + WaveFormat.Encoding);
byte[] buffer = new byte[4096];
int read = 0;
using (var ms = new MemoryStream()) {
while ((read = mfr.Read(buffer, 0, buffer.Length)) > 0)
ms.Write(buffer, 0, read);
AudioData = ms.ToArray();
}
}
}
}
public class CachedWavProvider : IWaveProvider
{
private readonly NetworkWavSound sound;
private long position;
private readonly RawSourceWaveStream stream;
public WaveFormat WaveFormat { get => sound.WaveFormat; }
public CachedWavProvider(NetworkWavSound cachedSound)
{
sound = cachedSound;
stream = new RawSourceWaveStream(new MemoryStream(sound.AudioData), sound.WaveFormat);
}
public int Read(byte[] buffer, int offset, int count)
{
return stream.Read(buffer, offset, count);
}
}

View File

@ -0,0 +1,76 @@
using NAudio.Wave;
public class TTSPlayer {
private PriorityQueue<TTSMessage, int> _messages; // ready to play
private PriorityQueue<TTSMessage, int> _buffer;
private Mutex _mutex;
private Mutex _mutex2;
public TTSPlayer() {
_messages = new PriorityQueue<TTSMessage, int>();
_buffer = new PriorityQueue<TTSMessage, int>();
_mutex = new Mutex();
_mutex2 = new Mutex();
}
public void Add(TTSMessage message) {
try {
_mutex2.WaitOne();
_buffer.Enqueue(message, message.Priority);
} finally {
_mutex2.ReleaseMutex();
}
}
public TTSMessage ReceiveReady() {
try {
_mutex.WaitOne();
if (_messages.TryDequeue(out TTSMessage message, out int _)) {
return message;
}
return null;
} finally {
_mutex.ReleaseMutex();
}
}
public TTSMessage ReceiveBuffer() {
try {
_mutex2.WaitOne();
if (_buffer.TryDequeue(out TTSMessage message, out int _)) {
return message;
}
return null;
} finally {
_mutex2.ReleaseMutex();
}
}
public void Ready(TTSMessage message) {
try {
_mutex.WaitOne();
_messages.Enqueue(message, message.Priority);
} finally {
_mutex.ReleaseMutex();
}
}
public bool IsEmpty() {
return _messages.Count == 0;
}
}
public class TTSMessage {
public string Voice { get; set; }
public string Channel { get; set; }
public string Username { get; set; }
public string Message { get; set; }
public string File { get; set; }
public DateTime Timestamp { get; set; }
public bool Moderator { get; set; }
public bool Bot { get; set; }
public IEnumerable<KeyValuePair<string, string>> Badges { get; set; }
public int Bits { get; set; }
public int Priority { get; set; }
public ISampleProvider Audio { get; set; }
}

View File

@ -0,0 +1,120 @@
using TwitchLib.Api;
using TwitchLib.Client;
using TwitchLib.Client.Events;
using TwitchLib.Client.Models;
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Events;
using TwitchLib.PubSub;
using TwitchLib.PubSub.Events;
public class TwitchApiClient {
private TwitchBotToken token;
private TwitchClient client;
private TwitchPubSub publisher;
private bool initialized;
public TwitchApiClient(TwitchBotToken token) {
client = new TwitchClient(new WebSocketClient());
publisher = new TwitchPubSub();
initialized = false;
this.token = token;
}
public async Task<bool> Authorize() {
var authorize = await WebHelper.Get("https://hermes.goblincaves.com/api/account/reauthorize");
var status = (int) authorize.StatusCode;
return status == 200 || status == 201;
}
public async Task Connect() {
client.Connect();
await publisher.ConnectAsync();
}
public void InitializeClient(HermesClient hermes, IEnumerable<string> channels) {
ConnectionCredentials credentials = new ConnectionCredentials(hermes.Username, token.access_token);
client.Initialize(credentials, channels.Distinct().ToList());
if (initialized) {
return;
}
initialized = true;
client.OnJoinedChannel += async Task (object? s, OnJoinedChannelArgs e) => {
Console.WriteLine("Joined Channel: " + e.Channel);
};
client.OnConnected += async Task (object? s, OnConnectedArgs e) => {
Console.WriteLine("-----------------------------------------------------------");
};
client.OnError += async Task (object? s, OnErrorEventArgs e) => {
Console.WriteLine("Log: " + e.Exception.Message + " (" + e.Exception.GetType().Name + ")");
};
client.OnIncorrectLogin += async Task (object? s, OnIncorrectLoginArgs e) => {
Console.WriteLine("Incorrect Login: " + e.Exception.Message + " (" + e.Exception.GetType().Name + ")");
};
client.OnConnectionError += async Task (object? s, OnConnectionErrorArgs e) => {
Console.WriteLine("Connection Error: " + e.Error.Message + " (" + e.Error.GetType().Name + ")");
};
client.OnError += async Task (object? s, OnErrorEventArgs e) => {
Console.WriteLine("Error: " + e.Exception.Message + " (" + e.Exception.GetType().Name + ")");
};
}
public void InitializePublisher(TTSPlayer player, IEnumerable<string> redeems) {
publisher.OnPubSubServiceConnected += async (s, e) => {
publisher.ListenToChannelPoints(token.broadcaster_id);
await publisher.SendTopicsAsync(token.access_token);
Console.WriteLine("Twitch PubSub has been connected.");
};
publisher.OnFollow += (s, e) => {
Console.WriteLine("Follow: " + e.DisplayName);
};
publisher.OnChannelPointsRewardRedeemed += (s, e) => {
Console.WriteLine($"Channel Point Reward Redeemed: {e.RewardRedeemed.Redemption.Reward.Title} (id: {e.RewardRedeemed.Redemption.Id})");
if (!redeems.Any(r => r.ToLower() == e.RewardRedeemed.Redemption.Reward.Title.ToLower()))
return;
player.Add(new TTSMessage() {
Voice = "Brian",
Message = e.RewardRedeemed.Redemption.Reward.Title,
File = $"redeems/{e.RewardRedeemed.Redemption.Reward.Title.ToLower()}.mp3",
Priority = -50
});
};
/*int psConnectionFailures = 0;
publisher.OnPubSubServiceError += async (s, e) => {
Console.WriteLine("PubSub ran into a service error. Attempting to connect again.");
await Task.Delay(Math.Min(3000 + (1 << psConnectionFailures), 120000));
var connect = await WebHelper.Get("https://hermes.goblincaves.com/api/account/reauthorize");
if ((int) connect.StatusCode == 200 || (int) connect.StatusCode == 201) {
psConnectionFailures = 0;
} else {
psConnectionFailures++;
}
var twitchBotData2 = await WebHelper.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
if (twitchBotData2 == null) {
Console.WriteLine("The API is down. Contact the owner.");
return;
}
twitchBotData.access_token = twitchBotData2.access_token;
await pubsub.ConnectAsync();
};*/
}
public void AddOnNewMessageReceived(AsyncEventHandler<OnMessageReceivedArgs> handler) {
client.OnMessageReceived += handler;
}
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="NAudio.Extras" Version="2.2.1" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageReference Include="TwitchLib.Api.Core" Version="3.10.0-preview-e47ba7f" />
<PackageReference Include="TwitchLib.Api.Core.Enums" Version="3.10.0-preview-e47ba7f" />
<PackageReference Include="TwitchLib.Api.Core.Interfaces" Version="3.10.0-preview-e47ba7f" />
<PackageReference Include="TwitchLib.Api.Helix" Version="3.10.0-preview-e47ba7f" />
<PackageReference Include="TwitchLib.Api.Helix.Models" Version="3.10.0-preview-e47ba7f" />
<PackageReference Include="TwitchLib.Client" Version="4.0.0-preview-fd131763416cb9f1a31705ca609566d7e7e7fac8" />
<PackageReference Include="TwitchLib.Client.Enums" Version="4.0.0-preview-fd131763416cb9f1a31705ca609566d7e7e7fac8" />
<PackageReference Include="TwitchLib.Client.Models" Version="4.0.0-preview-fd131763416cb9f1a31705ca609566d7e7e7fac8" />
<PackageReference Include="TwitchLib.Communication" Version="2.0.0" />
<PackageReference Include="TwitchLib.EventSub.Core" Version="2.5.1" />
<PackageReference Include="TwitchLib.PubSub" Version="4.0.0-preview-f833b1ab1ebef37618dba3fbb1e0a661ff183af5" />
<PackageReference Include="NAudio.Core" Version="2.2.1" />
<PackageReference Include="TwitchLib.Api" Version="3.10.0-preview-e47ba7f" />
</ItemGroup>
</Project>

29
TwitchChatTTS/Web.cs Normal file
View File

@ -0,0 +1,29 @@
using System.Net;
using System.Net.Http.Json;
public static class WebHelper {
private static HttpClient _client = new HttpClient();
public static void AddHeader(string key, string? value) {
_client.DefaultRequestHeaders.Add(key, value);
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13;
}
public static async Task<T?> GetJson<T>(string uri) {
return (T) await _client.GetFromJsonAsync(uri, typeof(T));
}
public static async Task<HttpResponseMessage> Get(string uri) {
return await _client.GetAsync(uri);
}
public static async Task<HttpResponseMessage> Post<T>(string uri, T data) {
return await _client.PostAsJsonAsync(uri, data);
}
public static async Task<HttpResponseMessage> Post(string uri) {
return await _client.PostAsJsonAsync(uri, new object());
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,9 @@
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More