diff --git a/README.md b/README.md index 0677f13..6ec5fb2 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This workflow requires three environment variables to connect to Strava. These v - `CLIENT_ID` - `CLIENT_SECRET` -- `REFRESH_TOKEN` +- `CODE` ### Follow these steps to get the values for these variables @@ -24,7 +24,7 @@ This workflow requires three environment variables to connect to Strava. These v 4. Click "Authorize". -5. Inspect the URL in the search bar and copy the value for `code`. This is the value for `REFRESH_TOKEN`. +5. Inspect the URL in the search bar and copy the value for `code`. This is the value for `CODE`. 6. That's it! Enter those values into the workflow configuration. @@ -34,11 +34,10 @@ The default keyword is "strava". After entering this keyword, hit 'return' and a ![screenshot](./screenshot.png "screenshot") - ## Optional Parameters To use metric units, set `METRIC_UNITS` to `true`. ## License -MIT \ No newline at end of file +MIT diff --git a/Strava.alfredworkflow b/Strava.alfredworkflow index 9e5f64c..5de857a 100644 Binary files a/Strava.alfredworkflow and b/Strava.alfredworkflow differ diff --git a/index.js b/index.js index 1ffcdf7..bdc434d 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,17 @@ -import alfy from 'alfy'; -import { Strava } from 'strava'; +import alfy from "alfy"; +import { Strava } from "strava"; +import fs from "fs"; +import fetch from "node-fetch"; import moment from "moment"; const METERS_TO_MILES = 0.0006213712; +const TOKEN_PATH = "./token.json"; const formatTime = (date) => { const dateString = moment(date).format("dddd, MMMM DD, YYYY"); const timeString = moment(date).format("H:mmA"); return `${timeString} on ${dateString}`; -} +}; const formatDistance = (distance) => { if (process.env.METRIC_UNITS) { @@ -16,24 +19,61 @@ const formatDistance = (distance) => { } return `${(distance * METERS_TO_MILES).toFixed(1)} mi`; -} +}; + +const getRefreshToken = async () => { + const url = `https://www.strava.com/oauth/token?client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&code=${process.env.CODE}&grant_type=authorization_code`; + const response = await fetch(url, { + method: "POST", + }); + return response.json(); +}; + +const loadRefreshToken = async () => { + let token; + try { + const data = JSON.parse(fs.readFileSync(TOKEN_PATH, "utf8")); + token = data.refresh_token; + } catch (e) { + const data = await getRefreshToken(); + fs.writeFileSync( + TOKEN_PATH, + JSON.stringify({ refresh_token: data.refresh_token }) + ); + } -const strava = new Strava({ - client_id: process.env.CLIENT_ID, - client_secret: process.env.CLIENT_SECRET, - refresh_token: process.env.REFRESH_TOKEN, -}) + return token; +}; -const activities = await strava.activities.getLoggedInAthleteActivities() +const refreshToken = await loadRefreshToken(); -const items = alfy - .inputMatches(activities, 'name') - .map(activity => { +let output; + +if (!!refreshToken) { + const strava = new Strava({ + client_id: process.env.CLIENT_ID, + client_secret: process.env.CLIENT_SECRET, + refresh_token: refreshToken, + }); + + const activities = await strava.activities.getLoggedInAthleteActivities(); + + output = alfy.inputMatches(activities, "name").map((activity) => { return { title: activity.name, - subtitle: `${formatDistance(activity.distance)} (${formatTime(activity.start_date)})`, - arg: activity.id - } + subtitle: `${formatDistance(activity.distance)} (${formatTime( + activity.start_date + )})`, + arg: activity.id, + }; }); +} else { + output = [ + { + title: "Please check your workflow configuration!", + subtitle: "Confirm your values for CLIENT_ID, CLIENT_SECRET, and CODE.", + }, + ]; +} -alfy.output(items); +alfy.output(output); diff --git a/package-lock.json b/package-lock.json index 633e03c..47ad5f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,10 +7,12 @@ "": { "name": "alfred-strava", "version": "1.0.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { "alfy": "^1.0.0", "moment": "^2.29.4", + "node-fetch": "^3.2.10", "strava": "^2.0.1" } }, @@ -1138,6 +1140,14 @@ "node": ">=0.10.0" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "engines": { + "node": ">= 12" + } + }, "node_modules/debounce-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", @@ -1332,6 +1342,28 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -1376,6 +1408,17 @@ "node": ">= 14.17" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1933,15 +1976,39 @@ "node": "*" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/normalize-package-data": { @@ -2765,6 +2832,17 @@ "node": ">=10" } }, + "node_modules/strava/node_modules/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -2930,6 +3008,14 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -3842,6 +3928,11 @@ "array-find-index": "^1.0.1" } }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" + }, "debounce-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", @@ -3974,6 +4065,15 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -4008,6 +4108,14 @@ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.0.tgz", "integrity": "sha512-njK60LnfhfDWy+AEUIf9ZQNRAcmXCdDfiNOm2emuPtzwh7U9k/mo9F3S54aPiaZ3vhqUjikVLfcPg2KuBddskQ==" }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4418,12 +4526,19 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "requires": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "normalize-package-data": { @@ -5000,6 +5115,16 @@ "requires": { "form-data": "^4.0.0", "node-fetch": "2.6.6" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "strip-ansi": { @@ -5115,6 +5240,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 02ba9da..a53e2ee 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "alfy": "^1.0.0", "moment": "^2.29.4", + "node-fetch": "^3.2.10", "strava": "^2.0.1" } }