aboutsummaryrefslogtreecommitdiff
path: root/src/mojang.ts
blob: 0ad22ab21fb1e1d2bb055e51ddda2263764b95cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
 * Fetch the Mojang username API through api.ashcon.app
 */

import { isUuid, sleep, undashUuid } from './util.js'
import * as nodeFetch from 'node-fetch'
import fetch from 'node-fetch'
import { Agent } from 'https'

// We need to create an agent to prevent memory leaks
const httpsAgent = new Agent({
	keepAlive: true
})

interface MojangApiResponse {
	/** These uuids are already undashed */
	uuid: string | null
	username: string | null
}

/**
 * Get mojang api data from the session server
 */
export let profileFromUuid = async function profileFromUuid(uuid: string): Promise<MojangApiResponse> {
	let fetchResponse: nodeFetch.Response

	try {
		fetchResponse = await fetch(
			// using mojang directly is faster than ashcon lol, also mojang removed the ratelimits from here
			`https://sessionserver.mojang.com/session/minecraft/profile/${undashUuid(uuid)}`,
			{ agent: () => httpsAgent }
		)
	} catch {
		// if there's an error, wait a second and try again
		await sleep(1000)
		return await profileFromUuid(uuid)
	}

	let dataString: string
	try {
		dataString = await fetchResponse.text()
	} catch (err) {
		return { uuid: null, username: null }
	}
	let data
	try {
		data = JSON.parse(dataString)
	} catch {
		// if it errors, just return null
		return { uuid: null, username: null }
	}
	return {
		uuid: data.id,
		username: data.name
	}
}


export let profileFromUsername = async function profileFromUsername(username: string): Promise<MojangApiResponse> {
	// since we don't care about anything other than the uuid, we can use /uuid/ instead of /user/

	let fetchResponse: nodeFetch.Response

	try {
		fetchResponse = await fetch(
			`https://api.mojang.com/users/profiles/minecraft/${username}`,
			{ agent: () => httpsAgent }
		)
	} catch {
		// if there's an error, wait a second and try again
		await sleep(1000)
		return await profileFromUsername(username)
	}

	let data: any = null
	const rawData = await fetchResponse.text()
	try {
		data = JSON.parse(rawData)
	} catch {}


	if (!data?.id) {
		// return { uuid: null, username: null }
		return await profileFromUsernameAlternative(username)
	}

	return {
		uuid: data.id,
		username: data.name
	}
}

export async function profileFromUsernameAlternative(username: string): Promise<MojangApiResponse> {
	let fetchResponse: nodeFetch.Response

	try {
		fetchResponse = await fetch(
			`https://api.ashcon.app/mojang/v2/user/${username}`,
			{ agent: () => httpsAgent }
		)
	} catch {
		// if there's an error, wait a second and try again
		await sleep(1000)
		return await profileFromUsernameAlternative(username)
	}

	let data
	try {
		data = await fetchResponse.json()
	} catch {
		return { uuid: null, username: null }
	}
	if (!data.uuid)
		return { uuid: null, username: null }
	return {
		uuid: undashUuid(data.uuid),
		username: data.username
	}
}

export let profileFromUser = async function profileFromUser(user: string): Promise<MojangApiResponse> {
	if (isUuid(user)) {
		return await profileFromUuid(user)
	} else
		return await profileFromUsername(user)
}


// this is necessary for mocking in the tests because es6
export function mockProfileFromUuid($value) { profileFromUuid = $value }
export function mockProfileFromUsername($value) { profileFromUsername = $value }
export function mockProfileFromUser($value) { profileFromUser = $value }