Skip to content

Instantly share code, notes, and snippets.

@EdwardHinkle
Created March 13, 2019 14:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EdwardHinkle/7681ab8000d0b1fcb04be3926410e11e to your computer and use it in GitHub Desktop.
Save EdwardHinkle/7681ab8000d0b1fcb04be3926410e11e to your computer and use it in GitHub Desktop.
Nintendo Switch Importer
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