This commit is contained in:
Adam R. Grey 2022-01-16 15:10:20 -05:00
parent 09c69bcd19
commit f56176fafb
7 changed files with 331 additions and 144 deletions

157
Httpd.cs Normal file
View File

@ -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<string, RequestHandler> psuedoResources = new Dictionary<string, RequestHandler>();
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";
// }
// }
}
}
}
}
}

View File

@ -1,11 +1,41 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
namespace twitcher
{
public class OAuthTokenGetter
{
public static OAuthToken DoIt(HttpClient client, string clientId, string clientSecret, string[] scopes = null)
private static readonly AutoResetEvent _signal = new AutoResetEvent(false);
public static string GetUser(Httpd server, string clientId, string redirectUri)
{
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;
}
public static OAuthToken GetApplication(HttpClient client, string clientId, string clientSecret, string[] scopes = null)
{
var scopeString = "";
if (scopes != null && scopes.Length > 0)
@ -26,7 +56,7 @@ public class OAuthTokenGetter
Console.Error.WriteLine(response);
throw new Exception(response);
}
//Console.WriteLine(response);
Console.WriteLine(response);
return JsonConvert.DeserializeObject<OAuthToken>(response);
}
}
@ -38,3 +68,4 @@ public class OAuthToken
public string[] scope { get; set; }
public string token_type { get; set; }
}
}

View File

@ -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;
@ -29,6 +32,12 @@ namespace twitcher
}
twitcherConf = JsonConvert.DeserializeObject<Config>(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;
@ -36,15 +45,9 @@ namespace twitcher
//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);
}
}

View File

@ -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));
}
}

View File

@ -26,13 +26,12 @@ namespace TwitchEventSub
private string clientId;
private HMACSHA256 hmacsha256 = null;
private List<Task> tasksToNotActuallyAwait = new List<Task>();
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<string>();
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;
}

View File

@ -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; }

27
www/login.html Normal file
View File

@ -0,0 +1,27 @@
<html>
<head>
<script type="text/javascript">
window.addEventListener('load', (event) => {
document.querySelector("body").appendChild(document.createTextNode(document.location.hash));
const myInit = {
method: 'POST',
mode: 'cors',
body: document.location.hash
};
let myRequest = new Request('token');
fetch(myRequest, myInit).then(function (response) {
document.querySelector("body").appendChild(document.createTextNode(response));
});
});
</script>
</head>
<body>
hi
</body>
</html>