Mercurial > grim > purple-plugin-pack
view irchelper/irchelper.c @ 984:5af0906311d6 org.guifications.plugins
Added support for 'or' dependencies
author | grim@guifications.org |
---|---|
date | Wed, 10 Dec 2008 04:08:40 -0500 |
parents | a33f3e9ee132 |
children | 7d269443846b |
line wrap: on
line source
/* * IRC Helper Plugin for libpurple * * Copyright (C) 2005-2008, 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_FREENODE ".freenode.net" #define DOMAIN_SUFFIX_FUNCOM ".funcom.com" #define DOMAIN_SUFFIX_GAMESURGE ".gamesurge.net" #define DOMAIN_SUFFIX_THUNDERCITY ".thundercity.org" #define DOMAIN_SUFFIX_DALNET ".dal.net" #define DOMAIN_SUFFIX_JEUX ".jeux.fr" #define DOMAIN_SUFFIX_QUAKENET ".quakenet.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_FRENCH_ADVERTISEMENT_START "<B>Avertissement</B> : Le pseudo <B>" #define MESSAGE_FRENCH_ADVERTISEMENT_END "<votre pass>" #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_SPOOFING_YOUR_IP "*** Spoofing your IP. congrats." #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'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" /* Generic AuthServ, not currently used for any networks. */ #define NICK_AUTHSERV "AuthServ" #define NICK_CHANSERV "ChanServ" #define NICK_FREENODE_CONNECT "freenode-connect" #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 4000 #define TIMEOUT_KILLING_GHOST 4000 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, } 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; 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; 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; g_free(username); return type; } static gboolean auth_timeout(gpointer 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"); g_hash_table_insert(states, proto_data, GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_DID_ID)); } 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\n", authentication); 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); /* 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->proto_data); } 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: %s\n", authentication); 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); /* 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->proto_data); } 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; purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: %s\n", authentication); 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)proto_data); } static void nickserv_identify(gpointer proto_data, PurpleConnection *gc, const char *nickpassword) { char *authentication = g_strdup_printf("quote %s IDENTIFY %s", NICK_NICKSERV, nickpassword); 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 %s", NICK_NICKSERV, command, nickpassword); 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; } userparts = g_strsplit(purple_account_get_username(stuff->account), "@", 2); /* Switch back to the normal nickname. */ 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: %s\n", command); 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; } g_strfreev(userparts); if (state & IRC_NETWORK_TYPE_THUNDERCITY) nickserv_msg_identify("AUTH", connection->proto_data, connection, nickpassword); else nickserv_identify(connection->proto_data, connection, nickpassword); } } 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 && !strncmp(name, ar->name, sizeof(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)) { 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 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; } /* 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_hash_table_insert(states, connection->proto_data, GINT_TO_POINTER((state & ~IRC_KILLING_GHOST & ~IRC_WILL_ID) | 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)) { g_hash_table_insert(states, connection->proto_data, GINT_TO_POINTER((state & ~IRC_KILLING_GHOST & ~IRC_WILL_ID) | 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)) g_hash_table_insert(states, connection->proto_data, GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_DID_ID)); /* The identification has failed. */ if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_GAMESURGE_AUTHSERV_ID_FAILURE)) { g_hash_table_insert(states, connection->proto_data, GINT_TO_POINTER((state & ~IRC_WILL_ID) | 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)) g_hash_table_insert(states, connection->proto_data, GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_DID_ID)); /* The identification has failed. */ if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_ID_FAILURE)) { g_hash_table_insert(states, connection->proto_data, GINT_TO_POINTER((state & ~IRC_WILL_ID) | 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", ""); purple_account_option_set_masked(option, TRUE); 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", ""); purple_account_option_set_masked(option, TRUE); 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); 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)