Add game DB lookup support (#32)

* Add game DB lookup support

* Fix compatibility typo

* Fix typo 2

* Use async function in game DB listing, avoid redundant HTTP requests
This commit is contained in:
James 2018-06-07 12:54:28 +10:00 committed by Flame Sage
parent 9d15288d9c
commit b914780a65
4 changed files with 112 additions and 0 deletions

View file

@ -41,5 +41,18 @@
"GITHUB_OLD_THRESHOLD": {
"required": false,
"description": "Issues below this treshold should be ignored"
},
"COMPAT_DB_SOURCE": {
"description": "URL to pull game compatibility information from"
},
"COMPAT_DB_REFRESH": {
"required": false,
"description": "Minimum time in milliseconds between compatibility DB refreshes (default 20 minutes)"
},
"COMPAT_ICON_BASE": {
"description": "URL (with trailing slash) to directory with <game-id>.png files"
},
"COMPAT_URL_BASE": {
"description": "URL (with trailing slash) to directory with game information pages"
}
}

View file

@ -15,6 +15,8 @@
"logdna": "^1.2.3",
"node-schedule": "^1.2.3",
"request": "^2.79.0",
"request-promise-native": "^1.0.5",
"string-similarity": "^1.2.0",
"winston": "^2.3.0"
},
"devDependencies": {

94
src/commands/game.js Normal file
View file

@ -0,0 +1,94 @@
const request = require('request-promise-native');
const discord = require('discord.js');
const stringSimilarity = require('string-similarity');
const logger = require('../logging.js');
const state = require('../state.js');
const targetServer = process.env.COMPAT_DB_SOURCE;
const refreshTime = process.env.COMPAT_REFRESH_TIME ? parseInt(process.env.COMPAT_REFRESH_TIME) : 1000 * 60 * 20;
const iconBase = process.env.COMPAT_ICON_BASE;
const urlBase = process.env.COMPAT_URL_BASE;
const compatStrings = {
0: { "key": "0", "name": "Perfect", "color": "#5c93ed", "description": "Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without any workarounds needed." },
1: { "key": "1", "name": "Great", "color": "#47d35c", "description": "Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds." },
2: { "key": "2", "name": "Okay", "color": "#94b242", "description": "Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds." },
3: { "key": "3", "name": "Bad", "color": "#f2d624", "description": "Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds." },
4: { "key": "4", "name": "Intro/Menu", "color": "red", "description": "Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen." },
5: { "key": "5", "name": "Won't Boot", "color": "#828282", "description": "The game crashes when attempting to startup." },
99: { "key": "99", "name": "Not Tested", "color": "black", "description": "The game has not yet been tested." }
};
async function updateDatabase() {
let body;
try {
body = await request(targetServer);
} catch (e) {
logger.error("Unable to download latest games list!");
throw e;
}
state.gameDB = JSON.parse(body);
state.lastGameDBUpdate = Date.now();
logger.info(`Updated games list (${state.gameDB.length} games)`);
state.gameDBPromise = null;
}
exports.command = async function (message) {
if (Date.now() - state.lastGameDBUpdate > refreshTime) {
// Update remote list of games locally.
const waitMessage = message.channel.send("This will take a second...");
if (state.gameDBPromise == null) {
state.gameDBPromise = updateDatabase(message);
}
try {
await state.gameDBPromise;
} catch (e) {
message.channel.send("Game compatibility feed temporarily unavailable - sorry!");
throw e;
} finally {
// We don't need this message anymore
waitMessage.then(waitMessageResult => waitMessageResult.delete());
}
}
const game = message.content.substr(message.content.indexOf(' ') + 1);
// Search all games. This is only linear time, so /shrug?
let bestGame = undefined;
let bestScore = 0.5; // Game names must have at least a 50% similarity to be matched
state.gameDB.forEach(testGame => {
const newDistance = stringSimilarity.compareTwoStrings(game.toLowerCase(), testGame.title.toLowerCase());
if (newDistance > bestScore) {
bestGame = testGame;
bestScore = newDistance;
}
});
if (bestGame === undefined) {
message.channel.send("Game could not be found.");
return;
}
const screenshot = `${iconBase}${bestGame.id}.png`;
const url = `${urlBase}${bestGame.id}/`;
const compat = compatStrings[bestGame.compatibility];
const embed = new discord.RichEmbed()
.addField("Status", compat.name, true)
.addField("Needs System Files", bestGame.needs_system_files ? "Yes" : "No", true)
.addField("Needs Shared Font", bestGame.needs_shared_font ? "Yes" : "No", true)
.setTitle(bestGame.title)
.setColor(compat.color)
.setDescription(bestGame.description)
.setURL(url)
.setThumbnail(screenshot);
message.channel.send(embed);
};

View file

@ -11,6 +11,9 @@ var State = function () {
leaves: 0,
warnings: 0
};
this.lastGameDBUpdate = 0;
this.gameDB = [];
this.gameDBPromise = null;
};
module.exports = new State();