pidgin/purple-plugin-pack

Add the turtles target to keep the foot clan at bay
default tip
13 months ago, Gary Kramlich
63ad7e4f10b4
Add the turtles target to keep the foot clan at bay

Testing Done:
Ran `ninja turtles` and verified it worked correctly.

Reviewed at https://reviews.imfreedom.org/r/2409/
/*
* IRC Helper Plugin for libpurple
*
* Copyright (C) 2005-2009, Richard Laager <rlaager@pidgin.im>
* Copyright (C) 2004-2005, Mathias Hasselmann <mathias@taschenorakel.de>
* Copyright (C) 2005, Daniel Beardsmore <uilleann@users.sf.net>
* Copyright (C) 2005, Björn Nilsson <BNI on irc.freenode.net>
* Copyright (C) 2005, Anthony Sofocleous <itchysoft_ant@users.sf.net>
*
* 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.
*/
/* If you can't figure out what this line is for, DON'T TOUCH IT. */
#include "../common/pp_internal.h"
#include <string.h>
#include <account.h>
#include <accountopt.h>
#include <cmds.h>
#include <connection.h>
#include <conversation.h>
#include <debug.h>
#include <notify.h>
#include <plugin.h>
#include <pluginpref.h>
#include <prefs.h>
#include <util.h>
#define PLUGIN_STATIC_NAME "irchelper"
#define PLUGIN_ID "core-rlaager-" PLUGIN_STATIC_NAME
#define PLUGIN_AUTHOR "Richard Laager <rlaager@guifications.org>"
/*****************************************************************************
* Constants *
*****************************************************************************/
#define IRC_PLUGIN_ID "prpl-irc"
/* Copied from SECS_BEFORE_RESENDING_AUTORESPONSE in src/server.c */
#define AUTO_RESPONSE_INTERVAL 600
#define DOMAIN_SUFFIX_DALNET ".dal.net"
#define DOMAIN_SUFFIX_FREENODE ".freenode.net"
#define DOMAIN_SUFFIX_FUNCOM ".funcom.com"
#define DOMAIN_SUFFIX_GAMESURGE ".gamesurge.net"
#define DOMAIN_SUFFIX_INDIEZEN ".indiezen.org"
#define DOMAIN_SUFFIX_JEUX ".jeux.fr"
#define DOMAIN_SUFFIX_QUAKENET ".quakenet.org"
#define DOMAIN_SUFFIX_SPIDERNET ".spidernet.org"
#define DOMAIN_SUFFIX_THUNDERCITY ".thundercity.org"
#define DOMAIN_SUFFIX_UNDERNET ".undernet.org"
#define MESSAGE_CHANSERV_ACCESS_LIST_ADD \
"You have been added to the access list for "
/* Omit the first character of the portion after the channel name. */
#define MESSAGE_CHANSERV_ACCESS_LIST_ADD_WITH_LEVEL "with level ["
#define MESSAGE_CHANSERV_ACCESS_LIST_DEL \
"You have been deleted from the access list for ["
#define MESSAGE_CHANSERV_NO_OP_ACCESS \
"You do not have channel operator access to"
#define MESSAGE_FREENODE_INFO "[freenode-info] "
#define MESSAGE_FRENCH_ADVERTISEMENT_START \
"<B>Avertissement</B> : Le pseudo <B>"
#define MESSAGE_FRENCH_ADVERTISEMENT_END "&lt;votre pass&gt;"
#define MESSAGE_FRENCH_LOGIN "Login <B>r?ussi</B>"
#define MESSAGE_FRENCH_MAXIMUM_CONNECTION_COUNT "Maximum de connexion"
#define MESSAGE_FRENCH_MOTD "Message du Jour :"
#define MESSAGE_PURPLE_NOTICE_PREFIX "(notice) "
#define MESSAGE_GAMESURGE_AUTHSERV_IDENTIFIED "I recognize you."
#define MESSAGE_GAMESURGE_AUTHSERV_ID_FAILURE \
"Incorrect password; please try again."
#define MESSAGE_GHOST_KILLED " has been killed"
#define MESSAGE_INVITED " invited "
#define MESSAGE_LOGIN_CONNECTION_COUNT "Highest connection count"
#define MESSAGE_MEMOSERV_NO_NEW_MEMOS "You have no new memos"
#define MESSAGE_MODE_NOTICE_PREFIX "mode (+"
#define MESSAGE_MODE_NOTICE_SUFFIX " ) by "
#define MESSAGE_NICKSERV_CLOAKED " set your hostname to"
#define MESSAGE_NICKSERV_ID_FAILURE "Password Incorrect"
#define MESSAGE_NICKSERV_IDENTIFIED \
"Password accepted - you are now recognized"
#define MESSAGE_NICKSERV_IDENTIFIED_FREENODE \
"You are now identified for"
#define MESSAGE_NICKSERV_IDENTIFIED_INDIEZEN \
"Password accepted -- you are now recognized."
#define MESSAGE_SPOOFING_YOUR_IP "*** Spoofing your IP. congrats."
#define MESSAGE_SET_HOSTNAME "idoru set your hostname to"
#define MESSAGE_QUAKENET_Q_CRUFT \
"Remember: NO-ONE from QuakeNet will ever ask for your password. " \
"NEVER send your password to ANYONE except Q@CServe.quakenet.org."
#define MESSAGE_QUAKENET_Q_ID_FAILURE \
"Lastly, When you do recover your password, please choose a " \
"NEW PASSWORD, not your old one! " \
"See the above URL for details."
#define MESSAGE_QUAKENET_Q_IDENTIFIED "AUTH&apos;d successfully."
#define MESSAGE_UNREAL_IRCD_HOSTNAME_FOUND "*** Found your hostname"
#define MESSAGE_UNREAL_IRCD_HOSTNAME_LOOKUP "*** Looking up your hostname..."
#define MESSAGE_UNREAL_IRCD_IDENT_LOOKUP "*** Checking ident..."
#define MESSAGE_UNREAL_IRCD_IDENT_NO_RESPONSE \
"*** No ident response; username prefixed with ~"
#define MESSAGE_UNREAL_IRCD_PONG_CRUFT \
"*** If you are having problems connecting due to ping timeouts, " \
"please type /quote pong"
#define MESSAGE_VOICE_ADD "mode (+v"
#define MESSAGE_VOICE_REMOVE "mode(-v"
/* Generic AuthServ, not currently used for any networks. */
#define NICK_AUTHSERV "AuthServ"
#define NICK_CHANSERV "ChanServ"
#define NICK_FREENODE_CONNECT "frigg"
#define NICK_FUNCOM_Q_SERVICE NICK_QUAKENET_Q "@cserve.funcom.com"
#define NICK_GAMESURGE_AUTHSERV "AuthServ"
#define NICK_GAMESURGE_AUTHSERV_SERVICE \
NICK_GAMESURGE_AUTHSERV "@Services.GameSurge.net"
#define NICK_GAMESURGE_GLOBAL "Global"
#define NICK_JEUX_Z "Z"
#define NICK_JEUX_WELCOME "[Welcome]"
#define NICK_MEMOSERV "MemoServ"
#define NICK_NICKSERV "NickServ"
#define NICK_DALNET_AUTHSERV_SERVICE NICK_NICKSERV "@services.dal.net"
#define NICK_QUAKENET_L "L"
#define NICK_QUAKENET_Q "Q"
#define NICK_QUAKENET_Q_SERVICE NICK_QUAKENET_Q "@CServe.quakenet.org"
#define NICK_UNDERNET_X "x@channels.undernet.org"
/* The %c exists so we can tell if a match occurred. */
#define PATTERN_WEIRD_LOGIN_CRUFT "o%c %*u ca %*u(%*u) ft %*u(%*u)"
#define TIMEOUT_IDENTIFY 8000
#define TIMEOUT_KILLING_GHOST 8000
typedef enum {
IRC_NONE = 0x0000,
IRC_KILLING_GHOST = 0x0001,
IRC_WILL_ID = 0x0002,
IRC_DID_ID = 0x0004,
IRC_ID_FAILED = 0x0008,
IRC_NETWORK_TYPE_UNKNOWN = 0x0010,
IRC_NETWORK_TYPE_GAMESURGE = 0x0020,
IRC_NETWORK_TYPE_NICKSERV = 0x0040,
IRC_NETWORK_TYPE_QUAKENET = 0x0080,
IRC_NETWORK_TYPE_JEUX = 0x0100,
IRC_NETWORK_TYPE_UNDERNET = 0x0200,
IRC_NETWORK_TYPE_THUNDERCITY = 0x0400,
IRC_NETWORK_TYPE_DALNET = 0x0800,
IRC_NETWORK_TYPE_FUNCOM = 0x1000,
IRC_NETWORK_TYPE_INDIEZEN = 0x2000,
IRC_NETWORK_TYPE_SPIDERNET = 0x4000,
IRC_NETWORK_TYPE_FREENODE = 0x8000,
} IRCHelperStateFlags;
struct proto_stuff
{
gpointer *proto_data;
PurpleAccount *account;
};
GHashTable *states;
/*****************************************************************************
* Prototypes *
*****************************************************************************/
static gboolean plugin_load(PurplePlugin *plugin);
static gboolean plugin_unload(PurplePlugin *plugin);
/*****************************************************************************
* Plugin Info *
*****************************************************************************/
static PurplePluginInfo info =
{
PURPLE_PLUGIN_MAGIC,
PURPLE_MAJOR_VERSION,
0,
PURPLE_PLUGIN_STANDARD, /**< type */
NULL, /**< ui_requirement */
0, /**< flags */
NULL, /**< dependencies */
PURPLE_PRIORITY_DEFAULT, /**< priority */
PLUGIN_ID, /**< id */
NULL, /**< name */
PP_VERSION, /**< version */
NULL, /**< summary */
NULL, /**< description */
PLUGIN_AUTHOR, /**< author */
PP_WEBSITE, /**< homepage */
plugin_load, /**< load */
plugin_unload, /**< unload */
NULL, /**< destroy */
NULL, /**< ui_info */
NULL, /**< extra_info */
NULL, /**< prefs_info */
NULL, /**< actions */
NULL, /**< reserved 1 */
NULL, /**< reserved 2 */
NULL, /**< reserved 3 */
NULL /**< reserved 4 */
};
/* XXX: This is a dirty hack. It's better than what I was doing before, though. */
static PurpleConversation *get_conversation(PurpleAccount *account)
{
PurpleConversation *conv;
#if PURPLE_VERSION_CHECK(3,0,0)
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, "None");
#else
conv = g_new0(PurpleConversation, 1);
conv->type = PURPLE_CONV_TYPE_IM;
/* If we use this then the conversation updated signal is fired and
* other plugins might start doing things to our conversation, such as
* setting data on it which we would then need to free etc. It's easier
* just to be more hacky by setting account directly. */
/* purple_conversation_set_account(conv, account); */
conv->account = account;
#endif
return conv;
}
static IRCHelperStateFlags get_connection_type(PurpleConnection *connection)
{
PurpleAccount *account;
const gchar *protocol;
gchar *username;
IRCHelperStateFlags type = IRC_NETWORK_TYPE_UNKNOWN;
g_return_val_if_fail(NULL != connection, IRC_NETWORK_TYPE_UNKNOWN);
account = purple_connection_get_account(connection);
protocol = purple_account_get_protocol_id(account);
g_return_val_if_fail(g_str_equal(protocol, IRC_PLUGIN_ID),
IRC_NETWORK_TYPE_UNKNOWN);
username = g_utf8_strdown(purple_account_get_username(account), -1);
if (g_str_has_suffix(username, DOMAIN_SUFFIX_GAMESURGE))
type = IRC_NETWORK_TYPE_GAMESURGE;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_THUNDERCITY))
type = IRC_NETWORK_TYPE_THUNDERCITY;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_DALNET))
type = IRC_NETWORK_TYPE_DALNET;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_QUAKENET))
type = IRC_NETWORK_TYPE_QUAKENET;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_FUNCOM))
type = IRC_NETWORK_TYPE_FUNCOM;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_JEUX))
type = IRC_NETWORK_TYPE_JEUX;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_UNDERNET))
type = IRC_NETWORK_TYPE_UNDERNET;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_INDIEZEN))
type = IRC_NETWORK_TYPE_INDIEZEN;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_SPIDERNET))
type = IRC_NETWORK_TYPE_SPIDERNET;
else if (g_str_has_suffix(username, DOMAIN_SUFFIX_FREENODE))
type = IRC_NETWORK_TYPE_FREENODE;
g_free(username);
return type;
}
#if PURPLE_VERSION_CHECK(2,7,0)
static gboolean autojoin_cb(PurpleConnection *connection, gpointer data)
{
IRCHelperStateFlags state;
g_return_val_if_fail(NULL != connection, FALSE);
state = GPOINTER_TO_INT(g_hash_table_lookup(states,
connection->proto_data));
if (state & IRC_WILL_ID || state & IRC_KILLING_GHOST)
{
/* Block autojoining; we'll re-fire the signal once authed. */
purple_debug_misc(PLUGIN_STATIC_NAME,
"Blocking the autojoin signal.\n");
return TRUE;
}
return FALSE;
}
#endif
static void identify_finished(PurpleConnection *connection,
IRCHelperStateFlags new_state)
{
IRCHelperStateFlags state;
#if PURPLE_VERSION_CHECK(2,7,0)
gboolean emit;
#endif
g_return_if_fail(NULL != connection);
state = GPOINTER_TO_INT(g_hash_table_lookup(states,
connection->proto_data));
#if PURPLE_VERSION_CHECK(2,7,0)
emit = (state & IRC_WILL_ID || state & IRC_KILLING_GHOST);
#endif
g_hash_table_insert(states, connection->proto_data,
GINT_TO_POINTER((state & ~IRC_KILLING_GHOST
& ~IRC_WILL_ID)
| new_state));
#if PURPLE_VERSION_CHECK(2,7,0)
if (emit)
{
/* This is done after updating the state so autojoin_cb
* won't re-block the signal. */
purple_debug_misc(PLUGIN_STATIC_NAME,
"Re-emitting the autojoin signal.\n");
purple_signal_emit(purple_connections_get_handle(),
"autojoin", connection);
}
#endif
}
static gboolean auth_timeout(gpointer data)
{
PurpleConnection *connection = data;
gpointer proto_data = connection->proto_data;
IRCHelperStateFlags state;
state = GPOINTER_TO_INT(g_hash_table_lookup(states, proto_data));
if (state & IRC_WILL_ID)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Authentication failed: timeout expired\n");
identify_finished(connection, IRC_ID_FAILED);
}
return FALSE;
}
/*****************************************************************************
* AuthServ Helper Functions *
*****************************************************************************/
static void authserv_identify(const char *command, PurpleConnection *connection, IRCHelperStateFlags state)
{
PurpleAccount *account;
gchar **userparts = NULL;
const gchar *username;
const gchar *password;
g_return_if_fail(NULL != connection);
account = purple_connection_get_account(connection);
username = purple_account_get_string(account, PLUGIN_ID "_authname", "");
if (NULL == username || '\0' == *username)
{
userparts = g_strsplit(purple_account_get_username(account), "@", 2);
username = userparts[0];
}
password = purple_account_get_string(account, PLUGIN_ID "_nickpassword", "");
if (NULL != username && '\0' != *username &&
NULL != password && '\0' != *password)
{
const gchar *authserv = NICK_AUTHSERV;
gchar *authentication = g_strconcat(command, " ", username, " ", password, NULL);
purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: %s %s <PASSWORD>\n", command, username);
g_hash_table_insert(states,
connection->proto_data,
GINT_TO_POINTER(state | IRC_WILL_ID));
if (state & IRC_NETWORK_TYPE_GAMESURGE)
authserv = NICK_GAMESURGE_AUTHSERV_SERVICE;
else if (state & IRC_NETWORK_TYPE_DALNET)
authserv = NICK_DALNET_AUTHSERV_SERVICE;
else if (state & IRC_NETWORK_TYPE_QUAKENET)
authserv = NICK_QUAKENET_Q_SERVICE;
else if (state & IRC_NETWORK_TYPE_FUNCOM)
authserv = NICK_FUNCOM_Q_SERVICE;
else if (state & IRC_NETWORK_TYPE_UNDERNET)
authserv = NICK_UNDERNET_X;
serv_send_im(connection, authserv, authentication, 0);
g_free(authentication);
/* Register a timeout... If we don't get the expected response from AuthServ,
* we need to stop suppressing messages from it at some point or the user
* could be very confused.
*/
purple_timeout_add(TIMEOUT_IDENTIFY, (GSourceFunc)auth_timeout,
(gpointer)connection);
}
g_strfreev(userparts);
}
/*****************************************************************************
* Operator Helper Functions *
*****************************************************************************/
static void jeux_identify(PurpleConnection *connection, IRCHelperStateFlags state)
{
PurpleAccount *account;
gchar **userparts;
const gchar *username;
const gchar *password;
g_return_if_fail(NULL != connection);
account = purple_connection_get_account(connection);
userparts = g_strsplit(purple_account_get_username(account), "@", 2);
username = userparts[0];
password = purple_account_get_string(account, PLUGIN_ID "_nickpassword", "");
if (NULL != username && '\0' != *username &&
NULL != password && '\0' != *password)
{
gchar *authentication = g_strdup_printf("quote %s login %s %s", NICK_JEUX_Z, username, password);
PurpleConversation *conv = get_conversation(account);
gchar *error;
purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: quote %s login %s <PASSWORD>\n", NICK_JEUX_Z, username);
g_hash_table_insert(states,
connection->proto_data,
GINT_TO_POINTER(state | IRC_WILL_ID));
if (purple_cmd_do_command(conv, authentication, authentication, &error) != PURPLE_CMD_STATUS_OK)
{
/* TODO: PRINT ERROR MESSAGE */
g_free(error);
}
g_free(conv);
g_free(authentication);
/* Register a timeout... If we don't get the expected response from AuthServ,
* we need to stop suppressing messages from it at some point or the user
* could be very confused.
*/
purple_timeout_add(TIMEOUT_IDENTIFY, (GSourceFunc)auth_timeout,
(gpointer)connection);
}
g_strfreev(userparts);
}
/*****************************************************************************
* Operator Helper Functions *
*****************************************************************************/
static void oper_identify(PurpleAccount *account)
{
const char *operpassword;
operpassword = purple_account_get_string(account, PLUGIN_ID "_operpassword", "");
if ('\0' != *operpassword)
{
PurpleConversation *conv = get_conversation(account);
PurpleConnection *connection = purple_account_get_connection(account);
const gchar *name = purple_connection_get_display_name(connection);
char *command = g_strdup_printf("quote OPER %s %s", name, operpassword);
gchar *error;
if (purple_cmd_do_command(conv, command, command, &error) != PURPLE_CMD_STATUS_OK)
{
/* TODO: PRINT ERROR MESSAGE */
g_free(error);
}
g_free(command);
g_free(conv);
}
}
/*****************************************************************************
* NickServ Helper Functions *
*****************************************************************************/
static void nickserv_do_identify(char *authentication, gpointer proto_data, PurpleConnection *gc, const char *nickpassword)
{
PurpleConversation *conv = get_conversation(purple_connection_get_account(gc));
gchar *error;
gchar *tmp;
purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: %s <PASSWORD>\n", authentication);
tmp = g_strconcat(authentication, " ", nickpassword, NULL);
g_free(authentication);
authentication = tmp;
if (purple_cmd_do_command(conv, authentication, authentication, &error) != PURPLE_CMD_STATUS_OK)
{
/* TODO: PRINT ERROR MESSAGE */
g_free(error);
}
g_free(authentication);
g_free(conv);
/* Register a timeout... If we don't get the expected response from NickServ,
* we need to stop suppressing messages from it at some point or the user
* could be very confused.
*/
purple_timeout_add(TIMEOUT_IDENTIFY, (GSourceFunc)auth_timeout,
(gpointer)gc);
}
static void nickserv_identify(gpointer proto_data, PurpleConnection *gc, const char *nickpassword)
{
char *authentication = g_strdup_printf("quote %s IDENTIFY", NICK_NICKSERV);
nickserv_do_identify(authentication, proto_data, gc, nickpassword);
}
static void nickserv_msg_identify(const char *command, gpointer proto_data, PurpleConnection *gc, const char *nickpassword)
{
char *authentication = g_strdup_printf("quote PRIVMSG %s : %s", NICK_NICKSERV, command);
nickserv_do_identify(authentication, proto_data, gc, nickpassword);
}
static gboolean ghosted_nickname_killed_cb(struct proto_stuff *stuff)
{
PurpleConnection *gc;
char **userparts;
IRCHelperStateFlags state;
PurpleConversation *conv;
char *command;
gchar *error;
state = GPOINTER_TO_INT(g_hash_table_lookup(states, stuff->proto_data));
/* We only want this function to act once. Under normal circumstances,
* it's going to get called twice: Once when NickServ tells us the ghost
* has been killed and once when the timeout expires. Under abnormal
* conditions, it will only be called when the timeout expires.
*/
if (!(state & IRC_KILLING_GHOST))
{
g_free(stuff);
return FALSE;
}
g_hash_table_insert(states, stuff->proto_data,
GINT_TO_POINTER((state & ~IRC_KILLING_GHOST) | IRC_WILL_ID));
gc = purple_account_get_connection(stuff->account);
if (NULL == gc)
{
g_free(stuff);
return FALSE;
}
/* Switch back to the normal nickname. */
userparts = g_strsplit(purple_account_get_username(stuff->account), \
"@", 2);
conv = get_conversation(stuff->account);
command = g_strdup_printf("nick %s", userparts[0]);
if (purple_cmd_do_command(conv, command, command, &error) != PURPLE_CMD_STATUS_OK)
{
/* TODO: PRINT ERROR MESSAGE */
g_free(error);
}
g_free(command);
g_free(conv);
nickserv_identify(stuff->proto_data, gc,
purple_account_get_string(stuff->account,
PLUGIN_ID "_nickpassword", ""));
g_strfreev(userparts);
g_free(stuff);
oper_identify(stuff->account);
return FALSE;
}
/*****************************************************************************
* Callbacks *
*****************************************************************************/
static void signed_on_cb(PurpleConnection *connection)
{
PurpleAccount *account;
IRCHelperStateFlags state;
const char *nickpassword;
g_return_if_fail(NULL != connection);
g_return_if_fail(NULL != connection->proto_data);
account = purple_connection_get_account(connection);
g_return_if_fail(NULL != account);
if (!g_str_equal(purple_account_get_protocol_id(account), IRC_PLUGIN_ID))
return;
state = get_connection_type(connection);
if (state & IRC_NETWORK_TYPE_GAMESURGE)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Connected with GameSurge: %s\n",
purple_connection_get_display_name(connection));
authserv_identify("AUTH", connection, state);
}
if (state & IRC_NETWORK_TYPE_DALNET)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Connected with DalNet: %s\n",
purple_connection_get_display_name(connection));
authserv_identify("IDENTIFY", connection, state);
}
else if (state & IRC_NETWORK_TYPE_JEUX)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Connected with Jeux.fr: %s\n",
purple_connection_get_display_name(connection));
jeux_identify(connection, state);
}
else if (state & IRC_NETWORK_TYPE_QUAKENET)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Connected with QuakeNet: %s\n",
purple_connection_get_display_name(connection));
authserv_identify("AUTH", connection, state);
}
else if (state & IRC_NETWORK_TYPE_UNDERNET)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Connected with UnderNet: %s\n",
purple_connection_get_display_name(connection));
authserv_identify("login ", connection, state);
}
else if (state & IRC_NETWORK_TYPE_FUNCOM)
{
purple_debug_info(PLUGIN_STATIC_NAME, "Connected with Funcom: %s\n",
purple_connection_get_display_name(connection));
authserv_identify("AUTH", connection, state);
}
else
{
nickpassword = purple_account_get_string(account, PLUGIN_ID "_nickpassword", "");
if ('\0' != *nickpassword)
{
char **userparts;
g_hash_table_insert(states, connection->proto_data,
GINT_TO_POINTER(IRC_NETWORK_TYPE_NICKSERV | IRC_WILL_ID));
userparts = g_strsplit(purple_account_get_username(account), "@", 2);
if (purple_account_get_bool(account, PLUGIN_ID "_disconnectghosts", 0) &&
strcmp(userparts[0], purple_connection_get_display_name(connection)))
{
struct proto_stuff *stuff = g_new0(struct proto_stuff, 1);
char *command;
PurpleConversation *conv;
gchar *error;
stuff->proto_data = connection->proto_data;
stuff->account = account;
/* Disconnect the ghosted connection. */
command = g_strdup_printf("quote %s GHOST %s %s", NICK_NICKSERV, userparts[0], nickpassword);
conv = get_conversation(account);
purple_debug_misc(PLUGIN_STATIC_NAME, "Sending command: quote %s GHOST %s <PASSWORD>\n", NICK_NICKSERV, userparts[0]);
if (purple_cmd_do_command(conv, command, command, &error) != PURPLE_CMD_STATUS_OK)
{
/* TODO: PRINT ERROR MESSAGE */
g_free(error);
}
g_free(command);
g_free(conv);
g_hash_table_insert(states, connection->proto_data,
GINT_TO_POINTER(IRC_NETWORK_TYPE_NICKSERV | IRC_KILLING_GHOST));
/* We have to wait for the server to disconnect the
* other username before we can reclaim it. This
* timeout sets an upper bound on the length of time
* we'll wait for the ghost to be killed.
*/
purple_timeout_add(TIMEOUT_KILLING_GHOST,
(GSourceFunc)ghosted_nickname_killed_cb,
(gpointer)stuff);
g_strfreev(userparts);
return;
}
if (state & IRC_NETWORK_TYPE_THUNDERCITY)
nickserv_msg_identify("AUTH", connection->proto_data, connection, nickpassword);
else if (state & (IRC_NETWORK_TYPE_INDIEZEN | IRC_NETWORK_TYPE_SPIDERNET))
nickserv_msg_identify("identify", connection->proto_data, connection, nickpassword);
else if (state & IRC_NETWORK_TYPE_FREENODE)
{
char *authentication = g_strdup_printf("quote %s IDENTIFY %s", NICK_NICKSERV, userparts[0]);
nickserv_do_identify(authentication, connection->proto_data, connection, nickpassword);
}
else
nickserv_identify(connection->proto_data, connection, nickpassword);
g_strfreev(userparts);
}
}
oper_identify(account);
}
static void conversation_created_cb(PurpleConversation *conv)
{
purple_conversation_set_data(conv, PLUGIN_ID "_start_time",
GINT_TO_POINTER((int)time(NULL)));
}
static gboolean writing_chat_msg_cb(PurpleAccount *account, const char *who,
char **message, PurpleConversation *conv,
PurpleMessageFlags flags)
{
const gchar *name;
const gchar *topic;
if (!g_str_equal(purple_account_get_protocol_id(account), IRC_PLUGIN_ID))
return FALSE;
if (NULL == *message)
return FALSE;
g_return_val_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT, FALSE);
if (flags & PURPLE_MESSAGE_SYSTEM &&
(g_str_has_prefix(*message, MESSAGE_MODE_NOTICE_PREFIX "v ") ||
g_str_has_prefix(*message, MESSAGE_MODE_NOTICE_PREFIX "o ")))
{
const char *tmp = *message + sizeof(MESSAGE_MODE_NOTICE_PREFIX "X ") - 1;
const char *name = purple_connection_get_display_name(purple_account_get_connection(account));
if (g_str_has_prefix(tmp, name) &&
g_str_has_prefix(tmp + strlen(name), MESSAGE_MODE_NOTICE_SUFFIX NICK_CHANSERV))
{
if (time(NULL) < (time_t)GPOINTER_TO_INT(purple_conversation_get_data(conv, PLUGIN_ID "_start_time")) + 10)
return TRUE;
}
}
if (flags & PURPLE_MESSAGE_SYSTEM &&
(topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv))) != NULL &&
(name = purple_conversation_get_name(conv)) != NULL)
{
char *name_escaped = g_markup_escape_text(name, -1);
char *topic_escaped = g_markup_escape_text(topic, -1);
char *topic_linkified = purple_markup_linkify(topic_escaped);
if (strstr(*message, name_escaped) != NULL &&
strstr(*message, topic_linkified) != NULL)
{
/* We've got a topic notice. */
PurpleBlistNode *node;
if ((node = (PurpleBlistNode *)purple_blist_find_chat(account, name)) != NULL)
{
const char *last_topic = purple_blist_node_get_string(node, PLUGIN_ID "_topic");
/* If we saw this the last time we joined, suppress it. */
if (last_topic != NULL && strcmp(topic, last_topic) == 0)
{
g_free(name_escaped);
g_free(topic_escaped);
g_free(topic_linkified);
return TRUE;
}
else
purple_blist_node_set_string(node, PLUGIN_ID "_topic", topic);
}
}
g_free(name_escaped);
g_free(topic_escaped);
g_free(topic_linkified);
}
return FALSE;
}
static GSList *auto_responses = NULL;
struct auto_response {
PurpleConnection *gc;
char *name;
time_t received;
char *message;
};
static gboolean
expire_auto_responses(gpointer data)
{
GSList *tmp, *cur;
struct auto_response *ar;
tmp = auto_responses;
while (tmp) {
cur = tmp;
tmp = tmp->next;
ar = (struct auto_response *)cur->data;
if ((time(NULL) - ar->received) > AUTO_RESPONSE_INTERVAL) {
auto_responses = g_slist_remove(auto_responses, ar);
g_free(ar->message);
g_free(ar);
}
}
return FALSE; /* do not run again */
}
static struct auto_response *
get_auto_response(PurpleConnection *gc, const char *name)
{
GSList *tmp;
struct auto_response *ar;
purple_timeout_add((AUTO_RESPONSE_INTERVAL + 1) * 1000, expire_auto_responses, NULL);
tmp = auto_responses;
while (tmp) {
ar = (struct auto_response *)tmp->data;
if (gc == ar->gc && purple_strequal(name, ar->name))
return ar;
tmp = tmp->next;
}
ar = (struct auto_response *)g_new0(struct auto_response, 1);
ar->name = g_strdup(name);
ar->gc = gc;
ar->received = 0;
auto_responses = g_slist_prepend(auto_responses, ar);
return ar;
}
static gboolean receiving_im_msg_cb(PurpleAccount *account, gchar **sender,
gchar **buffer,
PurpleConversation *conv,
gint *flags, gpointer data)
{
gchar *msg;
gchar *nick;
PurpleConversation *chat;
PurpleConnection *connection;
IRCHelperStateFlags state;
gchar *invite_prefix;
if (!g_str_equal(purple_account_get_protocol_id(account), IRC_PLUGIN_ID))
return FALSE;
msg = *buffer;
nick = *sender;
connection = purple_account_get_connection(account);
g_return_val_if_fail(NULL != connection, FALSE);
state = GPOINTER_TO_INT(g_hash_table_lookup(states, connection->proto_data));
/* SUPPRESS EXTRA AUTO-RESPONSES */
if (*flags & PURPLE_MESSAGE_AUTO_RESP)
{
/* The same idea as the code in src/server.c. */
struct auto_response *ar = get_auto_response(connection, nick);
time_t now = time(NULL);
if ((now - ar->received) <= AUTO_RESPONSE_INTERVAL &&
!strcmp(msg, ar->message))
{
/* We've recently received an auto-response
* and it's the same as the one we've just received.
* Drop it!
*/
ar->received = now;
return TRUE;
}
ar->received = now;
g_free(ar->message);
ar->message = g_strdup(msg);
/* None of the other rules are for auto-responses. */
return FALSE;
}
/* SIMPLE SUPPRESSION RULES */
/* Suppress the FreeNode stats collection bot. */
if (g_str_equal(nick, NICK_FREENODE_CONNECT) &&
g_str_has_prefix(msg, "Received CTCP &apos;VERSION&apos;"))
{
return TRUE;
}
/* Suppress useless ChanServ notification. */
if (g_str_equal(nick, NICK_CHANSERV) &&
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_NO_OP_ACCESS))
{
return TRUE;
}
/* Suppress GameSurge message(s) of the day. */
if (state & IRC_NETWORK_TYPE_GAMESURGE &&
g_str_equal(nick, NICK_GAMESURGE_GLOBAL))
{
return TRUE;
}
/* Suppress useless Jeux welcome. */
if (g_str_equal(nick, NICK_JEUX_WELCOME))
{
return TRUE;
}
/* Suppress FreeNode welcome messages. */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FREENODE_INFO))
{
return TRUE;
}
/* Suppress useless MemoServ notification. */
if (g_str_equal(nick, NICK_MEMOSERV) &&
g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_MEMOSERV_NO_NEW_MEMOS))
{
return TRUE;
}
/* Suppress QuakeNet Q password warning. */
if (g_str_equal(nick, NICK_QUAKENET_Q) &&
g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_CRUFT))
{
return TRUE;
}
/* Suppress Z registration notice. */
if (g_str_equal(nick, "Z") &&
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FRENCH_ADVERTISEMENT_START) &&
g_str_has_suffix(msg, MESSAGE_FRENCH_ADVERTISEMENT_END))
{
return TRUE;
}
/* Suppress Z's successful login notice. */
if (g_str_equal(nick, "Z") &&
(g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FRENCH_LOGIN) ||
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FRENCH_MOTD)))
{
return TRUE;
}
/* Suppress login message for the highest connection count. */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX
MESSAGE_LOGIN_CONNECTION_COUNT) ||
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX
MESSAGE_FRENCH_MAXIMUM_CONNECTION_COUNT))
{
return TRUE;
}
/* Suppress UnrealIRCd login cruft. */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_HOSTNAME_FOUND) ||
g_str_equal( msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_HOSTNAME_LOOKUP) ||
g_str_equal( msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_IDENT_LOOKUP) ||
g_str_equal( msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_IDENT_NO_RESPONSE) ||
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_PONG_CRUFT))
{
return TRUE;
}
/* Suppress hostname cloak notification on Freenode. */
if (g_str_has_suffix(nick, DOMAIN_SUFFIX_FREENODE) &&
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX NICK_NICKSERV MESSAGE_NICKSERV_CLOAKED))
{
return TRUE;
}
/* Suppress "IP spoofing" notice on worldforge.org:
* http://plugins.guifications.org/trac/ticket/524 */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_SPOOFING_YOUR_IP))
{
return TRUE;
}
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_SET_HOSTNAME))
{
return TRUE;
}
/* Suppress voice mode change messages. */
if (g_str_has_prefix(msg, MESSAGE_VOICE_ADD) ||
g_str_has_prefix(msg, MESSAGE_VOICE_REMOVE))
{
return TRUE;
}
/* SLIGHTLY COMPLICATED SUPPRESSION RULES */
/* Supress QuakeNet and UnderNet Weird Login Cruft */
{
char temp;
if (sscanf(msg, MESSAGE_PURPLE_NOTICE_PREFIX PATTERN_WEIRD_LOGIN_CRUFT, &temp) == 1)
{
return TRUE;
}
}
/* Suppress silly notices of my own invites. */
invite_prefix = g_strconcat(MESSAGE_PURPLE_NOTICE_PREFIX,
purple_connection_get_display_name(connection), MESSAGE_INVITED, NULL);
if (g_str_has_prefix(msg, invite_prefix))
{
g_free(invite_prefix);
return TRUE;
}
g_free(invite_prefix);
/* SERVICE MAGIC */
/* Display ChanServ Access List Add Notifications in the chat window. */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_ADD) &&
g_str_equal(nick, NICK_CHANSERV))
{
gchar *tmp;
gchar *channel;
gchar *level = NULL;
gchar *msg2;
channel = purple_markup_strip_html(msg + sizeof(MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_ADD) - 1);
if ((tmp = strchr(channel, ' ')) != NULL)
{
*tmp = '\0';
if (g_str_has_prefix(tmp + 1, MESSAGE_CHANSERV_ACCESS_LIST_ADD_WITH_LEVEL))
{
/* The + 1 and - 1 are both kept to make it clear how that relates with other code. */
level = tmp + 1 + sizeof(MESSAGE_CHANSERV_ACCESS_LIST_ADD_WITH_LEVEL) - 1;
if ((tmp = strchr(level, ']')) != NULL)
*tmp = '\0';
}
}
if (NULL == level)
msg2 = g_strdup(_("You have been added to the access list."));
else
msg2 = g_strdup_printf(_("You have been added to the access list with an access level of %s."), level);
chat = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, channel, account);
if (chat)
{
purple_conv_chat_write(PURPLE_CONV_CHAT(chat), nick,
msg2, PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(channel);
g_free(msg2);
return TRUE;
}
g_free(channel);
g_free(msg2);
return FALSE;
}
/* Display ChanServ Access List Delete Notifications in the chat window. */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_DEL) &&
g_str_equal(nick, NICK_CHANSERV))
{
gchar *tmp;
gchar *channel;
channel = purple_markup_strip_html(msg + sizeof(MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_DEL) - 1);
if ((tmp = strchr(channel, ']')) != NULL)
*tmp = '\0';
chat = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, channel, account);
if (chat)
{
purple_conv_chat_write(PURPLE_CONV_CHAT(chat), nick,
_("You have been removed from the access list."),
PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(channel);
return TRUE;
}
g_free(channel);
return FALSE;
}
/* Display ChanServ channel join messages in the chat window. */
if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX "[#") &&
(g_str_equal(nick, NICK_CHANSERV) || g_str_equal(nick, NICK_QUAKENET_L)))
{
gchar *msg2;
gchar *msg3;
gchar *channel;
/* Duplicate the message so we can modify the string in place. */
msg2 = g_strdup(msg);
/* We need to keep a pointer to the beginning of the string so we can free it later. */
msg3 = msg2;
/* channel needs to start with the # */
channel = msg3;
channel += sizeof(MESSAGE_PURPLE_NOTICE_PREFIX);
/* Find the "]", set it to the null character.
* This makes chan contain just the channel.
* Then, increment msg3 two spots so it contains
* just the message.
*/
if ((msg3 = g_strstr_len(msg3, strlen(msg3), "]")))
{
PurpleBlistNode *node;
*msg3 = '\0';
/* Make sure it's safe to increment the pointer */
if ('\0' == msg3[1] || '\0' == msg3[2])
{
g_free(msg2);
return FALSE;
}
msg3 += 2;
if ((node = (PurpleBlistNode *)purple_blist_find_chat(account, channel)) != NULL)
{
const char *last_msg = purple_blist_node_get_string(node, PLUGIN_ID "_chanserv_join_msg");
/* If we saw this the last time we joined, suppress it. */
if (last_msg != NULL && strcmp(msg3, last_msg) == 0)
{
g_free(msg2);
return TRUE;
}
else
purple_blist_node_set_string(node, PLUGIN_ID "_chanserv_join_msg", msg3);
}
/* Write the message to the chat as a system message. */
chat = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, channel, account);
if (chat)
{
purple_conv_chat_write(PURPLE_CONV_CHAT(chat), nick,
msg3, PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(msg2);
return TRUE;
}
}
g_free(msg2);
return FALSE;
}
/* Suppress useless NickServ notifications if we're identifying automatically. */
if (state & IRC_NETWORK_TYPE_NICKSERV &&
(state & IRC_WILL_ID || state & IRC_KILLING_GHOST) &&
g_str_equal(nick, NICK_NICKSERV))
{
/* Track that the identification is finished. */
if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_NICKSERV_IDENTIFIED) ||
g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_NICKSERV_IDENTIFIED_FREENODE) ||
g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_NICKSERV_IDENTIFIED_INDIEZEN))
identify_finished(connection, IRC_DID_ID);
/* The ghost has been killed, continue the signon. */
if (state & IRC_KILLING_GHOST && strstr(msg, MESSAGE_GHOST_KILLED))
{
struct proto_stuff *stuff = g_new0(struct proto_stuff, 1);
stuff->proto_data = connection->proto_data;
stuff->account = account;
ghosted_nickname_killed_cb(stuff);
}
/* The identification has failed. */
if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_NICKSERV_ID_FAILURE))
{
identify_finished(connection, IRC_ID_FAILED);
purple_notify_error(NULL,
_("NickServ Authentication Error"),
_("Error authenticating with NickServ"),
_("Check your password."));
}
return TRUE;
}
/* Suppress useless AuthServ notifications if we're identifying automatically. */
if (state & IRC_NETWORK_TYPE_GAMESURGE &&
state & IRC_WILL_ID &&
g_str_equal(nick, NICK_GAMESURGE_AUTHSERV))
{
/* Track that the identification is finished. */
if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_GAMESURGE_AUTHSERV_IDENTIFIED))
identify_finished(connection, IRC_DID_ID);
/* The identification has failed. */
if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_GAMESURGE_AUTHSERV_ID_FAILURE))
{
identify_finished(connection, IRC_ID_FAILED);
purple_notify_error(NULL,
_("GameSurge Authentication Error"),
_("Error authenticating with AuthServ"),
_("Check your password."));
}
return TRUE;
}
/* Suppress useless Q notifications if we're identifying automatically. */
if ((state & IRC_NETWORK_TYPE_QUAKENET ||
state & IRC_NETWORK_TYPE_FUNCOM) &&
state & IRC_WILL_ID &&
g_str_equal(nick, NICK_QUAKENET_Q))
{
/* Track that the identification is finished. */
if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_IDENTIFIED))
identify_finished(connection, IRC_DID_ID);
/* The identification has failed. */
if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_ID_FAILURE))
{
identify_finished(connection, IRC_ID_FAILED);
purple_notify_error(NULL,
_("QuakeNet Authentication Error"),
_("Error authenticating with Q"),
_("Check your password."));
}
return TRUE;
}
return FALSE;
}
/*****************************************************************************
* Plugin Code *
*****************************************************************************/
static gboolean plugin_load(PurplePlugin *plugin)
{
PurplePlugin *irc_prpl;
PurplePluginProtocolInfo *prpl_info;
PurpleAccountOption *option;
void *conn_handle;
void *conv_handle;
irc_prpl = purple_plugins_find_with_id(IRC_PLUGIN_ID);
if (NULL == irc_prpl)
return FALSE;
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(irc_prpl);
if (NULL == prpl_info)
return FALSE;
/* Create hash table. */
states = g_hash_table_new(g_direct_hash, g_direct_equal);
/* Register protocol preferences. */
option = purple_account_option_string_new(_("Auth name"), PLUGIN_ID "_authname", "");
prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
option = purple_account_option_string_new(_("Nick password"), PLUGIN_ID "_nickpassword", "");
#if PURPLE_VERSION_CHECK(3,0,0)
purple_account_option_string_set_masked(option, TRUE);
#else
purple_account_option_set_masked(option, TRUE);
#endif
prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
option = purple_account_option_bool_new(_("Disconnect ghosts (Duplicate nicknames)"),
PLUGIN_ID "_disconnectghosts", 0);
prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
option = purple_account_option_string_new(_("Operator password"), PLUGIN_ID "_operpassword", "");
#if PURPLE_VERSION_CHECK(3,0,0)
purple_account_option_string_set_masked(option, TRUE);
#else
purple_account_option_set_masked(option, TRUE);
#endif
prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
/* Register callbacks. */
conn_handle = purple_connections_get_handle();
conv_handle = purple_conversations_get_handle();
purple_signal_connect(conn_handle, "signed-on",
plugin, PURPLE_CALLBACK(signed_on_cb),
NULL);
#if PURPLE_VERSION_CHECK(2,7,0)
purple_signal_connect(conn_handle, "autojoin",
plugin, PURPLE_CALLBACK(autojoin_cb),
NULL);
#endif
purple_signal_connect(conv_handle, "conversation-created",
plugin, PURPLE_CALLBACK(conversation_created_cb),
NULL);
purple_signal_connect(conv_handle, "receiving-im-msg",
plugin, PURPLE_CALLBACK(receiving_im_msg_cb),
NULL);
purple_signal_connect(conv_handle, "writing-chat-msg",
plugin, PURPLE_CALLBACK(writing_chat_msg_cb),
NULL);
return TRUE;
}
static gboolean plugin_unload(PurplePlugin *plugin)
{
PurplePlugin *irc_prpl;
PurplePluginProtocolInfo *prpl_info;
GList *list;
irc_prpl = purple_plugins_find_with_id(IRC_PLUGIN_ID);
if (NULL == irc_prpl)
return FALSE;
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(irc_prpl);
if (NULL == prpl_info)
return FALSE;
list = prpl_info->protocol_options;
/* Remove protocol preferences. */
while (NULL != list)
{
PurpleAccountOption *option = (PurpleAccountOption *) list->data;
if (g_str_has_prefix(purple_account_option_get_setting(option), PLUGIN_ID "_"))
{
GList *llist = list;
/* Remove this element from the list. */
if (llist->prev)
llist->prev->next = llist->next;
if (llist->next)
llist->next->prev = llist->prev;
purple_account_option_destroy(option);
list = g_list_next(list);
g_list_free_1(llist);
}
else
list = g_list_next(list);
}
return TRUE;
}
static void plugin_init(PurplePlugin *plugin)
{
info.dependencies = g_list_append(info.dependencies, IRC_PLUGIN_ID);
#ifdef ENABLE_NLS
bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */
info.name = _("IRC Helper");
info.summary = _("Handles the rough edges of the IRC protocol.");
info.description = _("- Transparent authentication with a variety of "
"services.\n- Suppression of various useless messages");
}
PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, plugin_init, info)