├── TextToSpeechTTV ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── CommandHandler.cs ├── App.config ├── SpeechWordHandler.cs ├── TextToSpeechTTV.csproj ├── TwitchBot.cs ├── SpeechHelper.cs └── Config.cs ├── LICENSE ├── TextToSpeechTTV.sln ├── MigrationBackup └── 18800c69 │ └── TextToSpeechTTV │ ├── packages.config │ ├── TextToSpeechTTV.csproj │ └── NuGetUpgradeLog.html ├── README.md └── .gitignore /TextToSpeechTTV/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TextToSpeechTTV { 4 | class Program { 5 | static void Main (string[] args) { 6 | TwitchBot twitchBot = new TwitchBot(); 7 | Console.ReadLine(); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 takoz53 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TextToSpeechTTV.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextToSpeechTTV", "TextToSpeechTTV\TextToSpeechTTV.csproj", "{AB12D449-8E46-4C80-AFC5-7F6DEF2369F8}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {AB12D449-8E46-4C80-AFC5-7F6DEF2369F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AB12D449-8E46-4C80-AFC5-7F6DEF2369F8}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AB12D449-8E46-4C80-AFC5-7F6DEF2369F8}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AB12D449-8E46-4C80-AFC5-7F6DEF2369F8}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {A0632802-0E23-4133-AC7B-A64E5D640C9E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /TextToSpeechTTV/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TextToSpeechTTV")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TextToSpeechTTV")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("ab12d449-8e46-4c80-afc5-7f6def2369f8")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /TextToSpeechTTV/CommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TextToSpeechTTV { 9 | class CommandHandler { 10 | public CommandHandler () { 11 | 12 | } 13 | 14 | public bool BlockUser (string message) { 15 | SpeechWordHandler speechWordHandler = new SpeechWordHandler(); 16 | string[] messages; 17 | 18 | //First try splitting the message to check whether there is an username available 19 | try { messages = message.Split(' '); } catch { return false; } 20 | 21 | if (messages.Length > 2) 22 | return false; 23 | 24 | string user = messages[1]; 25 | if (speechWordHandler.CheckUserBlocked(user)) 26 | return false; 27 | 28 | File.AppendAllLines(speechWordHandler.GetBlockListLocation(), new string[] { "\n", user }); 29 | return true; 30 | } 31 | 32 | public bool UnblockUser (string message) { 33 | SpeechWordHandler speechWordHandler = new SpeechWordHandler(); 34 | string[] messages; 35 | 36 | //First try splitting the message to check whether there is an username available 37 | try { messages = message.Split(' '); } catch { return false; } 38 | if (messages.Length > 2) 39 | return false; 40 | 41 | string user = messages[1]; 42 | //If user is not in blocked list, return. 43 | if (!speechWordHandler.CheckUserBlocked(user)) 44 | return false; 45 | var lines = File.ReadAllLines(speechWordHandler.GetBlockListLocation()); 46 | 47 | //Remove user 48 | for (int i = 0; i < lines.Length; i++) { 49 | if (lines[i] == user) { 50 | lines[i] = lines[i].Replace(user, ""); 51 | break; 52 | } 53 | } 54 | //firstly, remove WhiteSpace and then replace old file with new file 55 | var newLines = lines.Where(arg => !string.IsNullOrWhiteSpace(arg)); 56 | File.WriteAllLines(speechWordHandler.GetBlockListLocation(), newLines); 57 | return true; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TextToSpeechTTV/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /TextToSpeechTTV/SpeechWordHandler.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace TextToSpeechTTV { 8 | class SpeechWordHandler { 9 | private static string badWordsLocation = 10 | Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "badwords.txt"); 11 | 12 | private static string blockListLocation = 13 | Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "blocklist.txt"); 14 | private static string whiteListLocation = 15 | Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "whitelist.txt"); 16 | private static string options = 17 | Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "options.txt"); 18 | 19 | public string defaultVoice; 20 | private List badWords; 21 | 22 | public void LoadDefaultVoice () { 23 | defaultVoice = File.ReadAllLines(options)[13]; 24 | } 25 | 26 | public SpeechWordHandler () { 27 | //Load bad words only once, because there are many and might cause performance issues, rather save them on a List. 28 | LoadBadWords(); 29 | } 30 | 31 | private void LoadBadWords () { 32 | badWords = new List(); 33 | string[] badwords = File.ReadAllLines(badWordsLocation); 34 | badWords.AddRange(badwords); 35 | } 36 | 37 | public List ContainsBadWord (string text) { 38 | List wordsFound = new List(); 39 | foreach (string s in badWords) { 40 | if (text.Contains(s)) { 41 | wordsFound.Add(s); 42 | } 43 | } 44 | return wordsFound; 45 | } 46 | 47 | public User ContainsJsonUsername (string username) { 48 | var jsonText = File.ReadAllText(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", 49 | "usernames.json")); 50 | var userList = JsonConvert.DeserializeObject(jsonText); 51 | foreach (var user in userList.Users) { 52 | if (user.Name == username) { 53 | try { 54 | return CreateTempUser(username, user.Nick, user.Voice, user.SpeakingSpeed, user.SpeakingPitch); 55 | } catch (Exception) { 56 | Console.ForegroundColor = ConsoleColor.Red; 57 | Console.WriteLine("Couldn't get User Data. Speaking Rate or Pitch might not be set for user!\n" + 58 | "Please delete the Config Folder (or cut it somewhere else) and restart the Program.\n" + 59 | "This will create a new usernames.json file with the new json-properties."); 60 | Console.ForegroundColor = ConsoleColor.Gray; 61 | } 62 | } 63 | } 64 | return CreateTempUser(username, username, defaultVoice, 1, 0); 65 | } 66 | 67 | public bool CheckUserBlocked (string username) { 68 | var usernames = File.ReadAllLines(blockListLocation).ToList(); 69 | return usernames.Contains(username); 70 | } 71 | 72 | public bool CheckUserWhitelisted (string username) { 73 | var usernames = File.ReadAllLines(whiteListLocation).ToList(); 74 | return usernames.Contains(username); 75 | } 76 | 77 | public string GetBlockListLocation () { 78 | return blockListLocation; 79 | } 80 | 81 | public User CreateTempUser (string username, string nick, string voice, double speakingSpeed, double speakingPitch) { 82 | var tempUser = new User { 83 | Name = username, 84 | Nick = nick, 85 | Voice = voice, 86 | SpeakingSpeed = speakingSpeed, 87 | SpeakingPitch = speakingPitch 88 | }; 89 | return tempUser; 90 | } 91 | } 92 | } 93 | 94 | public class User { 95 | public string Name { get; set; } 96 | public string Nick { get; set; } 97 | public string Voice { get; set; } 98 | public double SpeakingSpeed { get; set; } 99 | public double SpeakingPitch { get; set; } 100 | } 101 | 102 | public class Userlist { 103 | public List Users { get; set; } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /MigrationBackup/18800c69/TextToSpeechTTV/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /TextToSpeechTTV/TextToSpeechTTV.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AB12D449-8E46-4C80-AFC5-7F6DEF2369F8} 8 | Exe 9 | TextToSpeechTTV 10 | TextToSpeechTTV 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {00020430-0000-0000-C000-000000000046} 66 | 2 67 | 0 68 | 0 69 | primary 70 | False 71 | True 72 | 73 | 74 | 75 | 76 | 2.0.0 77 | 78 | 79 | 2.27.0 80 | 81 | 82 | 3.0.0-preview9.19423.4 83 | 84 | 85 | 1.10.0 86 | 87 | 88 | 2.9.0-dev-01124 89 | 90 | 91 | 3.0.2-dev-10256 92 | 93 | 94 | 4.5.0 95 | 96 | 97 | 4.1.1 98 | 99 | 100 | 4.6.0-preview5.19224.8 101 | 102 | 103 | 4.3.1 104 | 105 | 106 | 4.3.2 107 | 108 | 109 | 3.2.0 110 | 111 | 112 | 3.2.4 113 | 114 | 115 | 3.2.3 116 | 117 | 118 | 3.2.3 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TwitchTTS with Channel Awards 2 | A TTS client for Twitch chat, integrated with GCP Text-to-speech (500 voices from 40+ languages). 3 | 4 | ### Updated on 13.10.2021 5 | ## Download at [Releases](https://github.com/takoz53/TwitchTTS/releases) 6 | ## How to set up as developer? 7 | You'll (maybe?) need to add System.Speech available on your System as Reference 8 | 9 | You'll also need TwitchLib and Google.Cloud.TextToSpeech.V1 available on NuGet and you're good to go. 10 | 11 | 12 | ## How to set up as user? (Windows Only!) 13 | 1. Download the latest release and extract 14 | 1. Run the Program and you'll have to fill out multiple things 15 | 1. Firstly, whether you want to bind it on a specific Channel Award. If yes, type Y, else N. 16 | 1. If Y, type the Channel Award its Title, e.g. "TTS" without the "". 17 | 1. If you want the TTS to read out usernames (e.g. X said ...), hit Y, else N. 18 | 2. Enter your botname / username 19 | 4. Create an [OAuth Key](https://twitchapps.com/tmi/) and enter your Key (oauth:xxxxxx..) 20 | 5. Set the channel, where it should connect to (**all lowercase!**) 21 | 6. Create an [Access Token](https://twitchtokengenerator.com/) with Custom Scope Token. Tick following: 22 | 1. channel:read:redemptions 23 | 7. Hit **GENERATE TOKEN.** 24 | 8. Insert your Access Token. 25 | 9. Get your ~~[Channel ID](https://www.streamweasels.com/support/convert-twitch-username-to-user-id/).~~ This value is numbers only. 26 | 1. The website went down, so you need to find a way to get your ChannelID. For me, [this chrome add-on](https://chrome.google.com/webstore/detail/twitch-username-and-user/laonpoebfalkjijglbjbnkfndibbcoon/related) works just fine. 27 | 11. Insert your Twitch/Channel ID. 28 | 1. Everything should be working now. 29 | 2. Don't forget to set up a bad words filter or you might get banned if somebody wants to be funny. 30 | 3. I don't take responsibility for whatever happens in your stream. 31 | 32 | ## How to set up with GCP Text-To-Speech 33 | In order for the bot to be able to access the google API, a valid service account key with access to the text-to-speech API must be available in the gcp.json file in the config folder. 34 | 35 | 1. Register for a free account at https://cloud.google.com/free (1 million wavenet characters per month are free, and 4 million free standard characters) 36 | Try out the TTS here: https://cloud.google.com/text-to-speech 37 | 38 | I'd recommend setting up a budget of $0 just in case. 39 | 40 | ### Create credentials 41 | 1. Search for Text To Speech in your Console. 42 | 2. Enable the API. 43 | 3. Go to Manage and on the menu left, choose Credientials. 44 | 4. Create new Credientials for a Service Account. 45 | 7. Enter service account name (anything is just fine), select role project-> owner on step 2. Skip step 3 46 | 8. Click on the newly created Account and navigate to Keys. 47 | 9. Add a Key (Create New) and select JSON. 48 | 10. Save the file as gcp.json and put it in your Config folder. 49 | 11. Enable GCP in options.txt and start the program once to create voicelist.txt. 50 | 12. Ur good to go. 51 | 52 | ### Activate the text-to-speech API 53 | 54 | 1. In the menu on the left: APIs & Services -> Select Dashboard 55 | 1. Activate the Cloud Text-to-Speech API with the button '+ APIS and services' 56 | 57 | ### What's next? 58 | 1. Keep the program running while streaming and everyone will be able to hear the chat. 59 | 1. Use badwords.txt to create your own wordfilter, tip: There are many out in the internet, so maybe you want to download some! 60 | 1. Use blocklist.txt to block users (or bots) from TTS 61 | 1. **Example:** Nightbot answers to uptime? Block him or time will be read out always 62 | 1. Someone is being a bitch and abusing the Bot? Block him. This can be done while the Bot is running. 63 | 1. Use usernames.json to give usernames other nicknames 64 | 1. **Example:** Instead of takoz53, say "taco" 65 | 1. This can be done while the Bot is running. 66 | 1. The app should fall back on local Microsoft TTS if connectivity to GCP fails for whatever reason. 67 | ## FAQ 68 | 69 | #### The Bot doesn't say anything, but it says that it connected in Chat, what now?! 70 | 71 | Could be a number of things. If chat is not appearing in the console, it could be an issue with creds.txt. If gcp voices aren't working, it could be an issue with gcp.json, or maybe your account is out of requests (haven't tested this). 72 | 73 | #### I'm not getting a message that the Bot connected 74 | 75 | Then I'd recommend you checking if your Twitch ID is lowercase, your channel is lowercase and the oauth key is right. 76 | 77 | ### What can I customize? 78 | 79 | 1. Maximum allowed characters 80 | 1. Sentence, if maximum allowed characters are **exceeded** 81 | 1. Message connector 82 | 1. **Example:** "takoz5334 said hello" can be changed to "takoz5334 speaks hello" or into any other language etc. 83 | 1. Swearword replacing word, default is "beep" when something bad is written. 84 | 1. The TTS Voice - 85 | 1. Set GCP to True or False to enable/disable GCP voices 86 | 1. Default Voice Options (For options.txt 'Default' or usernames.json 'voice' attribute per user) 87 | - "Microsoft" - Uses local Microsoft TTS 88 | - "Random" - Uses Random GCP Voice for all messages for all users not defined by usernames.json 89 | - "Random-per-user" - Selects and stores a Random GCP Voice per user while the app is open 90 | - Anything from voicelist.txt 91 | 92 | ### How do I block users? 93 | 94 | Just write their name down in the blocklist, press enter and write another name in. Simple? Yes. 95 | 96 | ### How do I give users nicknames 97 | 98 | Just like in blocklist, go to usernames and assign each user a name, e.g. takoz53=taco xxswordmasterxx=swordmaster. 99 | Just note down, that the names **have to** be written in lowercase. 100 | 101 | ### Can I block and unblock users from chat? 102 | Yeah, definitely. You can by typing !block username and !unblock username. It'll do the checks whether the user is blocked and input is correct, so don't worry about typing something wrong. 103 | 104 | ### How do I stop a message! It is malicious! 105 | Either you or your moderators can use the !stoptts command to stop the text. It should stop immediately. Worst case you can stop the whole application! 106 | 107 | ## Feel free to [donate](https://streamelements.com/takoz5334/tip) 108 | If you liked the project and want to give me a bit of support, you can drop a few pennies here c: 109 | 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_h.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *_wpftmp.csproj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush personal settings 296 | .cr/personal 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # Local History for Visual Studio 334 | .localhistory/ 335 | *.rar 336 | -------------------------------------------------------------------------------- /TextToSpeechTTV/TwitchBot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using TwitchLib.Client; 5 | using TwitchLib.Client.Events; 6 | using TwitchLib.Client.Models; 7 | using System.Text.RegularExpressions; 8 | using TwitchLib.PubSub; 9 | 10 | namespace TextToSpeechTTV 11 | { 12 | class TwitchBot 13 | { 14 | private TwitchClient client; 15 | private Config config; 16 | private SpeechWordHandler speechWordHandler; 17 | private SpeechHelper speechHelper; 18 | private TwitchPubSub pubSub; 19 | 20 | 21 | //Set some defaults 22 | private int maxWordLength = 100; 23 | private string messageConnector = "said"; 24 | private string voice = "Microsoft David Desktop"; 25 | private string antiswear = "beep"; 26 | private string longMessage = "to be continued"; 27 | private bool readOut = true; 28 | private bool whiteList = false; 29 | 30 | private string rewardName = "RewardType.None"; 31 | //private string gcp = "false"; 32 | 33 | public TwitchBot() { 34 | //Set up Config Informations 35 | config = new Config(); 36 | maxWordLength = config.GetMaxCharacterLength(); 37 | messageConnector = config.SetMessageConnector(); 38 | antiswear = config.ReplaceSwearWord(); 39 | voice = config.SetVoice(); 40 | longMessage = config.GetLongMessage(); 41 | readOut = config.ReadOutNames(); 42 | rewardName = config.GetRewardName(); 43 | whiteList = config.IsWhiteListOnly(); 44 | //Set up Speech Helper 45 | speechHelper = new SpeechHelper(voice, 0); 46 | speechWordHandler = new SpeechWordHandler(); 47 | //Show all available voices to users 48 | List voices = SpeechHelper.GetAllInstalledVoices(); 49 | Console.ForegroundColor = ConsoleColor.Green; 50 | Console.WriteLine("All available voices: "); 51 | Console.ForegroundColor = ConsoleColor.Gray; 52 | foreach (string s in voices) 53 | Console.WriteLine(s); 54 | Console.WriteLine("----------------------------------------------------------------"); 55 | //Set up Twitch Info 56 | ConnectionCredentials credentials = new ConnectionCredentials(config.GetUsername(), config.GetOAuth()); 57 | 58 | client = new TwitchClient(); 59 | client.Initialize(credentials, config.GetChannel()); 60 | client.OnConnected += OnConnected; 61 | client.OnJoinedChannel += OnJoinedChannel; 62 | client.OnMessageReceived += OnMessageReceived; 63 | pubSub = new TwitchPubSub(); 64 | pubSub.OnChannelPointsRewardRedeemed += PubSub_OnChannelPointsRewardRedeemed; 65 | pubSub.OnPubSubServiceConnected += Ps_OnPubSubServiceConnected; 66 | //Log into Services 67 | client.Connect(); 68 | pubSub.Connect(); 69 | } 70 | 71 | private void PubSub_OnChannelPointsRewardRedeemed(object sender, 72 | TwitchLib.PubSub.Events.OnChannelPointsRewardRedeemedArgs e) { 73 | if (rewardName == "RewardType.None") 74 | { 75 | return; 76 | } 77 | 78 | // Redeemed something different. 79 | if (e.RewardRedeemed.Redemption.Reward.Title != rewardName) 80 | { 81 | return; 82 | } 83 | string username = e.RewardRedeemed.Redemption.User.Login; 84 | 85 | if (whiteList) { 86 | if (!speechWordHandler.CheckUserWhitelisted(username)) { 87 | Console.ForegroundColor = ConsoleColor.Yellow; 88 | Console.WriteLine($"{username} is not whitelisted. His message won't be read out!"); 89 | Console.ForegroundColor = ConsoleColor.Gray; 90 | return; 91 | } 92 | } 93 | 94 | Console.Write("TTS Reward was Redeemed: "); 95 | var newMessageEdited = CreateMessage(e.RewardRedeemed.Redemption.UserInput); 96 | Console.WriteLine(newMessageEdited); 97 | 98 | speechWordHandler.LoadDefaultVoice(); 99 | speechHelper.Speak_gcp(speechWordHandler.ContainsJsonUsername(username), 100 | readOut ? $"{messageConnector} {newMessageEdited}" : $"{newMessageEdited}", readOut); 101 | } 102 | 103 | private void Ps_OnPubSubServiceConnected(object sender, EventArgs e) { 104 | pubSub.ListenToChannelPoints(config.GetChannelId()); 105 | pubSub.SendTopics(config.GetAccessToken()); 106 | Console.WriteLine("PubSub Service started running!"); 107 | } 108 | 109 | private void OnConnected(object sender, OnConnectedArgs e) { 110 | Console.ForegroundColor = ConsoleColor.Green; 111 | Console.WriteLine("Successfully connected!"); 112 | Console.ForegroundColor = ConsoleColor.Gray; 113 | } 114 | 115 | private void OnJoinedChannel(object sender, OnJoinedChannelArgs e) { 116 | Console.ForegroundColor = ConsoleColor.Green; 117 | Console.WriteLine($"Successfully joined Channel: {e.Channel}"); 118 | Console.ForegroundColor = ConsoleColor.Gray; 119 | } 120 | 121 | private void OnMessageReceived(object sender, OnMessageReceivedArgs e) { 122 | speechWordHandler.LoadDefaultVoice(); 123 | CommandHandler commandHandler = new CommandHandler(); 124 | 125 | if (e.ChatMessage.IsModerator || e.ChatMessage.IsBroadcaster) 126 | { 127 | string message = e.ChatMessage.Message.ToLower(); 128 | if (message.StartsWith("!block")) 129 | { 130 | var blocked = commandHandler.BlockUser(e.ChatMessage.Message); 131 | client.SendMessage(config.GetChannel(), 132 | blocked 133 | ? "The user has been successfully blocked." 134 | : "The user is already blocked or the input was wrong."); 135 | } 136 | else if (message.StartsWith("!unblock")) 137 | { 138 | var unblocked = commandHandler.UnblockUser(e.ChatMessage.Message); 139 | client.SendMessage(config.GetChannel(), 140 | unblocked 141 | ? "The user has been successfully unblocked." 142 | : "The user isn't blocked or the input was wrong."); 143 | } 144 | else if (message.ToLower() == "!stoptts") 145 | { 146 | speechHelper.StopReading(); 147 | } 148 | } 149 | 150 | if (rewardName != "RewardType.None") 151 | { 152 | return; 153 | } 154 | 155 | if (whiteList) { 156 | if (!speechWordHandler.CheckUserWhitelisted(e.ChatMessage.Username)) { 157 | Console.ForegroundColor = ConsoleColor.Yellow; 158 | Console.WriteLine($"{e.ChatMessage.Username} is not whitelisted. His message won't be read out!"); 159 | Console.ForegroundColor = ConsoleColor.Gray; 160 | return; 161 | } 162 | } 163 | Console.WriteLine($"{e.ChatMessage.Username}:{e.ChatMessage.Message}"); 164 | 165 | if (speechWordHandler.CheckUserBlocked(e.ChatMessage.Username)) //Ignore blocked users 166 | return; 167 | if (e.ChatMessage.Message.StartsWith("!")) //Ignore Commands starting with ! 168 | return; 169 | 170 | var newMessageEdited = CreateMessage(e.ChatMessage.Message); 171 | speechHelper.Speak_gcp(speechWordHandler.ContainsJsonUsername(e.ChatMessage.Username), 172 | readOut ? $"{messageConnector} {newMessageEdited}" : $"{newMessageEdited}", readOut); 173 | } 174 | 175 | private string CreateMessage(string message) { 176 | //Check if URL is in Message 177 | Regex UrlMatch = 178 | new Regex( 179 | @"(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?"); 180 | Match url = UrlMatch.Match(message); 181 | 182 | //Create a List for multiple bad Words in sentence 183 | //Add first replaced sentence 184 | //Get first replaced sentence and replace it and continue this loop for each bad word. 185 | List badWords = new List(); 186 | 187 | badWords = speechWordHandler.ContainsBadWord(message); 188 | 189 | 190 | string newMessageEdited = message; 191 | 192 | if (url.Success) //Check if contains URL 193 | { 194 | newMessageEdited = message.Replace(url.Value, "url"); 195 | } 196 | 197 | if (badWords.Count != 0) //Check if containing bad words 198 | { 199 | for (int i = 0; i < badWords.Count; i++) 200 | newMessageEdited = newMessageEdited.Replace(badWords.ElementAt(i), antiswear); 201 | } 202 | 203 | if ((maxWordLength + longMessage.Length) <= newMessageEdited.Length && maxWordLength != 0 204 | ) //Check if Sentence is too long 205 | { 206 | newMessageEdited = newMessageEdited.Substring(0, Math.Min(newMessageEdited.Length, maxWordLength)) + 207 | "....... " + longMessage; 208 | } 209 | 210 | return newMessageEdited; 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /TextToSpeechTTV/SpeechHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Speech.Synthesis; 5 | using Google.Cloud.TextToSpeech.V1; 6 | using System.IO; 7 | using NAudio.Wave; 8 | using NAudio.CoreAudioApi; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Globalization; 12 | 13 | namespace TextToSpeechTTV { 14 | class SpeechHelper { 15 | private int rate; 16 | private bool randomVoice; 17 | private string ttsName; 18 | private double googleSpeakingRate; 19 | private double googlePitch; 20 | private SpeechSynthesizer speechSynthesizer; 21 | private TextToSpeechClient client; 22 | private List voiceList; 23 | private List voiceListCulture; 24 | private Config config; 25 | 26 | private WaveOutEvent waveOut; 27 | //private List voicelistWavenet; 28 | //private List voicelistStandard; 29 | 30 | public SpeechHelper (string ttsName, int rate) { 31 | this.rate = rate; 32 | this.ttsName = ttsName; 33 | 34 | try { 35 | CreateSpeechSynthesizer(); 36 | config = new Config(); 37 | string gcpType = config.GetGCP(); 38 | try { 39 | if (!randomVoice) { 40 | googleSpeakingRate = config.GetSpeakingRate(); 41 | googlePitch = config.GetSpeakingPitch(); 42 | } 43 | } catch (ArgumentException e) { 44 | Console.ForegroundColor = ConsoleColor.Red; 45 | Console.WriteLine(e.Message); 46 | Console.WriteLine("Press any Key to continue..."); 47 | Console.ReadLine(); 48 | Environment.Exit(0); 49 | } 50 | 51 | voiceList = config.voicelist; 52 | if (voiceList == null) { 53 | voiceList = new List(); 54 | } 55 | 56 | if (gcpType != "false") { 57 | client = config.GetGCPClient(); 58 | if (gcpType == "standard") { 59 | voiceList = voiceList.FindAll(delegate (string s) { return s.Contains("Standard"); }); 60 | } else if (gcpType == "wavenet") { 61 | voiceList = voiceList.FindAll(delegate (string s) { return s.Contains("Wavenet"); }); 62 | } 63 | } 64 | 65 | Console.WriteLine("Voicelist.txt is available in Config Folder."); 66 | 67 | //Returns: en-EN, we only want "en" 68 | string cultureFirst = CultureInfo.CurrentCulture.Name.Split('-')[0]; 69 | voiceListCulture = new List(); 70 | 71 | foreach (string voiceListItem in voiceList) { 72 | if (voiceListItem.StartsWith(cultureFirst)) { 73 | voiceListCulture.Add(voiceListItem); 74 | } 75 | } 76 | 77 | // Prod windows to actually initialize default device correctly 78 | using (var mmdEnumerator = new MMDeviceEnumerator()) 79 | using (var mmDevice = mmdEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)) 80 | using (var outDevice = new WasapiOut(mmDevice, AudioClientShareMode.Shared, false, 100)) 81 | { 82 | outDevice.Init(new SilenceProvider(new WaveFormat(48000, 16, 1))); 83 | outDevice.Play(); 84 | } 85 | 86 | } catch { 87 | Console.ForegroundColor = ConsoleColor.Red; 88 | Console.WriteLine("TTS Name does not exist. TTS will NOT work."); 89 | Console.ForegroundColor = ConsoleColor.Gray; 90 | } 91 | } 92 | 93 | private void CreateSpeechSynthesizer () { 94 | speechSynthesizer = new SpeechSynthesizer { 95 | Rate = rate 96 | }; 97 | speechSynthesizer.SelectVoice(ttsName); 98 | } 99 | 100 | public void StopReading () { 101 | //Reinstate speechSynthesizer 102 | speechSynthesizer.Dispose(); 103 | CreateSpeechSynthesizer(); 104 | waveOut.Stop(); 105 | waveOut.Dispose(); 106 | } 107 | 108 | public static List GetAllInstalledVoices () { 109 | List list = new List(); 110 | using (SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer()) { 111 | foreach (InstalledVoice installedVoice in speechSynthesizer.GetInstalledVoices()) { 112 | VoiceInfo voiceInfo = installedVoice.VoiceInfo; 113 | list.Add(voiceInfo.Name); 114 | } 115 | } 116 | 117 | return list; 118 | } 119 | 120 | public void Speak (User user, string text) { 121 | Prompt prompt = new Prompt($"{user.Nick} {text}"); 122 | 123 | using (var stream = new MemoryStream()) 124 | using (var mmdEnumerator = new MMDeviceEnumerator()) 125 | using (var mmDevice = mmdEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)) 126 | { 127 | speechSynthesizer.SetOutputToWaveStream(stream); 128 | speechSynthesizer.Speak(prompt); 129 | stream.Flush(); 130 | stream.Seek(0, SeekOrigin.Begin); 131 | 132 | using (var outDevice = new WasapiOut(mmDevice, AudioClientShareMode.Shared, false, 100)) 133 | using (var waveStream = new RawSourceWaveStream(stream, new WaveFormat(11025, 16, 2))) 134 | { 135 | 136 | outDevice.Init(waveStream); 137 | outDevice.Play(); 138 | while (outDevice.PlaybackState == PlaybackState.Playing) 139 | { 140 | Thread.Sleep(1000); 141 | } 142 | } 143 | } 144 | } 145 | 146 | public void Speak_gcp (User user, string text, bool readOut) { 147 | string voiceName; 148 | var userData = user; 149 | if (!readOut) { 150 | user.Name = ""; 151 | user.Nick = ""; 152 | } 153 | 154 | //Fallback on Microsoft 155 | if (voiceList?.Any() != true || user.Voice.ToLower() == "microsoft") { 156 | Speak(user, text); 157 | return; 158 | } 159 | 160 | // Set the text input to be synthesized. 161 | SynthesisInput input = new SynthesisInput { 162 | Text = $"{user.Nick} {text}" 163 | }; 164 | 165 | // Build the voice request, select the language code ("en-US"), 166 | // and the SSML voice gender ("neutral"). 167 | 168 | Random r = new Random(); 169 | int randName = r.Next(voiceList.Count); 170 | // Boundaries are -20, 20, but makes it sound awful. 171 | int randPitch = r.Next(-10, 10); 172 | switch (userData.Voice.ToLower()) { 173 | case "random": 174 | voiceName = voiceList[randName]; 175 | randomVoice = true; 176 | googleSpeakingRate = this.config.GetSpeakingRate(); 177 | googlePitch = randPitch; 178 | break; 179 | case "random-pc-language": 180 | randomVoice = true; 181 | int randNameCulture = r.Next(voiceListCulture.Count); 182 | voiceName = voiceListCulture[randNameCulture]; 183 | googleSpeakingRate = this.config.GetSpeakingRate(); 184 | googlePitch = randPitch; 185 | break; 186 | default: { 187 | if (voiceList.Any(n => n.ToLower() == user.Voice.ToLower())) { 188 | voiceName = userData.Voice; 189 | googlePitch = userData.SpeakingPitch; 190 | googleSpeakingRate = userData.SpeakingSpeed; 191 | } else { 192 | voiceName = "en-AU-Standard-B"; 193 | googlePitch = this.config.GetSpeakingPitch(); 194 | googleSpeakingRate = this.config.GetSpeakingRate(); 195 | } 196 | randomVoice = false; 197 | break; 198 | } 199 | } 200 | 201 | 202 | string[] voiceParts = voiceName.Split('-'); 203 | var languageCode = ($"{voiceParts[0]}-{voiceParts[1]}"); 204 | 205 | 206 | VoiceSelectionParams voice = new VoiceSelectionParams { 207 | Name = voiceName, 208 | LanguageCode = languageCode 209 | }; 210 | 211 | AudioConfig config = new AudioConfig { 212 | AudioEncoding = AudioEncoding.Linear16, 213 | SpeakingRate = googleSpeakingRate, 214 | Pitch = googlePitch 215 | }; 216 | 217 | //WIP 218 | var task = Task.Factory.StartNew(() => CreateResponse(input, voice, config)); 219 | var isCompletedSuccessfully = task.Wait(TimeSpan.FromMilliseconds(5000)); 220 | 221 | if (!isCompletedSuccessfully) { 222 | throw new TimeoutException("Can't read this piece of text! Exceeded Timeout limit! (5s)"); 223 | } 224 | SynthesizeSpeechResponse response = task.Result; 225 | 226 | using (var ms = new MemoryStream()) { 227 | response.AudioContent.WriteTo(ms); 228 | byte[] buf = ms.GetBuffer(); 229 | var source = new BufferedWaveProvider(new WaveFormat(24000, 16, 1)) { 230 | ReadFully = false, 231 | BufferLength = buf.Length 232 | }; 233 | try { 234 | source.AddSamples(buf, 0, buf.Length); 235 | } catch (Exception) { 236 | Console.WriteLine("Can't read the given TTS input!"); 237 | return; 238 | } 239 | 240 | waveOut = new WaveOutEvent(); 241 | waveOut.Init(source); 242 | 243 | AutoResetEvent stopped = new AutoResetEvent(false); 244 | waveOut.PlaybackStopped += (sender, e) => { 245 | stopped.Set(); 246 | waveOut.Dispose(); 247 | }; 248 | waveOut.Play(); 249 | stopped.WaitOne(); 250 | } 251 | } 252 | 253 | private SynthesizeSpeechResponse CreateResponse (SynthesisInput input, VoiceSelectionParams voice, AudioConfig config) { 254 | var response = client.SynthesizeSpeech(new SynthesizeSpeechRequest { 255 | Input = input, 256 | Voice = voice, 257 | AudioConfig = config 258 | }); 259 | return response; 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /TextToSpeechTTV/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using Google.Cloud.TextToSpeech.V1; 7 | 8 | 9 | namespace TextToSpeechTTV { 10 | 11 | 12 | class Config { 13 | //Path for every Config 14 | private readonly string creds = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "creds.txt"); 15 | private readonly string options = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "options.txt"); 16 | private readonly string blocklist = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "blocklist.txt"); 17 | private readonly string whitelist = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "whitelist.txt"); 18 | private readonly string badwords = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "badwords.txt"); 19 | private readonly string new_usernames = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "usernames.json"); 20 | private readonly string voicelistfile = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "voicelist.txt"); 21 | private readonly string foldername = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config"); 22 | public List voicelist; 23 | public TextToSpeechClient client; 24 | public string[] optionsList; 25 | public string[] credsList; 26 | 27 | public Config () { 28 | if (Directory.Exists(foldername)) { 29 | optionsList = File.ReadAllLines(options); 30 | credsList = File.ReadAllLines(creds); 31 | } 32 | CreateConfig(); 33 | } 34 | 35 | public object AuthExplicit () { 36 | System.Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Config", "gcp.json")); 37 | try { 38 | client = TextToSpeechClient.Create(); 39 | } catch (Exception e) { 40 | Console.WriteLine($"Failed to initialise GCP TTS Client. Set GCP to 'false' in options.txt\n-----------\n"); 41 | Console.WriteLine(e); 42 | Console.WriteLine("Press any key to quit..."); 43 | Console.ReadKey(); 44 | Environment.Exit(0); 45 | } 46 | var response = client.ListVoices(""); 47 | 48 | List voices = new List(); 49 | 50 | foreach (var voice in response.Voices) { 51 | Console.WriteLine($"{voice.Name} ({voice.SsmlGender}); Rate:{voice.NaturalSampleRateHertz} Language codes: {string.Join(", ", voice.LanguageCodes)}"); 52 | voices.Add(voice.Name); 53 | } 54 | voicelist = voices; 55 | 56 | File.WriteAllText(voicelistfile, string.Empty); 57 | File.WriteAllLines(voicelistfile, voices); 58 | return null; 59 | } 60 | 61 | private void CreateConfig () { 62 | if (Directory.Exists(foldername)) { 63 | if (GetGCP() != "false") { 64 | AuthExplicit(); 65 | } 66 | return; 67 | } 68 | 69 | 70 | if (!Directory.Exists(foldername)) 71 | Directory.CreateDirectory(foldername); 72 | if (!File.Exists(badwords)) 73 | File.Create(badwords).Dispose(); 74 | if (!File.Exists(whitelist)) 75 | FillWhitelistExamples(); 76 | if (!File.Exists(options)) 77 | FillOptionsFile(); 78 | if (!File.Exists(blocklist)) 79 | FillBlocklistExamples(); 80 | if (!File.Exists(new_usernames)) 81 | FillNewUsernamesExamples(); 82 | if (!File.Exists(creds)) 83 | FillCredsFile(); 84 | optionsList = File.ReadAllLines(options); 85 | credsList = File.ReadAllLines(creds); 86 | } 87 | private void FillNewUsernamesExamples () { 88 | File.WriteAllLines(new_usernames, new string[] 89 | { 90 | @"{ 91 | ""users"": [ 92 | { 93 | ""name"": ""youraccount"", 94 | ""nick"": ""you"", 95 | ""voice"": ""random"", 96 | ""speakingSpeed"": ""1"", 97 | ""speakingPitch"": ""0"" 98 | }, 99 | { 100 | ""name"": ""myaccount"", 101 | ""nick"": ""me"", 102 | ""voice"": ""fr-CA-Wavenet-B"", 103 | ""speakingSpeed"": ""2"", 104 | ""speakingPitch"": ""10"" 105 | } 106 | ] 107 | }" 108 | }); 109 | } 110 | private void FillBlocklistExamples () { 111 | File.WriteAllLines(blocklist, new string[] 112 | { 113 | "nightbot", 114 | "moobot", 115 | "phantombot", 116 | "coebot", 117 | "deepbot", 118 | "You can fill and save the blacklist without restarting the program or use !block user in chat." 119 | }); 120 | } 121 | 122 | private void FillWhitelistExamples () { 123 | File.WriteAllLines(whitelist, new string[] 124 | { 125 | "somebot", 126 | "yourself", 127 | "yourfavouriteviewer", 128 | "etc", 129 | "You can fill and save the whitelist without restarting the program." 130 | }); 131 | } 132 | private void FillCredsFile () { 133 | Console.Write("Please enter your Botname (or Username, if no Bot Account): "); 134 | string twitchId = Console.ReadLine(); 135 | Console.Write("Please enter your oauth key. URL in Readme (oauth:...): "); 136 | string oAuth = Console.ReadLine(); 137 | Console.Write("Please enter your Channel Name (lowercase): "); 138 | string channelName = Console.ReadLine(); 139 | Console.Write("Please enter your Access Token. URL in Readme: "); 140 | string accessToken = Console.ReadLine(); 141 | Console.Write("Lastly I need your ChannelID. You can get the ID from the URL in the Readme\n" + 142 | "Please Enter your Channel ID: "); 143 | string channelId = Console.ReadLine(); 144 | File.WriteAllLines(creds, new string[] { 145 | "Twitch ID (lowercase):", 146 | twitchId, 147 | "OAUTH (Twitch TMI):", 148 | oAuth, 149 | "Channel(lowercase):", 150 | channelName, 151 | "Access token:", 152 | accessToken, 153 | "ChannelID:", 154 | channelId 155 | }); 156 | Console.WriteLine("If anything doesn't seem to work out or changes, you can find this file in Config/creds.txt and edit it." + 157 | "\nOptions can be changed in options.txt as well. Pitch & Speed are only available for Google TTS."); 158 | Console.WriteLine("--------------------------------------------------------"); 159 | } 160 | 161 | 162 | private void FillOptionsFile () { 163 | Console.WriteLine("I've automatically set up the default options for the TTS.\n" + 164 | "You can change those Settings in the options.txt file."); 165 | string rewardName = ""; 166 | while (true) { 167 | Console.Write("Do you want to bind TTS to a Channel Reward? Y / N: "); 168 | string result = Console.ReadLine().ToLower(); 169 | if (result == "y") { 170 | Console.Write("Please enter the Reward Title (e.g. TTS-Reward), case sensitive: "); 171 | rewardName = Console.ReadLine(); 172 | break; 173 | } else if (result == "n") { 174 | Console.WriteLine("Alright! TTS will run on every message sent!"); 175 | rewardName = "RewardType.None"; 176 | break; 177 | } 178 | } 179 | 180 | string readOut = ""; 181 | while (true) { 182 | Console.Write("Should the TTS read out usernames? Y/N:"); 183 | readOut = Console.ReadLine().ToLower(); 184 | if (readOut == "y" || readOut == "n") 185 | break; 186 | } 187 | string whiteList = ""; 188 | while (true) { 189 | Console.Write("Should the TTS only read out messages from Whitelist? Y/N:"); 190 | whiteList = Console.ReadLine().ToLower(); 191 | if (whiteList == "y" || whiteList == "n") 192 | break; 193 | } 194 | double googleSpeakingRate; 195 | double googlePitch; 196 | 197 | while (true) { 198 | Console.Write("Please select Speaking Speed (0.25 to 4.0; 1 = Default Speed):"); 199 | string speakingRate = Console.ReadLine(); 200 | Console.Write("Please select the voice pitch (-20 to 20; 0 = Default Pitch):"); 201 | string speakingPitch = Console.ReadLine(); 202 | speakingRate = speakingRate.Replace(",", "."); 203 | speakingPitch = speakingPitch.Replace(",", "."); 204 | double.TryParse(speakingRate, NumberStyles.Any, CultureInfo.InvariantCulture, out googleSpeakingRate); 205 | double.TryParse(speakingPitch, NumberStyles.Any, CultureInfo.InvariantCulture, out googlePitch); 206 | if ((googleSpeakingRate <= 4 || googleSpeakingRate >= 0.25) 207 | && (googlePitch <= 20 || googlePitch >= -20)) { 208 | break; 209 | } 210 | Console.WriteLine("The entered values are either too high or too low! Please recheck the values."); 211 | } 212 | 213 | File.WriteAllLines(options, new string[] { 214 | "Set TTS Voice:", 215 | "Microsoft David Desktop", 216 | "Set Message Connector:", 217 | "said", 218 | "Maximum allowed Characters, 0 for no limit:", 219 | "100", 220 | "Replace swear word with:", 221 | "beep", 222 | "Say this, if long Sentence:", 223 | "to be continued", 224 | "GCP TTS? (true, wavenet, standard, false)", 225 | "false", 226 | "Default GCP Voice: (Select from voicelist.txt, random, random-pc-language:)", 227 | "Random-pc-language", 228 | "Bound Reward Name:", 229 | rewardName, 230 | "Read Names?:", 231 | readOut == "y" ? "true" : "false", 232 | "Google Speaking Speed:", 233 | googleSpeakingRate.ToString(), 234 | "Google Speaking Pitch:", 235 | googlePitch.ToString(), 236 | "Whitelist only?:", 237 | whiteList == "y" ? "true" : "false", 238 | 239 | }); 240 | Console.WriteLine("--------------------------------------------------------"); 241 | } 242 | 243 | public string GetUsername () { 244 | string id = credsList[1]; 245 | return id; 246 | } 247 | 248 | public string GetOAuth () { 249 | string password = credsList[3]; 250 | return password; 251 | } 252 | 253 | public string GetChannel () { 254 | string channel = credsList[5]; 255 | return channel; 256 | } 257 | 258 | public string GetAccessToken () { 259 | string accessToken = credsList[7]; 260 | return accessToken; 261 | } 262 | 263 | public string GetChannelId () { 264 | string channelId = credsList[9]; 265 | return channelId; 266 | } 267 | 268 | public bool ReadOutNames () { 269 | bool readOut = bool.Parse(optionsList[17]); 270 | return readOut; 271 | } 272 | 273 | public double GetSpeakingRate () { 274 | string strRate = optionsList[19].Replace(",", "."); 275 | bool parsable = double.TryParse(strRate, NumberStyles.Any, CultureInfo.InvariantCulture, out double speakingRate); 276 | if (speakingRate > 4 || speakingRate < 0.25) { 277 | throw new ArgumentException("Speaking Rate can't be higher than 4 or lower than 0.25!"); 278 | } 279 | if (!parsable) { 280 | throw new ArgumentException("Can't parse Speaking Rate!"); 281 | } 282 | return speakingRate; 283 | } 284 | public double GetSpeakingPitch () { 285 | string strPitch = optionsList[21].Replace(",", "."); 286 | bool parsable = double.TryParse(strPitch, NumberStyles.Any, CultureInfo.InvariantCulture, out double speakingPitch); 287 | 288 | if (speakingPitch > 20 || speakingPitch < -20) { 289 | throw new ArgumentException("Speaking Pitch can't be higher than 20 or lower than -20!"); 290 | } 291 | if (!parsable) { 292 | throw new ArgumentException("Can't parse Speaking Pitch!"); 293 | } 294 | return speakingPitch; 295 | } 296 | public bool IsWhiteListOnly () { 297 | bool whiteList = bool.Parse(optionsList[23]); 298 | return whiteList; 299 | } 300 | public string SetVoice () { 301 | string voice = optionsList[1]; 302 | return voice; 303 | } 304 | public string SetMessageConnector () { 305 | string say = optionsList[3]; 306 | return say; 307 | } 308 | 309 | public int GetMaxCharacterLength () { 310 | string wordLength = optionsList[5]; 311 | int.TryParse(wordLength, out int result); 312 | return result; 313 | } 314 | 315 | public string ReplaceSwearWord () { 316 | string antiswear = optionsList[7]; 317 | return antiswear; 318 | } 319 | 320 | public string GetLongMessage () { 321 | string longMessage = optionsList[9]; 322 | return longMessage; 323 | } 324 | 325 | public string GetGCP () { 326 | string gcp = optionsList[11].ToLower(); 327 | string[] settings = { "true", "wavenet", "standard", "false" }; 328 | if (settings.Contains(gcp)) 329 | return gcp; 330 | Console.WriteLine("Couldn't get GCP Settings in Settings file!"); 331 | throw new ArgumentException("Must be either true, false, wavenet, or standard"); 332 | } 333 | public string GetRewardName () { 334 | string rewardName = optionsList[15]; 335 | return rewardName; 336 | } 337 | 338 | public TextToSpeechClient GetGCPClient () { 339 | return client; 340 | } 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /MigrationBackup/18800c69/TextToSpeechTTV/TextToSpeechTTV.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AB12D449-8E46-4C80-AFC5-7F6DEF2369F8} 8 | Exe 9 | TextToSpeechTTV 10 | TextToSpeechTTV 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\Google.Api.CommonProtos.2.0.0\lib\net461\Google.Api.CommonProtos.dll 40 | 41 | 42 | ..\packages\Google.Api.Gax.3.0.0\lib\net461\Google.Api.Gax.dll 43 | 44 | 45 | ..\packages\Google.Api.Gax.Grpc.3.0.0\lib\net461\Google.Api.Gax.Grpc.dll 46 | 47 | 48 | ..\packages\Google.Api.Gax.Grpc.GrpcCore.3.0.0\lib\net461\Google.Api.Gax.Grpc.GrpcCore.dll 49 | 50 | 51 | ..\packages\Google.Apis.1.44.1\lib\net45\Google.Apis.dll 52 | 53 | 54 | ..\packages\Google.Apis.Auth.1.44.1\lib\net45\Google.Apis.Auth.dll 55 | 56 | 57 | ..\packages\Google.Apis.Auth.1.44.1\lib\net45\Google.Apis.Auth.PlatformServices.dll 58 | 59 | 60 | ..\packages\Google.Apis.Core.1.44.1\lib\net45\Google.Apis.Core.dll 61 | 62 | 63 | ..\packages\Google.Apis.1.44.1\lib\net45\Google.Apis.PlatformServices.dll 64 | 65 | 66 | ..\packages\Google.Cloud.TextToSpeech.V1.2.0.0\lib\net461\Google.Cloud.TextToSpeech.V1.dll 67 | 68 | 69 | ..\packages\Google.Protobuf.3.11.4\lib\net45\Google.Protobuf.dll 70 | 71 | 72 | ..\packages\Grpc.Auth.2.27.0\lib\net45\Grpc.Auth.dll 73 | 74 | 75 | ..\packages\Grpc.Core.2.27.0\lib\net45\Grpc.Core.dll 76 | 77 | 78 | ..\packages\Grpc.Core.Api.2.27.0\lib\net45\Grpc.Core.Api.dll 79 | 80 | 81 | ..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll 82 | 83 | 84 | ..\packages\Microsoft.Extensions.Configuration.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll 85 | 86 | 87 | ..\packages\Microsoft.Extensions.Configuration.Abstractions.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll 88 | 89 | 90 | ..\packages\Microsoft.Extensions.Configuration.Binder.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll 91 | 92 | 93 | ..\packages\Microsoft.Extensions.DependencyInjection.3.0.0-preview9.19423.4\lib\net461\Microsoft.Extensions.DependencyInjection.dll 94 | 95 | 96 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll 97 | 98 | 99 | ..\packages\Microsoft.Extensions.Logging.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.Logging.dll 100 | 101 | 102 | ..\packages\Microsoft.Extensions.Options.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.Options.dll 103 | 104 | 105 | ..\packages\Microsoft.Extensions.Primitives.3.0.0-preview9.19423.4\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll 106 | 107 | 108 | ..\packages\NAudio.1.10.0\lib\net35\NAudio.dll 109 | 110 | 111 | ..\packages\Serilog.2.9.0-dev-01124\lib\net46\Serilog.dll 112 | 113 | 114 | ..\packages\Serilog.Extensions.Logging.3.0.2-dev-10256\lib\netstandard2.0\Serilog.Extensions.Logging.dll 115 | 116 | 117 | 118 | ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll 119 | 120 | 121 | ..\packages\System.ComponentModel.Annotations.4.6.0-preview9.19421.4\lib\net461\System.ComponentModel.Annotations.dll 122 | 123 | 124 | 125 | 126 | ..\packages\System.Interactive.4.1.1\lib\net45\System.Interactive.dll 127 | 128 | 129 | ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll 130 | 131 | 132 | ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll 133 | True 134 | 135 | 136 | 137 | ..\packages\System.Numerics.Vectors.4.6.0-preview5.19224.8\lib\net46\System.Numerics.Vectors.dll 138 | 139 | 140 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.6.0-preview9.19421.4\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 141 | 142 | 143 | ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net461\System.Security.Cryptography.Algorithms.dll 144 | True 145 | 146 | 147 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 148 | True 149 | True 150 | 151 | 152 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 153 | True 154 | True 155 | 156 | 157 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll 158 | True 159 | 160 | 161 | 162 | ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | ..\packages\TwitchLib.Communication.1.0.3\lib\netstandard2.0\TwitchLib.Communication.dll 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | {00020430-0000-0000-C000-000000000046} 191 | 2 192 | 0 193 | 0 194 | primary 195 | False 196 | True 197 | 198 | 199 | 200 | 201 | 202 | 203 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /MigrationBackup/18800c69/TextToSpeechTTV/NuGetUpgradeLog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NuGetMigrationLog 6 |

153 | NuGet Migration Report - TextToSpeechTTV

Overview

Migration to PackageReference was completed successfully. Please build and run your solution to verify that all packages are available.
154 | If you run into any problems, have feedback, questions, or concerns, please 155 | file an issue on the NuGet GitHub repository.
156 | Changed files and this report have been backed up here: 157 | C:\Users\takocchi\Documents\GitHub\TwitchTTS\MigrationBackup\18800c69\TextToSpeechTTV

Packages processed

Top-level dependencies:

Package IdVersion
Google.Cloud.TextToSpeech.V1 158 | v2.0.0
Grpc.Core 159 | v2.27.0
Microsoft.Extensions.Logging 160 | v3.0.0-preview9.19423.4
NAudio 161 | v1.10.0
Serilog 162 | v2.9.0-dev-01124
Serilog.Extensions.Logging 163 | v3.0.2-dev-10256
System.Buffers 164 | v4.5.0
System.Interactive 165 | v4.1.1
System.Numerics.Vectors 166 | v4.6.0-preview5.19224.8
System.Security.Cryptography.Algorithms 167 | v4.3.1
System.Security.Cryptography.X509Certificates 168 | v4.3.2
TwitchLib 169 | v3.2.0
TwitchLib.Api 170 | v3.2.4
TwitchLib.Client 171 | v3.2.3
TwitchLib.PubSub 172 | v3.2.3

Transitive dependencies:

Package IdVersion
Google.Api.CommonProtos 173 | v2.0.0
Google.Api.Gax 174 | v3.0.0
Google.Api.Gax.Grpc 175 | v3.0.0
Google.Api.Gax.Grpc.GrpcCore 176 | v3.0.0
Google.Apis 177 | v1.44.1
Google.Apis.Auth 178 | v1.44.1
Google.Apis.Core 179 | v1.44.1
Google.Protobuf 180 | v3.11.4
Grpc.Auth 181 | v2.27.0
Grpc.Core.Api 182 | v2.27.0
Microsoft.Bcl.AsyncInterfaces 183 | v1.0.0
Microsoft.CSharp 184 | v4.7.0
Microsoft.Extensions.Configuration 185 | v3.0.0-preview9.19423.4
Microsoft.Extensions.Configuration.Abstractions 186 | v3.0.0-preview9.19423.4
Microsoft.Extensions.Configuration.Binder 187 | v3.0.0-preview9.19423.4
Microsoft.Extensions.DependencyInjection 188 | v3.0.0-preview9.19423.4
Microsoft.Extensions.DependencyInjection.Abstractions 189 | v3.0.0-preview9.19423.4
Microsoft.Extensions.Logging.Abstractions 190 | v5.0.0
Microsoft.Extensions.Options 191 | v3.0.0-preview9.19423.4
Microsoft.Extensions.Primitives 192 | v3.0.0-preview9.19423.4
Newtonsoft.Json 193 | v13.0.1
System.ComponentModel.Annotations 194 | v4.6.0-preview9.19421.4
System.Memory 195 | v4.5.3
System.Net.Http 196 | v4.3.4
System.Runtime.CompilerServices.Unsafe 197 | v4.6.0-preview9.19421.4
System.Security.Cryptography.Encoding 198 | v4.3.0
System.Security.Cryptography.Primitives 199 | v4.3.0
System.Threading.Tasks.Extensions 200 | v4.5.2
TwitchLib.Api.Core 201 | v3.2.4
TwitchLib.Api.Core.Enums 202 | v3.2.4
TwitchLib.Api.Core.Interfaces 203 | v3.2.4
TwitchLib.Api.Core.Models 204 | v3.2.4
TwitchLib.Api.Helix 205 | v3.2.4
TwitchLib.Api.Helix.Models 206 | v3.2.4
TwitchLib.Api.V5 207 | v3.2.4
TwitchLib.Api.V5.Models 208 | v3.2.4
TwitchLib.Client.Enums 209 | v3.2.3
TwitchLib.Client.Models 210 | v3.2.3
TwitchLib.Communication 211 | v1.0.3

Package compatibility issues

Description
212 | No issues were found. 213 |
--------------------------------------------------------------------------------