-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.ts
273 lines (241 loc) · 7.59 KB
/
index.ts
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import { Client, EmbedFieldData, Message, MessageEmbed } from 'discord.js';
import AWS from 'aws-sdk';
// Use .env variables
import * as dotenv from 'dotenv';
dotenv.config();
AWS.config.update({
region: process.env.AWS_DEFAULT_REGION,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
});
// Clients initialization
const client = new Client();
const dbClient = new AWS.DynamoDB.DocumentClient();
type Payload = {
user_id: string;
message: string;
log_date: string;
};
type DDBPutParams = {
TableName: string;
Item: Payload;
ConditionExpression: string;
ExpressionAttributeValues: Object;
};
type DDBQueryParams = {
TableName: string;
ExpressionAttributeValues: object;
KeyConditionExpression: string;
};
// Register an event so that when the bot is ready, it will log a messsage to the terminal
client.on('ready', () => {
console.log(`Logged in as ${client.user?.tag}!`);
});
// Register an event to handle incoming messages
client.on('message', async (msg: Message) => {
if (msg.content.startsWith('$help')) {
const reply = new MessageEmbed()
.setColor('#0099ff')
.setTitle(`#100DaysOfCloud Discord Bot help`)
.setDescription(
'Hey there! I am the official 100DaysOfCloud Discord bot!\n\nHere are a few commands that you can use!'
)
.addFields(
{
name: '$help',
value: 'Shows you this popup and the command list.',
inline: false,
},
{
name: '$logday',
value:
'Starts a conversation to log your day of progress. You can log only once a day!',
inline: false,
},
{
name: '$getlogs [number]',
value:
'Shows you your most recent logs. If you specify a number (eg: $getlogs x) it shows you your x most recent logs.',
}
)
.setTimestamp()
.setFooter(
'Want to see a new feature? Let us know in the #100-days-of-ideas channel!'
);
msg.reply(reply);
return;
}
// Handling daily logging messages
if (msg.content.startsWith('$logday')) {
// If the bot is the message author, disregard
let filter = (msg: Message) => !msg.author.bot;
// Accept only one answer in the next 15 seconds
let options = {
max: 1,
time: 30000,
};
// Create message collector to accept incoming chat messages
const collector = msg.channel.createMessageCollector(filter, options);
// Instantiate empty payload to be filled on collection
let payload = {} as Payload;
// Check if the message starts with '!hello' and respond with 'world!' if it does.
msg.reply('What do you want to log for today?');
collector.on('collect', (message) => {
const timestamp = new Date(message.createdAt);
payload = {
user_id: message.author.id,
message: message.content,
log_date: timestamp.toLocaleDateString('en-US'),
};
});
// When the collector is done, POST the the payload if the user_id/date combination is not already in DDB
collector.on('end', (_) => {
const params: DDBPutParams = {
TableName: process.env.DYNAMODB_TABLE_NAME as string,
Item: payload,
ConditionExpression: 'log_date <> :timestampVal',
ExpressionAttributeValues: {
':timestampVal': payload.log_date,
},
};
dbClient.put(params, (error) => {
if (!error) {
msg.reply(
`Success! Your log for ${payload.log_date} has been saved!`
);
} else {
if (error.code === 'ConditionalCheckFailedException') {
msg.reply(
"You already logged your progress today. You can't log more than once per day!"
);
} else {
msg.reply('Something went wrong, please try again!');
}
}
});
});
}
if (msg.content.startsWith('$getlogs')) {
// DDB query parameters to check for all the logs from the current user
// $getlogs only accepts 1 additional argument, the number of logs to show
const command = msg.content.split(' ').slice(1);
// defaults to 10 if not provided
let numberOfLogs = 10;
// Checking if we have exactly one argument
if (command.length == 1) {
numberOfLogs = Number(command[0]);
if (isNaN(numberOfLogs)) {
msg.reply('Please input a number');
return;
}
}
// Replying with an error message and preventing a DDB query to be executed
else if (command.length > 1) {
msg.reply('I received more arguments that I can handle!');
return;
}
if (numberOfLogs < 0) {
msg.reply(
'Please, enter either a positive number or 0 if you want to look at all your logs!'
);
return;
}
// Query parameters for the DynamoDB put call
var params: DDBQueryParams = {
TableName: process.env.DYNAMODB_TABLE_NAME as string,
ExpressionAttributeValues: {
':user_id': msg.author.id,
},
KeyConditionExpression: 'user_id = :user_id',
};
// Initialize empty user body
let logHistory: EmbedFieldData[] = [];
// Initialize empty date array for streak calculations
let logDates: string[] = [];
dbClient.query(params, function (err, data) {
// Reply something on error but logs the error.
if (err) {
console.log(err);
msg.reply('Something went wrong, sorry about that!');
return;
} else {
// When the query returns no items (user has not logged anything yet)
if (data.Items?.length === 0) {
msg.reply(
"You don't have any logged message! Start today by typing `$logday`!"
);
return;
} else {
// Iterate over the returned objects, creating of log objects and an array of ordered dates
data.Items?.forEach(function (element, index) {
let logBody = {
name: `Day ${index + 1} | ${element.log_date}`,
value: String(element.message),
inline: false,
};
logHistory = [...logHistory, logBody];
logDates = [...logDates, element.log_date];
});
// Actual logs to be showed.
// We still need to query the others to calculate the log streak
const newLog = logHistory.slice(
Math.max(
logHistory.length -
Math.min(logHistory.length, numberOfLogs),
0
)
);
// Gets the current streak of consecutive days with a log
const currentStreak = (arr: string[]): number => {
// Streak counter
let count = 0;
const UNIX_DAY = 86400000;
// We reverse the date array and check for each date if there's exactly a difference of 1 day * the current element's index
// First day would be (1 day) * 0, same day
// Second day would be (1 day) * 1, yesterday
// And so on until we don't have a match, at which point we know we don't have a streak anymore and we can just return the count
arr.reverse().forEach((date, i) => {
if (
new Date().setUTCHours(0, 0, 0, 0) -
new Date(date).setUTCHours(0, 0, 0, 0) ===
i * UNIX_DAY
)
count += 1;
});
return count;
};
// Creating the body of the embedded log history message
const msgReply = new MessageEmbed()
.setColor('#0099ff')
.setThumbnail(msg.author.avatarURL() as string)
.setTitle(`${msg.author.username} log report`)
.setDescription(
`Showing ${Math.min(
numberOfLogs,
logHistory.length
)} out of ${logHistory.length} logged days`
)
.addFields(
...newLog,
{
name: 'Days completed',
value: logHistory.length,
inline: true,
},
{
name: 'Current Streak',
value: currentStreak(logDates),
inline: true,
}
)
.setTimestamp()
.setFooter('Add a new log with `$logday`');
msg.reply(msgReply);
return;
}
}
});
}
});
// client.login logs the bot in and sets it up for use. You'll enter your token here.
client.login(process.env.DISCORD_TOKEN);