diff --git a/AWS_SETUP.md b/AWS_SETUP.md index ffbcdbc..3f47aef 100644 --- a/AWS_SETUP.md +++ b/AWS_SETUP.md @@ -65,12 +65,24 @@ 8. Navigate to the [`Tokens`](https://github.com/settings/tokens) page in your `GitHub` account settings 9. Create a new API Token, and give it the `Repo` permissions. Make sure to save this token! +### Twitter + +1. Visit [Twitter Apps console](https://apps.twitter.com/) +2. Click on `Create New App` +3. In Application Details, enter `Name` as `babel-bot`, enter `Description` as `babel-bot twitter app`. +4. For `Website` enter `http://localhost:3000` +5. For `Callback URL` enter `http://localhost:3000/cb` +6. Click on `Create your Twitter application` +7. Verify you were redirected to a page with `babel-bot` as header +8. Navigate to `Keys and Access Tokens` +9. Copy and `Consumer Key (API Key)`, `Consumer Secret (API Secret)`, `Access Token` and `Access Token Secret` + ### Final Steps 1. Go back to the `Lambda` dashboard in `AWS`, and click on your function to edit it 2. For `Code entry type`, choose `Upload a .ZIP file` 3. Run `yarn run package` in the root of this repository (make sure you've run `yarn` first), then upload the `function.zip` file in the root -4. Under `Environment variables`, add a variable for `GITHUB_API_KEY` and `GITHUB_SECRET`, using the values you setup in the `GitHub` section of this document +4. Under `Environment variables`, add a variable for `GITHUB_API_KEY` and `GITHUB_SECRET`, using the values you setup in the `GitHub` section of this document and add `TWITTER_CONSUMER_KEY`, `TWITTER_CONSUMER_SECRET`, `TWITTER_ACCESS_TOKEN_KEY`, `TWITTER_ACCESS_TOKEN_SECRET` using the values you setup in the `Twitter Apps console` 5. Click `Save` If everything was done correctly, your bot should now be live! To see the execution logs, you can click the `Monitoring` tab on your function page in `Lambda`, then click `View logs in CloudWatch`. diff --git a/package.json b/package.json index e82f0d4..08d4b01 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "common-tags": "^1.4.0", "got": "^6.6.3", "parse-diff": "^0.4.0", - "timing-safe-equal": "^1.0.0" + "timing-safe-equal": "^1.0.0", + "twitter": "^1.7.1" }, "devDependencies": { "aws-sdk": "^2.95.0", diff --git a/src/handlers/issues/__tests__/__fixtures__/beginner-friendly-label-added.json b/src/handlers/issues/__tests__/__fixtures__/beginner-friendly-label-added.json new file mode 100644 index 0000000..fb06913 --- /dev/null +++ b/src/handlers/issues/__tests__/__fixtures__/beginner-friendly-label-added.json @@ -0,0 +1,166 @@ +{ + "action": "labeled", + "issue": { + "url": "https://api.github.com/repos/DrewML/babel/issues/1", + "repository_url": "https://api.github.com/repos/DrewML/babel", + "labels_url": "https://api.github.com/repos/DrewML/babel/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/DrewML/babel/issues/1/comments", + "events_url": "https://api.github.com/repos/DrewML/babel/issues/1/events", + "html_url": "https://github.com/DrewML/babel/issues/1", + "id": 196255164, + "number": 1, + "title": "Test issue", + "user": { + "login": "DrewML", + "id": 5233399, + "avatar_url": "https://avatars.githubusercontent.com/u/5233399?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/DrewML", + "html_url": "https://github.com/DrewML", + "followers_url": "https://api.github.com/users/DrewML/followers", + "following_url": "https://api.github.com/users/DrewML/following{/other_user}", + "gists_url": "https://api.github.com/users/DrewML/gists{/gist_id}", + "starred_url": "https://api.github.com/users/DrewML/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/DrewML/subscriptions", + "organizations_url": "https://api.github.com/users/DrewML/orgs", + "repos_url": "https://api.github.com/users/DrewML/repos", + "events_url": "https://api.github.com/users/DrewML/events{/privacy}", + "received_events_url": "https://api.github.com/users/DrewML/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 502778608, + "url": "https://api.github.com/repos/DrewML/babel/labels/beginner-friendly", + "name": "Needs Info", + "color": "fbca04", + "default": false + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2016-12-18T02:59:51Z", + "updated_at": "2016-12-18T03:16:49Z", + "closed_at": null, + "body": "" + }, + "label": { + "id": 502778608, + "url": "https://api.github.com/repos/DrewML/babel/labels/beginner-friendly", + "name": "beginner-friendly", + "color": "fbca04", + "default": false + }, + "repository": { + "id": 76754829, + "name": "babel", + "full_name": "DrewML/babel", + "owner": { + "login": "DrewML", + "id": 5233399, + "avatar_url": "https://avatars.githubusercontent.com/u/5233399?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/DrewML", + "html_url": "https://github.com/DrewML", + "followers_url": "https://api.github.com/users/DrewML/followers", + "following_url": "https://api.github.com/users/DrewML/following{/other_user}", + "gists_url": "https://api.github.com/users/DrewML/gists{/gist_id}", + "starred_url": "https://api.github.com/users/DrewML/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/DrewML/subscriptions", + "organizations_url": "https://api.github.com/users/DrewML/orgs", + "repos_url": "https://api.github.com/users/DrewML/repos", + "events_url": "https://api.github.com/users/DrewML/events{/privacy}", + "received_events_url": "https://api.github.com/users/DrewML/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/DrewML/babel", + "description": ":tropical_fish: Babel is a compiler for writing next generation JavaScript.", + "fork": true, + "url": "https://api.github.com/repos/DrewML/babel", + "forks_url": "https://api.github.com/repos/DrewML/babel/forks", + "keys_url": "https://api.github.com/repos/DrewML/babel/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/DrewML/babel/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/DrewML/babel/teams", + "hooks_url": "https://api.github.com/repos/DrewML/babel/hooks", + "issue_events_url": "https://api.github.com/repos/DrewML/babel/issues/events{/number}", + "events_url": "https://api.github.com/repos/DrewML/babel/events", + "assignees_url": "https://api.github.com/repos/DrewML/babel/assignees{/user}", + "branches_url": "https://api.github.com/repos/DrewML/babel/branches{/branch}", + "tags_url": "https://api.github.com/repos/DrewML/babel/tags", + "blobs_url": "https://api.github.com/repos/DrewML/babel/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/DrewML/babel/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/DrewML/babel/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/DrewML/babel/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/DrewML/babel/statuses/{sha}", + "languages_url": "https://api.github.com/repos/DrewML/babel/languages", + "stargazers_url": "https://api.github.com/repos/DrewML/babel/stargazers", + "contributors_url": "https://api.github.com/repos/DrewML/babel/contributors", + "subscribers_url": "https://api.github.com/repos/DrewML/babel/subscribers", + "subscription_url": "https://api.github.com/repos/DrewML/babel/subscription", + "commits_url": "https://api.github.com/repos/DrewML/babel/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/DrewML/babel/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/DrewML/babel/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/DrewML/babel/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/DrewML/babel/contents/{+path}", + "compare_url": "https://api.github.com/repos/DrewML/babel/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/DrewML/babel/merges", + "archive_url": "https://api.github.com/repos/DrewML/babel/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/DrewML/babel/downloads", + "issues_url": "https://api.github.com/repos/DrewML/babel/issues{/number}", + "pulls_url": "https://api.github.com/repos/DrewML/babel/pulls{/number}", + "milestones_url": "https://api.github.com/repos/DrewML/babel/milestones{/number}", + "notifications_url": "https://api.github.com/repos/DrewML/babel/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/DrewML/babel/labels{/name}", + "releases_url": "https://api.github.com/repos/DrewML/babel/releases{/id}", + "deployments_url": "https://api.github.com/repos/DrewML/babel/deployments", + "created_at": "2016-12-18T00:27:53Z", + "updated_at": "2016-12-18T02:59:42Z", + "pushed_at": "2016-12-17T15:37:46Z", + "git_url": "git://github.com/DrewML/babel.git", + "ssh_url": "git@github.com:DrewML/babel.git", + "clone_url": "https://github.com/DrewML/babel.git", + "svn_url": "https://github.com/DrewML/babel", + "homepage": "https://babeljs.io/", + "size": 15672, + "stargazers_count": 0, + "watchers_count": 0, + "language": "JavaScript", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "DrewML", + "id": 5233399, + "avatar_url": "https://avatars.githubusercontent.com/u/5233399?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/DrewML", + "html_url": "https://github.com/DrewML", + "followers_url": "https://api.github.com/users/DrewML/followers", + "following_url": "https://api.github.com/users/DrewML/following{/other_user}", + "gists_url": "https://api.github.com/users/DrewML/gists{/gist_id}", + "starred_url": "https://api.github.com/users/DrewML/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/DrewML/subscriptions", + "organizations_url": "https://api.github.com/users/DrewML/orgs", + "repos_url": "https://api.github.com/users/DrewML/repos", + "events_url": "https://api.github.com/users/DrewML/events{/privacy}", + "received_events_url": "https://api.github.com/users/DrewML/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/handlers/issues/__tests__/labeled.js b/src/handlers/issues/__tests__/labeled.js index 1dbfcc8..eb16f50 100644 --- a/src/handlers/issues/__tests__/labeled.js +++ b/src/handlers/issues/__tests__/labeled.js @@ -5,9 +5,11 @@ import fs from 'fs'; const wrongLabelPayload = require('./__fixtures__/other-label-added.json'); const needsInfoPayload = require('./__fixtures__/info-label-added.json'); +const beginnerFriendlyPayload = require('./__fixtures__/beginner-friendly-label-added.json'); jest.mock('../../../github'); +jest.mock('../../../twitter'); -describe('Issue Opened Handler', () => { +describe('Issue Labeled Handler', () => { it('should not add a comment if label !== "Needs Info"', () => { handler(wrongLabelPayload); const github = require('../../../github'); @@ -19,4 +21,16 @@ describe('Issue Opened Handler', () => { const github = require('../../../github'); expect(github.addIssueComment).toHaveBeenCalledTimes(1); }); + + it('should not tweet if label !== "beginner-friendly"', () => { + handler(wrongLabelPayload); + const twitter = require('../../../twitter'); + expect(twitter.tweet).toHaveBeenCalledTimes(0); + }); + + it('should tweet if label === "beginner-friendly"', () => { + handler(beginnerFriendlyPayload); + const twitter = require('../../../twitter'); + expect(twitter.tweet).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/handlers/issues/labeled.js b/src/handlers/issues/labeled.js index 5f2ce19..286381b 100644 --- a/src/handlers/issues/labeled.js +++ b/src/handlers/issues/labeled.js @@ -1,11 +1,13 @@ // @flow import github from '../../github'; +import twitter from '../../twitter'; import messages from '../../messages'; type LabeledIssuePayload = { issue: { user: { login: string; }; + html_url: string; number: number; }, label: { @@ -19,11 +21,15 @@ type LabeledIssuePayload = { export default function({ label, issue, repository }: LabeledIssuePayload) { const issueSubmitter = issue.user.login; + const issueHtmlUrl = issue.html_url; const { login: owner } = repository.owner; const { name: repo } = repository; if (label.name === 'Needs Info') { const msg = messages.needsInfo(issueSubmitter); github.addIssueComment(issue.number, owner, repo, msg); + } else if(label.name === 'beginner-friendly') { + const msg = messages.tweetIssue(issueHtmlUrl); + twitter.tweet(msg); } } diff --git a/src/messages.js b/src/messages.js index 708a5c9..82648cd 100644 --- a/src/messages.js +++ b/src/messages.js @@ -31,6 +31,11 @@ exports.newIssue = (username: string) => stripIndent` for an invite. `; +exports.tweetIssue = (issueURL: string) => stripIndent` + If you're looking to contribute to Babel, we have another beginner-friendly issue for you ! 📄 + ${issueURL} +`; + exports.replPreview = (url: string) => stripIndent` Build successful! You can test your changes in the REPL here: ${url} `; diff --git a/src/twitter.js b/src/twitter.js new file mode 100644 index 0000000..b242c03 --- /dev/null +++ b/src/twitter.js @@ -0,0 +1,14 @@ +// @flow + +import Twitter from 'twitter'; + +const client = new Twitter({ + consumer_key: process.env.TWITTER_CONSUMER_KEY, + consumer_secret: process.env.TWITTER_CONSUMER_SECRET, + access_token_key: process.env.TWITTER_ACCESS_TOKEN_KEY, + access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET +}); + +exports.tweet = (msg: string) => { + return client.post('statuses/update', { status: msg }).then((tweet) => tweet); +} diff --git a/yarn.lock b/yarn.lock index d46adca..8b94db0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -448,21 +448,21 @@ babel-register@^6.18.0: mkdirp "^0.5.1" source-map-support "^0.4.2" -babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.20.0, babel-runtime@^6.9.0, babel-runtime@^6.9.1: - version "6.20.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.20.0.tgz#87300bdcf4cd770f09bf0048c64204e17806d16f" +babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.9.1: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" dependencies: core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-runtime@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" +babel-runtime@^6.18.0, babel-runtime@^6.9.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.20.0.tgz#87300bdcf4cd770f09bf0048c64204e17806d16f" dependencies: core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-template@^6.15.0, babel-template@^6.16.0, babel-template@^6.8.0: +babel-template@^6.15.0, babel-template@^6.16.0: version "6.16.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.16.0.tgz#e149dd1a9f03a35f817ddbc4d0481988e7ebc8ca" dependencies: @@ -472,7 +472,7 @@ babel-template@^6.15.0, babel-template@^6.16.0, babel-template@^6.8.0: babylon "^6.11.0" lodash "^4.2.0" -babel-template@^6.22.0, babel-template@^6.23.0: +babel-template@^6.22.0, babel-template@^6.23.0, babel-template@^6.8.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638" dependencies: @@ -482,21 +482,7 @@ babel-template@^6.22.0, babel-template@^6.23.0: babylon "^6.11.0" lodash "^4.2.0" -babel-traverse@^6.16.0, babel-traverse@^6.18.0, babel-traverse@^6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.21.0.tgz#69c6365804f1a4f69eb1213f85b00a818b8c21ad" - dependencies: - babel-code-frame "^6.20.0" - babel-messages "^6.8.0" - babel-runtime "^6.20.0" - babel-types "^6.21.0" - babylon "^6.11.0" - debug "^2.2.0" - globals "^9.0.0" - invariant "^2.2.0" - lodash "^4.2.0" - -babel-traverse@^6.22.0, babel-traverse@^6.23.0: +babel-traverse@^6.16.0, babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0: version "6.23.1" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48" dependencies: @@ -510,16 +496,21 @@ babel-traverse@^6.22.0, babel-traverse@^6.23.0: invariant "^2.2.0" lodash "^4.2.0" -babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.21.0: +babel-traverse@^6.21.0: version "6.21.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.21.0.tgz#314b92168891ef6d3806b7f7a917fdf87c11a4b2" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.21.0.tgz#69c6365804f1a4f69eb1213f85b00a818b8c21ad" dependencies: + babel-code-frame "^6.20.0" + babel-messages "^6.8.0" babel-runtime "^6.20.0" - esutils "^2.0.2" + babel-types "^6.21.0" + babylon "^6.11.0" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" lodash "^4.2.0" - to-fast-properties "^1.0.1" -babel-types@^6.22.0, babel-types@^6.23.0: +babel-types@^6.16.0, babel-types@^6.22.0, babel-types@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf" dependencies: @@ -528,11 +519,20 @@ babel-types@^6.22.0, babel-types@^6.23.0: lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.11.0, babylon@^6.13.0: +babel-types@^6.18.0, babel-types@^6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.21.0.tgz#314b92168891ef6d3806b7f7a917fdf87c11a4b2" + dependencies: + babel-runtime "^6.20.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.11.0: version "6.14.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.14.1.tgz#956275fab72753ad9b3435d7afe58f8bf0a29815" -babylon@^6.15.0: +babylon@^6.13.0, babylon@^6.15.0: version "6.16.1" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" @@ -831,6 +831,10 @@ decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +deep-extend@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.0.tgz#6ef4a09b05f98b0e358d6d93d4ca3caec6672803" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -2500,7 +2504,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.55.0, request@^2.79.0: +request@^2.55.0, request@^2.72.0, request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -2844,6 +2848,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" +twitter@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/twitter/-/twitter-1.7.1.tgz#0762378f1dc1c050e48f666aca904e24b1a962f4" + dependencies: + deep-extend "^0.5.0" + request "^2.72.0" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"