1 /*
2 * Hangouts Plugin for libpurple/Pidgin
3 * Copyright (c) 2015-2016 Eion Robb, Mike Ruprecht
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 "libhangouts.h"
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <glib.h>
25
26 #include "accountopt.h"
27 #include "cmds.h"
28 #include "debug.h"
29 #include "mediamanager.h"
30 #include "plugins.h"
31 #include "request.h"
32 #include "version.h"
33
34 #include "hangouts_auth.h"
35 #include "hangouts_pblite.h"
36 #include "hangouts_json.h"
37 #include "hangouts_events.h"
38 #include "hangouts_connection.h"
39 #include "hangouts_conversation.h"
40 #include "hangouts_media.h"
41
42
43 /*****************************************************************************/
44 //TODO move to nicer place
45
46 gboolean
hangouts_is_valid_id(const gchar * id)47 hangouts_is_valid_id(const gchar *id)
48 {
49 gint i;
50
51 for (i = strlen(id) - 1; i >= 0; i--) {
52 if (!g_ascii_isdigit(id[i])) {
53 return FALSE;
54 }
55 }
56
57 return TRUE;
58 }
59
60 PurpleMediaCaps
hangouts_get_media_caps(PurpleAccount * account,const char * who)61 hangouts_get_media_caps(PurpleAccount *account, const char *who)
62 {
63 return PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_AUDIO_VIDEO | PURPLE_MEDIA_CAPS_MODIFY_SESSION;
64 }
65
66 void
hangouts_set_idle(PurpleConnection * pc,int time)67 hangouts_set_idle(PurpleConnection *pc, int time)
68 {
69 HangoutsAccount *ha;
70
71 ha = purple_connection_get_protocol_data(pc);
72
73 if (time < HANGOUTS_ACTIVE_CLIENT_TIMEOUT) {
74 hangouts_set_active_client(pc);
75 }
76 ha->idle_time = time;
77 }
78
79 static GList *
hangouts_add_account_options(GList * account_options)80 hangouts_add_account_options(GList *account_options)
81 {
82 PurpleAccountOption *option;
83
84 option = purple_account_option_bool_new(N_("Show call links in chat"), "show-call-links", !purple_media_manager_get());
85 account_options = g_list_append(account_options, option);
86
87 option = purple_account_option_bool_new(N_("Un-Googlify URLs"), "unravel_google_url", FALSE);
88 account_options = g_list_append(account_options, option);
89
90 option = purple_account_option_bool_new(N_("Treat invisible users as offline"), "treat_invisible_as_offline", FALSE);
91 account_options = g_list_append(account_options, option);
92
93 option = purple_account_option_bool_new(N_("Hide self from buddy list (requires reconnect)"), "hide_self", FALSE);
94 account_options = g_list_append(account_options, option);
95
96 option = purple_account_option_bool_new(N_("Fetch image history when opening group chats"), "fetch_image_history", TRUE);
97 account_options = g_list_append(account_options, option);
98
99 return account_options;
100 }
101
102 static void
hangouts_blist_node_removed(PurpleBlistNode * node)103 hangouts_blist_node_removed(PurpleBlistNode *node)
104 {
105 PurpleChat *chat = NULL;
106 PurpleBuddy *buddy = NULL;
107 PurpleAccount *account = NULL;
108 PurpleConnection *pc;
109 const gchar *conv_id;
110 GHashTable *components;
111
112 if (PURPLE_IS_CHAT(node)) {
113 chat = PURPLE_CHAT(node);
114 account = purple_chat_get_account(chat);
115 } else if (PURPLE_IS_BUDDY(node)) {
116 buddy = PURPLE_BUDDY(node);
117 account = purple_buddy_get_account(buddy);
118 }
119
120 if (account == NULL) {
121 return;
122 }
123
124 if (!purple_strequal(purple_account_get_protocol_id(account), HANGOUTS_PLUGIN_ID)) {
125 return;
126 }
127
128 pc = purple_account_get_connection(account);
129 if (pc == NULL) {
130 return;
131 }
132
133 if (chat != NULL) {
134 components = purple_chat_get_components(chat);
135 conv_id = g_hash_table_lookup(components, "conv_id");
136 if (conv_id == NULL) {
137 conv_id = purple_chat_get_name_only(chat);
138 }
139
140 hangouts_chat_leave_by_conv_id(pc, conv_id, NULL);
141 } else {
142 HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
143 const gchar *gaia_id = purple_buddy_get_name(buddy);
144 conv_id = g_hash_table_lookup(ha->one_to_ones_rev, gaia_id);
145
146 hangouts_archive_conversation(ha, conv_id);
147
148 if (purple_strequal(gaia_id, ha->self_gaia_id)) {
149 purple_account_set_bool(account, "hide_self", TRUE);
150 }
151 }
152 }
153
154 static void
hangouts_blist_node_aliased(PurpleBlistNode * node,const char * old_alias)155 hangouts_blist_node_aliased(PurpleBlistNode *node, const char *old_alias)
156 {
157 PurpleChat *chat = NULL;
158 PurpleAccount *account = NULL;
159 PurpleConnection *pc;
160 const gchar *conv_id;
161 GHashTable *components;
162 HangoutsAccount *ha;
163 const gchar *new_alias;
164
165 if (PURPLE_IS_CHAT(node)) {
166 chat = PURPLE_CHAT(node);
167 account = purple_chat_get_account(chat);
168 }
169
170 if (account == NULL) {
171 return;
172 }
173
174 if (!purple_strequal(purple_account_get_protocol_id(account), HANGOUTS_PLUGIN_ID)) {
175 return;
176 }
177
178 pc = purple_account_get_connection(account);
179 if (pc == NULL) {
180 return;
181 }
182 ha = purple_connection_get_protocol_data(pc);
183
184 if (g_dataset_get_data(ha, "ignore_set_alias")) {
185 return;
186 }
187
188 if (chat != NULL) {
189 new_alias = purple_chat_get_alias(chat);
190
191 // Don't send update to existing update
192 if (!purple_strequal(old_alias, new_alias)) {
193 components = purple_chat_get_components(chat);
194 conv_id = g_hash_table_lookup(components, "conv_id");
195 if (conv_id == NULL) {
196 conv_id = purple_chat_get_name_only(chat);
197 }
198
199 hangouts_rename_conversation(ha, conv_id, new_alias);
200 }
201 }
202 }
203
204 static void
hangouts_chat_set_topic(PurpleConnection * pc,int id,const char * topic)205 hangouts_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
206 {
207 const gchar *conv_id;
208 PurpleChatConversation *chatconv;
209 HangoutsAccount *ha;
210
211 ha = purple_connection_get_protocol_data(pc);
212 chatconv = purple_conversations_find_chat(pc, id);
213 conv_id = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "conv_id");
214 if (conv_id == NULL) {
215 // Fix for a race condition around the chat data and serv_got_joined_chat()
216 conv_id = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
217 }
218
219 return hangouts_rename_conversation(ha, conv_id, topic);
220 }
221
222 static PurpleCmdRet
hangouts_cmd_leave(PurpleConversation * conv,const gchar * cmd,gchar ** args,gchar ** error,void * data)223 hangouts_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
224 {
225 PurpleConnection *pc = NULL;
226 int id = -1;
227
228 pc = purple_conversation_get_connection(conv);
229 id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
230
231 if (pc == NULL || id == -1)
232 return PURPLE_CMD_RET_FAILED;
233
234 hangouts_chat_leave(pc, id);
235
236 return PURPLE_CMD_RET_OK;
237 }
238
239 static PurpleCmdRet
hangouts_cmd_kick(PurpleConversation * conv,const gchar * cmd,gchar ** args,gchar ** error,void * data)240 hangouts_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
241 {
242 PurpleConnection *pc = NULL;
243 int id = -1;
244
245 pc = purple_conversation_get_connection(conv);
246 id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
247
248 if (pc == NULL || id == -1)
249 return PURPLE_CMD_RET_FAILED;
250
251 hangouts_chat_kick(pc, id, args[0]);
252
253 return PURPLE_CMD_RET_OK;
254 }
255
256 static GList *
hangouts_node_menu(PurpleBlistNode * node)257 hangouts_node_menu(PurpleBlistNode *node)
258 {
259 GList *m = NULL;
260 PurpleMenuAction *act;
261
262 if(PURPLE_IS_BUDDY(node))
263 {
264 act = purple_menu_action_new(_("Initiate _Chat"),
265 PURPLE_CALLBACK(hangouts_initiate_chat_from_node),
266 NULL, NULL);
267 m = g_list_append(m, act);
268 } else if (PURPLE_IS_CHAT(node)) {
269 act = purple_menu_action_new(_("_Leave Chat"),
270 PURPLE_CALLBACK(hangouts_blist_node_removed), // A strange coinkidink
271 NULL, NULL);
272 m = g_list_append(m, act);
273 }
274
275 return m;
276 }
277
278 static void
hangouts_join_chat_by_url_action(PurpleProtocolAction * action)279 hangouts_join_chat_by_url_action(PurpleProtocolAction *action)
280 {
281 PurpleConnection *pc = purple_protocol_action_get_connection(action);
282 HangoutsAccount *ha = purple_connection_get_protocol_data(pc);
283
284 purple_request_input(pc, _("Join chat..."),
285 _("Join a Hangouts group chat from the invite URL..."),
286 NULL,
287 NULL, FALSE, FALSE, "https://hangouts.google.com/group/...",
288 _("_Join"), G_CALLBACK(hangouts_join_chat_from_url),
289 _("_Cancel"), NULL,
290 purple_request_cpar_from_connection(pc),
291 ha);
292
293 }
294
295 static GList *
hangouts_actions(PurplePlugin * plugin,gpointer context)296 hangouts_actions(
297 #if !PURPLE_VERSION_CHECK(3, 0, 0)
298 PurplePlugin *plugin, gpointer context
299 #else
300 PurpleConnection *pc
301 #endif
302 )
303 {
304 GList *m = NULL;
305 PurpleProtocolAction *act;
306
307 act = purple_protocol_action_new(_("Search for friends..."), hangouts_search_users);
308 m = g_list_append(m, act);
309
310 act = purple_protocol_action_new(_("Join a group chat by URL..."), hangouts_join_chat_by_url_action);
311 m = g_list_append(m, act);
312
313 return m;
314 }
315
316 static void
hangouts_authcode_input_cb(gpointer user_data,const gchar * auth_code)317 hangouts_authcode_input_cb(gpointer user_data, const gchar *auth_code)
318 {
319 HangoutsAccount *ha = user_data;
320 PurpleConnection *pc = ha->pc;
321
322 purple_connection_update_progress(pc, _("Authenticating"), 1, 3);
323 hangouts_oauth_with_code(ha, auth_code);
324 }
325
326 static void
hangouts_authcode_input_cancel_cb(gpointer user_data)327 hangouts_authcode_input_cancel_cb(gpointer user_data)
328 {
329 HangoutsAccount *ha = user_data;
330 purple_connection_error(ha->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
331 _("User cancelled authorization"));
332 }
333
334 static gulong chat_conversation_typing_signal = 0;
335
336 #if !PURPLE_VERSION_CHECK(3, 0, 0)
337 static gulong deleting_chat_buddy_signal = 0;
338
339 // See workaround for purple_chat_conversation_find_user() in purplecompat.h
340 static void
hangouts_deleting_chat_buddy(PurpleConvChatBuddy * cb)341 hangouts_deleting_chat_buddy(PurpleConvChatBuddy *cb)
342 {
343 if (g_dataset_get_data(cb, "chat") != NULL) {
344 g_dataset_destroy(cb);
345 }
346 }
347 #endif
348
349 static void
hangouts_login(PurpleAccount * account)350 hangouts_login(PurpleAccount *account)
351 {
352 PurpleConnection *pc;
353 HangoutsAccount *ha; //hahaha
354 const gchar *password;
355 const gchar *self_gaia_id;
356 PurpleConnectionFlags pc_flags;
357
358 pc = purple_account_get_connection(account);
359 password = purple_connection_get_password(pc);
360
361 pc_flags = purple_connection_get_flags(pc);
362 pc_flags |= PURPLE_CONNECTION_FLAG_HTML;
363 pc_flags |= PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
364 pc_flags |= PURPLE_CONNECTION_FLAG_NO_BGCOLOR;
365 pc_flags &= ~PURPLE_CONNECTION_FLAG_NO_IMAGES;
366 purple_connection_set_flags(pc, pc_flags);
367
368 ha = g_new0(HangoutsAccount, 1);
369 ha->account = account;
370 ha->pc = pc;
371 ha->cookie_jar = purple_http_cookie_jar_new();
372 ha->channel_buffer = g_byte_array_sized_new(HANGOUTS_BUFFER_DEFAULT_SIZE);
373 ha->channel_keepalive_pool = purple_http_keepalive_pool_new();
374 ha->client6_keepalive_pool = purple_http_keepalive_pool_new();
375 ha->sent_message_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
376
377 ha->one_to_ones = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
378 ha->one_to_ones_rev = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
379 ha->group_chats = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
380 ha->google_voice_conversations = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
381
382 self_gaia_id = purple_account_get_string(account, "self_gaia_id", NULL);
383 if (self_gaia_id != NULL) {
384 ha->self_gaia_id = g_strdup(self_gaia_id);
385 purple_connection_set_display_name(pc, ha->self_gaia_id);
386 }
387
388 purple_connection_set_protocol_data(pc, ha);
389
390 if (password && *password) {
391 ha->refresh_token = g_strdup(password);
392 purple_connection_update_progress(pc, _("Authenticating"), 1, 3);
393 hangouts_oauth_refresh_token(ha);
394 } else {
395 //TODO get this code automatically
396 purple_notify_uri(pc, "https://www.youtube.com/watch?v=hlDhp-eNLMU");
397 purple_request_input(pc, _("Authorization Code"), "https://www.youtube.com/watch?v=hlDhp-eNLMU",
398 _ ("Please follow the YouTube video to get the OAuth code"),
399 _ ("and then paste the Google OAuth code here"), FALSE, FALSE, NULL,
400 _("OK"), G_CALLBACK(hangouts_authcode_input_cb),
401 _("Cancel"), G_CALLBACK(hangouts_authcode_input_cancel_cb),
402 purple_request_cpar_from_connection(pc), ha);
403 }
404
405 purple_signal_connect(purple_blist_get_handle(), "blist-node-removed", account, PURPLE_CALLBACK(hangouts_blist_node_removed), NULL);
406 purple_signal_connect(purple_blist_get_handle(), "blist-node-aliased", account, PURPLE_CALLBACK(hangouts_blist_node_aliased), NULL);
407 purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", account, PURPLE_CALLBACK(hangouts_mark_conversation_seen), NULL);
408 if (!chat_conversation_typing_signal) {
409 chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(hangouts_conv_send_typing), NULL);
410 }
411
412 #if !PURPLE_VERSION_CHECK(3, 0, 0)
413 if (!deleting_chat_buddy_signal) {
414 deleting_chat_buddy_signal = purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy", purple_connection_get_protocol(pc), PURPLE_CALLBACK(hangouts_deleting_chat_buddy), NULL);
415 }
416 #endif
417
418 ha->active_client_timeout = g_timeout_add_seconds(HANGOUTS_ACTIVE_CLIENT_TIMEOUT, ((GSourceFunc) hangouts_set_active_client), pc);
419 }
420
421 static void
hangouts_close(PurpleConnection * pc)422 hangouts_close(PurpleConnection *pc)
423 {
424 HangoutsAccount *ha; //not so funny anymore
425
426 ha = purple_connection_get_protocol_data(pc);
427 purple_signals_disconnect_by_handle(ha->account);
428
429 g_source_remove(ha->active_client_timeout);
430 g_source_remove(ha->channel_watchdog);
431 g_source_remove(ha->poll_buddy_status_timeout);
432
433 purple_http_conn_cancel_all(pc);
434
435 purple_http_keepalive_pool_unref(ha->channel_keepalive_pool);
436 purple_http_keepalive_pool_unref(ha->client6_keepalive_pool);
437 g_free(ha->self_gaia_id);
438 g_free(ha->self_phone);
439 g_free(ha->refresh_token);
440 g_free(ha->access_token);
441 g_free(ha->gsessionid_param);
442 g_free(ha->sid_param);
443 g_free(ha->client_id);
444 purple_http_cookie_jar_unref(ha->cookie_jar);
445 g_byte_array_free(ha->channel_buffer, TRUE);
446
447 g_hash_table_remove_all(ha->sent_message_ids);
448 g_hash_table_unref(ha->sent_message_ids);
449 g_hash_table_remove_all(ha->one_to_ones);
450 g_hash_table_unref(ha->one_to_ones);
451 g_hash_table_remove_all(ha->one_to_ones_rev);
452 g_hash_table_unref(ha->one_to_ones_rev);
453 g_hash_table_remove_all(ha->group_chats);
454 g_hash_table_unref(ha->group_chats);
455 g_hash_table_remove_all(ha->google_voice_conversations);
456 g_hash_table_unref(ha->google_voice_conversations);
457
458 g_free(ha);
459 }
460
461
462 static const char *
hangouts_list_icon(PurpleAccount * account,PurpleBuddy * buddy)463 hangouts_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
464 {
465 return "hangouts";
466 }
467
468 static void
hangouts_tooltip_text(PurpleBuddy * buddy,PurpleNotifyUserInfo * user_info,gboolean full)469 hangouts_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
470 {
471 PurplePresence *presence;
472 PurpleStatus *status;
473 const gchar *message;
474 HangoutsBuddy *hbuddy;
475
476 g_return_if_fail(buddy != NULL);
477
478 presence = purple_buddy_get_presence(buddy);
479 status = purple_presence_get_active_status(presence);
480 purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
481
482 message = purple_status_get_attr_string(status, "message");
483 if (message != NULL) {
484 purple_notify_user_info_add_pair_html(user_info, _("Message"), message);
485 }
486
487 hbuddy = purple_buddy_get_protocol_data(buddy);
488 if (hbuddy != NULL) {
489 if (hbuddy->last_seen != 0) {
490 gchar *seen = purple_str_seconds_to_string(time(NULL) - hbuddy->last_seen);
491 purple_notify_user_info_add_pair_html(user_info, _("Last seen"), seen);
492 g_free(seen);
493 }
494
495 if (hbuddy->in_call) {
496 purple_notify_user_info_add_pair_html(user_info, _("In call"), NULL);
497 }
498
499 if (hbuddy->device_type) {
500 purple_notify_user_info_add_pair_html(user_info, _("Device Type"),
501 hbuddy->device_type & HANGOUTS_DEVICE_TYPE_DESKTOP ? _("Desktop") :
502 hbuddy->device_type & HANGOUTS_DEVICE_TYPE_TABLET ? _("Tablet") :
503 hbuddy->device_type & HANGOUTS_DEVICE_TYPE_MOBILE ? _("Mobile") :
504 _("Unknown"));
505 }
506 }
507 }
508
509 GList *
hangouts_status_types(PurpleAccount * account)510 hangouts_status_types(PurpleAccount *account)
511 {
512 GList *types = NULL;
513 PurpleStatusType *status;
514
515 status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
516 types = g_list_append(types, status);
517
518 status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, NULL, NULL, FALSE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
519 types = g_list_append(types, status);
520
521 status = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, NULL, NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
522 types = g_list_append(types, status);
523
524 status = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, NULL, _("Do Not Disturb"), FALSE, TRUE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
525 types = g_list_append(types, status);
526
527 status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, "mobile", _("Phone"), FALSE, FALSE, FALSE);
528 types = g_list_append(types, status);
529
530 status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
531 types = g_list_append(types, status);
532
533 status = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, "gone", NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
534 types = g_list_append(types, status);
535
536 status = purple_status_type_new_with_attrs(PURPLE_STATUS_INVISIBLE, NULL, NULL, FALSE, FALSE, FALSE, "message", _("Mood"), purple_value_new(PURPLE_TYPE_STRING), NULL);
537 types = g_list_append(types, status);
538
539 return types;
540 }
541
542 static gchar *
hangouts_status_text(PurpleBuddy * buddy)543 hangouts_status_text(PurpleBuddy *buddy)
544 {
545 const gchar *message = purple_status_get_attr_string(purple_presence_get_active_status(purple_buddy_get_presence(buddy)), "message");
546
547 if (message == NULL) {
548 return NULL;
549 }
550
551 return g_markup_printf_escaped("%s", message);
552 }
553
554 static void
hangouts_buddy_free(PurpleBuddy * buddy)555 hangouts_buddy_free(PurpleBuddy *buddy)
556 {
557 HangoutsBuddy *hbuddy = purple_buddy_get_protocol_data(buddy);
558
559 g_return_if_fail(hbuddy != NULL);
560
561 g_free(hbuddy);
562 }
563
564 static gboolean
hangouts_offline_message(const PurpleBuddy * buddy)565 hangouts_offline_message(const PurpleBuddy *buddy)
566 {
567 return TRUE;
568 }
569
570
571 /*****************************************************************************/
572
573 static gboolean
plugin_load(PurplePlugin * plugin,GError ** error)574 plugin_load(PurplePlugin *plugin, GError **error)
575 {
576 purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
577 PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
578 HANGOUTS_PLUGIN_ID, hangouts_cmd_leave,
579 _("leave: Leave the group chat"), NULL);
580
581 purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
582 PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
583 HANGOUTS_PLUGIN_ID, hangouts_cmd_kick,
584 _("kick <user>: Kick a user from the room."), NULL);
585
586 return TRUE;
587 }
588
589 static gboolean
plugin_unload(PurplePlugin * plugin,GError ** error)590 plugin_unload(PurplePlugin *plugin, GError **error)
591 {
592 purple_signals_disconnect_by_handle(plugin);
593
594 return TRUE;
595 }
596
597 #if PURPLE_VERSION_CHECK(3, 0, 0)
598
599 G_MODULE_EXPORT GType hangouts_protocol_get_type(void);
600 #define HANGOUTS_TYPE_PROTOCOL (hangouts_protocol_get_type())
601 #define HANGOUTS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocol))
602 #define HANGOUTS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocolClass))
603 #define HANGOUTS_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), HANGOUTS_TYPE_PROTOCOL))
604 #define HANGOUTS_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), HANGOUTS_TYPE_PROTOCOL))
605 #define HANGOUTS_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), HANGOUTS_TYPE_PROTOCOL, HangoutsProtocolClass))
606
607 typedef struct _HangoutsProtocol
608 {
609 PurpleProtocol parent;
610 } HangoutsProtocol;
611
612 typedef struct _HangoutsProtocolClass
613 {
614 PurpleProtocolClass parent_class;
615 } HangoutsProtocolClass;
616
617 static void
hangouts_protocol_init(PurpleProtocol * prpl_info)618 hangouts_protocol_init(PurpleProtocol *prpl_info)
619 {
620 PurpleProtocol *plugin = prpl_info, *info = prpl_info;
621
622 info->id = HANGOUTS_PLUGIN_ID;
623 info->name = "Hangouts";
624
625 prpl_info->options = OPT_PROTO_NO_PASSWORD | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_MAIL_CHECK;
626 prpl_info->account_options = hangouts_add_account_options(prpl_info->account_options);
627
628 purple_signal_register(plugin, "hangouts-received-stateupdate",
629 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
630 PURPLE_TYPE_CONNECTION,
631 G_TYPE_OBJECT);
632
633 purple_signal_register(plugin, "hangouts-gmail-notification",
634 purple_marshal_VOID__POINTER_POINTER_POINTER, G_TYPE_NONE, 3,
635 PURPLE_TYPE_CONNECTION,
636 G_TYPE_STRING,
637 G_TYPE_OBJECT);
638
639 hangouts_register_events(plugin);
640 }
641
642 static void
hangouts_protocol_class_init(PurpleProtocolClass * prpl_info)643 hangouts_protocol_class_init(PurpleProtocolClass *prpl_info)
644 {
645 prpl_info->login = hangouts_login;
646 prpl_info->close = hangouts_close;
647 prpl_info->status_types = hangouts_status_types;
648 prpl_info->list_icon = hangouts_list_icon;
649 }
650
651 static void
hangouts_protocol_client_iface_init(PurpleProtocolClientIface * prpl_info)652 hangouts_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
653 {
654 prpl_info->get_actions = hangouts_actions;
655 prpl_info->blist_node_menu = hangouts_node_menu;
656 prpl_info->status_text = hangouts_status_text;
657 prpl_info->tooltip_text = hangouts_tooltip_text;
658 prpl_info->buddy_free = hangouts_buddy_free;
659 prpl_info->offline_message = hangouts_offline_message;
660 }
661
662 static void
hangouts_protocol_server_iface_init(PurpleProtocolServerIface * prpl_info)663 hangouts_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
664 {
665 prpl_info->get_info = hangouts_get_info;
666 prpl_info->set_status = hangouts_set_status;
667 prpl_info->set_idle = hangouts_set_idle;
668 }
669
670 static void
hangouts_protocol_privacy_iface_init(PurpleProtocolPrivacyIface * prpl_info)671 hangouts_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info)
672 {
673 prpl_info->add_deny = hangouts_block_user;
674 prpl_info->rem_deny = hangouts_unblock_user;
675 }
676
677 static void
hangouts_protocol_im_iface_init(PurpleProtocolIMIface * prpl_info)678 hangouts_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
679 {
680 prpl_info->send = hangouts_send_im;
681 prpl_info->send_typing = hangouts_send_typing;
682 }
683
684 static void
hangouts_protocol_chat_iface_init(PurpleProtocolChatIface * prpl_info)685 hangouts_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
686 {
687 prpl_info->send = hangouts_chat_send;
688 prpl_info->info = hangouts_chat_info;
689 prpl_info->info_defaults = hangouts_chat_info_defaults;
690 prpl_info->join = hangouts_join_chat;
691 prpl_info->get_name = hangouts_get_chat_name;
692 prpl_info->invite = hangouts_chat_invite;
693 prpl_info->set_topic = hangouts_chat_set_topic;
694 }
695
696 static void
hangouts_protocol_media_iface_init(PurpleProtocolMediaIface * prpl_info)697 hangouts_protocol_media_iface_init(PurpleProtocolMediaIface *prpl_info)
698 {
699 prpl_info->get_caps = hangouts_get_media_caps;
700 prpl_info->initiate_session = hangouts_initiate_media;
701 }
702
703 static void
hangouts_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface * prpl_info)704 hangouts_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
705 {
706 prpl_info->get_list = hangouts_roomlist_get_list;
707 }
708
709 static PurpleProtocol *hangouts_protocol;
710
711 PURPLE_DEFINE_TYPE_EXTENDED(
712 HangoutsProtocol, hangouts_protocol, PURPLE_TYPE_PROTOCOL, 0,
713
714 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
715 hangouts_protocol_im_iface_init)
716
717 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
718 hangouts_protocol_chat_iface_init)
719
720 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
721 hangouts_protocol_client_iface_init)
722
723 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
724 hangouts_protocol_server_iface_init)
725
726 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
727 hangouts_protocol_privacy_iface_init)
728
729 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_MEDIA_IFACE,
730 hangouts_protocol_media_iface_init)
731
732 PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
733 hangouts_protocol_roomlist_iface_init)
734 );
735
736 static gboolean
libpurple3_plugin_load(PurplePlugin * plugin,GError ** error)737 libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
738 {
739 hangouts_protocol_register_type(plugin);
740 hangouts_protocol = purple_protocols_add(HANGOUTS_TYPE_PROTOCOL, error);
741 if (!hangouts_protocol)
742 return FALSE;
743
744 return plugin_load(plugin, error);
745 }
746
747 static gboolean
libpurple3_plugin_unload(PurplePlugin * plugin,GError ** error)748 libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
749 {
750 if (!plugin_unload(plugin, error))
751 return FALSE;
752
753 if (!purple_protocols_remove(hangouts_protocol, error))
754 return FALSE;
755
756 return TRUE;
757 }
758
759 static PurplePluginInfo *
plugin_query(GError ** error)760 plugin_query(GError **error)
761 {
762 return purple_plugin_info_new(
763 "id", HANGOUTS_PLUGIN_ID,
764 "name", "Hangouts",
765 "version", HANGOUTS_PLUGIN_VERSION,
766 "category", N_("Protocol"),
767 "summary", N_("Hangouts Protocol Plugins."),
768 "description", N_("Adds Hangouts protocol support to libpurple."),
769 "website", "https://bitbucket.org/EionRobb/purple-hangouts/",
770 "abi-version", PURPLE_ABI_VERSION,
771 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
772 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
773 NULL
774 );
775 }
776
777 PURPLE_PLUGIN_INIT(hangouts, plugin_query,
778 libpurple3_plugin_load, libpurple3_plugin_unload);
779
780 #else
781
782 // Normally set in core.c in purple3
783 void _purple_socket_init(void);
784 void _purple_socket_uninit(void);
785
786
787 static gboolean
libpurple2_plugin_load(PurplePlugin * plugin)788 libpurple2_plugin_load(PurplePlugin *plugin)
789 {
790 _purple_socket_init();
791 purple_http_init();
792
793 return plugin_load(plugin, NULL);
794 }
795
796 static gboolean
libpurple2_plugin_unload(PurplePlugin * plugin)797 libpurple2_plugin_unload(PurplePlugin *plugin)
798 {
799 _purple_socket_uninit();
800 purple_http_uninit();
801
802 return plugin_unload(plugin, NULL);
803 }
804
805 static PurplePluginInfo info =
806 {
807 PURPLE_PLUGIN_MAGIC,
808 PURPLE_MAJOR_VERSION,
809 PURPLE_MINOR_VERSION,
810 PURPLE_PLUGIN_PROTOCOL, /**< type */
811 NULL, /**< ui_requirement */
812 0, /**< flags */
813 NULL, /**< dependencies */
814 PURPLE_PRIORITY_DEFAULT, /**< priority */
815
816 HANGOUTS_PLUGIN_ID, /**< id */
817 N_("Hangouts"), /**< name */
818 HANGOUTS_PLUGIN_VERSION, /**< version */
819
820 N_("Hangouts Protocol Plugins."), /**< summary */
821
822 N_("Adds Hangouts protocol support to libpurple."), /**< description */
823 "Eion Robb <eionrobb+hangouts@gmail.com>", /**< author */
824 "https://bitbucket.org/EionRobb/purple-hangouts/", /**< homepage */
825
826 libpurple2_plugin_load, /**< load */
827 libpurple2_plugin_unload, /**< unload */
828 NULL, /**< destroy */
829
830 NULL, /**< ui_info */
831 NULL, /**< extra_info */
832 NULL, /**< prefs_info */
833 NULL, /**< actions */
834
835 /* padding */
836 NULL,
837 NULL,
838 NULL,
839 NULL
840 };
841
842 static void
init_plugin(PurplePlugin * plugin)843 init_plugin(PurplePlugin *plugin)
844 {
845 PurplePluginInfo *info;
846 PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
847
848 info = plugin->info;
849 if (info == NULL) {
850 plugin->info = info = g_new0(PurplePluginInfo, 1);
851 }
852
853 prpl_info->options = OPT_PROTO_NO_PASSWORD | OPT_PROTO_IM_IMAGE | OPT_PROTO_CHAT_TOPIC | OPT_PROTO_MAIL_CHECK;
854 prpl_info->protocol_options = hangouts_add_account_options(prpl_info->protocol_options);
855
856 purple_signal_register(plugin, "hangouts-received-stateupdate",
857 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
858 PURPLE_TYPE_CONNECTION,
859 purple_value_new(PURPLE_TYPE_OBJECT));
860
861 purple_signal_register(plugin, "hangouts-gmail-notification",
862 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
863 PURPLE_TYPE_CONNECTION,
864 purple_value_new(PURPLE_TYPE_STRING),
865 purple_value_new(PURPLE_TYPE_OBJECT));
866
867 hangouts_register_events(plugin);
868
869 prpl_info->login = hangouts_login;
870 prpl_info->close = hangouts_close;
871 prpl_info->status_types = hangouts_status_types;
872 prpl_info->list_icon = hangouts_list_icon;
873 prpl_info->status_text = hangouts_status_text;
874 prpl_info->tooltip_text = hangouts_tooltip_text;
875 prpl_info->buddy_free = hangouts_buddy_free;
876 prpl_info->offline_message = hangouts_offline_message;
877
878 prpl_info->get_info = hangouts_get_info;
879 prpl_info->set_status = hangouts_set_status;
880 prpl_info->set_idle = hangouts_set_idle;
881
882 prpl_info->blist_node_menu = hangouts_node_menu;
883
884 prpl_info->send_im = hangouts_send_im;
885 prpl_info->send_typing = hangouts_send_typing;
886 prpl_info->chat_send = hangouts_chat_send;
887 prpl_info->chat_info = hangouts_chat_info;
888 prpl_info->chat_info_defaults = hangouts_chat_info_defaults;
889 prpl_info->join_chat = hangouts_join_chat;
890 prpl_info->get_chat_name = hangouts_get_chat_name;
891 prpl_info->chat_invite = hangouts_chat_invite;
892 prpl_info->set_chat_topic = hangouts_chat_set_topic;
893
894 prpl_info->get_media_caps = hangouts_get_media_caps;
895 prpl_info->initiate_media = hangouts_initiate_media;
896
897 prpl_info->add_deny = hangouts_block_user;
898 prpl_info->rem_deny = hangouts_unblock_user;
899
900 prpl_info->roomlist_get_list = hangouts_roomlist_get_list;
901
902 info->extra_info = prpl_info;
903 #if PURPLE_MINOR_VERSION >= 5
904 prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
905 #endif
906 #if PURPLE_MINOR_VERSION >= 8
907 //prpl_info->add_buddy_with_invite = skypeweb_add_buddy_with_invite;
908 #endif
909
910 info->actions = hangouts_actions;
911 }
912
913 PURPLE_INIT_PLUGIN(hangouts, init_plugin, info);
914
915 #endif
916