├── .editorconfig ├── .env.example ├── .eslintrc ├── .gitignore ├── CB_Logo.png ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.test.yml ├── glitch-bash-alias ├── package.json ├── src ├── app │ ├── routes │ │ ├── data │ │ │ ├── events │ │ │ │ └── initialWelcome.js │ │ │ ├── interactive │ │ │ │ └── initialResponse.js │ │ │ └── slash │ │ │ │ ├── resources │ │ │ │ ├── messageAttachments │ │ │ │ │ ├── genericAttachment.yml │ │ │ │ │ ├── html-css │ │ │ │ │ │ ├── advanced │ │ │ │ │ │ │ ├── cssguidelines.yml │ │ │ │ │ │ │ └── diveintohtml.yml │ │ │ │ │ │ ├── beginner │ │ │ │ │ │ │ ├── codecademy.yml │ │ │ │ │ │ │ ├── courserahtmlcssjs.yml │ │ │ │ │ │ │ ├── microsofthtml5css3.yml │ │ │ │ │ │ │ ├── shayhowehtmlcss.yml │ │ │ │ │ │ │ └── smashingmagazinecss3.yml │ │ │ │ │ │ └── intermediate │ │ │ │ │ │ │ ├── codeguide.yml │ │ │ │ │ │ │ ├── csstricksflexbox.yml │ │ │ │ │ │ │ ├── learnlayout.yml │ │ │ │ │ │ │ ├── mdnhtml.yml │ │ │ │ │ │ │ ├── netninjacss.yml │ │ │ │ │ │ │ └── shayhowehtmlcss.yml │ │ │ │ │ ├── javascript │ │ │ │ │ │ ├── advanced │ │ │ │ │ │ │ ├── effectivejs.yml │ │ │ │ │ │ │ ├── javascriptpatterns.yml │ │ │ │ │ │ │ ├── learningadvancedjs.yml │ │ │ │ │ │ │ └── wtheventloop.yml │ │ │ │ │ │ ├── beginner │ │ │ │ │ │ │ ├── javascript30.yml │ │ │ │ │ │ │ ├── jstheweirdparts.yml │ │ │ │ │ │ │ ├── watchandcode.yml │ │ │ │ │ │ │ └── youdontknowjs.yml │ │ │ │ │ │ └── intermediate │ │ │ │ │ │ │ ├── eloquentjavascript.yml │ │ │ │ │ │ │ ├── jsthehardparts.yml │ │ │ │ │ │ │ ├── rithmschool.yml │ │ │ │ │ │ │ └── scotchio.yml │ │ │ │ │ └── python │ │ │ │ │ │ ├── advanced │ │ │ │ │ │ ├── concurrencyinpython.yml │ │ │ │ │ │ ├── fluentpython.yml │ │ │ │ │ │ ├── magicmethods.yml │ │ │ │ │ │ ├── towerofabstraction.yml │ │ │ │ │ │ └── youwanttobeapythonexpert.yml │ │ │ │ │ │ ├── beginner │ │ │ │ │ │ ├── automatetheboringstuffvideos.yml │ │ │ │ │ │ ├── pythonmitx6001.yml │ │ │ │ │ │ ├── pythonofficaltutorial.yml │ │ │ │ │ │ └── thepythongurututorial.yml │ │ │ │ │ │ └── intermediate │ │ │ │ │ │ ├── builtinsuperheros.yml │ │ │ │ │ │ ├── effectivepython.yml │ │ │ │ │ │ ├── intermedatepythonprogramming.yml │ │ │ │ │ │ └── mitxpython6002.yml │ │ │ │ ├── messageBodies │ │ │ │ │ ├── genericBody.yml │ │ │ │ │ ├── html-css │ │ │ │ │ │ ├── htmlcssa.yml │ │ │ │ │ │ ├── htmlcssb.yml │ │ │ │ │ │ └── htmlcssm.yml │ │ │ │ │ ├── javascript │ │ │ │ │ │ ├── jsa.yml │ │ │ │ │ │ ├── jsb.yml │ │ │ │ │ │ └── jsm.yml │ │ │ │ │ └── python │ │ │ │ │ │ ├── pya.yml │ │ │ │ │ │ ├── pyb.yml │ │ │ │ │ │ └── pym.yml │ │ │ │ ├── messageTemplates │ │ │ │ │ ├── genericResourcesAttachmentTemplate.js │ │ │ │ │ ├── resourcesHelp.js │ │ │ │ │ └── resourcesMessage.js │ │ │ │ ├── resourcesData.yml │ │ │ │ ├── resourcesTestData.yml │ │ │ │ └── slashResources_test.js │ │ │ │ └── welcome │ │ │ │ ├── slashWelcome.js │ │ │ │ └── welcomeHelp.js │ │ └── endpoints │ │ │ ├── events │ │ │ └── initial.js │ │ │ ├── interactive │ │ │ └── reminder.js │ │ │ └── slash │ │ │ ├── resources │ │ │ └── resources.js │ │ │ └── welcome │ │ │ └── welcome.js │ └── util │ │ ├── groupBy.js │ │ ├── incomingParser.js │ │ ├── remind.js │ │ ├── sortArguments.js │ │ ├── ymlFilters.js │ │ └── ymlLoader.js ├── config │ ├── localtunnel.js │ └── routes.js ├── controllers │ ├── events.js │ ├── interactive.js │ ├── resources.js │ ├── templates.js │ └── welcome.js ├── index.js ├── public │ └── images │ │ ├── atrain.png │ │ ├── btrain.png │ │ ├── javascript.png │ │ ├── mtrain.png │ │ ├── python.png │ │ ├── return.png │ │ └── windy.png └── views │ ├── index.ejs │ └── templates │ ├── bodies │ ├── index.ejs │ ├── partials │ │ ├── edit_form.ejs │ │ └── readonly_form.ejs │ └── show.ejs │ ├── index.ejs │ └── resources │ ├── index.ejs │ ├── partials │ ├── edit_form.ejs │ └── readonly_form.ejs │ └── show.ejs └── test ├── .eslintrc ├── incomingParser.test.js ├── mocha.opts ├── routes.test.js └── welcome.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = spaces 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SLACK_TOKEN= 2 | SLACK_VERIFICATION_TOKEN= 3 | PORT=3000 4 | DEV_SUBDOMAIN=janefoster 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard"], 3 | "plugins": [ 4 | "standard", 5 | "promise" 6 | ], 7 | "rules": { 8 | "semi": [2, "always"], 9 | "spaced-comment": [2, "always", { 10 | "line": { 11 | "markers": ["*package", "!", ",", "="] }, 12 | "block": { 13 | "balanced": true, 14 | "markers": ["*package", "!", ","], 15 | "exceptions": ["*"] 16 | } 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .env 4 | *.log 5 | users.json 6 | package-lock.json 7 | .nvmrc 8 | .env.sample 9 | slush/* 10 | .config 11 | shrinkwrap.yaml 12 | -------------------------------------------------------------------------------- /CB_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/CB_Logo.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8-alpine 2 | WORKDIR /app 3 | ENV PORT=3000 4 | ADD package.json . 5 | ADD src ./src 6 | ADD test ./test 7 | RUN npm i 8 | 9 | CMD ["npm", "run", "start:dev"] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Codebuddies.org 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # greetbot 2 | greetbot is a Slack app built with [ExpressJS](https://expressjs.com/) to be used with the [Codebuddies](https://www.codebuddies.org) open Slack community. greetbot helps us welcome and onboard new members and provide valuable coding resources. 3 | 4 | ## Contributing 5 | ### Code of Conduct 6 | This is an open source project and we welcome all developers of all skill levels. We encourage all contributors to create Issues, submit Pull Requests, and review each other's code. As such, we expect people to be open for discussion and feedback. If you want to provide feedback or comment, please make sure that your comments are constructive. 7 | 8 | **TL;DR:** Be nice, we are all here to learn together. 9 | 10 | ### Development 11 | Development contribution requires that you have your own Slack workspace as your sandbox for local development purposes. For instructions on how to make your own, see Slack's [tutorial](https://get.slack.help/hc/en-us/articles/206845317-Create-a-Slack-workspace). 12 | 13 | #### Getting Started 14 | 1. Install dependencies with NPM 15 | ```bash 16 | npm install 17 | ``` 18 | 19 | 2. Create an `.env` file by following the same structure as `.env.example`. Change the `DEV_SUBDOMAIN` variable to your name without spaces. For example, if your name is Jane Fonda, then the `DEV_SUBDOMAIN` value should be `janefonda`. 20 | 21 | 3. Run Development mode 22 | ```bash 23 | npm run start:dev 24 | ``` 25 | 26 | #### Installing greetbot in your sandbox workspace 27 | We have created a guide on how to set up your local version of greetbot in your Slack workspace. Read it [here](https://github.com/codebuddies/greetbot/wiki/Setup-Greetbot-in-your-Slack-Workspace). 28 | 29 | #### Putting it all together :tada: 30 | Now that you have followed the tutorials and guides linked above to a T (right??), greetbot should work in your slack workspace. Try sending a slash command by typing `/welcome test`. greetbot should send you private message with a welcome message. 31 | 32 | ### Feature Requests / Ideas / Issues 33 | For bugs or other issues, feel free to file an [Issue](https://github.com/codebuddies/greetbot/issues). 34 | 35 | For ideas, join us for a discussion at the Codebuddies Slack channel, `#cb-code`. You can get an invite to Codebuddies [here](https://codebuddiesmeet.herokuapp.com/). 36 | 37 | ## License 38 | MIT Licensed, see [LICENSE](https://github.com/codebuddies/greetbot/blob/master/LICENSE) for details. 39 | -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | sut: 2 | build: . 3 | command: npm run test 4 | -------------------------------------------------------------------------------- /glitch-bash-alias: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # A list of bash aliases for the glitch console. 3 | # To use, run `source glitch-bash-alias` 4 | 5 | alias gb="git branch"; 6 | alias gco="git checkout"; 7 | alias gst="git status"; 8 | alias gcmsg="git commit -m"; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greetbot", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "Greetbot is a Slackbot built for use with Codebuddies to help welcome and onboard new members and provide valuable coding resources.", 6 | "engines": { 7 | "node": "8.x" 8 | }, 9 | "scripts": { 10 | "test": "mocha --timeout 10000 --exit", 11 | "start": "node src/index.js", 12 | "start:dev-server": "nodemon src/index.js", 13 | "start:dev-tunnel": "node src/config/localtunnel.js", 14 | "start:dev": "npm run start:dev-server & npm run start:dev-tunnel" 15 | }, 16 | "author": "CodeBuddies ", 17 | "dependencies": { 18 | "app-module-path": "^2.x", 19 | "axios": "^0.16.0", 20 | "body-parser": "^1.17.1", 21 | "dotenv": "^4.0.0", 22 | "ejs": "^2.5.8", 23 | "express": "^4.15.2", 24 | "glob": "^7.1.2", 25 | "node": "^8.x", 26 | "node-json-db": "^0.7.3", 27 | "npm": "^5.8.0", 28 | "querystring": "^0.2.0", 29 | "yamljs": "^0.3.0" 30 | }, 31 | "devDependencies": { 32 | "chai": "^4.1.2", 33 | "eslint": "^4.19.1", 34 | "eslint-config-standard": "^11.0.0", 35 | "eslint-plugin-import": "^2.11.0", 36 | "eslint-plugin-mocha": "^5.0.0", 37 | "eslint-plugin-node": "^6.0.1", 38 | "eslint-plugin-promise": "^3.7.0", 39 | "eslint-plugin-standard": "^3.0.1", 40 | "localtunnel": "^1.8.3", 41 | "mocha": "^5.1.1", 42 | "nodemon": "^1.17.1", 43 | "supertest": "^3.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/routes/data/events/initialWelcome.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/app/routes/data/events/initialWelcome.js -------------------------------------------------------------------------------- /src/app/routes/data/interactive/initialResponse.js: -------------------------------------------------------------------------------- 1 | const accept = require('routes/endpoints/events/initial'); 2 | 3 | const welcomeResponse = (req, res) => { 4 | const { token, user, team } = JSON.parse(req.body.payload); 5 | if (token === process.env.SLACK_VERIFICATION_TOKEN) { 6 | // simplest case with only a single button in the application 7 | // check `callback_id` and `value` if handling multiple buttons 8 | accept; 9 | res.send({ text: '_*Thank you!*_\n\n\nWe\'ll put this here so you don\'t forget:\n\n\n:smile: Don\'t be shy! Introduce yourself in & welcome fellow new members. _The more we know about each other, the better we can connect & collaborate._\n\n\n:point_left::skin-tone-5: Browse all our public Slack channels by clicking on `CHANNELS` over in the sidebar.\n\n\n:eyeglasses: Make sure to read our .\n\n\n:computer: Log into our using your new CodeBuddies Slack account & take a look around -- or join a hangout.\n\n\n:calendar: Try scheduling a hangout yourself! (_You don\’t need to be an expert to collaborate._) Simply click on the `Schedule a Hangout` button on the website & fill in the information. All hangouts get automatically posted back to .\n\n\n:books: Interested in going deep on topics like * Functional JS* or that special *Ruby* book or *Blockchain*? Start a ! All study groups created on the website also get automatically posted back to . Invite folks here on Slack to join in so that you can motivate each other to master your learning goal.\n\n\n:question: If you have any questions about the community or the project or want to talk to an admin, feel free to ask about it in . It\'s where discussions related to our community happen.\n\n\n*Some Quick Links:*\n\n\n\n\n\n\n\n\n_Happy Learning!_ :wave::skin-tone-5:' 10 | }); 11 | } else { res.sendStatus(500); } 12 | }; 13 | 14 | module.exports = { welcomeResponse }; 15 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/genericAttachment.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 | language: 3 | level: 4 | media-cost: 5 | media-cost-desc: 6 | media-type: 7 | media-icon: 8 | media-link: 9 | media-desc: 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/advanced/cssguidelines.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'html-css' 3 | level: 'advanced' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "website" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "_This is a very thorough CSS style guide meant to give advice or recommendations on how to keep stylesheets maintainable and scalable. It is very helpful in formatting and beautification as well as consistency with large projects._" 10 | community-rating: ":heavy_plus:" -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/advanced/diveintohtml.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'html-css' 3 | level: 'advanced' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "website" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "_A community driven guide to HTML5 syntax and concepts. Also discusses video, accessibility, and APIs_" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/beginner/codecademy.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'html-css' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "course" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "*Codecademy* _In these courses, you'll learn the fundamentals of HTML and CSS so that you can create visually appealing web pages._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/beginner/courserahtmlcssjs.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'html-css' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "class" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "*Coursera* _A 3-week MOOC (Massive open online course) from Hong Kong University of Science and Technology on the basics of HTML, CSS, and JavaScript._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/beginner/microsofthtml5css3.yml: -------------------------------------------------------------------------------- 1 | resource-number: 5 2 | language: 'html-css' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*Microsoft Visual Academy* _A 21-episode video course for absolute beginners, where you can follow along and code your very first website with HTML & CSS._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/beginner/shayhowehtmlcss.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'html-css' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "*Shay Howe* _Learn to Code HTML & CSS has one goal — to teach people how to build beautiful and intuitive websites by way of clear and organized lessons. The guide covers a variety of web design and development topics._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/beginner/smashingmagazinecss3.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'html-css' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "*Smashing Magazine* _This guide on CSS3 should be all you need to fully integrate styling in your projects._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/intermediate/codeguide.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'html-css' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "website" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "Standards for developing flexible, durable, and sustainable HTML and CSS. _An easy to read online guide on HTML and CSS syntax with some JavaScript concepts explained. Great for those who only have 10-15 minutes a day to spend on studying code._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/intermediate/csstricksflexbox.yml: -------------------------------------------------------------------------------- 1 | resource-number: 5 2 | language: 'html-css' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "*CSS Tricks* _This is a fun guide for flexbox which was published on the CSS-tricks website and offers a clear introduction with many illustrative examples on how to use flexbox for layout and alignment needs. The website also has a thorough collection of CSS tricks you can apply to any project._" 10 | community-rating: ":heavy_plus:" -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/intermediate/learnlayout.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'html-css' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "_Simple, easy to read 19-page tutorial on CSS layouts that dives into topics such as positioning within the box model, media queries, flexbox, etc. It’s also available in several different languages other than English._" 10 | community-rating: ":heavy_plus:" -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/intermediate/mdnhtml.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'html-css' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "website" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "*MDN* _Once you’ve taken a basic course on HTML & CSS and have had some practice, I recommend visiting Mozilla, which has some excellent references and guides to expand your vocabulary. They have been particularly helpful with understanding browser compatibility._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/intermediate/netninjacss.yml: -------------------------------------------------------------------------------- 1 | resource-number: 6 2 | language: 'html-css' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*Net Ninja* _This is a 12-part video tutorial that walks you through the world of flexbox. The ninja speaking in the video is very clear and detailed with explanations so you can easily follow along as he codes. He’s got other great tutorials on his channel so I definitely recommend subscribing._" 10 | community-rating: ":heavy_plus:" -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/html-css/intermediate/shayhowehtmlcss.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'html-css' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "*Shay Howe* _A follow-up to Learn to Code HTML & CSS. This guide covers responsive web design, preprocessors, jQuery, and more need-to-know web design topics._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/advanced/effectivejs.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'javascript' 3 | level: 'advanced' 4 | media-cost: "$22.97" 5 | media-cost-desc: "PAID" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "Amazon(dot)com _*Effective JavaScript* is organized around 68 proven approaches for writing better JavaScript, backed by concrete examples. You’ll learn how to choose the right programming style for each project, manage unanticipated problems, & work more successfully with every facet of JavaScript programming from data structures to concurrency._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/advanced/javascriptpatterns.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'javascript' 3 | level: 'advanced' 4 | media-cost: "$13.49" 5 | media-cost-desc: "PAID" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "Amazon(dot)com _What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced developer looking to solve problems related to objects, functions, inheritance, & other language-specific categories, the abstractions and & templates in this guide are ideal—whether you're using JavaScript to write a client-side, server-side, or desktop application._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/advanced/learningadvancedjs.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'javascript' 3 | level: 'advanced' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "website" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "_What it says in the title: just the facts._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/advanced/wtheventloop.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'javascript' 3 | level: 'advanced' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*Phillip Roberts* _JavaScript programmers like to use words like, “event-loop”, “non-blocking”, “callback”, 'asynchronous', 'single-threaded' & 'concurrency'. We say things like 'don’t block the event loop', 'make sure your code runs at 60 frames-per-second', 'well of course, it won’t work, that function is an asynchronous callback!' If you’re anything like me, you nod and agree, as if it’s all obvious, even though you don’t actually know what the words mean; & yet, finding good explanations of how JavaScript actually works isn’t all that easy... so let’s learn!_" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/beginner/javascript30.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'javascript' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "_Build things. Lots of things. Build 1,000 things. Keep it up and don't stop. Seriously. This has always been my advice. Just put in the work and you will get better. *30 projects in 30 days*._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/beginner/jstheweirdparts.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'javascript' 3 | level: 'beginner' 4 | media-cost: ($15-$170) 5 | media-cost-desc: "PAID" 6 | media-type: "class" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "Course from Udemy. _An advanced JavaScript course for everyone! Scope, closures, prototypes, 'this', build your own framework, and more._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/beginner/watchandcode.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'javascript' 3 | level: 'beginner' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "_Amazing video tutorials that turn beginners into professional software engineers (TM)._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/beginner/youdontknowjs.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'javascript' 3 | level: 'beginner' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "_This series is *the* reference for anyone who is eager to understand the fundamentals of the Javascript language. The way it's clearly split between the different paradigms of the language between the different books makes it a lot more accessible than other Javascript 'bibles' can be._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/intermediate/eloquentjavascript.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'javascript' 3 | level: 'intermediate' 4 | media-cost: "$31" 5 | media-cost-desc: "PAID" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "Amazon(dot)com. _Eloquent JavaScript, 2nd Edition dives deep into the JavaScript language to show you how to write beautiful, effective code. Author Marijn Haverbeke immerses you in example code from the start, while exercises and full-chapter projects give you hands-on experience with writing your own programs. As you build projects such as an artificial life simulation, a simple programming language, and a paint program._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/intermediate/jsthehardparts.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'javascript' 3 | level: 'intermediate' 4 | media-cost: "$39/mo" 5 | media-cost-desc: "PAID" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "Course from Front End Masters. _This will give you a solid understanding of callbacks & higher order functions, closure, asynchronous JavaScript, & object-oriented JavaScript. It's for developers with basic/intermediate knowledge of JavaScript who want to deepen their understanding to take it to the next level._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/intermediate/rithmschool.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'javascript' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "class" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "_Rithm School is a Bootcamp Located in San Francisco._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/javascript/intermediate/scotchio.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'javascript' 3 | level: 'intermediate' 4 | media-cost: "$0" 5 | media-cost-desc: "FREE" 6 | media-type: "website" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "_Code. Eat. Sleep. Loop (TM)._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/advanced/concurrencyinpython.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'python' 3 | level: 'advanced' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*Raymond Hettinger* _Core maintainer of Python -- responsible for dictionaries & Containers, among many other good things in core Python. Excellent talk on concurrency & that big bug-a-boo, the *Python GIL*. Turns out, it doesn't restrict you the way you think it does._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/advanced/fluentpython.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'python' 3 | level: 'advanced' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "(Free on Safari with a 10-day trial) *Luciano Ramalho* _Write effective, idiomatic Python code by leveraging its best & possibly most neglected—features. It gives hands-on demonstrations of core Python features & libraries & shows how to make your code shorter, faster, & more readable. Many experienced programmers try to bend Python to fit patterns they learned from other languages so they never discover language features outside of their experience. With this book, you'll *thoroughly* learn Python3._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/advanced/magicmethods.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'python' 3 | level: 'advanced' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "*Rafe Kettler* _A collection of detailed blog posts in PDF form which explain ‘magic methods’ in Python. Magic methods are surrounded by double underscores (duderscores) & can make classes & objects behave in different & :magicwand: magical ways. Want to bend Python to your will but don't want to become a C programmer? Playing with magic methods might be a good place to start._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/advanced/towerofabstraction.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'python' 3 | level: 'advanced' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*Alex Martelli* _Leads tech support for Google Cloud & wrote 'Python in a Nutshell' from O'Reilly. Abstraction is a powerful servant, but a dangerous master. We code, design, think, debug ... on a tower of abstractions. Spolsky's Law says *all abstractions leak*. Alex explores *why* they leak, why it's a problem, & what you can do about it. He also covers why sometimes abstractions *should* leak & how best to produce & consume abstraction layers._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/advanced/youwanttobeapythonexpert.yml: -------------------------------------------------------------------------------- 1 | resource-number: 5 2 | language: 'python' 3 | level: 'advanced' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*James Powell* takes a deep dive into some of the concepts and language features that will help you to write truly idiomatic python code" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/beginner/automatetheboringstuffvideos.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'python' 3 | level: 'beginner' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*Al Sweigart* _Video class version of his excellent book. Al is an accessible & cheerful companion on the Python journey & gives practical & effective advice to the newbie programmer._ His book is available free online at ." 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/beginner/pythonmitx6001.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'python' 3 | level: 'beginner' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "class" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "(Edx.org, Self-paced & online) *MITx* _This award-winning class is challenging & fast-paced for those new (or even not so new) to programming. It is very much worth your time. The exercises are meaty & well thought out & the online code evaluator is top-notch. It is hands down *the best* university-style class out there for learning CS & Python._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/beginner/pythonofficaltutorial.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'python' 3 | level: 'beginner' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "_Yes, it's documentation - but it's *good* documentation. Written especially for you: the beginner by the devlopers of the core Python language. Not a bad place to start._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/beginner/thepythongurututorial.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'python' 3 | level: 'beginner' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "tutorial" 7 | media-icon: ":link:" 8 | media-link: "" 9 | media-desc: "_A tutorial that focuses on beginner programmers. It covers many Python concepts in depth & also teaches some more advanced constructs like lambda expressions & regular expressions. It finishes off with 'How to access MySQL db using Python'._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/intermediate/builtinsuperheros.yml: -------------------------------------------------------------------------------- 1 | resource-number: 4 2 | language: 'python' 3 | level: 'intermediate' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "*David Beazley* _One of the best live-coders ever & a *very* engaging speaker. He's been using Python for 20 years, & knows it inside & out. In this PyData Chicago 2016 Keynote, he performs live data cleaning & data science using *only the tools available in Python's core library*. Amazing & humbling & very funny. You'll never look at Chicago food ratings the same way again._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/intermediate/effectivepython.yml: -------------------------------------------------------------------------------- 1 | resource-number: 3 2 | language: 'python' 3 | level: 'intermediate' 4 | media-cost: $25.59 5 | media-cost-desc: "PAID" 6 | media-type: "book" 7 | media-icon: ":books:" 8 | media-link: "" 9 | media-desc: "*Brett Slatkin* _This book contains 59 specific ways to improve writing Pythonic code. At 227 pages, it is a very brief overview of some of the most common adapations programmers need to become efficient intermediate level Python programmers._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/intermediate/intermedatepythonprogramming.yml: -------------------------------------------------------------------------------- 1 | resource-number: 1 2 | language: 'python' 3 | level: 'intermediate' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "video" 7 | media-icon: ":cinema:" 8 | media-link: "" 9 | media-desc: "(Free 10-day Trial Available) *Jessica McKellar* _Is head of development at Dropbox, & organized the Boston Python User Group (one of the world's largest). At Boston Python, she taught hundreds of wannabees to happily program like pros. What do Scrabble cheaters, Shakespearean sonnets, Twitter, & the Astronomy Picture of the Day have to do with software programming? For Jessica, they’re playful tools for teaching intermediate-level Python techniques to the next generation of Pythonistas in this engaging video._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageAttachments/python/intermediate/mitxpython6002.yml: -------------------------------------------------------------------------------- 1 | resource-number: 2 2 | language: 'python' 3 | level: 'intermediate' 4 | media-cost: $0 5 | media-cost-desc: "FREE" 6 | media-type: "class" 7 | media-icon: ":school_satchel:" 8 | media-link: "" 9 | media-desc: "(Edx.org, Self-paced & online) *MITx* _This award-winning class teaches how to use computation to accomplish a variety of goals, providing a brief introduction to topics in computational problem solving. Prior experience in Python & rudimentary knowledge of computational complexity (Big-O) recommended. You'll get considerable practice problem-solving & implementing concepts in code. Examples include simulating a robot vacuum cleaner, modeling population dynamics of replicating viruses vs. drug treatments, & an algorithm to effectivley navigate the shortest path on the MIT campus in the snow._" 10 | community-rating: ":heavy_plus:" 11 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/genericBody.yml: -------------------------------------------------------------------------------- 1 | name: 2 | sidebar-color: 3 | language: 4 | level: 5 | level-icon: 6 | language-icon: 7 | language-desc: 8 | language-blurb: 9 | help_link: 10 | more-questions: 11 | maintainer: 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/html-css/htmlcssa.yml: -------------------------------------------------------------------------------- 1 | name: "HTMLCSSA" 2 | sidebar-color: "#33E3FF" 3 | language: "HTML-CSS" 4 | level: "Advanced" 5 | level-icon: ":atrain:" 6 | language-icon: ":html5::css3:" 7 | language-desc: "Here are some HTML/CSS resources to help you attain mastery!\n:windy: " 8 | language-blurb: "\n_HTML (HyperText Markup Language) and CSS (Cascading Stylesheets) are the building blocks of the web. HTML describes and defines the content of a webpage and the first publicly available description of HTML was first mentioned by Tim Berners-Lee in late 1991. CSS is used to describe a web page's appearance/presentation and version 1 was completed in 1996 by the W3C (World Wide Web Consortium)._\n\n\n:return:" 9 | help_link: "\n\n\n:star::star: `If you get stuck` :white_check_mark: out the (Free).\n_Web developers of all levels swear by it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@uLan08* maintains this set of HTML/CSS resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/html-css/htmlcssb.yml: -------------------------------------------------------------------------------- 1 | name: "HTMLCSSB" 2 | sidebar-color: "#33E3FF" 3 | language: "HTML-CSS" 4 | level: "Beginner" 5 | level-icon: ":btrain:" 6 | language-icon: ":html5::css3:" 7 | language-desc: "Here are some HTML/CSS resources to get you started!\n:windy: " 8 | language-blurb: "\n_HTML (HyperText Markup Language) and CSS (Cascading Stylesheets) are the building blocks of the web. HTML describes and defines the content of a webpage and the first publicly available description of HTML was first mentioned by Tim Berners-Lee in late 1991. CSS is used to describe a web page's appearance/presentation and version 1 was completed in 1996 by the W3C (World Wide Web Consortium)._\n\n\n:return:" 9 | help_link: "\n\n\n:star::star: `If you get stuck` :white_check_mark: out the (Free).\n_Web developers of all levels swear by it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@kjng* maintains this set of HTML/CSS resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/html-css/htmlcssm.yml: -------------------------------------------------------------------------------- 1 | name: "HTMLCSSM" 2 | sidebar-color: "#B2FF33" 3 | language: "HTML-CSS" 4 | level: "Intermediate" 5 | level-icon: ":mtrain:" 6 | language-icon: ":html5::css3:" 7 | language-desc: "Here are some HTML/CSS resources to help you level up!\n:windy:" 8 | language-blurb: "\n_HTML (HyperText Markup Language) and CSS (Cascading Stylesheets) are the building blocks of the web. HTML describes and defines the content of a webpage and the first publicly available description of HTML was first mentioned by Tim Berners-Lee in late 1991. CSS is used to describe a web page's appearance/presentation and version 1 was completed in 1996 by the W3C (World Wide Web Consortium)._\n\n\n:return:" 9 | help_link: "\n\n\n:star::star: `If you get stuck` :white_check_mark: out the (Free).\n_Web developers of all levels swear by it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@uLan08* maintains this set of HTML/CSS resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/javascript/jsa.yml: -------------------------------------------------------------------------------- 1 | name: "JSA" 2 | sidebar-color: "#33E3FF" 3 | language: "JavaScript" 4 | level: "Advanced" 5 | level-icon: ":atrain:" 6 | language-icon: ":javascript:" 7 | language-desc: "Here are some JavaScript resources to help you attain mastery!\n:windy: " 8 | language-blurb: "_Today's *'Advanced Javascript™'* is tomorrow's *'Old & Busted Javascript™'*. It's just the way the web works. Think you know it all? How about these resources for a challenge? Work on real-world problems & push the boundaries of engineering practices. And understand it will all be new and different tomorrow._\n\n\n:return:" 9 | help_link: "\n\n\n:star: :star: `If you get stuck` :white_check_mark: out the (Free). \n_JavaScript developers of all levels swear by it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@bethanyg* maintains this set of JavaScript resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/javascript/jsb.yml: -------------------------------------------------------------------------------- 1 | name: "JSB" 2 | sidebar-color: "#FFE633" 3 | language: "JavaScript" 4 | level: "Beginner" 5 | level-icon: ":btrain:" 6 | language-icon: ":javascript:" 7 | language-desc: "Here's some JavaScript resources to get you started!\n:windy:" 8 | language-blurb: "\n_JavaScript, often abbreviated as JS, is a high-level, interpreted programming language. It's a language which is also characterized as dynamic, weakly typed, prototype-based and multi-paradigm. It first appeared on December 4th, 1995 and was designed by Brendan Eich._\n\n\n:return:" 9 | help_link: ":star: :star: `If you get stuck` :white_check_mark: out the (Free). \n_JavaScript developers of all levels swear by it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "_*@bethanyg* maintains this set of JavaScript resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/javascript/jsm.yml: -------------------------------------------------------------------------------- 1 | name: "JSM" 2 | sidebar-color: "#B2FF33" 3 | language: "JavaScript" 4 | level: "Intermediate" 5 | level-icon: ":mtrain:" 6 | language-icon: ":javascript:" 7 | language-desc: "Here's some JavaScript resources to help you level up!\n:windy:" 8 | language-blurb: "_You’re ready to learn how to debug like a professional, access & create values in more complex data structures, dive deep into callbacks & closures, and learn all about event-driven programming with the DOM. You might want to tackle advanced built-in methods on arrays - or even program your own browser-based interactive games._\n\n\n:return:" 9 | help_link: "\n\n\n:star: :star: `If you get stuck` :white_check_mark: out the (Free). \n_JavaScript developers of all levels swear by it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@bethanyg* maintains this set of JavaScript resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/python/pya.yml: -------------------------------------------------------------------------------- 1 | name: "PYA" 2 | sidebar-color: "#F6EE3C" 3 | level: "Advanced" 4 | level-icon: ":atrain:" 5 | language: "Python" 6 | language-icon: ":python:" 7 | language-desc: "Here's some Python resources to help you attain mastery!\n:windy: " 8 | language-blurb: "_You've got all the syntax & you've peeked under the covers. You've made your first package, & *comprehended* all the things. Now it's all about Magic Methods, Context Handlers & Cpython. With a side of spam._\n\n\n:return:" 9 | help_link: ":star: :star: `If you get stuck` :white_check_mark: out the docs, they're _surprisingly good:_ . *Also:* . _You'll wonder how you survived without it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@bethanyg* maintains this set of Python resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/python/pyb.yml: -------------------------------------------------------------------------------- 1 | name: "PYB" 2 | sidebar-color: "#337DFF" 3 | language: "Python" 4 | level: "Beginner" 5 | level-icon: ":btrain:" 6 | language-icon: ":python:" 7 | language-desc: "Here's some Python resources to get you started!\n:windy:" 8 | language-blurb: "\n_Python is general-purpose & interpreted. It's design philosophy emphasizes code readability & succinctness. Also? indentations *matter*. It's classed as a dynamic but strongly typed programming langauage with automatic memory management. It supports multiple programming paradigms, including OOP, functional, procedural & imperative. CPython (the reference implementation) is OSS & community-based. The development model is managed by the non-profit . Python was created by *Guido van Rossum* & first released in 1991._\n\n\n:return:" 9 | help_link: ":star: :star: `If you get stuck` :white_check_mark: out the docs, they're _surprisingly good:_ . *Also:* . _You'll wonder how you survived without it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@bethanyg* maintains this set of Python resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageBodies/python/pym.yml: -------------------------------------------------------------------------------- 1 | name: "PYM" 2 | sidebar-color: "#A1F63C" 3 | level: "Intermediate" 4 | level-icon: ":mtrain:" 5 | language: "Python" 6 | language-icon: ":python:" 7 | language-desc: "Here's some Python resources to help you level up!\n:windy:" 8 | language-blurb: "_You've mastered all the basics, & have heard that comprehensions, generators, iterators, & decorators are *a thing*. You've also been meaning to get around to all that OOP goodness everyone is going on about. Say *Ni!* Seize the day._\n\n\n:return:" 9 | help_link: ":star: :star: `If you get stuck` :white_check_mark: out the docs, they're _surprisingly good:_ . *Also:* . _You'll wonder how you survived without it._" 10 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 11 | maintainer: "\n\n\n_*@bethanyg* maintains this set of Python resources. Feedback welcome in _." 12 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageTemplates/genericResourcesAttachmentTemplate.js: -------------------------------------------------------------------------------- 1 | const generateResourcesMessage = (resources, templates) => { 2 | let attachments = []; 3 | for (var template in templates) { 4 | let tempLang = templates[template]['language']; 5 | let tempLevel = templates[template]['level']; 6 | const pretext = `${templates[template]['language-icon']}${templates[template]['level-icon']} *${tempLang} ${tempLevel}*\n${templates[template]['language-blurb']}`; 7 | const title = `${templates[template]['language-desc']}`; 8 | let resourceText = []; 9 | resources.filter(resource => { 10 | return resource['language'].toLowerCase() === tempLang.toLowerCase() && 11 | resource['level'].toLowerCase() === tempLevel.toLowerCase(); 12 | }).forEach(resource => { 13 | let costText = resource['media-cost-desc'] === "FREE" ? '' : `(PAID) ${resource['media-cost']}`; 14 | resourceText.push(`${resource['media-icon']} ${resource['media-link']} ${resource['media-desc']} ${costText}`); 15 | }); 16 | resourceText.push(templates[template]['help_link'], templates[template]['more-questions'], templates[template]['maintainer']); 17 | const text = resourceText.join("\n\n\n"); 18 | const mrkdwn_in = ["text", "pretext"]; 19 | const color = templates[template]['sidebar-color']; 20 | attachments.push({pretext, title, text, mrkdwn_in, color}); 21 | } 22 | return attachments; 23 | }; 24 | 25 | module.exports = { generateResourcesMessage }; 26 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageTemplates/resourcesHelp.js: -------------------------------------------------------------------------------- 1 | const help = { 2 | token: process.env.SLACK_TOKEN, 3 | as_user: true, 4 | link_names: true, 5 | mrkdwn_in: ['text', 'pretext'], 6 | text: '*How to use /resources*', 7 | attachments: JSON.stringify([ 8 | { 9 | title: '/resources is a command fetch recommended CodeBuddies resources on various topics.', 10 | text: [ '* /resources : Prints this message as a DM.', 11 | '* /resources `list`: Prints a list of all available resources, separated by language.', 12 | '* /resources `post [resource language]`: Sends _"getting started in [resource x]"_ message as a DM to the user who typed it.', 13 | '* /resources `post [resource language] [level]`: Sends _[resource x] [level y]_ message as a DM to the user who typed it.', 14 | '* /resources `post [resource language] [level] @username`: Sends the result of the command (resource language, etc._) as a DM to the @user specified.', 15 | '* /resources `post [resource language] [level] [#channel_name]`: Sends a given resource message to the specified channel.', 16 | '* /resources `post [resource language] [level] [#channel_name] @user`: Sends a given resource message to a specified channel AND as a DM to the @user.', 17 | '* NOTE: * multiple filters, channels, and users can be used per request.' 18 | ].join('\n'), 19 | color: '#74c8ed', 20 | }]), 21 | }; 22 | 23 | module.exports = { help }; 24 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/messageTemplates/resourcesMessage.js: -------------------------------------------------------------------------------- 1 | const message = { 2 | token: process.env.SLACK_TOKEN, 3 | as_user: true, 4 | link_names: true, 5 | mrkdwn_in: ['text', 'pretext'], 6 | }; 7 | 8 | 9 | module.exports = { message }; 10 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/resourcesData.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | - name: "JSB" 3 | sidebar-color: "#FFE633" 4 | level: "*Beginner*" 5 | language: "*JavaScript*" 6 | language-icon: ":javascript: :btrain:" 7 | language-desc: "Here's some resources to get you started!\n:windy:" 8 | language-blurb: "\n_JavaScript, often abbreviated as JS, is a high-level, interpreted programming language. It's a language which is also characterized as dynamic, weakly typed, prototype-based and multi-paradigm. It first appeared on December 4th, 1995 and was designed by Brendan Eich._\n\n\n:return:" 9 | video-link: "\n\n\n:cinema: " 10 | video-desc: "_Amazing video tutorials that turn beginners into professional software engineers (TM)._" 11 | tutorial-link: "\n\n:school_satchel: " 12 | tutorial-desc: "_Build things. Lots of things. Build 1,000 things. Keep it up and don't stop. Seriously. This has always been my advice. Just put in the work and you will get better. *30 projects in 30 days*._" 13 | book-link: "\n\n:books: " 14 | book-desc: "_This series represent the reference for anyone who is eager to understand the fundamentals of the Javascript language. The way it is clearly split between the different paradigms of the language with the different books makes it a lot more accessible than other Javascript 'bibles' can be._" 15 | class-link: "\n\n:school_satchel: (PAID) " 16 | class-desc: "Course from Udemy (15$-170$). _An advanced JavaScript course for everyone! Scope, closures, prototypes, 'this', build your own framework, and more._\n\n\n" 17 | help_link: ":star: :star: `If you get stuck` :white_check_mark: out the (Free). \n_JavaScript developers of all levels swear by it._" 18 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 19 | maintainer: "_*@bethanyg* maintains this set of JavaScript resources. Feedback welcome in _." 20 | - name: "JSM" 21 | sidebar-color: "#B2FF33" 22 | level: "*Intermediate*" 23 | language: "*JavaScript*" 24 | language-icon: ":javascript: :mtrain:" 25 | language-desc: "Here's some resources to help you level up!\n:windy:" 26 | language-blurb: "_You’re ready to learn how to debug like a professional, access & create values in more complex data structures, dive deep into callbacks & closures, and learn all about event-driven programming with the DOM. You might want to tackle advanced built-in methods on arrays - or even program your own browser-based interactive games._\n\n\n:return:" 27 | video-link: ":cinema: (PAID) " 28 | video-desc: "Course from Front End Masters (39$/mo) _This course will give you a solid understanding of callbacks & higher order functions, closure, asynchronous JavaScript, & object-oriented JavaScript. It's for developers with basic/intermediate knowledge of JavaScript who want to deepen their understanding to take it to the next level._" 29 | tutorial-link: "\n\n:link: " 30 | tutorial-desc: "_Code. Eat. Sleep. Loop (TM)._" 31 | book-link: "\n\n:books: (PAID) " 32 | book-desc: "Amazon(dot)com (31$) _Eloquent JavaScript, 2nd Edition dives deep into the JavaScript language to show you how to write beautiful, effective code. Author Marijn Haverbeke immerses you in example code from the start, while exercises and full-chapter projects give you hands-on experience with writing your own programs. As you build projects such as an artificial life simulation, a simple programming language, and a paint program._" 33 | class-link: "\n\n:school_satchel: " 34 | class-desc: "_Rithim School is a Bootcamp Located in San Francisco._" 35 | help_link: "\n\n\n:star: :star: `If you get stuck` :white_check_mark: out the (Free). \n_JavaScript developers of all levels swear by it._" 36 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 37 | maintainer: "\n\n\n_*@bethanyg* maintains this set of JavaScript resources. Feedback welcome in _." 38 | - name: "JSA" 39 | sidebar-color: "#33E3FF" 40 | level: "*Advanced*" 41 | language: "*JavaScript*" 42 | language-icon: ":javascript: :atrain:" 43 | language-desc: "Here's some resources to help you attain mastery!\n:windy: " 44 | language-blurb: "_Today's *'Advanced Javascript™'* is tomorrow's *'Old & Busted Javascript™'*. It's just the way the web works. Think you know it all? How about these resoures for a challenge? Work on real-world problems & push the boundaries of engineering practices. And understand it will all be new and different tomorrow._\n\n\n:return:" 45 | video-link: "\n\n\n:cinema: " 46 | video-desc: "*Phillip Roberts* _JavaScript programmers like to use words like, “event-loop”, “non-blocking”, “callback”, 'asynchronous', 'single-threaded' & 'concurrency'. We say things like 'don’t block the event loop', 'make sure your code runs at 60 frames-per-second', 'well of course, it won’t work, that function is an asynchronous callback!' If you’re anything like me, you nod and agree, as if it’s all obvious, even though you don’t actually know what the words mean; & yet, finding good explanations of how JavaScript actually works isn’t all that easy...so let’s learn!_" 47 | tutorial-link: "\n\n:books: (PAID) " 48 | tutorial-desc: "Amazon(dot)com ($22.97) _*Effective JavaScript* is organized around 68 proven approaches for writing better JavaScript, backed by concrete examples. You’ll learn how to choose the right programming style for each project, manage unanticipated problems, & work more successfully with every facet of JavaScript programming from data structures to concurrency._" 49 | book-link: "\n\n:books: (PAID) " 50 | book-desc: "Amazon(dot)com ($13.49) _What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced developer looking to solve problems related to objects, functions, inheritance, & other language-specific categories, the abstractions and & templates in this guide are ideal—whether you're using JavaScript to write a client-side, server-side, or desktop application._" 51 | class-link: "\n\n:link: " 52 | class-desc: "_What it says in the title: just the facts._" 53 | help_link: "\n\n\n:star: :star: `If you get stuck` :white_check_mark: out the (Free). \n_JavaScript developers of all levels swear by it._" 54 | more-questions: "...further :question::question: _you can always ask in _. :slightly_smiling_face:" 55 | maintainer: "\n\n\n_*@bethanyg* maintains this set of JavaScript resources. Feedback welcome in _." 56 | - name: "PyBeg1" 57 | sidebar-color: "#335EFF" 58 | level: "Beginner" 59 | language: "Python" 60 | language-icon: ":heart: :python:" 61 | language-desc: "BEG Here's some resources to help you learn about the language!" 62 | video-link: "BEG :cinema: " 63 | video-desc: "BEG Beazley's simply the BEST." 64 | tutorial-link: "BEG :books: " 65 | tutorial-desc: "BEG _This is the tutorial description._" 66 | book-link: "BEG :books: " 67 | book-desc: "BEG _This is the book description._" 68 | class-link: "BEG :male-teacher::skin-tone-5: " 69 | class-desc: "BEG _This is the class description._" 70 | help_link: "BEG :star: `If you get stuck` ---> :white_check_mark: out the (Free). _Developers of all levels swear by them._" 71 | more-questions: "BEG If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 72 | maintainer: "BEG _This is the *@maintainer* for this Python resource. Bother them with feedback._" 73 | - name: "PyInter1" 74 | sidebar-color: "#6E33FF" 75 | level: "Intermediate" 76 | language: "Python" 77 | language-icon: ":heart: :python:" 78 | language-desc: "INT Here's some resources to help you learn about the language!" 79 | video-link: "INT :cinema: " 80 | video-desc: "INT Beazley's simply the BEST." 81 | tutorial-link: "INT :books: " 82 | tutorial-desc: "INT _This is the tutorial description._" 83 | book-link: "INT :books: " 84 | book-desc: "INT _This is the book description._" 85 | class-link: "INT :male-teacher::skin-tone-5: " 86 | class-desc: "INT _This is the class description._" 87 | help_link: "INT :star: `If you get stuck` ---> :white_check_mark: out the (Free). _Developers of all levels swear by them._" 88 | more-questions: "INT If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 89 | maintainer: "INT _This is the *@maintainer* for this Python resource. Bother them with feedback._" 90 | - name: "PyAdv1" 91 | sidebar-color: "#D433FF" 92 | level: "Advanced" 93 | language: "Python" 94 | language-icon: ":heart: :python:" 95 | language-desc: "ADV Here's some resources to help you learn about the language!" 96 | video-link: "ADV :cinema: " 97 | video-desc: "ADV Beazley's simply the BEST." 98 | tutorial-link: "ADV :books: " 99 | tutorial-desc: "ADV _This is the tutorial description._" 100 | book-link: "ADV :books: " 101 | book-desc: "ADV _This is the book description._" 102 | class-link: "ADV :male-teacher::skin-tone-5: " 103 | class-desc: "ADV _This is the class description._" 104 | help_link: "ADV :star: `If you get stuck` ---> :white_check_mark: out the (Free). _Developers of all levels swear by them._" 105 | more-questions: "ADV If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 106 | maintainer: "ADV _This is the *@maintainer* for this Python resource. Bother them with feedback._" 107 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/resourcesTestData.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | - name: "JSBeg1" 3 | sidebar-color: "#FFE633" 4 | level: "Beginner" 5 | language: "JavaScript" 6 | language-icon: ":heart: :javascript:" 7 | language-desc: "Here's some resources to help you learn about the language!" 8 | video-link: ":cinema: " 9 | video-desc: "Place to _watch_ coding & _practice_ coding." 10 | tutorial-link: ":books: " 11 | tutorial-desc: "_This is the tutorial description._" 12 | book-link: ":books: " 13 | book-desc: "_This is the book description._" 14 | class-link: ":male-teacher::skin-tone-5: " 15 | class-desc: "_This is the class description._" 16 | help_link: ":star: `If you get stuck` ---> :white_check_mark: out the (Free). _JavaScript developers of all levels swear by it._" 17 | more-questions: "If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 18 | maintainer: "_This is the *@maintainer* for this JavaScript resource. Bother them with feedback._" 19 | - name: "JSInter1" 20 | sidebar-color: "#B2FF33" 21 | level: "Intermediate" 22 | language: "JavaScript" 23 | language-icon: ":heart: :javascript:" 24 | language-desc: "INT Here's some resources to help you learn about the language!" 25 | video-link: "INT :cinema: " 26 | video-desc: "INT Place to _watch_ coding & _practice_ coding." 27 | tutorial-link: "INT :books: " 28 | tutorial-desc: "INT _This is the tutorial description._" 29 | book-link: "INT :books: " 30 | book-desc: "INT _This is the book description._" 31 | class-link: "INT :male-teacher::skin-tone-5: " 32 | class-desc: "INT _This is the class description._" 33 | help_link: "INT :star: `If you get stuck` ---> :white_check_mark: out the (Free). _JavaScript developers of all levels swear by it._" 34 | more-questions: "INT If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 35 | maintainer: "INT _This is the *@maintainer* for this JavaScript resource. Bother them with feedback._" 36 | - name: "JSAdv1" 37 | sidebar-color: "#4CFF33" 38 | level: "Advanced" 39 | language: "JavaScript" 40 | language-icon: ":heart: :javascript:" 41 | language-desc: "ADV Here's some resources to help you learn about the language!" 42 | video-link: "ADV :cinema: " 43 | video-desc: "ADV Place to _watch_ coding & _practice_ coding." 44 | tutorial-link: "ADV :books: " 45 | tutorial-desc: "ADV _This is the tutorial description._" 46 | book-link: "ADV :books: " 47 | book-desc: "ADV _This is the book description._" 48 | class-link: "ADV :male-teacher::skin-tone-5: " 49 | class-desc: "ADV _This is the class description._" 50 | help_link: "ADV :star: `If you get stuck` ---> :white_check_mark: out the (Free). _JavaScript developers of all levels swear by it._" 51 | more-questions: "ADV If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 52 | maintainer: "ADV _This is the *@maintainer* for this JavaScript resource. Bother them with feedback._" 53 | - name: "PyBeg1" 54 | sidebar-color: "#335EFF" 55 | level: "Beginner" 56 | language: "Python" 57 | language-icon: ":heart: :python:" 58 | language-desc: "BEG Here's some resources to help you learn about the language!" 59 | video-link: "BEG :cinema: " 60 | video-desc: "BEG Beazley's simply the BEST." 61 | tutorial-link: "BEG :books: " 62 | tutorial-desc: "BEG _This is the tutorial description._" 63 | book-link: "BEG :books: " 64 | book-desc: "BEG _This is the book description._" 65 | class-link: "BEG :male-teacher::skin-tone-5: " 66 | class-desc: "BEG _This is the class description._" 67 | help_link: "BEG :star: `If you get stuck` ---> :white_check_mark: out the (Free). _Developers of all levels swear by them._" 68 | more-questions: "BEG If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 69 | maintainer: "BEG _This is the *@maintainer* for this Python resource. Bother them with feedback._" 70 | - name: "PyInter1" 71 | sidebar-color: "#6E33FF" 72 | level: "Intermediate" 73 | language: "Python" 74 | language-icon: ":heart: :python:" 75 | language-desc: "INT Here's some resources to help you learn about the language!" 76 | video-link: "INT :cinema: " 77 | video-desc: "INT Beazley's simply the BEST." 78 | tutorial-link: "INT :books: " 79 | tutorial-desc: "INT _This is the tutorial description._" 80 | book-link: "INT :books: " 81 | book-desc: "INT _This is the book description._" 82 | class-link: "INT :male-teacher::skin-tone-5: " 83 | class-desc: "INT _This is the class description._" 84 | help_link: "INT :star: `If you get stuck` ---> :white_check_mark: out the (Free). _Developers of all levels swear by them._" 85 | more-questions: "INT If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 86 | maintainer: "INT _This is the *@maintainer* for this Python resource. Bother them with feedback._" 87 | - name: "PyAdv1" 88 | sidebar-color: "#D433FF" 89 | level: "Advanced" 90 | language: "Python" 91 | language-icon: ":heart: :python:" 92 | language-desc: "ADV Here's some resources to help you learn about the language!" 93 | video-link: "ADV :cinema: " 94 | video-desc: "ADV Beazley's simply the BEST." 95 | tutorial-link: "ADV :books: " 96 | tutorial-desc: "ADV _This is the tutorial description._" 97 | book-link: "ADV :books: " 98 | book-desc: "ADV _This is the book description._" 99 | class-link: "ADV :male-teacher::skin-tone-5: " 100 | class-desc: "ADV _This is the class description._" 101 | help_link: "ADV :star: `If you get stuck` ---> :white_check_mark: out the (Free). _Developers of all levels swear by them._" 102 | more-questions: "ADV If you have any further :question: ---> you can always ask in . :slightly_smiling_face:" 103 | maintainer: "ADV _This is the *@maintainer* for this Python resource. Bother them with feedback._" 104 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/resources/slashResources_test.js: -------------------------------------------------------------------------------- 1 | function makeMessage(strings, ...values){ 2 | return 'message'; 3 | } 4 | 5 | let message = { 6 | token: process.env.SLACK_TOKEN, 7 | as_user: true, 8 | link_names: true, 9 | mrkdwn_in: ['text', 'pretext'], 10 | attachments: JSON.stringify([ 11 | { 12 | pretext: '${language-icon} ${language-desc}\n\n\n' 13 | title: '\n${level} ${language}\n\n', 14 | text: '${video-link} ${video-desc}\n\n${tutorial-link} ${tutorial-desc}\n\n${book-link} ${book-desc}\n\n${class-link} ${class-desc}\n\n${help_link}\n\n${more-questions}\n\n\n${maintainer}', 15 | color: '${sidebar-color}', 16 | }]), 17 | }; 18 | 19 | 20 | module.exports = { message }; 21 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/welcome/slashWelcome.js: -------------------------------------------------------------------------------- 1 | const message = { 2 | token: process.env.SLACK_TOKEN, 3 | as_user: true, 4 | link_names: true, 5 | mrkdwn_in: ['text', 'pretext'], 6 | attachments: JSON.stringify([ 7 | { 8 | pretext: 9 | "*Welcome!* _We're glad you're here._\n\n:question: *What exactly is CodeBuddies?* :question:\n\n\nWe’re a community of independent code learners sharing knowledge & helping each other learn faster. We're 100% volunteer. Our website, is open-sourced & welcomes contributions on .\n\n\nWe come from all over the world. We're all different ages; we work & study across all sorts of professions. Everyone is welcome - no previous experience required! \n\n\nThis community thrives thanks to the generosity of our members, Slack admin moderators, GitHub code contributors, website community mentors, & monetary donations on \n\n.", 10 | title: "\n:large_blue_diamond:\n\nLet's Get Started", 11 | text: 12 | "\n\n:smile: Don't be shy! Introduce yourself in & welcome fellow new members. _The more we know about each other, the better we can connect & collaborate._\n\n\n:point_left::skin-tone-5: Browse all our public Slack channels by clicking on `CHANNELS` over in the sidebar.\n\n\n:eyeglasses: Make sure to read our .\n\n\n:computer: Log into our using your new CodeBuddies Slack account & take a look around -- or join a hangout.\n\n\n:calendar: Try scheduling a hangout yourself! (_You don’t need to be an expert to collaborate._) Simply click on the `Schedule a Hangout` button on the website & fill in the information. All hangouts get automatically posted back to .\n\n\n:books: Interested in going deep on topics like * Functional JS* or that special *Ruby* book or *Blockchain*? Start a ! All study groups created on the website also get automatically posted back to . Invite folks here on Slack to join in so that you can motivate each other to master your learning goal.\n\n\n:question: If you have any questions about the community or the project or want to talk to an admin, feel free to ask about it in . It's where discussions related to our community happen.\n\n\n:heavy_plus_sign: If you'd like to add a new integration to the Slack, please ask in the channel as well! Because CodeBuddies is on a free plan, the number of new Slack apps we can integrate is limited, so we would need to choose our integrations wisely.\n\n\n_Happy Learning!_ :wave::skin-tone-5:", 13 | color: '#74c8ed' 14 | }, 15 | { 16 | title: '\n:star:\n\nSome Ground Rules', 17 | text: 18 | 'Our goal is to maintain a safe, helpful & friendly community for everyone, regardless of experience, gender identity & expression, sexual identity & orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, profession, or any other defining characteristic. \n\n\nPlease take the time to *read through the complete before continuing.', 19 | callback_id: 'code-of-conduct', 20 | color: '#3060f0' 21 | } 22 | ]) 23 | }; 24 | 25 | module.exports = { message }; 26 | -------------------------------------------------------------------------------- /src/app/routes/data/slash/welcome/welcomeHelp.js: -------------------------------------------------------------------------------- 1 | const help = { 2 | token: process.env.SLACK_TOKEN, 3 | as_user: true, 4 | link_names: true, 5 | mrkdwn_in: ['text', 'pretext'], 6 | text: '*How to use /welcome*', 7 | attachments: JSON.stringify([ 8 | { 9 | title: 'Greet users with the CB welcome message & Code of Conduct.', 10 | text: [ '* `/welcome` Prints this message.', 11 | '* /welcome `test`: Sends a test welcome message as a DM to the user who typed it.', 12 | '* /welcome `post` [channel]: Sends a welcome message to the channel (as long as the channel has invited the bot).', 13 | '* /welcome `[command] @`: Sends the result of the command (_test, post, etc._) as a DM to the user specified.', 14 | '* /welcome `[command] #channel_name`: Sends the result of the command (_test, post, etc._) to the specified channel.' 15 | ].join('\n'), 16 | color: '#74c8ed', 17 | }]), 18 | }; 19 | 20 | 21 | module.exports = { help }; 22 | -------------------------------------------------------------------------------- /src/app/routes/endpoints/events/initial.js: -------------------------------------------------------------------------------- 1 | const qs = require('querystring'); 2 | const axios = require('axios'); 3 | const JsonDB = require('node-json-db'); 4 | const db = new JsonDB('src/app/users', true, false); 5 | 6 | const postResult = result => console.log(result.data); 7 | 8 | const eventWelcome = (req, res) => { 9 | switch (req.body.type) { 10 | case 'url_verification': { 11 | // verify Events API endpoint by returning challenge if present 12 | res.send({ 13 | challenge: req.body.challenge 14 | }); 15 | break; 16 | } 17 | case 'event_callback': { 18 | if (req.body.token === process.env.SLACK_VERIFICATION_TOKEN) { 19 | const event = req.body.event; 20 | 21 | // `team_join` is fired whenever a new user (incl. a bot) joins the team 22 | // check if `event.is_restricted == true` to limit to guest accounts 23 | if (event.type === 'team_join' && !event.user.is_bot) { 24 | const { team_id, id } = event.user; 25 | initialMessage(team_id, id); 26 | } 27 | res.sendStatus(200); 28 | } else { 29 | res.sendStatus(500); 30 | } 31 | break; 32 | } 33 | default: { 34 | res.sendStatus(500); 35 | } 36 | } 37 | }; 38 | 39 | const initialMessage = (teamId, userId) => { 40 | let data = false; 41 | // try fetch team/user pair. This will throw an error if nothing exists in the db 42 | try { 43 | data = db.getData(`/${teamId}/${userId}`); 44 | } catch (error) { 45 | console.error(error); 46 | } 47 | 48 | // `data` will be false if nothing is found or the user hasn't accepted the ToS 49 | if (!data) { 50 | // add or update the team/user record 51 | db.push(`/${teamId}/${userId}`, false); 52 | 53 | // send the default message as a DM to the user 54 | message.channel = userId; 55 | const params = qs.stringify(message); 56 | const sendMessage = axios.post( 57 | 'https://slack.com/api/chat.postMessage', 58 | params 59 | ); 60 | sendMessage.then(postResult); 61 | } else { 62 | console.log('Already onboarded'); 63 | } 64 | }; 65 | 66 | const message = { 67 | token: process.env.SLACK_TOKEN, 68 | as_user: true, 69 | link_names: true, 70 | mrkdwn_in: ['text', 'pretext'], 71 | attachments: JSON.stringify([ 72 | { 73 | pretext: 74 | "*Welcome!* _We're glad you're here._\n\n:question: *What exactly is CodeBuddies?* :question:\n\n\nWe’re a community of independent code learners sharing knowledge & helping each other learn faster. We're 100% volunteer. Our website, is open-sourced & welcomes contributions on .\n\n\nWe come from all over the world. We're all different ages; we work & study across all sorts of professions. Everyone is welcome - no previous experience required! \n\n\nThis community thrives thanks to the generosity of our members, Slack admin moderators, GitHub code contributors, website community mentors, & monetary donations on \n\n.", 75 | title: "\n:large_blue_diamond:\n\nLet's Get Started", 76 | text: 77 | "\n\n:smile: Don't be shy! Introduce yourself in & welcome fellow new members. _The more we know about each other, the better we can connect & collaborate._\n\n\n:point_left::skin-tone-5: Browse all our public Slack channels by clicking on `CHANNELS` over in the sidebar.\n\n\n:eyeglasses: Make sure to read our Etiquette for General Members>.\n\n\n:computer: Log into our using your new CodeBuddies Slack account & take a look around -- or join a hangout.\n\n\n:calendar: Try scheduling a hangout yourself! (_You don’t need to be an expert to collaborate._) Simply click on the `Schedule a Hangout` button on the website & fill in the information. All hangouts get automatically posted back to .\n\n\n:books: Interested in going deep on topics like * Functional JS* or that special *Ruby* book or *Blockchain*? Start a ! All study groups created on the website also get automatically posted back to . Invite folks here on Slack to join in so that you can motivate each other to master your learning goal.\n\n\n:question: If you have any questions about the community or the project or want to talk to an admin, feel free to ask about it in . It's where discussions related to our community happen.\n\n\n:heavy_plus_sign: If you'd like to add a new integration to the Slack, please ask in the channel as well! Because CodeBuddies is on a free plan, the number of new Slack apps we can integrate is limited, so we would need to choose our integrations wisely.\n\n\n_Happy Learning!_ :wave::skin-tone-5:", 78 | color: '#74c8ed' 79 | }, 80 | { 81 | title: '\n:star:\n\nSome Ground Rules', 82 | text: 83 | 'Our goal is to maintain a safe, helpful & friendly community for everyone, regardless of experience, gender identity & expression, sexual identity & orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, profession, or any other defining characteristic. \n\n\nPlease take the time to *read through & acknowledge* the complete before continuing.', 84 | callback_id: 'code-of-conduct', 85 | color: '#3060f0', 86 | actions: [ 87 | { 88 | name: 'accept', 89 | text: 'Accept', 90 | type: 'button', 91 | value: 'accept', 92 | style: 'primary' 93 | } 94 | ] 95 | } 96 | ]) 97 | }; 98 | 99 | // set the team/user record to true to indicate that they've accepted the ToS 100 | // you might want to store the date/time that the terms were accepted 101 | 102 | const accept = (userId, teamId) => db.push(`/${teamId}/${userId}`, true); 103 | 104 | module.exports = { 105 | eventWelcome, 106 | initialMessage, 107 | accept 108 | }; 109 | -------------------------------------------------------------------------------- /src/app/routes/endpoints/interactive/reminder.js: -------------------------------------------------------------------------------- 1 | const qs = require('querystring'); 2 | const axios = require('axios'); 3 | const JsonDB = require('node-json-db'); 4 | 5 | const db = new JsonDB('users', true, false); 6 | 7 | const postResult = result => console.log(result.data); 8 | const message = require('routes/endpoints/slash/welcomeData'); 9 | 10 | 11 | // find all the users who've been presented the ToS and send them a reminder to accept. 12 | // the same logic can be applied to find users that need to be removed from the team 13 | const remind = () => { 14 | try { 15 | const data = db.getData('/'); 16 | Object.keys(data).forEach((team) => { 17 | Object.keys(data[team]).forEach((user) => { 18 | if (!data[team][user]) { 19 | message.channel = user; 20 | message.text = ':heavy_exclamation_mark: This is a quick reminder. At CodeBuddies, our goal is to maintain a safe, helpful & friendly community for everyone, regardless of experience, gender identity & expression, sexual identity & orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, profession, or any other defining characteristic. \n\n\nPlease take the time to *read through & acknowledge* the complete before continuing in our community.'; 21 | 22 | const params = qs.stringify(message); 23 | const sendMessage = axios.post('https://slack.com/api/chat.postMessage', params); 24 | 25 | sendMessage.then(postResult); 26 | } 27 | }); 28 | }); 29 | } catch (error) { console.error(error); } 30 | }; 31 | 32 | module.exports = { remind }; 33 | -------------------------------------------------------------------------------- /src/app/routes/endpoints/slash/resources/resources.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const qs = require('querystring'); 3 | const postResult = result => console.log(result.data); 4 | 5 | const resourcesData = require('util/ymlLoader.js').messageAttachments; 6 | const messageBodies = require('util/ymlLoader.js').messageBodies; 7 | 8 | const helpData = require('routes/data/slash/resources/messageTemplates/resourcesHelp.js').help; 9 | const resourcesTemplateMessage = require('routes/data/slash/resources/messageTemplates/resourcesMessage.js').message; 10 | const genericResourcesAttachmentTemplate = require('routes/data/slash/resources/messageTemplates/genericResourcesAttachmentTemplate.js').generateResourcesMessage; 11 | const incomingParser = require('util/incomingParser.js'); 12 | 13 | const filterResources = require('util/ymlFilters.js').filterResources; 14 | const filterBodies = require('util/ymlFilters.js').filterTemplates; 15 | 16 | const resources = async (req, res) => { 17 | console.log(`Received slash command ${req.body.command} from ${req.body.user_id} with ${req.body.text}.`); 18 | 19 | const parsedCommand = await incomingParser.parsePayload(req); 20 | 21 | console.log(`parsedCommand is ${JSON.stringify(parsedCommand)}`); 22 | 23 | if (req.body.token === process.env.SLACK_VERIFICATION_TOKEN) { 24 | switch (parsedCommand.action) { 25 | case 'list': { 26 | console.log(JSON.stringify(resourcesData)); 27 | let resourcesCounted = resourcesData.reduce((count, resource) => { 28 | count[resource.language.toLowerCase()] = (count[resource.language.toLowerCase()] || 0) + 1; 29 | return count; 30 | }, Object.create(null)); 31 | filterAndPostResults(parsedCommand.target_channel_id, parsedCommand.target_user_id, listResourcesMessage, resourcesTemplateMessage, "Here is the current count of resources", resourcesCounted); 32 | res.sendStatus(200); 33 | break; 34 | } 35 | case 'post': { 36 | let title; 37 | let attachments; 38 | if (!(Object.keys(parsedCommand.actionArguments).length && parsedCommand.actionArguments.constructor === Object)) { 39 | title = "*Here is the full list of resources:*"; 40 | attachments = genericResourcesAttachmentTemplate(resourcesData, messageBodies); 41 | } else { 42 | const filteredResources = filterResources(resourcesData, parsedCommand.actionArguments); 43 | const filteredBodies = filterBodies(filteredResources, messageBodies); 44 | title = `*Here are the resources you requested:*`; 45 | attachments = genericResourcesAttachmentTemplate(filteredResources, filteredBodies); 46 | } 47 | filterAndPostResults(parsedCommand.target_channel_id, parsedCommand.target_user_id, resourceMessage, resourcesTemplateMessage, title, attachments); 48 | res.sendStatus(200); 49 | break; 50 | } 51 | default: { 52 | filterAndPostResults(parsedCommand.target_channel_id, parsedCommand.target_user_id, helpMessage, helpData); 53 | res.sendStatus(200); 54 | } 55 | } 56 | } else { 57 | res.sendStatus(503); 58 | } 59 | }; 60 | 61 | const helpMessage = (helpData, target_channel_id, title, attachments) => { 62 | if (title) { 63 | helpData.text = title; 64 | } 65 | if (attachments) { 66 | helpData.attachments = JSON.stringify(attachments); 67 | } 68 | helpData.channel = target_channel_id; 69 | const params = qs.stringify(helpData); 70 | const sendMessage = axios.post('https://slack.com/api/chat.postMessage', params); 71 | sendMessage.then(postResult); 72 | }; 73 | 74 | const resourceMessage = (resourcesTemplateMessage, target_channel_id, title, attachments) => { 75 | if (title) { 76 | resourcesTemplateMessage.text = title; 77 | } 78 | if (attachments) { 79 | resourcesTemplateMessage.attachments = JSON.stringify(attachments); 80 | } 81 | resourcesTemplateMessage.channel = target_channel_id; 82 | const params = qs.stringify(resourcesTemplateMessage); 83 | const sendMessage = axios.post('https://slack.com/api/chat.postMessage', params); 84 | sendMessage.then(postResult); 85 | } 86 | 87 | const listResourcesMessage = (resourcesTemplateMessage, target_channel_id, title, attachments) => { 88 | resourcesTemplateMessage.text = title; 89 | resourcesTemplateMessage.attachments = JSON.stringify([ 90 | { 91 | text: Object.entries(attachments).map(entry => { 92 | return `We have ${entry[1]} ${entry[0]} resource${entry[1] == 1 ? '' : 's'}.` 93 | }).join('\n'), 94 | color: '#74C8ED', 95 | }, 96 | { 97 | title: 'How to view these resources:', 98 | text: 'To view a language list, type `/resources post {language}`.\nFor more specific resources, you can also filter by level (beginner, intermediate, or advanced), type (video, book, class, or tutorial), and whether it is free or not.\nFor example, `/resources post javascript beginner book free` would give you a list of free books, aimed at beginning your javascript career.', 99 | color: '#551A8B', 100 | }]); 101 | resourcesTemplateMessage.channel = target_channel_id; 102 | const params = qs.stringify(resourcesTemplateMessage); 103 | const sendMessage = axios.post('https://slack.com/api/chat.postMessage', params); 104 | sendMessage.then(postResult); 105 | } 106 | 107 | const filterAndPostResults = (channels, users, callback, data, title = '', attachments = '') => { 108 | const common = channels.filter(common_id => users.includes(common_id)); 109 | channels.forEach(channel_id => { 110 | if (!common.includes(channel_id)) { 111 | callback(data, channel_id, title, attachments); 112 | } 113 | }); 114 | users.forEach(user_id => { 115 | if (!common.includes(user_id)) { 116 | callback(data, user_id, title, attachments); 117 | } 118 | }); 119 | common.forEach(common_id => { 120 | callback(data, common_id, title, attachments); 121 | }); 122 | } 123 | 124 | module.exports = { resources }; 125 | -------------------------------------------------------------------------------- /src/app/routes/endpoints/slash/welcome/welcome.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const qs = require('querystring'); 3 | const postResult = result => console.log(result.data); 4 | const welcomeData = require('routes/data/slash/welcome/slashWelcome.js').message; 5 | const helpData = require('routes/data/slash/welcome/welcomeHelp.js').help; 6 | const incomingParser = require('util/incomingParser.js'); 7 | 8 | 9 | const welcome = async (req, res) => { 10 | console.log("Received slash command " + req.body.command + " from " + req.body.user_id + " with " + req.body.text); 11 | 12 | const recipients = await incomingParser.parsePayload(req); 13 | console.log(recipients); 14 | 15 | console.log(`USER ID :: ${req.body.user_id}`); 16 | console.log(`TARGET USER :: ${recipients.target_user_id}`); 17 | console.log(`ACTION :: ${recipients.action}`); 18 | console.log(`MESSAGE BODY :: ${recipients.payload}`); 19 | console.log(`CHANNEL NAME :: ${recipients.target_channel_id}`); 20 | 21 | if (req.body.token === process.env.SLACK_VERIFICATION_TOKEN) { 22 | switch (recipients.action) { 23 | case 'test': { 24 | filterAndPostResults(recipients.target_channel_id, recipients.target_user_id, welcomeMessage, welcomeData); 25 | res.sendStatus(200); 26 | break; 27 | } 28 | case 'post': { 29 | filterAndPostResults(recipients.target_channel_id, recipients.target_user_id, welcomeMessage, welcomeData); 30 | res.sendStatus(200); 31 | break; 32 | } 33 | default: { 34 | filterAndPostResults(recipients.target_channel_id, recipients.target_user_id, helpMessage, helpData); 35 | res.sendStatus(200); 36 | } 37 | } 38 | } else { res.sendStatus(503); } 39 | }; 40 | 41 | 42 | 43 | const welcomeMessage = (welcomeData, target_channel_id, title, attachments) => { 44 | if (title) { 45 | welcomeData.text = title; 46 | } 47 | if (attachments) { 48 | welcomeData.attachments = JSON.stringify(attachments); 49 | } 50 | welcomeData.channel = target_channel_id; 51 | const params = qs.stringify(welcomeData); 52 | const sendMessage = axios.post('https://slack.com/api/chat.postMessage', params); 53 | sendMessage.then(postResult); 54 | } 55 | 56 | const helpMessage = (helpData, target_channel_id, title, attachments) => { 57 | if (title) { 58 | helpData.text = title; 59 | } 60 | if (attachments) { 61 | helpData.attachments = JSON.stringify(attachments); 62 | } 63 | helpData.channel = target_channel_id; 64 | const params = qs.stringify(helpData); 65 | const sendMessage = axios.post('https://slack.com/api/chat.postMessage', params); 66 | sendMessage.then(postResult); 67 | }; 68 | 69 | const filterAndPostResults = (channels, users, callback, data, title = '', attachments = '') => { 70 | const common = channels.filter(common_id => users.includes(common_id)); 71 | channels.forEach(channel_id => { 72 | if (!common.includes(channel_id)) { 73 | callback(data, channel_id, title, attachments); 74 | } 75 | }); 76 | users.forEach(user_id => { 77 | if (!common.includes(user_id)) { 78 | callback(data, user_id, title, attachments); 79 | } 80 | }); 81 | common.forEach(common_id => { 82 | callback(data, common_id, title, attachments); 83 | }); 84 | } 85 | 86 | module.exports = { welcome }; 87 | -------------------------------------------------------------------------------- /src/app/util/groupBy.js: -------------------------------------------------------------------------------- 1 | const groupBy = (array, key) => { 2 | return array.reduce( (hash,elem) => { 3 | (hash[elem[key]] = hash[elem[key]] || []).push(elem); 4 | return hash; 5 | }, {}); 6 | }; 7 | 8 | const groupByArray = (array, key) => { 9 | return array.reduce( (hash, elem) => { 10 | let v = key instanceof Function ? key(elem) : elem[key]; 11 | let el = hash.find((r) => r && r.key === v); 12 | if (el) { 13 | el.values.push(elem); 14 | } else { 15 | hash.push({ key: v, values: [elem] }); 16 | } 17 | return hash; 18 | }, []); 19 | }; 20 | 21 | module.exports = { groupBy, groupByArray }; 22 | -------------------------------------------------------------------------------- /src/app/util/incomingParser.js: -------------------------------------------------------------------------------- 1 | // parsePayload is a utility class that takes in an input and returns a 2 | // sanitized object; helps with parsing out input arguments (parsePayload(arg)) 3 | // and returns a standardized object. 4 | // Usage: 5 | // const parsePayload = require('/util/incomingParser').parsePayload; 6 | const axios = require('axios'); 7 | const qs = require('querystring'); 8 | // TODO: Change to absolute paths due to `require` 9 | const sortArguments = require('./sortArguments'); 10 | 11 | const parsePayload = async (req) => { 12 | const textPayload = req.body.text.trim(); 13 | 14 | // user_id of the user who typed the slash command 15 | // If no other info is specified, the Msg should go back to this user 16 | // on the DM channel_id. 17 | // TODO: 18 | const defaultUserId = await findDmChannel(req.body.user_id); 19 | // const defaultUserId = req.body.user_id; 20 | 21 | // user_id of the @ typed with the slash command (if any). 22 | // Extracts all user ids 23 | let targetUserIdArray = []; 24 | const userIdRegexp = /@(.*?)\|/g; 25 | let userMatch; 26 | userMatch = userIdRegexp.exec(textPayload); 27 | while (userMatch != null) { 28 | targetUserIdArray.push(await findDmChannel(userMatch[1])); 29 | userMatch = userIdRegexp.exec(textPayload); 30 | } 31 | 32 | // public/private channel_id typed with the slash command (if any). 33 | // Msgs sent here will be in-channel, and the bot_user needs permissions/join 34 | // for it. 35 | // Extracts all channels 36 | let targetChannelIdArray = []; 37 | const channelIdRegexp = /#(.*?)\|/g; 38 | let channelMatch; 39 | channelMatch = channelIdRegexp.exec(textPayload); 40 | while (channelMatch != null) { 41 | targetChannelIdArray.push(channelMatch[1]); 42 | channelMatch = channelIdRegexp.exec(textPayload); 43 | } 44 | 45 | // Remove any usernames or channels, and extract keywords from the rest. 46 | // Check against a list of predefined keywords. 47 | const remainingText = textPayload.replace(/<.*?>/g, '').replace(/ +/g, ' ').toLowerCase().split(' '); 48 | let actionKeywords = []; 49 | let actionArguments = []; 50 | remainingText.forEach(word => { 51 | if (['test', 'list', 'post'].includes(word)) { 52 | actionKeywords.push(word); 53 | } else { 54 | actionArguments.push(word); 55 | } 56 | }); 57 | 58 | if (actionKeywords.length > 1) { 59 | actionKeywords = []; 60 | } 61 | 62 | if (actionArguments.length === 1 && actionArguments[0] === '') { 63 | actionArguments = {}; 64 | } else { 65 | actionArguments = sortArguments(actionArguments); 66 | } 67 | 68 | // NOTE: Currently not being used - good to remove? - Angelo 4/18 69 | // 70 | // The DM channel id. Default if no target_channel_id is specified. 71 | // Msgs sent here will come from the bot_user as a DM. 72 | // const default_channel_id = req.body.channel_id; 73 | 74 | // Return object 75 | // If both channel(s) and user(s) have been specified, 76 | // return both arrays as is 77 | if (targetChannelIdArray.length && targetUserIdArray.length) { 78 | return { 79 | target_user_id: targetUserIdArray, 80 | target_channel_id: targetChannelIdArray, 81 | action: actionKeywords[0], 82 | actionArguments: actionArguments, 83 | payload: textPayload 84 | }; 85 | 86 | // If only user(s) have been specified, 87 | // set the target channel array to the user ids 88 | } else if (!targetChannelIdArray.length && targetUserIdArray.length) { 89 | return { 90 | target_user_id: targetUserIdArray, 91 | target_channel_id: targetUserIdArray, 92 | action: actionKeywords[0], 93 | actionArguments: actionArguments, 94 | payload: textPayload 95 | }; 96 | 97 | // If only channel(s) have been specified, 98 | // no action is necessary on the arrays 99 | } else if (targetChannelIdArray.length && !targetUserIdArray.length) { 100 | return { 101 | target_user_id: targetUserIdArray, 102 | target_channel_id: targetChannelIdArray, 103 | action: actionKeywords[0], 104 | actionArguments: actionArguments, 105 | payload: textPayload 106 | }; 107 | 108 | // Finally, if no channels or users, add the requested user's id as target 109 | // channel to receive the message as a DM 110 | } else { 111 | targetUserIdArray.push(defaultUserId); 112 | 113 | return { 114 | target_user_id: targetUserIdArray, 115 | target_channel_id: targetUserIdArray, 116 | action: actionKeywords[0], 117 | actionArguments: actionArguments, 118 | payload: textPayload 119 | }; 120 | } 121 | }; 122 | 123 | const findDmChannel = async (userId) => { 124 | const dmRequest = { token: process.env.SLACK_TOKEN, user: userId }; 125 | const params = qs.stringify(dmRequest); 126 | const sendDmRequest = axios.post('https://slack.com/api/im.open', params); 127 | const reqId = await sendDmRequest.then(result => { 128 | return result.data.channel.id; 129 | }).catch(error => {/*console.log(error);*/}); 130 | 131 | return reqId || userId; 132 | }; 133 | 134 | module.exports = { parsePayload }; 135 | -------------------------------------------------------------------------------- /src/app/util/remind.js: -------------------------------------------------------------------------------- 1 | // run this file periodically to find users who have not accepted the ToS 2 | const reminders = require('routes/interactive/reminder'); 3 | 4 | reminders.remind(); 5 | -------------------------------------------------------------------------------- /src/app/util/sortArguments.js: -------------------------------------------------------------------------------- 1 | const sortArguments = (reqArguments) => { 2 | let languages = []; 3 | let levels = []; 4 | let types = []; 5 | let costs = []; 6 | reqArguments.forEach(argument => { 7 | switch (argument.toLowerCase()) { 8 | // languages sort 9 | case 'html-css': 10 | case 'html': 11 | case 'css': { 12 | languages.push('html-css'); 13 | break; 14 | } 15 | case 'javascript': 16 | case 'js': { 17 | languages.push('javascript'); 18 | break; 19 | } 20 | case 'python': 21 | case 'ni!': 22 | case 'spam': { 23 | languages.push('python'); 24 | break; 25 | } 26 | case 'ruby': { 27 | languages.push('ruby'); 28 | break; 29 | } 30 | 31 | // levels sort 32 | case 'beginner': 33 | case 'beg': { 34 | levels.push('beginner'); 35 | break; 36 | } 37 | case 'intermediate': 38 | case 'int': 39 | case 'inter': { 40 | levels.push('intermediate'); 41 | break; 42 | } 43 | case 'advanced': 44 | case 'adv': 45 | case 'moar': { 46 | levels.push('advanced'); 47 | break; 48 | } 49 | 50 | // types sort 51 | case 'book': { 52 | types.push('book'); 53 | break; 54 | } 55 | case 'tutorial': { 56 | types.push('tutorial'); 57 | break; 58 | } 59 | case 'class': { 60 | types.push('class'); 61 | break; 62 | } 63 | case 'video': { 64 | types.push('video'); 65 | break; 66 | } 67 | case 'website': { 68 | types.push('website'); 69 | break; 70 | } 71 | 72 | // costs sort 73 | case 'free': { 74 | costs.push('free'); 75 | break; 76 | } 77 | case 'paid': { 78 | costs.push('paid'); 79 | break; 80 | } 81 | } 82 | }); 83 | let returnArgs = {}; 84 | if (languages.length) { 85 | returnArgs['language'] = languages; 86 | } 87 | if (levels.length) { 88 | returnArgs['level'] = levels; 89 | } 90 | if (types.length) { 91 | returnArgs['media-type'] = types; 92 | } 93 | if (costs.length) { 94 | returnArgs['media-cost-desc'] = costs; 95 | } 96 | return returnArgs; 97 | }; 98 | 99 | module.exports = sortArguments; 100 | -------------------------------------------------------------------------------- /src/app/util/ymlFilters.js: -------------------------------------------------------------------------------- 1 | const filterResources = (resourceList, filters) => { 2 | return resourceList.filter(resource => { 3 | let keep = true; 4 | for (var filter in filters) { 5 | if (!filters.hasOwnProperty(filter)) continue; 6 | 7 | if (!filters[filter].includes(resource[filter].toLowerCase())) { 8 | keep = false; 9 | } 10 | } 11 | return keep; 12 | }) 13 | } 14 | 15 | const filterTemplates = (resources, messageTemplates) => { 16 | let toKeep = {}; 17 | resources.forEach(resource => { 18 | if (toKeep.hasOwnProperty(resource['language'])) { 19 | if (!toKeep[resource['language']].includes(resource['level'])){ 20 | toKeep[resource['language']].push(resource['level']); 21 | } 22 | } else { 23 | toKeep[resource['language']] = [resource['level']]; 24 | } 25 | }) 26 | let keptTemplates = {} 27 | for (var template in messageTemplates) { 28 | let language = messageTemplates[template]['language'].toLowerCase(); 29 | let level = messageTemplates[template]['level'].toLowerCase(); 30 | if ((toKeep.hasOwnProperty(language) && toKeep[language].includes(level))) { 31 | keptTemplates[template] = messageTemplates[template]; 32 | } 33 | } 34 | return keptTemplates; 35 | } 36 | 37 | module.exports = { filterResources, filterTemplates }; 38 | -------------------------------------------------------------------------------- /src/app/util/ymlLoader.js: -------------------------------------------------------------------------------- 1 | /* jshint esversion: 6 */ 2 | const YAML = require('yamljs'); 3 | const path = require('path'); 4 | const glob = require('glob'); 5 | 6 | const messageAttachments = []; 7 | const messageBodies = {}; 8 | 9 | // "cwd" sets the initial directory to start looking in 10 | // "absolute" returns an absolute path to the file, so YAML doesn't get confused 11 | glob("**/*.yml", { "cwd": path.join(__dirname, '..', 'routes', 'data', 'slash', 'resources', 'messageAttachments'), 'absolute': true }, function (err, files) { 12 | files.forEach(file => { 13 | if ((path.basename(file, '.yml') !== "genericAttachment")) { 14 | const resource = YAML.load(path.resolve(file)); 15 | resource.name = path.basename(file, '.yml'); 16 | messageAttachments.push(resource); 17 | } 18 | }); 19 | }); 20 | 21 | glob("**/*.yml", { "cwd": path.join(__dirname, '..', 'routes', 'data', 'slash', 'resources', 'messageBodies'), 'absolute': true }, function (err, files) { 22 | files.forEach(file => { 23 | if ((path.basename(file, '.yml') !== "genericBody")) { 24 | messageBodies[path.basename(file, '.yml')] = YAML.load(path.resolve(file)); 25 | } 26 | }); 27 | }); 28 | 29 | module.exports = { messageAttachments, messageBodies }; 30 | -------------------------------------------------------------------------------- /src/config/localtunnel.js: -------------------------------------------------------------------------------- 1 | // Module for creating a public tunnel to local server. 2 | // This is ran separately from the app itself and along side with 3 | // Node in `development` mode. 4 | require('dotenv').config(); 5 | 6 | const localtunnel = require('localtunnel'); 7 | const config = { 8 | port: process.env.PORT, 9 | subdomain: { "subdomain": process.env.DEV_SUBDOMAIN } 10 | } 11 | 12 | localtunnel(config.port, config.subdomain, function (err, tunnel) { 13 | console.log(`App can be publicly accessed at ${tunnel.url}`); 14 | }); 15 | -------------------------------------------------------------------------------- /src/config/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const path = require('path'); 4 | 5 | const resourcesController = require(path.join(__dirname, '..', 'controllers', 'resources')); 6 | const templatesController = require(path.join(__dirname, '..', 'controllers', 'templates')); 7 | const welcomeController = require(path.join(__dirname, '..', 'controllers', 'welcome')); 8 | const eventsController = require(path.join(__dirname, '..', 'controllers', 'events')); 9 | const interactiveController = require(path.join(__dirname, '..', 'controllers', 'interactive')); 10 | 11 | // Notes on Slack endpoint commands: 12 | // - A given `slash` command is fired whenever a user evokes the "slash ---" 13 | // in a channel. 14 | // - Each function attempts to confirm a verification token & then executes 15 | // various commmand scenarios. 16 | // - Command extentions for each slash command can be added via case 17 | // statements in the various command files. 18 | // 19 | // Notes on Slack Events API 20 | // - url_verification: Returns challenge token sent when present. 21 | // - event_callback: Confirm verification token & handle `team_join` event. 22 | 23 | 24 | // Assign all methods for /templates route 25 | router.route('/templates') 26 | // Landing page 27 | .get(templatesController.getIndex); 28 | 29 | // Assign all methods for /templates/resources route 30 | router.route('/templates/resources') 31 | // Used to display the current resources, 32 | // as an aid to creating Slack markdown formatting 33 | .get(templatesController.getAllResources); 34 | 35 | // Assign all methods for /templates/resources/:name route 36 | router.route('/templates/resources/:name') 37 | // Shows the current route fields 38 | .get(templatesController.getResource) 39 | // Saves any changes to the resource in development 40 | .post(templatesController.updateResource); 41 | 42 | // Assign all methods for /templates/bodies route 43 | router.route('/templates/bodies') 44 | // Used to display the current language bodies, 45 | // as an aid to creating Slack markdown formatting 46 | .get(templatesController.getAllBodies); 47 | 48 | // Assign all methods for /templates/bodies/:name route 49 | router.route('/templates/bodies/:name') 50 | // Shows the current body fields 51 | .get(templatesController.getBody) 52 | // Saves any changes to the message body in development 53 | .post(templatesController.updateBody); 54 | 55 | // Assign all methods for /resources route 56 | router.route('/resources') 57 | // The Slack endpoint command 58 | .post(resourcesController.postResources); 59 | 60 | // Assign all methods for /welcome route 61 | router.route('/welcome') 62 | // The Slack endpoint command 63 | .post(welcomeController.postWelcome); 64 | 65 | // Assign all methods for /events route 66 | router.route('/events') 67 | // The Slack endpoint event 68 | .post(eventsController.postEvent); 69 | 70 | // Assign all methods for /interactive-message route 71 | router.route('/interactive-message') 72 | // The Slack endpoint event 73 | .post(interactiveController.postResponse); 74 | 75 | module.exports = { router }; 76 | -------------------------------------------------------------------------------- /src/controllers/events.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const initalEvent = require(path.join('routes', 'endpoints', 'events', 'initial')); 4 | 5 | const postEvent = (req, res) => { 6 | initalEvent.eventWelcome(req, res); 7 | }; 8 | 9 | module.exports = { postEvent }; 10 | -------------------------------------------------------------------------------- /src/controllers/interactive.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const initalResponse = require(path.join('routes', 'data', 'interactive', 'initialResponse')); 4 | 5 | const postResponse = (req, res) => { 6 | initalResponse.welcomeResponse(req, res); 7 | }; 8 | 9 | module.exports = { postResponse }; 10 | -------------------------------------------------------------------------------- /src/controllers/resources.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const YAML = require('yamljs'); 3 | const fs = require('fs'); 4 | 5 | const resourceData = require(path.join('util', 'ymlLoader')).messageAttachments; 6 | const groupByArray = require(path.join('util', 'groupBy')).groupByArray; 7 | const slashResources = require(path.join('routes', 'endpoints', 'slash', 'resources', 'resources')); 8 | 9 | const getAll = (req, res) => { 10 | const resourcesByLang = groupByArray(resourceData, 'language'); 11 | const groupedResources = resourcesByLang.map( (lang) => { 12 | lang.values = groupByArray(lang.values, 'level'); 13 | return lang; 14 | }); 15 | res.render('resources/index', { title: 'Greetbot Resources', groupedResources: groupedResources }); 16 | }; 17 | 18 | const postResources = (req, res) => { 19 | slashResources.resources(req, res); 20 | }; 21 | 22 | const getResource = (req, res) => { 23 | const resource = resourceData.filter(resource => { 24 | return resource.name === req.params.name; 25 | })[0]; 26 | res.render('resources/show', { title: resource.name, resource: resource }); 27 | }; 28 | 29 | const updateResource = (req, res) => { 30 | const resource = req.body.resource; 31 | const ymltext = YAML.stringify(resource, 2); 32 | fs.writeFile(path.resolve(__dirname, '..', 'app', 'routes', 'data', 'slash', 'resources', 'messageAttachments', resource.language, resource.level, `${resource.name}.yml`), ymltext, (err) => { 33 | if (err) { 34 | console.log(err); 35 | } 36 | }); 37 | res.redirect('/resources'); 38 | }; 39 | 40 | module.exports = { getAll, postResources, getResource, updateResource }; 41 | -------------------------------------------------------------------------------- /src/controllers/templates.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const YAML = require('yamljs'); 3 | const fs = require('fs'); 4 | 5 | const resourceData = require(path.join('util', 'ymlLoader')).messageAttachments; 6 | const bodiesData = require(path.join('util', 'ymlLoader')).messageBodies; 7 | const groupByArray = require(path.join('util', 'groupBy')).groupByArray; 8 | 9 | const getIndex = (req, res) => { 10 | res.render('templates/index', { title: 'Greetbot Templates' }); 11 | }; 12 | 13 | const getAllResources = (req, res) => { 14 | const groupedResources = sortData(resourceData); 15 | res.render('templates/resources/index', { title: 'Greetbot Resources', groupedResources: groupedResources }); 16 | }; 17 | 18 | const getResource = (req, res) => { 19 | const resource = resourceData.filter(resource => { 20 | return resource.name === req.params.name; 21 | })[0]; 22 | res.render('templates/resources/show', { title: resource.name, resource: resource }); 23 | }; 24 | 25 | const updateResource = (req, res) => { 26 | const resource = req.body.resource; 27 | const ymltext = YAML.stringify(resource, 2); 28 | fs.writeFile(path.resolve(__dirname, '..', 'app', 'routes', 'data', 'slash', 'resources', 'messageAttachments', resource.language, resource.level, `${resource.name}.yml`), ymltext, (err) => { 29 | if (err) { 30 | console.log(err); 31 | } 32 | }); 33 | res.redirect('/templates/resources'); 34 | }; 35 | 36 | const getAllBodies = (req, res) => { 37 | const arrayOfBodies = arrayifyBodies(bodiesData); 38 | const groupedBodies = sortData(arrayOfBodies); 39 | res.render('templates/bodies/index', { title: 'Greetbot Language Bodies', groupedResources: groupedBodies }); 40 | }; 41 | 42 | const getBody = (req, res) => { 43 | const arrayOfBodies = arrayifyBodies(bodiesData); 44 | const bodyTemplate = arrayOfBodies.filter(bodyTemplate => { 45 | return bodyTemplate.name === req.params.name; 46 | })[0]; 47 | res.render('templates/bodies/show', { title: bodyTemplate.name, bodyTemplate: bodyTemplate }) 48 | }; 49 | 50 | const updateBody = (req, res) => { 51 | const bodyTemplate = req.body.bodyTemplate; 52 | const ymltext = YAML.stringify(bodyTemplate, 2); 53 | fs.writeFile(path.resolve(__dirname, '..', 'app', 'routes', 'data', 'slash', 'resources', 'messageBodies', bodyTemplate.language.toLowerCase(), `${bodyTemplate.name.toLowerCase()}.yml`), ymltext, (err) => { 54 | if (err) { 55 | console.log(err); 56 | } 57 | }); 58 | res.redirect('/templates/bodies'); 59 | }; 60 | 61 | const arrayifyBodies = (hashData) => { 62 | const arrayified = Object.keys(hashData).map( (key) => { 63 | return hashData[key]; 64 | }); 65 | return arrayified; 66 | } 67 | 68 | const sortData = (data) => { 69 | const dataByLang = groupByArray(data, 'language'); 70 | const groupedData = dataByLang.map( (lang) => { 71 | lang.values = groupByArray(lang.values, 'level'); 72 | return lang; 73 | }); 74 | return groupedData; 75 | }; 76 | 77 | module.exports = { 78 | getIndex, 79 | getAllResources, 80 | getResource, 81 | updateResource, 82 | getAllBodies, 83 | getBody, 84 | updateBody, 85 | }; 86 | -------------------------------------------------------------------------------- /src/controllers/welcome.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const slashWelcome = require(path.join('routes', 'endpoints', 'slash', 'welcome', 'welcome')); 4 | 5 | const postWelcome = (req, res) => { 6 | slashWelcome.welcome(req, res); 7 | }; 8 | 9 | module.exports = { postWelcome }; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* jshint esversion: 6 */ 2 | require('dotenv').config(); 3 | require('app-module-path').addPath(`${__dirname}/app`); 4 | 5 | const express = require('express'); 6 | const bodyParser = require('body-parser'); 7 | const path = require('path'); 8 | 9 | // Set up app and router 10 | const app = express(); 11 | const router = require(path.join(__dirname, 'config', 'routes')).router; 12 | 13 | // Set up views 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'ejs'); 16 | app.use(express.static(path.join(__dirname, 'public'))); 17 | 18 | // parse application/x-www-form-urlencoded && application/json 19 | app.use(bodyParser.urlencoded({ extended: true })); 20 | app.use(bodyParser.json()); 21 | 22 | app.get('/', (req, res) => { 23 | res.render('index', { 24 | title: 'CodeBuddies Greetbot', 25 | headline: 'The Welcome/Code of Conduct app for CodeBuddies is running.', 26 | paragraph: 'Follow the instructions in the README on GitHub to clone/configure this Slack App & your environment variables.' 27 | }); 28 | }); 29 | 30 | // Use defined routes 31 | app.use(router); 32 | 33 | app.listen(process.env.PORT, () => { 34 | console.log(`App listening on port ${process.env.PORT}!`); 35 | }); 36 | 37 | module.exports = app; 38 | -------------------------------------------------------------------------------- /src/public/images/atrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/atrain.png -------------------------------------------------------------------------------- /src/public/images/btrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/btrain.png -------------------------------------------------------------------------------- /src/public/images/javascript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/javascript.png -------------------------------------------------------------------------------- /src/public/images/mtrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/mtrain.png -------------------------------------------------------------------------------- /src/public/images/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/python.png -------------------------------------------------------------------------------- /src/public/images/return.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/return.png -------------------------------------------------------------------------------- /src/public/images/windy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebuddies/greetbot/18f83394c6a4706a4bc8f777605e99f7704eef70/src/public/images/windy.png -------------------------------------------------------------------------------- /src/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 |

<%= headline %>

8 |

<%= paragraph %>

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/templates/bodies/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 |
10 |
11 |

List of current language bodies

12 |

13 | Back to templates selector 14 |

15 |
16 | 17 | <% groupedResources.forEach(function(bodyLang) { %> 18 |
19 |
20 |
<%= bodyLang.key %>
21 |
22 |
23 | <% bodyLang.values.forEach(function(bodyLevel) { %> 24 |
25 |
<%= bodyLevel.key %>
26 | 31 |
32 | <% }); %> 33 |
34 |
35 |
36 |
37 | <% }); %> 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/templates/bodies/partials/edit_form.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | " /> 5 |
6 |
7 | 8 | " /> 9 |
10 |
11 | 12 | " /> 13 |
14 |
15 | 16 | " /> 17 |
18 |
19 | 20 | " /> 21 |
22 |
23 | 24 | " /> 25 |
26 |
27 | 28 | " /> 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 | -------------------------------------------------------------------------------- /src/views/templates/bodies/partials/readonly_form.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 |
6 |
7 | 8 |

9 |
10 |
11 | 12 |

13 |
14 |
15 | 16 |

17 |
18 |
19 | 20 |

21 |
22 |
23 | 24 |

25 |
26 |
27 | 28 |

29 |
30 |
31 | 32 |

33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 |

41 |
42 |
43 | 44 |

45 |
46 |
47 | 48 |

49 |
50 |
51 | -------------------------------------------------------------------------------- /src/views/templates/bodies/show.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 34 | 35 | 36 |
37 |
38 |

Info about this body template

39 |

40 | Back to body templates list 41 |

42 |
43 |
44 |
45 | <%- include partials/edit_form %> 46 |
47 |
48 | <%- include partials/readonly_form %> 49 |
50 |
51 |
52 | 53 | 54 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/views/templates/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 |
10 |
11 |

Which templates would you like to view?

12 | Resources 13 | Message Bodies 14 |
15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/views/templates/resources/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 |
10 |
11 |

List of current resources

12 |

13 | Back to templates selector 14 |

15 |
16 | 17 | <% groupedResources.forEach(function(resourceLang) { %> 18 |
19 |
20 |
<%= resourceLang.key %>
21 |
22 |
23 | <% resourceLang.values.forEach(function(resourceLevel) { %> 24 |
25 |
<%= resourceLevel.key %>
26 |
    27 | <% resourceLevel.values.forEach(function(resource) { %> 28 |
  • <%= resource.name %>
  • 29 | <% }); %> 30 |
31 |
32 | <% }); %> 33 |
34 |
35 |
36 |
37 | <% }); %> 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/templates/resources/partials/edit_form.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | " /> 5 |
6 |
7 | 8 | " /> 9 |
10 |
11 | 12 | " /> 13 |
14 |
15 | 16 | " /> 17 |
18 |
19 | 20 | " /> 21 |
22 |
23 | 24 | " /> 25 |
26 |
27 | 28 | " /> 29 |
30 |
31 | 32 | " /> 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | " /> 41 |
42 |
43 | 44 | " /> 45 |
46 | 47 |
48 | -------------------------------------------------------------------------------- /src/views/templates/resources/partials/readonly_form.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 |
6 |
7 | 8 |

9 |
10 |
11 | 12 |

13 |
14 |
15 | 16 |

17 |
18 |
19 | 20 |

21 |
22 |
23 | 24 |

25 |
26 |
27 | 28 |

29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 |

37 |
38 |
39 | 40 |

41 |
42 |
43 | 44 |

45 |
46 |
47 | 48 |

49 |
50 |
51 | -------------------------------------------------------------------------------- /src/views/templates/resources/show.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 14 | 15 | 16 |
17 |
18 |

Info about this resource

19 |

20 | Back to resources list 21 |

22 |
23 |
24 |
25 | <%- include partials/edit_form %> 26 |
27 |
28 | <%- include partials/readonly_form %> 29 |
30 |
31 |
32 | 33 | 34 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | mocha: true 4 | -------------------------------------------------------------------------------- /test/incomingParser.test.js: -------------------------------------------------------------------------------- 1 | // NOTE: This test suite tests the method parsePayload() in incomingParserJS. 2 | const expect = require('chai').expect; 3 | const parsePayload = require('../src/app/util/incomingParser').parsePayload; 4 | 5 | describe('parsePayload()', function () { 6 | let req; 7 | 8 | beforeEach(function () { 9 | req = { 10 | 'body': { 11 | 'command': '/welcome', 12 | 'text': 'test', 13 | 'user_id': '1A2B' 14 | } 15 | }; 16 | }); 17 | 18 | // TODO: Better to test if certain keywords are valid and if not, 19 | // it should return undefined. 20 | describe('when invoked with no arguments', function () { 21 | it('should target the user_id', async function () { 22 | req.body.text = ''; 23 | const res = await parsePayload(req); 24 | expect(res.target_channel_id[0]).to.equal(req.body.user_id); 25 | }); 26 | }); 27 | 28 | describe('when invoked with single keyword arguments', function () { 29 | it('should respond with the correct action', async function () { 30 | const res = await parsePayload(req); 31 | expect(res.action).to.equal(req.body.text); 32 | }); 33 | }); 34 | 35 | describe('when invoked with multiple keyword arguments', function () { 36 | it('should return an undefined action', async function () { 37 | req.body.text = 'list post'; 38 | const res = await parsePayload(req); 39 | expect(res.action).to.equal(undefined); 40 | }); 41 | }); 42 | 43 | describe('when invoked with a channel', function () { 44 | it('should pick up the channel id as an array', async function () { 45 | req.body.text = '<#123|general>'; 46 | const res = await parsePayload(req); 47 | 48 | expect(res.target_channel_id).to.be.an('array'); 49 | expect(res.target_channel_id).to.eql(['123']); 50 | }); 51 | 52 | it('should pick up multiple channels', async function () { 53 | req.body.text = '<#123|general> <#111|random>'; 54 | const res = await parsePayload(req); 55 | expect(res.target_channel_id).to.eql(['123', '111']); 56 | }); 57 | }); 58 | 59 | describe('when invoked with user', function () { 60 | it('should pick up users as an array', async function () { 61 | req.body.text = '<@1A2B|stain88>'; 62 | const res = await parsePayload(req); 63 | 64 | expect(res.target_user_id).to.be.an('array'); 65 | expect(res.target_user_id).to.eql(['1A2B']); 66 | }); 67 | 68 | it('should pick up multiple users in an array', async function () { 69 | req.body.text = `<@1A2B|stain88> <@1BBB|Bethany Tester`; 70 | const res = await parsePayload(req); 71 | 72 | expect(res.target_user_id).to.eql(['1A2B', '1BBB']); 73 | }); 74 | }); 75 | 76 | describe('when invoked with action_arguments', function () { 77 | it('should default to an empty object', async function () { 78 | req.body.text = ''; 79 | const res = await parsePayload(req); 80 | 81 | expect(res.actionArguments).to.be.an('object'); 82 | expect(res.actionArguments).to.eql({}); 83 | }); 84 | 85 | it('should return a language in an array', async function () { 86 | req.body.text = 'js'; 87 | const res = await parsePayload(req); 88 | expect(res.actionArguments.language).to.be.an('array'); 89 | expect(res.actionArguments).to.eql({ 'language': ['javascript'] }); 90 | }); 91 | 92 | it('should return multiple languages', async function () { 93 | req.body.text = 'js python'; 94 | const res = await parsePayload(req); 95 | const expected = { 'language': ['javascript', 'python'] }; 96 | 97 | expect(res.actionArguments).to.eql(expected); 98 | }); 99 | 100 | it('should be case-insensitive', async function () { 101 | req.body.text = 'SPAM'; 102 | const res = await parsePayload(req); 103 | 104 | expect(res.actionArguments).to.eql({ 'language': ['python'] }); 105 | }); 106 | }); 107 | 108 | describe('when invoked with level as arguments', function () { 109 | it('should return a level', async function () { 110 | req.body.text = 'beg'; 111 | const res = await parsePayload(req); 112 | 113 | expect(res.actionArguments).to.eql({ 'level': ['beginner'] }); 114 | }); 115 | 116 | it('should return multiple levels', async function () { 117 | req.body.text = 'int moar'; 118 | const res = await parsePayload(req); 119 | const expected = { 'level': ['intermediate', 'advanced'] }; 120 | 121 | expect(res.actionArguments).to.eql(expected); 122 | }); 123 | }); 124 | 125 | describe('when invoked with a media type', function () { 126 | it('should return the correct media type', async function () { 127 | req.body.text = 'book'; 128 | const res = await parsePayload(req); 129 | 130 | expect(res.actionArguments).to.eql({ 'media-type': ['book'] }); 131 | }); 132 | 133 | it('should return multiple types', async function () { 134 | req.body.text = 'class website'; 135 | const res = await parsePayload(req); 136 | const expected = { 'media-type': ['class', 'website'] }; 137 | 138 | expect(res.actionArguments).to.eql(expected); 139 | }); 140 | }); 141 | 142 | // NOTE: This seems a little brittle - should these args be in a certain 143 | // order? - Angelo 4/18 144 | describe('when invoked with complex args for media-type', function () { 145 | it('should return an object the correct values', async function () { 146 | req.body.text = 'javascript beginner free book'; 147 | const res = await parsePayload(req); 148 | const obj = res.actionArguments; 149 | 150 | expect(obj.language).to.eql(['javascript']); 151 | expect(obj.level).to.eql(['beginner']); 152 | expect(obj['media-cost-desc']).to.eql(['free']); 153 | expect(obj['media-type']).to.eql(['book']); 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | -------------------------------------------------------------------------------- /test/routes.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const request = require('supertest'); 3 | const greetbot = require('../src/index.js'); 4 | 5 | describe('Greetbot routes testing', function () { 6 | describe('#GET /', function () { 7 | it('should load a basic page', function (done) { 8 | request(greetbot).get('/') 9 | .end(function (err, res) { 10 | if (err) { console.log(err); } 11 | expect(res.statusCode).to.equal(200); 12 | done(); 13 | }); 14 | }); 15 | 16 | it('page should contain some basic intro text', function (done) { 17 | request(greetbot).get('/') 18 | .end(function (err, res) { 19 | if (err) { console.log(err); } 20 | expect(res.text.length).to.not.equal(0); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | describe('#GET /templates', function() { 27 | it('should load a templates index page', function (done) { 28 | request(greetbot).get('/templates') 29 | .end(function (err, res) { 30 | if (err) { console.log(err); } 31 | expect(res.statusCode).to.equal(200); 32 | done(); 33 | }); 34 | }); 35 | 36 | it('should contain some text', function (done) { 37 | request(greetbot).get('/templates') 38 | .end(function (err, res) { 39 | if (err) { console.log(err); } 40 | expect(res.text.length).to.not.equal(0); 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | describe('#GET /templates/resources', function() { 47 | it('should load a templates index page', function (done) { 48 | request(greetbot).get('/templates/resources') 49 | .end(function (err, res) { 50 | if (err) { console.log(err); } 51 | expect(res.statusCode).to.equal(200); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('should contain some text', function (done) { 57 | request(greetbot).get('/templates/resources') 58 | .end(function (err, res) { 59 | if (err) { console.log(err); } 60 | expect(res.text.length).to.not.equal(0); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | 66 | describe('#GET /templates/bodies', function() { 67 | it('should load a templates index page', function (done) { 68 | request(greetbot).get('/templates/bodies') 69 | .end(function (err, res) { 70 | if (err) { console.log(err); } 71 | expect(res.statusCode).to.equal(200); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('should contain some text', function (done) { 77 | request(greetbot).get('/templates/bodies') 78 | .end(function (err, res) { 79 | if (err) { console.log(err); } 80 | expect(res.text.length).to.not.equal(0); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | 86 | describe('#GET /templates/resources/:name', function() { 87 | it('should load a templates/resources show page', function (done) { 88 | request(greetbot).get('/templates/resources/effectivejs') 89 | .end(function (err, res) { 90 | if (err) { console.log(err); } 91 | expect(res.statusCode).to.equal(200); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('should contain some text', function (done) { 97 | request(greetbot).get('/templates/resources/effectivejs') 98 | .end(function (err, res) { 99 | if (err) { console.log(err); } 100 | expect(res.text.length).to.not.equal(0); 101 | done(); 102 | }); 103 | }); 104 | }); 105 | describe('#GET /templates/bodies/:name', function() { 106 | it('should load a templates/bodies show page', function (done) { 107 | request(greetbot).get('/templates/bodies/JSA') 108 | .end(function (err, res) { 109 | if (err) { console.log(err); } 110 | expect(res.statusCode).to.equal(200); 111 | done(); 112 | }); 113 | }); 114 | 115 | it('should contain some text', function (done) { 116 | request(greetbot).get('/templates/bodies/JSA') 117 | .end(function (err, res) { 118 | if (err) { console.log(err); } 119 | expect(res.text.length).to.not.equal(0); 120 | done(); 121 | }); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /test/welcome.test.js: -------------------------------------------------------------------------------- 1 | // NOTE: Skipping welcome test as `/welcome` could be deprecated soon. 2 | // Angelo 4/18 3 | // const expect = require('chai').expect; 4 | // const request = require('supertest'); 5 | // const greetbot = require('../src/index.js'); 6 | // 7 | // describe.skip('#POST /welcome', function () { 8 | // describe('no arguments', function () { 9 | // const req = { 'body': 10 | // { 'command': '/welcome', 11 | // 'text': '' 12 | // // 'user_id': user_id, 13 | // // 'token': slack_ver_token } 14 | // } 15 | // }; 16 | // 17 | // it('should be valid', function (done) { 18 | // request(greetbot).post('/welcome') 19 | // .send(req.body) 20 | // .end(function (err, res) { 21 | // if (err) { console.log(err); } 22 | // expect(res.statusCode).to.equal(200); 23 | // done(); 24 | // }); 25 | // }); 26 | // }); 27 | // }); 28 | --------------------------------------------------------------------------------