diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj
index a9ae90b..1726a99 100644
--- a/src/Application/Application.csproj
+++ b/src/Application/Application.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/Application/Features/ProcessMessageCommand.cs b/src/Application/Features/ProcessMessageCommand.cs
index 1a2f107..f27f133 100644
--- a/src/Application/Features/ProcessMessageCommand.cs
+++ b/src/Application/Features/ProcessMessageCommand.cs
@@ -1,13 +1,13 @@
using System.Text.Json;
-using Application.Infrastructure.Pubg;
-using TwitchLib.Client.Models;
+
namespace Application.Features;
-public record ProcessMessageCommand(ChatMessage ChatMessage) : IRequest;
+public record ProcessMessageCommand(ChatMessage ChatMessage, string ThreadId) : IRequest;
public class ProcessMessageCommandHandler(
IChatService chatService,
IAIClient aiClient,
+ IAssistantClient assistantClient,
IPubgAIClient aiPubgAIClient,
IModerationClient moderationClient,
IMediator mediator,
@@ -19,6 +19,7 @@ ILoggerFactory loggerFactory
{
private readonly IChatService chatService = chatService;
private readonly IAIClient aiClient = aiClient;
+ private readonly IAssistantClient assistantClient = assistantClient;
private readonly IPubgAIClient aiPubgAIClient = aiPubgAIClient;
private readonly IModerationClient moderationClient = moderationClient;
private readonly IMediator mediator = mediator;
@@ -84,6 +85,8 @@ public async Task Handle(ProcessMessageCommand request, CancellationToken cancel
logger.LogTrace("Messages: {Messages}", string.Join(", ", messages));
logger.LogTrace("Summary: {Summary}", messages.GetExtractiveSummary(request.ChatMessage.Channel));
+ await assistantClient.AddMessage(request.ThreadId, request.ChatMessage.Username, string.Empty, request.ChatMessage.Message);
+
// Ignore messages from some users (like yourself)
if (
request.ChatMessage.Username.Equals(options.Username, StringComparison.CurrentCultureIgnoreCase)
@@ -96,10 +99,10 @@ public async Task Handle(ProcessMessageCommand request, CancellationToken cancel
var randomResponseChance = random.NextDouble();
logger.LogDebug("Random value: {RandomValue}", randomResponseChance);
- if (request.ChatMessage.Message.Contains(options.Username, StringComparison.CurrentCultureIgnoreCase))
+ if (request.ChatMessage.Message.Contains(options.Username, StringComparison.CurrentCultureIgnoreCase) || randomResponseChance > RandomResponseChance)
{
// If the message contains the bot's name, use only that message as a prompt
- string completion = await aiClient.GetCompletion(request.ChatMessage.Message);
+ string completion = await assistantClient.RunAndWait(request.ThreadId);
logger.LogDebug("Completion ({Channel}): {Completion}", request.ChatMessage.Channel, completion);
@@ -116,21 +119,6 @@ public async Task Handle(ProcessMessageCommand request, CancellationToken cancel
}
}
}
- else if (randomResponseChance > RandomResponseChance)
- {
- // If this is a random response, use some of the chat history to generate a response
- // This also empties the history queue
-
- // Get the messages into a list
- var historyMessages = new List();
- while (messages.TryDequeue(out var message))
- {
- historyMessages.Add(message.Message);
- }
- string completion = await aiClient.GetAwareCompletion(historyMessages);
- logger.LogInformation("History aware completion ({Channel}): {Completion}", request.ChatMessage.Channel, completion);
- await chatService.SendMessage(request.ChatMessage.Channel, completion, cancellationToken);
- }
}
}
@@ -196,3 +184,5 @@ public string GetExtractiveSummary(string channel, int numSentences = 3)
}
public record HistoryMessage(string Channel, string Username, string Message, DateTime Timestamp);
+
+#pragma warning restore OPENAI001
diff --git a/src/Application/GlobalUsings.cs b/src/Application/GlobalUsings.cs
index 936ffa3..1d62526 100644
--- a/src/Application/GlobalUsings.cs
+++ b/src/Application/GlobalUsings.cs
@@ -10,6 +10,7 @@
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using OpenAI;
+global using OpenAI.Assistants;
global using OpenAI.Audio;
global using System.Text.RegularExpressions;
global using TwitchLib.Client.Models;
diff --git a/src/Application/Infrastructure/DependencyInjection.cs b/src/Application/Infrastructure/DependencyInjection.cs
index cc95fe4..c9aa374 100644
--- a/src/Application/Infrastructure/DependencyInjection.cs
+++ b/src/Application/Infrastructure/DependencyInjection.cs
@@ -29,6 +29,7 @@ private static IServiceCollection AddOpenAI(this IServiceCollection services, IC
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
return services;
}
diff --git a/src/Application/Infrastructure/OpenAI/AIClient.cs b/src/Application/Infrastructure/OpenAI/AIClient.cs
index d4d9acf..99e38ac 100644
--- a/src/Application/Infrastructure/OpenAI/AIClient.cs
+++ b/src/Application/Infrastructure/OpenAI/AIClient.cs
@@ -48,4 +48,5 @@ public class OpenAIClientOptions : IConfigurationOptions
public Guid SoundOutDeviceGuid { get; set; } = Guid.Empty;
public string AudioOutputPath { get; set; } = string.Empty;
public string GeneralSystemPrompt { get; set; } = string.Empty;
+ public string Assistant { get; set; } = string.Empty;
}
\ No newline at end of file
diff --git a/src/Application/Infrastructure/OpenAI/AssisstantClient.cs b/src/Application/Infrastructure/OpenAI/AssisstantClient.cs
new file mode 100644
index 0000000..c2df397
--- /dev/null
+++ b/src/Application/Infrastructure/OpenAI/AssisstantClient.cs
@@ -0,0 +1,61 @@
+namespace Application.Infrastructure.OpenAI;
+
+#pragma warning disable OPENAI001
+
+public interface IAssistantClient
+{
+ Task NewThread(ThreadCreationOptions threadCreationOptions);
+ Task AddMessage(string threadId, string nick, string role, string message);
+ Task RunAndWait(string threadId);
+}
+
+public class AssistantClient(IOptionsMonitor optionsMonitor, ILogger logger) : IAssistantClient
+{
+ readonly OpenAIClient openAIClient = new(optionsMonitor.CurrentValue.ApiKey);
+ private readonly ILogger logger = logger;
+
+ public async Task NewThread(ThreadCreationOptions threadCreationOptions)
+ {
+ var thread = await openAIClient.GetAssistantClient().CreateThreadAsync(threadCreationOptions);
+ return thread.Value.Id;
+ }
+
+ public async Task AddMessage(string threadId, string nick, string role, string message)
+ {
+ var assistantClient = openAIClient.GetAssistantClient();
+ var messageCreationOptions = new MessageCreationOptions();
+ messageCreationOptions.Metadata.Add("nick", nick);
+ messageCreationOptions.Metadata.Add("role", role);
+
+ var chatMessage = $"{nick} ({role}): {message}";
+
+ await assistantClient.CreateMessageAsync(threadId, MessageRole.User, [MessageContent.FromText(chatMessage)], messageCreationOptions);
+ }
+
+ public async Task RunAndWait(string threadId)
+ {
+ var assistantClient = openAIClient.GetAssistantClient();
+ var createRunClientResult = await assistantClient.CreateRunAsync(threadId, optionsMonitor.CurrentValue.Assistant);
+ var threadRun = createRunClientResult.Value;
+
+ do
+ {
+ Thread.Sleep(100);
+ threadRun = await assistantClient.GetRunAsync(threadRun.ThreadId, threadRun.Id);
+ } while (!threadRun.Status.IsTerminal);
+
+ var messages = assistantClient.GetMessages(threadRun.ThreadId, new MessageCollectionOptions() { Order = MessageCollectionOrder.Descending });
+
+ foreach (var message in messages)
+ {
+ foreach (var contentItem in message.Content)
+ {
+ logger.LogInformation("{Role}: {Message}", message.Role.ToString().ToUpper(), contentItem.Text);
+ }
+ }
+
+ return messages.First().Content.First().Text;
+ }
+}
+
+#pragma warning restore OPENAI001
diff --git a/src/Application/Infrastructure/Twitch/ChatService.cs b/src/Application/Infrastructure/Twitch/ChatService.cs
index 584d3eb..feee79a 100644
--- a/src/Application/Infrastructure/Twitch/ChatService.cs
+++ b/src/Application/Infrastructure/Twitch/ChatService.cs
@@ -4,6 +4,8 @@
namespace Application.Infrastructure.Twitch;
+#pragma warning disable OPENAI001
+
public interface IChatService
{
public Task StartAsync(string accessToken, CancellationToken cancellationToken);
@@ -13,12 +15,15 @@ public interface IChatService
public IReadOnlyList JoinedChannels { get; }
}
-public partial class ChatService(ILoggerFactory loggerFactory, ILogger logger, ChatOptions options, IMediator mediator) : IChatService
+public partial class ChatService(ILoggerFactory loggerFactory, ILogger logger, ChatOptions options, IMediator mediator, IAssistantClient assistantClient) : IChatService
{
readonly TwitchClient client = new(loggerFactory: loggerFactory);
private readonly ILogger logger = logger;
private readonly ChatOptions options = options;
private readonly IMediator mediator = mediator;
+ private readonly IAssistantClient assistantClient = assistantClient;
+ private string threadId = string.Empty;
+
private Task Client_OnConnected(object? sender, OnConnectedEventArgs e)
{
@@ -40,7 +45,7 @@ private async Task Client_OnWhisperReceived(object? sender, OnWhisperReceivedArg
private async Task Client_OnMessageReceived(object? sender, OnMessageReceivedArgs e)
{
- var processMessageCommand = new ProcessMessageCommand(e.ChatMessage);
+ var processMessageCommand = new ProcessMessageCommand(e.ChatMessage, threadId);
await mediator.Send(processMessageCommand);
}
@@ -52,6 +57,8 @@ private Task Client_OnJoinedChannel(object? sender, OnJoinedChannelArgs e)
public async Task StartAsync(string accessToken, CancellationToken cancellationToken)
{
+ threadId = await assistantClient.NewThread(new ThreadCreationOptions());
+
ConnectionCredentials credentials = new(options.Username, accessToken);
client.Initialize(credentials, options.Channel);
@@ -91,3 +98,5 @@ public async Task JoinChannel(string channel, CancellationToken cancellationToke
public IReadOnlyList JoinedChannels => client.JoinedChannels;
}
+
+#pragma warning restore OPENAI001
\ No newline at end of file
diff --git a/src/Application/Infrastructure/Twitch/WebSocketService.cs b/src/Application/Infrastructure/Twitch/WebSocketService.cs
index f4dbdfd..4218436 100644
--- a/src/Application/Infrastructure/Twitch/WebSocketService.cs
+++ b/src/Application/Infrastructure/Twitch/WebSocketService.cs
@@ -1,5 +1,6 @@
using TwitchLib.Api;
using TwitchLib.Api.Core.Enums;
+using TwitchLib.EventSub.Core.SubscriptionTypes.Channel;
using TwitchLib.EventSub.Websockets.Core.EventArgs.Stream;
namespace Application.Infrastructure.Twitch;
@@ -17,6 +18,7 @@ public class WebsocketService : IWebsocketService
private readonly TwitchAPI twitchApi = new();
private string userId = string.Empty;
private string broadcasterId = string.Empty;
+ private string botUserId = string.Empty;
public WebsocketService(
ILogger logger,
@@ -37,14 +39,295 @@ IMediator mediator
this.eventSubWebsocketClient.StreamOnline += OnStreamOnline;
this.eventSubWebsocketClient.StreamOffline += OnStreamOffline;
- this.eventSubWebsocketClient.ChannelFollow += OnChannelFollow;
- this.eventSubWebsocketClient.ChannelVipAdd += OnChannelVipAdd;
- this.eventSubWebsocketClient.ChannelVipRemove += OnChannelVipRemove;
this.eventSubWebsocketClient.ChannelAdBreakBegin += OnChannelAdBreakBegin;
+ this.eventSubWebsocketClient.ChannelBan += OnChannelBan;
+ this.eventSubWebsocketClient.ChannelCharityCampaignDonate += OnChannelCharityCampaignDonate;
+ this.eventSubWebsocketClient.ChannelCharityCampaignProgress += OnChannelCharityCampaignProgress;
+ this.eventSubWebsocketClient.ChannelCharityCampaignStart += OnChannelCharityCampaignStart;
+ this.eventSubWebsocketClient.ChannelChatMessage += OnChannelChatMessage;
+ this.eventSubWebsocketClient.ChannelCheer += OnChannelCheer;
+ this.eventSubWebsocketClient.ChannelFollow += OnChannelFollow;
+ this.eventSubWebsocketClient.ChannelGoalBegin += OnChannelGoalBegin;
+ this.eventSubWebsocketClient.ChannelGoalEnd += OnChannelGoalEnd;
+ this.eventSubWebsocketClient.ChannelGoalProgress += OnChannelGoalProgress;
+ this.eventSubWebsocketClient.ChannelGuestStarGuestUpdate += OnChannelGuestStarGuestUpdate;
+ this.eventSubWebsocketClient.ChannelGuestStarSessionBegin += OnChannelGuestStarSessionBegin;
+ this.eventSubWebsocketClient.ChannelGuestStarSessionEnd += OnChannelGuestStarSessionEnd;
+ this.eventSubWebsocketClient.ChannelGuestStarSettingsUpdate += OnChannelGuestStarSettingsUpdate;
+ this.eventSubWebsocketClient.ChannelGuestStarSlotUpdate += OnChannelGuestStarSlotUpdate;
+ this.eventSubWebsocketClient.ChannelHypeTrainBegin += OnChannelHypeTrainBegin;
+ this.eventSubWebsocketClient.ChannelHypeTrainEnd += OnChannelHypeTrainEnd;
+ this.eventSubWebsocketClient.ChannelModeratorAdd += OnChannelModeratorAdd;
+ this.eventSubWebsocketClient.ChannelModeratorRemove += OnChannelModeratorRemove;
+ this.eventSubWebsocketClient.ChannelPointsAutomaticRewardRedemptionAdd += OnChannelPointsAutomaticRewardRedemption;
+ this.eventSubWebsocketClient.ChannelPointsCustomRewardAdd += OnChannelPointsCustomRewardAdd;
+ this.eventSubWebsocketClient.ChannelPointsCustomRewardRedemptionAdd += OnChannelPointsCustomRewardRedemption;
+ this.eventSubWebsocketClient.ChannelPointsCustomRewardRedemptionUpdate += OnChannelPointsCustomRewardRedemptionUpdate;
+ this.eventSubWebsocketClient.ChannelPointsCustomRewardRemove += OnChannelPointsCustomRewardRemove;
+ this.eventSubWebsocketClient.ChannelPointsCustomRewardUpdate += OnChannelPointsCustomRewardUpdate;
+ this.eventSubWebsocketClient.ChannelPollBegin += OnChannelPollBegin;
+ this.eventSubWebsocketClient.ChannelPollEnd += OnChannelPollEnd;
+ this.eventSubWebsocketClient.ChannelPollProgress += OnChannelPollProgress;
+ this.eventSubWebsocketClient.ChannelPredictionBegin += OnChannelPredictionBegin;
+ this.eventSubWebsocketClient.ChannelPredictionEnd += OnChannelPredictionEnd;
+ this.eventSubWebsocketClient.ChannelPredictionLock += OnChannelPredictionLock;
+ this.eventSubWebsocketClient.ChannelPredictionProgress += OnChannelPredictionProgress;
+ this.eventSubWebsocketClient.ChannelRaid += OnChannelRaid;
+ this.eventSubWebsocketClient.ChannelShieldModeBegin += OnChannelShieldModeBegin;
+ this.eventSubWebsocketClient.ChannelShieldModeEnd += OnChannelShieldModeEnd;
+ this.eventSubWebsocketClient.ChannelShoutoutCreate += OnChannelShoutoutCreate;
+ this.eventSubWebsocketClient.ChannelShoutoutReceive += OnChannelShoutoutReceive;
this.eventSubWebsocketClient.ChannelSubscribe += OnChannelSubscribe;
+ this.eventSubWebsocketClient.ChannelSubscriptionEnd += OnChannelSubscriptionEnd;
+ this.eventSubWebsocketClient.ChannelSubscriptionGift += OnChannelSubscriptionGift;
this.eventSubWebsocketClient.ChannelSubscriptionMessage += OnChannelSubscriptionMessage;
+ this.eventSubWebsocketClient.ChannelSuspiciousUserMessage += OnChannelSuspiciousUserMessage;
+ this.eventSubWebsocketClient.ChannelSuspiciousUserUpdate += OnChannelSuspiciousUserUpdate;
+ this.eventSubWebsocketClient.ChannelUnban += OnChannelUnban;
+ this.eventSubWebsocketClient.ChannelUpdate += OnChannelUpdate;
+ this.eventSubWebsocketClient.ChannelVipAdd += OnChannelVipAdd;
+ this.eventSubWebsocketClient.ChannelVipRemove += OnChannelVipRemove;
+ this.eventSubWebsocketClient.ChannelWarningAcknowledge += OnChannelWarningAcknowledge;
+ this.eventSubWebsocketClient.ChannelWarningSend += OnChannelWarningSend;
+ }
+
+ private async Task OnChannelAdBreakBegin(object sender, ChannelAdBreakBeginArgs args)
+ {
+ var adDurationSeconds = args.Notification.Payload.Event.DurationSeconds;
+ var broadcasterUserId = args.Notification.Payload.Event.BroadcasterUserLogin;
+ logger.LogInformation("WebSocket ChannelAdBreakBegin: {DurationSeconds} seconds", adDurationSeconds);
+
+ var processInstructionCommand = new ProcessInstructionCommand(
+ $@"Tell the chat an AD started, it will be over in {adDurationSeconds} seconds.
+ Give chat a suggestion what to do in the meantime.
+ If nothing else you can invite them to join our Discord server with this link {options.DiscordJoinLink}
+ Remind the chat that they can use their Prime Sub to sub to the channel to avoid ads.",
+ broadcasterUserId
+ );
+
+ await mediator.Send(processInstructionCommand);
}
+ private Task OnChannelBan(object sender, ChannelBanArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelCharityCampaignDonate(object sender, ChannelCharityCampaignDonateArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelCharityCampaignProgress(object sender, ChannelCharityCampaignProgressArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelCharityCampaignStart(object sender, ChannelCharityCampaignStartArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelCheer(object sender, ChannelCheerArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private async Task OnChannelFollow(object? sender, ChannelFollowArgs e)
+ {
+ var newFollower = e.Notification.Payload.Event.UserName;
+ var broadcasterUserId = e.Notification.Payload.Event.BroadcasterUserLogin;
+
+ logger.LogInformation("{UserName} followed {BroadcasterUserName} at {FollowedAt}", newFollower, e.Notification.Payload.Event.BroadcasterUserName, e.Notification.Payload.Event.FollowedAt);
+
+ var processInstructionCommand = new ProcessInstructionCommand($"Welcome {newFollower} as a new follower! Post some hype in chat for the new follower!", broadcasterUserId);
+ await mediator.Send(processInstructionCommand);
+ }
+
+ private Task OnChannelGoalBegin(object sender, ChannelGoalBeginArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGoalEnd(object sender, ChannelGoalEndArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGoalProgress(object sender, ChannelGoalProgressArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGuestStarGuestUpdate(object sender, ChannelGuestStarGuestUpdateArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGuestStarSessionBegin(object sender, ChannelGuestStarSessionBegin args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@ChannelGuestStarSessionBegin}", args);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGuestStarSessionEnd(object sender, ChannelGuestStarSessionEnd args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@ChannelGuestStarSessionEnd}", args);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGuestStarSettingsUpdate(object sender, ChannelGuestStarSettingsUpdateArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelGuestStarSlotUpdate(object sender, ChannelGuestStarSlotUpdateArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelHypeTrainBegin(object sender, ChannelHypeTrainBeginArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelHypeTrainEnd(object sender, ChannelHypeTrainEndArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelModeratorAdd(object sender, ChannelModeratorArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelModeratorRemove(object sender, ChannelModeratorArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPointsAutomaticRewardRedemption(object sender, ChannelPointsAutomaticRewardRedemptionArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPointsCustomRewardAdd(object sender, ChannelPointsCustomRewardArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPointsCustomRewardRedemption(object sender, ChannelPointsCustomRewardRedemptionArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPointsCustomRewardRedemptionUpdate(object sender, ChannelPointsCustomRewardRedemptionArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPointsCustomRewardRemove(object sender, ChannelPointsCustomRewardArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPointsCustomRewardUpdate(object sender, ChannelPointsCustomRewardArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPollBegin(object sender, ChannelPollBeginArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPollEnd(object sender, ChannelPollEndArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPollProgress(object sender, ChannelPollProgressArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPredictionBegin(object sender, ChannelPredictionBeginArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPredictionEnd(object sender, ChannelPredictionEndArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPredictionLock(object sender, ChannelPredictionLockArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelPredictionProgress(object sender, ChannelPredictionProgressArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelRaid(object sender, ChannelRaidArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelShieldModeBegin(object sender, ChannelShieldModeBeginArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelShieldModeEnd(object sender, ChannelShieldModeEndArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelShoutoutCreate(object sender, ChannelShoutoutCreateArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelShoutoutReceive(object sender, ChannelShoutoutReceiveArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
// The channel.subscribe subscription type sends a notification when a specified channel receives a subscriber. This does not include resubscribes.
private async Task OnChannelSubscribe(object sender, ChannelSubscribeArgs args)
@@ -64,6 +347,18 @@ private async Task OnChannelSubscribe(object sender, ChannelSubscribeArgs args)
await mediator.Send(processInstructionCommand);
}
+ private Task OnChannelSubscriptionEnd(object sender, ChannelSubscriptionEndArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelSubscriptionGift(object sender, ChannelSubscriptionGiftArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
+
private async Task OnChannelSubscriptionMessage(object sender, ChannelSubscriptionMessageArgs args)
{
var userName = args.Notification.Payload.Event.BroadcasterUserName;
@@ -81,33 +376,28 @@ private async Task OnChannelSubscriptionMessage(object sender, ChannelSubscripti
await mediator.Send(processInstructionCommand);
}
- private Task OnStreamOnline(object sender, StreamOnlineArgs args)
+ private Task OnChannelSuspiciousUserMessage(object sender, ChannelSuspiciousUserMessageArgs args)
{
- var userId = args.Notification.Payload.Event.BroadcasterUserId;
- var userName = args.Notification.Payload.Event.BroadcasterUserName;
- var userLogin = args.Notification.Payload.Event.BroadcasterUserLogin;
- logger.LogInformation("Websocket OnStreamOnline: {BroadcasterUserName} is now online! {BroadcasterUserLogin}/{BroadcasterUserId}", userName, userLogin, userId);
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
return Task.CompletedTask;
}
- private Task OnStreamOffline(object sender, StreamOfflineArgs args)
+ private Task OnChannelSuspiciousUserUpdate(object sender, ChannelSuspiciousUserUpdateArgs args)
{
- var userId = args.Notification.Payload.Event.BroadcasterUserId;
- var userName = args.Notification.Payload.Event.BroadcasterUserName;
- var userLogin = args.Notification.Payload.Event.BroadcasterUserLogin;
- logger.LogInformation("Websocket OnStreamOffline: {BroadcasterUserName} is now offline! {BroadcasterUserLogin}/{BroadcasterUserId}", userName, userLogin, userId);
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
return Task.CompletedTask;
}
- private async Task OnChannelFollow(object? sender, ChannelFollowArgs e)
+ private Task OnChannelUnban(object sender, ChannelUnbanArgs args)
{
- var newFollower = e.Notification.Payload.Event.UserName;
- var broadcasterUserId = e.Notification.Payload.Event.BroadcasterUserLogin;
-
- logger.LogInformation("{UserName} followed {BroadcasterUserName} at {FollowedAt}", newFollower, e.Notification.Payload.Event.BroadcasterUserName, e.Notification.Payload.Event.FollowedAt);
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
- var processInstructionCommand = new ProcessInstructionCommand($"Welcome {newFollower} as a new follower! Post some hype in chat for the new follower!", broadcasterUserId);
- await mediator.Send(processInstructionCommand);
+ private Task OnChannelUpdate(object sender, ChannelUpdateArgs args)
+ {
+ logger.LogDebug("OnChannelUpdate: {@Notification}", args.Notification);
+ return Task.CompletedTask;
}
private async Task OnChannelVipAdd(object sender, ChannelVipArgs args)
@@ -121,27 +411,46 @@ private async Task OnChannelVipAdd(object sender, ChannelVipArgs args)
await mediator.Send(processInstructionCommand);
}
- private Task OnChannelVipRemove(object sender, ChannelVipArgs args)
+ private Task OnChannelWarningAcknowledge(object sender, ChannelWarningAcknowledgeArgs args)
{
- logger.LogInformation("WebSocket ChannelVipRemove");
+ logger.LogDebug("OnChannelWarningAcknowledge: {@Notification}", args.Notification);
return Task.CompletedTask;
}
- private async Task OnChannelAdBreakBegin(object sender, ChannelAdBreakBeginArgs args)
+ private Task OnChannelWarningSend(object sender, ChannelWarningSendArgs args)
{
- var adDurationSeconds = args.Notification.Payload.Event.DurationSeconds;
- var broadcasterUserId = args.Notification.Payload.Event.BroadcasterUserLogin;
- logger.LogInformation("WebSocket ChannelAdBreakBegin: {DurationSeconds} seconds", adDurationSeconds);
+ logger.LogDebug("OnChannelWarningSend: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
- var processInstructionCommand = new ProcessInstructionCommand(
- $@"Tell the chat an AD started, it will be over in {adDurationSeconds} seconds.
- Give chat a suggestion what to do in the meantime.
- If nothing else you can invite them to join our Discord server with this link {options.DiscordJoinLink}
- Remind the chat that they can use their Prime Sub to sub to the channel to avoid ads.",
- broadcasterUserId
- );
+ private Task OnChannelChatMessage(object sender, ChannelChatMessageArgs args)
+ {
+ logger.LogDebug("OnChannelChatMessage: {@Notification}", args.Notification);
+ return Task.CompletedTask;
+ }
- await mediator.Send(processInstructionCommand);
+ private Task OnStreamOnline(object sender, StreamOnlineArgs args)
+ {
+ var userId = args.Notification.Payload.Event.BroadcasterUserId;
+ var userName = args.Notification.Payload.Event.BroadcasterUserName;
+ var userLogin = args.Notification.Payload.Event.BroadcasterUserLogin;
+ logger.LogInformation("Websocket OnStreamOnline: {BroadcasterUserName} is now online! {BroadcasterUserLogin}/{BroadcasterUserId}", userName, userLogin, userId);
+ return Task.CompletedTask;
+ }
+
+ private Task OnStreamOffline(object sender, StreamOfflineArgs args)
+ {
+ var userId = args.Notification.Payload.Event.BroadcasterUserId;
+ var userName = args.Notification.Payload.Event.BroadcasterUserName;
+ var userLogin = args.Notification.Payload.Event.BroadcasterUserLogin;
+ logger.LogInformation("Websocket OnStreamOffline: {BroadcasterUserName} is now offline! {BroadcasterUserLogin}/{BroadcasterUserId}", userName, userLogin, userId);
+ return Task.CompletedTask;
+ }
+
+ private Task OnChannelVipRemove(object sender, ChannelVipArgs args)
+ {
+ logger.LogInformation("WebSocket ChannelVipRemove");
+ return Task.CompletedTask;
}
public async Task StartAsync(string accessToken, CancellationToken cancellationToken = default)
@@ -156,6 +465,9 @@ public async Task StartAsync(string accessToken, CancellationToken cancellationT
twitchApi.Settings.ClientId = options.ClientId;
twitchApi.Settings.AccessToken = accessToken;
+ var usersResponse = await twitchApi.Helix.Users.GetUsersAsync(ids: null, logins: ["andibanterbot"], accessToken);
+ botUserId = usersResponse.Users.First().Id;
+
await eventSubWebsocketClient.ConnectAsync();
}
@@ -170,143 +482,33 @@ private async Task OnWebsocketConnected(object? sender, WebsocketConnectedArgs e
if (!e.IsRequestedReconnect)
{
- /*
- Subscribe to topics via the TwitchApi.Helix.EventSub object, this example shows how to subscribe
- to the channel follow event used in the example above.
-
- var conditions = new Dictionary()
- {
- { "broadcaster_user_id", someUserId }
- };
- var subscriptionResponse = await TwitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync("channel.follow", "2", conditions,
- EventSubTransportMethod.Websocket, _eventSubWebsocketClient.SessionId);
-
- You can find more examples on the subscription types and their requirements here https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/
- Prerequisite: Twitchlib.Api nuget package installed (included in the Twitchlib package automatically)
- */
-
- // channel.follow
- try
- {
- var conditions = new Dictionary()
- {
- { "broadcaster_user_id", "165699060" },
- { "moderator_user_id", "165699060" }
- };
-
- var subscriptionResponse = await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "channel.follow",
- "2",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- }
- catch (Exception ex)
+ var conditions = new Dictionary()
{
- logger.LogError(ex, "channel.follow");
- }
+ { "broadcaster_user_id", broadcasterId },
+ };
- // channel.vip.add/remove
- try
- {
- var conditions = new Dictionary()
+ await SubscribeToEvent("channel.follow", "2", new Dictionary()
{
- { "broadcaster_user_id", "165699060" }
- };
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "channel.vip.add",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "channel.vip.remove",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- eventSubWebsocketClient.SessionId
- );
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "channel.vip.add/remove");
- }
+ { "broadcaster_user_id", broadcasterId },
+ { "moderator_user_id", broadcasterId }
+ });
- // channel.ad_break.begin
- try
- {
- var conditions = new Dictionary()
- {
- { "broadcaster_user_id", "165699060" }
- };
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "channel.ad_break.begin",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- }
- catch (System.Exception ex)
- {
- logger.LogError(ex, "channel.ad_break.begin");
- }
+ await SubscribeToEvent("channel.vip.add", "1", conditions);
+ await SubscribeToEvent("channel.vip.remove", "1", conditions);
- // stream.online/offline
- try
- {
- var conditions = new Dictionary()
- {
- { "broadcaster_user_id", "165699060" }
- };
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "stream.online",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "stream.offline",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- }
- catch (System.Exception ex)
- {
- logger.LogError(ex, "stream.online/offline");
- }
+ await SubscribeToEvent("channel.ad_break.begin", "1", conditions);
- // channel.subscribe / channel.subscription.message
- try
- {
- var conditions = new Dictionary()
+ await SubscribeToEvent("stream.online", "1", conditions);
+ await SubscribeToEvent("stream.offline", "1", conditions);
+
+ await SubscribeToEvent("channel.subscribe", "1", conditions);
+ await SubscribeToEvent("channel.subscription.message", "1", conditions);
+
+ await SubscribeToEvent("channel.chat.message", "1", new Dictionary()
{
- { "broadcaster_user_id", "165699060" }
- };
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "channel.subscribe",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
- "channel.subscription.message",
- "1",
- conditions,
- EventSubTransportMethod.Websocket,
- websocketSessionId: eventSubWebsocketClient.SessionId
- );
- }
- catch (System.Exception ex)
- {
- logger.LogError(ex, "channel.subscribe/subscription.message");
- }
+ { "broadcaster_user_id", broadcasterId },
+ { "user_id", broadcasterId }
+ });
}
}
@@ -333,4 +535,22 @@ private Task OnErrorOccurred(object? sender, ErrorOccuredArgs e)
logger.LogError(e.Exception, "Websocket {SessionId} - Error occurred!", eventSubWebsocketClient.SessionId);
return Task.CompletedTask;
}
+
+ private async Task SubscribeToEvent(string eventType, string version, Dictionary conditions)
+ {
+ try
+ {
+ await twitchApi.Helix.EventSub.CreateEventSubSubscriptionAsync(
+ eventType,
+ version,
+ conditions,
+ EventSubTransportMethod.Websocket,
+ websocketSessionId: eventSubWebsocketClient.SessionId
+ );
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "{EventType} subscription failed", eventType);
+ }
+ }
}
diff --git a/src/Host/Host.csproj b/src/Host/Host.csproj
index 6d4afe5..e527a88 100644
--- a/src/Host/Host.csproj
+++ b/src/Host/Host.csproj
@@ -6,7 +6,7 @@
-
+