Skip to content

Commit e52c930

Browse files
author
Dillon Christensen
committed
initial commit
0 parents  commit e52c930

File tree

3 files changed

+387
-0
lines changed

3 files changed

+387
-0
lines changed

gdq-schedule.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
let moment = require('moment');
2+
let jsdom = require('jsdom');
3+
4+
module.exports = url => {
5+
if (!url) {
6+
url = 'https://gamesdonequick.com/schedule';
7+
}
8+
return new Promise((resolve, reject) => {
9+
jsdom.env(url, (err, window) => {
10+
if (!err) {
11+
let results = parsePage(window);
12+
console.log(`loaded up ${results.length} results`);
13+
resolve(results);
14+
} else {
15+
console.log('nope', err);
16+
reject(err);
17+
}
18+
});
19+
});
20+
};
21+
22+
function getLocalMoment(d) {
23+
return moment(d).utcOffset('-06:00');
24+
}
25+
26+
function parsePage(window) {
27+
const document = window.document;
28+
let rows = document.querySelectorAll('#runTable tbody tr');
29+
let schedule = [];
30+
31+
Array.from(rows)
32+
.forEach(r => {
33+
let tds = r.querySelectorAll('td');
34+
if (r.className.indexOf('second-row') !== -1) {
35+
let game = schedule[schedule.length - 1];
36+
game.estimate = tds[0].textContent.trim();
37+
let estimate = game.estimate.split(':');
38+
game.ends = getLocalMoment(game.start).add(estimate[0], 'h').add(estimate[1], 'm').add(estimate[2], 's');
39+
game.done = getLocalMoment().isAfter(game.ends);
40+
} else if (tds.length === 4) {
41+
let startMoment = getLocalMoment(tds[0].textContent.trim());
42+
schedule.push({
43+
title: tds[1].textContent.trim(),
44+
start: startMoment,
45+
runners: tds[2].textContent.trim()
46+
});
47+
}
48+
});
49+
return schedule
50+
.filter(g => !g.done);
51+
}

index.js

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2+
______ ______ ______ __ __ __ ______
3+
/\ == \ /\ __ \ /\__ _\ /\ \/ / /\ \ /\__ _\
4+
\ \ __< \ \ \/\ \ \/_/\ \/ \ \ _"-. \ \ \ \/_/\ \/
5+
\ \_____\ \ \_____\ \ \_\ \ \_\ \_\ \ \_\ \ \_\
6+
\/_____/ \/_____/ \/_/ \/_/\/_/ \/_/ \/_/
7+
8+
9+
This is a sample Slack bot built with Botkit.
10+
11+
This bot demonstrates many of the core features of Botkit:
12+
13+
* Connect to Slack using the real time API
14+
* Receive messages based on "spoken" patterns
15+
* Reply to messages
16+
* Use the conversation system to ask questions
17+
* Use the built in storage system to store and retrieve information
18+
for a user.
19+
20+
# RUN THE BOT:
21+
22+
Get a Bot token from Slack:
23+
24+
-> http://my.slack.com/services/new/bot
25+
26+
Run your bot from the command line:
27+
28+
token=<MY TOKEN> node slack_bot.js
29+
30+
# USE THE BOT:
31+
32+
Find your bot inside Slack to send it a direct message.
33+
34+
Say: "Hello"
35+
36+
The bot will reply "Hello!"
37+
38+
Say: "who are you?"
39+
40+
The bot will tell you its name, where it is running, and for how long.
41+
42+
Say: "Call me <nickname>"
43+
44+
Tell the bot your nickname. Now you are friends.
45+
46+
Say: "who am I?"
47+
48+
The bot will tell you your nickname, if it knows one for you.
49+
50+
Say: "shutdown"
51+
52+
The bot will ask if you are sure, and then shut itself down.
53+
54+
Make sure to invite your bot into other channels using /invite @<my bot>!
55+
56+
# EXTEND THE BOT:
57+
58+
Botkit has many features for building cool and useful bots!
59+
60+
Read all about it here:
61+
62+
-> http://howdy.ai/botkit
63+
64+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
65+
66+
process.env.token = 'xoxb-125328283350-dcs7J8cxhCvQHCfO2L69z38u';
67+
if (!process.env.token) {
68+
console.log('Error: Specify token in environment');
69+
process.exit(1);
70+
}
71+
72+
var Botkit = require('botkit/lib/Botkit.js');
73+
var os = require('os');
74+
75+
var controller = Botkit.slackbot({
76+
debug: true,
77+
});
78+
79+
var bot = controller.spawn({
80+
token: process.env.token
81+
}).startRTM();
82+
83+
let gdq = require('./gdq-schedule');
84+
85+
controller.hears(['hello', 'hi'], 'direct_message,direct_mention,mention', function (bot, message) {
86+
87+
bot.api.reactions.add({
88+
timestamp: message.ts,
89+
channel: message.channel,
90+
name: 'robot_face',
91+
}, function (err, res) {
92+
if (err) {
93+
bot.botkit.log('Failed to add emoji reaction :(', err);
94+
}
95+
});
96+
97+
98+
controller.storage.users.get(message.user, function (err, user) {
99+
if (user && user.name) {
100+
bot.reply(message, 'Hello ' + user.name + '!!');
101+
} else {
102+
bot.reply(message, 'Hello.');
103+
}
104+
});
105+
});
106+
107+
controller.hears(['(start|stop) book shopping mode'], 'direct_message,direct_mention,mention', (bot, message) => {
108+
let isStarting = message.match[1].toLowerCase() === 'start';
109+
controller.storage.users.get(message.user, (err, user) => {
110+
if (!user) {
111+
user = {
112+
id: message.user,
113+
isBookShopping: false
114+
};
115+
}
116+
if (isStarting) {
117+
if (user.isBookShopping) {
118+
bot.reply(message, 'You are already in book shopping mode!');
119+
} else {
120+
user.isBookShopping = true;
121+
controller.storage.users.save(user, (err, id) => bot.reply(message, 'Okay, book shopping mode enabled.'));
122+
}
123+
} else {
124+
if (!user.isBookShopping) {
125+
bot.reply(message, 'You haven\'t even started book shopping mode yet.');
126+
} else {
127+
user.isBookShopping = false;
128+
controller.storage.users.save(user, (err, id) => bot.reply(message, 'Okay, book shopping mode disabled.'));
129+
}
130+
}
131+
});
132+
});
133+
134+
controller.hears(['call me (.*)', 'my name is (.*)'], 'direct_message,direct_mention,mention', function (bot, message) {
135+
var name = message.match[1];
136+
controller.storage.users.get(message.user, function (err, user) {
137+
if (!user) {
138+
user = {
139+
id: message.user,
140+
};
141+
}
142+
user.name = name;
143+
controller.storage.users.save(user, function (err, id) {
144+
bot.reply(message, 'Got it. I will call you ' + user.name + ' from now on.');
145+
});
146+
});
147+
});
148+
149+
controller.hears(['what is my name', 'who am i'], 'direct_message,direct_mention,mention', function (bot, message) {
150+
151+
controller.storage.users.get(message.user, function (err, user) {
152+
if (user && user.name) {
153+
bot.reply(message, 'Your name is ' + user.name);
154+
} else {
155+
bot.startConversation(message, function (err, convo) {
156+
if (!err) {
157+
convo.say('I do not know your name yet!');
158+
convo.ask('What should I call you?', function (response, convo) {
159+
convo.ask('You want me to call you `' + response.text + '`?', [
160+
{
161+
pattern: 'yes',
162+
callback: function (response, convo) {
163+
// since no further messages are queued after this,
164+
// the conversation will end naturally with status == 'completed'
165+
convo.next();
166+
}
167+
},
168+
{
169+
pattern: 'no',
170+
callback: function (response, convo) {
171+
// stop the conversation. this will cause it to end with status == 'stopped'
172+
convo.stop();
173+
}
174+
},
175+
{
176+
default: true,
177+
callback: function (response, convo) {
178+
convo.repeat();
179+
convo.next();
180+
}
181+
}
182+
]);
183+
184+
convo.next();
185+
186+
}, {'key': 'nickname'}); // store the results in a field called nickname
187+
188+
convo.on('end', function (convo) {
189+
if (convo.status == 'completed') {
190+
bot.reply(message, 'OK! I will update my dossier...');
191+
192+
controller.storage.users.get(message.user, function (err, user) {
193+
if (!user) {
194+
user = {
195+
id: message.user,
196+
};
197+
}
198+
user.name = convo.extractResponse('nickname');
199+
controller.storage.users.save(user, function (err, id) {
200+
bot.reply(message, 'Got it. I will call you ' + user.name + ' from now on.');
201+
});
202+
});
203+
204+
205+
} else {
206+
// this happens if the conversation ended prematurely for some reason
207+
bot.reply(message, 'OK, nevermind!');
208+
}
209+
});
210+
}
211+
});
212+
}
213+
});
214+
});
215+
216+
217+
controller.hears(['shutdown'], 'direct_message,direct_mention,mention', function (bot, message) {
218+
219+
bot.startConversation(message, function (err, convo) {
220+
221+
convo.ask('Are you sure you want me to shutdown?', [
222+
{
223+
pattern: bot.utterances.yes,
224+
callback: function (response, convo) {
225+
convo.say('Bye!');
226+
convo.next();
227+
setTimeout(function () {
228+
process.exit();
229+
}, 3000);
230+
}
231+
},
232+
{
233+
pattern: bot.utterances.no,
234+
default: true,
235+
callback: function (response, convo) {
236+
convo.say('*Phew!*');
237+
convo.next();
238+
}
239+
}
240+
]);
241+
});
242+
});
243+
244+
245+
controller.hears(['uptime', 'identify yourself', 'who are you', 'what is your name'],
246+
'direct_message,direct_mention,mention', function (bot, message) {
247+
248+
var hostname = os.hostname();
249+
var uptime = formatUptime(process.uptime());
250+
251+
bot.reply(message,
252+
':robot_face: I am a bot named <@' + bot.identity.name +
253+
'>. I have been running for ' + uptime + ' on ' + hostname + '.');
254+
255+
});
256+
257+
controller.hears(['gdq'], 'direct_message,direct_mention,mention', (b, m) => {
258+
gdq()
259+
.then(g => {
260+
if (g && g.length) {
261+
bot.reply(m, {
262+
attachments: g.slice(0, 5).map(e => ({
263+
author_name: e.runners,
264+
title: e.title,
265+
text: `Starts at *${e.start.format('h:mm A')}* and has an estimate of _${e.estimate}_, so expected to end at *${e.ends.format('h:mm A')}*.`,
266+
footer: 'GDQ',
267+
footer_icon: 'https://gamesdonequick.com/static/res/img/favicon/favicon.ico',
268+
mrkdwn_in: ['text']
269+
}))
270+
});
271+
} else {
272+
bot.reply(m, 'Sorry, I got nothin\'.');
273+
}
274+
})
275+
});
276+
277+
controller.hears('interactive', 'direct_message', function (bot, message) {
278+
279+
bot.reply(message, {
280+
attachments: [
281+
{
282+
title: 'Do you want to interact with my buttons?',
283+
callback_id: '123',
284+
attachment_type: 'default',
285+
actions: [
286+
{
287+
"name": "yes",
288+
"text": "Yes",
289+
"value": "yes",
290+
"type": "button",
291+
},
292+
{
293+
"name": "no",
294+
"text": "No",
295+
"value": "no",
296+
"type": "button",
297+
}
298+
]
299+
}
300+
]
301+
});
302+
});
303+
304+
function formatUptime(uptime) {
305+
var unit = 'second';
306+
if (uptime > 60) {
307+
uptime = uptime / 60;
308+
unit = 'minute';
309+
}
310+
if (uptime > 60) {
311+
uptime = uptime / 60;
312+
unit = 'hour';
313+
}
314+
if (uptime != 1) {
315+
unit = unit + 's';
316+
}
317+
318+
uptime = uptime + ' ' + unit;
319+
return uptime;
320+
}

package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "kowalski",
3+
"version": "1.0.0",
4+
"description": "A slack bot for fun",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "dillonchr",
10+
"license": "MIT",
11+
"dependencies": {
12+
"botkit": "^0.4.9",
13+
"jsdom": "^9.9.1",
14+
"momentjs": "^2.0.0"
15+
}
16+
}

0 commit comments

Comments
 (0)