├── lib ├── popup.js ├── assets │ ├── checkbox.png │ ├── verdana.ttf │ ├── checkbox-checked.svg │ ├── checkbox.svg │ ├── slack.svg │ ├── iframe.js │ ├── client.js │ ├── iframe-button.css │ ├── badge.js │ └── superagent.js ├── log.js ├── badge.js ├── iframe.js ├── slack-invite.js ├── slack.js ├── index.js └── splash.js ├── .travis.yml ├── test ├── mocha.opts ├── setup.js ├── slack-invite.js └── index.js ├── .gitignore ├── .bluemix ├── toolchain.png ├── toolchain_dark.png ├── locales.yml ├── nls │ └── messages.yml ├── toolchain.yml ├── pipeline.yml ├── icon.svg ├── deploy.json └── toolchain.svg ├── Procfile ├── Dockerfile ├── gulpfile.babel.js ├── license.md ├── app.json ├── package.json ├── bin └── slackin ├── readme.md ├── web.config ├── azuredeploy.json ├── scripts └── azuredeploy.sh └── HISTORY.md /lib/popup.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register 2 | --require ./test/setup 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.bluemix/toolchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rauchg/slackin/HEAD/.bluemix/toolchain.png -------------------------------------------------------------------------------- /lib/assets/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rauchg/slackin/HEAD/lib/assets/checkbox.png -------------------------------------------------------------------------------- /lib/assets/verdana.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rauchg/slackin/HEAD/lib/assets/verdana.ttf -------------------------------------------------------------------------------- /.bluemix/toolchain_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rauchg/slackin/HEAD/.bluemix/toolchain_dark.png -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | 3 | nock.disableNetConnect(); 4 | // allow websockets 5 | nock.enableNetConnect(/127\.0\.0\.1:\d+/); 6 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bin/slackin --coc "$SLACK_COC" --channels "$SLACK_CHANNELS" --port $PORT $SLACK_SUBDOMAIN $SLACK_API_TOKEN $GOOGLE_CAPTCHA_SECRET $GOOGLE_CAPTCHA_SITEKEY 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:slim 2 | 3 | ENV PORT 3000 4 | 5 | ADD . /srv/www 6 | 7 | WORKDIR /srv/www 8 | 9 | RUN npm install --unsafe-perm 10 | 11 | EXPOSE 3000 12 | 13 | CMD ./bin/slackin --coc "$SLACK_COC" --channels "$SLACK_CHANNELS" --port $PORT $SLACK_SUBDOMAIN $SLACK_API_TOKEN $GOOGLE_CAPTCHA_SECRET $GOOGLE_CAPTCHA_SITEKEY 14 | -------------------------------------------------------------------------------- /lib/assets/checkbox-checked.svg: -------------------------------------------------------------------------------- 1 | Untitled -------------------------------------------------------------------------------- /lib/assets/checkbox.svg: -------------------------------------------------------------------------------- 1 | Untitled -------------------------------------------------------------------------------- /.bluemix/locales.yml: -------------------------------------------------------------------------------- 1 | --- 2 | root: 3 | $ref: ./nls/messages.yml 4 | de: 5 | $ref: ./nls/messages_de.yml 6 | en-AA: 7 | $ref: ./nls/messages_en_AA.yml 8 | en-RR: 9 | $ref: ./nls/messages_en_RR.yml 10 | en-ZZ: 11 | $ref: ./nls/messages_en_ZZ.yml 12 | es: 13 | $ref: ./nls/messages_es.yml 14 | fr: 15 | $ref: ./nls/messages_fr.yml 16 | it: 17 | $ref: ./nls/messages_it.yml 18 | ja: 19 | $ref: ./nls/messages_ja.yml 20 | ko: 21 | $ref: ./nls/messages_ko.yml 22 | pt-BR: 23 | $ref: ./nls/messages_pt_BR.yml 24 | zh: 25 | $ref: ./nls/messages_zh.yml 26 | zh-HK: 27 | $ref: ./nls/messages_zh_HK.yml 28 | zh-TW: 29 | $ref: ./nls/messages_zh_TW.yml 30 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import babel from 'gulp-babel' 3 | import rimraf from 'gulp-rimraf' 4 | 5 | const paths = { 6 | in: { 7 | js: 'lib/*.js', 8 | assets: 'lib/assets/*' 9 | }, 10 | out: { 11 | js: 'dist', 12 | assets: 'dist/assets' 13 | } 14 | } 15 | 16 | gulp.task('transpile', () => { 17 | return gulp.src(paths.in.js) 18 | .pipe(babel()) 19 | .pipe(gulp.dest(paths.out.js)) 20 | }) 21 | 22 | gulp.task('assets', () => { 23 | return gulp.src(paths.in.assets) 24 | .pipe(gulp.dest(paths.out.assets)) 25 | }) 26 | 27 | gulp.task('clean', () => { 28 | return gulp.src(paths.out.js, { 29 | read: false 30 | }) 31 | .pipe(rimraf()) 32 | }) 33 | 34 | gulp.task('default', ['transpile', 'assets']) 35 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Guillermo Rauch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/slack-invite.js: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | import assert from 'assert'; 3 | import invite from '../lib/slack-invite'; 4 | 5 | describe('slack-invite', () => { 6 | describe('.invite()', () => { 7 | var opts; 8 | 9 | before(() => { 10 | opts = { 11 | channel: 'mychannel', 12 | email: 'user@example.com', 13 | org: 'myorg', 14 | token: 'mytoken' 15 | }; 16 | }); 17 | 18 | it("succeeds when ok", (done) => { 19 | nock(`https://${opts.org}.slack.com`). 20 | post('/api/users.admin.invite'). 21 | reply(200, { ok: true }); 22 | 23 | invite(opts, (err) => { 24 | assert.equal(err, null); 25 | done(); 26 | }); 27 | }); 28 | 29 | it("passes along an error message", (done) => { 30 | nock(`https://${opts.org}.slack.com`). 31 | post('/api/users.admin.invite'). 32 | reply(200, { 33 | ok: false, 34 | error: "other error" 35 | }); 36 | 37 | invite(opts, (err) => { 38 | assert.notEqual(err, null); 39 | assert.equal(err.message, "other error"); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | import dbg from 'debug' 2 | const debug = dbg('slackin') 3 | 4 | export default function log (slack, silent){ 5 | // keep track of elapsed time 6 | let last 7 | 8 | out('fetching') 9 | 10 | // attach events 11 | slack.on('ready', () => out('ready')) 12 | slack.on('retry', () => out('retrying')) 13 | 14 | slack.on('fetch', () => { 15 | last = new Date 16 | out('fetching') 17 | }) 18 | 19 | slack.on('data', online) 20 | 21 | // log online users 22 | function online (){ 23 | out('online %d, total %d %s', 24 | slack.users.active, 25 | slack.users.total, 26 | last ? `(+${new Date - last}ms)` : '') 27 | } 28 | 29 | // print out errors and warnings 30 | if (!silent) { 31 | slack.on('error', (err) => { 32 | console.error('%s – ' + err.stack, new Date) 33 | }) 34 | 35 | slack.on('ready', () => { 36 | if (!slack.org.logo && !silent) { 37 | console.warn('\u001b[92mWARN: no logo configured\u001b[39m') 38 | } 39 | }) 40 | } 41 | 42 | function out (...args){ 43 | if (args) { 44 | args[0] = `${new Date} – ${args[0]}` 45 | } 46 | 47 | if (silent) return debug(...args) 48 | console.log(...args) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/badge.js: -------------------------------------------------------------------------------- 1 | import svg from 'vd' 2 | 3 | const title = 'slack' 4 | const color = '#E01563' 5 | const pad = 8 // left / right padding 6 | const sep = 4 // middle separation 7 | 8 | export default function badge ({ total, active }){ 9 | let value = active ? `${active}/${total}` : ('' + total || '–') 10 | let lw = pad + width(title) + sep // left side width 11 | let rw = sep + width(value) + pad // right side width 12 | let tw = lw + rw // total width 13 | 14 | return svg(`svg xmlns="http://www.w3.org/2000/svg" width=${tw} height=20`, 15 | svg(`rect rx=3 width=${tw} height=20 fill=#555`), 16 | svg(`rect rx=3 x=${lw} width=${rw} height=20 fill=${color}`), 17 | svg(`path d="M${lw} 0h${sep}v20h-${sep}z" fill=${color}`), 18 | svg('g text-anchor=middle font-family=Verdana font-size=11', 19 | text({ str: title, x: Math.round(lw / 2), y: 14 }), 20 | text({ str: value, x: lw + Math.round(rw / 2), y: 14 }) 21 | ) 22 | ) 23 | } 24 | 25 | // generate text with 1px shadow 26 | function text ({str, x, y}){ 27 | return [ 28 | svg(`text fill=#010101 fill-opacity=.3 x=${x} y=${y + 1}`, str), 29 | svg(`text fill=#fff x=${x} y=${y}`, str) 30 | ] 31 | } 32 | 33 | // π=3 34 | function width (str){ 35 | return 7 * str.length 36 | } 37 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Slackin", 3 | "description": "Let people invite themselves to your Slack", 4 | "logo": "https://i.imgur.com/odDO1Fx.png", 5 | "repository": "https://github.com/rauchg/slackin", 6 | "keywords": ["node", "slack"], 7 | "env": { 8 | "SLACK_SUBDOMAIN": { 9 | "description": "Your Slack's subdomain (**this**.slack.com)", 10 | "required": true 11 | }, 12 | "SLACK_API_TOKEN": { 13 | "description": "A Slack API token (find it on https://api.slack.com/web)", 14 | "required": true 15 | }, 16 | "GOOGLE_CAPTCHA_SECRET": { 17 | "description": "Google captcha secret key", 18 | "required": true 19 | }, 20 | "GOOGLE_CAPTCHA_SITEKEY": { 21 | "description": "Google captcha site key", 22 | "required": true 23 | }, 24 | "SLACK_COC": { 25 | "description": "A URL to a Code of Conduct people must agree on before joining.", 26 | "required": false 27 | }, 28 | "SLACK_CHANNELS": { 29 | "description": "Comma-separated list of single guest channels to invite them to (leave blank for a normal, all-channel invite). In order to make this work, you have to have a paid account. You'll only be able to invite as many people as your number of paying members times 5.", 30 | "required": false 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/iframe.js: -------------------------------------------------------------------------------- 1 | import dom from 'vd' 2 | import { readFileSync as read } from 'fs' 3 | 4 | const logo = read(__dirname + '/assets/slack.svg').toString('base64') 5 | const js = read(__dirname + '/assets/iframe.js').toString() 6 | const css = read(__dirname + '/assets/iframe-button.css').toString() 7 | 8 | export default function iframe ({ path, active, total, large }){ 9 | let str = '' 10 | if (active) str = `${active}/` 11 | if (total) str += total 12 | if (!str.length) str = '–' 13 | 14 | let opts = { 'class': large ? 'slack-btn-large' : '' } 15 | let div = dom('span.slack-button', opts, 16 | dom('a.slack-btn href=/ target=_blank', 17 | dom('span.slack-ico'), 18 | dom('span.slack-text', 'Slack') 19 | ), 20 | dom('a.slack-count href=/ target=_blank', str), 21 | dom('style', css), 22 | dom.style().add('.slack-ico', { 23 | 'background-image': `url(data:image/svg+xml;base64,${logo})` 24 | }), 25 | dom('script', ` 26 | data = {}; 27 | data.path = ${JSON.stringify(path)}; 28 | data.total = ${total != null ? total : 'null'}; 29 | data.active = ${active != null ? active : 'null'}; 30 | `), 31 | dom('script', js) 32 | ) 33 | 34 | return div 35 | } 36 | 37 | function gradient (css, sel, params){ 38 | ['-webkit-', '-moz-', ''].forEach(p => { 39 | css.add(sel, { 40 | 'background-image': `${p}linear-gradient(${params})` 41 | }) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /lib/slack-invite.js: -------------------------------------------------------------------------------- 1 | import request from 'superagent' 2 | 3 | export default function invite ({ org, token, email, channel }, fn){ 4 | let data = { email, token } 5 | 6 | if (channel) { 7 | data.channels = channel 8 | data.ultra_restricted = 1 9 | data.set_active = true 10 | } 11 | 12 | request 13 | .post(`https://${org}.slack.com/api/users.admin.invite`) 14 | .type('form') 15 | .send(data) 16 | .end(function (err, res){ 17 | if (err) return fn(err) 18 | if (200 != res.status) { 19 | fn(new Error(`Invalid response ${res.status}.`)) 20 | return 21 | } 22 | 23 | // If the account that owns the token is not admin, Slack will oddly 24 | // return `200 OK`, and provide other information in the body. So we 25 | // need to check for the correct account scope and call the callback 26 | // with an error if it's not high enough. 27 | let {ok, error: providedError, needed} = res.body 28 | if (!ok) { 29 | if (providedError === 'missing_scope' && needed === 'admin') { 30 | fn(new Error(`Missing admin scope: The token you provided is for an account that is not an admin. You must provide a token from an admin account in order to invite users through the Slack API.`)) 31 | } else if (providedError === 'already_invited') { 32 | fn(new Error('You have already been invited to Slack. Check for an email from feedback@slack.com.')) 33 | } else if (providedError === 'already_in_team') { 34 | fn(new Error(`Sending you to Slack...`)) 35 | } else { 36 | fn(new Error(providedError)) 37 | } 38 | return 39 | } 40 | 41 | fn(null) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /lib/assets/slack.svg: -------------------------------------------------------------------------------- 1 | Group -------------------------------------------------------------------------------- /.bluemix/nls/messages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | template.name: "Deploy a Slackin server" 3 | template.description: "With this toolchain, you can deploy a Slackin server.This toolchain is preconfigured for continuous delivery, source control, issue tracking, and online editing.\n\nThis toolchain uses tools that are part of the Continuous Delivery service. If an instance of that service isn't already in the selected organization, when you click **Create**, it is automatically added with the free [Lite](/catalog/services/continuous-delivery/) plan selected.\n\nTo get started, click **Create**.\n\nFor step-by-step instructions, follow the [tutorial](https://www.ibm.com/devops/method/tutorials/tutorial_toolchain_flow)." 4 | template.gettingStarted: "**Your toolchain is ready!**" 5 | deploy.title: "Sample Deploy Stage" 6 | deploy.description: "sample toolchain" 7 | deploy.longDescription: "The Delivery Pipeline automates continuous deployment." 8 | deploy.appDescription: "The name of your Slackin server" 9 | deploy.appName: "App name" 10 | deploy.slackTeamId: "Slack Team Name" 11 | deploy.slackApiToken: "Slack Legacy Token" 12 | deploy.captchaSecret: "Google reCaptcha Secret" 13 | deploy.captchaSiteKey: "Google reCaptcha Site Key" 14 | region: "Region" 15 | organization: "Organization" 16 | space: "Space" 17 | prodStage: "Production stage" 18 | headerSVG.issueTracker: "ISSUE TRACKER" 19 | headerSVG.gitHub1: "GitHub" 20 | headerSVG.think: "THINK" 21 | headerSVG.code: "CODE" 22 | headerSVG.deliver: "DELIVER" 23 | headerSVG.run: "RUN" 24 | headerSVG.repository: "REPOSITORY" 25 | headerSVG.gitHub2: "GitHub" 26 | headerSVG.pipeline: "PIPELINE" 27 | headerSVG.bluemix: "BLUEMIX" 28 | headerSVG.ibmCloud: "IBM Cloud" 29 | headerSVG.webIde: "WEB IDE" 30 | 31 | -------------------------------------------------------------------------------- /.bluemix/toolchain.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | messages: 3 | $i18n: locales.yml 4 | template: 5 | name: 6 | $ref: "#/messages/template.name" 7 | description: 8 | $ref: "#/messages/template.description" 9 | header: '![](toolchain.svg?localize)' 10 | icon: icon.svg 11 | required: 12 | - slackin-build 13 | - slackin-repo 14 | info: 15 | git url: >- 16 | [https://github.com/rauchg/slackin](https://github.com/rauchg/slackin) 17 | git branch: >- 18 | [master](https://github.com/rauchg/slackin/tree/master) 19 | toolchain: 20 | name: 'slackin-toolchain-{{timestamp}}' 21 | template: 22 | getting_started: 23 | $ref: "#/messages/template.gettingStarted" 24 | services: 25 | slackin-repo: 26 | service_id: githubpublic 27 | parameters: 28 | repo_name: '{{toolchain.name}}' 29 | repo_url: 'https://github.com/rauchg/slackin' 30 | type: clone 31 | has_issues: true 32 | enable_traceability: true 33 | slackin-build: 34 | service_id: pipeline 35 | parameters: 36 | services: 37 | - slackin-repo 38 | name: '{{services.slackin-repo.parameters.repo_name}}' 39 | ui-pipeline: true 40 | configuration: 41 | content: 42 | $text: pipeline.yml 43 | env: 44 | SLACKIN_REPO: slackin-repo 45 | CF_APP_NAME: '{{form.pipeline.parameters.prod-app-name}}' 46 | PROD_SPACE_NAME: '{{form.pipeline.parameters.prod-space}}' 47 | PROD_ORG_NAME: '{{form.pipeline.parameters.prod-organization}}' 48 | PROD_REGION_ID: '{{form.pipeline.parameters.prod-region}}' 49 | SLACK_SUBDOMAIN: '{{form.pipeline.parameters.slack-teamid}}' 50 | SLACK_API_TOKEN: '{{form.pipeline.parameters.slack-apitoken}}' 51 | GOOGLE_CAPTCHA_SECRET: '{{form.pipeline.parameters.captcha-secret}}' 52 | GOOGLE_CAPTCHA_SITEKEY: '{{form.pipeline.parameters.captcha-sitekey}}' 53 | 54 | execute: true 55 | webide: 56 | service_id: orion 57 | form: 58 | pipeline: 59 | parameters: 60 | prod-app-name: '{{services.slackin-repo.parameters.repo_name}}' 61 | schema: 62 | $ref: deploy.json 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slackin", 3 | "version": "0.14.0", 4 | "description": "Public Slack organizations made easy", 5 | "repository": "rauchg/slackin", 6 | "main": "dist/index", 7 | "files": [ 8 | "dist", 9 | "bin" 10 | ], 11 | "babel": { 12 | "presets": [ 13 | "es2015" 14 | ] 15 | }, 16 | "dependencies": { 17 | "args": "1.3.0", 18 | "babel-core": "6.3.26", 19 | "babel-eslint": "6.0.4", 20 | "babel-polyfill": "6.3.14", 21 | "babel-preset-es2015": "6.3.13", 22 | "babel-register": "6.9.0", 23 | "body-parser": "1.10.2", 24 | "cors": "2.7.1", 25 | "debug": "3.0.1", 26 | "email-regex": "1.0.0", 27 | "express": "4.15.4", 28 | "gulp-babel": "6.1.1", 29 | "gulp-rimraf": "0.2.0", 30 | "gulp": "3.9.0", 31 | "hostenv": "1.0.1", 32 | "opentype.js": "0.4.4", 33 | "socket.io": "2.0.3", 34 | "superagent": "3.6.0", 35 | "vd": "0.1.0" 36 | }, 37 | "license": "MIT", 38 | "devDependencies": { 39 | "eslint": "2.12.0", 40 | "eslint-config-default": "0.2.0", 41 | "mocha": "2.2.4", 42 | "nock": "2.17.0", 43 | "supertest": "0.15.0" 44 | }, 45 | "engines": { 46 | "node": "6.11.1" 47 | }, 48 | "bin": { 49 | "slackin": "./bin/slackin" 50 | }, 51 | "eslintConfig": { 52 | "extends": "default", 53 | "parser": "babel-eslint", 54 | "rules": { 55 | "no-var": 0 56 | } 57 | }, 58 | "scripts": { 59 | "test": "mocha && eslint lib/**", 60 | "postinstall": "gulp", 61 | "build": "gulp", 62 | "start": "chmod +x bin/slackin && ./bin/slackin" 63 | }, 64 | "now": { 65 | "type": "npm", 66 | "files": [ 67 | "bin", 68 | "lib", 69 | "gulpfile.babel.js" 70 | ], 71 | "env": [ 72 | "SLACK_API_TOKEN", 73 | "SLACK_SUBDOMAIN", 74 | "GOOGLE_CAPTCHA_SECRET", 75 | "GOOGLE_CAPTCHA_SITEKEY" 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /bin/slackin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var pkg = require('./../package') 4 | var args = require('args') 5 | var slackin = require('./../dist').default 6 | 7 | args 8 | .option(['p', 'port'], 'Port to listen on [$PORT or 3000]', require('hostenv').PORT || process.env.PORT || 3000) 9 | .option(['h', 'hostname'], 'Hostname to listen on [$HOSTNAME or 0.0.0.0]', require('hostenv').HOSTNAME || process.env.WEBSITE_HOSTNAME || '0.0.0.0') 10 | .option(['c', 'channels'], 'One or more comma-separated channel names to allow single-channel guests [$SLACK_CHANNELS]', process.env.SLACK_CHANNELS) 11 | .option(['i', 'interval'], 'How frequently (ms) to poll Slack [$SLACK_INTERVAL or 5000]', process.env.SLACK_INTERVAL || 5000) 12 | .option(['P', 'path'], 'Path to serve slackin under', '/') 13 | .option(['s', 'silent'], 'Do not print out warns or errors') 14 | .option(['x', 'cors'], 'Enable CORS for all routes') 15 | .option(['C', 'coc'], 'Full URL to a CoC that needs to be agreed to') 16 | .option(['S', 'css'], 'Full URL to a custom CSS file to use on the main page') 17 | .option(['?', 'help'], 'Show the usage information') 18 | 19 | var flags = args.parse(process.argv, { 20 | value: ' ', 21 | help: false 22 | }) 23 | 24 | var org = args.sub[0] || process.env.SLACK_SUBDOMAIN 25 | var token = args.sub[1] || process.env.SLACK_API_TOKEN 26 | var emails = process.env.EMAIL_SLACK_LIST || '' 27 | 28 | var gcaptcha_secret = args.sub[2] || process.env.GOOGLE_CAPTCHA_SECRET 29 | var gcaptcha_sitekey = args.sub[3] || process.env.GOOGLE_CAPTCHA_SITEKEY 30 | 31 | 32 | 33 | if (flags.help) { 34 | args.showHelp() 35 | } 36 | 37 | if (!org || !token || !gcaptcha_sitekey || !gcaptcha_secret) { 38 | args.showHelp() 39 | } else { 40 | flags.org = org 41 | flags.token = token 42 | flags.emails = emails 43 | flags.gcaptcha_secret = gcaptcha_secret 44 | flags.gcaptcha_sitekey = gcaptcha_sitekey 45 | } 46 | 47 | var port = flags.port 48 | var hostname = flags.hostname 49 | 50 | slackin(flags).listen(port, hostname, function (err) { 51 | if (err) throw err 52 | if (!flags.silent) console.log('%s – listening on %s:%d', new Date, hostname, port) 53 | }) 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/zeit/art/blob/e081cf46e6609b51ac485dcc337ac6644c0da5e7/slackin/repo-banner.png) 2 | 3 | ## Features 4 | 5 | - A landing page you can point users to fill in their emails and receive an invite (`https://slack.yourdomain.com`) 6 | - An `