1 /*
2 * SkypeWeb Plugin for libpurple/Pidgin
3 * Copyright (c) 2014-2020 Eion Robb
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "skypeweb_contacts.h"
21 #include "skypeweb_connection.h"
22 #include "skypeweb_messages.h"
23 #include "skypeweb_util.h"
24
25 #include "http.h"
26 #include "xfer.h"
27 #include "image-store.h"
28
purple_conversation_write_system_message_ts(PurpleConversation * conv,const gchar * msg,PurpleMessageFlags flags,time_t ts)29 static void purple_conversation_write_system_message_ts(
30 PurpleConversation *conv, const gchar *msg, PurpleMessageFlags flags,
31 time_t ts) {
32 PurpleMessage *pmsg = purple_message_new_system(msg, flags);
33 purple_message_set_time(pmsg, ts);
34 purple_conversation_write_message(conv, pmsg);
35 purple_message_destroy(pmsg);
36 }
purple_conversation_write_img_message(PurpleConversation * conv,const char * who,const gchar * msg,PurpleMessageFlags flags,time_t ts)37 static void purple_conversation_write_img_message(
38 PurpleConversation *conv, const char* who, const gchar *msg,
39 PurpleMessageFlags flags, time_t ts) {
40 PurpleMessage *pmsg;
41
42 if (flags & PURPLE_MESSAGE_SEND) {
43 pmsg = purple_message_new_outgoing(who, msg, flags);
44 purple_message_set_time(pmsg, ts);
45 } else {
46 pmsg = purple_message_new_incoming(who, msg, flags, ts);
47 }
48
49 purple_conversation_write_message(conv, pmsg);
50 purple_message_destroy(pmsg);
51 }
52
53 // Check that the conversation hasn't been closed
54 static gboolean
purple_conversation_is_valid(PurpleConversation * conv)55 purple_conversation_is_valid(PurpleConversation *conv)
56 {
57 GList *convs = purple_conversations_get_all();
58
59 return (g_list_find(convs, conv) != NULL);
60 }
61
62
63 typedef struct {
64 PurpleXfer *xfer;
65 JsonObject *info;
66 gchar *from;
67 gchar *url;
68 gchar *id;
69 SkypeWebAccount *sa;
70 } SkypeWebFileTransfer;
71
72 static guint active_icon_downloads = 0;
73
74 static void
skypeweb_get_icon_cb(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)75 skypeweb_get_icon_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
76 {
77 PurpleHttpRequest *request = purple_http_conn_get_request(http_conn);
78 PurpleBuddy *buddy = user_data;
79 const gchar *url = purple_http_request_get_url(request);
80 const gchar *data;
81 gsize len;
82
83 active_icon_downloads--;
84
85 if (!buddy || !purple_http_response_is_successful(response)) {
86 return;
87 }
88
89 data = purple_http_response_get_data(response, &len);
90
91 if (!len || !*data) {
92 return;
93 }
94
95 purple_buddy_icons_set_for_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy), g_memdup(data, len), len, url);
96
97 }
98
99 static void
skypeweb_get_icon_now(PurpleBuddy * buddy)100 skypeweb_get_icon_now(PurpleBuddy *buddy)
101 {
102 SkypeWebBuddy *sbuddy;
103 SkypeWebAccount *sa;
104 gchar *url;
105
106 purple_debug_info("skypeweb", "getting new buddy icon for %s\n", purple_buddy_get_name(buddy));
107
108 sbuddy = purple_buddy_get_protocol_data(buddy);
109
110 if (!sbuddy || !sbuddy->sa || !sbuddy->sa->pc)
111 return;
112
113 if (sbuddy->avatar_url && sbuddy->avatar_url[0]) {
114 url = g_strdup(sbuddy->avatar_url);
115 } else {
116 url = g_strdup_printf("https://avatar.skype.com/v1/avatars/%s/public?returnDefaultImage=false", purple_url_encode(purple_buddy_get_name(buddy)));
117 }
118
119 sa = sbuddy->sa;
120
121 purple_http_get(sa->pc, skypeweb_get_icon_cb, buddy, url);
122 g_free(url);
123
124 active_icon_downloads++;
125 }
126
127 static gboolean
skypeweb_get_icon_queuepop(gpointer data)128 skypeweb_get_icon_queuepop(gpointer data)
129 {
130 PurpleBuddy *buddy = data;
131
132 // Only allow 4 simultaneous downloads
133 if (active_icon_downloads > 4)
134 return TRUE;
135
136 skypeweb_get_icon_now(buddy);
137 return FALSE;
138 }
139
140 void
skypeweb_get_icon(PurpleBuddy * buddy)141 skypeweb_get_icon(PurpleBuddy *buddy)
142 {
143 if (!buddy) return;
144
145 g_timeout_add(100, skypeweb_get_icon_queuepop, (gpointer)buddy);
146 }
147
148 typedef struct SkypeImgMsgContext_ {
149 PurpleConversation *conv;
150 time_t composetimestamp;
151 gchar* from;
152 } SkypeImgMsgContext;
153
154 static void
skypeweb_got_imagemessage(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)155 skypeweb_got_imagemessage(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
156 {
157 gint icon_id;
158 gchar *msg_tmp;
159 const gchar *url_text;
160 gsize len;
161 PurpleImage *image;
162
163 SkypeImgMsgContext *ctx = user_data;
164 PurpleConversation *conv = ctx->conv;
165 time_t ts = ctx->composetimestamp;
166 gchar* ctx_from = ctx->from;
167 ctx->from = NULL;
168 g_free(ctx);
169
170 // Conversation could have been closed before we retrieved the image
171 if (!purple_conversation_is_valid(conv)) {
172 return;
173 }
174
175 url_text = purple_http_response_get_data(response, &len);
176
177 if (!url_text || !len || url_text[0] == '{' || url_text[0] == '<')
178 return;
179
180 if (!purple_http_response_is_successful(response))
181 return;
182
183 image = purple_image_new_from_data(g_memdup(url_text, len), len);
184 icon_id = purple_image_store_add(image);
185 msg_tmp = g_strdup_printf("<img id='%d'>", icon_id);
186 purple_conversation_write_img_message(conv, ctx_from, msg_tmp, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_IMAGES, ts);
187 g_free(msg_tmp);
188 g_free(ctx_from);
189 }
190
191 void
skypeweb_download_uri_to_conv(SkypeWebAccount * sa,const gchar * uri,PurpleConversation * conv,time_t ts,const gchar * from)192 skypeweb_download_uri_to_conv(SkypeWebAccount *sa, const gchar *uri, PurpleConversation *conv, time_t ts, const gchar* from)
193 {
194 gchar *url, *text;
195 PurpleHttpRequest *request;
196
197 if (purple_strequal(purple_core_get_ui(), "BitlBee")) {
198 // Bitlbee doesn't support images, so just plop a url to the image instead
199
200 url = purple_strreplace(uri, "imgt1", "imgpsh_fullsize");
201 purple_conversation_write_system_message_ts(conv, url, PURPLE_MESSAGE_SYSTEM, ts);
202 g_free(url);
203
204 return;
205 }
206
207 request = purple_http_request_new(uri);
208 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
209 purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
210 purple_http_request_header_set(request, "Accept", "image/*");
211 SkypeImgMsgContext *ctx = g_new(SkypeImgMsgContext, 1);
212 ctx->composetimestamp = ts;
213 ctx->conv = conv;
214 ctx->from = g_strdup(from);
215 purple_http_request(sa->pc, request, skypeweb_got_imagemessage, ctx);
216 purple_http_request_unref(request);
217
218 url = purple_strreplace(uri, "imgt1", "imgpsh_fullsize");
219 text = g_strdup_printf("<a href=\"%s\">Click here to view full version</a>", url);
220 purple_conversation_write_img_message(conv, from, text, 0, ts);
221
222 g_free(url);
223 g_free(text);
224 }
225
226 void
skypeweb_download_moji_to_conv(SkypeWebAccount * sa,const gchar * text,const gchar * url_thumbnail,PurpleConversation * conv,time_t ts,const gchar * from)227 skypeweb_download_moji_to_conv(SkypeWebAccount *sa, const gchar *text, const gchar *url_thumbnail, PurpleConversation *conv, time_t ts, const gchar* from)
228 {
229 gchar *cdn_url_thumbnail;
230 PurpleHttpURL *httpurl;
231 PurpleHttpRequest *request;
232
233 httpurl = purple_http_url_parse(url_thumbnail);
234
235 cdn_url_thumbnail = g_strdup_printf("https://%s/%s", SKYPEWEB_STATIC_CDN_HOST, purple_http_url_get_path(httpurl));
236
237 request = purple_http_request_new(cdn_url_thumbnail);
238 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
239 purple_http_request_header_set_printf(request, "Cookie", "vdms-skype-token=%s", sa->vdms_token);
240 purple_http_request_header_set(request, "Accept", "image/*");
241 SkypeImgMsgContext *ctx = g_new(SkypeImgMsgContext, 1);
242 ctx->composetimestamp = ts;
243 ctx->conv = conv;
244 ctx->from = g_strdup(from);
245 purple_http_request(sa->pc, request, skypeweb_got_imagemessage, ctx);
246 purple_http_request_unref(request);
247
248 purple_conversation_write_img_message(conv, from, text, 0, ts);
249
250 g_free(cdn_url_thumbnail);
251 purple_http_url_free(httpurl);
252 }
253
254 static void
skypeweb_got_vm_file(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)255 skypeweb_got_vm_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
256 {
257 PurpleXfer *xfer = user_data;
258 const gchar *data;
259 gsize len;
260
261 data = purple_http_response_get_data(response, &len);
262 purple_xfer_write(xfer, (guchar *)data, len);
263 }
264
265 static void
skypeweb_init_vm_download(PurpleXfer * xfer)266 skypeweb_init_vm_download(PurpleXfer *xfer)
267 {
268 SkypeWebAccount *sa;
269 JsonObject *file = purple_xfer_get_protocol_data(xfer);
270 gint64 fileSize;
271 const gchar *url;
272 PurpleHttpRequest *request;
273
274 fileSize = json_object_get_int_member(file, "fileSize");
275 url = json_object_get_string_member(file, "url");
276
277 purple_xfer_set_completed(xfer, FALSE);
278 sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_xfer_get_account(xfer)));
279
280 request = purple_http_request_new(url);
281 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
282 purple_http_request_set_max_len(request, fileSize);
283 purple_http_request(sa->pc, request, skypeweb_got_vm_file, xfer);
284 purple_http_request_unref(request);
285
286 json_object_unref(file);
287 }
288
289 static void
skypeweb_cancel_vm_download(PurpleXfer * xfer)290 skypeweb_cancel_vm_download(PurpleXfer *xfer)
291 {
292 JsonObject *file = purple_xfer_get_protocol_data(xfer);
293 json_object_unref(file);
294 }
295
296 static void
skypeweb_got_vm_download_info(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)297 skypeweb_got_vm_download_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
298 {
299 PurpleConversation *conv = user_data;
300 PurpleXfer *xfer;
301 JsonObject *obj, *file;
302 JsonArray *files;
303 gint64 fileSize;
304 const gchar *url, *assetId, *status;
305 gchar *filename;
306
307 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
308 return;
309 obj = json_node_get_object(node);
310
311 files = json_object_get_array_member(obj, "files");
312 file = json_array_get_object_element(files, 0);
313 if (file != NULL) {
314 status = json_object_get_string_member(file, "status");
315 if (status && g_str_equal(status, "ok")) {
316 assetId = json_object_get_string_member(obj, "assetId");
317 fileSize = json_object_get_int_member(file, "fileSize");
318 url = json_object_get_string_member(file, "url");
319 (void) url;
320
321 filename = g_strconcat(assetId, ".mp4", NULL);
322
323 xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_RECEIVE, purple_conversation_get_name(conv));
324 purple_xfer_set_size(xfer, fileSize);
325 purple_xfer_set_filename(xfer, filename);
326 json_object_ref(file);
327 purple_xfer_set_protocol_data(xfer, file);
328 purple_xfer_set_init_fnc(xfer, skypeweb_init_vm_download);
329 purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_cancel_vm_download);
330 purple_xfer_add(xfer);
331
332 g_free(filename);
333 } else if (status && g_str_equal(status, "running")) {
334 //skypeweb_download_video_message(sa, sid??????, conv);
335 }
336 }
337 }
338
339 static void
skypeweb_got_vm_info(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)340 skypeweb_got_vm_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
341 {
342 PurpleConversation *conv = user_data;
343 JsonObject *obj, *response, *media_stream;
344 const gchar *filename;
345
346 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
347 return;
348 obj = json_node_get_object(node);
349
350 response = json_object_get_object_member(obj, "response");
351 media_stream = json_object_get_object_member(response, "media_stream");
352 filename = json_object_get_string_member(media_stream, "filename");
353
354 if (filename != NULL) {
355 // Need to keep retrying this url until it comes back with status:ok
356 gchar *url = g_strdup_printf("/vod/api-create?assetId=%s&profile=mp4-vm", purple_url_encode(filename));
357 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, "media.vm.skype.com", url, NULL, skypeweb_got_vm_download_info, conv, TRUE);
358 g_free(url);
359 }
360
361 }
362
363 void
skypeweb_download_video_message(SkypeWebAccount * sa,const gchar * sid,PurpleConversation * conv)364 skypeweb_download_video_message(SkypeWebAccount *sa, const gchar *sid, PurpleConversation *conv)
365 {
366 gchar *url, *username_encoded;
367
368 username_encoded = g_strdup(purple_url_encode(sa->username));
369 url = g_strdup_printf("/users/%s/video_mails/%s", username_encoded, purple_url_encode(sid));
370
371 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_VIDEOMAIL_HOST, url, NULL, skypeweb_got_vm_info, conv, TRUE);
372
373 g_free(url);
374 g_free(username_encoded);
375
376 }
377
378
379 static void
skypeweb_free_xfer(PurpleXfer * xfer)380 skypeweb_free_xfer(PurpleXfer *xfer)
381 {
382 SkypeWebFileTransfer *swft;
383
384 swft = purple_xfer_get_protocol_data(xfer);
385 g_return_if_fail(swft != NULL);
386
387 if (swft->info != NULL)
388 json_object_unref(swft->info);
389 g_free(swft->url);
390 g_free(swft->id);
391 g_free(swft->from);
392 g_free(swft);
393
394 purple_xfer_set_protocol_data(xfer, NULL);
395 }
396
397 static void
skypeweb_got_file(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)398 skypeweb_got_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
399 {
400 SkypeWebFileTransfer *swft = user_data;
401 PurpleXfer *xfer = swft->xfer;
402 SkypeWebAccount *sa = swft->sa;
403 const gchar *data;
404 gsize len;
405
406
407 if (!purple_http_response_is_successful(response)) {
408 purple_xfer_error(purple_xfer_get_xfer_type(xfer), sa->account, swft->from, purple_http_response_get_error(response));
409 purple_xfer_cancel_local(xfer);
410 } else {
411 data = purple_http_response_get_data(response, &len);
412 purple_xfer_write_file(xfer, (guchar *)data, len);
413 purple_xfer_set_completed(xfer, TRUE);
414 }
415
416 //cleanup
417 skypeweb_free_xfer(xfer);
418 purple_xfer_end(xfer);
419 }
420
421 static void
skypeweb_init_file_download(PurpleXfer * xfer)422 skypeweb_init_file_download(PurpleXfer *xfer)
423 {
424 SkypeWebAccount *sa;
425 SkypeWebFileTransfer *swft;
426 const gchar *view_location;
427 gint64 content_full_length;
428 PurpleHttpRequest *request;
429
430 swft = purple_xfer_get_protocol_data(xfer);
431 sa = swft->sa;
432
433 view_location = json_object_get_string_member(swft->info, "view_location");
434 content_full_length = json_object_get_int_member(swft->info, "content_full_length");
435
436 purple_xfer_start(xfer, -1, NULL, 0);
437
438 request = purple_http_request_new(view_location);
439 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
440 purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
441 purple_http_request_header_set(request, "Accept", "*/*");
442 purple_http_request_set_max_len(request, content_full_length);
443 purple_http_request(sa->pc, request, skypeweb_got_file, swft);
444 purple_http_request_unref(request);
445 }
446
447 static void
skypeweb_got_file_info(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)448 skypeweb_got_file_info(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
449 {
450 JsonObject *obj;
451 PurpleXfer *xfer;
452 SkypeWebFileTransfer *swft = user_data;
453 SkypeWebAccount *sa = swft->sa;
454 JsonParser *parser;
455 JsonNode *node;
456 const gchar *data;
457 gsize len;
458
459 data = purple_http_response_get_data(response, &len);
460
461 parser = json_parser_new();
462 if (!json_parser_load_from_data(parser, data, len, NULL)) {
463 g_free(swft->url);
464 g_free(swft->from);
465 g_free(swft);
466 g_object_unref(parser);
467 return;
468 }
469
470 node = json_parser_get_root(parser);
471 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
472 g_free(swft->url);
473 g_free(swft->from);
474 g_free(swft);
475 g_object_unref(parser);
476 return;
477 }
478 obj = json_node_get_object(node);
479
480 /*
481 {
482 "content_length": 40708,
483 "content_full_length": 40708,
484 "view_length": 40708,
485 "content_state": "ready",
486 "view_state": "ready",
487 "view_location": "uri/views/original",
488 "status_location": "uri/views/original/status",
489 "scan": {
490 "status": "passed"
491 },
492 "original_filename": "filename"
493 } */
494 purple_debug_info("skypeweb", "File info: %s\n", data);
495
496 if (!json_object_has_member(obj, "content_state") || !purple_strequal(json_object_get_string_member(obj, "content_state"), "ready")) {
497 skypeweb_present_uri_as_filetransfer(sa, json_object_get_string_member(obj, "status_location"), swft->from);
498 g_free(swft->url);
499 g_free(swft->from);
500 g_free(swft);
501 g_object_unref(parser);
502 return;
503 }
504
505 json_object_ref(obj);
506 swft->info = obj;
507
508 xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_RECEIVE, swft->from);
509 purple_xfer_set_size(xfer, json_object_get_int_member(obj, "content_full_length"));
510 purple_xfer_set_filename(xfer, json_object_get_string_member(obj, "original_filename"));
511 purple_xfer_set_init_fnc(xfer, skypeweb_init_file_download);
512 purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_free_xfer);
513
514 swft->xfer = xfer;
515 purple_xfer_set_protocol_data(xfer, swft);
516
517 purple_xfer_request(xfer);
518
519 g_object_unref(parser);
520 }
521
522 void
skypeweb_present_uri_as_filetransfer(SkypeWebAccount * sa,const gchar * uri,const gchar * from)523 skypeweb_present_uri_as_filetransfer(SkypeWebAccount *sa, const gchar *uri, const gchar *from)
524 {
525 SkypeWebFileTransfer *swft;
526 PurpleHttpRequest *request;
527
528 swft = g_new0(SkypeWebFileTransfer, 1);
529 swft->sa = sa;
530 swft->url = g_strdup(uri);
531 swft->from = g_strdup(from);
532
533 request = purple_http_request_new(uri);
534 if (!g_str_has_suffix(uri, "/views/original/status")) {
535 purple_http_request_set_url_printf(request, "%s%s", uri, "/views/original/status");
536 }
537 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
538 purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
539 purple_http_request_header_set(request, "Accept", "*/*");
540 purple_http_request(sa->pc, request, skypeweb_got_file_info, swft);
541 purple_http_request_unref(request);
542 }
543
544 static void
got_file_send_progress(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)545 got_file_send_progress(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
546 {
547 SkypeWebFileTransfer *swft = user_data;
548 PurpleXfer *xfer = swft->xfer;
549 SkypeWebAccount *sa = swft->sa;
550 JsonParser *parser;
551 JsonNode *node;
552 JsonObject *obj;
553 const gchar *data;
554 gsize len;
555
556 data = purple_http_response_get_data(response, &len);
557
558 //{"content_length":0,"content_full_length":0,"view_length":0,"content_state":"no content","view_state":"none","view_location":"https://nus1-api.asm.skype.com/v1/objects/0-cus-d1-61121cfae8cf601944627a66afdb77ad/views/original","status_location":"https://nus1-api.asm.skype.com/v1/objects/0-cus-d1-61121cfae8cf601944627a66afdb77ad/views/original/status"}
559 parser = json_parser_new();
560 if (!json_parser_load_from_data(parser, data, len, NULL)) {
561 //probably bad
562 return;
563 }
564 node = json_parser_get_root(parser);
565 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
566 //probably bad
567 return;
568 }
569 obj = json_node_get_object(node);
570
571
572 if (json_object_has_member(obj, "status_location")) {
573 g_free(swft->url);
574 swft->url = g_strdup(json_object_get_string_member(obj, "status_location"));
575 }
576
577 if (json_object_has_member(obj, "content_state") && purple_strequal(json_object_get_string_member(obj, "content_state"), "ready")) {
578 PurpleXmlNode *uriobject = purple_xmlnode_new("URIObject");
579 PurpleXmlNode *title = purple_xmlnode_new_child(uriobject, "Title");
580 PurpleXmlNode *description = purple_xmlnode_new_child(uriobject, "Description");
581 PurpleXmlNode *anchor = purple_xmlnode_new_child(uriobject, "a");
582 PurpleXmlNode *originalname = purple_xmlnode_new_child(uriobject, "OriginalName");
583 PurpleXmlNode *filesize = purple_xmlnode_new_child(uriobject, "FileSize");
584 gchar *message, *temp;
585 //We finally did it!
586 // May the pesants rejoyce
587 purple_xfer_set_completed(xfer, TRUE);
588
589 // Don't forget to let the other end know about it
590
591 purple_xmlnode_set_attrib(uriobject, "type", "File.1");
592 temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), NULL);
593 purple_xmlnode_set_attrib(uriobject, "uri", temp);
594 g_free(temp);
595 temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/thumbnail", NULL);
596 purple_xmlnode_set_attrib(uriobject, "url_thumbnail", temp);
597 g_free(temp);
598 purple_xmlnode_insert_data(title, purple_xfer_get_filename(xfer), -1);
599 purple_xmlnode_insert_data(description, "Description: ", -1);
600 temp = g_strconcat("https://login.skype.com/login/sso?go=webclient.xmm&docid=", purple_url_encode(swft->id), NULL);
601 purple_xmlnode_set_attrib(anchor, "href", temp);
602 purple_xmlnode_insert_data(anchor, temp, -1);
603 g_free(temp);
604 purple_xmlnode_set_attrib(originalname, "v", purple_xfer_get_filename(xfer));
605 temp = g_strdup_printf("%" G_GSIZE_FORMAT, (gsize) purple_xfer_get_size(xfer));
606 purple_xmlnode_set_attrib(filesize, "v", temp);
607 g_free(temp);
608
609 temp = purple_xmlnode_to_str(uriobject, NULL);
610 message = purple_strreplace(temp, "'", "\"");
611 g_free(temp);
612 #if PURPLE_VERSION_CHECK(3, 0, 0)
613 PurpleMessage *msg = purple_message_new_outgoing(swft->from, message, PURPLE_MESSAGE_SEND);
614 skypeweb_send_im(sa->pc, msg);
615 purple_message_destroy(msg);
616 #else
617 skypeweb_send_im(sa->pc, swft->from, message, PURPLE_MESSAGE_SEND);
618 #endif
619 g_free(message);
620
621 skypeweb_free_xfer(xfer);
622 purple_xfer_unref(xfer);
623
624 purple_xmlnode_free(uriobject);
625 g_object_unref(parser);
626 return;
627 }
628
629
630 g_object_unref(parser);
631
632 // probably good
633 }
634
635 static gboolean
poll_file_send_progress(gpointer user_data)636 poll_file_send_progress(gpointer user_data)
637 {
638 SkypeWebFileTransfer *swft = user_data;
639 SkypeWebAccount *sa = swft->sa;
640 PurpleHttpRequest *request;
641
642 request = purple_http_request_new(swft->url);
643 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
644 purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
645 purple_http_request_header_set(request, "Accept", "*/*");
646 purple_http_request(sa->pc, request, got_file_send_progress, swft);
647 purple_http_request_unref(request);
648
649 return FALSE;
650 }
651
652 static void
skypeweb_xfer_send_contents_reader(PurpleHttpConnection * con,gchar * buf,size_t offset,size_t len,gpointer user_data,PurpleHttpContentReaderCb cb)653 skypeweb_xfer_send_contents_reader(PurpleHttpConnection *con, gchar *buf, size_t offset, size_t len, gpointer user_data, PurpleHttpContentReaderCb cb)
654 {
655 SkypeWebFileTransfer *swft = user_data;
656 PurpleXfer *xfer = swft->xfer;
657 gsize read;
658
659 purple_debug_info("skypeweb", "Asked %" G_GSIZE_FORMAT " bytes from offset %" G_GSIZE_FORMAT "\n", len, offset);
660 purple_xfer_set_bytes_sent(xfer, offset);
661 read = purple_xfer_read_file(xfer, (guchar *)buf, len);
662 purple_debug_info("skypeweb", "Read %" G_GSIZE_FORMAT " bytes\n", read);
663
664 cb(con, TRUE, read != len, read);
665 }
666
667 static void
skypeweb_xfer_send_done(PurpleHttpConnection * conn,PurpleHttpResponse * resp,gpointer user_data)668 skypeweb_xfer_send_done(PurpleHttpConnection *conn, PurpleHttpResponse *resp, gpointer user_data)
669 {
670 gsize len;
671 const gchar *data = purple_http_response_get_data(resp, &len);
672 const gchar *error = purple_http_response_get_error(resp);
673 int code = purple_http_response_get_code(resp);
674 purple_debug_info("skypeweb", "Finished [%d]: %s\n", code, error);
675 purple_debug_info("skypeweb", "Server message: %s\n", data);
676 g_timeout_add_seconds(1, poll_file_send_progress, user_data);
677 }
678
679 static void
skypeweb_xfer_send_watcher(PurpleHttpConnection * http_conn,gboolean state,int processed,int total,gpointer user_data)680 skypeweb_xfer_send_watcher(PurpleHttpConnection *http_conn, gboolean state, int processed, int total, gpointer user_data)
681 {
682 SkypeWebFileTransfer *swft = user_data;
683 PurpleXfer *xfer = swft->xfer;
684 if (!state) purple_xfer_update_progress(xfer);
685 }
686
687 static void
skypeweb_xfer_send_begin(gpointer user_data)688 skypeweb_xfer_send_begin(gpointer user_data)
689 {
690 SkypeWebFileTransfer *swft = user_data;
691 PurpleXfer *xfer = swft->xfer;
692 SkypeWebAccount *sa = swft->sa;
693 PurpleHttpConnection *http_conn;
694
695 PurpleHttpRequest *request = purple_http_request_new("");
696 purple_http_request_set_url_printf(request, "https://%s/v1/objects/%s/content/original", SKYPEWEB_XFER_HOST, purple_url_encode(swft->id));
697 purple_http_request_set_method(request, "PUT");
698 purple_http_request_header_set(request, "Host", SKYPEWEB_XFER_HOST);
699 purple_http_request_header_set(request, "Content-Type", "multipart/form-data");
700 purple_http_request_header_set_printf(request, "Content-Length", "%" G_GSIZE_FORMAT, (gsize) purple_xfer_get_size(xfer));
701 purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token);
702 purple_http_request_set_contents_reader(request, skypeweb_xfer_send_contents_reader, purple_xfer_get_size(xfer), user_data);
703 purple_http_request_set_http11(request, TRUE);
704 purple_xfer_start(xfer, -1, NULL, 0);
705 http_conn = purple_http_request(sa->pc, request, skypeweb_xfer_send_done, user_data);
706 purple_http_conn_set_progress_watcher(http_conn, skypeweb_xfer_send_watcher, user_data, 1);
707
708 purple_http_request_unref(request);
709 }
710
711 static void
skypeweb_got_object_for_file(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)712 skypeweb_got_object_for_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
713 {
714 SkypeWebFileTransfer *swft = user_data;
715 PurpleXfer *xfer = swft->xfer;
716 JsonParser *parser;
717 JsonNode *node;
718 JsonObject *obj;
719 const gchar *data;
720 gsize len;
721
722 data = purple_http_response_get_data(response, &len);
723
724 //Get back {"id": "0-cus-d3-deadbeefdeadbeef012345678"}
725 parser = json_parser_new();
726 if (!json_parser_load_from_data(parser, data, len, NULL)) {
727 g_free(swft->from);
728 g_free(swft);
729 g_object_unref(parser);
730 return;
731 }
732 node = json_parser_get_root(parser);
733 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
734 g_free(swft->from);
735 g_free(swft);
736 g_object_unref(parser);
737 purple_xfer_cancel_local(xfer);
738 return;
739 }
740 obj = json_node_get_object(node);
741
742 if (!json_object_has_member(obj, "id")) {
743 g_free(swft->from);
744 g_free(swft);
745 g_object_unref(parser);
746 purple_xfer_cancel_local(xfer);
747 return;
748 }
749
750 swft->id = g_strdup(json_object_get_string_member(obj, "id"));
751 swft->url = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/original/status", NULL);
752
753 g_object_unref(parser);
754
755 //Send the data
756
757 //can't use fetch_url_request because it doesn't handle binary data
758 skypeweb_xfer_send_begin(user_data);
759
760 }
761
762 static void
skypeweb_xfer_send_init(PurpleXfer * xfer)763 skypeweb_xfer_send_init(PurpleXfer *xfer)
764 {
765 PurpleConnection *pc = purple_account_get_connection(purple_xfer_get_account(xfer));
766 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
767 gchar *basename = g_path_get_basename(purple_xfer_get_local_filename(xfer));
768 gchar *id, *post;
769 SkypeWebFileTransfer *swft = purple_xfer_get_protocol_data(xfer);
770 JsonObject *obj = json_object_new();
771 JsonObject *permissions = json_object_new();
772 JsonArray *userpermissions = json_array_new();
773 PurpleHttpRequest *request;
774
775 purple_xfer_set_filename(xfer, basename);
776 purple_xfer_ref(xfer);
777
778 json_object_set_string_member(obj, "type", "sharing/file");
779 json_object_set_string_member(obj, "filename", basename);
780
781 id = g_strconcat(skypeweb_user_url_prefix(swft->from), swft->from, NULL);
782 json_array_add_string_element(userpermissions, "read");
783 json_object_set_array_member(permissions, id, userpermissions);
784 json_object_set_object_member(obj, "permissions", permissions);
785
786 post = skypeweb_jsonobj_to_string(obj);
787 //POST to api.asm.skype.com /v1/objects
788 //{"type":"sharing/file","permissions":{"8:eionrobb":["read"]},"filename":"GiantLobsterMoose.txt"}
789
790 request = purple_http_request_new("https://" SKYPEWEB_XFER_HOST "/v1/objects");
791 purple_http_request_set_method(request, "POST");
792 purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
793 purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token); //slightly different to normal!
794 purple_http_request_header_set(request, "Content-Type", "application/json");
795 purple_http_request_header_set(request, "X-Client-Version", SKYPEWEB_CLIENTINFO_VERSION);
796 purple_http_request_set_contents(request, post, -1);
797 purple_http_request(sa->pc, request, skypeweb_got_object_for_file, swft);
798 purple_http_request_unref(request);
799
800 g_free(post);
801 json_object_unref(obj);
802 g_free(id);
803 g_free(basename);
804 }
805
806 PurpleXfer *
skypeweb_new_xfer(PurpleProtocolXfer * prplxfer,PurpleConnection * pc,const char * who)807 skypeweb_new_xfer(
808 #if PURPLE_VERSION_CHECK(3, 0, 0)
809 PurpleProtocolXfer *prplxfer,
810 #endif
811 PurpleConnection *pc, const char *who)
812 {
813 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
814 PurpleXfer *xfer;
815 SkypeWebFileTransfer *swft;
816
817 xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_SEND, who);
818
819 swft = g_new0(SkypeWebFileTransfer, 1);
820 swft->sa = sa;
821 swft->from = g_strdup(who);
822 swft->xfer = xfer;
823 purple_xfer_set_protocol_data(xfer, swft);
824
825 purple_xfer_set_init_fnc(xfer, skypeweb_xfer_send_init);
826 //purple_xfer_set_write_fnc(xfer, skypeweb_xfer_send_write);
827 //purple_xfer_set_end_fnc(xfer, skypeweb_xfer_send_end);
828 purple_xfer_set_request_denied_fnc(xfer, skypeweb_free_xfer);
829 purple_xfer_set_cancel_send_fnc(xfer, skypeweb_free_xfer);
830
831 return xfer;
832 }
833
834 void
skypeweb_send_file(PurpleProtocolXfer * prplxfer,PurpleConnection * pc,const gchar * who,const gchar * filename)835 skypeweb_send_file(
836 #if PURPLE_VERSION_CHECK(3, 0, 0)
837 PurpleProtocolXfer *prplxfer,
838 #endif
839 PurpleConnection *pc, const gchar *who, const gchar *filename)
840 {
841 PurpleXfer *xfer = skypeweb_new_xfer(
842 #if PURPLE_VERSION_CHECK(3, 0, 0)
843 prplxfer,
844 #endif
845 pc, who);
846
847 if (filename && *filename)
848 purple_xfer_request_accepted(xfer, filename);
849 else
850 purple_xfer_request(xfer);
851 }
852
853 gboolean
skypeweb_can_receive_file(PurpleProtocolXfer * prplxfer,PurpleConnection * pc,const gchar * who)854 skypeweb_can_receive_file(
855 #if PURPLE_VERSION_CHECK(3, 0, 0)
856 PurpleProtocolXfer *prplxfer,
857 #endif
858 PurpleConnection *pc, const gchar *who)
859 {
860 if (!who || g_str_equal(who, purple_account_get_username(purple_connection_get_account(pc))))
861 return FALSE;
862
863 return TRUE;
864 }
865
866
867 static void
skypeweb_got_self_details(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)868 skypeweb_got_self_details(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
869 {
870 JsonObject *userobj;
871 const gchar *old_alias;
872 const gchar *displayname = NULL;
873 const gchar *username;
874
875 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
876 return;
877 userobj = json_node_get_object(node);
878
879 username = json_object_get_string_member(userobj, "username");
880 g_free(sa->username); sa->username = g_strdup(username);
881 purple_connection_set_display_name(sa->pc, sa->username);
882
883 old_alias = purple_account_get_private_alias(sa->account);
884 if (!old_alias || !*old_alias) {
885 if (json_object_has_member(userobj, "displayname"))
886 displayname = json_object_get_string_member(userobj, "displayname");
887 if (!displayname || purple_strequal(displayname, username))
888 displayname = json_object_get_string_member(userobj, "firstname");
889
890 if (displayname)
891 purple_account_set_private_alias(sa->account, displayname);
892 }
893
894 if (!PURPLE_CONNECTION_IS_CONNECTED(sa->pc)) {
895 skypeweb_do_all_the_things(sa);
896 }
897
898 skypeweb_gather_self_properties(sa);
899 }
900
901
902 void
skypeweb_get_self_details(SkypeWebAccount * sa)903 skypeweb_get_self_details(SkypeWebAccount *sa)
904 {
905 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile", NULL, skypeweb_got_self_details, NULL, TRUE);
906 }
907
908
909
910
911
912
913
914
915
916 void
skypeweb_search_results_add_buddy(PurpleConnection * pc,GList * row,void * user_data)917 skypeweb_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data)
918 {
919 PurpleAccount *account = purple_connection_get_account(pc);
920
921 if (!purple_blist_find_buddy(account, g_list_nth_data(row, 0)))
922 purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Skype", g_list_nth_data(row, 1));
923 }
924
925 void
skypeweb_received_contacts(SkypeWebAccount * sa,PurpleXmlNode * contacts)926 skypeweb_received_contacts(SkypeWebAccount *sa, PurpleXmlNode *contacts)
927 {
928 PurpleNotifySearchResults *results;
929 PurpleNotifySearchColumn *column;
930
931 PurpleXmlNode *contact;
932
933 results = purple_notify_searchresults_new();
934 if (results == NULL) {
935 return;
936 }
937
938 /* columns: Friend ID, Name */
939 column = purple_notify_searchresults_column_new(_("Skype Name"));
940 purple_notify_searchresults_column_add(results, column);
941 column = purple_notify_searchresults_column_new(_("Display Name"));
942 purple_notify_searchresults_column_add(results, column);
943
944
945 purple_notify_searchresults_button_add(results,
946 PURPLE_NOTIFY_BUTTON_ADD,
947 skypeweb_search_results_add_buddy);
948
949 for(contact = purple_xmlnode_get_child(contacts, "c"); contact;
950 contact = purple_xmlnode_get_next_twin(contact))
951 {
952 GList *row = NULL;
953
954 gchar *contact_id = g_strdup(purple_xmlnode_get_attrib(contact, "s"));
955 gchar *contact_name = g_strdup(purple_xmlnode_get_attrib(contact, "f"));
956
957 row = g_list_append(row, contact_id);
958 row = g_list_append(row, contact_name);
959
960 purple_notify_searchresults_row_add(results, row);
961 }
962
963 purple_notify_searchresults(sa->pc, _("Received contacts"), NULL, NULL, results, NULL, NULL);
964 }
965
966 static PurpleNotifySearchResults*
create_search_results(JsonNode * node,gint * olength)967 create_search_results(JsonNode *node, gint *olength)
968 {
969 PurpleNotifySearchColumn *column;
970 gint index, length;
971 JsonObject *response = NULL;
972 JsonArray *resultsarray = NULL;
973
974 response = json_node_get_object(node);
975 resultsarray = json_object_get_array_member(response, "results");
976 length = json_array_get_length(resultsarray);
977
978 PurpleNotifySearchResults *results = purple_notify_searchresults_new();
979 if (results == NULL || length == 0)
980 {
981 if (olength)
982 {
983 *olength = 0;
984 }
985 return NULL;
986 }
987
988 /* columns: Friend ID, Name, Network */
989 column = purple_notify_searchresults_column_new(_("Skype Name"));
990 purple_notify_searchresults_column_add(results, column);
991 column = purple_notify_searchresults_column_new(_("Display Name"));
992 purple_notify_searchresults_column_add(results, column);
993 column = purple_notify_searchresults_column_new(_("City"));
994 purple_notify_searchresults_column_add(results, column);
995 column = purple_notify_searchresults_column_new(_("Country"));
996 purple_notify_searchresults_column_add(results, column);
997
998 purple_notify_searchresults_button_add(results,
999 PURPLE_NOTIFY_BUTTON_ADD,
1000 skypeweb_search_results_add_buddy);
1001
1002 for(index = 0; index < length; index++)
1003 {
1004 JsonObject *result = json_array_get_object_element(resultsarray, index);
1005 JsonObject *skypecontact = json_object_get_object_member(result, "nodeProfileData");
1006
1007 /* the row in the search results table */
1008 /* prepend to it backwards then reverse to speed up adds */
1009 GList *row = NULL;
1010
1011 #define add_skypecontact_row(value) (\
1012 row = g_list_prepend(row, \
1013 !json_object_has_member(skypecontact, (value)) ? NULL : \
1014 g_strdup(json_object_get_string_member(skypecontact, (value)))\
1015 ) \
1016 )
1017 add_skypecontact_row("skypeId");
1018 add_skypecontact_row("name");
1019 add_skypecontact_row("city");
1020 add_skypecontact_row("country");
1021
1022 row = g_list_reverse(row);
1023
1024 purple_notify_searchresults_row_add(results, row);
1025 }
1026
1027 if (olength)
1028 {
1029 *olength = length;
1030 }
1031 return results;
1032 }
1033
1034 static void
skypeweb_search_users_text_cb(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1035 skypeweb_search_users_text_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1036 {
1037 gint length;
1038 gchar *search_term = user_data;
1039 PurpleNotifySearchResults *results = create_search_results(node, &length);
1040
1041 if (results == NULL || length == 0)
1042 {
1043 gchar *primary_text = g_strdup_printf("Your search for the user \"%s\" returned no results", search_term);
1044 purple_notify_warning(sa->pc, _("No users found"), primary_text, "", purple_request_cpar_from_connection(sa->pc));
1045 g_free(primary_text);
1046 g_free(search_term);
1047 return;
1048 }
1049
1050 purple_notify_searchresults(sa->pc, NULL, search_term, NULL, results, NULL, NULL);
1051 }
1052
1053 static void
skypeweb_contact_suggestions_received_cb(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1054 skypeweb_contact_suggestions_received_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1055 {
1056 gint length;
1057 PurpleNotifySearchResults *results = create_search_results(node, &length);
1058
1059 if (results == NULL || length == 0)
1060 {
1061 purple_notify_warning(sa->pc, _("No results"), _("There are no contact suggestions available for you"), "", purple_request_cpar_from_connection(sa->pc));
1062 return;
1063 }
1064
1065 purple_notify_searchresults(sa->pc, _("Contact suggestions"), NULL, NULL, results, NULL, NULL);
1066 }
1067
1068 void
skypeweb_search_users_text(gpointer user_data,const gchar * text)1069 skypeweb_search_users_text(gpointer user_data, const gchar *text)
1070 {
1071 SkypeWebAccount *sa = user_data;
1072 GString *url = g_string_new("/search/v1.1/namesearch/swx/?");
1073
1074 g_string_append_printf(url, "searchstring=%s&", purple_url_encode(text));
1075 g_string_append(url, "requestId=1&");
1076
1077 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_GRAPH_HOST, url->str, NULL, skypeweb_search_users_text_cb, g_strdup(text), FALSE);
1078
1079 g_string_free(url, TRUE);
1080 }
1081
1082 void
skypeweb_search_users(PurpleProtocolAction * action)1083 skypeweb_search_users(PurpleProtocolAction *action)
1084 {
1085 PurpleConnection *pc = purple_protocol_action_get_connection(action);
1086 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1087
1088 purple_request_input(pc, "Search for Skype Friends",
1089 "Search for Skype Friends",
1090 NULL,
1091 NULL, FALSE, FALSE, NULL,
1092 _("_Search"), G_CALLBACK(skypeweb_search_users_text),
1093 _("_Cancel"), NULL,
1094 purple_request_cpar_from_connection(pc),
1095 sa);
1096
1097 }
1098
1099 void
skypeweb_contact_suggestions(PurpleProtocolAction * action)1100 skypeweb_contact_suggestions(PurpleProtocolAction *action)
1101 {
1102 PurpleConnection *pc = purple_protocol_action_get_connection(action);
1103 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1104
1105 GString *url = g_string_new("/v1.1/recommend?requestId=1&locale=en-US&count=20");
1106
1107 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_DEFAULT_CONTACT_SUGGESTIONS_HOST, url->str, NULL, skypeweb_contact_suggestions_received_cb, 0, FALSE);
1108
1109 g_string_free(url, TRUE);
1110 }
1111
1112 static void
skypeweb_got_friend_profiles(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1113 skypeweb_got_friend_profiles(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1114 {
1115 JsonArray *contacts;
1116 PurpleBuddy *buddy;
1117 SkypeWebBuddy *sbuddy;
1118 gint index, length;
1119
1120 if (node == NULL || json_node_get_node_type(node) != JSON_NODE_ARRAY)
1121 return;
1122 contacts = json_node_get_array(node);
1123 length = json_array_get_length(contacts);
1124
1125 for(index = 0; index < length; index++)
1126 {
1127 JsonObject *contact = json_array_get_object_element(contacts, index);
1128
1129 const gchar *username = json_object_get_string_member(contact, "username");
1130 const gchar *new_avatar;
1131
1132 buddy = purple_blist_find_buddy(sa->account, username);
1133 if (!buddy)
1134 continue;
1135 sbuddy = purple_buddy_get_protocol_data(buddy);
1136 if (sbuddy == NULL) {
1137 sbuddy = g_new0(SkypeWebBuddy, 1);
1138 purple_buddy_set_protocol_data(buddy, sbuddy);
1139 sbuddy->skypename = g_strdup(username);
1140 sbuddy->sa = sa;
1141 }
1142
1143 g_free(sbuddy->display_name); sbuddy->display_name = g_strdup(json_object_get_string_member(contact, "displayname"));
1144 purple_serv_got_alias(sa->pc, username, sbuddy->display_name);
1145 if (json_object_has_member(contact, "lastname")) {
1146 gchar *fullname = g_strconcat(json_object_get_string_member(contact, "firstname"), " ", json_object_get_string_member(contact, "lastname"), NULL);
1147
1148 purple_buddy_set_server_alias(buddy, fullname);
1149
1150 g_free(fullname);
1151 } else {
1152 purple_buddy_set_server_alias(buddy, json_object_get_string_member(contact, "firstname"));
1153 }
1154
1155 new_avatar = json_object_get_string_member(contact, "avatarUrl");
1156 if (new_avatar && *new_avatar && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, new_avatar))) {
1157 g_free(sbuddy->avatar_url);
1158 sbuddy->avatar_url = g_strdup(new_avatar);
1159 skypeweb_get_icon(buddy);
1160 }
1161
1162 g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(contact, "mood"));
1163 }
1164 }
1165
1166 void
skypeweb_get_friend_profiles(SkypeWebAccount * sa,GSList * contacts)1167 skypeweb_get_friend_profiles(SkypeWebAccount *sa, GSList *contacts)
1168 {
1169 const gchar *profiles_url = "/users/self/contacts/profiles";
1170 GString *postdata;
1171 GSList *cur = contacts;
1172
1173 if (contacts == NULL)
1174 return;
1175
1176 postdata = g_string_new("");
1177
1178 do {
1179 g_string_append_printf(postdata, "&contacts[]=%s", purple_url_encode(cur->data));
1180 } while((cur = g_slist_next(cur)));
1181
1182 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, profiles_url, postdata->str, skypeweb_got_friend_profiles, NULL, TRUE);
1183
1184 g_string_free(postdata, TRUE);
1185 }
1186
1187
1188 static void
skypeweb_got_info(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1189 skypeweb_got_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1190 {
1191 gchar *username = user_data;
1192 PurpleNotifyUserInfo *user_info;
1193 JsonObject *userobj;
1194 PurpleBuddy *buddy;
1195 SkypeWebBuddy *sbuddy;
1196
1197 if (node == NULL)
1198 return;
1199 if (json_node_get_node_type(node) == JSON_NODE_ARRAY)
1200 node = json_array_get_element(json_node_get_array(node), 0);
1201 if (json_node_get_node_type(node) != JSON_NODE_OBJECT)
1202 return;
1203 userobj = json_node_get_object(node);
1204
1205 user_info = purple_notify_user_info_new();
1206
1207 #define _SKYPE_USER_INFO(prop, key) if (prop && json_object_has_member(userobj, (prop)) && !json_object_get_null_member(userobj, (prop))) \
1208 purple_notify_user_info_add_pair_html(user_info, _(key), json_object_get_string_member(userobj, (prop)));
1209
1210 _SKYPE_USER_INFO("firstname", "First Name");
1211 _SKYPE_USER_INFO("lastname", "Last Name");
1212 _SKYPE_USER_INFO("birthday", "Birthday");
1213 //_SKYPE_USER_INFO("gender", "Gender");
1214 if (json_object_has_member(userobj, "gender") && !json_object_get_null_member(userobj, "gender")) {
1215 const gchar *gender_output = _("Unknown");
1216
1217 // Can be presented as either a string of a number or as a number argh
1218 if (json_node_get_value_type(json_object_get_member(userobj, "gender")) == G_TYPE_STRING) {
1219 const gchar *gender = json_object_get_string_member(userobj, "gender");
1220 if (gender && *gender == '1') {
1221 gender_output = _("Male");
1222 } else if (gender && *gender == '2') {
1223 gender_output = _("Female");
1224 }
1225 } else {
1226 gint64 gender = json_object_get_int_member(userobj, "gender");
1227 if (gender == 1) {
1228 gender_output = _("Male");
1229 } else if (gender == 2) {
1230 gender_output = _("Female");
1231 }
1232 }
1233
1234 purple_notify_user_info_add_pair_html(user_info, _("Gender"), gender_output);
1235 }
1236 _SKYPE_USER_INFO("language", "Language");
1237 _SKYPE_USER_INFO("country", "Country");
1238 _SKYPE_USER_INFO("province", "Province");
1239 _SKYPE_USER_INFO("city", "City");
1240 _SKYPE_USER_INFO("homepage", "Homepage");
1241 _SKYPE_USER_INFO("about", "About");
1242 _SKYPE_USER_INFO("jobtitle", "Job Title");
1243 _SKYPE_USER_INFO("phoneMobile", "Phone - Mobile");
1244 _SKYPE_USER_INFO("phoneHome", "Phone - Home");
1245 _SKYPE_USER_INFO("phoneOffice", "Phone - Office");
1246 //_SKYPE_USER_INFO("mood", "Mood");
1247 //_SKYPE_USER_INFO("richMood", "Mood");
1248 //_SKYPE_USER_INFO("avatarUrl", "Avatar");
1249
1250 buddy = purple_blist_find_buddy(sa->account, username);
1251 if (buddy) {
1252 sbuddy = purple_buddy_get_protocol_data(buddy);
1253 if (sbuddy == NULL) {
1254 sbuddy = g_new0(SkypeWebBuddy, 1);
1255 purple_buddy_set_protocol_data(buddy, sbuddy);
1256 sbuddy->skypename = g_strdup(username);
1257 sbuddy->sa = sa;
1258 }
1259
1260 //At the moment, "mood" field is present but always null via this call. Do not clear it.
1261 if (json_object_has_member(userobj, ("mood")) && !json_object_get_null_member(userobj, ("mood"))) {
1262 g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(userobj, "mood"));
1263 }
1264 }
1265
1266 purple_notify_userinfo(sa->pc, username, user_info, NULL, NULL);
1267
1268 g_free(username);
1269 }
1270
1271 void
skypeweb_get_info(PurpleConnection * pc,const gchar * username)1272 skypeweb_get_info(PurpleConnection *pc, const gchar *username)
1273 {
1274 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1275 gchar *post;
1276 const gchar *url = "/users/batch/profiles";
1277 JsonObject *obj;
1278 JsonArray *usernames_array;
1279
1280 obj = json_object_new();
1281 usernames_array = json_array_new();
1282
1283 json_array_add_string_element(usernames_array, username);
1284 json_object_set_array_member(obj, "usernames", usernames_array);
1285 post = skypeweb_jsonobj_to_string(obj);
1286
1287 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, post, skypeweb_got_info, g_strdup(username), TRUE);
1288
1289 g_free(post);
1290 json_object_unref(obj);
1291 }
1292
1293 void
skypeweb_get_friend_profile(SkypeWebAccount * sa,const gchar * who)1294 skypeweb_get_friend_profile(SkypeWebAccount *sa, const gchar *who)
1295 {
1296 GSList *contacts = NULL;
1297 gchar *whodup;
1298
1299 g_return_if_fail(sa && who && *who);
1300
1301 whodup = g_strdup(who);
1302 contacts = g_slist_prepend(contacts, whodup);
1303
1304 skypeweb_get_friend_profiles(sa, contacts);
1305
1306 g_free(contacts);
1307 g_free(whodup);
1308 }
1309
1310 static void
skypeweb_get_friend_list_cb(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1311 skypeweb_get_friend_list_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1312 {
1313 JsonObject *obj;
1314 JsonArray *contacts;
1315 PurpleGroup *group = NULL;
1316 GSList *users_to_fetch = NULL;
1317 guint index, length;
1318
1319 obj = json_node_get_object(node);
1320 contacts = json_object_get_array_member(obj, "contacts");
1321 length = json_array_get_length(contacts);
1322
1323 for(index = 0; index < length; index++)
1324 {
1325 JsonObject *contact = json_array_get_object_element(contacts, index);
1326 JsonObject *profile = json_object_get_object_member(contact, "profile");
1327 const gchar *mri = json_object_get_string_member(contact, "mri");
1328 const gchar *display_name = json_object_get_string_member(contact, "display_name");
1329 const gchar *avatar_url = NULL;
1330 gboolean authorized = json_object_get_boolean_member(contact, "authorized");
1331 gboolean blocked = json_object_get_boolean_member(contact, "blocked");
1332
1333 const gchar *mood = json_object_get_string_member(profile, "mood");
1334 JsonObject *name = json_object_get_object_member(profile, "name");
1335 const gchar *firstname = json_object_get_string_member(name, "first");
1336 const gchar *surname = NULL;
1337
1338 PurpleBuddy *buddy;
1339 const gchar *id;
1340
1341 if (json_object_has_member(contact, "suggested") && json_object_get_boolean_member(contact, "suggested") && !authorized) {
1342 // suggested buddies wtf? some kind of advertising?
1343 continue;
1344 }
1345
1346 id = skypeweb_strip_user_prefix(mri);
1347
1348 buddy = purple_blist_find_buddy(sa->account, id);
1349 if (!buddy)
1350 {
1351 if (!group)
1352 {
1353 group = purple_blist_find_group("Skype");
1354 if (!group)
1355 {
1356 group = purple_group_new("Skype");
1357 purple_blist_add_group(group, NULL);
1358 }
1359 }
1360 buddy = purple_buddy_new(sa->account, id, display_name);
1361 purple_blist_add_buddy(buddy, NULL, group, NULL);
1362 }
1363
1364 if (name && json_object_has_member(name, "surname"))
1365 surname = json_object_get_string_member(name, "surname");
1366
1367 // try to free the sbuddy here. no-op if it's not set before, otherwise prevents a leak.
1368 skypeweb_buddy_free(buddy);
1369
1370 SkypeWebBuddy *sbuddy = g_new0(SkypeWebBuddy, 1);
1371 sbuddy->skypename = g_strdup(id);
1372 sbuddy->sa = sa;
1373 sbuddy->fullname = g_strconcat(firstname, (surname ? " " : NULL), surname, NULL);
1374 sbuddy->display_name = g_strdup(display_name);
1375 sbuddy->authorized = authorized;
1376 sbuddy->blocked = blocked;
1377 sbuddy->avatar_url = g_strdup(purple_buddy_icons_get_checksum_for_user(buddy));
1378 sbuddy->mood = g_strdup(mood);
1379
1380 sbuddy->buddy = buddy;
1381 purple_buddy_set_protocol_data(buddy, sbuddy);
1382
1383 if (!purple_strequal(purple_buddy_get_local_alias(buddy), sbuddy->display_name)) {
1384 purple_serv_got_alias(sa->pc, id, sbuddy->display_name);
1385 }
1386 if (!purple_strequal(purple_buddy_get_server_alias(buddy), sbuddy->fullname)) {
1387 purple_buddy_set_server_alias(buddy, sbuddy->fullname);
1388 }
1389
1390 if (json_object_has_member(profile, "avatar_url")) {
1391 avatar_url = json_object_get_string_member(profile, "avatar_url");
1392 if (avatar_url && *avatar_url && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, avatar_url))) {
1393 g_free(sbuddy->avatar_url);
1394 sbuddy->avatar_url = g_strdup(avatar_url);
1395 skypeweb_get_icon(buddy);
1396 }
1397 }
1398
1399 if (blocked == TRUE) {
1400 purple_account_privacy_deny_add(sa->account, id, TRUE);
1401 } else {
1402 users_to_fetch = g_slist_prepend(users_to_fetch, sbuddy->skypename);
1403 }
1404
1405 if (purple_strequal(skypeweb_strip_user_prefix(id), sa->primary_member_name)) {
1406 g_free(sa->self_display_name);
1407 sa->self_display_name = g_strdup(display_name);
1408 }
1409 }
1410
1411 if (users_to_fetch)
1412 {
1413 //skypeweb_get_friend_profiles(sa, users_to_fetch);
1414 skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
1415 g_slist_free(users_to_fetch);
1416 }
1417 }
1418
1419 void
skypeweb_get_friend_list(SkypeWebAccount * sa)1420 skypeweb_get_friend_list(SkypeWebAccount *sa)
1421 {
1422 const gchar *url = "/contacts/v2/users/SELF?delta=&reason=default";
1423
1424 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, skypeweb_get_friend_list_cb, NULL, TRUE);
1425 }
1426
1427
1428
1429 void
skypeweb_auth_accept_cb(const gchar * who,gpointer sender)1430 skypeweb_auth_accept_cb(
1431 #if PURPLE_VERSION_CHECK(3, 0, 0)
1432 const gchar *who,
1433 #endif
1434 gpointer sender)
1435 {
1436 PurpleBuddy *buddy = sender;
1437 SkypeWebAccount *sa;
1438 gchar *url = NULL;
1439 GSList *users_to_fetch;
1440 gchar *buddy_name;
1441
1442 sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy)));
1443 buddy_name = g_strdup(purple_buddy_get_name(buddy));
1444
1445 url = g_strdup_printf("/contacts/v2/users/SELF/invites/%s%s/accept", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
1446 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
1447 g_free(url);
1448
1449 // Subscribe to status/message updates
1450 users_to_fetch = g_slist_prepend(NULL, buddy_name);
1451 skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
1452 g_slist_free(users_to_fetch);
1453 g_free(buddy_name);
1454 }
1455
1456 void
skypeweb_auth_reject_cb(const gchar * who,gpointer sender)1457 skypeweb_auth_reject_cb(
1458 #if PURPLE_VERSION_CHECK(3, 0, 0)
1459 const gchar *who,
1460 #endif
1461 gpointer sender)
1462 {
1463 PurpleBuddy *buddy = sender;
1464 SkypeWebAccount *sa;
1465 gchar *url = NULL;
1466 gchar *buddy_name;
1467
1468 sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy)));
1469 buddy_name = g_strdup(purple_buddy_get_name(buddy));
1470
1471 url = g_strdup_printf("/contacts/v2/users/SELF/invites/%s%s/decline", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
1472
1473 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
1474
1475 g_free(url);
1476 g_free(buddy_name);
1477 }
1478
1479 static void
skypeweb_got_authrequests(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1480 skypeweb_got_authrequests(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1481 {
1482 JsonObject *requests;
1483 JsonArray *invite_list;
1484 guint index, length;
1485 time_t latest_timestamp = 0;
1486
1487 requests = json_node_get_object(node);
1488 invite_list = json_object_get_array_member(requests, "invite_list");
1489 length = json_array_get_length(invite_list);
1490 for(index = 0; index < length; index++)
1491 {
1492 JsonObject *invite = json_array_get_object_element(invite_list, index);
1493 JsonArray *invites = json_object_get_array_member(invite, "invites");
1494 const gchar *event_time_iso = json_object_get_string_member(json_array_get_object_element(invites, 0), "time");
1495 time_t event_timestamp = purple_str_to_time(event_time_iso, TRUE, NULL, NULL, NULL);
1496 const gchar *sender = json_object_get_string_member(invite, "mri");
1497 const gchar *greeting = json_object_get_string_member(invite, "greeting");
1498 if (!greeting)
1499 greeting = json_object_get_string_member(json_array_get_object_element(invites, 0), "message");
1500 const gchar *displayname = json_object_get_string_member(invite, "displayname");
1501
1502 latest_timestamp = MAX(latest_timestamp, event_timestamp);
1503 if (sa->last_authrequest && event_timestamp <= sa->last_authrequest)
1504 continue;
1505
1506 if (sender == NULL)
1507 continue;
1508 sender = skypeweb_strip_user_prefix(sender);
1509
1510 purple_account_request_authorization(
1511 sa->account, sender, NULL,
1512 displayname, greeting, FALSE,
1513 skypeweb_auth_accept_cb, skypeweb_auth_reject_cb, purple_buddy_new(sa->account, sender, displayname));
1514
1515 }
1516
1517 sa->last_authrequest = latest_timestamp;
1518 }
1519
1520 gboolean
skypeweb_check_authrequests(SkypeWebAccount * sa)1521 skypeweb_check_authrequests(SkypeWebAccount *sa)
1522 {
1523 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, "/contacts/v2/users/SELF/invites", NULL, skypeweb_got_authrequests, NULL, TRUE);
1524 return TRUE;
1525 }
1526
1527
1528 void
skypeweb_add_buddy_with_invite(PurpleConnection * pc,PurpleBuddy * buddy,PurpleGroup * group,const char * message)1529 skypeweb_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
1530 {
1531 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1532 gchar *postdata;
1533 const gchar *url = "/contacts/v2/users/SELF/contacts";
1534 GSList *users_to_fetch;
1535 JsonObject *obj;
1536 gchar *buddy_name, *mri;
1537
1538 //https://contacts.skype.com/contacts/v2/users/SELF/contacts
1539 // POST {"mri":"2:eionrobb@dequis.onmicrosoft.com","greeting":"Hi, eionrobb@dequis.onmicrosoft.com, I'd like to add you as a contact."}
1540
1541 buddy_name = g_strdup(purple_buddy_get_name(buddy));
1542 mri = g_strconcat(skypeweb_user_url_prefix(buddy_name), buddy_name, NULL);
1543
1544 obj = json_object_new();
1545
1546 json_object_set_string_member(obj, "mri", mri);
1547 json_object_set_string_member(obj, "greeting", message ? message : _("Please authorize me so I can add you to my buddy list."));
1548 postdata = skypeweb_jsonobj_to_string(obj);
1549
1550 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
1551
1552 g_free(mri);
1553 g_free(postdata);
1554 json_object_unref(obj);
1555
1556 // Subscribe to status/message updates
1557 users_to_fetch = g_slist_prepend(NULL, buddy_name);
1558 skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
1559 g_slist_free(users_to_fetch);
1560
1561 g_free(buddy_name);
1562 }
1563
1564 void
skypeweb_add_buddy(PurpleConnection * pc,PurpleBuddy * buddy,PurpleGroup * group)1565 skypeweb_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
1566 {
1567 skypeweb_add_buddy_with_invite(pc, buddy, group, NULL);
1568 }
1569
1570 void
skypeweb_buddy_remove(PurpleConnection * pc,PurpleBuddy * buddy,PurpleGroup * group)1571 skypeweb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
1572 {
1573 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1574 gchar *url;
1575 const gchar *buddy_name = purple_buddy_get_name(buddy);
1576
1577 url = g_strdup_printf("/contacts/v2/users/SELF/contacts/%s%s", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
1578
1579 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
1580
1581 g_free(url);
1582
1583 skypeweb_unsubscribe_from_contact_status(sa, buddy_name);
1584 }
1585
1586 void
skypeweb_buddy_block(PurpleConnection * pc,const char * name)1587 skypeweb_buddy_block(PurpleConnection *pc, const char *name)
1588 {
1589 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1590 gchar *url;
1591 const gchar *postdata;
1592
1593 url = g_strdup_printf("/contacts/v2/users/SELF/contacts/blocklist/%s%s", skypeweb_user_url_prefix(name), purple_url_encode(name));
1594 postdata = "{\"report_abuse\":\"false\",\"ui_version\":\"skype.com\"}";
1595
1596 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
1597
1598 g_free(url);
1599 }
1600
1601 void
skypeweb_buddy_unblock(PurpleConnection * pc,const char * name)1602 skypeweb_buddy_unblock(PurpleConnection *pc, const char *name)
1603 {
1604 SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1605 gchar *url;
1606
1607 url = g_strdup_printf("/contacts/v2/users/SELF/contacts/blocklist/%s%s", skypeweb_user_url_prefix(name), purple_url_encode(name));
1608
1609 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
1610
1611 g_free(url);
1612 }
1613
1614
1615 void
skypeweb_set_mood_message(SkypeWebAccount * sa,const gchar * mood)1616 skypeweb_set_mood_message(SkypeWebAccount *sa, const gchar *mood)
1617 {
1618 JsonObject *obj, *payload;
1619 gchar *post;
1620
1621 obj = json_object_new();
1622 payload = json_object_new();
1623
1624 json_object_set_string_member(payload, "mood", mood ? mood : "");
1625 json_object_set_object_member(obj, "payload", payload);
1626 post = skypeweb_jsonobj_to_string(obj);
1627
1628 skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile/partial", post, NULL, NULL, TRUE);
1629
1630 g_free(post);
1631 json_object_unref(obj);
1632 }
1633