pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/**
* @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 <glib/gi18n-lib.h>
#include <gplugin.h>
#include <gplugin-native.h>
#include <purple.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"
struct _GGPProtocol {
PurpleProtocol parent;
};
/* ---------------------------------------------------------------------- */
static PurpleProtocol *my_protocol = NULL;
/* ---------------------------------------------------------------------- */
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(PurpleProtocolClient *client, 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 (g_file_set_contents(filename, buddylist, -1, NULL)) {
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(PurpleProtocolPrivacy *privacy, 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_remove_deny(PurpleProtocolPrivacy *privacy,
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;
PurpleConversation *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(im);
return TRUE;
}
/* ---------------------------------------------------------------------- */
/* ----- PurpleProtocol ----------------------------------------- */
/* ---------------------------------------------------------------------- */
static const char *ggp_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
{
return "gadu-gadu";
}
static PurpleBuddyIconSpec *
ggp_protocol_get_buddy_icon_spec(PurpleProtocol *protocol) {
return purple_buddy_icon_spec_new("png",
1, 1, 200, 200, 0,
PURPLE_ICON_SCALE_DISPLAY |
PURPLE_ICON_SCALE_SEND);
}
static GList *
ggp_protocol_get_account_options(PurpleProtocol *protocol) {
PurpleAccountOption *option = NULL;
PurpleKeyValuePair *kvp = NULL;
GList *encryption_options = NULL;
GList *protocol_version = NULL;
GList *opts = NULL;
option = purple_account_option_string_new(_("GG server"), "gg_server", "");
opts = g_list_append(opts, option);
/* setup encryption options */
kvp = purple_key_value_pair_new(_("Use encryption if available"),
"opportunistic_tls");
encryption_options = g_list_append(encryption_options, kvp);
kvp = purple_key_value_pair_new(_("Require encryption"), "require_tls");
encryption_options = g_list_append(encryption_options, kvp);
kvp = purple_key_value_pair_new(_("Don't use encryption"), "none");
encryption_options = g_list_append(encryption_options, kvp);
option = purple_account_option_list_new(_("Connection security"),
"encryption", encryption_options);
opts = g_list_append(opts, option);
/* setup the protocol version */
kvp = purple_key_value_pair_new(_("Default"), "default");
protocol_version = g_list_append(protocol_version, kvp);
kvp = purple_key_value_pair_new("GG 10", "gg10");
protocol_version = g_list_append(protocol_version, kvp);
kvp = purple_key_value_pair_new("GG 11", "gg11");
protocol_version = g_list_append(protocol_version, kvp);
option = purple_account_option_list_new(_("Protocol version"),
"protocol_version",
protocol_version);
opts = g_list_append(opts, option);
option = purple_account_option_bool_new(_("Show links from strangers"),
"show_links_from_strangers", 1);
opts = g_list_append(opts, option);
return opts;
}
static const char *
ggp_normalize(PurpleProtocolClient *client, 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(PurpleProtocolClient *client, 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_take_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("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(PurpleProtocolIM *im, 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(PurpleProtocolServer *protocol_server, PurpleConnection *gc,
PurpleBuddy *buddy, PurpleGroup *group, const gchar *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(protocol_server, gc, buddy, group, message);
ggp_pubdir_request_buddy_alias(gc, buddy);
}
static void
ggp_remove_buddy(PurpleProtocolServer *protocol_server, 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(protocol_server, gc, buddy, group);
}
static void
ggp_keepalive(PurpleProtocolServer *protocol_server, 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(PurpleProtocolClient *client, 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(PurpleProtocolClient *client, 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(PurpleProtocolClient *client, PurpleBuddy *buddy) {
return TRUE;
}
static GHashTable *
ggp_get_account_text_table(PurpleProtocolClient *client,
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(PurpleProtocolClient *client,
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)
{
}
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;
protocol_class->get_account_options = ggp_protocol_get_account_options;
protocol_class->get_buddy_icon_spec = ggp_protocol_get_buddy_icon_spec;
}
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->remove_deny = ggp_remove_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 PurpleProtocol *
ggp_protocol_new(void) {
return PURPLE_PROTOCOL(g_object_new(
GGP_TYPE_PROTOCOL,
"id", "prpl-gg",
"name", "Gadu-Gadu",
"description", "Gadu-Gadu is a Polish instant messaging client",
"icon-name", "im-gadu-gadu",
"icon-resource-path", "/im/pidgin/libpurple/gg/icons",
NULL));
}
static gchar *
plugin_extra(PurplePlugin *plugin)
{
return g_strdup_printf("Using libgadu version %s", gg_libgadu_version());
}
static GPluginPluginInfo *
gg_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
gg_load(GPluginPlugin *plugin, GError **error)
{
PurpleProtocolManager *manager = purple_protocol_manager_get_default();
ggp_protocol_register_type(G_TYPE_MODULE(plugin));
ggp_xfer_register(G_TYPE_MODULE(plugin));
my_protocol = ggp_protocol_new();
if(!purple_protocol_manager_register(manager, my_protocol, error)) {
g_clear_object(&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(NULL);
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
gg_unload(GPluginPlugin *plugin, gboolean shutdown, GError **error)
{
PurpleProtocolManager *manager = purple_protocol_manager_get_default();
if(!purple_protocol_manager_unregister(manager, my_protocol, error)) {
return FALSE;
}
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();
g_clear_object(&my_protocol);
return TRUE;
}
GPLUGIN_NATIVE_PLUGIN_DECLARE(gg)
/* vim: set ts=8 sts=0 sw=8 noet: */