Skip to content
eddiehinkle.comWashington, DC MetroEst. 2015

PostscodeClear, 45°F

Code: nintendo-switch-importer.ts

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://otherperson.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"); } }); }

Posted by Quill

Also on GitHub Gist