├── .nvmrc
├── commands
├── quickref
│ ├── references
│ │ ├── linux.txt
│ │ ├── regex.txt
│ │ ├── printf.txt
│ │ ├── sqljoin.txt
│ │ ├── dotnet.txt
│ │ ├── style.txt
│ │ ├── codeblock.txt
│ │ ├── nohello.txt
│ │ ├── subnet.txt
│ │ └── hotkeys.txt
│ └── quickref.js
├── pro
│ ├── proTerms.js
│ ├── proTerms.txt
│ └── pro.js
├── help
│ ├── topics
│ │ ├── cpp.txt
│ │ ├── interviews.txt
│ │ ├── css.txt
│ │ ├── dsa.txt
│ │ ├── go.txt
│ │ ├── java.txt
│ │ ├── podcasts.txt
│ │ ├── dotnet.txt
│ │ ├── react.txt
│ │ ├── ml.txt
│ │ ├── python.txt
│ │ ├── php.txt
│ │ ├── ruby.txt
│ │ └── javascript.txt
│ └── help.js
├── uptime
│ └── uptime.js
├── eval
│ ├── gentoken.js
│ └── eval.js
├── server
│ └── server.js
├── xkcd
│ └── xkcd.js
├── pbf
│ └── pbf.js
├── vote
│ └── vote.js
├── weather
│ └── weather.js
├── info
│ └── info.js
└── stream
│ └── stream.js
├── tokens.json.example
├── .codeclimate.yml
├── .gitignore
├── .eslintrc.yaml
├── start.js
├── lib
└── utils.js
├── test
└── test.js
├── .travis.yml
├── settings.json
├── LICENSE
├── package.json
├── README.md
└── TheAwesomeBot.js
/.nvmrc:
--------------------------------------------------------------------------------
1 | node
2 |
--------------------------------------------------------------------------------
/commands/quickref/references/linux.txt:
--------------------------------------------------------------------------------
1 | https://files.fosswire.com/2007/08/fwunixrefshot.png
--------------------------------------------------------------------------------
/commands/quickref/references/regex.txt:
--------------------------------------------------------------------------------
1 | http://www.computerhope.com/jargon/r/regular-expression.gif
--------------------------------------------------------------------------------
/commands/quickref/references/printf.txt:
--------------------------------------------------------------------------------
1 | https://codywu2010.files.wordpress.com/2014/10/printf_format.png
--------------------------------------------------------------------------------
/commands/quickref/references/sqljoin.txt:
--------------------------------------------------------------------------------
1 | http://www.ilearnttoday.com/wp-content/uploads/2016/07/Visual_SQL_JOINS_orig.jpg
--------------------------------------------------------------------------------
/tokens.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "discord": "",
3 | "google_geocode": "",
4 | "darksky": "",
5 | "replit": ""
6 | }
7 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | eslint:
4 | enabled: true
5 | channel: "eslint-3"
6 | fixme:
7 | enabled: true
8 | ratings:
9 | paths:
10 | - "**.js"
11 |
--------------------------------------------------------------------------------
/commands/quickref/references/dotnet.txt:
--------------------------------------------------------------------------------
1 | http://www.hanselman.com/blog/content/binary/Windows-Live-Writer/ASP.NET-5-is-dead---Introducing-ASP.NE.0_E068/image_60140ec0-ce46-4dbf-a14f-4210eab7f42c.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /npm-debug.log
3 |
4 | *.swp
5 | *.swo
6 | .vscode
7 |
8 | *.DS_Store
9 |
10 | # ignore tokens file
11 | /tokens.json
12 |
13 | # tern server
14 | .tern-port
15 |
--------------------------------------------------------------------------------
/commands/pro/proTerms.js:
--------------------------------------------------------------------------------
1 | const proTerms = require('fs')
2 | .readFileSync(`${__dirname}/proTerms.txt`, 'utf8')
3 | .split('\n')
4 | .map(str => str.split('|'));
5 |
6 | module.exports = proTerms;
7 |
--------------------------------------------------------------------------------
/commands/quickref/references/style.txt:
--------------------------------------------------------------------------------
1 | Here is a link to Discord's text styles:
2 |
3 | https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-
4 |
--------------------------------------------------------------------------------
/commands/help/topics/cpp.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning C++:
2 |
3 | Reference
4 |
5 |
6 | The Definitive C++ Book Guide and List
7 |
8 |
9 | C++ FAQ
10 |
11 |
--------------------------------------------------------------------------------
/commands/uptime/uptime.js:
--------------------------------------------------------------------------------
1 | const time = require('../../lib/utils.js').time;
2 |
3 | module.exports = {
4 | usage: 'uptime - prints my uptime',
5 | run: (bot, message) => {
6 | message.channel.sendMessage(
7 | `Uptime: ${time.timeElapsed(bot.bootTime, new Date())}`);
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/commands/eval/gentoken.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 |
3 | module.exports = (replitApiKey) => {
4 | const hmac = crypto.createHmac('sha256', replitApiKey);
5 |
6 | const timeCreated = Date.now();
7 | hmac.update(timeCreated.toString());
8 | const msgMac = hmac.digest('base64');
9 |
10 | return {
11 | time_created: timeCreated,
12 | msg_mac: msgMac,
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | env:
2 | node: true
3 | rules:
4 | no-console: off
5 | max-len: [error, 120]
6 | prefer-template: off
7 | no-constant-condition: [error, {checkLoops: false}]
8 | no-unused-vars: [error, {argsIgnorePattern: '^(err$|_)'}]
9 | no-param-reassign: [error, {props: false}]
10 | import/no-unresolved: [error, {ignore: ['tokens.json']}]
11 | extends: airbnb
12 | installedESLint: true
13 |
--------------------------------------------------------------------------------
/commands/quickref/references/codeblock.txt:
--------------------------------------------------------------------------------
1 | Use codeblocks for formatting code (line breaks matter!):
2 | \```language_name
3 | # code here
4 | \```
5 |
6 | For example:
7 | \```python
8 | if True:
9 | print("Hi!")
10 | \```
11 | prints:
12 | ```python
13 | if True:
14 | print("Hi!")
15 | ```
16 | You can find the available language names here:
17 |
--------------------------------------------------------------------------------
/commands/help/topics/interviews.txt:
--------------------------------------------------------------------------------
1 | Use these resources to prepare for interviews:
2 |
3 | Paid Book - Cracking the Coding Interview, 6th Edition
4 |
5 |
6 | Paid Book - Programming Interviews Exposed
7 |
8 |
9 | Paid Book - Elements of Programming Interviews !WARNING COMES IN SEVERAL LANGUAGES!
10 |
11 |
--------------------------------------------------------------------------------
/start.js:
--------------------------------------------------------------------------------
1 | const Bot = require('./TheAwesomeBot.js');
2 | const Tokens = require('./tokens.json');
3 |
4 | function start() {
5 | // check for the discord token
6 | let jsonToken = false;
7 | if (Tokens) {
8 | jsonToken = Tokens.discord;
9 | }
10 | const token = jsonToken || process.env.DISCORD_TOKEN;
11 | if (!token) {
12 | throw Error('Discord token not set');
13 | }
14 |
15 | (new Bot(token).init());
16 | }
17 |
18 | start();
19 |
20 |
--------------------------------------------------------------------------------
/commands/help/topics/css.txt:
--------------------------------------------------------------------------------
1 | See these links for learning CSS:
2 |
3 | Tutorials/Documentation (In-depth)
4 |
5 |
6 | A Free Visual Guide to CSS:
7 |
8 |
9 | Free Course: Learn the basics of HTML and CSS
10 |
11 |
12 | Tutorials/Documentation (Good for Quick Reference)
13 |
14 |
15 | Tutorial: Simple introduction to CSS
16 |
17 |
--------------------------------------------------------------------------------
/commands/help/topics/dsa.txt:
--------------------------------------------------------------------------------
1 | Use these resources to learn Data Structures and Algorithms:
2 |
3 | Free Course - Sedgewick's Coursera Part 1
4 |
5 |
6 | Free Course - Sedgewick's Coursera Part 2
7 |
8 |
9 | Paid Book - Sedgewick's Algorithms in Java
10 |
11 |
12 | Paid Book - CLRS/Introduction to Algorithms
13 |
--------------------------------------------------------------------------------
/commands/help/topics/go.txt:
--------------------------------------------------------------------------------
1 | See these resources and links for learning Go:
2 |
3 | Documentation:
4 |
5 |
6 | Official Tour:
7 |
8 |
9 | Introductory Video:
10 |
11 |
12 | Godoc
13 |
14 |
15 | Effective Go <- READ THIS
16 |
17 |
18 | Sentdex - Introductory Go Videos
19 |
--------------------------------------------------------------------------------
/commands/help/topics/java.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning Java:
2 |
3 | Documentation (Java 8)
4 |
5 |
6 | Official Oracle tutorials
7 |
8 |
9 | Free Course: Object-Oriented programming with Java, part I & II (University of Helsinki)
10 | http://mooc.fi/courses/2013/programming-part-1/
11 |
12 | Free materials: MIT 6.005: Software Construction (Warning: Needs some programming expirience)
13 | https://ocw.mit.edu/ans7870/6/6.005/s16/
14 |
--------------------------------------------------------------------------------
/commands/help/topics/podcasts.txt:
--------------------------------------------------------------------------------
1 | Here are some technology podcasts:
2 |
3 | *Coder Radio* by Jupiter Broadcasting
4 |
5 |
6 | *Talk Python to Me* by Michael Kennedy
7 |
8 |
9 | *Simple Programmer* by John Sonmez
10 |
11 |
12 | *Import This* by Kenneth Reitz
13 |
14 |
15 | *whiletruefm* by kennethlove, lethargilistic, samwho, and DiLemmA
16 |
17 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | const time = {
2 | MAGNITUDES: [
3 | [1000 * 60 * 60, 'hours'],
4 | [1000 * 60, 'minutes'],
5 | [1000, 'seconds'],
6 | [1, 'milliseconds'],
7 | ],
8 |
9 | timeElapsed(before, after) {
10 | let diff = Math.abs(after - before);
11 | return this.MAGNITUDES.reduce((out, m) => {
12 | const current = Math.floor(diff / m[0]);
13 | diff %= m[0];
14 | if (out.length || current) {
15 | out.push(`${current} ${m[1]}`);
16 | }
17 | return out;
18 | }, []).join(', ');
19 | },
20 | };
21 |
22 | module.exports = {
23 | time,
24 | };
25 |
--------------------------------------------------------------------------------
/commands/help/topics/dotnet.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning .NET/C#:
2 |
3 | Free Course: Fundamentals for Absolute Beginners
4 |
5 |
6 | Video Series: Getting Started with Visual Studio & C# .NET with Nerdgasm
7 |
8 |
9 | Book: C# in Depth, Third Edition by Jon Skeet (http://meta.stackexchange.com/q/9134)
10 |
11 |
12 | Paid Course Library: Fundamentals, ASP.NET MVC, LINQ, Entity Framework and more
13 |
14 |
--------------------------------------------------------------------------------
/commands/quickref/references/nohello.txt:
--------------------------------------------------------------------------------
1 | __**Please Don't Say Just Hello In Chat**__
2 | ```
3 | 2016-07-19 12:32:12 you: Hi
4 | 2016-07-19 12:32:15 co-worker: Hello.
5 | ## CO-WORKER WAITS WHILE YOU PHRASE YOUR QUESTION
6 | 2016-07-19 12:34:01 you: I'm working on [something] and I'm trying to do [etc...]
7 | 2016-07-19 12:35:21 co-worker: Oh, that's [answer...]
8 | ```
9 | It's as if you called someone on the phone and said "Hi!" and then put them on hold!
10 |
11 | Please do this instead:
12 | ```
13 | 2016-07-19 12:32:12 you: Hi -- I'm working on [something] and I'm trying to do [etc...]
14 | 2016-07-19 12:33:32 co-worker: [answers question]
15 | ```
16 | *You can learn more at http://nohello.com/*
17 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const test = require('tape');
2 | const TheAwesomeBot = require('../TheAwesomeBot');
3 |
4 | const token = process.env.DISCORD_TOKEN || require('../tokens.json').discord; // eslint-disable-line global-require
5 |
6 | test('connect & disconnect', (t) => {
7 | t.timeoutAfter(15000);
8 | t.ok(token, 'discord token should be set');
9 |
10 | const bot = new TheAwesomeBot(token);
11 | t.false(bot.isReady, 'bot should not be ready');
12 | bot.init();
13 | // wait for it to be ready
14 | const si = setInterval(() => {
15 | if (bot.isReady) {
16 | bot.deinit().then(() => {
17 | clearInterval(si);
18 | t.end();
19 | });
20 | }
21 | }, 5000);
22 | });
23 |
--------------------------------------------------------------------------------
/commands/server/server.js:
--------------------------------------------------------------------------------
1 | const discord = require('discord.js');
2 |
3 | module.exports = {
4 | usage: 'server - prints info about the server',
5 | run: (bot, message) => {
6 | const embed = new discord.RichEmbed();
7 | embed.setTitle('Server Owner')
8 | .setColor('#ff7260')
9 | .setAuthor(message.guild.name, message.guild.iconURL)
10 | .setDescription(message.guild.owner.user.username)
11 | .addField('Members', message.guild.members.size, true)
12 | .addField('Created', message.guild.createdAt.toString(), true)
13 | .addField('Emojis',
14 | message.guild.emojis.size > 0 ? message.guild.emojis.map(d => d.toString()).join(' ') : 'None');
15 | message.channel.sendEmbed(embed);
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | dist: trusty
4 | script:
5 | - npm test
6 | - npm run lint .
7 | env:
8 | global:
9 | secure: SpEGhkGHrgZX5zB8o1926TeQDgO1t9npUm3cmTGGhV1GKpz9mt0WRqmIReOLVSG9j0UxF8RfqCHfVulGMg7C2YfOi7KecOF58IVWnajyh+zdhuf1dOEhb9Bl6YWWSzMJUUTVVNmeAf9AkhpcseKBsZLMlJeeK5eWgBkPqsiPvq1xT/8Y+QQ/pDZvmTPqUOB7OAghLZJGC1UuSy7R6uwk77JlbknSuJdb9lM2z4jIDxRrWjiUoaKUDFCJnER23whxvQOp4kp74YlmRC6oT2gQkez6mNlVvJkKDU7FM3QAanKlMjbx12k4++HpdiPfm8RLXUXVHC5CsD3TvhuCXtbsS2j8gpLI34NSC4AC5u/Xp9RgfSE96I0zY2upURFavZIDsTSCsu6XCvRjS0ltsZbVdVj8/WQxLW1YvfpgMQ5+NREBwLQpB/dil2lpoRyz5rSPYc6ta6nq9bNonpeHU7UaQnXIrKoKPdJOxJ+59sxjTwoNbjiS2uJmRfYSo6BCQrYHdx5p5eA92hBqjicjHEM9Px5+X4piRbCULnczV0HsPFATaCrzfW7YcxDgUIXhmm4lagPhaYo5LPT2PLwZ+pTL2en8h9Gbd31e2AyJfHT6WnTzxfk1PGnQPT2GBfhiDV05by8OaFToYPXIJWu6E4EiQc0HuEp4avSsZLYvvlf4Me4=
10 |
--------------------------------------------------------------------------------
/commands/quickref/references/subnet.txt:
--------------------------------------------------------------------------------
1 | ```
2 | Addresses Hosts Netmask Amount of a Class C
3 | /30 4 2 255.255.255.252 1/64
4 | /29 8 6 255.255.255.248 1/32
5 | /28 16 14 255.255.255.240 1/16
6 | /27 32 30 255.255.255.224 1/8
7 | /26 64 62 255.255.255.192 1/4
8 | /25 128 126 255.255.255.128 1/2
9 | /24 256 254 255.255.255.0 1
10 | /23 512 510 255.255.254.0 2
11 | /22 1024 1022 255.255.252.0 4
12 | /21 2048 2046 255.255.248.0 8
13 | /20 4096 4094 255.255.240.0 16
14 | /19 8192 8190 255.255.224.0 32
15 | /18 16384 16382 255.255.192.0 64
16 | /17 32768 32766 255.255.128.0 128
17 | /16 65536 65534 255.255.0.0 256
18 | ```
19 |
--------------------------------------------------------------------------------
/commands/help/topics/react.txt:
--------------------------------------------------------------------------------
1 | See these links for learning ReactJS/Flux/Redux:
2 |
3 | Hello World in React
4 |
5 |
6 | Free Course: Get Quickly in Pace with React.js Development
7 |
8 |
9 | Free Course: Essential Concepts
10 |
11 |
12 | Free Course: Continuation: Lifecycle Methods, Stateless Components and more
13 |
14 |
15 | Awesome react
16 |
17 |
18 | Redux doc's
19 |
20 |
21 | Dan Abramov's free course for redux
22 |
23 |
24 | React-redux-links
25 |
26 |
--------------------------------------------------------------------------------
/commands/quickref/references/hotkeys.txt:
--------------------------------------------------------------------------------
1 | ```
2 | CTRL - K Quick Switcher
3 | CTRL - ALT - UP ARROW Server Up
4 | CTRL - ALT - DOWN ARROW Server Down
5 | ALT - UP ARROW Channel Up
6 | ALT - DOWN ARROW Channel Down
7 | ALT - SHIFT - UP ARROW Unread channel up
8 | ALT - SHIFT - DOWN ARROW Unread channel down
9 | CTRL - SHIFT - ALT - UP ARROW Unread mention up
10 | CTRL - SHIFT - ALT - DOWN ARROW Unread mention down
11 | ESC Mark channel as read
12 | SHIFT - ESC Mark server as read
13 | CTRL- ALT - A Return to active audio channel
14 | CTRL - B Return to last text channel
15 | SHIFT - PGUP Jump to oldest unread message
16 | ```
17 |
--------------------------------------------------------------------------------
/commands/help/topics/ml.txt:
--------------------------------------------------------------------------------
1 | See these resources for tackling Machine Learning:
2 |
3 | Free Course - Andrew Ng's Course
4 |
5 |
6 | Free Course - Columbia's Artifical Intelligence Course
7 |
8 |
9 | Free Course - fast.ai's introductory course for ML
10 |
11 |
12 | Free Book - Introduction to Statistical Learning
13 |
14 |
15 | Free Book - Deep Learning Book
16 |
17 |
18 | Free Book - Neural Networks and Deep Learning
19 |
20 |
21 | YouTube - Sentdex
22 |
23 |
24 | YouTube - MIT OCW Artificial Intelligence
25 |
26 |
27 |
--------------------------------------------------------------------------------
/commands/pro/proTerms.txt:
--------------------------------------------------------------------------------
1 | .NET|ASP.NET
2 | Ada
3 | Angular|Angular2
4 | AngularJS|Angular1
5 | Apache
6 | AppleScript
7 | Assembly|ASM|x86
8 | ATS
9 | AutoHotkey|AHK
10 | Bash
11 | Boo
12 | Bison
13 | C
14 | C--
15 | C#
16 | C++
17 | Caml
18 | Clojure
19 | Coq
20 | CoffeeScript
21 | CSS
22 | D|Dlang
23 | Dart
24 | Delphi
25 | Elm
26 | Eiffel
27 | Erlang
28 | Elixir
29 | F#
30 | Flex
31 | Groovy
32 | GLSL
33 | Go|Golang
34 | Haskell
35 | Haxe
36 | HLSL
37 | HTML
38 | Java
39 | JavaScript|JS
40 | Julia
41 | Kotlin
42 | LaTex
43 | Lua
44 | LOLCODE
45 | Lisp
46 | ML|SML
47 | Matlab
48 | Mongo
49 | Nim|Nimrod|Nimlang
50 | Ngnix
51 | OCaml
52 | Objective-C
53 | Oracle SQL
54 | Pascal
55 | Powershell
56 | Perl
57 | PHP
58 | Python
59 | R
60 | Redis
61 | Ruby
62 | Rust
63 | SmallTalk
64 | Squirrel
65 | Scheme
66 | Swift
67 | SQL
68 | Splunk
69 | TypeScript
70 | TeX
71 | VisualBasic
72 | Whitespace
73 | Wolfram
74 |
--------------------------------------------------------------------------------
/commands/help/topics/python.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning Python:
2 |
3 | Documentation
4 |
5 |
6 | Free to Read Online: A Byte of Python
7 |
8 |
9 | Free to Read Online: Introduction to Programming for Beginners
10 |
11 |
12 | Free to Read Online: Language Basics and Task Automation
13 |
14 |
15 | Paid Book (Free to Read Previous Versions): Tango With Django
16 |
17 |
18 | Free Course: Introduction to Computing using Python
19 |
20 |
21 | Paid Course Library: In-depth Fundamentals, Flask, Django and more
22 |
23 |
24 | Free Course: Problem Solving with Algorithms and Data Structures using Python
25 |
26 |
--------------------------------------------------------------------------------
/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "bot_cmd": "!bot",
3 | "voting": {
4 | "immuneRoles": ["Admins", "Mods"],
5 | "voteThreshold": 5,
6 | "timeout_in_minutes": 5
7 | },
8 | "xkcd": {
9 | "timeLimit": 60,
10 | "limitMessages": false
11 | },
12 | "weather": {
13 | "icons": {
14 | "clear-day": ":sunny:",
15 | "clear-night": ":crescent_moon:",
16 | "rain": ":cloud_rain:",
17 | "snow": ":cloud_snow:",
18 | "sleet": ":cloud_snow:",
19 | "partly-cloudy-day": ":partly_sunny:",
20 | "partly-cloudy-night": ":partly_sunny:",
21 | "fog": ":fog:",
22 | "cloudy":":cloud:",
23 | "wind": ":wind_blowing_face:"
24 | }
25 | },
26 | "info":{
27 | "repo":"rgoliveira/awesomebot"
28 | },
29 | "commands": [
30 | "help",
31 | "eval",
32 | "pro",
33 | "stream",
34 | "uptime",
35 | "vote",
36 | "xkcd",
37 | "weather",
38 | "server",
39 | "pbf",
40 | "quickref",
41 | "info"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/commands/help/help.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const getFileList = (dirName) => {
5 | const fileList = fs.readdirSync(dirName);
6 | return fileList || [];
7 | };
8 |
9 | const loadHelpText = (filename) => {
10 | const content = fs.readFileSync(filename, 'utf8');
11 | return content || '';
12 | };
13 |
14 | // basic help commands
15 | const knownTopics = {};
16 |
17 | module.exports = {
18 | usage: [
19 | 'help - links to the new resource list at https://github.com/progdisc/resources',
20 | ],
21 |
22 | run: (bot, message) => {
23 | let r = 'This command is deprecated.\n';
24 | r += 'See https://github.com/progdisc/resources for our new and improved resource list.';
25 | message.channel.sendMessage(r);
26 | },
27 |
28 | init: () => {
29 | console.log('Loading help topics...');
30 | getFileList(path.join(__dirname, 'topics')).forEach((fn) => {
31 | knownTopics[path.basename(fn, '.txt')] = loadHelpText(path.join(__dirname, 'topics', fn));
32 | });
33 | },
34 | };
35 |
36 |
--------------------------------------------------------------------------------
/commands/help/topics/php.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning PHP:
2 |
3 | PHP Official Documentation (select a language on the page)
4 |
5 |
6 | Learn PHP the right way (online free book)
7 |
8 |
9 | PHP 5 Tutorials
10 |
11 |
12 | A simple tutorial (good introduction)
13 |
14 |
15 | Free Course: Fundamental Programming Concepts
16 |
17 |
18 | Codeschool's PHP courses
19 |
20 |
21 | Beginner PHP at Treehouse
22 |
23 |
24 | Intermediate PHP at Treehouse
25 |
26 |
27 | PHP 5: Interactive tutorial
28 |
29 |
30 |
31 | See these resources for learning what's new in PHP 7:
32 |
33 | PHP 7: Up and running
34 |
35 |
36 | PHP 7: Interactive tutorial
37 |
38 |
--------------------------------------------------------------------------------
/commands/help/topics/ruby.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning Ruby:
2 |
3 | Official Ruby Core 2.4.1 Documentation
4 |
5 |
6 | Quickstart Guide: Ruby In Twenty Minutes
7 |
8 |
9 | Cheatsheet/Reference:
10 |
11 |
12 | Learn to Program with Ruby
13 |
14 |
15 | Free (Language Transition): Ruby From Other Languages
16 |
17 |
18 | Free (Exercise Focused): Learn Ruby the Hard Way
19 |
20 |
21 | Free (Exercise Focused): Codecademy Ruby
22 |
23 |
24 | Free (Ruby on Rails 5) : Ruby on Rails Tutorial
25 |
26 |
27 | Free (Ruby on Rails): Learn Ruby on Rails from Scratch
28 |
29 |
30 | Paid (Course Library): TeamTreehouse: Learn Ruby
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 All contributors of AwesomeBot
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TheAwesomeBot",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "TheAwesomeBot.js",
6 | "scripts": {
7 | "start": "node start.js",
8 | "test": "if [ \"$TRAVIS_PULL_REQUEST\" = \"false\" ]; then node test/*.js | tap-summary --no-progress; fi",
9 | "lint": "eslint"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/rgoliveira/AwesomeBot.git"
14 | },
15 | "author": "",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/rgoliveira/AwesomeBot/issues"
19 | },
20 | "homepage": "https://github.com/rgoliveira/AwesomeBot",
21 | "dependencies": {
22 | "cheerio": "^0.22.0",
23 | "discord.js": "^11.0.0",
24 | "replit-client": "^0.19.0",
25 | "request": "^2.81.0",
26 | "request-promise": "^4.2.0",
27 | "xmlhttprequest": "^1.8.0"
28 | },
29 | "devDependencies": {
30 | "eslint": "^3.0.1",
31 | "eslint-config-airbnb": "^14.1.0",
32 | "eslint-plugin-import": "^2.2.0",
33 | "eslint-plugin-jsx-a11y": "^4.0.0",
34 | "eslint-plugin-react": "^6.9.0",
35 | "tap-summary": "^3.0.1",
36 | "tape": "^4.6.3"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/commands/help/topics/javascript.txt:
--------------------------------------------------------------------------------
1 | See these links/courses for learning JavaScript:
2 |
3 | Tutorials/Documentation
4 |
5 |
6 | Free to Read Online: A Modern Introduction to Programming
7 |
8 |
9 | Free to Read Online: Exploring ES6
10 |
11 |
12 | Free to Read Online: Exploring ES2016-ES2017
13 |
14 |
15 | Free Course: Fundamental Programming Concepts
16 |
17 |
18 | Free Course Library: Learn to Code and Help Nonprofits
19 |
20 |
21 | Free Course: Basic JavaScript (~3 weeks)
22 |
23 |
24 | Talk: Four Layers of JavaScript OOP (1:09:02)
25 |
26 |
27 | Talk: How Node.js' Event Loop Works (26:52)
28 |
29 |
30 | Paid Course Library: In-depth Fundamentals, Node, React, jQuery and more
31 |
32 |
33 | Free to Read Online Series: You Don't Know JS (Core Mechanisms, Advanced)
34 |
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AwesomeBot [](https://travis-ci.org/progdisc/AwesomeBot) [](https://codeclimate.com/github/progdisc/AwesomeBot) [](https://david-dm.org/progdisc/AwesomeBot)
2 |
3 | ## How to run it
4 | First, make sure you have the latest version of [Node.js](https://nodejs.org/) and [npm](https://github.com/npm/npm).
5 | We recommend using [nvm](https://github.com/creationix/nvm) to manage these, and there's a `.nvmrc` file in this project, so just run this and you're all set:
6 | ```sh
7 | nvm use
8 | ```
9 |
10 | Now you need to install the dependencies:
11 | ```sh
12 | npm install
13 | ```
14 |
15 | Then, in order to log your bot into Discord, set [your bot token](https://discordapp.com/developers/applications/me):
16 | ```sh
17 | export DISCORD_TOKEN=
18 | # you could, instead, fill it in the ./settings.json file
19 | ```
20 |
21 | And finally, start your bot with:
22 | ```sh
23 | npm start
24 | ```
25 |
26 | Before pushing any changes or submitting PRs, don't forget to run eslint, or our CI may reject your request:
27 | ```sh
28 | npm run lint -- .
29 | ```
30 |
--------------------------------------------------------------------------------
/commands/quickref/quickref.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const getFileList = (dirName) => {
5 | const fileList = fs.readdirSync(dirName);
6 | return fileList || [];
7 | };
8 |
9 | const loadReferences = (filename) => {
10 | const content = fs.readFileSync(filename, 'utf8');
11 | return content || '';
12 | };
13 |
14 | const references = {};
15 |
16 | module.exports = {
17 | usage: [
18 | 'quickref - displays quick reference for ',
19 | 'quickref - list known references',
20 | ],
21 | run: (bot, message, cmdArgs) => {
22 | if (cmdArgs) {
23 | const response = references[cmdArgs.toLowerCase()];
24 |
25 | if (response) {
26 | message.channel.sendMessage(
27 | `${response}`);
28 | } else {
29 | message.channel.sendMessage('I don\'t have any references for that. If you have a suggestion, let us know!');
30 | }
31 | } else {
32 | let r = '\nreferences I have ready to go:';
33 | r += '\n```';
34 | r += Object.keys(references).map(t => `\n - ${t}`).join('');
35 | r += '\n```';
36 | message.channel.sendMessage(r);
37 | }
38 | },
39 | init: () => {
40 | console.log('Loading quickrefs...');
41 | getFileList(path.join(__dirname, 'references')).forEach((fn) => {
42 | references[path.basename(fn, '.txt')] = loadReferences(path.join(__dirname, 'references', fn));
43 | });
44 | },
45 | };
46 |
--------------------------------------------------------------------------------
/commands/xkcd/xkcd.js:
--------------------------------------------------------------------------------
1 | const request = require('request-promise');
2 | const cheerio = require('cheerio');
3 |
4 | const ddgHeaders = {
5 | 'accept-language': 'en-US,en;q=0.8',
6 | 'upgrade-insecure-requests': 1,
7 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
8 | 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
9 | };
10 |
11 | const ddgUrlTemplate = 'https://duckduckgo.com/html/?q=$q xkcd';
12 |
13 | function parseXkcdDataFromXkcdUrl(xkcdUrl) {
14 | return request(xkcdUrl).then((xkcdBody) => {
15 | const xkcdData = JSON.parse(xkcdBody);
16 |
17 | if (xkcdData) {
18 | return '```diff\n' +
19 | `Title: ${xkcdData.safe_title}\n` +
20 | `Alt Text: ${xkcdData.alt}\n` +
21 | '```\n' +
22 | `${xkcdData.img}`;
23 | }
24 | // eslint-disable-next-line no-throw-literal
25 | throw 'I\'m sorry, there was a problem retrieving a XKCD.';
26 | });
27 | }
28 |
29 | function parseXkcdUrlFromDuckDuckGo(ddgBody) {
30 | const ddgParsed = cheerio.load(ddgBody);
31 | let xkcdUrl = false;
32 |
33 | try {
34 | ddgParsed('.result__a').each((i, link) => {
35 | const href = link.attribs.href;
36 |
37 | if (href.search(/^https?:\/\/(www\.)?xkcd\.com\/\d+/) !== -1 && xkcdUrl === false) {
38 | xkcdUrl = href + 'info.0.json';
39 | }
40 | });
41 | } catch (err) {
42 | // eslint-disable-next-line no-throw-literal
43 | throw 'There was a problem with DuckDuckGo query.';
44 | }
45 |
46 | if (!xkcdUrl) {
47 | // eslint-disable-next-line no-throw-literal
48 | throw 'I\'m sorry, I couldn\'t find a xkcd.';
49 | } else {
50 | return xkcdUrl;
51 | }
52 | }
53 |
54 | function findXkcdFromKeywords(keywords) {
55 | const ddgUrl = ddgUrlTemplate.replace('$q', encodeURI(keywords));
56 |
57 | return request({
58 | url: ddgUrl,
59 | headers: ddgHeaders,
60 | }).then(parseXkcdUrlFromDuckDuckGo)
61 | .then(parseXkcdDataFromXkcdUrl);
62 | }
63 |
64 | module.exports = {
65 | usage: 'xkcd - finds a xkcd comic with relevant keywords',
66 | run: (bot, message, cmdArgs) => {
67 | if (!cmdArgs) {
68 | return true;
69 | }
70 |
71 | findXkcdFromKeywords(cmdArgs).then((data) => {
72 | message.channel.sendMessage(data);
73 | })
74 | .catch((err) => {
75 | message.channel.sendMessage(err);
76 | });
77 |
78 | return false;
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/commands/pbf/pbf.js:
--------------------------------------------------------------------------------
1 | const request = require('request');
2 | const cheerio = require('cheerio');
3 |
4 | function parsePbfLink(pbfLink, message) {
5 | request(pbfLink, (error, response, body) => {
6 | if (!error && response.statusCode === 200) {
7 | // we have successfully got a response
8 | const htmlBody = cheerio.load(body);
9 |
10 | if (htmlBody('#topimg')) {
11 | const img = htmlBody('#topimg');
12 | message.channel.sendMessage('```diff\n' +
13 | `Title: ${img.attr('alt')}\n` +
14 | '```\n' +
15 | `http://pbfcomics.com${img.attr('src')}`);
16 | } else {
17 | message.channel.sendMessage(`I'm sorry ${message.author}, i couldn't find a PBF Comic.`);
18 | }
19 | }
20 | });
21 | }
22 |
23 | module.exports = {
24 | usage: 'pbf - finds a pbf comic with relevant keywords. Random keyword selects random comic.',
25 | run: (bot, message, cmdArgs) => {
26 | let pbfLink = false;
27 |
28 | if (cmdArgs === 'random') {
29 | pbfLink = 'http://pbfcomics.com/random';
30 | parsePbfLink(pbfLink, message);
31 | } else {
32 | const options = {
33 | url: `https://duckduckgo.com/html/?q=${cmdArgs}%20pbfcomics`,
34 | headers: {
35 | 'accept-language': 'en-US,en;q=0.8',
36 | 'upgrade-insecure-requests': 1,
37 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
38 | 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
39 | },
40 | };
41 | request(options, (err, res, bod) => {
42 | const pbfBody = cheerio.load(bod);
43 | try {
44 | pbfBody('.result__a').each((i, link) => {
45 | const href = link.attribs.href;
46 | if (href.search(/^https?:\/\/(www\.)?pbfcomics\.com\/\d+\//) !== -1 && pbfLink === false) {
47 | pbfLink = href;
48 | }
49 | });
50 | } catch (e) {
51 | message.channel.sendMessage('There was a problem with DuckDuckGo query.');
52 | }
53 | // we are done with finding a link
54 | if (!pbfLink) {
55 | // link is either empty (this should NOT happen) or we don't have a link
56 | message.channel.sendMessage(`I'm sorry ${message.author}, i couldn't find a PBF Comic.`);
57 | } else {
58 | parsePbfLink(pbfLink, message);
59 | }
60 | });
61 | }
62 | return false;
63 | },
64 |
65 | };
66 |
--------------------------------------------------------------------------------
/commands/vote/vote.js:
--------------------------------------------------------------------------------
1 | function handleKick(bot, member, _guild) {
2 | member.kick().catch((err) => {
3 | if (err) console.log(err);
4 | });
5 | }
6 |
7 | function handleMute(bot, member, _guild) {
8 | member.setMute(true).catch((err) => {
9 | if (err) console.log(err);
10 | });
11 | }
12 |
13 | const voteTypes = {
14 | kick: handleKick,
15 | mute: handleMute,
16 | };
17 |
18 | /*
19 | * currentVotes: dictionary of votes
20 | * {
21 | * : {
22 | * : {
23 | * username: ,
24 | * votes: [,
26 | * }
27 | * }
28 | * }
29 | */
30 | const currentVotes = {};
31 | Object.keys(voteTypes).forEach((k) => {
32 | currentVotes[k] = {};
33 | });
34 |
35 | function setIntersection(setA, setB) {
36 | return new Set([...setA].filter(x => setB.has(x)));
37 | }
38 |
39 | function processVote(type, bot, message, guild, member) {
40 | let voting = currentVotes[type][member.user.username];
41 |
42 | if (!voting) {
43 | // sets a timeout for this voting
44 | const timeoutClj = () => {
45 | message.channel.sendMessage(`Vote to ${type} ${member} has timed out. Phew!`);
46 | delete currentVotes[type][member.user.username];
47 | };
48 | const timeoutObj = setTimeout(timeoutClj, bot.settings.voting.timeout_in_minutes * 1000 * 60);
49 |
50 | voting = {
51 | username: member.user.username,
52 | votes: [],
53 | timeout: timeoutObj,
54 | };
55 | currentVotes[type][member.user.username] = voting;
56 | }
57 |
58 | // ignore votes by the same user
59 | if (voting.votes.indexOf(message.author.username) >= 0) {
60 | return;
61 | }
62 | voting.votes.push(message.author.username);
63 | if (voting.votes.length >= bot.settings.voting.voteThreshold) {
64 | clearTimeout(voting.timeout);
65 | message.channel.sendMessage(`Sorry, ${member}, but their wish is my command!`);
66 | voteTypes[type](bot, member, guild);
67 | delete currentVotes[type][member.user.username];
68 | } else {
69 | let msg = `[${voting.votes.length}/${bot.settings.voting.voteThreshold}]`;
70 | msg += ` votes to ${type} ${member}!`;
71 | message.channel.sendMessage(msg);
72 | }
73 | }
74 |
75 | module.exports = {
76 | usage: `vote <${Object.keys(voteTypes).join('|')}> <@user> - start a vote against <@user>`,
77 |
78 | run: (bot, message, cmdArgs) => {
79 | // command validation
80 | const voteRe = new RegExp(`^(${Object.keys(voteTypes).join('|')})`, 'i');
81 | const reMatch = cmdArgs.match(voteRe);
82 | if (!reMatch) return true;
83 |
84 | const guild = message.channel.guild;
85 | const voteType = reMatch[1];
86 |
87 | const user = message.mentions.users.first();
88 | if (!user) {
89 | message.channel.sendMessage('You need to specify a valid member!');
90 | return false;
91 | }
92 | const member = guild.members.get(user.id);
93 |
94 | // user validation
95 | // warning: assume bot is in one guild only
96 | if (user === message.author) {
97 | message.channel.sendMessage('You can\'t start a vote against yourself, silly.');
98 | return false;
99 | } else if (user === bot.client.user) {
100 | message.channel.sendMessage(`I'm sorry ${message.author}, I'm afraid I can't let you do that.,`);
101 | return false;
102 | }
103 |
104 | // roles validation
105 | const userRoles = new Set(member.roles.array().map(r => r.name));
106 | if (setIntersection(userRoles, new Set(bot.settings.voting.immuneRoles)).size > 0) {
107 | message.channel.sendMessage('try.is(\'nice\') === true');
108 | return false;
109 | }
110 |
111 | processVote(voteType, bot, message, guild, member);
112 | return false;
113 | },
114 | };
115 |
116 |
--------------------------------------------------------------------------------
/commands/pro/pro.js:
--------------------------------------------------------------------------------
1 | function fixEscapes(str) {
2 | return str.replace(/[^a-z0-9|]/ig, '\\$&');
3 | }
4 |
5 | let proLangRe;
6 | let pros;
7 | const proHelpText = {};
8 |
9 | function updateProsMatcher() {
10 | /* eslint global-require: off */
11 | delete require.cache[require.resolve('./proTerms.js')];
12 | pros = {};
13 |
14 | const terms = require('./proTerms.js')
15 | .filter(termList => termList[0].length > 0)
16 | .map((termList) => {
17 | const termPros = new Set();
18 | termPros.original = termList[0];
19 |
20 | termList.forEach((term) => {
21 | pros[term.toLowerCase()] = termPros;
22 | });
23 |
24 | return fixEscapes(termList.join('|'));
25 | });
26 |
27 | proLangRe = new RegExp(`(?:^|\\W)(${terms.join('|')})(?:$|\\W)`, 'gi');
28 | }
29 |
30 |
31 | function getProsOnline(guild) {
32 | return new Set(guild.members
33 | .filter(m => m.roles.find('name', 'Helpers') && ['online', 'idle'].includes(m.presence.status))
34 | .map(p => p.user.username));
35 | }
36 |
37 |
38 | function loadAndMatchPros(bot) {
39 | updateProsMatcher();
40 | const helpChannel = bot.client.channels.find('name', 'help-directory');
41 |
42 | return helpChannel.fetchMessages({ limit: 100 })
43 | .then((messages) => {
44 | messages.forEach((messageObj) => {
45 | proHelpText[messageObj.author.id] = messageObj.content;
46 | proLangRe.lastIndex = 0;
47 | while (true) {
48 | const match = proLangRe.exec(messageObj.content);
49 | if (!match) {
50 | break;
51 | }
52 | pros[match[1].toLowerCase()].add(messageObj.author.username);
53 | }
54 | });
55 | });
56 | }
57 |
58 |
59 | function getPros(bot, lang) {
60 | if (!pros[lang]) {
61 | return null;
62 | }
63 | const langPros = Array.from(pros[lang]);
64 | const guild = bot.client.guilds.first();
65 | const online = getProsOnline(guild);
66 | return langPros.filter(user => online.has(user)).join('\n');
67 | }
68 |
69 | module.exports = {
70 | usage: [
71 | 'pro - list of people who knows about ',
72 | 'pro - get help directory entry for specific pro',
73 | 'pro reset - reload all the pro data (mod only)',
74 | ],
75 |
76 | run(bot, message, cmdArgs) {
77 | if (!cmdArgs) {
78 | return true;
79 | }
80 |
81 | if (message.mentions.users.size > 0) {
82 | const memberId = message.mentions.users.first().id;
83 | if (memberId in proHelpText) {
84 | let response = `**Help Directory entry for user ${message.mentions.users.first().username}:**\n`;
85 | response += proHelpText[memberId];
86 | message.channel.sendMessage(response);
87 | } else {
88 | message.channel.sendMessage(`Could not find user ${message.mentions.users.first().username} in directory`);
89 | }
90 | return false;
91 | }
92 |
93 | let lang = cmdArgs.toLowerCase().trim();
94 |
95 | if (lang === 'reset' && bot.isAdminOrMod(message.member)) {
96 | loadAndMatchPros(bot).then(() => {
97 | message.channel.sendMessage('Pros list refreshed.');
98 | return false;
99 | })
100 | .catch((err) => {
101 | console.error(err);
102 | console.error(err.stack);
103 | });
104 | return false;
105 | }
106 |
107 | proLangRe.lastIndex = 0;
108 | const match = proLangRe.exec(lang);
109 | lang = ((match && match[1]) || lang).toLowerCase();
110 |
111 | const foundPros = getPros(bot, lang);
112 | message.channel.sendMessage(foundPros ?
113 | `Here are some pros online that can help with **${pros[lang].original}**: \n${foundPros}` :
114 | `No pros found for ${cmdArgs} :(`);
115 | return false;
116 | },
117 |
118 | init(bot) {
119 | console.log('Loading pros...');
120 | loadAndMatchPros(bot)
121 | .then(() => {
122 | console.log('Done reading in pros from #helpdirectory!');
123 | })
124 | .catch((err) => {
125 | console.error(err);
126 | console.error(err.stack);
127 | });
128 | },
129 | };
130 |
131 |
--------------------------------------------------------------------------------
/commands/eval/eval.js:
--------------------------------------------------------------------------------
1 | const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
2 | const ReplitClient = require('replit-client');
3 | const gentoken = require('./gentoken');
4 |
5 | global.XMLHttpRequest = XMLHttpRequest;
6 |
7 | // list of langs:
8 | // https://github.com/replit/ReplitClient.js#replitclienthostname-port-language-token
9 | const availableLanguages = [
10 | 'c',
11 | 'cpp',
12 | 'cpp11',
13 | 'csharp',
14 | 'fsharp',
15 | 'go',
16 | 'java',
17 | 'lua',
18 | 'nodejs',
19 | 'php',
20 | 'python',
21 | 'python3',
22 | 'ruby',
23 | 'rust',
24 | 'swift',
25 | ];
26 |
27 | // map usual lang names to replit names
28 | const langAliases = {
29 | 'c#': 'csharp',
30 | 'c++': 'cpp',
31 | 'c++11': 'cpp11',
32 | 'f#': 'fsharp',
33 | js: 'nodejs',
34 | javascript: 'nodejs',
35 | py2: 'python',
36 | py: 'python3',
37 | py3: 'python3',
38 | rb: 'ruby',
39 | };
40 |
41 | const validateLang = (lang) => {
42 | if (typeof lang !== 'string') {
43 | return false;
44 | }
45 |
46 | const l = langAliases[lang.toLowerCase()] || lang.toLowerCase();
47 |
48 | if (availableLanguages.includes(l)) {
49 | return l;
50 | }
51 | return false;
52 | };
53 |
54 | module.exports = {
55 | usage: [
56 | 'eval - run code in repl.it',
57 | 'eval - show available languages',
58 | ],
59 |
60 | run: (bot, message, cmdArgs) => {
61 | if (!cmdArgs) {
62 | const langList = availableLanguages.reduce((prev, x) => {
63 | let langEntry = `${prev}${prev ? '\n' : ''}- ${x}`;
64 | const aliases = Object.keys(langAliases)
65 | .filter(alias => langAliases[alias] === x)
66 | .join(', ');
67 | if (aliases) {
68 | langEntry += ` (${aliases})`;
69 | }
70 | return langEntry;
71 | }, '');
72 | message.reply(`available languages:\n\`\`\`\n${langList}\n\`\`\`\n`);
73 | return;
74 | }
75 |
76 | // parsing the messages against regex to obtain the lang and code
77 | let rxLangs = Object.keys(langAliases)
78 | .map((v) => {
79 | if (v === 'c++') {
80 | return 'c\\+\\+';
81 | } else if (v === 'c++11') {
82 | return 'c\\+\\+11';
83 | }
84 | return v;
85 | });
86 | rxLangs.push(...availableLanguages);
87 | rxLangs = rxLangs.sort((a, b) => b.length - a.length).join('|');
88 | const rx = new RegExp('^(`{0,3})(' + rxLangs + ')\\s*((.|\\s)+)(\\1)$', 'gi');
89 | const argsArr = rx.exec(cmdArgs);
90 | let lang = argsArr[2].toLowerCase();
91 | const code = (new RegExp('^(`{0,3})((' + rxLangs + '|.{0})\\s)?\\s*((.|\\s)+)(\\1)$', 'gi')).exec(argsArr[3])[4];
92 | lang = validateLang(lang);
93 | if (!lang) {
94 | message.reply('Sorry, I don\'t know that language!');
95 | return;
96 | }
97 |
98 | const apiToken = bot.settings.tokens.replit || process.env.REPLIT_TOKEN;
99 | const repl = new ReplitClient(
100 | 'api.repl.it',
101 | 80,
102 | lang, gentoken(apiToken));
103 |
104 | message.channel.sendMessage('⏲ evaluating...')
105 | .then((evalMsg) => {
106 | let newContent = '';
107 | repl.evaluateOnce(
108 | code, {
109 | stdout: (output) => {
110 | newContent += `Code output:\n\`\`\`\n${output}\n\`\`\`\n`;
111 | },
112 | }).then(
113 | (result) => {
114 | if (result.error) {
115 | newContent += `Error:\n\`\`\`${result.error}\`\`\`\n`;
116 | } else {
117 | newContent += `Result:\n\`\`\`${result.data}\`\`\`\n`;
118 | }
119 | evalMsg.edit(newContent);
120 | },
121 | (error) => {
122 | newContent += `Error connecting to repl.it!\`\`\`${error}\`\`\`\n`;
123 | evalMsg.edit(newContent);
124 | console.error(error);
125 | });
126 | })
127 | .catch(console.error);
128 | },
129 | };
130 |
131 |
--------------------------------------------------------------------------------
/commands/weather/weather.js:
--------------------------------------------------------------------------------
1 | const request = require('request-promise');
2 | const discord = require('discord.js');
3 |
4 | let weatherConfig;
5 | let tokens;
6 | const geocodeEndpoint = 'https://maps.googleapis.com/maps/api/geocode/json?key=gkey&address=input';
7 | const darkskyEndpoint = 'https://api.darksky.net/forecast/key/lat,lng';
8 |
9 | function fahrenheitToCelcius(degree) {
10 | return (((degree - 32) * 5) / 9).toFixed(0);
11 | }
12 |
13 | function getGeocodeData(address) {
14 | let requestURL = geocodeEndpoint
15 | .replace('gkey', tokens.google_geocode)
16 | .replace('input', address);
17 | requestURL = encodeURI(requestURL);
18 |
19 | return request(requestURL).then((body) => {
20 | const geocodeData = JSON.parse(body);
21 |
22 | if (geocodeData.status !== 'OK') {
23 | // eslint-disable-next-line no-throw-literal
24 | throw 'I\'m sorry, the address couldn\'t be detected.';
25 | }
26 |
27 | const result = {
28 | address: geocodeData.results[0].formatted_address,
29 | coordinate: geocodeData.results[0].geometry.location,
30 | };
31 |
32 | return result;
33 | });
34 | }
35 |
36 | function getWeatherData(location) {
37 | const requestURL = darkskyEndpoint
38 | .replace('key', tokens.darksky)
39 | .replace('lat', location.coordinate.lat)
40 | .replace('lng', location.coordinate.lng);
41 |
42 | return request(requestURL).then((body) => {
43 | const weatherData = JSON.parse(body);
44 |
45 | const offset = weatherData.offset;
46 | const utcTime = weatherData.currently.time;
47 | // datetime is weird in javascript, please do change this part if you can
48 | const localTime = new Date(utcTime * 1000);
49 | localTime.setHours(localTime.getHours() + offset);
50 | let dateString;
51 | // toGMTString prints out timezone of host so we slice it off
52 | if (offset > 0) {
53 | dateString = localTime.toUTCString() + ' +' + offset;
54 | } else {
55 | dateString = localTime.toUTCString() + ' ' + offset;
56 | }
57 |
58 | const temperatureF = weatherData.currently.temperature.toFixed(0);
59 | const temperatureC = fahrenheitToCelcius(temperatureF);
60 | const summary = weatherData.currently.summary;
61 | const humidity = (weatherData.currently.humidity * 100).toFixed(0);
62 | // convert speed to freedom units
63 | const windSpeed = (weatherData.currently.windSpeed * 1.61).toFixed(0);
64 | const pressure = weatherData.currently.pressure.toFixed(0);
65 | const icon = weatherData.currently.icon;
66 | const timezone = weatherData.timezone;
67 |
68 | const result = {
69 | location: location.address,
70 | // A temporary fix until the eventual refactor.
71 | address: location.address,
72 | offset,
73 | localTime,
74 | dateString,
75 | temperatureC,
76 | temperatureF,
77 | summary,
78 | humidity,
79 | windSpeed,
80 | pressure,
81 | icon,
82 | timezone,
83 | };
84 |
85 | return result;
86 | });
87 | }
88 |
89 | function generateEmbed(data, verbose) {
90 | const embed = new discord.RichEmbed();
91 |
92 | if (verbose) {
93 | embed.setColor('#4286f4')
94 | .setFooter(`Local Time: ${data.dateString}`)
95 | .setTitle(`Weather in ${data.location}`)
96 | .addField('Summary', data.summary)
97 | .addField('Temperature °C', `${data.temperatureC} °C`, true)
98 | .addField('Temperature °F', `${data.temperatureF} °F`, true)
99 | .addField('Timezone', data.timezone, true)
100 | .addField('Humidity', `${data.humidity}%`, true)
101 | .addField('Wind Speed', `${data.windSpeed} km/h`, true)
102 | .addField('Air Pressure', `${data.pressure} mbar`, true)
103 | .setDescription(weatherConfig.icons[data.icon]);
104 | } else {
105 | embed.setColor('#4286f4')
106 | .setFooter(`Local Time: ${data.dateString}`)
107 | .setTitle(`Weather in ${data.address}`)
108 | .addField('Summary', data.summary)
109 | .addField('Temperature', `${data.temperatureC} °C / ${data.temperatureF} °F`, true)
110 | .addField('Humidity', `${data.humidity}%`, true)
111 | .setDescription(weatherConfig.icons[data.icon]);
112 | }
113 |
114 | return embed;
115 | }
116 |
117 | module.exports = {
118 | usage: [
119 | 'weather - brings current weather info for given address',
120 | 'weather -v - brings additional weather info for given address',
121 | ],
122 | run: (bot, message, cmdArgs) => {
123 | if (!cmdArgs) return true;
124 |
125 | let args = cmdArgs.split(' ');
126 | const verbose = args[0] === '-v';
127 | args = verbose ? args.slice(1) : args;
128 |
129 | getGeocodeData(args).then(getWeatherData)
130 | .then(data => generateEmbed(data, verbose))
131 | .then(embed => message.channel.sendEmbed(embed))
132 | .catch(err => message.channel.sendMessage(err.toString()));
133 | return false;
134 | },
135 | init: (bot) => {
136 | weatherConfig = bot.settings.weather;
137 | tokens = bot.settings.tokens;
138 | },
139 | };
140 |
--------------------------------------------------------------------------------
/commands/info/info.js:
--------------------------------------------------------------------------------
1 | const request = require('request');
2 | const exec = require('child_process').exec;
3 | const discord = require('discord.js');
4 |
5 | const time = require('../../lib/utils.js').time;
6 |
7 | const githubCommits = 'https://api.github.com/repos/$repo/commits';
8 | const githubContributors = 'https://api.github.com/repos/$repo/contributors';
9 | const githubRepo = 'https://github.com/$repo';
10 | const commitTemplate = '$username - $message';
11 | const markdownLink = '[$text]($link)';
12 | const description = 'Message cmd for available commands.';
13 | let config;
14 | const lastCommit = {};
15 | const currentCommit = {};
16 | let contributorsMessage = '';
17 | const githubHeaders = {
18 | 'User-Agent': 'TheAwesomeBot',
19 | 'Accept': 'application/vnd.github.v3+json', // eslint-disable-line quote-props
20 | };
21 |
22 | function getLastCommit() {
23 | request({
24 | url: githubCommits.replace('$repo', config.repo),
25 | headers: githubHeaders,
26 | }, (err, response, body) => {
27 | if (err || response.statusCode !== 200) {
28 | lastCommit.link = 'https://github.com/404';
29 | lastCommit.message = 'Couldn\'t retrieve commit data.';
30 | return;
31 | }
32 | const commitData = JSON.parse(body);
33 | // TODO (sam): Instead of depending on nullness of the variable,
34 | // act according to github api docs
35 | if (commitData[0] == null) {
36 | lastCommit.message = 'Couldn\'t retrieve commit data.';
37 | return;
38 | }
39 | const commitMessage = commitData[0].commit.message.replace('\n\n', ' ')
40 | .replace('\n', ' ');
41 | lastCommit.link = commitData[0].html_url;
42 | lastCommit.username = commitData[0].author ? commitData[0].author.login : 'unkown';
43 | lastCommit.message = commitTemplate.replace('$username', lastCommit.username)
44 | .replace('$message', commitMessage);
45 | });
46 | }
47 |
48 | function getCurrentCommit() {
49 | exec('git show --oneline -s', (err, stdout) => {
50 | const gitOutput = stdout.replace('\n\n', '').replace('\n', '');
51 | currentCommit.shortSHA = gitOutput.split(' ')[0];
52 |
53 | request({
54 | url: githubCommits.replace('$repo', config.repo) + '/' + currentCommit.shortSHA,
55 | headers: githubHeaders,
56 | }, (error, response, body) => {
57 | if (error || response.statusCode !== 200) {
58 | currentCommit.link = 'https://github.com/404';
59 | return;
60 | }
61 | currentCommit.link = JSON.parse(body).html_url;
62 | currentCommit.username = JSON.parse(body).author ? JSON.parse(body).author.login : 'unkown';
63 | currentCommit.message = commitTemplate.replace('$username', currentCommit.username)
64 | .replace('$message', gitOutput.split(' ').slice(1).join(' '));
65 | });
66 | });
67 | }
68 |
69 | function getContributors() {
70 | request({
71 | url: githubContributors.replace('$repo', config.repo),
72 | headers: githubHeaders,
73 | }, (error, response, body) => {
74 | let jsonData = JSON.parse(body);
75 | if (Array.isArray(jsonData) && jsonData.length >= 10) {
76 | jsonData = jsonData.slice(0, 10);
77 | contributorsMessage = jsonData.slice(0, 10).reduce((acc, cv) =>
78 | acc + markdownLink.replace('$text', cv.login).replace('$link', cv.html_url) + '\n'
79 | , '');
80 | } else {
81 | contributorsMessage = 'There was an error trying to get the contributors list :(';
82 | }
83 | });
84 | }
85 |
86 | function infoInit(bot) {
87 | config = bot.settings.info;
88 | // get current checked out commit from git
89 | getCurrentCommit();
90 | // get latest commit in git repo
91 | getLastCommit();
92 | getContributors();
93 | }
94 |
95 | function infoRun(bot, message, cmdArgs) {
96 | if (cmdArgs) {
97 | if (cmdArgs === 'contributors') {
98 | const contributorsEmbed = new discord.RichEmbed();
99 | contributorsEmbed.setColor('#4286f4')
100 | .setTitle('Top 10 contributors of AwesomeBot:')
101 | .setDescription(contributorsMessage);
102 | message.channel.sendEmbed(contributorsEmbed);
103 | }
104 | } else {
105 | const embed = new discord.RichEmbed();
106 | embed.setColor('#4286f4')
107 | .setAuthor('TheAwesomeBot', bot.client.user.avatarURL, githubRepo.replace('$repo', config.repo))
108 | .setFooter(description.replace('cmd', bot.settings.bot_cmd))
109 | .addField('Uptime', time.timeElapsed(bot.bootTime, new Date()))
110 | .addField('Latest Commit', markdownLink.replace('$text', lastCommit.message).replace('$link', lastCommit.link))
111 | .setDescription('An open source bot made with :heart:');
112 | // let the users know if bot is working on a rolled back commit
113 | if (currentCommit !== lastCommit) {
114 | embed.addField('Current Commit', markdownLink.replace('$text', currentCommit.message)
115 | .replace('$link', currentCommit.link));
116 | }
117 | message.channel.sendEmbed(embed);
118 | }
119 | }
120 |
121 | module.exports = {
122 | usage: [
123 | 'info - displays information about bot',
124 | 'info contributors - displays contributors',
125 | ],
126 | init: infoInit,
127 | run: infoRun,
128 | };
129 |
--------------------------------------------------------------------------------
/TheAwesomeBot.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | const path = require('path');
3 | const Discord = require('discord.js');
4 |
5 | const Settings = require(path.join(__dirname, 'settings.json')); // eslint-disable-line import/no-dynamic-require
6 | let Tokens;
7 | try {
8 | // eslint-disable-next-line global-require, import/no-dynamic-require
9 | Tokens = require(path.join(__dirname, 'tokens.json'));
10 | } catch (e) {
11 | Tokens = {};
12 | }
13 |
14 | class TheAwesomeBot {
15 | constructor(token, discordOpt) {
16 | this.bootTime = new Date();
17 | this.token = token;
18 | this.client = new Discord.Client(discordOpt || { autoReconnect: true });
19 | this.settings = Settings;
20 | this.settings.tokens = Tokens; // insert tokens into our settings obj
21 | this.commands = {};
22 | this.usageList = '';
23 |
24 | // store the RE as they're expensive to create
25 | this.cmd_re = new RegExp(`^${this.settings.bot_cmd}\\s+([^\\s]+)\\s*([^]*)\\s*`, 'i');
26 |
27 | // flags if connected and client is ready
28 | this.isReady = false;
29 | }
30 |
31 | onMessage() {
32 | return (message) => {
33 | // don't respond to own messages
34 | if (this.client.user.username === message.author.username) {
35 | return;
36 | }
37 |
38 | // check if message is a command
39 | const cmdMatch = message.cleanContent.match(this.cmd_re);
40 |
41 | // not a known command
42 | if (!cmdMatch || Object.keys(this.commands).indexOf(cmdMatch[1]) === -1) {
43 | if (message.content.match(new RegExp(`^${this.settings.bot_cmd}[\\s]*( .*)?$`, 'i'))) {
44 | let helpText = 'maybe try these valid commands? *kthnxbye!*\n\n```';
45 | helpText += this.usageList;
46 | helpText += '```';
47 | message.channel.sendMessage(helpText);
48 | }
49 | return;
50 | }
51 |
52 | // process commands
53 | const cmd = cmdMatch[1];
54 | const cmdArgs = cmdMatch[2].trim();
55 |
56 | let showUsage;
57 |
58 | try {
59 | showUsage = this.commands[cmd].run(this, message, cmdArgs);
60 | } catch (err) {
61 | message.channel.sendMessage('There was an error running the command:\n' +
62 | '```\n' + err.toString() + '\n```');
63 | console.error(err);
64 | console.error(err.stack);
65 | }
66 |
67 | if (showUsage === true) {
68 | let usage = this.commands[cmd].usage;
69 | if (typeof usage !== 'string') {
70 | usage = usage.join('\n');
71 | }
72 | message.channel.sendMessage('```\n' + usage + '\n```');
73 | }
74 | };
75 | }
76 |
77 | onReady() {
78 | return (() => {
79 | console.log('\nConnected to discord server!');
80 | console.log('Running initializations...');
81 | Object.keys(this.commands).filter(cmd =>
82 | typeof this.commands[cmd].init === 'function')
83 | .forEach(cmd => this.commands[cmd].init(this));
84 | this.isReady = true;
85 | });
86 | }
87 |
88 | serverNewMember() {
89 | return ((server, user) => this.client.sendMessage(user, this.usageList));
90 | }
91 |
92 | onDisconnected() {
93 | return () =>
94 | console.warn('Bot has been disconnected from server...');
95 | }
96 |
97 | onError() {
98 | return ((err) => {
99 | console.error('error: ', err);
100 | console.error(err.trace);
101 | });
102 | }
103 |
104 | loadCommands(cmdList) {
105 | this.usageList = '';
106 | cmdList.forEach((cmd) => {
107 | const fullpath = path.join(__dirname, 'commands', cmd, `${cmd}.js`);
108 | const script = require(fullpath); // eslint-disable-line global-require, import/no-dynamic-require
109 | this.commands[cmd] = script;
110 |
111 | const usageObj = script.usage;
112 | if (usageObj) {
113 | const usageStrs = [];
114 | if (Array.isArray(usageObj)) {
115 | usageObj.forEach(u => usageStrs.push(u));
116 | } else {
117 | usageStrs.push(usageObj.toString());
118 | }
119 |
120 | this.usageList += usageStrs.reduce((list, str) => list + `\n- ${this.settings.bot_cmd} ${str}`, '');
121 | }
122 | });
123 | }
124 |
125 | init() {
126 | // load commands
127 | console.log('Loading commands...');
128 | this.loadCommands(this.settings.commands);
129 |
130 | // setup events
131 | console.log('Setting up event bindings...');
132 | this.client
133 | .on('ready', this.onReady())
134 | .on('serverNewMember', this.serverNewMember())
135 | .on('message', this.onMessage())
136 | .on('error', this.onError());
137 |
138 | console.log('Connecting...');
139 | // return the promise from "login()"
140 | return this.client.login(this.token);
141 | }
142 |
143 | deinit() {
144 | // disconnect gracefully
145 | this.isReady = false;
146 | // return the promise from "destroy()"
147 | return this.client.destroy();
148 | }
149 |
150 | isAdminOrMod(member) {
151 | const immuneRoles = new Set(this.settings.voting.immuneRoles);
152 | const userRoles = new Set(member.roles.array().map(r => r.name));
153 | const setIntersection = [...userRoles].filter(r => immuneRoles.has(r));
154 | return setIntersection.length > 0;
155 | }
156 | }
157 |
158 | module.exports = TheAwesomeBot;
159 |
160 |
--------------------------------------------------------------------------------
/commands/stream/stream.js:
--------------------------------------------------------------------------------
1 | /*
2 | This module is meant to handle the command '!bot streams #channel'
3 |
4 | Notes:
5 | - streamCommands is an array with the valid stream commands for the keys in channels.js:
6 | e.g ['!bot streams python', '!bot streams php'...]
7 | e.g ['!bot streams remove [channel] [user]']
8 |
9 | How it works:
10 | - streams is an object with the following schema:
11 |
12 | streams: {
13 | [channel] : {
14 | [user]: link ,
15 | [user]: link ,
16 | .
17 | .
18 | .
19 | }
20 | }
21 | - streams is an in-memory-object of sorts to keep track of streams.
22 | */
23 |
24 | // in memory object containing join.me stream
25 | const streams = {};
26 |
27 | function putStreamInObject(topic, user, link, description) {
28 | if (!streams[topic]) {
29 | streams[topic] = {};
30 | }
31 |
32 | streams[topic][user] = { link, description, channel: '' };
33 |
34 | // console.log(`Put in Object: Key: ${topic}, User Key: ${user}`);
35 | }
36 |
37 | function createChannel(title, bot, message, topic, user) {
38 | return message.guild.createChannel(title, 'text').then((channel) => {
39 | streams[topic][user].channel = channel;
40 | return channel;
41 | });
42 | }
43 |
44 | function setTopicToLink(channel, link, bot, topic, user) {
45 | return channel.setTopic(link).then(() => {
46 | streams[topic][user].channel = channel;
47 | return channel;
48 | });
49 | }
50 |
51 |
52 | function deleteStreamInObject(topic, user) {
53 | if (Object.keys(streams[topic]).length === 1) {
54 | delete streams[topic];
55 | } else {
56 | delete streams[topic][user];
57 | }
58 | }
59 |
60 |
61 | const commands = {
62 | create: function handleCreateStream(bot, message, args) {
63 | let [, topic, link, user] = args.split(' '); // eslint-disable-line prefer-const
64 |
65 | if (!topic || !link) {
66 | return message.channel.sendMessage('err, please provide topic and link!');
67 | }
68 |
69 | if (link.indexOf('http://') === -1 && link.indexOf('https://') === -1) {
70 | return message.channel.sendMessage('a valid link must be supplied (starting with http/https)!');
71 | }
72 |
73 | user = message.mentions.users.first();
74 | if (user) {
75 | // Creating a channel for someone else
76 | // The keys in the topics object are username mention id
77 | } else {
78 | // Creating a channel for you
79 | // The keys in the topics object are username mention id
80 | user = message.author;
81 | }
82 | const channelFormat = `stream_${user.username}_${topic}`;
83 |
84 | const defaultDescription =
85 | `${user} is streaming about ${topic}`;
86 |
87 | putStreamInObject(topic, user.id, link, defaultDescription);
88 |
89 | const existingChannel = bot.client.channels.get('name', channelFormat);
90 |
91 | if (existingChannel) {
92 | streams[topic][user.id].channel = existingChannel;
93 | streams[topic][user.id].link = link;
94 |
95 | existingChannel.setTopic(link).catch(err =>
96 | existingChannel.sendMessage('There was an error setting the existings channel topic!'));
97 |
98 | return message.channel.sendMessage('Channel already exists.. Updated stream link!');
99 | }
100 | return createChannel(channelFormat, bot, message, topic, user.id)
101 | .then(createdChannel => setTopicToLink(createdChannel, link, bot, topic, user.id))
102 | .then(channelWithTopic => message.channel.sendMessage(`Created ${channelWithTopic}!`))
103 | .catch(err => message.channel.sendMessage(`Sorry, could not create channel (${err})`));
104 | },
105 |
106 | remove: function handleRemoveStream(bot, message) {
107 | const user = message.mentions.users.first();
108 |
109 | const topics = Object.keys(streams);
110 | const id = user ? user.id : message.author.id;
111 |
112 | if (!bot.isAdminOrMod(message.member) && id !== message.author.id) {
113 | message.channel.sendMessage('Only admins or mods can remove others\' streams.');
114 | return;
115 | }
116 |
117 | topics.forEach((topic) => {
118 | if (streams[topic][id]) {
119 | const channelToDelete = streams[topic][id].channel;
120 |
121 | deleteStreamInObject(topic, id);
122 |
123 | channelToDelete.delete().catch(err =>
124 | message.channel.sendMessage('Sorry, could not delete channel'));
125 |
126 | message.channel.sendMessage(
127 | `Removed ${user || message.author} from active streamers list and deleted #${channelToDelete.name}`);
128 | } else {
129 | // user has no stream in this topic
130 | // return message.channel.sendMessage(`Could not find ${user}`);
131 | }
132 | });
133 | },
134 |
135 | list: function listStreams(bot, message) {
136 | let buildMessage = 'Available streams: \n';
137 |
138 | const topics = Object.keys(streams);
139 |
140 | if (topics.length === 0) {
141 | return message.channel.sendMessage('No streams! :frowning:');
142 | }
143 |
144 | topics.forEach((topic) => {
145 | buildMessage += `**\n${topic}**\n`;
146 | Object.keys(streams[topic]).forEach((stream, index) => {
147 | const link = streams[topic][stream].link;
148 | const description = streams[topic][stream].description;
149 |
150 | buildMessage += ` ${index + 1}. ${link} - ${description}\n`;
151 | });
152 | });
153 |
154 | return message.channel.sendMessage(buildMessage);
155 | },
156 |
157 | removeall: function removeAllStreams(bot, message) {
158 | if (message && !bot.isAdminOrMod(message.member)) {
159 | message.channel.sendMessage('Only Admins or Mods can delete all stream channels');
160 | return;
161 | }
162 |
163 | console.log('Removing all stream channels..');
164 | bot.client.guilds.first().channels.filter(channel =>
165 | channel && channel.name !== undefined && channel.name.startsWith('stream'))
166 | .forEach(channel => channel.delete());
167 |
168 | Object.keys(streams).forEach(topic => delete streams[topic]);
169 | },
170 | };
171 |
172 | module.exports = {
173 | usage: [
174 | 'stream create [@user] - creates stream about [by @user]',
175 | 'stream list - displays streamings about ',
176 | 'stream remove <@user> - removes a streaming by <@user>',
177 | 'stream removeall - removes every streaming (Mods only)',
178 | ],
179 |
180 | run: (bot, message, cmdArgs) => {
181 | const cmdFn = commands[cmdArgs.split(' ')[0]];
182 | if (!cmdFn) return true;
183 | cmdFn(bot, message, cmdArgs);
184 | return false;
185 | },
186 |
187 | init: (bot) => {
188 | commands.removeall(bot);
189 | },
190 | };
191 |
192 |
--------------------------------------------------------------------------------