-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Connect app to the c0d3r (Discord Bot) #949
Conversation
@JasirZaeem is attempting to deploy a commit to a Personal Account owned by @garageScript on Vercel. @garageScript first needs to authorize it. |
Codecov Report
@@ Coverage Diff @@
## master #949 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 115 116 +1
Lines 1974 2000 +26
Branches 485 487 +2
=========================================
+ Hits 1974 2000 +26
|
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/c0d3/c0d3-app/3F31RSxk1t8upFVLZi982LzwQevj |
The review link was for the same lesson as the lesson to send notification to, fixed
helpers/discordBot.ts
Outdated
fetch(`${messagesEndpoint}/channel/${channelId}`, { | ||
method: 'POST', | ||
headers, | ||
body: JSON.stringify({ | ||
message, | ||
embed | ||
}) | ||
}).then(r => r.json()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably add a .catch()
on these fetches to handle unexpected errors that could interrupt the API call.
Could also maybe extract a helper function here with the fetch
call since the parameters are mostly the same for all the functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well there is actually another issue were we need to rememeber we are running serverless functions so unless you await every async call there will be no work finished after the user gets there response.
But I don't think we really want our users waiting for a discord msg to go through so not awaiting is good in this case.
I think the real proper pattern we need to implement is if we want to catch all the errors is either auto respond with a msg received update and await that or just send it and know all error reporting is responsible via the receiver
(We could still add a catch but I dont believe the errors would ever make it to our logs or would do so inconsistently)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well there is actually another issue were we need to remember we are running serverless functions so unless you await every async call there will be no work finished after the user gets there response.
Yeah, when solving the issue with emails not being sent I believe part of the problem was that the promise was not being awaited. In this case the await should be put on the function that will be calling this, because the return of these functions are going to be 'Promise' even with the await.
For the error catching, in the case of these notifications I think they should be handled gracefully and not propagate up the call stack. Since these notifications are not crucial for the app they can be ignored and just be logged so us on the engineering team can look into it, but the response for the client should still be successful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is all these discord functions should be fire-and-forget. They send a command to the bot and that's it, this is to ensure that requests, of which these are called a part of, don't wait on response from discord as that is not necessary.
And so, keeping this in mind, like @tomrule007 pointed out, the serverless invocation that was handling a request may have already ended before discord responds.
Considering this, I'm not sure what a catch function would do here. The intended use is to call these without awaiting them or adding .then
, if the caller needs the returned response then it would be their responsibility to take care of the error handling by either adding .catch
then or if using async/await then surrounding it in a try catch block.
Does this make sense? if not then I'd need some suggestion as to what to do inside a .catch
if a attach it to these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ggwadera So is your suggestion then to await where they are called, and add a catch to these functions which silently logs the error on failure and lets the rest of the caller function run like usual ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm good idea. The bot could reply immediately since we don't to need to wait for the Discord API call to finish it here. After the bot sends the response back it could then process the notification normally.
Detailed error logs could be done on the bot side too, here we could just leave a generic catch to avoid halting the execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I am working on that, I will change the default behaviour of the bot to respond immediately without waiting on discord, while providing an option to get details of the message if needed, as these details can be used to perform tasks like replying to these messages, editing them reacting to them and so on.
So the default behaviour of the bot will be to respond without waiting on discord, but if requested it will wait for the message sending to complete and respond with the details of the message, this can then be used in the future to build more features.
Adding logging in the bot itself is a task in progress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is supposed to be fire and forget function, then why do you need .then(r=>r.json())
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's because in most cases you would just fire and forget it, but, it does return a json body with any errors and (optionally) details of the message the was created at discord, so if a function wants to use any of these it can just acquire an object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Calls to dicord functions are now awaited.
- Common fetch calls in discord functions have been moved into a separate function.
- The function calling fetch for c0d3r is now chained by
.catch
to prevent promise rejections from affecting called functions. - c0d3r will immediately respond to requests by default after Make bot respond to api requests without waiting for discord. c0d3r#11
@types/discordBot.d.ts
Outdated
@@ -0,0 +1,70 @@ | |||
import { BotErrorType, IdType } from '../helpers/discordBot' | |||
|
|||
export type snowflake = string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this snowflake
type. Why not use string instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just for convenience, to make it clear at a glance that it'll be something like '828783458469675019' and not an arbitrary string https://en.wikipedia.org/wiki/Snowflake_ID .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just an type alias. This way we can know that this string type actually has a snowflake id format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL about snowflake ids. This wiki link should be in a comment IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discord.js has been added as dependency, snowflake type alias is now imported from it. A comment describing it has been added in discordBot.d.ts
@types/discordBot.d.ts
Outdated
color?: any | ||
fields?: any[] | ||
files?: any[] | ||
author?: any | ||
thumbnail?: any | ||
image?: any | ||
video?: any | ||
footer?: any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to type these fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/discordjs/discord.js/blob/master/typings/index.d.ts#L3546-L3558 it depends on many other types I'm not sure how deep to go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you add a comment with the link?
couldn't we import discord.js's types instead of recreating this? Of no, then perhaps instead of any
, we just type the specific resource that we need. If in the future someone needs more, they can add it in by following your comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, discord.js
has been added as a dependency and relevant types are now imported from it rather than been manually recreated.
helpers/discordBot.ts
Outdated
fetch(`${messagesEndpoint}/channel/${channelId}`, { | ||
method: 'POST', | ||
headers, | ||
body: JSON.stringify({ | ||
message, | ||
embed | ||
}) | ||
}).then(r => r.json()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is supposed to be fire and forget function, then why do you need .then(r=>r.json())
here?
helpers/discordBot.test.js
Outdated
const channelId = '1234' | ||
const message = 'hello' | ||
const embed = { description: 'an embed' } | ||
const lessonId = 1 | ||
const notificationLessonId = 2 | ||
const idType = IdType.C0D3 | ||
const userName = 'test_user' | ||
const discordUserId = '9898' | ||
const challengeTitle = 'test challenge' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please make this into an object instead? I think it's neater than having separate variables for everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might not look the neatest but it does make all the code below it very easy to read IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const mockData = {
channelId: '1234',
message: 'hello',
embed: { description: 'an embed' },
lessonId: 1,
notificationLessonId: 2,
idType: IdType.C0D3,
userName: 'test_user',
discordUserId: '9898',
challengeTitle: 'test challenge'
}
Is this fine? variable names will be longer where used but should be readable
@JasirZaeem I assigned this issue to you perhaps you could take a look afterward? Its sort of related to this feature: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just got the one question about discord.js
possibly being a dev dependency if it is only used for Types? (I'm still a ts noob so i dont know how that all works)
And lastly I thought you could remove some extra database calls by doing some if the id look ups on the c0d3r side. Not really sure how much time an extra database call roundtrip adds.
@@ -116,3 +116,6 @@ dist | |||
|
|||
#removing package-lock as the master branch has yarn.lock | |||
package-lock.json | |||
|
|||
# Jetbrains ide config files | |||
.idea |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first time I've seen someone use Jetbrains as an IDE for JS/TS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find WebStorm very convenient :D
Yeah, sure, I'll work on it. |
Part of #843
As a part of the effort to integrate the discord server with c0d3.com I had created a Discord bot, c0d3r, here which exposes a REST Api to interact with Discord.
This PR adds utility functions to the app that allows interaction with this bot, right now this lets us send messages to Discord from the app and more features can be added later as required.
Full integration to achieve the same that we had mattermost can be done once the OAuth integration, under process, is completed.
Until then the following notifications have been added in this PR which the app will send to Discord.
On challenge submission
When a new challenge is submitted the app will call the bot to send a message to the channel of the next lesson to notify reviewers. e.g. to the JS2 channel for submissions in lesson JS1 (submissions for the last lesson will be notified in its channel itself, currently this is JS9)
Example
On lesson completion
When a student completes a lesson two notifications will be sent, one to the completed lesson to notify that the student has completed the lesson, and one to the next lesson's channel to notify that a new student is beginning the lesson, as was the case with mattermost. In case of the last lesson (currently JS9) the second notification will be skipped.
Example
1st notification
![image](https://user-images.githubusercontent.com/20666236/125163260-f91bbf80-e1a9-11eb-85ca-1372997e07ac.png)
2nd notification
![image](https://user-images.githubusercontent.com/20666236/125163221-cc67a800-e1a9-11eb-99de-8e082a419b6a.png)
On receiving a star
When a reviewer receives a star a notification is sent to the channel for the lesson they received the star for.
Example
TODO
Once the OAuth integration is complete and the app has knowledge of the user's discord id more ways to communicate with discord through the bot can be implemented. For example challenge submission notifications can mention the discord user with their correct username/server nickname (if set) once we have their id like this
![image](https://user-images.githubusercontent.com/20666236/125163599-7d227700-e1ab-11eb-9802-5d346406c13b.png)
Other changes
discord.js
to be able to use it's types for better type safety.includeDetails
can be passed in json bodies of requests, requests with these will be responded to after the message is sent to discord with the id of the message. Relevant PR.idea
to.gitignore
to ignore files created by the jetbrains ides like webstorm.