-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathapi.mjs
More file actions
272 lines (257 loc) · 9.38 KB
/
Copy pathapi.mjs
File metadata and controls
272 lines (257 loc) · 9.38 KB
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
import { InterwikiDataImpl, MetadataImpl } from './metadata.mjs';
import { getStringProperty, getWikiUrl } from './util.mjs';
const USER_AGENT = `tiled-datamaps/1.0 (https://github.com/utdrwiki/maps; admin@undertale.wiki) tiled/${tiled.version}`;
/**
* Retrieves the URL to the wiki's Action API from project properties.
* @param {string} language Language code for the wiki
* @returns URL to the wiki's Action API
*/
export function getApiUrl(language = 'en') {
const scriptPath = getStringProperty(tiled.project, 'scriptPath') || '/';
return `${getWikiUrl(language)}${scriptPath}api.php`;
}
/**
* Retrieves the URL to the wiki's REST API from project properties.
* @param {string} language Language code for the wiki
* @returns URL to the wiki's Action API
*/
export function getRestUrl(language = 'en') {
const scriptPath = getStringProperty(tiled.project, 'scriptPath') || '/';
return `${getWikiUrl(language)}${scriptPath}rest.php`;
}
/**
* Generates a ready state change handler for a given XMLHttpRequest.
* @param {XMLHttpRequest} xhr Associated XMLHttpRequest
* @param {(value: any|PromiseLike<any>) => void} resolve Promise
* resolution function
* @param {(reason: any?) => void} reject Promise rejection function
* @param {boolean} isArrayBuffer Whether the request expects a binary response
* @returns {() => void} Ready state change handler
*/
const readyStateChange = (xhr, resolve, reject, isArrayBuffer = false) => () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
if (isArrayBuffer) {
resolve(xhr.response);
} else {
resolve(JSON.parse(xhr.responseText));
}
} catch (error) {
reject(new Error(`Failed to parse response: ${xhr.responseText}`));
}
} else {
reject(new Error(`Request failed with ${xhr.status}: ${xhr.responseText}`));
}
}
};
/**
* Send a GET request with query string parameters.
* @param {string} baseUrl API URL
* @param {Record<string, string>} params Request parameters
* @param {string?} accessToken Optional access token for authorization
* @returns {Promise<any>} Parsed JSON response
*/
export function httpGet(baseUrl, params = {}, accessToken = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', `${baseUrl}?${new URLSearchParams(params)}`, true);
if (accessToken) {
xhr.setRequestHeader('Authorization', `Bearer ${accessToken}`);
}
xhr.onreadystatechange = readyStateChange(xhr, resolve, reject);
xhr.setRequestHeader('User-Agent', USER_AGENT);
xhr.send();
});
}
/**
* Send a POST request with application/x-www-form-urlencoded body.
* @param {string} baseUrl API URL
* @param {Record<string, string>} params Request parameters
* @param {string?} accessToken Optional access token for authorization
* @returns {Promise<any>} Parsed JSON response
*/
export function httpPost(baseUrl, params = {}, accessToken = null) {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open('POST', baseUrl, true);
if (accessToken) {
xhr.setRequestHeader('Authorization', `Bearer ${accessToken}`);
}
xhr.onreadystatechange = readyStateChange(xhr, resolve, reject);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('User-Agent', USER_AGENT);
xhr.send(new URLSearchParams(params).toString());
});
}
/**
* Validates an access token and returns the currently logged in user's
* username.
* @param {string} accessToken Access token for the current user
* @param {string} language Wiki language
* @returns {Promise<string|null>} Currently logged in user's username
*/
export function getLoggedInUser(accessToken, language = 'en') {
return httpGet(
`${getRestUrl(language)}/oauth2/resource/profile`,
{},
accessToken
).then(({username}) => username).catch(() => null);
}
/**
* Retrieves a CSRF token for the current user.
* @param {string} accessToken Access token for the wiki
* @param {string} language Wiki language
* @returns {Promise<string>} CSRF token for the current user
*/
function getCsrfToken(accessToken, language = 'en') {
return httpGet(getApiUrl(language), {
action: 'query',
meta: 'tokens',
type: 'csrf',
format: 'json'
}, accessToken).then(data => data.query.tokens.csrftoken);
}
/**
* Custom error class for errors returned from the MediaWiki API.
*/
export class APIError extends Error {
/**
* Constructs an error object from the MediaWiki API error response.
* @param {object} error Error object from the MediaWiki API
* @param {string} error.code Error code
* @param {string} error.info Error information
*/
constructor(error) {
super(`API error: ${error.info}`);
this.name = 'APIError';
this.code = error.code;
this.info = error.info;
}
}
/**
* Edits a wiki page.
* @param {string} title Wiki page title
* @param {string} text Wiki page content
* @param {string} summary Summary to use when editing the page
* @param {string} accessToken Access token for the wiki
* @param {string} language Wiki language
* @returns {Promise<any>} Resolves on edit completion
*/
export function edit(title, text, summary, accessToken, language = 'en') {
return getCsrfToken(accessToken, language).then(token =>
httpPost(getApiUrl(language), {
action: 'edit',
title,
text,
summary,
format: 'json',
formatversion: '2',
token
}, accessToken)
).then(response => {
if (response.error) {
throw new APIError(response.error);
}
return response.edit;
});
}
/**
* Retrieves maps from the wiki under specific criteria.
* @param {object} options Options for retrieving maps
* @param {string} language Wiki language
* @returns {Promise<DataMap[]>} List of maps on the wiki
*/
function getMaps(options, language = 'en') {
return httpGet(getApiUrl(language), Object.assign({
action: 'query',
prop: 'revisions',
rvprop: 'ids|content',
rvslots: 'main',
format: 'json',
formatversion: '2',
}, options)).then(data => data.query.pages
.filter((/** @type {any} */ page) =>
page.revisions &&
page.revisions.length > 0 &&
page.revisions[0].slots &&
page.revisions[0].slots.main &&
page.revisions[0].slots.main.contentmodel === 'datamap'
)
.map((/** @type {any} */ page) => {
const {slots, revid} = page.revisions[0];
const /** @type {DataMap} */ datamap = JSON.parse(slots.main.content);
datamap.custom = datamap.custom || new MetadataImpl();
datamap.custom.interwiki = datamap.custom.interwiki || {};
datamap.custom.interwiki[language] = new InterwikiDataImpl({
mapName: page.title.split(':').slice(1).join(':'),
});
datamap.custom.interwiki[language].revision = revid;
return datamap;
})
.filter((/** @type {DataMap} */ datamap) => !datamap.$fragment)
);
}
/**
* Retrieves all maps from the wiki.
* @param {string} language Wiki language
* @returns {Promise<DataMap[]>} List of maps on the wiki
*/
export function getAllMaps(language = 'en') {
return getMaps({
generator: 'allpages',
gapnamespace: '2900',
gapfilterredir: 'nonredirects',
gaplimit: 'max',
}, language);
}
/**
* Retrieves a single map from the wiki.
* @param {string} name Map name
* @param {string} language Wiki language
* @returns {Promise<DataMap>} Specified map from the wiki
*/
export function getMap(name, language = 'en') {
return getMaps({
titles: `Map:${name}`
}, language).then(maps => maps[0]);
}
/**
* Returns the URLs of the given map files on the wiki.
* @param {string[]} filenames Map file names
* @param {string} language Wiki language
* @returns {Promise<string[]>} URLs of the given map files on the wiki
*/
export function getFileUrls(filenames, language = 'en') {
return httpGet(getApiUrl(language), {
action: 'query',
titles: filenames.map(name => `File:${name}`).join('|'),
prop: 'imageinfo',
iiprop: 'url',
format: 'json',
formatversion: '2'
}).then(data => filenames.map(filename => data.query.pages
.find((/** @type {any} */ page) => page.title === `File:${
data.query.normalized
?.find((/** @type {any} */ n) => n.from === filename)
?.to ||
filename
}`)
?.imageinfo[0].url)
.filter(Boolean));
}
/**
* Downloads a file from a URL and returns it as an ArrayBuffer.
* @param {string} url URL to download the file from
* @returns {Promise<ArrayBuffer>} Downloaded file data
*/
export function downloadFile(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = readyStateChange(xhr, resolve, reject, true);
xhr.setRequestHeader('User-Agent', USER_AGENT);
xhr.send();
});
}