33using TwitchLib . Client ;
44using TwitchLib . Client . Events ;
55using TwitchLib . Client . Models ;
6+ using TwitchLib . Communication . Clients ;
7+ using TwitchLib . Communication . Models ;
68using Message = TTX . App . Interfaces . Chat . Message ;
79
810namespace TTX . Infrastructure . Twitch . Chat ;
@@ -11,28 +13,46 @@ public sealed class TwitchBot : IAsyncDisposable
1113{
1214 public bool IsConnected => _client . IsConnected ;
1315 public event EventHandler < Message > OnMessage = null ! ;
14- public int ChannelCount => _client . JoinedChannels . Count ;
16+ public int ChannelCount => _channels . Count ;
1517 private readonly CancellationTokenSource _cts = new ( ) ;
1618 private readonly ILogger < TwitchBot > _logger ;
1719 private readonly TwitchClient _client ;
1820 private readonly Channel < string > _joinChannel = Channel . CreateUnbounded < string > ( ) ;
21+ private readonly HashSet < string > _channels = new ( StringComparer . OrdinalIgnoreCase ) ;
1922
2023 public TwitchBot ( ILogger < TwitchBot > logger , ILoggerFactory loggerFactory )
2124 {
2225 _logger = logger ;
23- _client = new TwitchClient ( loggerFactory : loggerFactory ) ;
26+ ClientOptions clientOptions = new (
27+ reconnectionPolicy : new ReconnectionPolicy ( 3_000 )
28+ ) ;
29+ _client = new TwitchClient (
30+ new WebSocketClient ( clientOptions ) ,
31+ loggerFactory : loggerFactory
32+ ) ;
2433
2534 _client . Initialize ( new ConnectionCredentials ( ) ) ;
2635 _client . OnMessageReceived += OnMessageReceived ;
27- _client . OnConnected += async ( _ , _ ) => { _ = OnConnected ( ) ; } ;
36+ _client . OnConnected += OnClientConnected ;
37+ _client . OnReconnected += OnClientReconnected ;
38+ _client . OnDisconnected += OnClientDisconnected ;
39+ _client . OnConnectionError += OnConnectionError ;
2840 _client . OnFailureToReceiveJoinConfirmation += OnFailureToReceiveJoinConfirmation ;
2941 }
3042
3143 public Task Start ( ) => _client . ConnectAsync ( ) ;
3244
33- public ValueTask AddChannel ( string channel ) => _joinChannel . Writer . WriteAsync ( channel ) ;
45+ public ValueTask AddChannel ( string channel )
46+ {
47+ _channels . Add ( channel ) ;
48+ return _joinChannel . Writer . WriteAsync ( channel ) ;
49+ }
3450
35- public Task RemoveChannel ( string channel ) => _client . LeaveChannelAsync ( channel ) ;
51+ public async Task RemoveChannel ( string channel )
52+ {
53+ _channels . Remove ( channel ) ;
54+ await _client . LeaveChannelAsync ( channel ) ;
55+ }
3656
3757 private Task OnMessageReceived ( object ? _ , OnMessageReceivedArgs e )
3858 {
@@ -44,14 +64,60 @@ private Task OnFailureToReceiveJoinConfirmation(object? _, OnFailureToReceiveJoi
4464 {
4565 if ( _logger . IsEnabled ( LogLevel . Warning ) )
4666 {
47- _logger . LogWarning ( "Failed to join {channel }: {error }" , e . Exception . Channel , e . Exception . Details ) ;
67+ _logger . LogWarning ( "Failed to join {Channel }: {Error }" , e . Exception . Channel , e . Exception . Details ) ;
4868 }
49-
5069 _joinChannel . Writer . TryWrite ( e . Exception . Channel ) ;
70+
71+ return Task . CompletedTask ;
72+ }
73+
74+ private Task OnClientConnected ( object ? _ , OnConnectedEventArgs e )
75+ {
76+ if ( _logger . IsEnabled ( LogLevel . Information ) )
77+ {
78+ _logger . LogInformation ( "TwitchBot connected as {Username}" , e . BotUsername ) ;
79+ }
80+
81+ _ = DrainJoinQueue ( ) ;
82+ return Task . CompletedTask ;
83+ }
84+
85+ private Task OnClientReconnected ( object ? _ , OnConnectedEventArgs e )
86+ {
87+ if ( _logger . IsEnabled ( LogLevel . Information ) )
88+ {
89+ _logger . LogInformation ( "TwitchBot reconnected as {Username}" , e . BotUsername ) ;
90+ }
91+
92+ foreach ( string channel in _channels )
93+ {
94+ _joinChannel . Writer . TryWrite ( channel ) ;
95+ }
96+
97+ return Task . CompletedTask ;
98+ }
99+
100+ private Task OnClientDisconnected ( object ? _ , OnDisconnectedArgs e )
101+ {
102+ if ( _logger . IsEnabled ( LogLevel . Warning ) )
103+ {
104+ _logger . LogWarning ( "TwitchBot disconnected" ) ;
105+ }
106+
107+ return Task . CompletedTask ;
108+ }
109+
110+ private Task OnConnectionError ( object ? _ , OnConnectionErrorArgs e )
111+ {
112+ if ( _logger . IsEnabled ( LogLevel . Error ) )
113+ {
114+ _logger . LogError ( "TwitchBot connection error: {Message}" , e . Error . Message ) ;
115+ }
116+
51117 return Task . CompletedTask ;
52118 }
53119
54- private async Task OnConnected ( )
120+ private async Task DrainJoinQueue ( )
55121 {
56122 while ( ! _cts . Token . IsCancellationRequested && await _joinChannel . Reader . WaitToReadAsync ( ) )
57123 {
@@ -65,6 +131,10 @@ private async Task OnConnected()
65131 public async ValueTask DisposeAsync ( )
66132 {
67133 _client . OnMessageReceived -= OnMessageReceived ;
134+ _client . OnConnected -= OnClientConnected ;
135+ _client . OnReconnected -= OnClientReconnected ;
136+ _client . OnDisconnected -= OnClientDisconnected ;
137+ _client . OnConnectionError -= OnConnectionError ;
68138 _client . OnFailureToReceiveJoinConfirmation -= OnFailureToReceiveJoinConfirmation ;
69139 await _cts . CancelAsync ( ) ;
70140 await _client . DisconnectAsync ( ) ;
0 commit comments