예제 #1
0
        public async Task <GameInfo> RefreshCategory(string broadcasterId, CancellationToken cancellationToken = default)
        {
            _logger.LogInformation("Starting category refresh for channelId {broadcasterId}", broadcasterId);

            try
            {
                var results = await _twitchAPIClient.SearchChannelsAsync(broadcasterId, cancellationToken);

                var newResults = results.First(c => c.BroadcasterLogin == broadcasterId);

                if (newResults.GameId != _lastResult?.GameId)
                {
                    _lastInfo = await _twitchAPIClient.GetChannelInfoAsync(newResults.Id, cancellationToken);

                    _gameInfo = await _gameLocalization.ResolveLocalizedGameInfoAsync(newResults.BroadcasterLanguage, newResults.GameId, cancellationToken);

                    if (OnUpdate != null)
                    {
                        OnUpdate(this, _gameInfo);
                    }
                }

                _lastResult      = newResults;
                _broacasterLogin = broadcasterId;
                _lastCheck       = DateTime.UtcNow;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error refreshing category for channelId {broadcasterId}", broadcasterId);
            }

            return(_gameInfo);
        }
예제 #2
0
        public async Task OnChannelUpdate(HelixChannelInfo info)
        {
            _channelState.State.LastCategoryId   = info.GameId;
            _channelState.State.LastCategoryName = info.GameName;
            _channelState.State.LastLanguage     = info.BroadcasterLanguage;
            _channelState.State.LastTitle        = info.Title;
            await _channelState.WriteStateAsync();

            if (_chatBot == null)
            {
                return;
            }

            var botContext = new ProcessorContext
            {
                ChannelId   = info.BroadcasterId,
                ChannelName = info.BroadcasterName,
                Language    = info.BroadcasterLanguage,
                CategoryId  = info.GameId,
            };
            var key = new CategoryKey {
                TwitchCategoryId = info.GameId, Locale = info.BroadcasterLanguage
            };

            if (_categoriesState.State.Descriptions.TryGetValue(key, out var customDescription))
            {
                botContext.CustomCategoryDescription = customDescription.Description;
            }
            else
            {
                botContext.CustomCategoryDescription = null;
            }
            await _chatBot.UpdateContext(botContext);
        }
예제 #3
0
        async Task IChannelGrain.Activate(string userToken)
        {
            var userAuthenticated = Twitch.Authenticate()
                                    .FromOAuthToken(userToken)
                                    .Build();
            var userClient = TwitchAPIClient.CreateFromBase(_appClient, userAuthenticated);
            var validated  = await userClient.ValidateToken();

            if (validated == null || validated.UserId != _channelId || validated.ExpiresIn == 0)
            {
                throw new ArgumentException("Could not validate token");
            }
            _userClient = userClient;

            var channelInfoTask = _userClient.GetChannelInfoAsync(_channelId);
            List <HelixChannelModerator> moderators = new List <HelixChannelModerator>();
            var editorsTask = _userClient.GetHelixChannelEditorsAsync(_channelId);

            await foreach (var moderator in _userClient.EnumerateChannelModeratorsAsync(_channelId))
            {
                moderators.Add(moderator);
            }
            _channelState.State.BroadcasterToken = userToken;
            _channelState.State.Editors          = (await editorsTask).ToList();
            _channelState.State.Moderators       = moderators.ToList();
            await _channelState.WriteStateAsync();

            var editorsTasks = _channelState.State.Editors.Select(editor =>
                                                                  GrainFactory.GetGrain <IUserGrain>(editor.UserId).SetRole(new UserRole
            {
                Role        = ChannelRole.Editor,
                ChannelId   = _channelId,
                ChannelName = _channelInfo.BroadcasterName,
            })
                                                                  );

            var modsTasks = _channelState.State.Moderators.Select(moderator =>
                                                                  GrainFactory.GetGrain <IUserGrain>(moderator.UserId).SetRole(new UserRole
            {
                Role        = ChannelRole.Moderator,
                ChannelId   = _channelId,
                ChannelName = _channelInfo.BroadcasterName,
            })
                                                                  );

            await Task.WhenAll(editorsTasks);

            await Task.WhenAll(modsTasks);

            _channelInfo = await channelInfoTask;
            await RegisterEventSubSubscriptions(CancellationToken.None);
        }
예제 #4
0
        public override async Task OnActivateAsync()
        {
            _channelId = this.GetPrimaryKeyString();
            _logger.LogInformation("Activating channel grain {channelId}", _channelId);

            if (!_channelBotState.RecordExists)
            {
                var defaultBotInfo = new BotAccountInfo
                {
                    IsActive  = true,
                    UserId    = _options.TokenInfo.UserId,
                    UserLogin = _options.TokenInfo.Login,
                };
                _channelBotState.State.AllowedBotAccounts.Add(defaultBotInfo);
                _channelBotState.State.Commands = new Dictionary <Guid, Conceptoire.Twitch.Commands.CommandOptions>();
            }
            else
            {
                // Temps: fix existing ids
                foreach ((var id, var command) in _channelBotState.State.Commands)
                {
                    command.Id = id;
                    if (command.Type == "MessageTracer")
                    {
                        command.Type = "Logger";
                    }
                }
                // Activate bot if it is supposed to be running
                if (_channelBotState.State.IsActive)
                {
                    await RegisterEventSubSubscriptions(CancellationToken.None);
                    await StartBot(_channelState.State.BroadcasterToken);
                }
            }

            await base.OnActivateAsync();

            _channelInfo = await _appClient.GetChannelInfoAsync(_channelId);
            await OnChannelUpdate(_channelInfo);
        }
예제 #5
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo {
                    Title = "BlipBloopWeb", Version = "v1"
                });
            });
            services.AddApplicationInsightsTelemetry();

            services.AddTransient <IClientBuilder>(services =>
            {
                var builder = new ClientBuilder()
                              // Clustering information
                              .Configure <ClusterOptions>(options =>
                {
                    options.ClusterId = "dev";
                    options.ServiceId = "TwitchServices";
                })
                              .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(IChannelGrain).Assembly));

                var redisClusteringUrl = Configuration.GetValue <string>("REDIS_URL");
                if (!string.IsNullOrEmpty(redisClusteringUrl))
                {
                    builder.UseRedisClustering(redisClusteringUrl);
                }
                else
                {
                    builder.UseLocalhostClustering();
                }
                return(builder);
            });
            services.AddSingleton <IClientProvider, GrainClientProvider>();

            // Load config, mainly the EventSub secret used for registration
            services.Configure <EventSubOptions>(Configuration.GetSection("twitch:EventSub"));
            // Add the EventSub handler instance to DI
            services.AddEventSub();
            // Register an EventSub event Handler for channel.update events
            services.AddEventSubHandler <TwitchEventSubChannelUpdateEvent>(async(context, eventSub) =>
            {
                context.Logger.LogInformation("Received a channel update for {channelName}, streaming {category} - {text}", eventSub.BroadcasterUserName, eventSub.CategoryName, eventSub.Title);

                var grainClientProvider = context.Services.GetRequiredService <IClientProvider>();
                var grainClient         = await grainClientProvider.GetConnectedClient();
                var helixInfo           = new HelixChannelInfo
                {
                    BroadcasterId       = eventSub.BroadcasterUserId,
                    BroadcasterName     = eventSub.BroadcasterUserName,
                    BroadcasterLanguage = eventSub.Language,
                    GameId   = eventSub.CategoryId,
                    GameName = eventSub.CategoryName,
                    Title    = eventSub.Title,
                };
                var channelGrain = grainClient.GetGrain <IChannelGrain>(helixInfo.BroadcasterId);
                await channelGrain.OnChannelUpdate(helixInfo);
            });

            var twitchOptions = Configuration.GetSection("twitch").Get <TwitchApplicationOptions>();

            services.AddTransient <IAuthenticated>(services =>
                                                   Twitch.Authenticate()
                                                   .FromAppCredentials(twitchOptions.ClientId, twitchOptions.ClientSecret)
                                                   .Build());
            services.AddSingleton <TwitchAPIClient>();
            services.Configure <AzureGameLocalizationStoreOptions>(Configuration.GetSection("loc:azure"));
            services.AddSingleton <IGameLocalizationStore, AzureStorageGameLocalizationStore>();
            services.AddHttpClient();

            Action <TwitchConstants.TwitchOAuthScopes[], string, OpenIdConnectOptions> configureTwitchOpenId = (scopes, callbackPath, options) =>
            {
                options.Authority = "https://id.twitch.tv/oauth2";
                //options.MetadataAddress = "https://id.twitch.tv/oauth2/.well-known/openid-configuration";
                options.ClientId     = twitchOptions.ClientId;
                options.ClientSecret = twitchOptions.ClientSecret;
                options.ResponseType = "token id_token";
                options.ResponseMode = "form_post";
                options.CallbackPath = callbackPath;
                options.Events.OnRedirectToIdentityProvider = (context) =>
                {
                    context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri + "-fragment";
                    return(Task.CompletedTask);
                };
                options.Events.OnTokenValidated = (context) =>
                {
                    ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity;
                    identity.AddClaim(new Claim("access_token", context.ProtocolMessage.AccessToken));
                    identity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));

                    foreach (var scope in scopes)
                    {
                        identity.AddClaim(new Claim("scope", TwitchConstants.ScopesValues[scope]));
                    }

                    return(Task.CompletedTask);
                };
                options.Scope.Remove("profile");

                foreach (var scope in scopes)
                {
                    options.Scope.Add(TwitchConstants.ScopesValues[scope]);
                }
            };

            // Identity
            services
            .AddAuthentication(options =>
            {
                options.DefaultScheme          = "cookie";
                options.DefaultChallengeScheme = "twitch";
            })
            .AddCookie("cookie", options =>
            {
                options.Events.OnRedirectToLogin = context =>
                {
                    if (context.Request.Path.StartsWithSegments("/api"))
                    {
                        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    }
                    else
                    {
                        context.Response.Redirect(context.RedirectUri);
                    }

                    return(Task.CompletedTask);
                };
            })
            .AddOpenIdConnect("twitch", "Twitch", options => configureTwitchOpenId(new TwitchConstants.TwitchOAuthScopes[] {
                TwitchConstants.TwitchOAuthScopes.ChannelReadEditors,
                TwitchConstants.TwitchOAuthScopes.ModerationRead,
                TwitchConstants.TwitchOAuthScopes.UserReadBlockedUsers,
                TwitchConstants.TwitchOAuthScopes.UserReadBroadcast,
                TwitchConstants.TwitchOAuthScopes.UserReadSubscriptions,
                TwitchConstants.TwitchOAuthScopes.ChatRead,
            }, "/signin-oidc", options))
            .AddOpenIdConnect("twitchBot", "Twitch - Bot Account", options => configureTwitchOpenId(new TwitchConstants.TwitchOAuthScopes[] {
                TwitchConstants.TwitchOAuthScopes.ChannelReadEditors,
                TwitchConstants.TwitchOAuthScopes.ModerationRead,
                TwitchConstants.TwitchOAuthScopes.UserReadBlockedUsers,
                TwitchConstants.TwitchOAuthScopes.UserReadBroadcast,
                TwitchConstants.TwitchOAuthScopes.ChatRead,
                TwitchConstants.TwitchOAuthScopes.ChatEdit,
                TwitchConstants.TwitchOAuthScopes.UserEditFollows,
            }, "/signin-oidc-bot", options));

            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddMudServices();
            services.AddAuthorizationCore();
        }