Skip to content

Instantly share code, notes, and snippets.

@virtuallyunknown
Last active June 15, 2024 18:12
Show Gist options
  • Save virtuallyunknown/fa0dfba15cc6791f1b0d4e203ee4c80f to your computer and use it in GitHub Desktop.
Save virtuallyunknown/fa0dfba15cc6791f1b0d4e203ee4c80f to your computer and use it in GitHub Desktop.
TM API
import axios from 'axios';
import { decode } from 'jsonwebtoken';
import { z } from 'zod';
const cotdListSchema = z.object({
monthList: z.array(
z.object({
year: z.number(),
month: z.number(),
lastDay: z.number(),
days: z.array(
z.object({
campaignId: z.number(),
mapUid: z.string(),
day: z.number(),
monthDay: z.number(),
seasonUid: z.string(),
leaderboardGroup: z.null(),
startTimestamp: z.number(),
endTimestamp: z.number(),
relativeStart: z.number(),
relativeEnd: z.number(),
})
),
media: z.object({
buttonBackgroundUrl: z.string(),
buttonForegroundUrl: z.string(),
decalUrl: z.string(),
popUpBackgroundUrl: z.string(),
popUpImageUrl: z.string(),
liveButtonBackgroundUrl: z.string(),
liveButtonForegroundUrl: z.string()
})
})
),
itemCount: z.number(),
nextRequestTimestamp: z.number(),
relativeNextRequest: z.number()
});
const authTicketResponseSchema = z.object({
ticket: z.string().min(2000),
});
const tokenResponseSchema = z.object({
accessToken: z.string(),
refreshToken: z.string(),
});
const jwtTokenSchema = z.object({
exp: z.number(),
});
type Credentials = {
email: string;
password: string;
};
class TMApi {
private email: Credentials['email'];
private password: Credentials['password'];
private authToken: string;
private userAgent: string;
private tokens?: {
accessToken: {
token: string;
expires: number;
};
refreshToken: {
token: string;
expires: number;
};
};
constructor({ email, password, userAgent }: Credentials & { userAgent: string }) {
this.email = email;
this.password = password;
this.authToken = Buffer.from(`${this.email}:${this.password}`).toString('base64');
this.userAgent = userAgent;
}
private async getTokens() {
const ticketRes = await axios({
url: 'https://public-ubiservices.ubi.com/v3/profiles/sessions',
method: 'post',
headers: {
'User-Agent': this.userAgent,
'Content-Type': 'application/json',
'Ubi-AppId': '86263886-327a-4328-ac69-527f0d20a237',
'Authorization': `Basic ${this.authToken}`
}
});
const ticketData = authTicketResponseSchema.parse(ticketRes.data);
const tokensRes = await axios({
url: 'https://prod.trackmania.core.nadeo.online/v2/authentication/token/ubiservices',
method: 'post',
headers: {
'User-Agent': this.userAgent,
'Content-Type': 'application/json',
'Authorization': `ubi_v1 t=${ticketData.ticket}`
},
data: {
audience: 'NadeoLiveServices'
}
});
const parsedTokens = tokenResponseSchema.parse(tokensRes.data);
return {
accessToken: {
token: parsedTokens.accessToken,
expires: jwtTokenSchema.parse(decode(parsedTokens.accessToken)).exp * 1000
},
refreshToken: {
token: parsedTokens.refreshToken,
expires: jwtTokenSchema.parse(decode(parsedTokens.refreshToken)).exp * 1000
}
};
}
private async refreshAccessToken(refreshToken: string) {
const tokenRes = await axios({
url: 'https://prod.trackmania.core.nadeo.online/v2/authentication/token/refresh',
method: 'post',
headers: {
'User-Agent': this.userAgent,
'Content-Type': 'application/json',
'Authorization': `nadeo_v1 t=${refreshToken}`
},
});
const parsedTokens = tokenResponseSchema.parse(tokenRes.data);
return {
token: parsedTokens.accessToken,
expires: jwtTokenSchema.parse(decode(parsedTokens.accessToken)).exp * 1000
};
}
private async ensureAccessToken() {
if (!this.tokens) {
const tokens = await this.getTokens();
this.tokens = tokens;
}
if (this.tokens.accessToken.expires > Date.now()) {
return;
}
if (this.tokens.refreshToken.expires <= Date.now()) {
const tokens = await this.getTokens();
this.tokens = tokens;
return;
}
if (this.tokens.accessToken.expires <= Date.now()) {
const accessToken = await this.refreshAccessToken(this.tokens.refreshToken.token);
this.tokens = ({ accessToken, refreshToken: this.tokens.refreshToken });
return;
}
}
public async getCOTDmaps() {
await this.ensureAccessToken();
const res = await axios({
url: 'https://live-services.trackmania.nadeo.live/api/token/campaign/month?length=99&offset=0',
method: 'get',
headers: {
'User-Agent': this.userAgent,
'Content-Type': 'application/json',
'Authorization': `nadeo_v1 t=${this.tokens?.accessToken.token}`
},
});
return cotdListSchema.parse(res.data)
}
}
export const tmApi = new TMApi({
userAgent: 'Your user agent',
email: 'Ubisoft account email',
password: 'Ubisoft account pass',
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment