1 /*
2 * Copyright 2015 Artem Savkov <artem.savkov@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include <config.h>
18 #include <http_client.h>
19 #include <json.h>
20 #include <json_util.h>
21
22 #include "discord.h"
23 #include "discord-http.h"
24 #include "discord-handlers.h"
25 #include "discord-websockets.h"
26 #include "discord-util.h"
27
28 typedef struct _casm_data {
29 struct im_connection *ic;
30 char *msg;
31 } casm_data;
32
33 typedef struct _mstr_data {
34 struct im_connection *ic;
35 char *sid;
36 } mstr_data;
37
38 typedef struct _retry_req {
39 char *request;
40 struct im_connection *ic;
41 http_input_function func;
42 gpointer data;
43 gint evid;
44 } retry_req;
45
_discord_http_get(struct im_connection * ic,char * request,http_input_function cb_func,gpointer data)46 static void _discord_http_get(struct im_connection *ic, char *request,
47 http_input_function cb_func, gpointer data)
48 {
49 discord_data *dd = ic->proto_data;
50 struct http_request *req;
51
52 req = http_dorequest(set_getstr(&ic->acc->set, "host"), 443, 1,
53 request, cb_func, data);
54
55 dd->pending_reqs = g_slist_prepend(dd->pending_reqs, req);
56 }
57
discord_http_get(struct im_connection * ic,const char * api_path,http_input_function cb_func,gpointer data)58 static void discord_http_get(struct im_connection *ic, const char *api_path,
59 http_input_function cb_func, gpointer data)
60 {
61 discord_data *dd = ic->proto_data;
62 GString *request = g_string_new("");
63 g_string_printf(request, "GET /api/%s HTTP/1.1\r\n"
64 "Host: %s\r\n"
65 "User-Agent: Bitlbee-Discord\r\n"
66 "Content-Type: application/json\r\n"
67 "authorization: %s\r\n\r\n",
68 api_path,
69 set_getstr(&ic->acc->set, "host"),
70 dd->token);
71
72 discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
73 _discord_http_get(ic, request->str, cb_func, data);
74 g_string_free(request, TRUE);
75 }
76
discord_http_retry(retry_req * rreq,gint fd,b_input_condition cond)77 static gboolean discord_http_retry(retry_req *rreq, gint fd,
78 b_input_condition cond)
79 {
80 struct im_connection *ic = rreq->ic;
81 discord_data *dd = ic->proto_data;
82
83 _discord_http_get(ic, rreq->request, rreq->func, rreq->data);
84
85 dd->pending_events = g_slist_remove(dd->pending_events,
86 GINT_TO_POINTER(rreq->evid));
87 g_free(rreq->request);
88 g_free(rreq);
89
90 return FALSE;
91 }
92
discord_http_check_retry(struct http_request * req)93 static int discord_http_check_retry(struct http_request *req)
94 {
95 struct im_connection *ic = req->data;
96 discord_data *dd = ic->proto_data;
97
98 if (req->status_code == 429) {
99 json_value *js = json_parse(req->reply_body, req->body_size);
100 if (!js || js->type != json_object) {
101 imcb_error(ic, "Error while parsing ratelimit message");
102 json_value_free(js);
103 return 0;
104 }
105
106 json_value *retry = json_o_get(js, "retry_after");
107 guint32 timeout = (retry && retry->type == json_integer) ? retry->u.integer : 0;
108 retry_req *rreq = g_new0(retry_req, 1);
109 rreq->request = g_strdup(req->request);
110 rreq->ic = ic;
111 rreq->func = req->func;
112 rreq->data = req->data;
113
114 gint evid = b_timeout_add(timeout, (b_event_handler)discord_http_retry,
115 rreq);
116 rreq->evid = evid;
117
118 dd->pending_events = g_slist_prepend(dd->pending_events,
119 GINT_TO_POINTER(evid));
120 discord_debug("(%s) %s [%d] retry scheduled in %u", dd->uname, __func__,
121 evid, timeout);
122
123 json_value_free(js);
124 return 1;
125 }
126 return 0;
127 }
128
discord_http_gateway_cb(struct http_request * req)129 static void discord_http_gateway_cb(struct http_request *req)
130 {
131 struct im_connection *ic = req->data;
132 discord_data *dd = ic->proto_data;
133
134 discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
135 req->status_code, req->body_size, req->reply_body);
136
137 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
138
139 if (req->status_code == 200) {
140 json_value *js = json_parse(req->reply_body, req->body_size);
141 if (!js || js->type != json_object) {
142 imcb_error(ic, "Failed to parse json reply (%s)", __func__);
143 imc_logout(ic, TRUE);
144 json_value_free(js);
145 return;
146 }
147 discord_data *dd = ic->proto_data;
148
149 const char *gw = json_o_str(js, "url");
150 GMatchInfo *match = NULL;
151 GRegex *gwregex = g_regex_new("^(wss?://)?([^/]+)(/.*)?$", 0, 0, NULL);
152
153 g_regex_match(gwregex, gw, 0, &match);
154
155 if (match == NULL) {
156 imcb_error(ic, "Failed to get gateway (%s).", gw);
157 json_value_free(js);
158 g_regex_unref(gwregex);
159 imc_logout(ic, TRUE);
160 return;
161 }
162
163 dd->gateway = g_new0(gw_data, 1);
164
165 gchar *wss = g_match_info_fetch(match, 1);
166 if (g_strcmp0(wss, "wss://") == 0) {
167 dd->gateway->wss = 1;
168 } else {
169 dd->gateway->wss = 0;
170 }
171 g_free(wss);
172
173 dd->gateway->addr = g_match_info_fetch(match, 2);
174 dd->gateway->path = g_match_info_fetch(match, 3);
175
176 if (dd->gateway->path == NULL) {
177 dd->gateway->path = g_strdup("/?encoding=json&v=6");
178 } else if (g_strcmp0(dd->gateway->path, "") == 0) {
179 g_free(dd->gateway->path);
180 dd->gateway->path = g_strdup("/?encoding=json&v=6");
181 }
182
183 g_match_info_free(match);
184 g_regex_unref(gwregex);
185
186 if (discord_ws_init(ic, dd) < 0) {
187 imcb_error(ic, "Failed to create websockets context.");
188 imc_logout(ic, TRUE);
189 json_value_free(js);
190 return;
191 }
192 dd->state = WS_CONNECTING;
193
194 json_value_free(js);
195 } else {
196 if (discord_http_check_retry(req) == 0) {
197 imcb_error(ic, "Failed to get info about self.");
198 imc_logout(ic, TRUE);
199 }
200 }
201 }
202
discord_http_get_gateway(struct im_connection * ic,const char * token)203 void discord_http_get_gateway(struct im_connection *ic, const char *token)
204 {
205 discord_data *dd = ic->proto_data;
206
207 dd->token = g_strdup(token);
208 set_setstr(&ic->acc->set, "token_cache", dd->token);
209 discord_http_get(ic, "gateway", discord_http_gateway_cb, ic);
210 }
211
discord_http_mfa_cb(struct http_request * req)212 static void discord_http_mfa_cb(struct http_request *req)
213 {
214 struct im_connection *ic = req->data;
215 discord_data *dd = ic->proto_data;
216
217 discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
218 req->status_code, req->body_size, req->reply_body);
219 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
220
221 json_value *js = json_parse(req->reply_body, req->body_size);
222 if (!js || js->type != json_object) {
223 imcb_error(ic, "Failed to parse json reply (%s)", __func__);
224 imc_logout(ic, TRUE);
225 json_value_free(js);
226 return;
227 }
228
229 imcb_remove_buddy(ic, DISCORD_MFA_HANDLE, NULL);
230 if (req->status_code == 200) {
231 discord_data *dd = ic->proto_data;
232
233 g_free(dd->token);
234 discord_http_get_gateway(ic, json_o_str(js, "token"));
235 } else {
236 if (discord_http_check_retry(req) == 0) {
237 imcb_error(ic, "MFA Error: %s", (char*)json_o_str(js, "message"));
238 imc_logout(ic, TRUE);
239 }
240 }
241 json_value_free(js);
242 }
243
discord_http_login_cb(struct http_request * req)244 static void discord_http_login_cb(struct http_request *req)
245 {
246 struct im_connection *ic = req->data;
247 discord_data *dd = ic->proto_data;
248
249 discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
250 req->status_code, req->body_size, req->reply_body);
251 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
252
253 json_value *js = json_parse(req->reply_body, req->body_size);
254 if (!js || js->type != json_object) {
255 imcb_error(ic, "Failed to parse json reply (%s)", __func__);
256 imc_logout(ic, TRUE);
257 json_value_free(js);
258 return;
259 }
260
261 if (req->status_code == 200) {
262 discord_data *dd = ic->proto_data;
263 json_value *mfa = json_o_get(js, "mfa");
264
265 if (mfa != NULL && mfa->type == json_boolean && mfa->u.boolean == TRUE) {
266 dd->token = json_o_strdup(js, "ticket");
267 imcb_log(ic, "Starting MFA authentication");
268 imcb_add_buddy(ic, DISCORD_MFA_HANDLE, NULL);
269 imcb_buddy_msg(ic, DISCORD_MFA_HANDLE, "Two-factor auth is enabled. "
270 "Please respond to this message with your token.", 0, 0);
271 } else {
272 discord_http_get_gateway(ic, json_o_str(js, "token"));
273 }
274 } else {
275 if (discord_http_check_retry(req) == 0) {
276 char *errmsg = (char*)json_o_str(js, "message");
277
278 if (errmsg == NULL) {
279 json_value *em = NULL;
280 json_value *email = json_o_get(js, "email");
281 json_value *password = json_o_get(js, "password");
282 json_value *captcha_key = json_o_get(js, "captcha_key");
283
284 if (email != NULL && email->type == json_array) {
285 em = email->u.array.values[0];
286 } else if (password != NULL && password->type == json_array) {
287 em = password->u.array.values[0];
288 } else if (captcha_key != NULL && captcha_key->type == json_array) {
289 em = captcha_key->u.array.values[0];
290 }
291
292 if (em != NULL && em->type == json_string) {
293 errmsg = em->u.string.ptr;
294 }
295 }
296
297 imcb_error(ic, "Login error: %s", errmsg);
298 imc_logout(ic, TRUE);
299 }
300 }
301 json_value_free(js);
302 }
303
discord_http_noop_cb(struct http_request * req)304 static void discord_http_noop_cb(struct http_request *req)
305 {
306 discord_data *dd = req->data;
307 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
308 return;
309 }
310
discord_http_send_msg_cb(struct http_request * req)311 static void discord_http_send_msg_cb(struct http_request *req)
312 {
313 struct im_connection *ic = req->data;
314 discord_data *dd = ic->proto_data;
315
316 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
317
318 discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
319 req->status_code, req->body_size, req->reply_body);
320
321 if (req->status_code != 200) {
322 if (discord_http_check_retry(req) == 0) {
323 char *json_text = strstr(req->request, "{\"content\":\"");
324 json_value *js = json_parse(json_text, strlen(json_text));
325 const char *message = json_o_str(js, "content");
326
327 imcb_error(ic, "Failed to send message (%d; %s).", req->status_code, message);
328 json_value_free(js);
329 }
330 }
331 }
332
discord_http_backlog_cb(struct http_request * req)333 static void discord_http_backlog_cb(struct http_request *req)
334 {
335 struct im_connection *ic = req->data;
336 discord_data *dd = ic->proto_data;
337
338 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
339
340 discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
341 req->status_code, req->body_size, req->reply_body);
342
343 if (req->status_code != 200) {
344 if (discord_http_check_retry(req) == 0) {
345 imcb_error(ic, "Failed to get backlog (%d).", req->status_code);
346 }
347 } else {
348 json_value *messages = json_parse(req->reply_body, req->body_size);
349 if (!messages || messages->type != json_array) {
350 imcb_error(ic, "Failed to parse json reply (%s)", __func__);
351 imc_logout(ic, TRUE);
352 json_value_free(messages);
353 return;
354 }
355
356 for (int midx = messages->u.array.length - 1; midx >= 0; midx--) {
357 json_value *minfo = messages->u.array.values[midx];
358 discord_handle_message(ic, minfo, ACTION_CREATE, TRUE);
359 }
360
361 json_value_free(messages);
362 }
363 }
364
discord_http_get_backlog(struct im_connection * ic,const char * channel_id)365 void discord_http_get_backlog(struct im_connection *ic, const char *channel_id)
366 {
367 GString *api = g_string_new("");
368
369 g_string_printf(api, "channels/%s/messages?limit=%d", channel_id,
370 set_getint(&ic->acc->set, "max_backlog"));
371
372 discord_http_get(ic, api->str, discord_http_backlog_cb, ic);
373
374 g_string_free(api, TRUE);
375 }
376
discord_http_pinned_cb(struct http_request * req)377 static void discord_http_pinned_cb(struct http_request *req)
378 {
379 struct im_connection *ic = req->data;
380 discord_data *dd = ic->proto_data;
381
382 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
383
384 discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
385 req->status_code, req->body_size, req->reply_body);
386
387 if (req->status_code != 200) {
388 if (discord_http_check_retry(req) == 0) {
389 imcb_error(ic, "Failed to get pinned messages (%d).", req->status_code);
390 }
391 } else {
392 json_value *messages = json_parse(req->reply_body, req->body_size);
393 if (!messages || messages->type != json_array) {
394 imcb_error(ic, "Failed to parse json reply (%s)", __func__);
395 imc_logout(ic, TRUE);
396 json_value_free(messages);
397 return;
398 }
399
400 for (int midx = messages->u.array.length - 1; midx >= 0; midx--) {
401 json_value *minfo = messages->u.array.values[midx];
402 discord_handle_message(ic, minfo, ACTION_CREATE, TRUE);
403 }
404
405 json_value_free(messages);
406 }
407 }
408
discord_http_get_pinned(struct im_connection * ic,const char * channel_id)409 void discord_http_get_pinned(struct im_connection *ic, const char *channel_id)
410 {
411 GString *api = g_string_new("");
412
413 g_string_printf(api, "channels/%s/pins", channel_id);
414
415 discord_http_get(ic, api->str, discord_http_pinned_cb, ic);
416
417 g_string_free(api, TRUE);
418 }
419
discord_mentions_string(const GMatchInfo * match,GString * result,gpointer user_data)420 static gboolean discord_mentions_string(const GMatchInfo *match,
421 GString *result,
422 gpointer user_data)
423 {
424 mstr_data *md = (mstr_data *)user_data;
425 struct im_connection *ic = md->ic;
426 discord_data *dd = ic->proto_data;
427 gchar *name = g_match_info_fetch(match, 1);
428
429 search_t stype = SEARCH_IRC_USER_NAME;
430 if (set_getbool(&ic->acc->set, "mention_ignorecase") == TRUE) {
431 stype = SEARCH_IRC_USER_NAME_IGNORECASE;
432 }
433
434 user_info *uinfo = get_user(dd, name, md->sid, stype);
435 g_free(name);
436
437 if (uinfo != NULL) {
438 gchar *id = g_strdup_printf("<@%s>", uinfo->id);
439 result = g_string_append(result, id);
440 g_free(id);
441 } else {
442 gchar *fmatch = g_match_info_fetch(match, 0);
443 result = g_string_append(result, fmatch);
444 g_free(fmatch);
445 }
446
447 return FALSE;
448 }
449
discord_channel_string(const GMatchInfo * match,GString * result,gpointer user_data)450 static gboolean discord_channel_string(const GMatchInfo *match,
451 GString *result,
452 gpointer user_data)
453 {
454 mstr_data *md = (mstr_data *)user_data;
455 struct im_connection *ic = md->ic;
456 discord_data *dd = ic->proto_data;
457
458 gchar *name = g_match_info_fetch(match, 1);
459
460 search_t stype = SEARCH_NAME;
461 if (set_getbool(&ic->acc->set, "mention_ignorecase") == TRUE) {
462 stype = SEARCH_NAME_IGNORECASE;
463 }
464
465 channel_info *cinfo = get_channel(dd, name, md->sid, stype);
466 g_free(name);
467
468 if (cinfo != NULL) {
469 gchar *id = g_strdup_printf("<#%s>", cinfo->id);
470 result = g_string_append(result, id);
471 g_free(id);
472 } else {
473 gchar *fmatch = g_match_info_fetch(match, 0);
474 result = g_string_append(result, fmatch);
475 g_free(fmatch);
476 }
477
478 return FALSE;
479 }
480
discord_http_send_msg(struct im_connection * ic,const char * id,const char * msg)481 void discord_http_send_msg(struct im_connection *ic, const char *id,
482 const char *msg)
483 {
484 discord_data *dd = ic->proto_data;
485 GString *request = g_string_new("");
486 GString *content = g_string_new("");
487 channel_info *cinfo = get_channel(dd, id, NULL, SEARCH_ID);
488 mstr_data *md = g_new0(mstr_data, 1);
489
490 md->ic = ic;
491 if (cinfo != NULL && cinfo->type == CHANNEL_TEXT) {
492 md->sid = cinfo->to.channel.sinfo->id;
493 }
494
495 gchar *nmsg = NULL;
496 gchar *emsg = discord_escape_string(msg);
497
498 if (strlen(set_getstr(&ic->acc->set,"mention_suffix")) > 0) {
499 gchar *hlrstr = g_strdup_printf("(\\S+)%s", set_getstr(&ic->acc->set,
500 "mention_suffix"));
501 GRegex *hlregex = g_regex_new(hlrstr, 0, 0, NULL);
502
503 g_free(hlrstr);
504 nmsg = g_regex_replace_eval(hlregex, emsg, -1, 0, 0,
505 discord_mentions_string, md, NULL);
506 g_free(emsg);
507 emsg = nmsg;
508 g_regex_unref(hlregex);
509 }
510
511 GRegex *hlregex = g_regex_new("@(\\S+)", 0, 0, NULL);
512
513 nmsg = g_regex_replace_eval(hlregex, emsg, -1, 0, 0,
514 discord_mentions_string, md, NULL);
515 g_free(emsg);
516 emsg = nmsg;
517 g_regex_unref(hlregex);
518
519 hlregex = g_regex_new("#(\\S+)", 0, 0, NULL);
520 nmsg = g_regex_replace_eval(hlregex, emsg, -1, 0, 0,
521 discord_channel_string, md, NULL);
522 g_free(emsg);
523 emsg = nmsg;
524 g_regex_unref(hlregex);
525 g_free(md);
526
527 if (g_str_has_prefix(emsg, "/me ")) {
528 nmsg = g_strdup_printf("_%s_", emsg + 4);
529 g_free(emsg);
530 emsg = nmsg;
531 }
532
533 gchar *nonce;
534 guchar nonce_bytes[16];
535
536 random_bytes(nonce_bytes, sizeof(nonce_bytes));
537 nonce = g_base64_encode(nonce_bytes, sizeof(nonce_bytes));
538 g_hash_table_insert(dd->sent_message_ids, nonce,
539 GUINT_TO_POINTER((guint)time(NULL)));
540 g_string_printf(content, "{\"content\":\"%s\", \"nonce\":\"%s\"}",
541 emsg, nonce);
542 g_free(emsg);
543 g_string_printf(request, "POST /api/channels/%s/messages HTTP/1.1\r\n"
544 "Host: %s\r\n"
545 "User-Agent: Bitlbee-Discord\r\n"
546 "authorization: %s\r\n"
547 "Content-Type: application/json\r\n"
548 "Content-Length: %zd\r\n\r\n"
549 "%s",
550 id,
551 set_getstr(&ic->acc->set, "host"),
552 dd->token,
553 content->len,
554 content->str);
555
556 discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
557
558 _discord_http_get(ic, request->str, discord_http_send_msg_cb, ic);
559
560 g_string_free(content, TRUE);
561 g_string_free(request, TRUE);
562 }
563
discord_http_send_ack(struct im_connection * ic,const char * channel_id,const char * message_id)564 void discord_http_send_ack(struct im_connection *ic, const char *channel_id,
565 const char *message_id)
566 {
567 if (set_getbool(&ic->acc->set, "send_acks") == FALSE) {
568 return;
569 }
570
571 discord_data *dd = ic->proto_data;
572 GString *request = g_string_new("");
573
574 g_string_printf(request, "POST /api/channels/%s/messages/%s/ack HTTP/1.1\r\n"
575 "Host: %s\r\n"
576 "User-Agent: Bitlbee-Discord\r\n"
577 "Content-Type: application/json\r\n"
578 "Authorization: %s\r\n"
579 "Content-Length: 2\r\n\r\n"
580 "{}",
581 channel_id, message_id,
582 set_getstr(&ic->acc->set, "host"),
583 dd->token);
584
585 discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
586
587 _discord_http_get(ic, request->str, discord_http_noop_cb, dd);
588
589 g_string_free(request, TRUE);
590 }
591
discord_http_mfa_auth(struct im_connection * ic,const char * msg)592 void discord_http_mfa_auth(struct im_connection *ic, const char *msg)
593 {
594 GString *request = g_string_new("");
595 GString *auth = g_string_new("");
596 discord_data *dd = ic->proto_data;
597
598 g_string_printf(auth, "{\"code\":\"%s\",\"ticket\":\"%s\"}",
599 msg,
600 dd->token);
601
602 g_string_printf(request, "POST /api/auth/mfa/totp HTTP/1.1\r\n"
603 "Host: %s\r\n"
604 "User-Agent: Bitlbee-Discord\r\n"
605 "Content-Type: application/json\r\n"
606 "Content-Length: %zd\r\n\r\n"
607 "%s",
608 set_getstr(&ic->acc->set, "host"),
609 auth->len,
610 auth->str);
611
612 discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
613
614 _discord_http_get(ic, request->str, discord_http_mfa_cb, ic);
615
616 g_string_free(auth, TRUE);
617 g_string_free(request, TRUE);
618 }
619
discord_http_login(account_t * acc)620 void discord_http_login(account_t *acc)
621 {
622 GString *request = g_string_new("");
623 GString *jlogin = g_string_new("");
624 gchar *epass = discord_escape_string(acc->pass);
625 discord_data *dd = acc->ic->proto_data;
626
627 g_string_printf(jlogin, "{\"email\":\"%s\",\"password\":\"%s\"}",
628 acc->user,
629 epass);
630
631 g_string_printf(request, "POST /api/auth/login HTTP/1.1\r\n"
632 "Host: %s\r\n"
633 "User-Agent: Bitlbee-Discord\r\n"
634 "Content-Type: application/json\r\n"
635 "Content-Length: %zd\r\n\r\n"
636 "%s",
637 set_getstr(&acc->set, "host"),
638 jlogin->len,
639 jlogin->str);
640
641 discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
642
643 _discord_http_get(acc->ic, request->str, discord_http_login_cb, acc->ic);
644
645 g_free(epass);
646 g_string_free(jlogin, TRUE);
647 g_string_free(request, TRUE);
648 }
649
discord_http_casm_cb(struct http_request * req)650 static void discord_http_casm_cb(struct http_request *req)
651 {
652 casm_data *cd = req->data;
653 struct im_connection *ic = cd->ic;
654 discord_data *dd = ic->proto_data;
655 dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
656 if (req->status_code != 200) {
657 if (discord_http_check_retry(req) == 0) {
658 imcb_error(ic, "Failed to create private channel (%d).",
659 req->status_code);
660 }
661 goto out;
662 }
663
664 json_value *channel = json_parse(req->reply_body, req->body_size);
665 if (!channel || channel->type != json_object) {
666 imcb_error(ic, "Failed to create private channel.");
667 goto jout;
668 }
669
670 discord_handle_channel(ic, channel, NULL, ACTION_CREATE);
671 discord_http_send_msg(ic, json_o_str(channel, "id"), cd->msg);
672
673 jout:
674 json_value_free(channel);
675
676 out:
677 g_free(cd->msg);
678 g_free(cd);
679 }
680
discord_http_create_and_send_msg(struct im_connection * ic,const char * handle,const char * msg)681 void discord_http_create_and_send_msg(struct im_connection *ic,
682 const char *handle, const char *msg)
683 {
684 discord_data *dd = ic->proto_data;
685 user_info *uinfo = get_user(dd, handle, NULL, SEARCH_IRC_USER_NAME);
686
687 if (uinfo == NULL) {
688 imcb_error(ic, "Failed to create channel for unknown user: '%s'.",
689 handle);
690 return;
691 }
692
693 GString *request = g_string_new("");
694 GString *content = g_string_new("");
695
696 g_string_printf(content, "{\"recipient_id\":\"%s\"}", uinfo->id);
697 g_string_printf(request, "POST /api/users/%s/channels HTTP/1.1\r\n"
698 "Host: %s\r\n"
699 "User-Agent: Bitlbee-Discord\r\n"
700 "authorization: %s\r\n"
701 "Content-Type: application/json\r\n"
702 "Content-Length: %zd\r\n\r\n"
703 "%s",
704 dd->id,
705 set_getstr(&ic->acc->set, "host"),
706 dd->token,
707 content->len,
708 content->str);
709
710 casm_data *cd = g_new0(casm_data, 1);
711 cd->ic = ic;
712 cd->msg = g_strdup(msg);
713
714 discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
715
716 _discord_http_get(ic, request->str, discord_http_casm_cb, cd);
717
718 g_string_free(content, TRUE);
719 g_string_free(request, TRUE);
720 }
721