-
-
Save EdwardHinkle/7681ab8000d0b1fcb04be3926410e11e to your computer and use it in GitHub Desktop.
Nintendo Switch Importer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as request from 'request-promise'; | |
import * as moment from 'moment'; | |
import * as fs from 'fs'; | |
import * as cron from 'cron'; | |
// You will need to create the nintendo config file below | |
interface NintendoConfig { | |
"grant_type": string; | |
"session_token": string; | |
"client_id": string; | |
"token_type": 'Bearer'; | |
"access_token": string; | |
"device_id": string; | |
"smart_device_id": string; | |
} | |
const NINTENDO_CONFIG_FILE = __dirname + '/../nintendo_config.json'; | |
let nintendoConfig = require(NINTENDO_CONFIG_FILE) as NintendoConfig; | |
const dailySummaryUrl = `https://api-lp1.pctl.srv.nintendo.net/moon/v1/devices/${nintendoConfig.device_id}/daily_summaries`; | |
const tokenUrlEndpoint = "https://accounts.nintendo.com/connect/1.0.0/api/token"; | |
// You will need to create the Micropub config file below | |
interface MicropubConfig { | |
"endpoint": string; | |
"token": string; | |
} | |
const MICROPUB_CONFIG_FILE = __dirname + '/../micropub_config.json'; | |
let micropubConfig = require(MICROPUB_CONFIG_FILE) as MicropubConfig; | |
// How frequently the cron job runs will indicate how accurate the final "Play" post is to the time of game play. | |
// Essentially "0,30" rounds your game play UP to the closest hour or 30 minute mark. | |
// Game play that ends at 1:13 gets posted as 1:30. | |
// Also note, because the script needs to wait until it knows there isn't any more game play going on, | |
// your 1:30 game play won't be posted until the next increment, in this case 2:00. | |
new cron.CronJob('0 0,30 * * * *', function () { | |
console.log('running routine cron job'); | |
periodicFetch(); | |
}).start(); | |
console.log('Setting up Cron Job'); | |
function periodicFetch() { | |
getDailySummary().then( | |
body=> { | |
if (body.items !== undefined) { | |
// Get previous data | |
let previousFetchedData; | |
let previousPlayTime; | |
try { | |
previousFetchedData = JSON.parse(fs.readFileSync(`${__dirname}/../last_fetched_data.json`, { encoding: 'utf8' })); | |
} catch(error) { | |
console.log(`OOPS! Couldn't get previous data`); | |
console.log(error); | |
} | |
try { | |
previousPlayTime = JSON.parse(fs.readFileSync(`${__dirname}/../previous_play_time.json`, { encoding: 'utf8' })); | |
} catch(error) { | |
console.log(`OOPS! Couldn't get previous play time`); | |
previousPlayTime = {}; | |
} | |
// Set current timestamp and write current data to file | |
body.fetched_timestamp = moment().format(); | |
fs.writeFile(__dirname + '/../last_fetched_data.json', JSON.stringify(body), function(err) { | |
if(err) { | |
return console.log(err); | |
} | |
}); | |
// We will want to reverse the day arrays to be in chronological order | |
previousFetchedData.items.reverse(); | |
body.items.reverse(); | |
body.items.forEach((daySummary, dayIndex) => { | |
daySummary.devicePlayers.forEach((player, playerIndex) => { | |
player.playedApps.forEach((gameData, gameDataIndex) => { | |
let game = daySummary.playedApps.find(app => app.applicationId === gameData.applicationId); | |
let playDate = moment(daySummary.date, "YYYY-MM-DD"); | |
let previousPlayedDates = Object.keys(previousPlayTime); | |
let mostRecentPlayedDate = previousPlayedDates.length > 0 ? previousPlayedDates.pop() : null; | |
// Check that this was played today | |
if (mostRecentPlayedDate === null || | |
moment(mostRecentPlayedDate, "YYYY-MM-DD").isSameOrBefore(playDate, "days")) { | |
// Group the data the same way for the previous data as the current data | |
let previousDataDay = previousFetchedData.items[dayIndex]; | |
let previousDataPlayer = previousDataDay.devicePlayers[playerIndex]; | |
let previousDataGameData = previousDataPlayer.playedApps[gameDataIndex]; | |
let playTimeForDay = (previousPlayTime[playDate.format("YYYY-MM-DD")] !== undefined && | |
previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname] !== undefined && | |
previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname][gameData.applicationId] !== undefined) ? parseInt(previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname][gameData.applicationId]) : 0; | |
// Check that the playing time is the same as the last time we checked. | |
// If so, the game is no longer being played | |
// Also verify the player and the game are the same | |
if ((player.nickname === "Eddie" || player.nickname === "Shared") && | |
previousDataPlayer.nickname === player.nickname && | |
previousDataGameData.applicationId === gameData.applicationId && | |
previousDataGameData.playingTime === gameData.playingTime && | |
(gameData.playingTime - playTimeForDay > 0)) { | |
let current = moment(); | |
let lastPlayedTime = current.isSame(playDate, "days") ? previousFetchedData.fetched_timestamp : playDate.clone().hour(23).minute(59).second(0).millisecond(0); | |
let newPost = { | |
"type": ["h-entry"], | |
"properties": { | |
"published": [lastPlayedTime], | |
"duration": (gameData.playingTime - playTimeForDay), | |
"play-of": { | |
"type": "h-cite", | |
"properties": { | |
"name": game.title, | |
"url": game.shopUri, | |
"featured": game.imageUri.extraLarge, | |
} | |
}, | |
"category": ["games"] | |
} | |
}; | |
if (player.nickname === "Shared") { | |
newPost.properties.category.push("https://ashleyhinkle.com/"); | |
} | |
sendViaMicropub(newPost); | |
console.log(JSON.stringify(newPost, null, 2)); | |
// Track previous play time used | |
if (previousPlayTime[playDate.format("YYYY-MM-DD")] === undefined) { | |
previousPlayTime[playDate.format("YYYY-MM-DD")] = {}; | |
} | |
if (previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname] === undefined) { | |
previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname] = {}; | |
} | |
previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname][gameData.applicationId] = playTimeForDay + gameData.playingTime; | |
} | |
} | |
}); | |
}); | |
}); | |
fs.writeFile(__dirname + '/../previous_play_time.json', JSON.stringify(previousPlayTime), { encoding: 'utf8' }, function(err) { | |
if(err) { | |
return console.log(err); | |
} | |
}); | |
} | |
console.log('success'); | |
}, error => { | |
console.log('uh oh error'); | |
console.log(error.statusCode); | |
if (error.statusCode === 401) { | |
console.log("Fetching new access info"); | |
getNewAccessToken().then(accessInfo => { | |
nintendoConfig.access_token = accessInfo.access_token; | |
nintendoConfig.token_type = accessInfo.token_type; | |
saveNewAccessInfo(nintendoConfig); | |
periodicFetch(); | |
}); | |
} | |
}); | |
} | |
function getNewAccessToken() { | |
return request.post({ | |
uri: tokenUrlEndpoint, | |
form: { | |
"grant_type": nintendoConfig.grant_type, | |
"session_token": nintendoConfig.session_token, | |
"client_id": nintendoConfig.client_id | |
}, | |
json: true | |
}); | |
} | |
function saveNewAccessInfo(info) { | |
fs.writeFile(NINTENDO_CONFIG_FILE, JSON.stringify(info), function(err) { | |
if(err) { | |
return console.log(err); | |
} | |
console.log("New Access Info Saved"); | |
}); | |
} | |
function getDailySummary() { | |
return request.get(dailySummaryUrl, { | |
headers: { | |
"x-moon-os-language": "en-US", | |
"x-moon-app-language": "en-US", | |
"authorization": `${nintendoConfig.token_type} ${nintendoConfig.access_token}`, | |
"x-moon-app-internal-version": 265, | |
"x-moon-app-display-version": "1.7.2", | |
"x-moon-app-id": "com.nintendo.znma", | |
"x-moon-os": "IOS", | |
"x-moon-os-version": "12.1.4", | |
"x-moon-model": "iPhone10,3", | |
"accept-encoding": "gzip;q=1.0, compress;q=0.5", | |
"accept-language": "en-US;q=1.0", | |
"user-agent": "moon_ios/1.7.2 (com.nintendo.znma; build:265; iOS 12.1.4) Alamofire/4.7.3", | |
"x-moon-timezone": "America/New_York", | |
"x-moon-smart-device-id": nintendoConfig.smart_device_id | |
}, | |
json: true | |
}); | |
} | |
function sendViaMicropub(postToSend) { | |
request.post(micropubConfig.endpoint, { | |
'auth': { | |
'bearer': micropubConfig.token | |
}, | |
body: postToSend, | |
json: true | |
}, (err, data) => { | |
if (err != undefined) { | |
console.log(`ERROR: ${err}`); | |
} | |
if (data.statusCode !== 201 && data.statusCode !== 202) { | |
console.log("Play Micropub error"); | |
} else { | |
console.log("Successfully created Micropub request"); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment