pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
2020-02-03, Gary Kramlich
2f836435c33c
closing merged branch
/**
* @file gg.c Gadu-Gadu protocol plugin
*
* purple
*
* Copyright (C) 2005 Bartosz Oler <bartosz@bzimage.us>
*
* Some parts of the code are adapted or taken from the previous implementation
* of this plugin written by Arkadiusz Miskiewicz <misiek@pld.org.pl>
* Some parts Copyright (C) 2009 Krzysztof Klinikowski <grommasher@gmail.com>
*
* Thanks to Google's Summer of Code Program.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include <internal.h>
#include "action.h"
#include "plugins.h"
#include "version.h"
#include "notify.h"
#include "buddylist.h"
#include "purpleaccountoption.h"
#include "core.h"
#include "debug.h"
#include "util.h"
#include "request.h"
#include "xmlnode.h"
#include "gg.h"
#include "chat.h"
#include "search.h"
#include "blist.h"
#include "utils.h"
#include "resolver-purple.h"
#include "purplew.h"
#include "libgadu-events.h"
#include "multilogon.h"
#include "status.h"
#include "servconn.h"
#include "tcpsocket.h"
#include "pubdir-prpl.h"
#include "message-prpl.h"
#include "html.h"
#include "libgaduw.h"
/* ---------------------------------------------------------------------- */
static PurpleProtocol *my_protocol = NULL;
static PurpleAccountOption *ggp_server_option;
/* ---------------------------------------------------------------------- */
ggp_buddy_data * ggp_buddy_get_data(PurpleBuddy *buddy)
{
ggp_buddy_data *buddy_data = purple_buddy_get_protocol_data(buddy);
if (buddy_data)
return buddy_data;
buddy_data = g_new0(ggp_buddy_data, 1); /* TODO: leak */
purple_buddy_set_protocol_data(buddy, buddy_data);
return buddy_data;
}
static void ggp_buddy_free(PurpleBuddy *buddy)
{
ggp_buddy_data *buddy_data = purple_buddy_get_protocol_data(buddy);
if (!buddy_data)
return;
g_free(buddy_data);
purple_buddy_set_protocol_data(buddy, NULL);
}
const gchar * ggp_get_imtoken(PurpleConnection *gc)
{
GGPInfo *accdata = purple_connection_get_protocol_data(gc);
if (accdata->imtoken)
return accdata->imtoken;
if (accdata->imtoken_warned)
return NULL;
accdata->imtoken_warned = TRUE;
purple_notify_error(gc, _("Authentication failed"),
_("IMToken value has not been received."),
_("Some features will be disabled. "
"You may try again after a while."),
purple_request_cpar_from_connection(gc));
return NULL;
}
uin_t ggp_own_uin(PurpleConnection *gc)
{
return ggp_str_to_uin(purple_account_get_username(
purple_connection_get_account(gc)));
}
/* ---------------------------------------------------------------------- */
/* buddy list import/export from/to file */
static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *filename)
{
PurpleAccount *account = purple_connection_get_account(gc);
char *buddylist = ggp_buddylist_dump(account);
purple_debug_info("gg", "Saving...\n");
purple_debug_info("gg", "file = %s\n", filename);
if (buddylist == NULL) {
purple_notify_info(account, _("Save Buddylist..."), _("Your "
"buddylist is empty, nothing was written to the file."),
NULL, purple_request_cpar_from_connection(gc));
return;
}
if (purple_util_write_data_to_file_absolute(filename, buddylist, -1)) {
purple_notify_info(account, _("Save Buddylist..."),
_("Buddylist saved successfully!"), NULL,
purple_request_cpar_from_connection(gc));
} else {
gchar *primary = g_strdup_printf(
_("Couldn't write buddy list for %s to %s"),
purple_account_get_username(account), filename);
purple_notify_error(account, _("Save Buddylist..."),
primary, NULL, purple_request_cpar_from_connection(gc));
g_free(primary);
}
g_free(buddylist);
}
static void ggp_callback_buddylist_load_ok(PurpleConnection *gc, gchar *file)
{
PurpleAccount *account = purple_connection_get_account(gc);
GError *error = NULL;
char *buddylist = NULL;
gsize length;
purple_debug_info("gg", "file_name = %s\n", file);
if (!g_file_get_contents(file, &buddylist, &length, &error)) {
purple_notify_error(account, _("Couldn't load buddylist"),
_("Couldn't load buddylist"), error->message,
purple_request_cpar_from_connection(gc));
purple_debug_error("gg",
"Couldn't load buddylist. file = %s; error = %s\n",
file, error->message);
g_error_free(error);
return;
}
ggp_buddylist_load(gc, buddylist);
g_free(buddylist);
purple_notify_info(account, _("Load Buddylist..."),
_("Buddylist loaded successfully!"), NULL,
purple_request_cpar_from_connection(gc));
}
/* }}} */
/*
*/
/* static void ggp_action_buddylist_save(PurpleProtocolAction *action) {{{ */
static void ggp_action_buddylist_save(PurpleProtocolAction *action)
{
PurpleConnection *gc = action->connection;
purple_request_file(action, _("Save buddylist..."), NULL, TRUE,
G_CALLBACK(ggp_callback_buddylist_save_ok), NULL,
purple_request_cpar_from_connection(gc), gc);
}
static void ggp_action_buddylist_load(PurpleProtocolAction *action)
{
PurpleConnection *gc = action->connection;
purple_request_file(action, _("Load buddylist from file..."), NULL,
FALSE, G_CALLBACK(ggp_callback_buddylist_load_ok), NULL,
purple_request_cpar_from_connection(gc), gc);
}
/* ----- BLOCK BUDDIES -------------------------------------------------- */
static void ggp_add_deny(PurpleConnection *gc, const char *who)
{
GGPInfo *info = purple_connection_get_protocol_data(gc);
uin_t uin = ggp_str_to_uin(who);
purple_debug_info("gg", "ggp_add_deny: %u\n", uin);
gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
}
static void ggp_rem_deny(PurpleConnection *gc, const char *who)
{
GGPInfo *info = purple_connection_get_protocol_data(gc);
uin_t uin = ggp_str_to_uin(who);
purple_debug_info("gg", "ggp_rem_deny: %u\n", uin);
gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
}
/* ---------------------------------------------------------------------- */
/* ----- INTERNAL CALLBACKS --------------------------------------------- */
/* ---------------------------------------------------------------------- */
static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) {
gchar *from;
from = g_strdup_printf("%u", uin);
if (length)
purple_serv_got_typing(gc, from, 0, PURPLE_IM_TYPING);
else
purple_serv_got_typing_stopped(gc, from);
g_free(from);
}
/**
* Handling of XML events.
*
* @param gc PurpleConnection.
* @param data Raw XML contents.
*
* @see http://toxygen.net/libgadu/protocol/#ch1.13
* @todo: this may not be necessary anymore
*/
static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
{
PurpleXmlNode *xml = NULL;
PurpleXmlNode *xmlnode_next_event;
xml = purple_xmlnode_from_str(data, -1);
if (xml == NULL) {
purple_debug_error("gg", "ggp_xml_event_handler: "
"invalid xml: [%s]\n", data);
goto out;
}
xmlnode_next_event = purple_xmlnode_get_child(xml, "event");
while (xmlnode_next_event != NULL) {
PurpleXmlNode *xmlnode_current_event = xmlnode_next_event;
PurpleXmlNode *xmlnode_type;
char *event_type_raw;
int event_type = 0;
PurpleXmlNode *xmlnode_sender;
char *event_sender_raw;
uin_t event_sender = 0;
xmlnode_next_event = purple_xmlnode_get_next_twin(xmlnode_next_event);
xmlnode_type = purple_xmlnode_get_child(xmlnode_current_event, "type");
if (xmlnode_type == NULL)
continue;
event_type_raw = purple_xmlnode_get_data(xmlnode_type);
if (event_type_raw != NULL)
event_type = atoi(event_type_raw);
g_free(event_type_raw);
xmlnode_sender = purple_xmlnode_get_child(xmlnode_current_event, "sender");
if (xmlnode_sender != NULL) {
event_sender_raw = purple_xmlnode_get_data(xmlnode_sender);
if (event_sender_raw != NULL)
event_sender = ggp_str_to_uin(event_sender_raw);
g_free(event_sender_raw);
}
switch (event_type)
{
case 28: /* avatar update */
purple_debug_info("gg",
"ggp_xml_event_handler: avatar updated (uid: %u)\n",
event_sender);
break;
default:
purple_debug_error("gg",
"ggp_xml_event_handler: unsupported event type=%d from=%u\n",
event_type, event_sender);
}
}
out:
if (xml)
purple_xmlnode_free(xml);
}
static void ggp_callback_recv(gpointer _gc, gint fd, PurpleInputCondition cond)
{
PurpleConnection *gc = _gc;
GGPInfo *info = purple_connection_get_protocol_data(gc);
struct gg_event *ev;
if (!(ev = gg_watch_fd(info->session))) {
purple_debug_error("gg",
"ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n");
purple_connection_error (gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Unable to read from socket"));
return;
}
if (purple_debug_is_verbose()) {
purple_debug_misc("gg", "ggp_callback_recv: got event %s",
gg_debug_event(ev->type));
}
purple_input_remove(info->inpa);
info->inpa = purple_input_add(info->session->fd,
ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
ggp_callback_recv, gc);
switch (ev->type) {
case GG_EVENT_NONE:
/* Nothing happened. */
break;
case GG_EVENT_CONN_FAILED:
purple_connection_error (gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Server disconnected"));
break;
case GG_EVENT_MSG:
ggp_message_got(gc, &ev->event.msg);
break;
case GG_EVENT_ACK:
case GG_EVENT_ACK110:
break;
case GG_EVENT_IMAGE_REPLY:
ggp_image_recv(gc, &ev->event.image_reply);
break;
case GG_EVENT_IMAGE_REQUEST:
ggp_image_send(gc, &ev->event.image_request);
break;
case GG_EVENT_NOTIFY60:
case GG_EVENT_STATUS60:
ggp_status_got_others(gc, ev);
break;
case GG_EVENT_TYPING_NOTIFICATION:
ggp_typing_notification_handler(gc, ev->event.typing_notification.uin,
ev->event.typing_notification.length);
break;
case GG_EVENT_XML_EVENT:
purple_debug_info("gg", "GG_EVENT_XML_EVENT\n");
ggp_xml_event_handler(gc, ev->event.xml_event.data);
break;
case GG_EVENT_USER_DATA:
ggp_events_user_data(gc, &ev->event.user_data);
break;
case GG_EVENT_JSON_EVENT:
ggp_events_json(gc, &ev->event.json_event);
break;
case GG_EVENT_USERLIST100_VERSION:
ggp_roster_version(gc, &ev->event.userlist100_version);
break;
case GG_EVENT_USERLIST100_REPLY:
ggp_roster_reply(gc, &ev->event.userlist100_reply);
break;
case GG_EVENT_MULTILOGON_MSG:
ggp_message_got_multilogon(gc, &ev->event.multilogon_msg);
break;
case GG_EVENT_MULTILOGON_INFO:
ggp_multilogon_info(gc, &ev->event.multilogon_info);
break;
case GG_EVENT_IMTOKEN:
purple_debug_info("gg", "gg11: got IMTOKEN\n");
g_free(info->imtoken);
info->imtoken = g_strdup(ev->event.imtoken.imtoken);
break;
case GG_EVENT_PONG110:
purple_debug_info("gg", "gg11: got PONG110 %lu\n",
(long unsigned)ev->event.pong110.time);
break;
case GG_EVENT_CHAT_INFO:
case GG_EVENT_CHAT_INFO_GOT_ALL:
case GG_EVENT_CHAT_INFO_UPDATE:
case GG_EVENT_CHAT_CREATED:
case GG_EVENT_CHAT_INVITE_ACK:
ggp_chat_got_event(gc, ev);
break;
case GG_EVENT_DISCONNECT:
ggp_servconn_remote_disconnect(gc);
break;
default:
purple_debug_warning("gg",
"unsupported event type=%d\n", ev->type);
break;
}
gg_free_event(ev);
}
void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
{
PurpleConnection *gc = _gc;
GGPInfo *info;
struct gg_event *ev;
PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
info = purple_connection_get_protocol_data(gc);
purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
info->session->check, info->session->state);
switch (info->session->state) {
case GG_STATE_ERROR:
purple_debug_info("gg", "GG_STATE_ERROR\n");
break;
case GG_STATE_RESOLVING:
purple_debug_info("gg", "GG_STATE_RESOLVING\n");
break;
case GG_STATE_RESOLVING_GG:
purple_debug_info("gg", "GG_STATE_RESOLVING_GG\n");
break;
case GG_STATE_CONNECTING_HUB:
purple_debug_info("gg", "GG_STATE_CONNECTING_HUB\n");
break;
case GG_STATE_READING_DATA:
purple_debug_info("gg", "GG_STATE_READING_DATA\n");
break;
case GG_STATE_CONNECTING_GG:
purple_debug_info("gg", "GG_STATE_CONNECTING_GG\n");
break;
case GG_STATE_READING_KEY:
purple_debug_info("gg", "GG_STATE_READING_KEY\n");
break;
case GG_STATE_READING_REPLY:
purple_debug_info("gg", "GG_STATE_READING_REPLY\n");
break;
case GG_STATE_TLS_NEGOTIATION:
purple_debug_info("gg", "GG_STATE_TLS_NEGOTIATION\n");
break;
case GG_STATE_RESOLVING_HUB:
purple_debug_info("gg", "GG_STATE_RESOLVING_HUB\n");
break;
case GG_STATE_READING_HUB:
purple_debug_info("gg", "GG_STATE_READING_HUB\n");
break;
default:
purple_debug_error("gg", "unknown state = %d\n",
info->session->state);
break;
}
if (!(ev = gg_watch_fd(info->session))) {
purple_debug_error("gg", "login_handler: gg_watch_fd failed!\n");
purple_connection_error (gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Unable to read from socket"));
return;
}
purple_debug_info("gg", "login_handler: session->fd = %d\n", info->session->fd);
purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
info->session->check, info->session->state);
purple_input_remove(info->inpa);
info->inpa = 0;
/** XXX I think that this shouldn't be done if ev->type is GG_EVENT_CONN_FAILED or GG_EVENT_CONN_SUCCESS -datallah */
if (info->session->fd >= 0)
info->inpa = purple_input_add(info->session->fd,
(info->session->check == 1) ? PURPLE_INPUT_WRITE :
PURPLE_INPUT_READ,
ggp_async_login_handler, gc);
switch (ev->type) {
case GG_EVENT_NONE:
/* Nothing happened. */
purple_debug_info("gg", "GG_EVENT_NONE\n");
break;
case GG_EVENT_CONN_SUCCESS:
{
purple_debug_info("gg", "GG_EVENT_CONN_SUCCESS:"
" successfully connected to %s\n",
info->session->connect_host);
ggp_servconn_add_server(info->session->
connect_host);
purple_input_remove(info->inpa);
info->inpa = purple_input_add(info->session->fd,
PURPLE_INPUT_READ,
ggp_callback_recv, gc);
purple_connection_update_progress(gc, _("Connected"), 1, 2);
purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED);
ggp_buddylist_send(gc);
ggp_roster_request_update(gc);
}
break;
case GG_EVENT_CONN_FAILED:
if (info->inpa > 0) {
purple_input_remove(info->inpa);
info->inpa = 0;
}
purple_debug_info("gg", "Connection failure: %d\n",
ev->event.failure);
switch (ev->event.failure) {
case GG_FAILURE_RESOLVING:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Unable to resolve "
"hostname"));
break;
case GG_FAILURE_PASSWORD:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
_("Incorrect password"));
break;
case GG_FAILURE_TLS:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
_("SSL Connection Failed"));
break;
case GG_FAILURE_INTRUDER:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
_("Your account has been "
"disabled because too many "
"incorrect passwords were "
"entered"));
break;
case GG_FAILURE_UNAVAILABLE:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Service temporarily "
"unavailable"));
break;
case GG_FAILURE_PROXY:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Error connecting to proxy "
"server"));
break;
case GG_FAILURE_HUB:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Error connecting to master "
"server"));
break;
case GG_FAILURE_INTERNAL:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_OTHER_ERROR,
_("Internal error"));
break;
default:
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Connection failed"));
}
break;
case GG_EVENT_MSG:
if (ev->event.msg.sender == 0) {
if (ev->event.msg.message == NULL)
break;
/* system messages are mostly ads */
purple_debug_info("gg", "System message:\n%s\n",
ev->event.msg.message);
} else {
purple_debug_warning("gg", "GG_EVENT_MSG: message from user %u "
"unexpected while connecting:\n%s\n",
ev->event.msg.sender,
ev->event.msg.message);
}
break;
default:
purple_debug_error("gg", "strange event: %d\n", ev->type);
break;
}
gg_free_event(ev);
}
static gint
gg_uri_handler_find_account(PurpleAccount *account,
G_GNUC_UNUSED gconstpointer data)
{
const gchar *protocol_id;
protocol_id = purple_account_get_protocol_id(account);
if (purple_strequal(protocol_id, "prpl-gg") &&
purple_account_is_connected(account)) {
return 0;
} else {
return -1;
}
}
static gboolean
gg_uri_handler(const gchar *scheme, const gchar *screenname,
GHashTable *params)
{
GList *accounts;
GList *account_node;
PurpleIMConversation *im;
g_return_val_if_fail(screenname != NULL, FALSE);
if (!purple_strequal(scheme, "gg")) {
return FALSE;
}
if (screenname[0] == '\0') {
purple_debug_warning("gg", "Invalid empty screenname in URI");
return FALSE;
}
/* Find online Gadu-Gadu account */
accounts = purple_accounts_get_all();
account_node = g_list_find_custom(
accounts, NULL, (GCompareFunc)gg_uri_handler_find_account);
if (account_node == NULL) {
return FALSE;
}
im = purple_im_conversation_new(account_node->data, screenname);
purple_conversation_present(PURPLE_CONVERSATION(im));
return TRUE;
}
/* ---------------------------------------------------------------------- */
/* ----- PurpleProtocol ----------------------------------------- */
/* ---------------------------------------------------------------------- */
static const char *ggp_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
{
return "gadu-gadu";
}
static const char *ggp_normalize(const PurpleAccount *account, const char *who)
{
static char normalized[21]; /* maximum unsigned long long int size */
uin_t uin = ggp_str_to_uin(who);
if (uin <= 0)
return NULL;
g_snprintf(normalized, sizeof(normalized), "%u", uin);
return normalized;
}
/* TODO:
* - move to status.c ?
* - add information about not adding to his buddy list (not_a_friend)
*/
static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
{
PurpleStatus *status;
char *tmp;
const char *name, *alias;
gchar *msg;
g_return_if_fail(b != NULL);
status = purple_presence_get_active_status(purple_buddy_get_presence(b));
name = purple_status_get_name(status);
alias = purple_buddy_get_alias(b);
ggp_status_from_purplestatus(status, &msg);
purple_notify_user_info_add_pair_plaintext(user_info, _("Alias"), alias);
if (msg != NULL) {
if (PURPLE_BUDDY_IS_ONLINE(b)) {
tmp = g_strdup_printf("%s: %s", name, msg);
purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), tmp);
g_free(tmp);
} else {
purple_notify_user_info_add_pair_plaintext(user_info, _("Message"), msg);
}
g_free(msg);
/* We don't want to duplicate 'Status: Offline'. */
} else if (PURPLE_BUDDY_IS_ONLINE(b)) {
purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), name);
}
}
static void ggp_login(PurpleAccount *account)
{
PurpleConnection *gc = purple_account_get_connection(account);
struct gg_login_params *glp;
GGPInfo *info;
const char *address;
const gchar *encryption_type, *protocol_version;
GProxyResolver *resolver;
GError *error;
purple_connection_set_flags(gc,
PURPLE_CONNECTION_FLAG_HTML |
PURPLE_CONNECTION_FLAG_NO_URLDESC);
resolver = purple_proxy_get_proxy_resolver(account, &error);
if (resolver == NULL) {
purple_debug_error("gg", "Unable to get account proxy resolver: %s",
error->message);
purple_connection_g_error(gc, error);
return;
}
glp = g_new0(struct gg_login_params, 1);
glp->struct_size = sizeof(struct gg_login_params);
info = g_new0(GGPInfo, 1);
purple_connection_set_protocol_data(gc, info);
info->http = soup_session_new_with_options(SOUP_SESSION_PROXY_RESOLVER,
resolver, NULL);
ggp_tcpsocket_setup(gc, glp);
ggp_image_setup(gc);
ggp_avatar_setup(gc);
ggp_roster_setup(gc);
ggp_multilogon_setup(gc);
ggp_status_setup(gc);
ggp_chat_setup(gc);
ggp_message_setup(gc);
ggp_edisc_setup(gc, resolver);
g_object_unref(resolver);
glp->uin = ggp_str_to_uin(purple_account_get_username(account));
glp->password =
ggp_convert_to_cp1250(purple_connection_get_password(gc));
if (glp->uin == 0) {
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_INVALID_USERNAME,
_("The username specified is invalid."));
purple_str_wipe(glp->password);
g_free(glp);
return;
}
glp->image_size = 255;
glp->status_flags = GG_STATUS_FLAG_UNKNOWN;
if (purple_account_get_bool(account, "show_links_from_strangers", 1))
glp->status_flags |= GG_STATUS_FLAG_SPAM;
glp->encoding = GG_ENCODING_UTF8;
glp->protocol_features = (GG_FEATURE_DND_FFC |
GG_FEATURE_TYPING_NOTIFICATION | GG_FEATURE_MULTILOGON |
GG_FEATURE_USER_DATA);
glp->async = 1;
encryption_type = purple_account_get_string(account, "encryption",
"opportunistic_tls");
purple_debug_info("gg", "Requested encryption type: %s\n",
encryption_type);
if (purple_strequal(encryption_type, "opportunistic_tls"))
glp->tls = GG_SSL_ENABLED;
else if (purple_strequal(encryption_type, "require_tls")) {
if (gg_libgadu_check_feature(GG_LIBGADU_FEATURE_SSL))
glp->tls = GG_SSL_REQUIRED;
else {
purple_connection_error(gc,
PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
_("SSL support unavailable"));
purple_str_wipe(glp->password);
g_free(glp);
return;
}
}
else /* encryption_type == "none" */
glp->tls = GG_SSL_DISABLED;
purple_debug_misc("gg", "TLS mode: %d\n", glp->tls);
protocol_version = purple_account_get_string(account,
"protocol_version", "default");
purple_debug_info("gg", "Requested protocol version: %s\n",
protocol_version);
if (purple_strequal(protocol_version, "gg10"))
glp->protocol_version = GG_PROTOCOL_VERSION_100;
else if (purple_strequal(protocol_version, "gg11"))
glp->protocol_version = GG_PROTOCOL_VERSION_110;
glp->compatibility = GG_COMPAT_1_12_0;
ggp_status_set_initial(gc, glp);
address = purple_account_get_string(account, "gg_server", "");
if (address && *address)
glp->connect_host = g_strdup(address);
info->session = gg_login(glp);
g_free(glp->connect_host);
purple_str_wipe(glp->password);
g_free(glp);
purple_connection_update_progress(gc, _("Connecting"), 0, 2);
if (info->session == NULL) {
purple_connection_error (gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Connection failed"));
return;
}
if (info->session->fd > 0) {
info->inpa = purple_input_add(info->session->fd,
PURPLE_INPUT_READ, ggp_async_login_handler, gc);
}
}
static void ggp_close(PurpleConnection *gc)
{
PurpleAccount *account;
GGPInfo *info;;
g_return_if_fail(gc != NULL);
account = purple_connection_get_account(gc);
info = purple_connection_get_protocol_data(gc);
purple_notify_close_with_handle(gc);
if (info) {
if (info->session != NULL) {
ggp_status_set_disconnected(account);
gg_logoff(info->session);
gg_free_session(info->session);
}
ggp_image_cleanup(gc);
ggp_avatar_cleanup(gc);
ggp_roster_cleanup(gc);
ggp_multilogon_cleanup(gc);
ggp_status_cleanup(gc);
ggp_chat_cleanup(gc);
ggp_message_cleanup(gc);
ggp_edisc_cleanup(gc);
if (info->inpa > 0)
purple_input_remove(info->inpa);
g_free(info->imtoken);
if (info->http) {
soup_session_abort(info->http);
g_object_unref(info->http);
}
purple_connection_set_protocol_data(gc, NULL);
g_free(info);
}
purple_debug_info("gg", "Connection closed.\n");
}
static unsigned int ggp_send_typing(PurpleConnection *gc, const char *name, PurpleIMTypingState state)
{
GGPInfo *info = purple_connection_get_protocol_data(gc);
int dummy_length; /* we don't send real length of typed message */
if (state == PURPLE_IM_TYPED) /* not supported */
return 1;
if (state == PURPLE_IM_TYPING)
dummy_length = (int)g_random_int();
else /* PURPLE_IM_NOT_TYPING */
dummy_length = 0;
gg_typing_notification(
info->session,
ggp_str_to_uin(name),
dummy_length);
return 1; /* wait 1 second before another notification */
}
static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
{
PurpleAccount *account = purple_connection_get_account(gc);
GGPInfo *info = purple_connection_get_protocol_data(gc);
const gchar *name = purple_buddy_get_name(buddy);
gg_add_notify(info->session, ggp_str_to_uin(name));
/* gg server won't tell us our status here */
if (purple_strequal(purple_account_get_username(account), name))
ggp_status_fake_to_self(gc);
ggp_roster_add_buddy(gc, buddy, group, message);
ggp_pubdir_request_buddy_alias(gc, buddy);
}
static void ggp_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
PurpleGroup *group)
{
GGPInfo *info = purple_connection_get_protocol_data(gc);
gg_remove_notify(info->session, ggp_str_to_uin(purple_buddy_get_name(buddy)));
ggp_roster_remove_buddy(gc, buddy, group);
}
static void ggp_keepalive(PurpleConnection *gc)
{
GGPInfo *info = purple_connection_get_protocol_data(gc);
/* purple_debug_info("gg", "Keeping connection alive....\n"); */
if (gg_ping(info->session) < 0) {
purple_debug_info("gg", "Not connected to the server "
"or gg_session is not correct\n");
purple_connection_error (gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Not connected to the server"));
}
}
static void ggp_action_multilogon(PurpleProtocolAction *action)
{
ggp_multilogon_dialog(action->connection);
}
static void ggp_action_status_broadcasting(PurpleProtocolAction *action)
{
ggp_status_broadcasting_dialog(action->connection);
}
static void ggp_action_search(PurpleProtocolAction *action)
{
ggp_pubdir_search(action->connection, NULL);
}
static void ggp_action_set_info(PurpleProtocolAction *action)
{
ggp_pubdir_set_info(action->connection);
}
static GList *ggp_get_actions(PurpleConnection *gc)
{
GList *m = NULL;
PurpleProtocolAction *act;
act = purple_protocol_action_new(_("Show other sessions"),
ggp_action_multilogon);
m = g_list_append(m, act);
act = purple_protocol_action_new(_("Show status only for buddies"),
ggp_action_status_broadcasting);
m = g_list_append(m, act);
m = g_list_append(m, NULL);
act = purple_protocol_action_new(_("Find buddies..."),
ggp_action_search);
m = g_list_append(m, act);
act = purple_protocol_action_new(_("Set User Info"),
ggp_action_set_info);
m = g_list_append(m, act);
m = g_list_append(m, NULL);
act = purple_protocol_action_new(_("Save buddylist to file..."),
ggp_action_buddylist_save);
m = g_list_append(m, act);
act = purple_protocol_action_new(_("Load buddylist from file..."),
ggp_action_buddylist_load);
m = g_list_append(m, act);
return m;
}
static const char* ggp_list_emblem(PurpleBuddy *buddy)
{
ggp_buddy_data *buddy_data = ggp_buddy_get_data(buddy);
if (buddy_data->blocked)
return "not-authorized";
if (buddy_data->not_a_friend)
return "unavailable";
return NULL;
}
static gboolean ggp_offline_message(const PurpleBuddy *buddy)
{
return TRUE;
}
static GHashTable * ggp_get_account_text_table(PurpleAccount *account)
{
GHashTable *table;
table = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(table, "login_label", (gpointer)_("GG number..."));
return table;
}
static gssize
ggp_get_max_message_size(PurpleConversation *conv)
{
/* TODO: it may depend on protocol version or other factors */
return 1200; /* no more than 1232 */
}
static void
ggp_protocol_init(GGPProtocol *self)
{
PurpleProtocol *protocol = PURPLE_PROTOCOL(self);
PurpleAccountOption *option;
GList *encryption_options = NULL;
GList *protocol_version = NULL;
protocol->id = "prpl-gg";
protocol->name = "Gadu-Gadu";
protocol->icon_spec = purple_buddy_icon_spec_new("png",
1, 1, 200, 200, 0,
PURPLE_ICON_SCALE_DISPLAY |
PURPLE_ICON_SCALE_SEND);
option = purple_account_option_string_new(_("GG server"),
"gg_server", "");
protocol->account_options = g_list_append(protocol->account_options,
option);
ggp_server_option = option;
#define ADD_VALUE(list, desc, v) { \
PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \
kvp->key = g_strdup((desc)); \
kvp->value = g_strdup((v)); \
list = g_list_append(list, kvp); \
}
ADD_VALUE(encryption_options, _("Use encryption if available"),
"opportunistic_tls");
ADD_VALUE(encryption_options, _("Require encryption"), "require_tls");
ADD_VALUE(encryption_options, _("Don't use encryption"), "none");
option = purple_account_option_list_new(_("Connection security"),
"encryption", encryption_options);
protocol->account_options = g_list_append(protocol->account_options,
option);
ADD_VALUE(protocol_version, _("Default"), "default");
ADD_VALUE(protocol_version, "GG 10", "gg10");
ADD_VALUE(protocol_version, "GG 11", "gg11");
option = purple_account_option_list_new(_("Protocol version"),
"protocol_version", protocol_version);
protocol->account_options = g_list_append(protocol->account_options,
option);
option = purple_account_option_bool_new(_("Show links from strangers"),
"show_links_from_strangers", 1);
protocol->account_options = g_list_append(protocol->account_options,
option);
}
static void
ggp_protocol_class_init(GGPProtocolClass *klass)
{
PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
protocol_class->login = ggp_login;
protocol_class->close = ggp_close;
protocol_class->status_types = ggp_status_types;
protocol_class->list_icon = ggp_list_icon;
}
static void
ggp_protocol_class_finalize(G_GNUC_UNUSED GGPProtocolClass *klass)
{
}
static void
ggp_protocol_client_iface_init(PurpleProtocolClientInterface *client_iface)
{
client_iface->get_actions = ggp_get_actions;
client_iface->list_emblem = ggp_list_emblem;
client_iface->status_text = ggp_status_buddy_text;
client_iface->tooltip_text = ggp_tooltip_text;
client_iface->buddy_free = ggp_buddy_free;
client_iface->normalize = ggp_normalize;
client_iface->offline_message = ggp_offline_message;
client_iface->get_account_text_table = ggp_get_account_text_table;
client_iface->get_max_message_size = ggp_get_max_message_size;
}
static void
ggp_protocol_server_iface_init(PurpleProtocolServerInterface *server_iface)
{
server_iface->get_info = ggp_pubdir_get_info_protocol;
server_iface->set_status = ggp_status_set_purplestatus;
server_iface->add_buddy = ggp_add_buddy;
server_iface->remove_buddy = ggp_remove_buddy;
server_iface->keepalive = ggp_keepalive;
server_iface->alias_buddy = ggp_roster_alias_buddy;
server_iface->group_buddy = ggp_roster_group_buddy;
server_iface->rename_group = ggp_roster_rename_group;
server_iface->set_buddy_icon = ggp_avatar_own_set;
}
static void
ggp_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface)
{
im_iface->send = ggp_message_send_im;
im_iface->send_typing = ggp_send_typing;
}
static void
ggp_protocol_chat_iface_init(PurpleProtocolChatInterface *chat_iface)
{
chat_iface->info = ggp_chat_info;
chat_iface->info_defaults = ggp_chat_info_defaults;
chat_iface->join = ggp_chat_join;
chat_iface->get_name = ggp_chat_get_name;
chat_iface->invite = ggp_chat_invite;
chat_iface->leave = ggp_chat_leave;
chat_iface->send = ggp_chat_send;
chat_iface->reject = NULL; /* TODO */
}
static void
ggp_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface *roomlist_iface)
{
roomlist_iface->get_list = ggp_chat_roomlist_get_list;
}
static void
ggp_protocol_privacy_iface_init(PurpleProtocolPrivacyInterface *privacy_iface)
{
privacy_iface->add_deny = ggp_add_deny;
privacy_iface->rem_deny = ggp_rem_deny;
}
static void
ggp_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
{
xfer_iface->can_receive = ggp_edisc_xfer_can_receive_file;
xfer_iface->send_file = ggp_edisc_xfer_send_file;
xfer_iface->new_xfer = ggp_edisc_xfer_send_new;
}
G_DEFINE_DYNAMIC_TYPE_EXTENDED(
GGPProtocol, ggp_protocol, PURPLE_TYPE_PROTOCOL, 0,
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT,
ggp_protocol_client_iface_init)
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_SERVER,
ggp_protocol_server_iface_init)
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_IM,
ggp_protocol_im_iface_init)
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CHAT,
ggp_protocol_chat_iface_init)
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ROOMLIST,
ggp_protocol_roomlist_iface_init)
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_PRIVACY,
ggp_protocol_privacy_iface_init)
G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_XFER,
ggp_protocol_xfer_iface_init));
static gchar *
plugin_extra(PurplePlugin *plugin)
{
return g_strdup_printf("Using libgadu version %s", gg_libgadu_version());
}
static PurplePluginInfo *
plugin_query(GError **error)
{
const gchar * const authors[] = {
"boler@sourceforge.net",
NULL
};
return purple_plugin_info_new(
"id", "prpl-gg",
"name", "Gadu-Gadu Protocol",
"version", DISPLAY_VERSION,
"category", N_("Protocol"),
"summary", N_("Gadu-Gadu Protocol Plugin"),
"description", N_("Polish popular IM"),
"authors", authors,
"website", PURPLE_WEBSITE,
"abi-version", PURPLE_ABI_VERSION,
"extra-cb", plugin_extra,
"flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
NULL
);
}
static gboolean
plugin_load(PurplePlugin *plugin, GError **error)
{
ggp_protocol_register_type(G_TYPE_MODULE(plugin));
ggp_xfer_register(G_TYPE_MODULE(plugin));
my_protocol = purple_protocols_add(GGP_TYPE_PROTOCOL, error);
if (!my_protocol)
return FALSE;
purple_prefs_add_none("/plugins/prpl/gg");
purple_debug_info("gg", "Loading Gadu-Gadu protocol plugin with "
"libgadu %s...\n", gg_libgadu_version());
ggp_libgaduw_setup();
ggp_resolver_purple_setup();
ggp_servconn_setup(ggp_server_option);
ggp_html_setup();
ggp_message_setup_global();
purple_signal_connect(purple_get_core(), "uri-handler", plugin,
PURPLE_CALLBACK(gg_uri_handler), NULL);
return TRUE;
}
static gboolean
plugin_unload(PurplePlugin *plugin, GError **error)
{
purple_signal_disconnect(purple_get_core(), "uri-handler", plugin,
PURPLE_CALLBACK(gg_uri_handler));
ggp_servconn_cleanup();
ggp_html_cleanup();
ggp_message_cleanup_global();
ggp_libgaduw_cleanup();
if (!purple_protocols_remove(my_protocol, error))
return FALSE;
return TRUE;
}
PURPLE_PLUGIN_INIT(gg, plugin_query, plugin_load, plugin_unload);
/* vim: set ts=8 sts=0 sw=8 noet: */