mobygames API implementation

This commit is contained in:
Wouter Groeneveld 2021-04-01 13:19:09 +02:00
parent 71572a67c5
commit d3d50847ff
9 changed files with 67548 additions and 14 deletions

4
.gitignore vendored
View File

@ -1,7 +1,9 @@
*.sublime-workspace
data/ac/*.png
data/**/*.png
data/**/*.jpg
data/**/*.gif
src/config.js

67403
data/mobygames/games_10.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@
"main": "src/chat.js",
"scripts": {
"test": "jest",
"jam": "node src/chat.js"
"toot": "node src/chat.js"
},
"devDependencies": {
"jest": "^26.6.3"

View File

@ -2,7 +2,7 @@
const { collect } = require('./collector')
const md5 = require('md5')
async function chat() {
async function chat(buddyConfig) {
/* format:
{
"href": "/wiki/Admiral",
@ -22,7 +22,7 @@ async function chat() {
.replace(/\n/g, "")
return {
"toot": `${record.name}: "${quote}"\n\n${birthday} is my birthday.\nhttps://animalcrossing.fandom.com${record.href}`,
"attach": `data/ac/${md5(record.img)}.png`,
"attach": [ `data/ac/${md5(record.img)}.png` ],
"attachDescription": `Screenshot of Animal Crossing villager ${record.name}`
}
})

View File

@ -0,0 +1,20 @@
const { collect, collectScreenshotsFor } = require('./collector')
async function chat(buddyConfig) {
const possibleGames = (await collect(buddyConfig)).games
const game = possibleGames[Math.floor(Math.random() * possibleGames.length)]
const screenshots = await collectScreenshotsFor(game, buddyConfig)
const release = game.platforms.find(pl => pl["platform_id"] === buddyConfig.platform)["first_release_date"]
return {
"toot": `${game.title} (${release})\n\n${game["moby_url"]}`,
"attach": screenshots,
"attachDescription": `Screenshots of Game Boy game ${game.title}`
}
}
module.exports = {
chat
}

View File

@ -0,0 +1,85 @@
const axios = require('axios')
const fsp = require('fs').promises
const { existsSync, createWriteStream } = require('fs')
const endpoint = "https://api.mobygames.com/v1"
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
// TODO move AC code to axios, move this to utilities.js
async function download(url, filename) {
console.log(` -- mobygames: downloading file ${url}...`)
const result = await axios({
url,
method: 'GET',
responseType: 'stream'
})
await result.data.pipe(createWriteStream(filename))
}
/**
game example from cache:
{
"game_id": 138,
"moby_url": "http://www.mobygames.com/game/pac-man",
"title": "Pac-Man"
}
*/
async function collectScreenshotsFor(game, buddyConfig) {
const { platform, mobyGamesToken } = buddyConfig
const id = game['game_id']
const ext = url => url.split(".").pop()
const screenshots = (await axios.get(`${endpoint}/games/${id}/platforms/${platform}/screenshots?api_key=${mobyGamesToken}`)).data.screenshots
const titleshots = screenshots.filter(s => s.caption?.toLowerCase()?.indexOf("title screen") >= 0)
const titleshot = titleshots.length == 0 ? screenshots[0].image : titleshots[0].image
const titleshotfile = `data/mobygames/title_${id}_1.${ext(titleshot)}`
// not sure if this is ok, we now take the SECOND LAST screenshot that doesn't contain 'title screen'
// however, the screenshot could be a dull one, but taking something random isn't great either
const gameshots = screenshots.filter(s => s.caption?.toLowerCase()?.indexOf("title screen") == -1)
const gameshot = gameshots[gameshots.length < 2 ? 0 : gameshots.length - 2].image
const gameshotfile = `data/mobygames/title_${id}_2.${ext(gameshot)}`
await download(titleshot, titleshotfile)
await download(gameshot, gameshotfile)
return [ titleshotfile, gameshotfile ]
}
// for the gameboy: 10, 688 - see https://www.mobygames.com/browse/games/gameboy/list-games/
async function collect(buddyConfig) {
const { platform, totalAmountOfGamesForPlatform, mobyGamesToken } = buddyConfig
const overviewFile = `data/mobygames/games_${platform}.json`
if(existsSync(overviewFile)) {
console.log(' -- mobygames: retrieving list from cache...')
const fileData = await fsp.readFile(overviewFile)
return JSON.parse(fileData)
}
console.log(' -- mobygames: no overview json file found for platform, fetching...')
const allGames = []
for(let offset = 0; offset < totalAmountOfGamesForPlatform; offset += 100) {
console.log(` -- mobygames: fetching platform ${platform} offset ${offset}...`)
const result = await axios.get(`${endpoint}/games?platform=${platform}&api_key=${mobyGamesToken}&offset=${offset}`)
allGames.push(result.data.games)
// there's a rate limit of 1 request per second.
await delay(1200)
}
const games = allGames.flat()
await fsp.writeFile(overviewFile, JSON.stringify({ games }, null, 2))
console.log(` -- mobygames: done collecting, result in games_${platform}.json!`)
return { games }
}
module.exports = {
collect,
collectScreenshotsFor
}

View File

@ -7,14 +7,15 @@ if(!buddies) throw "Did you seutp your config?"
const { toot } = require('./toot');
(async function() {
for await(buddy of buddies) {
const { chat } = require(`./buddies/${buddy}/buddy`)
for await(buddyConfig of buddies) {
console.log(`bootstrapping chat for ${buddyConfig.name}...`)
const { chat } = require(`./buddies/${buddyConfig.name}/buddy`)
// contains 'toot', and maybe 'attach' / 'attachDescription'
const message = await chat()
console.log(`buddy ${buddy} has this to say: ${JSON.stringify(message)}`)
const message = await chat(buddyConfig)
console.log(`buddy ${buddyConfig.name} has this to say: ${JSON.stringify(message)}`)
await toot(message)
//await toot(message, buddyConfig)
}
console.log('Done! for now... ')

21
src/config.sample.js Normal file
View File

@ -0,0 +1,21 @@
// do NOT check this in.
// which ones are active?
const buddies = [{
name: "animalcrossing",
instance: "https://mastodon.social",
oauthToken: "my-token"
}, {
name: "mobygames",
instance: "https://botsin.space",
oauthToken: "my-token-for-botsin-space",
mobyGamesToken: "my-mobygames-api-token",
platform: 10, // gameboy
totalAmountOfGames: 688 // mobygames has a total of 688 GB games in their library
}]
module.exports = {
buddies
}

View File

@ -2,15 +2,17 @@ const axios = require('axios')
const FormData = require('form-data')
const { createReadStream } = require('fs')
const { instance, oauthToken } = require('./config')
async function toot(data, opts) {
const { instance, oauthToken } = opts
async function toot(data) {
// contains 'toot', and maybe 'attach' / 'attachDescription'
const media_ids = []
if(data.attach) {
const id = await uploadMedia(data.attach, data.attachDescription)
for await(file of data.attach) {
const id = await uploadMedia(file, data.attachDescription, instance, oauthToken)
media_ids.push(id)
}
}
// https://docs.joinmastodon.org/methods/statuses/
const result = await axios.post(`${instance}/api/v1/statuses`, {
@ -25,7 +27,7 @@ async function toot(data) {
console.log(` toot: status result: ${result.status}`)
}
async function uploadMedia(fileName, description) {
async function uploadMedia(fileName, description, instance, oauthToken) {
const formData = new FormData()
formData.append('file', createReadStream(fileName))
formData.append('description', description)