Skip to content

Commit d9af5fa

Browse files
KA account linking 1.0
1 parent 947fa08 commit d9af5fa

File tree

3 files changed

+5825
-15
lines changed

3 files changed

+5825
-15
lines changed

bot.js

+231-14
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,126 @@
44

55
/* MODULES */
66

7+
//jshint esversion:6
8+
79
// 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();
1129

12-
var prefix = "./";
30+
const prefix = "./";
1331

1432
var runOnMessage = [];
1533

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) {
17127
if (codePoint - 127462 >= numReacts) {
18128
return;
19129
} else {
@@ -36,11 +146,11 @@ var commands = {
36146
message.channel.send("Your \"username\" contains an invalid character.");
37147
return;
38148
}
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) {
41151
if (response) {
42152
if (!error && response.statusCode === 200) {
43-
data = JSON.parse(body);
153+
var data = JSON.parse(body);
44154
if (data) {
45155
message.channel.send("This username is available! :confetti_ball:");
46156
} else {
@@ -57,12 +167,121 @@ var commands = {
57167
}
58168
},
59169

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+
60279
bitcoin(message) {
61280
message.channel.startTyping();
62281
request("https://blockchain.info/tobtc?currency=USD&value=1", function (error, response, body) {
63282
if (response) {
64283
if (!error && response.statusCode === 200) {
65-
data = JSON.parse(body);
284+
var data = JSON.parse(body);
66285
message.channel.send(`1 Bitcoin is currently worth $${(1/data).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",")}.`);
67286
} else {
68287
if (response) {
@@ -71,7 +290,7 @@ var commands = {
71290
}
72291
message.channel.stopTyping();
73292
}
74-
})
293+
});
75294
},
76295

77296
help(message) {
@@ -170,7 +389,6 @@ var commands = {
170389
}
171390
}catch(e) {
172391
message.channel.send("```" + e + "```");
173-
console.log(code);
174392
}
175393
},
176394
}
@@ -232,6 +450,7 @@ Client.on('message', (message) => {
232450
})
233451

234452
Client.on('ready', () => {
453+
console.log("Connected to Discord");
235454
// Client.user.setGame(prefix + 'help')
236455
// Client.user.setAvatar('./images/image.png')
237456
})
@@ -246,12 +465,10 @@ Client.on('messageUpdate', (oldMess, newMess) => {
246465

247466
function exitHandler(options, err) {
248467
Client.destroy();
249-
console.log("");
468+
con.end();
250469
process.exit();
251470
}
252471

253472
process.on('SIGINT', exitHandler);
254473
process.on('exit', exitHandler);
255474
process.on('uncaughtException', exitHandler);
256-
257-
Client.login(require('./token.json')).catch(console.error)

0 commit comments

Comments
 (0)