4
4
5
5
/* MODULES */
6
6
7
+ //jshint esversion:6
8
+
7
9
// Global dependencies
8
- const Discord = require ( 'discord.js' )
9
- const request = require ( 'request' )
10
- const Client = new Discord . Client ( )
10
+ const request = require ( 'request' ) ;
11
+ const querystring = require ( 'querystring' ) ;
12
+ var j = request . jar ( ) ;
13
+
14
+ const passwords = require ( './passwords.json' ) ;
15
+ const btoa = require ( "btoa" ) ;
16
+ const mysql = require ( 'mysql' ) ;
17
+
18
+ const Discord = require ( 'discord.js' ) ;
19
+ const Client = new Discord . Client ( ) ;
20
+
21
+ const con = mysql . createConnection ( {
22
+ host : "localhost" ,
23
+ user : "SaihttamBot" ,
24
+ password : passwords . mysql ,
25
+ database : "saihttambot" ,
26
+ } ) ;
27
+
28
+ con . connect ( ) ;
11
29
12
- var prefix = "./" ;
30
+ const prefix = "./" ;
13
31
14
32
var runOnMessage = [ ] ;
15
33
16
- var addReacts = function ( message , codePoint , numReacts ) {
34
+ //Login to Khan Academy
35
+ var fkey ;
36
+ var KArequest ;
37
+
38
+ var settings = {
39
+ method : "POST" ,
40
+ form : {
41
+ identifier : "SaihttamBot" ,
42
+ password : passwords . khanacademy ,
43
+ } ,
44
+ url : "https://www.khanacademy.org/login" ,
45
+ jar : j ,
46
+ } ;
47
+ request ( settings , function ( error , response , body ) {
48
+ if ( response && response . statusCode === 200 ) {
49
+ fkey = j . getCookies ( "https://www.khanacademy.org" ) . find ( c => c . key === "fkey" ) . value ;
50
+
51
+ KArequest = request . defaults ( { "baseUrl" : "https://www.khanacademy.org" , jar : j , headers : { "X-KA-FKEY" : fkey } } ) ;
52
+
53
+ console . log ( "Logged in to Khan Academy." ) ;
54
+
55
+ Client . login ( passwords . discord ) . catch ( console . error ) ;
56
+ } else {
57
+ console . log ( error || response ) ;
58
+ }
59
+ } ) ;
60
+
61
+ var pending_links = [ ] ;
62
+
63
+ function replyCheckLoop ( ) {
64
+ KArequest ( "/api/internal/user/profile?username=SaihttamBot&projection={\"countBrandNewNotifications\":1}" , function ( e , r , d ) {
65
+ d = JSON . parse ( d ) ;
66
+
67
+ console . log ( "Tick, notifs: " , d . countBrandNewNotifications ) ;
68
+ //If there's a notif
69
+ if ( d . countBrandNewNotifications > 0 ) {
70
+ //Check the replies to any pending links.
71
+ for ( var i = 0 ; i < pending_links . length ; i ++ ) {
72
+
73
+ //Sort by recent--we want the for loop to hit the oldest first
74
+ KArequest ( `/api/internal/discussions/${ pending_links [ i ] . kaEncryptedId } /replies?sort=2&projection=[{"normal":{"key":1,"content":1,"authorKaid":1,"authorNickname":1}}]&link_index=${ i } ` , function ( error , response , data ) {
75
+ data = JSON . parse ( data ) ;
76
+
77
+ if ( response && ! error && response . statusCode === 200 ) {
78
+ var obj = pending_links [ parseInt ( querystring . parse ( response . request . uri . query ) . link_index ) ] ;
79
+
80
+ for ( var j = 0 ; j < data . length ; j ++ ) {
81
+
82
+ var reply_data = data [ j ] ;
83
+
84
+ if ( reply_data . content . includes ( obj . verifyToken ) ) {
85
+ //Save a link into the database with the discord_id and KAID -
86
+ con . query ( `INSERT INTO user_links (discord_id, kaid) VALUES ('${ obj . author . id } ', '${ reply_data . authorKaid } ')` , function ( error , results , fields ) {
87
+ if ( error ) throw error ;
88
+
89
+ //Send message to the user stating the comment that was made and providing instructions on how to remove it +
90
+ obj . author . send ( `I've seen a comment from KA user` +
91
+ ` ${ reply_data . authorNickname } (https://khanacademy.org/profile/${ reply_data . authorKaid } ) with your reply code, and an account link has been successfully created.` ) ;
92
+ obj . author . send ( "If this is the wrong user, or you want to link a different account, you can remove this link by issuing `./linkkaaccount remove`" ) ;
93
+
94
+ //Kill the timout
95
+ clearTimeout ( obj . deleteTimer ) ;
96
+
97
+ //Remove the pending link from the database +
98
+ pending_links . splice ( obj . index , 1 ) ;
99
+
100
+ //Delete the comment
101
+ KArequest . delete ( `/api/internal/feedback/${ obj . kaEncryptedId } ` , function ( e , r , d ) {
102
+ if ( r . statusCode !== 200 ) console . error ( e , r , d ) ;
103
+ } ) ;
104
+ } ) ;
105
+ }
106
+ }
107
+ }
108
+ } ) ;
109
+ }
110
+
111
+ KArequest . post ( "/api/internal/user/notifications/clear_brand_new" , function ( e , r , d ) {
112
+ if ( ! r || r . statusCode !== 200 ) {
113
+ console . log ( e , r ) ;
114
+ } else {
115
+ console . log ( "Read notifs" )
116
+ }
117
+ } ) ;
118
+ }
119
+
120
+ if ( pending_links . length > 0 ) {
121
+ replyCheckLoop ( ) ;
122
+ }
123
+ } ) ;
124
+ }
125
+
126
+ function addReacts ( message , codePoint , numReacts ) {
17
127
if ( codePoint - 127462 >= numReacts ) {
18
128
return ;
19
129
} else {
@@ -36,11 +146,11 @@ var commands = {
36
146
message . channel . send ( "Your \"username\" contains an invalid character." ) ;
37
147
return ;
38
148
}
39
- var url = "https://www.khanacademy.org /api/internal/signup/check-username?username=" + content ;
40
- request ( url , function ( error , response , body ) {
149
+ var url = "/api/internal/signup/check-username?username=" + content ;
150
+ KArequest ( url , function ( error , response , body ) {
41
151
if ( response ) {
42
152
if ( ! error && response . statusCode === 200 ) {
43
- data = JSON . parse ( body ) ;
153
+ var data = JSON . parse ( body ) ;
44
154
if ( data ) {
45
155
message . channel . send ( "This username is available! :confetti_ball:" ) ;
46
156
} else {
@@ -57,12 +167,121 @@ var commands = {
57
167
}
58
168
} ,
59
169
170
+ linkkaaccount ( message , content ) {
171
+ var args = content . split ( " " ) ;
172
+
173
+ var discord_id = message . author . id ;
174
+ if ( args . length && args [ 0 ] === "cancel" ) {
175
+ var index = pending_links . findIndex ( l => l . author . id === message . author . id ) ;
176
+ if ( index === - 1 ) {
177
+ message . channel . send ( "You didn't have a pending link request." ) ;
178
+ } else {
179
+ var obj = pending_links . splice ( index , 1 ) [ 0 ] ;
180
+
181
+ KArequest . delete ( `/api/internal/feedback/${ obj . kaEncryptedId } ` ,
182
+ ( e , r , d ) => { if ( r . statusCode !== 200 ) console . log ( e , r , d ) ; }
183
+ ) ;
184
+
185
+ message . channel . send ( "Your pending request has been deleted." ) ;
186
+ }
187
+ } else if ( args . length && args [ 0 ] === "remove" ) {
188
+ con . query ( `DELETE from user_links WHERE discord_id=${ message . author . id } ` , function ( error , results , fields ) {
189
+ if ( error ) throw error ;
190
+ if ( results . affectedRows ) {
191
+ message . channel . send ( "KA account link removed successfully." ) ;
192
+ } else {
193
+ message . channel . send ( "You don't appear to have a KA account linked with your Discord account." )
194
+ }
195
+ } ) ;
196
+ } else if ( ! args . length || ! args [ 0 ] ) {
197
+ con . query ( `SELECT kaid FROM user_links WHERE discord_id="${ discord_id } "` , function ( error , results , fields ) {
198
+ if ( error ) return console . error ( error ) ;
199
+
200
+ if ( results . length ) {
201
+ message . channel . send ( `Your Discord account is already linked to the following KA account: https://khanacademy.org/profile/${ results [ 0 ] . kaid } ` ) ;
202
+ return ;
203
+ }
204
+
205
+ //Also, check if there's an already pending link.
206
+ if ( pending_links . find ( l => l . discord_id === message . author . id ) ) {
207
+ message . channel . send ( "You already have a pending link request. Use `./linkkaaccount cancel` to cancel it." ) ;
208
+ return ;
209
+ }
210
+
211
+ message . channel . send ( "I'll PM you instructions on linking your KA account. One second." ) ;
212
+
213
+ var settings = {
214
+ url : "/api/internal/discussions/scratchpad/5118767575367680/comments" ,
215
+ method : "POST" ,
216
+ json : {
217
+ text : `If you are Discord user ${ message . author . tag } , please comment below with the code I sent you.` ,
218
+ topic_slug :"computer-programming" ,
219
+ } ,
220
+ } ;
221
+ KArequest ( settings , function ( error , response , data ) {
222
+ if ( response ) {
223
+ if ( ! error && response . statusCode === 200 ) {
224
+ var time = Date . now ( ) ;
225
+ //Generate a verification token
226
+ var verifyToken = btoa ( `${ discord_id } ;time;${ btoa ( Math . round ( Math . random ( ) * 10000000 ) ) } ` ) ;
227
+ //Save link, time, discord_id, and verify_code
228
+ var link = `https://khanacademy.org${ data . focusUrl } ?qa_expand_key=${ data . expandKey } ` ;
229
+ var kaEncryptedId = data . key ;
230
+
231
+ var obj = {
232
+ "time" : time ,
233
+ "author" : message . author ,
234
+ "index" : null ,
235
+ "kaEncryptedId" : kaEncryptedId ,
236
+ "verifyToken" : verifyToken
237
+ } ;
238
+
239
+ obj . index = pending_links . push ( obj ) - 1 ;
240
+
241
+ //Send user a message with a link to ther location and their verify code
242
+ message . author . send ( "In order to continue linking this Discord account with a KA account, please copy the following verification token." ) ;
243
+ message . author . send ( '\u2015' . repeat ( 20 ) + "\n" + obj . verifyToken + "\n" + '\u2015' . repeat ( 20 ) ) ;
244
+ message . author . send ( "Please reply to the linked comment that I've created with your token.\n" + link ) ;
245
+ message . author . send ( "If you do nothing after 3 minutes, or after you comment with your token, the KA comment will be deleted to protect the privacy of your Discord account." ) ;
246
+ message . author . send ( "If you'd like to cancel this request you can reply with `./linkkaaccount cancel`." ) ;
247
+
248
+ //Start a timeout for 3 minutes to delete the comment
249
+ obj . deleteTimer = setTimeout ( function ( ) {
250
+ KArequest . delete ( `/api/internal/feedback/${ kaEncryptedId } ` , function ( e , r , d ) {
251
+ if ( e || r . statusCode !== 200 ) {
252
+ console . error ( e , r , d ) ;
253
+ } else {
254
+ pending_links . splice ( obj . index , 1 ) ;
255
+
256
+ message . author . send ( "Since you haven't replied within 3 minutes, I've deleted my comment and canceled your request." ) ;
257
+ message . author . send ( "If you'd like to link your Discord and KA accounts, you can restart the process by replying with `./linkkaaccount`." ) ;
258
+ }
259
+ } ) ;
260
+
261
+
262
+ } , 1000 * 60 * 3 ) ;
263
+
264
+ //If it's not already running, start an interval to check notifications
265
+ if ( pending_links . length === 1 ) {
266
+ replyCheckLoop ( ) ;
267
+ }
268
+ } else {
269
+ console . log ( error , response , data ) ;
270
+ }
271
+ }
272
+ } ) ;
273
+ } ) ;
274
+ } else {
275
+ message . channel . send ( "Sorry, argument not recognized. Use `./linkkaaccount` in order to link your KA and Discord accounts." )
276
+ }
277
+ } ,
278
+
60
279
bitcoin ( message ) {
61
280
message . channel . startTyping ( ) ;
62
281
request ( "https://blockchain.info/tobtc?currency=USD&value=1" , function ( error , response , body ) {
63
282
if ( response ) {
64
283
if ( ! error && response . statusCode === 200 ) {
65
- data = JSON . parse ( body ) ;
284
+ var data = JSON . parse ( body ) ;
66
285
message . channel . send ( `1 Bitcoin is currently worth $${ ( 1 / data ) . toFixed ( 2 ) . replace ( / \B (? = ( \d { 3 } ) + (? ! \d ) ) / g, "," ) } .` ) ;
67
286
} else {
68
287
if ( response ) {
@@ -71,7 +290,7 @@ var commands = {
71
290
}
72
291
message . channel . stopTyping ( ) ;
73
292
}
74
- } )
293
+ } ) ;
75
294
} ,
76
295
77
296
help ( message ) {
@@ -170,7 +389,6 @@ var commands = {
170
389
}
171
390
} catch ( e ) {
172
391
message . channel . send ( "```" + e + "```" ) ;
173
- console . log ( code ) ;
174
392
}
175
393
} ,
176
394
}
@@ -232,6 +450,7 @@ Client.on('message', (message) => {
232
450
} )
233
451
234
452
Client . on ( 'ready' , ( ) => {
453
+ console . log ( "Connected to Discord" ) ;
235
454
// Client.user.setGame(prefix + 'help')
236
455
// Client.user.setAvatar('./images/image.png')
237
456
} )
@@ -246,12 +465,10 @@ Client.on('messageUpdate', (oldMess, newMess) => {
246
465
247
466
function exitHandler ( options , err ) {
248
467
Client . destroy ( ) ;
249
- console . log ( "" ) ;
468
+ con . end ( ) ;
250
469
process . exit ( ) ;
251
470
}
252
471
253
472
process . on ( 'SIGINT' , exitHandler ) ;
254
473
process . on ( 'exit' , exitHandler ) ;
255
474
process . on ( 'uncaughtException' , exitHandler ) ;
256
-
257
- Client . login ( require ( './token.json' ) ) . catch ( console . error )
0 commit comments