Eddie Hinkle

nintendo-switch-importer.ts

  1. import * as request from 'request-promise';
  2. import * as moment from 'moment';
  3. import * as fs from 'fs';
  4. import * as cron from 'cron';
  5. // You will need to create the nintendo config file below
  6. interface NintendoConfig {
  7. "grant_type": string;
  8. "session_token": string;
  9. "client_id": string;
  10. "token_type": 'Bearer';
  11. "access_token": string;
  12. "device_id": string;
  13. "smart_device_id": string;
  14. }
  15. const NINTENDO_CONFIG_FILE = __dirname + '/../nintendo_config.json';
  16. let nintendoConfig = require(NINTENDO_CONFIG_FILE) as NintendoConfig;
  17. const dailySummaryUrl = `https://api-lp1.pctl.srv.nintendo.net/moon/v1/devices/${nintendoConfig.device_id}/daily_summaries`;
  18. const tokenUrlEndpoint = "https://accounts.nintendo.com/connect/1.0.0/api/token";
  19. // You will need to create the Micropub config file below
  20. interface MicropubConfig {
  21. "endpoint": string;
  22. "token": string;
  23. }
  24. const MICROPUB_CONFIG_FILE = __dirname + '/../micropub_config.json';
  25. let micropubConfig = require(MICROPUB_CONFIG_FILE) as MicropubConfig;
  26. // How frequently the cron job runs will indicate how accurate the final "Play" post is to the time of game play.
  27. // Essentially "0,30" rounds your game play UP to the closest hour or 30 minute mark.
  28. // Game play that ends at 1:13 gets posted as 1:30.
  29. // Also note, because the script needs to wait until it knows there isn't any more game play going on,
  30. // your 1:30 game play won't be posted until the next increment, in this case 2:00.
  31. new cron.CronJob('0 0,30 * * * *', function () {
  32. console.log('running routine cron job');
  33. periodicFetch();
  34. }).start();
  35. console.log('Setting up Cron Job');
  36. function periodicFetch() {
  37. getDailySummary().then(
  38. body=> {
  39. if (body.items !== undefined) {
  40. // Get previous data
  41. let previousFetchedData;
  42. let previousPlayTime;
  43. try {
  44. previousFetchedData = JSON.parse(fs.readFileSync(`${__dirname}/../last_fetched_data.json`, { encoding: 'utf8' }));
  45. } catch(error) {
  46. console.log(`OOPS! Couldn't get previous data`);
  47. console.log(error);
  48. }
  49. try {
  50. previousPlayTime = JSON.parse(fs.readFileSync(`${__dirname}/../previous_play_time.json`, { encoding: 'utf8' }));
  51. } catch(error) {
  52. console.log(`OOPS! Couldn't get previous play time`);
  53. previousPlayTime = {};
  54. }
  55. // Set current timestamp and write current data to file
  56. body.fetched_timestamp = moment().format();
  57. fs.writeFile(__dirname + '/../last_fetched_data.json', JSON.stringify(body), function(err) {
  58. if(err) {
  59. return console.log(err);
  60. }
  61. });
  62. // We will want to reverse the day arrays to be in chronological order
  63. previousFetchedData.items.reverse();
  64. body.items.reverse();
  65. body.items.forEach((daySummary, dayIndex) => {
  66. daySummary.devicePlayers.forEach((player, playerIndex) => {
  67. player.playedApps.forEach((gameData, gameDataIndex) => {
  68. let game = daySummary.playedApps.find(app => app.applicationId === gameData.applicationId);
  69. let playDate = moment(daySummary.date, "YYYY-MM-DD");
  70. let previousPlayedDates = Object.keys(previousPlayTime);
  71. let mostRecentPlayedDate = previousPlayedDates.length > 0 ? previousPlayedDates.pop() : null;
  72. // Check that this was played today
  73. if (mostRecentPlayedDate === null ||
  74. moment(mostRecentPlayedDate, "YYYY-MM-DD").isSameOrBefore(playDate, "days")) {
  75. // Group the data the same way for the previous data as the current data
  76. let previousDataDay = previousFetchedData.items[dayIndex];
  77. let previousDataPlayer = previousDataDay.devicePlayers[playerIndex];
  78. let previousDataGameData = previousDataPlayer.playedApps[gameDataIndex];
  79. let playTimeForDay = (previousPlayTime[playDate.format("YYYY-MM-DD")] !== undefined &&
  80. previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname] !== undefined &&
  81. previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname][gameData.applicationId] !== undefined) ? parseInt(previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname][gameData.applicationId]) : 0;
  82. // Check that the playing time is the same as the last time we checked.
  83. // If so, the game is no longer being played
  84. // Also verify the player and the game are the same
  85. if ((player.nickname === "Eddie" || player.nickname === "Shared") &&
  86. previousDataPlayer.nickname === player.nickname &&
  87. previousDataGameData.applicationId === gameData.applicationId &&
  88. previousDataGameData.playingTime === gameData.playingTime &&
  89. (gameData.playingTime - playTimeForDay > 0)) {
  90. let current = moment();
  91. let lastPlayedTime = current.isSame(playDate, "days") ? previousFetchedData.fetched_timestamp : playDate.clone().hour(23).minute(59).second(0).millisecond(0);
  92. let newPost = {
  93. "type": ["h-entry"],
  94. "properties": {
  95. "published": [lastPlayedTime],
  96. "duration": (gameData.playingTime - playTimeForDay),
  97. "play-of": {
  98. "type": "h-cite",
  99. "properties": {
  100. "name": game.title,
  101. "url": game.shopUri,
  102. "featured": game.imageUri.extraLarge,
  103. }
  104. },
  105. "category": ["games"]
  106. }
  107. };
  108. if (player.nickname === "Shared") {
  109. newPost.properties.category.push("https://ashleyhinkle.com/");
  110. }
  111. sendViaMicropub(newPost);
  112. console.log(JSON.stringify(newPost, null, 2));
  113. // Track previous play time used
  114. if (previousPlayTime[playDate.format("YYYY-MM-DD")] === undefined) {
  115. previousPlayTime[playDate.format("YYYY-MM-DD")] = {};
  116. }
  117. if (previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname] === undefined) {
  118. previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname] = {};
  119. }
  120. previousPlayTime[playDate.format("YYYY-MM-DD")][player.nickname][gameData.applicationId] = playTimeForDay + gameData.playingTime;
  121. }
  122. }
  123. });
  124. });
  125. });
  126. fs.writeFile(__dirname + '/../previous_play_time.json', JSON.stringify(previousPlayTime), { encoding: 'utf8' }, function(err) {
  127. if(err) {
  128. return console.log(err);
  129. }
  130. });
  131. }
  132. console.log('success');
  133. }, error => {
  134. console.log('uh oh error');
  135. console.log(error.statusCode);
  136. if (error.statusCode === 401) {
  137. console.log("Fetching new access info");
  138. getNewAccessToken().then(accessInfo => {
  139. nintendoConfig.access_token = accessInfo.access_token;
  140. nintendoConfig.token_type = accessInfo.token_type;
  141. saveNewAccessInfo(nintendoConfig);
  142. periodicFetch();
  143. });
  144. }
  145. });
  146. }
  147. function getNewAccessToken() {
  148. return request.post({
  149. uri: tokenUrlEndpoint,
  150. form: {
  151. "grant_type": nintendoConfig.grant_type,
  152. "session_token": nintendoConfig.session_token,
  153. "client_id": nintendoConfig.client_id
  154. },
  155. json: true
  156. });
  157. }
  158. function saveNewAccessInfo(info) {
  159. fs.writeFile(NINTENDO_CONFIG_FILE, JSON.stringify(info), function(err) {
  160. if(err) {
  161. return console.log(err);
  162. }
  163. console.log("New Access Info Saved");
  164. });
  165. }
  166. function getDailySummary() {
  167. return request.get(dailySummaryUrl, {
  168. headers: {
  169. "x-moon-os-language": "en-US",
  170. "x-moon-app-language": "en-US",
  171. "authorization": `${nintendoConfig.token_type} ${nintendoConfig.access_token}`,
  172. "x-moon-app-internal-version": 265,
  173. "x-moon-app-display-version": "1.7.2",
  174. "x-moon-app-id": "com.nintendo.znma",
  175. "x-moon-os": "IOS",
  176. "x-moon-os-version": "12.1.4",
  177. "x-moon-model": "iPhone10,3",
  178. "accept-encoding": "gzip;q=1.0, compress;q=0.5",
  179. "accept-language": "en-US;q=1.0",
  180. "user-agent": "moon_ios/1.7.2 (com.nintendo.znma; build:265; iOS 12.1.4) Alamofire/4.7.3",
  181. "x-moon-timezone": "America/New_York",
  182. "x-moon-smart-device-id": nintendoConfig.smart_device_id
  183. },
  184. json: true
  185. });
  186. }
  187. function sendViaMicropub(postToSend) {
  188. request.post(micropubConfig.endpoint, {
  189. 'auth': {
  190. 'bearer': micropubConfig.token
  191. },
  192. body: postToSend,
  193. json: true
  194. }, (err, data) => {
  195. if (err != undefined) {
  196. console.log(`ERROR: ${err}`);
  197. }
  198. if (data.statusCode !== 201 && data.statusCode !== 202) {
  199. console.log("Play Micropub error");
  200. } else {
  201. console.log("Successfully created Micropub request");
  202. }
  203. });
  204. }
41.77 ℉☀️Frederick, Maryland
posted using quill.p3k.io
Please note: This site is in an active redesign. Some things might be a little off 🧐