Skip to content

Commit 97a0744

Browse files
Implement a wiki synchronization system.
This way, we do not have to keep Tiled files for publishing maps, because users can just pull the maps from the wiki itself and get notified if any of the maps they already pulled have been modified.
1 parent 979e65f commit 97a0744

File tree

8 files changed

+873
-16
lines changed

8 files changed

+873
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
*.mw-datamaps
12
*.png
23
*.tiled-session
34
node_modules

extensions/includes/api.mjs

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { InterwikiDataImpl, MetadataImpl } from './metadata.mjs';
12
import { getStringProperty, getWikiUrl } from './util.mjs';
23

34
const USER_AGENT = `tiled-datamaps/1.0 (https://github.com/utdrwiki/maps; admin@undertale.wiki) tiled/${tiled.version}`;
@@ -28,13 +29,18 @@ export function getRestUrl(language = 'en') {
2829
* @param {(value: any|PromiseLike<any>) => void} resolve Promise
2930
* resolution function
3031
* @param {(reason: any?) => void} reject Promise rejection function
32+
* @param {boolean} isArrayBuffer Whether the request expects a binary response
3133
* @returns {() => void} Ready state change handler
3234
*/
33-
const readyStateChange = (xhr, resolve, reject) => () => {
35+
const readyStateChange = (xhr, resolve, reject, isArrayBuffer = false) => () => {
3436
if (xhr.readyState === XMLHttpRequest.DONE) {
3537
if (xhr.status === 200) {
3638
try {
37-
resolve(JSON.parse(xhr.responseText));
39+
if (isArrayBuffer) {
40+
resolve(xhr.response);
41+
} else {
42+
resolve(JSON.parse(xhr.responseText));
43+
}
3844
} catch (error) {
3945
reject(new Error(`Failed to parse response: ${xhr.responseText}`));
4046
}
@@ -160,3 +166,107 @@ export function edit(title, text, summary, accessToken, language = 'en') {
160166
return response.edit;
161167
});
162168
}
169+
170+
/**
171+
* Retrieves maps from the wiki under specific criteria.
172+
* @param {object} options Options for retrieving maps
173+
* @param {string} language Wiki language
174+
* @returns {Promise<DataMap[]>} List of maps on the wiki
175+
*/
176+
function getMaps(options, language = 'en') {
177+
return httpGet(getApiUrl(language), Object.assign({
178+
action: 'query',
179+
prop: 'revisions',
180+
rvprop: 'ids|content',
181+
rvslots: 'main',
182+
format: 'json',
183+
formatversion: '2',
184+
}, options)).then(data => data.query.pages
185+
.filter((/** @type {any} */ page) =>
186+
page.revisions &&
187+
page.revisions.length > 0 &&
188+
page.revisions[0].slots &&
189+
page.revisions[0].slots.main &&
190+
page.revisions[0].slots.main.contentmodel === 'datamap'
191+
)
192+
.map((/** @type {any} */ page) => {
193+
const {slots, revid} = page.revisions[0];
194+
const /** @type {DataMap} */ datamap = JSON.parse(slots.main.content);
195+
datamap.custom = datamap.custom || new MetadataImpl();
196+
datamap.custom.interwiki = datamap.custom.interwiki || {};
197+
datamap.custom.interwiki[language] = new InterwikiDataImpl({
198+
mapName: page.title.split(':').slice(1).join(':'),
199+
});
200+
datamap.custom.interwiki[language].revision = revid;
201+
return datamap;
202+
})
203+
.filter((/** @type {DataMap} */ datamap) => !datamap.$fragment)
204+
);
205+
}
206+
207+
/**
208+
* Retrieves all maps from the wiki.
209+
* @param {string} language Wiki language
210+
* @returns {Promise<DataMap[]>} List of maps on the wiki
211+
*/
212+
export function getAllMaps(language = 'en') {
213+
return getMaps({
214+
generator: 'allpages',
215+
gapnamespace: '2900',
216+
gapfilterredir: 'nonredirects',
217+
gaplimit: 'max',
218+
}, language);
219+
}
220+
221+
/**
222+
* Retrieves a single map from the wiki.
223+
* @param {string} name Map name
224+
* @param {string} language Wiki language
225+
* @returns {Promise<DataMap>} Specified map from the wiki
226+
*/
227+
export function getMap(name, language = 'en') {
228+
return getMaps({
229+
titles: `Map:${name}`
230+
}, language).then(maps => maps[0]);
231+
}
232+
233+
/**
234+
* Returns the URLs of the given map files on the wiki.
235+
* @param {string[]} filenames Map file names
236+
* @param {string} language Wiki language
237+
* @returns {Promise<string[]>} URLs of the given map files on the wiki
238+
*/
239+
export function getFileUrls(filenames, language = 'en') {
240+
return httpGet(getApiUrl(language), {
241+
action: 'query',
242+
titles: filenames.map(name => `File:${name}`).join('|'),
243+
prop: 'imageinfo',
244+
iiprop: 'url',
245+
format: 'json',
246+
formatversion: '2'
247+
}).then(data => filenames.map(filename => data.query.pages
248+
.find((/** @type {any} */ page) => page.title === `File:${
249+
data.query.normalized
250+
?.find((/** @type {any} */ n) => n.from === filename)
251+
?.to ||
252+
filename
253+
}`)
254+
?.imageinfo[0].url)
255+
.filter(Boolean));
256+
}
257+
258+
/**
259+
* Downloads a file from a URL and returns it as an ArrayBuffer.
260+
* @param {string} url URL to download the file from
261+
* @returns {Promise<ArrayBuffer>} Downloaded file data
262+
*/
263+
export function downloadFile(url) {
264+
return new Promise((resolve, reject) => {
265+
const xhr = new XMLHttpRequest();
266+
xhr.open('GET', url, true);
267+
xhr.responseType = 'arraybuffer';
268+
xhr.onreadystatechange = readyStateChange(xhr, resolve, reject, true);
269+
xhr.setRequestHeader('User-Agent', USER_AGENT);
270+
xhr.send();
271+
});
272+
}

0 commit comments

Comments
 (0)