├── docs
├── .nojekyll
├── _navbar.md
├── openapi
│ ├── redoc.html
│ └── swagger-ui.html
├── _coverpage.md
└── index.html
├── toc.yml
├── .github
├── FUNDING.yml
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.md
│ └── bug_report.yml
└── workflows
│ ├── fly.yml
│ ├── dotnet.yml
│ ├── pages.yml
│ ├── sync.yml
│ └── codeql.yml
├── .gitmodules
├── src
├── Helldivers-2-Models
│ ├── ArrowHead
│ │ ├── WarId.cs
│ │ ├── Info
│ │ │ ├── PlanetCoordinates.cs
│ │ │ ├── HomeWorld.cs
│ │ │ ├── PlanetRegion.cs
│ │ │ └── PlanetInfo.cs
│ │ ├── Status
│ │ │ ├── JointOperation.cs
│ │ │ ├── PlanetAttack.cs
│ │ │ ├── SpaceStation.cs
│ │ │ ├── Campaign.cs
│ │ │ ├── PlanetStatus.cs
│ │ │ ├── PlanetRegionStatus.cs
│ │ │ └── PlanetEvent.cs
│ │ ├── Assignments
│ │ │ ├── Reward.cs
│ │ │ ├── Task.cs
│ │ │ └── Setting.cs
│ │ ├── NewsFeedItem.cs
│ │ ├── SpaceStations
│ │ │ ├── Cost.cs
│ │ │ └── TacticalAction.cs
│ │ ├── Assignment.cs
│ │ ├── WarSummary.cs
│ │ ├── SpaceStation.cs
│ │ ├── WarInfo.cs
│ │ ├── WarStatus.cs
│ │ └── Summary
│ │ │ ├── GalaxyStats.cs
│ │ │ └── PlanetStats.cs
│ ├── Steam
│ │ ├── SteamNewsFeed.cs
│ │ ├── SteamAppNews.cs
│ │ └── SteamNewsFeedItem.cs
│ ├── V1
│ │ ├── Planets
│ │ │ ├── Position.cs
│ │ │ ├── Biome.cs
│ │ │ ├── RegionSize.cs
│ │ │ ├── Hazard.cs
│ │ │ ├── Event.cs
│ │ │ └── Region.cs
│ │ ├── Assignments
│ │ │ ├── Reward.cs
│ │ │ └── Task.cs
│ │ ├── Dispatch.cs
│ │ ├── Campaign.cs
│ │ ├── SteamNews.cs
│ │ ├── War.cs
│ │ ├── Assignment.cs
│ │ ├── Statistics.cs
│ │ └── Planet.cs
│ ├── V2
│ │ ├── SpaceStations
│ │ │ ├── TacticalAction.cs
│ │ │ └── Cost.cs
│ │ ├── Dispatch.cs
│ │ └── SpaceStation.cs
│ ├── SteamSerializerContext.cs
│ ├── V2SerializerContext.cs
│ ├── ArrowHeadSerializerContext.cs
│ ├── Helldivers-2-Models.csproj
│ ├── V1SerializerContext.cs
│ └── Domain
│ │ └── Localization
│ │ ├── LocalizedMessageConverter.cs
│ │ └── LocalizedMessage.cs
├── Helldivers-2-Core
│ ├── Storage
│ │ ├── V1
│ │ │ ├── WarStore.cs
│ │ │ ├── PlanetStore.cs
│ │ │ ├── CampaignStore.cs
│ │ │ ├── DispatchStore.cs
│ │ │ └── AssignmentStore.cs
│ │ ├── V2
│ │ │ ├── DispatchStore.cs
│ │ │ └── SpaceStationStore.cs
│ │ ├── Steam
│ │ │ └── SteamNewsStore.cs
│ │ ├── StoreBase.cs
│ │ └── ArrowHead
│ │ │ └── ArrowHeadStore.cs
│ ├── Helldivers-2-Core.csproj
│ ├── Facades
│ │ ├── SteamFacade.cs
│ │ ├── V2Facade.cs
│ │ └── V1Facade.cs
│ ├── Mapping
│ │ ├── Steam
│ │ │ └── SteamNewsMapper.cs
│ │ ├── V1
│ │ │ ├── WarMapper.cs
│ │ │ ├── CampaignMapper.cs
│ │ │ ├── DispatchMapper.cs
│ │ │ ├── StatisticsMapper.cs
│ │ │ └── AssignmentMapper.cs
│ │ ├── V2
│ │ │ ├── DispatchMapper.cs
│ │ │ └── SpaceStationMapper.cs
│ │ └── MappingContext.cs
│ ├── Contracts
│ │ ├── IStore.cs
│ │ └── Collections
│ │ │ └── IStore.cs
│ ├── Localization
│ │ └── CultureDictionary.cs
│ ├── StorageFacade.cs
│ └── Extensions
│ │ └── ServiceCollectionExtensions.cs
├── Helldivers-2-SourceGen
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Contracts
│ │ └── IJsonParser.cs
│ ├── Parsers
│ │ ├── FactionsParser.cs
│ │ ├── BiomesParser.cs
│ │ ├── EnvironmentalsParser.cs
│ │ ├── PlanetRegionsParser.cs
│ │ ├── PlanetsParser.cs
│ │ └── BaseJsonParser.cs
│ ├── Readme.md
│ ├── Helldivers-2-SourceGen.csproj
│ └── StaticJsonSourceGenerator.cs
├── Helldivers-2-API
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Constants.cs
│ ├── Controllers
│ │ ├── V1
│ │ │ ├── WarController.cs
│ │ │ ├── CampaignsController.cs
│ │ │ ├── DispatchController.cs
│ │ │ ├── AssignmentsController.cs
│ │ │ ├── SteamController.cs
│ │ │ └── PlanetController.cs
│ │ ├── V2
│ │ │ ├── DispatchController.cs
│ │ │ └── SpaceStationController.cs
│ │ ├── DevelopmentController.cs
│ │ └── ArrowHeadController.cs
│ ├── appsettings.Development.json
│ ├── Middlewares
│ │ ├── RedirectFlyDomainMiddleware.cs
│ │ ├── BlacklistMiddleware.cs
│ │ ├── EtagMiddleware.cs
│ │ └── RateLimitMiddleware.cs
│ ├── Configuration
│ │ ├── AuthenticationConfiguration.cs
│ │ └── ApiConfiguration.cs
│ ├── appsettings.json
│ ├── Extensions
│ │ └── ClaimsPrincipalExtensions.cs
│ ├── OpenApi
│ │ ├── TypeMappers
│ │ │ ├── EnumStringTypeMapper.cs
│ │ │ └── LocalizedMessageTypeMapper.cs
│ │ ├── DocumentProcessors
│ │ │ ├── HelldiversDocumentProcessor.cs
│ │ │ └── ArrowHeadDocumentProcessor.cs
│ │ └── OperationProcessors
│ │ │ └── SuperHeadersProcessor.cs
│ ├── Dockerfile
│ ├── Helldivers-2-API.csproj
│ └── Metrics
│ │ └── ClientMetric.cs
├── Helldivers-2-CI
│ ├── Helldivers-2-CI.csproj
│ └── Program.cs
└── Helldivers-2-Sync
│ ├── Helldivers-2-Sync.csproj
│ ├── Services
│ └── SteamApiService.cs
│ ├── Extensions
│ └── ServiceCollectionExtensions.cs
│ ├── Configuration
│ └── HelldiversSyncConfiguration.cs
│ └── Hosted
│ └── SteamSyncService.cs
├── index.md
├── Directory.Build.props
├── .dockerignore
├── SECURITY.md
├── LICENSE
├── fly.toml
├── .gitattributes
└── README.md
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/toc.yml:
--------------------------------------------------------------------------------
1 | - name: Docs
2 | href: docs/
3 | - name: OpenAPI
4 | href: docs/openapi/swagger-ui.html
5 | - name: API
6 | href: api/
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: dealloc
4 | patreon: Helldivers2API
5 | buy_me_a_coffee: dealloc
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/Helldivers-2-Models/json"]
2 | path = src/Helldivers-2-Models/json
3 | url = https://github.com/helldivers-2/json.git
4 |
--------------------------------------------------------------------------------
/docs/_navbar.md:
--------------------------------------------------------------------------------
1 | * [Self host](containers.md)
2 | * OpenAPI
3 | * [Redoc](openapi/redoc.html ':ignore')
4 | * [Swagger UI](openapi/swagger-ui.html ':ignore')
5 |
--------------------------------------------------------------------------------
/docs/openapi/redoc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Defines owners of specific parts of the codebase.
2 |
3 | # Global owner of all code
4 | * @dealloc
5 |
6 | # All documentation should be approved by following owners
7 | *.md @chatterchats
8 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/WarId.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead;
2 |
3 | ///
4 | /// Represents the ID returned from the WarID endpoint.
5 | ///
6 | public sealed record WarId(int Id);
7 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V1/WarStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Storage.V1;
5 |
6 | ///
7 | public sealed class WarStore : StoreBase
8 | {
9 | //
10 | }
11 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | _layout: landing
3 | ---
4 |
5 | # This is the **HOMEPAGE**.
6 |
7 | Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.
8 |
9 | ## Quick Start Notes:
10 |
11 | 1. Add images to the *images* folder if the file is referencing an image.
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "DebugRoslynSourceGenerator": {
5 | "commandName": "DebugRoslynComponent",
6 | "targetProject": "../Helldivers_2_SourceGen.Sample/Helldivers_2_SourceGen.Sample.csproj"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/Steam/SteamNewsFeed.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.Steam;
2 |
3 | ///
4 | /// Represents the response of the Steam news feed API call.
5 | ///
6 | /// Contains the object.
7 | public sealed record SteamNewsFeed(
8 | SteamAppNews AppNews
9 | );
10 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planets/Position.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1;
2 |
3 | ///
4 | /// Represents a position on the galactic war map.
5 | ///
6 | /// The X coordinate.
7 | /// The Y coordinate.
8 | public record struct Position(
9 | double X,
10 | double Y
11 | );
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Info/PlanetCoordinates.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Info;
2 |
3 | ///
4 | /// Represents a set of coordinates returned by ArrowHead's API.
5 | ///
6 | /// The X coordinate
7 | /// The Y coordinate
8 | public sealed record PlanetCoordinates(double X, double Y);
9 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V1/PlanetStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Storage.V1;
5 |
6 | ///
7 | public class PlanetStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(Planet planet, int index) => planet.Index == index;
11 | }
12 |
--------------------------------------------------------------------------------
/docs/_coverpage.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Helldivers 2API
4 |
5 | > A community driven API wrapper for Helldivers 2
6 |
7 | - Provides fast & cached access to the official Helldivers 2 API
8 | - Convenience abstractions for modern API design
9 | - You can self host!
10 |
11 | [GitHub](https://github.com/helldivers-2/api)
12 | [Get Started](README.md)
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/JointOperation.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Status;
2 |
3 | ///
4 | /// Represents a joint operation.
5 | ///
6 | ///
7 | ///
8 | ///
9 | public sealed record JointOperation(
10 | int Id,
11 | int PlanetIndex,
12 | int HqNodeIndex
13 | );
14 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | enable
4 | enable
5 | net10.0
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/Steam/SteamAppNews.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.Steam;
2 |
3 | ///
4 | /// Represents the response of the Steam news feed API call.
5 | ///
6 | /// The appid of Helldivers 2 on Steam.
7 | /// A list of newsfeed items.
8 | public sealed record SteamAppNews(
9 | long AppId,
10 | List NewsItems
11 | );
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V1/CampaignStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Storage.V1;
5 |
6 | ///
7 | public sealed class CampaignStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(Campaign campaign, int key)
11 | => campaign.Id == key;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V1/DispatchStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Storage.V1;
5 |
6 | ///
7 | public sealed class DispatchStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(Dispatch dispatch, int key)
11 | => dispatch.Id == key;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V2/DispatchStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V2;
3 |
4 | namespace Helldivers.Core.Storage.V2;
5 |
6 | ///
7 | public sealed class DispatchStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(Dispatch dispatch, int key)
11 | => dispatch.Id == key;
12 | }
13 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.dockerignore
2 | **/.env
3 | **/.git
4 | **/.gitignore
5 | **/.project
6 | **/.settings
7 | **/.toolstarget
8 | **/.vs
9 | **/.vscode
10 | **/.idea
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/azds.yaml
15 | **/bin
16 | **/charts
17 | **/docker-compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
26 | tests/
27 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V2/SpaceStationStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V2;
3 |
4 | namespace Helldivers.Core.Storage.V2;
5 |
6 | ///
7 | public sealed class SpaceStationStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(SpaceStation station, long index) => station.Id32 == index;
11 | }
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/V1/AssignmentStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Storage.V1;
5 |
6 | ///
7 | public sealed class AssignmentStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(Assignment assignment, long key)
11 | => assignment.Id == key;
12 | }
13 |
--------------------------------------------------------------------------------
/docs/openapi/swagger-ui.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Info/HomeWorld.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Info;
2 |
3 | ///
4 | /// Represents information about the homeworld(s) of a given race.
5 | ///
6 | /// The identifier of the race (faction) this describes the homeworld of.
7 | /// A list of identifiers.
8 | public sealed record HomeWorld(int Race, List PlanetIndices);
9 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "http": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "launchBrowser": true,
8 | "launchUrl": "",
9 | "applicationUrl": "http://localhost:5000",
10 | "environmentVariables": {
11 | "ASPNETCORE_ENVIRONMENT": "Development"
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/PlanetAttack.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Info;
2 |
3 | namespace Helldivers.Models.ArrowHead.Status;
4 |
5 | ///
6 | /// Represents an attack on a .
7 | ///
8 | /// Where the attack originates from.
9 | /// The planet under attack.
10 | public sealed record PlanetAttack(
11 | int Source,
12 | int Target
13 | );
14 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Assignments/Reward.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Assignments;
2 |
3 | ///
4 | /// The reward for completing an .
5 | ///
6 | /// The type of reward (medals, super credits, ...).
7 | /// The amount of that will be awarded.
8 | public sealed record Reward(
9 | int Type, // TODO: map to enum
10 | ulong Amount
11 | );
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/Steam/SteamNewsStore.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Storage.Steam;
5 |
6 | ///
7 | public class SteamNewsStore : StoreBase
8 | {
9 | ///
10 | protected override bool GetAsyncPredicate(SteamNews item, string key)
11 | => string.Equals(item.Id, key, StringComparison.InvariantCultureIgnoreCase);
12 | }
13 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V2/SpaceStations/TacticalAction.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V2.SpaceStations;
2 |
3 | ///
4 | /// Represents a tactical action that the Space Station can take.
5 | ///
6 | public sealed record TacticalAction(
7 | long Id32,
8 | long MediaId32,
9 | string Name,
10 | string Description,
11 | string StrategicDescription,
12 | int Status,
13 | DateTime StatusExpire,
14 | List Costs,
15 | List EffectIds
16 | );
17 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Helldivers-2-Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Helldivers.Core
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Discord Chat
4 | url: https://discord.gg/E8UUfWYmf9
5 | about: Ask questions and discuss with other developers in real time.
6 | - name: Questions & Discussions
7 | url: https://github.com/helldivers-2/api/discussions
8 | about: Use GitHub discussions for message-board style questions and discussions.
9 | - name: Buy me a coffee
10 | url: https://www.buymeacoffee.com/dealloc
11 | about: If you want to support the development, check out buymeacoffee
12 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/SteamSerializerContext.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Steam;
2 | using Helldivers.Models.V1;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace Helldivers.Models;
6 |
7 | ///
8 | /// Source generated for Steam models.
9 | ///
10 | [JsonSerializable(typeof(SteamNewsFeed))]
11 | [JsonSerializable(typeof(List))]
12 | [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
13 | public sealed partial class SteamSerializerContext : JsonSerializerContext
14 | {
15 | //
16 | }
17 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.API;
2 |
3 | ///
4 | /// Contains constant strings used througout the API.
5 | ///
6 | public static class Constants
7 | {
8 | ///
9 | /// The name of the header that identifies the client to the API.
10 | ///
11 | public static string CLIENT_HEADER_NAME = "X-Super-Client";
12 |
13 | ///
14 | /// The name of the header with developer contact information.
15 | ///
16 | public static string CONTACT_HEADER_NAME = "X-Super-Contact";
17 | }
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Assignments/Task.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Assignments;
2 |
3 | ///
4 | /// Represents a task in an that needs to be completed
5 | /// to finish the assignment.
6 | ///
7 | /// The type of task this represents.
8 | /// A list of numbers, purpose unknown.
9 | /// A list of numbers, purpose unknown
10 | public sealed record Task(
11 | int Type,
12 | List Values,
13 | List ValueTypes
14 | );
15 |
--------------------------------------------------------------------------------
/src/Helldivers-2-CI/Helldivers-2-CI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net10.0
6 | Helldivers_2_CI
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planets/Biome.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Planets;
2 |
3 | ///
4 | /// Represents information about a biome of a planet.
5 | ///
6 | /// The name of this biome.
7 | /// A human-readable description of the biome.
8 | public sealed record Biome(
9 | string Name,
10 | string Description
11 | )
12 | {
13 | ///
14 | /// Used when the biome could not be determined.
15 | ///
16 | public static readonly Biome Unknown = new("UNKNOWN BIOME", string.Empty);
17 | };
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Assignments/Reward.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Assignments;
2 |
3 | ///
4 | /// Represents the reward of an .
5 | ///
6 | /// The type of reward, currently only one value is known: 1 which represents Medals
7 | /// Internal identifier of this .
8 | /// The amount of the players will receive upon completion.
9 | public sealed record Reward(
10 | int Type,
11 | ulong Id32,
12 | ulong Amount
13 | );
14 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Assignments/Task.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Assignments;
2 |
3 | ///
4 | /// Represents a task in an .
5 | /// It's exact values are not known, therefore little of it's purpose is clear.
6 | ///
7 | /// A numerical value, purpose unknown.
8 | /// A list of numerical values, purpose unknown.
9 | /// A list of numerical values, purpose unknown.
10 | public sealed record Task(
11 | int Type,
12 | List Values,
13 | List ValueTypes
14 | );
15 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/NewsFeedItem.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead;
2 |
3 | ///
4 | /// Represents an item in the newsfeed of Super Earth.
5 | ///
6 | /// The identifier of this newsfeed item.
7 | /// A unix timestamp (in seconds) when this item was published.
8 | /// A numerical type, purpose unknown.
9 | /// The message containing a human readable text.
10 | public sealed record NewsFeedItem(
11 | int Id,
12 | long Published,
13 | int Type,
14 | string Message
15 | );
16 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting security issues
2 | We take security very seriously and appreciate your efforts in responsibly disclosing your findings.
3 | We will make every effort to acknowledge your contributions to the safety of the API.
4 |
5 | To report a security issue related to either ArrowHead's API or the unofficial community wrapper, please use
6 | Github Security Advisory ["Report a Vulnerability"](https://github.com/helldivers-2/api/security/advisories/new) tab.
7 |
8 | This will notify the relevant maintainers and action will be taken as soon as possible.
9 | We will also try to acknowledge receiving your report as soon as possible.
10 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planets/RegionSize.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Planets;
2 |
3 | ///
4 | /// Indicates what size a has.
5 | ///
6 | public enum RegionSize
7 | {
8 | ///
9 | /// The region is a settlement.
10 | ///
11 | Settlement = 0,
12 | ///
13 | /// The region is a town.
14 | ///
15 | Town = 1,
16 | ///
17 | /// The region is a city.
18 | ///
19 | City = 2,
20 | ///
21 | /// The region is a megacity
22 | ///
23 | MegaCity = 3,
24 | }
25 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V2SerializerContext.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.V2;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Helldivers.Models;
5 |
6 | ///
7 | /// Source generated for V2's models.
8 | ///
9 | [JsonSerializable(typeof(Dispatch))]
10 | [JsonSerializable(typeof(List))]
11 | [JsonSerializable(typeof(SpaceStation))]
12 | [JsonSerializable(typeof(List))]
13 | [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true, UseStringEnumConverter = true)]
14 | public sealed partial class V2SerializerContext : JsonSerializerContext
15 | {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planets/Hazard.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Planets;
2 |
3 | ///
4 | /// Describes an environmental hazard that can be present on a .
5 | ///
6 | /// The name of this environmental hazard.
7 | /// The description of the environmental hazard.
8 | public sealed record Hazard(
9 | string Name,
10 | string Description
11 | )
12 | {
13 | ///
14 | /// Used when the hazard could not be determined.
15 | ///
16 | public static readonly Hazard Unknown = new("-UNKNOWN HAZARD-", string.Empty);
17 | };
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Dispatch.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Domain.Localization;
2 |
3 | namespace Helldivers.Models.V1;
4 |
5 | ///
6 | /// A message from high command to the players, usually updates on the status of the war effort.
7 | ///
8 | /// The unique identifier of this dispatch.
9 | /// When the dispatch was published.
10 | /// The type of dispatch, purpose unknown.
11 | /// The message this dispatch represents.
12 | public sealed record Dispatch(
13 | int Id,
14 | DateTime Published,
15 | int Type,
16 | LocalizedMessage Message
17 | );
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V2/Dispatch.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Domain.Localization;
2 |
3 | namespace Helldivers.Models.V2;
4 |
5 | ///
6 | /// A message from high command to the players, usually updates on the status of the war effort.
7 | ///
8 | /// The unique identifier of this dispatch.
9 | /// When the dispatch was published.
10 | /// The type of dispatch, purpose unknown.
11 | /// The message this dispatch represents.
12 | public sealed record Dispatch(
13 | int Id,
14 | DateTime Published,
15 | int Type,
16 | LocalizedMessage Message
17 | );
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V1/WarController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts;
2 | using Helldivers.Models.V1;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V1;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class WarController
11 | {
12 | ///
13 | /// Gets the current state.
14 | ///
15 | [ProducesResponseType(StatusCodes.Status200OK)]
16 | public static async Task Show(HttpContext context, IStore store)
17 | {
18 | var war = await store.Get(context.RequestAborted);
19 |
20 | return Results.Ok(war);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V2/SpaceStations/Cost.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V2.SpaceStations;
2 |
3 | ///
4 | /// Represents the "Cost" of a tactical action
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 | ///
12 | ///
13 | public sealed record Cost(
14 | string Id,
15 | long ItemMixId,
16 | long TargetValue,
17 | double CurrentValue,
18 | double DeltaPerSecond,
19 | long MaxDonationAmmount,
20 | long MaxDonationPeriodSeconds
21 | );
22 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/SpaceStations/Cost.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.SpaceStations;
2 |
3 | ///
4 | /// Represents the "Cost" of a tactical action
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 | ///
12 | ///
13 | public sealed record Cost(
14 | string Id,
15 | long ItemMixId,
16 | long TargetValue,
17 | double CurrentValue,
18 | double DeltaPerSecond,
19 | long MaxDonationAmmount,
20 | long MaxDonationPeriodSeconds
21 | );
22 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Info/PlanetRegion.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Info;
2 |
3 | ///
4 | /// A region of a planet, containing information about its health and size.
5 | ///
6 | /// The index of the region's planet
7 | /// The index of the region
8 | /// The ID that identifies the region in the JSON files.
9 | /// The maximum health of this region.
10 | /// The size of the region.
11 | public sealed record PlanetRegion(
12 | int PlanetIndex,
13 | int RegionIndex,
14 | ulong SettingsHash,
15 | ulong MaxHealth,
16 | int RegionSize
17 | );
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest new features for the API
4 | title: '[FEATURE]: '
5 | labels: ['feature request']
6 | ---
7 |
8 | ### What problem does it solve
9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
10 |
11 | ### What is the solution
12 | A clear and concise description of what you want to happen.
13 |
14 | ### Existing alternatives
15 | A clear and concise description of any alternative solutions or features you've considered.
16 |
17 | ### Added value
18 | A clear description of the value this feature would add to the community and applications.
19 |
20 | ### Additional notes
21 | Add any other context or screenshots about the feature request here.
22 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Assignment.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Assignments;
2 |
3 | namespace Helldivers.Models.ArrowHead;
4 |
5 | ///
6 | /// Represents an assignment given from Super Earth to the Helldivers.
7 | ///
8 | /// Internal identifier of this assignment.
9 | /// A list of numbers, how they represent progress is unknown.
10 | /// The amount of seconds until this assignment expires.
11 | /// Contains detailed information on this assignment like briefing, rewards, ...
12 | public sealed record Assignment(
13 | long Id32,
14 | List Progress,
15 | long ExpiresIn,
16 | Setting Setting
17 | );
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/SpaceStation.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Status;
2 |
3 | ///
4 | /// Represents one of the space stations as passed from the ArrowHead API.
5 | ///
6 | /// The unique identifier of the station.
7 | /// The id of the planet it's currently orbiting
8 | /// When the election for the next planet will end (in seconds relative to game start).
9 | /// A set of flags, purpose currently unknown.
10 | public sealed record SpaceStation(
11 | long Id32,
12 | int PlanetIndex,
13 | // TODO PlanetActiveEffects
14 | ulong CurrentElectionEndWarTime,
15 | int Flags
16 | );
17 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/WarSummary.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Summary;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Helldivers.Models.ArrowHead;
5 |
6 | ///
7 | /// Gets general statistics about the galaxy and specific planets.
8 | ///
9 | public sealed record WarSummary
10 | {
11 | ///
12 | /// Contains galaxy wide statistics aggregated from all planets.
13 | ///
14 | [JsonPropertyName("galaxy_stats")]
15 | public GalaxyStats GalaxyStats { get; set; } = null!;
16 |
17 | ///
18 | /// Contains statistics for specific planets.
19 | ///
20 | [JsonPropertyName("planets_stats")]
21 | public List PlanetsStats { get; set; } = null!;
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Facades/SteamFacade.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Core.Mapping.Steam;
3 | using Helldivers.Core.Mapping.V1;
4 | using Helldivers.Models.Steam;
5 | using Helldivers.Models.V1;
6 |
7 | namespace Helldivers.Core.Facades;
8 |
9 | ///
10 | /// Handles dispatching incoming data to all stores for Steam.
11 | ///
12 | public sealed class SteamFacade(IStore store, SteamNewsMapper mapper)
13 | {
14 | ///
15 | public async ValueTask UpdateStores(SteamNewsFeed feed)
16 | {
17 | if (feed is { AppNews.NewsItems: var items })
18 | {
19 | await store.SetStore(items.Select(mapper.MapToDomain).ToList());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Campaign.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1;
2 |
3 | ///
4 | /// Represents an ongoing campaign on a planet.
5 | ///
6 | /// The unique identifier of this .
7 | /// The planet on which this campaign is being fought.
8 | /// The type of campaign, this should be mapped onto an enum.
9 | /// Indicates how many campaigns have already been fought on this .
10 | /// The faction that is currently fighting this campaign.
11 | public record Campaign(
12 | int Id,
13 | Planet Planet,
14 | int Type, // TODO: map to enum
15 | ulong Count,
16 | string Faction
17 | );
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/Steam/SteamNewsFeedItem.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.Steam;
2 |
3 | ///
4 | /// Represents a single item in the Steam news feed response.
5 | ///
6 | /// A unique identifier assigned by Steam.
7 | /// The title of the news article.
8 | /// The URL to the full article on Steam
9 | /// The name of the author that released the news item.
10 | /// The contents of the news item.
11 | /// When the news item was published.
12 | public sealed record SteamNewsFeedItem(
13 | string Gid,
14 | string Title,
15 | string Url,
16 | string Author,
17 | string Contents,
18 | long Date
19 | );
20 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Helldivers": "Trace",
6 | "Microsoft.AspNetCore": "Warning",
7 | "System.Net.Http.HttpClient.ApiService": "Warning",
8 | "Microsoft.AspNetCore.Authentication": "Warning"
9 | }
10 | },
11 | "Helldivers": {
12 | "API": {
13 | "RateLimit": 500,
14 | "RateLimitWindow": 10,
15 | "ValidateClients": false,
16 | "Authentication": {
17 | "SigningKey": "I4eGmsXbDXfxlRo5N+w0ToRGN8aWSIaYWbZ2zMFqqnI="
18 | }
19 | },
20 | "Synchronization": {
21 | "IntervalSeconds": 300,
22 | "DefaultLanguage": "en-US",
23 | "Languages": [
24 | "en-US",
25 | "de-DE"
26 | ]
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/Campaign.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Status;
2 |
3 | ///
4 | /// Contains information of ongoing campaigns.
5 | ///
6 | /// The identifier of this campaign.
7 | /// The of the planet this campaign refers to.
8 | /// A numerical type, indicates the type of campaign (see helldivers-2/json).
9 | /// A numerical count, the amount of campaigns the planet has seen.
10 | /// A numerical race, the race of the planet this campaign refers to.
11 | public sealed record Campaign(
12 | int Id,
13 | int PlanetIndex,
14 | int Type,
15 | ulong Count,
16 | int Race
17 | );
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHeadSerializerContext.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Helldivers.Models;
5 |
6 | ///
7 | /// Source generated for ArrowHead models.
8 | ///
9 | [JsonSerializable(typeof(WarId))]
10 | [JsonSerializable(typeof(WarInfo))]
11 | [JsonSerializable(typeof(WarStatus))]
12 | [JsonSerializable(typeof(NewsFeedItem))]
13 | [JsonSerializable(typeof(WarSummary))]
14 | [JsonSerializable(typeof(List))]
15 | [JsonSerializable(typeof(List))]
16 | [JsonSerializable(typeof(List))]
17 | [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
18 | public sealed partial class ArrowHeadSerializerContext : JsonSerializerContext
19 | {
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/Steam/SteamNewsMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Steam;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Mapping.Steam;
5 |
6 | ///
7 | /// Maps from Steam's models.
8 | ///
9 | public sealed class SteamNewsMapper
10 | {
11 | ///
12 | /// Maps information from Steam's API into a V1 .
13 | ///
14 | public SteamNews MapToDomain(SteamNewsFeedItem item)
15 | {
16 | return new SteamNews(
17 | Id: item.Gid,
18 | Title: item.Title,
19 | Url: item.Url,
20 | Author: item.Author,
21 | Content: item.Contents,
22 | PublishedAt: DateTime.UnixEpoch.AddSeconds(item.Date)
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Middlewares/RedirectFlyDomainMiddleware.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.API.Middlewares;
2 |
3 | ///
4 | /// Automatically generates a redirect if a user still uses the deprecated `fly.io` URL.
5 | ///
6 | public class RedirectFlyDomainMiddleware : IMiddleware
7 | {
8 | private const string RedirectDomain = "https://api.helldivers2.dev";
9 |
10 | ///
11 | public async Task InvokeAsync(HttpContext context, RequestDelegate next)
12 | {
13 | if (context.Request.Host.Host.Equals("helldivers-2-dotnet.fly.dev", StringComparison.InvariantCultureIgnoreCase))
14 | {
15 | var url = $"{RedirectDomain}{context.Request.Path}";
16 |
17 | context.Response.Redirect(url, permanent: true);
18 | }
19 |
20 | await next(context);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/SteamNews.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1;
2 |
3 | ///
4 | /// Represents a new article from Steam's news feed.
5 | ///
6 | /// The identifier assigned by Steam to this news item.
7 | /// The title of the Steam news item.
8 | /// The URL to Steam where this news item was posted.
9 | /// The author who posted this message on Steam.
10 | /// The message posted by Steam, currently in Steam's weird markdown format.
11 | /// When this message was posted.
12 | public sealed record SteamNews(
13 | string Id,
14 | string Title,
15 | string Url,
16 | string Author,
17 | string Content,
18 | DateTime PublishedAt
19 | );
20 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V2/SpaceStation.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.V1;
2 | using Helldivers.Models.V2.SpaceStations;
3 |
4 | namespace Helldivers.Models.V2;
5 |
6 | ///
7 | /// Represents a Super Earth Democracy Space Station.
8 | ///
9 | /// The unique identifier of the station.
10 | /// The planet it's currently orbiting.
11 | /// When the election for the next planet will end.
12 | /// A set of flags, purpose currently unknown.
13 | /// A list of tactical actions the space station supports.
14 | public sealed record SpaceStation(
15 | long Id32,
16 | Planet Planet,
17 | DateTime ElectionEnd,
18 | int Flags,
19 | List TacticalActions
20 | );
21 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Contracts/IJsonParser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.Text;
3 |
4 | namespace Helldivers.SourceGen.Contracts;
5 |
6 | ///
7 | /// Interface for parsing JSON strings and generating corresponding C# source code.
8 | ///
9 | public interface IJsonParser
10 | {
11 | ///
12 | /// Parses the given source text and generates corresponding C# source code.
13 | ///
14 | /// The source file to be parsed.
15 | /// A for cancelling the parse process.
16 | /// A object representing the generated C# source code.
17 | SourceText Parse(AdditionalText file, CancellationToken cancellationToken = default);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Sync/Helldivers-2-Sync.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Helldivers.Sync
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/PlanetStatus.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Info;
2 |
3 | namespace Helldivers.Models.ArrowHead.Status;
4 |
5 | ///
6 | /// Represents the 'current' status of a planet in the galactic war.
7 | ///
8 | /// The identifier of the this status refers to.
9 | /// The faction currently owning the planet.
10 | /// The current health / liberation of a planet.
11 | /// If left alone, how much the health of the planet would regenerate.
12 | /// The amount of helldivers currently active on this planet.
13 | public sealed record PlanetStatus(
14 | int Index,
15 | int Owner,
16 | long Health,
17 | double RegenPerSecond,
18 | ulong Players
19 | );
20 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Parsers/FactionsParser.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.Json;
3 |
4 | namespace Helldivers.SourceGen.Parsers;
5 |
6 | ///
7 | /// Parses JSON data of factions and generates the corresponding C# source code representation.
8 | ///
9 | public class FactionsParser : BaseJsonParser
10 | {
11 | ///
12 | protected override (string Type, string Source) Parse(string json)
13 | {
14 | var builder = new StringBuilder("new Dictionary()\n\t{\n");
15 | var entries = JsonSerializer.Deserialize>(json)!;
16 | foreach (var pair in entries)
17 | builder.AppendLine($@"{'\t'}{'\t'}{{ {pair.Key}, ""{pair.Value}"" }},");
18 |
19 | builder.Append("\t}");
20 | return ("IReadOnlyDictionary", builder.ToString());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Contracts/IStore.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace Helldivers.Core.Contracts;
4 |
5 | ///
6 | /// Provides access to the current version of .
7 | ///
8 | ///
9 | /// If multiple variants per exist, the underlying
10 | /// implementation is responsible for selecting the correct variant
11 | /// based on .
12 | ///
13 | public interface IStore where T : class
14 | {
15 | ///
16 | /// Updates the state of the store with the given .
17 | ///
18 | ValueTask SetStore(T value);
19 |
20 | ///
21 | /// Gets the current snapshot of .
22 | ///
23 | Task Get(CancellationToken cancellationToken = default);
24 | }
25 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Middlewares/BlacklistMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.API.Configuration;
2 | using Helldivers.API.Metrics;
3 | using Microsoft.Extensions.Options;
4 |
5 | namespace Helldivers.API.Middlewares;
6 |
7 | ///
8 | /// Handles closing connections from blacklisted clients that violate ToS.
9 | ///
10 | public sealed class BlacklistMiddleware(IOptions options) : IMiddleware
11 | {
12 | ///
13 | public async Task InvokeAsync(HttpContext context, RequestDelegate next)
14 | {
15 | var client = ClientMetric.GetClientName(context);
16 | if (options.Value.Blacklist.Contains(client, StringComparison.InvariantCultureIgnoreCase))
17 | {
18 | // don't send response, only wastes more bytes.
19 | context.Abort();
20 | return;
21 | }
22 |
23 | await next(context);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/Helldivers-2-Models.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Helldivers.Models
6 |
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Helldivers 2 API
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Sync/Services/SteamApiService.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models;
2 | using Helldivers.Models.Steam;
3 | using System.Text.Json;
4 |
5 | namespace Helldivers.Sync.Services;
6 |
7 | ///
8 | /// Handles communication with the Steam API for fetching information.
9 | ///
10 | public sealed class SteamApiService(HttpClient http)
11 | {
12 | ///
13 | /// Fetches the latest from Steam's news feed for Helldivers 2.
14 | ///
15 | public async Task GetLatest(int count = 20)
16 | {
17 | var url = $"https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=553850&count={count}&feeds=steam_community_announcements";
18 | await using var response = await http.GetStreamAsync(url);
19 |
20 | var feed = await JsonSerializer.DeserializeAsync(
21 | response,
22 | SteamSerializerContext.Default.SteamNewsFeed
23 | );
24 |
25 | return feed!;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Configuration/AuthenticationConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.API.Configuration;
2 |
3 | ///
4 | /// Contains configuration for the authentication functionality of the API.
5 | ///
6 | public sealed class AuthenticationConfiguration
7 | {
8 | ///
9 | /// Whether the API authentication is enabled or disabled.
10 | ///
11 | public bool Enabled { get; set; } = true;
12 |
13 | ///
14 | /// A list of valid issuers of authentication tokens.
15 | ///
16 | public List ValidIssuers { get; set; } = [];
17 |
18 | ///
19 | /// A list of valid audiences for said authentication tokens.
20 | ///
21 | public List ValidAudiences { get; set; } = [];
22 |
23 | ///
24 | /// A string containing a base64 encoded secret used for signing and verifying authentication tokens.
25 | ///
26 | public string SigningKey { get; set; } = null!;
27 | }
28 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/SpaceStations/TacticalAction.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.SpaceStations;
2 |
3 | ///
4 | /// Represents information of a space station from the 'SpaceStation' endpoint returned by ArrowHead's API.
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 | ///
12 | ///
13 | ///
14 | ///
15 | ///
16 | public sealed record TacticalAction(
17 | long Id32,
18 | long MediaId32,
19 | string Name,
20 | string Description,
21 | string StrategicDescription,
22 | int Status,
23 | int StatusExpireAtWarTimeSeconds,
24 | List Cost,
25 | List EffectIds,
26 | List ActiveEffectIds
27 | );
28 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Parsers/BiomesParser.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.Json;
3 |
4 | namespace Helldivers.SourceGen.Parsers;
5 |
6 | ///
7 | /// BiomesParser is responsible for parsing a JSON string representation of biomes.
8 | ///
9 | public class BiomesParser : BaseJsonParser
10 | {
11 | ///
12 | protected override (string Type, string Source) Parse(string json)
13 | {
14 | var builder = new StringBuilder("new Dictionary()\n\t{\n");
15 | var entries = JsonSerializer.Deserialize>>(json)!;
16 | foreach (var pair in entries)
17 | builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Biome(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");
18 |
19 | builder.Append("\t}");
20 | return ("IReadOnlyDictionary", builder.ToString());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Helldivers": "Information",
6 | "System.Net.Http.HttpClient": "Warning",
7 | "Microsoft.AspNetCore": "Warning"
8 | }
9 | },
10 | "AllowedHosts": "*",
11 | "Helldivers": {
12 | "API": {
13 | "RateLimit": 5,
14 | "RateLimitWindow": 10,
15 | "Blacklist": "",
16 | "Authentication": {
17 | "Enabled": true,
18 | "ValidIssuers": ["dealloc"],
19 | "ValidAudiences": [
20 | "https://api.helldivers2.dev"
21 | ]
22 | }
23 | },
24 | "Synchronization": {
25 | "IntervalSeconds": 20,
26 | "DefaultLanguage": "en-US",
27 | "SpaceStations": [
28 | 749875195
29 | ],
30 | "Languages": [
31 | "en-US",
32 | "de-DE",
33 | "es-ES",
34 | "ru-RU",
35 | "fr-FR",
36 | "it-IT",
37 | "pl-PL",
38 | "zh-Hans",
39 | "zh-Hant"
40 | ]
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/War.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1;
2 |
3 | ///
4 | /// Global information of the ongoing war.
5 | ///
6 | /// When this war was started.
7 | /// When this war will end (or has ended).
8 | /// The time the snapshot of the war was taken, also doubles as the timestamp of which all other data dates from.
9 | /// The minimum game client version required to play in this war.
10 | /// A list of factions currently involved in the war.
11 | /// A fraction used to calculate the impact of a mission on the war effort.
12 | /// The statistics available for the galaxy wide war effort.
13 | public record War(
14 | DateTime Started,
15 | DateTime Ended,
16 | DateTime Now,
17 | string ClientVersion,
18 | List Factions,
19 | double ImpactMultiplier,
20 | Statistics Statistics
21 | );
22 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Extensions/ClaimsPrincipalExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 |
3 | namespace Helldivers.API.Extensions;
4 |
5 | ///
6 | /// Contains extension methods for working with s.
7 | ///
8 | public static class ClaimsPrincipalExtensions
9 | {
10 | ///
11 | /// Attempts to retrieve the integer value of a with type .
12 | ///
13 | /// Thrown if the claim could not be found, or is not a valid integer.
14 | public static int GetIntClaim(this ClaimsPrincipal user, string type)
15 | {
16 | var claim = user.Claims.FirstOrDefault(c =>
17 | string.Equals(c.Type, type, StringComparison.InvariantCultureIgnoreCase));
18 |
19 | if (claim is { Value: var str } && int.TryParse(str, out var result))
20 | return result;
21 |
22 | throw new InvalidOperationException($"Cannot fetch {type} or it is not a valid number");
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V1/WarMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Mapping.V1;
5 |
6 | ///
7 | /// Handles mapping for .
8 | ///
9 | public sealed class WarMapper(StatisticsMapper statisticsMapper)
10 | {
11 | ///
12 | /// Handles mapping to V1.
13 | ///
14 | public War MapToV1(MappingContext context, List planets)
15 | {
16 | return new War(
17 | Started: DateTime.UnixEpoch.AddSeconds(context.WarInfo.StartDate),
18 | Ended: DateTime.UnixEpoch.AddSeconds(context.WarInfo.EndDate),
19 | Now: DateTime.UnixEpoch.AddSeconds(context.InvariantWarStatus.Time),
20 | ClientVersion: context.WarInfo.MinimumClientVersion,
21 | Factions: Static.Factions.Values.ToList(),
22 | ImpactMultiplier: context.InvariantWarStatus.ImpactMultiplier,
23 | Statistics: statisticsMapper.MapToV1(context.WarSummary.GalaxyStats, planets)
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1SerializerContext.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.V1;
2 | using Helldivers.Models.V1.Planets;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace Helldivers.Models;
6 |
7 | ///
8 | /// Source generated for V1's models.
9 | ///
10 | [JsonSerializable(typeof(Assignment))]
11 | [JsonSerializable(typeof(List))]
12 | [JsonSerializable(typeof(Campaign))]
13 | [JsonSerializable(typeof(List))]
14 | [JsonSerializable(typeof(Dispatch))]
15 | [JsonSerializable(typeof(List))]
16 | [JsonSerializable(typeof(Planet))]
17 | [JsonSerializable(typeof(List))]
18 | [JsonSerializable(typeof(Statistics))]
19 | [JsonSerializable(typeof(SteamNews))]
20 | [JsonSerializable(typeof(List))]
21 | [JsonSerializable(typeof(War))]
22 | [JsonSerializable(typeof(Region))]
23 | [JsonSerializable(typeof(List))]
24 | [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true, UseStringEnumConverter = true)]
25 | public sealed partial class V1SerializerContext : JsonSerializerContext
26 | {
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Parsers/EnvironmentalsParser.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.Json;
3 |
4 | namespace Helldivers.SourceGen.Parsers;
5 |
6 | ///
7 | /// The EnvironmentalsParser class is responsible for parsing JSON strings
8 | /// representing environmental hazards and converting them into C# source code.
9 | ///
10 | public class EnvironmentalsParser : BaseJsonParser
11 | {
12 | ///
13 | protected override (string Type, string Source) Parse(string json)
14 | {
15 | var builder = new StringBuilder("new Dictionary()\n\t{\n");
16 | var entries = JsonSerializer.Deserialize>>(json)!;
17 | foreach (var pair in entries)
18 | builder.AppendLine($@"{'\t'}{'\t'}{{ ""{pair.Key}"", new Helldivers.Models.V1.Planets.Hazard(""{pair.Value["name"]}"", ""{pair.Value["description"]}"") }},");
19 |
20 | builder.Append("\t}");
21 | return ("IReadOnlyDictionary", builder.ToString());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Helldivers 2 Community
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 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Configuration/ApiConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.API.Configuration;
2 |
3 | ///
4 | /// Contains configuration of the API.
5 | ///
6 | public sealed class ApiConfiguration
7 | {
8 | ///
9 | /// The amount of requests that can be made within the time limit.
10 | ///
11 | public int RateLimit { get; set; }
12 |
13 | ///
14 | /// The time before the rate limit resets (in seconds).
15 | ///
16 | public int RateLimitWindow { get; set; }
17 |
18 | ///
19 | /// A comma separated list of clients that are (temporarily) blacklisted from making requests.
20 | ///
21 | public string Blacklist { get; set; } = string.Empty;
22 |
23 | ///
24 | /// Whether X-Super-Client and X-Super-Contact headers are validated.
25 | ///
26 | public bool ValidateClients { get; set; } = true;
27 |
28 | ///
29 | /// Contains the for the API.
30 | ///
31 | public AuthenticationConfiguration Authentication { get; set; } = null!;
32 | }
33 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planets/Event.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Planets;
2 |
3 | ///
4 | /// An ongoing event on a .
5 | ///
6 | /// The unique identifier of this event.
7 | /// The type of event.
8 | /// The faction that initiated the event.
9 | /// The health of the at the time of snapshot.
10 | /// The maximum health of the at the time of snapshot.
11 | /// When the event started.
12 | /// When the event will end.
13 | /// The identifier of the linked to this event.
14 | /// A list of joint operation identifier linked to this event.
15 | public sealed record Event(
16 | int Id,
17 | int EventType,
18 | string Faction,
19 | long Health,
20 | long MaxHealth,
21 | DateTime StartTime,
22 | DateTime EndTime,
23 | int CampaignId,
24 | List JointOperationIds
25 | );
26 |
--------------------------------------------------------------------------------
/fly.toml:
--------------------------------------------------------------------------------
1 | # fly.toml app configuration file generated for helldivers-2 on 2024-02-28T00:35:23+01:00
2 | #
3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4 | #
5 |
6 | app = 'helldivers-2-dotnet'
7 | primary_region = 'ams'
8 | kill_signal = 'SIGTERM'
9 |
10 | [build]
11 | dockerfile = 'src/Helldivers-2-API/Dockerfile'
12 | ignorefile = '.dockerignore'
13 |
14 | [env]
15 |
16 | [metrics]
17 | port = 8080
18 | path = "/metrics"
19 |
20 | [http_service]
21 | internal_port = 8080
22 | force_https = true
23 | auto_stop_machines = true
24 | auto_start_machines = true
25 | min_machines_running = 0
26 | max_machines_running = 1
27 | processes = ['app']
28 |
29 | [http_service.concurrency]
30 | type = 'connections'
31 | hard_limit = 1000
32 | soft_limit = 1000
33 |
34 | [[http_service.checks]]
35 | grace_period = "10s"
36 | interval = "60s"
37 | method = "GET"
38 | timeout = "10s"
39 | path = "/raw/api/WarSeason/current/WarID"
40 | [http_service.checks.headers]
41 | X-Super-Client = "Fly Health Check"
42 | X-Super-Contact = "fly.io"
43 |
44 |
45 |
46 | [[vm]]
47 | memory = "256mb"
48 | size = 'shared-cpu-1x'
49 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Sync/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Sync.Hosted;
2 | using Helldivers.Sync.Services;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace Helldivers.Sync.Extensions;
6 |
7 | ///
8 | /// Contains extension methods for .
9 | ///
10 | public static class ServiceCollectionExtensions
11 | {
12 | ///
13 | /// Adds all services related to the Helldivers API to the service container.
14 | ///
15 | public static IServiceCollection AddHelldiversSync(this IServiceCollection services)
16 | {
17 | services.AddSingleton();
18 | services.AddSingleton();
19 | services.AddHostedService(provider => provider.GetRequiredService());
20 | services.AddHostedService(provider => provider.GetRequiredService());
21 |
22 | services.AddHttpClient();
23 | services.AddHttpClient(http =>
24 | {
25 | http.BaseAddress = new Uri("https://api.live.prod.thehelldiversgame.com");
26 | });
27 |
28 | return services;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/SpaceStation.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.SpaceStations;
2 |
3 | namespace Helldivers.Models.ArrowHead;
4 |
5 | ///
6 | /// Represents an assignment given from Super Earth to the Helldivers.
7 | ///
8 | /// The unique identifier of the station.
9 | /// The id of the planet it's currently orbiting
10 | /// The id of the previous planet election.
11 | /// The id of the current planet election.
12 | /// The id of the next planet election.
13 | /// When the election for the next planet will end (in seconds relative to game start).
14 | /// A set of flags, purpose currently unknown.
15 | /// The list of actions the space station crew can perform.
16 | public sealed record SpaceStation(
17 | long Id32,
18 | int PlanetIndex,
19 | string LastElectionId,
20 | string CurrentElectionId,
21 | string NextElectionId,
22 | ulong CurrentElectionEndWarTime,
23 | int Flags,
24 | List TacticalActions
25 | );
26 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/WarInfo.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Info;
2 |
3 | namespace Helldivers.Models.ArrowHead;
4 |
5 | ///
6 | /// Represents mostly static information of the current galactic war.
7 | ///
8 | /// The identifier of the war season this represents.
9 | /// A unix timestamp (in seconds) when this season started.
10 | /// A unix timestamp (in seconds) when this season will end.
11 | /// A version string indicating the minimum game client version the API supports.
12 | /// A list of planets involved in this season's war.
13 | /// A list of homeworlds for the races (factions) involved in this war.
14 | /// The regions that can be found on this planet.
15 | public sealed record WarInfo(
16 | int WarId,
17 | long StartDate,
18 | long EndDate,
19 | string MinimumClientVersion,
20 | List PlanetInfos,
21 | List HomeWorlds,
22 | // TODO: capitalInfo's
23 | // TODO planetPermanentEffects
24 | List PlanetRegions
25 | );
26 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Assignments/Setting.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Assignments;
2 |
3 | ///
4 | /// Contains the details of an like reward and requirements.
5 | ///
6 | /// The type of assignment, values unknown at the moment.
7 | /// The title of this assignment.
8 | /// The briefing (description) of this assignment.
9 | /// A description of what is expected of Helldivers to complete the assignment.
10 | /// A list of s describing the assignment requirements.
11 | /// Contains information on the reward players will receive upon completion.
12 | /// Contains information on the rewards players will receive upon completion.
13 | /// Flags, suspected to be a binary OR'd value, purpose unknown.
14 | public sealed record Setting(
15 | int Type,
16 | string OverrideTitle,
17 | string OverrideBrief,
18 | string TaskDescription,
19 | List Tasks,
20 | Reward? Reward,
21 | List Rewards,
22 | int Flags
23 | );
24 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/PlanetRegionStatus.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Info;
2 |
3 | namespace Helldivers.Models.ArrowHead.Status;
4 |
5 | ///
6 | /// Represents the 'current' status of a planet's region in the galactic war.
7 | ///
8 | /// The identifier of the this region is on.
9 | /// The identifier of the this data refers to.
10 | /// The current faction that controls the region.
11 | /// The current health / liberation of the region.
12 | /// If left alone, how much the health of the region would regenerate.
13 | /// Unknown purpose.
14 | /// Whether this region is currently available to play on(?).
15 | /// The amount of helldivers currently active on this planet.
16 | public sealed record PlanetRegionStatus(
17 | int PlanetIndex,
18 | int RegionIndex,
19 | int Owner,
20 | long Health,
21 | double RegerPerSecond,
22 | double AvailabilityFactor,
23 | bool IsAvailable,
24 | long Players
25 | );
26 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/OpenApi/TypeMappers/EnumStringTypeMapper.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG
2 | using NJsonSchema;
3 | using NJsonSchema.Generation.TypeMappers;
4 | using System.Collections.ObjectModel;
5 |
6 | namespace Helldivers.API.OpenApi.TypeMappers;
7 |
8 | ///
9 | /// Forces enums to be generated as an enumeration of strings rather than integers.
10 | ///
11 | /// The enum type to convert.
12 | public sealed class EnumStringTypeMapper : ITypeMapper where T : struct, Enum
13 | {
14 | ///
15 | public Type MappedType => typeof(T);
16 |
17 | ///
18 | public bool UseReference => false;
19 |
20 | ///
21 | public void GenerateSchema(JsonSchema schema, TypeMapperContext context)
22 | {
23 | schema.Type = JsonObjectType.String;
24 | schema.Format = null;
25 |
26 | var names = Enum.GetNames();
27 |
28 | schema.Enumeration.Clear();
29 | schema.EnumerationNames.Clear();
30 |
31 | foreach (var name in names)
32 | schema.Enumeration.Add(name);
33 |
34 | schema.EnumerationNames = new Collection(names.ToList());
35 | schema.ExtensionData ??= new Dictionary()!;
36 | }
37 | }
38 | #endif
39 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Status/PlanetEvent.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Info;
2 |
3 | namespace Helldivers.Models.ArrowHead.Status;
4 |
5 | ///
6 | /// An ongoing event on a planet.
7 | ///
8 | /// The unique identifier of this event.
9 | /// The index of the planet.
10 | /// A numerical identifier that indicates what type of event this is.
11 | /// The identifier of the faction that owns the planet currently.
12 | /// The current health of the event.
13 | /// The current maximum health of the event.
14 | /// When this event started.
15 | /// When the event will end.
16 | /// The unique identifier of a related campaign.
17 | /// A list of identifiers of related joint operations.
18 | public sealed record PlanetEvent(
19 | int Id,
20 | int PlanetIndex,
21 | int EventType,
22 | int Race,
23 | long Health,
24 | long MaxHealth,
25 | long StartTime,
26 | long ExpireTime,
27 | int CampaignId,
28 | List JointOperationIds
29 | );
30 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Info/PlanetInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Info;
2 |
3 | ///
4 | /// Represents information of a planet from the 'WarInfo' endpoint returned by ArrowHead's API.
5 | ///
6 | /// The numerical identifier for this planet, used as reference by other properties throughout the API (like ).
7 | /// Purpose unknown at this time.
8 | /// A set of X/Y coordinates specifying the position of this planet on the galaxy map.
9 | /// A list of links to other planets (supply lines).
10 | /// The identifier of the sector this planet is located in.
11 | /// The 'health' of this planet, indicates how much liberation it needs to switch sides.
12 | /// Whether this planet is currently considered active in the galactic war.
13 | /// The identifier of the faction that initially owned this planet.
14 | public sealed record PlanetInfo(
15 | int Index,
16 | long SettingsHash,
17 | PlanetCoordinates Position,
18 | List Waypoints,
19 | int Sector,
20 | long MaxHealth,
21 | bool Disabled,
22 | int InitialOwner
23 | );
24 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V1/CampaignsController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V1;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class CampaignsController
11 | {
12 | ///
13 | /// Fetches a list of all available information available.
14 | ///
15 | [ProducesResponseType>(StatusCodes.Status200OK)]
16 | public static async Task Index(HttpContext context, IStore store)
17 | {
18 | var campaigns = await store.AllAsync(context.RequestAborted);
19 |
20 | return Results.Ok(campaigns);
21 | }
22 |
23 |
24 | ///
25 | /// Fetches a specific identified by .
26 | ///
27 | [ProducesResponseType(StatusCodes.Status200OK)]
28 | public static async Task Show(HttpContext context, IStore store, [FromRoute] int index)
29 | {
30 | var campaign = await store.GetAsync(index, context.RequestAborted);
31 | if (campaign is null)
32 | return Results.NotFound();
33 |
34 | return Results.Ok(campaign);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V1/DispatchController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V1;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class DispatchController
11 | {
12 | ///
13 | /// Fetches a list of all available information available.
14 | ///
15 | [ProducesResponseType>(StatusCodes.Status200OK)]
16 | public static async Task Index(HttpContext context, IStore store)
17 | {
18 | var dispatches = await store.AllAsync(context.RequestAborted);
19 |
20 | return Results.Ok(dispatches);
21 | }
22 |
23 |
24 | ///
25 | /// Fetches a specific identified by .
26 | ///
27 | [ProducesResponseType(StatusCodes.Status200OK)]
28 | public static async Task Show(HttpContext context, IStore store, [FromRoute] int index)
29 | {
30 | var dispatch = await store.GetAsync(index, context.RequestAborted);
31 | if (dispatch is null)
32 | return Results.NotFound();
33 |
34 | return Results.Ok(dispatch);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V2/DispatchController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V2;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V2;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class DispatchController
11 | {
12 | ///
13 | /// Fetches a list of all available information available.
14 | ///
15 | [ProducesResponseType>(StatusCodes.Status200OK)]
16 | public static async Task Index(HttpContext context, IStore store)
17 | {
18 | var dispatches = await store.AllAsync(context.RequestAborted);
19 |
20 | return Results.Ok(dispatches);
21 | }
22 |
23 |
24 | ///
25 | /// Fetches a specific identified by .
26 | ///
27 | [ProducesResponseType(StatusCodes.Status200OK)]
28 | public static async Task Show(HttpContext context, IStore store, [FromRoute] int index)
29 | {
30 | var dispatch = await store.GetAsync(index, context.RequestAborted);
31 | if (dispatch is null)
32 | return Results.NotFound();
33 |
34 | return Results.Ok(dispatch);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Parsers/PlanetRegionsParser.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.Json;
3 |
4 | namespace Helldivers.SourceGen.Parsers;
5 |
6 |
7 | ///
8 | /// Handles parsing the planetRegion.json and generating the resulting region data.
9 | ///
10 | public class PlanetRegionsParser : BaseJsonParser
11 | {
12 | ///
13 | protected override (string Type, string Source) Parse(string json)
14 | {
15 | var builder = new StringBuilder("new Dictionary()\n\t{\n");
16 | var document = JsonDocument.Parse(json);
17 | foreach (var property in document.RootElement.EnumerateObject())
18 | {
19 | var index = property.Name;
20 | var name = property.Value.GetProperty("name").GetString();
21 | string? description = property.Value.GetProperty("description").GetString();
22 | if (string.IsNullOrWhiteSpace(description))
23 | description = "null";
24 | else
25 | description = $@"""{description}""";
26 |
27 | builder.AppendLine($@"{'\t'}{'\t'}{{ {index}, (""{name}"", {description}) }},");
28 | }
29 |
30 | builder.Append("\t}");
31 | return ("IReadOnlyDictionary", builder.ToString());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Contracts/Collections/IStore.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace Helldivers.Core.Contracts.Collections;
4 |
5 | ///
6 | /// Provides access to the current version of .
7 | ///
8 | ///
9 | /// If multiple variants per exist, the underlying
10 | /// implementation is responsible for selecting the correct variant
11 | /// based on .
12 | ///
13 | public interface IStore where T : class
14 | {
15 | ///
16 | /// Updates the state of the store with the given .
17 | ///
18 | ValueTask SetStore(List values);
19 |
20 | ///
21 | /// Fetches all instances available.
22 | ///
23 | Task> AllAsync(CancellationToken cancellationToken = default);
24 |
25 | ///
26 | /// Attempts to fetch a single by .
27 | ///
28 | ///
29 | /// An instance of if found, or null if no instance associated with exists.
30 | ///
31 | Task GetAsync(TKey key, CancellationToken cancellationToken = default);
32 | }
33 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V1/AssignmentsController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V1;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class AssignmentsController
11 | {
12 | ///
13 | /// Fetches a list of all available information available.
14 | ///
15 | [ProducesResponseType>(StatusCodes.Status200OK)]
16 | public static async Task Index(HttpContext context, IStore store)
17 | {
18 | var assignments = await store.AllAsync(context.RequestAborted);
19 |
20 | return Results.Ok(assignments);
21 | }
22 |
23 |
24 | ///
25 | /// Fetches a specific identified by .
26 | ///
27 | [ProducesResponseType(StatusCodes.Status200OK)]
28 | public static async Task Show(HttpContext context, IStore store, [FromRoute] long index)
29 | {
30 | var assignment = await store.GetAsync(index, context.RequestAborted);
31 | if (assignment is null)
32 | return Results.NotFound();
33 |
34 | return Results.Ok(assignment);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V2/SpaceStationController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V2;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V2;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class SpaceStationController
11 | {
12 | ///
13 | /// Fetches a list of all available information available.
14 | ///
15 | [ProducesResponseType>(StatusCodes.Status200OK)]
16 | public static async Task Index(HttpContext context, IStore store)
17 | {
18 | // TODO: check implementation.
19 | var stations = await store.AllAsync(context.RequestAborted);
20 |
21 | return Results.Ok(stations);
22 | }
23 |
24 | ///
25 | /// Fetches a specific identified by .
26 | ///
27 | [ProducesResponseType(StatusCodes.Status200OK)]
28 | public static async Task Show(HttpContext context, IStore store, [FromRoute] long index)
29 | {
30 | var station = await store.GetAsync(index, context.RequestAborted);
31 |
32 | if (station is null)
33 | return Results.NotFound();
34 |
35 | return Results.Ok(station);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/fly.yml:
--------------------------------------------------------------------------------
1 | name: Fly Deploy
2 | on:
3 | release:
4 | types: [ published ]
5 | workflow_dispatch:
6 | inputs:
7 | version:
8 | description: 'Version to use (e.g. v1.2.3)'
9 | required: true
10 | jobs:
11 | deploy:
12 | environment: Fly
13 | name: Deploy app
14 | runs-on: ubuntu-latest
15 | concurrency: deploy-group # optional: ensure only one action runs at a time
16 | steps:
17 | - uses: actions/checkout@v4
18 |
19 | - uses: superfly/flyctl-actions/setup-flyctl@master
20 |
21 | - name: Initial static JSON schema submodule
22 | run: git submodule update --init ./src/Helldivers-2-Models/json
23 |
24 | - name: Set Version
25 | id: set_version
26 | run: |
27 | if [[ -n "${{ github.event.inputs.version }}" ]]; then
28 | VERSION="${{ github.event.inputs.version }}"
29 | else
30 | VERSION="${GITHUB_REF##*/}"
31 | fi
32 |
33 | VERSION_WITHOUT_V="${VERSION#v}"
34 |
35 | echo "VERSION=$VERSION"
36 | echo "VERSION_WITHOUT_V=$VERSION_WITHOUT_V"
37 | echo "version=$VERSION" >> $GITHUB_OUTPUT
38 | echo "version-without-v=$VERSION_WITHOUT_V" >> $GITHUB_OUTPUT
39 |
40 | - run: flyctl deploy --remote-only --build-arg VERSION=${{ steps.set_version.outputs.version-without-v }}
41 | env:
42 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
43 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V1/CampaignMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models;
2 | using Helldivers.Models.V1;
3 |
4 | namespace Helldivers.Core.Mapping.V1;
5 |
6 | ///
7 | /// Handles mapping for .
8 | ///
9 | public class CampaignMapper
10 | {
11 | ///
12 | /// Maps all in the current context.
13 | ///
14 | public IEnumerable MapToV1(MappingContext context, List planets)
15 | {
16 | foreach (var campaign in context.InvariantWarStatus.Campaigns)
17 | {
18 | var result = MapToV1(campaign, planets);
19 |
20 | if (result is not null)
21 | yield return result;
22 | }
23 | }
24 |
25 | ///
26 | /// Maps ArrowHead's onto V1's.
27 | ///
28 | private Campaign? MapToV1(Models.ArrowHead.Status.Campaign campaign, List planets)
29 | {
30 | var planet = planets.FirstOrDefault(p => p.Index == campaign.PlanetIndex);
31 | Static.Factions.TryGetValue(campaign.Race, out var currentOwner);
32 |
33 | if (planet is null)
34 | return null;
35 |
36 | return new Campaign(
37 | Id: campaign.Id,
38 | Planet: planet,
39 | Type: campaign.Type,
40 | Count: campaign.Count,
41 | Faction: currentOwner ?? string.Empty
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Middlewares/EtagMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Sync.Hosted;
2 | using System.Globalization;
3 |
4 | namespace Helldivers.API.Middlewares;
5 |
6 | ///
7 | /// Automatically appends the `Etag` header to responses.
8 | ///
9 | public sealed class EtagMiddleware(ArrowHeadSyncService arrowHead) : IMiddleware
10 | {
11 | ///
12 | public async Task InvokeAsync(HttpContext context, RequestDelegate next)
13 | {
14 | if (arrowHead.LastUpdated is { } lastUpdated)
15 | {
16 | var key = $"{lastUpdated.Subtract(DateTime.UnixEpoch).TotalMilliseconds:0}";
17 | context.Response.Headers.ETag = key;
18 |
19 | var isModifiedSince = context.Request.Headers.IfModifiedSince.Any(value =>
20 | {
21 | if (DateTime.TryParseExact(value, "R", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal,
22 | out var date))
23 | {
24 | if (date >= lastUpdated)
25 | {
26 | return false;
27 | }
28 | }
29 |
30 | return true;
31 | }) || context.Request.Headers.IfModifiedSince.Count == 0;
32 |
33 | if (isModifiedSince is false)
34 | {
35 | context.Response.StatusCode = StatusCodes.Status304NotModified;
36 | return;
37 | }
38 | }
39 |
40 | await next(context);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Assignment.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Domain.Localization;
2 | using Helldivers.Models.V1.Assignments;
3 | using Task = Helldivers.Models.V1.Assignments.Task;
4 |
5 | namespace Helldivers.Models.V1;
6 |
7 | ///
8 | /// Represents an assignment given by Super Earth to the community.
9 | /// This is also known as 'Major Order's in the game.
10 | ///
11 | /// The unique identifier of this assignment.
12 | /// A list of numbers, how they represent progress is unknown.
13 | /// The title of the assignment.
14 | /// A long form description of the assignment, usually contains context.
15 | /// A very short summary of the description.
16 | /// A list of tasks that need to be completed for this assignment.
17 | /// The reward for completing the assignment.
18 | /// A list of rewards for completing the assignment.
19 | /// The date when the assignment will expire.
20 | /// Flags regarding the assignment.
21 | public sealed record Assignment(
22 | long Id,
23 | List Progress,
24 | LocalizedMessage Title,
25 | LocalizedMessage Briefing,
26 | LocalizedMessage Description,
27 | List Tasks,
28 | Reward? Reward,
29 | List Rewards,
30 | DateTime Expiration,
31 | int Flags
32 | );
33 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Localization/CultureDictionary.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace Helldivers.Core.Localization;
4 |
5 | ///
6 | /// Specialized version of that intelligently maps as
7 | /// keys (eg, if "en-UK" does not exist but "en-US" it'll match).
8 | ///
9 | public class CultureDictionary
10 | {
11 | private readonly Dictionary _items;
12 |
13 | /// Creates a new instance of ,
14 | public CultureDictionary(IEnumerable> items)
15 | {
16 | _items = items
17 | .Select(pair => new KeyValuePair(new CultureInfo(pair.Key), pair.Value))
18 | .SelectMany(pair => new List>([
19 | new KeyValuePair(pair.Key, pair.Value),
20 | new KeyValuePair(pair.Key.Parent, pair.Value)
21 | ]))
22 | .DistinctBy(pair => pair.Key)
23 | .ToDictionary(pair => pair.Key, pair => pair.Value);
24 | }
25 |
26 | ///
27 | /// Attempts to resolve a by it's closest common value or parent.
28 | ///
29 | public T? Get(CultureInfo? cultureInfo = default)
30 | {
31 | cultureInfo ??= CultureInfo.CurrentCulture;
32 |
33 | return _items.GetValueOrDefault(cultureInfo)
34 | ?? _items.GetValueOrDefault(cultureInfo.Parent);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/OpenApi/DocumentProcessors/HelldiversDocumentProcessor.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG
2 | using NSwag;
3 | using NSwag.Generation.Processors;
4 | using NSwag.Generation.Processors.Contexts;
5 |
6 | namespace Helldivers.API.OpenApi.DocumentProcessors;
7 |
8 | ///
9 | /// Handles processing of the OpenAPI specification of the Production Helldivers API.
10 | ///
11 | public class HelldiversDocumentProcessor : IDocumentProcessor
12 | {
13 | private const string HelldiversFlyServer = "https://api.helldivers2.dev/";
14 |
15 | ///
16 | public void Process(DocumentProcessorContext context)
17 | {
18 | // First we make a copy of the Paths, since C# doesn't like it when collections are modified during iteration.
19 | var paths = context.Document.Paths.ToList();
20 | var server = new OpenApiServer
21 | {
22 | Description = "The dotnet helldivers server",
23 | Url = HelldiversFlyServer
24 | };
25 |
26 | foreach (var (_, item) in paths)
27 | {
28 | item.Servers.Clear();
29 | item.Servers.Add(server);
30 | }
31 |
32 | foreach (var (_, schema) in context.Document.Components.Schemas)
33 | {
34 | foreach (var (key, property) in schema.Properties)
35 | {
36 | foreach (var oneOf in property.OneOf)
37 | {
38 | property.AnyOf.Add(oneOf);
39 | }
40 |
41 | property.OneOf.Clear();
42 | }
43 | }
44 | }
45 | }
46 | #endif
47 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planets/Region.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1.Planets;
2 |
3 | ///
4 | /// A region on a planet.
5 | ///
6 | /// The Name and Description fields may be empty when the underlying data store doesn't contain information on them.
7 | /// This is typically when ArrowHead adds new regions that aren't updated in the data store (helldivers-2/json) yet.
8 | ///
9 | /// Note that some properties may be unavailable when the region is inactive.
10 | ///
11 | /// The identifier of this region.
12 | /// The underlying hash identifier of ArrowHead.
13 | /// The name of the region.
14 | /// A long-form description of the region.
15 | /// The current health of the region.
16 | /// The maximum health of this region.
17 | /// The size of this region.
18 | /// The amount of health this region generates when left alone.
19 | /// Unknown purpose.
20 | /// Whether the region is currently playable(?).
21 | /// The amount of helldivers currently active in this region.
22 | public record struct Region(
23 | int Id,
24 | ulong Hash,
25 | string? Name,
26 | string? Description,
27 | long? Health,
28 | ulong MaxHealth,
29 | RegionSize Size,
30 | double? RegenPerSecond,
31 | double? AvailabilityFactor,
32 | bool IsAvailable,
33 | long Players
34 | );
35 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/WarStatus.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Status;
2 |
3 | namespace Helldivers.Models.ArrowHead;
4 |
5 | ///
6 | /// Represents a snapshot of the current status of the galactic war.
7 | ///
8 | /// The war season this snapshot refers to.
9 | /// The time this snapshot was taken.
10 | /// This is the factor by which influence at the end of a mission is multiplied to calculate the impact on liberation
11 | /// Internal identifier, purpose unknown.
12 | /// A list of statuses for planets.
13 | /// A list of attacks currently ongoing.
14 | /// A list of ongoing campaigns in the galactic war.
15 | /// A list of s.
16 | /// A list of ongoing s.
17 | /// The regions that have a status.
18 | public sealed record WarStatus(
19 | int WarId,
20 | long Time,
21 | double ImpactMultiplier,
22 | long StoryBeatId32,
23 | List PlanetStatus,
24 | List PlanetAttacks,
25 | List Campaigns,
26 | // TODO CommunityTargets
27 | List JointOperations,
28 | List PlanetEvents,
29 | // TODO PlanetActiveEffects
30 | // TODO activeElectionPolicyEffects
31 | // TODO globalEvents
32 | // TODO superEarthWarResults
33 | List PlanetRegions
34 | );
35 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Sync/Configuration/HelldiversSyncConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Sync.Configuration;
2 |
3 | ///
4 | /// Represents configuration for the synchronization system of the Helldivers 2 API.
5 | ///
6 | public sealed class HelldiversSyncConfiguration
7 | {
8 | ///
9 | /// The interval (in seconds) at which the sync services will fetch information from the API.
10 | ///
11 | public int IntervalSeconds { get; set; } = 10;
12 |
13 | ///
14 | /// The default language which the API will use.
15 | ///
16 | public string DefaultLanguage { get; set; } = "en-US";
17 |
18 | ///
19 | /// The list of space stations to fetch information for.
20 | ///
21 | public List SpaceStations { get; set; } = new(0);
22 |
23 | ///
24 | /// A list of language codes for which translations will be provided.
25 | ///
26 | public List Languages { get; set; } = new(0);
27 |
28 | ///
29 | /// Flag to indicate if the application should only run the sync once.
30 | /// This is used in CI testing to validate sync works.
31 | ///
32 | public bool RunOnce { get; set; } = false;
33 |
34 | ///
35 | /// Get the maximum number of entries returned by ArrowHead from the newsfeed API.
36 | ///
37 | public uint NewsFeedMaxEntries { get; set; } = 1024;
38 |
39 | ///
40 | /// Get all news feed entries that were published after this timestamp
41 | ///
42 | public uint NewsFeedFromTimestamp { get; set; } = 1000;
43 | }
44 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V1/SteamController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V1;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class SteamController
11 | {
12 | ///
13 | /// Fetches the Steam newsfeed for Helldivers 2.
14 | ///
15 | /// You can definitely get this yourself, however it's included for your convenience!
16 | [ProducesResponseType>(StatusCodes.Status200OK)]
17 | public static async Task Index(HttpContext context, IStore store)
18 | {
19 | var news = await store.AllAsync(context.RequestAborted);
20 |
21 | return Results.Ok(news);
22 | }
23 |
24 | ///
25 | /// Fetches a specific newsfeed item from the Helldivers 2 Steam newsfeed.
26 | ///
27 | /// You can definitely get this yourself, however it's included for your convenience!
28 | /// Returned when the key is not recognized.
29 | [ProducesResponseType(StatusCodes.Status404NotFound)]
30 | [ProducesResponseType>(StatusCodes.Status200OK)]
31 | public static async Task Show(HttpContext context, IStore store, [FromRoute] string gid)
32 | {
33 | var news = await store.GetAsync(gid, context.RequestAborted);
34 |
35 | if (news is null)
36 | return Results.NotFound();
37 |
38 | return Results.Ok(news);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/OpenApi/OperationProcessors/SuperHeadersProcessor.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG
2 | using NSwag;
3 | using NSwag.Generation.Processors;
4 | using NSwag.Generation.Processors.Contexts;
5 |
6 | namespace Helldivers.API.OpenApi.OperationProcessors;
7 |
8 | ///
9 | /// The is used to add headers that are required for the API to identify
10 | /// the client and to provide developer contact information.
11 | ///
12 | public class SuperHeadersProcessor : IOperationProcessor
13 | {
14 | ///
15 | public bool Process(OperationProcessorContext context)
16 | {
17 | context.OperationDescription.Operation.Parameters.Add(
18 | new OpenApiParameter
19 | {
20 | Name = Constants.CLIENT_HEADER_NAME,
21 | Kind = OpenApiParameterKind.Header,
22 | Type = NJsonSchema.JsonObjectType.String,
23 | IsRequired = true,
24 | Description = "The name of the header that identifies the client to the API.",
25 | Default = string.Empty
26 | }
27 | );
28 |
29 | context.OperationDescription.Operation.Parameters.Add(
30 | new OpenApiParameter
31 | {
32 | Name = Constants.CONTACT_HEADER_NAME,
33 | Kind = OpenApiParameterKind.Header,
34 | Type = NJsonSchema.JsonObjectType.String,
35 | IsRequired = true,
36 | Description = "The name of the header with developer contact information.",
37 | Default = string.Empty
38 | }
39 | );
40 |
41 | return true;
42 | }
43 | }
44 | #endif
45 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Facades/V2Facade.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Core.Mapping;
3 | using Helldivers.Core.Mapping.V2;
4 | using Helldivers.Models.V1;
5 | using Helldivers.Models.V2;
6 | using Dispatch = Helldivers.Models.V2.Dispatch;
7 |
8 | namespace Helldivers.Core.Facades;
9 |
10 | ///
11 | /// Handles dispatching incoming data to all stores for V2.
12 | ///
13 | public sealed class V2Facade(
14 | IStore dispatchStore,
15 | DispatchMapper dispatchMapper,
16 | IStore planetStore,
17 | IStore spaceStationStore,
18 | SpaceStationMapper spaceStationMapper
19 | )
20 | {
21 | ///
22 | public async ValueTask UpdateStores(MappingContext context)
23 | {
24 | await UpdateDispatchStore(context);
25 |
26 | // Some mappers need access to the list of planets, so we fetch it from the freshly-mapped store.
27 | var planets = await planetStore.AllAsync();
28 | await UpdateSpaceStationStore(context, planets);
29 | }
30 |
31 | private async ValueTask UpdateDispatchStore(MappingContext context)
32 | {
33 | var dispatches = dispatchMapper
34 | .MapToV2(context)
35 | .OrderByDescending(dispatch => dispatch.Id)
36 | .ToList();
37 |
38 | await dispatchStore.SetStore(dispatches);
39 | }
40 |
41 | private async ValueTask UpdateSpaceStationStore(MappingContext context, List planets)
42 | {
43 | var spaceStations = spaceStationMapper
44 | .MapToV2(context, planets)
45 | .ToList();
46 |
47 | await spaceStationStore.SetStore(spaceStations);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/OpenApi/TypeMappers/LocalizedMessageTypeMapper.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG
2 | using Helldivers.Models.Domain.Localization;
3 | using NJsonSchema;
4 | using NJsonSchema.Generation.TypeMappers;
5 |
6 | namespace Helldivers.API.OpenApi.TypeMappers;
7 |
8 | ///
9 | /// Maps the LocalizedMessage type to a JSON schema describing what values it serializes to.
10 | ///
11 | /// Contains all languages that are supported (and thus should be OpenAPI documented).
12 | public sealed class LocalizedMessageTypeMapper(IReadOnlyList languages) : ITypeMapper
13 | {
14 | ///
15 | public Type MappedType => typeof(LocalizedMessage);
16 |
17 | ///
18 | public bool UseReference => false;
19 |
20 | ///
21 | /// Gets the generated schema for the LocalizedMessage type.
22 | ///
23 | public void GenerateSchema(JsonSchema schema, TypeMapperContext context)
24 | {
25 | var dictionarySchema = new JsonSchema
26 | {
27 | Title = nameof(LocalizedMessage),
28 | Type = JsonObjectType.Object,
29 | Description = "When passing in ivl-IV as Accept-Language, all available languages are returned"
30 | };
31 |
32 | // NJsonSchema's Properties is read-only, so we can't use the object assignment syntax.
33 | foreach (string language in languages)
34 | {
35 | dictionarySchema.Properties.Add(language, new JsonSchemaProperty
36 | {
37 | Type = JsonObjectType.String,
38 | Description = $"The message in {language}"
39 | });
40 | }
41 |
42 | schema.OneOf.Add(JsonSchema.FromType(typeof(string)));
43 | schema.OneOf.Add(dictionarySchema);
44 | }
45 | }
46 | #endif
47 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Summary/GalaxyStats.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.ArrowHead.Summary;
2 |
3 | ///
4 | /// Represents galaxy wide statistics.
5 | ///
6 | /// The amount of missions won.
7 | /// The amount of missions lost.
8 | /// The total amount of time spent planetside (in seconds).
9 | /// The total amount of bugs killed since start of the season.
10 | /// The total amount of automatons killed since start of the season.
11 | /// The total amount of Illuminate killed since start of the season.
12 | /// The total amount of bullets fired
13 | /// The total amount of bullets hit
14 | /// The total amount of time played (including off-planet) in seconds.
15 | /// The amount of casualties on the side of humanity.
16 | /// The amount of revives(?).
17 | /// The amount of friendly fire casualties.
18 | /// A percentage indicating how many started missions end in success.
19 | /// A percentage indicating average accuracy of Helldivers.
20 | public sealed record GalaxyStats(
21 | ulong MissionsWon,
22 | ulong MissionsLost,
23 | ulong MissionTime,
24 | ulong BugKills,
25 | ulong AutomatonKills,
26 | ulong IlluminateKills,
27 | ulong BulletsFired,
28 | ulong BulletsHit,
29 | ulong TimePlayed,
30 | ulong Deaths,
31 | ulong Revives,
32 | ulong Friendlies,
33 | ulong MissionSuccessRate,
34 | ulong Accurracy
35 | );
36 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Parsers/PlanetsParser.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.Json;
3 |
4 | namespace Helldivers.SourceGen.Parsers;
5 |
6 | ///
7 | /// Handles parsing the planets.json and generating the resulting planet data.
8 | ///
9 | public class PlanetsParser : BaseJsonParser
10 | {
11 | ///
12 | protected override (string Type, string Source) Parse(string json)
13 | {
14 | var builder = new StringBuilder("new Dictionary()\n\t{\n");
15 | var document = JsonDocument.Parse(json);
16 | foreach (var property in document.RootElement.EnumerateObject())
17 | {
18 | var index = property.Name;
19 | var names = property
20 | .Value
21 | .GetProperty("names")
22 | .EnumerateObject()
23 | .ToDictionary(prop => prop.Name, prop => prop.Value.GetString()!);
24 | var sector = property.Value.GetProperty("sector").GetString();
25 | var biome = property.Value.GetProperty("biome").GetString();
26 | var environmentals = property
27 | .Value
28 | .GetProperty("environmentals")
29 | .EnumerateArray()
30 | .Select(prop => $@"""{prop.GetString()!}""")
31 | .ToList();
32 |
33 | builder.AppendLine($@"{'\t'}{'\t'}{{ {index}, (LocalizedMessage.FromStrings([{string.Join(", ", names.Select(pair => $@"[""{pair.Key}"", ""{pair.Value}""]"))}]), ""{sector}"", ""{biome}"", [{string.Join(", ", environmentals)}]) }},");
34 | }
35 |
36 | builder.Append("\t}");
37 | return ("IReadOnlyDictionary", builder.ToString());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/OpenApi/DocumentProcessors/ArrowHeadDocumentProcessor.cs:
--------------------------------------------------------------------------------
1 | #if DEBUG
2 | using NSwag;
3 | using NSwag.Generation.Processors;
4 | using NSwag.Generation.Processors.Contexts;
5 |
6 | namespace Helldivers.API.OpenApi.DocumentProcessors;
7 |
8 | ///
9 | /// Handles processing of the OpenAPI specification of the ArrowHead studio API.
10 | /// Specifically, it removes all community API specific aspects (like the '/raw' prefix).
11 | ///
12 | public class ArrowHeadDocumentProcessor : IDocumentProcessor
13 | {
14 | // "/raw".length
15 | private const int PrefixLength = 4;
16 |
17 | private const string ArrowHeadServer = "https://api.live.prod.thehelldiversgame.com/";
18 |
19 | ///
20 | public void Process(DocumentProcessorContext context)
21 | {
22 | // First we make a copy of the Paths, since C# doesn't like it when collections are modified during iteration.
23 | var paths = context.Document.Paths.ToList();
24 | var server = new OpenApiServer
25 | {
26 | Description = "The official ArrowHead server",
27 | Url = ArrowHeadServer
28 | };
29 |
30 | foreach (var (url, item) in paths)
31 | {
32 | // If it's a '/raw' prefixed URL, we strip the /raw prefix.
33 | if (string.IsNullOrWhiteSpace(url) is false && url.StartsWith("/raw"))
34 | {
35 | item.Servers.Clear();
36 | item.Servers.Add(server);
37 |
38 | context.Document.Paths[url[PrefixLength..]] = item;
39 | context.Document.Paths.Remove(url);
40 | }
41 | }
42 |
43 | foreach (var operation in context.Document.Operations)
44 | {
45 | operation.Operation.OperationId = operation.Operation.OperationId.Replace("Raw", string.Empty);
46 | }
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/V1/PlanetController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Models.V1;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers.V1;
6 |
7 | ///
8 | /// Contains API endpoints for .
9 | ///
10 | public static class PlanetController
11 | {
12 | ///
13 | /// Fetches a list of all available information available.
14 | ///
15 | [ProducesResponseType>(StatusCodes.Status200OK)]
16 | public static async Task Index(HttpContext context, IStore store)
17 | {
18 | var planets = await store.AllAsync(context.RequestAborted);
19 |
20 | return Results.Ok(planets);
21 | }
22 |
23 |
24 | ///
25 | /// Fetches a specific identified by .
26 | ///
27 | [ProducesResponseType(StatusCodes.Status200OK)]
28 | public static async Task Show(HttpContext context, IStore store, [FromRoute] int index)
29 | {
30 | var planet = await store.GetAsync(index, context.RequestAborted);
31 | if (planet is null)
32 | return Results.NotFound();
33 |
34 | return Results.Ok(planet);
35 | }
36 |
37 | ///
38 | /// Fetches all planets with an active .
39 | ///
40 | [ProducesResponseType>(StatusCodes.Status200OK)]
41 | public static async Task WithEvents(HttpContext context, IStore store)
42 | {
43 | var planets = await store.AllAsync(context.RequestAborted);
44 | var withEvents = planets
45 | .Where(planet => planet.Event is not null)
46 | .ToList();
47 |
48 | return Results.Ok(withEvents);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine-extra AS base
2 | USER $APP_UID
3 | WORKDIR /app
4 |
5 | FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine-aot AS build
6 | RUN apk add --no-cache build-base clang lld llvm zlib-dev libstdc++
7 | ARG BUILD_CONFIGURATION=Release
8 | ARG BUILD_RUNTIME=linux-musl-x64
9 | ARG OPENAPI=false
10 | ARG VERSION=0.0.0
11 |
12 | WORKDIR /
13 | COPY ./docs/openapi /docs/openapi
14 |
15 | WORKDIR /src
16 |
17 | COPY ["src/Helldivers-2-API/Helldivers-2-API.csproj", "Helldivers-2-API/"]
18 | COPY ["src/Helldivers-2-Models/Helldivers-2-Models.csproj", "Helldivers-2-Models/"]
19 | COPY ["src/Helldivers-2-Sync/Helldivers-2-Sync.csproj", "Helldivers-2-Sync/"]
20 | COPY ["src/Helldivers-2-Core/Helldivers-2-Core.csproj", "Helldivers-2-Core/"]
21 | COPY ["src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj", "Helldivers-2-SourceGen/"]
22 | COPY ["Directory.Build.props", "."]
23 |
24 | RUN dotnet restore -r $BUILD_RUNTIME "Helldivers-2-API/Helldivers-2-API.csproj"
25 | COPY ./src .
26 | WORKDIR "/src/Helldivers-2-API"
27 | RUN if [[ "${OPENAPI}" = 'true' ]]; \
28 | then \
29 | echo "OPENAPI is set to ${OPENAPI}, running OpenAPI generators" \
30 | && dotnet build "Helldivers-2-API.csproj" /p:Version="$VERSION" --no-restore -c Debug \
31 | && mkdir -p wwwroot \
32 | && cp /docs/openapi/* wwwroot/ \
33 | ; fi
34 | RUN dotnet build "Helldivers-2-API.csproj" --no-restore -r $BUILD_RUNTIME -c $BUILD_CONFIGURATION -o /app/build
35 |
36 | FROM build AS publish
37 | ARG BUILD_CONFIGURATION=Release
38 | ARG BUILD_RUNTIME=linux-musl-x64
39 | ARG VERSION=0.0.0
40 | RUN dotnet publish "Helldivers-2-API.csproj" /p:Version="$VERSION" --self-contained -r $BUILD_RUNTIME -c $BUILD_CONFIGURATION -o /app/publish
41 |
42 | FROM base AS final
43 | WORKDIR /app
44 | EXPOSE 8080
45 | COPY --from=publish /app/publish .
46 | ENTRYPOINT ["./Helldivers-2-API"]
47 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Parsers/BaseJsonParser.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.SourceGen.Contracts;
2 | using Microsoft.CodeAnalysis;
3 | using Microsoft.CodeAnalysis.Text;
4 | using System.Text;
5 |
6 | namespace Helldivers.SourceGen.Parsers;
7 |
8 | ///
9 | /// An abstract base class for parsing JSON data and generating corresponding C# source code.
10 | ///
11 | public abstract class BaseJsonParser : IJsonParser
12 | {
13 | private const string TEMPLATE = @"//
14 | #nullable enable
15 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
16 | using global::System.Collections.Generic;
17 | using global::Helldivers.Models.Domain.Localization;
18 |
19 | namespace Helldivers.Models;
20 |
21 | public static partial class Static
22 | {{
23 | /// Public list of {0} entries from {1}
24 | public static {2} {0} = {3};
25 | }}";
26 |
27 | ///
28 | public SourceText Parse(AdditionalText file, CancellationToken cancellationToken = default)
29 | {
30 | var filename = Path.GetFileName(file.Path);
31 | var json = file.GetText(cancellationToken)?.ToString();
32 |
33 | var name = Path.GetFileNameWithoutExtension(file.Path);
34 | name = $"{char.ToUpper(name[0])}{name.Substring(1)}";
35 |
36 | if (string.IsNullOrWhiteSpace(json) is false)
37 | {
38 | var (type, csharp) = Parse(json!);
39 |
40 | var output = string.Format(TEMPLATE, name, filename, type, csharp);
41 | return SourceText.From(output, Encoding.UTF8);
42 | }
43 |
44 | return SourceText.From("// Could not read JSON file", Encoding.UTF8);
45 | }
46 |
47 | ///
48 | /// Convert the JSON string into C# code that can be injected.
49 | ///
50 | protected abstract (string Type, string Source) Parse(string json);
51 | }
52 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 | name: .NET CI
4 |
5 | on:
6 | push:
7 | branches: [ "master" ]
8 | pull_request:
9 | branches: [ "master" ]
10 |
11 | permissions:
12 | contents: read
13 | actions: write
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: 10.0.x
26 | - name: download JSON submodule
27 | run: git submodule update --init ./src/Helldivers-2-Models/json
28 | - name: Restore dependencies
29 | run: dotnet restore
30 | - name: Build
31 | run: dotnet build --no-restore --no-incremental /p:TreatWarningsAsErrors=true
32 | - name: Check formatting
33 | run: dotnet format --no-restore --verify-no-changes --exclude-diagnostics IL2026 IL3050
34 | - name: Check tests
35 | run: dotnet test --no-build --verbosity normal
36 |
37 | - name: Upload artifacts
38 | uses: actions/upload-artifact@v4
39 | with:
40 | retention-days: 5
41 | name: openapi-schemas
42 | path: docs/openapi/
43 |
44 | docker-build:
45 | runs-on: ubuntu-latest
46 |
47 | steps:
48 | - uses: actions/checkout@v4
49 | with:
50 | submodules: true
51 | - name: Set up Docker Buildx
52 | uses: docker/setup-buildx-action@v3
53 | - name: Build Docker image
54 | uses: docker/build-push-action@v6
55 | with:
56 | context: .
57 | file: ./src/Helldivers-2-API/Dockerfile
58 | push: false
59 | tags: helldivers-api:test
60 | cache-from: type=gha
61 | cache-to: type=gha,mode=max
62 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V1/DispatchMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead;
2 | using Helldivers.Models.Domain.Localization;
3 | using Helldivers.Models.V1;
4 |
5 | namespace Helldivers.Core.Mapping.V1;
6 |
7 | ///
8 | /// Handles mapping for .
9 | ///
10 | public sealed class DispatchMapper
11 | {
12 | ///
13 | /// Maps ArrowHead's onto V1's es.
14 | ///
15 | public IEnumerable MapToV1(MappingContext context)
16 | {
17 | // Get a list of all items across all translations.
18 | var invariants = context.NewsFeeds
19 | .SelectMany(pair => pair.Value)
20 | .DistinctBy(assignment => assignment.Id);
21 |
22 | foreach (var item in invariants)
23 | {
24 | // Build a dictionary of all translations for this item
25 | var translations = context.NewsFeeds.Select(pair =>
26 | new KeyValuePair(
27 | pair.Key,
28 | pair.Value.FirstOrDefault(a => a.Id == item.Id)
29 | )
30 | ).Where(pair => pair.Value is not null)
31 | .ToDictionary(pair => pair.Key, pair => pair.Value!);
32 |
33 | yield return MapToV1(context, translations);
34 | }
35 | }
36 |
37 | private Dispatch MapToV1(MappingContext context, Dictionary translations)
38 | {
39 | var invariant = translations.Values.First();
40 | var messages = translations.Select(pair => new KeyValuePair(pair.Key, pair.Value.Message));
41 |
42 | return new Dispatch(
43 | Id: invariant.Id,
44 | Published: context.RelativeGameStart.AddSeconds(invariant.Published),
45 | Type: invariant.Type,
46 | Message: LocalizedMessage.FromStrings(messages)
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Statistics.cs:
--------------------------------------------------------------------------------
1 | namespace Helldivers.Models.V1;
2 |
3 | ///
4 | /// Contains statistics of missions, kills, success rate etc.
5 | ///
6 | /// The amount of missions won.
7 | /// The amount of missions lost.
8 | /// The total amount of time spent planetside (in seconds).
9 | /// The total amount of bugs killed since start of the season.
10 | /// The total amount of automatons killed since start of the season.
11 | /// The total amount of Illuminate killed since start of the season.
12 | /// The total amount of bullets fired
13 | /// The total amount of bullets hit
14 | /// The total amount of time played (including off-planet) in seconds.
15 | /// The amount of casualties on the side of humanity.
16 | /// The amount of revives(?).
17 | /// The amount of friendly fire casualties.
18 | /// A percentage indicating how many started missions end in success.
19 | /// A percentage indicating average accuracy of Helldivers.
20 | /// The total amount of players present (at the time of the snapshot).
21 | public sealed record Statistics(
22 | ulong MissionsWon,
23 | ulong MissionsLost,
24 | ulong MissionTime,
25 | ulong TerminidKills,
26 | ulong AutomatonKills,
27 | ulong IlluminateKills,
28 | ulong BulletsFired,
29 | ulong BulletsHit,
30 | ulong TimePlayed,
31 | ulong Deaths,
32 | ulong Revives,
33 | ulong Friendlies,
34 | ulong MissionSuccessRate,
35 | ulong Accuracy,
36 | ulong PlayerCount
37 | );
38 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/DevelopmentController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.API.Configuration;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.Extensions.Options;
4 | using Microsoft.IdentityModel.Tokens;
5 | using System.IdentityModel.Tokens.Jwt;
6 | using System.Security.Claims;
7 |
8 | namespace Helldivers.API.Controllers;
9 |
10 | ///
11 | /// Controller class that's only available in development, contains local debugging endpoints etc.
12 | ///
13 | public static class DevelopmentController
14 | {
15 | private static readonly JwtSecurityTokenHandler TokenHandler = new();
16 |
17 | ///
18 | /// Creates a JWT token for the given and .
19 | ///
20 | public static IResult CreateToken([FromQuery] string name, [FromQuery] int limit, [FromServices] IOptions options)
21 | {
22 | var key = new SymmetricSecurityKey(Convert.FromBase64String(options.Value.Authentication.SigningKey));
23 | var token = new JwtSecurityToken(
24 | issuer: options.Value.Authentication.ValidIssuers.First(),
25 | audience: options.Value.Authentication.ValidAudiences.First(),
26 | claims: [
27 | new Claim("sub", name),
28 | new Claim(ClaimTypes.Name, name),
29 | new Claim("nbf", $"{DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds:0}"),
30 | new Claim("iat", $"{DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds:0}"),
31 | new Claim("exp", $"{DateTime.UtcNow.AddDays(30).Subtract(DateTime.UnixEpoch).TotalSeconds:0}"),
32 | new Claim("RateLimit", $"{limit}")
33 | ],
34 | expires: DateTime.UtcNow.AddDays(30),
35 | signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
36 | );
37 |
38 | var jwt = TokenHandler.WriteToken(token);
39 | return Results.Ok(jwt);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Readme.md:
--------------------------------------------------------------------------------
1 | # Roslyn Source Generators Sample
2 |
3 | A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs.
4 |
5 | ## Content
6 | ### Helldivers_2_SourceGen
7 | A .NET Standard project with implementations of sample source generators.
8 | **You must build this project to see the result (generated code) in the IDE.**
9 |
10 | - [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry).
11 | - [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute.
12 |
13 | ### Helldivers_2_SourceGen.Sample
14 | A project that references source generators. Note the parameters of `ProjectReference` in [Helldivers_2_SourceGen.Sample.csproj](../Helldivers_2_SourceGen.Sample/Helldivers_2_SourceGen.Sample.csproj), they make sure that the project is referenced as a set of source generators.
15 |
16 | ### Helldivers_2_SourceGen.Tests
17 | Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests.
18 |
19 | ## How To?
20 | ### How to debug?
21 | - Use the [launchSettings.json](Properties/launchSettings.json) profile.
22 | - Debug tests.
23 |
24 | ### How can I determine which syntax nodes I should expect?
25 | Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/).
26 |
27 | ### How to learn more about wiring source generators?
28 | Watch the walkthrough video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI)
29 | The complete set of information is available in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md).
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/ArrowHead/Summary/PlanetStats.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead.Info;
2 |
3 | namespace Helldivers.Models.ArrowHead.Summary;
4 |
5 | ///
6 | /// Similar to , but scoped to a specific planet.
7 | ///
8 | /// The for which planet these stats are.
9 | /// The amount of missions won.
10 | /// The amount of missions lost.
11 | /// The total amount of time spent planetside (in seconds).
12 | /// The total amount of bugs killed since start of the season.
13 | /// The total amount of automatons killed since start of the season.
14 | /// The total amount of Illuminate killed since start of the season.
15 | /// The total amount of bullets fired
16 | /// The total amount of bullets hit
17 | /// The total amount of time played (including off-planet) in seconds.
18 | /// The amount of casualties on the side of humanity.
19 | /// The amount of revives(?).
20 | /// The amount of friendly fire casualties.
21 | /// A percentage indicating how many started missions end in success.
22 | /// A percentage indicating average accuracy of Helldivers.
23 | public sealed record PlanetStats(
24 | int PlanetIndex,
25 | ulong MissionsWon,
26 | ulong MissionsLost,
27 | ulong MissionTime,
28 | ulong BugKills,
29 | ulong AutomatonKills,
30 | ulong IlluminateKills,
31 | ulong BulletsFired,
32 | ulong BulletsHit,
33 | ulong TimePlayed,
34 | ulong Deaths,
35 | ulong Revives,
36 | ulong Friendlies,
37 | ulong MissionSuccessRate,
38 | ulong Accurracy
39 | );
40 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V2/DispatchMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Hdml;
2 | using Helldivers.Models.ArrowHead;
3 | using Helldivers.Models.Domain.Localization;
4 | using Helldivers.Models.V2;
5 |
6 | namespace Helldivers.Core.Mapping.V2;
7 |
8 | ///
9 | /// Handles mapping for .
10 | ///
11 | public sealed class DispatchMapper(HdmlParser parser)
12 | {
13 | ///
14 | /// Maps ArrowHead's onto V2's es.
15 | ///
16 | public IEnumerable MapToV2(MappingContext context)
17 | {
18 | // Get a list of all items across all translations.
19 | var invariants = context.NewsFeeds
20 | .SelectMany(pair => pair.Value)
21 | .DistinctBy(assignment => assignment.Id);
22 |
23 | foreach (var item in invariants)
24 | {
25 | // Build a dictionary of all translations for this item
26 | var translations = context.NewsFeeds.Select(pair =>
27 | new KeyValuePair(
28 | pair.Key,
29 | pair.Value.FirstOrDefault(a => a.Id == item.Id)
30 | )
31 | ).Where(pair => pair.Value is not null)
32 | .ToDictionary(pair => pair.Key, pair => pair.Value!);
33 |
34 | yield return MapToV2(context, translations);
35 | }
36 | }
37 |
38 | private Dispatch MapToV2(MappingContext context, Dictionary translations)
39 | {
40 | var invariant = translations.Values.First();
41 | var messages = translations.Select(pair =>
42 | new KeyValuePair(pair.Key, parser.Compile(pair.Value.Message)));
43 |
44 | return new Dispatch(
45 | Id: invariant.Id,
46 | Published: context.RelativeGameStart.AddSeconds(invariant.Published),
47 | Type: invariant.Type,
48 | Message: LocalizedMessage.FromStrings(messages)
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/Domain/Localization/LocalizedMessageConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace Helldivers.Models.Domain.Localization;
6 |
7 | internal sealed class LocalizedMessageConverter : JsonConverter
8 | {
9 | ///
10 | public override LocalizedMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
11 | {
12 | throw new InvalidOperationException($"Unable to deserialize {nameof(LocalizedMessage)} from JSON");
13 | }
14 |
15 | ///
16 | public override void Write(Utf8JsonWriter writer, LocalizedMessage value, JsonSerializerOptions options)
17 | {
18 | // If the invariant culture is selected, serialize a dictionary with all languages.
19 | if (Equals(CultureInfo.CurrentCulture, LocalizedMessage.InvariantCulture))
20 | {
21 | writer.WriteStartObject();
22 |
23 | // We keep both country language and global language (eg. 'en-US' and 'en'), so we filter out the globals.
24 | foreach (var (language, message) in value.Messages.Where(culture => culture.Key.Name.Contains('-')))
25 | writer.WriteString(language.Name, message);
26 |
27 | writer.WriteEndObject();
28 |
29 | // Don't write out the translated string
30 | return;
31 | }
32 |
33 | if (value.Messages.TryGetValue(CultureInfo.CurrentCulture, out var result) is false)
34 | {
35 | if (value.Messages.TryGetValue(CultureInfo.CurrentCulture.Parent, out result) is false)
36 | {
37 | // Fall back to the configured default culture if one is available
38 | value.Messages.TryGetValue(LocalizedMessage.FallbackCulture, out result);
39 | }
40 | }
41 |
42 | if (string.IsNullOrWhiteSpace(result) is false)
43 | writer.WriteStringValue(result);
44 | else
45 | writer.WriteNullValue();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: [ "master" ]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | build:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - uses: actions/checkout@v4
29 | - name: Initial static JSON schema submodule
30 | run: git submodule update --init ./src/Helldivers-2-Models/json
31 | - name: Setup .NET
32 | uses: actions/setup-dotnet@v4
33 | with:
34 | dotnet-version: 10.0.x
35 | - name: Generate OpenAPI JSON files
36 | run: dotnet build -c Debug
37 | - name: Copy README
38 | run: cp README.md docs/README.md
39 | - name: Upload docsify website
40 | uses: actions/upload-artifact@master
41 | with:
42 | name: 'docs'
43 | path: './docs/'
44 | deploy:
45 | needs: build
46 | environment:
47 | name: github-pages
48 | url: ${{ steps.deployment.outputs.page_url }}
49 | runs-on: ubuntu-latest
50 | steps:
51 | - name: Download docsify website
52 | uses: actions/download-artifact@master
53 | with:
54 | name: 'docs'
55 | path: '.'
56 | - name: Setup Pages
57 | uses: actions/configure-pages@v4
58 | - name: Upload artifact
59 | uses: actions/upload-pages-artifact@v3
60 | with:
61 | path: '.'
62 | - name: Deploy to GitHub Pages
63 | id: deployment
64 | uses: actions/deploy-pages@v4
65 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Storage/StoreBase.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts;
2 | using Helldivers.Core.Contracts.Collections;
3 |
4 | namespace Helldivers.Core.Storage;
5 |
6 | ///
7 | /// Base class for
8 | ///
9 | public abstract class StoreBase : IStore where T : class
10 | {
11 | private T _state = null!;
12 | private readonly TaskCompletionSource _syncState = new();
13 |
14 | ///
15 | /// Updates the state of the store with the new value.
16 | ///
17 | public virtual ValueTask SetStore(T value)
18 | {
19 | _state = value;
20 | _syncState.TrySetResult();
21 |
22 | return ValueTask.CompletedTask;
23 | }
24 |
25 | ///
26 | public virtual async Task Get(CancellationToken cancellationToken = default)
27 | {
28 | await _syncState.Task.WaitAsync(cancellationToken);
29 |
30 | return _state;
31 | }
32 | }
33 |
34 | ///
35 | /// Base class for
36 | ///
37 | public abstract class StoreBase : IStore where T : class
38 | {
39 | private List _state = null!;
40 | private readonly TaskCompletionSource _syncState = new();
41 |
42 | ///
43 | /// Updates the state of the store with the new value.
44 | ///
45 | public virtual ValueTask SetStore(List value)
46 | {
47 | _state = value;
48 | _syncState.TrySetResult();
49 |
50 | return ValueTask.CompletedTask;
51 | }
52 |
53 | ///
54 | public virtual async Task> AllAsync(CancellationToken cancellationToken = default)
55 | {
56 | await _syncState.Task.WaitAsync(cancellationToken);
57 |
58 | return _state;
59 | }
60 |
61 | ///
62 | public virtual async Task GetAsync(TKey key, CancellationToken cancellationToken = default)
63 | {
64 | await _syncState.Task.WaitAsync(cancellationToken);
65 |
66 | return _state.FirstOrDefault(value => GetAsyncPredicate(value, key));
67 | }
68 |
69 | ///
70 | /// Handles the filter for
71 | ///
72 | protected abstract bool GetAsyncPredicate(T item, TKey key);
73 | }
74 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/V1/Planet.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Domain.Localization;
2 | using Helldivers.Models.V1.Planets;
3 |
4 | namespace Helldivers.Models.V1;
5 |
6 | ///
7 | /// Contains all aggregated information AH has about a planet.
8 | ///
9 | /// The unique identifier ArrowHead assigned to this planet.
10 | /// The name of the planet, as shown in game.
11 | /// The name of the sector the planet is in, as shown in game.
12 | /// The biome this planet has.
13 | /// All s that are applicable to this planet.
14 | /// A hash assigned to the planet by ArrowHead, purpose unknown.
15 | /// The coordinates of this planet on the galactic war map.
16 | /// A list of of all the planets to which this planet is connected.
17 | /// The maximum health pool of this planet.
18 | /// The current planet this planet has.
19 | /// Whether or not this planet is disabled, as assigned by ArrowHead.
20 | /// The faction that originally owned the planet.
21 | /// The faction that currently controls the planet.
22 | /// How much the planet regenerates per second if left alone.
23 | /// Information on the active event ongoing on this planet, if one is active.
24 | /// A set of statistics scoped to this planet.
25 | /// A list of integers that this planet is currently attacking.
26 | /// All regions on this planet, including their status (if any).
27 | public record Planet(
28 | int Index,
29 | LocalizedMessage Name,
30 | string Sector,
31 | Biome Biome,
32 | List Hazards,
33 | long Hash,
34 | Position Position,
35 | List Waypoints,
36 | long MaxHealth,
37 | long Health,
38 | bool Disabled,
39 | string InitialOwner,
40 | string CurrentOwner,
41 | double RegenPerSecond,
42 | Event? Event,
43 | Statistics Statistics,
44 | List Attacking,
45 | List Regions
46 | );
47 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Helldivers-2-API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | Helldivers.API
6 | Linux
7 | true
8 | true
9 |
10 |
11 |
12 |
13 | $(SolutionDir)/docs/openapi
14 | true
15 | true
16 |
17 |
18 |
19 |
20 | .dockerignore
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | all
43 | runtime; build; native; contentfiles; analyzers; buildtransitive
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.github/workflows/sync.yml:
--------------------------------------------------------------------------------
1 | name: Validate sync
2 |
3 | permissions:
4 | contents: read
5 | pull-requests: write
6 |
7 | on:
8 | push:
9 | branches: ["master"]
10 | pull_request:
11 | branches: ["master"]
12 | schedule:
13 | - cron: '0 0 * * *'
14 | workflow_dispatch:
15 |
16 | jobs:
17 | validate-sync:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 |
23 | - name: Setup .NET
24 | uses: actions/setup-dotnet@v4
25 | with:
26 | dotnet-version: '10.0.x'
27 |
28 | - name: Download JSON submodule
29 | run: git submodule update --init ./src/Helldivers-2-Models/json
30 |
31 | - name: Run sync and capture logs
32 | id: run_sync
33 | shell: bash
34 | continue-on-error: true
35 | run: |
36 | set -o pipefail
37 | dotnet build
38 | dotnet run --project ./src/Helldivers-2-CI/Helldivers-2-CI.csproj 2>&1 | tee sync.log
39 |
40 | - name: Upload artifacts
41 | if: always()
42 | uses: actions/upload-artifact@v4
43 | with:
44 | name: sync-artifacts
45 | path: |
46 | v1/*.json
47 | v2/*.json
48 | sync.log
49 |
50 | - name: Capture error log
51 | id: sync_log
52 | if: ${{ steps.run_sync.outcome == 'failure' && github.event_name == 'pull_request' }}
53 | run: |
54 | # open a multi-line output called "log"
55 | echo "log<> $GITHUB_OUTPUT
56 | cat sync.log >> $GITHUB_OUTPUT
57 | echo "EOF" >> $GITHUB_OUTPUT
58 |
59 | - name: Comment on PR on failure
60 | if: ${{ steps.run_sync.outcome == 'failure' && github.event_name == 'pull_request' }}
61 | uses: peter-evans/create-or-update-comment@v4
62 | with:
63 | token: ${{ secrets.GITHUB_TOKEN }}
64 | issue-number: ${{ github.event.pull_request.number }}
65 | body: |
66 | ⚠️ **Sync validation failed** (run #${{ github.run_number }} exited with ${{ steps.run_sync.outcome }})
67 |
68 |
69 | Error log
70 |
71 | ```text
72 | ${{ steps.sync_log.outputs.log }}
73 | ```
74 |
75 |
76 | **Artifacts** (JSON + log) here:
77 | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
78 |
79 | - name: Fail job on error
80 | if: ${{ steps.run_sync.outcome == 'failure' }}
81 | run: exit 1
82 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V1/StatisticsMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.V1;
2 |
3 | namespace Helldivers.Core.Mapping.V1;
4 |
5 | ///
6 | /// Handles mapping for .
7 | ///
8 | public sealed class StatisticsMapper
9 | {
10 | ///
11 | /// Maps galaxy wide statistics onto V1's statistics.
12 | ///
13 | public Statistics MapToV1(Models.ArrowHead.Summary.GalaxyStats statistics, List planets)
14 | {
15 | var playerCount = planets.Aggregate(0ul, (total, planet) => total + planet.Statistics.PlayerCount);
16 |
17 | return new Statistics(
18 | MissionsWon: statistics.MissionsWon,
19 | MissionsLost: statistics.MissionsLost,
20 | MissionTime: statistics.MissionTime,
21 | TerminidKills: statistics.BugKills,
22 | AutomatonKills: statistics.AutomatonKills,
23 | IlluminateKills: statistics.IlluminateKills,
24 | BulletsFired: statistics.BulletsFired,
25 | BulletsHit: statistics.BulletsHit,
26 | TimePlayed: statistics.TimePlayed,
27 | Deaths: statistics.Deaths,
28 | Revives: statistics.Revives,
29 | Friendlies: statistics.Friendlies,
30 | MissionSuccessRate: statistics.MissionSuccessRate,
31 | Accuracy: statistics.Accurracy,
32 | PlayerCount: playerCount
33 | );
34 | }
35 |
36 | ///
37 | /// Maps statistics of a specific planet onto V1's statistics.
38 | ///
39 | public Statistics MapToV1(Models.ArrowHead.Summary.PlanetStats? statistics, Models.ArrowHead.Status.PlanetStatus status)
40 | {
41 | return new Statistics(
42 | MissionsWon: statistics?.MissionsWon ?? 0,
43 | MissionsLost: statistics?.MissionsLost ?? 0,
44 | MissionTime: statistics?.MissionTime ?? 0,
45 | TerminidKills: statistics?.BugKills ?? 0,
46 | AutomatonKills: statistics?.AutomatonKills ?? 0,
47 | IlluminateKills: statistics?.IlluminateKills ?? 0,
48 | BulletsFired: statistics?.BulletsFired ?? 0,
49 | BulletsHit: statistics?.BulletsHit ?? 0,
50 | TimePlayed: statistics?.TimePlayed ?? 0,
51 | Deaths: statistics?.Deaths ?? 0,
52 | Revives: statistics?.Revives ?? 0,
53 | Friendlies: statistics?.Friendlies ?? 0,
54 | MissionSuccessRate: statistics?.MissionSuccessRate ?? 0,
55 | Accuracy: statistics?.Accurracy ?? 0,
56 | PlayerCount: status.Players
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Facades/V1Facade.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts;
2 | using Helldivers.Core.Contracts.Collections;
3 | using Helldivers.Core.Mapping;
4 | using Helldivers.Core.Mapping.V1;
5 | using Helldivers.Models.V1;
6 |
7 | namespace Helldivers.Core.Facades;
8 |
9 | ///
10 | /// Handles dispatching incoming data to all stores for V1.
11 | ///
12 | public sealed class V1Facade(
13 | IStore warStore,
14 | WarMapper warMapper,
15 | IStore planetStore,
16 | PlanetMapper planetMapper,
17 | IStore campaignStore,
18 | CampaignMapper campaignMapper,
19 | IStore assignmentStore,
20 | AssignmentMapper assignmentMapper,
21 | IStore dispatchStore,
22 | DispatchMapper dispatchMapper
23 | )
24 | {
25 | ///
26 | public async ValueTask UpdateStores(MappingContext context)
27 | {
28 | // TODO: map warId
29 |
30 | await UpdatePlanetStore(context);
31 | // Some mappers need access to the list of planets, so we fetch it from the freshly-mapped store.
32 | var planets = await planetStore.AllAsync();
33 |
34 | await UpdateWarStore(context, planets);
35 | await UpdateCampaignStore(context, planets);
36 | await UpdateAssignmentsStore(context);
37 | await UpdateDispatchStore(context);
38 | }
39 |
40 | private async ValueTask UpdateWarStore(MappingContext context, List planets)
41 | {
42 | var war = warMapper.MapToV1(context, planets);
43 |
44 | await warStore.SetStore(war);
45 | }
46 |
47 | private async ValueTask UpdatePlanetStore(MappingContext context)
48 | {
49 | var planets = planetMapper.MapToV1(context).ToList();
50 |
51 | await planetStore.SetStore(planets);
52 | }
53 |
54 | private async ValueTask UpdateCampaignStore(MappingContext context, List planets)
55 | {
56 | var campaigns = campaignMapper.MapToV1(context, planets).ToList();
57 |
58 | await campaignStore.SetStore(campaigns);
59 | }
60 |
61 | private async ValueTask UpdateAssignmentsStore(MappingContext context)
62 | {
63 | var assignments = assignmentMapper
64 | .MapToV1(context)
65 | .ToList();
66 |
67 | await assignmentStore.SetStore(assignments);
68 | }
69 |
70 | private async ValueTask UpdateDispatchStore(MappingContext context)
71 | {
72 | var dispatches = dispatchMapper
73 | .MapToV1(context)
74 | .OrderByDescending(dispatch => dispatch.Id)
75 | .ToList();
76 |
77 | await dispatchStore.SetStore(dispatches);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Sync/Hosted/SteamSyncService.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core;
2 | using Helldivers.Sync.Configuration;
3 | using Helldivers.Sync.Services;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Extensions.Options;
8 | using Prometheus;
9 |
10 | namespace Helldivers.Sync.Hosted;
11 |
12 | ///
13 | /// Handles synchronization of Steam API information.
14 | ///
15 | public sealed partial class SteamSyncService(
16 | ILogger logger,
17 | StorageFacade storage,
18 | IServiceScopeFactory scopeFactory,
19 | IOptions configuration
20 | ) : BackgroundService
21 | {
22 | ///
23 | /// Timestamp the store was last updated successfully.
24 | ///
25 | public DateTime? LastUpdated { get; internal set; }
26 |
27 | private static readonly Histogram SteamSyncMetric =
28 | Metrics.CreateHistogram("helldivers_sync_steam", "All Steam synchronizations");
29 |
30 | #region Source generated logging
31 |
32 | [LoggerMessage(Level = LogLevel.Error, Message = "An exception was thrown when synchronizing from Steam API")]
33 | private static partial void LogSyncThrewAnError(ILogger logger, Exception exception);
34 |
35 | [LoggerMessage(Level = LogLevel.Information, Message = "SteamSyncService finished processing, shutting down.")]
36 | private static partial void LogRunOnceCompleted(ILogger logger);
37 |
38 | #endregion
39 |
40 | ///
41 | protected override async Task ExecuteAsync(CancellationToken cancellationToken)
42 | {
43 | var delay = TimeSpan.FromHours(1);
44 |
45 | while (cancellationToken.IsCancellationRequested is false)
46 | {
47 | try
48 | {
49 | using var _ = SteamSyncMetric.NewTimer();
50 | await using var scope = scopeFactory.CreateAsyncScope();
51 | var api = scope.ServiceProvider.GetRequiredService();
52 |
53 | var feed = await api.GetLatest();
54 |
55 | await storage.UpdateStores(feed);
56 | LastUpdated = DateTime.UtcNow;
57 | }
58 | catch (Exception exception)
59 | {
60 | LogSyncThrewAnError(logger, exception);
61 |
62 | if (configuration.Value.RunOnce)
63 | throw;
64 | }
65 |
66 |
67 | // If we should only run once, we exit the loop (and thus service) after this.
68 | if (configuration.Value.RunOnce)
69 | {
70 | LogRunOnceCompleted(logger);
71 | return;
72 | }
73 | await Task.Delay(delay, cancellationToken);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Models/Domain/Localization/LocalizedMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Helldivers.Models.Domain.Localization;
5 |
6 | ///
7 | /// Represents a string that is available in multiple languages.
8 | ///
9 | /// A list of all messages and the in which they are available.
10 | [JsonConverter(typeof(LocalizedMessageConverter))]
11 | public sealed record LocalizedMessage(IReadOnlyDictionary Messages)
12 | {
13 | ///
14 | /// The culture to retrieve when the is not available.
15 | ///
16 | public static CultureInfo FallbackCulture { get; set; } = null!;
17 |
18 | ///
19 | /// Represents the as a named culture that can be assigned by the request
20 | /// localization middleware.
21 | /// Selecting this culture will result in all languages being serialized at once.
22 | ///
23 | public static readonly CultureInfo InvariantCulture = new CultureInfo("ivl-IV");
24 |
25 | ///
26 | /// Factory method for creating a from a list of {language, value} pairs.
27 | ///
28 | public static LocalizedMessage FromStrings(IEnumerable> values)
29 | {
30 | var messages = values.SelectMany(pair =>
31 | {
32 | var (key, value) = pair;
33 | var culture = new CultureInfo(key);
34 | var parent = culture.Parent;
35 |
36 | return new[]
37 | {
38 | new KeyValuePair(culture, value),
39 | new KeyValuePair(parent, value)
40 | };
41 | })
42 | .DistinctBy(pair => pair.Key)
43 | .ToDictionary(pair => pair.Key, pair => pair.Value);
44 |
45 | return new LocalizedMessage(messages);
46 | }
47 |
48 | ///
49 | /// Factory method for creating a from a list of {language, value} pairs.
50 | ///
51 | public static LocalizedMessage FromStrings(IEnumerable values)
52 | {
53 | var messages = values.SelectMany(pair =>
54 | {
55 | var key = pair[0];
56 | var value = pair[1];
57 |
58 | var culture = new CultureInfo(key);
59 | var parent = culture.Parent;
60 |
61 | return new[]
62 | {
63 | new KeyValuePair(culture, value),
64 | new KeyValuePair(parent, value)
65 | };
66 | })
67 | .DistinctBy(pair => pair.Key)
68 | .ToDictionary(pair => pair.Key, pair => pair.Value);
69 |
70 | return new LocalizedMessage(messages);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/Helldivers-2-SourceGen.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | enable
7 | latest
8 |
9 | true
10 | true
11 |
12 | Helldivers.SourceGen
13 | Helldivers.SourceGen
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | @(NuGetPackageId -> Distinct())
42 |
43 |
44 |
45 |
47 |
48 |
49 |
50 | @(PackageReferenceDependency -> '%(LocalSourceGenerators)')
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Metrics/ClientMetric.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace Helldivers.API.Metrics;
4 |
5 | ///
6 | /// Handles the logic for generating the `Client` label in the Prometheus metrics for HTTP requests.
7 | ///
8 | public static partial class ClientMetric
9 | {
10 | ///
11 | /// The name of a client that couldn't be identified.
12 | ///
13 | private const string UNKNOWN_CLIENT_NAME = "Unknown";
14 |
15 | ///
16 | /// The name for clients that are developing locally (e.g. localhost, ...).
17 | ///
18 | private const string LOCAL_DEVELOPMENT_CLIENT_NAME = "Development";
19 |
20 | [GeneratedRegex(@"^[a-z0-9]{24}--.+\.netlify\.app$")]
21 | private static partial Regex IsNetlifyApp();
22 |
23 | ///
24 | /// Extracts the name of the client for the incoming request.
25 | ///
26 | public static string GetClientName(HttpContext context)
27 | {
28 | // If we have an authenticated user, use their name instead
29 | if (context.User.Identity is { Name: { } name })
30 | return name;
31 |
32 | // If the client sends `X-Super-Client` we use that name
33 | if (context.Request.Headers.TryGetValue(Constants.CLIENT_HEADER_NAME, out var superClient))
34 | if (string.IsNullOrWhiteSpace(superClient) is false)
35 | return superClient!;
36 |
37 | if (GetBrowserClientName(context.Request) is { } clientName)
38 | return clientName;
39 |
40 | return UNKNOWN_CLIENT_NAME;
41 | }
42 |
43 | ///
44 | /// If the client is a browser it sends specific headers along.
45 | /// Attempt to parse out those headers to get the domain and perform some additional normalization.
46 | ///
47 | /// If we can't find any of those headers simply return null
48 | ///
49 | private static string? GetBrowserClientName(HttpRequest request)
50 | {
51 | string? result = null;
52 | if (string.IsNullOrWhiteSpace(request.Headers.Referer) is false)
53 | result = request.Headers.Referer!;
54 | if (string.IsNullOrWhiteSpace(request.Headers.Origin) is false)
55 | result = request.Headers.Origin!;
56 |
57 | if (result is not null && Uri.TryCreate(result, UriKind.Absolute, out var uri))
58 | {
59 | // Localhost etc just return local development for a client name.
60 | if (uri.Host.Equals("localhost", StringComparison.InvariantCultureIgnoreCase))
61 | return LOCAL_DEVELOPMENT_CLIENT_NAME;
62 |
63 | // Netlify seems to generate urls with random prefixes like following:
64 | // <24 characters random>--.netlify.app
65 | // we normalize to .netlify.app
66 | if (IsNetlifyApp().IsMatch(uri.Host))
67 | return uri.Host[26..];
68 |
69 | return uri.Host;
70 | }
71 |
72 | return result;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V1/AssignmentMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.Domain.Localization;
2 | using Helldivers.Models.V1;
3 | using Helldivers.Models.V1.Assignments;
4 | using Task = Helldivers.Models.V1.Assignments.Task;
5 |
6 | namespace Helldivers.Core.Mapping.V1;
7 |
8 | ///
9 | /// Handles mapping for .
10 | ///
11 | public sealed class AssignmentMapper
12 | {
13 | ///
14 | /// Maps a set of multi-language s into a list of .
15 | ///
16 | public IEnumerable MapToV1(MappingContext context)
17 | {
18 | // Get a list of all assignments across all translations.
19 | var invariants = context.Assignments
20 | .SelectMany(pair => pair.Value)
21 | .DistinctBy(assignment => assignment.Id32);
22 |
23 | foreach (var assignment in invariants)
24 | {
25 | // Build a dictionary of all translations for this assignment
26 | var translations = context.Assignments.Select(pair =>
27 | new KeyValuePair(
28 | pair.Key,
29 | pair.Value.FirstOrDefault(a => a.Id32 == assignment.Id32)
30 | )
31 | ).Where(pair => pair.Value is not null)
32 | .ToDictionary(pair => pair.Key, pair => pair.Value!);
33 |
34 | yield return MapToV1(translations);
35 | }
36 | }
37 |
38 | ///
39 | /// Maps all translations of an into one assignment.
40 | ///
41 | private Assignment MapToV1(Dictionary translations)
42 | {
43 | var invariant = translations.First().Value;
44 | var titles = translations.Select(assignment => new KeyValuePair(assignment.Key, assignment.Value.Setting.OverrideTitle));
45 | var briefings = translations.Select(assignment => new KeyValuePair(assignment.Key, assignment.Value.Setting.OverrideBrief));
46 | var descriptions = translations.Select(assignment => new KeyValuePair(assignment.Key, assignment.Value.Setting.TaskDescription));
47 | var expiration = DateTime.UtcNow.AddSeconds(invariant.ExpiresIn);
48 |
49 | return new Assignment(
50 | Id: invariant.Id32,
51 | Progress: invariant.Progress,
52 | Title: LocalizedMessage.FromStrings(titles),
53 | Briefing: LocalizedMessage.FromStrings(briefings),
54 | Description: LocalizedMessage.FromStrings(descriptions),
55 | Tasks: invariant.Setting.Tasks.Select(MapToV1).ToList(),
56 | Reward: MapToV1(invariant.Setting.Reward),
57 | Rewards: invariant.Setting.Rewards.Select(MapToV1).Where(reward => reward is not null).ToList()!,
58 | Expiration: expiration,
59 | Flags: invariant.Setting.Flags
60 | );
61 | }
62 |
63 | private Task MapToV1(Models.ArrowHead.Assignments.Task task)
64 | {
65 | return new Task(
66 | Type: task.Type,
67 | Values: task.Values,
68 | ValueTypes: task.ValueTypes
69 | );
70 | }
71 |
72 | private Reward? MapToV1(Models.ArrowHead.Assignments.Reward? reward)
73 | {
74 | if (reward is null)
75 | return null;
76 |
77 | return new Reward(
78 | Type: reward.Type,
79 | Amount: reward.Amount
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Helldivers-2-SourceGen/StaticJsonSourceGenerator.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.SourceGen.Contracts;
2 | using Helldivers.SourceGen.Parsers;
3 | using Microsoft.CodeAnalysis;
4 |
5 | namespace Helldivers.SourceGen;
6 |
7 | ///
8 | /// A sample source generator that creates C# classes based on the text file (in this case, Domain Driven Design ubiquitous language registry).
9 | /// When using a simple text file as a baseline, we can create a non-incremental source generator.
10 | ///
11 | [Generator]
12 | public class StaticJsonSourceGenerator : IIncrementalGenerator
13 | {
14 | private static readonly IJsonParser PlanetParser = new PlanetsParser();
15 | private static readonly IJsonParser PlanetRegionParser = new PlanetRegionsParser();
16 | private static readonly IJsonParser BiomesParser = new BiomesParser();
17 | private static readonly IJsonParser EnvironmentalsParser = new EnvironmentalsParser();
18 | private static readonly IJsonParser FactionsParser = new FactionsParser();
19 |
20 | ///
21 | public void Initialize(IncrementalGeneratorInitializationContext context)
22 | {
23 | var generated = context
24 | .AdditionalTextsProvider
25 | .Where(static file => file.Path.EndsWith(".json"))
26 | .Select(static (file, cancellationToken) =>
27 | {
28 | var parser = GetParserForFile(file);
29 |
30 | var name = Path.GetFileNameWithoutExtension(file.Path);
31 |
32 | return (parser.Parse(file, cancellationToken), name);
33 | });
34 |
35 | context.RegisterSourceOutput(generated, static (context, pair) =>
36 | {
37 | var (source, name) = pair;
38 |
39 | try
40 | {
41 | context.AddSource(name, source);
42 | }
43 | catch (Exception exception)
44 | {
45 | context.AddSource($"{name}.g.cs", $"// An exception was thrown processing {name}.json\n{exception.ToString()}");
46 | context.ReportDiagnostic(
47 | Diagnostic.Create(
48 | new DiagnosticDescriptor(
49 | id: "HDJSON", // Unique ID for your error
50 | title: "JSON source generator failed", // Title of the error
51 | messageFormat: $"An error occured generating C# code from JSON files: {exception}", // Message format
52 | category: "HD2", // Category of the error
53 | DiagnosticSeverity.Error, // Severity of the error
54 | isEnabledByDefault: true // Whether the error is enabled by default
55 | ),
56 | Location.None // No specific location provided for simplicity
57 | )
58 | );
59 | }
60 | });
61 | }
62 |
63 | private static IJsonParser GetParserForFile(AdditionalText file)
64 | {
65 | var name = Path.GetFileNameWithoutExtension(file.Path);
66 | name = $"{char.ToUpper(name[0])}{name.Substring(1)}";
67 |
68 | return name.ToLowerInvariant() switch
69 | {
70 | "planets" => PlanetParser,
71 | "planetregion" => PlanetRegionParser,
72 | "biomes" => BiomesParser,
73 | "environmentals" => EnvironmentalsParser,
74 | "factions" => FactionsParser,
75 | _ => throw new Exception($"Generator does not know how to parse {name}")
76 | };
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/V2/SpaceStationMapper.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Hdml;
2 | using Helldivers.Models.V1;
3 | using Helldivers.Models.V2;
4 | using Helldivers.Models.V2.SpaceStations;
5 |
6 | namespace Helldivers.Core.Mapping.V2;
7 |
8 | ///
9 | /// Handles mapping for .
10 | ///
11 | public sealed class SpaceStationMapper(HdmlParser parser)
12 | {
13 | ///
14 | /// Maps all space stations in the .
15 | ///
16 | /// The mapping context containing the invariant war status and other relevant data.
17 | /// The list of planets to map with.
18 | /// An enumerable list of space stations mapped to the V1 model.
19 | public IEnumerable MapToV2(MappingContext context, List planets)
20 | {
21 | // Get a list of all assignments across all translations.
22 | var invariants = context.SpaceStations
23 | .SelectMany(pair => pair.Value)
24 | .DistinctBy(spaceStation => spaceStation.Id32);
25 |
26 | foreach (var spaceStation in invariants)
27 | {
28 | // Build a dictionary of all translations for this assignment
29 | var translations = context.SpaceStations.Select(pair =>
30 | new KeyValuePair(
31 | pair.Key,
32 | pair.Value.FirstOrDefault(a => a.Id32 == spaceStation.Id32)
33 | )
34 | ).Where(pair => pair.Value is not null)
35 | .ToDictionary(pair => pair.Key, pair => pair.Value!);
36 |
37 | yield return Map(translations, context, planets);
38 | }
39 | }
40 |
41 | private SpaceStation Map(Dictionary translations, MappingContext context, List planets)
42 | {
43 | var invariant = translations.First().Value;
44 | var planet = planets.First(p => p.Index == invariant.PlanetIndex);
45 |
46 | return new SpaceStation(
47 | Id32: invariant.Id32,
48 | Planet: planet,
49 | ElectionEnd: context.RelativeGameStart.AddSeconds(invariant.CurrentElectionEndWarTime),
50 | Flags: invariant.Flags,
51 | TacticalActions: invariant.TacticalActions.Select(rawAction => MapTacticalAction(context, rawAction)).ToList()
52 | );
53 | }
54 |
55 | private TacticalAction MapTacticalAction(MappingContext context, Helldivers.Models.ArrowHead.SpaceStations.TacticalAction raw)
56 | {
57 | return new TacticalAction(
58 | Id32: raw.Id32,
59 | MediaId32: raw.MediaId32,
60 | Name: raw.Name,
61 | Description: raw.Description,
62 | StrategicDescription: parser.Compile(raw.StrategicDescription),
63 | Status: raw.Status,
64 | StatusExpire: context.RelativeGameStart.AddSeconds(raw.StatusExpireAtWarTimeSeconds),
65 | Costs: raw.Cost.Select(MapCost).ToList(),
66 | EffectIds: raw.EffectIds
67 | );
68 | }
69 |
70 | private Cost MapCost(Models.ArrowHead.SpaceStations.Cost cost)
71 | {
72 | return new Cost(
73 | Id: cost.Id,
74 | ItemMixId: cost.ItemMixId,
75 | TargetValue: cost.TargetValue,
76 | CurrentValue: cost.CurrentValue,
77 | DeltaPerSecond: cost.DeltaPerSecond,
78 | MaxDonationAmmount: cost.MaxDonationAmmount,
79 | MaxDonationPeriodSeconds: cost.MaxDonationPeriodSeconds
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # helldivers-2/api
2 |
3 | The `helldivers-2/api` project is a community API around the popular video game
4 | [Helldivers 2](https://store.steampowered.com/app/553850/HELLDIVERS_2/?curator_clanid=33602140) by ArrowHead studios.
5 | It provides (most) information you can also find in-game in a neat JSON format so you can build awesome applications
6 | (see [Community](#community) below).
7 |
8 | Important to note is that this is **not** an officially endorsed API and is in **no** way affiliated with ArrowHead studios,
9 | but rather a community driven effort to provide the information of Helldivers 2 to it's community.
10 |
11 | ### Getting started
12 | The API does not require authentication (unless you'd like higher rate limits than the default, see [rate limits](#rate-limits)),
13 | so all you need to do is call it's publicly available endpoints.
14 |
15 | We provide an [OpenAPI](https://helldivers-2.github.io/api/openapi/Helldivers-2-API.json) specification of the community API as well as
16 | a [SwaggerUI](https://helldivers-2.github.io/api/openapi/swagger-ui.html) (which visualizes the OpenAPI document). We also provide an
17 | [OpenAPI](https://helldivers-2.github.io/api/openapi/Helldivers-2-API_arrowhead.json) of the official ArrowHead studio's API we use
18 | internally, however we strongly encourage you to use the `/raw` endpoints of the community wrapper instead of accessing
19 | the ArrowHead API directly, as it puts additional load on their servers (besides, we have shinier endpoints, I swear!).
20 |
21 | The root URL of the API is available here: https://api.helldivers2.dev
22 | > [!WARNING]
23 | > The root domain of the API recently changed, it's recommended you use the domain above to avoid problems in the future
24 |
25 | We ask that you send along a `X-Super-Client` header with the name of your application / domain
26 | (e.g. `X-Super-Client: api.helldivers2.dev`) and optionally a `X-Super-Contact` with some form of contact if your site
27 | does not link to any form of contact information we can find. We use this information in case we need to notify our users
28 | of important changes to the API that may cause disruption of service or when additional restrictions would be imposed
29 | on your app (to prevent abuse, unintentional or otherwise).
30 |
31 | > [!IMPORTANT]
32 | > While adding `X-Super-Client` and `X-Super-Contact` is currently not required, the `X-Super-Client` header **will**
33 | > be made obligatory in the future, causing clients who don't send it to fail. For more information see #94
34 |
35 | ### Rate limits
36 | Currently the rate limit is set at 5 requests/10 seconds.
37 | This limit will probably be increased in the future, and is used during the transition to our new version.
38 |
39 | To avoid hitting rate limits in your clients check the following headers in your response:
40 | - `X-Ratelimit-Limit` contains the total amount of requests you can make in the given timeframe
41 | - `X-RateLimit-Remaining` how many requests you can still make in the current window
42 | - `Retry-After` only added to 429 requests, the amount of seconds to wait before making a new request
43 |
44 | ### Contributing
45 | make sure to check out our [contribution](CONTRIBUTING.md) guidelines for more detailed information on how to
46 | help us contributing!
47 |
48 | ### Community
49 | Check out some awesome projects made by our community!
50 | - [sebsebmc/helldivers-history](https://github.com/sebsebmc/helldivers-history)
51 | Dashboard and graphs made by git scraping the community made API
52 | - [stonemercy/galactic-wide-web](https://github.com/Stonemercy/Galactic-Wide-Web)
53 | The Galactic Wide Web - a discord bot
54 | - [helldivers-2/api-wrapper](https://github.com/helldivers-2/api-wrapper)
55 | Typescript client code generated from OpenAPI
56 |
--------------------------------------------------------------------------------
/src/Helldivers-2-API/Controllers/ArrowHeadController.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Storage.ArrowHead;
2 | using Helldivers.Models.ArrowHead;
3 | using Microsoft.AspNetCore.Mvc;
4 |
5 | namespace Helldivers.API.Controllers;
6 |
7 | ///
8 | /// Contains API endpoints to mimic the endpoints exposed by the official ArrowHead API.
9 | ///
10 | public static class ArrowHeadController
11 | {
12 | ///
13 | /// Returns the currently active war season ID.
14 | ///
15 | [ProducesResponseType(StatusCodes.Status200OK)]
16 | public static async Task WarId(HttpContext context, ArrowHeadStore store)
17 | {
18 | var warId = await store.GetWarId(context.RequestAborted);
19 |
20 | return Results.Bytes(warId, contentType: "application/json");
21 | }
22 |
23 | ///
24 | /// Get a snapshot of the current war status.
25 | ///
26 | [ProducesResponseType(StatusCodes.Status200OK)]
27 | public static async Task Status(HttpContext context, ArrowHeadStore store)
28 | {
29 | var status = await store.GetWarStatus(context.RequestAborted);
30 |
31 | return Results.Bytes(status, contentType: "application/json");
32 | }
33 |
34 | ///
35 | /// Gets the current war info.
36 | ///
37 | [ProducesResponseType(StatusCodes.Status200OK)]
38 | public static async Task WarInfo(HttpContext context, ArrowHeadStore store)
39 | {
40 | var info = await store.GetWarInfo(context.RequestAborted);
41 |
42 | return Results.Bytes(info, contentType: "application/json");
43 | }
44 |
45 | ///
46 | /// Gets the current war info.
47 | ///
48 | [ProducesResponseType(StatusCodes.Status200OK)]
49 | public static async Task Summary(HttpContext context, ArrowHeadStore store)
50 | {
51 | var summary = await store.GetWarSummary(context.RequestAborted);
52 |
53 | return Results.Bytes(summary, contentType: "application/json");
54 | }
55 |
56 | ///
57 | /// Retrieves a list of news messages from Super Earth.
58 | ///
59 | [ProducesResponseType>(StatusCodes.Status200OK)]
60 | public static async Task NewsFeed(HttpContext context, ArrowHeadStore store)
61 | {
62 | var items = await store.GetNewsFeeds(context.RequestAborted);
63 |
64 | return Results.Bytes(items, contentType: "application/json");
65 | }
66 |
67 | ///
68 | /// Retrieves a list of currently active assignments (like Major Orders).
69 | ///
70 | [ProducesResponseType>(StatusCodes.Status200OK)]
71 | [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
72 | public static async Task Assignments(HttpContext context, ArrowHeadStore store)
73 | {
74 | var assignments = await store.GetAssignments(context.RequestAborted);
75 |
76 | return Results.Bytes(assignments, contentType: "application/json");
77 | }
78 |
79 | ///
80 | /// Fetches THE specific (749875195).
81 | ///
82 | [ProducesResponseType>(StatusCodes.Status200OK)]
83 | [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
84 | public static async Task SpaceStation(HttpContext context, ArrowHeadStore store, [FromRoute] long index)
85 | {
86 | var spaceStation = await store.GetSpaceStation(index, context.RequestAborted);
87 | if (spaceStation is { } bytes)
88 | return Results.Bytes(bytes, contentType: "application/json");
89 |
90 | return Results.NotFound();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/StorageFacade.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Facades;
2 | using Helldivers.Core.Mapping;
3 | using Helldivers.Core.Storage.ArrowHead;
4 | using Helldivers.Models;
5 | using Helldivers.Models.Steam;
6 | using System.Text.Json;
7 | using System.Text.Json.Serialization.Metadata;
8 |
9 | namespace Helldivers.Core;
10 |
11 | ///
12 | /// Rather than having the sync service be aware of all mappings and storage versions,
13 | /// this facade class handles dispatching incoming data to the correct underlying stores.
14 | ///
15 | public sealed class StorageFacade(ArrowHeadStore arrowHead, SteamFacade steam, V1Facade v1, V2Facade v2)
16 | {
17 | ///
18 | /// Updates all stores that rely on .
19 | ///
20 | public ValueTask UpdateStores(SteamNewsFeed feed)
21 | => steam.UpdateStores(feed);
22 |
23 | ///
24 | /// Updates all stores that rely on ArrowHead's models.
25 | ///
26 | public async ValueTask UpdateStores(Memory rawWarId,
27 | Memory rawWarInfo,
28 | Dictionary> rawWarStatuses,
29 | Memory rawWarSummary,
30 | Dictionary> rawNewsFeeds,
31 | Dictionary> rawAssignments,
32 | Dictionary>> rawStations)
33 | {
34 | arrowHead.UpdateRawStore(
35 | rawWarId,
36 | rawWarInfo,
37 | rawWarSummary,
38 | rawWarStatuses,
39 | rawNewsFeeds,
40 | rawAssignments,
41 | rawStations
42 | );
43 |
44 | var warId = DeserializeOrThrow(rawWarId, ArrowHeadSerializerContext.Default.WarId);
45 | var warInfo = DeserializeOrThrow(rawWarInfo, ArrowHeadSerializerContext.Default.WarInfo);
46 | var warStatuses = rawWarStatuses.ToDictionary(
47 | pair => pair.Key,
48 | pair => DeserializeOrThrow(pair.Value, ArrowHeadSerializerContext.Default.WarStatus)
49 | );
50 | var warSummary = DeserializeOrThrow(rawWarSummary, ArrowHeadSerializerContext.Default.WarSummary);
51 | var newsFeeds = rawNewsFeeds.ToDictionary(
52 | pair => pair.Key,
53 | pair => DeserializeOrThrow(pair.Value, ArrowHeadSerializerContext.Default.ListNewsFeedItem)
54 | );
55 | var assignments = rawAssignments.ToDictionary(
56 | pair => pair.Key,
57 | pair => DeserializeOrThrow(pair.Value, ArrowHeadSerializerContext.Default.ListAssignment)
58 | );
59 |
60 | var spaceStations = new Dictionary>();
61 | foreach (var (id, translations) in rawStations)
62 | {
63 | foreach (var (lang, translation) in translations)
64 | {
65 | if (spaceStations.ContainsKey(lang) is false)
66 | spaceStations.Add(lang, new List());
67 |
68 | var stations = spaceStations[lang];
69 | stations.Add(DeserializeOrThrow(translation, ArrowHeadSerializerContext.Default.SpaceStation));
70 | }
71 | }
72 |
73 | var context = new MappingContext(
74 | warId,
75 | warInfo,
76 | warStatuses,
77 | warSummary,
78 | newsFeeds,
79 | assignments,
80 | spaceStations
81 | );
82 |
83 | await v1.UpdateStores(context);
84 | await v2.UpdateStores(context);
85 | }
86 |
87 | private T DeserializeOrThrow(Memory memory, JsonTypeInfo typeInfo)
88 | => JsonSerializer.Deserialize(memory.Span, typeInfo) ?? throw new InvalidOperationException();
89 | }
90 |
--------------------------------------------------------------------------------
/src/Helldivers-2-CI/Program.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts.Collections;
2 | using Helldivers.Core.Extensions;
3 | using Helldivers.Models.Domain.Localization;
4 | using Helldivers.Models.V1;
5 | using Helldivers.Models.V2;
6 | using Helldivers.Sync.Configuration;
7 | using Helldivers.Sync.Extensions;
8 | using Helldivers.Sync.Hosted;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 | using System.Diagnostics;
13 | using System.Globalization;
14 | using System.Text.Json;
15 | using Dispatch = Helldivers.Models.V1.Dispatch;
16 |
17 | var maxRuntime = new CancellationTokenSource(TimeSpan.FromSeconds(30));
18 | var builder = new HostApplicationBuilder(args);
19 |
20 | LocalizedMessage.FallbackCulture = new CultureInfo("en-US");
21 | builder.Services.AddHelldivers();
22 | builder.Services.AddHelldiversSync();
23 | builder.Services.Configure(configuration =>
24 | {
25 | configuration.RunOnce = true;
26 | // Add all languages we want to CI test here.
27 | configuration.Languages =
28 | [
29 | "en-US",
30 | "de-DE",
31 | "es-ES",
32 | "ru-RU",
33 | "fr-FR",
34 | "it-IT",
35 | "pl-PL",
36 | "zh-Hans",
37 | "zh-Hant"
38 | ];
39 | });
40 |
41 | var stopwatch = new Stopwatch();
42 | var app = builder.Build();
43 |
44 | // Run our host, but make it shutdown after *max* 30 seconds.
45 | stopwatch.Start();
46 | var arrowhead = app.Services.GetRequiredService();
47 | var steam = app.Services.GetRequiredService();
48 | await arrowhead.StartAsync(maxRuntime.Token);
49 | await steam.StartAsync(maxRuntime.Token);
50 |
51 | // now we await completion of both.
52 | await Task.WhenAll([
53 | arrowhead.ExecuteTask!,
54 | steam.ExecuteTask!,
55 | ]).WaitAsync(maxRuntime.Token);
56 | stopwatch.Stop();
57 |
58 | app.Services.GetRequiredService>().LogInformation("Sync succeeded in {}", stopwatch.Elapsed);
59 |
60 | // Fetch all information from the stores to write to disk.
61 | var assignments = await app.Services.GetRequiredService>().AllAsync(maxRuntime.Token);
62 | var campaigns = await app.Services.GetRequiredService>().AllAsync(maxRuntime.Token);
63 | var dispatchesv1 = await app.Services.GetRequiredService>().AllAsync(maxRuntime.Token);
64 | var planets = await app.Services.GetRequiredService>().AllAsync(maxRuntime.Token);
65 | var war = await app.Services.GetRequiredService>().Get(maxRuntime.Token);
66 | var dispatchesv2 = await app.Services.GetRequiredService>().AllAsync(maxRuntime.Token);
67 | var store = await app.Services.GetRequiredService>().AllAsync(maxRuntime.Token);
68 |
69 | Directory.CreateDirectory("v1");
70 | Directory.CreateDirectory("v2");
71 |
72 | var options = new JsonSerializerOptions { WriteIndented = true };
73 | await File.WriteAllBytesAsync("v1/assignments.json", JsonSerializer.SerializeToUtf8Bytes(assignments, options), maxRuntime.Token);
74 | await File.WriteAllBytesAsync("v1/campaigns.json", JsonSerializer.SerializeToUtf8Bytes(campaigns, options), maxRuntime.Token);
75 | await File.WriteAllBytesAsync("v1/dispatches.json", JsonSerializer.SerializeToUtf8Bytes(dispatchesv1, options), maxRuntime.Token);
76 | await File.WriteAllBytesAsync("v1/planets.json", JsonSerializer.SerializeToUtf8Bytes(planets, options), maxRuntime.Token);
77 | await File.WriteAllBytesAsync("v1/war.json", JsonSerializer.SerializeToUtf8Bytes(war, options), maxRuntime.Token);
78 | await File.WriteAllBytesAsync("v2/dispatches.json", JsonSerializer.SerializeToUtf8Bytes(dispatchesv2, options), maxRuntime.Token);
79 | await File.WriteAllBytesAsync("v2/space-stations.json", JsonSerializer.SerializeToUtf8Bytes(store, options), maxRuntime.Token);
80 |
81 | return 0;
82 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Mapping/MappingContext.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Models.ArrowHead;
2 |
3 | namespace Helldivers.Core.Mapping;
4 |
5 | ///
6 | /// Contains the context of the ArrowHead models currently being mapped.
7 | /// It's main purpose is to provide a convenient way to pass around all ArrowHead data
8 | /// at once without having to pass 7-8 parameters to each function (or when adding more data,
9 | /// having to update all dependent signatures).
10 | ///
11 | public sealed class MappingContext
12 | {
13 | /// The currently being mapped.
14 | public WarId WarId { get; private init; }
15 |
16 | /// The currently being mapped.
17 | public WarInfo WarInfo { get; private init; }
18 |
19 | /// The es currently being mapped.
20 | public Dictionary WarStatuses { get; private init; }
21 |
22 | /// A that can be used when accessing non-localized data.
23 | public WarStatus InvariantWarStatus { get; private init; }
24 |
25 | /// The currently being mapped.
26 | public WarSummary WarSummary { get; private init; }
27 |
28 | /// The s currently being mapped.
29 | public Dictionary> NewsFeeds { get; private init; }
30 |
31 | /// The s currently being mapped.
32 | public Dictionary> Assignments { get; private init; }
33 |
34 | /// The s currently being mapped.
35 | public Dictionary> SpaceStations { get; private init; }
36 |
37 | ///
38 | /// A that represents the 'start' of the time in Helldivers 2.
39 | /// This accounts for the and .
40 | ///
41 | public DateTime RelativeGameStart { get; private init; }
42 |
43 | ///
44 | /// There's a deviation between gametime and real world time.
45 | /// When calculating dates using ArrowHead timestamps, add this to compensate for the deviation.
46 | ///
47 | public TimeSpan GameTimeDeviation { get; private init; }
48 |
49 | /// Initializes a new .
50 | internal MappingContext(WarId warId,
51 | WarInfo warInfo,
52 | Dictionary warStatuses,
53 | WarSummary warSummary,
54 | Dictionary> newsFeeds,
55 | Dictionary> assignments,
56 | Dictionary> spaceStations)
57 | {
58 | WarId = warId;
59 | WarInfo = warInfo;
60 | WarStatuses = warStatuses;
61 | WarSummary = warSummary;
62 | NewsFeeds = newsFeeds;
63 | Assignments = assignments;
64 | SpaceStations = spaceStations;
65 |
66 | InvariantWarStatus = warStatuses.FirstOrDefault().Value
67 | ?? throw new InvalidOperationException("No warstatus available");
68 |
69 |
70 | var gameTime = DateTime.UnixEpoch.AddSeconds(warInfo.StartDate + InvariantWarStatus.Time);
71 | GameTimeDeviation = TruncateToSeconds(DateTime.UtcNow).Subtract(gameTime);
72 | RelativeGameStart = DateTime.UnixEpoch.Add(GameTimeDeviation).AddSeconds(warInfo.StartDate);
73 | }
74 |
75 | ///
76 | /// ArrowHead doesn't send timestamps more accurate than seconds, so we truncate our relative time to seconds.
77 | /// This prevents timestamps for the same value from being different (due to milli/micro second differences).
78 | ///
79 | private static DateTime TruncateToSeconds(DateTime dateTime) => dateTime.AddTicks(-(dateTime.Ticks % TimeSpan.TicksPerSecond));
80 | }
81 |
--------------------------------------------------------------------------------
/src/Helldivers-2-Core/Extensions/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Helldivers.Core.Contracts;
2 | using Helldivers.Core.Contracts.Collections;
3 | using Helldivers.Core.Facades;
4 | using Helldivers.Core.Hdml;
5 | using Helldivers.Core.Mapping.Steam;
6 | using Helldivers.Core.Mapping.V1;
7 | using Helldivers.Core.Storage.ArrowHead;
8 | using Helldivers.Core.Storage.Steam;
9 | using Helldivers.Core.Storage.V1;
10 | using Helldivers.Models.V1;
11 | using Microsoft.Extensions.DependencyInjection;
12 |
13 | namespace Helldivers.Core.Extensions;
14 |
15 | ///
16 | /// Contains extension methods for