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 #include "skypeweb_messages.h"
20 #include "skypeweb_util.h"
21 #include "skypeweb_connection.h"
22 #include "skypeweb_contacts.h"
23 #include "skypeweb_login.h"
24 
make_last_timestamp_setting(const gchar * convname)25 static GString* make_last_timestamp_setting(const gchar *convname) {
26 	GString *rv = g_string_new(NULL);
27 	g_string_printf(rv, "%s_last_message_timestamp", convname);
28 	return rv;
29 }
30 
31 static gboolean
skypeweb_is_user_self(SkypeWebAccount * sa,const gchar * username)32 skypeweb_is_user_self(SkypeWebAccount *sa, const gchar *username) {
33 	if (!username || *username == 0) {
34 		return FALSE;
35 	}
36 
37 	if (sa->username) {
38 		if (g_str_equal(username, sa->username)) {
39 			return TRUE;
40 		}
41 	}
42 
43 	if (sa->primary_member_name) {
44 		if (g_str_equal(username, sa->primary_member_name)) {
45 			return TRUE;
46 		}
47 	}
48 
49 	return !g_ascii_strcasecmp(username, purple_account_get_username(sa->account));
50 }
51 
52 static gchar *
skypeweb_meify(const gchar * message,gint skypeemoteoffset)53 skypeweb_meify(const gchar *message, gint skypeemoteoffset)
54 {
55 	guint len;
56 	len = strlen(message);
57 
58 	if (skypeemoteoffset <= 0 || skypeemoteoffset >= len)
59 		return g_strdup(message);
60 
61 	return g_strconcat("/me ", message + skypeemoteoffset, NULL);
62 }
63 
64 static void
process_userpresence_resource(SkypeWebAccount * sa,JsonObject * resource)65 process_userpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
66 {
67 	const gchar *selfLink = json_object_get_string_member(resource, "selfLink");
68 	const gchar *status = json_object_get_string_member(resource, "status");
69 	// const gchar *capabilities = json_object_get_string_member(resource, "capabilities");
70 	// const gchar *lastSeenAt = json_object_get_string_member(resource, "lastSeenAt");
71 	const gchar *from;
72 	gboolean is_idle;
73 
74 	from = skypeweb_contact_url_to_name(selfLink);
75 	g_return_if_fail(from);
76 
77 	if (!purple_blist_find_buddy(sa->account, from))
78 	{
79 		PurpleGroup *group = purple_blist_find_group("Skype");
80 		if (!group)
81 		{
82 			group = purple_group_new("Skype");
83 			purple_blist_add_group(group, NULL);
84 		}
85 
86 		if (skypeweb_is_user_self(sa, from)) {
87 			return;
88 		}
89 
90 		purple_blist_add_buddy(purple_buddy_new(sa->account, from, NULL), NULL, group, NULL);
91 	}
92 
93 	// if (g_str_equal(capabilities, "IsMobile")) {  //"Seamless | IsMobile"
94 		// purple_protocol_got_user_status(sa->account, from, "mobile", NULL);
95 	// }
96 
97 	is_idle = purple_strequal(status, SKYPEWEB_STATUS_IDLE);
98 	if (!is_idle) {
99 		purple_protocol_got_user_status(sa->account, from, status, NULL);
100 	} else {
101 		purple_protocol_got_user_status(sa->account, from, SKYPEWEB_STATUS_ONLINE, NULL);
102 	}
103 
104 	purple_protocol_got_user_idle(sa->account, from, is_idle, 0);
105 }
106 
107 // static gboolean
108 // skypeweb_clear_typing_hack(PurpleChatUser *cb)
109 // {
110 	// PurpleChatUserFlags cbflags;
111 
112 	// cbflags = purple_chat_user_get_flags(cb);
113 	// cbflags &= ~PURPLE_CHAT_USER_TYPING & ~PURPLE_CHAT_USER_VOICE;
114 	// purple_chat_user_set_flags(cb, cbflags);
115 
116 	// return FALSE;
117 // }
118 
119 static void
process_message_resource(SkypeWebAccount * sa,JsonObject * resource)120 process_message_resource(SkypeWebAccount *sa, JsonObject *resource)
121 {
122 	const gchar *clientmessageid = NULL;
123 	const gchar *skypeeditedid = NULL;
124 	const gchar *messagetype = json_object_get_string_member(resource, "messagetype");
125 	const gchar *from = json_object_get_string_member(resource, "from");
126 	const gchar *content = NULL;
127 	const gchar *composetime = json_object_get_string_member(resource, "composetime");
128 	const gchar *conversationLink = json_object_get_string_member(resource, "conversationLink");
129 	time_t composetimestamp = purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
130 	gchar **messagetype_parts;
131 	PurpleConversation *conv = NULL;
132 	gchar *convname = NULL;
133 
134 	g_return_if_fail(messagetype != NULL);
135 
136 	messagetype_parts = g_strsplit(messagetype, "/", -1);
137 
138 	if (json_object_has_member(resource, "clientmessageid"))
139 		clientmessageid = json_object_get_string_member(resource, "clientmessageid");
140 
141 	if (clientmessageid && *clientmessageid && g_hash_table_remove(sa->sent_messages_hash, clientmessageid)) {
142 		// We sent this message from here already
143 		g_strfreev(messagetype_parts);
144 		return;
145 	}
146 
147 	if (json_object_has_member(resource, "skypeeditedid"))
148 		skypeeditedid = json_object_get_string_member(resource, "skypeeditedid");
149 	if (json_object_has_member(resource, "content"))
150 		content = json_object_get_string_member(resource, "content");
151 
152 	if (conversationLink && strstr(conversationLink, "/19:")) {
153 		// This is a Thread/Group chat message
154 		const gchar *chatname, *topic;
155 		PurpleChatConversation *chatconv;
156 
157 		chatname = skypeweb_thread_url_to_name(conversationLink);
158 		convname = g_strdup(chatname);
159 		chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
160 		if (!chatconv) {
161 			chatconv = purple_serv_got_joined_chat(sa->pc, g_str_hash(chatname), chatname);
162 			purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "chatname", g_strdup(chatname));
163 
164 			if (json_object_has_member(resource, "threadtopic")) {
165 				topic = json_object_get_string_member(resource, "threadtopic");
166 				purple_chat_conversation_set_topic(chatconv, NULL, topic);
167 			}
168 
169 			skypeweb_get_conversation_history(sa, chatname);
170 			skypeweb_get_thread_users(sa, chatname);
171 		}
172 		GString *chat_last_timestamp = make_last_timestamp_setting(convname);
173 		purple_account_set_int(sa->account, chat_last_timestamp->str, composetimestamp);
174 		g_string_free(chat_last_timestamp, TRUE);
175 
176 		conv = PURPLE_CONVERSATION(chatconv);
177 
178 		if (g_str_equal(messagetype, "Control/Typing")) {
179 			PurpleChatUserFlags cbflags;
180 			PurpleChatUser *cb;
181 
182 			from = skypeweb_contact_url_to_name(from);
183 			if (from == NULL) {
184 				g_strfreev(messagetype_parts);
185 				g_return_if_reached();
186 				return;
187 			}
188 
189 			// typing notification text, not personalized because of multiple "typing" events
190 			if (purple_account_get_bool(sa->account, "show-typing-as-text", FALSE)) {
191 				const gchar *message = g_strdup_printf("%s ...", N_("buddy typing"));
192 				const gchar *last_message = NULL;
193 
194 				// get last message (first in GList)
195 				if (conv && g_list_length(purple_conversation_get_message_history(conv))) {
196 					PurpleMessage *last = g_list_nth_data(g_list_first(purple_conversation_get_message_history(conv)),0);
197 					last_message = purple_message_get_contents(last);
198 				}
199 
200 				// add typing notification to chat
201 				if (last_message && !g_str_equal(last_message, message)) {
202 					PurpleMessage *msg = purple_message_new_system(message, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_ACTIVE_ONLY);
203 					purple_message_set_time(msg, composetimestamp);
204 					purple_conversation_write_message(conv, msg);
205 					purple_message_destroy(msg);
206 				}
207 			}
208 
209 			cb = purple_chat_conversation_find_user(chatconv, from);
210 			if (cb != NULL) {
211 				cbflags = purple_chat_user_get_flags(cb);
212 
213 				cbflags |= PURPLE_CHAT_USER_TYPING;
214 
215 				// typing notification icon
216 				if (purple_account_get_bool(sa->account, "show-typing-as-icon", FALSE)) {
217 					cbflags |= PURPLE_CHAT_USER_VOICE;
218 				}
219 
220 				purple_chat_user_set_flags(cb, cbflags);
221 
222 				//purple_timeout_add_seconds(7, skypeweb_clear_typing_hack, cb);
223 			}
224 
225 		} else if ((g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text"))) {
226 			gchar *html;
227 			gint64 skypeemoteoffset = 0;
228 			PurpleChatUserFlags cbflags;
229 			PurpleChatUser *cb;
230 
231 			if (json_object_has_member(resource, "skypeemoteoffset")) {
232 				skypeemoteoffset = g_ascii_strtoll(json_object_get_string_member(resource, "skypeemoteoffset"), NULL, 10);
233 			}
234 
235 			from = skypeweb_contact_url_to_name(from);
236 			if (from == NULL) {
237 				g_free(messagetype_parts);
238 				g_return_if_reached();
239 				return;
240 			}
241 
242 			// Remove typing notification icon w/o "show-typing-as-icon" option check.
243 			// Hard reset cbflags even if user changed settings while someone typing message.
244 
245 			cb = purple_chat_conversation_find_user(chatconv, from);
246 			if (cb != NULL) {
247 				cbflags = purple_chat_user_get_flags(cb);
248 
249 				cbflags &= ~PURPLE_CHAT_USER_TYPING & ~PURPLE_CHAT_USER_VOICE;
250 
251 				purple_chat_user_set_flags(cb, cbflags);
252 			}
253 
254 			if (content && *content) {
255 				if (g_str_equal(messagetype, "Text")) {
256 					gchar *temp = skypeweb_meify(content, skypeemoteoffset);
257 					html = purple_markup_escape_text(temp, -1);
258 					g_free(temp);
259 				} else {
260 					html = skypeweb_meify(content, skypeemoteoffset);
261 				}
262 
263 				if (skypeeditedid && *skypeeditedid) {
264 					gchar *temp = g_strconcat(_("Edited: "), html, NULL);
265 					g_free(html);
266 					html = temp;
267 				}
268 
269 				purple_serv_got_chat_in(sa->pc, g_str_hash(chatname), from, skypeweb_is_user_self(sa, from) ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV, html, composetimestamp);
270 
271 				g_free(html);
272 			}
273 		} else if (g_str_equal(messagetype, "ThreadActivity/AddMember")) {
274 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
275 			PurpleXmlNode *target;
276 			for(target = purple_xmlnode_get_child(blob, "target"); target;
277 				target = purple_xmlnode_get_next_twin(target))
278 			{
279 				gchar *user = purple_xmlnode_get_data(target);
280 				if (!purple_chat_conversation_find_user(chatconv, &user[2]))
281 					purple_chat_conversation_add_user(chatconv, &user[2], NULL, PURPLE_CHAT_USER_NONE, TRUE);
282 				g_free(user);
283 			}
284 			purple_xmlnode_free(blob);
285 		} else if (g_str_equal(messagetype, "ThreadActivity/DeleteMember")) {
286 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
287 			PurpleXmlNode *target;
288 			for(target = purple_xmlnode_get_child(blob, "target"); target;
289 				target = purple_xmlnode_get_next_twin(target))
290 			{
291 				gchar *user = purple_xmlnode_get_data(target);
292 				if (skypeweb_is_user_self(sa, &user[2]))
293 					purple_chat_conversation_leave(chatconv);
294 				purple_chat_conversation_remove_user(chatconv, &user[2], NULL);
295 				g_free(user);
296 			}
297 			purple_xmlnode_free(blob);
298 		} else if (g_str_equal(messagetype, "ThreadActivity/TopicUpdate")) {
299 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
300 			gchar *initiator = purple_xmlnode_get_data(purple_xmlnode_get_child(blob, "initiator"));
301 			gchar *value = purple_xmlnode_get_data(purple_xmlnode_get_child(blob, "value"));
302 
303 			purple_chat_conversation_set_topic(chatconv, &initiator[2], value);
304 			purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_TOPIC);
305 
306 			g_free(initiator);
307 			g_free(value);
308 			purple_xmlnode_free(blob);
309 		} else if (g_str_equal(messagetype, "ThreadActivity/RoleUpdate")) {
310 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
311 			PurpleXmlNode *target;
312 			PurpleChatUser *cb;
313 
314 			for(target = purple_xmlnode_get_child(blob, "target"); target;
315 				target = purple_xmlnode_get_next_twin(target))
316 			{
317 				gchar *user = purple_xmlnode_get_data(purple_xmlnode_get_child(target, "id"));
318 				gchar *role = purple_xmlnode_get_data(purple_xmlnode_get_child(target, "role"));
319 				PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
320 
321 				if (role && *role) {
322 					if (g_str_equal(role, "Admin") || g_str_equal(role, "admin")) {
323 						cbflags = PURPLE_CHAT_USER_OP;
324 					} else if (g_str_equal(role, "User") || g_str_equal(role, "user")) {
325 						//cbflags = PURPLE_CHAT_USER_VOICE;
326 					}
327 				}
328 				#if !PURPLE_VERSION_CHECK(3, 0, 0)
329 					purple_conv_chat_user_set_flags(chatconv, &user[2], cbflags);
330 					(void) cb;
331 				#else
332 					cb = purple_chat_conversation_find_user(chatconv, &user[2]);
333 					purple_chat_user_set_flags(cb, cbflags);
334 				#endif
335 				g_free(user);
336 				g_free(role);
337 			}
338 
339 			purple_xmlnode_free(blob);
340 		} else if (g_str_equal(messagetype, "RichText/UriObject")) {
341 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
342 			const gchar *uri = purple_xmlnode_get_attrib(blob, "url_thumbnail");
343 
344 			from = skypeweb_contact_url_to_name(from);
345 			g_return_if_fail(from);
346 
347 			skypeweb_download_uri_to_conv(sa, uri, conv, composetimestamp, from);
348 			purple_xmlnode_free(blob);
349 		} else {
350 			purple_debug_warning("skypeweb", "Unhandled thread message resource messagetype '%s'\n", messagetype);
351 		}
352 
353 	} else {
354 		gchar *convbuddyname;
355 		// This is a One-to-one/IM message
356 
357 		convbuddyname = g_strdup(skypeweb_contact_url_to_name(conversationLink));
358 		convname = g_strconcat(skypeweb_user_url_prefix(convbuddyname), convbuddyname, NULL);
359 
360 		from = skypeweb_contact_url_to_name(from);
361 		if (from == NULL) {
362 			g_free(convbuddyname);
363 			g_free(convname);
364 			g_return_if_reached();
365 			return;
366 		}
367 
368 		if (g_str_equal(messagetype_parts[0], "Control")) {
369 			if (g_str_equal(messagetype_parts[1], "ClearTyping")) {
370 				purple_serv_got_typing(sa->pc, from, 7, PURPLE_IM_NOT_TYPING);
371 			} else if (g_str_equal(messagetype_parts[1], "Typing")) {
372 				purple_serv_got_typing(sa->pc, from, 7, PURPLE_IM_TYPING);
373 			}
374 		} else if ((g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text")) && content && *content) {
375 			gchar *html;
376 			gint64 skypeemoteoffset = 0;
377 			PurpleIMConversation *imconv;
378 
379 			if (json_object_has_member(resource, "skypeemoteoffset")) {
380 				skypeemoteoffset = g_ascii_strtoll(json_object_get_string_member(resource, "skypeemoteoffset"), NULL, 10);
381 			}
382 
383 			if (g_str_equal(messagetype, "Text")) {
384 				gchar *temp = skypeweb_meify(content, skypeemoteoffset);
385 				html = purple_markup_escape_text(temp, -1);
386 				g_free(temp);
387 			} else {
388 				html = skypeweb_meify(content, skypeemoteoffset);
389 			}
390 
391 			if (skypeeditedid && *skypeeditedid) {
392 				gchar *temp = g_strconcat(_("Edited: "), html, NULL);
393 				g_free(html);
394 				html = temp;
395 			}
396 
397 			if (json_object_has_member(resource, "imdisplayname")) {
398 				//TODO use this for an alias
399 			}
400 
401 			if (skypeweb_is_user_self(sa, from)) {
402 				if (!g_str_has_prefix(html, "?OTR")) {
403 					PurpleMessage *msg;
404 					imconv = purple_conversations_find_im_with_account(convbuddyname, sa->account);
405 					if (imconv == NULL)
406 					{
407 						imconv = purple_im_conversation_new(sa->account, convbuddyname);
408 					}
409 					conv = PURPLE_CONVERSATION(imconv);
410 
411 					msg = purple_message_new_outgoing(convbuddyname, html, PURPLE_MESSAGE_SEND);
412 					purple_message_set_time(msg, composetimestamp);
413 					purple_conversation_write_message(conv, msg);
414 					purple_message_destroy(msg);
415 				}
416 			} else {
417 				purple_serv_got_im(sa->pc, from, html, PURPLE_MESSAGE_RECV, composetimestamp);
418 
419 				imconv = purple_conversations_find_im_with_account(from, sa->account);
420 				conv = PURPLE_CONVERSATION(imconv);
421 			}
422 			g_free(html);
423 		} else if (g_str_equal(messagetype, "RichText/UriObject")) {
424 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
425 			const gchar *uri = purple_xmlnode_get_attrib(blob, "url_thumbnail");
426 			PurpleIMConversation *imconv;
427 
428 			if (skypeweb_is_user_self(sa, from)) {
429 				from = convbuddyname;
430 			}
431 			if (from != NULL) {
432 				imconv = purple_conversations_find_im_with_account(from, sa->account);
433 				if (imconv == NULL)
434 				{
435 					imconv = purple_im_conversation_new(sa->account, from);
436 				}
437 
438 				conv = PURPLE_CONVERSATION(imconv);
439 				skypeweb_download_uri_to_conv(sa, uri, conv, composetimestamp, from);
440 			}
441 			purple_xmlnode_free(blob);
442 		} else if (g_str_equal(messagetype, "RichText/Media_GenericFile")) {
443 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
444 			const gchar *uri = purple_xmlnode_get_attrib(blob, "uri");
445 
446 			if (!skypeweb_is_user_self(sa, from)) {
447 
448 				skypeweb_present_uri_as_filetransfer(sa, uri, from);
449 
450 				from = convbuddyname;
451 			}
452 			purple_xmlnode_free(blob);
453 		} else if (g_str_equal(messagetype, "Event/SkypeVideoMessage")) {
454 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
455 			const gchar *sid = purple_xmlnode_get_attrib(blob, "sid");
456 			PurpleIMConversation *imconv;
457 
458 			if (skypeweb_is_user_self(sa, from)) {
459 				from = convbuddyname;
460 			}
461 			if (from != NULL) {
462 				imconv = purple_conversations_find_im_with_account(from, sa->account);
463 				if (imconv == NULL)
464 				{
465 					imconv = purple_im_conversation_new(sa->account, from);
466 				}
467 
468 				conv = PURPLE_CONVERSATION(imconv);
469 				//skypeweb_download_video_message(sa, sid, conv); //TODO
470 				(void) sid;
471 				purple_serv_got_im(sa->pc, from, content, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
472 			}
473 			purple_xmlnode_free(blob);
474 		} else if (g_str_equal(messagetype, "Event/Call")) {
475 			PurpleXmlNode *partlist = purple_xmlnode_from_str(content, -1);
476 			const gchar *partlisttype = purple_xmlnode_get_attrib(partlist, "type");
477 			const gchar *message = NULL;
478 			PurpleIMConversation *imconv;
479 			gboolean incoming = TRUE;
480 
481 			if (skypeweb_is_user_self(sa, from)) {
482 				incoming = FALSE;
483 				(void) incoming;
484 				from = convbuddyname;
485 			}
486 
487 			if (from != NULL) {
488 				if (partlisttype) {
489 					imconv = purple_conversations_find_im_with_account(from, sa->account);
490 					if (imconv == NULL)
491 					{
492 						imconv = purple_im_conversation_new(sa->account, from);
493 					}
494 
495 					conv = PURPLE_CONVERSATION(imconv);
496 					if (g_str_equal(partlisttype, "started")) {
497 						message = _("Call started");
498 					} else if (g_str_equal(partlisttype, "ended")) {
499 						PurpleXmlNode *part;
500 						gint duration_int = -1;
501 
502 						for(part = purple_xmlnode_get_child(partlist, "part");
503 							part;
504 							part = purple_xmlnode_get_next_twin(part))
505 						{
506 							const gchar *identity = purple_xmlnode_get_attrib(part, "identity");
507 							if (identity && skypeweb_is_user_self(sa, identity)) {
508 								PurpleXmlNode *duration = purple_xmlnode_get_child(part, "duration");
509 								if (duration != NULL) {
510 									gchar *duration_str;
511 									duration_str = purple_xmlnode_get_data(duration);
512 									duration_int = atoi(duration_str);
513 									break;
514 								}
515 							}
516 						}
517 						if (duration_int < 0) {
518 							message = _("Call missed");
519 						} else {
520 							//TODO report how long the call was
521 							message = _("Call ended");
522 						}
523 					}
524 				}
525 				else {
526 					message = _("Unsupported call received");
527 				}
528 
529 				purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
530 			}
531 
532 			purple_xmlnode_free(partlist);
533 		} else if (g_str_equal(messagetype, "Signal/Flamingo")) {
534 			const gchar *message = NULL;
535 
536 			if (skypeweb_is_user_self(sa, from)) {
537 				from = convbuddyname;
538 			}
539 
540 			if (from != NULL) {
541 				message = _("Unsupported call received");
542 
543 				purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
544 			}
545 		} else if (g_str_equal(messagetype, "RichText/Contacts")) {
546 			PurpleXmlNode *contacts = purple_xmlnode_from_str(content, -1);
547 			PurpleXmlNode *contact;
548 
549 			for(contact = purple_xmlnode_get_child(contacts, "c"); contact;
550 				contact = purple_xmlnode_get_next_twin(contact))
551 			{
552 				const gchar *contact_id = purple_xmlnode_get_attrib(contact, "s");
553 				const gchar *contact_name = purple_xmlnode_get_attrib(contact, "f");
554 
555 				gchar *message = g_strdup_printf(_("The user sent a contact: %s (%s)"), contact_id, contact_name);
556 
557 				purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
558 
559 				g_free(message);
560 			}
561 
562 			skypeweb_received_contacts(sa, contacts);
563 			purple_xmlnode_free(contacts);
564 		} else if (g_str_equal(messagetype, "RichText/Media_FlikMsg")) {
565 
566 
567 			PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
568 
569 			const gchar *url_thumbnail = purple_xmlnode_get_attrib(blob, "url_thumbnail");
570 			gchar *text = purple_markup_strip_html(content); //purple_xmlnode_get_data_unescaped doesn't work properly in this situation
571 
572 			PurpleIMConversation *imconv;
573 
574 			if (skypeweb_is_user_self(sa, from)) {
575 				from = convbuddyname;
576 			}
577 			if (from != NULL) {
578 				imconv = purple_conversations_find_im_with_account(from, sa->account);
579 				if (imconv == NULL) {
580 					imconv = purple_im_conversation_new(sa->account, from);
581 				}
582 
583 				conv = PURPLE_CONVERSATION(imconv);
584 
585 				skypeweb_download_moji_to_conv(sa, text, url_thumbnail, conv, composetimestamp, from);
586 
587 				const gchar *message = _("The user sent a Moji");
588 
589 				purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_NO_LOG, composetimestamp);
590 
591 				g_free(text);
592 			}
593 
594 			purple_xmlnode_free(blob);
595 		} else if (g_str_equal(messagetype, "RichText/Files")) {
596 			purple_serv_got_im(sa->pc, convbuddyname, _("The user sent files in an unsupported way"), PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_ERROR, composetimestamp);
597 		} else {
598 			purple_debug_warning("skypeweb", "Unhandled message resource messagetype '%s'\n", messagetype);
599 		}
600 
601 		g_free(convbuddyname);
602 	}
603 
604 	if (conv != NULL) {
605 		const gchar *id = json_object_get_string_member(resource, "id");
606 
607 		g_free(purple_conversation_get_data(conv, "last_skypeweb_id"));
608 
609 		if (purple_conversation_has_focus(conv)) {
610 			// Mark message as seen straight away
611 			gchar *post, *url;
612 
613 			url = g_strdup_printf("/v1/users/ME/conversations/%s/properties?name=consumptionhorizon", purple_url_encode(convname));
614 			post = g_strdup_printf("{\"consumptionhorizon\":\"%s;%" G_GINT64_FORMAT ";%s\"}", id ? id : "", skypeweb_get_js_time(), id ? id : "");
615 
616 			skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
617 
618 			g_free(post);
619 			g_free(url);
620 
621 			purple_conversation_set_data(conv, "last_skypeweb_id", NULL);
622 		} else {
623 			purple_conversation_set_data(conv, "last_skypeweb_id", g_strdup(id));
624 		}
625 	}
626 
627 	g_free(convname);
628 	g_strfreev(messagetype_parts);
629 
630 	if (composetimestamp > purple_account_get_int(sa->account, "last_message_timestamp", 0))
631 		purple_account_set_int(sa->account, "last_message_timestamp", composetimestamp);
632 }
633 
634 static void
process_conversation_resource(SkypeWebAccount * sa,JsonObject * resource)635 process_conversation_resource(SkypeWebAccount *sa, JsonObject *resource)
636 {
637 	const gchar *id = json_object_get_string_member(resource, "id");
638 	(void) id;
639 
640 	JsonObject *threadProperties;
641 
642 	if (json_object_has_member(resource, "threadProperties")) {
643 		threadProperties = json_object_get_object_member(resource, "threadProperties");
644 	}
645 	(void) threadProperties;
646 }
647 
648 static void
process_thread_resource(SkypeWebAccount * sa,JsonObject * resource)649 process_thread_resource(SkypeWebAccount *sa, JsonObject *resource)
650 {
651 
652 }
653 
654 static void
process_endpointpresence_resource(SkypeWebAccount * sa,JsonObject * resource)655 process_endpointpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
656 {
657 	JsonObject *publicInfo = json_object_get_object_member(resource, "publicInfo");
658 	if (publicInfo != NULL) {
659 		const gchar *typ_str = json_object_get_string_member(publicInfo, "typ");
660 		const gchar *skypeNameVersion = json_object_get_string_member(publicInfo, "skypeNameVersion");
661 
662 		if (typ_str && *typ_str) {
663 			if (g_str_equal(typ_str, "website")) {
664 
665 			} else {
666 				gint typ = atoi(typ_str);
667 				switch(typ) {
668 					case 17: //Android
669 						break;
670 					case 16: //iOS
671 						break;
672 					case 12: //WinRT/Metro
673 						break;
674 					case 15: //Winphone
675 						break;
676 					case 13: //OSX
677 						break;
678 					case 11: //Windows
679 						break;
680 					case 14: //Linux
681 						break;
682 					case 10: //XBox ? skypeNameVersion 11/1.8.0.1006
683 						break;
684 					case 1:  //SkypeWeb
685 						break;
686 					default:
687 						purple_debug_warning("skypeweb", "Unknown typ %d: %s\n", typ, skypeNameVersion ? skypeNameVersion : "");
688 						break;
689 				}
690 			}
691 		}
692 	}
693 }
694 
695 gboolean
skypeweb_timeout(gpointer userdata)696 skypeweb_timeout(gpointer userdata)
697 {
698 	SkypeWebAccount *sa = userdata;
699 	skypeweb_poll(sa);
700 
701 	// If no response within 3 minutes, assume connection lost and try again
702 	g_source_remove(sa->watchdog_timeout);
703 	sa->watchdog_timeout = g_timeout_add_seconds(3 * 60, skypeweb_timeout, sa);
704 
705 	return FALSE;
706 }
707 
708 static void
skypeweb_poll_cb(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)709 skypeweb_poll_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
710 {
711 	JsonArray *messages = NULL;
712 	gint index, length;
713 	JsonObject *obj = NULL;
714 
715 
716 	if (((int)time(NULL)) > sa->vdms_expiry) {
717 		skypeweb_get_vdms_token(sa);
718 	}
719 
720 	if (node == NULL && ((int)time(NULL)) > sa->registration_expiry) {
721 		skypeweb_get_registration_token(sa);
722 		return;
723 	}
724 
725 
726 	if (node != NULL && json_node_get_node_type(node) == JSON_NODE_OBJECT)
727 		obj = json_node_get_object(node);
728 
729 	if (obj != NULL) {
730 		if (json_object_has_member(obj, "eventMessages"))
731 			messages = json_object_get_array_member(obj, "eventMessages");
732 
733 		if (messages != NULL) {
734 			length = json_array_get_length(messages);
735 			for(index = length - 1; index >= 0; index--)
736 			{
737 				JsonObject *message = json_array_get_object_element(messages, index);
738 				const gchar *resourceType = json_object_get_string_member(message, "resourceType");
739 				JsonObject *resource = json_object_get_object_member(message, "resource");
740 
741 				if (purple_strequal(resourceType, "NewMessage"))
742 				{
743 					process_message_resource(sa, resource);
744 				} else if (purple_strequal(resourceType, "UserPresence"))
745 				{
746 					process_userpresence_resource(sa, resource);
747 				} else if (purple_strequal(resourceType, "EndpointPresence"))
748 				{
749 					process_endpointpresence_resource(sa, resource);
750 				} else if (purple_strequal(resourceType, "ConversationUpdate"))
751 				{
752 					process_conversation_resource(sa, resource);
753 				} else if (purple_strequal(resourceType, "ThreadUpdate"))
754 				{
755 					process_thread_resource(sa, resource);
756 				}
757 			}
758 		} else if (json_object_has_member(obj, "errorCode")) {
759 			gint64 errorCode = json_object_get_int_member(obj, "errorCode");
760 
761 			if (errorCode == 729) {
762 				// "You must create an endpoint before performing this operation"
763 				// Dammit, Jim; I'm a programmer, not a surgeon!
764 				skypeweb_get_registration_token(sa);
765 				return;
766 			} else if (errorCode == 450) {
767 				// "Subscription requested could not be found."
768 				// No more Womens Weekly? :O
769 			}
770 		}
771 
772 		//TODO record id of highest recieved id to make sure we dont process the same id twice
773 	}
774 
775 	if (!purple_connection_is_disconnecting(sa->pc)) {
776 		sa->poll_timeout = g_timeout_add_seconds(1, skypeweb_timeout, sa);
777 	}
778 }
779 
780 void
skypeweb_poll(SkypeWebAccount * sa)781 skypeweb_poll(SkypeWebAccount *sa)
782 {
783 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/subscriptions/0/poll", NULL, skypeweb_poll_cb, NULL, TRUE);
784 }
785 
786 void
skypeweb_mark_conv_seen(PurpleConversation * conv,PurpleConversationUpdateType type)787 skypeweb_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type)
788 {
789 	PurpleConnection *pc = purple_conversation_get_connection(conv);
790 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
791 		return;
792 
793 	if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), SKYPEWEB_PLUGIN_ID))
794 		return;
795 
796 	if (type == PURPLE_CONVERSATION_UPDATE_UNSEEN) {
797 		gchar *last_skypeweb_id = purple_conversation_get_data(conv, "last_skypeweb_id");
798 
799 		if (last_skypeweb_id && *last_skypeweb_id) {
800 			SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
801 			gchar *post, *url, *convname;
802 
803 			if (PURPLE_IS_IM_CONVERSATION(conv)) {
804 				const gchar *buddyname = purple_conversation_get_name(conv);
805 				convname = g_strconcat(skypeweb_user_url_prefix(buddyname), buddyname, NULL);
806 			} else {
807 				convname = g_strdup(purple_conversation_get_data(conv, "chatname"));
808 			}
809 
810 			url = g_strdup_printf("/v1/users/ME/conversations/%s/properties?name=consumptionhorizon", purple_url_encode(convname));
811 			post = g_strdup_printf("{\"consumptionhorizon\":\"%s;%" G_GINT64_FORMAT ";%s\"}", last_skypeweb_id, skypeweb_get_js_time(), last_skypeweb_id);
812 
813 			skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
814 
815 			g_free(convname);
816 			g_free(post);
817 			g_free(url);
818 
819 			g_free(last_skypeweb_id);
820 			purple_conversation_set_data(conv, "last_skypeweb_id", NULL);
821 		}
822 	}
823 }
824 
825 static void
skypeweb_got_thread_users(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)826 skypeweb_got_thread_users(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
827 {
828 	PurpleChatConversation *chatconv;
829 	gchar *chatname = user_data;
830 	JsonObject *response;
831 	JsonArray *members;
832 	gint length, index;
833 
834 	chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
835 	if (chatconv == NULL)
836 		return;
837 	purple_chat_conversation_clear_users(chatconv);
838 
839 	if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
840 		return;
841 	response = json_node_get_object(node);
842 
843 	members = json_object_get_array_member(response, "members");
844 	length = json_array_get_length(members);
845 	for(index = length - 1; index >= 0; index--)
846 	{
847 		JsonObject *member = json_array_get_object_element(members, index);
848 		const gchar *userLink = json_object_get_string_member(member, "userLink");
849 		const gchar *role = json_object_get_string_member(member, "role");
850 		const gchar *username = skypeweb_contact_url_to_name(userLink);
851 		PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
852 
853 		if (role && *role) {
854 			if (g_str_equal(role, "Admin") || g_str_equal(role, "admin")) {
855 				cbflags = PURPLE_CHAT_USER_OP;
856 			} else if (g_str_equal(role, "User") || g_str_equal(role, "user")) {
857 				//cbflags = PURPLE_CHAT_USER_VOICE;
858 			}
859 		}
860 
861 		if (username == NULL && json_object_has_member(member, "linkedMri")) {
862 			username = skypeweb_contact_url_to_name(json_object_get_string_member(member, "linkedMri"));
863 		}
864 		if (username != NULL) {
865 			purple_chat_conversation_add_user(chatconv, username, NULL, cbflags, FALSE);
866 		}
867 	}
868 }
869 
870 void
skypeweb_get_thread_users(SkypeWebAccount * sa,const gchar * convname)871 skypeweb_get_thread_users(SkypeWebAccount *sa, const gchar *convname)
872 {
873 	gchar *url;
874 	url = g_strdup_printf("/v1/threads/%s?view=msnp24Equivalent", purple_url_encode(convname));
875 
876 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_thread_users, g_strdup(convname), TRUE);
877 
878 	g_free(url);
879 }
880 
881 static void
skypeweb_got_conv_history(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)882 skypeweb_got_conv_history(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
883 {
884 	gint since = GPOINTER_TO_INT(user_data);
885 	JsonObject *obj;
886 	JsonArray *messages;
887 	gint index, length;
888 
889 	if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
890 		return;
891 	obj = json_node_get_object(node);
892 
893 	messages = json_object_get_array_member(obj, "messages");
894 	length = json_array_get_length(messages);
895 	for(index = length - 1; index >= 0; index--)
896 	{
897 		JsonObject *message = json_array_get_object_element(messages, index);
898 		const gchar *composetime = json_object_get_string_member(message, "composetime");
899 		gint composetimestamp = (gint) purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
900 
901 		if (composetimestamp > since) {
902 			process_message_resource(sa, message);
903 		}
904 	}
905 }
906 
907 void
skypeweb_get_conversation_history_since(SkypeWebAccount * sa,const gchar * convname,gint since)908 skypeweb_get_conversation_history_since(SkypeWebAccount *sa, const gchar *convname, gint since)
909 {
910 	gchar *url;
911 	url = g_strdup_printf("/v1/users/ME/conversations/%s/messages?startTime=%d000&pageSize=30&view=msnp24Equivalent&targetType=Passport|Skype|Lync|Thread|PSTN|Agent", purple_url_encode(convname), since);
912 
913 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_conv_history, GINT_TO_POINTER(since), TRUE);
914 
915 	g_free(url);
916 }
917 
918 void
skypeweb_get_conversation_history(SkypeWebAccount * sa,const gchar * convname)919 skypeweb_get_conversation_history(SkypeWebAccount *sa, const gchar *convname)
920 {
921 	GString *timestamp_key = make_last_timestamp_setting(convname);
922 	skypeweb_get_conversation_history_since(sa, convname, purple_account_get_int(sa->account, timestamp_key->str, 0));
923 	g_string_free(timestamp_key, TRUE);
924 }
925 
926 static void
skypeweb_got_all_convs(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)927 skypeweb_got_all_convs(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
928 {
929 	gint since = GPOINTER_TO_INT(user_data);
930 	JsonObject *obj;
931 	JsonArray *conversations;
932 	gint index, length;
933 
934 	if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
935 		return;
936 	obj = json_node_get_object(node);
937 
938 	conversations = json_object_get_array_member(obj, "conversations");
939 	length = json_array_get_length(conversations);
940 	for(index = 0; index < length; index++) {
941 		JsonObject *conversation = json_array_get_object_element(conversations, index);
942 		const gchar *id = json_object_get_string_member(conversation, "id");
943 		JsonObject *lastMessage = json_object_get_object_member(conversation, "lastMessage");
944 		if (lastMessage != NULL && json_object_has_member(lastMessage, "composetime")) {
945 			const gchar *composetime = json_object_get_string_member(lastMessage, "composetime");
946 			gint composetimestamp = (gint) purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
947 
948 			if (composetimestamp > since) {
949 				skypeweb_get_conversation_history_since(sa, id, since);
950 			}
951 		}
952 	}
953 }
954 
955 void
skypeweb_get_all_conversations_since(SkypeWebAccount * sa,gint since)956 skypeweb_get_all_conversations_since(SkypeWebAccount *sa, gint since)
957 {
958 	gchar *url;
959 	url = g_strdup_printf("/v1/users/ME/conversations?startTime=%d000&pageSize=100&view=msnp24Equivalent&targetType=Passport|Skype|Lync|Thread|PSTN|Agent", since);
960 
961 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_all_convs, GINT_TO_POINTER(since), TRUE);
962 
963 	g_free(url);
964 }
965 
966 void
skype_web_get_offline_history(SkypeWebAccount * sa)967 skype_web_get_offline_history(SkypeWebAccount *sa)
968 {
969 	skypeweb_get_all_conversations_since(sa, purple_account_get_int(sa->account, "last_message_timestamp", ((gint) time(NULL))));
970 }
971 
972 
973 static void
skypeweb_got_roomlist_threads(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)974 skypeweb_got_roomlist_threads(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
975 {
976 	PurpleRoomlist *roomlist = user_data;
977 	JsonObject *obj;
978 	JsonArray *conversations;
979 	gint index, length;
980 
981 	if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
982 		return;
983 	obj = json_node_get_object(node);
984 
985 	conversations = json_object_get_array_member(obj, "conversations");
986 	length = json_array_get_length(conversations);
987 	for(index = 0; index < length; index++) {
988 		JsonObject *conversation = json_array_get_object_element(conversations, index);
989 		const gchar *id = json_object_get_string_member(conversation, "id");
990 		PurpleRoomlistRoom *room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, id, NULL);
991 
992 		purple_roomlist_room_add_field(roomlist, room, id);
993 		if (json_object_has_member(conversation, "threadProperties")) {
994 			JsonObject *threadProperties = json_object_get_object_member(conversation, "threadProperties");
995 			if (threadProperties != NULL) {
996 				const gchar *num_members = json_object_get_string_member(threadProperties, "membercount");
997 				purple_roomlist_room_add_field(roomlist, room, num_members);
998 				const gchar *topic = json_object_get_string_member(threadProperties, "topic");
999 				purple_roomlist_room_add_field(roomlist, room, topic);
1000 			}
1001 		}
1002 		purple_roomlist_room_add(roomlist, room);
1003 	}
1004 
1005 	purple_roomlist_set_in_progress(roomlist, FALSE);
1006 }
1007 
1008 PurpleRoomlist *
skypeweb_roomlist_get_list(PurpleConnection * pc)1009 skypeweb_roomlist_get_list(PurpleConnection *pc)
1010 {
1011 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1012 	const gchar *url = "/v1/users/ME/conversations?startTime=0&pageSize=100&view=msnp24Equivalent&targetType=Thread";
1013 	PurpleRoomlist *roomlist;
1014 	GList *fields = NULL;
1015 	PurpleRoomlistField *f;
1016 
1017 	roomlist = purple_roomlist_new(sa->account);
1018 
1019 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("ID"), "chatname", TRUE);
1020 	fields = g_list_append(fields, f);
1021 
1022 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Users"), "users", FALSE);
1023 	fields = g_list_append(fields, f);
1024 
1025 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
1026 	fields = g_list_append(fields, f);
1027 
1028 	purple_roomlist_set_fields(roomlist, fields);
1029 	purple_roomlist_set_in_progress(roomlist, TRUE);
1030 
1031 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_roomlist_threads, roomlist, FALSE);
1032 
1033 	return roomlist;
1034 }
1035 
1036 void
skypeweb_unsubscribe_from_contact_status(SkypeWebAccount * sa,const gchar * who)1037 skypeweb_unsubscribe_from_contact_status(SkypeWebAccount *sa, const gchar *who)
1038 {
1039 	const gchar *contacts_url = "/v1/users/ME/contacts";
1040 	gchar *url;
1041 
1042 	url = g_strconcat(contacts_url, "/", skypeweb_user_url_prefix(who), purple_url_encode(who), NULL);
1043 
1044 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, NULL, NULL, TRUE);
1045 
1046 	g_free(url);
1047 }
1048 
1049 static void
skypeweb_got_contact_status(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1050 skypeweb_got_contact_status(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1051 {
1052 	JsonObject *obj = json_node_get_object(node);
1053 	JsonArray *responses = json_object_get_array_member(obj, "Responses");
1054 
1055 	if (responses != NULL) {
1056 		guint length = json_array_get_length(responses);
1057 		gint index;
1058 		for(index = length - 1; index >= 0; index--)
1059 		{
1060 			JsonObject *response = json_array_get_object_element(responses, index);
1061 			JsonObject *payload = json_object_get_object_member(response, "Payload");
1062 			process_userpresence_resource(sa, payload);
1063 		}
1064 	}
1065 }
1066 
1067 static void
skypeweb_lookup_contact_status(SkypeWebAccount * sa,const gchar * contact)1068 skypeweb_lookup_contact_status(SkypeWebAccount *sa, const gchar *contact)
1069 {
1070 	if (contact == NULL) {
1071 		return;
1072 	}
1073 
1074 	// Allowed to be up to 10 at once
1075 	gchar *url = g_strdup_printf("/v1/users/ME/contacts/ALL/presenceDocs/messagingService?cMri=%s%s", skypeweb_user_url_prefix(contact), purple_url_encode(contact));
1076 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_contact_status, NULL, TRUE);
1077 
1078 	g_free(url);
1079 }
1080 
1081 void
skypeweb_subscribe_to_contact_status(SkypeWebAccount * sa,GSList * contacts)1082 skypeweb_subscribe_to_contact_status(SkypeWebAccount *sa, GSList *contacts)
1083 {
1084 	const gchar *contacts_url = "/v1/users/ME/contacts";
1085 	gchar *post;
1086 	GSList *cur = contacts;
1087 	JsonObject *obj;
1088 	JsonArray *contacts_array;
1089 	guint count = 0;
1090 
1091 	if (contacts == NULL)
1092 		return;
1093 
1094 	obj = json_object_new();
1095 	contacts_array = json_array_new();
1096 
1097 	JsonArray *interested = json_array_new();
1098 	json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
1099 	json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
1100 	json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
1101 	json_array_add_string_element(interested, "/v1/threads/ALL");
1102 
1103 	do {
1104 		JsonObject *contact;
1105 		gchar *id;
1106 
1107 		if (SKYPEWEB_BUDDY_IS_BOT(cur->data)) {
1108 			purple_protocol_got_user_status(sa->account, cur->data, SKYPEWEB_STATUS_ONLINE, NULL);
1109 			continue;
1110 		}
1111 
1112 		contact = json_object_new();
1113 
1114 		id = g_strconcat(skypeweb_user_url_prefix(cur->data), cur->data, NULL);
1115 		json_object_set_string_member(contact, "id", id);
1116 		json_array_add_object_element(contacts_array, contact);
1117 
1118 		if (id && id[0] == '8') {
1119 			gchar *contact_url = g_strconcat("/v1/users/ME/contacts/", id, NULL);
1120 			json_array_add_string_element(interested, contact_url);
1121 			g_free(contact_url);
1122 		}
1123 
1124 		g_free(id);
1125 
1126 		if (count++ >= 100) {
1127 			// Send off the current batch and continue
1128 			json_object_set_array_member(obj, "contacts", contacts_array);
1129 			post = skypeweb_jsonobj_to_string(obj);
1130 
1131 			skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, contacts_url, post, NULL, NULL, TRUE);
1132 
1133 			g_free(post);
1134 			json_object_unref(obj);
1135 
1136 			obj = json_object_new();
1137 			contacts_array = json_array_new();
1138 			count = 0;
1139 		}
1140 	} while((cur = g_slist_next(cur)));
1141 
1142 	json_object_set_array_member(obj, "contacts", contacts_array);
1143 	post = skypeweb_jsonobj_to_string(obj);
1144 
1145 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, contacts_url, post, NULL, NULL, TRUE);
1146 
1147 	g_free(post);
1148 	json_object_unref(obj);
1149 
1150 
1151 	gchar *url = g_strdup_printf("/v1/users/ME/endpoints/%s/subscriptions/0?name=interestedResources", purple_url_encode(sa->endpoint));
1152 
1153 	obj = json_object_new();
1154 	json_object_set_array_member(obj, "interestedResources", interested);
1155 
1156 	skypeweb_lookup_contact_status(sa, NULL);
1157 	post = skypeweb_jsonobj_to_string(obj);
1158 
1159 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
1160 
1161 	g_free(url);
1162 	g_free(post);
1163 	json_object_unref(obj);
1164 }
1165 
1166 
1167 static void
skypeweb_subscribe_cb(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1168 skypeweb_subscribe_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1169 {
1170 	skypeweb_do_all_the_things(sa);
1171 }
1172 
1173 static void
skypeweb_subscribe(SkypeWebAccount * sa)1174 skypeweb_subscribe(SkypeWebAccount *sa)
1175 {
1176 	JsonObject *obj;
1177 	JsonArray *interested;
1178 	gchar *post;
1179 
1180 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/properties?name=supportsMessageProperties", "{\"supportsMessageProperties\":true}", NULL, NULL, TRUE);
1181 
1182 	interested = json_array_new();
1183 	json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
1184 	json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
1185 	json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
1186 	json_array_add_string_element(interested, "/v1/threads/ALL");
1187 
1188 	obj = json_object_new();
1189 	json_object_set_array_member(obj, "interestedResources", interested);
1190 	json_object_set_string_member(obj, "template", "raw");
1191 	json_object_set_string_member(obj, "channelType", "httpLongPoll");
1192 
1193 	post = skypeweb_jsonobj_to_string(obj);
1194 
1195 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/subscriptions", post, skypeweb_subscribe_cb, NULL, TRUE);
1196 
1197 	g_free(post);
1198 	json_object_unref(obj);
1199 }
1200 
1201 static void
skypeweb_got_registration_token(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)1202 skypeweb_got_registration_token(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
1203 {
1204 	const gchar *registration_token = NULL;
1205 	gchar *endpointId = NULL;
1206 	gchar *expires = NULL;
1207 	SkypeWebAccount *sa = user_data;
1208 	gchar *new_messages_host = NULL;
1209 	const gchar *data;
1210 	gsize len;
1211 
1212 	data = purple_http_response_get_data(response, &len);
1213 
1214 	if (data == NULL) {
1215 		if (purple_major_version == 2 && (
1216 			purple_minor_version < 10 ||
1217 			(purple_minor_version == 10 && purple_micro_version < 11))
1218 			) {
1219 			purple_connection_error (sa->pc,
1220 									PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
1221 									_("Your version of libpurple is too old.\nUpgrade to 2.10.11 or newer and try again."));
1222 			return;
1223 		}
1224 	}
1225 
1226 	new_messages_host = skypeweb_string_get_chunk(purple_http_response_get_header(response, "Location"), -1, "https://", "/");
1227 	if (new_messages_host != NULL && !g_str_equal(sa->messages_host, new_messages_host)) {
1228 		g_free(sa->messages_host);
1229 		sa->messages_host = new_messages_host;
1230 
1231 		// Your princess is in another castle
1232 		purple_debug_info("skypeweb", "Messages host has changed to %s\n", sa->messages_host);
1233 
1234 		skypeweb_get_registration_token(sa);
1235 		return;
1236 	}
1237 	g_free(new_messages_host);
1238 
1239 	registration_token = purple_http_response_get_header(response, "Set-RegistrationToken");
1240 
1241 	if (registration_token == NULL) {
1242 		if (purple_account_get_string(sa->account, "refresh-token", NULL)) {
1243 			skypeweb_refresh_token_login(sa);
1244 		} else {
1245 			purple_connection_error (sa->pc,
1246 									PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1247 									_("Failed getting Registration Token"));
1248 		}
1249 		return;
1250 	}
1251 	//purple_debug_info("skypeweb", "New RegistrationToken is %s\n", registration_token);
1252 	endpointId = skypeweb_string_get_chunk(registration_token, -1, "endpointId=", NULL);
1253 	expires = skypeweb_string_get_chunk(registration_token, -1, "expires=", ";");
1254 
1255 	g_free(sa->registration_token); sa->registration_token = g_strndup(registration_token, strchr(registration_token, ';') - registration_token);
1256 	g_free(sa->endpoint); sa->endpoint = endpointId;
1257 	if (expires && *expires) {
1258 		sa->registration_expiry = atoi(expires);
1259 		g_free(expires);
1260 	}
1261 
1262 	if (sa->endpoint) {
1263 		gchar *url = g_strdup_printf("/v1/users/ME/endpoints/%s/presenceDocs/messagingService", purple_url_encode(sa->endpoint));
1264 		const gchar *post = "{\"id\":\"messagingService\", \"type\":\"EndpointPresenceDoc\", \"selfLink\":\"uri\", \"privateInfo\":{\"epname\":\"skype\"}, \"publicInfo\":{\"capabilities\":\"\", \"type\":1, \"typ\":1, \"skypeNameVersion\":\"" SKYPEWEB_CLIENTINFO_VERSION "/" SKYPEWEB_CLIENTINFO_NAME "\", \"nodeInfo\":\"\", \"version\":\"" SKYPEWEB_CLIENTINFO_VERSION "\"}}";
1265 		skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
1266 		g_free(url);
1267 	}
1268 
1269 	skypeweb_gather_self_properties(sa);
1270 	skypeweb_subscribe(sa);
1271 }
1272 
1273 static void
skypeweb_got_vdms_token(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)1274 skypeweb_got_vdms_token(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
1275 {
1276 	const gchar *token;
1277 	SkypeWebAccount *sa = user_data;
1278 	JsonParser *parser = json_parser_new();
1279 	const gchar *data;
1280 	gsize len;
1281 
1282 	data = purple_http_response_get_data(response, &len);
1283 
1284 	if (json_parser_load_from_data(parser, data, len, NULL)) {
1285 		JsonNode *root = json_parser_get_root(parser);
1286 		JsonObject *obj = json_node_get_object(root);
1287 
1288 		token = json_object_get_string_member(obj, "token");
1289 		g_free(sa->vdms_token);
1290 		sa->vdms_token = g_strdup(token);
1291 		sa->vdms_expiry = (int)time(NULL) + SKYPEWEB_VDMS_TTL;
1292 	}
1293 
1294 	g_object_unref(parser);
1295 
1296 }
1297 
1298 void
skypeweb_get_registration_token(SkypeWebAccount * sa)1299 skypeweb_get_registration_token(SkypeWebAccount *sa)
1300 {
1301 	gchar *messages_url;
1302 	PurpleHttpRequest *request;
1303 	gchar *curtime;
1304 	gchar *response;
1305 
1306 	g_free(sa->registration_token); sa->registration_token = NULL;
1307 	g_free(sa->endpoint); sa->endpoint = NULL;
1308 
1309 	curtime = g_strdup_printf("%d", (int) time(NULL));
1310 	response = skypeweb_hmac_sha256(curtime);
1311 
1312 	messages_url = g_strdup_printf("https://%s/v1/users/ME/endpoints", sa->messages_host);
1313 
1314 	request = purple_http_request_new(messages_url);
1315 	purple_http_request_set_method(request, "POST");
1316 	purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
1317 	purple_http_request_set_max_redirects(request, 0);
1318 	purple_http_request_header_set(request, "Accept", "*/*");
1319 	purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
1320 	purple_http_request_header_set_printf(request, "LockAndKey", "appId=" SKYPEWEB_LOCKANDKEY_APPID "; time=%s; lockAndKeyResponse=%s", curtime, response);
1321 	purple_http_request_header_set(request, "ClientInfo", "os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=" SKYPEWEB_CLIENTINFO_NAME "; clientVer=" SKYPEWEB_CLIENTINFO_VERSION);
1322 	purple_http_request_header_set(request, "Content-Type", "application/json");
1323 	purple_http_request_header_set_printf(request, "Authentication", "skypetoken=%s", sa->skype_token);
1324 	purple_http_request_set_contents(request, "{\"endpointFeatures\":\"Agent\"}", -1);
1325 	purple_http_request(sa->pc, request, skypeweb_got_registration_token, sa);
1326 	purple_http_request_unref(request);
1327 
1328 	g_free(curtime);
1329 	g_free(response);
1330 	g_free(messages_url);
1331 }
1332 
1333 void
skypeweb_get_vdms_token(SkypeWebAccount * sa)1334 skypeweb_get_vdms_token(SkypeWebAccount *sa)
1335 {
1336 	const gchar *messages_url = "https://" SKYPEWEB_STATIC_HOST "/pes/v1/petoken";
1337 	PurpleHttpRequest *request;
1338 
1339 	request = purple_http_request_new(messages_url);
1340 	purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
1341 	purple_http_request_header_set(request, "Accept", "*/*");
1342 	purple_http_request_header_set(request, "Origin", "https://web.skype.com");
1343 	purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token);
1344 	purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
1345 	purple_http_request_set_contents(request, "{}", -1);
1346 	purple_http_request(sa->pc, request, skypeweb_got_vdms_token, sa);
1347 	purple_http_request_unref(request);
1348 }
1349 
1350 
1351 guint
skypeweb_conv_send_typing(PurpleConversation * conv,PurpleIMTypingState state)1352 skypeweb_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state)
1353 {
1354 	PurpleConnection *pc = purple_conversation_get_connection(conv);
1355 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1356 	gchar *post, *url;
1357 	JsonObject *obj;
1358 
1359 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
1360 		return 0;
1361 
1362 	if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), SKYPEWEB_PLUGIN_ID))
1363 		return 0;
1364 
1365 	url = g_strdup_printf("/v1/users/ME/conversations/%s/messages", purple_url_encode(purple_conversation_get_name(conv)));
1366 
1367 	obj = json_object_new();
1368 	json_object_set_int_member(obj, "clientmessageid", time(NULL));
1369 	json_object_set_string_member(obj, "content", "");
1370 	json_object_set_string_member(obj, "messagetype", state == PURPLE_IM_TYPING ? "Control/Typing" : "Control/ClearTyping");
1371 	json_object_set_string_member(obj, "contenttype", "text");
1372 
1373 	post = skypeweb_jsonobj_to_string(obj);
1374 
1375 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
1376 
1377 	g_free(post);
1378 	json_object_unref(obj);
1379 	g_free(url);
1380 
1381 	return 5;
1382 }
1383 
1384 guint
skypeweb_send_typing(PurpleConnection * pc,const gchar * name,PurpleIMTypingState state)1385 skypeweb_send_typing(PurpleConnection *pc, const gchar *name, PurpleIMTypingState state)
1386 {
1387 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1388 	gchar *post, *url;
1389 	JsonObject *obj;
1390 
1391 	url = g_strdup_printf("/v1/users/ME/conversations/%s%s/messages", skypeweb_user_url_prefix(name), purple_url_encode(name));
1392 
1393 	obj = json_object_new();
1394 	json_object_set_int_member(obj, "clientmessageid", time(NULL));
1395 	json_object_set_string_member(obj, "content", "");
1396 	json_object_set_string_member(obj, "messagetype", state == PURPLE_IM_TYPING ? "Control/Typing" : "Control/ClearTyping");
1397 	json_object_set_string_member(obj, "contenttype", "text");
1398 
1399 	post = skypeweb_jsonobj_to_string(obj);
1400 
1401 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
1402 
1403 	g_free(post);
1404 	json_object_unref(obj);
1405 	g_free(url);
1406 
1407 	return 5;
1408 }
1409 
1410 
1411 static void
skypeweb_set_statusid(SkypeWebAccount * sa,const gchar * status)1412 skypeweb_set_statusid(SkypeWebAccount *sa, const gchar *status)
1413 {
1414 	gchar *post;
1415 
1416 	g_return_if_fail(status);
1417 
1418 	post = g_strdup_printf("{\"status\":\"%s\"}", status);
1419 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/presenceDocs/messagingService", post, NULL, NULL, TRUE);
1420 	g_free(post);
1421 }
1422 
1423 void
skypeweb_set_status(PurpleAccount * account,PurpleStatus * status)1424 skypeweb_set_status(PurpleAccount *account, PurpleStatus *status)
1425 {
1426 	PurpleConnection *pc = purple_account_get_connection(account);
1427 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1428 
1429 	skypeweb_set_statusid(sa, purple_status_get_id(status));
1430 	skypeweb_set_mood_message(sa, purple_status_get_attr_string(status, "message"));
1431 }
1432 
1433 void
skypeweb_set_idle(PurpleConnection * pc,int time)1434 skypeweb_set_idle(PurpleConnection *pc, int time)
1435 {
1436 	const gchar *status_id;
1437 	PurpleStatus *status;
1438 
1439 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1440 
1441 	status = purple_account_get_active_status(purple_connection_get_account(pc));
1442 	status_id = purple_status_get_id(status);
1443 
1444 	/* Only go idle if active status is online  */
1445 	if (!strcmp(status_id, SKYPEWEB_STATUS_ONLINE)) {
1446 		if (time < 30) {
1447 			skypeweb_set_statusid(sa, SKYPEWEB_STATUS_ONLINE);
1448 		} else {
1449 			skypeweb_set_statusid(sa, SKYPEWEB_STATUS_IDLE);
1450 		}
1451 	}
1452 }
1453 
1454 
1455 static void
skypeweb_sent_message_cb(SkypeWebAccount * sa,JsonNode * node,gpointer user_data)1456 skypeweb_sent_message_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1457 {
1458 	gchar *convname = user_data;
1459 	JsonObject *obj = NULL;
1460 
1461 	if (node != NULL && json_node_get_node_type(node) == JSON_NODE_OBJECT)
1462 		obj = json_node_get_object(node);
1463 
1464 	if (obj != NULL) {
1465 		if (json_object_has_member(obj, "errorCode")) {
1466 			PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(convname, sa->account);
1467 			if (chatconv == NULL) {
1468 				purple_conversation_present_error(skypeweb_strip_user_prefix(convname), sa->account, json_object_get_string_member(obj, "message"));
1469 			} else {
1470 				PurpleMessage *msg = purple_message_new_system(json_object_get_string_member(obj, "message"), PURPLE_MESSAGE_ERROR);
1471 				purple_conversation_write_message(PURPLE_CONVERSATION(chatconv), msg);
1472 				purple_message_destroy(msg);
1473 			}
1474 		}
1475 	}
1476 
1477 	g_free(convname);
1478 }
1479 
1480 static void
skypeweb_send_message(SkypeWebAccount * sa,const gchar * convname,const gchar * message)1481 skypeweb_send_message(SkypeWebAccount *sa, const gchar *convname, const gchar *message)
1482 {
1483 	gchar *post, *url;
1484 	JsonObject *obj;
1485 	gint64 clientmessageid;
1486 	gchar *clientmessageid_str;
1487 	gchar *stripped;
1488 	static GRegex *font_strip_regex = NULL;
1489 	gchar *font_stripped;
1490 
1491 	url = g_strdup_printf("/v1/users/ME/conversations/%s/messages", purple_url_encode(convname));
1492 
1493 	clientmessageid = skypeweb_get_js_time();
1494 	clientmessageid_str = g_strdup_printf("%" G_GINT64_FORMAT "", clientmessageid);
1495 
1496 	// Some clients don't receive messages with <br>'s in them
1497 	stripped = purple_strreplace(message, "<br>", "\r\n");
1498 
1499 	// Pidgin has a nasty habit of sending <font size="3"> when copy-pasting text
1500 	if (font_strip_regex == NULL) {
1501 		font_strip_regex = g_regex_new("(<font [^>]*)size=\"[0-9]+\"([^>]*>)", G_REGEX_CASELESS | G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
1502 	}
1503 	font_stripped = g_regex_replace(font_strip_regex, stripped, -1, 0, "\\1\\2", 0, NULL);
1504 	if (font_stripped != NULL) {
1505 		g_free(stripped);
1506 		stripped = font_stripped;
1507 	}
1508 
1509 	obj = json_object_new();
1510 	json_object_set_string_member(obj, "clientmessageid", clientmessageid_str);
1511 	json_object_set_string_member(obj, "content", stripped);
1512 	if (G_UNLIKELY(g_str_has_prefix(message, "<URIObject "))) {
1513 		json_object_set_string_member(obj, "messagetype", "RichText/Media_GenericFile"); //hax!
1514 	} else {
1515 		json_object_set_string_member(obj, "messagetype", "RichText");
1516 	}
1517 	json_object_set_string_member(obj, "contenttype", "text");
1518 	json_object_set_string_member(obj, "imdisplayname", sa->self_display_name ? sa->self_display_name : sa->username);
1519 
1520 	if (g_str_has_prefix(message, "/me ")) {
1521 		json_object_set_string_member(obj, "skypeemoteoffset", "4"); //Why is this a string :(
1522 	}
1523 
1524 	post = skypeweb_jsonobj_to_string(obj);
1525 
1526 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, skypeweb_sent_message_cb, g_strdup(convname), TRUE);
1527 
1528 	g_free(post);
1529 	json_object_unref(obj);
1530 	g_free(url);
1531 	g_free(stripped);
1532 
1533 	g_hash_table_insert(sa->sent_messages_hash, clientmessageid_str, clientmessageid_str);
1534 }
1535 
1536 
1537 gint
skypeweb_chat_send(PurpleConnection * pc,gint id,PurpleMessage * msg)1538 skypeweb_chat_send(PurpleConnection *pc, gint id,
1539 #if PURPLE_VERSION_CHECK(3, 0, 0)
1540 PurpleMessage *msg)
1541 {
1542 	const gchar *message = purple_message_get_contents(msg);
1543 #else
1544 const gchar *message, PurpleMessageFlags flags)
1545 {
1546 #endif
1547 
1548 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1549 
1550 	PurpleChatConversation *chatconv;
1551 	const gchar* chatname;
1552 
1553 	chatconv = purple_conversations_find_chat(pc, id);
1554 	chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
1555 	if (!chatname) {
1556 		// Fix for a condition around the chat data and serv_got_joined_chat()
1557 		chatname = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
1558 		if (!chatname)
1559 			return -1;
1560 		}
1561 
1562 	skypeweb_send_message(sa, chatname, message);
1563 
1564 	purple_serv_got_chat_in(pc, id, sa->username, PURPLE_MESSAGE_SEND, message, time(NULL));
1565 
1566 	return 1;
1567 }
1568 
1569 gint
1570 skypeweb_send_im(PurpleConnection *pc,
1571 #if PURPLE_VERSION_CHECK(3, 0, 0)
1572 PurpleMessage *msg)
1573 {
1574 	const gchar *who = purple_message_get_recipient(msg);
1575 	const gchar *message = purple_message_get_contents(msg);
1576 #else
1577 const gchar *who, const gchar *message, PurpleMessageFlags flags)
1578 {
1579 #endif
1580 
1581 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1582 	gchar *convname;
1583 
1584 	convname = g_strconcat(skypeweb_user_url_prefix(who), who, NULL);
1585 	skypeweb_send_message(sa, convname, message);
1586 	g_free(convname);
1587 
1588 	return 1;
1589 }
1590 
1591 
1592 void
1593 skypeweb_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who)
1594 {
1595 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1596 	PurpleChatConversation *chatconv;
1597 	gchar *chatname;
1598 	gchar *post;
1599 	GString *url;
1600 
1601 	chatconv = purple_conversations_find_chat(pc, id);
1602 	chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
1603 
1604 	url = g_string_new("/v1/threads/");
1605 	g_string_append_printf(url, "%s", purple_url_encode(chatname));
1606 	g_string_append(url, "/members/");
1607 	g_string_append_printf(url, "%s%s", skypeweb_user_url_prefix(who), purple_url_encode(who));
1608 
1609 	post = "{\"role\":\"User\"}";
1610 
1611 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
1612 
1613 	g_string_free(url, TRUE);
1614 }
1615 
1616 void
1617 skypeweb_chat_kick(PurpleConnection *pc, int id, const char *who)
1618 {
1619 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1620 	PurpleChatConversation *chatconv;
1621 	gchar *chatname;
1622 	gchar *post;
1623 	GString *url;
1624 
1625 	chatconv = purple_conversations_find_chat(pc, id);
1626 	chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
1627 
1628 	url = g_string_new("/v1/threads/");
1629 	g_string_append_printf(url, "%s", purple_url_encode(chatname));
1630 	g_string_append(url, "/members/");
1631 	g_string_append_printf(url, "%s%s", skypeweb_user_url_prefix(who), purple_url_encode(who));
1632 
1633 	post = "";
1634 
1635 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
1636 
1637 	g_string_free(url, TRUE);
1638 }
1639 
1640 void
1641 skypeweb_initiate_chat(SkypeWebAccount *sa, const gchar *who)
1642 {
1643 	JsonObject *obj, *contact;
1644 	JsonArray *members;
1645 	gchar *id, *post;
1646 
1647 	obj = json_object_new();
1648 	members = json_array_new();
1649 
1650 	contact = json_object_new();
1651 	id = g_strconcat(skypeweb_user_url_prefix(who), who, NULL);
1652 	json_object_set_string_member(contact, "id", id);
1653 	json_object_set_string_member(contact, "role", "User");
1654 	json_array_add_object_element(members, contact);
1655 	g_free(id);
1656 
1657 	contact = json_object_new();
1658 	id = g_strconcat(skypeweb_user_url_prefix(sa->username), sa->username, NULL);
1659 	json_object_set_string_member(contact, "id", id);
1660 	json_object_set_string_member(contact, "role", "Admin");
1661 	json_array_add_object_element(members, contact);
1662 	g_free(id);
1663 
1664 	json_object_set_array_member(obj, "members", members);
1665 	post = skypeweb_jsonobj_to_string(obj);
1666 
1667 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/threads", post, NULL, NULL, TRUE);
1668 
1669 	g_free(post);
1670 	json_object_unref(obj);
1671 }
1672 
1673 void
1674 skypeweb_initiate_chat_from_node(PurpleBlistNode *node, gpointer userdata)
1675 {
1676 	if(PURPLE_IS_BUDDY(node))
1677 	{
1678 		PurpleBuddy *buddy = (PurpleBuddy *) node;
1679 		SkypeWebAccount *sa;
1680 
1681 		if (userdata) {
1682 			sa = userdata;
1683 		} else {
1684 			PurpleConnection *pc = purple_account_get_connection(purple_buddy_get_account(buddy));
1685 			sa = purple_connection_get_protocol_data(pc);
1686 		}
1687 
1688 		skypeweb_initiate_chat(sa, purple_buddy_get_name(buddy));
1689 	}
1690 }
1691 
1692 void
1693 skypeweb_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
1694 {
1695 	SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
1696 	PurpleChatConversation *chatconv;
1697 	JsonObject *obj;
1698 	gchar *chatname;
1699 	gchar *post;
1700 	GString *url;
1701 
1702 	chatconv = purple_conversations_find_chat(pc, id);
1703 	chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
1704 
1705 	url = g_string_new("/v1/threads/");
1706 	g_string_append_printf(url, "%s", purple_url_encode(chatname));
1707 	g_string_append(url, "/properties?name=topic");
1708 
1709 	obj = json_object_new();
1710 	json_object_set_string_member(obj, "topic", topic);
1711 	post = skypeweb_jsonobj_to_string(obj);
1712 
1713 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
1714 
1715 	g_string_free(url, TRUE);
1716 	g_free(post);
1717 	json_object_unref(obj);
1718 }
1719 
1720 void
1721 skypeweb_get_thread_url(SkypeWebAccount *sa, const gchar *thread)
1722 {
1723 	//POST https://api.scheduler.skype.com/threads
1724 	//{"baseDomain":"https://join.skype.com/launch/","threadId":"%s"}
1725 
1726 	// {"Id":"MeMxigEAAAAxOTo5NDZkMjExMGQ4YmU0ZjQzODc3NjMxNDQ3ZTgxYWNmNkB0aHJlYWQuc2t5cGU","Blob":null,"JoinUrl":"https://join.skype.com/ALXsHZ2RFQnk","ThreadId":"19:946d2110d8be4f43877631447e81acf6@thread.skype"}
1727 }
1728 
1729 
1730 static void
1731 skypeweb_got_self_properties(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
1732 {
1733 	JsonObject *userobj;
1734 	if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
1735 		return;
1736 	userobj = json_node_get_object(node);
1737 
1738 	if (json_object_has_member(userobj, "primaryMemberName")) {
1739 		g_free(sa->primary_member_name); sa->primary_member_name = g_strdup(json_object_get_string_member(userobj, "primaryMemberName"));
1740 	}
1741 }
1742 
1743 void
1744 skypeweb_gather_self_properties(SkypeWebAccount *sa)
1745 {
1746 	skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/properties", NULL, skypeweb_got_self_properties, NULL, TRUE);
1747 }
1748