A wrapper for these web APIs:
- Nadeo API (official TM2020 ingame API)
- Trackmania web API
- ManiaPlanet web API
- Trackmania.io
- Trackmania Web Services (old TMF web API)
- Trackmania Exchange
- XML protocol (for TMF, TMT, and ManiaPlanet)
This set of libraries was made to be very easy and straightforward to use, but also easily mocked, so that it can be integrated into the real world in no time.
Anything you can imagine!
- ManiaAPI.NadeoAPI
- ManiaAPI.NadeoAPI.Extensions.Hosting
- ManiaAPI.NadeoAPI.Extensions.Gbx
- ManiaAPI.TrackmaniaAPI
- ManiaAPI.TrackmaniaAPI.Extensions.Hosting
- ManiaAPI.ManiaPlanetAPI
- ManiaAPI.ManiaPlanetAPI.Extensions.Hosting
- ManiaAPI.TrackmaniaIO
- ManiaAPI.TrackmaniaWS
- ManiaAPI.TrackmaniaWS.Extensions.Hosting
- ManiaAPI.TMX
- ManiaAPI.TMX.Extensions.Hosting
- ManiaAPI.TMX.Extensions.Gbx
- ManiaAPI.Xml
- ManiaAPI.Xml.Extensions.Hosting
- WebAppXmlExample - Blazor Server web application that demonstrates the use of the ManiaAPI.Xml API, currently only TMTurbo.
- WebAppTmxExample - Blazor Server web application that demonstrates the use of the ManiaAPI.TMX API.
- WebAppAuthorizationExample - Simple ASP.NET Core web application to show how to conveniently use the OAuth2 from
ManiaAPI.TrackmaniaAPI.Extensions.Hosting
andManiaAPI.ManiaPlanetAPI.Extensions.Hosting
.
See the Samples folder for more.
Wraps the official Nadeo API used in the latest Trackmania (2020). This API requires authorization.
After initial authentication, the connectivity is managed by the library, so you don't have to worry about refreshing the token.
The game provides 3 domains, and they are split into 3 separate services:
NadeoServices
for the core functionalityNadeoLiveServices
for leaderboards, clubs, and other live contentNadeoMeetServices
for getting the current Cup of the Day
For NadeoServices
:
- Get map records
- Get account records
- Get player zones
- Get API routes
- Get all available zones
- Get player club tags
- Get map info
For NadeoLiveServices
:
- Edit club campaigns
- Edit club activities
- Get map info
- Get map leaderboards
- Get map medal records
- Get seasonal campaigns
- Get weekly shorts
- Get TOTDs
- Get club campaigns
- Get club info
- Get club members
- Get club activities
- Get club rooms
- Get player season rankings
- Get active advertisements
- Join daily channel (COTD)
For NadeoMeetServices
:
- Get the current Cup of the Day
using ManiaAPI.NadeoAPI;
var ns = new NadeoServices();
await ns.AuthorizeAsync("mylogin", "mypassword", AuthorizationMethod.UbisoftAccount);
// Ready to use
var zones = await ns.GetZonesAsync();
You can also use a dedicated server. Just be aware it has some limitations.
await ns.AuthorizeAsync("my_dedicated_server", "ls>97jO>e3>>D/Ce", AuthorizationMethod.DedicatedServer);
For other services, just replace NadeoServices
with NadeoLiveServices
or NadeoMeetServices
.
using ManiaAPI.NadeoAPI;
var login = "mylogin";
var password = "mypassword";
var ns = new NadeoServices();
await ns.AuthorizeAsync(login, password, AuthorizationMethod.UbisoftAccount);
var nls = new NadeoLiveServices();
await nls.AuthorizeAsync(login, password, AuthorizationMethod.UbisoftAccount);
// Ready to use combined
// With NadeoLiveServices
var weeklyCampaigns = await nls.GetSeasonalCampaignsAsync(1);
var campaignMap = weeklyCampaigns.CampaignList.First().Playlist.First();
var mapInfo = await nls.GetMapInfoAsync(campaignMap.MapUid);
var mapLeaderboard = await nls.GetTopLeaderboardAsync(campaignMap.MapUid);
// With NadeoServices
var records = await ns.GetMapRecordsAsync(mapLeaderboard.Top.Top.Select(x => x.AccountId), mapInfo.MapId);
For DI, consider using the ManiaAPI.NadeoAPI.Extensions.Hosting
package. It handles the authorization for you without additional startup code.
Provides an efficient way to inject all Nadeo services into your application.
Providing options.Credentials
is optional, but setting it will automatically authorize on the first request and maintain that connection, so you don't have to call AuthorizeAsync
.
using ManiaAPI.TrackmaniaAPI.Extensions.Hosting;
builder.Services.AddNadeoAPI(options =>
{
options.Credentials = new NadeoAPICredentials(
builder.Configuration["NadeoAPI:Login"]!,
builder.Configuration["NadeoAPI:Password"]!,
AuthorizationMethod.DedicatedServer);
});
Features this setup brings:
NadeoServices
,NadeoLiveServices
, andNadeoMeetServices
will be available as transients- HTTP client will be handled properly
- Credentials will be handled as a singleton
Connects ManiaAPI.NadeoAPI
with GBX.NET features to provide convenient map upload and map update.
- Upload a map
- Update a map
A bit more advanced example to show how you can update a map without having to manually specify the map ID:
using ManiaAPI.NadeoAPI;
using ManiaAPI.NadeoAPI.Extensions.Gbx;
using GBX.NET;
using GBX.NET.Engines.Game;
var ns = new NadeoServices();
await ns.AuthorizeAsync("mylogin", "mypassword", AuthorizationMethod.UbisoftAccount);
// Parse the map Gbx header
var mapFileName = "Path/To/Map.Map.Gbx";
var map = Gbx.ParseHeaderNode<CGameCtnChallenge>(mapFileName);
// Get the map info (we need map ID, not map UID)
var mapInfo = await ns.GetMapInfoAsync(map.MapUid);
// Update the map (no leaderboard lost!)
await ns.UpdateMapAsync(mapInfo.MapId, mapFileName);
You can also pass the CGameCtnChallenge
instance directly, but it is not recommended as the object is re-serialized and some data might change or corrupt (rarely, but still possible).
This example parses the map twice, which is not optimal. Currently, a method overload that can call
GetMapInfoAsync
behind the scenes is missing.
Wraps https://api.trackmania.com/doc (Trackmania web API). This API requires authorization.
- Get display names
- Get account IDs from display names
- Get user's map records
More will be added in the future.
For the list of scopes, see the API docs. Generate your credentials here.
using ManiaAPI.TrackmaniaAPI;
var tm = new TrackmaniaAPI();
await tm.AuthorizeAsync("clientId", "clientSecret", ["clubs", "read_favorite"]);
// Ready to use
For DI, consider using the ManiaAPI.TrackmaniaAPI.Extensions.Hosting
package.
Provides Trackmania OAuth2 authorization for ASP.NET Core applications and an efficient way to inject TrackmaniaAPI
into your application.
TrackmaniaAPI
will be available as a transient, with a singleton handling of credentials. This will make sure the HttpClient
beneath is handled properly.
Providing options.Credentials
is optional, but setting it will automatically authorize on the first request and maintain that connection, so you don't have to call AuthorizeAsync
.
using ManiaAPI.TrackmaniaAPI.Extensions.Hosting;
builder.Services.AddTrackmaniaAPI(options =>
{
options.Credentials = new ManiaPlanetAPICredentials(
builder.Configuration["Trackmania:ClientId"]!,
builder.Configuration["Trackmania:ClientSecret"]!);
});
For the list of scopes, see the API docs. Generate your credentials here. The redirect URL is the /signin-trackmania
relative to the web root, for example: https://localhost:7864/signin-trackmania
.
using ManiaAPI.TrackmaniaAPI.Extensions.Hosting;
using ManiaAPI.TrackmaniaAPI.Extensions.Hosting.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddTrackmania(options =>
{
options.ClientId = builder.Configuration["OAuth2:Trackmania:ClientId"]!;
options.ClientSecret = builder.Configuration["OAuth2:Trackmania:ClientSecret"]!;
options.Scope.Add("clubs");
});
var app = builder.Build();
app.MapGet("/login", () =>
{
return TypedResults.Challenge(new() { RedirectUri = "/" }, [TrackmaniaAuthenticationDefaults.AuthenticationScheme]);
});
app.Run();
You can inject TrackmaniaAPI
if you create a special HTTP client handler to provide the token from HttpContext.GetTokenAsync("access_token")
and use that to get more information from the authorized user. Don't forget to set SaveTokens = true
in options - see the sample.
Wraps https://maniaplanet.com/swagger (ManiaPlanet web API). This API does not require authorization, but you can authorize to have more methods available.
For the list of scopes, see here at the bottom. Generate your credentials here.
using ManiaAPI.ManiaPlanetAPI;
var mp = new ManiaPlanetAPI();
// You can optionally authorize to do more things, and possibly be less limited
await mp.AuthorizeAsync("clientId", "clientSecret", ["basic", "dedicated", "maps"]);
// Ready to use
For DI, consider using the ManiaAPI.ManiaPlanetAPI.Extensions.Hosting
package.
Provides ManiaPlanet OAuth2 authorization for ASP.NET Core applications and an efficient way to inject ManiaPlanetAPI
into your application.
ManiaPlanetAPI
will be available as a transient, with a singleton handling of credentials. This will make sure the HttpClient
beneath is handled properly.
Providing options.Credentials
is optional, but setting it will automatically authorize on the first request and maintain that connection, so you don't have to call AuthorizeAsync
.
using ManiaAPI.ManiaPlanetAPI.Extensions.Hosting;
builder.Services.AddManiaPlanetAPI(options =>
{
options.Credentials = new ManiaPlanetAPICredentials(
builder.Configuration["ManiaPlanet:ClientId"]!,
builder.Configuration["ManiaPlanet:ClientSecret"]!);
});
For the list of scopes, see here at the bottom. Generate your credentials here. The redirect URL is the /signin-maniaplanet
relative to the web root, for example: https://localhost:7864/signin-maniaplanet
.
using ManiaAPI.ManiaPlanetAPI.Extensions.Hosting;
using ManiaAPI.ManiaPlanetAPI.Extensions.Hosting.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddManiaPlanet(options =>
{
options.ClientId = builder.Configuration["OAuth2:ManiaPlanet:ClientId"]!;
options.ClientSecret = builder.Configuration["OAuth2:ManiaPlanet:ClientSecret"]!;
Array.ForEach(["basic", "dedicated", "titles"], options.Scope.Add);
});
var app = builder.Build();
app.MapGet("/login", () =>
{
return TypedResults.Challenge(new() { RedirectUri = "/" }, [ManiaPlanetAuthenticationDefaults.AuthenticationScheme]);
});
app.Run();
You can inject ManiaPlanetAPI
if you create a special HTTP client handler to provide the token from HttpContext.GetTokenAsync("access_token")
and use that to get more information from the authorized user. Don't forget to set SaveTokens = true
in options - see the sample.
Wraps the https://trackmania.io/ API which provides services around the Nadeo API.
This API is more moderated and cached, but doesn't require you to authorize with it.
- Get various campaigns (including weekly shorts)
- Get leaderboards
- Get recent world records
- Get map info
- Get clubs, including their members and activities
- Get club rooms
- Get track of the days
- Get advertisements
- Get competitions
using ManiaAPI.TrackmaniaIO;
var tmio = new TrackmaniaIO("Example from ManiaAPI.NET"); // User agent to comply with https://openplanet.dev/tmio/api
or with DI, using an injected HttpClient
:
using ManiaAPI.TrackmaniaIO;
builder.Services.AddHttpClient<TrackmaniaIO>();
Wraps https://ws.trackmania.com/ (old TMF web API). This API requires authorization (via constructor).
- Get player info (registration ID from login for example)
More will be added in the future.
using ManiaAPI.TrackmaniaWS;
var ws = new TrackmaniaWS("tmf_yourapp", "password");
// Ready to use
For DI, consider using the ManiaAPI.TrackmaniaWS.Extensions.Hosting
package.
Provides an efficient way to inject TrackmaniaWS
into your application.
using ManiaAPI.TrackmaniaWS.Extensions.Hosting;
builder.Services.AddTrackmaniaWS(new TrackmaniaWSOptions
{
Credentials = new("tmf_yourapp", "password")
});
Wraps https://tm-exchange.com/ (old TMX).
- Get replays
- Search leaderboards
- Search trackpacks
- Search tracks
- Search users
- Get Gbx URLs and HTTP responses
- Get image URLs and HTTP responses
using ManiaAPI.TMX;
// Pick one from TMUF, TMNF, Nations, Sunrise, Original
var tmx = new TMX(TmxSite.TMUF);
or with DI, for a specific site, using an injected HttpClient
:
using ManiaAPI.TMX;
builder.Services.AddHttpClient("TMX_TMNF");
builder.Services.AddScoped<TMX>(provider => new TMX(
provider.GetRequiredService<IHttpClientFactory>().CreateClient("TMX_TMNF"), TmxSite.TMNF));
For advanced DI, consider using the ManiaAPI.TMX.Extensions.Hosting
package.
Provides an efficient way to inject all TMX services into your application.
using ManiaAPI.TMX.Extensions.Hosting;
builder.Services.AddTMX();
Features this setup brings:
- You can inject
ImmutableDictionary<TmxSite, TMX>
to get all TMX sites as individual instances - If you don't need specific site context, you can inject
IEnumerable<TMX>
to get all TMX sites - Specific
TMX
can be injected using[FromKeyedServices(TmxSite.TMNF)]
Warning
If you just inject TMX
alone, it will give the last-registered one (in this case, Original). If you need a specific site, use [FromKeyedServices(...)]
.
Connects ManiaAPI.TMX
with GBX.NET features.
- Get track Gbx header
- Get track Gbx object
- Get replay Gbx header
- Get replay Gbx object
using ManiaAPI.TMX;
using ManiaAPI.TMX.Extensions.Gbx;
// Pick one from TMUF, TMNF, Nations, Sunrise, Original
var tmx = new TMX(TmxSite.TMUF);
// Get the track object
var map = await tmx.GetTrackGbxNodeAsync(12345);
Console.WriteLine("Number of blocks: " + map.GetBlocks().Count());
Wraps TMF, TMT, and ManiaPlanet XML ingame APIs. Does not relate to the dedicated server XML-RPC.
It currently does not support any authentication for its complexity and security reasons. If some of the leaderboard methods will become secured with authentication though, this will be considered. For authenticated functionality in TMUF, use the TMF.NET library.
For dedicated server XML-RPC, use the GbxRemote.Net library.
For TMUF:
- Get scores
- Top 10 leaderboards
- All records (without identities)
- Skillpoints
- Medals
- Get ladder zone rankings
- Get ladder player rankings
For ManiaPlanet:
- Get campaign and map leaderboard from multiple campaigns/maps at once
- Top 10 leaderboards
- All records (without identities)
- Skillpoints
- Medals
- Get campaign and map leaderboards
- Any range of records
- Skillpoints
- Medals
- Get available master servers
For TMT:
- Get all map records (without identities)
- Get campaign medal rankings (without identities)
- Get available master servers
For all games:
- Get all available zones
using ManiaAPI.Xml;
var masterServer = new MasterServerTMUF();
First examples assume Maniaplanet relay 2
master server is still running.
using ManiaAPI.Xml;
var masterServer = new MasterServerMP4();
Because the responses can be quite large sometimes, it's recommended to accept compression on the client.
using ManiaAPI.Xml;
var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip })
{
BaseAddress = new Uri(MasterServerMP4.DefaultAddress)
};
var masterServer = new MasterServerMP4(httpClient);
In case Maniaplanet relay 2
shuts down / errors out, you have to reach out to init server with GetWaitingParams
and retrieve an available relay. That's how the game client does it (thanks Mystixor for figuring this out).
To be most inline with the game client, you should validate the master server first with ValidateAsync
. Behind the scenes, it first requests GetApplicationConfig
, then on catched HTTP exception, it requests GetWaitingParams
from the init server and use the available master server instead.
using ManiaAPI.Xml;
var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip })
{
BaseAddress = new Uri(MasterServerMP4.DefaultAddress)
};
var masterServer = new MasterServerMP4(httpClient);
await masterServer.ValidateAsync(); // Do this for reliability
// The master server is now ready to use
TMT handles 3 platforms: PC, XB1, and PS4. Each have their own init server and master server. Nadeo still tends to change these master servers, so it's recommended to first go through the init server.
using ManiaAPI.Xml;
var initServer = new InitServerTMT(Platform.PC);
var waitingParams = await initServer.GetWaitingParamsAsync();
var masterServer = new MasterServerTMT(waitingParams.MasterServers.First());
// You can repeat this exact setup for XB1 and PS4 as well if you want to work with those platforms, with something like Dictionary<Platform, MasterServerTMT> ...
Because the responses can be quite large sometimes, it's recommended to accept compression on the client for the master server. Init server does not return large responses, so it's not necessary for that one.
using ManiaAPI.Xml;
var initServer = new InitServerTMT(Platform.PC);
var waitingParams = await initServer.GetWaitingParamsAsync();
var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip })
{
BaseAddress = waitingParams.MasterServers.First().GetUri()
};
var masterServer = new MasterServerTMT(httpClient);
// You can repeat this exact setup for XB1 and PS4 as well if you want to work with those platforms, with something like Dictionary<Platform, MasterServerTMT> ...
For a simple setup with multiple platforms, the AggregatedMasterServerTMT
is recommended:
using ManiaAPI.Xml;
var waitingParams = Enum.GetValues<Platform>().ToDictionary(
platform => platform,
platform => new InitServerTMT(platform).GetWaitingParamsAsync(cancellationToken));
await Task.WhenAll(waitingParams.Values);
var aggregatedMasterServer = new AggregatedMasterServerTMT(waitingParams.ToDictionary(
pair => pair.Key,
pair => new MasterServerTMT(
new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip })
{
BaseAddress = pair.Value.Result.MasterServers.First().GetUri()
})
));
// You can now use aggregatedMasterServer to work with all master servers at once
Provides an efficient way to inject all XML services into your application.
using ManiaAPI.Xml.Extensions.Hosting;
builder.Services.AddMasterServerTMUF();
Relying on Maniaplanet relay 2
to continue running:
using ManiaAPI.Xml.Extensions.Hosting;
builder.Services.AddMasterServerMP4();
You can now inject MasterServerMP4
(registered as singleton) and use it without additional steps. Compression is enabled.
If you want to be extra sure the correct master server is selected, use this setup:
using ManiaAPI.Xml.Extensions.Hosting;
builder.Services.AddInitServerMP4();
builder.Services.AddMasterServerMP4();
// In a scope, retrieve init server, and use it for validating the master server
// No need for CreateScopeAsync if your service is scoped
await using var scope = provider.CreateScopeAsync();
var initServer = scope.ServiceProvider.GetRequiredService<InitServerMP4>();
var masterServer = scope.ServiceProvider.GetRequiredService<MasterServerMP4>();
await masterServer.ValidateAsync(initServer);
using ManiaAPI.Xml.Extensions.Hosting;
// Register the services
builder.Services.AddMasterServerTMT();
// Do the setup
// This should run at the start of your application, or when you need to refresh the master servers
// No need for CreateScopeAsync if your service is scoped
await using var scope = provider.CreateScopeAsync();
foreach (var platform in Enum.GetValues<Platform>())
{
var initServer = scope.ServiceProvider.GetRequiredKeyedService<InitServerTMT>(platform);
var waitingParams = await initServer.GetWaitingParamsAsync(cancellationToken);
var masterServer = scope.ServiceProvider.GetRequiredKeyedService<MasterServerTMT>(platform);
masterServer.Client.BaseAddress = waitingParams.MasterServers.First().GetUri();
}
Features this last setup brings:
- You can inject
AggregatedMasterServerTMT
to conveniently work with all master servers - You can inject
ImmutableDictionary<Platform, MasterServerTMT>
to get all master servers as individual instances - If you don't need specific platform context, you can inject
IEnumerable<MasterServerTMT>
to get all master servers - Specific
InitServerTMT
andMasterServerTMT
can be injected using[FromKeyedServices(Platform.PC)]
- All
MasterServerTMT
handle compression
Warning
If you just inject MasterServerTMT
alone, it will give the last-registered one (in this case, PS4). If you need a specific platform, use [FromKeyedServices(...)]
.
Note
You don't need to build the solution/repository to use ManiaAPI.NET, NuGet packages have been made for you. This is only for internal development purposes.
Make sure you have these framework SDKs available:
- .NET 9
- .NET 8
Visual Studio 2022 should be able to install those with default installation settings. Using Visual Studio 2019 will not work.
In Visual Studio, you can just use Build Solution and everything should build. JetBrains Rider has been tested and also works.
In .NET CLI, run dotnet build
on the solution (.sln
) level.
Contributions are welcome! Please fork the repository and submit a pull request with your changes.
ManiaAPI.NET is entirely MIT Licensed.
Respect the rate limits and cache the responses where possible. Caching is not done internally to give you better control.
If you need to make a lot of requests, do so in a way that doesn't overwhelm the APIs.