├── .editorconfig
├── .gitignore
├── Directory.Build.props
├── LICENSE.md
├── README.md
├── SpeedrunComSharp.sln
├── props
├── SpeedrunComSharp.Paths.props
└── SpeedrunComSharp.props
└── src
└── SpeedrunComSharp
├── APIException.cs
├── CachedEnumerable.cs
├── Categories
├── CategoriesClient.cs
├── CategoriesOrdering.cs
├── Category.cs
├── CategoryEmbeds.cs
├── CategoryType.cs
└── Players.cs
├── Common
├── Assets.cs
├── ImageAsset.cs
├── Moderator.cs
├── ModeratorType.cs
├── Player.cs
├── PlayersType.cs
└── TimingMethod.cs
├── ElementDescription.cs
├── ElementType.cs
├── Embeds.cs
├── Games
├── Game.cs
├── GameEmbeds.cs
├── GameHeader.cs
├── GamesClient.cs
├── GamesOrdering.cs
└── Ruleset.cs
├── Guests
├── Guest.cs
└── GuestsClient.cs
├── HttpWebLink.cs
├── IElementWithID.cs
├── JSON.cs
├── Leaderboards
├── EmulatorsFilter.cs
├── Leaderboard.cs
├── LeaderboardEmbeds.cs
├── LeaderboardScope.cs
├── LeaderboardsClient.cs
└── Record.cs
├── Levels
├── Level.cs
├── LevelEmbeds.cs
├── LevelsClient.cs
└── LevelsOrdering.cs
├── NotAuthorizedException.cs
├── Notifications
├── Notification.cs
├── NotificationStatus.cs
├── NotificationType.cs
├── NotificationsClient.cs
└── NotificationsOrdering.cs
├── Platforms
├── Platform.cs
├── PlatformsClient.cs
└── PlatformsOrdering.cs
├── PotentialEmbed.cs
├── Regions
├── Region.cs
├── RegionsClient.cs
└── RegionsOrdering.cs
├── Runs
├── Run.cs
├── RunEmbeds.cs
├── RunStatus.cs
├── RunStatusType.cs
├── RunSystem.cs
├── RunTimes.cs
├── RunVideos.cs
├── RunsClient.cs
└── RunsOrdering.cs
├── Series
├── Series.cs
├── SeriesClient.cs
├── SeriesEmbeds.cs
└── SeriesOrdering.cs
├── SpeedrunComClient.cs
├── SpeedrunComSharp.csproj
├── StringHelpers.cs
├── Users
├── Country.cs
├── CountryRegion.cs
├── Location.cs
├── User.cs
├── UserNameStyle.cs
├── UserRole.cs
├── UsersClient.cs
└── UsersOrdering.cs
└── Variables
├── Variable.cs
├── VariableScope.cs
├── VariableScopeType.cs
├── VariableValue.cs
├── VariablesClient.cs
└── VariablesOrdering.cs
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE options/cache directories
2 | .vs/
3 | .vscode/
4 | .idea/
5 | .fleet/
6 |
7 | # Build artifacts
8 | artifacts/
9 | bin/
10 | obj/
11 | publish/
12 |
13 | # User-specific files
14 | *.user
15 |
16 | # MSBuild
17 | *.binlog
18 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Christopher Serr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpeedrunComSharp
2 |
3 | [](https://github.com/speedruncom/api/tree/c4413b86d7c2088c317de8e3940b022b11c26323)
4 |
5 | SpeedrunComSharp is a .NET wrapper Library for the [Speedrun.com API](https://github.com/speedruncom/api).
6 |
7 | ## How to use
8 |
9 | Download and compile the library and add it as a reference to your project. You then need to create an object of the `SpeedrunComClient` like so:
10 |
11 | ```C#
12 | var client = new SpeedrunComClient();
13 | ```
14 |
15 | The Client is separated into the following Sub-Clients, just like the Speedrun.com API:
16 | * Categories
17 | * Games
18 | * Guests
19 | * Leaderboards
20 | * Levels
21 | * Notifications
22 | * Platforms
23 | * Profile
24 | * Regions
25 | * Runs
26 | * Series
27 | * Users
28 | * Variables
29 |
30 | The Sub-Clients implement all the API Calls for retrieving the Objects from the API. Once you obtained objects from those Clients, you can either use the References within the Objects to retrieve additional objects or you can use their IDs to retrieve them through the Clients.
31 |
32 | ## Example Usage
33 |
34 | ```C#
35 | //Creating the Client
36 | var client = new SpeedrunComClient();
37 |
38 | //Searching for a game called "Wind Waker"
39 | var game = client.Games.SearchGame(name: "Wind Waker");
40 |
41 | //Printing all the categories of the game
42 | foreach (var category in game.Categories)
43 | {
44 | Console.WriteLine(category.Name);
45 | }
46 |
47 | //Searching for the category "Any%"
48 | var anyPercent = game.Categories.First(category => category.Name == "Any%");
49 |
50 | //Finding the World Record of the category
51 | var worldRecord = anyPercent.WorldRecord;
52 |
53 | //Printing the World Record's information
54 | Console.WriteLine("The World Record is {0} by {1}", worldRecord.Times.Primary, worldRecord.Player.Name);
55 |
56 | ```
57 |
58 | ## Optimizations
59 |
60 | The Clients are somewhat more flexible as the Properties in the individual Objects for traversing the API, because they can embed additional objects to decrease Network Usage. If you want to optimize your API usage, make sure to use the Clients where possible.
61 |
62 | The Library automatically minimizes Network Usage, so iterating over `category.Runs` multiple times for example only results in a single API Call. If you are iterating over an IEnumerable that results in a Paginated API Call, the API will only be called for those pages that you are iterating over. If two completely unrelated events result in the same API Call, the SpeedrunComSharp Library will notice that and return a cached result for the second API Call.
63 |
--------------------------------------------------------------------------------
/SpeedrunComSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2DDA8DE7-9195-4278-9AB2-F660CBE900ED}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeedrunComSharp", "src\SpeedrunComSharp\SpeedrunComSharp.csproj", "{49FD8C4A-8E54-440F-AC3A-571F9B99704F}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {49FD8C4A-8E54-440F-AC3A-571F9B99704F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {49FD8C4A-8E54-440F-AC3A-571F9B99704F}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {49FD8C4A-8E54-440F-AC3A-571F9B99704F}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {49FD8C4A-8E54-440F-AC3A-571F9B99704F}.Release|Any CPU.Build.0 = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(NestedProjects) = preSolution
25 | {49FD8C4A-8E54-440F-AC3A-571F9B99704F} = {2DDA8DE7-9195-4278-9AB2-F660CBE900ED}
26 | EndGlobalSection
27 | EndGlobal
28 |
--------------------------------------------------------------------------------
/props/SpeedrunComSharp.Paths.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildThisFileDirectory)..
5 |
6 | $(RootPath)\src
7 | $(RootPath)\test
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/props/SpeedrunComSharp.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 12
6 |
7 | true
8 | disable
9 |
10 |
11 |
12 |
13 | true
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/APIException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 |
6 | namespace SpeedrunComSharp;
7 |
8 | public class APIException : ArgumentException
9 | {
10 | public ReadOnlyCollection Errors { get; }
11 |
12 | public APIException(string message)
13 | : this(message, new List().AsReadOnly())
14 | { }
15 |
16 | public APIException(string message, IEnumerable errors)
17 | : base(message)
18 | {
19 | Errors = errors.ToList().AsReadOnly();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/CachedEnumerable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | internal static class CacheExtensions
6 | {
7 | internal static IEnumerable Cache(this IEnumerable enumerable)
8 | {
9 | return new CachedEnumerable(enumerable);
10 | }
11 | }
12 |
13 | internal class CachedEnumerable : IEnumerable
14 | {
15 | private readonly IEnumerable baseEnumerable;
16 | private IEnumerator baseEnumerator;
17 | private readonly List cachedElements;
18 |
19 | public CachedEnumerable(IEnumerable baseEnumerable)
20 | {
21 | this.baseEnumerable = baseEnumerable;
22 | cachedElements = [];
23 | }
24 |
25 | public IEnumerator GetEnumerator()
26 | {
27 | foreach (T element in cachedElements)
28 | {
29 | yield return element;
30 | }
31 |
32 | baseEnumerator ??= baseEnumerable.GetEnumerator();
33 |
34 | while (baseEnumerator.MoveNext())
35 | {
36 | T current = baseEnumerator.Current;
37 | cachedElements.Add(current);
38 | yield return current;
39 | }
40 | }
41 |
42 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
43 | {
44 | return GetEnumerator();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Categories/CategoriesClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 |
5 | namespace SpeedrunComSharp;
6 |
7 | public class CategoriesClient
8 | {
9 | public const string Name = "categories";
10 |
11 | private readonly SpeedrunComClient baseClient;
12 |
13 | public CategoriesClient(SpeedrunComClient baseClient)
14 | {
15 | this.baseClient = baseClient;
16 | }
17 |
18 | public static Uri GetCategoriesUri(string subUri)
19 | {
20 | return SpeedrunComClient.GetAPIUri(string.Format("{0}{1}", Name, subUri));
21 | }
22 |
23 | ///
24 | /// Fetch a Category object identified by its URI.
25 | ///
26 | /// The site URI for the category.
27 | /// Optional. If included, will dictate the embedded resources included in the response.
28 | ///
29 | public Category GetCategoryFromSiteUri(string siteUri, CategoryEmbeds embeds = default)
30 | {
31 | string id = GetCategoryIDFromSiteUri(siteUri);
32 |
33 | if (string.IsNullOrEmpty(id))
34 | {
35 | return null;
36 | }
37 |
38 | return GetCategory(id, embeds);
39 | }
40 |
41 | ///
42 | /// Fetch a Category ID identified by its URI.
43 | ///
44 | /// The site URI for the category.
45 | ///
46 | public string GetCategoryIDFromSiteUri(string siteUri)
47 | {
48 | ElementDescription elementDescription = baseClient.GetElementDescriptionFromSiteUri(siteUri);
49 |
50 | if (elementDescription == null
51 | || elementDescription.Type != ElementType.Category)
52 | {
53 | return null;
54 | }
55 |
56 | return elementDescription.ID;
57 | }
58 |
59 | ///
60 | /// Fetch a Category object identified by its ID.
61 | ///
62 | /// The ID for the category.
63 | /// Optional. If included, will dictate the additional resources embedded in the response.
64 | ///
65 | public Category GetCategory(string categoryId, CategoryEmbeds embeds = default)
66 | {
67 | Uri uri = GetCategoriesUri(string.Format("/{0}{1}", Uri.EscapeDataString(categoryId), embeds.ToString().ToParameters()));
68 | dynamic result = baseClient.DoRequest(uri);
69 |
70 | return Category.Parse(baseClient, result.data);
71 | }
72 |
73 | ///
74 | /// Fetch a Collection of Variable objects from a category's ID.
75 | ///
76 | /// The ID for the category.
77 | /// Optional. If omitted, variables will be in the same order as the API.
78 | ///
79 | public ReadOnlyCollection GetVariables(string categoryId,
80 | VariablesOrdering orderBy = default)
81 | {
82 | var parameters = new List(orderBy.ToParameters());
83 |
84 | Uri uri = GetCategoriesUri(string.Format("/{0}/variables{1}",
85 | Uri.EscapeDataString(categoryId),
86 | parameters.ToParameters()));
87 |
88 | return baseClient.DoDataCollectionRequest(uri,
89 | x => Variable.Parse(baseClient, x));
90 | }
91 |
92 | ///
93 | /// Fetch a Leaderboard object from a category's ID.
94 | ///
95 | /// The ID for the category.
96 | /// Optional. If included, will dictate the amount of top runs included in the response.
97 | /// Optional. If included, will dictate whether or not empty leaderboards are included in the response.
98 | /// Optional. If included, will dictate the amount of elements included in each pagination.
99 | /// Optional. If included, will dictate the additional resources embedded in the response.
100 | ///
101 | public IEnumerable GetRecords(string categoryId,
102 | int? top = null, bool skipEmptyLeaderboards = false,
103 | int? elementsPerPage = null,
104 | LeaderboardEmbeds embeds = default)
105 | {
106 | var parameters = new List() { embeds.ToString() };
107 |
108 | if (top.HasValue)
109 | {
110 | parameters.Add(string.Format("top={0}", top.Value));
111 | }
112 |
113 | if (skipEmptyLeaderboards)
114 | {
115 | parameters.Add("skip-empty=true");
116 | }
117 |
118 | if (elementsPerPage.HasValue)
119 | {
120 | parameters.Add(string.Format("max={0}", elementsPerPage.Value));
121 | }
122 |
123 | Uri uri = GetCategoriesUri(string.Format("/{0}/records{1}",
124 | Uri.EscapeDataString(categoryId),
125 | parameters.ToParameters()));
126 |
127 | return baseClient.DoPaginatedRequest(uri,
128 | x => Leaderboard.Parse(baseClient, x));
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Categories/CategoriesOrdering.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | ///
6 | /// Options for ordering Categories in responses.
7 | ///
8 | public enum CategoriesOrdering : int
9 | {
10 | Position = 0,
11 | PositionDescending,
12 | Name,
13 | NameDescending,
14 | Miscellaneous,
15 | MiscellaneousDescending
16 | }
17 |
18 | internal static class CategoriesOrderingHelpers
19 | {
20 | internal static IEnumerable ToParameters(this CategoriesOrdering ordering)
21 | {
22 | bool isDescending = ((int)ordering & 1) == 1;
23 | if (isDescending)
24 | {
25 | ordering = (CategoriesOrdering)((int)ordering - 1);
26 | }
27 |
28 | string str = "";
29 |
30 | switch (ordering)
31 | {
32 | case CategoriesOrdering.Name:
33 | str = "name"; break;
34 | case CategoriesOrdering.Miscellaneous:
35 | str = "miscellaneous"; break;
36 | }
37 |
38 | var list = new List();
39 |
40 | if (!string.IsNullOrEmpty(str))
41 | {
42 | list.Add(string.Format("orderby={0}", str));
43 | }
44 |
45 | if (isDescending)
46 | {
47 | list.Add("direction=desc");
48 | }
49 |
50 | return list;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Categories/Category.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Linq;
6 |
7 | namespace SpeedrunComSharp;
8 |
9 | public class Category : IElementWithID
10 | {
11 | public string ID { get; private set; }
12 | public string Name { get; private set; }
13 | public Uri WebLink { get; private set; }
14 | public CategoryType Type { get; private set; }
15 | public string Rules { get; private set; }
16 | public Players Players { get; private set; }
17 | public bool IsMiscellaneous { get; private set; }
18 |
19 | #region Links
20 |
21 | internal Lazy game;
22 | private Lazy> variables;
23 | private Lazy leaderboard;
24 | private Lazy worldRecord;
25 |
26 | public string GameID { get; private set; }
27 | public Game Game => game.Value;
28 | public ReadOnlyCollection Variables => variables.Value;
29 | public IEnumerable Runs { get; private set; }
30 | public Leaderboard Leaderboard => leaderboard.Value;
31 | public Record WorldRecord => worldRecord.Value;
32 |
33 | #endregion
34 |
35 | private Category() { }
36 |
37 | public static Category Parse(SpeedrunComClient client, dynamic categoryElement)
38 | {
39 | if (categoryElement is ArrayList)
40 | {
41 | return null;
42 | }
43 |
44 | var category = new Category
45 | {
46 | //Parse Attributes
47 |
48 | ID = categoryElement.id as string,
49 | Name = categoryElement.name as string,
50 | WebLink = new Uri(categoryElement.weblink as string),
51 | Type = categoryElement.type == "per-game" ? CategoryType.PerGame : CategoryType.PerLevel,
52 | Rules = categoryElement.rules as string,
53 | Players = Players.Parse(client, categoryElement.players),
54 | IsMiscellaneous = categoryElement.miscellaneous
55 | };
56 |
57 | //Parse Links
58 |
59 | var properties = categoryElement.Properties as IDictionary;
60 | var links = properties["links"] as IEnumerable;
61 |
62 | string gameUri = links.First(x => x.rel == "game").uri as string;
63 | category.GameID = gameUri[(gameUri.LastIndexOf('/') + 1)..];
64 |
65 | if (properties.ContainsKey("game"))
66 | {
67 | dynamic gameElement = properties["game"].data;
68 | var game = Game.Parse(client, gameElement) as Game;
69 | category.game = new Lazy(() => game);
70 | }
71 | else
72 | {
73 | category.game = new Lazy(() => client.Games.GetGame(category.GameID));
74 | }
75 |
76 | if (properties.ContainsKey("variables"))
77 | {
78 | Variable parser(dynamic x)
79 | {
80 | return Variable.Parse(client, x) as Variable;
81 | }
82 |
83 | dynamic variables = client.ParseCollection(properties["variables"].data, (Func)parser);
84 | category.variables = new Lazy>(() => variables);
85 | }
86 | else
87 | {
88 | category.variables = new Lazy>(() => client.Categories.GetVariables(category.ID));
89 | }
90 |
91 | category.Runs = client.Runs.GetRuns(categoryId: category.ID);
92 |
93 | if (category.Type == CategoryType.PerGame)
94 | {
95 |
96 | category.leaderboard = new Lazy(() =>
97 | {
98 | Leaderboard leaderboard = client.Leaderboards
99 | .GetLeaderboardForFullGameCategory(category.GameID, category.ID);
100 |
101 | leaderboard.game = new Lazy(() => category.Game);
102 | leaderboard.category = new Lazy(() => category);
103 |
104 | foreach (Record record in leaderboard.Records)
105 | {
106 | record.game = leaderboard.game;
107 | record.category = leaderboard.category;
108 | }
109 |
110 | return leaderboard;
111 | });
112 | category.worldRecord = new Lazy(() =>
113 | {
114 | if (category.leaderboard.IsValueCreated)
115 | {
116 | return category.Leaderboard.Records.FirstOrDefault();
117 | }
118 |
119 | Leaderboard leaderboard = client.Leaderboards
120 | .GetLeaderboardForFullGameCategory(category.GameID, category.ID, top: 1);
121 |
122 | leaderboard.game = new Lazy(() => category.Game);
123 | leaderboard.category = new Lazy(() => category);
124 |
125 | foreach (Record record in leaderboard.Records)
126 | {
127 | record.game = leaderboard.game;
128 | record.category = leaderboard.category;
129 | }
130 |
131 | return leaderboard.Records.FirstOrDefault();
132 | });
133 | }
134 | else
135 | {
136 | category.leaderboard = new Lazy(() => null);
137 | category.worldRecord = new Lazy(() => null);
138 | }
139 |
140 | return category;
141 | }
142 |
143 | public override int GetHashCode()
144 | {
145 | return (ID ?? string.Empty).GetHashCode();
146 | }
147 |
148 | public override bool Equals(object obj)
149 | {
150 | if (obj is not Category other)
151 | {
152 | return false;
153 | }
154 |
155 | return ID == other.ID;
156 | }
157 |
158 | public override string ToString()
159 | {
160 | return Name;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Categories/CategoryEmbeds.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public struct CategoryEmbeds
4 | {
5 | private Embeds embeds;
6 |
7 | public bool EmbedGame { get => embeds["game"]; set => embeds["game"] = value; }
8 | public bool EmbedVariables { get => embeds["variables"]; set => embeds["variables"] = value; }
9 |
10 | ///
11 | /// Options for embedding resources in Category responses.
12 | ///
13 | /// Dictates whether a Game object is included in the response.
14 | /// Dictates whether a Collection of Variable objects is included in the response.
15 | public CategoryEmbeds(
16 | bool embedGame = false,
17 | bool embedVariables = false)
18 | {
19 | embeds = new Embeds();
20 | EmbedGame = embedGame;
21 | EmbedVariables = embedVariables;
22 | }
23 |
24 | public override string ToString()
25 | {
26 | return embeds.ToString();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Categories/CategoryType.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public enum CategoryType
4 | {
5 | PerGame, PerLevel
6 | }
7 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Categories/Players.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public class Players
4 | {
5 | public PlayersType Type { get; private set; }
6 | public int Value { get; private set; }
7 |
8 | private Players() { }
9 |
10 | public static Players Parse(SpeedrunComClient client, dynamic playersElement)
11 | {
12 | var players = new Players
13 | {
14 | Value = (int)playersElement.value,
15 | Type = playersElement.type == "exactly" ? PlayersType.Exactly : PlayersType.UpTo
16 | };
17 |
18 | return players;
19 | }
20 |
21 | public override string ToString()
22 | {
23 | return Type.ToString() + " " + Value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/Assets.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | public class Assets
6 | {
7 | public ImageAsset Logo { get; private set; }
8 | public ImageAsset CoverTiny { get; private set; }
9 | public ImageAsset CoverSmall { get; private set; }
10 | public ImageAsset CoverMedium { get; private set; }
11 | public ImageAsset CoverLarge { get; private set; }
12 | public ImageAsset Icon { get; private set; }
13 | public ImageAsset TrophyFirstPlace { get; private set; }
14 | public ImageAsset TrophySecondPlace { get; private set; }
15 | public ImageAsset TrophyThirdPlace { get; private set; }
16 | public ImageAsset TrophyFourthPlace { get; private set; }
17 | public ImageAsset BackgroundImage { get; private set; }
18 | public ImageAsset ForegroundImage { get; private set; }
19 |
20 | private Assets() { }
21 |
22 | public static Assets Parse(SpeedrunComClient client, dynamic assetsElement)
23 | {
24 | var assets = new Assets();
25 |
26 | var properties = assetsElement.Properties as IDictionary;
27 |
28 | assets.Logo = ImageAsset.Parse(client, assetsElement.logo) as ImageAsset;
29 | assets.CoverTiny = ImageAsset.Parse(client, properties["cover-tiny"]) as ImageAsset;
30 | assets.CoverSmall = ImageAsset.Parse(client, properties["cover-small"]) as ImageAsset;
31 | assets.CoverMedium = ImageAsset.Parse(client, properties["cover-medium"]) as ImageAsset;
32 | assets.CoverLarge = ImageAsset.Parse(client, properties["cover-large"]) as ImageAsset;
33 | assets.Icon = ImageAsset.Parse(client, assetsElement.icon) as ImageAsset;
34 | assets.TrophyFirstPlace = ImageAsset.Parse(client, properties["trophy-1st"]) as ImageAsset;
35 | assets.TrophySecondPlace = ImageAsset.Parse(client, properties["trophy-2nd"]) as ImageAsset;
36 | assets.TrophyThirdPlace = ImageAsset.Parse(client, properties["trophy-3rd"]) as ImageAsset;
37 | assets.TrophyFourthPlace = ImageAsset.Parse(client, properties["trophy-4th"]) as ImageAsset;
38 | assets.BackgroundImage = ImageAsset.Parse(client, assetsElement.background) as ImageAsset;
39 | assets.ForegroundImage = ImageAsset.Parse(client, assetsElement.foreground) as ImageAsset;
40 |
41 | return assets;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/ImageAsset.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | public class ImageAsset
6 | {
7 | public Uri Uri { get; private set; }
8 |
9 | private ImageAsset() { }
10 |
11 | public static ImageAsset Parse(SpeedrunComClient client, dynamic imageElement)
12 | {
13 | if (imageElement == null || imageElement.uri == null)
14 | {
15 | return null;
16 | }
17 |
18 | var image = new ImageAsset();
19 |
20 | string uri = imageElement.uri as string;
21 | image.Uri = new Uri(uri);
22 |
23 | return image;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/Moderator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace SpeedrunComSharp;
5 |
6 | public class Moderator
7 | {
8 | public string UserID { get; private set; }
9 | public ModeratorType Type { get; private set; }
10 |
11 | #region Links
12 |
13 | internal Lazy user;
14 |
15 | public User User => user.Value;
16 | public string Name => User.Name;
17 |
18 | #endregion
19 |
20 | private Moderator() { }
21 |
22 | public static Moderator Parse(SpeedrunComClient client, KeyValuePair moderatorElement)
23 | {
24 | var moderator = new Moderator
25 | {
26 | UserID = moderatorElement.Key,
27 | Type = (moderatorElement.Value as string) == "moderator"
28 | ? ModeratorType.Moderator
29 | : ModeratorType.SuperModerator
30 | };
31 |
32 | moderator.user = new Lazy(() => client.Users.GetUser(moderator.UserID));
33 |
34 | return moderator;
35 | }
36 |
37 | public override string ToString()
38 | {
39 | return Name;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/ModeratorType.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public enum ModeratorType
4 | {
5 | Moderator,
6 | SuperModerator
7 | }
8 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/Player.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace SpeedrunComSharp;
5 |
6 | public class Player
7 | {
8 | public bool IsUser => string.IsNullOrEmpty(GuestName);
9 | public string UserID { get; private set; }
10 | public string GuestName { get; private set; }
11 |
12 | #region Links
13 |
14 | internal Lazy user;
15 | private Lazy guest;
16 |
17 | public User User => user.Value;
18 | public Guest Guest => guest.Value;
19 | public string Name => IsUser ? User.Name : GuestName;
20 |
21 | #endregion
22 |
23 | private Player() { }
24 |
25 | public static Player Parse(SpeedrunComClient client, dynamic playerElement)
26 | {
27 | var player = new Player();
28 |
29 | var properties = playerElement.Properties as IDictionary;
30 |
31 | if (properties.ContainsKey("uri"))
32 | {
33 | if ((playerElement.rel as string) == "user")
34 | {
35 | player.UserID = playerElement.id as string;
36 | player.user = new Lazy(() => client.Users.GetUser(player.UserID));
37 | player.guest = new Lazy(() => null);
38 | }
39 | else
40 | {
41 | player.GuestName = playerElement.name as string;
42 | player.guest = new Lazy(() => client.Guests.GetGuest(player.GuestName));
43 | player.user = new Lazy(() => null);
44 | }
45 | }
46 | else
47 | {
48 | if ((playerElement.rel as string) == "user")
49 | {
50 | var user = User.Parse(client, playerElement) as User;
51 | player.user = new Lazy(() => user);
52 | player.UserID = user.ID;
53 | player.guest = new Lazy(() => null);
54 | }
55 | else
56 | {
57 | var guest = Guest.Parse(client, playerElement) as Guest;
58 | player.guest = new Lazy(() => guest);
59 | player.GuestName = guest.Name;
60 | player.user = new Lazy(() => null);
61 | }
62 | }
63 |
64 | return player;
65 | }
66 |
67 | public override int GetHashCode()
68 | {
69 | return (UserID ?? string.Empty).GetHashCode()
70 | ^ (GuestName ?? string.Empty).GetHashCode();
71 | }
72 |
73 | public override bool Equals(object obj)
74 | {
75 | if (obj is not Player player)
76 | {
77 | return false;
78 | }
79 |
80 | return UserID == player.UserID
81 | && GuestName == player.GuestName;
82 | }
83 |
84 | public override string ToString()
85 | {
86 | return Name;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/PlayersType.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public enum PlayersType
4 | {
5 | Exactly, UpTo
6 | }
7 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Common/TimingMethod.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | public enum TimingMethod
6 | {
7 | GameTime, RealTime, RealTimeWithoutLoads
8 | }
9 |
10 | public static class TimingMethodHelpers
11 | {
12 | public static string ToAPIString(this TimingMethod timingMethod)
13 | {
14 | return timingMethod switch
15 | {
16 | TimingMethod.RealTime => "realtime",
17 | TimingMethod.RealTimeWithoutLoads => "realtime_noloads",
18 | TimingMethod.GameTime => "ingame",
19 | _ => throw new ArgumentException("timingMethod"),
20 | };
21 | }
22 |
23 | public static TimingMethod FromString(string element)
24 | {
25 | return element switch
26 | {
27 | "realtime" => TimingMethod.RealTime,
28 | "realtime_noloads" => TimingMethod.RealTimeWithoutLoads,
29 | "ingame" => TimingMethod.GameTime,
30 | _ => throw new ArgumentException("element"),
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/ElementDescription.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | public class ElementDescription
6 | {
7 | public string ID { get; private set; }
8 | public ElementType Type { get; private set; }
9 |
10 | internal ElementDescription(string id, ElementType type)
11 | {
12 | ID = id;
13 | Type = type;
14 | }
15 |
16 | private static ElementType parseUriType(string type)
17 | {
18 | return type switch
19 | {
20 | CategoriesClient.Name => ElementType.Category,
21 | GamesClient.Name => ElementType.Game,
22 | GuestsClient.Name => ElementType.Guest,
23 | LevelsClient.Name => ElementType.Level,
24 | NotificationsClient.Name => ElementType.Notification,
25 | PlatformsClient.Name => ElementType.Platform,
26 | RegionsClient.Name => ElementType.Region,
27 | RunsClient.Name => ElementType.Run,
28 | SeriesClient.Name => ElementType.Series,
29 | UsersClient.Name => ElementType.User,
30 | VariablesClient.Name => ElementType.Variable,
31 | _ => throw new ArgumentException("type"),
32 | };
33 | }
34 |
35 | public static ElementDescription ParseUri(string uri)
36 | {
37 | string[] splits = uri.Split('/');
38 |
39 | if (splits.Length < 2)
40 | {
41 | return null;
42 | }
43 |
44 | string id = splits[^1];
45 | string uriTypeString = splits[^2];
46 |
47 | try
48 | {
49 | ElementType uriType = parseUriType(uriTypeString);
50 | return new ElementDescription(id, uriType);
51 | }
52 | catch
53 | {
54 | return null;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/ElementType.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public enum ElementType
4 | {
5 | Category,
6 | Game,
7 | Guest,
8 | Level,
9 | Notification,
10 | Platform,
11 | Region,
12 | Run,
13 | Series,
14 | User,
15 | Variable
16 | }
17 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Embeds.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace SpeedrunComSharp;
6 |
7 | internal struct Embeds
8 | {
9 | private Dictionary embedDictionary;
10 |
11 | public bool this[string name]
12 | {
13 | get
14 | {
15 | MakeSureInit();
16 |
17 | if (embedDictionary.ContainsKey(name))
18 | {
19 | return embedDictionary[name];
20 | }
21 | else
22 | {
23 | return false;
24 | }
25 | }
26 | set
27 | {
28 | MakeSureInit();
29 |
30 | if (embedDictionary.ContainsKey(name))
31 | {
32 | embedDictionary[name] = value;
33 | }
34 | else
35 | {
36 | embedDictionary.Add(name, value);
37 | }
38 | }
39 | }
40 |
41 | private void MakeSureInit()
42 | {
43 | embedDictionary ??= [];
44 | }
45 |
46 | public override string ToString()
47 | {
48 | MakeSureInit();
49 |
50 | if (!embedDictionary.Values.Any(x => x))
51 | {
52 | return "";
53 | }
54 |
55 | return "embed=" +
56 | embedDictionary
57 | .Where(x => x.Value)
58 | .Select(x => Uri.EscapeDataString(x.Key))
59 | .Aggregate(",");
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Games/GameEmbeds.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public struct GameEmbeds
4 | {
5 | private Embeds embeds;
6 |
7 | public bool EmbedLevels
8 | {
9 | get => embeds["levels"];
10 | set => embeds["levels"] = value;
11 | }
12 | public bool EmbedCategories
13 | {
14 | get => embeds["categories"];
15 | set => embeds["categories"] = value;
16 | }
17 | public bool EmbedModerators
18 | {
19 | get => embeds["moderators"];
20 | set => embeds["moderators"] = value;
21 | }
22 | public bool EmbedPlatforms
23 | {
24 | get => embeds["platforms"];
25 | set => embeds["platforms"] = value;
26 | }
27 | public bool EmbedRegions
28 | {
29 | get => embeds["regions"];
30 | set => embeds["regions"] = value;
31 | }
32 | public bool EmbedVariables
33 | {
34 | get => embeds["variables"];
35 | set => embeds["variables"] = value;
36 | }
37 |
38 | ///
39 | /// Options for embedding resources in Game responses.
40 | ///
41 | /// Dictates whether a Collection of Level objects is included in the response.
42 | /// Dictates whether a Collection of Category objects is included in the response.
43 | /// Dictates whether a Collection of User objects containing each moderator is included in the response.
44 | /// Dictates whether a Collection of Platform objects is included in the response.
45 | /// Dictates whether a Collection of Region objects is included in the response.
46 | /// Dictates whether a Collection of Variable objects is included in the response.
47 | public GameEmbeds(
48 | bool embedLevels = false,
49 | bool embedCategories = false,
50 | bool embedModerators = false,
51 | bool embedPlatforms = false,
52 | bool embedRegions = false,
53 | bool embedVariables = false)
54 | {
55 | embeds = new Embeds();
56 | EmbedLevels = embedLevels;
57 | EmbedCategories = embedCategories;
58 | EmbedModerators = embedModerators;
59 | EmbedPlatforms = embedPlatforms;
60 | EmbedRegions = embedRegions;
61 | EmbedVariables = embedVariables;
62 | }
63 |
64 | public override string ToString()
65 | {
66 | return embeds.ToString();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Games/GameHeader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | ///
6 | /// An optimized class for simple data for games.
7 | ///
8 | public class GameHeader : IElementWithID
9 | {
10 | public string ID { get; private set; }
11 | public string Name { get; private set; }
12 | public string JapaneseName { get; private set; }
13 | public string TwitchName { get; private set; }
14 | public string Abbreviation { get; private set; }
15 | public Uri WebLink { get; private set; }
16 |
17 | private GameHeader() { }
18 |
19 | public static GameHeader Parse(SpeedrunComClient client, dynamic gameHeaderElement)
20 | {
21 | var gameHeader = new GameHeader
22 | {
23 | ID = gameHeaderElement.id as string,
24 | Name = gameHeaderElement.names.international as string,
25 | JapaneseName = gameHeaderElement.names.japanese as string,
26 | TwitchName = gameHeaderElement.names.twitch as string,
27 | WebLink = new Uri(gameHeaderElement.weblink as string),
28 | Abbreviation = gameHeaderElement.abbreviation as string
29 | };
30 |
31 | return gameHeader;
32 | }
33 |
34 | public override int GetHashCode()
35 | {
36 | return (ID ?? string.Empty).GetHashCode();
37 | }
38 |
39 | public override bool Equals(object obj)
40 | {
41 | if (obj is not GameHeader other)
42 | {
43 | return false;
44 | }
45 |
46 | return ID == other.ID;
47 | }
48 |
49 | public override string ToString()
50 | {
51 | return Name;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Games/GamesOrdering.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | ///
6 | /// Options for ordering Games in responses.
7 | ///
8 | public enum GamesOrdering : int
9 | {
10 | Similarity = 0,
11 | SimilarityDescending,
12 | Name,
13 | NameDescending,
14 | JapaneseName,
15 | JapaneseNameDescending,
16 | Abbreviation,
17 | AbbreviationDescending,
18 | YearOfRelease,
19 | YearOfReleaseDescending,
20 | CreationDate,
21 | CreationDateDescending
22 | }
23 |
24 | internal static class GamesOrderingHelpers
25 | {
26 | internal static IEnumerable ToParameters(this GamesOrdering ordering)
27 | {
28 | bool isDescending = ((int)ordering & 1) == 1;
29 | if (isDescending)
30 | {
31 | ordering = (GamesOrdering)((int)ordering - 1);
32 | }
33 |
34 | string str = "";
35 |
36 | switch (ordering)
37 | {
38 | case GamesOrdering.Name:
39 | str = "name.int"; break;
40 | case GamesOrdering.JapaneseName:
41 | str = "name.jap"; break;
42 | case GamesOrdering.Abbreviation:
43 | str = "abbreviation"; break;
44 | case GamesOrdering.YearOfRelease:
45 | str = "released"; break;
46 | case GamesOrdering.CreationDate:
47 | str = "created"; break;
48 | }
49 |
50 | var list = new List();
51 |
52 | if (!string.IsNullOrEmpty(str))
53 | {
54 | list.Add(string.Format("orderby={0}", str));
55 | }
56 |
57 | if (isDescending)
58 | {
59 | list.Add("direction=desc");
60 | }
61 |
62 | return list;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Games/Ruleset.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 |
6 | namespace SpeedrunComSharp;
7 |
8 | public class Ruleset
9 | {
10 | public bool ShowMilliseconds { get; private set; }
11 | public bool RequiresVerification { get; private set; }
12 | public bool RequiresVideo { get; private set; }
13 | public ReadOnlyCollection TimingMethods { get; private set; }
14 | public TimingMethod DefaultTimingMethod { get; private set; }
15 | public bool EmulatorsAllowed { get; private set; }
16 |
17 | private Ruleset() { }
18 |
19 | public static Ruleset Parse(SpeedrunComClient client, dynamic rulesetElement)
20 | {
21 | var ruleset = new Ruleset();
22 |
23 | var properties = rulesetElement.Properties as IDictionary;
24 |
25 | ruleset.ShowMilliseconds = properties["show-milliseconds"];
26 | ruleset.RequiresVerification = properties["require-verification"];
27 | ruleset.RequiresVideo = properties["require-video"];
28 |
29 | static TimingMethod timingMethodParser(dynamic x)
30 | {
31 | return TimingMethodHelpers.FromString(x as string);
32 | }
33 |
34 | ruleset.TimingMethods = client.ParseCollection(properties["run-times"], (Func)timingMethodParser);
35 | ruleset.DefaultTimingMethod = TimingMethodHelpers.FromString(properties["default-time"]);
36 |
37 | ruleset.EmulatorsAllowed = properties["emulators-allowed"];
38 |
39 | return ruleset;
40 | }
41 |
42 | public override string ToString()
43 | {
44 | var list = new List();
45 | if (ShowMilliseconds)
46 | {
47 | list.Add("Show Milliseconds");
48 | }
49 |
50 | if (RequiresVerification)
51 | {
52 | list.Add("Requires Verification");
53 | }
54 |
55 | if (RequiresVideo)
56 | {
57 | list.Add("Requires Video");
58 | }
59 |
60 | if (EmulatorsAllowed)
61 | {
62 | list.Add("Emulators Allowed");
63 | }
64 |
65 | if (!list.Any())
66 | {
67 | list.Add("No Rules");
68 | }
69 |
70 | return list.Aggregate(", ");
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Guests/Guest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | public class Guest
6 | {
7 | public string Name { get; private set; }
8 |
9 | #region Links
10 |
11 | public IEnumerable Runs { get; private set; }
12 |
13 | #endregion
14 |
15 | private Guest() { }
16 |
17 | public static Guest Parse(SpeedrunComClient client, dynamic guestElement)
18 | {
19 | var guest = new Guest
20 | {
21 | Name = guestElement.name
22 | };
23 | guest.Runs = client.Runs.GetRuns(guestName: guest.Name);
24 |
25 | return guest;
26 | }
27 |
28 | public override string ToString()
29 | {
30 | return Name;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/Guests/GuestsClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SpeedrunComSharp;
4 |
5 | public class GuestsClient
6 | {
7 | public const string Name = "guests";
8 |
9 | private readonly SpeedrunComClient baseClient;
10 |
11 | public GuestsClient(SpeedrunComClient baseClient)
12 | {
13 | this.baseClient = baseClient;
14 | }
15 |
16 | public static Uri GetGuestsUri(string subUri)
17 | {
18 | return SpeedrunComClient.GetAPIUri(string.Format("{0}{1}", Name, subUri));
19 | }
20 |
21 | ///
22 | /// Fetch a Guest object identified by its URI.
23 | ///
24 | /// The site URI for the guest.
25 | ///
26 | public Guest GetGuestFromSiteUri(string siteUri)
27 | {
28 | string id = GetGuestIDFromSiteUri(siteUri);
29 |
30 | if (string.IsNullOrEmpty(id))
31 | {
32 | return null;
33 | }
34 |
35 | return GetGuest(id);
36 | }
37 |
38 | ///
39 | /// Fetch a Guest ID identified by its URI.
40 | ///
41 | /// The site URI for the guest.
42 | ///
43 | public string GetGuestIDFromSiteUri(string siteUri)
44 | {
45 | ElementDescription elementDescription = baseClient.GetElementDescriptionFromSiteUri(siteUri);
46 |
47 | if (elementDescription == null
48 | || elementDescription.Type != ElementType.Guest)
49 | {
50 | return null;
51 | }
52 |
53 | return elementDescription.ID;
54 | }
55 |
56 | ///
57 | /// Fetch a Guest object identified by its name.
58 | ///
59 | /// The name of the guest.
60 | ///
61 | public Guest GetGuest(string guestName)
62 | {
63 | Uri uri = GetGuestsUri(string.Format("/{0}", Uri.EscapeDataString(guestName)));
64 | dynamic result = baseClient.DoRequest(uri);
65 |
66 | return Guest.Parse(baseClient, result.data);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/HttpWebLink.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 |
5 | namespace SpeedrunComSharp;
6 |
7 | internal class HttpWebLink
8 | {
9 | public string Uri { get; private set; }
10 | public string Relation { get; private set; }
11 | public string Anchor { get; private set; }
12 | public string RelationTypes { get; private set; }
13 | public string Language { get; private set; }
14 | public string Media { get; private set; }
15 | public string Title { get; private set; }
16 | public string Titles { get; private set; }
17 | public string Type { get; private set; }
18 |
19 | private HttpWebLink() { }
20 |
21 | public static ReadOnlyCollection ParseLinks(string linksString)
22 | {
23 | return (linksString ?? string.Empty)
24 | .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
25 | .Select(x => ParseLink(x.Trim(' ')))
26 | .ToList()
27 | .AsReadOnly();
28 | }
29 |
30 | public static HttpWebLink ParseLink(string linkString)
31 | {
32 | var link = new HttpWebLink();
33 |
34 | int leftAngledParenthesis = linkString.IndexOf('<');
35 | int rightAngledParenthesis = linkString.IndexOf('>');
36 |
37 | if (leftAngledParenthesis >= 0 && rightAngledParenthesis >= 0)
38 | {
39 | link.Uri = linkString.Substring(leftAngledParenthesis + 1, rightAngledParenthesis - leftAngledParenthesis - 1);
40 | }
41 |
42 | linkString = linkString[(rightAngledParenthesis + 1)..];
43 | string[] parameters = linkString.Split(new[] { "; " }, StringSplitOptions.RemoveEmptyEntries);
44 |
45 | foreach (string parameter in parameters)
46 | {
47 | string[] splits = parameter.Split(['='], 2);
48 | if (splits.Length == 2)
49 | {
50 | string parameterType = splits[0];
51 | string parameterValue = splits[1].Trim('"');
52 |
53 | switch (parameterType)
54 | {
55 | case "rel":
56 | link.Relation = parameterValue;
57 | break;
58 | case "anchor":
59 | link.Anchor = parameterValue;
60 | break;
61 | case "rev":
62 | link.RelationTypes = parameterValue;
63 | break;
64 | case "hreflang":
65 | link.Language = parameterValue;
66 | break;
67 | case "media":
68 | link.Media = parameterValue;
69 | break;
70 | case "title":
71 | link.Title = parameterValue;
72 | break;
73 | case "title*":
74 | link.Titles = parameterValue;
75 | break;
76 | case "type":
77 | link.Type = parameterValue;
78 | break;
79 | }
80 | }
81 | }
82 |
83 | return link;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/IElementWithID.cs:
--------------------------------------------------------------------------------
1 | namespace SpeedrunComSharp;
2 |
3 | public interface IElementWithID
4 | {
5 | string ID { get; }
6 | }
7 |
--------------------------------------------------------------------------------
/src/SpeedrunComSharp/JSON.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Dynamic;
6 | using System.Globalization;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Net;
10 | using System.Text;
11 | using System.Text.RegularExpressions;
12 | using System.Web;
13 | using System.Web.Script.Serialization;
14 |
15 | namespace SpeedrunComSharp;
16 |
17 | internal static class JSON
18 | {
19 | public static dynamic FromResponse(WebResponse response)
20 | {
21 | using Stream stream = response.GetResponseStream();
22 | return FromStream(stream);
23 | }
24 |
25 | public static dynamic FromStream(Stream stream)
26 | {
27 | var reader = new StreamReader(stream);
28 | string json = "";
29 | try
30 | {
31 | json = reader.ReadToEnd();
32 | }
33 | catch { }
34 |
35 | return FromString(json);
36 | }
37 |
38 | public static dynamic FromString(string value)
39 | {
40 | var serializer = new JavaScriptSerializer()
41 | {
42 | MaxJsonLength = int.MaxValue
43 | };
44 | serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
45 |
46 | return serializer.Deserialize