From f56176fafbf94d4cdfdd40b8948991f670965291 Mon Sep 17 00:00:00 2001 From: "Adam R. Grey" Date: Sun, 16 Jan 2022 15:10:20 -0500 Subject: [PATCH] final --- Httpd.cs | 157 ++++++++++++++++++ OAuthTokenGetter.cs | 85 +++++++--- Program.cs | 33 ++-- SilverMeddlistsSpecific.cs | 66 ++++++++ TwitchEventSub/Receiver.cs | 103 +----------- .../Types/EventSubSubscription/Condition.cs | 4 +- www/login.html | 27 +++ 7 files changed, 331 insertions(+), 144 deletions(-) create mode 100644 Httpd.cs create mode 100644 SilverMeddlistsSpecific.cs create mode 100644 www/login.html diff --git a/Httpd.cs b/Httpd.cs new file mode 100644 index 0000000..5abf531 --- /dev/null +++ b/Httpd.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +namespace twitcher +{ + public class Httpd + { + public class ResponseSet + { + public string Text { get; set; } = ":)"; + public HttpStatusCode statusCode { get; set; } = HttpStatusCode.OK; + } + public delegate ResponseSet RequestHandler(HttpListenerRequest request); + private Dictionary psuedoResources = new Dictionary(); + private HttpListener server; + public Httpd(int port) + { + server = new HttpListener(); + server.Prefixes.Add($"http://*:{port}/"); + server.Start(); + } + public void HandleEndpoint(string destination, RequestHandler handler) + { + lock (psuedoResources) + { + psuedoResources[destination] = handler; + } + } + public void UnhandleEndpoint(string destination, RequestHandler handler) + { + lock (psuedoResources) + { + psuedoResources.Remove(destination); + } + } + public async Task go() + { + while (true) + { + HttpListenerContext context = await server.GetContextAsync(); + HttpListenerResponse response = context.Response; + + string page = context.Request.Url.LocalPath.Substring(1); + var resp = "no resource"; + response.StatusCode = (int)HttpStatusCode.NotFound; + lock (psuedoResources) + { + if (psuedoResources.ContainsKey(page)) + { + string responseText; + int responseCode; + try + { + var responseSet = psuedoResources[page](context.Request); + responseText = responseSet.Text; + responseCode = (int)responseSet.statusCode; + } + catch (Exception e) + { + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + responseText = ":("; + Console.Error.WriteLine(JsonConvert.SerializeObject(e)); + } + + if (response.StatusCode != (int)HttpStatusCode.OK) + { + Console.WriteLine($"returning status code {response.StatusCode}"); + } + byte[] buffer = Encoding.UTF8.GetBytes(resp); + response.ContentLength64 = buffer.Length; + Stream st = response.OutputStream; + st.Write(buffer, 0, buffer.Length); + response.Close(); + } + // if (page == "twitcherize") + // { + // byte[] rawIncomingBytes = new byte[context.Request.ContentLength64]; + // var readBytes = context.Request.InputStream.Read(rawIncomingBytes, 0, (int)context.Request.ContentLength64); + + + // if (VerifySignature(context.Request.Headers["Twitch-Eventsub-Message-Signature"], + // context.Request.ContentEncoding, + // context.Request.Headers["Twitch-Eventsub-Message-Id"], + // context.Request.Headers["Twitch-Eventsub-Message-Timestamp"], + // rawIncomingBytes)) + // { + // try + // { + // if (twitchogrambuffer.Contains(context.Request.Headers["Twitch-Eventsub-Message-Id"])) + // { + // Console.WriteLine("duplicate message received. Ignoring."); + // } + // else + // { + // var incomingText = context.Request.ContentEncoding.GetString(rawIncomingBytes); + // //Console.WriteLine($"{context.Request.Headers["Twitch-Eventsub-Message-Id"]} looks new to me."); + // lock (twitchogrambuffer) + // { + // var fromTwitch = SubscribableTypesTranslation.RefineTwitchogram(incomingText); + // if (fromTwitch.challenge != null) + // { + // resp = fromTwitch.challenge; + // Console.WriteLine("challenge responded to, should be subscribed"); + // } + // else if (fromTwitch.subscription.status == "authorization_revoked") + // { + // resp = "ok... :("; + // Console.WriteLine($"auth revoked for {fromTwitch.subscription.type} ({fromTwitch.subscription.id})"); + // } + // else + // { + // var foundAHandler = false; + // foreach (var kvp in psuedoResources) + // { + // if (kvp.Key.id == fromTwitch.subscription.id) + // { + // kvp.Value(fromTwitch); + // resp = ":)"; + // foundAHandler = true; + // break; + // } + // } + // if (!foundAHandler) + // { + // Console.WriteLine($"I don't have a handler for {fromTwitch.subscription.type} ({fromTwitch.subscription.id})? o_O"); + // resp = ":S"; + // } + // } + // } + // tasksToNotActuallyAwait.Add(logMessageId(context.Request.Headers["Twitch-Eventsub-Message-Id"])); + // response.StatusCode = (int)HttpStatusCode.OK; + // } + // } + // catch (Exception e) + // { + // Console.Error.WriteLine("something went wrong calling resource."); + // Console.Error.WriteLine(JsonConvert.SerializeObject(e)); + // response.StatusCode = (int)HttpStatusCode.InternalServerError; + // resp = "error :("; + // } + // } + // else + // { + // Console.WriteLine("couldn't verify signature."); + // response.StatusCode = (int)HttpStatusCode.Forbidden; + // resp = "VoteNay"; + // } + // } + } + } + } + } +} \ No newline at end of file diff --git a/OAuthTokenGetter.cs b/OAuthTokenGetter.cs index 331d5c6..67dc825 100644 --- a/OAuthTokenGetter.cs +++ b/OAuthTokenGetter.cs @@ -1,40 +1,71 @@ using Newtonsoft.Json; using System; using System.IO; +using System.Net; using System.Net.Http; +using System.Text; +using System.Threading; -public class OAuthTokenGetter +namespace twitcher { - public static OAuthToken DoIt(HttpClient client, string clientId, string clientSecret, string[] scopes = null) + public class OAuthTokenGetter { - var scopeString = ""; - if (scopes != null && scopes.Length > 0) + private static readonly AutoResetEvent _signal = new AutoResetEvent(false); + public static string GetUser(Httpd server, string clientId, string redirectUri) { - scopeString = "&scope=" + string.Join(' ', scopes); + server.HandleEndpoint("login", (HttpListenerRequest request) => + { + var toReturn = new Httpd.ResponseSet(); + Console.WriteLine($"query string: {request.QueryString}"); + Console.WriteLine($"RawUrl: {request.RawUrl}"); + toReturn.Text = File.ReadAllText("login.html"); + return toReturn; + }); + var token = ""; + server.HandleEndpoint("token", (HttpListenerRequest request) => + { + byte[] rawIncomingBytes = new byte[request.ContentLength64]; + request.InputStream.Read(rawIncomingBytes, 0, (int)request.ContentLength64); + token = request.ContentEncoding.GetString(rawIncomingBytes); + _signal.Set(); + return new Httpd.ResponseSet() {Text = "k" }; + }); + var sendTo = $"https://id.twitch.tv/oauth2/authorize?client_id={clientId}&redirect_uri={redirectUri}&response_type=token&state=constant_agitation&scope=channel.ban"; + Console.WriteLine($"go to {sendTo} plz, I'll wait here"); + _signal.WaitOne(new TimeSpan(1, 0, 0)); + return token; } - var postURI = $"https://id.twitch.tv/oauth2/token?client_id={clientId}&client_secret={clientSecret}&grant_type=client_credentials{scopeString}"; - //Console.WriteLine(postURI); - var r = client.PostAsync(postURI, null).Result; + public static OAuthToken GetApplication(HttpClient client, string clientId, string clientSecret, string[] scopes = null) + { + var scopeString = ""; + if (scopes != null && scopes.Length > 0) + { + scopeString = "&scope=" + string.Join(' ', scopes); + } + var postURI = $"https://id.twitch.tv/oauth2/token?client_id={clientId}&client_secret={clientSecret}&grant_type=client_credentials{scopeString}"; + //Console.WriteLine(postURI); + var r = client.PostAsync(postURI, null).Result; - var response = ""; - using (var streamReader = new StreamReader(r.Content.ReadAsStream())) - { - response = streamReader.ReadToEnd(); + var response = ""; + using (var streamReader = new StreamReader(r.Content.ReadAsStream())) + { + response = streamReader.ReadToEnd(); + } + if (!r.IsSuccessStatusCode) + { + Console.Error.WriteLine(response); + throw new Exception(response); + } + Console.WriteLine(response); + return JsonConvert.DeserializeObject(response); } - if(!r.IsSuccessStatusCode) - { - Console.Error.WriteLine(response); - throw new Exception(response); - } - //Console.WriteLine(response); - return JsonConvert.DeserializeObject(response); } -} -public class OAuthToken -{ - public string access_token { get; set; } - public string refresh_token { get; set; } - public int expires_in { get; set; } - public string[] scope { get; set; } - public string token_type { get; set; } + public class OAuthToken + { + public string access_token { get; set; } + public string refresh_token { get; set; } + public int expires_in { get; set; } + public string[] scope { get; set; } + public string token_type { get; set; } + } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index f93b7d0..38b0f0f 100644 --- a/Program.cs +++ b/Program.cs @@ -5,17 +5,20 @@ using System.IO; using System.Threading.Tasks; using TwitchEventSub.Types.EventSubSubscription; using Newtonsoft.Json; +using System.Net; namespace twitcher { class Program { - public static TwitchEventSub.Receiver httpd; - public static Config twitcherConf; - //public static Telefranz tf; + static TwitchEventSub.Receiver httpd; + static Config twitcherConf; + static Telefranz tf; + static HttpListener server; static void Main(string[] args) { - JsonConvert.DefaultSettings = () => { + JsonConvert.DefaultSettings = () => + { var s = new JsonSerializerSettings(); s.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); return s; @@ -28,23 +31,23 @@ namespace twitcher return; } twitcherConf = JsonConvert.DeserializeObject(File.ReadAllText("appsettings.json")); - + + var httpd = new Httpd(twitcherConf.port); + var theRunTask = httpd.go(); + var token = OAuthTokenGetter.GetUser(httpd, twitcherConf.clientId, "localhost:8420/login"); + + //var token = OAuthTokenGetter.DoIt(client, twitcherConf.clientId, twitcherConf.clientSecret, new string[]{"channel:moderate"}); + // Telefranz.Configure(name: twitcherConf.kafka_name, bootstrap_servers: twitcherConf.kafka_bootstrap); // Task.WaitAll(Task.Delay(1000)); // tf = Telefranz.Instance; - + //TODO: throw a request out somewhere asking for public url //tf.ProduceMessage(new silver_messages.directorial.execute_command(){command = "ssl_expose", args = {twitcherConf.port.ToString()}}); - httpd = new TwitchEventSub.Receiver(twitcherConf.port, twitcherConf.clientId, twitcherConf.clientSecret, twitcherConf.public_uri); - var theRunTask = httpd.go(); - httpd.Subscribe(SubscribableTypes.channel_follow, - new TwitchEventSub.Types.Conditions.ChannelFollow() { broadcaster_user_id = "12826" }, - (tg) => { - Console.WriteLine("I'm the handler for a twitchogram!"); - Console.WriteLine(JsonConvert.SerializeObject(tg)); - } - ); + var rec = new TwitchEventSub.Receiver(server, twitcherConf.clientId, twitcherConf.clientSecret, twitcherConf.public_uri); + + var sm = new SilverMeddlistsSpecific(httpd, tf, twitcherConf.clientId); Task.WaitAll(theRunTask); } } diff --git a/SilverMeddlistsSpecific.cs b/SilverMeddlistsSpecific.cs new file mode 100644 index 0000000..6c282f8 --- /dev/null +++ b/SilverMeddlistsSpecific.cs @@ -0,0 +1,66 @@ +using System; +using franz; +using TwitchEventSub; +using TwitchEventSub.Types; +using Conditions = TwitchEventSub.Types.Conditions; +using TwitchEventSub.Types.EventSubSubscription; +using Newtonsoft.Json; + +public class SilverMeddlistsSpecific +{ + public Receiver Httpd { get; } + public Telefranz Tf { get; } + private const string silverMeddlistsBroadcasterID = "598251923"; + public SilverMeddlistsSpecific(Receiver httpd, Telefranz tf, string clientId) + { + Httpd = httpd; + Tf = tf; + + var targetSilverMeddlistsChannel = new Conditions.TargetChannel(){broadcaster_user_id = silverMeddlistsBroadcasterID}; + var targetCustomChannelPointsReward = new Conditions.TargetCustomChannelPointsReward(){}; + var targetSelf = new Conditions.TargetSelf(){client_id = clientId}; + + Httpd.Subscribe(SubscribableTypes.channel_ban, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_channel_points_custom_reward_add, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_channel_points_custom_reward_redemption_add, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_channel_points_custom_reward_redemption_update, targetCustomChannelPointsReward, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_channel_points_custom_reward_remove, targetCustomChannelPointsReward, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_channel_points_custom_reward_update, targetCustomChannelPointsReward, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_cheer, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_follow, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_goal_begin, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_goal_end, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_goal_progress, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_hype_train_begin, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_hype_train_end, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_hype_train_progress, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_moderator_add, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_moderator_remove, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_poll_begin, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_poll_end, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_poll_progress, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_prediction_begin, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_prediction_end, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_prediction_lock, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_prediction_progress, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_raid, new Conditions.ChannelRaid() { to_broadcaster_user_id = silverMeddlistsBroadcasterID}, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_subscribe, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_subscription_end, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_subscription_gift, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_subscription_message, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_unban, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.channel_update, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.drop_entitlement_grant, new Conditions.DropEntitlementGrant(), defaultEvent); + Httpd.Subscribe(SubscribableTypes.extension_bits_transaction_create, new Conditions.TargetExtension(), defaultEvent); + Httpd.Subscribe(SubscribableTypes.stream_offline, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.stream_online, targetSilverMeddlistsChannel, defaultEvent); + Httpd.Subscribe(SubscribableTypes.user_authorization_grant, targetSelf, defaultEvent); + Httpd.Subscribe(SubscribableTypes.user_authorization_revoke, targetSelf, defaultEvent); + Httpd.Subscribe(SubscribableTypes.user_update, targetSilverMeddlistsChannel, defaultEvent); + } + private void defaultEvent(Twitchogram twitchogram) + { + Console.WriteLine(JsonConvert.SerializeObject(twitchogram)); + } + +} \ No newline at end of file diff --git a/TwitchEventSub/Receiver.cs b/TwitchEventSub/Receiver.cs index 1127755..0feedcf 100644 --- a/TwitchEventSub/Receiver.cs +++ b/TwitchEventSub/Receiver.cs @@ -26,13 +26,12 @@ namespace TwitchEventSub private string clientId; private HMACSHA256 hmacsha256 = null; private List tasksToNotActuallyAwait = new List(); - public Receiver(int port, string clientId, string clientSecret, string publicUrl) + public Receiver(HttpListener server, string clientId, string clientSecret, string publicUrl) { this.clientId = clientId; this.publicUrl = new Uri(publicUrl + "/twitcherize"); twitchogrambuffer = new Queue(); - server = new HttpListener(); - server.Prefixes.Add($"http://*:{port}/"); + this.server = server; authorizeClient(clientId, clientSecret); @@ -55,7 +54,6 @@ namespace TwitchEventSub private void authorizeClient(string clientId, string clientSecret) { - this.token = OAuthTokenGetter.DoIt(client, clientId, clientSecret); Console.WriteLine($"got a token"); client.DefaultRequestHeaders.Add("Client-ID", $"{this.clientId }"); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {this.token.access_token}"); @@ -124,104 +122,9 @@ namespace TwitchEventSub public async Task go() { going = true; - server.Start(); await Task.Run(() => { - while (true) - { - HttpListenerContext context = server.GetContext(); - HttpListenerResponse response = context.Response; - - string page = context.Request.Url.LocalPath.Substring(1); - var resp = "no resource"; - response.StatusCode = (int)HttpStatusCode.NotFound; - lock (psuedoResources) - { - if (page == "twitcherize") - { - byte[] rawIncomingBytes = new byte[context.Request.ContentLength64]; - var readBytes = context.Request.InputStream.Read(rawIncomingBytes, 0, (int)context.Request.ContentLength64); - - - if (VerifySignature(context.Request.Headers["Twitch-Eventsub-Message-Signature"], - context.Request.ContentEncoding, - context.Request.Headers["Twitch-Eventsub-Message-Id"], - context.Request.Headers["Twitch-Eventsub-Message-Timestamp"], - rawIncomingBytes)) - { - try - { - if (twitchogrambuffer.Contains(context.Request.Headers["Twitch-Eventsub-Message-Id"])) - { - Console.WriteLine("duplicate message received. Ignoring."); - } - else - { - var incomingText = context.Request.ContentEncoding.GetString(rawIncomingBytes); - //Console.WriteLine($"{context.Request.Headers["Twitch-Eventsub-Message-Id"]} looks new to me."); - lock (twitchogrambuffer) - { - var fromTwitch = SubscribableTypesTranslation.RefineTwitchogram(incomingText); - if (fromTwitch.challenge != null) - { - resp = fromTwitch.challenge; - Console.WriteLine("challenge responded to, should be subscribed"); - } - else if (fromTwitch.subscription.status == "authorization_revoked") - { - resp = "ok... :("; - Console.WriteLine($"auth revoked for {fromTwitch.subscription.type} ({fromTwitch.subscription.id})"); - } - else - { - var foundAHandler = false; - foreach (var kvp in psuedoResources) - { - if (kvp.Key.id == fromTwitch.subscription.id) - { - kvp.Value(fromTwitch); - resp = ":)"; - foundAHandler = true; - break; - } - } - if (!foundAHandler) - { - Console.WriteLine($"I don't have a handler for {fromTwitch.subscription.type} ({fromTwitch.subscription.id})? o_O"); - resp = ":S"; - } - } - } - tasksToNotActuallyAwait.Add(logMessageId(context.Request.Headers["Twitch-Eventsub-Message-Id"])); - response.StatusCode = (int)HttpStatusCode.OK; - } - } - catch (Exception e) - { - Console.Error.WriteLine("something went wrong calling resource."); - Console.Error.WriteLine(JsonConvert.SerializeObject(e)); - response.StatusCode = (int)HttpStatusCode.InternalServerError; - resp = "error :("; - } - } - else - { - Console.WriteLine("couldn't verify signature."); - response.StatusCode = (int)HttpStatusCode.Forbidden; - resp = "VoteNay"; - } - } - } - if(response.StatusCode != (int)HttpStatusCode.OK) - { - Console.WriteLine($"returning status code {response.StatusCode}"); - } - byte[] buffer = Encoding.UTF8.GetBytes(resp); - response.ContentLength64 = buffer.Length; - Stream st = response.OutputStream; - st.Write(buffer, 0, buffer.Length); - response.Close(); - } + }); going = false; } diff --git a/TwitchEventSub/Types/EventSubSubscription/Condition.cs b/TwitchEventSub/Types/EventSubSubscription/Condition.cs index 4c4f450..20cee97 100644 --- a/TwitchEventSub/Types/EventSubSubscription/Condition.cs +++ b/TwitchEventSub/Types/EventSubSubscription/Condition.cs @@ -12,7 +12,7 @@ namespace TwitchEventSub.Types.Conditions } } - public abstract class TargetChannel : Condition + public class TargetChannel : Condition { public string broadcaster_user_id { get; set; } } @@ -28,7 +28,7 @@ namespace TwitchEventSub.Types.Conditions { public string user_id { get; set; } } - public abstract class TargetCustomChannelPointsReward : Condition + public class TargetCustomChannelPointsReward : Condition { public string broadcaster_user_id { get; set; } public string reward_id { get; set; } diff --git a/www/login.html b/www/login.html new file mode 100644 index 0000000..065c97c --- /dev/null +++ b/www/login.html @@ -0,0 +1,27 @@ + + + + + + + + hi + + + \ No newline at end of file