{"action":"created","comment":{"url":"https://api.github.com/repos/github/hubot/pulls/comments/28735409","id":28735409,"diff_hunk":"@@ -17,6 +17,8 @@ describe 'Listener', ->\n     @robot =\n       # Why is this part of the Robot object??\n       Response: Response\n+      runPrelistenHooks: (listener, response) ->\n+        listener.callback(response)","path":"test/listener_test.coffee","position":5,"original_position":5,"commit_id":"a5d7a697d3cff5c2d8c30e77c02c599168bee426","original_commit_id":"a5d7a697d3cff5c2d8c30e77c02c599168bee426","user":{"login":"bhuga","id":12676,"avatar_url":"https://avatars.githubusercontent.com/u/12676?v=3","gravatar_id":"","url":"https://api.github.com/users/bhuga","html_url":"https://github.com/bhuga","followers_url":"https://api.github.com/users/bhuga/followers","following_url":"https://api.github.com/users/bhuga/following{/other_user}","gists_url":"https://api.github.com/users/bhuga/gists{/gist_id}","starred_url":"https://api.github.com/users/bhuga/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bhuga/subscriptions","organizations_url":"https://api.github.com/users/bhuga/orgs","repos_url":"https://api.github.com/users/bhuga/repos","events_url":"https://api.github.com/users/bhuga/events{/privacy}","received_events_url":"https://api.github.com/users/bhuga/received_events","type":"User","site_admin":true},"body":"We're stubbing out response, so I have worsened the problem for now. If we like the direction I would try to refactor the hook handling out of the listener and let the robot handle it all, and this could go away.","created_at":"2015-04-20T22:21:38Z","updated_at":"2015-04-20T22:21:38Z","html_url":"https://github.com/github/hubot/pull/924#discussion_r28735409","pull_request_url":"https://api.github.com/repos/github/hubot/pulls/924","_links":{"self":{"href":"https://api.github.com/repos/github/hubot/pulls/comments/28735409"},"html":{"href":"https://github.com/github/hubot/pull/924#discussion_r28735409"},"pull_request":{"href":"https://api.github.com/repos/github/hubot/pulls/924"}}},"pull_request":{"url":"https://api.github.com/repos/github/hubot/pulls/924","id":33712039,"html_url":"https://github.com/github/hubot/pull/924","diff_url":"https://github.com/github/hubot/pull/924.diff","patch_url":"https://github.com/github/hubot/pull/924.patch","issue_url":"https://api.github.com/repos/github/hubot/issues/924","number":924,"state":"open","locked":false,"title":"Hooks","user":{"login":"bhuga","id":12676,"avatar_url":"https://avatars.githubusercontent.com/u/12676?v=3","gravatar_id":"","url":"https://api.github.com/users/bhuga","html_url":"https://github.com/bhuga","followers_url":"https://api.github.com/users/bhuga/followers","following_url":"https://api.github.com/users/bhuga/following{/other_user}","gists_url":"https://api.github.com/users/bhuga/gists{/gist_id}","starred_url":"https://api.github.com/users/bhuga/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bhuga/subscriptions","organizations_url":"https://api.github.com/users/bhuga/orgs","repos_url":"https://api.github.com/users/bhuga/repos","events_url":"https://api.github.com/users/bhuga/events{/privacy}","received_events_url":"https://api.github.com/users/bhuga/received_events","type":"User","site_admin":true},"body":"Hooks\r\n\r\nThis is a work-in-progress. I'm opening it early, because there's several parts I'd like feedback on.\r\n\r\nThis is inspired heavily by @michaelansel's work over in https://github.com/github/hubot/pull/803. We have been abusing `robot.respond /.*/` quite a bit in order to accomplish similar goals, and that pull was eye-opening. This pull does not include an equivalent of the listener metadata from that pull, because that pull does it so elegantly it does not seem worthwhile to revisit. We should get that into master no matter what.\r\n\r\nThe middleware itself, however, was a little uncomfortable for a few of our use cases. I played around with it and implemented a few things and came up with these issues:\r\n\r\n * They only apply to one phase of a message, \"when a listener has matched.\" I found distinct use cases for at least 3 places (read on).\r\n * The name 'middleware' has a lot of context from its use in e.g. express, but these do a different thing. They don't have access to responses, for example, and an incoming message can create several responses. It's a hook, not a middleware.\r\n * It makes listeners async. This complicates testing and would be breaking the API enough that I think it would require calling it 3.0.\r\n * It added a dependency, and I always want to examine that closely.\r\n\r\nThis pull adds support for 3 hooks: `prereceive`, `prelisten`, and `prereply`. They run before any listeners have executed, before an individual listener executes, and before any message is sent, respectively.\r\n\r\nAn example of a `prereceive` hook that blacklists a room but for certain users:\r\n\r\n```\r\n  robot.prereceive (hook) ->\r\n    if blacklistedRoom(hook.message) && !whitelistedUser(hook.message)\r\n      if hook.message.text.match(/^(\\/|hubot)/) # if the message starts with / or 'hubot'\r\n        hook.response.reply \"I'm sorry, but I'm configured to ignore commands from this room.\"\r\n      # But ignore everything, even hear commands.\r\n      hook.finish()\r\n```\r\n\r\nAn example of a `prelisten` hook, taken from #803. It assumes the listener middleware, which is not in this pull:\r\n\r\n```\r\n # node-statsd like interface\r\n  stats =\r\n    increment: (key) ->\r\n      console.log \"#{key}:1|c\"\r\n      # noop\r\nrobot.prelisten (hook) ->\r\n    listenerId = hook.listener.options?.id\r\n    if listenerId?\r\n      stats.increment \"hubot.listeners.total.#{listenerId}\"\r\n      if hook.response.message.user?.name?\r\n        stats.increment \"hubot.listeners.by_user.#{hook.response.message.user.name}.#{listenerId}\"\r\n      if hook.response.message.user?.room?\r\n        stats.increment \"hubot.listeners.by_room.#{hook.response.message.user.room}.#{listenerId}\"\r\n```\r\n\r\nAn example of a `prereply` hook. This prevents sending a message that matches `/passwords/`:\r\n```\r\nrobot.prereply (hook) ->\r\n  if hook.reply.text.match(/passwords/)\r\n    hook.finish()\r\n```\r\n\r\nAnd this changes the outgoing message:\r\n```\r\nrobot.prereply (hook) ->\r\n  if hook.reply.text.match(/passwords/)\r\n    hook.reply.text = \"Sorry meatbag, no passwords.\"\r\n```\r\n\r\nI am pretty happy with the hook API as used, but I still need to do some more testing. I'm very interested in feedback, though.\r\n\r\nI'm less happy with what happened in the guts. There are a few cases where things could be cleaned up internally if we refactored a few things. For example, `Response`s don't know about their `Listener`s, so a `prereceive` hook constructor needs a response, likewise a `prelistener` and its `Listener`. So the Hook API needs to handle both, but if responses had an when a smarter API might let it handle them a little more elegantly. If we like this direction, this pull would get a bit bigger as I did some reworking internally.\r\n\r\ncc @technicalpickles ","created_at":"2015-04-20T22:17:44Z","updated_at":"2015-04-20T22:21:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e75174784d7dce79fafa77bd2386ab07682489b6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/github/hubot/pulls/924/commits","review_comments_url":"https://api.github.com/repos/github/hubot/pulls/924/comments","review_comment_url":"https://api.github.com/repos/github/hubot/pulls/comments{/number}","comments_url":"https://api.github.com/repos/github/hubot/issues/924/comments","statuses_url":"https://api.github.com/repos/github/hubot/statuses/a5d7a697d3cff5c2d8c30e77c02c599168bee426","head":{"label":"github:hooks","ref":"hooks","sha":"a5d7a697d3cff5c2d8c30e77c02c599168bee426","user":{"login":"github","id":9919,"avatar_url":"https://avatars.githubusercontent.com/u/9919?v=3","gravatar_id":"","url":"https://api.github.com/users/github","html_url":"https://github.com/github","followers_url":"https://api.github.com/users/github/followers","following_url":"https://api.github.com/users/github/following{/other_user}","gists_url":"https://api.github.com/users/github/gists{/gist_id}","starred_url":"https://api.github.com/users/github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github/subscriptions","organizations_url":"https://api.github.com/users/github/orgs","repos_url":"https://api.github.com/users/github/repos","events_url":"https://api.github.com/users/github/events{/privacy}","received_events_url":"https://api.github.com/users/github/received_events","type":"Organization","site_admin":false},"repo":{"id":2278524,"name":"hubot","full_name":"github/hubot","owner":{"login":"github","id":9919,"avatar_url":"https://avatars.githubusercontent.com/u/9919?v=3","gravatar_id":"","url":"https://api.github.com/users/github","html_url":"https://github.com/github","followers_url":"https://api.github.com/users/github/followers","following_url":"https://api.github.com/users/github/following{/other_user}","gists_url":"https://api.github.com/users/github/gists{/gist_id}","starred_url":"https://api.github.com/users/github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github/subscriptions","organizations_url":"https://api.github.com/users/github/orgs","repos_url":"https://api.github.com/users/github/repos","events_url":"https://api.github.com/users/github/events{/privacy}","received_events_url":"https://api.github.com/users/github/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/github/hubot","description":"A customizable life embetterment robot.","fork":false,"url":"https://api.github.com/repos/github/hubot","forks_url":"https://api.github.com/repos/github/hubot/forks","keys_url":"https://api.github.com/repos/github/hubot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/github/hubot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/github/hubot/teams","hooks_url":"https://api.github.com/repos/github/hubot/hooks","issue_events_url":"https://api.github.com/repos/github/hubot/issues/events{/number}","events_url":"https://api.github.com/repos/github/hubot/events","assignees_url":"https://api.github.com/repos/github/hubot/assignees{/user}","branches_url":"https://api.github.com/repos/github/hubot/branches{/branch}","tags_url":"https://api.github.com/repos/github/hubot/tags","blobs_url":"https://api.github.com/repos/github/hubot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/github/hubot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/github/hubot/git/refs{/sha}","trees_url":"https://api.github.com/repos/github/hubot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/github/hubot/statuses/{sha}","languages_url":"https://api.github.com/repos/github/hubot/languages","stargazers_url":"https://api.github.com/repos/github/hubot/stargazers","contributors_url":"https://api.github.com/repos/github/hubot/contributors","subscribers_url":"https://api.github.com/repos/github/hubot/subscribers","subscription_url":"https://api.github.com/repos/github/hubot/subscription","commits_url":"https://api.github.com/repos/github/hubot/commits{/sha}","git_commits_url":"https://api.github.com/repos/github/hubot/git/commits{/sha}","comments_url":"https://api.github.com/repos/github/hubot/comments{/number}","issue_comment_url":"https://api.github.com/repos/github/hubot/issues/comments{/number}","contents_url":"https://api.github.com/repos/github/hubot/contents/{+path}","compare_url":"https://api.github.com/repos/github/hubot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/github/hubot/merges","archive_url":"https://api.github.com/repos/github/hubot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/github/hubot/downloads","issues_url":"https://api.github.com/repos/github/hubot/issues{/number}","pulls_url":"https://api.github.com/repos/github/hubot/pulls{/number}","milestones_url":"https://api.github.com/repos/github/hubot/milestones{/number}","notifications_url":"https://api.github.com/repos/github/hubot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/github/hubot/labels{/name}","releases_url":"https://api.github.com/repos/github/hubot/releases{/id}","created_at":"2011-08-27T08:30:59Z","updated_at":"2015-04-20T19:37:43Z","pushed_at":"2015-04-20T22:17:45Z","git_url":"git://github.com/github/hubot.git","ssh_url":"git@github.com:github/hubot.git","clone_url":"https://github.com/github/hubot.git","svn_url":"https://github.com/github/hubot","homepage":"http://hubot.github.com","size":5928,"stargazers_count":7832,"watchers_count":7832,"language":"CoffeeScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1695,"mirror_url":null,"open_issues_count":84,"forks":1695,"open_issues":84,"watchers":7832,"default_branch":"master"}},"base":{"label":"github:master","ref":"master","sha":"58eb23c87e6b4b637fdad73308f4c7d48686f2af","user":{"login":"github","id":9919,"avatar_url":"https://avatars.githubusercontent.com/u/9919?v=3","gravatar_id":"","url":"https://api.github.com/users/github","html_url":"https://github.com/github","followers_url":"https://api.github.com/users/github/followers","following_url":"https://api.github.com/users/github/following{/other_user}","gists_url":"https://api.github.com/users/github/gists{/gist_id}","starred_url":"https://api.github.com/users/github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github/subscriptions","organizations_url":"https://api.github.com/users/github/orgs","repos_url":"https://api.github.com/users/github/repos","events_url":"https://api.github.com/users/github/events{/privacy}","received_events_url":"https://api.github.com/users/github/received_events","type":"Organization","site_admin":false},"repo":{"id":2278524,"name":"hubot","full_name":"github/hubot","owner":{"login":"github","id":9919,"avatar_url":"https://avatars.githubusercontent.com/u/9919?v=3","gravatar_id":"","url":"https://api.github.com/users/github","html_url":"https://github.com/github","followers_url":"https://api.github.com/users/github/followers","following_url":"https://api.github.com/users/github/following{/other_user}","gists_url":"https://api.github.com/users/github/gists{/gist_id}","starred_url":"https://api.github.com/users/github/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/github/subscriptions","organizations_url":"https://api.github.com/users/github/orgs","repos_url":"https://api.github.com/users/github/repos","events_url":"https://api.github.com/users/github/events{/privacy}","received_events_url":"https://api.github.com/users/github/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/github/hubot","description":"A customizable life embetterment robot.","fork":false,"url":"https://api.github.com/repos/github/hubot","forks_url":"https://api.github.com/repos/github/hubot/forks","keys_url":"https://api.github.com/repos/github/hubot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/github/hubot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/github/hubot/teams","hooks_url":"https://api.github.com/repos/github/hubot/hooks","issue_events_url":"https://api.github.com/repos/github/hubot/issues/events{/number}","events_url":"https://api.github.com/repos/github/hubot/events","assignees_url":"https://api.github.com/repos/github/hubot/assignees{/user}","branches_url":"https://api.github.com/repos/github/hubot/branches{/branch}","tags_url":"https://api.github.com/repos/github/hubot/tags","blobs_url":"https://api.github.com/repos/github/hubot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/github/hubot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/github/hubot/git/refs{/sha}","trees_url":"https://api.github.com/repos/github/hubot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/github/hubot/statuses/{sha}","languages_url":"https://api.github.com/repos/github/hubot/languages","stargazers_url":"https://api.github.com/repos/github/hubot/stargazers","contributors_url":"https://api.github.com/repos/github/hubot/contributors","subscribers_url":"https://api.github.com/repos/github/hubot/subscribers","subscription_url":"https://api.github.com/repos/github/hubot/subscription","commits_url":"https://api.github.com/repos/github/hubot/commits{/sha}","git_commits_url":"https://api.github.com/repos/github/hubot/git/commits{/sha}","comments_url":"https://api.github.com/repos/github/hubot/comments{/number}","issue_comment_url":"https://api.github.com/repos/github/hubot/issues/comments{/number}","contents_url":"https://api.github.com/repos/github/hubot/contents/{+path}","compare_url":"https://api.github.com/repos/github/hubot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/github/hubot/merges","archive_url":"https://api.github.com/repos/github/hubot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/github/hubot/downloads","issues_url":"https://api.github.com/repos/github/hubot/issues{/number}","pulls_url":"https://api.github.com/repos/github/hubot/pulls{/number}","milestones_url":"https://api.github.com/repos/github/hubot/milestones{/number}","notifications_url":"https://api.github.com/repos/github/hubot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/github/hubot/labels{/name}","releases_url":"https://api.github.com/repos/github/hubot/releases{/id}","created_at":"2011-08-27T08:30:59Z","updated_at":"2015-04-20T19:37:43Z","pushed_at":"2015-04-20T22:17:45Z","git_url":"git://github.com/github/hubot.git","ssh_url":"git@github.com:github/hubot.git","clone_url":"https://github.com/github/hubot.git","svn_url":"https://github.com/github/hubot","homepage":"http://hubot.github.com","size":5928,"stargazers_count":7832,"watchers_count":7832,"language":"CoffeeScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1695,"mirror_url":null,"open_issues_count":84,"forks":1695,"open_issues":84,"watchers":7832,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/github/hubot/pulls/924"},"html":{"href":"https://github.com/github/hubot/pull/924"},"issue":{"href":"https://api.github.com/repos/github/hubot/issues/924"},"comments":{"href":"https://api.github.com/repos/github/hubot/issues/924/comments"},"review_comments":{"href":"https://api.github.com/repos/github/hubot/pulls/924/comments"},"review_comment":{"href":"https://api.github.com/repos/github/hubot/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/github/hubot/pulls/924/commits"},"statuses":{"href":"https://api.github.com/repos/github/hubot/statuses/a5d7a697d3cff5c2d8c30e77c02c599168bee426"}}}}