1
1
using Microsoft . Extensions . Logging ;
2
2
using Microsoft . Extensions . Options ;
3
3
using Newtonsoft . Json ;
4
- using Newtonsoft . Json . Linq ;
5
4
using System ;
6
5
using System . Collections . Generic ;
7
6
using System . Linq ;
11
10
using System . Threading . Tasks ;
12
11
using System . Timers ;
13
12
14
- namespace Fritz . Twitch . PubSub
15
- {
13
+ namespace Fritz . Twitch . PubSub {
16
14
17
15
/// <summary>
18
16
/// Manage interactions with the Twitch pubsub API
19
17
/// </summary>
20
- public class Proxy : IDisposable
21
- {
18
+ public class Proxy : IDisposable {
22
19
23
20
private ClientWebSocket _Socket ;
24
21
private System . Timers . Timer _PingTimer ;
25
22
private System . Timers . Timer _PongTimer ;
26
- private ConfigurationSettings _Configuration ;
27
- private ILogger _Logger ;
23
+ private readonly ConfigurationSettings _Configuration ;
24
+ private readonly ILogger _Logger ;
28
25
private static bool _Reconnect ;
29
26
30
- public Proxy ( IOptions < ConfigurationSettings > settings , ILoggerFactory loggerFactory )
31
- {
27
+ public Proxy ( IOptions < ConfigurationSettings > settings , ILoggerFactory loggerFactory ) {
32
28
33
29
InitializeMethodStrategies ( ) ;
34
30
@@ -37,8 +33,7 @@ public Proxy(IOptions<ConfigurationSettings> settings, ILoggerFactory loggerFact
37
33
38
34
}
39
35
40
- public async Task StartAsync ( IEnumerable < TwitchTopic > topics , CancellationToken token )
41
- {
36
+ public async Task StartAsync ( IEnumerable < TwitchTopic > topics , CancellationToken token ) {
42
37
43
38
_Topics = topics ;
44
39
@@ -49,17 +44,15 @@ public async Task StartAsync(IEnumerable<TwitchTopic> topics, CancellationToken
49
44
50
45
await StartListening ( topics ) ;
51
46
52
- while ( ! token . IsCancellationRequested )
53
- {
47
+ while ( ! token . IsCancellationRequested ) {
54
48
55
49
var buffer = new byte [ 1024 ] ;
56
50
var messageBuffer = new ArraySegment < byte > ( buffer ) ;
57
51
var completeMessage = new StringBuilder ( ) ;
58
52
59
53
var result = await _Socket . ReceiveAsync ( messageBuffer , token ) ;
60
54
completeMessage . Append ( Encoding . UTF8 . GetString ( messageBuffer ) ) ;
61
- while ( ! result . EndOfMessage )
62
- {
55
+ while ( ! result . EndOfMessage ) {
63
56
buffer = new byte [ 1024 ] ;
64
57
messageBuffer = new ArraySegment < byte > ( buffer ) ;
65
58
result = await _Socket . ReceiveAsync ( messageBuffer , token ) ;
@@ -71,12 +64,13 @@ public async Task StartAsync(IEnumerable<TwitchTopic> topics, CancellationToken
71
64
break ;
72
65
}
73
66
74
- try
75
- {
67
+ try {
76
68
HandleMessage ( completeMessage . ToString ( ) ) ;
77
- } catch ( UnhandledPubSubMessageException ) {
69
+ }
70
+ catch ( UnhandledPubSubMessageException ) {
78
71
// do nothing
79
- } catch ( Exception e ) {
72
+ }
73
+ catch ( Exception e ) {
80
74
_Logger . LogError ( e , "Error while parsing message from Twitch: " + completeMessage . ToString ( ) ) ;
81
75
_Logger . LogError ( "Reconnecting..." ) ;
82
76
_Reconnect = true ;
@@ -88,44 +82,39 @@ public async Task StartAsync(IEnumerable<TwitchTopic> topics, CancellationToken
88
82
89
83
}
90
84
91
- if ( _Reconnect ) _ = Task . Run ( ( ) => StartAsync ( topics , token ) ) ;
92
-
85
+ if ( _Reconnect ) {
86
+ _ = Task . Run ( ( ) => StartAsync ( topics , token ) ) ;
87
+ }
93
88
}
94
89
95
90
96
- private delegate bool OnReceivedMessage ( string message ) ;
97
- private List < OnReceivedMessage > _Strategies = new List < OnReceivedMessage > ( ) ;
91
+ private delegate bool OnReceivedMessage ( IPubSubReceiveMessage message ) ;
92
+ private readonly List < OnReceivedMessage > _Strategies = new List < OnReceivedMessage > ( ) ;
98
93
99
- private void HandleMessage ( string receivedMessage )
100
- {
94
+ private void HandleMessage ( string receivedMessage ) {
95
+ var message = JsonConvert . DeserializeObject < IPubSubReceiveMessage > ( receivedMessage ) ;
101
96
102
- var jDoc = JObject . Parse ( receivedMessage ) ;
103
- var messageType = jDoc [ "type" ] . Value < string > ( ) ;
104
- if ( messageType == "RESPONSE" && jDoc [ "error" ] . Value < string > ( ) != "" )
105
- {
106
- throw new Exception ( "Unable to connect" ) ;
107
- } else if ( messageType == "RESPONSE" ) {
97
+ if ( message is ResponseReceiveMessage response ) {
98
+ if ( ! string . IsNullOrWhiteSpace ( response . Error ) ) {
99
+ throw new Exception ( $ "Unable to connect: { response . Error } ") ;
100
+ }
108
101
return ;
109
102
}
110
103
111
- foreach ( var strategy in _Strategies )
112
- {
113
- if ( strategy ( receivedMessage ) ) return ;
104
+ foreach ( var strategy in _Strategies ) {
105
+ if ( strategy ( message ) ) {
106
+ return ;
107
+ }
114
108
}
115
109
116
110
throw new UnhandledPubSubMessageException ( ) ;
117
-
118
111
}
119
112
120
- private async Task StartListening ( IEnumerable < TwitchTopic > topics )
121
- {
122
-
113
+ private async Task StartListening ( IEnumerable < TwitchTopic > topics ) {
123
114
_Socket = new ClientWebSocket ( ) ;
124
115
125
- var message = new PubSubListen
126
- {
127
- data = new PubSubListen . PubSubListenData
128
- {
116
+ var message = new PubSubListen {
117
+ data = new PubSubListen . PubSubListenData {
129
118
auth_token = _Configuration . OAuthToken ,
130
119
topics = topics . Select ( t => t . TopicString ) . ToArray ( )
131
120
}
@@ -136,31 +125,30 @@ await _Socket.ConnectAsync(new Uri("wss://pubsub-edge.twitch.tv:443"), Cancellat
136
125
137
126
}
138
127
139
- private void _PingTimer_Elapsed ( object sender , ElapsedEventArgs e )
140
- {
128
+ private void _PingTimer_Elapsed ( object sender , ElapsedEventArgs e ) {
141
129
var message = @"{ ""type"": ""PING"" }" ;
142
130
SendMessageOnSocket ( message ) . GetAwaiter ( ) . GetResult ( ) ;
143
131
_PongTimer = new System . Timers . Timer ( TimeSpan . FromSeconds ( 10 ) . TotalMilliseconds ) ;
144
132
_PongTimer . Elapsed += _PongTimer_Elapsed ;
145
133
_PongTimer . Start ( ) ;
146
134
_PingAcknowledged = false ;
147
135
148
- // TODO: handle the lack of returned PONG message
136
+ // TODO: handle the lack of returned PONG message
149
137
150
138
}
151
139
152
- private void _PongTimer_Elapsed ( object sender , ElapsedEventArgs e )
153
- {
140
+ private void _PongTimer_Elapsed ( object sender , ElapsedEventArgs e ) {
154
141
if ( ! _PingAcknowledged ) {
155
142
_Reconnect = true ;
156
143
_PongTimer . Dispose ( ) ;
157
144
}
158
145
}
159
146
160
- private Task SendMessageOnSocket ( string message )
161
- {
147
+ private Task SendMessageOnSocket ( string message ) {
162
148
163
- if ( _Socket . State != WebSocketState . Open ) return Task . CompletedTask ;
149
+ if ( _Socket . State != WebSocketState . Open ) {
150
+ return Task . CompletedTask ;
151
+ }
164
152
165
153
var byteArray = Encoding . ASCII . GetBytes ( message ) ;
166
154
return _Socket . SendAsync ( byteArray , WebSocketMessageType . Text , true , CancellationToken . None ) ;
@@ -183,9 +171,9 @@ private void InitializeMethodStrategies() {
183
171
184
172
}
185
173
186
- private bool HandlePongMessage ( string message ) {
174
+ private bool HandlePongMessage ( IPubSubReceiveMessage message ) {
187
175
188
- if ( message . Contains ( @"""PONG""" ) ) {
176
+ if ( message is PongReceiveMessage ) {
189
177
_PingAcknowledged = true ;
190
178
_PongTimer . Stop ( ) ;
191
179
_PongTimer . Dispose ( ) ;
@@ -197,9 +185,9 @@ private bool HandlePongMessage(string message) {
197
185
198
186
}
199
187
200
- private bool HandleReconnectMessage ( string message ) {
188
+ private bool HandleReconnectMessage ( IPubSubReceiveMessage message ) {
201
189
202
- if ( message . Contains ( @"""RECONNECT""" ) ) {
190
+ if ( message is ReconnectReceiveMessage ) {
203
191
204
192
_Reconnect = true ;
205
193
@@ -210,26 +198,12 @@ private bool HandleReconnectMessage(string message) {
210
198
211
199
}
212
200
213
- private bool HandleChannelPointsMessage ( string message ) {
214
-
215
- var jDoc = JObject . Parse ( message ) ;
216
-
217
- if ( jDoc [ "type" ] . Value < string > ( ) == "MESSAGE" && jDoc [ "data" ] [ "topic" ] . Value < string > ( ) . StartsWith ( "channel-points-channel-v1" ) ) {
201
+ private bool HandleChannelPointsMessage ( IPubSubReceiveMessage message ) {
218
202
219
- var innerMessage = jDoc [ "data" ] [ "message" ] . Value < string > ( ) ;
220
-
221
- PubSubRedemptionMessage messageObj = null ;
222
- try
223
- {
224
- messageObj = JsonConvert . DeserializeObject < PubSubRedemptionMessage > ( innerMessage ) ;
225
- } catch ( Exception e ) {
226
- _Logger . LogError ( e , "Error while deserializing the message" ) ;
227
- _Logger . LogInformation ( "Message contents: " + innerMessage ) ;
228
- }
229
- _Logger . LogWarning ( $ "Channel Points redeemed: { innerMessage } ") ;
230
- OnChannelPointsRedeemed ? . Invoke ( null , messageObj ? . data ) ;
203
+ if ( message is ChannelPointsReceiveMessage channelPointsMessage ) {
204
+ _Logger . LogWarning ( $ "Channel Points redeemed: { channelPointsMessage . Data . Message } ") ;
205
+ OnChannelPointsRedeemed ? . Invoke ( null , channelPointsMessage ? . Data ? . Message ) ;
231
206
return true ;
232
-
233
207
}
234
208
235
209
return false ;
@@ -239,35 +213,30 @@ private bool HandleChannelPointsMessage(string message) {
239
213
#endregion
240
214
241
215
#region IDisposable Support
242
- private bool disposedValue = false ; // To detect redundant calls
216
+ private bool _DisposedValue = false ; // To detect redundant calls
243
217
private bool _PingAcknowledged ;
244
218
private IEnumerable < TwitchTopic > _Topics ;
245
219
246
- protected virtual void Dispose ( bool disposing )
247
- {
248
- if ( ! disposedValue )
249
- {
250
- if ( disposing )
251
- {
220
+ protected virtual void Dispose ( bool disposing ) {
221
+ if ( ! _DisposedValue ) {
222
+ if ( disposing ) {
252
223
_PingTimer . Dispose ( ) ;
253
224
_PongTimer . Dispose ( ) ;
254
225
}
255
226
256
227
_Socket . Dispose ( ) ;
257
228
258
- disposedValue = true ;
229
+ _DisposedValue = true ;
259
230
}
260
231
}
261
232
262
- ~ Proxy ( )
263
- {
233
+ ~ Proxy ( ) {
264
234
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
265
235
Dispose ( false ) ;
266
236
}
267
237
268
238
// This code added to correctly implement the disposable pattern.
269
- public void Dispose ( )
270
- {
239
+ public void Dispose ( ) {
271
240
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
272
241
Dispose ( true ) ;
273
242
// TODO: uncomment the following line if the finalizer is overridden above.
0 commit comments