Skip to content

Commit

Permalink
Add project to update Patrons.yml from a csv file containing Patreon …
Browse files Browse the repository at this point in the history
…webhooks, add missing Patrons (#20942)
  • Loading branch information
DrSmugleaf authored Oct 12, 2023
1 parent 1e14333 commit 388e424
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 0 deletions.
15 changes: 15 additions & 0 deletions Content.PatreonParser/Attributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class Attributes
{
[JsonPropertyName("full_name")]
public string FullName = default!;

[JsonPropertyName("pledge_relationship_start")]
public DateTime? PledgeRelationshipStart;

[JsonPropertyName("title")]
public string Title = default!;
}
14 changes: 14 additions & 0 deletions Content.PatreonParser/Content.PatreonParser.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions Content.PatreonParser/CurrentlyEntitledTiers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class CurrentlyEntitledTiers
{
[JsonPropertyName("data")]
public List<TierData> Data = default!;
}
18 changes: 18 additions & 0 deletions Content.PatreonParser/Data.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class Data
{
[JsonPropertyName("id")]
public string Id = default!;

[JsonPropertyName("type")]
public string Type = default!;

[JsonPropertyName("attributes")]
public Attributes Attributes = default!;

[JsonPropertyName("relationships")]
public Relationships Relationships = default!;
}
15 changes: 15 additions & 0 deletions Content.PatreonParser/Included.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class Included
{
[JsonPropertyName("id")]
public int Id;

[JsonPropertyName("type")]
public string Type = default!;

[JsonPropertyName("attributes")]
public Attributes Attributes = default!;
}
3 changes: 3 additions & 0 deletions Content.PatreonParser/Patron.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Content.PatreonParser;

public readonly record struct Patron(string FullName, string TierName, DateTime Start);
125 changes: 125 additions & 0 deletions Content.PatreonParser/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Content.PatreonParser;
using CsvHelper;
using CsvHelper.Configuration;
using static System.Environment;

var repository = new DirectoryInfo(Directory.GetCurrentDirectory()).Parent!.Parent!.Parent!.Parent!;
var patronsPath = Path.Combine(repository.FullName, "Resources/Credits/Patrons.yml");
if (!File.Exists(patronsPath))
{
Console.WriteLine($"File {patronsPath} not found.");
return;
}

Console.WriteLine($"Updating {patronsPath}");
Console.WriteLine("Is this correct? [Y/N]");
var response = Console.ReadLine()?.ToUpper();
if (response != "Y")
{
Console.WriteLine("Exiting");
return;
}

var delimiter = ",";
var hasHeaderRecord = false;
var mode = CsvMode.RFC4180;
var escape = '\'';
Console.WriteLine($"""
Delimiter: {delimiter}
HasHeaderRecord: {hasHeaderRecord}
Mode: {mode}
Escape Character: {escape}
""");

Console.WriteLine("Enter the full path to the .csv file containing the Patreon webhook data:");
var filePath = Console.ReadLine();
if (filePath == null)
{
Console.Write("No path given.");
return;
}

var file = File.OpenRead(filePath);
var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = delimiter,
HasHeaderRecord = hasHeaderRecord,
Mode = mode,
Escape = escape,
};

using var reader = new CsvReader(new StreamReader(file), csvConfig);

// This does not handle tier name changes, but we haven't had any yet
var patrons = new Dictionary<Guid, Patron>();
var jsonOptions = new JsonSerializerOptions
{
IncludeFields = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};

// This assumes that the rows are already sorted by id
foreach (var record in reader.GetRecords<Row>())
{
if (record.Trigger == "members:create")
continue;

var content = JsonSerializer.Deserialize<Root>(record.ContentJson, jsonOptions)!;

var id = Guid.Parse(content.Data.Id);
patrons.Remove(id);

var tiers = content.Data.Relationships.CurrentlyEntitledTiers.Data;
if (tiers.Count == 0)
continue;
else if (tiers.Count > 1)
throw new ArgumentException("Found more than one tier");

var tier = tiers[0];
var tierName = content.Included.SingleOrDefault(i => i.Id == tier.Id && i.Type == tier.Type)?.Attributes.Title;
if (tierName == null)
continue;

if (record.Trigger == "members:delete")
continue;

var fullName = content.Data.Attributes.FullName.Trim();
var pledgeStart = content.Data.Attributes.PledgeRelationshipStart;

switch (record.Trigger)
{
case "members:create":
break;
case "members:delete":
break;
case "members:update":
patrons.Add(id, new Patron(fullName, tierName, pledgeStart!.Value));
break;
case "members:pledge:create":
if (pledgeStart == null)
continue;

patrons.Add(id, new Patron(fullName, tierName, pledgeStart.Value));
break;
case "members:pledge:delete":
// Deleted pledge but still not expired, expired is handled earlier
patrons.Add(id, new Patron(fullName, tierName, pledgeStart!.Value));
break;
case "members:pledge:update":
patrons.Add(id, new Patron(fullName, tierName, pledgeStart!.Value));
break;
}
}

var patronList = patrons.Values.ToList();
patronList.Sort((a, b) => a.Start.CompareTo(b.Start));
var yaml = patronList.Select(p => $"""
- Name: "{p.FullName.Replace("\"", "\\\"")}"
Tier: {p.TierName}
""");
var output = string.Join(NewLine, yaml) + NewLine;
File.WriteAllText(patronsPath, output);
Console.WriteLine($"Updated {patronsPath} with {patronList.Count} patrons.");
9 changes: 9 additions & 0 deletions Content.PatreonParser/Relationships.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class Relationships
{
[JsonPropertyName("currently_entitled_tiers")]
public CurrentlyEntitledTiers CurrentlyEntitledTiers = default!;
}
12 changes: 12 additions & 0 deletions Content.PatreonParser/Root.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class Root
{
[JsonPropertyName("data")]
public Data Data = default!;

[JsonPropertyName("included")]
public List<Included> Included = default!;
}
19 changes: 19 additions & 0 deletions Content.PatreonParser/Row.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using CsvHelper.Configuration.Attributes;

namespace Content.PatreonParser;

// These need to be properties or CSVHelper will not find them
public sealed class Row
{
[Name("Id"), Index(0)]
public int Id { get; set; }

[Name("Trigger"), Index(1)]
public string Trigger { get; set; } = default!;

[Name("Time"), Index(2)]
public DateTime Time { get; set; }

[Name("Content"), Index(3)]
public string ContentJson { get; set; } = default!;
}
12 changes: 12 additions & 0 deletions Content.PatreonParser/TierData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace Content.PatreonParser;

public sealed class TierData
{
[JsonPropertyName("id")]
public int Id;

[JsonPropertyName("type")]
public string Type = default!;
}
6 changes: 6 additions & 0 deletions Resources/Credits/Patrons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
Tier: Revolutionary
- Name: "Mikhail"
Tier: Revolutionary
- Name: "Ramiro Agis"
Tier: Revolutionary
- Name: "osborn"
Tier: Syndicate Agent
- Name: "Uinseann"
Expand Down Expand Up @@ -108,6 +110,8 @@
Tier: Syndicate Agent
- Name: "Odin The Wanderer"
Tier: Revolutionary
- Name: "tokie"
Tier: Nuclear Operative
- Name: "Wallace Megas"
Tier: Revolutionary
- Name: "Vandell"
Expand All @@ -130,6 +134,8 @@
Tier: Revolutionary
- Name: "eric156"
Tier: Revolutionary
- Name: "SHANE ALAN ZINDA"
Tier: Nuclear Operative
- Name: "Glenn Olsen"
Tier: Syndicate Agent
- Name: "Constellations"
Expand Down
10 changes: 10 additions & 0 deletions SpaceStation14.sln
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Shared.CompNetworkGe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Serialization.Generator", "RobustToolbox\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj", "{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.PatreonParser", "Content.PatreonParser\Content.PatreonParser.csproj", "{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -430,6 +432,14 @@ Global
{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU
{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Tools|Any CPU.Build.0 = Debug|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Release|Any CPU.Build.0 = Release|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Tools|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down

0 comments on commit 388e424

Please sign in to comment.