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 @@

Want to battle a friend?

This will give you a game link you can share with your friend.

[Loading]

+ +

Like WikiBattle?

diff --git a/src/client/index.js b/src/client/index.js index 644b05f6..0ed60175 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -27,13 +27,13 @@ const Player = newless(class Player { this.path = [] } - navigateTo (page, cb) { + navigateTo (page, cb, language) { this.path.push(page) bus.emit('article-loading', { player: this, title: page }) loadPage(page, (e, body) => { bus.emit('article-loaded', { player: this, title: page, body: body }) if (cb) cb(e) - }) + }, language) } }) @@ -58,12 +58,14 @@ function init () { const startGameWrapper = document.querySelector('#go') const startGamePrivateWrapper = document.querySelector('#go-priv') - render(empty(startGameWrapper), startGameButton(false)) - render(empty(startGamePrivateWrapper), startGameButton(true)) + const language = document.querySelector('#language').value + + render(empty(startGameWrapper), startGameButton(false, language)) + render(empty(startGamePrivateWrapper), startGameButton(true, language)) bus.on('connect', go) } -function go (isPrivate) { +function go (isPrivate, language) { _private = isPrivate bus.on('navigate', onNavigate) @@ -124,7 +126,7 @@ function go (isPrivate) { waiting() }) - sock.emit('gameType', connectType, connectId) + sock.emit('gameType', connectType, connectId, language) } function onNavigate (next) { @@ -158,9 +160,9 @@ function onBacklinks (e, backlinks) { bus.emit('backlinks', backlinks) } -function onNavigated (playerId, page, cb) { +function onNavigated (playerId, page, cb, language) { if (_players[playerId] && page !== null) { - _players[playerId].navigateTo(page, cb) + _players[playerId].navigateTo(page, cb, language) } } function onOpponentScrolled (id, top, width) { diff --git a/src/client/load-page.js b/src/client/load-page.js index 989d1303..bfa68cc0 100644 --- a/src/client/load-page.js +++ b/src/client/load-page.js @@ -2,15 +2,14 @@ const xhr = require('xhr') const callbacks = {} -module.exports = function load (page, cb) { +module.exports = function load (page, cb, language) { if (callbacks[page]) { callbacks[page].push(cb) return } callbacks[page] = [cb] - - xhr(`./wiki/${page}`, (err, response) => { + xhr(`./wiki/${language}/${page}`, (err, response) => { if (err) done(err) else done(null, response.body) }) diff --git a/src/client/views/start-game-button.js b/src/client/views/start-game-button.js index 8eb77ec9..8d8b3960 100644 --- a/src/client/views/start-game-button.js +++ b/src/client/views/start-game-button.js @@ -2,15 +2,16 @@ const render = require('crel') const { on, off } = require('dom-event') const bus = require('../bus') -module.exports = function startGameButton (isPrivate) { - return new StartGameButton(isPrivate).el +module.exports = function startGameButton (isPrivate, language) { + return new StartGameButton(isPrivate, language).el } class StartGameButton { - constructor (isPrivate) { + constructor (isPrivate, language) { this.onClick = this.onClick.bind(this) this.isPrivate = isPrivate + this.language = language this.el = render('button', 'ยป Go!') on(this.el, 'click', this.onClick) @@ -22,7 +23,7 @@ class StartGameButton { } onClick () { - bus.emit('connect', this.isPrivate) + bus.emit('connect', this.isPrivate, this.language) this.disable() } } diff --git a/src/wiki.js b/src/wiki.js index 1dbe4147..036ce135 100644 --- a/src/wiki.js +++ b/src/wiki.js @@ -19,10 +19,11 @@ const fetch = makeFetch.defaults({ */ const WikiPage = class WikiPage { - constructor (title, content, links) { + constructor (title, content, links, language) { this.title = title this.content = content this.links = links + this.language = language } /** @@ -79,7 +80,7 @@ const WikiPage = class WikiPage { bllimit: BACKLINKS_LIMIT }) - const response = await fetch(`https://en.wikipedia.org/w/api.php?${query}`) + const response = await fetch(`https://${this.language}.wikipedia.org/w/api.php?${query}`) const body = await response.json() return body.query.backlinks.map((l) => l.title) } @@ -91,7 +92,7 @@ const pageRequests = new Map() * Load a wikipedia page with metadata. */ -async function getPage (title, cb) { +async function getPage (title, cb, language) { // if we're already fetching this page, don't start a new request if (pageRequests.has(title)) return pageRequests.get(title) @@ -106,9 +107,10 @@ async function getPage (title, cb) { async function load () { const query = qs.stringify({ action: 'parse', format: 'json', page: title.replace(/ /g, '_') }) - const response = await fetch(`https://en.wikipedia.org/w/api.php?${query}`) + // const response = await fetch(`https://en.wikipedia.org/w/api.php?${query}`) + const response = await fetch(`https://${language}.wikipedia.org/w/api.php?${query}`) const data = await response.json() - return new WikiPage(title, data.parse.text['*'], data.parse.links) + return new WikiPage(title, data.parse.text['*'], data.parse.links, language) } }