diff --git a/package-lock.json b/package-lock.json index d31660c2..ef77f2d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4472,18 +4472,18 @@ "dev": true }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" }, "dependencies": { "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } diff --git a/src/MatchMaker.js b/src/MatchMaker.js index 121521ef..1f47a329 100644 --- a/src/MatchMaker.js +++ b/src/MatchMaker.js @@ -4,7 +4,7 @@ const RecentGames = require('./RecentGames') module.exports = class MatchMaker { constructor (opts) { - this.waitingPair = null + this.waitingPair = {} this.games = {} this.wikiPages = opts.pages this.recentGames = new RecentGames() @@ -20,9 +20,9 @@ module.exports = class MatchMaker { * Create a new game hosted by the given player. */ - createGame (player) { - const [origin, goal] = this.wikiPages.randomPair() - const game = new WikiBattle(origin, goal) + async createGame (player, language) { + const [origin, goal] = await this.wikiPages.randomPair(language) + const game = new WikiBattle(origin, goal, language) game.connect(player) return game } @@ -32,11 +32,11 @@ module.exports = class MatchMaker { * either the previous, or the next player that is passed to this method. */ - pair (player) { - if (this.waitingPair) { + async pair (player, language) { + if (this.waitingPair[language]) { debug('pairing with existing') - const game = this.waitingPair - this.waitingPair = null + const game = this.waitingPair[language] + this.waitingPair[language] = null game.connect(player) @@ -49,8 +49,8 @@ module.exports = class MatchMaker { } debug('waiting for pairing') - const game = this.createGame(player) - this.waitingPair = game + const game = await this.createGame(player, language) + this.waitingPair[language] = game player.notifyJoinedGame(game) @@ -63,10 +63,10 @@ module.exports = class MatchMaker { * shareable URL. */ - new (player) { + async new (player, language) { debug('forcing new game') - const game = this.createGame(player) + const game = await this.createGame(player, language) this.games[game.id] = game player.notifyJoinedGame(game) diff --git a/src/SocketHandler.js b/src/SocketHandler.js index 9548a900..347ff78b 100644 --- a/src/SocketHandler.js +++ b/src/SocketHandler.js @@ -14,13 +14,13 @@ module.exports = class SocketHandler { const sock = new SocketEvents(raw) const player = new Player(sock) - sock.on('gameType', (type, id) => { + sock.on('gameType', async (type, id, language) => { switch (type) { case 'pair': - game = this.matchMaker.pair(player) + game = await this.matchMaker.pair(player, language) break case 'new': - game = this.matchMaker.new(player) + game = await this.matchMaker.new(player, language) break case 'join': try { @@ -47,8 +47,8 @@ module.exports = class SocketHandler { } }) - raw.on('close', () => { - if (game) { + raw.on('close', async () => { + if (await game) { game.disconnect(player) this.matchMaker.disconnected(game) diff --git a/src/WikiBattle.js b/src/WikiBattle.js index f6ed10a8..dc6ef068 100644 --- a/src/WikiBattle.js +++ b/src/WikiBattle.js @@ -12,12 +12,13 @@ const BACKLINKS_TIMEOUT = ms('90 seconds') */ module.exports = class WikiBattle extends EventEmitter { - constructor (origin, goal) { + constructor (origin, goal, language) { super() this.id = generateId({ length: 7 }) this.players = [] this.origin = origin this.goal = goal + this.language = language || 'en' } /** @@ -87,7 +88,7 @@ module.exports = class WikiBattle extends EventEmitter { */ navigateInner (player, to) { - player.navigateTo(to) + player.navigateTo(to, null, this.language) this.emitSocket('navigated', player.id, to) this.checkWin() } @@ -102,7 +103,7 @@ module.exports = class WikiBattle extends EventEmitter { return this.navigateInner(player, to) } // Check that the current article links to the next. - const page = await wiki.get(player.current()) + const page = await wiki.get(player.current(), null, this.language) if (page.linksTo(to)) { this.navigateInner(player, to) } @@ -114,7 +115,7 @@ module.exports = class WikiBattle extends EventEmitter { async sendHint () { debug('sending hint for', this.goal) - const page = await wiki.get(this.goal) + const page = await wiki.get(this.goal, null, this.language) this.emitSocket('hint', page.getHint()) } @@ -125,7 +126,7 @@ module.exports = class WikiBattle extends EventEmitter { async sendBacklinks () { debug('sending backlinks for', this.goal) try { - const page = await wiki.get(this.goal) + const page = await wiki.get(this.goal, null, this.language) const back = await page.getBacklinks() this.emitSocket('backlinks', null, back) } catch (err) { diff --git a/src/WikiPages.js b/src/WikiPages.js index 85d9b23b..8573ad28 100644 --- a/src/WikiPages.js +++ b/src/WikiPages.js @@ -4,6 +4,8 @@ const event = require('p-event') const ms = require('ms') const getRandom = require('random-item') const debug = require('debug')('WikiBattle:pages') +const qs = require('querystring') +const fetch = require('make-fetch-happen') /** * Possible starting and goal wikipedia articles manager. @@ -55,16 +57,39 @@ module.exports = class WikiPages extends EventEmitter { return getRandom(this.pages) } + async translate (article, language) { + if (language === 'en') return article + + const query = qs.stringify({ + action: 'query', + format: 'json', + prop: 'langlinks', + titles: this.title, + lllang: language + }) + + const response = await fetch(`https://en.wikipedia.org/w/api.php?${query}`) + const body = await response.json() + const langlink = Object.values(body.query.pages)[0].langlinks + + return langlink ? langlink[0]['*'] : null + } + /** * Get a pair of random article names, guaranteed to be two different pages. */ - randomPair () { - const one = this.random() + async randomPair (language) { + let one = null + let two = null + + while (!(one && two)) { + one = one || await this.translate(this.random(), language) + two = two || await this.translate(this.random(), language) + } - let two = this.random() while (one === two) { - two = this.random() + two = await this.translate(this.random(), language) } return [one, two] diff --git a/src/app.js b/src/app.js index 1c85bff7..185cbbd6 100644 --- a/src/app.js +++ b/src/app.js @@ -68,8 +68,8 @@ app.use(t(async (req, res, next) => { * Serve the application. */ -function gameToJson ({ origin, goal, startedAt }) { - return { origin, goal, startedAt } +function gameToJson ({ origin, goal, startedAt, language }) { + return { origin, goal, startedAt, language } } app.get('/current', (req, res) => { @@ -87,7 +87,16 @@ app.use(serveStatic(path.join(__dirname, '../public'))) */ app.get('/wiki/:page', t(async (req, res) => { - const body = await wiki.get(req.params.page) + const body = await wiki.get(req.params.page, null, 'en') + res.end(body.content) +})) + +/** + * Serve proxied Wikipedia articles in other languages than English. + */ + +app.get('/wiki/:lang(\\w+)/:page', t(async (req, res) => { + const body = await wiki.get(req.params.page, null, req.params.lang) res.end(body.content) })) diff --git a/src/client/index.html b/src/client/index.html index 8db37f0d..0cb1321c 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -44,6 +44,12 @@
This will give you a game link you can share with your friend.
[Loading]
+ +