/* HexChat * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include "hexchat-plugin.h" static hexchat_plugin *ph; static char name[] = "ZNC Extras"; static char desc[] = "Integrate with optional ZNC plugins"; static char version[] = "1.0"; /* Playback: http://wiki.znc.in/Playback */ /* This should behave like this: * On connect (end of MOTD): * if new server, play all * if old server, play all after latest timestamp * On new query: * play all * On close query: * clear all * On new message: * update latest timestamp */ static GHashTable *playback_servers = NULL; #define PLAYBACK_CAP "znc.in/playback" static int get_current_context_type (void) { int type = 0; hexchat_list *list; hexchat_context *cur_ctx; list = hexchat_list_get (ph, "channels"); if (!list) return 0; cur_ctx = hexchat_get_context (ph); while (hexchat_list_next (ph, list)) { if ((hexchat_context*)hexchat_list_str (ph, list, "context") == cur_ctx) { type = hexchat_list_int (ph, list, "type"); break; } } hexchat_list_free (ph, list); return type; } static int get_current_context_id (void) { gint id; if (hexchat_get_prefs (ph, "id", NULL, &id) != 2) return -1; return id; } static int capls_cb (char *word[], void *userdata) { if (strstr (word[2], PLAYBACK_CAP) != NULL) hexchat_command (ph, "quote CAP REQ :" PLAYBACK_CAP); return HEXCHAT_EAT_NONE; } static int capack_cb (char *word[], void *userdata) { if (strstr (word[2], PLAYBACK_CAP) != NULL) { int id; /* FIXME: emit this after request but before ack.. */ hexchat_emit_print (ph, "Capability Request", PLAYBACK_CAP, NULL); id = get_current_context_id (); if (!g_hash_table_contains (playback_servers, GINT_TO_POINTER(id))) g_hash_table_insert (playback_servers, GINT_TO_POINTER(id), g_strdup ("0")); } return HEXCHAT_EAT_NONE; } static int connected_cb (char *word[], char *word_eol[], void *userdata) { int id = get_current_context_id (); if (g_hash_table_contains (playback_servers, GINT_TO_POINTER(id))) { g_print("connected\n"); char *timestamp = g_hash_table_lookup (playback_servers, GINT_TO_POINTER(id)); hexchat_commandf (ph, "quote PRIVMSG *playback :play * %s", timestamp); } return HEXCHAT_EAT_NONE; } static int opencontext_cb (char *word[], void *userdata) { #if 0 int id = get_current_context_id (); if (g_hash_table_contains (playback_servers, GINT_TO_POINTER(id)) && get_current_context_type () == 3) { const char *channel = hexchat_get_info (ph, "channel"); char *time_str = g_hash_table_lookup (playback_servers, GINT_TO_POINTER(id)); hexchat_commandf (ph, "quote PRIVMSG *playback :play %s %s", channel, time_str); } #endif return HEXCHAT_EAT_NONE; } static int closecontext_cb (char *word[], void *userdata) { int id = get_current_context_id (); if (g_hash_table_contains (playback_servers, GINT_TO_POINTER(id))) { int type = get_current_context_type (); if (type == 3) /* Dialog */ { const char *channel = hexchat_get_info (ph, "channel"); hexchat_commandf (ph, "quote PRIVMSG *playback :clear %s", channel); } else if (type == 1) /* Server */ { g_hash_table_remove (playback_servers, GINT_TO_POINTER(id)); } } return HEXCHAT_EAT_NONE; } static int chanactivity_cb (char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata) { int id; if (!attrs->server_time_utc) return HEXCHAT_EAT_NONE; id = get_current_context_id (); if (g_hash_table_contains (playback_servers, GINT_TO_POINTER(id))) { char *time_str; time_str = g_strdup_printf ("%ld", attrs->server_time_utc); g_hash_table_replace (playback_servers, GINT_TO_POINTER(id), time_str); } return HEXCHAT_EAT_NONE; } /* End Playback */ /* Buffextras: http://wiki.znc.in/Buffextras */ static inline char * strip_brackets (char *str) { str++; str[strlen(str) - 1] = '\0'; return str; } static int buffextras_cb (char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata) { char *channel, *user_nick, *user_host = NULL, *p; if (g_ascii_strncasecmp (word[1] + 1, "*buffextras", 11) != 0) return HEXCHAT_EAT_NONE; channel = word[3]; user_nick = word[4] + 1; p = strchr (user_nick, '!'); if (p != NULL) { *p = '\0'; user_host = p + 1; } /* This might be gross, but there are a lot of repeated calls.. */ #define EMIT(x,...) hexchat_emit_print_attrs(ph,attrs,x,__VA_ARGS__,NULL) #define IS_EVENT(x) g_str_has_prefix(word_eol[5],x) if (IS_EVENT ("joined")) { EMIT ("Join", user_nick, channel, user_host); } else if (IS_EVENT ("quit with message")) { EMIT ("Quit", user_nick, strip_brackets(word_eol[7]), user_host); } else if (IS_EVENT ("parted with message")) { char *reason = strip_brackets(word_eol[7]); if (*reason) EMIT ("Part with Reason", user_nick, user_host, channel, reason); else EMIT ("Part", user_nick, user_host, channel); } else if (IS_EVENT ("is now known as")) { EMIT ("Change Nick", user_nick, word[9]); } else if (IS_EVENT ("set mode")) { char *prefix = (word[7][0] == '+' ? "+" : "-"); EMIT ("Channel Mode Generic", user_nick, prefix, word[7] + 1, channel); } else if (IS_EVENT ("changed the topic to")) { EMIT ("Topic Change", user_nick, word_eol[9], channel); } else if (IS_EVENT ("kicked")) /* X Reason: [Y] */ { EMIT ("Kick", user_nick, word[6], channel, strip_brackets(word_eol[8])); } else { return HEXCHAT_EAT_NONE; /* We don't know, let it print */ } return HEXCHAT_EAT_ALL; } /* End Buffextras */ /* Privmsg: http://wiki.znc.in/Privmsg */ static gboolean prefix_is_channel (char prefix) { gboolean is_channel = TRUE; /* Defaults to TRUE as being wrong would be annoying */ int id; hexchat_list *list; if (hexchat_get_prefs (ph, "id", NULL, &id) != 2) return TRUE; list = hexchat_list_get (ph, "channels"); if (!list) return TRUE; while (hexchat_list_next (ph, list)) { if (hexchat_list_int (ph, list, "id") == id) { const char *chantypes = hexchat_list_str (ph, list, "chantypes"); if (strchr (chantypes, prefix) == NULL) is_channel = FALSE; break; } } hexchat_list_free (ph, list); return is_channel; } static int privmsg_cb (char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata) { char *sender, *recipient, *msg, *p; const char *network, *mynick; if (prefix_is_channel (word[3][0])) return HEXCHAT_EAT_NONE; mynick = hexchat_get_info (ph, "nick"); network = hexchat_get_info (ph, "network"); recipient = word[3]; msg = word_eol[4] + 1; sender = word[1] + 1; p = strchr (sender, '!'); if (p != NULL) *p = '\0'; if (hexchat_nickcmp (ph, sender, mynick) == 0 && hexchat_nickcmp (ph, recipient, mynick) != 0) { hexchat_context *new_ctx; char *event; hexchat_commandf (ph, "query -nofocus %s", recipient); new_ctx = hexchat_find_context (ph, network, recipient); hexchat_set_context (ph, new_ctx); if (g_ascii_strncasecmp (msg, "\001ACTION", 7) == 0) { msg += 7; msg[strlen (msg) - 1] = '\0'; msg = g_strstrip (msg); event = "Your Action"; } else { event = "Your Message"; } hexchat_emit_print_attrs (ph, attrs, event, mynick, msg, NULL); return HEXCHAT_EAT_ALL; } return HEXCHAT_EAT_NONE; } /* End Privmsg */ int hexchat_plugin_init (hexchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg) { ph = plugin_handle; *plugin_name = name; *plugin_desc = desc; *plugin_version = version; /* Privmsg */ hexchat_hook_server_attrs (ph, "PRIVMSG", HEXCHAT_PRI_HIGH, privmsg_cb, NULL); /* Buffextras */ hexchat_hook_server_attrs (ph, "PRIVMSG", HEXCHAT_PRI_HIGHEST, buffextras_cb, NULL); /* Playback */ playback_servers = g_hash_table_new_full (NULL, NULL, NULL, g_free); hexchat_hook_print (ph, "Capability List", HEXCHAT_PRI_NORM, capls_cb, NULL); hexchat_hook_print (ph, "Capability Acknowledgement", HEXCHAT_PRI_NORM, capack_cb, NULL); hexchat_hook_print (ph, "Open Context", HEXCHAT_PRI_NORM, opencontext_cb, NULL); hexchat_hook_print (ph, "Close Context", HEXCHAT_PRI_NORM, closecontext_cb, NULL); hexchat_hook_server (ph, "376", HEXCHAT_PRI_NORM, connected_cb, NULL); /* FIXME: Events buffextras uses */ hexchat_hook_server_attrs (ph, "PRIVMSG", HEXCHAT_PRI_LOW, chanactivity_cb, NULL); hexchat_printf (ph, "%s plugin loaded\n", name); return 1; } int hexchat_plugin_deinit (void) { g_hash_table_unref (playback_servers); hexchat_printf (ph, "%s plugin unloaded\n", name); return 1; }