Skip to content

Instantly share code, notes, and snippets.

@billw2012
Last active May 5, 2021 14:22
Show Gist options
  • Save billw2012/0260c511086ddaf4de0e542d41d943be to your computer and use it in GitHub Desktop.
Save billw2012/0260c511086ddaf4de0e542d41d943be to your computer and use it in GitHub Desktop.
Allows spoofing of affiliate status when using TwitchLib, for testing channel point rewards
// Usage:
// var affiliateSpoofing = new Dummy.AffiliateSpoofingHttpCallHandler();
// api = new TwitchAPI(http: affiliateSpoofing);
// affiliateSpoofing.OnRewardRedeemed += OnRewardRedeemed;
//
// Then add/remove rewards via the normal API.
// Test redemption like:
// affiliateSpoofing.FakeRedeem("My Reward Title", "fake_user_name", "text");
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TwitchLib.Api.Core.Enums;
using TwitchLib.Api.Core.HttpCallHandlers;
using TwitchLib.Api.Core.Interfaces;
using TwitchLib.Api.Helix.Models.ChannelPoints;
using TwitchLib.Api.Helix.Models.ChannelPoints.CreateCustomReward;
using TwitchLib.PubSub.Events;
public class TwitchRedirectingHttpCallHandler : IHttpCallHandler
{
private readonly IHttpCallHandler httpCallHandlerImplementation;
private const string BaseV5 = "https://api.twitch.tv/kraken";
private const string BaseHelix = "https://api.twitch.tv/helix";
private const string BaseOauthToken = "https://id.twitch.tv/oauth2/token";
public TwitchRedirectingHttpCallHandler(
IHttpCallHandler http = null,
ILogger<TwitchHttpClient> logger = null)
{
httpCallHandlerImplementation = http ?? new TwitchHttpClient(logger);
}
public delegate KeyValuePair<int, string> RedirectHandler(string payload, string clientId, string accessToken, Func<KeyValuePair<int, string>> realCall, Dictionary<string, string[]> urlParams);
private readonly List<(string url, string method, RedirectHandler handler)> redirects = new();
public void AddRedirect(ApiVersion api, string resource, string method, RedirectHandler handler)
{
string fullUrl = (api == ApiVersion.Helix ? BaseHelix : BaseV5) + (resource.StartsWith("/") ? "" : "/") + resource;
redirects.Add((fullUrl, method, handler));
}
private KeyValuePair<int, string>? DoRedirect(string url, string method, string payload, ApiVersion api = ApiVersion.V5,
string clientId = null, string accessToken = null)
{
string strippedUrl = url.Split('?').First();
return redirects.FirstOrDefault(r => strippedUrl == r.url && r.method == method)
.handler?.Invoke(payload, clientId, accessToken, () => httpCallHandlerImplementation.GeneralRequest(url, method, payload, api, clientId, accessToken), ParseUrlParameters(url));
}
public KeyValuePair<int, string> GeneralRequest(
string url, string method, string payload = null, ApiVersion api = ApiVersion.V5,
string clientId = null, string accessToken = null)
{
return DoRedirect(url, method, payload, api, clientId, accessToken)
?? httpCallHandlerImplementation.GeneralRequest(url, method, payload, api, clientId, accessToken);
}
public void PutBytes(string url, byte[] payload)
{
httpCallHandlerImplementation.PutBytes(url, payload);
}
public int RequestReturnResponseCode(string url, string method, List<KeyValuePair<string, string>> getParams = null)
{
return httpCallHandlerImplementation.RequestReturnResponseCode(url, method, getParams);
}
public static Dictionary<string, string[]> ParseUrlParameters(string url)
{
string paramsString = url.Split('?')
.Skip(1)
.FirstOrDefault();
if (paramsString == null)
return new();
return HttpUtility.UrlDecode(paramsString)
.Split('&')
.Select(kv => kv.Split('='))
.Select(kv => (key: kv[0], value: kv[1]))
.GroupBy(kv => kv.key)
.ToDictionary(kv => kv.Key, kv => kv.Select(x => x.value).ToArray());
}
}
public class AffiliateSpoofingHttpCallHandler : TwitchRedirectingHttpCallHandler
{
private class CustomReward : TwitchLib.Api.Helix.Models.ChannelPoints.CustomReward
{
public CustomReward(CreateCustomRewardsRequest from)
{
Id = Guid.NewGuid().ToString();
Title = from.Title;
Prompt = from.Prompt;
Cost = from.Cost;
IsEnabled = from.IsEnabled;
BackgroundColor = from.BackgroundColor;
IsUserInputRequired = from.IsUserInputRequired;
// IsMaxPerStreamEnabled = from.IsMaxPerStreamEnabled;
// MaxPerStream = from.MaxPerStream;
// IsMaxPerUserPerStreamEnabled = from.IsMaxPerUserPerStreamEnabled;
// MaxPerUserPerStream = from.MaxPerUserPerStream;
// IsGlobalCooldownEnabled = from.IsGlobalCooldownEnabled;
// GlobalCooldownSeconds = from.GlobalCooldownSeconds;
ShouldRedemptionsSkipQueue = from.ShouldRedemptionsSkipRequestQueue;
}
}
private class Redemption : RewardRedemption
{
public Redemption(string id, string user, string args, string rewardId, string rewardTitle)
{
Id = id;
UserLogin = user;
UserName = user;
UserInput = args;
Status = CustomRewardRedemptionStatus.UNFULFILLED;
RedeemedAt = DateTime.Now;
Reward = JsonConvert.DeserializeObject<TwitchLib.Api.Helix.Models.ChannelPoints.Reward>(
JsonConvert.SerializeObject(new { id = rewardId, title = rewardTitle }) );
}
}
private readonly List<CustomReward> customRewards = new ();
private readonly List<Redemption> activeRedemptions = new ();
private readonly JsonSerializerSettings deserializerSettings = new() { NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore };
public event EventHandler<OnRewardRedeemedArgs> OnRewardRedeemed;
public AffiliateSpoofingHttpCallHandler(IHttpCallHandler http = null, ILogger<TwitchHttpClient> logger = null) : base(http, logger)
{
AddRedirect(ApiVersion.Helix, "/channel_points/custom_rewards", "POST", CreateCustomRewards);
AddRedirect(ApiVersion.Helix, "/channel_points/custom_rewards", "DELETE", DeleteCustomRewards);
AddRedirect(ApiVersion.Helix, "/channel_points/custom_rewards", "GET", GetCustomRewards);
AddRedirect(ApiVersion.Helix, "/users", "GET", GetUsers);
AddRedirect(ApiVersion.Helix, "/channel_points/custom_rewards/redemptions", "PATCH", PatchRedemptions);
}
public void FakeRedeem(string title, string user, string args)
{
var reward = customRewards.FirstOrDefault(r => r.Title == title);
if (reward != null)
{
var id = Guid.NewGuid();
activeRedemptions.Add(new Redemption(id.ToString(), user, args, reward.Id, reward.Title));
OnRewardRedeemed?.Invoke(this, new OnRewardRedeemedArgs {
Login = user,
DisplayName = user,
Message = args,
RewardId = Guid.Parse(reward.Id),
RewardTitle = reward.Title,
Status = "UNFULFILLED",
RedemptionId = id,
});
}
}
private KeyValuePair<int, string> GetUsers(string payload, string clientid, string accesstoken, Func<KeyValuePair<int, string>> realcall, Dictionary<string, string[]> urlparams)
{
var results = realcall();
if (urlparams.Count == 0)
{
var response = JObject.Parse(results.Value);
response["data"][0]["broadcaster_type"] = new JValue("affiliate");
return new KeyValuePair<int, string>(200, JsonConvert.SerializeObject(response, deserializerSettings));
}
return results;
}
private KeyValuePair<int, string> CreateCustomRewards(string payload, string clientid, string accesstoken, Func<KeyValuePair<int, string>> realcall, Dictionary<string, string[]> urlParams)
{
var reward = JsonConvert.DeserializeObject<CreateCustomRewardsRequest>(payload);
var newReward = new CustomReward(reward);
customRewards.Add(newReward);
return new KeyValuePair<int, string>(
200,
JsonConvert.SerializeObject(new
{
data = new [] { newReward }
}, deserializerSettings));
}
private KeyValuePair<int, string> DeleteCustomRewards(string payload, string clientid, string accesstoken, Func<KeyValuePair<int, string>> realcall, Dictionary<string, string[]> urlparams)
{
if (customRewards.RemoveAll(c => c.Id == urlparams["id"].FirstOrDefault()) != 0)
{
return new KeyValuePair<int, string>(204, "");
}
else
{
return new KeyValuePair<int, string>(404, "");
}
}
private KeyValuePair<int, string> GetCustomRewards(string payload, string clientid, string accesstoken, Func<KeyValuePair<int, string>> realcall, Dictionary<string, string[]> urlparams)
{
string id = urlparams.ContainsKey("id")
? urlparams["id"].FirstOrDefault()
: null;
var rewards = customRewards.Where(c => id == null || c.Id == id).ToArray();
if (rewards.Length == 0)
{
return new KeyValuePair<int, string>(404, "");
}
return new KeyValuePair<int, string>(
200,
JsonConvert.SerializeObject(new
{
data = rewards
}, deserializerSettings));
}
private KeyValuePair<int, string> PatchRedemptions(string payload, string clientid, string accesstoken, Func<KeyValuePair<int, string>> realcall, Dictionary<string, string[]> urlparams)
{
string[] ids = urlparams["id"];
// Just assume we are cancelling or redeeming...
activeRedemptions.RemoveAll(r => ids.Contains(r.Id));
// var request = JsonConvert.DeserializeObject<UpdateCustomRewardRedemptionStatusRequest>(payload, deserializerSettings);
// foreach (string id in ids)
// {
// var redemption = activeRedemptions.FirstOrDefault(r => r.Id == id);
// if (redemption != null)
// {
// redemption.Status = request.Status;
// }
// }
return new KeyValuePair<int, string>(200, @"{ ""data"": [] }");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment