/* * SkypeWeb Plugin for libpurple/Pidgin * Copyright (c) 2014-2015 Eion Robb * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "skypeweb_contacts.h" #include "skypeweb_connection.h" #include "skypeweb_messages.h" #include "skypeweb_util.h" #if !PURPLE_VERSION_CHECK(3, 0, 0) # include "ft.h" #else # include "xfer.h" #endif // Check that the conversation hasn't been closed static gboolean purple_conversation_is_valid(PurpleConversation *conv) { GList *convs = purple_get_conversations(); return (g_list_find(convs, conv) != NULL); } typedef struct { PurpleXfer *xfer; JsonObject *info; gchar *from; gchar *url; gchar *id; SkypeWebAccount *sa; PurpleSslConnection *conn; } SkypeWebFileTransfer; static guint active_icon_downloads = 0; static void skypeweb_get_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { PurpleBuddy *buddy = user_data; SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy); SkypeWebAccount *sa = sbuddy->sa; sa->url_datas = g_slist_remove(sa->url_datas, url_data); active_icon_downloads--; if (!buddy) return; purple_buddy_icons_set_for_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy), g_memdup(url_text, len), len, url_data->url); } static void skypeweb_get_icon_now(PurpleBuddy *buddy) { SkypeWebBuddy *sbuddy; gchar *url; purple_debug_info("skypeweb", "getting new buddy icon for %s\n", purple_buddy_get_name(buddy)); sbuddy = purple_buddy_get_protocol_data(buddy); if (sbuddy != NULL && sbuddy->avatar_url && sbuddy->avatar_url[0]) { url = g_strdup(sbuddy->avatar_url); } else { url = g_strdup_printf("https://api.skype.com/users/%s/profile/avatar", purple_url_encode(purple_buddy_get_name(buddy))); } skypeweb_fetch_url_request(sbuddy->sa, url, TRUE, NULL, FALSE, NULL, FALSE, 524288, skypeweb_get_icon_cb, buddy); g_free(url); active_icon_downloads++; } static gboolean skypeweb_get_icon_queuepop(gpointer data) { PurpleBuddy *buddy = data; // Only allow 4 simultaneous downloads if (active_icon_downloads > 4) return TRUE; skypeweb_get_icon_now(buddy); return FALSE; } void skypeweb_get_icon(PurpleBuddy *buddy) { if (!buddy) return; purple_timeout_add(100, skypeweb_get_icon_queuepop, (gpointer)buddy); } static void skypeweb_got_imagemessage(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { PurpleConversation *conv = user_data; PurpleConnection *pc; SkypeWebAccount *sa; gint icon_id; gchar *msg_tmp; // Conversation could have been closed before we retrieved the image if (!purple_conversation_is_valid(conv)) { return; } pc = purple_conversation_get_connection(conv); sa = purple_connection_get_protocol_data(pc); sa->url_datas = g_slist_remove(sa->url_datas, url_data); if (url_text == NULL && url_data->data_len) { gchar *location; location = skypeweb_string_get_chunk(url_data->webdata, url_data->data_len, "Location: https://", "/"); if (location && *location) { skypeweb_download_uri_to_conv(sa, location, conv); g_free(location); } return; } if (!url_text || !len || url_text[0] == '{' || url_text[0] == '<') return; if (error_message && *error_message) return; icon_id = purple_imgstore_add_with_id(g_memdup(url_text, len), len, NULL); msg_tmp = g_strdup_printf("", icon_id); purple_conversation_write(conv, conv->name, msg_tmp, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_IMAGES, time(NULL)); g_free(msg_tmp); purple_imgstore_unref_by_id(icon_id); } void skypeweb_download_uri_to_conv(SkypeWebAccount *sa, const gchar *uri, PurpleConversation *conv) { gchar *headers; PurpleUtilFetchUrlData *requestdata; PurpleHttpURL *httpurl; httpurl = purple_http_url_parse(uri); headers = g_strdup_printf("GET /%s HTTP/1.0\r\n" "Connection: close\r\n" "Accept: image/*\r\n" "Cookie: skypetoken_asm=%s\r\n" "Host: %s\r\n" "\r\n", purple_http_url_get_path(httpurl), sa->skype_token, purple_http_url_get_host(httpurl)); requestdata = skypeweb_fetch_url_request(sa, uri, TRUE, NULL, FALSE, headers, FALSE, -1, skypeweb_got_imagemessage, conv); if (requestdata != NULL) requestdata->num_times_redirected = 10; /* Prevent following redirects */ g_free(headers); purple_http_url_free(httpurl); } static void skypeweb_got_vm_file(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { PurpleXfer *xfer = user_data; SkypeWebAccount *sa = purple_connection_get_protocol_data(purple_account_get_connection(xfer->account)); sa->url_datas = g_slist_remove(sa->url_datas, url_data); purple_xfer_write(xfer, (guchar *)url_text, len); } static void skypeweb_init_vm_download(PurpleXfer *xfer) { SkypeWebAccount *sa; JsonObject *file = xfer->data; gint64 fileSize; const gchar *url; fileSize = json_object_get_int_member(file, "fileSize"); url = json_object_get_string_member(file, "url"); purple_xfer_set_completed(xfer, FALSE); sa = purple_connection_get_protocol_data(purple_account_get_connection(xfer->account)); skypeweb_fetch_url_request(sa, url, TRUE, NULL, FALSE, NULL, FALSE, fileSize, skypeweb_got_vm_file, xfer); json_object_unref(file); } static void skypeweb_cancel_vm_download(PurpleXfer *xfer) { JsonObject *file = xfer->data; json_object_unref(file); } static void skypeweb_got_vm_download_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { PurpleConversation *conv = user_data; PurpleXfer *xfer; JsonObject *obj, *file; JsonArray *files; gint64 fileSize; const gchar *url, *assetId, *status; gchar *filename; if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) return; obj = json_node_get_object(node); files = json_object_get_array_member(obj, "files"); file = json_array_get_object_element(files, 0); if (file != NULL) { status = json_object_get_string_member(file, "status"); if (status && g_str_equal(status, "ok")) { assetId = json_object_get_string_member(obj, "assetId"); fileSize = json_object_get_int_member(file, "fileSize"); url = json_object_get_string_member(file, "url"); (void) url; filename = g_strconcat(assetId, ".mp4", NULL); xfer = purple_xfer_new(sa->account, PURPLE_XFER_RECEIVE, conv->name); purple_xfer_set_size(xfer, fileSize); purple_xfer_set_filename(xfer, filename); json_object_ref(file); purple_xfer_set_protocol_data(xfer, file); purple_xfer_set_init_fnc(xfer, skypeweb_init_vm_download); purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_cancel_vm_download); purple_xfer_add(xfer); g_free(filename); } else if (status && g_str_equal(status, "running")) { //skypeweb_download_video_message(sa, sid??????, conv); } } } static void skypeweb_got_vm_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { PurpleConversation *conv = user_data; JsonObject *obj, *response, *media_stream; const gchar *filename; if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) return; obj = json_node_get_object(node); response = json_object_get_object_member(obj, "response"); media_stream = json_object_get_object_member(response, "media_stream"); filename = json_object_get_string_member(media_stream, "filename"); if (filename != NULL) { // Need to keep retrying this url until it comes back with status:ok gchar *url = g_strdup_printf("/vod/api-create?assetId=%s&profile=mp4-vm", purple_url_encode(filename)); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, "media.vm.skype.com", url, NULL, skypeweb_got_vm_download_info, conv, TRUE); g_free(url); } } void skypeweb_download_video_message(SkypeWebAccount *sa, const gchar *sid, PurpleConversation *conv) { gchar *url, *username_encoded; username_encoded = g_strdup(purple_url_encode(sa->username)); url = g_strdup_printf("/users/%s/video_mails/%s", username_encoded, purple_url_encode(sid)); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_VIDEOMAIL_HOST, url, NULL, skypeweb_got_vm_info, conv, TRUE); g_free(url); g_free(username_encoded); } static void skypeweb_free_xfer(PurpleXfer *xfer) { SkypeWebFileTransfer *swft; swft = purple_xfer_get_protocol_data(xfer); g_return_if_fail(swft != NULL); if (swft->info != NULL) json_object_unref(swft->info); if (swft->conn != NULL) purple_ssl_close(swft->conn); g_free(swft->url); g_free(swft->id); g_free(swft->from); g_free(swft); purple_xfer_set_protocol_data(xfer, NULL); } static void skypeweb_got_file(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { SkypeWebFileTransfer *swft = user_data; PurpleXfer *xfer = swft->xfer; SkypeWebAccount *sa = swft->sa; sa->url_datas = g_slist_remove(sa->url_datas, url_data); if (error_message) { purple_xfer_error(purple_xfer_get_type(xfer), sa->account, swft->from, error_message); purple_xfer_cancel_local(xfer); } else { purple_xfer_write_file(xfer, (guchar *)url_text, len); purple_xfer_set_bytes_sent(xfer, len); purple_xfer_set_completed(xfer, TRUE); } //cleanup skypeweb_free_xfer(xfer); } static void skypeweb_init_file_download(PurpleXfer *xfer) { SkypeWebFileTransfer *swft; const gchar *view_location; gint64 content_full_length; PurpleHttpURL *httpurl; gchar *headers; swft = purple_xfer_get_protocol_data(xfer); view_location = json_object_get_string_member(swft->info, "view_location"); content_full_length = json_object_get_int_member(swft->info, "content_full_length"); purple_xfer_start(xfer, -1, NULL, 0); httpurl = purple_http_url_parse(view_location); headers = g_strdup_printf("GET /%s HTTP/1.0\r\n" "Connection: close\r\n" "Cookie: skypetoken_asm=%s\r\n" "Host: %s\r\n" "\r\n", purple_http_url_get_path(httpurl), swft->sa->skype_token, purple_http_url_get_host(httpurl)); skypeweb_fetch_url_request(swft->sa, view_location, TRUE, NULL, FALSE, headers, FALSE, content_full_length, skypeweb_got_file, swft); g_free(headers); purple_http_url_free(httpurl); } static void skypeweb_got_file_info(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { JsonObject *obj; PurpleXfer *xfer; SkypeWebFileTransfer *swft = user_data; SkypeWebAccount *sa = swft->sa; JsonParser *parser; JsonNode *node; sa->url_datas = g_slist_remove(sa->url_datas, url_data); parser = json_parser_new(); if (!json_parser_load_from_data(parser, url_text, len, NULL)) { g_free(swft->url); g_free(swft->from); g_free(swft); g_object_unref(parser); return; } node = json_parser_get_root(parser); if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) { g_free(swft->url); g_free(swft->from); g_free(swft); g_object_unref(parser); return; } obj = json_node_get_object(node); /* { "content_length": 40708, "content_full_length": 40708, "view_length": 40708, "content_state": "ready", "view_state": "ready", "view_location": "uri/views/original", "status_location": "uri/views/original/status", "scan": { "status": "passed" }, "original_filename": "filename" } */ purple_debug_info("skypeweb", "File info: %s\n", url_text); if (!json_object_has_member(obj, "content_state") || !g_str_equal(json_object_get_string_member(obj, "content_state"), "ready")) { skypeweb_present_uri_as_filetransfer(sa, json_object_get_string_member(obj, "status_location"), swft->from); g_free(swft->url); g_free(swft->from); g_free(swft); g_object_unref(parser); return; } json_object_ref(obj); swft->info = obj; xfer = purple_xfer_new(sa->account, PURPLE_XFER_RECEIVE, swft->from); purple_xfer_set_size(xfer, json_object_get_int_member(obj, "content_full_length")); purple_xfer_set_filename(xfer, json_object_get_string_member(obj, "original_filename")); purple_xfer_set_init_fnc(xfer, skypeweb_init_file_download); purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_free_xfer); swft->xfer = xfer; purple_xfer_set_protocol_data(xfer, swft); purple_xfer_request(xfer); g_object_unref(parser); } void skypeweb_present_uri_as_filetransfer(SkypeWebAccount *sa, const gchar *uri, const gchar *from) { PurpleHttpURL *httpurl; const gchar *host; gchar *path; SkypeWebFileTransfer *swft; gchar *headers; httpurl = purple_http_url_parse(uri); host = purple_http_url_get_host(httpurl); if (g_str_has_suffix(uri, "/views/original/status")) { path = g_strconcat(purple_http_url_get_path(httpurl), NULL); } else { path = g_strconcat(purple_http_url_get_path(httpurl), "/views/original/status", NULL); } headers = g_strdup_printf("GET /%s HTTP/1.0\r\n" "Connection: close\r\n" "Cookie: skypetoken_asm=%s\r\n" "Host: %s\r\n" "\r\n", path, sa->skype_token, host); swft = g_new0(SkypeWebFileTransfer, 1); swft->sa = sa; swft->url = g_strdup(uri); swft->from = g_strdup(from); skypeweb_fetch_url_request(sa, uri, TRUE, NULL, FALSE, headers, FALSE, -1, skypeweb_got_file_info, swft); g_free(path); g_free(headers); purple_http_url_free(httpurl); } static gssize skypeweb_xfer_send_write(const guchar *buf, size_t len, PurpleXfer *xfer) { SkypeWebFileTransfer *swft = purple_xfer_get_protocol_data(xfer); SkypeWebAccount *sa = swft->sa; return purple_ssl_write(swft->conn, buf, len); } static void skypeweb_read_stuff(gpointer user_data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond) { gchar buf[1024]; //Flush the server buffer purple_ssl_read(ssl_connection, buf, 1024); //purple_debug_info("skypeweb", "Server file send response was %s\n", buf); } static void skypeweb_xfer_send_connect_cb(gpointer user_data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond) { SkypeWebFileTransfer *swft = user_data; SkypeWebAccount *sa = swft->sa; PurpleXfer *xfer = swft->xfer; gchar *headers; headers = g_strdup_printf("PUT /v1/objects/%s/content/original HTTP/1.0\r\n" "Connection: close\r\n" "Authorization: skype_token %s\r\n" //slightly different to normal! "Host: " SKYPEWEB_XFER_HOST "\r\n" "Content-Length: %d\r\n" "Content-Type: application/json\r\n" "\r\n", purple_url_encode(swft->id), sa->skype_token, purple_xfer_get_size(xfer)); purple_ssl_write(ssl_connection, headers, strlen(headers)); swft->conn = ssl_connection; purple_ssl_input_add(swft->conn, skypeweb_read_stuff, swft); purple_xfer_ref(xfer); purple_xfer_start(xfer, ssl_connection->fd, NULL, 0); purple_xfer_prpl_ready(xfer); g_free(headers); } static gboolean poll_file_send_progress(gpointer user_data); static void got_file_send_progress(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { SkypeWebFileTransfer *swft = user_data; PurpleXfer *xfer = swft->xfer; SkypeWebAccount *sa = swft->sa; JsonParser *parser; JsonNode *node; JsonObject *obj; sa->url_datas = g_slist_remove(sa->url_datas, url_data); //{"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"} parser = json_parser_new(); if (!json_parser_load_from_data(parser, url_text, len, NULL)) { //probably bad poll_file_send_progress(swft); return; } node = json_parser_get_root(parser); if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) { //probably bad poll_file_send_progress(swft); return; } obj = json_node_get_object(node); if (json_object_has_member(obj, "status_location")) { g_free(swft->url); swft->url = g_strdup(json_object_get_string_member(obj, "status_location")); } if (json_object_has_member(obj, "content_state") && g_str_equal(json_object_get_string_member(obj, "content_state"), "ready")) { xmlnode *uriobject = xmlnode_new("URIObject"); xmlnode *title = xmlnode_new_child(uriobject, "Title"); xmlnode *description = xmlnode_new_child(uriobject, "Description"); xmlnode *anchor = xmlnode_new_child(uriobject, "a"); xmlnode *originalname = xmlnode_new_child(uriobject, "OriginalName"); xmlnode *filesize = xmlnode_new_child(uriobject, "FileSize"); gchar *message, *temp; //We finally did it! // May the pesants rejoyce purple_xfer_set_completed(xfer, TRUE); // Don't forget to let the other end know about it xmlnode_set_attrib(uriobject, "type", "File.1"); temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), NULL); xmlnode_set_attrib(uriobject, "uri", temp); g_free(temp); temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/thumbnail", NULL); xmlnode_set_attrib(uriobject, "url_thumbnail", temp); g_free(temp); xmlnode_insert_data(title, purple_xfer_get_filename(xfer), -1); xmlnode_insert_data(description, "Description: ", -1); temp = g_strconcat("https://login.skype.com/login/sso?go=webclient.xmm&docid=", purple_url_encode(swft->id), NULL); xmlnode_set_attrib(anchor, "href", temp); xmlnode_insert_data(anchor, temp, -1); g_free(temp); xmlnode_set_attrib(originalname, "v", purple_xfer_get_filename(xfer)); temp = g_strdup_printf("%d", purple_xfer_get_size(xfer)); xmlnode_set_attrib(filesize, "v", temp); g_free(temp); message = xmlnode_to_str(uriobject, NULL); skypeweb_send_im(sa->pc, swft->from, message, PURPLE_MESSAGE_SEND); g_free(message); skypeweb_free_xfer(xfer); purple_xfer_unref(xfer); xmlnode_free(uriobject); g_object_unref(parser); return; } g_object_unref(parser); // probably good poll_file_send_progress(swft); } static gboolean poll_file_send_progress(gpointer user_data) { SkypeWebFileTransfer *swft = user_data; SkypeWebAccount *sa = swft->sa; PurpleHttpURL *httpurl; const gchar *host, *path; gchar *headers; httpurl = purple_http_url_parse(swft->url); host = purple_http_url_get_host(httpurl); path = purple_http_url_get_path(httpurl); headers = g_strdup_printf("GET /%s HTTP/1.0\r\n" "Connection: close\r\n" "Cookie: skypetoken_asm=%s\r\n" "Host: %s\r\n" "\r\n", path, sa->skype_token, host); skypeweb_fetch_url_request(sa, swft->url, TRUE, NULL, FALSE, headers, FALSE, -1, got_file_send_progress, swft); g_free(headers); purple_http_url_free(httpurl); return FALSE; } static void skypeweb_xfer_send_end(PurpleXfer *xfer) { SkypeWebFileTransfer *swft = purple_xfer_get_protocol_data(xfer); gchar buf[1024]; //Flush the server buffer purple_ssl_read(swft->conn, buf, 1024); //purple_debug_info("skypeweb", "Server file send response was %s\n", buf); purple_ssl_close(swft->conn); swft->conn = NULL; //poll swft->url for progress purple_timeout_add_seconds(1, poll_file_send_progress, swft); } static void skypeweb_got_object_for_file(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { SkypeWebFileTransfer *swft = user_data; SkypeWebAccount *sa = swft->sa; PurpleXfer *xfer = swft->xfer; JsonParser *parser; JsonNode *node; JsonObject *obj; gchar *id; sa->url_datas = g_slist_remove(sa->url_datas, url_data); //Get back {"id": "0-cus-d3-deadbeefdeadbeef012345678"} parser = json_parser_new(); if (!json_parser_load_from_data(parser, url_text, len, NULL)) { g_free(swft->from); g_free(swft); g_object_unref(parser); return; } node = json_parser_get_root(parser); if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) { g_free(swft->from); g_free(swft); g_object_unref(parser); purple_xfer_cancel_local(xfer); return; } obj = json_node_get_object(node); if (!json_object_has_member(obj, "id")) { g_free(swft->from); g_free(swft); g_object_unref(parser); purple_xfer_cancel_local(xfer); return; } swft->id = g_strdup(json_object_get_string_member(obj, "id")); swft->url = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/original/status", NULL); g_object_unref(parser); //Send the data //can't use fetch_url_request because it doesn't handle binary data //TODO make an error handler callback func purple_ssl_connect(sa->account, SKYPEWEB_XFER_HOST, 443, skypeweb_xfer_send_connect_cb, NULL, swft); } static void skypeweb_xfer_send_init(PurpleXfer *xfer) { PurpleConnection *pc = purple_account_get_connection(purple_xfer_get_account(xfer)); SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); gchar *basename = g_path_get_basename(purple_xfer_get_local_filename(xfer)); gchar *id, *post, *headers; SkypeWebFileTransfer *swft = purple_xfer_get_protocol_data(xfer); JsonObject *obj = json_object_new(); JsonObject *permissions = json_object_new(); JsonArray *userpermissions = json_array_new(); purple_xfer_set_filename(xfer, basename); purple_xfer_ref(xfer); json_object_set_string_member(obj, "type", "sharing/file"); json_object_set_string_member(obj, "filename", basename); if (SKYPEWEB_BUDDY_IS_MSN(swft->from)) { id = g_strconcat("1:", swft->from, NULL); } else { id = g_strconcat("8:", swft->from, NULL); } json_array_add_string_element(userpermissions, "read"); json_object_set_array_member(permissions, id, userpermissions); json_object_set_object_member(obj, "permissions", permissions); post = skypeweb_jsonobj_to_string(obj); //POST to api.asm.skype.com /v1/objects //{"type":"sharing/file","permissions":{"8:eionrobb":["read"]},"filename":"GiantLobsterMoose.txt"} headers = g_strdup_printf("POST /v1/objects HTTP/1.0\r\n" "Connection: close\r\n" "Authorization: skype_token %s\r\n" //slightly different to normal! "Host: " SKYPEWEB_XFER_HOST "\r\n" "Content-Length: %d\r\n" "Content-Type: application/json\r\n" "\r\n%s", sa->skype_token, strlen(post), post); skypeweb_fetch_url_request(sa, "https://" SKYPEWEB_XFER_HOST, TRUE, NULL, FALSE, headers, FALSE, -1, skypeweb_got_object_for_file, swft); g_free(post); json_object_unref(obj); g_free(id); g_free(basename); } PurpleXfer * skypeweb_new_xfer(PurpleConnection *pc, const char *who) { SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); PurpleXfer *xfer; SkypeWebFileTransfer *swft; xfer = purple_xfer_new(sa->account, PURPLE_XFER_SEND, who); swft = g_new0(SkypeWebFileTransfer, 1); swft->sa = sa; swft->from = g_strdup(who); swft->xfer = xfer; purple_xfer_set_protocol_data(xfer, swft); purple_xfer_set_init_fnc(xfer, skypeweb_xfer_send_init); purple_xfer_set_write_fnc(xfer, skypeweb_xfer_send_write); purple_xfer_set_end_fnc(xfer, skypeweb_xfer_send_end); purple_xfer_set_request_denied_fnc(xfer, skypeweb_free_xfer); purple_xfer_set_cancel_send_fnc(xfer, skypeweb_free_xfer); return xfer; } void skypeweb_send_file(PurpleConnection *pc, const gchar *who, const gchar *filename) { PurpleXfer *xfer = skypeweb_new_xfer(pc, who); if (filename && *filename) purple_xfer_request_accepted(xfer, filename); else purple_xfer_request(xfer); } gboolean skypeweb_can_receive_file(PurpleConnection *pc, const gchar *who) { if (!who || g_str_equal(who, purple_account_get_username(purple_connection_get_account(pc)))) return FALSE; return TRUE; } static void skypeweb_got_self_details(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { JsonObject *userobj; const gchar *old_alias; const gchar *displayname = NULL; const gchar *username; if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) return; userobj = json_node_get_object(node); username = json_object_get_string_member(userobj, "username"); g_free(sa->username); sa->username = g_strdup(username); old_alias = purple_account_get_private_alias(sa->account); if (!old_alias || !*old_alias) { if (json_object_has_member(userobj, "displayname")) displayname = json_object_get_string_member(userobj, "displayname"); if (!displayname || g_str_equal(displayname, username)) displayname = json_object_get_string_member(userobj, "firstname"); if (displayname) purple_account_set_private_alias(sa->account, displayname); } if (!PURPLE_CONNECTION_IS_CONNECTED(sa->pc)) { skypeweb_do_all_the_things(sa); } } void skypeweb_get_self_details(SkypeWebAccount *sa) { skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile", NULL, skypeweb_got_self_details, NULL, TRUE); } void skypeweb_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data) { PurpleAccount *account = purple_connection_get_account(pc); if (!purple_blist_find_buddy(account, g_list_nth_data(row, 0))) purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Skype", g_list_nth_data(row, 1)); } void skypeweb_search_users_text_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { JsonArray *resultsarray = NULL; gint index, length; GString *userids; gchar *search_term = user_data; PurpleNotifySearchResults *results; PurpleNotifySearchColumn *column; resultsarray = json_node_get_array(node); length = json_array_get_length(resultsarray); if (length == 0) { gchar *primary_text = g_strdup_printf("Your search for the user \"%s\" returned no results", search_term); purple_notify_warning(sa->pc, "No users found", primary_text, ""); g_free(primary_text); g_free(search_term); return; } userids = g_string_new(""); resultsarray = json_node_get_array(node); for(index = 0; index < length; index++) { JsonObject *result = json_array_get_object_element(resultsarray, index); g_string_append_printf(userids, "%s,", json_object_get_string_member(result, "skypewebid")); } results = purple_notify_searchresults_new(); if (results == NULL) { g_free(search_term); return; } /* columns: Friend ID, Name, Network */ column = purple_notify_searchresults_column_new(_("Skype Name")); purple_notify_searchresults_column_add(results, column); column = purple_notify_searchresults_column_new(_("Display Name")); purple_notify_searchresults_column_add(results, column); column = purple_notify_searchresults_column_new(_("City")); purple_notify_searchresults_column_add(results, column); column = purple_notify_searchresults_column_new(_("Country")); purple_notify_searchresults_column_add(results, column); purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, skypeweb_search_results_add_buddy); for(index = 0; index < length; index++) { JsonObject *contact = json_array_get_object_element(resultsarray, index); JsonObject *contactcards = json_object_get_object_member(contact, "ContactCards"); JsonObject *skypecontact = json_object_get_object_member(contactcards, "Skype"); JsonObject *currentlocation = json_object_get_object_member(contactcards, "CurrentLocation"); /* the row in the search results table */ /* prepend to it backwards then reverse to speed up adds */ GList *row = NULL; row = g_list_prepend(row, g_strdup(json_object_get_string_member(skypecontact, "SkypeName"))); row = g_list_prepend(row, g_strdup(json_object_get_string_member(skypecontact, "DisplayName"))); row = g_list_prepend(row, g_strdup(json_object_get_string_member(currentlocation, "City"))); row = g_list_prepend(row, g_strdup(json_object_get_string_member(currentlocation, "Country"))); row = g_list_reverse(row); purple_notify_searchresults_row_add(results, row); } purple_notify_searchresults(sa->pc, NULL, search_term, NULL, results, NULL, NULL); } void skypeweb_search_users_text(gpointer user_data, const gchar *text) { SkypeWebAccount *sa = user_data; GString *url = g_string_new("/search/users/any?"); g_string_append_printf(url, "keyWord=%s&", purple_url_encode(text)); g_string_append(url, "contactTypes[]=skype&"); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url->str, NULL, skypeweb_search_users_text_cb, g_strdup(text), FALSE); g_string_free(url, TRUE); } void skypeweb_search_users(PurplePluginAction *action) { PurpleConnection *pc = (PurpleConnection *) action->context; SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); purple_request_input(pc, "Search for Skype Friends", "Search for Skype Friends", NULL, NULL, FALSE, FALSE, NULL, _("_Search"), G_CALLBACK(skypeweb_search_users_text), _("_Cancel"), NULL, purple_connection_get_account(pc), NULL, NULL, sa); } static void skypeweb_got_friend_profiles(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { JsonArray *contacts; PurpleBuddy *buddy; SkypeWebBuddy *sbuddy; gint index, length; if (node == NULL || json_node_get_node_type(node) != JSON_NODE_ARRAY) return; contacts = json_node_get_array(node); length = json_array_get_length(contacts); for(index = 0; index < length; index++) { JsonObject *contact = json_array_get_object_element(contacts, index); const gchar *username = json_object_get_string_member(contact, "username"); const gchar *new_avatar; buddy = purple_find_buddy(sa->account, username); if (!buddy) continue; sbuddy = purple_buddy_get_protocol_data(buddy); if (sbuddy == NULL) { sbuddy = g_new0(SkypeWebBuddy, 1); purple_buddy_set_protocol_data(buddy, sbuddy); sbuddy->skypename = g_strdup(username); sbuddy->sa = sa; } g_free(sbuddy->display_name); sbuddy->display_name = g_strdup(json_object_get_string_member(contact, "displayname")); purple_serv_got_alias(sa->pc, username, sbuddy->display_name); if (json_object_has_member(contact, "lastname")) { gchar *fullname = g_strconcat(json_object_get_string_member(contact, "firstname"), " ", json_object_get_string_member(contact, "lastname"), NULL); purple_blist_server_alias_buddy(buddy, fullname); g_free(fullname); } else { purple_blist_server_alias_buddy(buddy, json_object_get_string_member(contact, "firstname")); } new_avatar = json_object_get_string_member(contact, "avatarUrl"); if (new_avatar && *new_avatar && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, new_avatar))) { g_free(sbuddy->avatar_url); sbuddy->avatar_url = g_strdup(new_avatar); skypeweb_get_icon(buddy); } g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(contact, "mood")); } } void skypeweb_get_friend_profiles(SkypeWebAccount *sa, GSList *contacts) { const gchar *profiles_url = "/users/self/contacts/profiles"; GString *postdata; GSList *cur = contacts; if (contacts == NULL) return; postdata = g_string_new(""); do { g_string_append_printf(postdata, "&contacts[]=%s", purple_url_encode(cur->data)); } while((cur = g_slist_next(cur))); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, profiles_url, postdata->str, skypeweb_got_friend_profiles, NULL, TRUE); g_string_free(postdata, TRUE); } static void skypeweb_got_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { gchar *username = user_data; PurpleNotifyUserInfo *user_info; JsonObject *userobj; PurpleBuddy *buddy; SkypeWebBuddy *sbuddy; const gchar *new_avatar; if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) return; userobj = json_node_get_object(node); user_info = purple_notify_user_info_new(); #define _SKYPE_USER_INFO(prop, key) if (prop && json_object_has_member(userobj, (prop)) && !json_object_get_null_member(userobj, (prop))) \ purple_notify_user_info_add_pair_html(user_info, _(key), json_object_get_string_member(userobj, (prop))); _SKYPE_USER_INFO("firstname", "First Name"); _SKYPE_USER_INFO("lastname", "Last Name"); _SKYPE_USER_INFO("birthday", "Birthday"); //_SKYPE_USER_INFO("gender", "Gender"); if (json_object_has_member(userobj, "gender") && !json_object_get_null_member(userobj, "gender")) { const gchar *gender = json_object_get_string_member(userobj, "gender"); const gchar *gender_output; if (*gender == '1') { gender_output = _("Male"); } else if (*gender == '2') { gender_output = _("Female"); } else { gender_output = _("Unknown"); } purple_notify_user_info_add_pair_html(user_info, _("Gender"), gender_output); } _SKYPE_USER_INFO("language", "Language"); _SKYPE_USER_INFO("country", "Country"); _SKYPE_USER_INFO("province", "Province"); _SKYPE_USER_INFO("city", "City"); _SKYPE_USER_INFO("homepage", "Homepage"); _SKYPE_USER_INFO("about", "About"); _SKYPE_USER_INFO("jobtitle", "Job Title"); _SKYPE_USER_INFO("phoneMobile", "Phone - Mobile"); _SKYPE_USER_INFO("phoneHome", "Phone - Home"); _SKYPE_USER_INFO("phoneOffice", "Phone - Office"); //_SKYPE_USER_INFO("mood", "Mood"); //_SKYPE_USER_INFO("richMood", "Mood"); //_SKYPE_USER_INFO("avatarUrl", "Avatar"); buddy = purple_find_buddy(sa->account, username); if (buddy) { sbuddy = purple_buddy_get_protocol_data(buddy); if (sbuddy == NULL) { sbuddy = g_new0(SkypeWebBuddy, 1); purple_buddy_set_protocol_data(buddy, sbuddy); sbuddy->skypename = g_strdup(username); sbuddy->sa = sa; } new_avatar = json_object_get_string_member(userobj, "avatarUrl"); if (new_avatar && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, new_avatar))) { g_free(sbuddy->avatar_url); sbuddy->avatar_url = g_strdup(new_avatar); skypeweb_get_icon(buddy); } g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(userobj, "mood")); } purple_notify_userinfo(sa->pc, username, user_info, NULL, NULL); g_free(username); } void skypeweb_get_info(PurpleConnection *pc, const gchar *username) { SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); gchar *url; url = g_strdup_printf("/users/%s/profile", purple_url_encode(username)); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, NULL, skypeweb_got_info, g_strdup(username), TRUE); g_free(url); } void skypeweb_get_friend_profile(SkypeWebAccount *sa, const gchar *who) { GSList *contacts = NULL; gchar *whodup; g_return_if_fail(sa && who && *who); whodup = g_strdup(who); contacts = g_slist_prepend(contacts, whodup); skypeweb_get_friend_profiles(sa, contacts); g_free(contacts); g_free(whodup); } static void skypeweb_get_friend_list_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { JsonObject *obj; JsonArray *contacts; PurpleGroup *group = NULL; GSList *users_to_fetch = NULL; guint index, length; obj = json_node_get_object(node); contacts = json_object_get_array_member(obj, "contacts"); length = json_array_get_length(contacts); for(index = 0; index < length; index++) { JsonObject *contact = json_array_get_object_element(contacts, index); const gchar *id = json_object_get_string_member(contact, "id"); const gchar *display_name = json_object_get_string_member(contact, "display_name"); const gchar *avatar_url = NULL; gboolean authorized = json_object_get_boolean_member(contact, "authorized"); gboolean blocked = json_object_get_boolean_member(contact, "blocked"); const gchar *type = json_object_get_string_member(contact, "type"); JsonObject *name = json_object_get_object_member(contact, "name"); const gchar *firstname = json_object_get_string_member(name, "first"); const gchar *surname = NULL; PurpleBuddy *buddy; //TODO make this work for "pstn" if (!g_str_equal(type, "skype") && !g_str_equal(type, "msn")) continue; if (json_object_has_member(contact, "suggested") && json_object_get_boolean_member(contact, "suggested") && !authorized) { // suggested buddies wtf? some kind of advertising? continue; } buddy = purple_find_buddy(sa->account, id); if (!buddy) { if (!group) { group = purple_blist_find_group("Skype"); if (!group) { group = purple_group_new("Skype"); purple_blist_add_group(group, NULL); } } buddy = purple_buddy_new(sa->account, id, display_name); purple_blist_add_buddy(buddy, NULL, group, NULL); } if (json_object_has_member(name, "surname")) surname = json_object_get_string_member(name, "surname"); // try to free the sbuddy here. no-op if it's not set before, otherwise prevents a leak. skypeweb_buddy_free(buddy); SkypeWebBuddy *sbuddy = g_new0(SkypeWebBuddy, 1); sbuddy->skypename = g_strdup(id); sbuddy->sa = sa; sbuddy->fullname = g_strconcat(firstname, (surname ? " " : NULL), surname, NULL); sbuddy->display_name = g_strdup(display_name); sbuddy->authorized = authorized; sbuddy->blocked = blocked; sbuddy->avatar_url = g_strdup(purple_buddy_icons_get_checksum_for_user(buddy)); sbuddy->buddy = buddy; purple_buddy_set_protocol_data(buddy, sbuddy); purple_serv_got_alias(sa->pc, id, sbuddy->display_name); purple_blist_server_alias_buddy(buddy, sbuddy->fullname); if (json_object_has_member(contact, "avatar_url")) { avatar_url = json_object_get_string_member(contact, "avatar_url"); if (avatar_url && *avatar_url && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, avatar_url))) { g_free(sbuddy->avatar_url); sbuddy->avatar_url = g_strdup(avatar_url); skypeweb_get_icon(buddy); } } if (blocked == TRUE) { purple_privacy_deny_add(sa->account, id, TRUE); } else { users_to_fetch = g_slist_prepend(users_to_fetch, sbuddy->skypename); } } if (users_to_fetch) { //skypeweb_get_friend_profiles(sa, users_to_fetch); skypeweb_subscribe_to_contact_status(sa, users_to_fetch); g_slist_free(users_to_fetch); } } void skypeweb_get_friend_list(SkypeWebAccount *sa) { gchar *url; if (!sa->username) { //purple_debug_error("skypeweb", "Can't fetch contacts yet, don't have local username"); return; } url = g_strdup_printf("/contacts/v1/users/%s/contacts", purple_url_encode(sa->username)); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, skypeweb_get_friend_list_cb, NULL, TRUE); g_free(url); } void skypeweb_auth_accept_cb(gpointer sender) { PurpleBuddy *buddy = sender; SkypeWebAccount *sa; gchar *url = NULL; GSList *users_to_fetch; gchar *buddy_name; sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy))); buddy_name = g_strdup(purple_buddy_get_name(buddy)); url = g_strdup_printf("/users/self/contacts/auth-request/%s/accept", purple_url_encode(buddy_name)); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE); g_free(url); // Subscribe to status/message updates users_to_fetch = g_slist_prepend(NULL, buddy_name); skypeweb_subscribe_to_contact_status(sa, users_to_fetch); g_slist_free(users_to_fetch); g_free(buddy_name); } void skypeweb_auth_reject_cb(gpointer sender) { PurpleBuddy *buddy = sender; SkypeWebAccount *sa; gchar *url = NULL; sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy))); url = g_strdup_printf("/users/self/contacts/auth-request/%s/decline", purple_url_encode(purple_buddy_get_name(buddy))); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE); g_free(url); } static void skypeweb_got_authrequests(SkypeWebAccount *sa, JsonNode *node, gpointer user_data) { JsonArray *requests; guint index, length; time_t latest_timestamp = 0; requests = json_node_get_array(node); length = json_array_get_length(requests); for(index = 0; index < length; index++) { JsonObject *request = json_array_get_object_element(requests, index); const gchar *event_time_iso = json_object_get_string_member(request, "event_time_iso"); const gchar *sender = json_object_get_string_member(request, "sender"); const gchar *greeting = json_object_get_string_member(request, "greeting"); time_t event_timestamp = purple_str_to_time(event_time_iso, TRUE, NULL, NULL, NULL); latest_timestamp = MAX(latest_timestamp, event_timestamp); if (sa->last_authrequest && event_timestamp <= sa->last_authrequest) continue; purple_account_request_authorization( sa->account, sender, NULL, NULL, greeting, FALSE, skypeweb_auth_accept_cb, skypeweb_auth_reject_cb, purple_buddy_new(sa->account, sender, NULL)); } sa->last_authrequest = latest_timestamp; } gboolean skypeweb_check_authrequests(SkypeWebAccount *sa) { skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/contacts/auth-request", NULL, skypeweb_got_authrequests, NULL, TRUE); return TRUE; } void skypeweb_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char* message) { SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); gchar *url, *postdata; GSList *users_to_fetch; gchar *buddy_name; buddy_name = g_strdup(purple_buddy_get_name(buddy)); url = g_strdup_printf("/users/self/contacts/auth-request/%s", purple_url_encode(buddy_name)); postdata = g_strdup_printf("greeting=%s", message ? purple_url_encode(message) : ""); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE); g_free(postdata); g_free(url); // Subscribe to status/message updates users_to_fetch = g_slist_prepend(NULL, buddy_name); skypeweb_subscribe_to_contact_status(sa, users_to_fetch); g_slist_free(users_to_fetch); g_free(buddy_name); } void skypeweb_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { skypeweb_add_buddy_with_invite(gc, buddy, group, _("Please authorize me so I can add you to my buddy list.")); } void skypeweb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) { SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); gchar *url; url = g_strdup_printf("/users/self/contacts/%s", purple_url_encode(purple_buddy_get_name(buddy))); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE); g_free(url); skypeweb_unsubscribe_from_contact_status(sa, purple_buddy_get_name(buddy)); } void skypeweb_buddy_block(PurpleConnection *pc, const char *name) { SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); gchar *url, *postdata; url = g_strdup_printf("/users/self/contacts/%s/block", purple_url_encode(name)); postdata = g_strdup_printf("reporterIp=127.0.0.1&uiVersion=" SKYPEWEB_CLIENTINFO_VERSION "/" SKYPEWEB_CLIENTINFO_NAME); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE); g_free(postdata); g_free(url); } void skypeweb_buddy_unblock(PurpleConnection *pc, const char *name) { SkypeWebAccount *sa = purple_connection_get_protocol_data(pc); gchar *url; url = g_strdup_printf("/users/self/contacts/%s/unblock", purple_url_encode(name)); skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, "", NULL, NULL, TRUE); g_free(url); }