├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── ConsoleChatbot ├── ConsoleChatService.cs ├── ConsoleChatbot.csproj ├── Program.cs └── StubServiceProvider.cs ├── Fritz.Chatbot ├── AssemblyInfo.cs ├── AttentionHub.cs ├── ChatUserInfo.cs ├── Commands │ ├── AttentionCommand.cs │ ├── AzureQnACommand.cs │ ├── EchoCommand.cs │ ├── GitHubCommand.cs │ ├── HelpCommand.cs │ ├── HereCommand.cs │ ├── HttpPageTitleCommand.cs │ ├── HypeCommand.cs │ ├── HyperlinkCommand.cs │ ├── IBasicCommand.cs │ ├── IExtendedCommand.cs │ ├── ImageDescriptorCommand.cs │ ├── PingCommand.cs │ ├── PredictHatCommand.cs │ ├── ProjectCommand.cs │ ├── QnAMakerResult.cs │ ├── QuotesCommand.cs │ ├── ResetHatAiCommand.cs │ ├── SentimentCommand.cs │ ├── SentimentSink.cs │ ├── ShoutoutCommand.cs │ ├── SkeetCommand.cs │ ├── SoundFxCommand.cs │ ├── TeamCommand.cs │ ├── TextCommand.cs │ ├── ToDoCommand.cs │ └── UptimeCommand.cs ├── Fritz.Chatbot.csproj ├── FritzBot.cs ├── Helpers │ ├── LogExtensions.cs │ └── StringExtensions.cs ├── ITrainHat.cs ├── Models │ └── VisionDescription.cs ├── ObsHub.cs ├── ScreenshotTrainingService.cs ├── SkeetQuotes.txt └── VisionApiReadme.txt ├── Fritz.ObsProxy ├── BotClient.cs ├── Fritz.ObsProxy.csproj ├── ObsClient.cs ├── Program.cs ├── Worker.cs ├── appsettings.Development.json └── appsettings.json ├── Fritz.StreamLib.Core ├── ChannelPointRedemption.cs ├── ChatMessageEventArgs.cs ├── ChatUserInfoEventArgs.cs ├── Fritz.StreamLib.Core.csproj ├── IAttentionClient.cs ├── IChatService.cs ├── IStreamService.cs ├── ITakeScreenshots.cs ├── ServiceUpdatedEventArgs.cs └── TwitchTokenConfig.cs ├── Fritz.StreamTools.sln ├── Fritz.StreamTools ├── .gitignore ├── .npmrc ├── Controllers │ ├── AttentionController.cs │ ├── FollowersController.cs │ ├── GitHubController.cs │ ├── HomeController.cs │ ├── RundownController.cs │ ├── RundownItemController.cs │ └── ViewersController.cs ├── Dockerfile ├── Fritz.StreamTools.csproj ├── GlobalSuppressions.cs ├── Helpers │ ├── DisplayHelper.cs │ ├── JsonExtensions.cs │ ├── LogExtensions.cs │ ├── ReflectionHelper.cs │ └── TaskExtensions.cs ├── Hubs │ ├── AttentionHub.cs │ ├── BaseHub.cs │ ├── FollowerHub.cs │ └── GithubyMcGithubFace.cs ├── Interfaces │ └── IRundownService.cs ├── Models │ ├── FollowerCountConfiguration.cs │ ├── FollowerGoalConfiguration.cs │ ├── GitHubConfiguration.cs │ ├── GitHubContributor.cs │ ├── GitHubInformation.cs │ ├── GitHubRepository.cs │ ├── GitHubUpdatedEventArgs.cs │ ├── RundownItem.cs │ ├── RundownItemRepository.cs │ └── RundownRepository.cs ├── Pages │ ├── Admin.cshtml │ ├── Admin.cshtml.cs │ ├── CurrentViewers.cshtml │ ├── CurrentViewers.cshtml.cs │ ├── HatDisplay.cshtml │ ├── Rundown.cshtml │ ├── Rundown.cshtml.cs │ ├── Sentiment.cshtml │ ├── Sentiment.cshtml.cs │ ├── SentimentGauge.cshtml │ ├── Team.cshtml │ ├── TestTwitchbot.cshtml │ ├── TestTwitchbot.cshtml.cs │ ├── ToDo.cshtml │ └── _ViewImports.cshtml ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── SampleQuotes.txt ├── Services │ ├── FakeService.cs │ ├── FollowerClient.cs │ ├── GitHubService.cs │ ├── GithubyMcGithubFaceClient.cs │ ├── RundownService.cs │ ├── SentimentService.cs │ ├── StreamService.cs │ ├── TwitchPubSubService.cs │ └── TwitchService.cs ├── SkeetQuotes.txt ├── Startup.cs ├── StartupServices │ ├── ConfigureServices.cs │ └── ConfigureSignalrTagHelperOptions.cs ├── TagHelpers │ ├── ActiveRouteTagHelper.cs │ ├── GoogleFontTagHelper.cs │ ├── SignalrTagHelper.cs │ ├── SignalrTagHelperOptions.cs │ └── VersionTagHelper.cs ├── ViewComponents │ └── FooterViewComponent.cs ├── Views │ ├── Attention │ │ ├── Index.cshtml │ │ ├── Points.cshtml │ │ └── TestClient.cshtml │ ├── Followers │ │ ├── Count.cshtml │ │ ├── CountConfiguration.cshtml │ │ ├── Docs_Count.cshtml │ │ ├── Docs_Goal.cshtml │ │ ├── Goal.cshtml │ │ └── GoalConfiguration.cshtml │ ├── GitHub │ │ ├── Configuration.cshtml │ │ ├── Contributor_h-scroll.cshtml │ │ ├── ContributorsInformation.cshtml │ │ └── _ContributorTickerSegment.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Shared │ │ ├── Components │ │ │ └── Footer │ │ │ │ └── Default.cshtml │ │ ├── _Footer.cshtml │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── bundleconfig.json ├── package.json └── wwwroot │ ├── animatedProgress.html │ ├── contents │ ├── Scott!!.mp3 │ ├── hey_listen.mp3 │ └── img │ │ └── teamlogo.png │ ├── css │ ├── animatedProgress.scss │ ├── site.css │ └── site.min.css │ ├── favicon.ico │ ├── img │ └── sentiment-bar-divider.png │ ├── js │ ├── GoalConfiguration │ │ ├── GoalConfiguration.js │ │ ├── GoogleFonts.js │ │ └── Preview.js │ ├── attentionhub.js │ ├── mcGitHubbub.js │ ├── site.js │ ├── site.min.js │ └── streamhub.js │ └── lib │ └── bootstrap │ ├── .bower.json │ ├── LICENSE │ └── dist │ ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ └── bootstrap.min.css.map │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ └── js │ ├── bootstrap.js │ └── npm.js ├── Fritz.Twitch ├── AspNetExtensions.cs ├── AssemblyInfo.cs ├── ChatClient.cs ├── ChatConnectedEventArgs.cs ├── ChatUserJoinedEventArgs.cs ├── ConfigurationSettings.cs ├── Fritz.Twitch.csproj ├── NewFollowersEventArgs.cs ├── NewMessageEventArgs.cs ├── NewViewersEventArgs.cs ├── Proxy.cs ├── PubSub │ ├── ChannelRedemption.cs │ ├── Proxy.cs │ ├── PubSubListenMessage.cs │ ├── PubSubMessage.cs │ └── UnhandledPubSubMessageException.cs ├── StreamData.cs └── TwitchTopic.cs ├── LICENSE ├── README.md ├── StreamAnalytics ├── .gitignore ├── Function1.cs ├── NewFollower.cs ├── StreamAnalytics.csproj └── host.json ├── Test ├── AutoMoqDataAttribute.cs ├── AutoMoqStreamServiceWithNameAndCountAttribute.cs ├── AutoMoqStreamServiceWithNameAndCountCustomization.cs ├── BaseFixture.cs ├── Chatbot │ ├── ChatBotTests.cs │ ├── ShoutoutCommandFixture.cs │ └── WhenChatMessageSent.cs ├── GlobalSuppressions.cs ├── ImageCommand │ └── MessageTest.cs ├── Services │ ├── StreamService │ │ ├── FollowerCountByService.cs │ │ ├── Sum.cs │ │ └── ViewerCountByService.cs │ └── TwitchService │ │ └── OnNewFollowers.cs ├── Startup │ └── ConfigureServicesTests.cs ├── TagHelpers │ └── SignalrTagHelper │ │ ├── IdentifyClientLibrary.cs │ │ └── Process.cs ├── Test.csproj ├── Twitch │ ├── Chat │ │ └── OnConnect.cs │ └── Proxy │ │ ├── GetFollowerCount.cs │ │ └── GetStreamData.cs └── XunitLogger.cs ├── build.cmd ├── docker-compose.ci.build.yml ├── docker-compose.dcproj ├── docker-compose.override.yml ├── docker-compose.yml ├── docs └── images │ └── FollowerGoalSample.PNG ├── global.json └── remove.cmd /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | docker-compose.yml 8 | docker-compose.*.yml 9 | */bin 10 | */obj 11 | !obj/Docker/publish/* 12 | !obj/Docker/empty/ 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = crlf 5 | indent_style = tab 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.cs] 11 | csharp_style_var_for_built_in_types = true:error 12 | csharp_style_var_when_type_is_apparent = true:error 13 | csharp_style_var_elsewhere = true:error 14 | csharp_prefer_braces = true:warning 15 | 16 | 17 | 18 | [*.js] 19 | 20 | # ASP0000: Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices' 21 | dotnet_diagnostic.ASP0000.severity = error 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: Microsoft.AspNetCore.App 11 | versions: 12 | - "> 2.1.6" 13 | - dependency-name: AutoFixture.Xunit2 14 | versions: 15 | - 4.15.0 16 | - 4.16.0 17 | - dependency-name: AutoFixture.AutoMoq 18 | versions: 19 | - 4.15.0 20 | - 4.16.0 21 | - dependency-name: AutoFixture 22 | versions: 23 | - 4.15.0 24 | - 4.16.0 25 | - dependency-name: System.IO.Abstractions 26 | versions: 27 | - 13.2.10 28 | - 13.2.11 29 | - 13.2.15 30 | - 13.2.17 31 | - 13.2.20 32 | - 13.2.23 33 | - 13.2.24 34 | - 13.2.25 35 | - 13.2.28 36 | - 13.2.9 37 | - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack 38 | versions: 39 | - 5.0.2 40 | - 5.0.3 41 | - 5.0.4 42 | - dependency-name: Microsoft.NET.Test.Sdk 43 | versions: 44 | - 16.8.3 45 | - 16.9.1 46 | - dependency-name: Octokit 47 | versions: 48 | - 0.48.0 49 | - 0.49.0 50 | - dependency-name: Moq 51 | versions: 52 | - 4.16.0 53 | -------------------------------------------------------------------------------- /ConsoleChatbot/ConsoleChatService.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace ConsoleChatbot 6 | { 7 | public class ConsoleChatService : IChatService 8 | { 9 | public string Name => "Console"; 10 | 11 | public bool IsAuthenticated => true; 12 | 13 | public string BotUserName => "ConsoleBot"; 14 | 15 | public event EventHandler ChatMessage; 16 | public event EventHandler UserJoined; 17 | public event EventHandler UserLeft; 18 | 19 | public void ConsoleMessageReceived(string message) 20 | { 21 | 22 | ChatMessage.Invoke(this, new ChatMessageEventArgs 23 | { 24 | Message = message, 25 | UserName = "ConsoleUser" 26 | }); 27 | 28 | } 29 | 30 | public Task BanUserAsync(string userName) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public Task SendMessageAsync(string message) 36 | { 37 | Console.Out.WriteLine(message); 38 | return Task.FromResult(true); 39 | } 40 | 41 | public Task SendWhisperAsync(string userName, string message) 42 | { 43 | Console.Out.WriteLine($"<<{userName}>> {message}"); 44 | return Task.FromResult(true); 45 | } 46 | 47 | public Task TimeoutUserAsync(string userName, TimeSpan time) 48 | { 49 | throw new NotImplementedException(); 50 | } 51 | 52 | public Task UnbanUserAsync(string userName) 53 | { 54 | throw new NotImplementedException(); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /ConsoleChatbot/ConsoleChatbot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | 7 | 8 | 9 | 10 | PreserveNewest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ConsoleChatbot/Program.cs: -------------------------------------------------------------------------------- 1 | using Fritz.Chatbot.Commands; 2 | using Fritz.StreamLib.Core; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Logging.Abstractions; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using Fritz.Chatbot; 11 | 12 | namespace ConsoleChatbot 13 | { 14 | class Program 15 | { 16 | 17 | static void Main(string[] args) 18 | { 19 | 20 | Console.WriteLine("Interactive console for testing FritzBot"); 21 | Console.WriteLine("Enter ZZ to exit console"); 22 | 23 | var result = ""; 24 | var consoleChat = new ConsoleChatService(); 25 | var theBot = CreateFritzBot(consoleChat); 26 | theBot.StartAsync(new System.Threading.CancellationToken()); 27 | while (result != "ZZ") { 28 | 29 | result = Console.ReadLine(); 30 | consoleChat.ConsoleMessageReceived(result); 31 | 32 | } 33 | 34 | } 35 | 36 | private static FritzBot CreateFritzBot(IChatService chatService) 37 | { 38 | 39 | var serviceCollection = new ServiceCollection(); 40 | serviceCollection.AddSingleton(chatService) 41 | .AddLogging(); 42 | 43 | var config = new ConfigurationBuilder() 44 | .AddJsonFile("appsettings.json", true) 45 | .AddUserSecrets("78c713a0-80e0-4e16-956a-33cf16f08a02") // Same as Fritz.StreamTools 46 | .Build(); 47 | serviceCollection.AddSingleton(config); 48 | 49 | serviceCollection.AddHttpClient("ShoutoutCommand", c => 50 | { 51 | c.DefaultRequestHeaders.Add("client-id", config["StreamServices:Twitch:ClientId"]); 52 | }); 53 | 54 | FritzBot.RegisterCommands(serviceCollection); 55 | 56 | var loggerService = LoggerFactory.Create(configure => 57 | configure.AddSimpleConsole(options => 58 | { 59 | options.IncludeScopes = true; 60 | }) 61 | .SetMinimumLevel(LogLevel.Information) 62 | ); 63 | var svcProvider = serviceCollection.BuildServiceProvider(); 64 | var loggerFactory = svcProvider.GetService(); 65 | 66 | return new FritzBot(config, svcProvider, loggerFactory); 67 | 68 | } 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /ConsoleChatbot/StubServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleChatbot 5 | { 6 | public class StubServiceProvider : IServiceProvider 7 | { 8 | 9 | private readonly Dictionary _Services = new Dictionary(); 10 | 11 | public object GetService(Type serviceType) 12 | { 13 | 14 | var result = _Services[serviceType]; 15 | if (result is Type) { 16 | return Activator.CreateInstance(result as Type); 17 | } 18 | 19 | return result; 20 | 21 | } 22 | 23 | internal void Add(T service) 24 | { 25 | 26 | _Services.Add(typeof(T), service); 27 | 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Fritz.Chatbot/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Test")] 4 | -------------------------------------------------------------------------------- /Fritz.Chatbot/AttentionHub.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Microsoft.AspNetCore.SignalR; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Fritz.StreamTools.Hubs 9 | { 10 | public interface IAttentionHubClient 11 | { 12 | 13 | // Cheer 200 parithon 12/18/2018 14 | // Cheer 500 pharewings 12/18/2018 15 | Task AlertFritz(); 16 | Task ClientConnected(string connectionId); 17 | Task SummonScott(); 18 | 19 | Task PlaySoundEffect(string fileName); 20 | 21 | Task NotifyChannelPoints(ChannelPointRedemption redemption); 22 | 23 | } 24 | 25 | public class AttentionHub : Hub, IAttentionClient 26 | { 27 | public override Task OnConnectedAsync() 28 | { 29 | return this.Clients.Others.ClientConnected(this.Context.ConnectionId); 30 | } 31 | 32 | public Task AlertFritz() 33 | { 34 | return this.Clients.Others.AlertFritz(); 35 | } 36 | 37 | public Task SummonScott() 38 | { 39 | 40 | return this.Clients.Others.SummonScott(); 41 | 42 | } 43 | 44 | public Task PlaySoundEffect(string fileName) 45 | { 46 | 47 | return this.Clients.Others.PlaySoundEffect(fileName); 48 | 49 | } 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Fritz.Chatbot/ChatUserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Chatbot 4 | { 5 | public class ChatUserInfo 6 | { 7 | public DateTime LastCommandTime { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/AttentionCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Fritz.StreamLib.Core; 5 | using Fritz.StreamTools.Hubs; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.AspNetCore.SignalR.Client; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace Fritz.Chatbot.Commands 13 | { 14 | public class AttentionCommand : IBasicCommand 15 | { 16 | private readonly IConfiguration Configuration; 17 | 18 | public ILogger Logger { get; } 19 | public IHubContext HubContext { get; } 20 | 21 | public AttentionCommand(IConfiguration configuration, IHubContext hubContext, ILoggerFactory loggerFactory) 22 | { 23 | this.Configuration = configuration; 24 | this.Logger = loggerFactory.CreateLogger("AttentionCommand"); 25 | 26 | this.HubContext = hubContext; 27 | 28 | //var thisUri = new Uri(configuration["FritzBot:ServerUrl"], UriKind.Absolute); 29 | //var attentionUri = new Uri(thisUri, "attentionhub"); 30 | 31 | //Logger.LogTrace($"Connecting AttentionCommand to: {attentionUri}"); 32 | 33 | //this.Client = new HubConnectionBuilder().WithUrl(attentionUri.ToString()).Build(); 34 | 35 | } 36 | 37 | //protected HubConnection Client { get; } 38 | 39 | public string Trigger => "attention"; 40 | 41 | public string Description => "Play audio queue to divert attention to chat"; 42 | 43 | #if DEBUG 44 | public TimeSpan? Cooldown => TimeSpan.FromSeconds(10); 45 | #else 46 | public TimeSpan? Cooldown => TimeSpan.Parse(Configuration["FritzBot:AttentionCommand:Cooldown"]); 47 | #endif 48 | 49 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 50 | { 51 | 52 | await this.HubContext.Clients.All.AlertFritz(); 53 | 54 | var attentionText = Configuration["FritzBot:AttentionCommand:TemplateText"]; 55 | 56 | await chatService.SendMessageAsync(string.Format(attentionText, userName)); 57 | } 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/EchoCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fritz.StreamLib.Core; 7 | 8 | namespace Fritz.Chatbot.Commands 9 | { 10 | public class EchoCommand : IBasicCommand 11 | { 12 | public string Trigger => "echo"; 13 | public string Description => "Repeat the text that was requested by the echo command"; 14 | public TimeSpan? Cooldown => null; 15 | 16 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 17 | { 18 | if (rhs.IsEmpty) 19 | return; 20 | await chatService.SendWhisperAsync(userName, "Echo reply: " + rhs); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/GitHubCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fritz.StreamLib.Core; 7 | 8 | namespace Fritz.Chatbot.Commands 9 | { 10 | public class GitHubCommand : IBasicCommand 11 | { 12 | public string Trigger => "github"; 13 | public string Description => "Outputs the URL of Jeff's Github Repository"; 14 | public TimeSpan? Cooldown => null; 15 | 16 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 17 | { 18 | await chatService.SendMessageAsync("Jeff's Github repository can by found here: https://github.com/csharpfritz/"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fritz.StreamLib.Core; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Fritz.Chatbot.Commands 10 | { 11 | public class HelpCommand : IBasicCommand 12 | { 13 | public string Trigger => "help"; 14 | public string Description => "Get information about the functionality available on this channel"; 15 | public TimeSpan? Cooldown => null; 16 | 17 | private readonly IServiceProvider _serviceProvider; 18 | 19 | public HelpCommand(IServiceProvider serviceProvider) 20 | { 21 | _serviceProvider = serviceProvider; 22 | } 23 | 24 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 25 | { 26 | var commands = _serviceProvider.GetServices(); 27 | var textCommandArray = TextCommand._Commands.Select(kv => "!" + kv.Key.ToLower()); 28 | var soundFxCommands = SoundFxCommand.Effects.Select(kv => "!" + kv.Key.ToLower()); 29 | 30 | if (rhs.IsEmpty) 31 | { 32 | 33 | var availableCommandArray = commands.Where(c => !string.IsNullOrEmpty(c.Trigger)).Select(c => $"!{c.Trigger.ToLower()}"); 34 | 35 | var availableCommands = string.Join(' ', availableCommandArray.Union(textCommandArray).Union(soundFxCommands).OrderBy(s => s)); 36 | 37 | await chatService.SendMessageAsync($"Supported commands: {availableCommands}"); 38 | return; 39 | } 40 | 41 | var cmd = commands.FirstOrDefault(c => rhs.Span.Equals(c.Trigger.AsSpan(), StringComparison.OrdinalIgnoreCase)); 42 | if (cmd == null) 43 | { 44 | 45 | if (textCommandArray.Contains("!" + rhs.Span.ToString())) 46 | { 47 | await chatService.SendMessageAsync($"{rhs}: A helpful text message"); 48 | } 49 | else if (soundFxCommands.Contains("!" + rhs.Span.ToString())) 50 | { 51 | await chatService.SendMessageAsync($"{rhs}: A fun sound effect"); 52 | } 53 | else 54 | { 55 | await chatService.SendMessageAsync("Unknown command to provide help with."); 56 | } 57 | return; 58 | } 59 | 60 | await chatService.SendMessageAsync($"{rhs}: {cmd.Description}"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/HereCommand.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fritz.Chatbot.Commands 8 | { 9 | public class HereCommand : IBasicCommand 10 | { 11 | public string Trigger { get; } = "here"; 12 | public string Description { get; } = "A do-nothing command that will not crash the bot when we run a giveaway"; 13 | public TimeSpan? Cooldown { get; } = null; 14 | 15 | public Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 16 | { 17 | // do nothing 18 | return Task.CompletedTask; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/HypeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fritz.StreamLib.Core; 7 | using Microsoft.Extensions.Configuration; 8 | 9 | namespace Fritz.Chatbot.Commands 10 | { 11 | public class HypeCommand : IBasicCommand 12 | { 13 | 14 | private readonly IConfiguration Configuration; 15 | 16 | // TurricanDE Cheered 500 bits on November 1, 2018 17 | // MrDemonWolf Cheered 100 bits on November 1, 2018 18 | // Pharewings Cheered 100 bits on November 1, 2018 19 | 20 | public HypeCommand(IConfiguration configuration) 21 | { 22 | this.Configuration = configuration; 23 | } 24 | 25 | 26 | public string Trigger => "hype"; 27 | 28 | public string Description => "Let's hype up the channel with some cool emotes"; 29 | 30 | public TimeSpan? Cooldown => TimeSpan.FromSeconds(5); 31 | 32 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 33 | { 34 | 35 | var hypeText = Configuration["FritzBot:HypeCommand:TemplateText"]; 36 | var repeatCount = int.Parse(Configuration["FritzBot:HypeCommand:RepeatCount"]); 37 | 38 | 39 | var sb = new StringBuilder(); 40 | for (var i=0; i "HyperLink"; 11 | public string Description => ""; 12 | public int Order => 2; 13 | public bool Final => false; 14 | 15 | public TimeSpan? Cooldown => null; 16 | 17 | private const string HttpCheckPattern = @"http(s)?:?"; 18 | private static readonly Regex reCheck = new Regex(HttpCheckPattern, RegexOptions.IgnoreCase); 19 | 20 | public bool CanExecute(string userName, string fullCommandText) 21 | { 22 | 23 | // Match the regular expression pattern against a text string. 24 | return !ImageDescriptorCommand._InstagramCheck.IsMatch(fullCommandText) && reCheck.IsMatch(fullCommandText); 25 | 26 | } 27 | 28 | public Task Execute(IChatService chatService, string userName, string fullCommandText) 29 | { 30 | 31 | // Use HttpClient to request URL 32 | 33 | // Grab HTML title from URL 34 | 35 | // ??Moderate as needed?? 36 | 37 | // Output title to ChatService 38 | 39 | return Task.CompletedTask; 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/IBasicCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Fritz.StreamLib.Core; 4 | 5 | namespace Fritz.Chatbot.Commands 6 | { 7 | /// 8 | /// Simple keyword based command interface 9 | /// 10 | public interface IBasicCommand 11 | { 12 | /// 13 | /// The command keyword 14 | /// 15 | string Trigger { get; } 16 | 17 | /// 18 | /// Description of the command (used by !help) 19 | /// 20 | string Description { get; } 21 | 22 | /// 23 | /// Cooldown for this command, or null 24 | /// 25 | /// 26 | TimeSpan? Cooldown { get; } 27 | 28 | /// 29 | /// Execute the command. 30 | /// 31 | /// The chatservice to use 32 | /// User that invoked the command 33 | /// The remaining text after the trigger keyword 34 | Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs); 35 | } 36 | 37 | public interface IBasicCommand2 : IBasicCommand { 38 | 39 | /// 40 | /// Execute the command. 41 | /// 42 | /// The chatservice to use 43 | /// User that invoked the command 44 | /// Badges carried by the user 45 | /// The remaining text after the trigger keyword 46 | Task Execute(IChatService chatService, string userName, bool isModerator, bool isVip, bool isBroadcaster, ReadOnlyMemory rhs); 47 | 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/IExtendedCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Fritz.StreamLib.Core; 4 | 5 | namespace Fritz.Chatbot.Commands 6 | { 7 | public interface IExtendedCommand 8 | { 9 | string Name { get; } 10 | string Description { get; } 11 | 12 | /// 13 | /// Order by wich CanExecute are called, the higher the later 14 | /// 15 | int Order { get; } 16 | 17 | /// 18 | /// If true, don't run other commands after this one 19 | /// 20 | bool Final { get; } 21 | 22 | /// 23 | /// Cooldown for this command, or null 24 | /// 25 | /// 26 | TimeSpan? Cooldown { get; } 27 | 28 | bool CanExecute(string userName, string fullCommandText); 29 | Task Execute(IChatService chatService, string userName, string fullCommandText); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/PingCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Fritz.StreamLib.Core; 4 | 5 | namespace Fritz.Chatbot.Commands 6 | { 7 | public class PingCommand : IBasicCommand 8 | { 9 | public string Trigger => "ping"; 10 | public string Description => "Receive a quick acknowledgement from the bot through a whisper"; 11 | public TimeSpan? Cooldown => null; 12 | 13 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 14 | { 15 | await chatService.SendWhisperAsync(userName, "pong"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/ProjectCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Fritz.StreamLib.Core; 4 | using Fritz.StreamTools.Hubs; 5 | using Microsoft.AspNetCore.SignalR; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace Fritz.Chatbot.Commands 9 | { 10 | public class ProjectCommand : IBasicCommand2 11 | { 12 | private readonly IConfiguration Configuration; 13 | private readonly IHubContext _HubContext; 14 | 15 | public ProjectCommand(IConfiguration configuration, IHubContext hubContext) 16 | { 17 | this.Configuration = configuration; 18 | _HubContext = hubContext; 19 | } 20 | 21 | public string Trigger => "project"; 22 | 23 | public string Description => "Return the name of the project that is currently being worked on on stream"; 24 | 25 | public TimeSpan? Cooldown => TimeSpan.FromSeconds(30); 26 | 27 | public static string CurrentProject { get; private set; } 28 | 29 | public async Task Execute(IChatService chatService, string userName, bool isModerator, bool isVip, bool isBroadcaster, ReadOnlyMemory rhs) 30 | { 31 | if ((isModerator || isBroadcaster) && !rhs.IsEmpty) 32 | { 33 | CurrentProject = rhs.ToString(); 34 | await _HubContext.Clients.All.SendAsync("project_update", CurrentProject); 35 | } 36 | 37 | var projectText = Configuration["FritzBot:ProjectCommand:TemplateText"]; 38 | 39 | var project = CurrentProject; 40 | if (CurrentProject == null) 41 | project = Configuration["FritzBot:ProjectCommand:DefaultText"]; 42 | else 43 | project = CurrentProject; 44 | 45 | await chatService.SendMessageAsync(string.Format(projectText, userName, project)); 46 | } 47 | 48 | public Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/QnAMakerResult.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Fritz.Chatbot.Commands 5 | { 6 | internal class QnAMakerResult 7 | { 8 | 9 | 10 | // public string Answer { get; set; } 11 | 12 | // /// 13 | // /// The score in range [0, 100] corresponding to the top answer found in the QnA Service. 14 | // /// 15 | // [JsonProperty(PropertyName = "score")] 16 | // public double Score { get; set; } 17 | 18 | [JsonProperty("answers")] 19 | public QnAMakerAnswer[] Answers { get; set; } 20 | 21 | 22 | 23 | } 24 | 25 | public class QnAMakerAnswer 26 | { 27 | [JsonProperty("questions")] 28 | public string[] Questions { get; set; } 29 | 30 | [JsonProperty("answer")] 31 | public string Answer { get; set; } 32 | 33 | [JsonProperty("score")] 34 | public double Score { get; set; } 35 | 36 | [JsonProperty("id")] 37 | public long Id { get; set; } 38 | 39 | [JsonProperty("source")] 40 | public Uri Source { get; set; } 41 | 42 | [JsonProperty("metadata")] 43 | public object[] Metadata { get; set; } 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/QuotesCommand.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 | using Fritz.StreamLib.Core; 8 | 9 | namespace Fritz.Chatbot.Commands 10 | { 11 | public class QuotesCommand : IBasicCommand 12 | { 13 | 14 | const string QUOTES_FILENAME = "SampleQuotes.txt"; 15 | internal string[] _quotes; 16 | private readonly Random _random = new Random(); 17 | public TimeSpan? Cooldown => null; 18 | 19 | public QuotesCommand() 20 | { 21 | 22 | if (File.Exists(QUOTES_FILENAME)) 23 | { 24 | _quotes = File.ReadLines(QUOTES_FILENAME).ToArray(); 25 | } 26 | 27 | } 28 | 29 | public string Trigger => "quote"; 30 | 31 | public string Description => "Return a random quote to the chat room"; 32 | 33 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 34 | { 35 | 36 | if (_quotes == null) return; 37 | 38 | await chatService.SendMessageAsync(_quotes[_random.Next(_quotes.Length)]); 39 | 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/ResetHatAiCommand.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace Fritz.Chatbot.Commands 6 | { 7 | public class ResetHatAiCommand : IBasicCommand2 8 | { 9 | 10 | public string Trigger => "resethatai"; 11 | public string Description => "Reset the Hat detection AI after adding new hats"; 12 | public TimeSpan? Cooldown { get; } = TimeSpan.FromSeconds(30); 13 | 14 | public async Task Execute(IChatService chatService, string userName, bool isModerator, bool isVip, bool isBroadcaster, ReadOnlyMemory rhs) 15 | { 16 | 17 | if (!(isModerator || isBroadcaster)) return; 18 | 19 | //PredictHatCommand.IterationName = string.Empty; 20 | await chatService.SendMessageAsync("Reset the AI iteration and will detect the latest for Hat identification next time !hat is called"); 21 | 22 | } 23 | 24 | public Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 25 | { 26 | return Task.CompletedTask; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/SentimentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamLib.Core; 6 | 7 | namespace Fritz.Chatbot.Commands 8 | { 9 | public class SentimentCommand : IBasicCommand 10 | { 11 | public string Trigger => "sentiment"; 12 | public string Description => "Information about the sentiment analysis features of the chat room"; 13 | public TimeSpan? Cooldown => TimeSpan.FromSeconds(30); 14 | 15 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 16 | { 17 | 18 | var sb = new StringBuilder(); 19 | sb.Append($"The sentiment over the last minute is {SentimentSink.Sentiment1Minute.ToString("00.0%")} percent positive "); 20 | sb.Append((SentimentSink.Sentiment1Minute < SentimentSink.Sentiment5Minute) ? "and trending upward (up arrow) " : "and trending downward (down arrow) "); 21 | sb.Append("compared to the last five minutes "); 22 | sb.Append($"The most recent messages were {((SentimentSink.SentimentInstant >=0.5) ? "positive" : "negative")} (smiley face)"); 23 | 24 | await chatService.SendMessageAsync(sb.ToString()); 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/SentimentSink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Fritz.Chatbot.Commands 6 | { 7 | 8 | public class SentimentSink 9 | { 10 | 11 | public static Queue RecentChatMessages { get; } = new Queue(); 12 | 13 | public static double SentimentInstant { get; set; } 14 | 15 | public static double Sentiment1Minute { get; set; } 16 | 17 | public static double Sentiment5Minute { get; set; } 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/ShoutoutCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamLib.Core; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Fritz.Chatbot.Commands 9 | { 10 | public class ShoutoutCommand : IBasicCommand2 11 | { 12 | private readonly HttpClient _HttpClient; 13 | private readonly ILogger _Logger; 14 | 15 | public ShoutoutCommand(IHttpClientFactory httpClientFactory, ILoggerFactory logger, TwitchTokenConfig twitchConfig = null) 16 | { 17 | 18 | _Logger = logger.CreateLogger(nameof(ShoutoutCommand)); 19 | 20 | if (!string.IsNullOrEmpty(TwitchTokenConfig.Tokens?.access_token)) 21 | { 22 | _HttpClient = httpClientFactory.CreateClient("ShoutoutCommand"); 23 | _HttpClient.BaseAddress = new Uri("https://api.twitch.tv/helix/users"); 24 | _HttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {TwitchTokenConfig.Tokens.access_token}"); 25 | } else { 26 | _Logger.LogError("Unable to create HttpClient for Twitch with Bearer token"); 27 | } 28 | } 29 | 30 | 31 | public string Trigger => "so"; 32 | 33 | public string Description => "Issue a shout out to another streamer, promoting them on stream"; 34 | 35 | public TimeSpan? Cooldown => TimeSpan.FromSeconds(5); 36 | 37 | public async Task Execute(IChatService chatService, string userName, bool isModerator, bool isVip, bool isBroadcaster, ReadOnlyMemory rhs) 38 | { 39 | 40 | if (!(isModerator || isVip || isBroadcaster)) return; 41 | 42 | var rhsTest = rhs.ToString(); 43 | if (rhsTest.StartsWith("@")) rhsTest = rhsTest.Substring(1); 44 | if (rhsTest.StartsWith("http")) return; 45 | if (rhsTest.Contains(" ")) return; 46 | 47 | rhsTest = WebUtility.UrlEncode(rhsTest); 48 | var result = await _HttpClient.GetAsync($"?login={rhsTest}"); 49 | if (result.StatusCode == HttpStatusCode.Unauthorized) { 50 | _Logger.LogError("Request to Twitch endpoint was unauthorized -- consider rotating keys"); 51 | return; 52 | } 53 | else if (result.StatusCode != HttpStatusCode.OK) 54 | { 55 | _Logger.LogWarning($"Unable to verify Shoutout for {rhsTest}"); 56 | return; 57 | } 58 | 59 | await chatService.SendMessageAsync($"Please follow @{rhsTest} at: https://twitch.tv/{rhsTest}"); 60 | 61 | } 62 | 63 | public Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/SkeetCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamLib.Core; 6 | 7 | namespace Fritz.Chatbot.Commands 8 | { 9 | public class SkeetCommand : IBasicCommand 10 | { 11 | const string QUOTES_FILENAME = "SkeetQuotes.txt"; 12 | internal string[] _quotes; 13 | private readonly Random _random = new Random(); 14 | public TimeSpan? Cooldown => null; 15 | 16 | public SkeetCommand() 17 | { 18 | if (File.Exists(QUOTES_FILENAME)) 19 | { 20 | _quotes = File.ReadLines(QUOTES_FILENAME).ToArray(); 21 | } 22 | } 23 | 24 | public SkeetCommand(string[] quotes) 25 | { 26 | _quotes = quotes; 27 | } 28 | 29 | public string Trigger => "jonskeet"; 30 | public string Description => "Return a random quote about Jon Skeet to the chat room"; 31 | 32 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 33 | { 34 | if (_quotes == null) return; 35 | await chatService.SendMessageAsync(_quotes[_random.Next(_quotes.Length)]); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/TextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamLib.Core; 6 | using Microsoft.Extensions.Configuration; 7 | using System.Linq; 8 | using System.Xml; 9 | using System.Diagnostics; 10 | 11 | namespace Fritz.Chatbot.Commands 12 | { 13 | 14 | public class TextCommand : IExtendedCommand 15 | { 16 | 17 | // Cheer 143 cpayette 03/3/19 18 | // Cheer 142 cpayette 2/06/19 19 | // Cheer 150 ramblinggeek 2/06/19 20 | 21 | public TextCommand(IConfiguration configuration) 22 | { 23 | this.Configuration = configuration; 24 | 25 | var children = Configuration.GetSection("FritzBot:TextCommand").GetChildren().ToArray(); 26 | foreach (var cmd in children) 27 | { 28 | _Commands.Add(cmd.GetValue("command"), cmd.GetValue("response")); 29 | } 30 | 31 | } 32 | 33 | public string Name => "TextCommand"; 34 | public string Description => "Return a simple line of text"; 35 | public int Order => 0; 36 | public bool Final => true; 37 | public TimeSpan? Cooldown => TimeSpan.FromSeconds(5); 38 | 39 | public IConfiguration Configuration { get; } 40 | 41 | internal static readonly Dictionary _Commands = new Dictionary(); 42 | 43 | internal static bool IsCommand(string commandText) 44 | { 45 | 46 | return _Commands.ContainsKey(commandText); 47 | 48 | } 49 | 50 | public bool CanExecute(string userName, string fullCommandText) 51 | { 52 | 53 | if (!fullCommandText.StartsWith("!")) return false; 54 | var cmd = fullCommandText.Substring(1).ToLowerInvariant(); 55 | return _Commands.ContainsKey(cmd); 56 | 57 | } 58 | 59 | public Task Execute(IChatService chatService, string userName, string fullCommandText) 60 | { 61 | 62 | var cmd = fullCommandText.Substring(1).ToLowerInvariant(); 63 | 64 | if (cmd == "lurk") 65 | return chatService.SendMessageAsync($"@{userName} {_Commands[cmd]}"); 66 | 67 | if (!_Commands[cmd].Contains('\n')) 68 | { 69 | return chatService.SendMessageAsync($"@{userName} - {_Commands[cmd]}"); 70 | } else { 71 | var messages = _Commands[cmd].Split('\n'); 72 | 73 | Task firstTask = null; 74 | Task previousTask = null; 75 | foreach (var msg in messages) 76 | { 77 | if (firstTask == null) 78 | { 79 | firstTask = chatService.SendMessageAsync(msg); 80 | previousTask = firstTask; 81 | } 82 | else 83 | { 84 | var thisTask = chatService.SendMessageAsync(msg); 85 | previousTask.ContinueWith((o) => thisTask); 86 | previousTask = thisTask; 87 | } 88 | } 89 | return firstTask; 90 | } 91 | 92 | } 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Commands/UptimeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamLib.Core; 6 | 7 | namespace Fritz.Chatbot.Commands 8 | { 9 | public class UptimeCommand : IBasicCommand 10 | { 11 | public string Trigger => "uptime"; 12 | public string Description => "Report how long the stream has been on the air"; 13 | public TimeSpan? Cooldown => TimeSpan.FromMinutes(1); 14 | 15 | public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory rhs) 16 | { 17 | 18 | if (!(chatService is IStreamService svc)) 19 | { 20 | return; 21 | } 22 | 23 | var uptime = await svc.Uptime(); 24 | if (uptime.HasValue) 25 | { 26 | await chatService.SendMessageAsync($"The stream has been up for {uptime.Value.ToString(@"hh\:mm\:ss")}"); 27 | } 28 | else 29 | { 30 | await chatService.SendMessageAsync("Stream is offline"); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Fritz.Chatbot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 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 | PreserveNewest 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Helpers/LogExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Fritz.ChatBot.Helpers 5 | { 6 | public static class LogExtensions 7 | { 8 | public static bool LogAndSwallow(this ILogger logger, string action, Exception e) 9 | { 10 | logger.LogWarning($"Exception while {action}: '{e.Message}'"); 11 | return true; 12 | 13 | } 14 | 15 | public static bool LogAndRethrow(ILogger logger, string action, Exception e) 16 | { 17 | logger.LogWarning($"Exception while {action}: '{e.Message}'"); 18 | return false; 19 | 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Helpers/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Fritz.Chatbot.Helpers 7 | { 8 | public static class StringExtensions 9 | { 10 | 11 | public static string HandleMarkdownLinks(this string value) 12 | { 13 | 14 | value = Markdig.Markdown.ToPlainText(value); 15 | 16 | value = value.Replace("\n", " "); 17 | 18 | return value; 19 | } 20 | 21 | public static bool IsValidRegularExpression(this string value) 22 | { 23 | if (string.IsNullOrEmpty(value)) 24 | { 25 | return false; 26 | } 27 | 28 | try 29 | { 30 | Regex.IsMatch("", value); 31 | } 32 | catch (ArgumentException) 33 | { 34 | return false; 35 | } 36 | 37 | return true; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Fritz.Chatbot/ITrainHat.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Fritz.Chatbot 4 | { 5 | 6 | public interface ITrainHat 7 | { 8 | 9 | void StartTraining(int? count); 10 | 11 | Task AddScreenshot(); 12 | 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Fritz.Chatbot/Models/VisionDescription.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | 3 | namespace Fritz.Chatbot.Models 4 | { 5 | 6 | public class VisionDescription 7 | { 8 | public Category[] categories { get; set; } 9 | public Description description { get; set; } 10 | public Adult adult { get; set; } 11 | public Color color { get; set; } 12 | public string requestId { get; set; } 13 | public Metadata metadata { get; set; } 14 | } 15 | 16 | public class Description 17 | { 18 | public string[] tags { get; set; } 19 | public Caption[] captions { get; set; } 20 | } 21 | 22 | public class Caption 23 | { 24 | public string text { get; set; } 25 | public float confidence { get; set; } 26 | } 27 | 28 | public class Adult 29 | { 30 | public bool isAdultContent { get; set; } 31 | public float adultScore { get; set; } 32 | public bool isRacyContent { get; set; } 33 | public float racyScore { get; set; } 34 | } 35 | 36 | public class Color 37 | { 38 | public string dominantColorForeground { get; set; } 39 | public string dominantColorBackground { get; set; } 40 | public string[] dominantColors { get; set; } 41 | public string accentColor { get; set; } 42 | public bool isBwImg { get; set; } 43 | } 44 | 45 | public class Metadata 46 | { 47 | public int height { get; set; } 48 | public int width { get; set; } 49 | public string format { get; set; } 50 | } 51 | 52 | public class Category 53 | { 54 | public string name { get; set; } 55 | public float score { get; set; } 56 | public Detail detail { get; set; } 57 | } 58 | 59 | public class Detail 60 | { 61 | public object[] landmarks { get; set; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Fritz.Chatbot/ObsHub.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Microsoft.AspNetCore.SignalR; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Fritz.StreamTools.Hubs 11 | { 12 | 13 | public class ObsHub : Hub 14 | { 15 | 16 | public static int ConnectedCount = 0; 17 | public static List _ConnectionIds = new List(); 18 | 19 | public override async Task OnConnectedAsync() 20 | { 21 | 22 | _ConnectionIds.Add(Context.ConnectionId); 23 | 24 | ConnectedCount++; 25 | 26 | await base.OnConnectedAsync(); 27 | 28 | } 29 | 30 | public override async Task OnDisconnectedAsync(Exception exception) 31 | { 32 | ConnectedCount--; 33 | 34 | await base.OnDisconnectedAsync(exception); 35 | } 36 | 37 | public async Task PostScreenshot(IAsyncEnumerable stream) 38 | { 39 | 40 | var sb = new StringBuilder(); 41 | await foreach (var item in stream) 42 | { 43 | sb.Append(item); 44 | } 45 | 46 | Debug.WriteLine(sb.Length); 47 | var cleanString = sb.ToString().Replace("data:image/webp;base64,", ""); 48 | 49 | var bytes = Convert.FromBase64String(cleanString); 50 | 51 | ScreenshotSink.Instance.OnScreenshotReceived(bytes); 52 | 53 | } 54 | 55 | } 56 | 57 | public interface IServerTakeScreenshot 58 | { 59 | Task TakeScreenshot(); 60 | } 61 | 62 | public class ScreenshotReceivedEventArgs : EventArgs 63 | { 64 | 65 | public Stream Screenshot { get; set; } 66 | 67 | } 68 | 69 | public class ScreenshotSink 70 | { 71 | 72 | public static readonly ScreenshotSink Instance = new ScreenshotSink(); 73 | 74 | private ScreenshotSink() { } 75 | 76 | public event EventHandler ScreenshotReceived; 77 | 78 | public void OnScreenshotReceived(byte[] imageData) 79 | { 80 | 81 | var args = new ScreenshotReceivedEventArgs() { Screenshot = new MemoryStream(imageData) }; 82 | ScreenshotReceived?.Invoke(null, args); 83 | 84 | } 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Fritz.Chatbot/SkeetQuotes.txt: -------------------------------------------------------------------------------- 1 | Jon Skeet is immutable. If something's going to change, it's going to have to be the rest of the universe. 2 | Jon Skeet can divide by zero. 3 | When Jon Skeet's code fails to compile the compiler apologises. 4 | Jon Skeet has already written a book about C# 5.0. It’s currently sealed up. In three years, Anders Hejlsberg is going to open the book to see if the language design team got it right. 5 | Jon Skeet can stop an infinite loop just by thinking about it 6 | Jon Skeet doesn't need a debugger, he just stares down the bug until the code confesses 7 | -------------------------------------------------------------------------------- /Fritz.Chatbot/VisionApiReadme.txt: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | stream of messages.. 5 | parse the message - find if it has a url - w/ suffix .png. jpg, jpeg, gif 6 | 7 | new code inside your bot to find image URL 8 | 9 | see code in :: MessageTest.cs 10 | -------------- 11 | 12 | 13 | > ImageCommand : icommand 14 | reg expression \s htpp ...... (.png) 15 | - stack overflow for regex url finder 16 | - capture the sender 17 | 18 | if img found - send it to vision api 19 | -- > conection to azure - send the url 20 | 21 | 22 | get back a descs + tags 23 | 24 | Photo Found: {file name} 25 | 26 | post back to the stream - the photo desc 27 | 28 | -------------------------------------------------------------------------------- /Fritz.ObsProxy/BotClient.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Microsoft.AspNetCore.SignalR.Client; 3 | using System.Diagnostics; 4 | 5 | namespace Fritz.ObsProxy 6 | { 7 | 8 | public class BotClient : IAsyncDisposable, ITakeScreenshots 9 | { 10 | private readonly ILogger _Logger; 11 | private readonly Uri _BotUrl; 12 | private readonly ObsClient _ObsClient; 13 | private HubConnection _Client; 14 | 15 | public BotClient(ILoggerFactory loggerFactory, IConfiguration configuration, ObsClient obsClient) 16 | { 17 | _Logger = loggerFactory.CreateLogger("BotClient"); 18 | _BotUrl = new Uri(configuration["BotUrl"]); 19 | _ObsClient = obsClient; 20 | } 21 | 22 | public async Task Connect() 23 | { 24 | 25 | _Client = new HubConnectionBuilder() 26 | .WithUrl(_BotUrl) 27 | .WithAutomaticReconnect() 28 | .AddJsonProtocol() 29 | .Build(); 30 | 31 | _Client.On("TakeScreenshot", TakeScreenshot); 32 | 33 | await StartAsync(); 34 | 35 | // await _Client.SendAsync("PostScreenshot", "Foo"); 36 | 37 | async Task StartAsync(int retryCount = 0) 38 | { 39 | 40 | try 41 | { 42 | await _Client.StartAsync(); 43 | } 44 | catch (Exception ex) 45 | { 46 | // do nothing, we're gonna try again... 47 | } 48 | 49 | if (_Client.State == HubConnectionState.Connected) 50 | { 51 | _Logger.LogWarning("Connected to ObsHub"); 52 | 53 | } 54 | else if (retryCount < 20) 55 | { 56 | _Logger.LogWarning($"Retrying connection to {_BotUrl} {retryCount}"); 57 | retryCount++; 58 | await Task.Delay(100); 59 | await StartAsync(retryCount); 60 | } 61 | } 62 | } 63 | 64 | public HubConnectionState ConnectedState => _Client.State; 65 | 66 | public async ValueTask DisposeAsync() 67 | { 68 | 69 | if (_Client != null) 70 | { 71 | await _Client.DisposeAsync(); 72 | } 73 | 74 | } 75 | 76 | private Task _PostScreenshotTask; 77 | //private string 78 | public Task TakeScreenshot() 79 | { 80 | 81 | var result = _ObsClient.TakeScreenshot(); 82 | //_Client. 83 | //_PostScreenshotTask = Task.Run(() => _Client.SendAsync("PostScreenshot", result)); 84 | 85 | _Client.SendAsync("PostScreenshot", clientStreamData(result)); 86 | 87 | return Task.CompletedTask; 88 | 89 | async IAsyncEnumerable clientStreamData(string imageData) 90 | { 91 | 92 | var sr = new StringReader(imageData); 93 | Debug.WriteLine($"ImageData ({imageData.Length}): " + imageData.Substring(imageData.Length - 20, 20)); 94 | 95 | var buffer = new char[2000]; 96 | while (true) 97 | { 98 | var lengthRead = sr.ReadBlock(buffer, 0, 2000); 99 | if (lengthRead == 0) { break; } 100 | yield return new string(buffer, 0, lengthRead); 101 | buffer = new char[2000]; 102 | } 103 | //After the for loop has completed and the local function exits the stream completion will be sent. 104 | } 105 | 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Fritz.ObsProxy/Fritz.ObsProxy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | true 6 | dotnet-Fritz.ObsProxy-63F1EE89-A75A-469F-919F-8238E927CEB8 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Fritz.ObsProxy/Program.cs: -------------------------------------------------------------------------------- 1 | using Fritz.ObsProxy; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | var host = Host.CreateDefaultBuilder(args) 6 | .ConfigureServices((hostContext, services) => { 7 | services.AddHostedService(); 8 | services.AddSingleton(); 9 | services.AddTransient(); 10 | }); 11 | 12 | var runningHost = host.Build(); 13 | 14 | ObsClient.OnDisconnect = () => { 15 | runningHost.StopAsync(); 16 | }; 17 | 18 | runningHost.Run(); 19 | -------------------------------------------------------------------------------- /Fritz.ObsProxy/Worker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Fritz.ObsProxy 12 | { 13 | public class Worker : IHostedService 14 | { 15 | private readonly ILogger _logger; 16 | private readonly ObsClient _ObsClient; 17 | private readonly BotClient _BotClient; 18 | private readonly IConfiguration _Configuration; 19 | 20 | public Worker(ILogger logger, ObsClient obsClient, BotClient botClient, IConfiguration configuration) 21 | { 22 | _logger = logger; 23 | _ObsClient = obsClient; 24 | _BotClient = botClient; 25 | _Configuration = configuration; 26 | } 27 | 28 | public async Task StartAsync(CancellationToken cancellationToken) 29 | { 30 | 31 | _ObsClient.Connect(); 32 | 33 | while (!_ObsClient.IsReady) { 34 | await Task.Delay(100); 35 | Console.WriteLine("Waiting to connect"); 36 | } 37 | 38 | if (string.IsNullOrEmpty(_Configuration["ObsTest"]) || !bool.Parse(_Configuration["ObsTest"])) 39 | { 40 | await _BotClient.Connect(); 41 | 42 | } else { 43 | var imgString = _ObsClient.TakeScreenshot(); 44 | var bytes = Convert.FromBase64String(imgString); 45 | File.WriteAllBytes("c:\\dev\\stream\\screenshots\\test.png", bytes); 46 | } 47 | 48 | } 49 | 50 | public async Task StopAsync(CancellationToken cancellationToken) 51 | { 52 | 53 | _ObsClient.Dispose(); 54 | await _BotClient.DisposeAsync(); 55 | 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Fritz.ObsProxy/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "BotUrl": "https://localhost:63972/obshub" 10 | } 11 | -------------------------------------------------------------------------------- /Fritz.ObsProxy/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Debug", 6 | "Microsoft.Hosting.Lifetime": "Debug" 7 | } 8 | }, 9 | "ImageFolder": "C:\\dev\\stream\\Screenshots", 10 | "ImageFolderOrg": "C:\\dev\\stream\\Screenshots", 11 | "BotUrl": "https://localhost:63972/obshub", 12 | "ObsIpAddress": "192.168.1.80:4455", 13 | "ObsPassword": "7UpJXXXwLMfXblPS", 14 | "CameraSource": "Local Sony", 15 | "ObsTest": "false", 16 | "OverlayScene": "S - Overlays", 17 | "OverlayCountdownSource": "Countdown-Shutter" 18 | } 19 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/ChannelPointRedemption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Fritz.StreamLib.Core 6 | { 7 | public class ChannelPointRedemption 8 | { 9 | 10 | public string RedeemingUserName { get; set; } 11 | 12 | public string RedeemingUserId { get; set; } 13 | 14 | public string RewardName { get; set; } 15 | 16 | public int RewardValue { get; set; } 17 | 18 | public string RewardPrompt { get; set; } 19 | 20 | public string BackgroundColor { get; set; } 21 | 22 | public Uri Image_1x { get; set; } 23 | 24 | public Uri Image_2x { get; set; } 25 | 26 | public Uri Image_4x { get; set; } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/ChatMessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Fritz.StreamLib.Core 5 | { 6 | public class ChatMessageEventArgs : EventArgs 7 | { 8 | /// Name of the originating service 9 | public string ServiceName { get; set; } 10 | 11 | /// Service specific properties (like AvatarUrl) 12 | public Dictionary Properties { get; } = new Dictionary(); 13 | 14 | public uint ChannelId { get; set; } 15 | public uint UserId { get; set; } 16 | public string UserName { get; set; } 17 | public bool IsWhisper { get; set; } 18 | public bool IsOwner { get; set; } 19 | public bool IsModerator { get; set; } 20 | public bool IsVip { get; set; } 21 | public string Message { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/ChatUserInfoEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Fritz.StreamLib.Core 5 | { 6 | public class ChatUserInfoEventArgs : EventArgs 7 | { 8 | /// Name of the originating service 9 | public string ServiceName { get; set; } 10 | 11 | /// Service specific properties (user roles etc) 12 | public Dictionary Properties { get; } = new Dictionary(); 13 | 14 | public uint ChannelId { get; set; } 15 | public uint UserId { get; set; } 16 | public string UserName { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/Fritz.StreamLib.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/IAttentionClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Fritz.StreamLib.Core 7 | { 8 | public interface IAttentionClient 9 | { 10 | Task AlertFritz(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/IChatService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Fritz.StreamLib.Core 6 | { 7 | public interface IChatService 8 | { 9 | string Name { get; } 10 | bool IsAuthenticated { get; } 11 | 12 | string BotUserName { get; } 13 | 14 | event EventHandler ChatMessage; 15 | event EventHandler UserJoined; 16 | event EventHandler UserLeft; 17 | 18 | Task SendMessageAsync(string message); 19 | Task SendWhisperAsync(string userName, string message); 20 | Task TimeoutUserAsync(string userName, TimeSpan time); 21 | Task BanUserAsync(string userName); 22 | Task UnbanUserAsync(string userName); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/IStreamService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Fritz.StreamLib.Core 5 | { 6 | 7 | public interface IStreamService 8 | { 9 | 10 | string Name { get; } 11 | 12 | int CurrentFollowerCount { get; } 13 | 14 | int CurrentViewerCount { get; } 15 | 16 | ValueTask Uptime(); 17 | 18 | /// 19 | /// Event raised when the number of Followers or Viewers changes 20 | /// 21 | event EventHandler Updated; 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/ITakeScreenshots.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Fritz.StreamLib.Core 7 | { 8 | public interface ITakeScreenshots 9 | { 10 | 11 | /// 12 | /// Take a screenshot and return the image as a byte array 13 | /// 14 | /// The screenshot image requested 15 | Task TakeScreenshot(); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/ServiceUpdatedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // REF: https://dev.mixer.com/reference/constellation/index.html 4 | 5 | namespace Fritz.StreamLib.Core 6 | { 7 | public class ServiceUpdatedEventArgs : EventArgs 8 | { 9 | 10 | public string ServiceName { get; set; } 11 | 12 | public uint ChannelId { get; set; } 13 | 14 | public int? NewFollowers { get; set; } 15 | 16 | public int? NewViewers { get; set; } 17 | 18 | public bool? IsOnline { get; set; } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Fritz.StreamLib.Core/TwitchTokenConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Text.Json; 8 | using System.Threading.Tasks; 9 | 10 | namespace Fritz.StreamLib.Core 11 | { 12 | public class TwitchTokenConfig 13 | { 14 | private readonly HttpClient _Client; 15 | private readonly IConfiguration _Configuration; 16 | private readonly ILogger _Logger; 17 | 18 | public TwitchTokenConfig(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILoggerFactory loggerFactory) 19 | { 20 | 21 | _Client = httpClientFactory.CreateClient("TwitchToken"); 22 | _Configuration = configuration; 23 | _Logger = loggerFactory.CreateLogger("TwitchToken"); 24 | 25 | GetNewToken().GetAwaiter().GetResult(); 26 | 27 | } 28 | public static TwitchTokenResponse Tokens { get; private set; } 29 | 30 | private async Task GetNewToken() 31 | { 32 | 33 | var sb = new StringBuilder("https://id.twitch.tv/oauth2/token"); 34 | sb.Append($"?client_id={_Configuration["StreamServices:Twitch:ClientId"]}"); 35 | sb.Append($"&client_secret={_Configuration["StreamServices:Twitch:ClientAccessToken"]}"); 36 | sb.Append($"&grant_type=client_credentials"); 37 | sb.Append($"&scope="); 38 | 39 | var result = await _Client.PostAsync(sb.ToString(), null); 40 | 41 | try 42 | { 43 | result.EnsureSuccessStatusCode(); 44 | } catch (Exception e) { 45 | _Logger.LogError("Error while fetching access tokens: " + e); 46 | return; 47 | } 48 | var json = await result.Content.ReadAsStringAsync(); 49 | TwitchTokenConfig.Tokens = JsonSerializer.Deserialize(json); 50 | 51 | } 52 | 53 | } 54 | 55 | 56 | public class TwitchTokenResponse 57 | { 58 | 59 | private DateTime _CreateDate = DateTime.Now; 60 | 61 | public string access_token { get; set; } 62 | public string refresh_token { get; set; } 63 | public int expires_in { get; set; } 64 | public string[] scope { get; set; } 65 | public string token_type { get; set; } 66 | 67 | public DateTime Expiration { get { return _CreateDate.Add(TimeSpan.FromSeconds(expires_in)); } } 68 | 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Fritz.StreamTools/.gitignore: -------------------------------------------------------------------------------- 1 | wwwroot/js/signalr-client.js 2 | wwwroot/js/GoalConfiguration.min.js 3 | -------------------------------------------------------------------------------- /Fritz.StreamTools/.npmrc: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Fritz.StreamTools/Controllers/AttentionController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Fritz.StreamTools.Controllers 8 | { 9 | public class AttentionController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | 16 | public IActionResult DoTheThing() 17 | { 18 | 19 | return View(); 20 | 21 | } 22 | 23 | public IActionResult Points() { 24 | return View(); 25 | } 26 | 27 | public IActionResult TestClient() 28 | { 29 | return View(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace Fritz.StreamTools.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Fritz.StreamTools/Controllers/RundownController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamTools.Interfaces; 6 | using Fritz.StreamTools.Models; 7 | using Fritz.StreamTools.Services; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc; 10 | 11 | namespace Fritz.StreamTools.Controllers 12 | { 13 | [Produces("application/json")] 14 | [Route("api/rundown")] 15 | public class RundownController : Controller 16 | { 17 | 18 | private IRundownService rundownService; 19 | 20 | public RundownController(IRundownService rundownService) 21 | { 22 | this.rundownService = rundownService; 23 | } 24 | 25 | [HttpGet("title")] 26 | public IActionResult Get() 27 | { 28 | return Ok(rundownService.GetRundownTitle()); 29 | } 30 | 31 | // PUT: api/Rundown/Title/ 32 | [HttpPut("title")] 33 | public IActionResult Put([FromBody]string value) 34 | { 35 | rundownService.UpdateTitle(value); 36 | return Ok(value); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Controllers/RundownItemController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Fritz.StreamTools.Interfaces; 7 | using Fritz.StreamTools.Models; 8 | using Fritz.StreamTools.Services; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Mvc; 11 | 12 | namespace Fritz.StreamTools.Controllers 13 | { 14 | [Produces("application/json")] 15 | [Route("api/items")] 16 | public class RundownItemController : Controller 17 | { 18 | 19 | private IRundownService rundownService; 20 | 21 | public RundownItemController(IRundownService rundownService) 22 | { 23 | this.rundownService = rundownService; 24 | } 25 | 26 | // GET: api/items 27 | [HttpGet] 28 | public IActionResult Get() 29 | { 30 | return Ok(rundownService.GetAllItems()); 31 | } 32 | 33 | // GET: api/items/5 34 | [HttpGet("{id}", Name = "Get")] 35 | public IActionResult Get(int id) 36 | { 37 | var outValue = rundownService.GetItem(id); 38 | if (outValue == null) return NotFound(); 39 | return Ok(outValue); 40 | } 41 | 42 | // POST: api/items 43 | [HttpPost] 44 | public IActionResult Post() 45 | { 46 | var newItem = rundownService.AddNewRundownItem(); 47 | return Ok(newItem); 48 | } 49 | 50 | // PUT: api/items/5 51 | [HttpPut("{id}")] 52 | public IActionResult Put(int id, [FromBody]RundownItem value) 53 | { 54 | rundownService.UpdateRundownItem(id, value); 55 | 56 | return Ok(value); 57 | } 58 | 59 | // DELETE: api/items/5 60 | [HttpDelete("{id}")] 61 | public IActionResult Delete(int id) 62 | { 63 | rundownService.DeleteRundownItem(id); 64 | return NoContent(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Controllers/ViewersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamTools.Services; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace Fritz.StreamTools.Controllers 9 | { 10 | public class ViewersController : Controller 11 | { 12 | 13 | public ViewersController(StreamService streamService) 14 | { 15 | this.StreamService = streamService; 16 | } 17 | 18 | public StreamService StreamService { get; } 19 | 20 | [ResponseCache(NoStore = true)] 21 | public IActionResult Current() 22 | { 23 | return View(StreamService.CurrentViewerCount); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Fritz.StreamTools/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build 9 | WORKDIR /src 10 | COPY ["Fritz.StreamTools/Fritz.StreamTools.csproj", "Fritz.StreamTools/"] 11 | COPY ["Fritz.Chatbot/Fritz.Chatbot.csproj", "Fritz.Chatbot/"] 12 | COPY ["Fritz.StreamLib.Core/Fritz.StreamLib.Core.csproj", "Fritz.StreamLib.Core/"] 13 | COPY ["Fritz.Twitch/Fritz.Twitch.csproj", "Fritz.Twitch/"] 14 | RUN dotnet restore "Fritz.StreamTools/Fritz.StreamTools.csproj" 15 | COPY . . 16 | WORKDIR "/src/Fritz.StreamTools" 17 | RUN dotnet build "Fritz.StreamTools.csproj" -c Release -o /app/build 18 | 19 | FROM build AS publish 20 | RUN dotnet publish "Fritz.StreamTools.csproj" -c Release -o /app/publish 21 | 22 | FROM base AS final 23 | WORKDIR /app 24 | COPY --from=publish /app/publish . 25 | ENV Logging__Console__FormatterName=Simple 26 | ENTRYPOINT ["dotnet", "Fritz.StreamTools.dll"] 27 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Fritz.StreamTools.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | ..\docker-compose.dcproj 5 | 78c713a0-80e0-4e16-956a-33cf16f08a02 6 | true 7 | Linux 8 | 9 | 10 | False 11 | 12 | 13 | False 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 | PreserveNewest 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Fritz.StreamTools/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Readability", "RCS1018:Add default access modifier.", Justification = "")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "RCS1141:Add parameter to documentation comment.", Justification = "")] 9 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "")] 10 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "RCS1090:Call 'ConfigureAwait(false)'.", Justification = "No SynchronizationContext in AspNet core")] 11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0007:Use implicit type", Justification = "Dont want to use var for bool, int ...")] 12 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Helpers/DisplayHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fritz.StreamTools.Helpers 8 | { 9 | public static class DisplayHelper 10 | { 11 | /// 12 | /// Produces the color gradient string required for linear-gradient based on a set of colors and the requested blending 13 | /// 14 | /// An array of valid CSS colors such as red,green,blue or #F00,#0F0,#00F 15 | /// An array of percentage blends required for each color - expressed between 0 and 1 16 | /// The total width to blend over 17 | /// 18 | public static string Gradient(string[] bgcolors, double[] bgblend, int width) 19 | { 20 | 21 | var count = (double)bgcolors.Length; 22 | 23 | // no colors 24 | if (count == 0) return ""; 25 | // 1 color = no gradient 26 | if (count == 1) return $"{bgcolors[0]},{bgcolors[0]}"; 27 | 28 | var colorWidth = (int)(width / (count - 1)); 29 | 30 | var result = new StringBuilder(); 31 | for (var c = 0; c < count - 1; c++) 32 | { 33 | var distance = c * colorWidth; 34 | 35 | // Each color has an anchor equidistant from the other colors 36 | if (result.Length > 0) 37 | { 38 | result.Append($",{bgcolors[c]} {distance}px"); 39 | } 40 | else 41 | { 42 | result.Append($"{bgcolors[c]} {distance}px"); 43 | } 44 | 45 | var blend = 1.0; 46 | 47 | if (bgblend?.Length > c) 48 | { 49 | blend = bgblend?[c] ?? 1.0; 50 | } 51 | 52 | // Mark the end of this color based on its blend % 53 | distance = (int)(c * colorWidth + (1 - blend) * colorWidth * 0.5); 54 | 55 | result.Append($",{bgcolors[c]} {distance}px"); 56 | 57 | // Now add the start of the next color based on this blend % 58 | distance = (int)((c + 1) * colorWidth - (1 - blend) * colorWidth * 0.5); 59 | 60 | result.Append($",{bgcolors[c + 1]} {distance}px"); 61 | } 62 | 63 | result.Append($", {bgcolors.Last()}"); 64 | return result.ToString(); 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Helpers/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json.Linq; 3 | 4 | namespace Fritz.StreamTools.Helpers 5 | { 6 | public static class JsonExtensions 7 | { 8 | public static bool IsNullOrEmpty(this JToken token) 9 | { 10 | return (token == null) || 11 | (token.Type == JTokenType.Array && !token.HasValues) || 12 | (token.Type == JTokenType.Object && !token.HasValues) || 13 | (token.Type == JTokenType.String && token.ToString() == String.Empty) || 14 | (token.Type == JTokenType.Null); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Helpers/LogExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Fritz.ChatBot.Helpers 5 | { 6 | public static class LogExtensions 7 | { 8 | public static bool LogAndSwallow(this ILogger logger, string action, Exception e) 9 | { 10 | logger.LogWarning($"Exception while {action}: '{e.Message}'"); 11 | return true; 12 | 13 | } 14 | 15 | public static bool LogAndRethrow(ILogger logger, string action, Exception e) 16 | { 17 | logger.LogWarning($"Exception while {action}: '{e.Message}'"); 18 | return false; 19 | 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Helpers/ReflectionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Fritz.StreamTools.Helpers 6 | { 7 | public static class ReflectionHelper 8 | { 9 | // 10 | // Uses reflection to find the named event and calls DynamicInvoke on it 11 | // 12 | public static void RaiseEvent(object instance, string name, EventArgs args) 13 | { 14 | if (instance == null) 15 | throw new ArgumentNullException(nameof(instance)); 16 | if (string.IsNullOrEmpty(name)) 17 | throw new ArgumentException("message", nameof(name)); 18 | 19 | var fieldInfo = instance.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); 20 | if (fieldInfo == null) 21 | return; 22 | var multicastDelegate = fieldInfo.GetValue(instance) as MulticastDelegate; 23 | 24 | // NOTE: Using DynamicInvoke so tests work! 25 | multicastDelegate?.DynamicInvoke(new object[] { instance, args }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Helpers/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace System.Threading.Tasks 6 | { 7 | public static class TaskHelpers 8 | { 9 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 10 | public static void Forget(this Task task) 11 | { 12 | // Empty on purpose! 13 | } 14 | 15 | private const int s_defaultTimeout = 5000; 16 | 17 | public static Task OrTimeout(this Task task, int milliseconds = s_defaultTimeout) 18 | => OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds)); 19 | 20 | public static async Task OrTimeout(this Task task, TimeSpan timeout) 21 | { 22 | var completed = await Task.WhenAny(task, Task.Delay(timeout)); 23 | if (completed != task) 24 | { 25 | throw new TimeoutException(); 26 | } 27 | 28 | await task; 29 | } 30 | 31 | public static Task OrTimeout(this Task task, int milliseconds = s_defaultTimeout) 32 | => OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds)); 33 | 34 | public static async Task OrTimeout(this Task task, TimeSpan timeout) 35 | { 36 | var completed = await Task.WhenAny(task, Task.Delay(timeout)); 37 | if (completed != task) 38 | { 39 | throw new TimeoutException(); 40 | } 41 | 42 | return await task; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Hubs/AttentionHub.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Microsoft.AspNetCore.SignalR; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Fritz.StreamTools.Hubs 9 | { 10 | public interface IAttentionHubClient 11 | { 12 | 13 | // Cheer 200 parithon 12/18/2018 14 | // Cheer 500 pharewings 12/18/2018 15 | Task AlertFritz(); 16 | Task ClientConnected(string connectionId); 17 | } 18 | 19 | public class AttentionHub : Hub 20 | { 21 | public override Task OnConnectedAsync() 22 | { 23 | return this.Clients.Others.ClientConnected(this.Context.ConnectionId); 24 | } 25 | 26 | public Task AlertFritz() 27 | { 28 | return this.Clients.Others.AlertFritz(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Hubs/BaseHub.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.SignalR; 4 | 5 | namespace Fritz.StreamTools.Hubs 6 | { 7 | 8 | public abstract class BaseHub : Hub { 9 | 10 | public override async Task OnConnectedAsync() 11 | { 12 | var groupNames = Context.GetHttpContext().Request.Query["groups"].SingleOrDefault(); 13 | if (groupNames != null) 14 | { 15 | // Join the group(s) the user has specified in the 'groups' query-string 16 | // NOTE: SignalR will automatically take care of removing the client from the group(s) when they disconnect 17 | foreach (var groupName in groupNames.Split(',')) 18 | await Groups.AddToGroupAsync(Context.ConnectionId, groupName.ToLowerInvariant()); 19 | } 20 | 21 | await base.OnConnectedAsync(); 22 | } 23 | 24 | 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Hubs/FollowerHub.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Mime; 2 | using Fritz.StreamLib.Core; 3 | using Fritz.StreamTools.Services; 4 | using Microsoft.AspNetCore.SignalR; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Fritz.StreamTools.Models; 10 | 11 | namespace Fritz.StreamTools.Hubs 12 | { 13 | 14 | public class FollowerHub : BaseHub 15 | { 16 | public StreamService StreamService { get; } 17 | public FollowerClient FollowerClient { get; } 18 | 19 | public FollowerHub( 20 | StreamService streamService, 21 | FollowerClient client 22 | ) 23 | { 24 | 25 | this.StreamService = streamService; 26 | this.FollowerClient = client; 27 | 28 | StreamService.Updated += StreamService_Updated; 29 | } 30 | 31 | private void StreamService_Updated(object sender, ServiceUpdatedEventArgs e) 32 | { 33 | if (e.NewFollowers.HasValue) 34 | { 35 | this.FollowerClient.UpdateFollowers(StreamService.CurrentFollowerCount); 36 | } 37 | 38 | if (e.NewViewers.HasValue) 39 | { 40 | this.FollowerClient.UpdateViewers(e.ServiceName, e.NewViewers.Value); 41 | } 42 | } 43 | 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Hubs/GithubyMcGithubFace.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Fritz.StreamTools.Models; 3 | using Fritz.StreamTools.Services; 4 | using Microsoft.AspNetCore.SignalR; 5 | 6 | namespace Fritz.StreamTools.Hubs 7 | { 8 | 9 | /// So named because @rachelAppel said so.. 10 | public class GithubyMcGithubFace : BaseHub 11 | { 12 | public GithubyMcGithubFaceClient GithubyMcGithubFaceClient { get; } 13 | 14 | public GithubyMcGithubFace( 15 | GithubyMcGithubFaceClient client 16 | ) 17 | { 18 | this.GithubyMcGithubFaceClient = client; 19 | } 20 | 21 | private void Git_Updated(object sender, GitHubNewContributorsEventArgs e) 22 | { 23 | 24 | this.GithubyMcGithubFaceClient.UpdateGitHub(e.Repository, e.UserName, e.NewCommits); 25 | 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Interfaces/IRundownService.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamTools.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fritz.StreamTools.Interfaces 8 | { 9 | public interface IRundownService 10 | { 11 | string GetRundownTitle(); 12 | void UpdateTitle(string title); 13 | RundownItem AddNewRundownItem(); 14 | void UpdateRundownItem(int id, RundownItem item); 15 | void DeleteRundownItem(int idToDelete); 16 | List GetAllItems(); 17 | RundownItem GetItem(int id); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/FollowerCountConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Fritz.StreamTools.Models 5 | { 6 | public class FollowerCountConfiguration 7 | { 8 | [Display(Name = "Font Color")] 9 | public string FontColor { get; set; } 10 | 11 | [Display(Name = "Current Value")] 12 | public int CurrentValue { get; set; } 13 | 14 | [Display(Name = "Background Color")] 15 | public string BackgroundColor { get; set; } 16 | 17 | internal void LoadDefaultSettings(FollowerCountConfiguration config) 18 | { 19 | this.BackgroundColor = string.IsNullOrWhiteSpace(this.BackgroundColor) ? config.BackgroundColor : this.BackgroundColor; 20 | this.FontColor = string.IsNullOrWhiteSpace(this.FontColor) ? config.FontColor : this.FontColor; 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/FollowerGoalConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamTools.Helpers; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | 6 | namespace Fritz.StreamTools.Models 7 | { 8 | 9 | public class FollowerGoalConfiguration 10 | { 11 | 12 | public string Caption { get; set; } = "Follower Goal"; 13 | public int Goal { get; set; } = 0; 14 | [Display(Name = "Current Value")] 15 | public int CurrentValue { get; set; } = 0; 16 | public int Width { get; set; } = 800; 17 | [Display(Name = "Empty Background Color")] 18 | public string EmptyBackgroundColor { get; set; } 19 | 20 | [Display(Name = "Empty Font Color")] 21 | public string EmptyFontColor { get; set; } 22 | 23 | [Display(Name = "Fill Font Color")] 24 | public string FillFontColor { get; set; } 25 | 26 | [Display(Name = "Google Font Name")] 27 | public string FontName { get; set; } = "~"; 28 | public string Gradient { get; set; } 29 | public string FillBackgroundColor { get; set; } = ""; 30 | public string[] FillBgColorArray 31 | { 32 | get 33 | { 34 | return FillBackgroundColor.Split(','); 35 | } 36 | } 37 | public string FillBackgroundColorBlend { get; set; } = "0"; 38 | public double[] FillBgBlendArray 39 | { 40 | get 41 | { 42 | return FillBackgroundColorBlend.Split(',').Select(x => double.Parse(x)).ToArray(); 43 | } 44 | } 45 | 46 | internal void LoadDefaultValues(FollowerGoalConfiguration configuration) 47 | { 48 | 49 | this.Caption = string.IsNullOrEmpty(this.Caption) ? configuration.Caption : this.Caption == "null" ? "Add a caption" : this.Caption; 50 | this.Goal = this.Goal == 0 ? configuration.Goal : this.Goal; 51 | var backColors = string.IsNullOrEmpty(this.FillBackgroundColor) ? configuration.FillBgColorArray : this.FillBackgroundColor.Split(','); 52 | var backBlend = string.IsNullOrEmpty(this.FillBackgroundColorBlend) ? configuration.FillBgBlendArray : this.FillBackgroundColorBlend.Split(',').Select(a => double.Parse(a)).ToArray(); 53 | 54 | this.EmptyBackgroundColor = string.IsNullOrWhiteSpace(this.EmptyBackgroundColor) ? configuration.EmptyBackgroundColor : this.EmptyBackgroundColor; 55 | this.EmptyFontColor = string.IsNullOrWhiteSpace(this.EmptyFontColor) ? configuration.EmptyFontColor : this.EmptyFontColor; 56 | this.FontName = this.FontName == "~" ? configuration.FontName : this.FontName; 57 | 58 | this.Gradient = DisplayHelper.Gradient(backColors, backBlend, this.Width); 59 | 60 | 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/GitHubConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace Fritz.StreamTools.Models 7 | { 8 | public class GitHubConfiguration 9 | { 10 | [Required] 11 | [Display(Name = "Repository Owner")] 12 | [Remote(action: "VerifyUser", controller: "GitHub")] 13 | public string RepositoryOwner { get; set; } 14 | 15 | [Required] 16 | [Display(Name = "Repository Name")] 17 | [Remote(action: "VerifyRepository", controller: "GitHub", AdditionalFields = nameof(RepositoryOwner))] 18 | public string RepositoryName { get; set; } 19 | 20 | public ICollection ExcludeUsers { get; set; } 21 | 22 | public string RepositoryCsv { get; set; } = "csharpfritz/Fritz.StreamTools,csharpfritz/CoreWiki"; 23 | 24 | public string DisplayMode {get;set;} = "h-scroll"; 25 | 26 | public int Width { get; set; } = 600; 27 | 28 | public int SpeedMs { get; set; } = 15000; 29 | 30 | public string Font { get; set; } = "Arial"; 31 | 32 | public int FontSizePt { get; set; } = 14; 33 | 34 | public bool CaptionBold { get; set; } = true; 35 | 36 | public string CaptionColor { get; set; } = "yellow"; 37 | 38 | public string TextColor { get; set; } = "white"; 39 | 40 | public string BackgroundColor { get; set; } = "#666"; 41 | 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/GitHubContributor.cs: -------------------------------------------------------------------------------- 1 | namespace Fritz.StreamTools.Models 2 | { 3 | public class GitHubContributor 4 | { 5 | public string Author { get; set; } 6 | public int Commits { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/GitHubInformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Fritz.StreamTools.Models 4 | { 5 | public class GitHubInformation 6 | { 7 | public GitHubInformation() 8 | { 9 | TopWeekContributors = new List(); 10 | TopMonthContributors = new List(); 11 | TopEverContributors = new List(); 12 | } 13 | 14 | public string Repository { get; set; } 15 | 16 | public List TopWeekContributors { get; private set; } 17 | public List TopMonthContributors { get; private set; } 18 | public List TopEverContributors { get; private set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/GitHubUpdatedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Fritz.StreamTools.Models 5 | { 6 | public class GitHubUpdatedEventArgs : EventArgs 7 | { 8 | 9 | public GitHubUpdatedEventArgs(IEnumerable newInformation, DateTime lastUpdate) 10 | { 11 | this.Contributors = newInformation; 12 | this.LastUpdate = lastUpdate; 13 | } 14 | 15 | public IEnumerable Contributors { get; } 16 | public DateTime LastUpdate { get; } 17 | } 18 | 19 | public class GitHubNewContributorsEventArgs : EventArgs { 20 | 21 | public GitHubNewContributorsEventArgs(string repository, string userName, int newNumberOfCommits) { 22 | 23 | this.Repository = repository; 24 | this.UserName = userName; 25 | this.NewCommits = newNumberOfCommits; 26 | 27 | } 28 | 29 | public string Repository { get; } 30 | public string UserName { get; } 31 | public int NewCommits { get; } 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/RundownItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace Fritz.StreamTools.Models 6 | { 7 | public class RundownItem 8 | { 9 | 10 | public int ID { get; set; } 11 | 12 | public string Text { get; set; } 13 | 14 | public bool IsCompleted { get; set; } 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/RundownItemRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Fritz.StreamTools.Models 5 | { 6 | public class RundownItemRepository 7 | { 8 | 9 | private static readonly List _Items = new List() 10 | { 11 | new RundownItem {ID=10, Text="PRE-SHOW: Set stream titles"}, 12 | new RundownItem {ID=20, Text="PRE-SHOW: Send 'Stream starting soon' tweet"}, 13 | new RundownItem {ID=30, Text="PRE-SHOW: Set background to 'Starting shortly' and start music"}, 14 | new RundownItem {ID=40, Text="Start Stream!"}, 15 | new RundownItem {ID=50, Text="Introduction"}, 16 | new RundownItem {ID=60, Text="Music to Code By"}, 17 | new RundownItem {ID=70, Text="Today's Hat"}, 18 | new RundownItem {ID=80, Text="Housekeeping"}, 19 | new RundownItem {ID=90, Text="Follower Goals"}, 20 | new RundownItem {ID=110, Text="Pull Requests"}, 21 | new RundownItem {ID=120, Text="New Code!"}, 22 | new RundownItem {ID=130, Text="Log issue with last update"}, 23 | new RundownItem {ID=140, Text="Commit and push source code"}, 24 | new RundownItem {ID=150, Text="Next stream..."} 25 | }; 26 | 27 | public IEnumerable Get() 28 | { 29 | 30 | return _Items.OrderBy(i => i.ID); 31 | 32 | } 33 | 34 | public void Add(RundownItem item) 35 | { 36 | _Items.Add(item); 37 | } 38 | 39 | public void Update(int id, RundownItem item) 40 | { 41 | Delete(id); 42 | _Items.Add(item); 43 | } 44 | 45 | public void Delete(int id) 46 | { 47 | 48 | _Items.Remove(_Items.FirstOrDefault(i => i.ID == id)); 49 | 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Models/RundownRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Fritz.StreamTools.Models 7 | { 8 | public class RundownRepository 9 | { 10 | private static string _Title = "Rundown Title"; 11 | 12 | public void UpdateTitle(string title) 13 | { 14 | _Title = title; 15 | } 16 | 17 | public string GetTitle() 18 | { 19 | return _Title; 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/Admin.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamTools.Interfaces; 6 | using Fritz.StreamTools.Models; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | 10 | namespace Fritz.StreamTools.Pages 11 | { 12 | public class AdminModel : PageModel 13 | { 14 | 15 | public AdminModel(IRundownService service) 16 | { 17 | this.Service = service; 18 | } 19 | 20 | public IRundownService Service { get; private set; } 21 | 22 | public void OnGet() 23 | { 24 | 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/CurrentViewers.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Fritz.StreamTools.Pages.CurrentViewersModel 3 | @using System.Collections.Generic 4 | @{ 5 | Layout = null; 6 | 7 | var fontIcon = new Dictionary 8 | { 9 | {"Mixer", "microsoft"}, 10 | {"Fake", "tumblr" } 11 | }; 12 | 13 | } 14 | 15 | 16 | 17 | 18 | 19 | 20 | CurrentViewers 21 | 22 | 23 | 24 | @foreach (var service in Model.StreamService.ViewerCountByService.OrderByDescending(s => s.service)) 25 | { 26 | 27 | @service.count 28 | 29 | } 30 | 31 | @if (Model.StreamService.ViewerCountByService.Count() > 1) 32 | { 33 | @:Total: @(Model.StreamService.ViewerCountByService.Sum(s => s.count)) 34 | } 35 | 36 | 37 | 38 | 39 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/CurrentViewers.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamTools.Services; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace Fritz.StreamTools.Pages 10 | { 11 | public class CurrentViewersModel : PageModel 12 | { 13 | 14 | public CurrentViewersModel( 15 | StreamService streamService 16 | ) 17 | { 18 | this.StreamService = streamService; 19 | } 20 | 21 | public StreamService StreamService { get; } 22 | 23 | public void OnGet() 24 | { 25 | 26 | } 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/HatDisplay.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @{ 3 | } 4 | 5 | 6 | 7 | 8 | 9 | ToDo Widget 10 | 11 | 44 | 45 | 46 | 47 | 48 |
49 |

Jeff is wearing his hat. ( accuracy)

50 |
51 |

52 |
53 | 54 | 55 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/Rundown.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Fritz.StreamTools.Pages.RundownModel 3 | @{ 4 | var items = Model.Service.GetAllItems(); 5 | var title = Model.Service.GetRundownTitle(); 6 | } 7 | 8 | 9 | 10 | Rundown for the Stream 11 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |

@title

36 | 37 |
    38 | @foreach (var item in items) 39 | { 40 |
  • 41 | 42 | 43 |
  • 44 | } 45 |
46 | 47 |
48 | 49 | 50 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/Rundown.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.StreamTools.Interfaces; 6 | using Fritz.StreamTools.Models; 7 | using Fritz.StreamTools.Services; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | 11 | namespace Fritz.StreamTools.Pages 12 | { 13 | public class RundownModel : PageModel 14 | { 15 | 16 | public RundownModel(IRundownService service) 17 | { 18 | this.Service = service; 19 | } 20 | 21 | public IRundownService Service { get; private set; } 22 | 23 | public void OnGet() 24 | { 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/Sentiment.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Fritz.StreamTools.Pages.SentimentModel 3 | @{ 4 | } 5 | 6 | 7 | 8 | 9 | 10 | Sentiment 11 | 12 | 13 | 14 | 15 |
16 | Current Sentiment: 17 |
18 |
19 | 20 | 21 | 22 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/Sentiment.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Fritz.Chatbot.Commands; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace Fritz.StreamTools.Pages 10 | { 11 | public class SentimentModel : PageModel 12 | { 13 | 14 | public void OnGet() 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/SentimentGauge.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Fritz.StreamTools.Pages.SentimentModel 3 | @{ 4 | } 5 | 6 | 7 | 8 | 9 | 10 | Sentiment 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 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/Team.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @inject Microsoft.Extensions.Configuration.IConfiguration Configuration 3 | 4 | 5 | 6 | 7 | 8 | ToDo Widget 9 | 10 | 43 | 44 | 45 | 46 | 47 |
48 |

Welcome from @Configuration["StreamServices:Twitch:TeamDisplayName"]

49 | 50 |
51 | 52 | 53 | 54 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/TestTwitchbot.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Fritz.StreamTools.Pages.TestTwitchbotModel 3 | @* 4 | For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 5 | *@ 6 | 7 |
8 |

UserName:

9 | 10 | 11 |
12 | 13 |

Message to send:

14 | 15 | 16 | 17 | 18 |
19 | 20 |

21 | Current Uptime: @Model.Uptime 22 |

23 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/TestTwitchbot.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Fritz.StreamTools.Services; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace Fritz.StreamTools.Pages 13 | { 14 | public class TestTwitchbotModel : PageModel 15 | { 16 | 17 | public TwitchService Service { get; } 18 | public ILogger Logger { get; } 19 | public Twitch.Proxy TwitchProxy { get; } 20 | 21 | public TestTwitchbotModel(Services.TwitchService service, ILoggerFactory loggerFactory, Twitch.Proxy proxy) 22 | { 23 | Service = service; 24 | this.Logger = loggerFactory.CreateLogger("FritzBot"); 25 | this.TwitchProxy = proxy; 26 | } 27 | 28 | [BindProperty] 29 | public string Message { get; set; } 30 | 31 | 32 | [BindProperty] 33 | public string UserName { get; set; } 34 | 35 | public TimeSpan? Uptime { get; set; } 36 | 37 | public void OnGet() 38 | { 39 | 40 | 41 | 42 | } 43 | 44 | public void OnPost() 45 | { 46 | 47 | Service.MessageReceived(false, false, Message, UserName); 48 | 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Fritz.StreamTools.Models 2 | @using Microsoft.Extensions.Options 3 | @using Microsoft.AspNetCore.Hosting 4 | 5 | @addTagHelper *, Fritz.StreamTools 6 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /Fritz.StreamTools/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Fritz.StreamTools 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args) 18 | .Build() 19 | .Run(); 20 | } 21 | 22 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 23 | WebHost.CreateDefaultBuilder(args) 24 | .UseStartup(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | 7 | [assembly:InternalsVisibleTo("Test")] -------------------------------------------------------------------------------- /Fritz.StreamTools/SampleQuotes.txt: -------------------------------------------------------------------------------- 1 | It’s not a bug. It’s an undocumented feature! 2 | “Software Developer” – An organism that turns caffeine into software 3 | If debugging is the process of removing software bugs, then programming must be the process of putting them in – Edsger Dijkstra 4 | A user interface is like a joke. If you have to explain it, it’s not that good. 5 | I don’t care if it works on your machine! We are not shipping your machine!” – Vidiu Platon 6 | Measuring programming progress by lines of code is like measuring aircraft building progress by weight. – Bill Gates (co-founder of Microsoft) 7 | I’m very font of you because you are just my type. 8 | My code DOESN’T work, I have no idea why. My code WORKS, I have no idea why. 9 | Things aren’t always #000000 and #FFFFFF 10 | One man’s crappy software is another man’s full time job. – Jessica Gaston 11 | Software undergoes beta testing shortly before it’s released. Beta is Latin for “still doesn’t work”. 12 | Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. – Martin Golding 13 | Talk is cheap. Show me the code. – Linus Torvalds 14 | Writing the first 90 percent of a computer program takes 90 percent of the time. The remaining ten percent also takes 90 percent of the time and the final touches also take 90 percent of the time. – N.J. Rubenking 15 | Old programmers never die. They simply give up their resources. 16 | There are only two industries that refer to their customers as “users”. – (Edward Tufte) 17 | Any code of your own that you haven’t looked at for six or more months might as well have been written by someone else. – (Eagleson’s Law) 18 | If at first you don’t succeed; call it version 1.0 19 | If Internet Explorer is brave enough to ask to be your default browser, You are brave enough to ask that girl out. 20 | "To teach is to learn twice." - Anonymous 21 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Services/FollowerClient.cs: -------------------------------------------------------------------------------- 1 | // REF: https://dev.mixer.com/reference/constellation/index.html 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using Fritz.StreamTools.Hubs; 6 | using Fritz.StreamTools.Models; 7 | using Microsoft.AspNetCore.SignalR; 8 | 9 | namespace Fritz.StreamTools.Services 10 | { 11 | public class FollowerClient 12 | { 13 | 14 | public FollowerClient(IHubContext followerContext) 15 | { 16 | 17 | this.FollowerContext = followerContext; 18 | 19 | } 20 | 21 | private IHubContext FollowerContext { get; } 22 | 23 | public void UpdateFollowers(int newFollowers) 24 | { 25 | 26 | FollowerContext.Clients.Group("followers").SendAsync("OnFollowersCountUpdated", newFollowers); 27 | 28 | } 29 | 30 | public void UpdateViewers(string serviceName, int viewerCount) 31 | { 32 | FollowerContext.Clients.Group("viewers").SendAsync("OnViewersCountUpdated", serviceName.ToLowerInvariant(), viewerCount); 33 | } 34 | 35 | internal void UpdateGitHub(IEnumerable contributors) 36 | { 37 | FollowerContext.Clients.Group("github").SendAsync("OnGitHubUpdated", contributors); 38 | } 39 | 40 | internal void UpdateSentiment(double newSentiment, double oneMinuteSentiment, double fiveMinuteSentiment, double allSentiment ) { 41 | 42 | FollowerContext.Clients.Group("sentiment").SendAsync("OnSentimentUpdated", newSentiment, oneMinuteSentiment, fiveMinuteSentiment, allSentiment); 43 | 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Services/GitHubService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Fritz.StreamTools.Models; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Options; 11 | 12 | namespace Fritz.StreamTools.Services 13 | { 14 | public class GitHubService : IHostedService 15 | { 16 | 17 | public static DateTime LastUpdate = DateTime.MinValue; 18 | private CancellationToken _Token; 19 | private Task _RunningTask; 20 | 21 | public GitHubService(IServiceProvider services, ILogger logger) 22 | { 23 | this.Services = services; 24 | this.Logger = logger; 25 | } 26 | 27 | public IServiceProvider Services { get; } 28 | public ILogger Logger { get; } 29 | 30 | public Task StartAsync(CancellationToken cancellationToken) 31 | { 32 | _Token = cancellationToken; 33 | _RunningTask = MonitorUpdates(); 34 | return Task.CompletedTask; 35 | } 36 | 37 | public Task StopAsync(CancellationToken cancellationToken) 38 | { 39 | return Task.CompletedTask; 40 | } 41 | 42 | private async Task MonitorUpdates() 43 | { 44 | var lastRequest = DateTime.Now; 45 | using (var scope = Services.CreateScope()) 46 | { 47 | var repo = scope.ServiceProvider.GetService(typeof(GitHubRepository)) as GitHubRepository; 48 | var mcGithubFaceClient = scope.ServiceProvider.GetService(typeof(GithubyMcGithubFaceClient)) as GithubyMcGithubFaceClient; 49 | while (!_Token.IsCancellationRequested) 50 | { 51 | if (repo != null) 52 | { 53 | var lastUpdate = await repo.GetLastCommitTimestamp(); 54 | if (lastUpdate.Item1 > LastUpdate) 55 | { 56 | 57 | LastUpdate = lastUpdate.Item1; 58 | 59 | Logger.LogWarning($"Triggering refresh of GitHub scoreboard with updates as of {lastUpdate}"); 60 | mcGithubFaceClient?.UpdateGitHub("", "", 0); 61 | } 62 | } 63 | await Task.Delay(500, _Token); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Services/GithubyMcGithubFaceClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Fritz.StreamTools.Hubs; 3 | using Fritz.StreamTools.Models; 4 | using Microsoft.AspNetCore.SignalR; 5 | 6 | namespace Fritz.StreamTools.Services 7 | { 8 | public class GithubyMcGithubFaceClient 9 | { 10 | 11 | public GithubyMcGithubFaceClient(IHubContext mcGitHubContext) 12 | { 13 | 14 | this.McGitHubContext = mcGitHubContext; 15 | 16 | } 17 | 18 | private IHubContext McGitHubContext { get; } 19 | 20 | internal void UpdateGitHub(string repository, string userName, int commits) 21 | { 22 | McGitHubContext.Clients.Group("github").SendAsync("OnGitHubUpdated", repository, userName, commits); 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Services/RundownService.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamTools.Interfaces; 2 | using Fritz.StreamTools.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Fritz.StreamTools.Services 9 | { 10 | public class RundownService : IRundownService 11 | { 12 | private RundownItemRepository itemRepository; 13 | private RundownRepository repository; 14 | 15 | public RundownService(RundownItemRepository itemRepository, RundownRepository repository) 16 | { 17 | this.repository = repository; 18 | this.itemRepository = itemRepository; 19 | } 20 | 21 | public RundownItem AddNewRundownItem() 22 | { 23 | var largestItemId = itemRepository.Get().Max(i => i.ID); 24 | var newItem = new RundownItem() { ID = largestItemId + 10, Text = "New Item" }; 25 | itemRepository.Add(newItem); 26 | return newItem; 27 | } 28 | 29 | public void DeleteRundownItem(int idToDelete) 30 | { 31 | itemRepository.Delete(idToDelete); 32 | } 33 | 34 | public List GetAllItems() 35 | { 36 | return itemRepository.Get().ToList(); 37 | } 38 | 39 | public RundownItem GetItem(int id) 40 | { 41 | return itemRepository.Get().Where(i => i.ID == id).FirstOrDefault(); 42 | } 43 | 44 | public string GetRundownTitle() 45 | { 46 | return repository.GetTitle(); 47 | } 48 | 49 | public void UpdateRundownItem(int id, RundownItem item) 50 | { 51 | itemRepository.Update(id, item); 52 | } 53 | 54 | public void UpdateTitle(string title) 55 | { 56 | repository.UpdateTitle(title); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Services/StreamService.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Fritz.StreamTools.Services 9 | { 10 | 11 | public class StreamService : IStreamService 12 | { 13 | 14 | private IEnumerable _services; 15 | 16 | public StreamService( 17 | IEnumerable services 18 | ) 19 | { 20 | 21 | _services = services; 22 | 23 | } 24 | 25 | public int CurrentFollowerCount { get { return _services.Sum(s => s.CurrentFollowerCount); } } 26 | 27 | public int CurrentViewerCount { get { return _services.Sum(s => s.CurrentViewerCount); } } 28 | 29 | public string Name { get { return "Aggregate"; } } 30 | 31 | public IEnumerable<(string service, int count)> ViewerCountByService 32 | { 33 | get 34 | { 35 | 36 | return _services.Select(s => (s.Name, s.CurrentViewerCount)); 37 | 38 | } 39 | } 40 | 41 | public IEnumerable<(string service, int count)> FollowerCountByService 42 | { 43 | get 44 | { 45 | 46 | return _services.Select(s => (s.Name, s.CurrentFollowerCount)); 47 | 48 | } 49 | } 50 | 51 | public ValueTask Uptime() => new ValueTask((TimeSpan?)null); 52 | 53 | public event EventHandler Updated 54 | { 55 | add 56 | { 57 | foreach (var s in _services) 58 | { 59 | s.Updated += value; 60 | } 61 | } 62 | remove 63 | { 64 | foreach (var s in _services) 65 | { 66 | s.Updated -= value; 67 | } 68 | } 69 | } 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Fritz.StreamTools/SkeetQuotes.txt: -------------------------------------------------------------------------------- 1 | Jon Skeet is immutable. If something's going to change, it's going to have to be the rest of the universe. 2 | Jon Skeet can divide by zero. 3 | When Jon Skeet's code fails to compile the compiler apologises. 4 | Jon Skeet has already written a book about C# 5.0. It’s currently sealed up. In three years, Anders Hejlsberg is going to open the book to see if the language design team got it right. 5 | Jon Skeet can stop an infinite loop just by thinking about it. 6 | Jon Skeet doesn't need a debugger, he just stares down the bug until the code confesses. 7 | Jon Skeet's addition operator doesn't commute; it teleports to where he needs it to be. 8 | Anonymous methods and anonymous types are really all called Jon Skeet. They just don't like to boast. 9 | Jon Skeet's code doesn't follow a coding convention. It is the coding convention. 10 | Jon Skeet doesn't have performance bottlenecks. He just makes the universe wait its turn. 11 | Jon Skeet coded his last project entirely in Microsoft Paint, just for the challenge. 12 | Jon Skeet does not use exceptions when programming. He has not been able to identify any of his code that is not exceptional. 13 | Jon Skeet does not use revision control software. None of his code has ever needed revision. 14 | When you search for "guru" on Google it says "Did you mean Jon Skeet?" 15 | Jon Skeet can recite π. Backwards. 16 | When Jon Skeet points to null, null quakes in fear. 17 | When Jon gives a method an argument, the method loses. 18 | When Jon pushes a value onto a stack, it stays pushed. 19 | When invoking one of Jon's callbacks, the runtime adds "please". -------------------------------------------------------------------------------- /Fritz.StreamTools/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Fritz.StreamLib.Core; 4 | using Fritz.StreamTools.Hubs; 5 | using Fritz.StreamTools.Services; 6 | using Fritz.Twitch; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | 13 | namespace Fritz.StreamTools 14 | { 15 | public class Startup 16 | { 17 | private static Dictionary _servicesRequiredConfiguration = new Dictionary() 18 | { 19 | { typeof(SentimentService), new [] { "FritzBot:SentimentAnalysisKey" } } 20 | }; 21 | 22 | public Startup(IConfiguration configuration) 23 | { 24 | Configuration = configuration; 25 | } 26 | 27 | public IConfiguration Configuration { get; } 28 | 29 | // This method gets called by the runtime. Use this method to add services to the container. 30 | public void ConfigureServices(IServiceCollection services) 31 | { 32 | services.AddTwitchClient(); 33 | 34 | StartupServices.ConfigureServices.Execute(services, Configuration, _servicesRequiredConfiguration); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IHostEnvironment env, IConfiguration config) 39 | { 40 | 41 | // Cheer 100 Crazy240sx 12/18/2018 42 | 43 | if (env.IsDevelopment()) 44 | { 45 | app.UseDeveloperExceptionPage(); 46 | } 47 | else 48 | { 49 | app.UseExceptionHandler("/Error"); 50 | } 51 | 52 | app.UseHsts(); 53 | //app.UseHttpsRedirection(); 54 | 55 | app.UseStaticFiles(); 56 | 57 | app.UseRouting(); 58 | 59 | app.UseEndpoints(endpoints => 60 | { 61 | 62 | endpoints.MapHub("/followerstream"); 63 | endpoints.MapHub("/github"); 64 | endpoints.MapHub("/attentionhub"); 65 | endpoints.MapHub("/obshub"); 66 | 67 | endpoints.MapRazorPages(); 68 | 69 | endpoints.MapDefaultControllerRoute(); 70 | 71 | }); 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Fritz.StreamTools/StartupServices/ConfigureSignalrTagHelperOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Fritz.StreamTools.TagHelpers; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace Fritz.StreamTools.StartupServices 11 | { 12 | 13 | public class ConfigureSignalrTagHelperOptions : IConfigureOptions 14 | { 15 | 16 | private readonly IWebHostEnvironment Env; 17 | private readonly ILogger Logger; 18 | 19 | public ConfigureSignalrTagHelperOptions(IWebHostEnvironment env, ILogger logger) 20 | { 21 | 22 | Env = env; 23 | Logger = logger; 24 | 25 | } 26 | 27 | public void Configure(SignalrTagHelperOptions options) 28 | { 29 | 30 | var folder = new DirectoryInfo(Path.Combine(Env.WebRootPath, "lib", "signalr")); 31 | 32 | var fileInfo = folder.Exists 33 | ? folder.GetFiles("signalr-client-*.min.js").OrderByDescending(f => f.Name).FirstOrDefault() 34 | : null; 35 | 36 | if (fileInfo == null) 37 | { 38 | var error = "Required signalr-client library not found."; 39 | Logger.LogCritical(error); 40 | throw new InvalidOperationException(error); 41 | } 42 | 43 | options.ClientLibarySource = $"/lib/signalr/{fileInfo.Name}"; 44 | 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Fritz.StreamTools/TagHelpers/GoogleFontTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fritz.StreamTools.TagHelpers 8 | { 9 | [HtmlTargetElement("google-font")] 10 | public class GoogleFontTagHelper : TagHelper 11 | { 12 | public override void Process(TagHelperContext context, TagHelperOutput output) 13 | { 14 | var content = Task.Run(function: async ()=> await output.GetChildContentAsync()).Result; 15 | var fontName = content.GetContent(); 16 | if (string.IsNullOrWhiteSpace(fontName)) 17 | return; 18 | 19 | output.TagName = "link"; 20 | output.TagMode = TagMode.StartTagOnly; 21 | output.Attributes.Add("rel", "stylesheet"); 22 | output.Attributes.Add("href", $"https://fonts.googleapis.com/css?family={fontName}"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Fritz.StreamTools/TagHelpers/SignalrTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamTools.StartupServices; 2 | using Microsoft.AspNetCore.Razor.TagHelpers; 3 | 4 | namespace Fritz.StreamTools.TagHelpers 5 | { 6 | 7 | //[HtmlTargetElement("signalr")] 8 | public class SignalrTagHelper : TagHelper 9 | { 10 | 11 | private readonly SignalrTagHelperOptions Options; 12 | 13 | public SignalrTagHelper(SignalrTagHelperOptions options) 14 | { 15 | Options = options; 16 | } 17 | 18 | public override void Process(TagHelperContext context, TagHelperOutput output) 19 | { 20 | 21 | output.TagName = "script"; 22 | output.TagMode = TagMode.StartTagAndEndTag; 23 | output.Attributes.Add("src", Options.ClientLibarySource); 24 | 25 | } 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /Fritz.StreamTools/TagHelpers/SignalrTagHelperOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Fritz.StreamTools.TagHelpers 2 | { 3 | 4 | public class SignalrTagHelperOptions 5 | { 6 | 7 | public string ClientLibarySource { get; set; } 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Fritz.StreamTools/TagHelpers/VersionTagHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.TagHelpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | 8 | namespace Fritz.StreamTools.TagHelpers 9 | { 10 | [HtmlTargetElement("version")] 11 | public class VersionTagHelper : TagHelper 12 | { 13 | public enum VersionType 14 | { 15 | FileVersion = 1, 16 | ProductVersion = 2, 17 | InformationalVersion = 2, 18 | AssemblyVersion = 3 19 | } 20 | 21 | /// 22 | /// Assembly represents the type of the assembly to be versioned. 23 | /// 24 | [HtmlAttributeName("assembly")] 25 | public System.Type AssemblyType { get; set; } 26 | 27 | /// 28 | /// Type represents the type of version to be returned 29 | /// One of FileVersion, ProductVersion or AssemblyVersion 30 | /// Defaults to ProductVersion (InformationalVersion) 31 | /// 32 | [HtmlAttributeName("type")] 33 | public VersionType Type { get; set; } = VersionType.ProductVersion; 34 | 35 | public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 36 | { 37 | var versionString = ""; 38 | output.TagName = "span"; 39 | output.TagMode = TagMode.StartTagAndEndTag; 40 | var childContent = await output.GetChildContentAsync(); 41 | output.Content.AppendHtml(childContent); 42 | if (AssemblyType == null) 43 | { 44 | AssemblyType = GetType(); 45 | } 46 | switch (Type) 47 | { 48 | case VersionType.FileVersion: 49 | versionString = AssemblyType 50 | .GetTypeInfo().Assembly 51 | .GetCustomAttribute()? 52 | .Version ?? ""; 53 | break; 54 | case VersionType.ProductVersion: // also covers VersionType.InformationalVersion 55 | versionString = AssemblyType 56 | .GetTypeInfo().Assembly 57 | .GetCustomAttribute()? 58 | .InformationalVersion ?? ""; 59 | break; 60 | case VersionType.AssemblyVersion: 61 | versionString = AssemblyType 62 | .Assembly.GetName().Version.ToString(); 63 | break; 64 | default: 65 | break; 66 | } 67 | output.Content.Append(versionString); 68 | 69 | await base.ProcessAsync(context, output); 70 | return; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Fritz.StreamTools/ViewComponents/FooterViewComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fritz.StreamTools.ViewComponents 8 | { 9 | 10 | public class FooterViewComponent : ViewComponent 11 | { 12 | 13 | public async Task InvokeAsync() 14 | { 15 | await Task.Delay(0); 16 | return View("default"); 17 | 18 | } 19 | 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Attention/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | Attention 10 | 11 | 12 | 13 | 14 | 15 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Attention/Points.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | Channel Points Alert 10 | 11 | 54 | 55 | 56 | 57 | 58 |
59 | 60 | 61 | 62 | 65 | 70 | 71 |
63 |
64 |
66 |

has just redeemed

67 |

for Fritz Bitz

68 |

69 |
72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Attention/TestClient.cshtml: -------------------------------------------------------------------------------- 1 |  2 | @{ 3 | ViewData["Title"] = "Test Client"; 4 | } 5 | 6 |

Test Client

7 | 8 | Send Alert 9 | 10 | @section scripts { 11 | 12 | 13 | 24 | } 25 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Followers/Count.cshtml: -------------------------------------------------------------------------------- 1 | @model FollowerCountConfiguration 2 | @{ 3 | Layout = null; 4 | } 5 | 6 | 7 | 8 | 9 | 10 | Follower Count 11 | 12 | 13 | 38 | 39 | 40 | 41 | @Model.CurrentValue 42 | 43 | 44 | 45 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Followers/Goal.cshtml: -------------------------------------------------------------------------------- 1 | @model FollowerGoalConfiguration 2 | @{ 3 | Layout = null; 4 | } 5 | 6 | 7 | 8 | 9 | 10 | Followers Goal 11 | @Model.FontName 12 | 24 | 61 | 62 | 63 | 64 |
65 |
66 | @Model.Caption 67 | @Model.CurrentValue 68 | @Model.Goal 69 |
70 |
71 |
72 | @Model.Caption 73 | @Model.CurrentValue 74 | @Model.Goal 75 |
76 | 77 | 78 | 79 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/GitHub/Configuration.cshtml: -------------------------------------------------------------------------------- 1 | @model GitHubConfiguration 2 | 3 |

GitHubConfiguration

4 | 5 |
6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/GitHub/ContributorsInformation.cshtml: -------------------------------------------------------------------------------- 1 | @model GitHubInformation 2 | @{ 3 | Layout = null; 4 | } 5 | 6 | 7 | 8 | 9 | 10 | 40 | 41 | 42 | 43 | 44 |
45 | 46 |

Top week:

47 | @if (Model.TopWeekContributors.Count() == 0) { 48 | No contributors this week 49 | } else { 50 | 51 | @foreach (var item in Model.TopWeekContributors) 52 | { 53 | @Html.DisplayFor(modelItem => item.Author) 54 | @Html.DisplayFor(modelItem => item.Commits) 55 | } 56 | 57 | } 58 | 59 |

Top month:

60 | 61 | @foreach (var item in Model.TopMonthContributors) 62 | { 63 | @Html.DisplayFor(modelItem => item.Author) 64 | @Html.DisplayFor(modelItem => item.Commits) 65 | } 66 | 67 | 68 |

Top All Time:

69 | 70 | @foreach (var item in Model.TopEverContributors) 71 | { 72 | @Html.DisplayFor(modelItem => item.Author) 73 | @Html.DisplayFor(modelItem => item.Commits) 74 | } 75 | 76 | 77 |
78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/GitHub/_ContributorTickerSegment.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | 3 | @foreach (var item in Model) 4 | { 5 | @item.Author @item.Commits 6 | } 7 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 

Stream Tools for Streamers from Streamers!

2 | 3 |

4 | Incredible, awe-inspiring text full of buzz words that will hype up streamers to use the awesome tools built by Jeff and his friends! 5 |

6 | 7 | 8 |

Available Widgets

9 |
10 | 11 |
12 |

Current Viewers

13 | 14 |
15 | /CurrentViewers 16 |
17 | 18 |
19 |

Current Followers

20 | 21 |
22 | /Followers/Count 23 |     Configure 24 |
25 | 26 |
27 |

Follower Goal

28 | 29 |
30 | /Followers/Goal 31 |
32 | 33 |
34 |

GitHub Information

35 | /GitHub/ContributorsInformation 36 | Configure 37 |
38 |
39 | 40 | @section styles { 41 | 49 | } 50 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Shared/Components/Footer/Default.cshtml: -------------------------------------------------------------------------------- 1 |  2 |
3 | 4 | © @DateTime.Now.Year - Jeff Fritz and Friends 5 | 6 |
-------------------------------------------------------------------------------- /Fritz.StreamTools/Views/Shared/_Footer.cshtml: -------------------------------------------------------------------------------- 1 |  2 |
3 |
4 | © @DateTime.Now.Year - Jeff Fritz and Friends 5 | - 6 |
7 |
8 | -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Fritz.StreamTools.Models 2 | @using Microsoft.Extensions.Options 3 | @using Microsoft.AspNetCore.Hosting 4 | 5 | @addTagHelper *, Fritz.StreamTools 6 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /Fritz.StreamTools/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Fritz.StreamTools/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | //"Default": "Debug", 6 | //"System": "Information", 7 | //"Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Fritz.StreamTools/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFileName": "wwwroot/css/site.min.css", 4 | "inputFiles": [ 5 | "wwwroot/css/site.css" 6 | ] 7 | }, 8 | { 9 | "outputFileName": "wwwroot/js/site.min.js", 10 | "inputFiles": [ 11 | "wwwroot/js/site.js" 12 | ], 13 | "minify": { 14 | "enabled": false 15 | }, 16 | "sourceMap": false 17 | }, 18 | { 19 | "outputFileName": "wwwroot/js/GoalConfiguration.min.js", 20 | "inputFiles": [ 21 | "wwwroot/js/GoalConfiguration/GoogleFonts.js", 22 | "wwwroot/js/GoalConfiguration/Preview.js" 23 | ,"wwwroot/js/GoalConfiguration/GoalConfiguration.js" 24 | ], 25 | "minify": { 26 | "enabled": true 27 | }, 28 | "sourceMap": false 29 | }, 30 | { 31 | "outputFileName": "wwwroot/lib/signalr/signalr-client.js", 32 | "inputFiles": [ 33 | "node_modules/@microsoft/signalr/dist/browser/signalr.min.js", 34 | "node_modules/msgpack5/dist/msgpack5.js", 35 | "node_modules/@microsoft/signalr-protocol-msgpack/dist/browser/signalr-protocol-msgpack.js" 36 | ], 37 | "minify": { 38 | "enabled": false 39 | } 40 | }, 41 | { 42 | "outputFileName": "wwwroot/lib/jquery.js", 43 | "inputFiles": [ 44 | "node_modules/jquery/dist/jquery.js", 45 | "node_modules/jquery-validation/dist/jquery.validate.js", 46 | "node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js" 47 | ], 48 | "minify": { 49 | "enabled": false 50 | } 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /Fritz.StreamTools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.1", 3 | "name": "fritz.streamtools", 4 | "private": true, 5 | "devDependencies": { 6 | "del": "^3.0.0", 7 | "gulp": "4.0.2", 8 | "gulp-concat": "2.6.1", 9 | "gulp-cssmin": "0.2.0", 10 | "gulp-htmlmin": "5.0.1", 11 | "gulp-uglify": "3.0.2", 12 | "merge-stream": "2.0.0" 13 | }, 14 | "dependencies": { 15 | "@microsoft/signalr": "6.0.2", 16 | "@microsoft/signalr-protocol-msgpack": "6.0.2", 17 | "jquery": "^3.3.1", 18 | "jquery-validation": "^1.17.0", 19 | "jquery-validation-unobtrusive": "^3.2.9" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/animatedProgress.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

CSS progress bar with inverted colors

10 |
11 |
12 |
13 | LEFT 14 | 54% 15 | RIGHT 16 |
17 |
18 |
19 |
20 | LEFT 21 | 54% 22 | RIGHT 23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/contents/Scott!!.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/contents/Scott!!.mp3 -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/contents/hey_listen.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/contents/hey_listen.mp3 -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/contents/img/teamlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/contents/img/teamlogo.png -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/css/animatedProgress.scss: -------------------------------------------------------------------------------- 1 | $bar-width: 400px; 2 | $bar-height: 40px; 3 | 4 | h1 { 5 | text-align: center; 6 | font-family: "Trebuchet MS", Helvetica, sans-serif; 7 | font-weight: normal; 8 | margin: 50px 0 0 9 | } 10 | 11 | .progress-bar { 12 | position: relative; 13 | border: #ccc solid 1px; 14 | width: $bar-width; 15 | height: $bar-height; 16 | line-height: $bar-height; 17 | vertical-align: middle; 18 | overflow: hidden; 19 | margin: 100px auto 50px; 20 | font-family: arial, sans-serif; 21 | font-weight: bold; 22 | font-size: 30px; 23 | box-shadow: 0 4px 10px -5px rgba(0, 0, 0, 0.25); 24 | 25 | .bar { 26 | position: absolute; 27 | top: 0; 28 | height: 100%; 29 | overflow: hidden; 30 | 31 | div { 32 | position: absolute; 33 | display: flex; 34 | width: $bar-width; 35 | height: 100%; 36 | text-align: center; 37 | } 38 | 39 | &.positive { 40 | background: #fff; 41 | left: 0; 42 | width: 54%; 43 | 44 | span { 45 | left: 0; 46 | color: #333; 47 | } 48 | 49 | -webkit-animation: animate-positive 4s; 50 | animation: animate-positive 4s; 51 | } 52 | 53 | &.negative { 54 | background: #07A4DD; 55 | right: 0; 56 | width: 46%; 57 | 58 | div { 59 | right: 0; 60 | color: #fff; 61 | } 62 | 63 | -webkit-animation: animate-negative 4s; 64 | animation: animate-negative 4s; 65 | } 66 | } 67 | } 68 | 69 | @-webkit-keyframes animate-positive { 70 | 0% { 71 | width: 0%; 72 | } 73 | } 74 | 75 | @keyframes animate-positive { 76 | 0% { 77 | width: 0%; 78 | } 79 | } 80 | 81 | @-webkit-keyframes animate-negative { 82 | 0% { 83 | width: 100%; 84 | } 85 | } 86 | 87 | @keyframes animate-negative { 88 | 0% { 89 | width: 100%; 90 | } 91 | } 92 | 93 | .content { 94 | width: 100%; 95 | display: flex; 96 | flex-wrap: nowrap; 97 | } 98 | 99 | .content span { 100 | flex: 1; 101 | } 102 | 103 | .left { 104 | text-align: left; 105 | } 106 | 107 | .right { 108 | text-align: right; 109 | } 110 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | html{position:relative;min-height:100%}body{margin-bottom:60px}.footer{position:absolute;bottom:0;width:100%;height:60px;line-height:60px;background-color:#f5f5f5}body>.container{padding:60px 15px 0}.footer>.container{padding-right:15px;padding-left:15px}code{font-size:80%}iframe#widgetPreview{height:60px;width:100%;border:0}#fontsPanel{position:absolute;z-index:1}a#outputUrl{font-size:80%}input[type=color]{padding:0;width:44px;cursor:pointer}.ryg-gauge{background:linear-gradient(90deg,rgba(255,0,0,1) 0%,rgba(213,255,0,1) 50%,rgba(0,71,0,1) 100%);top:46px;left:11px;display:block;width:300px;height:15px}#sentimentBlock{display:block;height:100px;width:320px;border:2px solid #fff;position:relative}#sentimentBlock>label{display:block;text-align:right;font-family:'Uni Sans';font-weight:bold;font-size:22px;color:#fff;text-transform:uppercase;text-shadow:2px 2px 2px black;position:absolute;width:310px}#sentimentBlock>.toprow{top:14px}#sentimentBlock>.bottomrow{top:68px}#brands{left:10px;position:absolute;display:block}#brands>i{font-size:22px;color:#fff}.sentimentGauge{position:absolute;top:46px;left:11px;display:block;width:300px;height:15px}#constrain{overflow:hidden;position:absolute;top:46px;left:11px;height:15px}.todoWidget{background-color:#200a48;display:grid;align-content:start;width:270px;height:270px;overflow:hidden;margin:0;padding:0}.todoWidget{background-color:#200a48;width:270px;height:270px;min-height:270px;overflow:hidden;margin:0;padding:0;display:grid}.todoWidget *{font-family:'Uni Sans';font-weight:bold;color:#fff}.todoWidget li.active{color:#ff0;text-decoration:underline;font-weight:bold}.todoWidget h4{font-size:22px;margin:2px;text-align:center}.todoWidget ul{list-style-type:none;position:relative;margin:0;padding:2px}.todoWidget li{font-size:20px}#checklistContainer{overflow:hidden;position:relative;width:270px;min-height:200px}#checklistContainer ul{animation-iteration-count:infinite;animation-timing-function:linear}#checklistContainer ul:last-child{position:absolute} -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/img/sentiment-bar-divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/img/sentiment-bar-divider.png -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/GoalConfiguration/GoogleFonts.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | 'use strict'; 3 | 4 | window.fontsModule = { 5 | initModule: initModulePublic, 6 | getSupportedFonts: getSupportedFontsPublic, 7 | setSupportedFonts: setSupportedFontsPublic 8 | }; 9 | 10 | let googleFontsApiKey = ''; 11 | let supportedFonts = []; 12 | 13 | const defaultFonts = [ 'Arial', 'Helvetica', 'Times New Roman', 'Times', 'Courier New' ]; 14 | 15 | function initModulePublic(moduleConfig) { 16 | if (!moduleConfig.googleFontsApiKey) { 17 | log('No Google Fonts Api key provided'); 18 | } else { 19 | googleFontsApiKey = moduleConfig.googleFontsApiKey; 20 | } 21 | 22 | const cachedFonts = JSON.parse(localStorage.getItem('supportedFonts')); 23 | 24 | if (cachedFonts && Array.isArray(cachedFonts)) { 25 | log(`Setting [${cachedFonts.length}] supported fonts from local storage`); 26 | 27 | supportedFonts = cachedFonts; 28 | updateFontList(filterFontList(supportedFonts)); 29 | } else if (googleFontsApiKey) { 30 | requestGoogleFonts(); 31 | 32 | } else { 33 | useDefaultFonts(); 34 | } 35 | } 36 | 37 | function getSupportedFontsPublic() { 38 | return supportedFonts; 39 | } 40 | 41 | function setSupportedFontsPublic(fonts) { 42 | supportedFonts = fonts; 43 | 44 | localStorage.setItem('supportedFonts', JSON.stringify(supportedFonts)); 45 | updateFontList(filterFontList(supportedFonts)); 46 | } 47 | 48 | function requestGoogleFonts() { 49 | log('Requesting fonts list from google fonts api'); 50 | 51 | const requestUrl = `https://www.googleapis.com/webfonts/v1/webfonts?key=${googleFontsApiKey}`; 52 | 53 | const request = new Request(requestUrl); 54 | 55 | fetch(request) 56 | .then(response => response.json()) 57 | .then(responseData => responseData.items.map(v => v.family)) 58 | .then(fonts => setSupportedFontsPublic(fonts)) 59 | .catch(error => { 60 | log('Error requesting google fonts', error); 61 | 62 | useDefaultFonts(); 63 | }); 64 | } 65 | 66 | function useDefaultFonts() { 67 | log('Falling back to default fonts', defaultFonts); 68 | 69 | setSupportedFontsPublic(defaultFonts); 70 | } 71 | }(window)); 72 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/GoalConfiguration/Preview.js: -------------------------------------------------------------------------------- 1 | function InitPreview() { 2 | const quickPreviewButton = "preview"; 3 | const quickPreviewTextBoxes = ["preview", ConfigurationModel.Caption, 4 | ConfigurationModel.Goal, 5 | ConfigurationModel.CurrentValue, 6 | ConfigurationModel.EmptyBackgroundColor, 7 | ConfigurationModel.EmptyFontColor, 8 | ConfigurationModel.FillFontColor, 9 | "bgcolor1", "bgblend1"]; 10 | 11 | for (var tb of quickPreviewTextBoxes) { 12 | document.getElementById(tb).onchange = loadPreview; 13 | } 14 | 15 | document.getElementById(quickPreviewButton).onclick = loadPreview; 16 | 17 | 18 | } 19 | 20 | 21 | function loadPreview() { 22 | 23 | if (isLoadingFromStorage) return; 24 | 25 | const iframeWidth = document.getElementById("widgetPreview").clientWidth - 40; 26 | const fontName = document.getElementById(ConfigurationModel.FontName).value; 27 | 28 | const supportedFonts = window.fontsModule.getSupportedFonts(); 29 | 30 | if (supportedFonts.length > 0 && filterFontList(supportedFonts, fontName, filterExactMatchOperation).length !== 1) { 31 | alert('Font not supported by Google Fonts'); 32 | return; 33 | } 34 | 35 | var urlTemplate = "/followers/goal/"; 36 | urlTemplate += `${document.getElementById(ConfigurationModel.Goal).value}/`; 37 | urlTemplate += `${document.getElementById(ConfigurationModel.Caption).value}`; 38 | urlTemplate += `?width=${iframeWidth}`; 39 | urlTemplate += getBgColors(); 40 | urlTemplate += getBgBlend(); 41 | urlTemplate += `&${ConfigurationModel.EmptyBackgroundColor}=${escape(document.getElementById(ConfigurationModel.EmptyBackgroundColor).value)}`; 42 | urlTemplate += `&${ConfigurationModel.EmptyFontColor}=${escape(document.getElementById(ConfigurationModel.EmptyFontColor).value)}`; 43 | urlTemplate += `&${ConfigurationModel.FillFontColor}=${escape(document.getElementById(ConfigurationModel.FillFontColor).value)}`; 44 | urlTemplate += `&fontName=${escape(fontName)}`; 45 | 46 | document.getElementById("widgetPreview").src = urlTemplate + `&${ConfigurationModel.CurrentValue}=${document.getElementById(ConfigurationModel.CurrentValue).value}` 47 | log(urlTemplate); 48 | 49 | document.getElementById("outputUrl").textContent = urlTemplate; 50 | document.getElementById("outputUrl").href = urlTemplate; 51 | saveValues(); 52 | 53 | } 54 | 55 | // build the bgcolors parameter 56 | function getBgColors() { 57 | const spans = document.getElementsByName("spanBGColor"); 58 | var result = ""; 59 | if (spans) { 60 | for (let i = 0; i < spans.length; i++) { 61 | result += `,${escape(spans[i].getElementsByTagName('input')[0].value.trim())}`; 62 | } 63 | result = `&${ConfigurationModel.FillBackgroundColor}=${result.substr(1)}`; 64 | } 65 | return result; 66 | } 67 | 68 | // build the bgblend parameter 69 | function getBgBlend() { 70 | const spans = document.getElementsByName("spanBGColor"); 71 | var result = ""; 72 | if (spans) { 73 | for (let i = 0; i < spans.length; i++) { 74 | result += `,${spans[i].getElementsByTagName('input')[1].value}`; 75 | } 76 | result = `&${ConfigurationModel.FillBackgroundColorBlend}=${result.substr(1)}`; 77 | } 78 | return result; 79 | } 80 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/attentionhub.js: -------------------------------------------------------------------------------- 1 |  2 | class AttentionHub { 3 | constructor() { 4 | this.onAlertFritz = null; 5 | this.onSummonScott = null; 6 | this.onPlaySoundEffect = null; 7 | this.debug = true; 8 | this._hub = null; 9 | } 10 | 11 | start(groups) { 12 | let url = groups ? "/attentionhub?groups=" + groups : "/attentionhub"; 13 | this._hub = new signalR.HubConnectionBuilder() 14 | .withUrl(url) 15 | .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 16 | .withAutomaticReconnect([0, 2000, 10000, 15000, 20000, 30000, 30000, 30000, 30000, 30000, 45000, 60000 ]) 17 | .build(); 18 | 19 | this._hub.on("ClientConnected", connectionId => { 20 | if (this.debug) console.debug(`Client connected: ${connectionId}`); 21 | }); 22 | 23 | this._hub.on("AlertFritz", () => { 24 | if (this.debug) console.debug("AlertFritz"); 25 | if (this.onAlertFritz) this.onAlertFritz(); 26 | }); 27 | 28 | this._hub.on("SummonScott", () => { 29 | if (this.debug) console.debug("Summoning Scott!"); 30 | if (this.onSummonScott) this.onSummonScott(); 31 | }); 32 | 33 | this._hub.on("PlaySoundEffect", (fileName) => { 34 | if (this.debug) console.debug(`Playing file: ${fileName}`); 35 | if (this.onPlaySoundEffect) this.onPlaySoundEffect(fileName); 36 | }); 37 | 38 | this._hub.on("NotifyChannelPoints", redemption => { 39 | if (this.debug) console.debug(`Redeemed: ${redemption}`); 40 | if (this.onRedemption) this.onRedemption(redemption); 41 | }); 42 | 43 | this._hub.on("Teammate", teammate => { 44 | if (this.debug) console.debug(`Teammate arrived: ${teammate}`); 45 | if (this.onTeammate) this.onTeammate(teammate); 46 | }); 47 | 48 | 49 | return this._hub.start(); 50 | } 51 | 52 | sendTest() { 53 | if (this.debug) console.debug("sending AlertFritz"); 54 | this._hub.send("AlertFritz"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/mcGitHubbub.js: -------------------------------------------------------------------------------- 1 |  2 | class McGitHubbub { 3 | constructor() { 4 | this.onUpdated = null; 5 | this.debug = true; 6 | this._hub = null; 7 | } 8 | 9 | start(groups) { 10 | let url = (groups) ? "/github?groups=" + groups : "/github"; 11 | this._hub = new signalR.HubConnectionBuilder() 12 | .withUrl(url) 13 | .build(); 14 | 15 | this._hub.onclose(() => { 16 | if (this.debug) console.debug("hub connection closed"); 17 | 18 | // Hub connection was closed for some reason 19 | let interval = setInterval(() => { 20 | // Try to reconnect hub every 5 secs 21 | this.start(groups).then(() => { 22 | // Reconnect succeeded 23 | clearInterval(interval); 24 | if (this.debug) console.debug("hub reconnected"); 25 | }); 26 | }, 5000); 27 | }); 28 | 29 | this._hub.on('OnGitHubUpdated', (repository, userName, commits) => { 30 | if (this.debug) console.debug("OnGitHubUpdated", { repository, userName, commits }); 31 | if (this.onUpdated) this.onUpdated(repository, userName, commits); 32 | }); 33 | 34 | return this._hub.start(); 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/js/streamhub.js: -------------------------------------------------------------------------------- 1 |  2 | class StreamHub { 3 | constructor() { 4 | this.onFollowers = null; 5 | this.onViewers = null; 6 | this.onSentiment = null; 7 | this.debug = true; 8 | this._hub = null; 9 | } 10 | 11 | start(groups) { 12 | let url = (groups) ? "/followerstream?groups=" + groups : "/followerstream"; 13 | this._hub = new signalR.HubConnectionBuilder() 14 | .withUrl(url) 15 | .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 16 | .build(); 17 | 18 | this._hub.onclose(() => { 19 | if (this.debug) console.debug("hub connection closed"); 20 | 21 | // Hub connection was closed for some reason 22 | let interval = setInterval(() => { 23 | // Try to reconnect hub every 5 secs 24 | this.start(groups).then(() => { 25 | // Reconnect succeeded 26 | clearInterval(interval); 27 | if (this.debug) console.debug("hub reconnected"); 28 | }); 29 | }, 5000); 30 | }); 31 | 32 | this._hub.on('OnFollowersCountUpdated', followerCount => { 33 | if (this.debug) console.debug("OnFollowersCountUpdated", { followerCount }); 34 | if (this.onFollowers) this.onFollowers(followerCount); 35 | }); 36 | this._hub.on('OnViewersCountUpdated', (serviceName, viewerCount) => { 37 | if (this.debug) console.debug("OnViewersCountUpdated", { serviceName, viewerCount }); 38 | if (this.onViewers) this.onViewers(serviceName, viewerCount); 39 | }); 40 | this._hub.on('OnSentimentUpdated', (newSentiment, oneMinute, fiveMinute, all) => { 41 | if (this.debug) console.debug("OnSentimentUpdated", { newSentiment, oneMinute, fiveMinute, all }); 42 | if (this.onSentiment) this.onSentiment(newSentiment, oneMinute, fiveMinute, all); 43 | }); 44 | 45 | return this._hub.start(); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/Fritz.StreamTools/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Fritz.StreamTools/wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /Fritz.Twitch/AspNetExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Fritz.Twitch 7 | { 8 | 9 | public static class AspNetExtensions 10 | { 11 | 12 | public static IServiceCollection AddTwitchClient(this IServiceCollection services) 13 | { 14 | 15 | services.AddHttpClient(); 16 | services.AddSingleton(); 17 | 18 | return services; 19 | 20 | } 21 | 22 | private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 23 | 24 | /// 25 | /// Convert a Unix timestamp to a .NET DateTime 26 | /// 27 | /// 28 | /// 29 | public static DateTime ToDateTime(this long unixTime) 30 | { 31 | return epoch.AddSeconds(unixTime); 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Fritz.Twitch/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | 7 | [assembly:InternalsVisibleTo("Test")] -------------------------------------------------------------------------------- /Fritz.Twitch/ChatConnectedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch 4 | { 5 | public class ChatConnectedEventArgs : EventArgs 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Fritz.Twitch/ChatUserJoinedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch 4 | { 5 | public class ChatUserJoinedEventArgs : EventArgs 6 | { 7 | 8 | public string UserName { get; set; } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Fritz.Twitch/ConfigurationSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Fritz.Twitch 6 | { 7 | public class ConfigurationSettings 8 | { 9 | 10 | public virtual string ChannelName { get; set; } 11 | 12 | public virtual string ClientId { get; set; } 13 | 14 | public virtual string UserId { get; set; } 15 | 16 | public virtual string ChatBotName { get; set; } 17 | 18 | public virtual string OAuthToken { get; set; } 19 | 20 | public virtual string PubSubAuthToken { get; set; } 21 | 22 | [Obsolete] 23 | public string Channel { get => ChannelName; set => ChannelName = value; } 24 | 25 | [Obsolete] 26 | public string ChatToken { get => OAuthToken; set => OAuthToken = value; } 27 | 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Fritz.Twitch/Fritz.Twitch.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Fritz.Twitch/NewFollowersEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch 4 | { 5 | public class NewFollowersEventArgs : EventArgs 6 | { 7 | private int foundFollowerCount; 8 | 9 | public NewFollowersEventArgs(int foundFollowerCount) 10 | { 11 | this.foundFollowerCount = foundFollowerCount; 12 | } 13 | 14 | public int FollowerCount => foundFollowerCount; 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Fritz.Twitch/NewMessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch 4 | { 5 | public class NewMessageEventArgs : EventArgs 6 | { 7 | 8 | public string UserName { get; set; } 9 | 10 | public string Message { get; set; } 11 | 12 | public string[] Badges { get; set; } 13 | 14 | public bool IsWhisper { get; set; } = false; 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Fritz.Twitch/NewViewersEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch 4 | { 5 | public class NewViewersEventArgs : EventArgs 6 | { 7 | private int foundViewerCount; 8 | 9 | public NewViewersEventArgs(int foundViewerCount) 10 | { 11 | this.foundViewerCount = foundViewerCount; 12 | } 13 | 14 | public int ViewerCount => foundViewerCount; 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Fritz.Twitch/PubSub/ChannelRedemption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch.PubSub 4 | { 5 | 6 | public class PubSubRedemptionMessage : PubSubMessage { } 7 | 8 | public class ChannelRedemption : EventArgs, IPubSubData 9 | { 10 | public string timestamp { get; set; } 11 | public Redemption redemption { get; set; } 12 | } 13 | 14 | public class Redemption 15 | { 16 | public string id { get; set; } 17 | public User user { get; set; } 18 | public string channel_id { get; set; } 19 | public string redeemed_at { get; set; } 20 | public Reward reward { get; set; } 21 | public string user_input { get; set; } 22 | public string status { get; set; } 23 | } 24 | 25 | public class User 26 | { 27 | public string id { get; set; } 28 | public string login { get; set; } 29 | public string display_name { get; set; } 30 | } 31 | 32 | public class Reward 33 | { 34 | public string id { get; set; } 35 | public string channel_id { get; set; } 36 | public string title { get; set; } 37 | public string prompt { get; set; } 38 | public int cost { get; set; } 39 | public bool is_user_input_required { get; set; } 40 | public bool is_sub_only { get; set; } 41 | public Image image { get; set; } 42 | public Default_Image default_image { get; set; } 43 | public string background_color { get; set; } 44 | public bool is_enabled { get; set; } 45 | public bool is_paused { get; set; } 46 | public bool is_in_stock { get; set; } 47 | public Max_Per_Stream max_per_stream { get; set; } 48 | public bool should_redemptions_skip_request_queue { get; set; } 49 | } 50 | 51 | public class Image 52 | { 53 | public string url_1x { get; set; } 54 | public string url_2x { get; set; } 55 | public string url_4x { get; set; } 56 | } 57 | 58 | public class Default_Image 59 | { 60 | public string url_1x { get; set; } 61 | public string url_2x { get; set; } 62 | public string url_4x { get; set; } 63 | } 64 | 65 | public class Max_Per_Stream 66 | { 67 | public bool is_enabled { get; set; } 68 | public int max_per_stream { get; set; } 69 | } 70 | 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Fritz.Twitch/PubSub/PubSubListenMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Fritz.Twitch.PubSub 6 | { 7 | 8 | public class PubSubListen : PubSubMessage 9 | { 10 | 11 | public PubSubListen() 12 | { 13 | type = "LISTEN"; 14 | } 15 | 16 | public class PubSubListenData : IPubSubData 17 | { 18 | public string[] topics { get; set; } 19 | public string auth_token { get; set; } 20 | } 21 | 22 | 23 | 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Fritz.Twitch/PubSub/PubSubMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fritz.Twitch.PubSub 4 | { 5 | 6 | public abstract class PubSubMessage where DataType : IPubSubData 7 | { 8 | 9 | public string type { get; protected set; } 10 | 11 | public string nonce { get; set; } = Guid.NewGuid().ToString().Replace("-", ""); 12 | 13 | public DataType data { get; set; } 14 | 15 | } 16 | 17 | public interface IPubSubData { } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Fritz.Twitch/PubSub/UnhandledPubSubMessageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Fritz.Twitch.PubSub 5 | { 6 | [Serializable] 7 | internal class UnhandledPubSubMessageException : Exception 8 | { 9 | public UnhandledPubSubMessageException() 10 | { 11 | } 12 | 13 | public UnhandledPubSubMessageException(string message) : base(message) 14 | { 15 | } 16 | 17 | public UnhandledPubSubMessageException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fritz.Twitch/StreamData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Nodes; 3 | 4 | namespace Fritz.Twitch 5 | { 6 | public class StreamData 7 | { 8 | 9 | public long Id { get; set; } 10 | 11 | public long UserId { get; set; } 12 | 13 | public long GameId { get; set; } 14 | 15 | public string Type { get; set; } 16 | 17 | public string Title { get; set; } 18 | 19 | public int ViewerCount { get; set; } 20 | 21 | public DateTime StartedAt { get; set; } 22 | 23 | public string Language { get; set; } 24 | 25 | public static explicit operator StreamData(JsonNode obj) 26 | { 27 | 28 | return new StreamData 29 | { 30 | Id = obj["id"].GetValue(), 31 | UserId = obj["user_id"].GetValue(), 32 | GameId = obj["game_id"].GetValue(), 33 | Type = obj["type"].GetValue(), 34 | Title = obj["title"].GetValue(), 35 | ViewerCount = obj["viewer_count"].GetValue(), 36 | StartedAt = obj["started_at"].GetValue(), 37 | Language = obj["language"].GetValue() 38 | }; 39 | 40 | } 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Fritz.Twitch/TwitchTopic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Fritz.Twitch 4 | { 5 | 6 | public sealed class TwitchTopic 7 | { 8 | 9 | private TwitchTopic() { } 10 | 11 | public string TopicString { get; set; } 12 | 13 | 14 | public static TwitchTopic ChannelPoints(string channelId) { 15 | 16 | return new TwitchTopic 17 | { 18 | TopicString = $"channel-points-channel-v1.{channelId}" 19 | }; 20 | 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jeffrey T. Fritz 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 | -------------------------------------------------------------------------------- /StreamAnalytics/Function1.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.IO; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Extensions.Http; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Newtonsoft.Json; 9 | using System.Net.Http; 10 | using System.Threading.Tasks; 11 | 12 | namespace StreamAnalytics 13 | { 14 | public static class Function1 15 | { 16 | [FunctionName("Function1")] 17 | public static IActionResult Run( 18 | [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage message, 19 | TraceWriter log, 20 | [CosmosDB("MyCosmosAnalyticsDatabase", "NewFollowers", ConnectionStringSetting ="SomeEnvironmentVariable")]out NewFollower newFollower 21 | ) 22 | { 23 | log.Info("C# HTTP trigger function processed a request."); 24 | 25 | var task = message.Content.ReadAsAsync(); 26 | Task.WaitAll(); 27 | newFollower = task.Result; 28 | 29 | 30 | //string requestBody = new StreamReader(req.Body).ReadToEnd(); 31 | //dynamic data = JsonConvert.DeserializeObject(requestBody); 32 | //name = name ?? data?.name; 33 | 34 | return newFollower != null 35 | ? (ActionResult)new OkObjectResult($"New Follower on {newFollower.StreamService}: {newFollower.Handle}") 36 | : new BadRequestObjectResult("Bad format..."); 37 | } 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /StreamAnalytics/NewFollower.cs: -------------------------------------------------------------------------------- 1 | namespace StreamAnalytics 2 | { 3 | public class NewFollower 4 | { 5 | 6 | public string Handle { get; set; } 7 | 8 | public string StreamService { get; set; } 9 | 10 | } 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /StreamAnalytics/StreamAnalytics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | PreserveNewest 20 | Never 21 | 22 | 23 | -------------------------------------------------------------------------------- /StreamAnalytics/host.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /Test/AutoMoqDataAttribute.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using AutoFixture.AutoMoq; 3 | using AutoFixture.Xunit2; 4 | 5 | namespace Test 6 | { 7 | 8 | public class AutoMoqDataAttribute : AutoDataAttribute 9 | { 10 | 11 | public AutoMoqDataAttribute() 12 | : base(() => new Fixture().Customize(new AutoMoqCustomization())) 13 | { 14 | } 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Test/AutoMoqStreamServiceWithNameAndCountAttribute.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using AutoFixture.AutoMoq; 3 | using AutoFixture.Xunit2; 4 | 5 | namespace Test 6 | { 7 | 8 | public class AutoMoqStreamServiceWithNameAndCountAttribute : AutoDataAttribute 9 | { 10 | 11 | public AutoMoqStreamServiceWithNameAndCountAttribute() 12 | : base(() => new Fixture().Customize( 13 | new CompositeCustomization( 14 | new AutoMoqStreamServiceWithNameAndCountCustomization(), 15 | new AutoMoqCustomization()))) 16 | { 17 | } 18 | 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /Test/AutoMoqStreamServiceWithNameAndCountCustomization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AutoFixture; 3 | using AutoFixture.Kernel; 4 | using Fritz.StreamLib.Core; 5 | using Fritz.StreamTools.Services; 6 | using Moq; 7 | 8 | namespace Test 9 | { 10 | 11 | public class AutoMoqStreamServiceWithNameAndCountCustomization : ICustomization 12 | { 13 | 14 | public void Customize(IFixture fixture) 15 | { 16 | 17 | fixture.Customizations.Add(new SpecimenBuilder()); 18 | 19 | } 20 | 21 | private class SpecimenBuilder : ISpecimenBuilder 22 | { 23 | 24 | public object Create(object request, ISpecimenContext context) 25 | { 26 | 27 | var type = request as Type; 28 | 29 | if (type == null || type != typeof(IStreamService)) 30 | { 31 | return new NoSpecimen(); 32 | } 33 | 34 | var fixture = new Fixture(); 35 | var mock = new Mock(); 36 | mock.SetupGet(s => s.CurrentFollowerCount).Returns(fixture.Create()); 37 | mock.SetupGet(s => s.CurrentViewerCount).Returns(fixture.Create()); 38 | mock.SetupGet(s => s.Name).Returns(fixture.Create()); 39 | 40 | return mock.Object; 41 | 42 | } 43 | 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Test/BaseFixture.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Test 7 | { 8 | 9 | 10 | public abstract class BaseFixture 11 | { 12 | 13 | public BaseFixture() 14 | { 15 | Mockery = new MockRepository(MockBehavior.Loose); 16 | } 17 | 18 | protected MockRepository Mockery { get; private set; } 19 | 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /Test/Chatbot/ShoutoutCommandFixture.cs: -------------------------------------------------------------------------------- 1 | using Fritz.Chatbot.Commands; 2 | using Fritz.StreamLib.Core; 3 | using Microsoft.Extensions.Logging; 4 | using Moq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace Test.Chatbot 14 | { 15 | 16 | public class ShoutoutCommandFixture 17 | { 18 | private readonly MockRepository _Mockery; 19 | private readonly Mock _ClientFactory; 20 | private readonly Mock _ChatService; 21 | private readonly ILoggerFactory _Logger; 22 | 23 | public ShoutoutCommandFixture(ITestOutputHelper output) 24 | { 25 | _Mockery = new MockRepository(MockBehavior.Default); 26 | _ClientFactory = new Mock(); 27 | _ChatService = new Mock(); 28 | 29 | var thisLogger = new Mock(); 30 | thisLogger.Setup(f => f.CreateLogger(It.IsAny())).Returns( new XunitLogger(output)); 31 | _Logger = thisLogger.Object; 32 | 33 | } 34 | 35 | 36 | [Fact] 37 | public async Task ShouldShoutoutCsharpfritz() { 38 | 39 | // arrange 40 | var client = new HttpClient(); 41 | client.DefaultRequestHeaders.Add("client-id", "t7y5txan5q662t7zj7p3l4wlth8zhv"); 42 | _ClientFactory.Setup(f => f.CreateClient(It.IsAny())) 43 | .Returns(client); 44 | var sut = new ShoutoutCommand(_ClientFactory.Object, _Logger); 45 | _ChatService.Setup(c => c.SendMessageAsync(It.Is(s => s.StartsWith("Please follow @csharpfritz")))).Verifiable(); 46 | 47 | // act 48 | await sut.Execute(_ChatService.Object, "csharpfritz", false, false, true, "csharpfritz".ToCharArray()); 49 | 50 | // assert 51 | _ChatService.Verify(); 52 | 53 | 54 | } 55 | 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Test/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Readability", "RCS1018:Add default access modifier.", Justification = "")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "RCS1141:Add parameter to documentation comment.", Justification = "")] 9 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "")] 10 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "RCS1090:Call 'ConfigureAwait(false)'.", Justification = "No SynchronizationContext in AspNet core")] 11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0007:Use implicit type", Justification = "Dont want to use var for bool, int ...")] 12 | -------------------------------------------------------------------------------- /Test/ImageCommand/MessageTest.cs: -------------------------------------------------------------------------------- 1 | using Fritz.Chatbot.Commands; 2 | using Fritz.StreamLib.Core; 3 | using Moq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace Test.ImageCommand 13 | { 14 | public class MessageTest 15 | { 16 | 17 | private readonly MockRepository _Mockery; 18 | private const string url = "https://sites.google.com/site/commodoren25semiacousticguitar/_/rsrc/1436296758012/basschat/Commodore%20Semi%20-%20Acoustic%20Bass%20Guitar%20%28006%29.jpg"; 19 | 20 | public ITestOutputHelper Output { get; } 21 | 22 | public MessageTest(ITestOutputHelper outputHelper) 23 | { 24 | _Mockery = new MockRepository(MockBehavior.Loose); 25 | this.Output = outputHelper; 26 | } 27 | 28 | 29 | [Fact] 30 | public void Contains_URL_With_Image_Extension() 31 | { 32 | var pattern = @"http(s)?:?(\/\/[^""']*\.(?:png|jpg|jpeg|gif))"; 33 | 34 | var message = $"this is the photo {url}"; 35 | 36 | // Instantiate the regular expression object. 37 | var r = new Regex(pattern, RegexOptions.IgnoreCase); 38 | 39 | // Match the regular expression pattern against a text string. 40 | var m = r.Match(message); 41 | 42 | Assert.True(m.Captures.Count > 0); 43 | Assert.Equal(url, m.Captures[0].Value); 44 | 45 | } 46 | 47 | [Fact(Skip ="Azure test")] 48 | public async void Identifies_Guitar() 49 | { 50 | 51 | // Arrange 52 | string outDescription = ""; 53 | var chatService = _Mockery.Create(); 54 | chatService 55 | .Setup(c => c.SendMessageAsync(It.IsAny())) 56 | .Callback(msg => 57 | { 58 | outDescription = msg; 59 | Output.WriteLine(msg); 60 | }) 61 | .Returns(Task.FromResult(true)); 62 | 63 | var sut = new ImageDescriptorCommand( 64 | "testlocation", 65 | "testkey"); 66 | 67 | // Act 68 | //var urlToTest = "https://media.discordapp.net/attachments/336580722653528075/550152641632534558/unknown.png?width=1020&height=574"; 69 | await sut.Execute(chatService.Object, "test", url); 70 | 71 | // Assert 72 | Assert.Contains("guitar", outDescription); 73 | 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Test/Services/StreamService/FollowerCountByService.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Fritz.StreamTools.Services; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace Test.Services.StreamService 7 | { 8 | 9 | public class FollowerCountByService 10 | { 11 | 12 | [Theory] 13 | [AutoMoqStreamServiceWithNameAndCount] 14 | public void ShouldCountSeparately(IStreamService jeffStreams, IStreamService otherStreamService) 15 | { 16 | 17 | // arrange 18 | 19 | // act 20 | var sut = new Fritz.StreamTools.Services.StreamService(new[] { jeffStreams, otherStreamService }); 21 | var count = sut.FollowerCountByService.ToList(); 22 | 23 | // assert 24 | Assert.Equal(2, count.Count()); 25 | Assert.Contains(count, c => c.service == jeffStreams.Name); 26 | Assert.Contains(count, c => c.service == otherStreamService.Name); 27 | Assert.Equal(jeffStreams.CurrentFollowerCount, count.First(c => c.service == jeffStreams.Name).count); 28 | Assert.Equal(otherStreamService.CurrentFollowerCount, count.First(c => c.service == otherStreamService.Name).count); 29 | 30 | 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Test/Services/StreamService/Sum.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Fritz.StreamTools.Services; 3 | using Xunit; 4 | 5 | namespace Test.Services.StreamService 6 | { 7 | 8 | 9 | public class Sum 10 | { 11 | 12 | [Theory] 13 | [AutoMoqStreamServiceWithNameAndCount] 14 | public void CurrentFollowerCountHandlesOneService(IStreamService jeffStreams) 15 | { 16 | 17 | 18 | // arrange 19 | 20 | // act 21 | var sut = new Fritz.StreamTools.Services.StreamService(new[] { jeffStreams }); 22 | var sum = sut.CurrentFollowerCount; 23 | 24 | // assert 25 | Assert.Equal(jeffStreams.CurrentFollowerCount, sum); 26 | 27 | } 28 | 29 | [Theory] 30 | [AutoMoqStreamServiceWithNameAndCount] 31 | public void CurrentFollowerCountHandlesMultipleServices(IStreamService jeffStreams, IStreamService otherStreamService) 32 | { 33 | 34 | 35 | // arrange 36 | 37 | // act 38 | var sut = new Fritz.StreamTools.Services.StreamService(new[] { jeffStreams, otherStreamService }); 39 | var sum = sut.CurrentFollowerCount; 40 | 41 | // assert 42 | Assert.Equal(jeffStreams.CurrentFollowerCount + otherStreamService.CurrentFollowerCount, sum); 43 | 44 | } 45 | 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Test/Services/StreamService/ViewerCountByService.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Fritz.StreamTools.Services; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace Test.Services.StreamService 7 | { 8 | 9 | public class ViewerCountByService 10 | { 11 | 12 | [Theory] 13 | [AutoMoqStreamServiceWithNameAndCount] 14 | 15 | public void ShouldCountSeparately(IStreamService jeffStreams, IStreamService otherStreamService) 16 | { 17 | 18 | // arrange 19 | 20 | // act 21 | var sut = new Fritz.StreamTools.Services.StreamService(new[] { jeffStreams, otherStreamService }); 22 | var count = sut.ViewerCountByService.ToList(); 23 | 24 | // assert 25 | Assert.Equal(2, count.Count()); 26 | Assert.Equal(jeffStreams.CurrentViewerCount, count.First(c => c.service == jeffStreams.Name).count); 27 | Assert.Equal(otherStreamService.CurrentViewerCount, count.First(c => c.service == otherStreamService.Name).count); 28 | 29 | 30 | } 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Test/Services/TwitchService/OnNewFollowers.cs: -------------------------------------------------------------------------------- 1 | using Fritz.StreamLib.Core; 2 | using Fritz.StreamTools.Services; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Logging; 5 | using Xunit; 6 | using FRITZ = Fritz.StreamTools.Services; 7 | 8 | namespace Test.Services.TwitchService 9 | { 10 | 11 | public class OnNewFollowers 12 | { 13 | 14 | //[Theory(Skip ="not now")] 15 | //[AutoMoqData] 16 | 17 | //public void ShouldSetCurrentFollowerCount( 18 | // IConfiguration configuration, 19 | // ILoggerFactory loggerFactory, 20 | // int initialFollowers, 21 | // OnNewFollowersDetectedArgs args) 22 | //{ 23 | 24 | // // arrange 25 | 26 | // // act 27 | // var sut = new FRITZ.TwitchService(configuration, loggerFactory, null, null) 28 | // { 29 | // CurrentFollowerCount = initialFollowers 30 | // }; 31 | 32 | // sut.Service_OnNewFollowersDetected(null, args); 33 | 34 | // // assert 35 | // Assert.Equal(args.NewFollowers.Count + initialFollowers, sut.CurrentFollowerCount); 36 | 37 | //} 38 | 39 | 40 | //[Theory(Skip ="not now")] 41 | //[AutoMoqData] 42 | //public void ShouldRaiseEventProperly( 43 | // IConfiguration configuration, 44 | // ILoggerFactory loggerFactory, 45 | // int initialFollowers, 46 | // OnNewFollowersDetectedArgs args) 47 | //{ 48 | 49 | // // arrange 50 | // var sut = new FRITZ.TwitchService(configuration, loggerFactory, null, null) 51 | // { 52 | // CurrentFollowerCount = initialFollowers 53 | // }; 54 | 55 | // // assert 56 | // var evt = Assert.Raises( 57 | // h => sut.Updated += h, 58 | // h => sut.Updated -= h, 59 | // () => sut.Service_OnNewFollowersDetected(null, args) 60 | // ); 61 | 62 | // Assert.Equal(initialFollowers + args.NewFollowers.Count, evt.Arguments.NewFollowers); 63 | // Assert.Null(evt.Arguments.NewViewers); 64 | 65 | //} 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Test/TagHelpers/SignalrTagHelper/IdentifyClientLibrary.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO.Abstractions; 5 | using System.Text; 6 | using Xunit; 7 | using WEB = Fritz.StreamTools; 8 | 9 | namespace Test.TagHelpers.SignalrTagHelper 10 | { 11 | 12 | public class IdentifyClientLibrary : BaseFixture 13 | { 14 | 15 | [Fact] 16 | public void WhenClientLibraryFoundShouldOutput() { 17 | 18 | // arrange 19 | var hostingEnvironment = Mockery.Create(); 20 | var fileSystem = Mockery.Create(); 21 | 22 | // act 23 | //var sut = new WEB.TagHelpers.SignalrTagHelper(hostingEnvironment.Object); 24 | // var result = sut.IdentifyClientLibrary(fileSystem.Object, "TESTPATH"); 25 | 26 | // assert 27 | 28 | 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Test/TagHelpers/SignalrTagHelper/Process.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Fritz.StreamTools.TagHelpers; 3 | using Microsoft.AspNetCore.Razor.TagHelpers; 4 | using Xunit; 5 | 6 | namespace Test.TagHelpers.SignalrTagHelper 7 | { 8 | 9 | public class Process 10 | { 11 | 12 | [Theory] 13 | [AutoMoqData] 14 | public void ReturnsScriptTag(SignalrTagHelperOptions options, TagHelperContext context, TagHelperOutput output) 15 | { 16 | 17 | // arrange 18 | var sut = new Fritz.StreamTools.TagHelpers.SignalrTagHelper(options); 19 | 20 | // act 21 | sut.Process(context, output); 22 | 23 | // assert 24 | Assert.Equal("script", output.TagName); 25 | Assert.Equal(TagMode.StartTagAndEndTag, output.TagMode); 26 | Assert.Single(output.Attributes); 27 | Assert.Equal("src", output.Attributes.First().Name); 28 | Assert.Equal(options.ClientLibarySource, output.Attributes.First().Value); 29 | 30 | } 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Test/Twitch/Chat/OnConnect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Fritz.Twitch; 6 | using Test.Twitch.Proxy; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Test.Twitch.Chat 11 | { 12 | public class OnConnect 13 | { 14 | 15 | public OnConnect(ITestOutputHelper outputHelper) 16 | { 17 | this.OutputHelper = outputHelper; 18 | 19 | _Settings = new Fritz.Twitch.ConfigurationSettings 20 | { 21 | ChannelName = "csharpfritz", 22 | ClientId = "t7y5txan5q662t7zj7p3l4wlth8zhv", 23 | UserId = "96909659", 24 | ChatBotName = "fritzbot_", 25 | OAuthToken = "-MY TOKEN-" 26 | }; 27 | } 28 | 29 | public ITestOutputHelper OutputHelper { get; } 30 | 31 | private ConfigurationSettings _Settings; 32 | 33 | 34 | [Fact(Skip ="Need OAuth Token")] 35 | public async Task ShouldWork() 36 | { 37 | 38 | var sut = new ChatClient(_Settings, new XUnitLogger(OutputHelper)); 39 | sut.Init(); 40 | 41 | await Task.Delay(1000); 42 | 43 | // sut.WhisperMessage("Hello from your bot", "csharpfritz"); 44 | 45 | await Task.Delay(1000); 46 | sut.Dispose(); 47 | 48 | } 49 | 50 | [Fact] 51 | public void UserNameIdentificationWorks() 52 | { 53 | 54 | var sampleMessage = "@badges=broadcaster/1,subscriber/0,premium/1;color=#0000FF;display-name=csharpfritz;emotes=;id=b5d0d4c2-f4d1-416f-952d-62807bbf9084;mod=0;room-id=96909659;subscriber=1;tmi-sent-ts=1523285299657;turbo=0;user-id=96909659;user-type= :csharpfritz!csharpfritz@csharpfritz.tmi.twitch.tv PRIVMSG #csharpfritz :test"; 55 | 56 | Assert.True(ChatClient.reUserName.Match(sampleMessage).Success); 57 | Assert.Equal(1, ChatClient.reUserName.Match(sampleMessage).Captures.Count); 58 | Assert.Equal("csharpfritz", ChatClient.reUserName.Match(sampleMessage).Groups[1].Value); 59 | 60 | } 61 | 62 | [Fact] 63 | public void MessageIdentificationWorks() 64 | { 65 | 66 | var sampleMessage = "@badges=broadcaster/1,subscriber/0,premium/1;color=#0000FF;display-name=csharpfritz;emotes=;id=b5d0d4c2-f4d1-416f-952d-62807bbf9084;mod=0;room-id=96909659;subscriber=1;tmi-sent-ts=1523285299657;turbo=0;user-id=96909659;user-type= :csharpfritz!csharpfritz@csharpfritz.tmi.twitch.tv PRIVMSG #csharpfritz :test"; 67 | 68 | var sut = new ChatClient(_Settings, new XUnitLogger(OutputHelper)); 69 | 70 | Assert.True(ChatClient.reChatMessage.Match(sampleMessage).Success); 71 | Assert.Equal(1, ChatClient.reChatMessage.Match(sampleMessage).Captures.Count); 72 | Assert.Equal("test", ChatClient.reChatMessage.Match(sampleMessage).Groups[1].Value); 73 | 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Test/Twitch/Proxy/GetFollowerCount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Fritz.Twitch; 8 | using Microsoft.Extensions.Logging; 9 | using Moq; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace Test.Twitch.Proxy 14 | { 15 | 16 | public class GetFollowerCount 17 | { 18 | private static readonly HttpClient _Client; 19 | private static readonly ConfigurationSettings _Settings; 20 | 21 | static GetFollowerCount() 22 | { 23 | 24 | _Client = new HttpClient(); 25 | _Settings = new Fritz.Twitch.ConfigurationSettings 26 | { 27 | ChannelName = "csharpfritz", 28 | ClientId = "t7y5txan5q662t7zj7p3l4wlth8zhv", 29 | UserId = "96909659" 30 | }; 31 | 32 | } 33 | 34 | public GetFollowerCount(ITestOutputHelper output) 35 | { 36 | Mockery = new MockRepository(MockBehavior.Loose); 37 | this.Output = output; 38 | this.Logger = new XUnitLogger(Output); 39 | } 40 | 41 | public MockRepository Mockery { get; } 42 | public ITestOutputHelper Output { get; } 43 | public XUnitLogger Logger { get; } 44 | 45 | [Fact(Skip ="Not used")] 46 | public async Task ShouldReturnNonZeroCount() 47 | { 48 | 49 | // Arrange 50 | var sut = new Fritz.Twitch.Proxy(_Client, _Settings, Logger); 51 | 52 | // Act 53 | var followerCount = await sut.GetFollowerCountAsync(); 54 | Output.WriteLine($"csharpfritz Twitch follower count: {followerCount}"); 55 | 56 | // Assert 57 | Assert.NotEqual(0, followerCount); 58 | 59 | } 60 | 61 | 62 | [Fact] 63 | public void ShouldParseFollowerResult() 64 | { 65 | 66 | var sampleData = @"{ 67 | ""total"": 12345, 68 | ""data"": [], 69 | ""pagination"":{ 70 | ""cursor"": ""eyJiIjpudWxsLCJhIjoiMTUwMzQ0MTc3NjQyNDQyMjAwMCJ9"" 71 | } 72 | }"; 73 | 74 | var count = Fritz.Twitch.Proxy.ParseFollowerResult(sampleData); 75 | Output.WriteLine($"Follower Count: {count}"); 76 | 77 | Assert.Equal(12345, count); 78 | 79 | } 80 | 81 | 82 | } 83 | 84 | public class XUnitLogger : ILogger 85 | { 86 | 87 | public XUnitLogger(ITestOutputHelper outputHelper) 88 | { 89 | this.OutputHelper = outputHelper; 90 | } 91 | 92 | public ITestOutputHelper OutputHelper { get; } 93 | 94 | public IDisposable BeginScope(TState state) 95 | { 96 | // throw new NotImplementedException(); 97 | return null; 98 | } 99 | 100 | public bool IsEnabled(LogLevel logLevel) 101 | { 102 | return true; 103 | } 104 | 105 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 106 | { 107 | 108 | //var logValues = state as FormattedLogValues; 109 | 110 | //OutputHelper.WriteLine(logValues[0].Value.ToString()); 111 | //Debug.WriteLine(logValues[0].Value.ToString()); 112 | 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /Test/Twitch/Proxy/GetStreamData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fritz.Twitch; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Test.Twitch.Proxy 11 | { 12 | public class GetStreamData 13 | { 14 | private static readonly HttpClient _Client; 15 | private static readonly ConfigurationSettings _Settings; 16 | 17 | static GetStreamData() 18 | { 19 | 20 | _Client = new HttpClient(); 21 | _Settings = new Fritz.Twitch.ConfigurationSettings 22 | { 23 | ChannelName = "csharpfritz", 24 | ClientId = "t7y5txan5q662t7zj7p3l4wlth8zhv", 25 | UserId = "96909659" 26 | }; 27 | 28 | } 29 | 30 | public GetStreamData(ITestOutputHelper output) 31 | { 32 | 33 | this.Output = output; 34 | this.Logger = new XUnitLogger(Output); 35 | 36 | } 37 | 38 | public ITestOutputHelper Output { get; } 39 | public XUnitLogger Logger { get; } 40 | 41 | [Fact] 42 | public void ShouldParseStreamResult() 43 | { 44 | 45 | var sampleData = @"{ 46 | ""data"": [ 47 | 48 | { 49 | ""id"": ""26007494656"", 50 | ""user_id"": ""23161357"", 51 | ""game_id"": ""417752"", 52 | ""community_ids"": [ 53 | ""5181e78f-2280-42a6-873d-758e25a7c313"", 54 | ""848d95be-90b3-44a5-b143-6e373754c382"", 55 | ""fd0eab99-832a-4d7e-8cc0-04d73deb2e54"" 56 | ], 57 | ""type"": ""live"", 58 | ""title"": ""Hey Guys, It's Monday - Twitter: @Lirik"", 59 | ""viewer_count"": 32575, 60 | ""started_at"": ""2017-08-14T16:08:32Z"", 61 | ""language"": ""en"", 62 | ""thumbnail_url"": ""https://static-cdn.jtvnw.net/previews-ttv/live_user_lirik-{width}x{height}.jpg"" 63 | } 64 | ], 65 | ""pagination"": { 66 | ""cursor"": ""eyJiIjpudWxsLCJhIjp7Ik9mZnNldCI6MjB9fQ=="" 67 | } 68 | }"; 69 | 70 | var stream = Fritz.Twitch.Proxy.ParseStreamResult(sampleData); 71 | Output.WriteLine($"Viewer Count: {stream.ViewerCount}"); 72 | 73 | Assert.Equal(32575, stream.ViewerCount); 74 | 75 | } 76 | 77 | [Fact(Skip = "Not reliable.. need to refactor to always work")] 78 | public async Task ShouldReturnZeroWhenNotStreaming() 79 | { 80 | 81 | // Arrange 82 | var sut = new Fritz.Twitch.Proxy(_Client, _Settings, Logger); 83 | 84 | // Act 85 | var viewerCount = await sut.GetViewerCountAsync(); 86 | Output.WriteLine($"csharpfritz Twitch viewer count: {viewerCount}"); 87 | 88 | // Assert 89 | Assert.Equal(0, viewerCount); 90 | 91 | } 92 | 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Test/XunitLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Xunit.Abstractions; 6 | 7 | namespace Test 8 | { 9 | public class XunitLogger : ILogger, IDisposable 10 | { 11 | private ITestOutputHelper _output; 12 | 13 | public XunitLogger(ITestOutputHelper output) 14 | { 15 | _output = output; 16 | } 17 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 18 | { 19 | _output.WriteLine(state.ToString()); 20 | } 21 | 22 | public bool IsEnabled(LogLevel logLevel) 23 | { 24 | return true; 25 | } 26 | 27 | public IDisposable BeginScope(TState state) 28 | { 29 | return this; 30 | } 31 | 32 | public void Dispose() 33 | { 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | docker build --no-cache -t fritz.streamtools:%1 -t fritz.streamtools:latest -f Fritz.StreamTools\Dockerfile . 2 | 3 | docker tag fritz.streamtools:%1 fritzregistry.azurecr.io/fritz.streamtools:%1 4 | docker tag fritz.streamtools:%1 fritzregistry.azurecr.io/fritz.streamtools:latest 5 | 6 | git tag v%1 7 | git push --tags 8 | 9 | docker push fritzregistry.azurecr.io/fritz.streamtools:%1 10 | docker push fritzregistry.azurecr.io/fritz.streamtools:latest 11 | -------------------------------------------------------------------------------- /docker-compose.ci.build.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | ci-build: 5 | image: microsoft/aspnetcore-build:1.0-2.0 6 | volumes: 7 | - .:/src 8 | working_dir: /src 9 | command: /bin/bash -c "dotnet restore ./Fritz.StreamTools.sln && dotnet publish ./Fritz.StreamTools.sln -c Release -o ./obj/Docker/publish" 10 | -------------------------------------------------------------------------------- /docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1 5 | Linux 6 | 520b1405-ac30-493a-8f04-6aa9b8fe2cdd 7 | True 8 | http://localhost:{ServicePort} 9 | fritz.rundown 10 | 11 | 12 | 13 | docker-compose.yml 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | fritz.streamtools: 5 | environment: 6 | - ASPNETCORE_ENVIRONMENT=Development 7 | ports: 8 | - "80" 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | fritz.streamtools: 5 | image: fritz.streamtools 6 | build: 7 | context: . 8 | dockerfile: ./Fritz.StreamTools/Dockerfile 9 | -------------------------------------------------------------------------------- /docs/images/FollowerGoalSample.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/Fritz.StreamTools/6bf0c186299837a9030cf8b4551f3445f3d6e33f/docs/images/FollowerGoalSample.PNG -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.100", 4 | "rollForward": "latestMajor" 5 | } 6 | } -------------------------------------------------------------------------------- /remove.cmd: -------------------------------------------------------------------------------- 1 | docker rmi fritzregistry.azurecr.io/fritz.streamtools:%1 2 | docker rmi fritz.streamtools:%1 3 | --------------------------------------------------------------------------------