Skip to content

Commit 88deeb5

Browse files
committed
Notification sound for channel points WORKING!
1 parent 3b489ce commit 88deeb5

File tree

6 files changed

+124
-46
lines changed

6 files changed

+124
-46
lines changed

Fritz.StreamTools/Services/TwitchPubSubService.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Fritz.Twitch.PubSub;
44
using Microsoft.AspNetCore.SignalR;
55
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Hosting;
78
using Microsoft.Extensions.Options;
89
using System;
@@ -16,13 +17,13 @@ namespace Fritz.StreamTools.Services
1617
public class TwitchPubSubService : IHostedService
1718
{
1819

19-
private readonly IHubContext<AttentionHub, IAttentionHubClient> _HubContext;
20+
private IServiceProvider _ServiceProvider;
2021
private readonly Twitch.PubSub.Proxy _Proxy;
2122
private readonly ConfigurationSettings _Configuration;
2223

23-
public TwitchPubSubService(IHubContext<AttentionHub, IAttentionHubClient> hubContext, Twitch.PubSub.Proxy proxy, IOptions<ConfigurationSettings> settings)
24+
public TwitchPubSubService(IServiceProvider serviceProvider, Twitch.PubSub.Proxy proxy, IOptions<ConfigurationSettings> settings)
2425
{
25-
_HubContext = hubContext;
26+
_ServiceProvider = serviceProvider;
2627
_Proxy = proxy;
2728
_Configuration = settings.Value;
2829
}
@@ -35,7 +36,11 @@ public Task StartAsync(CancellationToken cancellationToken)
3536

3637
private void _Proxy_OnChannelPointsRedeemed(object sender, ChannelRedemption e)
3738
{
38-
_HubContext.Clients.All.PlaySoundEffect("pointsredeemed.mp3");
39+
using (var scope = _ServiceProvider.CreateScope())
40+
{
41+
var context = scope.ServiceProvider.GetRequiredService<IHubContext<AttentionHub, IAttentionHubClient>>();
42+
context.Clients.All.PlaySoundEffect("pointsredeemed.mp3");
43+
}
3944
}
4045

4146
public Task StopAsync(CancellationToken cancellationToken)

Fritz.StreamTools/StartupServices/ConfigureServices.cs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ namespace Fritz.StreamTools.StartupServices
2222
{
2323
public static class ConfigureServices
2424
{
25-
private static Dictionary<Type, string[]> _servicesRequiredConfiguration;
26-
private static IConfiguration _configuration;
25+
private static Dictionary<Type, string[]> _ServicesRequiredConfiguration;
26+
private static IConfiguration _Configuration;
2727

2828
public static void Execute(IServiceCollection services, IConfiguration configuration, Dictionary<Type, string[]> servicesRequiredConfiguration)
2929
{
30-
_configuration = configuration;
31-
_servicesRequiredConfiguration = servicesRequiredConfiguration;
30+
_Configuration = configuration;
31+
_ServicesRequiredConfiguration = servicesRequiredConfiguration;
3232

3333
services.AddSingleton<RundownItemRepository>();
3434
services.AddSingleton<RundownRepository>();
@@ -56,13 +56,17 @@ public static void Execute(IServiceCollection services, IConfiguration configura
5656

5757
services.AddLazyCache();
5858

59+
services.RegisterTwitchPubSub();
60+
5961
RegisterConfiguredServices(services, configuration);
6062
RegisterGitHubServices(services, configuration);
63+
64+
6165
}
6266

6367
private static void RegisterConfiguredServices(IServiceCollection services, IConfiguration configuration)
6468
{
65-
foreach (var configuredService in _servicesRequiredConfiguration)
69+
foreach (var configuredService in _ServicesRequiredConfiguration)
6670
{
6771
if (!configuredService.Value.Any(cs => configuration[cs] == null))
6872
{
@@ -78,7 +82,7 @@ private static void RegisterGitHubServices(IServiceCollection services, IConfigu
7882

7983
services.AddTransient(_ => new GitHubClient(new ProductHeaderValue("Fritz.StreamTools"))
8084
{
81-
Credentials = new Credentials(_configuration["GitHub:User"], _configuration["GitHub:AuthenticationToken"])
85+
Credentials = new Credentials(_Configuration["GitHub:User"], _Configuration["GitHub:AuthenticationToken"])
8286
});
8387

8488
services.AddHttpClient("GitHub", c =>
@@ -138,10 +142,6 @@ private static void AddStreamService<TStreamService>(this IServiceCollection ser
138142
return;
139143
}
140144

141-
if (typeof(TStreamService) == typeof(TwitchService)) {
142-
services.AddSingleton<Twitch.PubSub.Proxy>();
143-
}
144-
145145
// Configure and grab a logger so that we can log information
146146
// about the creation of the services
147147
var provider = services.BuildServiceProvider(); // Build a 'temporary' instance of the DI container
@@ -153,17 +153,6 @@ private static void AddStreamService<TStreamService>(this IServiceCollection ser
153153
services.AddSingleton(service as IStreamService);
154154
services.AddSingleton(service);
155155

156-
if (typeof(TStreamService) == typeof(TwitchService))
157-
{
158-
var pubSub = new TwitchPubSubService(
159-
provider.GetRequiredService<IHubContext<AttentionHub, IAttentionHubClient>>(),
160-
provider.GetRequiredService<Twitch.PubSub.Proxy>(),
161-
provider.GetRequiredService<IOptions<Twitch.ConfigurationSettings>>());
162-
services.AddSingleton(pubSub as IHostedService);
163-
services.AddSingleton(pubSub);
164-
}
165-
166-
167156
if (service is IChatService chatService)
168157
{
169158
services.AddSingleton(chatService);
@@ -190,5 +179,24 @@ private static void AddAspNetFeatures(this IServiceCollection services)
190179

191180
}
192181

182+
private static void RegisterTwitchPubSub(this IServiceCollection services) {
183+
184+
services.AddSingleton<Twitch.PubSub.Proxy>();
185+
services.AddHostedService<TwitchPubSubService>();
186+
//var provider = services.BuildServiceProvider();
187+
188+
//var pubSub = new TwitchPubSubService(
189+
//provider,
190+
//provider.GetRequiredService<Twitch.PubSub.Proxy>(),
191+
//provider.GetRequiredService<IOptions<Twitch.ConfigurationSettings>>());
192+
//services.AddSingleton(pubSub as IHostedService);
193+
//services.AddSingleton(pubSub);
194+
195+
}
196+
197+
private static bool IsTwitchEnabled {
198+
get { return string.IsNullOrEmpty(_Configuration["StreamServices:Twitch:ClientId"]); }
199+
}
200+
193201
}
194202
}

Fritz.Twitch/PubSub/ChannelRedemption.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Fritz.Twitch.PubSub
44
{
55

6+
public class PubSubRedemptionMessage : PubSubMessage<ChannelRedemption> { }
7+
68
public class ChannelRedemption : EventArgs, IPubSubData
79
{
810
public string timestamp { get; set; }

Fritz.Twitch/PubSub/Proxy.cs

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public class Proxy : IDisposable
2121
{
2222

2323
private ClientWebSocket _Socket;
24-
private readonly System.Timers.Timer _PingTimer;
24+
private System.Timers.Timer _PingTimer;
25+
private System.Timers.Timer _PongTimer;
2526
private ConfigurationSettings _Configuration;
2627
private ILogger _Logger;
2728
private static bool _Reconnect;
@@ -34,29 +35,52 @@ public Proxy(IOptions<ConfigurationSettings> settings, ILoggerFactory loggerFact
3435
_Configuration = settings.Value;
3536
_Logger = loggerFactory.CreateLogger("TwitchPubSub");
3637

37-
_Socket = new ClientWebSocket();
38-
39-
// Start a timer to manage the connection over the websocket
40-
_PingTimer = new System.Timers.Timer(TimeSpan.FromMinutes(4).TotalMilliseconds);
41-
_PingTimer.Elapsed += _PingTimer_Elapsed;
42-
_PingTimer.Start();
43-
4438
}
4539

4640
public async Task StartAsync(IEnumerable<TwitchTopic> topics, CancellationToken token)
4741
{
4842

4943
_Topics = topics;
50-
await StartListening(topics);
51-
var messageBuffer = new Memory<byte>();
5244

45+
// Start a timer to manage the connection over the websocket
46+
_PingTimer = new System.Timers.Timer(TimeSpan.FromSeconds(30).TotalMilliseconds);
47+
_PingTimer.Elapsed += _PingTimer_Elapsed;
48+
_PingTimer.Start();
49+
50+
await StartListening(topics);
5351

5452
while (!token.IsCancellationRequested)
5553
{
5654

57-
await _Socket.ReceiveAsync(messageBuffer, token);
55+
var buffer = new byte[1024];
56+
var messageBuffer = new ArraySegment<byte>(buffer);
57+
var completeMessage = new StringBuilder();
58+
59+
var result = await _Socket.ReceiveAsync(messageBuffer, token);
60+
completeMessage.Append(Encoding.UTF8.GetString(messageBuffer));
61+
while (!result.EndOfMessage)
62+
{
63+
buffer = new byte[1024];
64+
messageBuffer = new ArraySegment<byte>(buffer);
65+
result = await _Socket.ReceiveAsync(messageBuffer, token);
66+
completeMessage.Append(Encoding.UTF8.GetString(messageBuffer));
67+
}
68+
69+
if (result.MessageType == WebSocketMessageType.Close) {
70+
_Reconnect = true;
71+
break;
72+
}
5873

59-
HandleMessage(UTF8Encoding.UTF8.GetString(messageBuffer.Span));
74+
try
75+
{
76+
HandleMessage(completeMessage.ToString());
77+
} catch (UnhandledPubSubMessageException) {
78+
// do nothing
79+
} catch (Exception e) {
80+
_Logger.LogError(e, "Error while parsing message from Twitch: " + completeMessage.ToString());
81+
_Logger.LogError("Reconnecting...");
82+
_Reconnect = true;
83+
}
6084

6185
if (_Reconnect) {
6286
break;
@@ -75,6 +99,15 @@ public async Task StartAsync(IEnumerable<TwitchTopic> topics, CancellationToken
7599
private void HandleMessage(string receivedMessage)
76100
{
77101

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") {
108+
return;
109+
}
110+
78111
foreach (var strategy in _Strategies)
79112
{
80113
if (strategy(receivedMessage)) return;
@@ -87,7 +120,7 @@ private void HandleMessage(string receivedMessage)
87120
private async Task StartListening(IEnumerable<TwitchTopic> topics)
88121
{
89122

90-
await _Socket.ConnectAsync(new Uri("wss://pubsub-edge.twitch.tv"), CancellationToken.None);
123+
_Socket = new ClientWebSocket();
91124

92125
var message = new PubSubListen
93126
{
@@ -98,25 +131,39 @@ private async Task StartListening(IEnumerable<TwitchTopic> topics)
98131
}
99132
};
100133

101-
await SendMessageOnSocket(JsonConvert.SerializeObject(message));
134+
await _Socket.ConnectAsync(new Uri("wss://pubsub-edge.twitch.tv:443"), CancellationToken.None)
135+
.ContinueWith(t => SendMessageOnSocket(JsonConvert.SerializeObject(message)));
102136

103137
}
104138

105139
private void _PingTimer_Elapsed(object sender, ElapsedEventArgs e)
106140
{
107141
var message = @"{ ""type"": ""PING"" }";
108142
SendMessageOnSocket(message).GetAwaiter().GetResult();
143+
_PongTimer = new System.Timers.Timer(TimeSpan.FromSeconds(10).TotalMilliseconds);
144+
_PongTimer.Elapsed += _PongTimer_Elapsed;
145+
_PongTimer.Start();
109146
_PingAcknowledged = false;
110147

111148
// TODO: handle the lack of returned PONG message
112149

113150
}
114151

115-
private async Task SendMessageOnSocket(string message)
152+
private void _PongTimer_Elapsed(object sender, ElapsedEventArgs e)
116153
{
154+
if (!_PingAcknowledged) {
155+
_Reconnect = true;
156+
_PongTimer.Dispose();
157+
}
158+
}
159+
160+
private Task SendMessageOnSocket(string message)
161+
{
162+
163+
if (_Socket.State != WebSocketState.Open) return Task.CompletedTask;
117164

118-
var byteArray = Encoding.UTF8.GetBytes(message);
119-
await _Socket.SendAsync(byteArray, WebSocketMessageType.Text, false, CancellationToken.None);
165+
var byteArray = Encoding.ASCII.GetBytes(message);
166+
return _Socket.SendAsync(byteArray, WebSocketMessageType.Text, true, CancellationToken.None);
120167

121168
}
122169

@@ -140,6 +187,8 @@ private bool HandlePongMessage(string message) {
140187

141188
if (message.Contains(@"""PONG""")) {
142189
_PingAcknowledged = true;
190+
_PongTimer.Stop();
191+
_PongTimer.Dispose();
143192
return true;
144193
}
145194

@@ -164,11 +213,19 @@ private bool HandleChannelPointsMessage(string message) {
164213

165214
var jDoc = JObject.Parse(message);
166215

167-
if (jDoc["type"].Value<string>() == "reward-redeemed") {
216+
if (jDoc["type"].Value<string>() == "MESSAGE" && jDoc["data"]["topic"].Value<string>().StartsWith("channel-points-channel-v1") ) {
168217

169-
var messageObj = JsonConvert.DeserializeObject<PubSubMessage<ChannelRedemption>>(message);
218+
var innerMessage = jDoc["data"]["message"].Value<string>();
170219

171-
OnChannelPointsRedeemed?.BeginInvoke(null, messageObj.data, null, null);
220+
PubSubRedemptionMessage messageObj = null;
221+
try
222+
{
223+
messageObj = JsonConvert.DeserializeObject<PubSubRedemptionMessage>(innerMessage);
224+
} catch (Exception e) {
225+
_Logger.LogError(e, "Error while deserializing the message");
226+
_Logger.LogInformation("Message contents: " + innerMessage);
227+
}
228+
OnChannelPointsRedeemed?.Invoke(null, messageObj?.data);
172229
return true;
173230

174231
}
@@ -191,6 +248,7 @@ protected virtual void Dispose(bool disposing)
191248
if (disposing)
192249
{
193250
_PingTimer.Dispose();
251+
_PongTimer.Dispose();
194252
}
195253

196254
_Socket.Dispose();

Fritz.Twitch/PubSub/PubSubListenMessage.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ public class PubSubListenData : IPubSubData
2323

2424
}
2525

26+
2627
}

Fritz.Twitch/PubSub/PubSubMessage.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
namespace Fritz.Twitch.PubSub
1+
using System;
2+
3+
namespace Fritz.Twitch.PubSub
24
{
35

46
public abstract class PubSubMessage<DataType> where DataType : IPubSubData
57
{
68

79
public string type { get; protected set; }
810

11+
public string nonce { get; set; } = Guid.NewGuid().ToString().Replace("-", "");
12+
913
public DataType data { get; set; }
1014

1115
}

0 commit comments

Comments
 (0)