├── .gitignore ├── f ├── webhook │ ├── function.json │ ├── mongo.js │ ├── hacker-news.js │ └── index.js ├── auth │ ├── function.json │ ├── index.js │ └── view.ejs └── auth_callback │ ├── function.json │ ├── view.ejs │ ├── mongo.js │ └── index.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | env.json 3 | run.js 4 | -------------------------------------------------------------------------------- /f/webhook/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webhook", 3 | "description": "Function", 4 | "args": [ 5 | "First argument", 6 | "Second argument" 7 | ], 8 | "kwargs": { 9 | "alpha": "Keyword argument alpha", 10 | "beta": "Keyword argument beta" 11 | }, 12 | "http": { 13 | "headers": {} 14 | } 15 | } -------------------------------------------------------------------------------- /f/auth/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "description": "Function", 4 | "args": [ 5 | "First argument", 6 | "Second argument" 7 | ], 8 | "kwargs": { 9 | "alpha": "Keyword argument alpha", 10 | "beta": "Keyword argument beta" 11 | }, 12 | "http": { 13 | "headers": { 14 | "Content-Type": "text/html" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /f/auth_callback/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth_callback", 3 | "description": "Function", 4 | "args": [ 5 | "First argument", 6 | "Second argument" 7 | ], 8 | "kwargs": { 9 | "alpha": "Keyword argument alpha", 10 | "beta": "Keyword argument beta" 11 | }, 12 | "http": { 13 | "headers": { 14 | "Content-Type": "text/html" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hacker News Bot 2 | [![stdlib.com service](https://img.shields.io/badge/stdlib-0.1.7-green.svg?raw=true "stdlib.com service")](https://stdlib.com/services/nemo/slack-bot) 3 | 4 | This is a Hacker News Slack bot built on top of the official YC [Hacker News API](https://github.com/HackerNews/API) and run using [stdlib](https://stdlib.com/services/nemo/slack-bot). 5 | 6 | You can install it [here](https://f.stdlib.com/nemo/slack-bot). 7 | 8 | Contributions are welcome! Feel free to add more features to the bot :) 9 | 10 | # License 11 | MIT 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slack-bot", 3 | "version": "0.1.7", 4 | "description": "Hacker News Bot", 5 | "author": "nemo ", 6 | "main": "f/auth/index.js", 7 | "dependencies": { 8 | "@slack/client": "^3.6.1", 9 | "async": "^2.1.4", 10 | "botkit": "^0.4.2", 11 | "ejs": "^2.5.3", 12 | "hacker-news-api": "^2.0.0", 13 | "install": "^0.8.2", 14 | "monk": "^3.1.3", 15 | "request": "^2.79.0" 16 | }, 17 | "private": true, 18 | "stdlib": { 19 | "name": "nemo/slack-bot", 20 | "defaultFunction": "auth", 21 | "timeout": 10000, 22 | "publish": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /f/auth_callback/view.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hacker News Bot - Authentication 5 | 16 | 17 | 18 |

<%= message %>

19 |
20 | <%= content %> 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /f/auth/index.js: -------------------------------------------------------------------------------- 1 | var Request = require('request') 2 | var async = require('async'); 3 | var ejs = require('ejs'); 4 | var template = __dirname + "/view.ejs"; 5 | 6 | /** 7 | * Your function call 8 | * @param {Object} params Execution parameters 9 | * Members 10 | * - {Array} args Arguments passed to function 11 | * - {Object} kwargs Keyword arguments (key-value pairs) passed to function 12 | * - {String} remoteAddress The IPv4 or IPv6 address of the caller 13 | * 14 | * @param {Function} callback Execute this to end the function call 15 | * Arguments 16 | * - {Error} error The error to show if function fails 17 | * - {Any} returnValue JSON serializable (or Buffer) return value 18 | */ 19 | module.exports = (params, callback) => { 20 | ejs.renderFile(template, process.env, {}, (err, response) => callback(err, new Buffer(response || ''))); 21 | }; 22 | -------------------------------------------------------------------------------- /f/webhook/mongo.js: -------------------------------------------------------------------------------- 1 | var db = require('monk'); 2 | /** 3 | * botkit-storage-mongo - MongoDB driver for Botkit 4 | * 5 | * @param {Object} config 6 | * @return {Object} 7 | */ 8 | module.exports = function() { 9 | if (!process.env.MONGO_URL) throw new Error('Need to provide mongo address.'); 10 | 11 | var Teams = db(process.env.MONGO_URL).get('teams'), 12 | Users = db(process.env.MONGO_URL).get('users'), 13 | Channels = db(process.env.MONGO_URL).get('channels'); 14 | 15 | var unwrapFromList = function(cb) { 16 | return function(err, data) { 17 | if (err) return cb(err); 18 | cb(null, data); 19 | }; 20 | }; 21 | 22 | var storage = { 23 | teams: { 24 | get: function(id, cb) { 25 | Teams.findOne({id: id}, unwrapFromList(cb)); 26 | }, 27 | save: function(data, cb) { 28 | Teams.findOneAndUpdate({ 29 | id: data.id 30 | }, data, { 31 | upsert: true, 32 | new: true 33 | }, cb); 34 | }, 35 | all: function(cb) { 36 | Teams.find({}, cb); 37 | } 38 | }, 39 | users: { 40 | get: function(id, cb) { 41 | Users.findOne({id: id}, unwrapFromList(cb)); 42 | }, 43 | save: function(data, cb) { 44 | Users.findOneAndUpdate({ 45 | id: data.id 46 | }, data, { 47 | upsert: true, 48 | new: true 49 | }, cb); 50 | }, 51 | all: function(cb) { 52 | Users.find({}, cb); 53 | } 54 | }, 55 | channels: { 56 | get: function(id, cb) { 57 | Channels.findOne({id: id}, unwrapFromList(cb)); 58 | }, 59 | save: function(data, cb) { 60 | Channels.findOneAndUpdate({ 61 | id: data.id 62 | }, data, { 63 | upsert: true, 64 | new: true 65 | }, cb); 66 | }, 67 | all: function(cb) { 68 | Channels.find({}, cb); 69 | } 70 | } 71 | }; 72 | 73 | return storage; 74 | }; 75 | -------------------------------------------------------------------------------- /f/auth_callback/mongo.js: -------------------------------------------------------------------------------- 1 | var db = require('monk'); 2 | /** 3 | * botkit-storage-mongo - MongoDB driver for Botkit 4 | * 5 | * @param {Object} config 6 | * @return {Object} 7 | */ 8 | module.exports = function() { 9 | if (!process.env.MONGO_URL) throw new Error('Need to provide mongo address.'); 10 | 11 | var Teams = db(process.env.MONGO_URL).get('teams'), 12 | Users = db(process.env.MONGO_URL).get('users'), 13 | Channels = db(process.env.MONGO_URL).get('channels'); 14 | 15 | var unwrapFromList = function(cb) { 16 | return function(err, data) { 17 | if (err) return cb(err); 18 | cb(null, data); 19 | }; 20 | }; 21 | 22 | var storage = { 23 | teams: { 24 | get: function(id, cb) { 25 | Teams.findOne({id: id}, unwrapFromList(cb)); 26 | }, 27 | save: function(data, cb) { 28 | Teams.findOneAndUpdate({ 29 | id: data.id 30 | }, data, { 31 | upsert: true, 32 | new: true 33 | }, cb); 34 | }, 35 | all: function(cb) { 36 | Teams.find({}, cb); 37 | } 38 | }, 39 | users: { 40 | get: function(id, cb) { 41 | Users.findOne({id: id}, unwrapFromList(cb)); 42 | }, 43 | save: function(data, cb) { 44 | Users.findOneAndUpdate({ 45 | id: data.id 46 | }, data, { 47 | upsert: true, 48 | new: true 49 | }, cb); 50 | }, 51 | all: function(cb) { 52 | Users.find({}, cb); 53 | } 54 | }, 55 | channels: { 56 | get: function(id, cb) { 57 | Channels.findOne({id: id}, unwrapFromList(cb)); 58 | }, 59 | save: function(data, cb) { 60 | Channels.findOneAndUpdate({ 61 | id: data.id 62 | }, data, { 63 | upsert: true, 64 | new: true 65 | }, cb); 66 | }, 67 | all: function(cb) { 68 | Channels.find({}, cb); 69 | } 70 | } 71 | }; 72 | 73 | return storage; 74 | }; 75 | -------------------------------------------------------------------------------- /f/webhook/hacker-news.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var _ = require('lodash'); 3 | var Request = require('request'); 4 | var util = require('util'); 5 | var HN_API_BASE = "https://hacker-news.firebaseio.com/v0/"; 6 | 7 | exports.api = function(options, callback) { 8 | if (!options) throw new Error("HN.api.options is required"); 9 | if (!options.path) throw new Error("HN.api.options.path is required"); 10 | 11 | var urlPath = util.format("%s%s%s.json", HN_API_BASE, options.path, options.id ? ("/" + options.id) : ''); 12 | 13 | console.log("urlPath", urlPath); 14 | Request.get(urlPath, options.query || {}, (err, response, body) => { 15 | if (err) return callback(err); 16 | return callback(null, JSON.parse(body)); 17 | }) 18 | }; 19 | 20 | exports.item = function(id, callback) { 21 | exports.api({ 22 | path: 'item', 23 | id: id 24 | }, callback); 25 | }; 26 | 27 | exports.topStories = function(options, callback) { 28 | if (_.isFunction(options)) { 29 | callback = options; 30 | options = {}; 31 | } 32 | 33 | exports.api({ 34 | path: 'topstories', 35 | query: options || {} 36 | }, (err, storyIds) => { 37 | if (err) return callback(err); 38 | 39 | async.map(_.slice(storyIds, 0, 5), (storyId, cb) => exports.item(storyId, cb), callback) 40 | }); 41 | }; 42 | 43 | exports.newStories = function(options, callback) { 44 | if (_.isFunction(options)) { 45 | callback = options; 46 | options = {}; 47 | } 48 | 49 | exports.api({ 50 | path: 'newstories', 51 | query: options || {} 52 | }, (err, storyIds) => { 53 | if (err) return callback(err); 54 | 55 | async.map(_.slice(storyIds, 0, 5), (storyId, cb) => exports.item(storyId, cb), callback); 56 | }); 57 | }; 58 | 59 | exports.bestStories = function(options, callback) { 60 | if (_.isFunction(options)) { 61 | callback = options; 62 | options = {}; 63 | } 64 | 65 | exports.api({ 66 | path: 'beststories', 67 | query: options || {} 68 | }, (err, storyIds) => { 69 | if (err) return callback(err); 70 | 71 | async.map(_.slice(storyIds, 0, 5), (storyId, cb) => exports.item(storyId, cb), callback); 72 | }) 73 | }; 74 | 75 | exports.askStories = function(options, callback) { 76 | if (_.isFunction(options)) { 77 | callback = options; 78 | options = {}; 79 | } 80 | 81 | exports.api({ 82 | path: 'askstories', 83 | query: options || {} 84 | }, (err, storyIds) => { 85 | if (err) return callback(err); 86 | 87 | async.map(_.slice(storyIds, 0, 5), (storyId, cb) => exports.item(storyId, cb), callback); 88 | }) 89 | }; 90 | 91 | 92 | exports.showStories = function(options, callback) { 93 | if (_.isFunction(options)) { 94 | callback = options; 95 | options = {}; 96 | } 97 | 98 | exports.api({ 99 | path: 'showstories', 100 | query: options || {} 101 | }, (err, storyIds) => { 102 | if (err) return callback(err); 103 | 104 | async.map(_.slice(storyIds, 0, 5), (storyId, cb) => exports.item(storyId, cb), callback); 105 | }) 106 | }; 107 | 108 | 109 | exports.jobStories = function(options, callback) { 110 | if (_.isFunction(options)) { 111 | callback = options; 112 | options = {}; 113 | } 114 | 115 | exports.api({ 116 | path: 'jobstories', 117 | query: options || {} 118 | }, (err, storyIds) => { 119 | if (err) return callback(err); 120 | 121 | async.map(_.slice(storyIds, 0, 5), (storyId, cb) => exports.item(storyId, cb), callback); 122 | }) 123 | }; 124 | 125 | 126 | exports.chain = function(chain, callback) { 127 | async.map(chain, (funcName, callback) => { 128 | if (!exports[funcName]) return callback(null, []); 129 | 130 | exports[funcName](callback); 131 | }, (err, results) => { 132 | if (err) return callback(err); 133 | 134 | return callback(null, _.flatten(results)); 135 | }); 136 | }; 137 | -------------------------------------------------------------------------------- /f/auth_callback/index.js: -------------------------------------------------------------------------------- 1 | var Request = require('request') 2 | var async = require('async'); 3 | var ejs = require('ejs'); 4 | var template = __dirname + '/view.ejs'; 5 | var Botkit = require('botkit'); 6 | 7 | /** 8 | * Your function call 9 | * @param {Object} params Execution parameters 10 | * Members 11 | * - {Array} args Arguments passed to function 12 | * - {Object} kwargs Keyword arguments (key-value pairs) passed to function 13 | * - {String} remoteAddress The IPv4 or IPv6 address of the caller 14 | * 15 | * @param {Function} callback Execute this to end the function call 16 | * Arguments 17 | * - {Error} error The error to show if function fails 18 | * - {Any} returnValue JSON serializable (or Buffer) return value 19 | */ 20 | module.exports = (params, callback) => { 21 | var authCode = params.kwargs.code; 22 | 23 | // if (authCode) return ejs.renderFile(template, { 24 | // message: "Code", 25 | // content: authCode 26 | // }, {}, (err, response) => callback(err, new Buffer(response || ''))); 27 | 28 | if (!authCode) return ejs.renderFile(template, { 29 | message: "Failure", 30 | content: params.kwargs.error || "No auth code given. Try again? " 31 | }, {}, (err, response) => callback(err, new Buffer(response || ''))); 32 | 33 | var mongo = require('./mongo'); 34 | var controller = Botkit.slackbot({ 35 | storage: mongo() 36 | }); 37 | 38 | async.auto({ 39 | auth: (callback) => { 40 | //post code, app ID, and app secret, to get token 41 | var authAddress = 'https://slack.com/api/oauth.access?' 42 | authAddress += 'client_id=' + process.env.SLACK_ID 43 | authAddress += '&client_secret=' + process.env.SLACK_SECRET 44 | authAddress += '&code=' + authCode 45 | authAddress += '&redirect_uri=' + process.env.SLACK_REDIRECT; 46 | 47 | Request.get(authAddress, function (error, response, body) { 48 | if (error) return callback(error); 49 | 50 | var auth = JSON.parse(body); 51 | 52 | if (!auth.ok) return callback(new Error(auth.error)); 53 | 54 | callback(null, auth); 55 | }); 56 | }, 57 | identity: ['auth', (results, callback) => { 58 | //first, get authenticating user ID 59 | var auth = (results || {}).auth || {}; 60 | var url = 'https://slack.com/api/auth.test?' 61 | url += 'token=' + auth.access_token 62 | 63 | Request.get(url, (error, response, body) => { 64 | if (error) return callback(error); 65 | try { 66 | identity = JSON.parse(body); 67 | 68 | var team = { 69 | id: identity.team_id, 70 | identity: identity, 71 | auth: auth, 72 | createdBy: identity.user_id, 73 | url: identity.url, 74 | name: identity.team 75 | }; 76 | 77 | return callback(null, identity); 78 | } catch(e) { 79 | return callback(e); 80 | } 81 | }); 82 | }], 83 | user: ['identity', (results, callback) => { 84 | var auth = (results || {}).auth || {}; 85 | var identity = (results || {}).identity || {}; 86 | 87 | // what scopes did we get approved for? 88 | var scopes = auth.scope.split(/\,/); 89 | 90 | controller.storage.users.get(identity.user_id, (err, user) => { 91 | if (!user) { 92 | user = { 93 | id: identity.user_id, 94 | access_token: auth.access_token, 95 | scopes: scopes, 96 | team_id: identity.team_id, 97 | user: identity.user, 98 | }; 99 | } 100 | 101 | controller.storage.users.save(user, function(err, id) { 102 | if (err) return callback(err) 103 | return callback(null, user); 104 | }); 105 | }); 106 | }], 107 | team: ['identity', (results, callback) => { 108 | var auth = (results || {}).auth || {}; 109 | var identity = (results || {}).identity || {}; 110 | var scopes = auth.scope.split(/\,/); 111 | 112 | controller.storage.teams.get(identity.team_id, (err, team) => { 113 | if (!team) { 114 | team = { 115 | id: identity.team_id, 116 | identity: identity, 117 | bot: auth.bot, 118 | auth: auth, 119 | createdBy: identity.user_id, 120 | url: identity.url, 121 | name: identity.team, 122 | access_token: auth.access_token 123 | } 124 | } 125 | 126 | controller.storage.teams.save(team, (err, id) => { 127 | if (err) return callback(err); 128 | return callback(null, team); 129 | }); 130 | }); 131 | }] 132 | }, (err, results) => { 133 | if (err) return ejs.renderFile(template, { 134 | message: "Failure", 135 | content: err && err.message 136 | }, {}, (err, response) => callback(err, new Buffer(response || ''))); 137 | 138 | ejs.renderFile(template, { 139 | message: "Success!", 140 | content: "You can now invite @hackernewsbot to your channels and use it!" 141 | }, {}, (err, response) => callback(err, new Buffer(response || ''))); 142 | }); 143 | }; 144 | -------------------------------------------------------------------------------- /f/webhook/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var url = require('url'); 3 | var async = require('async'); 4 | var util = require('util'); 5 | var Botkit = require('botkit'); 6 | var HN = require('./hacker-news'); 7 | var WebClient = require('@slack/client').WebClient; 8 | 9 | /** 10 | * Your function call 11 | * @param {Object} params Execution parameters 12 | * Members 13 | * - {Array} args Arguments passed to function 14 | * - {Object} kwargs Keyword arguments (key-value pairs) passed to function 15 | * - {String} remoteAddress The IPv4 or IPv6 address of the caller 16 | * 17 | * @param {Function} callback Execute this to end the function call 18 | * Arguments 19 | * - {Error} error The error to show if function fails 20 | * - {Any} returnValue JSON serializable (or Buffer) return value 21 | */ 22 | module.exports = (params, callback) => { 23 | if (params.kwargs.token !== process.env.SLACK_VERTIFICATION_TOKEN) 24 | return callback(new Error("Bad verification token.")); 25 | 26 | if (params.kwargs.challenge) return callback(null, {challenge: params.kwargs.challenge}); 27 | 28 | 29 | var mongo = require('./mongo'); 30 | var controller = Botkit.slackbot({ 31 | storage: mongo() 32 | }); 33 | 34 | async.auto({ 35 | team: (callback) => { 36 | controller.storage.teams.get(params.kwargs.team_id, (err, team) => callback(err, team)); 37 | }, 38 | process: ['team', (results, callback) => { 39 | console.log("team", results.team); 40 | if (!results.team) return callback(new Error("No team found.")); 41 | 42 | return processEvent(params.kwargs.event, results.team, callback); 43 | }] 44 | }, (err) => { 45 | // Don't block, return right away. 46 | callback(null, "OK"); 47 | }); 48 | }; 49 | 50 | 51 | function processEvent(event, team, next) { 52 | console.log("event", event); 53 | if (!_.includes(event.type, ['message'])) return next && next(); // Unsupported event type 54 | 55 | var message = getMessageFromEvent(event, team); 56 | if (!message) return next && next(); 57 | 58 | if (message.type === 'help') { 59 | return sendMessage((team.bot || {}).bot_access_token, { 60 | text: "Here's how you can use me:", 61 | attachments: [{ 62 | fields: [ 63 | { 64 | title: "top", 65 | value: "Shows the top stories", 66 | short: true 67 | }, 68 | { 69 | title: "show | Show HN", 70 | value: "Show the top Show HNs", 71 | short: true 72 | }, 73 | { 74 | title: "new", 75 | value: "Show the most recent posts", 76 | short: true 77 | }, 78 | { 79 | title: "ask | Ask HN", 80 | value: "Show the top Ask HNs", 81 | short: true 82 | }, 83 | { 84 | title: "job | job HN", 85 | value: "Show the top job posts", 86 | short: true 87 | }, 88 | { 89 | title: "help", 90 | value: "Shows this help text!", 91 | short: true 92 | } 93 | ], 94 | footer: "Note: you can use multiple commands at the same time!", 95 | }], 96 | channel: message.channel_id 97 | }, next); 98 | } 99 | 100 | console.log("chain", message.chain); 101 | async.auto({ 102 | received: (callback) => { 103 | if (!message.chain.length) return callback(); 104 | 105 | var funcPath = _.map(message.chain, (curr) => curr.name + "(" + (curr.hasParams && curr.param ? curr.param : "") + ")").join("."); 106 | sendMessage((team.bot || {}).bot_access_token, { 107 | text: "Fetching from HN: " + "`hn." + funcPath + "`", 108 | channel: message.channel_id 109 | }, callback); 110 | }, 111 | query: (callback) => { 112 | if (!message.chain.length) return callback(); 113 | 114 | try { 115 | var processCallback = (err, data) => { 116 | if (err) throw err; 117 | 118 | console.log("data", data); 119 | var attachments = _.map(_.filter(data, (post) => post && post.title && post.url && post.url.length && post.title.length), (post) => ({ 120 | title: post.title, 121 | title_link: post.url, 122 | footer: util.format("(%s) – %s", url.parse(post.url).host, post.by) 123 | })); 124 | 125 | debugger; 126 | var response = { 127 | text: attachments.length ? "Here's what I found:" : "Looks like no posts matched that query.", 128 | channel: message.channel_id, 129 | attachments: attachments 130 | }; 131 | 132 | sendMessage((team.bot || {}).bot_access_token, response, callback); 133 | }; 134 | 135 | HN.chain(_.map(message.chain, 'name'), processCallback); 136 | } catch (err) { 137 | console.error("err", err); 138 | return callback && callback(); 139 | } 140 | } 141 | }, next); 142 | } 143 | 144 | function getMessageFromEvent(event, team) { 145 | console.log("getMessageFromEvent"); 146 | if (!event) return null; 147 | if (!event.type) return null; 148 | if (event.type !== 'message') return null; 149 | if (event.subtype === 'message_changed') return null; 150 | if (!event.text && !(event.message || {}).text) return null; 151 | if (event.bot_id && event.bot_id === ((team || {}).bot || {}).bot_user_id) return null; 152 | 153 | console.log("parsing message"); 154 | var message = {}; 155 | 156 | message.channel_id = event.channel; 157 | message.text = event.text; 158 | message.type = getMessageType(message.text, ((team || {}).bot || {}).bot_user_id); 159 | message.user_id = event.user; 160 | message.timestamp = event.ts; 161 | 162 | console.log("message.type", message.type); 163 | 164 | if (message.type !== 'command') return null; 165 | 166 | message.chain = getCommandChain(message.text); 167 | 168 | 169 | if (!message.chain.length && /help/i.test(message.text)) 170 | message.type = "help"; 171 | 172 | console.log("message", message); 173 | return message; 174 | }; 175 | 176 | function getMessageType(text, botId) { 177 | console.log("getMessageType", text, botId); 178 | var botCommandRegex = new RegExp(util.format("\<@%s\>\\s+(\\S+)\\s?(.*)?", botId), 'ig'); 179 | 180 | if (botCommandRegex.test(text)) return "command"; 181 | 182 | return "message"; 183 | }; 184 | 185 | 186 | function getCommandChain(text) { 187 | var availableFunctions = [ 188 | { 189 | name: 'jobStories', 190 | description: "Ask HN", 191 | match: /job\_hn|job|jobs|job\_hn/i 192 | }, 193 | { 194 | name: 'askStories', 195 | description: "Ask HN", 196 | match: /ask\_hn|ask|asks|ask\_hn/i 197 | }, 198 | { 199 | name: 'showStories', 200 | match: /show|shows|show\_hn|show\_hns/i 201 | }, 202 | { 203 | name: 'topStories', 204 | match: /top|top_stories/ 205 | }, 206 | { 207 | name: 'newStories', 208 | match: /recent|new/ 209 | }, 210 | { 211 | name: "bestStories", 212 | match: /best/ 213 | } 214 | ]; 215 | 216 | var parts = _.filter((text || '').split(" "), (part) => { 217 | if (!part) return false; 218 | 219 | if (!part.length) return false; 220 | 221 | return true; 222 | }).map((part) => part.toLowerCase().trim()); 223 | 224 | var chain = []; 225 | for (var index = 0; index < parts.length; index ++) { 226 | var part = parts[index]; 227 | 228 | var matchingFunction = _.reduce(availableFunctions, (result, func) => { 229 | if (func.match && func.match.test(part)) return func; 230 | 231 | return result; 232 | }, null) 233 | 234 | if (!matchingFunction) continue; 235 | 236 | chain.push(matchingFunction); 237 | } 238 | 239 | return chain; 240 | } 241 | 242 | function sendMessage(token, message, next) { 243 | var web = new WebClient(token); 244 | var params = _.assign(message, { 245 | as_user: false 246 | }); 247 | 248 | web.chat.postMessage(message.channel, message.text, message, (err, data) => { 249 | if (err) return next && next(err); 250 | if (!data.ok) return next && next(data); 251 | 252 | return next && next(null, data); 253 | }); 254 | }; 255 | -------------------------------------------------------------------------------- /f/auth/view.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HackerNews Bot - built with stdlib 5 | 6 | 64 | 65 | 66 | 79 | 80 |
81 |

Usage

82 |

Use @hackernewsbot help to view how you can use the bot. All of HackerNews within your Slack team.

83 | HackerNews Bot Usage 84 |
85 | 86 | 89 | 90 | 91 | --------------------------------------------------------------------------------