1 /*
2  *   Mattermost plugin for libpurple
3  *   Copyright (C) 2016  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 <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #ifdef __GNUC__
23 	#include <unistd.h>
24 #endif
25 #include <errno.h>
26 
27 #include <glib.h>
28 #include <json-glib/json-glib.h>
29 #include "glibcompat.h"
30 
31 #include <purple.h>
32 
33 #include "purplecompat.h"
34 #include "image-store.h"
35 #include "image.h"
36 #include "libmattermost-json.h"
37 #include "libmattermost-markdown.h"
38 #include "libmattermost-helpers.h"
39 #include "libmattermost.h"
40 
41 static gint
mm_get_next_seq(MattermostAccount * ma)42 mm_get_next_seq(MattermostAccount *ma)
43 {
44 	return ma->seq++;
45 }
46 
47 static void
mm_update_cookies(MattermostAccount * ma,const GList * cookie_headers)48 mm_update_cookies(MattermostAccount *ma, const GList *cookie_headers)
49 {
50 	const gchar *cookie_start;
51 	const gchar *cookie_end;
52 	gchar *cookie_name;
53 	gchar *cookie_value;
54 	const GList *cur;
55 
56 	for (cur = cookie_headers; cur != NULL; cur = g_list_next(cur))
57 	{
58 		cookie_start = cur->data;
59 
60 		cookie_end = strchr(cookie_start, '=');
61 		if (cookie_end != NULL) {
62 			cookie_name = g_strndup(cookie_start, cookie_end-cookie_start);
63 			cookie_start = cookie_end + 1;
64 			cookie_end = strchr(cookie_start, ';');
65 			if (cookie_end != NULL) {
66 				cookie_value = g_strndup(cookie_start, cookie_end-cookie_start);
67 				cookie_start = cookie_end;
68 
69 				g_hash_table_replace(ma->cookie_table, cookie_name, cookie_value);
70 			}
71 		}
72 	}
73 }
74 
75 
76 static void
mm_response_callback(PurpleHttpConnection * http_conn,PurpleHttpResponse * response,gpointer user_data)77 mm_response_callback(PurpleHttpConnection *http_conn,
78 PurpleHttpResponse *response, gpointer user_data)
79 {
80 	gsize body_len;
81 	const gchar *body = purple_http_response_get_data(response, &body_len);
82 	const gchar *error_message = purple_http_response_get_error(response);
83 	const GList *headers = purple_http_response_get_headers_by_name(response, "Set-Cookie");
84 
85 	MattermostProxyConnection *conn = user_data;
86 	JsonParser *parser = json_parser_new();
87 
88 	conn->ma->http_conns = g_slist_remove(conn->ma->http_conns, http_conn);
89 
90 	mm_update_cookies(conn->ma, headers);
91 
92 	if (body == NULL && error_message != NULL) {
93 		//connection error - unersolvable dns name, non existing server
94 		gchar *error_msg_formatted = g_strdup_printf(_("Connection error: %s."), error_message);
95 		purple_connection_error(conn->ma->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg_formatted);
96 		g_free(error_msg_formatted);
97 		g_free(conn);
98 		return;
99 	}
100 
101 	if (body != NULL && !json_parser_load_from_data(parser, body, body_len, NULL)) {
102 		//purple_debug_error("mattermost", "Error parsing response: %s\n", body);
103 		if (conn->callback) {
104 			JsonNode *dummy_node = json_node_new(JSON_NODE_OBJECT);
105 			JsonObject *dummy_object = json_object_new();
106 
107 			json_node_set_object(dummy_node, dummy_object);
108 			json_object_set_string_member(dummy_object, "body", body);
109 			json_object_set_int_member(dummy_object, "len", body_len);
110 			if (body_len >= 12 && g_str_has_prefix(body, "HTTP/1.")) {
111 				json_object_set_int_member(dummy_object, "status_code", g_ascii_strtoll(body + 9, NULL, 10));
112 			} else {
113 				json_object_set_int_member(dummy_object, "status_code", 500);
114 			}
115 			g_dataset_set_data(dummy_node, "raw_body", (gpointer) body);
116 
117 			conn->callback(conn->ma, dummy_node, conn->user_data);
118 
119 			g_dataset_destroy(dummy_node);
120 			json_node_free(dummy_node);
121 			json_object_unref(dummy_object);
122 		}
123 	} else {
124 		JsonNode *root = json_parser_get_root(parser);
125 
126 		purple_debug_misc("mattermost", "Got response: %s\n", body);
127 		if (conn->callback) {
128 			conn->callback(conn->ma, root, conn->user_data);
129 		}
130 	}
131 
132 	g_object_unref(parser);
133 	g_free(conn);
134 }
135 
136 gchar *
137 mm_build_url(MattermostAccount *ma, const gchar *url_format, ...)
138 	__attribute__ ((format (printf, 2, 3)));
139 
140 gchar *
mm_build_url(MattermostAccount * ma,const gchar * url_format,...)141 mm_build_url(MattermostAccount *ma, const gchar *url_format, ...)
142 {
143 	GString *url = g_string_new(NULL);
144 	const gchar *last_cur, *cur, *tok;
145 	va_list args;
146 
147 	if (purple_account_get_bool(ma->account, "use-ssl", TRUE)) {
148 		g_string_append(url, "https://");
149 	} else {
150 		g_string_append(url, "http://");
151 	}
152 	g_string_append(url, ma->server);
153 
154 	g_string_append(url, ma->api_endpoint);
155 
156 	va_start(args, url_format);
157 	for(last_cur = cur = url_format; cur; last_cur = cur, cur = strchr(cur, '%')) {
158 		g_string_append_len(url, last_cur, cur - last_cur);
159 
160 		if (*cur == '%') {
161 			if (*(cur + 1) == 's') {
162 				tok = va_arg(args, char *);
163 				g_string_append_uri_escaped(url, tok, NULL, TRUE);
164 			} else if (*(cur + 1) == '%') {
165 				g_string_append_c(url, '%');
166 			} else if (*(cur + 1) == 'd') {
167 				int d = va_arg(args, int);
168 				g_string_append_printf(url, "%d", d);
169 			} else if (*(cur + 1) == 'c') {
170 				char c = va_arg(args, int);
171 				g_string_append_c(url, c);
172 			} else if (strncmp((cur + 1), G_GINT64_FORMAT, sizeof(G_GINT64_FORMAT) - 1) == 0) {
173 				gint64 i = va_arg(args, gint64);
174 				g_string_append_printf(url, "%" G_GINT64_FORMAT, i);
175 				cur += sizeof(G_GINT64_FORMAT) - 2;
176 			}
177 			cur += 2;
178 		}
179 	}
180 	va_end(args);
181 
182 	g_string_append(url, last_cur);
183 
184 	return g_string_free(url, FALSE);
185 }
186 
187 static gboolean
mm_check_mattermost_response(MattermostAccount * ma,JsonNode * node,gchar * errtitle,gchar * errtext,gboolean show)188 mm_check_mattermost_response(MattermostAccount *ma, JsonNode *node, gchar *errtitle, gchar *errtext, gboolean show)
189 {
190 	if (json_node_get_node_type(node) == JSON_NODE_OBJECT) {
191 		JsonObject *response = json_node_get_object(node);
192 		if (json_object_get_int_member(response, "status_code") >= 400) {
193 			if (show) {
194 				purple_notify_error(ma->pc, errtitle, errtext, json_object_get_string_member(response, "message"), purple_request_cpar_from_connection(ma->pc));
195 			}
196 		return FALSE;
197 		}
198 		return TRUE;
199 	}
200 	if (json_node_get_node_type(node) == JSON_NODE_ARRAY) {
201 		return TRUE;
202 }
203 	purple_notify_error(ma->pc, _("Error"), _("Cannot parse Mattermost reply"), _("(not json object or array)"), purple_request_cpar_from_connection(ma->pc));
204 	return FALSE;
205 }
206 
207 static void
mm_fetch_url(MattermostAccount * ma,const gchar * url,const guint optype,const gchar * postdata,const guint postdata_size,MattermostProxyCallbackFunc callback,gpointer user_data)208 mm_fetch_url(MattermostAccount *ma, const gchar *url, const guint optype, const gchar *postdata, const guint postdata_size, MattermostProxyCallbackFunc callback, gpointer user_data)
209 {
210 	PurpleAccount *account;
211 	MattermostProxyConnection *conn;
212 	PurpleHttpConnection *http_conn;
213 
214 	account = ma->account;
215 	if (purple_account_is_disconnected(account)) return;
216 
217 	conn = g_new0(MattermostProxyConnection, 1);
218 	conn->ma = ma;
219 	conn->callback = callback;
220 	conn->user_data = user_data;
221 
222 	purple_debug_info("mattermost", "Fetching url %s\n", url);
223 
224 
225 	PurpleHttpRequest *request = purple_http_request_new(url);
226 	purple_http_request_header_set(request, "Accept", "*/*");
227 	purple_http_request_header_set(request, "User-Agent", MATTERMOST_USERAGENT);
228         purple_http_request_header_set(request, "X-Requested-With", "XMLHttpRequest");
229 	if (ma->session_token) {
230 		purple_http_request_header_set_printf(request, "Authorization", "Bearer %s", ma->session_token);
231 	}
232 
233 	if (postdata) {
234 		purple_debug_info("mattermost", "With postdata %s\n", postdata);
235 
236 		if (postdata[0] == '{') {
237 			purple_http_request_header_set(request, "Content-Type", "application/json");
238 			purple_http_request_set_contents(request, postdata, -1);
239 		} else if (postdata_size > 0){
240 			purple_http_request_header_set(request, "Content-Type", "application/octet-stream");
241 			purple_http_request_set_contents(request, postdata, postdata_size);
242 		} else {
243 			purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
244 			purple_http_request_set_contents(request, postdata, -1);
245 		}
246 
247 	}
248 	switch(optype) {
249 		case MATTERMOST_HTTP_DELETE: purple_http_request_set_method(request,"DELETE"); break;
250 		case MATTERMOST_HTTP_PUT: purple_http_request_set_method(request,"PUT"); break;
251 		case MATTERMOST_HTTP_POST: purple_http_request_set_method(request,"POST"); break;
252 		default: purple_http_request_set_method(request,"GET");
253 	}
254 	http_conn = purple_http_request(ma->pc, request, mm_response_callback, conn);
255 	purple_http_request_unref(request);
256 
257 	if (http_conn != NULL)
258 		ma->http_conns = g_slist_prepend(ma->http_conns, http_conn);
259 
260 }
261 
262 const gchar *
mm_split_topic(gchar * str)263 mm_split_topic(gchar *str)
264 {
265 	gchar *p = g_strstr_len(str, -1, MATTERMOST_CHAT_TOPIC_SEP);
266 	if (p == NULL) return NULL;
267 	*p = '\0';
268 	return p + strlen(MATTERMOST_CHAT_TOPIC_SEP);
269 }
270 
271 const gchar *
mm_make_topic(const gchar * header,const gchar * purpose,const gchar * old_topic)272 mm_make_topic(const gchar *header, const gchar *purpose, const gchar *old_topic)
273 {
274 	//TODO: limit len !
275 	const gchar *old_purpose = mm_split_topic((gchar *)old_topic);
276 	const gchar *old_header = old_topic;
277 
278 	const gchar *topic = g_strconcat((header && *header) ? header : old_header, MATTERMOST_CHAT_TOPIC_SEP, (purpose && *purpose) ? purpose : old_purpose, NULL);
279 
280 	return topic;
281 }
282 
283 static void
mm_send_email_cb(PurpleBuddy * buddy)284 mm_send_email_cb(PurpleBuddy *buddy)
285 {
286 	PurpleBlistNode *bnode = PURPLE_BLIST_NODE(buddy);
287 	const gchar *email = purple_blist_node_get_string(bnode, "email");
288 	const gchar *first_name = purple_blist_node_get_string(bnode, "first_name");
289 	const gchar *last_name = purple_blist_node_get_string(bnode, "last_name");
290 	GString *full_email = g_string_new("mailto:");
291 
292 	if (first_name) {
293 		g_string_append_printf(full_email, "%s ", first_name);
294 	}
295 	if (last_name) {
296 		g_string_append_printf(full_email, "%s ", last_name);
297 	}
298 
299 	g_string_append_printf(full_email, "<%s>", email);
300 
301 	gchar *uri = g_string_free(full_email, FALSE);
302 	purple_notify_uri(purple_account_get_connection(purple_buddy_get_account(buddy)), uri);
303 	g_free(uri);
304 }
305 
306 static GList *
mm_buddy_menu(PurpleBuddy * buddy)307 mm_buddy_menu(PurpleBuddy *buddy)
308 {
309 	GList *menu = NULL;
310 	if (purple_blist_node_get_string(PURPLE_BLIST_NODE(buddy), "email")) {
311 		PurpleMenuAction *action = purple_menu_action_new(_("Email Buddy"), PURPLE_CALLBACK(mm_send_email_cb), NULL, NULL);
312 		menu = g_list_append(menu, action);
313 	}
314 	return menu;
315 }
316 
317 static GList *
mm_blist_node_menu(PurpleBlistNode * node)318 mm_blist_node_menu(PurpleBlistNode *node)
319 {
320 	if(PURPLE_BUDDY(node)) {
321 		return mm_buddy_menu((PurpleBuddy *) node);
322 	}
323 	return NULL;
324 }
325 
326 static const gchar *
mm_get_first_team_id(MattermostAccount * ma)327 mm_get_first_team_id(MattermostAccount *ma)
328 {
329 	GList *team_ids = g_hash_table_get_keys(ma->teams);
330 	const gchar *first_team_id = team_ids ? team_ids->data : NULL;
331 
332 	g_list_free(team_ids);
333 
334 	return first_team_id;
335 }
336 
337 static void mm_get_user_prefs(MattermostAccount *ma);
338 
339 PurpleGroup* mm_get_or_create_default_group();
340 static void mm_get_history_of_room(MattermostAccount *ma, MattermostChannel *channel, gint64 since);
341 
342 static void mm_start_socket(MattermostAccount *ma);
343 static void mm_socket_write_json(MattermostAccount *ma, JsonObject *data);
344 static void mm_get_users_by_ids(MattermostAccount *ma, GList *ids);
345 static void mm_get_avatar(MattermostAccount *ma, PurpleBuddy *buddy);
346 
347 static void mm_join_room(MattermostAccount *ma, MattermostChannel *channel);
348 static PurpleChatUserFlags mm_role_to_purple_flag(MattermostAccount *ma, const gchar *rolelist);
349 
350 static void mm_get_channel_by_id(MattermostAccount *ma, const gchar *team_id, const gchar *id);
351 static void mm_mark_room_messages_read_timeout_response(MattermostAccount *ma, JsonNode *node, gpointer user_data);
352 static void mm_save_user_pref(MattermostAccount *ma, MattermostUserPref *pref);
353 static void mm_close(PurpleConnection *pc);
354 const gchar *
mm_get_alias(MattermostUser * mu)355 mm_get_alias(MattermostUser *mu)
356 {
357 	gchar *nickname = NULL;
358 	gchar *full_name = NULL;
359 	gchar *alias = NULL;
360 
361 	if (mu->nickname && *mu->nickname) { nickname = g_strconcat(" (",mu->nickname,")",NULL); }
362 	full_name = g_strconcat(mu->first_name ? mu->first_name : "", (mu->first_name && *mu->first_name) ? " " : "", mu->last_name, nickname,  NULL);
363     alias = g_strdup((full_name && *full_name) ? full_name : (mu->email && *mu->email) ? mu->email : NULL);
364 
365 	g_free(nickname);
366 	g_free(full_name);
367 
368 	return alias;
369 }
370 
371 const gchar *
mm_get_chat_alias(MattermostAccount * ma,MattermostChannel * ch)372 mm_get_chat_alias(MattermostAccount *ma, MattermostChannel *ch)
373 {
374 	gchar *alias = NULL;
375 	gchar *type = NULL;
376 
377 	//FIXME: redo with some pattern matching.. this is ugly.
378 	if (ch->type && purple_strequal(ch->type,MATTERMOST_CHANNEL_TYPE_STRING(MATTERMOST_CHANNEL_GROUP))) {
379 		const gchar *tmpa = g_strjoinv("", g_strsplit(ch->display_name, ma->username, -1));
380 		const gchar *tmpb = g_strjoinv(",", g_strsplit(tmpa,", ",-1));
381 		const gchar *tmpc = g_strjoinv(",", g_strsplit(tmpb,",,",-1));
382 		if (g_str_has_prefix(tmpc,",")) {
383 			alias = g_strndup(tmpc+1,strlen(tmpc));
384 		} else {
385 			alias = g_strdup(tmpc);
386 		}
387 		return alias;
388 	}
389 
390 	type = g_strconcat((ch->type && purple_strequal(ch->type,MATTERMOST_CHANNEL_TYPE_STRING(MATTERMOST_CHANNEL_PRIVATE))) ? MATTERMOST_CHANNEL_PRIVATE_VISUAL : "", NULL);
391 
392 	alias = g_strconcat(type, ch->display_name, MATTERMOST_CHANNEL_SEPARATOR_VISUAL, g_hash_table_lookup(ma->teams_display_names, ch->team_id), NULL);
393 
394 	g_free(type);
395 
396 	return alias;
397 }
398 static void mm_set_group_chat(MattermostAccount *ma, const gchar *team_id, const gchar *channel_name, const gchar *channel_id);
399 
400 // only non-changing values are channel_id and team_id !
401 // name and display_name for teams and channels can change
402 PurpleChat *
mm_purple_blist_find_chat(MattermostAccount * ma,const gchar * channel_id)403 mm_purple_blist_find_chat(MattermostAccount *ma, const gchar *channel_id)
404 {
405 	PurpleBlistNode *bnode;
406 	for (bnode = purple_blist_get_root(); bnode != NULL; bnode = purple_blist_node_next(bnode, FALSE)) {
407 		if (!PURPLE_IS_CHAT(bnode)) continue;
408 		if (purple_chat_get_account(PURPLE_CHAT(bnode)) != ma->account) continue;
409 
410 		GHashTable *components = purple_chat_get_components(PURPLE_CHAT(bnode));
411 
412 		if (purple_strequal(g_hash_table_lookup(components, "id"), channel_id)) return PURPLE_CHAT(bnode);
413 	}
414 	return NULL;
415 }
416 
417 void
mm_purple_blist_remove_chat(MattermostAccount * ma,const gchar * channel_id)418 mm_purple_blist_remove_chat(MattermostAccount *ma, const gchar *channel_id)
419 {
420 	PurpleBlistNode *bnode;
421 	for (bnode = purple_blist_get_root(); bnode != NULL; bnode = purple_blist_node_next(bnode, FALSE)) {
422 		if (!PURPLE_IS_CHAT(bnode)) continue;
423 		if (purple_chat_get_account(PURPLE_CHAT(bnode)) != ma->account) continue;
424 
425 		GHashTable *components = purple_chat_get_components(PURPLE_CHAT(bnode));
426 
427 		if (purple_strequal(g_hash_table_lookup(components, "id"), channel_id)) purple_blist_remove_chat(PURPLE_CHAT(bnode));
428 	}
429 }
430 
431 static gboolean
mm_channel_is_hidden(MattermostAccount * ma,const gchar * id)432 mm_channel_is_hidden(MattermostAccount *ma, const gchar *id)
433 {
434 	GList *prefs;
435 
436 	for(prefs=ma->user_prefs; prefs != NULL; prefs = g_list_next(prefs)) {
437 		MattermostUserPref *pref = prefs->data;
438 		if(purple_strequal(pref->name,id)) {
439 			if(purple_strequal(pref->category,"direct_channel_show") || purple_strequal(pref->category,"group_channel_show")) {
440 				if(purple_strequal(pref->value,"false")) {
441 					return TRUE;
442 				}
443 			}
444 		}
445 	}
446 	return FALSE;
447 }
448 
449 static gint64
mm_find_channel_approximate_view_time(MattermostAccount * ma,const gchar * id)450 mm_find_channel_approximate_view_time(MattermostAccount *ma, const gchar *id)
451 {
452 //	GList *prefs;
453 	gint64 now = (g_get_real_time() / 1000); // -(60*60*24*5*1000); //(- 5 days for debug, remove !)
454 //	gint64 then = 0;
455 
456 //	if (!id) return now;
457 
458 //OK, so this does not work as expected in MM server 5.0 .. lets do timekeeping ourselves.
459 //	for (prefs=ma->user_prefs; prefs != NULL; prefs = g_list_next(prefs)) {
460 //		MattermostUserPref *pref = prefs->data;
461 //		if (purple_strequal(pref->category,"channel_approximate_view_time") && purple_strequal(pref->name,id)) {
462 //			then = g_ascii_strtoll(pref->value,NULL,10);
463 //			return (then ? then : now);
464 //		}
465 //	}
466 	return now;
467 }
468 
469 gboolean
470 mm_idle_updater_timeout(gpointer data);
471 
472 void
473 mm_set_status(PurpleAccount *account, PurpleStatus *status);
474 
475 static void
mm_add_channels_to_blist(MattermostAccount * ma,JsonNode * node,gpointer user_data)476 mm_add_channels_to_blist(MattermostAccount *ma, JsonNode *node, gpointer user_data)
477 {
478 	gchar *team_id = user_data;
479 	//gboolean first_team = FALSE;
480 
481 	//if (purple_strequal(mm_get_first_team_id(ma),team_id)) first_team = TRUE;
482 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost channels"),TRUE)) return;
483 
484 	JsonArray *channels = json_node_get_array(node);
485 	guint i, len = json_array_get_length(channels);
486 	GList *mm_users = NULL;
487 	GList *mm_channels = NULL;
488 	GList *j = NULL;
489 	GList *removenodes = NULL;
490 	PurpleBlistNode *bnode;
491 
492 	// channels = buddies and chats
493 	for (i = 0; i < len; i++) {
494 		MattermostChannel *mm_channel = g_new0(MattermostChannel,1);
495 		JsonObject *channel = json_array_get_object_element(channels, i);
496 		mm_channel->id = g_strdup(json_object_get_string_member(channel, "id"));
497 		mm_channel->display_name = g_strdup(json_object_get_string_member(channel, "display_name"));
498 		mm_channel->type = g_strdup(json_object_get_string_member(channel, "type"));
499 		mm_channel->creator_id = g_strdup(json_object_get_string_member(channel, "creator_id"));
500 		mm_channel->channel_approximate_view_time = mm_find_channel_approximate_view_time(ma, mm_channel->id);
501 
502 		const gchar *name = json_object_get_string_member(channel, "name");
503 
504 		if (mm_channel->type && *(mm_channel->type) == MATTERMOST_CHANNEL_DIRECT) {
505 			MattermostUser *mm_user = g_new0(MattermostUser, 1);
506 			gchar **names = g_strsplit(name, "__", 2);
507 			mm_user->user_id = g_strdup(purple_strequal(names[0], ma->self->user_id) ? names[1] : names[0]);
508 			mm_user->room_id = g_strdup(mm_channel->id);
509 			g_strfreev(names);
510 
511 			if (mm_channel_is_hidden(ma, mm_user->user_id)) {
512 				mm_g_free_mattermost_user(mm_user);
513 			} else {
514 				mm_users = g_list_prepend(mm_users, mm_user);
515 			}
516 
517 		} else {
518 			// group channels do not belong to any team, avoid duplicating.
519 			//if (mm_channel->type && *(mm_channel->type) == MATTERMOST_CHANNEL_GROUP && !first_team) continue;
520 			//OK : this is done for each team now, but we get group channels below other in initial sort list. no dups.
521 
522 			mm_channel->name=g_strdup(name);
523 			mm_channel->team_id = g_strdup(json_object_get_string_member(channel, "team_id")); // NULL for group channels
524 			if (mm_channel_is_hidden(ma, mm_channel->id)) {
525 				mm_g_free_mattermost_channel(mm_channel);
526 			} else {
527 				mm_channels = g_list_prepend(mm_channels, mm_channel);
528 			}
529 		}
530 
531 	}
532 
533 	// remove from blist unseen buddies and chats (removed MM channels)
534 	for (bnode = purple_blist_get_root(); bnode != NULL; bnode = purple_blist_node_next(bnode, FALSE)) {
535 		MattermostChannel *tmpchannel = g_new0(MattermostChannel,1);
536 		MattermostUser *tmpuser = g_new0(MattermostUser,1);
537 
538 		gboolean founduser, foundchannel;
539 
540 		if (PURPLE_IS_CHAT(bnode) && purple_chat_get_account(PURPLE_CHAT(bnode)) == ma->account) {
541 			GHashTable *components = purple_chat_get_components(PURPLE_CHAT(bnode));
542 			tmpchannel->id = g_hash_table_lookup(components, "id");
543 			tmpchannel->team_id = g_hash_table_lookup(components, "team_id");
544 			tmpchannel->name = g_hash_table_lookup(components, "name");
545 			tmpchannel->type = g_hash_table_lookup(components, "type");
546 			tmpchannel->display_name = g_hash_table_lookup(components, "display_name");
547 
548 			if(tmpchannel->team_id == NULL || purple_strequal(tmpchannel->team_id, team_id)) {
549 				GList *tmplist;foundchannel = FALSE;
550 				for(tmplist=mm_channels;tmplist != NULL; tmplist=g_list_next(tmplist)){
551 					MattermostChannel *tmp2channel = tmplist->data;
552 					if(purple_strequal(tmp2channel->id,tmpchannel->id)) {
553 						foundchannel = TRUE;
554 					}
555 				}
556 				if (!foundchannel || mm_channel_is_hidden(ma, tmpchannel->id)) {
557 						removenodes = g_list_prepend(removenodes, bnode);
558 				}
559 			}
560 
561 		} else if (PURPLE_IS_BUDDY(bnode) && purple_buddy_get_account(PURPLE_BUDDY(bnode)) == ma->account) {
562 			tmpuser->room_id = g_strdup(purple_blist_node_get_string(bnode, "room_id"));
563 			tmpuser->user_id = g_strdup(purple_blist_node_get_string(bnode, "user_id"));
564 			tmpuser->email = g_strdup(purple_blist_node_get_string(bnode, "email"));
565 			GList *tmplist;founduser = FALSE;
566 				for(tmplist=mm_users;tmplist != NULL; tmplist=g_list_next(tmplist)){
567 					MattermostUser *tmp2user = tmplist->data;
568 					if(purple_strequal(tmp2user->user_id,tmpuser->user_id)) {
569 						founduser = TRUE;
570 					}
571 				}
572 			if (!founduser || mm_channel_is_hidden(ma, tmpuser->room_id)) {
573 				removenodes = g_list_prepend(removenodes, bnode);
574 			}
575 		}
576 		g_free(tmpchannel);
577 		g_free(tmpuser);
578 	}
579 
580 	//TODO: use mm_remove_blist_by_id here.
581 	for (j = removenodes; j != NULL; j = j->next) {
582 		if (PURPLE_IS_CHAT(j->data)) {
583 			purple_blist_remove_chat(PURPLE_CHAT(j->data));
584 		} else if (PURPLE_IS_BUDDY(j->data)) {
585 			purple_blist_remove_buddy(PURPLE_BUDDY(j->data));
586 		}
587 	}
588 	g_list_free(removenodes);
589 
590 	//gboolean autojoin = purple_account_get_bool(ma->account, "use-autojoin", FALSE);
591 
592 
593 	mm_channels = g_list_sort(mm_channels, mm_compare_channels_by_display_name_int);
594 	mm_channels = g_list_sort(mm_channels, mm_compare_channels_by_type_int);
595 
596 	for (j = mm_channels; j != NULL; j=j->next) {
597 		MattermostChannel *channel = j->data;
598 		mm_set_group_chat(ma, channel->team_id, channel->name, channel->id);
599 
600 		PurpleChat *chat = mm_purple_blist_find_chat(ma, channel->id);
601 
602 		if (chat == NULL) {
603 			GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
604 
605 			const gchar *alias;
606 			g_hash_table_insert(defaults, "team_id", g_strdup(channel->team_id));
607 			g_hash_table_insert(defaults, "id", g_strdup(channel->id));
608 			g_hash_table_insert(defaults, "creator_id", g_strdup(channel->creator_id));
609 			g_hash_table_insert(defaults, "type", g_strdup(channel->type));
610 			g_hash_table_insert(defaults, "display_name", g_strdup(channel->display_name));
611 
612 			alias = mm_get_chat_alias(ma,channel);
613 
614 			if (channel->type && *(channel->type) != MATTERMOST_CHANNEL_GROUP) {
615 				g_hash_table_insert(defaults, "name", g_strconcat(channel->name, MATTERMOST_CHANNEL_SEPARATOR, g_hash_table_lookup(ma->teams, channel->team_id), NULL));
616 			} else {
617 				g_hash_table_insert(defaults, "name", g_strdup(channel->name));
618 			}
619 
620 			//g_hash_table_insert(defaults,"display_name",g_strdup(alias));
621 
622 			chat = purple_chat_new(ma->account, alias, defaults);
623 			purple_blist_add_chat(chat, mm_get_or_create_default_group(), NULL);
624 			purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin", FALSE /*autojoin*/);
625 			purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-persistent", TRUE);
626 
627 			purple_chat_set_alias(chat, alias);
628 			g_hash_table_replace(ma->group_chats, g_strdup(channel->id), g_strdup(channel->name));
629 			g_hash_table_replace(ma->group_chats_rev, g_strdup(channel->name), g_strdup(channel->id));
630 			g_hash_table_replace(ma->aliases,g_strdup(channel->id),g_strdup(alias));
631 			if (channel->creator_id) {
632 				g_hash_table_replace(ma->group_chats_creators, g_strdup(channel->id), g_strdup(channel->creator_id));
633 			}
634 		}
635 
636 		const gchar *alias;
637 		alias = mm_get_chat_alias(ma,channel);
638 
639 		g_hash_table_replace(ma->aliases,g_strdup(channel->id),g_strdup(alias));
640 
641 		PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel->id));
642 
643 		if (chatconv || purple_blist_node_get_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin")) {
644 
645 			PurpleChatConversation *conv = purple_serv_got_joined_chat(ma->pc, g_str_hash(channel->id), alias);
646 			purple_conversation_set_data(PURPLE_CONVERSATION(conv), "id", g_strdup(channel->id));
647 			purple_conversation_set_data(PURPLE_CONVERSATION(conv), "team_id", g_strdup(channel->team_id));
648 			purple_conversation_set_data(PURPLE_CONVERSATION(conv), "name", g_strdup(channel->name));
649 			purple_conversation_present(PURPLE_CONVERSATION(conv));
650 		}
651 		// already called from mm_join_chat
652 		if (!purple_blist_node_get_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin")) mm_get_channel_by_id(ma, channel->team_id, channel->id);
653 	}
654 
655 	mm_get_users_by_ids(ma,mm_users);
656 
657         // Is this the last team we are waiting to receive channels
658         // for?  If so mark the connection as connected.
659         ma->groupchat_team_count --;
660         if (ma->groupchat_team_count == 0) {
661           purple_connection_set_state(ma->pc, PURPLE_CONNECTION_CONNECTED);
662           mm_set_status(ma->account, purple_presence_get_active_status(purple_account_get_presence(ma->account)));
663           ma->idle_timeout = g_timeout_add_seconds(270, mm_idle_updater_timeout, ma->pc);
664         }
665 
666 
667 }
668 
669 static void
mm_get_open_channels_for_team(MattermostAccount * ma,const gchar * team_id)670 mm_get_open_channels_for_team(MattermostAccount *ma, const gchar *team_id)
671 {
672 	gchar *url;
673 
674 	//FIXME: v4 API bug ? 'me' instead of user_id does not work here ? ...
675 	url = mm_build_url(ma,"/users/%s/teams/%s/channels", ma->self->user_id, team_id);
676 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_add_channels_to_blist, g_strdup(team_id));
677 	g_free(url);
678 }
679 
680 gboolean mm_idle_updater_timeout(gpointer data);
681 
682 
683 void mm_set_status(PurpleAccount *account, PurpleStatus *status);
684 
685 static gchar *mm_purple_flag_to_role(PurpleChatUserFlags flags);
686 
687 static PurpleNotifyUserInfo *
mm_user_info(MattermostUser * mu)688 mm_user_info(MattermostUser *mu)
689 {
690 	PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
691 	purple_notify_user_info_add_pair_plaintext(user_info,_("Nickname"), mu->nickname);
692 	purple_notify_user_info_add_pair_plaintext(user_info,_("First Name"), mu->first_name);
693 	purple_notify_user_info_add_pair_plaintext(user_info,_("Last Name"), mu->last_name);
694 	purple_notify_user_info_add_pair_plaintext(user_info,_("Email address"), mu->email);
695 	purple_notify_user_info_add_pair_plaintext(user_info,_("Position"), mu->position);
696 	purple_notify_user_info_add_pair_plaintext(user_info,_("Locale"), mu->locale);
697 	purple_notify_user_info_add_section_break(user_info);
698 	purple_notify_user_info_add_pair_plaintext(user_info,_("Username"), mu->username);
699 	purple_notify_user_info_add_pair_plaintext(user_info,_("User ID"), mu->user_id);
700 
701 	gchar *rolelist = mm_purple_flag_to_role(mu->roles);
702 	purple_notify_user_info_add_pair_plaintext(user_info,_("Roles"), rolelist);
703 	g_free(rolelist);
704 
705 	return user_info;
706 }
707 
708 static void
mm_about_server(PurpleProtocolAction * action)709 mm_about_server(PurpleProtocolAction *action)
710 {
711 	PurpleConnection *pc = purple_protocol_action_get_connection(action);
712 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
713 	PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
714 	purple_notify_user_info_add_pair_plaintext(user_info,_("Server Version"), ma->client_config->server_version);
715 	purple_notify_user_info_add_pair_plaintext(user_info,_("Site Name"), ma->client_config->site_name);
716 	purple_notify_user_info_add_pair_plaintext(user_info,_("Site URL"), ma->client_config->site_url);
717 	purple_notify_user_info_add_pair_plaintext(user_info,_("Support Email"), ma->client_config->support_email);
718 	purple_notify_user_info_add_pair_plaintext(user_info,_("Report Problems"), ma->client_config->report_a_problem_link);
719 
720 	purple_notify_user_info_add_section_break(user_info);
721 
722 	if(ma->client_config->enable_commands) {
723 		purple_notify_user_info_add_pair_plaintext(user_info,_("Slash commands"),_("enabled"));
724 	} else {
725 		purple_notify_user_info_add_pair_plaintext(user_info,_("Slash commands"),_("disabled"));
726 	}
727 
728 	if(ma->client_config->public_link) {
729 		purple_notify_user_info_add_pair_plaintext(user_info,_("Public file links"),_("enabled"));
730 	} else {
731 		purple_notify_user_info_add_pair_plaintext(user_info,_("Public file links"),_("disabled"));
732 	}
733 
734 	purple_notify_user_info_add_section_break(user_info);
735 
736 	purple_notify_user_info_add_pair_plaintext(user_info,_("Build number"), ma->client_config->build_number);
737 	purple_notify_user_info_add_pair_plaintext(user_info,_("Build hash"), ma->client_config->build_hash);
738 	purple_notify_user_info_add_pair_plaintext(user_info,_("Build date"), ma->client_config->build_date);
739 	purple_notify_user_info_add_pair_plaintext(user_info,_("Enterprise ready"), ma->client_config->enterprise_ready);
740 
741 	purple_notify_userinfo(ma->pc, _("Mattermost Server"), user_info, NULL, NULL);
742 
743 	purple_notify_user_info_destroy(user_info);
744 }
745 
746 static void
mm_about_commands(PurpleProtocolAction * action)747 mm_about_commands(PurpleProtocolAction *action)
748 {
749 	PurpleConnection *pc = purple_protocol_action_get_connection(action);
750 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
751 	PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
752 	GList *i = NULL;
753 	MattermostCommand *cmd = NULL;
754 	for(i=ma->commands;i;i=i->next) {
755 		cmd = i->data;
756 		const gchar *info = g_strconcat("/",cmd->trigger," ",
757 				strlen(cmd->auto_complete_hint) ? g_strconcat(cmd->auto_complete_hint," | ",NULL) : " | ",
758 				strlen(cmd->auto_complete_desc) ? g_strconcat(cmd->auto_complete_desc," ",NULL) : "",
759 				( !strlen(cmd->auto_complete_desc) && strlen(cmd->description) ) ? g_strconcat(cmd->description," ",NULL) : " ",
760 				strlen(cmd->team_id) ? g_strconcat("[team only: ",g_hash_table_lookup(ma->teams, cmd->team_id),"]",NULL) : "",
761 				NULL);
762 
763 		purple_notify_user_info_add_pair_plaintext(user_info,cmd->trigger, info);
764 	}
765 	purple_notify_userinfo(ma->pc, _("Mattermost Slash Commands"), user_info, NULL, NULL);
766 	purple_notify_user_info_destroy(user_info);
767 }
768 
769 static void
mm_about_myself(PurpleProtocolAction * action)770 mm_about_myself(PurpleProtocolAction *action)
771 {
772 	PurpleConnection *pc = purple_protocol_action_get_connection(action);
773 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
774 	PurpleNotifyUserInfo *user_info = mm_user_info(ma->self);
775 
776 	purple_notify_user_info_add_section_break(user_info);
777 
778 	GList *team_names = g_hash_table_get_values(ma->teams);
779 	GList *team_name = NULL;
780 
781 	for (team_name = team_names; team_name != NULL; team_name=team_name->next) {
782 		purple_notify_user_info_add_pair_plaintext(user_info,_("Team"), team_name->data);
783 	}
784 	g_list_free(team_names);
785 
786 	purple_notify_user_info_add_section_break(user_info);
787 
788 	GString *mention_keys = g_string_new(NULL);
789 	GList *i;
790 
791 	for (i = ma->mention_words; i != NULL; i=i->next) {
792 		 g_string_append(mention_keys,i->data);
793 		 g_string_append(mention_keys,",");
794 	}
795 
796 	gchar *tmp = g_string_free(mention_keys, FALSE);
797 	purple_notify_user_info_add_pair_plaintext(user_info,_("Mention"), tmp);
798 
799 
800 	purple_notify_userinfo(ma->pc, ma->self->username, user_info, NULL, NULL);
801 
802 	purple_notify_user_info_destroy(user_info);
803 
804 	g_free(tmp);
805 }
806 
807 static void
mm_got_teams(MattermostAccount * ma,JsonNode * node,gpointer user_data)808 mm_got_teams(MattermostAccount *ma, JsonNode *node, gpointer user_data)
809 {
810 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost teams"),TRUE)) { return; };
811 
812 	JsonArray *teams = json_node_get_array(node);
813 	guint i, len = json_array_get_length(teams);
814 
815         // Set the counter so we know how many teams to wait for
816         // before marking the connection as connected.
817         ma->groupchat_team_count = len;
818 	for (i = 0; i < len; i++) {
819 		JsonObject *team = json_array_get_object_element(teams, i);
820 
821 		const gchar *team_id = json_object_get_string_member(team, "id");
822 		const gchar *name = json_object_get_string_member(team, "name");
823 		const gchar *display_name = json_object_get_string_member(team, "display_name");
824 
825 		g_hash_table_replace(ma->teams, g_strdup(team_id), g_strdup(name));
826 		g_hash_table_replace(ma->teams_display_names, g_strdup(team_id), g_strdup(display_name));
827 
828 		mm_get_commands_for_team(ma, team_id);
829                 mm_get_open_channels_for_team(ma, team_id);
830 	}
831 }
832 
833 
834 static void
mm_set_user_blist(MattermostAccount * ma,MattermostUser * mu,PurpleBuddy * buddy)835 mm_set_user_blist(MattermostAccount *ma, MattermostUser *mu, PurpleBuddy *buddy)
836 {
837 	PurpleBlistNode *bnode = PURPLE_BLIST_NODE(buddy);
838 
839 	purple_blist_node_set_string(bnode, "nickname", mu->nickname);
840 	purple_blist_node_set_string(bnode, "first_name", mu->first_name);
841 	purple_blist_node_set_string(bnode, "last_name", mu->last_name);
842 
843 	// room_id exists only if a direct channel has been created.
844 	if (mu->room_id && *mu->room_id) {
845 		purple_blist_node_set_string(bnode, "room_id", mu->room_id);
846 	}
847 
848 	purple_blist_node_set_string(bnode, "email", mu->email);
849 	purple_blist_node_set_string(bnode, "locale", mu->locale);
850 	purple_blist_node_set_string(bnode, "position", mu->position);
851 	purple_blist_node_set_int(bnode, "roles", mu->roles);
852 
853 	if(purple_account_get_bool(ma->account, "use-alias", FALSE)) {
854 		gchar *alias = g_strdup(mm_get_alias(mu));
855 		purple_buddy_set_local_alias(buddy, alias);
856 		g_free(alias);
857 	}
858 
859 }
860 
861 static void
mm_info_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)862 mm_info_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
863 {
864 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost user information"),TRUE)) return;
865 
866 	JsonObject *user = json_node_get_object(node);
867 
868 	PurpleBuddy *buddy = user_data;
869 	MattermostUser *mu = g_new0(MattermostUser, 1);
870 
871 	mu->nickname = g_strdup(json_object_get_string_member(user, "nickname"));
872 	mu->first_name = g_strdup(json_object_get_string_member(user, "first_name"));
873 	mu->last_name = g_strdup(json_object_get_string_member(user, "last_name"));
874 	mu->email = g_strdup(json_object_get_string_member(user, "email"));
875 	mu->username = g_strdup(json_object_get_string_member(user, "username"));
876 	mu->user_id = g_strdup(json_object_get_string_member(user, "id"));
877 	mu->locale = g_strdup(json_object_get_string_member(user, "locale"));
878 	mu->position = g_strdup(json_object_get_string_member(user, "position"));
879 	mu->roles = mm_role_to_purple_flag(ma, json_object_get_string_member(user, "roles"));
880 
881 	PurpleNotifyUserInfo *user_info = mm_user_info(mu);
882 
883 	purple_notify_userinfo(ma->pc, purple_buddy_get_name(buddy), user_info, NULL, NULL);
884 
885 	purple_notify_user_info_destroy(user_info);
886 
887 	if (!purple_strequal(purple_buddy_get_name(buddy), ma->self->username)) {
888 		mm_set_user_blist(ma, mu, buddy);
889 	}
890 
891 	mm_g_free_mattermost_user(mu);
892 }
893 
894 static void
mm_get_info(PurpleConnection * pc,const gchar * username)895 mm_get_info(PurpleConnection *pc,const gchar *username)
896 {
897 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
898 	PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, username);
899 	gchar *url;
900 
901 	// Don't add BOT to buddies
902 	// hope no user account/alias ends in [BOT] ...
903 	if (purple_str_has_suffix(username, MATTERMOST_BOT_LABEL)) {
904 		PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
905 		purple_notify_user_info_add_pair_plaintext(user_info,_("BOT Name"), purple_strreplace(username, MATTERMOST_BOT_LABEL, ""));
906 		gchar *info = g_strconcat(purple_account_get_bool(ma->account, "use-ssl", TRUE) ? "see https://" : "http://", ma->server, "/ -> team -> integrations", NULL); //We do not know which team is the BOT on.
907 		purple_notify_user_info_add_pair_plaintext(user_info,_("Information"), info);
908 		purple_notify_user_info_add_section_break(user_info);
909 		purple_notify_user_info_add_pair_plaintext(user_info, NULL, _("Mattermost webhook integration"));
910 		purple_notify_userinfo(ma->pc, username, user_info, NULL, NULL);
911 		purple_notify_user_info_destroy(user_info);
912 		g_free(info);
913 		return;
914 	}
915 
916 	if (buddy == NULL) {
917 		buddy = purple_buddy_new(ma->account, username, NULL);
918 	}
919 
920 	url = mm_build_url(ma,"/users/username/%s", username);
921 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_info_response, buddy);
922 	g_free(url);
923 }
924 
925 static void
mm_add_user_to_channel_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)926 mm_add_user_to_channel_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
927 {
928 	MattermostChannel *channel = user_data;
929 	JsonObject *obj = json_node_get_object(node);
930 
931 	if (json_object_get_int_member(obj, "status_code") >= 400) {
932 		purple_notify_error(ma->pc, "Error", "Error joining channel", json_object_get_string_member(obj, "message"), purple_request_cpar_from_connection(ma->pc));
933 		PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel->id));
934 		if (chatconv) purple_conv_chat_left(chatconv);
935 		return;
936 	}
937 
938 	if (mm_purple_blist_find_chat(ma, channel->id) == NULL) {
939 		PurpleChat *chat = NULL;
940 		GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
941 		const gchar *alias = mm_get_chat_alias(ma, channel);
942 
943 		g_hash_table_insert(defaults, "team_id", g_strdup(channel->team_id));
944 		g_hash_table_insert(defaults, "id", g_strdup(channel->id));
945 		g_hash_table_insert(defaults, "type", g_strdup(channel->type));
946 		g_hash_table_insert(defaults, "creator_id", g_strdup(channel->creator_id));
947 		g_hash_table_insert(defaults,"display_name",g_strdup(channel->display_name));
948 
949 		if (channel->type && *(channel->type) != MATTERMOST_CHANNEL_GROUP) {
950 			g_hash_table_insert(defaults, "name", g_strconcat(channel->name, MATTERMOST_CHANNEL_SEPARATOR, g_hash_table_lookup(ma->teams, channel->team_id), NULL));
951 		} else {
952 			g_hash_table_insert(defaults, "name", g_strdup(channel->name));
953 		}
954 
955 		chat = purple_chat_new(ma->account, alias, defaults);
956 		purple_blist_add_chat(chat, mm_get_or_create_default_group(), NULL);
957 
958 		mm_set_group_chat(ma, channel->team_id, channel->name, channel->id);
959 
960 		purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-persistent", TRUE);
961 		purple_blist_node_set_bool(PURPLE_BLIST_NODE(chat), "gtk-autojoin", FALSE /*autojoin*/);
962 
963 		purple_chat_set_alias(chat, alias);
964 
965 	}
966 	PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel->id));
967 	if (chatconv != NULL) {
968 		purple_chat_conversation_set_topic(chatconv, NULL, mm_make_topic(channel->header, channel->purpose, purple_chat_conversation_get_topic(chatconv)));
969 	}
970 	mm_join_room(ma, channel);
971 }
972 
973 static void
mm_add_user_to_channel(MattermostAccount * ma,MattermostChannel * channel)974 mm_add_user_to_channel(MattermostAccount *ma, MattermostChannel *channel)
975 {
976 	const gchar *user_id;
977 	JsonObject *data;
978 	gchar *postdata;
979 	gchar *url;
980 
981 	data = json_object_new();
982 	user_id = ma->self->user_id;
983 	json_object_set_string_member(data, "user_id", user_id);
984 
985 	postdata = json_object_to_string(data);
986 
987 	url = mm_build_url(ma,"/channels/%s/members", channel->id);
988 
989 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_add_user_to_channel_response, channel);
990 
991 	g_free(postdata);
992 	g_free(url);
993 }
994 
995 static void
mm_get_channel_by_id_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)996 mm_get_channel_by_id_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
997 {
998 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost channel information"),TRUE)) return;
999 
1000 	JsonObject *channel = json_node_get_object(node);
1001 	const gchar *id = json_object_get_string_member(channel, "id");
1002 	const gchar *name = json_object_get_string_member(channel, "name");
1003 	const gchar *display_name = json_object_get_string_member(channel, "display_name");
1004 	const gchar *type = json_object_get_string_member(channel, "type");
1005 	const gchar *creator_id = json_object_get_string_member(channel, "creator_id");
1006 	const gchar *team_id = user_data;
1007 	const gchar *header = json_object_get_string_member(channel, "header");
1008 	const gchar *purpose = json_object_get_string_member(channel, "purpose");
1009 
1010 	const gchar *alias;
1011 	//gboolean autojoin = purple_account_get_bool(ma->account, "use-autojoin", FALSE);
1012 
1013 	if (creator_id && *creator_id) {
1014 		g_hash_table_replace(ma->group_chats_creators, g_strdup(id), g_strdup(creator_id));
1015 	}
1016 
1017 	MattermostChannel *tmpchannel = g_new0(MattermostChannel,1);
1018 	tmpchannel->id = g_strdup(id);
1019 	tmpchannel->display_name = g_strdup(display_name);
1020 	tmpchannel->type = g_strdup(type);
1021 	tmpchannel->creator_id = g_strdup(creator_id);
1022 	tmpchannel->name = g_strdup(name);
1023 	tmpchannel->team_id = g_strdup(team_id);
1024 	tmpchannel->header = g_strdup(header);
1025 	tmpchannel->purpose = g_strdup(purpose);
1026 	tmpchannel->channel_approximate_view_time = mm_find_channel_approximate_view_time(ma, tmpchannel->id);
1027 
1028 	alias = mm_get_chat_alias(ma, tmpchannel);
1029 
1030 	if (mm_purple_blist_find_chat(ma, id) == NULL) {
1031 		// user is trying to join a new channel
1032 		mm_add_user_to_channel(ma, tmpchannel);
1033 	} else {
1034 		// user is already present in the channel, we just open the chat conv
1035 		purple_chat_set_alias(mm_purple_blist_find_chat(ma, id),alias);
1036 		PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(tmpchannel->id));
1037 		if (chatconv != NULL) {
1038 			purple_chat_conversation_set_topic(chatconv, NULL, mm_make_topic(header, purpose, purple_chat_conversation_get_topic(chatconv)));
1039 		}
1040 		mm_join_room(ma, tmpchannel);
1041 	}
1042 }
1043 
1044 
1045 static void
mm_get_channel_by_id(MattermostAccount * ma,const gchar * team_id,const gchar * id)1046 mm_get_channel_by_id(MattermostAccount *ma, const gchar *team_id, const gchar *id)
1047 {
1048 	gchar *url;
1049 	GList *tmpl;
1050 	gboolean joined = FALSE;
1051 
1052 	for(tmpl=ma->joined_channels;tmpl != NULL; tmpl=g_list_next(tmpl))
1053 		if (purple_strequal(tmpl->data,id)) {
1054 			joined = TRUE; continue;
1055 		}
1056 
1057 	// user list is lost when conv window is closed, we need to re-read data from MM
1058 	// this is rather a workaround .. reimplement the workflow ? ...
1059 	if (joined && purple_conv_chat_get_users(purple_conversations_find_chat(ma->pc, g_str_hash(id))) != NULL) {
1060 		return; }
1061 	if (!joined) ma->joined_channels = g_list_prepend(ma->joined_channels, g_strdup(id));
1062 
1063 	url = mm_build_url(ma,"/channels/%s",id);
1064 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_get_channel_by_id_response, g_strdup(team_id));
1065 	g_free(url);
1066 }
1067 
1068 
1069 static void mm_refresh_statuses(MattermostAccount *ma, const gchar *id);
1070 
1071 
1072 
1073 static MattermostUser *mm_user_from_json(MattermostAccount *ma, JsonObject *user);
1074 
1075 static void
mm_get_users_by_ids_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1076 mm_get_users_by_ids_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1077 {
1078 
1079 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost users list"),TRUE)) return;
1080 
1081 	PurpleGroup *default_group = mm_get_or_create_default_group();
1082 	MattermostUser *mm_user = NULL;
1083 	GList *mm_users = user_data;
1084 	GList *i = NULL;
1085 	JsonArray *users = json_node_get_array(node);
1086 	guint j, len = json_array_get_length(users);
1087 
1088 	if (len == 0) return;
1089 
1090 	for (i=mm_users;i;i=i->next) {
1091 		mm_user = i->data;
1092 		for (j = 0; j < len; j++) {
1093 			JsonObject *user = json_array_get_object_element(users,j);
1094 			if (g_strcmp0(mm_user->user_id,json_object_get_string_member(user,"id")) == 0){
1095 				mm_user->username = g_strdup(json_object_get_string_member(user, "username"));
1096 				mm_user->nickname = g_strdup(json_object_get_string_member(user, "nickname"));
1097 				mm_user->first_name = g_strdup(json_object_get_string_member(user, "first_name"));
1098 				mm_user->last_name = g_strdup(json_object_get_string_member(user, "last_name"));
1099 				mm_user->email = g_strdup(json_object_get_string_member(user, "email"));
1100 				mm_user->locale = g_strdup(json_object_get_string_member(user, "locale"));
1101 				mm_user->position = g_strdup(json_object_get_string_member(user, "position"));
1102 				mm_user->alias = g_strdup(mm_get_alias(mm_user));
1103 				mm_user->channel_approximate_view_time = mm_find_channel_approximate_view_time(ma, g_hash_table_lookup(ma->one_to_ones_rev,mm_user->username));
1104 			}
1105 		}
1106 	}
1107 
1108 	mm_users = g_list_sort(mm_users, mm_compare_users_by_alias_int);
1109 
1110 	for (i=mm_users; i; i=i->next) {
1111 
1112 		MattermostUser *mm_user = i->data;
1113 		PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, mm_user->username);
1114 		if (buddy == NULL) {
1115 			buddy = purple_buddy_new(ma->account, mm_user->username, NULL);
1116 			purple_blist_add_buddy(buddy, NULL, default_group, NULL);
1117 		} else {
1118 			MattermostChannel *tmpchannel = g_new0(MattermostChannel,1);
1119 			tmpchannel->id = g_strdup(mm_user->room_id);
1120 			tmpchannel->page_history = 0;
1121 			mm_get_history_of_room(ma, tmpchannel, -1);
1122 			//FIXME: GFREE THAT !
1123 		}
1124 
1125 		if (mm_user->user_id && mm_user->username) {
1126 			g_hash_table_replace(ma->ids_to_usernames, g_strdup(mm_user->user_id), g_strdup(mm_user->username));
1127 			g_hash_table_replace(ma->usernames_to_ids, g_strdup(mm_user->username), g_strdup(mm_user->user_id));
1128 		}
1129 
1130 		mm_set_user_blist(ma, mm_user, buddy);
1131 
1132 		purple_blist_node_set_string(PURPLE_BLIST_NODE(buddy), "user_id", mm_user->user_id);
1133 
1134 		//this is called for new buddies or on startup: set a flag to read history from server
1135 		purple_blist_node_set_bool(PURPLE_BLIST_NODE(buddy), "seen", FALSE);
1136 
1137 		if(purple_account_get_bool(ma->account, "use-alias", FALSE)) {
1138 			gchar *alias = g_strdup(mm_get_alias(mm_user));
1139 			purple_buddy_set_local_alias(buddy, alias);
1140 			g_free(alias);
1141 		}
1142 
1143 		mm_get_avatar(ma,buddy);
1144 		mm_refresh_statuses(ma, mm_user->user_id);
1145 
1146 	}
1147 	g_list_free_full(user_data, mm_g_free_mattermost_user);
1148 }
1149 
1150 
1151 
1152 static void
mm_get_users_by_ids(MattermostAccount * ma,GList * ids)1153 mm_get_users_by_ids(MattermostAccount *ma, GList *ids)
1154 {
1155 	GList *i;
1156 	gchar *url, *postdata;
1157 	MattermostUser *mm_user;
1158 
1159 	if (ids == NULL) {
1160 		return;
1161 	}
1162 
1163 	JsonArray *data = json_array_new();
1164 
1165 	for (i = ids; i; i = i->next) {
1166 		mm_user = i->data;
1167 		json_array_add_string_element(data, mm_user->user_id);
1168 	}
1169 
1170 	postdata = json_array_to_string(data);
1171 
1172 	url = mm_build_url(ma,"/users/ids");
1173 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_get_users_by_ids_response, ids);
1174 
1175 	json_array_unref(data);
1176 	g_free(postdata);
1177 	g_free(url);
1178 }
1179 
1180 static void
mm_tooltip_text(PurpleBuddy * buddy,PurpleNotifyUserInfo * user_info,gboolean full)1181 mm_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
1182 {
1183 	const PurplePresence *presence = purple_buddy_get_presence(buddy);
1184 
1185 	if(purple_presence_is_online(presence)) {
1186 		_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("Status"),NULL,purple_status_get_name(purple_presence_get_active_status(presence)));
1187 	}
1188 
1189 	_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("Nickname"),"nickname",NULL);
1190 	_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("First Name"),"first_name",NULL);
1191 	_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("Last Name"),"last_name",NULL);
1192 	_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("Email"),"email",NULL);
1193 	_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("Position"),"position",NULL);
1194 	_MM_TOOLTIP_LINE_ADD(buddy,user_info,_("Locale"),"locale",NULL);
1195 
1196 	gchar *rolelist = mm_purple_flag_to_role(purple_blist_node_get_int(PURPLE_BLIST_NODE(buddy),"roles"));
1197 	purple_notify_user_info_add_pair_plaintext(user_info,_("Roles"), rolelist);
1198 	g_free(rolelist);
1199 }
1200 
1201 static void
mm_set_group_chat(MattermostAccount * ma,const gchar * team_id,const gchar * channel_name,const gchar * channel_id)1202 mm_set_group_chat(MattermostAccount *ma, const gchar *team_id, const gchar *channel_name, const gchar *channel_id)
1203 {
1204 	g_hash_table_replace(ma->group_chats, g_strdup(channel_id), g_strdup(channel_name));
1205 	g_hash_table_replace(ma->group_chats_rev, g_strdup(channel_name), g_strdup(channel_id));
1206 	if (team_id) g_hash_table_replace(ma->channel_teams, g_strdup(channel_id), g_strdup(team_id));
1207 }
1208 
1209 static void
mm_remove_group_chat(MattermostAccount * ma,const gchar * channel_id)1210 mm_remove_group_chat(MattermostAccount *ma, const gchar *channel_id)
1211 {
1212 	if (!g_hash_table_lookup(ma->group_chats, channel_id)) return;
1213 
1214 	g_hash_table_remove(ma->group_chats_rev, g_hash_table_lookup(ma->group_chats, channel_id));
1215 	g_hash_table_remove(ma->group_chats, channel_id);
1216 	g_hash_table_remove(ma->channel_teams, channel_id);
1217 }
1218 
1219 static void
mm_set_me(MattermostAccount * ma)1220 mm_set_me(MattermostAccount *ma)
1221 {
1222 	if (!purple_account_get_private_alias(ma->account)) {
1223 		purple_account_set_private_alias(ma->account, ma->self->username);
1224 	}
1225 
1226 	purple_connection_set_display_name(ma->pc, ma->self->username);
1227 
1228 	g_hash_table_replace(ma->ids_to_usernames, g_strdup(ma->self->user_id), g_strdup(ma->self->username));
1229 	g_hash_table_replace(ma->usernames_to_ids, g_strdup(ma->self->username), g_strdup(ma->self->user_id));
1230 
1231 }
1232 
1233 static void
mm_get_teams(MattermostAccount * ma)1234 mm_get_teams(MattermostAccount *ma)
1235 {
1236 	gchar *url;
1237 
1238 	mm_start_socket(ma);
1239 
1240 	url = mm_build_url(ma,"/users/me/teams");
1241 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_got_teams, NULL);
1242 
1243 	g_free(url);
1244 }
1245 
1246 static void
mm_save_user_pref_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1247 mm_save_user_pref_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1248 {
1249 	MattermostUserPref *pref = user_data;
1250 	g_free(pref);
1251 	mm_check_mattermost_response(ma,node,_("Error"),_("Error saving Mattermost user preferences"),TRUE);
1252 }
1253 
1254 static void
mm_save_user_pref(MattermostAccount * ma,MattermostUserPref * pref)1255 mm_save_user_pref(MattermostAccount *ma, MattermostUserPref *pref)
1256 {
1257 	JsonArray *data = json_array_new();
1258 	JsonObject *pref_data = json_object_new();
1259 	gchar *postdata, *url;
1260 
1261 	json_object_set_string_member(pref_data, "user_id", pref->user_id);
1262 	json_object_set_string_member(pref_data, "category", pref->category);
1263 	json_object_set_string_member(pref_data, "name", pref->name);
1264 	json_object_set_string_member(pref_data, "value", pref->value);
1265 
1266 	json_array_add_object_element(data,pref_data);
1267 	postdata = json_array_to_string(data);
1268 
1269 	if (purple_strequal(pref->category,"direct_channel_show") || purple_strequal(pref->category,"group_channel_show")) {
1270 		url = mm_build_url(ma,"/users/me/preferences");
1271 		mm_fetch_url(ma, url, MATTERMOST_HTTP_PUT, postdata, -1, mm_save_user_pref_response, pref);
1272 	}
1273 
1274 	g_free(postdata);
1275 	json_array_unref(data);
1276 }
1277 
1278 
1279 //static void mm_chat_leave(PurpleConnection *pc, int id);
1280 /*
1281 static void
1282 mm_remove_blist_by_id(MattermostAccount *ma, const gchar *id)
1283 {
1284 	if (mm_hash_table_contains(ma->ids_to_usernames, id)) {
1285 		const gchar *user_name = g_hash_table_lookup(ma->ids_to_usernames, id);
1286 		PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, user_name);
1287 		if (buddy) {
1288 			g_hash_table_remove(ma->ids_to_usernames, id);
1289 			g_hash_table_remove(ma->usernames_to_ids, user_name);
1290 			purple_blist_remove_buddy(buddy);
1291 		}
1292 		//TODO: leave imconversation ?
1293 	} else {
1294 		PurpleBlistNode *node;
1295 		gboolean found = FALSE;
1296 		for (node = purple_blist_get_root(); node != NULL && !found; node = purple_blist_node_next(node, TRUE)) {
1297 			if (PURPLE_IS_CHAT(node) && purple_chat_get_account(PURPLE_CHAT(node)) == ma->account &&
1298 				purple_strequal(purple_blist_node_get_string(node, "type"), MATTERMOST_CHANNEL_TYPE_STRING(MATTERMOST_CHANNEL_GROUP))) {
1299 
1300 				found = TRUE;
1301 			}
1302 		}
1303 
1304 		if (found && PURPLE_IS_CHAT(node)) {
1305 			purple_blist_remove_chat(PURPLE_CHAT(node));
1306 			mm_chat_leave(ma->pc, g_str_hash(id));
1307 		}
1308 			//TODO: leave chatconversation ?
1309 			// 3.0 purple_chat_conversation_leave(chatconv);
1310 	}
1311 }
1312 */
1313 
1314 static void
mm_get_user_prefs_response(MattermostAccount * ma,JsonNode * node,gpointer userdata)1315 mm_get_user_prefs_response(MattermostAccount *ma, JsonNode *node, gpointer userdata)
1316 {
1317 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost user preferences"),TRUE)) return;
1318 
1319 		JsonArray *arr = json_node_get_array(node);
1320 		GList *prefs = json_array_get_elements(arr);
1321 		GList *i;
1322 
1323 		g_list_free(ma->user_prefs);
1324 
1325 		for (i = prefs; i != NULL; i = i->next) {
1326 
1327 			JsonNode *prefnode = i->data;
1328 			JsonObject *tmppref = json_node_get_object(prefnode);
1329 
1330 			MattermostUserPref *pref = g_new0(MattermostUserPref,1);
1331 			pref->user_id = g_strdup(ma->self->user_id); // not really needed
1332 			pref->category = g_strdup(json_object_get_string_member(tmppref, "category"));
1333 			pref->name = g_strdup(json_object_get_string_member(tmppref, "name"));
1334 			pref->value = g_strdup(json_object_get_string_member(tmppref, "value"));
1335 			ma->user_prefs = g_list_prepend(ma->user_prefs,pref);
1336 		}
1337 }
1338 
1339 
1340 static void
mm_get_user_prefs(MattermostAccount * ma)1341 mm_get_user_prefs(MattermostAccount *ma)
1342 {
1343 //TODO: preference categories (v5 server):
1344 //		 direct_channel_show { name:channel_id,value:bool}
1345 //		 group_channel_show { name:channel_id,value:bool}
1346 //		 tutorial_step { name:user_id,value:4 }
1347 //		 last { name:channel,value:channel_id }
1348 //		 display_settings { name:use_military_time,value:bool}
1349 //		 				{ name:name_format,value:username} (or?)
1350 //		 				{ name:selected_font,value:Open Sans} (or?)
1351 //		 				{ name:channel_display_mode,value:full} (or?}
1352 //		 				{ name:message_display,value:compact} (or?}
1353 //		 				{.name:collapse_previews,value:bool}
1354 //		 channel_approximate_view_time { name:channel_id,value:unixseconds}
1355 //		 channel_open_time { name:channel_id,value:unixseconds}
1356 
1357 	gchar *url;
1358 	url = mm_build_url(ma,"/users/me/preferences");
1359 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_get_user_prefs_response, NULL);
1360 	g_free(url);
1361 
1362 }
1363 
1364 
1365 static void
mm_get_commands_for_team_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1366 mm_get_commands_for_team_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1367 {
1368 	JsonArray *response = json_node_get_array(node);
1369 	guint i, len = json_array_get_length(response);
1370 
1371 	for (i = 0; i < len; i++) {
1372 		JsonObject *command = json_array_get_object_element(response, i);
1373 		//printf("command is:%s\n",json_object_to_string(command));
1374 		MattermostCommand *cmd = g_new0(MattermostCommand,1);
1375 		cmd->trigger = g_strdup(json_object_get_string_member(command,"trigger"));
1376 		cmd->team_id = g_strdup(json_object_get_string_member(command,"team_id"));
1377 		cmd->display_name = g_strdup(json_object_get_string_member(command,"display_name"));
1378 		cmd->description = g_strdup(json_object_get_string_member(command,"description"));
1379 		cmd->auto_complete_hint = g_strdup(json_object_get_string_member(command,"auto_complete_hint"));
1380 		cmd->auto_complete_desc = g_strdup(json_object_get_string_member(command,"auto_complete_desc"));
1381 
1382 		if (!g_list_find_custom(ma->commands,cmd,mm_compare_cmd_int)) {
1383 			// we implement these commands ourselves.
1384 			if (!purple_strequal(cmd->trigger,"help") &&
1385 					!purple_strequal(cmd->trigger,"leave") &&
1386 					!purple_strequal(cmd->trigger,"online") &&
1387 					!purple_strequal(cmd->trigger,"away") &&
1388 					!purple_strequal(cmd->trigger,"dnd") &&
1389 					!purple_strequal(cmd->trigger,"offline") &&
1390 					!purple_strequal(cmd->trigger,"logout")) {
1391 				ma->commands=g_list_prepend(ma->commands,cmd);
1392 				const gchar *info = g_strconcat(cmd->trigger," ",
1393 				strlen(cmd->auto_complete_hint) ? g_strconcat(cmd->auto_complete_hint," | ",NULL) : " | ",
1394 				strlen(cmd->auto_complete_desc) ? g_strconcat(cmd->auto_complete_desc," ",NULL) : "",
1395 				( !strlen(cmd->auto_complete_desc) && strlen(cmd->description) ) ? g_strconcat(cmd->description," ",NULL) : " ",
1396 				strlen(cmd->team_id) ? g_strconcat("[team only: ",g_hash_table_lookup(ma->teams, cmd->team_id),"]",NULL) : "",
1397 				NULL);
1398 
1399 				purple_cmd_register(cmd->trigger, "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
1400 						PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
1401 						MATTERMOST_PLUGIN_ID, mm_slash_command, info, NULL);
1402 			} else {
1403 				mm_g_free_mattermost_command(cmd);
1404 			}
1405 		} else {
1406 			mm_g_free_mattermost_command(cmd);
1407 		}
1408 	}
1409 	ma->commands = g_list_sort(ma->commands,mm_compare_cmd_2_int);
1410 }
1411 
1412 void
mm_get_commands_for_team(MattermostAccount * ma,const gchar * team_id)1413 mm_get_commands_for_team(MattermostAccount *ma,const gchar *team_id)
1414 {
1415 	gchar *url;
1416 
1417 	url = mm_build_url(ma,"/commands?team_id=%s",team_id);
1418 
1419 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_get_commands_for_team_response, g_strdup(team_id));
1420 	g_free(url);
1421 }
1422 
1423 static void
mm_get_client_config_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1424 mm_get_client_config_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1425 {
1426 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost client configuration"),TRUE)) return;
1427 
1428 	JsonObject *response = json_node_get_object(node);
1429 
1430 	if (json_object_get_string_member(response,"EnablePublicLink"), "true") {
1431 		ma->client_config->public_link = TRUE;
1432 	} else {
1433 		ma->client_config->public_link = FALSE;
1434 	}
1435 
1436 	if (json_object_get_string_member(response,"EnableCommands"), "true") {
1437 		ma->client_config->enable_commands = TRUE;
1438 	} else {
1439 		ma->client_config->enable_commands = FALSE;
1440 	}
1441 
1442 	ma->client_config->site_name = g_strdup(json_object_get_string_member(response,"SiteName"));
1443 	ma->client_config->support_email = g_strdup(json_object_get_string_member(response,"SupportEmail"));
1444 	ma->client_config->server_version = g_strdup(json_object_get_string_member(response,"Version"));
1445 	ma->client_config->site_url = g_strdup(json_object_get_string_member(response,"SiteURL"));
1446 	ma->client_config->report_a_problem_link = g_strdup(json_object_get_string_member(response,"ReportAProblemLink"));
1447 	ma->client_config->build_number = g_strdup(json_object_get_string_member(response,"BuildNumber"));
1448 	ma->client_config->build_hash = g_strdup(json_object_get_string_member(response,"BuildHash"));
1449 	ma->client_config->build_date = g_strdup(json_object_get_string_member(response,"BuildDate"));
1450 	ma->client_config->enterprise_ready = g_strdup(json_object_get_string_member(response,"BuildEnterpriseReady"));
1451 }
1452 
1453 static void
mm_get_client_config(MattermostAccount * ma)1454 mm_get_client_config(MattermostAccount *ma)
1455 {
1456 	gchar *url;
1457 	//NOTE: MM 5.3 still does not implement 'new' format.
1458 	url = mm_build_url(ma,"/config/client?format=old");
1459 
1460 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_get_client_config_response, NULL);
1461 	g_free(url);
1462 }
1463 
1464 static void
mm_me_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1465 mm_me_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1466 {
1467 	JsonObject *response;
1468 	gboolean gitlabauth = FALSE;
1469 
1470 	if (node == NULL) {
1471 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Invalid or expired Gitlab cookie"));
1472 		return;
1473 	}
1474 
1475 	response = json_node_get_object(node);
1476 
1477 	if (json_object_get_int_member(response, "status_code") >= 400) {
1478 		if (purple_account_get_bool(ma->account, "use-mmauthtoken", FALSE)) {
1479 			gitlabauth = TRUE;
1480 		}
1481 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, g_strconcat(json_object_get_string_member(response, "message"), gitlabauth ? _("(Invalid or expired Gitlab cookie)") : "",NULL));
1482 		return;
1483 	}
1484 
1485 	mm_g_free_mattermost_user(ma->self);
1486 	ma->self = g_new0(MattermostUser, 1);
1487 
1488 	if (!json_object_get_string_member(response, "id") || !json_object_get_string_member(response, "username")) {
1489 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("User ID/Name not received from server"));
1490 		return;
1491 	}
1492 
1493 	ma->self = mm_user_from_json(ma, response);
1494 
1495 	JsonObject *notify_props = json_object_get_object_member(response, "notify_props");
1496 
1497 	if (purple_strequal(json_object_get_string_member(notify_props, "all"), "true")) {
1498 		ma->mention_words = g_list_prepend(ma->mention_words,"@all");
1499 	}
1500 
1501 	if (purple_strequal(json_object_get_string_member(notify_props, "channel"), "true")) {
1502 		ma->mention_words = g_list_prepend(ma->mention_words,"@channel");
1503 	}
1504 
1505 	if (purple_strequal(json_object_get_string_member(notify_props, "first_name"), "true")) {
1506 		ma->mention_words = g_list_prepend(ma->mention_words,g_strconcat("@", ma->self->first_name, NULL));
1507 		ma->mention_words = g_list_prepend(ma->mention_words, ma->self->first_name);
1508 	}
1509 
1510 	gchar **mention_keys;
1511 	mention_keys = g_strsplit_set(json_object_get_string_member(notify_props, "mention_keys"), ",", -1);
1512 	gint i;
1513 	for (i =0 ; mention_keys[i] != NULL; i++) {
1514 		const gchar *mkey = mention_keys[i];
1515 		ma->mention_words=g_list_prepend(ma->mention_words, g_strdup(mkey));
1516 		if (mkey[0] != '@') {
1517 			ma->mention_words=g_list_prepend(ma->mention_words, g_strconcat("@", mkey, NULL));
1518 		}
1519 	}
1520 	g_strfreev(mention_keys);
1521 
1522 	gchar *regex = g_strdup("");
1523 
1524 	GList *j;
1525 	for (j = ma->mention_words; j != NULL; j=j->next) {
1526 		const gchar *tmp = j->data;
1527 		if (j != ma->mention_words) {
1528 			regex = g_strconcat(regex, "|", tmp, NULL);
1529 		} else {
1530 			regex = g_strdup(tmp);
1531 		}
1532 	}
1533 
1534 	if (ma->mention_all_regex) {
1535 		g_regex_unref(ma->mention_all_regex);
1536 	}
1537 	ma->mention_all_regex = g_regex_new(MATTERMOST_MENTION_ALL_MATCH, G_REGEX_CASELESS|G_REGEX_DOTALL|G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
1538 
1539 	if (ma->mention_me_regex) {
1540 		g_regex_unref(ma->mention_me_regex);
1541 	}
1542 
1543 	if (!purple_strequal(regex,"")) {
1544 		ma->mention_me_regex = g_regex_new(MATTERMOST_MENTION_ME_MATCH(regex), G_REGEX_CASELESS|G_REGEX_DOTALL|G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
1545 	} else {
1546 		ma->mention_me_regex = NULL;
1547 	}
1548 
1549 	g_free(regex);
1550 
1551 	//TODO: get avatar ?
1552 	mm_get_user_prefs(ma);
1553 	mm_get_client_config(ma);
1554 	mm_set_me(ma);
1555 	mm_get_teams(ma);
1556 }
1557 
1558 static void
mm_get_me(MattermostAccount * ma)1559 mm_get_me(MattermostAccount *ma)
1560 {
1561 	gchar *url;
1562 	url = mm_build_url(ma,"/users/me");
1563 
1564 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_me_response, NULL);
1565 	g_free(url);
1566 }
1567 
1568 static void
mm_login_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1569 mm_login_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1570 {
1571 	JsonObject *response;
1572 
1573 	if (node == NULL) {
1574 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Bad username/password"));
1575 		return;
1576 	}
1577 
1578 	response = json_node_get_object(node);
1579 
1580 	if (mm_hash_table_contains(ma->cookie_table, "MMAUTHTOKEN")) {
1581 		g_free(ma->session_token);
1582 		ma->session_token = g_strdup(g_hash_table_lookup(ma->cookie_table, "MMAUTHTOKEN"));
1583 	} else if (json_object_has_member(response, "body")) {
1584 		// Uh oh, error
1585 		gchar *stripped = purple_markup_strip_html(json_object_get_string_member(response, "body"));
1586 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, stripped);
1587 		g_free(stripped);
1588 		return;
1589 	}
1590 
1591 	if (json_object_get_int_member(response, "status_code") >= 400) {
1592 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, json_object_get_string_member(response, "message"));
1593 		return;
1594 	}
1595 
1596 	if (!json_object_get_string_member(response, "id") || !json_object_get_string_member(response, "username")) {
1597 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("User ID/Name not received from server"));
1598 		return;
1599 	}
1600 
1601 	mm_get_me(ma);
1602 }
1603 
1604 
1605 
1606 static PurpleChatUserFlags
mm_role_to_purple_flag(MattermostAccount * ma,const gchar * rolelist)1607 mm_role_to_purple_flag(MattermostAccount *ma, const gchar *rolelist)
1608 {
1609 	PurpleChatUserFlags flags = PURPLE_CHAT_USER_NONE;
1610 	gchar **roles = g_strsplit_set(rolelist, " ", -1);
1611 	gint i;
1612 
1613 	for(i = 0; roles[i]; i++) {
1614 		const gchar *role = roles[i];
1615 
1616 		// we are always channel_user
1617 		if (purple_strequal(role, "channel_admin")) {
1618 			flags |= PURPLE_CHAT_USER_OP;
1619 		} else if (purple_strequal(role, "system_admin")) {
1620 			flags |= PURPLE_CHAT_USER_FOUNDER;
1621 		}
1622 	}
1623 
1624 	g_strfreev(roles);
1625 
1626 	return flags;
1627 }
1628 
1629 static gchar *
mm_purple_flag_to_role(PurpleChatUserFlags flags)1630 mm_purple_flag_to_role(PurpleChatUserFlags flags)
1631 {
1632 	const gchar *cu_str = _("Channel User");
1633 	const gchar *ca_str = _("Channel Administrator");
1634 	const gchar *sa_str = _("System Administrator");
1635 	gboolean ca = FALSE;
1636 	gboolean sa = FALSE;
1637 
1638 	// we are always channel_user
1639 	if (flags & PURPLE_CHAT_USER_OP) {
1640 		ca = TRUE;
1641 	}
1642 	if (flags & PURPLE_CHAT_USER_FOUNDER) {
1643 		sa = TRUE;
1644 	}
1645 
1646 	return g_strjoin(", ", cu_str, ca ? ca_str : "", sa ? sa_str : "", NULL);
1647 }
1648 
1649 static void
mm_purple_message_file_send(MattermostAccount * ma,MattermostFile * mmfile,const gchar * anchor,gboolean isimage)1650 mm_purple_message_file_send(MattermostAccount *ma, MattermostFile *mmfile, const gchar *anchor, gboolean isimage)
1651 {
1652 	PurpleMessageFlags msg_flags = (purple_strequal(mmfile->mmchlink->sender, ma->self->username) ? PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_REMOTE_SEND | PURPLE_MESSAGE_DELAYED : PURPLE_MESSAGE_RECV);
1653 
1654 	if (isimage) msg_flags |= PURPLE_MESSAGE_IMAGES;
1655 
1656 	if (mm_hash_table_contains(ma->group_chats, mmfile->mmchlink->channel_id)) {
1657 		purple_serv_got_chat_in(ma->pc, g_str_hash(mmfile->mmchlink->channel_id), mmfile->mmchlink->sender, msg_flags, anchor, mmfile->mmchlink->timestamp);
1658 	} else {
1659 		if (msg_flags == PURPLE_MESSAGE_RECV) {
1660 			purple_serv_got_im(ma->pc, mmfile->mmchlink->sender, anchor, msg_flags, mmfile->mmchlink->timestamp);
1661 		} else {
1662 			const gchar *other_user = g_hash_table_lookup(ma->one_to_ones, mmfile->mmchlink->channel_id);
1663 			// TODO null check
1664 			PurpleIMConversation *imconv = purple_conversations_find_im_with_account(other_user, ma->account);
1665 			PurpleMessage *pmsg = purple_message_new_outgoing(other_user, anchor, msg_flags);
1666 
1667 			if (imconv == NULL) {
1668 				imconv = purple_im_conversation_new(ma->account, other_user);
1669 			}
1670 			purple_message_set_time(pmsg, mmfile->mmchlink->timestamp);
1671 			purple_conversation_write_message(PURPLE_CONVERSATION(imconv), pmsg);
1672 			purple_message_destroy(pmsg);
1673 		}
1674 	}
1675 }
1676 
1677 static void
mm_process_message_image_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1678 mm_process_message_image_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1679 {
1680 	MattermostFile *mmfile = user_data;
1681 	PurpleImage *image;
1682 	guint image_id;
1683 	gsize response_len;
1684 	gpointer response_dup;
1685 	const gchar *response_str;
1686 	gchar *anchor;
1687 
1688 	JsonObject *response = json_node_get_object(node);
1689 
1690 	response_str = g_dataset_get_data(node, "raw_body");
1691 	response_len = json_object_get_int_member(response,"len");
1692 	response_dup = g_memdup(response_str, response_len);
1693 
1694 	image = purple_image_new_from_data(response_dup,response_len);
1695 	image_id = purple_image_store_add(image);
1696 
1697 	if (purple_account_get_bool(ma->account,"show-full-images", FALSE)) {
1698 		anchor = g_strdup_printf("<img id='%d' src='%s' />", image_id, mmfile->uri);
1699 	} else {
1700 		anchor = g_strdup_printf("<a href='%s'>%s <img id='%d' src='%s' /></a>", mmfile->uri, _("[view full image]"), image_id, mmfile->uri);
1701 	}
1702 
1703 	mm_purple_message_file_send(ma, mmfile, anchor, TRUE);
1704 
1705 	g_free(anchor);
1706 	mm_g_free_mattermost_file(mmfile);
1707 }
1708 
1709 static void
mm_process_message_image(MattermostAccount * ma,MattermostFile * mmfile)1710 mm_process_message_image(MattermostAccount *ma, MattermostFile *mmfile)
1711 {
1712 	gchar *url;
1713 
1714 	if (mmfile->has_preview_image) {
1715 		url=mm_build_url(ma,"/files/%s/preview", mmfile->id);
1716 	} else if (purple_account_get_bool(ma->account,"show-full-images", FALSE)) {
1717 		url=mm_build_url(ma,"/files/%s" , mmfile->id);
1718 	} else {
1719 		url=mm_build_url(ma,"/files/%s/thumbnail" , mmfile->id);
1720 	}
1721 
1722 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_process_message_image_response, mmfile);
1723 	g_free(url);
1724 }
1725 
1726 static void
mm_file_metadata_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)1727 mm_file_metadata_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1728 {
1729 
1730 	JsonObject *response = json_node_get_object(node);
1731 	MattermostFile *mmfile = user_data;
1732 	gchar *anchor = NULL;
1733 
1734 //todo: if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting Mattermost file metadata"),TRUE)) return;
1735 	if (json_object_get_int_member(response, "status_code") >= 400) {
1736 		anchor = g_strdup(mmfile->uri);
1737 	} else {
1738 		mmfile->name = g_strdup(json_object_get_string_member(response, "name"));
1739 		mmfile->mime_type = g_strdup(json_object_get_string_member(response, "mime_type"));
1740 		mmfile->id = g_strdup(json_object_get_string_member(response, "id"));
1741 		if (purple_strequal(json_object_get_string_member(response, "has_preview_image"),"true")) {
1742 			mmfile->has_preview_image = TRUE;
1743 		} else {
1744 			mmfile->has_preview_image = FALSE;
1745 		}
1746 	}
1747 
1748 // do we really support any image type ? ...
1749 	if (g_str_has_prefix(mmfile->mime_type,"image/") && purple_account_get_bool(ma->account, "show-images", TRUE)) {
1750 		mm_process_message_image(ma,mmfile);
1751 		return;
1752 	}
1753 
1754 	//TODO: that file can have thumbnail, display it ? ...
1755 	if (!mmfile->uri || !ma->client_config->public_link) {
1756 		// get team_id for this channel, if possible
1757 		const gchar *team_id = NULL;
1758 		if (mmfile->mmchlink->channel_id) {
1759 			team_id = g_hash_table_lookup(ma->channel_teams, mmfile->mmchlink->channel_id);
1760 		}
1761 
1762 		// if there is no channel id or the lookup failed, use first team_id
1763 		if (!team_id || strlen(team_id) == 0) {
1764 			team_id = mm_get_first_team_id(ma);
1765 		}
1766 
1767 		const gchar *team_name = g_hash_table_lookup(ma->teams, team_id);
1768 		gchar *link_error_str = g_strconcat("[error: public links disabled on server, cannot get file: ",mmfile->name, NULL);
1769 		if (team_name) {
1770 			gchar *url = g_strconcat((purple_account_get_bool(ma->account, "use-ssl", TRUE)?"https://":"http://"), ma->server,"/", team_name, "/pl/", mmfile->mmchlink->post_id, NULL);
1771 			anchor = g_strconcat(link_error_str, ", visit ","<a href=\"", url, "\">", url, "</a> to access the file]" , NULL);
1772 			g_free(url);
1773 		} else {
1774 			anchor = g_strconcat(link_error_str, "]", NULL);
1775 		}
1776 		g_free(link_error_str);
1777 	} else {
1778 		if (!anchor) anchor = g_strconcat("<a href=\"", mmfile->uri, "\">", mmfile->name, "</a>", NULL);
1779 	}
1780 
1781 	mm_purple_message_file_send(ma, mmfile, anchor, FALSE);
1782 
1783 	mm_g_free_mattermost_file(mmfile);
1784 	g_free(anchor);
1785 }
1786 
1787 
1788 static void
mm_fetch_file_metadata(MattermostAccount * ma,JsonNode * node,gpointer user_data)1789 mm_fetch_file_metadata(MattermostAccount *ma, JsonNode *node, gpointer user_data)
1790 {
1791 	MattermostChannelLink *mmchlink = user_data;
1792 	MattermostFile *mmfile = g_new0(MattermostFile,1);
1793 	mmfile->uri = g_strdup(json_object_get_string_member(json_node_get_object(node),"link"));
1794 	mmfile->mmchlink = mmchlink;
1795 
1796 	gchar *url;
1797 
1798 	url = mm_build_url(ma,"/files/%s/info", mmfile->mmchlink->file_id);
1799 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_file_metadata_response, mmfile);
1800 
1801 	g_free(url);
1802 }
1803 
1804 static void
mm_fetch_file_link_for_channel(MattermostAccount * ma,const gchar * file_id,const gchar * channel_id,const gchar * post_id,const gchar * username,gint64 timestamp)1805 mm_fetch_file_link_for_channel(MattermostAccount *ma, const gchar *file_id, const gchar *channel_id, const gchar *post_id, const gchar *username, gint64 timestamp)
1806 {
1807 	MattermostChannelLink *info = g_new0(MattermostChannelLink, 1);
1808 	gchar *url;
1809 
1810 	info->channel_id = g_strdup(channel_id);
1811 	info->file_id = g_strdup(file_id);
1812 	info->post_id = g_strdup(post_id);
1813 	info->sender = g_strdup(username);
1814 	info->timestamp = timestamp;
1815 
1816 	url = mm_build_url(ma,"/files/%s/link", file_id);
1817 
1818 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_fetch_file_metadata, info);
1819 
1820 	g_free(url);
1821 }
1822 
1823 static gboolean
mm_have_seen_message_id(MattermostAccount * ma,const gchar * message_id)1824 mm_have_seen_message_id(MattermostAccount *ma, const gchar *message_id)
1825 {
1826 	guint message_hash = g_str_hash(message_id);
1827 	gpointer message_hash_ptr = GINT_TO_POINTER(message_hash);
1828 
1829 	if (g_queue_find(ma->received_message_queue, message_hash_ptr)) {
1830 		return TRUE;
1831 	}
1832 
1833 	g_queue_push_head(ma->received_message_queue, message_hash_ptr);
1834 	g_queue_pop_nth(ma->received_message_queue, 10);
1835 
1836 	return FALSE;
1837 }
1838 
1839 static void mm_mark_room_messages_read(MattermostAccount *ma, const gchar *room_id);
1840 
1841 static gchar *mm_process_attachment(JsonObject *attachment);
1842 
1843 static gint64
mm_process_room_message(MattermostAccount * ma,JsonObject * post,JsonObject * data)1844 mm_process_room_message(MattermostAccount *ma, JsonObject *post, JsonObject *data)
1845 {
1846 	const gchar *id = json_object_get_string_member(post, "id");
1847 	const gchar *msg_text = json_object_get_string_member(post, "message");
1848 	const gchar *channel_id = json_object_get_string_member(post, "channel_id");
1849 	const gchar *msg_type = json_object_get_string_member(post, "type");
1850 	const gchar *user_id = json_object_get_string_member(post, "user_id");
1851 	const gchar *username = json_object_get_string_member(data, "sender_name");
1852 	const gchar *channel_type = json_object_get_string_member(data, "channel_type");
1853 	const gchar *type = json_object_get_string_member(post, "type");
1854 	const gchar *pending_post_id = json_object_get_string_member(post, "pending_post_id");
1855 	JsonObject *props = json_object_get_object_member(post, "props");
1856 	const gchar *override_username = json_object_get_string_member(props, "override_username");
1857 	const gchar *from_webhook = json_object_get_string_member(props, "from_webhook");
1858 	gint64 update_at = json_object_get_int_member(post, "update_at");
1859 	gint64 timestamp = update_at / 1000;
1860 	gchar *use_username;
1861 
1862 	gchar *attachments = NULL;
1863 
1864 	// Strip '@' from username
1865 	if (username && username[0] == '@') {
1866 		username++;
1867 	}
1868 
1869 	if (purple_strequal(type, "slack_attachment")) {
1870 		JsonArray *attchs = json_object_get_array_member(props, "attachments");
1871 		guint i, len = json_array_get_length(attchs);
1872 		gchar *tmpa1, *tmpa2;
1873 		for (i=0; i < len ; i++) {
1874 			JsonObject *attch = json_node_get_object(json_array_get_element(attchs, i));
1875 			tmpa1 = g_strdup(attachments);
1876 			tmpa2 = mm_process_attachment(attch);
1877 			g_free(attachments);
1878 			if (tmpa1) {
1879 				attachments = g_strconcat(tmpa1, tmpa2, NULL);
1880 			} else {
1881 				attachments = g_strdup(tmpa2);
1882 			}
1883 			g_free(tmpa1);
1884 			g_free(tmpa2);
1885 		}
1886 	}
1887 
1888 	// ephemeral messages have update_at:0
1889 	if (!timestamp) {
1890 		gint64 create_at =  json_object_get_int_member(post, "create_at");
1891 		timestamp = create_at / 1000;
1892 		update_at = create_at;
1893 	}
1894 
1895 	PurpleMessageFlags msg_flags;
1896 
1897 	if (username != NULL && !mm_hash_table_contains(ma->ids_to_usernames, user_id)) {
1898 		g_hash_table_replace(ma->ids_to_usernames, g_strdup(user_id), g_strdup(username));
1899 		g_hash_table_replace(ma->usernames_to_ids, g_strdup(username), g_strdup(user_id));
1900 	} else if (username == NULL) {
1901 		username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
1902 	}
1903 
1904 	if (purple_strequal(from_webhook, "true") && override_username && *override_username) {
1905 		use_username = g_strconcat(override_username, MATTERMOST_BOT_LABEL, NULL);
1906 		msg_flags = PURPLE_MESSAGE_RECV;	// user_id for BOT is webhook owner ID .. t own BOTS as such too !
1907 	} else {
1908 		use_username = g_strdup(username);
1909 		msg_flags = (purple_strequal(user_id, ma->self->user_id) ? PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_REMOTE_SEND | PURPLE_MESSAGE_DELAYED : PURPLE_MESSAGE_RECV);
1910 	}
1911 
1912 	if (use_username == NULL) {
1913 		//we get here when reading history of a chat
1914 		//but have not yet read user list for the channel
1915 		//since this calls run in parallel
1916 		//FIXME: or not ?
1917 		use_username=g_strdup("[unknown]");
1918 	}
1919 
1920 	if (!mm_hash_table_contains(ma->channel_teams, channel_id)) {
1921 		const gchar *team_id = json_object_get_string_member(data, "team_id");
1922 		if (team_id != NULL) {
1923 			g_hash_table_replace(ma->channel_teams, g_strdup(channel_id), g_strdup(team_id));
1924 		}
1925 	}
1926 
1927 	if (g_str_has_prefix(msg_type, "system_")) {
1928 		msg_flags |= PURPLE_MESSAGE_SYSTEM;
1929 	}
1930 
1931 	if (!mm_have_seen_message_id(ma, id) || json_object_get_int_member(post, "edit_at")) {
1932 		// Dont display duplicate messages (eg where the server inspects urls to give icons/header/content)
1933 		//  but do display edited messages
1934 
1935 		// check we didn't send this ourselves
1936 		if (msg_flags == PURPLE_MESSAGE_RECV || !g_hash_table_remove(ma->sent_message_ids, pending_post_id)) {
1937 			gchar *msg_pre = mm_markdown_to_html(ma, msg_text);
1938 			gchar *msg_post = g_regex_replace(ma->mention_me_regex, msg_pre, -1, 0, MATTERMOST_MENTION_ME_REPLACE, G_REGEX_MATCH_NOTEMPTY, NULL);
1939 			gchar *message = g_regex_replace(ma->mention_all_regex, msg_post, -1, 0, MATTERMOST_MENTION_ALL_REPLACE, G_REGEX_MATCH_NOTEMPTY, NULL);
1940 
1941 			if (!purple_strequal(msg_pre, msg_post)) {
1942 				msg_flags |= PURPLE_MESSAGE_NICK;
1943 			}
1944 
1945 			g_free(msg_pre);
1946 			g_free(msg_post);
1947 
1948 			if (json_object_get_int_member(post, "delete_at")) {
1949 				gchar *tmp = g_strconcat(_("Deleted: "), message, NULL);
1950 				g_free(message);
1951 				message = tmp;
1952 			} else if (json_object_get_int_member(post, "edit_at")) {
1953 				gchar *tmp = g_strconcat(_("Edited: "), message, NULL);
1954 				g_free(message);
1955 				message = tmp;
1956 			}
1957 
1958 			if (json_object_has_member(post, "file_ids")) {
1959 				JsonArray *file_ids = json_object_get_array_member(post, "file_ids");
1960 				guint i, len = json_array_get_length(file_ids);
1961 
1962 				// pass post_id so that permalink to the post can be displayed in case of error
1963 				const gchar *post_id = json_object_get_string_member(post, "id");
1964 
1965 				for (i = 0; i < len; i++) {
1966 					const gchar *file_id = json_array_get_string_element(file_ids, i);
1967 
1968 					mm_fetch_file_link_for_channel(ma, file_id, channel_id, post_id, use_username, timestamp);
1969 				}
1970 			}
1971 
1972 //FIXME JAREK: dont know the TEAM here
1973 
1974 			if ((channel_type != NULL && *channel_type != MATTERMOST_CHANNEL_DIRECT) || mm_hash_table_contains(ma->group_chats, channel_id)) {
1975 				PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
1976 
1977 				if (chatconv) {
1978 					if (purple_strequal(msg_type, "system_header_change") || purple_strequal(msg_type, "system_purpose_change")) {
1979 						const gchar *new_header = json_object_get_string_member(props, "new_header");
1980 						const gchar *new_purpose = json_object_get_string_member(props, "new_purpose");
1981 						const gchar *new_topic_who = json_object_get_string_member(props, "username");
1982 						purple_chat_conversation_set_topic(chatconv, new_topic_who, mm_make_topic(new_header, new_purpose, purple_chat_conversation_get_topic(chatconv)));
1983 					}
1984 
1985 					// Group chat message
1986 					gchar *msg_out = g_strconcat( message ? message : " " , attachments ? attachments : NULL, NULL);
1987 					gchar *alias = g_hash_table_lookup(ma->aliases,channel_id);
1988 
1989 					if (alias && chatconv) {
1990 						purple_conversation_set_name(PURPLE_CONVERSATION(chatconv),alias);
1991 					}
1992 
1993 					purple_serv_got_chat_in(ma->pc, g_str_hash(channel_id), use_username, msg_flags, msg_out, timestamp);
1994 
1995 					mm_get_channel_by_id(ma, g_hash_table_lookup(ma->channel_teams, channel_id), channel_id);
1996 
1997 					g_free(msg_out);
1998 
1999 					if (purple_conversation_has_focus(PURPLE_CONVERSATION(chatconv))) {
2000 						mm_mark_room_messages_read(ma, channel_id);
2001 					}
2002 				} //TODO: else { ERROR } - we have received a group chat message for a chat we dont know about ?
2003 			} else {
2004 				if (msg_flags == PURPLE_MESSAGE_RECV) {
2005 					gchar *msg_out = g_strconcat( message ? message : " " , attachments ? attachments : NULL, NULL);
2006 					purple_serv_got_im(ma->pc, use_username, msg_out, msg_flags, timestamp);
2007 
2008 					g_free(msg_out);
2009 
2010 					if (channel_type && *channel_type == MATTERMOST_CHANNEL_DIRECT && !mm_hash_table_contains(ma->one_to_ones, channel_id)) {
2011 						g_hash_table_replace(ma->one_to_ones, g_strdup(channel_id), g_strdup(username));
2012 						g_hash_table_replace(ma->one_to_ones_rev, g_strdup(username), g_strdup(channel_id));
2013 					}
2014 
2015 					if (purple_conversation_has_focus(PURPLE_CONVERSATION(purple_conversations_find_im_with_account(username, ma->account)))) {
2016 						mm_mark_room_messages_read(ma, channel_id);
2017 					}
2018 
2019 				} else {
2020 					const gchar *other_user = g_hash_table_lookup(ma->one_to_ones, channel_id);
2021 					// TODO null check
2022 					PurpleIMConversation *imconv = purple_conversations_find_im_with_account(other_user, ma->account);
2023 					PurpleMessage *pmsg = purple_message_new_outgoing(other_user, message, msg_flags);
2024 
2025 					if (imconv == NULL) {
2026 						imconv = purple_im_conversation_new(ma->account, other_user);
2027 					}
2028 					purple_message_set_time(pmsg, timestamp);
2029 					purple_conversation_write_message(PURPLE_CONVERSATION(imconv), pmsg);
2030 					purple_message_destroy(pmsg);
2031 				}
2032 			}
2033 
2034 			g_free(message);
2035 		}
2036 
2037 	}
2038 
2039 	g_free(use_username);
2040 	g_free(attachments);
2041 
2042 	return update_at;
2043 }
2044 
2045 static void
mm_got_hello_user_statuses(MattermostAccount * ma,JsonNode * node,gpointer user_data)2046 mm_got_hello_user_statuses(MattermostAccount *ma, JsonNode *node, gpointer user_data)
2047 {
2048 
2049 	JsonObject *obj = json_node_get_object(node);
2050 	JsonObject *data = json_object_get_object_member(obj, "data");
2051 	GList *ids = json_object_get_members(data);
2052 	GList *i;
2053 
2054 	for (i = ids; i; i = i->next) {
2055 		const gchar *user_id = i->data;
2056 		const gchar *status = json_object_get_string_member(data, user_id);
2057 		const gchar *username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
2058 
2059 		if (username != NULL && status != NULL) {
2060 			purple_protocol_got_user_status(ma->account, username, status, NULL);
2061 		}
2062 	}
2063 
2064 	g_list_free(ids);
2065 }
2066 
2067 static void
mm_got_user_statuses_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)2068 mm_got_user_statuses_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
2069 {
2070 
2071 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error getting user statuses"),TRUE)) return;
2072 
2073 	JsonArray *users = json_node_get_array(node);
2074 	guint i, len = json_array_get_length(users);
2075 
2076 	for (i = 0; i < len; i++) {
2077 		JsonObject *user = json_array_get_object_element(users,i);
2078 		const gchar *user_id = json_object_get_string_member(user, "user_id");
2079 		const gchar *status = json_object_get_string_member(user, "status");
2080 		const gchar *username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
2081 
2082 		if (username != NULL && status != NULL) {
2083 			purple_protocol_got_user_status(ma->account, username, status, NULL);
2084 		}
2085 	}
2086 }
2087 
2088 static void
mm_refresh_statuses(MattermostAccount * ma,const gchar * id)2089 mm_refresh_statuses(MattermostAccount *ma, const gchar *id)
2090 {
2091 	JsonArray *user_ids;
2092 	gchar *url;
2093 	gchar *postdata;
2094 	user_ids = json_array_new();
2095 
2096 	if (id != NULL) {
2097 		json_array_add_string_element(user_ids, id);
2098 	} else {
2099 		GSList *buddies = purple_find_buddies(ma->account, NULL);
2100 		GSList *buddy_it = buddies;
2101 		while(buddy_it != NULL){
2102 			PurpleBuddy *buddy = buddy_it->data;
2103 			const gchar *buddy_name = purple_buddy_get_name(buddy);
2104 			const gchar *user_id = g_hash_table_lookup(ma->usernames_to_ids, buddy_name);
2105 			json_array_add_string_element(user_ids, user_id);
2106 
2107 			buddy_it = g_slist_next(buddy_it);
2108 		}
2109 		g_slist_free(buddies);
2110 	}
2111 	guint len = json_array_get_length(user_ids);
2112 	if(len == 0){
2113 		return;
2114 	}
2115 	postdata = json_array_to_string(user_ids);
2116 
2117 	url = mm_build_url(ma,"/users/status/ids");
2118 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_got_user_statuses_response, NULL);
2119 }
2120 
2121 
2122 static gchar *
mm_process_attachment(JsonObject * attachment)2123 mm_process_attachment(JsonObject *attachment)
2124 {
2125 //TODO: sanitze input strings !
2126 //TODO: libpurple xhtml-im parser is .. fragile .. easy to get output not htmlized ...
2127 
2128 	gchar *msg_top = NULL;
2129 	gchar *msg_fields = NULL;
2130 	gchar *message = NULL;
2131 
2132 	//fallback
2133 	const gchar *color = json_object_get_string_member(attachment, "color");
2134 	const gchar *pretext = json_object_get_string_member(attachment, "pretext");
2135 	const gchar *text = json_object_get_string_member(attachment, "text");
2136 	const gchar *author_name = json_object_get_string_member(attachment, "author_name");
2137 	//const gchar *author_icon = json_object_get_string_member(attachment, "author_icon");
2138 	const gchar *author_link = json_object_get_string_member(attachment,"author_link");
2139 	const gchar *title = json_object_get_string_member(attachment, "title");
2140 	const gchar *title_link = json_object_get_string_member(attachment, "title_link");
2141 	// following are not implemented in MM 3.09 (yet?)
2142 	const gchar *image_url = json_object_get_string_member(attachment, "image_url");
2143 	//const gchar *thumb_url = json_object_get_string_member(attachment, "thumb_url");
2144 	//const gchar *footer = json_object_get_string_member(attachment, "footer");
2145 	//const gchar *footer_icon = json_object_get_string_member(attachment, "footer_icon");
2146 	//const gint64 *ts = json_object_get_int_member(attachment, "ts");
2147 
2148 	JsonArray *fields = json_object_get_array_member(attachment, "fields");
2149 	guint fields_len = json_array_get_length(fields);
2150 
2151 	GList *flds_list = NULL;
2152 	guint i;
2153 
2154 	for (i = 0; i < fields_len; i++) {
2155 		MattermostAttachmentField *fld_cont = g_new0(MattermostAttachmentField, 1);
2156 		JsonObject *field = json_node_get_object(json_array_get_element(fields, i));
2157 
2158 		fld_cont->title = g_strdup(json_object_get_string_member(field, "title"));
2159 		fld_cont->value = g_strdup(json_object_get_string_member(field, "value"));
2160 		//fld_cont-> short : we cannot format in multi-column (easily..)
2161 		flds_list = g_list_append(flds_list, fld_cont);
2162 	}
2163 
2164 	//TODO: symbolic color names .. and checking
2165 
2166 	if (!color) {
2167 		color = "#FFFFFF";
2168 	}
2169 
2170 	msg_top = g_strconcat(
2171 		MM_ATT_BREAK, MM_ATT_TEXT(pretext),
2172 		MM_ATT_LINE,
2173 		MM_ATT_BORDER(color), MM_ATT_AUTHOR(author_name,author_link),
2174 		MM_ATT_BORDER(color), MM_ATT_TITLE(title,title_link),
2175 		MM_ATT_BORDER(color), MM_ATT_TEXT(text),
2176 		MM_ATT_BORDER(color), MM_ATT_IMAGE(image_url),
2177 		NULL);
2178 
2179 	GList *j;
2180 	gchar *tmpl1 = NULL;
2181 	gchar *tmpl2 = NULL;
2182 
2183 	for (j=flds_list; j != NULL; j=j->next) {
2184 		MattermostAttachmentField *af = j->data;
2185 		tmpl1 = g_strdup(msg_fields);
2186 		g_free(msg_fields);
2187 		tmpl2 = g_strconcat(
2188 			MM_ATT_BORDER(color), MM_ATT_FTITLE(af->title),
2189 			MM_ATT_BORDER(color), MM_ATT_TEXT(af->value),
2190 			NULL);
2191 		if (tmpl1) {
2192 			msg_fields = g_strconcat(tmpl1, tmpl2, NULL);
2193 		} else {
2194 			msg_fields = g_strdup(tmpl2);
2195 		}
2196 		g_free(tmpl1);
2197 		g_free(tmpl2);
2198 	}
2199 
2200 	message = g_strconcat(msg_top, msg_fields ? msg_fields : " ", MM_ATT_LINE, NULL);
2201 	g_free(msg_top);
2202 	g_free(msg_fields);
2203 	g_list_free_full(flds_list, mm_g_free_mattermost_attachment_field);
2204 
2205 	return message;
2206 }
2207 
2208 static void
mm_process_msg(MattermostAccount * ma,JsonNode * element_node)2209 mm_process_msg(MattermostAccount *ma, JsonNode *element_node)
2210 {
2211 	//JsonObject *response = NULL;
2212 	JsonObject *obj = json_node_get_object(element_node);
2213 
2214 	const gchar *event = json_object_get_string_member(obj, "event");
2215 	const gchar *status = json_object_get_string_member(obj, "status");
2216 	JsonObject *data = json_object_get_object_member(obj, "data");
2217 	JsonObject *broadcast = json_object_get_object_member(obj, "broadcast");
2218 	mm_get_or_create_default_group();
2219 
2220 	if (event == NULL) {
2221 		gint seq_reply = json_object_get_int_member(obj, "seq_reply");
2222 		MattermostProxyConnection *proxy = g_hash_table_lookup(ma->result_callbacks, GINT_TO_POINTER(seq_reply));
2223 
2224 		if (proxy != NULL) {
2225 			if (proxy->callback != NULL) {
2226 				proxy->callback(ma, element_node, proxy->user_data);
2227 			}
2228 			g_hash_table_remove(ma->result_callbacks, GINT_TO_POINTER(seq_reply));
2229 		}
2230 	}
2231 
2232 	if (purple_strequal(event, "posted") || purple_strequal(event, "post_edited") || purple_strequal(event, "ephemeral_message")) {
2233 		JsonParser *post_parser = json_parser_new();
2234 		const gchar *post_str = json_object_get_string_member(data, "post");
2235 
2236 		if (json_parser_load_from_data(post_parser, post_str, -1, NULL)) {
2237 			JsonObject *post = json_node_get_object(json_parser_get_root(post_parser));
2238 			const gchar *channel_id = json_object_get_string_member(post, "channel_id");
2239 			const gchar *user_id =  mm_data_or_broadcast_string("user_id");
2240 			const gchar *team_id = json_object_get_string_member(post, "team_id");
2241 
2242 			// detect posts with reactions (update time is larger than edit) and ignore them
2243 			if (purple_strequal(event, "post_edited") && json_object_get_int_member(post, "update_at") > json_object_get_int_member(post, "edit_at")) {
2244 				// do nothing
2245 			} else
2246 			//type system_join_channel, channel_id is ""
2247 			if (!purple_strequal(channel_id,"") && purple_strequal(ma->self->user_id, user_id)) {
2248 				mm_get_channel_by_id(ma, team_id, channel_id);
2249 
2250 			} else if (!purple_strequal(channel_id,"") && g_hash_table_lookup(ma->group_chats, channel_id)) {
2251 				PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
2252 				if (!chatconv) {
2253 					PurpleChat *chat = mm_purple_blist_find_chat(ma,channel_id);
2254 						if (chat) {
2255 							GHashTable *components = purple_chat_get_components(chat);
2256 							gchar *team_id = g_hash_table_lookup(components, "team_id");
2257 							gchar *channel_id = g_hash_table_lookup(components, "id");
2258 							gchar *type = g_hash_table_lookup(components, "type");
2259 							gchar *display_name = g_hash_table_lookup(components, "display_name");
2260 							const gchar *alias;
2261 
2262 							MattermostChannel *tmpchannel = g_new0(MattermostChannel,1);
2263 							tmpchannel->id = g_strdup(channel_id);
2264 							tmpchannel->team_id = g_strdup(team_id);
2265 							tmpchannel->display_name = g_strdup(display_name);
2266 							tmpchannel->type = g_strdup(type);
2267 
2268 							alias = mm_get_chat_alias(ma,tmpchannel);
2269 
2270 							PurpleChatConversation *conv = purple_serv_got_joined_chat(ma->pc, g_str_hash(channel_id), alias);
2271 							purple_conversation_set_data(PURPLE_CONVERSATION(conv), "id", g_strdup(channel_id));
2272 							purple_conversation_set_data(PURPLE_CONVERSATION(conv), "team_id", g_strdup(team_id));
2273 							purple_conversation_set_data(PURPLE_CONVERSATION(conv), "name", g_strdup(alias));
2274 							purple_conversation_set_data(PURPLE_CONVERSATION(conv), "display_name", g_strdup(display_name));
2275 							purple_conversation_present(PURPLE_CONVERSATION(conv));
2276 						}
2277 				}
2278 			}
2279 
2280 			if (!purple_strequal(channel_id,"")) {
2281 				mm_process_room_message(ma, post, data);
2282 			}
2283 		}
2284 		g_object_unref(post_parser);
2285 	} else if (purple_strequal(event, "typing")) {
2286 		const gchar *channel_id = mm_data_or_broadcast_string("channel_id");
2287 		const gchar *user_id = mm_data_or_broadcast_string("user_id");
2288 		const gchar *username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
2289 
2290 		if (mm_hash_table_contains(ma->group_chats, channel_id)) {
2291 			// This is a group conversation
2292 			PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
2293 			if (chatconv != NULL) {
2294 				PurpleChatUser *cb = purple_chat_conversation_find_user(chatconv, username);
2295 				PurpleChatUserFlags cbflags;
2296 
2297 				if (cb == NULL) {
2298 					// Getting notified about a buddy we dont know about yet
2299 					//TODO add buddy
2300 					return;
2301 				}
2302 				cbflags = purple_chat_user_get_flags(cb);
2303 
2304 				//if (is_typing)
2305 					cbflags |= PURPLE_CHAT_USER_TYPING;
2306 				//else //TODO
2307 				//	cbflags &= ~PURPLE_CHAT_USER_TYPING;
2308 
2309 				purple_chat_user_set_flags(cb, cbflags);
2310 			}
2311 		} else {
2312 			purple_serv_got_typing(ma->pc, username, 15, PURPLE_IM_TYPING);
2313 		}
2314 
2315 	} else if (purple_strequal(event, "status_change")) {
2316 		const gchar *user_id = json_object_get_string_member(data, "user_id");
2317 		const gchar *status = json_object_get_string_member(data, "status");
2318 		const gchar *username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
2319 		if (username != NULL && status != NULL) {
2320 			purple_protocol_got_user_status(ma->account, username, status, NULL);
2321 		}
2322 	} else if (purple_strequal(event, "user_added")) {
2323 		const gchar *user_id = mm_data_or_broadcast_string("user_id");
2324 		const gchar *team_id = json_object_get_string_member(data, "team_id");
2325 		const gchar *channel_id = mm_data_or_broadcast_string("channel_id");
2326 		const gchar *username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
2327 		PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
2328 
2329 		if (chatconv != NULL) {
2330 			if (!purple_chat_conversation_has_left(chatconv))
2331 				//FIXME: we can end up here with username == NULL and segfault pidgin.
2332 				if (username)
2333 					purple_chat_conversation_add_user(chatconv, username, NULL, PURPLE_CHAT_USER_NONE, FALSE);
2334 		} else if (purple_strequal(user_id, ma->self->user_id)) {
2335 			mm_get_channel_by_id(ma, team_id, channel_id);
2336 		}
2337 
2338 	} else if (purple_strequal(event, "user_removed")) {
2339 		const gchar *channel_id = mm_data_or_broadcast_string("channel_id");
2340 		const gchar *user_id = mm_data_or_broadcast_string("user_id");
2341 
2342 		const gchar *username = g_hash_table_lookup(ma->ids_to_usernames, user_id);
2343 		PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
2344 		if (chatconv != NULL) {
2345 			purple_chat_conversation_remove_user(chatconv, username, NULL);
2346 		}
2347 
2348 		if (purple_strequal(user_id, ma->self->user_id)) {
2349 			if (mm_hash_table_contains(ma->group_chats, channel_id)) {
2350 				PurpleChat *chat = mm_purple_blist_find_chat(ma, channel_id);
2351 				if (chat) {
2352 					PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
2353 					if (chatconv) purple_chat_conversation_leave(chatconv);
2354 					mm_remove_group_chat(ma, channel_id);
2355 					//FIXME twice ? mm_remove_group_chat(ma, channel_id);
2356 					purple_blist_remove_chat(chat);
2357 				}
2358 			}
2359 		}
2360 	} else if (purple_strequal(event, "preferences_changed") && purple_strequal(mm_data_or_broadcast_string("user_id"), ma->self->user_id)) {
2361 		GList *users = json_array_get_elements(json_array_from_string(json_node_get_string(json_object_get_member(data, "preferences"))));
2362 		GList *user = NULL;
2363 		GList *mm_users = NULL;
2364 		for (user = users; user != NULL; user = user->next) {
2365 			JsonObject *object = json_node_get_object(user->data);
2366 			const gchar *id = json_object_get_string_member(object, "name");
2367 			if (purple_strequal(json_object_get_string_member(object, "category"), "direct_channel_show")) {
2368 				if (purple_strequal(json_object_get_string_member(object, "value"), "false")) {
2369 					if (mm_hash_table_contains(ma->ids_to_usernames, id)) {
2370 						const gchar *user_name = g_hash_table_lookup(ma->ids_to_usernames, id);
2371 						PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, user_name);
2372 						if (buddy) {
2373 							// don't remove conversation if any: direct channel is not destroyed so it is reuseable.
2374 							g_hash_table_remove(ma->ids_to_usernames, id);
2375 							g_hash_table_remove(ma->usernames_to_ids, user_name);
2376 							purple_blist_remove_buddy(buddy);
2377 						}
2378 					}
2379 				} else {
2380 					MattermostUser *mm_user = g_new0(MattermostUser,1);
2381 					mm_user->user_id=g_strdup(id);
2382 					mm_users = g_list_prepend(mm_users, mm_user);
2383 				}
2384 			}
2385 			if (purple_strequal(json_object_get_string_member(object, "category"), "group_channel_show")) {
2386 				if (purple_strequal(json_object_get_string_member(object, "value"), "false")) {
2387 					if (mm_hash_table_contains(ma->group_chats, id)) {
2388 						PurpleChat *chat = mm_purple_blist_find_chat(ma, id);
2389 						if (chat) {
2390 							// don't remove conversation if any: group channel is not destroyed so it is reuseable.
2391 							mm_remove_group_chat(ma, id);
2392 							purple_blist_remove_chat(chat);
2393 						}
2394 					}
2395 				} else {
2396 					// not efficient: one callback per channel:
2397 					// but no API to do it on multiple channels at once ? ...
2398 					const gchar *team_id = json_object_get_string_member(data, "team_id");
2399 					mm_get_channel_by_id(ma, team_id, id);
2400 				}
2401 			}
2402 		}
2403 		mm_get_users_by_ids(ma, mm_users);
2404 		g_list_free(users);
2405 	} else if (purple_strequal(event, "channel_created") && purple_strequal(mm_data_or_broadcast_string("user_id"), ma->self->user_id)) {
2406 		const gchar *channel_id = mm_data_or_broadcast_string("channel_id");
2407 		const gchar *team_id = json_object_get_string_member(data, "team_id");
2408 		mm_get_channel_by_id(ma, team_id, channel_id);
2409 	} else if (purple_strequal(event, "channel_deleted")) {
2410 		const gchar *channel_id = mm_data_or_broadcast_string("channel_id");
2411 		if (mm_hash_table_contains(ma->group_chats, channel_id)) {
2412 			PurpleChat *chat = mm_purple_blist_find_chat(ma, channel_id);
2413 			if (chat) {
2414 				PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel_id));
2415 				if (chatconv) purple_chat_conversation_leave(chatconv);
2416 				mm_remove_group_chat(ma, channel_id);
2417 				purple_blist_remove_chat(chat);
2418 			}
2419 		}
2420 //	} else if (purple_strequal(event, "channel_converted")) {
2421 //		//TODO: implement: remove & add to blist again (see above) or just change type ?
2422 //	} else if (purple_strequal(event, "channel_updated")) {
2423 //		//TODO: implement
2424 	} else if (purple_strequal(event, "channel_viewed")) {
2425 		//we have marked it viewed already with purple_conversation_has_focus()
2426 	} else if (purple_strequal(event, "hello")) {
2427 		mm_refresh_statuses(ma, NULL);
2428 	} else if (purple_strequal(event, "user_updated")) {
2429 	//TODO: implement reusing (partsof) mm_get_users_by_ids_response()
2430 	//	{"event":"user_updated","data":{"user":
2431 	//	{	"id":"XXXXX","create_at":XXXX,"update_at":XXXX ,"delete_at":0,
2432 	//	"username":"aaa","auth_data":"","auth_service":"","email":"aa@aa.oo","nickname":"AA",
2433 	//	"first_name":"AAA","last_name":"AAA","position":"CCC","roles":"system_user",
2434 	//	"last_picture_update": XXXXX,"locale":"en",
2435 	//	"timezone":{"automaticTimezone":"","manualTimezone":"","useAutomaticTimezone":"true"}}},"broadcast":
2436 	//	{"omit_users":null,"user_id":"","channel_id":"","team_id":""},"seq":5}
2437 	}	else if (event) {
2438 		// can be one of: https://api.mattermost.com/#tag/WebSocket
2439 		purple_debug_info("mattermost", "unhandled event %s [%s]\n", event,json_object_to_string(obj));
2440 	} else if (purple_strequal(status,"OK")) {
2441 		//TODO: this can be a reply to client sending 'user_typing', 'get_statuses' or 'get_statuses_by_ids'
2442 		//      we dont know since would need to track seq number... so assume it was one of statuses replies...
2443 		JsonNode *tmpjsonnode=json_node_new(JSON_NODE_OBJECT);
2444 		json_node_set_object(tmpjsonnode,obj);
2445 		mm_got_hello_user_statuses(ma, tmpjsonnode, NULL);
2446 		json_node_free(tmpjsonnode);
2447 	} else if (status) {
2448 		//TODO: this will be FAIL status in reply to 'user_typing', 'get_statuses' or 'get_statuses_by_ids'
2449 		//      we dont know since would need to track seq number... so just ignore it..
2450 		purple_debug_info("mattermost", "unhandled status %s [%s]\n", status,json_object_to_string(obj));
2451 	} else {
2452 		purple_debug_info("mattermost", "unhandled message [%s]\n", json_object_to_string(obj));
2453 	}
2454 }
2455 
2456 
2457 #undef	mm_data_or_broadcast_string
2458 
2459 PurpleGroup *
mm_get_or_create_default_group()2460 mm_get_or_create_default_group()
2461 {
2462 	PurpleGroup *mm_group = NULL;
2463 
2464 	mm_group = purple_blist_find_group(MATTERMOST_DEFAULT_BLIST_GROUP_NAME);
2465 	if (!mm_group)
2466 	{
2467 		mm_group = purple_group_new(MATTERMOST_DEFAULT_BLIST_GROUP_NAME);
2468 		purple_blist_add_group(mm_group, NULL);
2469 	}
2470 
2471 	return mm_group;
2472 }
2473 
2474 static void
mm_roomlist_got_list(MattermostAccount * ma,JsonNode * node,gpointer user_data)2475 mm_roomlist_got_list(MattermostAccount *ma, JsonNode *node, gpointer user_data)
2476 {
2477 
2478 #define _MAX_COLS 33
2479 
2480 	MatterMostTeamRoomlist *mmtrl = user_data;
2481 	PurpleRoomlist *roomlist = mmtrl->roomlist;
2482 	JsonArray *channels = json_node_get_array(node);
2483 	guint i, len = json_array_get_length(channels);
2484 	PurpleRoomlistRoom *team_category = NULL;
2485 	const gchar *team_id = mmtrl->team_id;
2486 	const gchar *team_name;
2487 
2488 	team_name = g_strconcat(g_hash_table_lookup(ma->teams_display_names, team_id), " ", mmtrl->team_desc, NULL);
2489 
2490 	team_category = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_CATEGORY, team_name, NULL);
2491 	purple_roomlist_room_add_field(roomlist, team_category, team_id);
2492 	purple_roomlist_room_add(roomlist, team_category);
2493 
2494 	for (i = 0; i < len; i++) {
2495 		JsonObject *channel = json_array_get_object_element(channels, i);
2496 		const gchar *room_type = json_object_get_string_member(channel, "type");
2497 
2498 		if (*room_type == MATTERMOST_CHANNEL_DIRECT) {
2499 			continue; // these are buddies - dont show empty 'rooms' in room list
2500 		}
2501 
2502 		const gchar *id = json_object_get_string_member(channel, "id");
2503 		const gchar *display_name = json_object_get_string_member(channel, "display_name");
2504 		const gchar *name = json_object_get_string_member(channel, "name");
2505 		const gchar *header = json_object_get_string_member(channel, "header");
2506 		const gchar *purpose = json_object_get_string_member(channel, "purpose");
2507 		const gchar *team_id = json_object_get_string_member(channel, "team_id");
2508 
2509 		//FIXME: in v4 api team_id is NULL for group chats, that breaks the code in many places.
2510 		//       should be rewritten.
2511 
2512 		//if (team_id == NULL || strlen(team_id) == 0) {
2513 		//	team_id = mm_get_first_team_id(ma);
2514 		//}
2515 
2516 		const gchar *team_name = g_hash_table_lookup(ma->teams, team_id);
2517 
2518 		PurpleRoomlistRoom *room;
2519 		const gchar *type_str;
2520 		gchar *tmp_h = strlen(header) > _MAX_COLS ? g_strdup_printf("%.*s...", _MAX_COLS-3, header) : NULL;
2521 		gchar *tmp_p = strlen(purpose) > _MAX_COLS ? g_strdup_printf("%.*s...", _MAX_COLS-3, purpose) : NULL;
2522 
2523 		switch(*room_type) {
2524 			case MATTERMOST_CHANNEL_OPEN: type_str = _("Open"); break;
2525 			case MATTERMOST_CHANNEL_PRIVATE: type_str = _("Private"); break;
2526 			case MATTERMOST_CHANNEL_GROUP: type_str = _("Group"); break;
2527 			default:  type_str = _("Unknown"); break;
2528 		}
2529 
2530 		room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, team_category);
2531 
2532 		purple_roomlist_room_add_field(roomlist, room, id);
2533 		purple_roomlist_room_add_field(roomlist, room, team_id);
2534 		purple_roomlist_room_add_field(roomlist, room, team_name);
2535 		purple_roomlist_room_add_field(roomlist, room, name);
2536 		purple_roomlist_room_add_field(roomlist, room, display_name);
2537 		purple_roomlist_room_add_field(roomlist, room, type_str);
2538 		purple_roomlist_room_add_field(roomlist, room, tmp_h ? tmp_h : header);
2539 		purple_roomlist_room_add_field(roomlist, room, tmp_p ? tmp_p : purpose);
2540 
2541 		purple_roomlist_room_add(roomlist, room);
2542 
2543 		mm_set_group_chat(ma, team_id, name, id);//ALIAS ???
2544 
2545 		g_hash_table_replace(ma->channel_teams, g_strdup(id), g_strdup(team_id));
2546 
2547 		g_free(tmp_h);
2548 		g_free(tmp_p);
2549 	}
2550 
2551 	//Only after last team
2552 	ma->roomlist_team_count--;
2553 	if (ma->roomlist_team_count <= 0) {
2554 		purple_roomlist_set_in_progress(roomlist, FALSE);
2555 		ma->roomlist_team_count = 0;
2556 	}
2557 
2558 	g_free(mmtrl->team_id);
2559 	g_free(mmtrl->team_desc);
2560 	g_free(mmtrl);
2561 
2562 #undef _MAX_COLS
2563 }
2564 
2565 static gchar *
mm_roomlist_serialize(PurpleRoomlistRoom * room)2566 mm_roomlist_serialize(PurpleRoomlistRoom *room) {
2567 	GList *fields = purple_roomlist_room_get_fields(room);
2568 
2569 	const gchar *id = g_list_nth_data(fields, 0);
2570 	const gchar *team_id = g_list_nth_data(fields, 1);
2571 	const gchar *team_name = g_list_nth_data(fields, 2);
2572 	const gchar *name = g_list_nth_data(fields, 3);
2573 
2574 	//TODO: add alias ?
2575 	return g_strconcat(team_id, "^", id, "^", name, MATTERMOST_CHANNEL_SEPARATOR, team_name, NULL); //FIXME: need proper separator - unique !
2576 }
2577 
2578 //roomlist_deserialize
2579 static GHashTable *
mm_chat_info_defaults(PurpleConnection * pc,const char * chatname)2580 mm_chat_info_defaults(PurpleConnection *pc, const char *chatname)
2581 {
2582 	GHashTable *defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
2583 
2584 	if (chatname != NULL)
2585 	{
2586 		gchar **chat_parts = g_strsplit_set(chatname, "^", 3); //FIXME: need proper separator - unique !
2587 
2588 		if (chat_parts[0]) {
2589 			g_hash_table_insert(defaults, "team_id", g_strdup(chat_parts[0]));
2590 			if (chat_parts[1]) {
2591 				g_hash_table_insert(defaults, "id", g_strdup(chat_parts[1]));
2592 				if (chat_parts[2]) {
2593 					g_hash_table_insert(defaults, "name", g_strdup(chat_parts[2]));
2594 				}
2595 			}
2596 		}
2597 		//TODO: add alias ?
2598 		g_strfreev(chat_parts);
2599 	} else {
2600 		g_hash_table_insert(defaults, "team_id", g_strdup(mm_get_first_team_id(purple_connection_get_protocol_data(pc))));
2601 	}
2602 
2603 	return defaults;
2604 }
2605 
2606 PurpleRoomlist *
mm_roomlist_get_list(PurpleConnection * pc)2607 mm_roomlist_get_list(PurpleConnection *pc)
2608 {
2609 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
2610 	PurpleRoomlist *roomlist;
2611 	GList *fields = NULL;
2612 	PurpleRoomlistField *f;
2613 	gchar *url;
2614 	GList *teams, *i;
2615 
2616 	roomlist = purple_roomlist_new(ma->account);
2617 
2618 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("ID"), "id", TRUE);
2619 	fields = g_list_append(fields, f);
2620 
2621 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Team ID"), "team_id", TRUE);
2622 	fields = g_list_append(fields, f);
2623 
2624 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Team Name"), "team_name", TRUE);
2625 	fields = g_list_append(fields, f);
2626 
2627 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Name"), "name", TRUE);
2628 	fields = g_list_append(fields, f);
2629 
2630 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Display Name"), "display_name", FALSE);
2631 	fields = g_list_append(fields, f);
2632 
2633 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Type"), "type", FALSE);
2634 	fields = g_list_append(fields, f);
2635 
2636 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Header"), "header", FALSE);
2637 	fields = g_list_append(fields, f);
2638 
2639 	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Purpose"), "purpose", FALSE);
2640 	fields = g_list_append(fields, f);
2641 
2642 	purple_roomlist_set_fields(roomlist, fields);
2643 	purple_roomlist_set_in_progress(roomlist, TRUE);
2644 
2645 	//Loop through teams and look for channels within each
2646 	for(i = teams = g_hash_table_get_keys(ma->teams); i; i = i->next)
2647 	{
2648 		MatterMostTeamRoomlist *mmtrl;
2649 		const gchar *team_id = i->data;
2650 
2651 		// Get a list of public channels the user has *not* yet joined
2652 		mmtrl = g_new0(MatterMostTeamRoomlist, 1);
2653 		mmtrl->team_id = g_strdup(team_id);
2654 		mmtrl->team_desc = g_strdup(_(": More public channels"));
2655 		mmtrl->roomlist = roomlist;
2656 		url = mm_build_url(ma,"/teams/%s/channels", team_id);
2657 		mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_roomlist_got_list, mmtrl);
2658 		g_free(url);
2659 
2660 		ma->roomlist_team_count++;
2661 	}
2662 
2663 	return roomlist;
2664 }
2665 
2666 
2667 void
mm_set_idle(PurpleConnection * pc,int time)2668 mm_set_idle(PurpleConnection *pc, int time)
2669 {
2670 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
2671 	const gchar *channel_id = "";
2672 
2673 	if (time < 20) {
2674 		channel_id = ma->last_channel_id;
2675 	}
2676 
2677 	mm_mark_room_messages_read(ma, channel_id);
2678 }
2679 
2680 gboolean
mm_idle_updater_timeout(gpointer data)2681 mm_idle_updater_timeout(gpointer data)
2682 {
2683 	PurpleConnection *pc = data;
2684 	PurpleAccount *account = purple_connection_get_account(pc);
2685 	PurplePresence *presence = purple_account_get_presence(account);
2686 	time_t idle_time = purple_presence_get_idle_time(presence);
2687 
2688 	if (idle_time > 0) {
2689 		idle_time -= time(NULL);
2690 	}
2691 
2692 	mm_set_idle(pc, idle_time);
2693 
2694 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
2695 	mm_refresh_statuses(ma,NULL);
2696 
2697 	return TRUE;
2698 }
2699 
2700 void
mm_set_status(PurpleAccount * account,PurpleStatus * status)2701 mm_set_status(PurpleAccount *account, PurpleStatus *status)
2702 {
2703 	PurpleConnection *pc = purple_account_get_connection(account);
2704 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
2705 	const char *status_id = purple_status_get_id(status);
2706 	JsonObject *data;
2707 	gchar *setstatus;
2708 	gchar *postdata, *url;
2709 
2710 	// tell MM that we are going offline but do not disconnect.
2711 	// will stay in MM as offline until next status change.
2712 	// when posting status changes for online for ~ 30 secs
2713 	// then changes back again.
2714 	if (purple_strequal(status_id, "invisible")) {
2715 		setstatus = g_strdup("offline");
2716 	} else {
2717 		setstatus = g_strdup(status_id);
2718 	}
2719 
2720 	data = json_object_new();
2721 	json_object_set_string_member(data, "status", setstatus);
2722 	if(ma->self == NULL){
2723 		purple_debug_error("mattermost","Mattermost Account is NULL");
2724 		return;
2725 	}
2726 	json_object_set_string_member(data, "user_id", ma->self->user_id);
2727 	postdata = json_object_to_string(data);
2728 	url = mm_build_url(ma,"/users/%s/status", ma->self->user_id);
2729 	mm_fetch_url(ma, url, MATTERMOST_HTTP_PUT, postdata, -1, NULL, NULL);
2730 	g_free(url);
2731 
2732 	g_free(postdata);
2733 	json_object_unref(data);
2734 	g_free(setstatus);
2735 }
2736 
2737 static void
mm_restart_channel(MattermostAccount * ma)2738 mm_restart_channel(MattermostAccount *ma)
2739 {
2740 	purple_connection_set_state(ma->pc, PURPLE_CONNECTION_CONNECTING);
2741 	mm_start_socket(ma);
2742 }
2743 
2744 static void
mm_build_groups_from_blist(MattermostAccount * ma)2745 mm_build_groups_from_blist(MattermostAccount *ma)
2746 {
2747 	PurpleBlistNode *node;
2748 
2749 	for (node = purple_blist_get_root();
2750 		node != NULL;
2751 		node = purple_blist_node_next(node, TRUE)) {
2752 		if (PURPLE_IS_CHAT(node)) {
2753 			PurpleChat *chat = PURPLE_CHAT(node);
2754 
2755 			if (purple_chat_get_account(chat) != ma->account) {
2756 				continue;
2757 			}
2758 
2759 			GHashTable *components = purple_chat_get_components(chat);
2760 
2761 			if (components != NULL) {
2762 				mm_set_group_chat(ma, g_hash_table_lookup(components, "team_id") , g_hash_table_lookup(components, "name"), g_hash_table_lookup(components, "id"));
2763 			} //TODO: else { ERROR }
2764 
2765 		} else if (PURPLE_IS_BUDDY(node)) {
2766 			const gchar *room_id;
2767 			const gchar *user_id;
2768 			const gchar *username;
2769 			PurpleBuddy *buddy = PURPLE_BUDDY(node);
2770 			if (purple_buddy_get_account(buddy) != ma->account) {
2771 				continue;
2772 			}
2773 
2774 			username = purple_buddy_get_name(buddy);
2775 			room_id = purple_blist_node_get_string(node, "room_id");
2776 			user_id = purple_blist_node_get_string(node, "user_id");
2777 			if (room_id != NULL) {
2778 				g_hash_table_replace(ma->one_to_ones, g_strdup(room_id), g_strdup(username));
2779 				g_hash_table_replace(ma->one_to_ones_rev, g_strdup(username), g_strdup(room_id));
2780 			}
2781 			if (user_id != NULL) {
2782 				g_hash_table_replace(ma->ids_to_usernames, g_strdup(user_id), g_strdup(username));
2783 				g_hash_table_replace(ma->usernames_to_ids, g_strdup(username), g_strdup(user_id));
2784 			}
2785 		}
2786 	}
2787 }
2788 
2789 static guint mm_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state, MattermostAccount *ma);
2790 static gulong chat_conversation_typing_signal = 0;
2791 static void mm_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type);
2792 static gulong conversation_updated_signal = 0;
2793 
2794 void
mm_login(PurpleAccount * account)2795 mm_login(PurpleAccount *account)
2796 {
2797 	MattermostAccount *ma;
2798 	PurpleConnection *pc = purple_account_get_connection(account);
2799 	gchar **userparts;
2800 	gchar **serverparts;
2801 	const gchar *username = purple_account_get_username(account);
2802 	gchar *url;
2803 	PurpleConnectionFlags pc_flags;
2804 	const gchar *split_string = (char[2]) {MATTERMOST_SERVER_SPLIT_CHAR, '\0'};
2805 
2806 	pc_flags = purple_connection_get_flags(pc);
2807 	pc_flags |= PURPLE_CONNECTION_FLAG_HTML;
2808 	pc_flags |= PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
2809 	pc_flags |= PURPLE_CONNECTION_FLAG_NO_BGCOLOR;
2810 	purple_connection_set_flags(pc, pc_flags);
2811 
2812 	ma = g_new0(MattermostAccount, 1);
2813 	purple_connection_set_protocol_data(pc, ma);
2814 	ma->account = account;
2815 	ma->pc = pc;
2816 	ma->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2817 	ma->seq = 1;
2818 
2819 	if (purple_account_get_string(account, "last_message_timestamp", NULL)) {
2820 		const gchar *last_message_timestamp_str = purple_account_get_string(account, "last_message_timestamp", NULL);
2821 		ma->last_load_last_message_timestamp = g_ascii_strtoll(last_message_timestamp_str, NULL, 10);
2822 	} else {
2823 		ma->last_load_last_message_timestamp = purple_account_get_int(account, "last_message_timestamp_high", 0);
2824 		if (ma->last_load_last_message_timestamp != 0) {
2825 			ma->last_load_last_message_timestamp = (ma->last_load_last_message_timestamp << 32) | ((guint64) purple_account_get_int(account, "last_message_timestamp_low", 0) & 0xFFFFFFFF);
2826 		}
2827 	}
2828 	if (ma->last_load_last_message_timestamp < 0) {
2829 		ma->last_load_last_message_timestamp = 0;
2830 	}
2831 
2832 	ma->client_config = g_new0(MattermostClientConfig,1);
2833 	ma->client_config->public_link = FALSE;
2834 	ma->client_config->enable_commands = FALSE;
2835 
2836 	ma->one_to_ones = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2837 	ma->one_to_ones_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2838 	ma->group_chats = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2839 	ma->group_chats_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2840 	ma->aliases = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2841 	ma->group_chats_creators = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2842 	ma->sent_message_ids = g_hash_table_new_full(g_str_insensitive_hash, g_str_insensitive_equal, g_free, NULL);
2843 	ma->result_callbacks = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
2844 	ma->usernames_to_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2845 	ma->ids_to_usernames = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2846 	ma->teams = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2847 	ma->teams_display_names = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2848 	ma->channel_teams = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2849 	ma->received_message_queue = g_queue_new();
2850 
2851 	userparts = g_strsplit(username, split_string, 2);
2852 
2853 	if (userparts[0] == NULL) {
2854 		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "No username supplied");
2855 		return;
2856 	}
2857 	if (userparts[1] == NULL) {
2858 		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "No server supplied (use username|server)");
2859 		return;
2860 	}
2861 
2862         serverparts = g_strsplit(userparts[1], "/", 2);
2863         if( serverparts[0] == NULL ) {
2864 		purple_connection_error(pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "No server supplied (use username|server)");
2865 		return;
2866         }
2867 
2868 	purple_connection_set_display_name(pc, userparts[0]);
2869 	ma->username = g_strdup(userparts[0]);
2870 	ma->server = g_strdup(serverparts[0]);
2871 	g_strfreev(userparts);
2872         if( serverparts[1] == NULL ) {
2873 	        ma->api_endpoint = g_strdup(MATTERMOST_API_EP);
2874         }
2875         else {
2876 	        ma->api_endpoint = g_strconcat("/", serverparts[1], MATTERMOST_API_EP, NULL);
2877         }
2878 	g_strfreev(serverparts);
2879 
2880 	purple_connection_set_state(pc, PURPLE_CONNECTION_CONNECTING);
2881 
2882 	//Build the initial hash tables from the current buddy list
2883 	mm_build_groups_from_blist(ma);
2884 
2885 	//TODO check for two-factor-auth
2886 	{
2887 		JsonObject *data = json_object_new();
2888 		gchar *postdata;
2889 
2890 		if (purple_account_get_bool(ma->account, "use-mmauthtoken", FALSE)) {
2891 			ma->session_token = g_strdup(purple_connection_get_password(pc));
2892 
2893 			mm_get_me(ma);
2894 
2895 		} else {
2896 			json_object_set_string_member(data, "login_id", ma->username);
2897 			json_object_set_string_member(data, "password", purple_connection_get_password(pc));
2898 			json_object_set_string_member(data, "token", ""); //TODO 2FA
2899 
2900 			postdata = json_object_to_string(data);
2901 
2902 			url = mm_build_url(ma,"/users/login");
2903 			mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_login_response, NULL);
2904 
2905 			g_free(postdata);
2906 			g_free(url);
2907 		}
2908 		json_object_unref(data);
2909 	}
2910 
2911 	if (!chat_conversation_typing_signal) {
2912 		chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(mm_conv_send_typing), NULL);
2913 	}
2914 	if (!conversation_updated_signal) {
2915 		conversation_updated_signal = purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", purple_connection_get_protocol(pc), PURPLE_CALLBACK(mm_mark_conv_seen), NULL);
2916 	}
2917 }
2918 
2919 
2920 static void
mm_close(PurpleConnection * pc)2921 mm_close(PurpleConnection *pc)
2922 {
2923 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
2924 
2925 	g_return_if_fail(ma != NULL);
2926 
2927 	mm_set_status(ma->account, purple_presence_get_active_status(purple_account_get_presence(ma->account)));
2928 
2929 	if(ma->idle_timeout > 0){
2930 		g_source_remove(ma->idle_timeout);
2931 	}
2932 	if(ma->read_messages_timeout > 0){
2933 		g_source_remove(ma->read_messages_timeout);
2934 	}
2935 
2936 	purple_proxy_connect_cancel_with_handle(pc);
2937 	if (ma->websocket != NULL) purple_ssl_close(ma->websocket);
2938 	if (ma->websocket_inpa) purple_input_remove(ma->websocket_inpa);
2939 	if (ma->websocket_fd >= 0) close(ma->websocket_fd);
2940 
2941 	g_hash_table_remove_all(ma->one_to_ones);
2942 	g_hash_table_unref(ma->one_to_ones);
2943 	g_hash_table_remove_all(ma->one_to_ones_rev);
2944 	g_hash_table_unref(ma->one_to_ones_rev);
2945 	g_hash_table_remove_all(ma->group_chats);
2946 	g_hash_table_unref(ma->group_chats);
2947 	g_hash_table_remove_all(ma->aliases);
2948 	g_hash_table_unref(ma->aliases);
2949 	g_hash_table_remove_all(ma->group_chats_creators);
2950 	g_hash_table_unref(ma->group_chats_creators);
2951 	g_hash_table_remove_all(ma->sent_message_ids);
2952 	g_hash_table_unref(ma->sent_message_ids);
2953 	g_hash_table_remove_all(ma->result_callbacks);
2954 	g_hash_table_unref(ma->result_callbacks);
2955 	g_hash_table_remove_all(ma->usernames_to_ids);
2956 	g_hash_table_unref(ma->usernames_to_ids);
2957 	g_hash_table_remove_all(ma->ids_to_usernames);
2958 	g_hash_table_unref(ma->ids_to_usernames);
2959 	g_hash_table_remove_all(ma->teams);
2960 	g_hash_table_unref(ma->teams);
2961 	g_hash_table_remove_all(ma->teams_display_names);
2962 	g_hash_table_unref(ma->teams_display_names);
2963 	g_hash_table_remove_all(ma->channel_teams);
2964 	g_hash_table_unref(ma->channel_teams);
2965 	g_queue_free(ma->received_message_queue);
2966 
2967 	while (ma->http_conns) {
2968 		purple_http_conn_cancel(ma->http_conns->data);
2969 		ma->http_conns = g_slist_delete_link(ma->http_conns, ma->http_conns);
2970 	}
2971 
2972 	while (ma->pending_writes) {
2973 		json_object_unref(ma->pending_writes->data);
2974 		ma->pending_writes = g_slist_delete_link(ma->pending_writes, ma->pending_writes);
2975 	}
2976 
2977 	mm_g_free_mattermost_user(ma->self);
2978 	mm_g_free_mattermost_client_config(ma->client_config);
2979 
2980 	g_hash_table_destroy(ma->cookie_table); ma->cookie_table = NULL;
2981 	g_free(ma->last_channel_id); ma->last_channel_id = NULL;
2982 	g_free(ma->current_channel_id); ma->current_channel_id = NULL;
2983 	g_free(ma->username); ma->username = NULL;
2984 	g_free(ma->server); ma->server = NULL;
2985 	g_free(ma->api_endpoint); ma->api_endpoint = NULL;
2986 	g_free(ma->frame); ma->frame = NULL;
2987 	g_free(ma->session_token); ma->session_token = NULL;
2988 	g_free(ma->channel); ma->channel = NULL;
2989 	g_regex_unref(ma->mention_me_regex); ma->mention_me_regex = NULL;
2990 	g_regex_unref(ma->mention_all_regex); ma->mention_all_regex = NULL;
2991 	g_free(ma);
2992 }
2993 
2994 
2995 //static void mm_start_polling(MattermostAccount *ma);
2996 
2997 static gboolean
mm_process_frame(MattermostAccount * ma,const gchar * frame)2998 mm_process_frame(MattermostAccount *ma, const gchar *frame)
2999 {
3000 	JsonParser *parser = json_parser_new();
3001 	JsonNode *root;
3002 
3003 	purple_debug_info("mattermost", "got frame data: %s\n", frame);
3004 
3005 	if (!json_parser_load_from_data(parser, frame, -1, NULL))
3006 	{
3007 		purple_debug_error("mattermost", "Error parsing response: %s\n", frame);
3008 		return TRUE;
3009 	}
3010 
3011 	root = json_parser_get_root(parser);
3012 
3013 	if (root != NULL) {
3014 		mm_process_msg(ma, root);
3015 	}
3016 
3017 	g_object_unref(parser);
3018 	return TRUE;
3019 }
3020 
3021 static size_t
mm_socket_read(MattermostAccount * ma,gpointer buffer,size_t len)3022 mm_socket_read(MattermostAccount *ma, gpointer buffer, size_t len)
3023 {
3024 	if (ma->websocket) {
3025 		return purple_ssl_read(ma->websocket, buffer, len);
3026 	}
3027 
3028 	return read(ma->websocket_fd, buffer, len);
3029 }
3030 
3031 static size_t
mm_socket_write(MattermostAccount * ma,gconstpointer buffer,size_t len)3032 mm_socket_write(MattermostAccount *ma, gconstpointer buffer, size_t len)
3033 {
3034 	if (ma->websocket) {
3035 		return purple_ssl_write(ma->websocket, buffer, len);
3036 	}
3037 
3038 	return write(ma->websocket_fd, buffer, len);
3039 }
3040 
3041 static guchar *
mm_websocket_mask(guchar key[4],const guchar * pload,guint64 psize)3042 mm_websocket_mask(guchar key[4], const guchar *pload, guint64 psize)
3043 {
3044 	guint64 i;
3045 	guchar *ret = g_new0(guchar, psize);
3046 
3047 	for (i = 0; i < psize; i++) {
3048 		ret[i] = pload[i] ^ key[i % 4];
3049 	}
3050 
3051 	return ret;
3052 }
3053 
3054 static void
mm_socket_write_data(MattermostAccount * ma,guchar * data,gssize data_len,guchar type)3055 mm_socket_write_data(MattermostAccount *ma, guchar *data, gssize data_len, guchar type)
3056 {
3057 	guchar *full_data;
3058 	guint len_size = 1;
3059 	guchar mkey[4] = { 0x12, 0x34, 0x56, 0x78 };
3060 
3061 	if (data_len == -1) {
3062 		data_len = strlen((gchar *) data);
3063 	}
3064 
3065 	if (data_len) {
3066 		purple_debug_info("mattermost", "sending frame: %*s\n", (int)data_len, data);
3067 	}
3068 
3069 	data = mm_websocket_mask(mkey, data, data_len);
3070 
3071 	if (data_len > 125) {
3072 		if (data_len <= G_MAXUINT16) {
3073 			len_size += 2;
3074 		} else {
3075 			len_size += 8;
3076 		}
3077 	}
3078 	full_data = g_new0(guchar, 1 + data_len + len_size + 4);
3079 
3080 	if (type == 0) {
3081 		type = 129;
3082 	}
3083 	full_data[0] = type;
3084 
3085 	if (data_len <= 125) {
3086 		full_data[1] = data_len | 0x80;
3087 	} else if (data_len <= G_MAXUINT16) {
3088 		guint16 be_len = GUINT16_TO_BE(data_len);
3089 		full_data[1] = 126 | 0x80;
3090 		memmove(full_data + 2, &be_len, 2);
3091 	} else {
3092 		guint64 be_len = GUINT64_TO_BE(data_len);
3093 		full_data[1] = 127 | 0x80;
3094 		memmove(full_data + 2, &be_len, 8);
3095 	}
3096 
3097 	memmove(full_data + (1 + len_size), &mkey, 4);
3098 	memmove(full_data + (1 + len_size + 4), data, data_len);
3099 
3100 	mm_socket_write(ma, full_data, 1 + data_len + len_size + 4);
3101 
3102 	g_free(full_data);
3103 	g_free(data);
3104 }
3105 
3106 /* takes ownership of data parameter */
3107 static void
mm_socket_write_json(MattermostAccount * ma,JsonObject * data)3108 mm_socket_write_json(MattermostAccount *ma, JsonObject *data)
3109 {
3110 	gchar *str;
3111 
3112 	if (ma->websocket == NULL && ma->websocket_fd <= 0) {
3113 		if (data != NULL) {
3114 			ma->pending_writes = g_slist_append(ma->pending_writes, data);
3115 		}
3116 		return;
3117 	}
3118 
3119 	str = json_object_to_string(data);
3120 
3121 	mm_socket_write_data(ma, (guchar *)str, -1, 0);
3122 
3123 	g_free(str);
3124 }
3125 
3126 static void
mm_socket_got_data(gpointer userdata,PurpleSslConnection * conn,PurpleInputCondition cond)3127 mm_socket_got_data(gpointer userdata, PurpleSslConnection *conn, PurpleInputCondition cond)
3128 {
3129 	MattermostAccount *ma = userdata;
3130 	guchar length_code;
3131 	int read_len = 0;
3132 	gboolean done_some_reads = FALSE;
3133 
3134 
3135 	if (G_UNLIKELY(!ma->websocket_header_received)) {
3136 		gint nlbr_count = 0;
3137 		gchar nextchar;
3138 
3139 		while(nlbr_count < 4 && (read_len = mm_socket_read(ma, &nextchar, 1)) == 1) {
3140 			if (nextchar == '\r' || nextchar == '\n') {
3141 				nlbr_count++;
3142 			} else {
3143 				nlbr_count = 0;
3144 			}
3145 		}
3146 
3147 		if (nlbr_count == 4) {
3148 			ma->websocket_header_received = TRUE;
3149 			done_some_reads = TRUE;
3150 
3151 			/* flush stuff that we attempted to send before the websocket was ready */
3152 			while (ma->pending_writes) {
3153 				mm_socket_write_json(ma, ma->pending_writes->data);
3154 				ma->pending_writes = g_slist_delete_link(ma->pending_writes, ma->pending_writes);
3155 			}
3156 		}
3157 	}
3158 
3159 	while(ma->frame || (read_len = mm_socket_read(ma, &ma->packet_code, 1)) == 1) {
3160 		if (!ma->frame) {
3161 			if (ma->packet_code != 129) {
3162 				if (ma->packet_code == 136) {
3163 					purple_debug_error("mattermost", "websocket closed\n");
3164 
3165 					// Try reconnect
3166 					mm_start_socket(ma);
3167 
3168 					return;
3169 				} else if (ma->packet_code == 137) {
3170 					// Ping
3171 					gint ping_frame_len = 0;
3172 					length_code = 0;
3173 					mm_socket_read(ma, &length_code, 1);
3174 					if (length_code <= 125) {
3175 						ping_frame_len = length_code;
3176 					} else if (length_code == 126) {
3177 						guchar len_buf[2];
3178 						mm_socket_read(ma, len_buf, 2);
3179 						ping_frame_len = (len_buf[0] << 8) + len_buf[1];
3180 					} else if (length_code == 127) {
3181 						mm_socket_read(ma, &ping_frame_len, 8);
3182 						ping_frame_len = GUINT64_FROM_BE(ping_frame_len);
3183 					}
3184 					if (ping_frame_len) {
3185 						guchar *pong_data = g_new0(guchar, ping_frame_len);
3186 						mm_socket_read(ma, pong_data, ping_frame_len);
3187 
3188 						mm_socket_write_data(ma, pong_data, ping_frame_len, 138);
3189 						g_free(pong_data);
3190 					} else {
3191 						mm_socket_write_data(ma, (guchar *) "", 0, 138);
3192 					}
3193 					return;
3194 				} else if (ma->packet_code == 138) {
3195 					// Pong
3196 					//who cares
3197 					return;
3198 				}
3199 				purple_debug_error("mattermost", "unknown websocket error %d\n", ma->packet_code);
3200 				return;
3201 			}
3202 
3203 			length_code = 0;
3204 			mm_socket_read(ma, &length_code, 1);
3205 			if (length_code <= 125) {
3206 				ma->frame_len = length_code;
3207 			} else if (length_code == 126) {
3208 				guchar len_buf[2];
3209 				mm_socket_read(ma, len_buf, 2);
3210 				ma->frame_len = (len_buf[0] << 8) + len_buf[1];
3211 			} else if (length_code == 127) {
3212 				mm_socket_read(ma, &ma->frame_len, 8);
3213 				ma->frame_len = GUINT64_FROM_BE(ma->frame_len);
3214 			}
3215 			//purple_debug_info("mattermost", "frame_len: %" G_GUINT64_FORMAT "\n", ma->frame_len);
3216 
3217 			ma->frame = g_new0(gchar, ma->frame_len + 1);
3218 			ma->frame_len_progress = 0;
3219 		}
3220 
3221 		do {
3222 			read_len = mm_socket_read(ma, ma->frame + ma->frame_len_progress, ma->frame_len - ma->frame_len_progress);
3223 			if (read_len > 0) {
3224 				ma->frame_len_progress += read_len;
3225 			}
3226 		} while (read_len > 0 && ma->frame_len_progress < ma->frame_len);
3227 		done_some_reads = TRUE;
3228 
3229 		if (ma->frame_len_progress == ma->frame_len) {
3230 			gboolean success = mm_process_frame(ma, ma->frame);
3231 			g_free(ma->frame); ma->frame = NULL;
3232 			ma->packet_code = 0;
3233 			ma->frame_len = 0;
3234 			ma->frames_since_reconnect++;
3235 
3236 			if (G_UNLIKELY((ma->websocket == NULL && ma->websocket_fd <= 0) || success == FALSE)) {
3237 				return;
3238 			}
3239 		} else {
3240 			return;
3241 		}
3242 	}
3243 
3244 	if (done_some_reads == FALSE && read_len <= 0) {
3245 		if (read_len < 0 && errno == EAGAIN) {
3246 			return;
3247 		}
3248 
3249 		purple_debug_error("mattermost", "got errno %d, read_len %d from websocket thread\n", errno, read_len);
3250 
3251 		if (ma->frames_since_reconnect < 2) {
3252 			purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Lost connection to server"));
3253 		} else {
3254 			// Try reconnect
3255 			mm_start_socket(ma);
3256 		}
3257 	}
3258 }
3259 
3260 static void
mm_socket_got_data_nonssl(gpointer userdata,gint fd,PurpleInputCondition cond)3261 mm_socket_got_data_nonssl(gpointer userdata, gint fd, PurpleInputCondition cond)
3262 {
3263 	mm_socket_got_data(userdata, NULL, cond);
3264 }
3265 
3266 static void
mm_socket_send_headers(MattermostAccount * ma)3267 mm_socket_send_headers(MattermostAccount *ma)
3268 {
3269 	gchar *websocket_header;
3270 	const gchar *websocket_key = "15XF+ptKDhYVERXoGcdHTA=="; //TODO don't be lazy
3271 
3272 //	websocket_header = g_strdup_printf("GET %s/users/websocket HTTP/1.1\r\n"
3273 	websocket_header = g_strdup_printf("GET %s/websocket HTTP/1.0\r\n"
3274 							"Host: %s\r\n"
3275 							"Connection: Upgrade\r\n"
3276 							"Pragma: no-cache\r\n"
3277 							"Cache-Control: no-cache\r\n"
3278 							"Upgrade: websocket\r\n"
3279 							"Sec-WebSocket-Version: 13\r\n"
3280 							"Sec-WebSocket-Key: %s\r\n"
3281 							"User-Agent: " MATTERMOST_USERAGENT "\r\n"
3282                                                         "X-Requested-With: XMLHttpRequest\r\n"
3283 							"Authorization: Bearer %s\r\n"
3284 							//"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"
3285 							"\r\n", ma->api_endpoint, ma->server,
3286 							websocket_key, ma->session_token);
3287 
3288 	mm_socket_write(ma, websocket_header, strlen(websocket_header));
3289 
3290 	g_free(websocket_header);
3291 }
3292 
3293 static void
mm_socket_connected(gpointer userdata,PurpleSslConnection * conn,PurpleInputCondition cond)3294 mm_socket_connected(gpointer userdata, PurpleSslConnection *conn, PurpleInputCondition cond)
3295 {
3296 	MattermostAccount *ma = userdata;
3297 
3298 	ma->websocket = conn;
3299 
3300 	purple_ssl_input_add(ma->websocket, mm_socket_got_data, ma);
3301 
3302 	mm_socket_send_headers(ma);
3303 }
3304 
3305 static void
mm_socket_failed(PurpleSslConnection * conn,PurpleSslErrorType errortype,gpointer userdata)3306 mm_socket_failed(PurpleSslConnection *conn, PurpleSslErrorType errortype, gpointer userdata)
3307 {
3308 	MattermostAccount *ma = userdata;
3309 
3310 	ma->websocket = NULL;
3311 	ma->websocket_header_received = FALSE;
3312 
3313 	if (ma->frames_since_reconnect < 1) {
3314 		purple_connection_error(ma->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Couldn't connect to gateway"));
3315 	} else {
3316 		mm_restart_channel(ma);
3317 	}
3318 }
3319 
3320 static void
mm_socket_connected_nonssl(gpointer userdata,gint source,const gchar * error_message)3321 mm_socket_connected_nonssl(gpointer userdata, gint source, const gchar *error_message)
3322 {
3323 	MattermostAccount *ma = userdata;
3324 
3325 	if (source < 0) {
3326 		// Error when connecting
3327 		mm_socket_failed(NULL, 0, ma);
3328 		return;
3329 	}
3330 
3331 	ma->websocket_fd = source;
3332 	ma->websocket_inpa = purple_input_add(source, PURPLE_INPUT_READ, mm_socket_got_data_nonssl, ma);
3333 
3334 	mm_socket_send_headers(ma);
3335 }
3336 
3337 static void
mm_start_socket(MattermostAccount * ma)3338 mm_start_socket(MattermostAccount *ma)
3339 {
3340 	gchar **server_split;
3341 	gint port = 443;
3342 
3343 	//Reset all the old stuff
3344 	if (ma->websocket != NULL) {
3345 		purple_ssl_close(ma->websocket);
3346 	}
3347 	if (ma->websocket_inpa) {
3348 		purple_input_remove(ma->websocket_inpa);
3349 	}
3350 	if (ma->websocket_fd > 0) {
3351 		close(ma->websocket_fd);
3352 	}
3353 
3354 	if (!purple_account_get_bool(ma->account, "use-ssl", TRUE)) {
3355 		port = 80;
3356 	}
3357 
3358 	ma->websocket_fd = 0;
3359 	ma->websocket_inpa = 0;
3360 	ma->websocket = NULL;
3361 	ma->websocket_header_received = FALSE;
3362 	g_free(ma->frame); ma->frame = NULL;
3363 	ma->packet_code = 0;
3364 	ma->frame_len = 0;
3365 	ma->frames_since_reconnect = 0;
3366 
3367 	server_split = g_strsplit(ma->server, ":", 2);
3368 	if (server_split[1] != NULL) {
3369 		port = atoi(server_split[1]);
3370 	}
3371 
3372 	if (purple_account_get_bool(ma->account, "use-ssl", TRUE)) {
3373 		ma->websocket = purple_ssl_connect(ma->account, server_split[0], port, mm_socket_connected, mm_socket_failed, ma);
3374 	} else {
3375 		purple_proxy_connect(ma->pc, ma->account, server_split[0], port, mm_socket_connected_nonssl, ma);
3376 	}
3377 
3378 	g_strfreev(server_split);
3379 }
3380 
3381 
3382 static void
mm_chat_leave(PurpleConnection * pc,int id)3383 mm_chat_leave(PurpleConnection *pc, int id)
3384 {
3385 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
3386 	const gchar *channel_id; //, *team_id;
3387 	PurpleChatConversation *chatconv;
3388 	gchar *url;
3389 
3390 	chatconv = purple_conversations_find_chat(pc, id);
3391 	if (chatconv == NULL) {
3392 		return;
3393 	}
3394 
3395 	channel_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
3396 	if (channel_id == NULL) {
3397 		channel_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
3398 	}
3399 
3400 	url = mm_build_url(ma,"/channels/%s/members/%s", channel_id, ma->self->user_id);
3401 	mm_fetch_url(ma, url, MATTERMOST_HTTP_DELETE, NULL, -1, NULL, NULL);
3402 
3403 	g_free(url);
3404 
3405 }
3406 
3407 static void
mm_chat_invite(PurpleConnection * pc,int id,const char * message,const char * who)3408 mm_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who)
3409 {
3410 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
3411 	PurpleChatConversation *chatconv;
3412 	const gchar *user_id;
3413 	JsonObject *data;
3414 	gchar *postdata;
3415 	gchar *url;
3416 	const gchar *channel_id;
3417 
3418 	chatconv = purple_conversations_find_chat(pc, id);
3419 	if (chatconv == NULL) {
3420 		return;
3421 	}
3422 
3423 	channel_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
3424 	if (channel_id == NULL) {
3425 		channel_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
3426 	}
3427 
3428 	user_id = g_hash_table_lookup(ma->usernames_to_ids, who);
3429 	if (user_id == NULL) {
3430 		//TODO search for user
3431 
3432 		//  /users/search
3433 
3434 		//"term", buddy_name
3435 		//"allow_inactive", TRUE
3436 
3437 		return;
3438 	}
3439 
3440 	data = json_object_new();
3441 	json_object_set_string_member(data, "user_id", user_id);
3442 
3443 	postdata = json_object_to_string(data);
3444 
3445 	url = mm_build_url(ma,"/channels/%s/members", channel_id);
3446 
3447 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, NULL, NULL);
3448 
3449 	g_free(postdata);
3450 	g_free(url);
3451 }
3452 
3453 static GList *
mm_chat_info(PurpleConnection * pc)3454 mm_chat_info(PurpleConnection *pc)
3455 {
3456 	GList *m = NULL;
3457 	PurpleProtocolChatEntry *pce;
3458 
3459 	pce = g_new0(PurpleProtocolChatEntry, 1);
3460 	pce->label = _("Name");
3461 	pce->identifier = "name";
3462 	pce->required = TRUE;
3463 	m = g_list_append(m, pce);
3464 
3465 	pce = g_new0(PurpleProtocolChatEntry, 1);
3466 	pce->label = _("Channel ID");
3467 	pce->identifier = "id";
3468 	pce->required = TRUE;
3469 	m = g_list_append(m, pce);
3470 
3471 	pce = g_new0(PurpleProtocolChatEntry, 1);
3472 	pce->label = _("Team ID");
3473 	pce->identifier = "team_id";
3474 	pce->required = TRUE;
3475 	m = g_list_append(m, pce);
3476 
3477 	return m;
3478 }
3479 
3480 static gchar *
mm_get_chat_name(GHashTable * data)3481 mm_get_chat_name(GHashTable *data)
3482 {
3483 	gchar *temp;
3484 
3485 	if (data == NULL) {
3486 		return NULL;
3487 	}
3488 
3489 	temp = g_hash_table_lookup(data, "name");
3490 
3491 	if (temp == NULL) {
3492 		temp = g_hash_table_lookup(data, "id");
3493 	}
3494 
3495 	if (temp == NULL) {
3496 		return NULL;
3497 	}
3498 
3499 	return g_strdup(temp);
3500 }
3501 
3502 
3503 static void mm_get_users_of_room(MattermostAccount *ma, MattermostChannel *channel);
3504 
3505 static void
mm_got_users_of_room(MattermostAccount * ma,JsonNode * node,gpointer user_data)3506 mm_got_users_of_room(MattermostAccount *ma, JsonNode *node, gpointer user_data)
3507 {
3508 	MattermostChannel *channel = user_data;
3509 	PurpleGroup *default_group = mm_get_or_create_default_group();
3510 
3511 	if (!mm_check_mattermost_response(ma,node,_("Error"),g_strconcat(_("Error getting Mattermost channel users ("),channel->display_name,")",NULL),TRUE)) {
3512 		channel->page_users = MATTERMOST_MAX_PAGES;
3513 		return;
3514 	}
3515 
3516 	PurpleChatConversation *chatconv = purple_conversations_find_chat(ma->pc, g_str_hash(channel->id));
3517 
3518 	GList *users_list = NULL, *flags_list = NULL;
3519 
3520 	JsonArray *users = json_node_get_array(node);
3521 	guint i, len = json_array_get_length(users);
3522 
3523 	for (i = 0; i < len; i++) {
3524 		JsonObject *user = json_array_get_object_element(users,i);
3525 		const gchar *user_id = json_object_get_string_member(user, "id");
3526 		const gchar *username = json_object_get_string_member(user, "username");
3527 		const gchar *roles = json_object_get_string_member(user, "roles");
3528 
3529 		if (!mm_hash_table_contains(ma->ids_to_usernames, user_id)) {
3530 			g_hash_table_replace(ma->ids_to_usernames, g_strdup(user_id), g_strdup(username));
3531 			g_hash_table_replace(ma->usernames_to_ids, g_strdup(username), g_strdup(user_id));
3532 
3533 			if (chatconv == NULL && mm_hash_table_contains(ma->one_to_ones, channel->id)) {
3534 				//Probably a direct message, add them to the buddy list
3535 				PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, username);
3536 				if (buddy == NULL) {
3537 					buddy = purple_buddy_new(ma->account, username, NULL);
3538 					purple_blist_add_buddy(buddy, NULL, default_group, NULL);
3539 
3540 					PurpleIMConversation *imconv = purple_conversations_find_im_with_account(username, ma->account);
3541 					if (imconv == NULL) {
3542 						imconv = purple_im_conversation_new(ma->account, username);
3543 					}
3544 						mm_add_buddy(ma->pc, buddy, NULL, NULL);
3545 					}
3546 				purple_blist_node_set_string(PURPLE_BLIST_NODE(buddy), "room_id", channel->id);
3547 			}
3548 		}
3549 
3550 		if (chatconv != NULL) {
3551 			PurpleChatUserFlags flags = mm_role_to_purple_flag(ma, roles);
3552 			if (purple_strequal(channel->creator_id, user_id)) {
3553 				flags |= PURPLE_CHAT_USER_OP;
3554 			}
3555 			if (!purple_chat_conversation_has_user(chatconv,username)) {
3556 				users_list = g_list_prepend(users_list, g_strdup(username));
3557 				flags_list = g_list_prepend(flags_list, GINT_TO_POINTER(flags));
3558 			}
3559 		}
3560 	}
3561 
3562 	if (chatconv != NULL) {
3563 		purple_chat_conversation_add_users(chatconv, users_list, NULL, flags_list, FALSE);
3564 	}
3565 
3566 	while (users_list != NULL) {
3567 		g_free(users_list->data);
3568 		users_list = g_list_delete_link(users_list, users_list);
3569 	}
3570 	g_list_free(users_list);
3571 	g_list_free(flags_list);
3572 
3573 	if (len == MATTERMOST_USER_PAGE_SIZE && channel->page_users < MATTERMOST_MAX_PAGES) {
3574 		channel->page_users = channel->page_users + 1;
3575 		mm_get_users_of_room(ma, channel);
3576 	} else {
3577 		channel->page_history = 0;
3578 		mm_get_history_of_room(ma, channel, -1);
3579 	}
3580 }
3581 
3582 
3583 
3584 static void
mm_get_users_of_room(MattermostAccount * ma,MattermostChannel * channel)3585 mm_get_users_of_room(MattermostAccount *ma, MattermostChannel *channel)
3586 {
3587 	gchar *url;
3588 
3589 	if (channel->page_users == MATTERMOST_MAX_PAGES) return;
3590 
3591 	url = mm_build_url(ma,"/users?in_channel=%s&page=%s&per_page=%s", channel->id,g_strdup_printf("%i",channel->page_users), g_strdup_printf("%i", MATTERMOST_USER_PAGE_SIZE));
3592 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_got_users_of_room, channel);
3593 	g_free(url);
3594 }
3595 
3596 static gint64
mm_get_channel_approximate_view_time(MattermostAccount * ma,MattermostChannel * channel)3597 mm_get_channel_approximate_view_time(MattermostAccount *ma, MattermostChannel *channel)
3598 {
3599 	gchar *tmptime = NULL;
3600 
3601 	PurpleChat *chat = mm_purple_blist_find_chat(ma, channel->id);
3602 	if (chat) {
3603 		tmptime = g_strdup(purple_blist_node_get_string(PURPLE_BLIST_NODE(chat),"channel_approximate_view_time"));
3604 	} else {
3605 		PurpleBuddy *buddy = purple_blist_find_buddy(ma->account,g_hash_table_lookup(ma->one_to_ones,channel->id));
3606 		if (buddy) {
3607 			tmptime = g_strdup(purple_blist_node_get_string(PURPLE_BLIST_NODE(buddy),"channel_approximate_view_time"));
3608 		}
3609 	}
3610 
3611 	if(!tmptime) {
3612 		tmptime = g_strdup_printf("%" G_GINT64_FORMAT, (g_get_real_time() / 1000)); // now.
3613 	}
3614 
3615 	gint64 viewtime = g_ascii_strtoll(tmptime, NULL, 10);
3616 	g_free(tmptime);
3617 	return viewtime;
3618 }
3619 
3620 static void mm_get_history_of_room(MattermostAccount *ma, MattermostChannel *channel, gint64 since);
3621 
3622 static void
mm_got_history_of_room(MattermostAccount * ma,JsonNode * node,gpointer user_data)3623 mm_got_history_of_room(MattermostAccount *ma, JsonNode *node, gpointer user_data)
3624 {
3625 	MattermostChannel *channel = user_data;
3626 
3627 	if (!mm_check_mattermost_response(ma,node,_("Error"),g_strconcat(_("Error getting Mattermost channel history ("),channel->display_name,")",NULL),TRUE)) {
3628 		channel->page_history = MATTERMOST_MAX_PAGES;
3629 		return;
3630 	}
3631 
3632 	JsonObject *obj = json_node_get_object(node);
3633 	JsonObject *posts = json_object_get_object_member(obj, "posts");
3634 	JsonArray *order = json_object_get_array_member(obj, "order");
3635 
3636 	gint i, len = json_array_get_length(order);
3637 
3638 	// do not show updates (such as reactions). only edits, new posts and deletes
3639 	for (i = len - 1; i >= 0; i--) {
3640 		const gchar *post_id = json_array_get_string_element(order, i);
3641 		JsonObject *post = json_object_get_object_member(posts, post_id);
3642 
3643 		const gint64 since = mm_get_channel_approximate_view_time(ma, channel);
3644 		if (json_object_get_int_member(post, "create_at") < since && json_object_get_int_member(post, "edit_at") < since && json_object_get_int_member(post, "delete_at") < since) {
3645 			json_array_remove_element(order, i);
3646 		}
3647 	}
3648 	len = json_array_get_length(order);
3649 
3650 	if (len > 0) {
3651 		if (!g_hash_table_lookup(ma->one_to_ones,channel->id)) { // not one to one
3652 			PurpleChatConversation *chatconv=purple_conversations_find_chat(ma->pc, g_str_hash(channel->id));
3653 			if (!chatconv) {
3654 				PurpleChat *chat = mm_purple_blist_find_chat(ma,channel->id);
3655 				if (chat) {
3656 					GHashTable *components = purple_chat_get_components(chat);
3657 					gchar *team_id = g_hash_table_lookup(components, "team_id");
3658 					gchar *alias = g_hash_table_lookup(ma->aliases,channel->id);
3659 
3660 					PurpleChatConversation *conv = purple_serv_got_joined_chat(ma->pc, g_str_hash(channel->id), alias);
3661 					purple_conversation_set_data(PURPLE_CONVERSATION(conv), "id", g_strdup(channel->id));
3662 					purple_conversation_set_data(PURPLE_CONVERSATION(conv), "team_id", g_strdup(team_id));
3663 					purple_conversation_set_data(PURPLE_CONVERSATION(conv), "name", g_strdup(alias));
3664 					purple_conversation_set_data(PURPLE_CONVERSATION(conv), "display_name", g_strdup(channel->display_name));
3665 					purple_conversation_present(PURPLE_CONVERSATION(conv));
3666 
3667 					//HERE we already went through mm_get_users_of_room but since
3668 					//chat window was not open, user list is empty, need to do it again
3669 					//FIXME: this should be rewritten ...we call users read twice per channel
3670 					channel->page_users = 0;
3671 					mm_get_users_of_room(ma, channel);
3672 					return;
3673 				}
3674 			}
3675 		}
3676 		// for IMCONV pidgin opens the conversation by itself.
3677   }
3678 
3679 
3680 	for (i = len - 1; i >= 0; i--) {
3681 		const gchar *post_id = json_array_get_string_element(order, i);
3682 		JsonObject *post = json_object_get_object_member(posts, post_id);
3683 		mm_process_room_message(ma, post, NULL);
3684 	}
3685 
3686 // 'since' does follow the page size
3687 if (len == MATTERMOST_HISTORY_PAGE_SIZE && channel->page_history < MATTERMOST_MAX_PAGES) {
3688 		channel->page_history = channel->page_history + 1;
3689 
3690 		mm_get_history_of_room(ma, channel, -1); // FIXME: that should be parametrized !
3691 	} else {
3692 		channel->page_history = MATTERMOST_MAX_PAGES;
3693 		// history will be stored in purple log, even if channel not read now, avoid re-reading later.
3694 		mm_mark_room_messages_read_timeout_response(ma, NULL, channel->id);
3695 
3696 		mm_g_free_mattermost_channel(channel);
3697 	}
3698 // for now we could just tell user...
3699 
3700 }
3701 
3702 static void
mm_get_history_of_room(MattermostAccount * ma,MattermostChannel * channel,gint64 since)3703 mm_get_history_of_room(MattermostAccount *ma, MattermostChannel *channel, gint64 since)
3704 {
3705 	gchar *url;
3706 
3707 	if (channel->page_history == MATTERMOST_MAX_PAGES) return;
3708 	if (!channel->id) return;
3709 
3710 	if (since < 0) {
3711 		since = mm_get_channel_approximate_view_time(ma, channel);
3712 	}
3713 
3714 	url = mm_build_url(ma,"/channels/%s/posts?page=%s&per_page=%s&since=%" G_GINT64_FORMAT "", channel->id, g_strdup_printf("%i",channel->page_history), g_strdup_printf("%i", MATTERMOST_HISTORY_PAGE_SIZE), since);
3715 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_got_history_of_room, channel);
3716 	g_free(url);
3717 }
3718 
3719 static void
mm_join_room(MattermostAccount * ma,MattermostChannel * channel)3720 mm_join_room(MattermostAccount *ma, MattermostChannel *channel)
3721 {
3722 	mm_set_group_chat(ma, channel->team_id, channel->name, channel->id);
3723 	mm_get_users_of_room(ma, channel);
3724 }
3725 
3726 
3727 static void
mm_join_chat(PurpleConnection * pc,GHashTable * chatdata)3728 mm_join_chat(PurpleConnection *pc, GHashTable *chatdata)
3729 {
3730 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
3731 	const gchar *id = g_hash_table_lookup(chatdata, "id");
3732 	const gchar *name = g_hash_table_lookup(chatdata, "name");
3733 	const gchar *team_id = g_hash_table_lookup(chatdata, "team_id");
3734 	const gchar *type = g_hash_table_lookup(chatdata, "type");
3735 	const gchar *creator_id = g_hash_table_lookup(chatdata, "creator_id");
3736 
3737 	guint id_hash;
3738 	PurpleChatConversation *chatconv;
3739 
3740 	if (id == NULL && name == NULL) {
3741 		//What do?
3742 		return;
3743 	}
3744 
3745 	if (id == NULL) {
3746 		id = g_hash_table_lookup(ma->group_chats_rev, name);
3747 	}
3748 	//TODO use the api look up name info from the id
3749 	if (id == NULL) {
3750 		return;
3751 	}
3752 
3753 	id_hash = g_str_hash(id);
3754 	chatconv = purple_conversations_find_chat(ma->pc, id_hash);
3755 
3756 	if (chatconv != NULL && !purple_chat_conversation_has_left(chatconv)) {
3757 		purple_conversation_present(PURPLE_CONVERSATION(chatconv));
3758 		return;
3759 	}
3760 
3761 	const gchar *alias = g_hash_table_lookup(ma->aliases,id);
3762 
3763 	chatconv = purple_serv_got_joined_chat(pc, id_hash, alias);//ALIAS ?
3764 
3765 	purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "id", g_strdup(id));
3766 	purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "team_id", g_strdup(team_id));
3767 	purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "name", g_strdup(name));
3768 	purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "type", g_strdup(type));
3769 	purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "creator_id", g_strdup(creator_id));
3770 	purple_conversation_present(PURPLE_CONVERSATION(chatconv));
3771 
3772 	mm_get_channel_by_id(ma,team_id,id);
3773 }
3774 
3775 static void
mm_mark_room_messages_read_timeout_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)3776 mm_mark_room_messages_read_timeout_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
3777 {
3778 //reply contains 'last_viewed_at_times' which does not correspond to our activity ?
3779 //also prefs chanel_approximate_view_time is not updated ?
3780 // do we miss something ? do we need to update this ourselves ?.. for now lets do our own timekeeping.
3781   gchar *channel_id = user_data;
3782   gint64 now = g_get_real_time()/1000;
3783 	PurpleChat *chat = mm_purple_blist_find_chat(ma, channel_id);
3784 	if (chat) {
3785 			purple_blist_node_set_string(PURPLE_BLIST_NODE(chat), "channel_approximate_view_time", g_strdup_printf("%" G_GINT64_FORMAT,now));
3786   } else {
3787     const gchar *username = g_hash_table_lookup(ma->one_to_ones,channel_id);
3788     if (username) {
3789       PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, username);
3790       if (buddy) {
3791         purple_blist_node_set_string(PURPLE_BLIST_NODE(buddy), "channel_approximate_view_time", g_strdup_printf("%" G_GINT64_FORMAT,now));
3792       }
3793     }
3794   }
3795 }
3796 
3797 static gboolean
mm_mark_room_messages_read_timeout(gpointer userdata)3798 mm_mark_room_messages_read_timeout(gpointer userdata)
3799 {
3800 	MattermostAccount *ma = userdata;
3801 	JsonObject *obj;
3802 	gchar *url;
3803 	gchar *postdata;
3804 
3805 	obj = json_object_new();
3806 	json_object_set_string_member(obj, "channel_id", ma->current_channel_id);
3807 	json_object_set_string_member(obj, "prev_channel_id", ma->last_channel_id);
3808 	postdata = json_object_to_string(obj);
3809 
3810 	//FIXME: this could be NULL on first call, but why later ? check!
3811 	if (!ma->current_channel_id) {
3812 		return FALSE;
3813 	}
3814 
3815 	g_free(ma->last_channel_id);
3816 	ma->last_channel_id = g_strdup(ma->current_channel_id);
3817 
3818 	url = mm_build_url(ma,"/channels/members/me/view");
3819 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_mark_room_messages_read_timeout_response, g_strdup(ma->current_channel_id));
3820 
3821 	g_free(postdata);
3822 	g_free(url);
3823 	json_object_unref(obj);
3824 
3825 
3826 
3827 	return FALSE;
3828 }
3829 
3830 static void
mm_mark_room_messages_read(MattermostAccount * ma,const gchar * room_id)3831 mm_mark_room_messages_read(MattermostAccount *ma, const gchar *room_id)
3832 {
3833 	g_free(ma->current_channel_id);
3834 	ma->current_channel_id = g_strdup(room_id);
3835 
3836 	g_source_remove(ma->read_messages_timeout);
3837 	ma->read_messages_timeout = g_timeout_add_seconds(1, mm_mark_room_messages_read_timeout, ma);
3838 }
3839 
3840 static void
mm_mark_conv_seen(PurpleConversation * conv,PurpleConversationUpdateType type)3841 mm_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type)
3842 {
3843 	PurpleConnection *pc;
3844 	MattermostAccount *ma;
3845 	const gchar *room_id;
3846 
3847 	if (type != PURPLE_CONVERSATION_UPDATE_UNSEEN)
3848 		return;
3849 
3850 	pc = purple_conversation_get_connection(conv);
3851 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
3852 		return;
3853 
3854 	if (g_strcmp0(purple_protocol_get_id(purple_connection_get_protocol(pc)), MATTERMOST_PLUGIN_ID))
3855 		return;
3856 
3857 	ma = purple_connection_get_protocol_data(pc);
3858 
3859 	room_id = purple_conversation_get_data(conv, "id");
3860 
3861 	if (PURPLE_IS_IM_CONVERSATION(conv)) {
3862 		room_id = g_hash_table_lookup(ma->one_to_ones_rev, purple_conversation_get_name(conv));
3863 		// new conversation: selecting IM in chat room people list on a non-buddy
3864 		if (room_id == NULL) {
3865 			// name of a new IM conv. == buddy username: better way to do it ?
3866 			const gchar *username = purple_conversation_get_name(conv);
3867 			PurpleBuddy *buddy = purple_blist_find_buddy(ma->account, username);
3868 			if (buddy == NULL) {
3869 				buddy = purple_buddy_new(ma->account, username, NULL);
3870 				purple_blist_add_buddy(buddy, NULL, mm_get_or_create_default_group(), NULL);
3871 				mm_add_buddy(pc, buddy, NULL, NULL);
3872 			}
3873 			return;
3874 		}
3875 	} else {
3876 		//FIXME	room_id = g_hash_table_lookup(ma->group_chats_rev, room_id);
3877 		//TODO:  if (room_id) == NULL - create new group chat
3878 	}
3879 
3880 	g_return_if_fail(room_id != NULL);
3881 
3882 	mm_mark_room_messages_read(ma, room_id);
3883 }
3884 
3885 static guint
mm_conv_send_typing(PurpleConversation * conv,PurpleIMTypingState state,MattermostAccount * ma)3886 mm_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state, MattermostAccount *ma)
3887 {
3888 	PurpleConnection *pc;
3889 	const gchar *room_id;
3890 	JsonObject *data;
3891 	JsonObject *data_inside;
3892 
3893 	if (state != PURPLE_IM_TYPING) {
3894 		return 0;
3895 	}
3896 
3897 	pc = ma ? ma->pc : purple_conversation_get_connection(conv);
3898 
3899 	if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
3900 		return 0;
3901 
3902 	if (g_strcmp0(purple_protocol_get_id(purple_connection_get_protocol(pc)), MATTERMOST_PLUGIN_ID))
3903 		return 0;
3904 
3905 	if (ma == NULL) {
3906 		ma = purple_connection_get_protocol_data(pc);
3907 	}
3908 
3909 	room_id = purple_conversation_get_data(conv, "id");
3910 
3911 	if (PURPLE_IS_IM_CONVERSATION(conv)) {
3912 		room_id = g_hash_table_lookup(ma->one_to_ones_rev, purple_conversation_get_name(conv));
3913 	} else {
3914 		room_id = g_hash_table_lookup(ma->group_chats_rev, room_id);
3915 	}
3916 
3917 	g_return_val_if_fail(room_id, -1); // this can happen if we try to type in a removed chat for which conv still exists ?
3918 
3919 	data = json_object_new();
3920 	data_inside = json_object_new();
3921 
3922 	json_object_set_string_member(data_inside, "channel_id", room_id);
3923 	json_object_set_string_member(data_inside, "parent_id", ""); //TODO what is this? (a reply to a post ?)
3924 
3925 	json_object_set_string_member(data, "action", "user_typing");
3926 	json_object_set_object_member(data, "data", data_inside);
3927 	json_object_set_int_member(data, "seq", mm_get_next_seq(ma));
3928 
3929 	mm_socket_write_json(ma, data);
3930 
3931 	return 10;
3932 }
3933 
3934 static guint
mm_send_typing(PurpleConnection * pc,const gchar * who,PurpleIMTypingState state)3935 mm_send_typing(PurpleConnection *pc, const gchar *who, PurpleIMTypingState state)
3936 {
3937 	PurpleConversation *conv;
3938 
3939 	conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(who, purple_connection_get_account(pc)));
3940 	g_return_val_if_fail(conv, -1);
3941 
3942 	return mm_conv_send_typing(conv, state, NULL);
3943 }
3944 
3945 
3946 static gint
3947 mm_conversation_send_message(MattermostAccount *ma, const gchar *team_id, const gchar *channel_id, const gchar *message, GList *file_ids);
3948 
3949 static void
mm_coversation_send_image_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)3950 mm_coversation_send_image_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
3951 {
3952 	const gchar *channel_id = user_data;
3953 
3954 	if (!mm_check_mattermost_response(ma,node,_("Error"),_("Error uploading image file"),TRUE)) return;
3955 
3956 	JsonObject *response = json_node_get_object(node);
3957 	JsonArray *file_infos = json_object_get_array_member(response,"file_infos");
3958 	guint i, len = json_array_get_length(file_infos);
3959 	for (i=0; i < len ; i++) {
3960 		JsonObject *file_info = json_node_get_object(json_array_get_element(file_infos, i));
3961 		const gchar *file_id = json_object_get_string_member(file_info,"id");
3962 
3963 		GList *file_ids = NULL;
3964 		file_ids = g_list_append(file_ids,g_strdup(file_id));
3965 		mm_conversation_send_message(ma, NULL, channel_id, "", file_ids);
3966 	}
3967 }
3968 static void
mm_conversation_send_image(MattermostAccount * ma,const gchar * channel_id,PurpleImage * image)3969 mm_conversation_send_image(MattermostAccount *ma,const gchar *channel_id, PurpleImage *image)
3970 {
3971 	gchar *url, *postdata;
3972 	const gchar *filename = purple_image_get_path(image);
3973 
3974 	postdata = g_memdup(purple_image_get_data(image),purple_image_get_size(image));
3975 
3976 	url = mm_build_url(ma,"/files?channel_id=%s&filename=%s",channel_id,filename);
3977 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, purple_image_get_size(image), mm_coversation_send_image_response, g_strdup(channel_id));
3978 
3979 	g_free(url);
3980 	g_free(postdata);
3981 }
3982 
3983 //FIXME: merge two funcs below.
3984 
3985 static int
mm_conversation_find_imageid(const gchar * message)3986 mm_conversation_find_imageid(const gchar *message)
3987 {
3988 	const gchar *img;
3989 
3990 	if ((img = strstr(message, "<img ")) || (img = strstr(message, "<IMG "))) {
3991 		const gchar *id;
3992 		const gchar *close = strchr(img, '>');
3993 
3994 		if (((id = strstr(img, "ID=\"")) || (id = strstr(img, "id=\""))) && id < close) {
3995 			int imgid = atoi(id + 4);
3996 			return imgid;
3997 		}
3998 	}
3999 	return 0;
4000 }
4001 
4002 static void
mm_conversation_send_files(MattermostAccount * ma,const gchar * team_id,const gchar * channel_id,const gchar * message)4003 mm_conversation_send_files(MattermostAccount *ma, const gchar *team_id, const gchar *channel_id, const gchar *message)
4004 {
4005 	const gchar *msgpt = message;
4006 
4007 	msgpt = g_strstr_len(message, strlen(message), "<img");
4008 	if (!msgpt) msgpt = g_strstr_len(message, strlen(message), "<IMG");
4009 	while (msgpt && strlen(msgpt)) {
4010 
4011 		int imgid = mm_conversation_find_imageid(msgpt);
4012 
4013 		PurpleImage *image = purple_image_store_get(imgid);
4014 		if (image) {mm_conversation_send_image(ma, channel_id, image);}
4015 
4016 		msgpt = g_strstr_len(msgpt, strlen(msgpt), ">");
4017 		if (msgpt != NULL) { msgpt = msgpt + 1; }
4018 	}
4019 }
4020 
4021 static void
mm_conversation_send_message_response(MattermostAccount * ma,JsonNode * node,gpointer user_data)4022 mm_conversation_send_message_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4023 {
4024 	JsonObject *obj = json_node_get_object(node);
4025 	if (json_object_get_int_member(obj, "status_code") >= 400) {
4026 		purple_notify_error(ma->pc, _("Error"), _("Error sending Message"), json_object_get_string_member(obj, "message"), purple_request_cpar_from_connection(ma->pc));
4027 	}
4028 }
4029 
4030 static gint
mm_conversation_send_message(MattermostAccount * ma,const gchar * team_id,const gchar * channel_id,const gchar * message,GList * file_ids)4031 mm_conversation_send_message(MattermostAccount *ma, const gchar *team_id, const gchar *channel_id, const gchar *message, GList *file_ids)
4032 {
4033 	JsonObject *data = json_object_new();
4034 	gchar *stripped;
4035 	gchar *_id;
4036 	gchar *postdata;
4037 	gchar *url;
4038 	JsonArray *tmparr;
4039 	GList *file_id;
4040 
4041 	_id = g_strdup_printf("%012XFFFF", g_random_int());
4042 	json_object_set_string_member(data, "pending_post_id", _id);
4043 	g_hash_table_insert(ma->sent_message_ids, _id, _id);
4044 
4045 	json_object_set_string_member(data, "channel_id", channel_id);
4046 
4047 	stripped = mm_html_to_markdown(message);
4048 	json_object_set_string_member(data, "message", stripped);
4049 	g_free(stripped);
4050 
4051 	json_object_set_string_member(data, "user_id", ma->self->user_id);
4052 	json_object_set_int_member(data, "create_at", 0);
4053 
4054 	tmparr = json_array_new();
4055 
4056 	if (file_ids) {
4057 		for (file_id = file_ids; file_id != NULL; file_id = file_id->next) {
4058 			json_array_add_string_element(tmparr,file_id->data);
4059 		}
4060 		json_object_set_array_member(data, "file_ids", tmparr);
4061 	}
4062 
4063 	postdata = json_object_to_string(data);
4064 
4065 	//url = mm_build_url(ma,"/teams/%s/channels/%s/posts/create", team_id, channel_id);
4066 	url = mm_build_url(ma,"/posts");
4067 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_conversation_send_message_response, NULL); //todo look at callback
4068 
4069 	if (!file_ids) mm_conversation_send_files(ma, team_id, channel_id, message);
4070 
4071 	json_array_unref(tmparr);
4072 	g_free(postdata);
4073 	g_free(url);
4074 
4075 	return 1;
4076 }
4077 
4078 static gint
mm_chat_send(PurpleConnection * pc,gint id,PurpleMessage * msg)4079 mm_chat_send(PurpleConnection *pc, gint id,
4080 #if PURPLE_VERSION_CHECK(3, 0, 0)
4081 PurpleMessage *msg)
4082 {
4083 	const gchar *message = purple_message_get_contents(msg);
4084 #else
4085 const gchar *message, PurpleMessageFlags flags)
4086 {
4087 #endif
4088 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4089 	PurpleChatConversation *chatconv = purple_conversations_find_chat(pc, id);
4090 	const gchar *room_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
4091 	const gchar *team_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "team_id");
4092 	gint ret = 0;
4093 
4094 	// this should not happen.
4095 	g_return_val_if_fail(room_id, -1);
4096 //g_return_val_if_fail(team_id, -1);  // it IS NULL for group channel.
4097 
4098 	ret = mm_conversation_send_message(ma, team_id, room_id, mm_purple_xhtml_im_to_html_parse(ma, message), NULL);
4099 
4100 	if (ret > 0) {
4101 		gchar *message_out = mm_markdown_to_html(ma, message);
4102 		purple_serv_got_chat_in(pc, g_str_hash(room_id), ma->self->username, PURPLE_MESSAGE_SEND, message_out, time(NULL));
4103 		g_free(message_out);
4104 	}
4105 	return ret;
4106 }
4107 
4108 
4109 static void
4110 mm_created_direct_message_send(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4111 {
4112 	PurpleMessage *msg = user_data;
4113 	JsonObject *result;
4114 	const gchar *who = purple_message_get_recipient(msg);
4115 	const gchar *message;
4116 	const gchar *room_id;
4117 	PurpleBuddy *buddy;
4118 
4119 	if (node == NULL) {
4120 		purple_conversation_present_error(who, ma->account, _("Could not create conversation"));
4121 		purple_message_destroy(msg);
4122 		return;
4123 	}
4124 	result = json_node_get_object(node);
4125 
4126 	if (json_object_get_int_member(result, "status_code") >= 400) {
4127 		purple_notify_error(ma->pc, _("Error"), _("Error creating Mattermost Channel"), json_object_get_string_member(result, "message"), purple_request_cpar_from_connection(ma->pc));
4128 		return;
4129 	}
4130 
4131 	message = purple_message_get_contents(msg);
4132 	room_id = json_object_get_string_member(result, "id");
4133 	buddy = purple_blist_find_buddy(ma->account, who);
4134 
4135 	if (room_id != NULL && who != NULL) {
4136 		g_hash_table_replace(ma->one_to_ones, g_strdup(room_id), g_strdup(who));
4137 		g_hash_table_replace(ma->one_to_ones_rev, g_strdup(who), g_strdup(room_id));
4138 	}
4139 
4140 	if (buddy != NULL) {
4141 		purple_blist_node_set_string(PURPLE_BLIST_NODE(buddy), "room_id", room_id);
4142 	}
4143 	//API: user is MM global, still need a team_id to contact, why ? ..
4144 	mm_conversation_send_message(ma, mm_get_first_team_id(ma), room_id, message, NULL);
4145 }
4146 
4147 static int
4148 mm_send_im(PurpleConnection *pc,
4149 #if PURPLE_VERSION_CHECK(3, 0, 0)
4150 PurpleMessage *msg)
4151 {
4152 	const gchar *who = purple_message_get_recipient(msg);
4153 	const gchar *message = purple_message_get_contents(msg);
4154 #else
4155 const gchar *who, const gchar *message, PurpleMessageFlags flags)
4156 {
4157 #endif
4158 
4159 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4160 	gchar *room_id = g_hash_table_lookup(ma->one_to_ones_rev, who);
4161 
4162 	//API: user is MM global, still need team_id to contact, why ? ..
4163 	const gchar *team_id = mm_get_first_team_id(ma);
4164 
4165 	if (room_id == NULL) {
4166 
4167 		if (purple_str_has_suffix(who, MATTERMOST_BOT_LABEL)) {
4168 			purple_notify_error(ma->pc, _("Error"), _("You cannot send instant message to a BOT"), _("(However you may be able to interact with it using \"/cmd command\" in a chat)"), purple_request_cpar_from_connection(ma->pc));
4169 			//TODO: 'disable' im conv window ?
4170 			return -1;
4171 		}
4172 
4173 		JsonArray *data;
4174 		gchar *url, *postdata;
4175 		const gchar *user_id = g_hash_table_lookup(ma->usernames_to_ids, who);
4176 #if !PURPLE_VERSION_CHECK(3, 0, 0)
4177 		PurpleMessage *msg = purple_message_new_outgoing(who, message, flags);
4178 #endif
4179 
4180 		data = json_array_new();
4181 		json_array_add_string_element(data, user_id);
4182 		json_array_add_string_element(data, ma->self->user_id);
4183 
4184 		postdata = json_array_to_string(data);
4185 		url = mm_build_url(ma,"/channels/direct");
4186 		mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_created_direct_message_send, msg);
4187 		g_free(url);
4188 
4189 		g_free(postdata);
4190 		json_array_unref(data);
4191 
4192 		MattermostUserPref *pref = g_new0(MattermostUserPref, 1);
4193 		pref->user_id = g_strdup(ma->self->user_id);
4194 		pref->category = g_strdup("direct_channel_show");
4195 		pref->name = g_strdup(user_id);
4196 		pref->value = g_strdup("true");
4197 
4198 		mm_save_user_pref(ma, pref);
4199 		// free pref in callback
4200 		return 1;
4201 	}
4202 	return mm_conversation_send_message(ma, team_id, room_id, message, NULL);
4203 }
4204 
4205 
4206 static void
4207 mm_chat_set_header_purpose(PurpleConnection *pc, int id, const char *topic, const gboolean isheader)
4208 {
4209 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4210 	PurpleChatConversation *chatconv;
4211 	JsonObject *data;
4212 	gchar *postdata;
4213 	gchar *url;
4214 	const gchar *channel_id;
4215 
4216 	chatconv = purple_conversations_find_chat(pc, id);
4217 	if (chatconv == NULL) return;
4218 
4219 	channel_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "id");
4220 
4221 	data = json_object_new();
4222 	json_object_set_string_member(data, "id", channel_id);
4223 
4224 	if (isheader) {
4225 		json_object_set_string_member(data, "header", topic);
4226 	} else {
4227 		json_object_set_string_member(data, "purpose", topic);
4228 	}
4229 
4230 	url = mm_build_url(ma,"/channels/%s", channel_id);
4231 
4232 	postdata = json_object_to_string(data);
4233 
4234 	mm_fetch_url(ma, url, MATTERMOST_HTTP_PUT, postdata, -1, NULL, NULL);
4235 
4236 	g_free(postdata);
4237 	g_free(url);
4238 }
4239 
4240 
4241 static void
4242 mm_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
4243 {
4244 	mm_chat_set_header_purpose(pc, id, topic, TRUE);
4245 }
4246 
4247 
4248 void
4249 mm_search_results_send_im(PurpleConnection *pc, GList *row, void *user_data)
4250 {
4251 	PurpleAccount *account = purple_connection_get_account(pc);
4252 	const gchar *who = g_list_nth_data(row, 0);
4253 	PurpleIMConversation *imconv;
4254 
4255 	imconv = purple_conversations_find_im_with_account(who, account);
4256 	if (imconv == NULL) {
4257 		imconv = purple_im_conversation_new(account, who);
4258 	}
4259 	purple_conversation_present(PURPLE_CONVERSATION(imconv));
4260 }
4261 
4262 void
4263 mm_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data)
4264 {
4265 	PurpleAccount *account = purple_connection_get_account(pc);
4266 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4267 	gboolean usealias = FALSE;
4268 
4269 	MattermostUser *mm_user=g_new0(MattermostUser,1);
4270 	mm_user->username = g_strdup(g_list_nth_data(row, 0));
4271 	mm_user->first_name = g_strdup(g_list_nth_data(row, 1));
4272 	mm_user->last_name = g_strdup(g_list_nth_data(row, 2));
4273 	mm_user->nickname = g_strdup(g_list_nth_data(row, 3));
4274 	mm_user->email = g_strdup(g_list_nth_data(row, 4));
4275 	mm_user->alias = g_strdup(mm_get_alias(mm_user));
4276 
4277 	if (purple_account_get_bool(ma->account, "use-alias", FALSE)) {
4278 		usealias = TRUE;
4279 	}
4280 
4281 	if (!purple_blist_find_buddy(account, mm_user->username)) {
4282 		purple_blist_request_add_buddy(account, mm_user->username, MATTERMOST_DEFAULT_BLIST_GROUP_NAME, usealias ? mm_user->alias : NULL); //NO room_id
4283 		//TODO: get info here to fill in all buddy data ?
4284 	}
4285 	mm_g_free_mattermost_user(mm_user);
4286 }
4287 
4288 static void
4289 mm_got_add_buddy_search(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4290 {
4291 	gchar *search_term = user_data;
4292 	GList *users, *i;
4293 	PurpleNotifySearchResults *results;
4294 	PurpleNotifySearchColumn *column;
4295 
4296 	// api docs says this should be an object response, but the api returns an array (v3?)
4297 	if (json_node_get_node_type(node) == JSON_NODE_OBJECT) {
4298 		JsonObject *obj = json_node_get_object(node);
4299 		if (json_object_has_member(obj, "status_code")) {
4300 			purple_notify_error(ma->pc, _("Search Error"), _("There was an error searching for the user"), json_object_get_string_member(obj, "message"), purple_request_cpar_from_connection(ma->pc));
4301 			return;
4302 		}
4303 
4304 		users = json_object_get_values(obj);
4305 	} else {
4306 		JsonArray *arr = json_node_get_array(node);
4307 
4308 		users = json_array_get_elements(arr);
4309 	}
4310 
4311 	if (users == NULL) {
4312 		gchar *primary_text = g_strdup_printf(_("Your search for the user \"%s\" returned no results"), search_term);
4313 		purple_notify_warning(ma->pc, _("No users found"), primary_text, "", purple_request_cpar_from_connection(ma->pc));
4314 		g_free(primary_text);
4315 
4316 		g_free(search_term);
4317 		return;
4318 	}
4319 
4320 	results = purple_notify_searchresults_new();
4321 	if (results == NULL)
4322 	{
4323 		g_list_free(users);
4324 		// This UI can't show search results
4325 		return;
4326 	}
4327 
4328 	/* columns: username, First Name, Last Name, Nickname, Email */
4329 	column = purple_notify_searchresults_column_new(_("Username"));
4330 	purple_notify_searchresults_column_add(results, column);
4331 	column = purple_notify_searchresults_column_new(_("First Name"));
4332 	purple_notify_searchresults_column_add(results, column);
4333 	column = purple_notify_searchresults_column_new(_("Last Name"));
4334 	purple_notify_searchresults_column_add(results, column);
4335 	column = purple_notify_searchresults_column_new(_("Nickname"));
4336 	purple_notify_searchresults_column_add(results, column);
4337 	column = purple_notify_searchresults_column_new(_("Email"));
4338 	purple_notify_searchresults_column_add(results, column);
4339 
4340 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, mm_search_results_add_buddy);
4341 	//purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_INFO, mm_search_results_get_info);
4342 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, mm_search_results_send_im);
4343 
4344 	for (i = users; i; i = i->next) {
4345 		JsonNode *usernode = i->data;
4346 		JsonObject *user = json_node_get_object(usernode);
4347 		const gchar *username = json_object_get_string_member(user, "username");
4348 
4349 		GList *row = NULL;
4350 
4351 		row = g_list_append(row, g_strdup(username));
4352 		row = g_list_append(row, g_strdup(json_object_get_string_member(user, "first_name")));
4353 		row = g_list_append(row, g_strdup(json_object_get_string_member(user, "last_name")));
4354 		row = g_list_append(row, g_strdup(json_object_get_string_member(user, "nickname")));
4355 		row = g_list_append(row, g_strdup(json_object_get_string_member(user, "email")));
4356 
4357 		purple_notify_searchresults_row_add(results, row);
4358 
4359 		if (!mm_hash_table_contains(ma->usernames_to_ids, username)) {
4360 			const gchar *id = json_object_get_string_member(user, "id");
4361 			g_hash_table_replace(ma->ids_to_usernames, g_strdup(id), g_strdup(username));
4362 			g_hash_table_replace(ma->usernames_to_ids, g_strdup(username), g_strdup(id));
4363 		}
4364 	}
4365 
4366 	purple_notify_searchresults(ma->pc, NULL, search_term, NULL, results, NULL, NULL);
4367 
4368 	g_list_free(users);
4369 	g_free(search_term);
4370 }
4371 
4372 
4373 void
4374 mm_search_users_text(MattermostAccount *ma, const gchar *text)
4375 {
4376 	JsonObject *obj = json_object_new();
4377 	gchar *url;
4378 	gchar *postdata;
4379 
4380 	json_object_set_string_member(obj, "term", text);
4381 	json_object_set_boolean_member(obj, "allow_inactive", TRUE);
4382 
4383 	postdata = json_object_to_string(obj);
4384 
4385 	url = mm_build_url(ma,"/users/search");
4386 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_got_add_buddy_search, g_strdup(text));
4387 	g_free(url);
4388 
4389 	g_free(postdata);
4390 	json_object_unref(obj);
4391 }
4392 
4393 void
4394 mm_search_users(PurpleProtocolAction *action)
4395 {
4396 	PurpleConnection *pc = purple_protocol_action_get_connection(action);
4397 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4398 
4399 	purple_request_input(pc, _("Search for users..."),
4400 					_("Search for users..."),
4401 					NULL,
4402 					NULL, FALSE, FALSE, NULL,
4403 					_("_Search"), G_CALLBACK(mm_search_users_text),
4404 					_("_Cancel"), NULL,
4405 					purple_request_cpar_from_connection(pc),
4406 					ma);
4407 
4408 }
4409 
4410 
4411 void
4412 mm_roomlist_show(PurpleProtocolAction *action)
4413 {
4414 	PurpleConnection *pc = purple_protocol_action_get_connection(action);
4415 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4416 	purple_roomlist_show_with_account(ma->account);
4417 }
4418 
4419 
4420 static MattermostUser *
4421 mm_user_from_json(MattermostAccount *ma, JsonObject *user)
4422 {
4423 	MattermostUser *mu = g_new0(MattermostUser, 1);
4424 
4425 	mu->user_id = g_strdup(json_object_get_string_member(user, "id"));
4426 	mu->username = g_strdup(json_object_get_string_member(user, "username"));
4427 	mu->first_name = g_strdup(json_object_get_string_member(user, "first_name"));
4428 	mu->last_name = g_strdup(json_object_get_string_member(user, "last_name"));
4429 	mu->nickname = g_strdup(json_object_get_string_member(user, "nickname"));
4430 	mu->email = g_strdup(json_object_get_string_member(user, "email"));
4431 	mu->position = g_strdup(json_object_get_string_member(user, "position"));
4432 	mu->locale = g_strdup(json_object_get_string_member(user, "locale"));
4433 	mu->alias = g_strdup(mm_get_alias(mu));
4434 	mu->roles = mm_role_to_purple_flag(ma, json_object_get_string_member(user, "roles"));
4435 
4436 	return mu;
4437 }
4438 
4439 //TODO: integrate with mm_get_users_by_ids() ?
4440 static void
4441 mm_got_add_buddy_user(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4442 {
4443 	JsonObject *user = json_node_get_object(node);
4444 	PurpleBuddy *buddy = user_data;
4445 
4446 	if (json_object_has_member(user, "status_code")) {
4447 		// There was an error in the response, which generally means the buddy is invalid somehow
4448 		const gchar *buddy_name = purple_buddy_get_name(buddy);
4449 		PurpleIMConversation *imconv = purple_conversations_find_im_with_account(buddy_name, ma->account);
4450 
4451 		if (imconv != NULL) {
4452 			PurpleConversation *conv = PURPLE_CONVERSATION(imconv);
4453 			purple_conversation_write_system_message(conv, _("Cannot sent message, invalid buddy"), PURPLE_MESSAGE_ERROR);
4454 		} else {
4455 			purple_notify_error(ma->pc, _("Add Buddy Error"), _("There was an error searching for the user"), json_object_get_string_member(user, "message"), purple_request_cpar_from_connection(ma->pc));
4456 		}
4457 
4458 		// bad user, delete
4459 		purple_blist_remove_buddy(buddy);
4460 		return;
4461 	}
4462 
4463 	MattermostUser *mm_user = mm_user_from_json(ma, user);
4464 
4465 	g_hash_table_replace(ma->ids_to_usernames, g_strdup(mm_user->user_id), g_strdup(mm_user->username));
4466 	g_hash_table_replace(ma->usernames_to_ids, g_strdup(mm_user->username), g_strdup(mm_user->user_id));
4467 
4468 	mm_add_buddy(ma->pc, buddy, NULL, NULL);
4469 
4470 	if (purple_account_get_bool(ma->account,"use-alias", FALSE)) {
4471 		purple_buddy_set_local_alias(buddy, mm_user->alias);
4472 	}
4473 
4474 	mm_g_free_mattermost_user(mm_user);
4475 }
4476 
4477 
4478 static void
4479 mm_got_avatar(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4480 {
4481 	if (node != NULL) {
4482 		JsonObject *response = json_node_get_object(node);
4483 		const gchar *buddy_name = user_data;
4484 		const gchar *response_str;
4485 		gsize response_len;
4486 		gpointer response_dup;
4487 
4488 		response_str = g_dataset_get_data(node, "raw_body");
4489 		response_len = json_object_get_int_member(response, "len");
4490 		response_dup = g_memdup(response_str, response_len);
4491 
4492 		if(purple_blist_find_buddy(ma->account, buddy_name)) {
4493 			purple_buddy_icons_set_for_user(ma->account, buddy_name, response_dup, response_len, NULL);
4494 		}
4495 	}
4496 }
4497 
4498 
4499 static void
4500 mm_get_avatar(MattermostAccount *ma, PurpleBuddy *buddy)
4501 {
4502 	gchar *url = mm_build_url(ma,"/users/%s/image", purple_blist_node_get_string(PURPLE_BLIST_NODE(buddy), "user_id"));
4503 	const gchar *buddy_name = g_strdup(purple_buddy_get_name(buddy));
4504 	mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_got_avatar, (gpointer) buddy_name);
4505 	g_free(url);
4506 }
4507 
4508 
4509 static void
4510 mm_fake_group_buddy(PurpleConnection *pc, const char *who, const char *old_group, const char *new_group)
4511 {
4512 	// Do nothing to stop the remove+add behaviour
4513 }
4514 
4515 
4516 static void
4517 mm_fake_group_rename(PurpleConnection *pc, const char *old_name, PurpleGroup *group, GList *moved_buddies)
4518 {
4519 	// Do nothing to stop the remove+add behaviour
4520 }
4521 
4522 
4523 static void
4524 mm_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
4525 {
4526 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4527 	MattermostUserPref *pref = g_new0(MattermostUserPref,1);
4528 	pref->user_id = g_strdup(ma->self->user_id);
4529 	pref->category = g_strdup("direct_channel_show");
4530 	pref->name = g_strdup(purple_blist_node_get_string(PURPLE_BLIST_NODE(buddy), "user_id"));
4531 	pref->value = g_strdup("false");
4532 	mm_save_user_pref(ma, pref);
4533 	// free pref in callback
4534 }
4535 
4536 static void
4537 mm_create_direct_channel_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4538 {
4539 	const gchar *user_id = user_data;
4540 	JsonObject *response = json_node_get_object(node);
4541 	const gchar *room_id;
4542 
4543 	if (json_object_get_int_member(response, "status_code") >= 400) {
4544 		purple_notify_error(ma->pc, _("Error"), _("Error creating Mattermost Channel"), json_object_get_string_member(response, "message"), purple_request_cpar_from_connection(ma->pc));
4545 		return;
4546 	}
4547 
4548 	room_id = json_object_get_string_member(response, "id");
4549 
4550 	if (room_id == NULL) {
4551 		return;
4552 	}
4553 
4554 	PurpleBlistNode *bnode;
4555 	gboolean found = FALSE;
4556 	for (bnode = purple_blist_get_root(); bnode != NULL && !found; bnode = purple_blist_node_next(bnode, TRUE)) {
4557 		if (!PURPLE_IS_BUDDY(bnode)) { continue; }
4558 		if (purple_strequal(purple_blist_node_get_string(bnode, "user_id"), user_id)) {
4559 			purple_blist_node_set_string(bnode, "room_id", room_id);
4560 			found = TRUE;
4561 		}
4562 	}
4563 }
4564 
4565 static void
4566 mm_create_direct_channel(MattermostAccount *ma, PurpleBuddy *buddy)
4567 {
4568 	gchar *url, *postdata;
4569 	const gchar *user_id;
4570 	JsonArray *data;
4571 
4572 	if (purple_blist_node_get_string(PURPLE_BLIST_NODE(buddy), "room_id")) {
4573 		return;
4574 	}
4575 
4576 	data = json_array_new();
4577 
4578 	user_id = purple_blist_node_get_string(PURPLE_BLIST_NODE(buddy), "user_id");
4579 	json_array_add_string_element(data, user_id);
4580 	json_array_add_string_element(data, ma->self->user_id);
4581 
4582 	postdata = json_array_to_string(data);
4583 	url = mm_build_url(ma,"/channels/direct");
4584 
4585 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_create_direct_channel_response, g_strdup(user_id));
4586 
4587 	g_free(url);
4588 	json_array_unref(data);
4589 }
4590 
4591 
4592 void
4593 mm_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
4594 {
4595 	MattermostAccount *ma = purple_connection_get_protocol_data(pc);
4596 	const gchar *buddy_name = purple_buddy_get_name(buddy);
4597 	const gchar *user_id = g_hash_table_lookup(ma->usernames_to_ids, buddy_name);
4598 
4599 	if (purple_strequal(user_id,ma->self->user_id)) {
4600 		purple_blist_remove_buddy(buddy);
4601 		return;
4602 	}
4603 
4604 	if (purple_str_has_suffix(buddy_name, MATTERMOST_BOT_LABEL)) {
4605 		purple_blist_remove_buddy(buddy);
4606 		return;
4607 	}
4608 
4609 	if (user_id == NULL) {
4610 		gchar *url;
4611 
4612 		//Search for user
4613 		// if they've entered what we think is a username, sanitise it
4614 		if (!strchr(buddy_name, ' ') && !strchr(buddy_name, '@')) {
4615 			url = mm_build_url(ma,"/users/username/%s", buddy_name);
4616 			mm_fetch_url(ma, url, MATTERMOST_HTTP_GET, NULL, -1, mm_got_add_buddy_user, buddy);
4617 			g_free(url);
4618 		} else {
4619 			// Doesn't look like a username, do a search
4620 			mm_search_users_text(ma, buddy_name);
4621 			purple_blist_remove_buddy(buddy);
4622 		}
4623 
4624 		return;
4625 	}
4626 
4627 	purple_blist_node_set_string(PURPLE_BLIST_NODE(buddy), "user_id", user_id);
4628 
4629 	mm_get_avatar(ma,buddy);
4630 
4631 	mm_create_direct_channel(ma, buddy);
4632 
4633 	MattermostUserPref *pref = g_new0(MattermostUserPref,1);
4634 	pref->user_id = g_strdup(ma->self->user_id);
4635 	pref->category = g_strdup("direct_channel_show");
4636 	pref->name = g_strdup(user_id);
4637 	pref->value = g_strdup("true");
4638 	mm_save_user_pref(ma,pref);
4639 	// free pref in callback
4640 
4641 	mm_refresh_statuses(ma, user_id);
4642 }
4643 
4644 static const char *
4645 mm_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
4646 {
4647 	return "mattermost";
4648 }
4649 
4650 static GList *
4651 mm_status_types(PurpleAccount *account)
4652 {
4653 	GList *types = NULL;
4654 	PurpleStatusType *status;
4655 
4656 	status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, "online", "Online", TRUE, TRUE, FALSE);
4657 	types = g_list_append(types, status);
4658 
4659 	status = purple_status_type_new_full(PURPLE_STATUS_AWAY, "away", "Away", TRUE, TRUE, FALSE);
4660 	types = g_list_append(types, status);
4661 
4662 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, "offline", "Offline", TRUE, TRUE, FALSE);
4663 	types = g_list_append(types, status);
4664 
4665 	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, "dnd", "Busy", TRUE, TRUE, FALSE);
4666 	types = g_list_append(types, status);
4667 
4668 	status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE, "invisible", "Invisible", TRUE, TRUE, FALSE);
4669 	types = g_list_append(types, status);
4670 
4671 	return types;
4672 }
4673 
4674 static GHashTable *
4675 mm_get_account_text_table(PurpleAccount *unused)
4676 {
4677 	GHashTable *table;
4678 
4679 	table = g_hash_table_new(g_str_hash, g_str_equal);
4680 
4681 	g_hash_table_insert(table, "login_label", (gpointer)_("Email or AD/LDAP Username..."));
4682 
4683 	return table;
4684 }
4685 
4686 static GList *
4687 mm_add_account_options(GList *account_options)
4688 {
4689 	PurpleAccountOption *option;
4690 
4691 	option = purple_account_option_bool_new(N_("Use SSL/HTTPS"), "use-ssl", TRUE);
4692 	account_options = g_list_append(account_options, option);
4693 
4694 	option = purple_account_option_bool_new(N_("Password is Gitlab cookie"), "use-mmauthtoken", FALSE);
4695 	account_options = g_list_append(account_options, option);
4696 
4697 	option = purple_account_option_bool_new(N_("Interpret (subset of) markdown"), "use-markdown", TRUE);
4698 	account_options = g_list_append(account_options, option);
4699 
4700 	option = purple_account_option_bool_new(N_("Auto generate buddies aliases"), "use-alias", FALSE);
4701 	account_options = g_list_append(account_options, option);
4702 
4703 	option = purple_account_option_bool_new(N_("Show images in messages"), "show-images", TRUE);
4704 	account_options = g_list_append(account_options, option);
4705 
4706 	//FIXME: this one shall depend on above one !
4707 	option = purple_account_option_bool_new(N_("Show full images in messages"), "show-full-images", FALSE);
4708 	account_options = g_list_append(account_options, option);
4709 
4710 	return account_options;
4711 }
4712 
4713 static PurpleCmdRet
4714 mm_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, gpointer data)
4715 {
4716 	PurpleConnection *pc = NULL;
4717 	int id = -1;
4718 
4719 	pc = purple_conversation_get_connection(conv);
4720 	id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
4721 
4722 	if (pc == NULL || id == -1)
4723 		return PURPLE_CMD_RET_FAILED;
4724 
4725 	mm_chat_leave(pc, id);
4726 
4727 	return PURPLE_CMD_RET_OK;
4728 }
4729 
4730 static void
4731 mm_slash_command_response(MattermostAccount *ma, JsonNode *node, gpointer user_data)
4732 {
4733 	gchar *cmd = user_data;
4734 	mm_check_mattermost_response(ma,node,_("Error"),g_strconcat(_("Error executing Mattermost Slash Command (/"),cmd,")",NULL),TRUE);
4735 }
4736 
4737 
4738 PurpleCmdRet
4739 mm_slash_command(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, gpointer userdata)
4740 {
4741 	PurpleConnection *pc = NULL;
4742 	MattermostAccount *ma = NULL;
4743 	const gchar *channel_id = NULL;
4744 	JsonObject *data;
4745 	gchar *postdata;
4746 	gchar *url;
4747 	gchar *params_str, *command;
4748 
4749 	pc = purple_conversation_get_connection(conv);
4750 	if (pc == NULL) {
4751 		return PURPLE_CMD_RET_FAILED;
4752 	}
4753 	ma = purple_connection_get_protocol_data(pc);
4754 	if (ma == NULL) {
4755 		return PURPLE_CMD_RET_FAILED;
4756 	}
4757 
4758 	channel_id = purple_conversation_get_data(conv, "id");
4759 	if (channel_id == NULL) {
4760 		if (PURPLE_IS_IM_CONVERSATION(conv)) {
4761 			channel_id = g_hash_table_lookup(ma->one_to_ones_rev, purple_conversation_get_name(conv));
4762 		} else {
4763 			channel_id = purple_conversation_get_name(conv);
4764 			if (g_hash_table_lookup(ma->group_chats_rev, channel_id)) {
4765 				// Convert friendly name into id
4766 				channel_id = g_hash_table_lookup(ma->group_chats_rev, channel_id);
4767 			}
4768 		}
4769 	}
4770 	if (channel_id == NULL) {
4771 		return PURPLE_CMD_RET_FAILED;
4772 	}
4773 
4774 	if (PURPLE_IS_IM_CONVERSATION(conv)) {
4775 		purple_notify_error(pc, _("Error"), _("Not implemented."),
4776 														_("Slash commands not implemented (yet) for private channels."), purple_request_cpar_from_connection(pc));
4777 		return PURPLE_CMD_RET_FAILED;
4778 	}
4779 
4780 	if (!ma->client_config->enable_commands) {
4781 		purple_notify_error(pc, _("Error"), _("Custom Slash Commands are disabled on Mattermost server"),
4782 														_("(See: https://docs.mattermost.com/administration/config-settings.html#integrations)"), purple_request_cpar_from_connection(pc));
4783 		return PURPLE_CMD_RET_FAILED;
4784 	}
4785 
4786 	params_str = g_strjoinv(" ", args);
4787 	command = g_strconcat("/", cmd, " ", params_str ? params_str : "", NULL);
4788 
4789 	g_free(params_str);
4790 
4791 	data = json_object_new();
4792 	json_object_set_string_member(data, "command", command);
4793 	json_object_set_string_member(data, "channel_id", channel_id);
4794 	postdata = json_object_to_string(data);
4795 
4796 	url = mm_build_url(ma,"/commands/execute");
4797 	mm_fetch_url(ma, url, MATTERMOST_HTTP_POST, postdata, -1, mm_slash_command_response, g_strdup(cmd));
4798 	g_free(url);
4799 
4800 	g_free(postdata);
4801 	json_object_unref(data);
4802 
4803 	return PURPLE_CMD_RET_OK;
4804 }
4805 
4806 static GList *
4807 mm_actions(
4808 #if !PURPLE_VERSION_CHECK(3, 0, 0)
4809 PurplePlugin *plugin, gpointer context
4810 #else
4811 PurpleConnection *pc
4812 #endif
4813 )
4814 {
4815 	GList *m = NULL;
4816 	PurpleProtocolAction *act;
4817 
4818 	act = purple_protocol_action_new(_("Search for Users..."), mm_search_users);
4819 	m = g_list_append(m, act);
4820 
4821 	act = purple_protocol_action_new(_("Room List"), mm_roomlist_show);
4822 	m = g_list_append(m, act);
4823 
4824 	act = purple_protocol_action_new(_("About Myself"), mm_about_myself);
4825 	m = g_list_append(m, act);
4826 
4827 	act = purple_protocol_action_new(_("Server Info"), mm_about_server);
4828 	m = g_list_append(m, act);
4829 
4830 	act = purple_protocol_action_new(_("Slash Commands"), mm_about_commands);
4831 	m = g_list_append(m, act);
4832 
4833 	return m;
4834 }
4835 
4836 static gboolean
4837 plugin_load(PurplePlugin *plugin, GError **error)
4838 {
4839 
4840 	mm_purple_xhtml_im_html_init();
4841 
4842 	// we do not want the server to initiate channel leave, we do it ourselves.
4843 	purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
4844 						PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
4845 						MATTERMOST_PLUGIN_ID, mm_cmd_leave,
4846 						_("leave:  Leave the channel"), NULL);
4847 
4848 	return TRUE;
4849 
4850 }
4851 
4852 static gboolean
4853 plugin_unload(PurplePlugin *plugin, GError **error)
4854 {
4855 	purple_signals_disconnect_by_handle(plugin);
4856 
4857 	return TRUE;
4858 }
4859 
4860 // Purple2 Plugin Load Functions
4861 #if !PURPLE_VERSION_CHECK(3, 0, 0)
4862 
4863 // Normally set in core.c in purple3
4864 void _purple_socket_init(void);
4865 void _purple_socket_uninit(void);
4866 
4867 static gboolean
4868 libpurple2_plugin_load(PurplePlugin *plugin)
4869 {
4870 	_purple_socket_init();
4871 	purple_http_init();
4872 
4873 	return plugin_load(plugin, NULL);
4874 }
4875 
4876 static gboolean
4877 libpurple2_plugin_unload(PurplePlugin *plugin)
4878 {
4879 	_purple_socket_uninit();
4880 	purple_http_uninit();
4881 
4882 	return plugin_unload(plugin, NULL);
4883 }
4884 
4885 static void
4886 plugin_init(PurplePlugin *plugin)
4887 {
4888 	// PurpleAccountOption *option;
4889 	// PurplePluginInfo *info = plugin->info;
4890 	// PurplePluginProtocolInfo *prpl_info = info->extra_info;
4891 	//purple_signal_connect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(mm_uri_handler), NULL);
4892 
4893 	PurpleAccountUserSplit *split;
4894 	PurplePluginInfo *info;
4895 	PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
4896 
4897 	split = purple_account_user_split_new(_("Server"), MATTERMOST_DEFAULT_SERVER, MATTERMOST_SERVER_SPLIT_CHAR);
4898 	prpl_info->user_splits = g_list_append(prpl_info->user_splits, split);
4899 
4900 	info = plugin->info;
4901 	if (info == NULL) {
4902 		plugin->info = info = g_new0(PurplePluginInfo, 1);
4903 	}
4904 	info->actions = mm_actions;
4905 	info->extra_info = prpl_info;
4906 	#if PURPLE_MINOR_VERSION >= 5
4907 		prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
4908 	#endif
4909 	#if PURPLE_MINOR_VERSION >= 8
4910 		//prpl_info->add_buddy_with_invite = mm_add_buddy;
4911 	#endif
4912 
4913 	prpl_info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_SLASH_COMMANDS_NATIVE | OPT_PROTO_IM_IMAGE;
4914 	prpl_info->protocol_options = mm_add_account_options(prpl_info->protocol_options);
4915 	prpl_info->icon_spec.format = "png,gif,jpeg";
4916 	prpl_info->icon_spec.min_width = 0;
4917 	prpl_info->icon_spec.min_height = 0;
4918 	prpl_info->icon_spec.max_width = 96;
4919 	prpl_info->icon_spec.max_height = 96;
4920 	prpl_info->icon_spec.max_filesize = 0;
4921 	prpl_info->icon_spec.scale_rules = PURPLE_ICON_SCALE_DISPLAY;
4922 
4923 	prpl_info->get_account_text_table = mm_get_account_text_table;
4924 	prpl_info->list_icon = mm_list_icon;
4925 	prpl_info->set_status = mm_set_status;
4926 	prpl_info->set_idle = mm_set_idle;
4927 	prpl_info->status_types = mm_status_types;
4928 	prpl_info->chat_info = mm_chat_info;
4929 	prpl_info->chat_info_defaults = mm_chat_info_defaults;
4930 	prpl_info->login = mm_login;
4931 	prpl_info->close = mm_close;
4932 	prpl_info->send_im = mm_send_im;
4933 	prpl_info->send_typing = mm_send_typing;
4934 	prpl_info->join_chat = mm_join_chat;
4935 	prpl_info->get_chat_name = mm_get_chat_name;
4936 	prpl_info->chat_invite = mm_chat_invite;
4937 	prpl_info->chat_send = mm_chat_send;
4938 	prpl_info->set_chat_topic = mm_chat_set_topic;
4939 	prpl_info->add_buddy = mm_add_buddy_no_message;
4940 	prpl_info->remove_buddy = mm_remove_buddy;
4941 	prpl_info->group_buddy = mm_fake_group_buddy;
4942 	prpl_info->rename_group = mm_fake_group_rename;
4943 	prpl_info->blist_node_menu = mm_blist_node_menu;
4944 	prpl_info->get_info = mm_get_info;
4945 	prpl_info->tooltip_text = mm_tooltip_text;
4946 
4947 	prpl_info->roomlist_get_list = mm_roomlist_get_list;
4948 	prpl_info->roomlist_room_serialize = mm_roomlist_serialize;
4949 }
4950 
4951 
4952 static PurplePluginInfo info = {
4953 	PURPLE_PLUGIN_MAGIC,
4954 /*	PURPLE_MAJOR_VERSION,
4955 	PURPLE_MINOR_VERSION,
4956 */
4957 	2, 1,
4958 	PURPLE_PLUGIN_PROTOCOL,            // type
4959 	NULL,                              // ui_requirement
4960 	0,                                 // flags
4961 	NULL,                              // dependencies
4962 	PURPLE_PRIORITY_DEFAULT,           // priority
4963 	MATTERMOST_PLUGIN_ID,              // id
4964 	"Mattermost",                      // name
4965 	MATTERMOST_PLUGIN_VERSION,         // version
4966 	N_("Mattermost Protocol Plugin."), // summary
4967 	N_("Adds Mattermost protocol support to libpurple."), // description
4968 	"Eion Robb <eion@robbmob.com>",    // author
4969 	MATTERMOST_PLUGIN_WEBSITE,         // homepage
4970 	libpurple2_plugin_load,            // load
4971 	libpurple2_plugin_unload,          // unload
4972 	NULL,                              // destroy
4973 	NULL,                              // ui_info
4974 	NULL,                              // extra_info
4975 	NULL,                              // prefs_info
4976 	NULL,                              // actions
4977 	NULL,                              // padding
4978 	NULL,
4979 	NULL,
4980 	NULL
4981 };
4982 
4983 PURPLE_INIT_PLUGIN(mattermost, plugin_init, info);
4984 
4985 #else
4986 //Purple 3 plugin load functions
4987 
4988 static void
4989 mm_protocol_init(PurpleProtocol *prpl_info)
4990 {
4991 	PurpleProtocol *info = prpl_info;
4992 	PurpleAccountUserSplit *split;
4993 
4994 	info->id = MATTERMOST_PLUGIN_ID;
4995 	info->name = "Mattermost";
4996 	info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_SLASH_COMMANDS_NATIVE | OPT_PROTO_IM_IMAGE;
4997 	info->account_options = mm_add_account_options(info->account_options);
4998 
4999 	split = purple_account_user_split_new(_("Server"), MATTERMOST_DEFAULT_SERVER, MATTERMOST_SERVER_SPLIT_CHAR);
5000 	info->user_splits = g_list_append(info->user_splits, split);
5001 }
5002 
5003 static void
5004 mm_protocol_class_init(PurpleProtocolClass *prpl_info)
5005 {
5006 	prpl_info->login = mm_login;
5007 	prpl_info->close = mm_close;
5008 	prpl_info->status_types = mm_status_types;
5009 	prpl_info->list_icon = mm_list_icon;
5010 }
5011 
5012 
5013 static void
5014 mm_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
5015 {
5016 	prpl_info->send = mm_send_im;
5017 	prpl_info->send_typing = mm_send_typing;
5018 }
5019 
5020 static void
5021 mm_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
5022 {
5023 	prpl_info->send = mm_chat_send;
5024 	prpl_info->info = mm_chat_info;
5025 	prpl_info->info_defaults = mm_chat_info_defaults;
5026 	prpl_info->join = mm_join_chat;
5027 	prpl_info->get_name = mm_get_chat_name;
5028 	prpl_info->invite = mm_chat_invite;
5029 	prpl_info->set_topic = mm_chat_set_topic;
5030 }
5031 
5032 
5033 static void
5034 mm_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
5035 {
5036 	prpl_info->add_buddy = mm_add_buddy;
5037 	prpl_info->remove_buddy = mm_remove_buddy;
5038 	prpl_info->group_buddy = mm_fake_group_buddy;
5039 	prpl_info->rename_group = mm_fake_group_rename;
5040 	prpl_info->set_status = mm_set_status;
5041 	prpl_info->set_idle = mm_set_idle;
5042 	prpl_info->get_info = mm_get_info;
5043 }
5044 
5045 
5046 static void
5047 mm_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
5048 {
5049 	prpl_info->get_actions = mm_actions;
5050 	prpl_info->get_account_text_table = mm_get_account_text_table;
5051 	prpl_info->blist_node_menu = mm_blist_node_menu;
5052 	prpl_info->tooltip_text = mm_tooltip_text;
5053 
5054 }
5055 
5056 
5057 static void
5058 mm_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
5059 {
5060 	prpl_info->get_list = mm_roomlist_get_list;
5061 	prpl_info->room_serialize = mm_roomlist_serialize;
5062 }
5063 
5064 
5065 static PurpleProtocol *mm_protocol;
5066 
5067 PURPLE_DEFINE_TYPE_EXTENDED(
5068 	MattermostProtocol, mm_protocol, PURPLE_TYPE_PROTOCOL, 0,
5069 
5070 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
5071 	                                  mm_protocol_im_iface_init)
5072 
5073 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
5074 	                                  mm_protocol_chat_iface_init)
5075 
5076 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
5077 	                                  mm_protocol_server_iface_init)
5078 
5079 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
5080 	                                  mm_protocol_client_iface_init)
5081 
5082 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
5083 	                                  mm_protocol_roomlist_iface_init)
5084 
5085 );
5086 
5087 static gboolean
5088 libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
5089 {
5090 	mm_protocol_register_type(plugin);
5091 	mm_protocol = purple_protocols_add(MATTERMOST_TYPE_PROTOCOL, error);
5092 	if (!mm_protocol)
5093 		return FALSE;
5094 
5095 	return plugin_load(plugin, error);
5096 }
5097 
5098 static gboolean
5099 libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
5100 {
5101 	if (!plugin_unload(plugin, error))
5102 		return FALSE;
5103 
5104 	if (!purple_protocols_remove(mm_protocol, error))
5105 		return FALSE;
5106 
5107 	return TRUE;
5108 }
5109 
5110 static PurplePluginInfo *
5111 plugin_query(GError **error)
5112 {
5113 	return purple_plugin_info_new(
5114 		"id",          MATTERMOST_PLUGIN_ID,
5115 		"name",        "Mattermost",
5116 		"version",     MATTERMOST_PLUGIN_VERSION,
5117 		"category",    N_("Protocol"),
5118 		"summary",     N_("Mattermost Protocol Plugin."),
5119 		"description", N_("Adds Mattermost protocol support to libpurple."),
5120 		"website",     MATTERMOST_PLUGIN_WEBSITE,
5121 		"abi-version", PURPLE_ABI_VERSION,
5122 		"flags",       PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
5123 		               PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
5124 		NULL
5125 	);
5126 }
5127 
5128 PURPLE_PLUGIN_INIT(mattermost, plugin_query, libpurple3_plugin_load, libpurple3_plugin_unload);
5129 
5130 #endif
5131