Mercurial > pidgin
view src/protocols/irc/parse.c @ 8986:8cf32769ba1b
[gaim-migrate @ 9761]
" This patch adds a Plugin Actions menu item after the
Account Actions menu. The Plugin Actions menu is
populated from the added 'actions' slot in
GaimPluginInfo. As a demonstration, the Idle Maker
plugin has been converted to no longer require GTK code
and the Preferences interface just to perform its
actions. Instead, it uses a Plugin Action to spawn a
Fields Request.
There's also a minor fix for consistency in the menu
building for buddy actions. The pre-existing method for
instructing a menu list to display a separator was to
insert a NULL rather than a proto_buddy_menu into the
GList of actions. The code for the buddy menus was
instead checking for a proto_buddy_menu with a '-'
label. This has been fixed, and it now correctly uses
NULL to indicate a separator."
"Date: 2004-05-16 02:25
Sender: taliesein
Logged In: YES
user_id=77326
I need to add a callback to this patch to watch for
loading/unloading of plugins (to determine when to rebuild
the menu). Since the appropriate way to handle Plugin
Actions is still mildly up for debate, I'm holding of on
correcting the patch until I know for sure whether I should
fix this patch, or scrap it and write a new one using a
different method."
"Date: 2004-05-18 12:26
Sender: taliesein
Logged In: YES
user_id=77326
I've completed changes to this patch to also add plugin load
and unload signals (it looks like plugin.c actually had
pre-signal callbacks in place, but they were never used or
converted to signals)
This patch now will correctly update the Plugin Action menu
as plugins load and unload."
I'm not entirely sure i like the ui of a plugins actions menu, but i think
that having some way for plugins to add actions on an account is a good
thing, and i'm not sure that every viable action fits under the accounts
actions menu. we may want to merge the two (the existing accounts actions
and this plugins actions), but both times it came up in #gaim no one seemed
to want to comment, and on one commented to the gaim-devel post either.
committer: Tailor Script <tailor@pidgin.im>
| author | Luke Schierer <lschiere@pidgin.im> |
|---|---|
| date | Thu, 20 May 2004 05:11:44 +0000 |
| parents | c60f82d78dea |
| children | 933a19e3a6b3 |
line wrap: on
line source
/** * @file parse.c * * gaim * * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu> * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "internal.h" #include "accountopt.h" #include "conversation.h" #include "notify.h" #include "debug.h" #include "irc.h" #include <stdio.h> #include <stdlib.h> #include <ctype.h> static char *irc_send_convert(struct irc_conn *irc, const char *string); static char *irc_recv_convert(struct irc_conn *irc, const char *string); static void irc_parse_error_cb(struct irc_conn *irc, char *input); static char *irc_mirc_colors[16] = { "white", "black", "blue", "dark green", "red", "brown", "purple", "orange", "yellow", "green", "teal", "cyan", "light blue", "pink", "grey", "light grey" }; /*typedef void (*IRCMsgCallback)(struct irc_conn *irc, char *from, char *name, char **args);*/ static struct _irc_msg { char *name; char *format; void (*cb)(struct irc_conn *irc, const char *name, const char *from, char **args); } _irc_msgs[] = { { "301", "nn:", irc_msg_away }, /* User is away */ { "303", "n:", irc_msg_ison }, /* ISON reply */ { "311", "nnvvv:", irc_msg_whois }, /* Whois user */ { "312", "nnv:", irc_msg_whois }, /* Whois server */ { "313", "nn:", irc_msg_whois }, /* Whois ircop */ { "317", "nnvv", irc_msg_whois }, /* Whois idle */ { "318", "nt:", irc_msg_endwhois }, /* End of WHOIS */ { "319", "nn:", irc_msg_whois }, /* Whois channels */ { "320", "nn:", irc_msg_whois }, /* Whois (fn ident) */ { "321", "*", irc_msg_list }, /* Start of list */ { "322", "ncv:", irc_msg_list }, /* List. */ { "323", ":", irc_msg_list }, /* End of list. */ { "324", "ncv:", irc_msg_chanmode }, /* Channel modes */ { "331", "nc:", irc_msg_topic }, /* No channel topic */ { "332", "nc:", irc_msg_topic }, /* Channel topic */ { "333", "*", irc_msg_ignore }, /* Topic setter stuff */ { "353", "nvc:", irc_msg_names }, /* Names list */ { "366", "nc:", irc_msg_names }, /* End of names */ { "372", "n:", irc_msg_motd }, /* MOTD */ { "375", "n:", irc_msg_motd }, /* Start MOTD */ { "376", "n:", irc_msg_endmotd }, /* End of MOTD */ { "401", "nt:", irc_msg_nonick }, /* No such nick/chan */ { "403", "nc:", irc_msg_nochan }, /* No such channel */ { "404", "nt:", irc_msg_nosend }, /* Cannot send to chan */ { "421", "nv:", irc_msg_unknown }, /* Unknown command */ { "422", "nv:", irc_msg_endmotd }, /* No MOTD available */ { "433", "vn:", irc_msg_nickused }, /* Nickname already in use */ { "438", "nn:", irc_msg_nochangenick }, /* Nick may not change */ { "442", "nc:", irc_msg_notinchan }, /* Not in channel */ { "473", "nc:", irc_msg_inviteonly }, /* Tried to join invite-only */ { "474", "nc:", irc_msg_banned }, /* Banned from channel */ { "482", "nc:", irc_msg_notop }, /* Need to be op to do that */ { "501", "n:", irc_msg_badmode }, /* Unknown mode flag */ { "506", "nc:", irc_msg_nosend }, /* Must identify to send */ { "515", "nc:", irc_msg_regonly }, /* Registration required */ { "invite", "n:", irc_msg_invite }, /* Invited */ { "join", ":", irc_msg_join }, /* Joined a channel */ { "kick", "cn:", irc_msg_kick }, /* KICK */ { "mode", "tv:", irc_msg_mode }, /* MODE for channel */ { "nick", ":", irc_msg_nick }, /* Nick change */ { "notice", "t:", irc_msg_notice }, /* NOTICE recv */ { "part", "c:", irc_msg_part }, /* Parted a channel */ { "ping", ":", irc_msg_ping }, /* Received PING from server */ { "pong", "v:", irc_msg_pong }, /* Received PONG from server */ { "privmsg", "t:", irc_msg_privmsg }, /* Received private message */ { "topic", "c:", irc_msg_topic }, /* TOPIC command */ { "quit", ":", irc_msg_quit }, /* QUIT notice */ { "wallops", ":", irc_msg_wallops }, /* WALLOPS command */ { NULL, NULL, NULL } }; static struct _irc_user_cmd { char *name; char *format; IRCCmdCallback cb; } _irc_cmds[] = { { "action", ":", irc_cmd_ctcp_action }, { "away", ":", irc_cmd_away }, { "deop", ":", irc_cmd_op }, { "devoice", ":", irc_cmd_op }, { "help", "v", irc_cmd_help }, { "invite", ":", irc_cmd_invite }, { "j", "cv", irc_cmd_join }, { "join", "cv", irc_cmd_join }, { "kick", "n:", irc_cmd_kick }, { "list", ":", irc_cmd_list }, { "me", ":", irc_cmd_ctcp_action }, { "mode", ":", irc_cmd_mode }, { "msg", "t:", irc_cmd_privmsg }, { "names", "c", irc_cmd_names }, { "nick", "n", irc_cmd_nick }, { "op", ":", irc_cmd_op }, { "operwall", ":", irc_cmd_wallops }, { "part", "c:", irc_cmd_part }, { "ping", "n", irc_cmd_ping }, { "query", "n:", irc_cmd_query }, { "quit", ":", irc_cmd_quit }, { "quote", "*", irc_cmd_quote }, { "remove", "n:", irc_cmd_remove }, { "topic", ":", irc_cmd_topic }, { "umode", ":", irc_cmd_mode }, { "voice", ":", irc_cmd_op }, { "wallops", ":", irc_cmd_wallops }, { "whois", "n", irc_cmd_whois }, { NULL, NULL, NULL } }; static char *irc_send_convert(struct irc_conn *irc, const char *string) { char *utf8; GError *err = NULL; utf8 = g_convert(string, strlen(string), gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET), "UTF-8", NULL, NULL, &err); if (err) { gaim_debug(GAIM_DEBUG_ERROR, "irc", "send conversion error: %s\n", err->message); gaim_debug(GAIM_DEBUG_ERROR, "irc", "Sending raw, which probably isn't right\n"); utf8 = g_strdup(string); g_error_free(err); } return utf8; } static char *irc_recv_convert(struct irc_conn *irc, const char *string) { char *utf8; GError *err = NULL; utf8 = g_convert(string, strlen(string), "UTF-8", gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET), NULL, NULL, &err); if (err) { gaim_debug(GAIM_DEBUG_ERROR, "irc", "recv conversion error: %s\n", err->message); utf8 = g_strdup(_("(There was an error converting this message. Check the 'Encoding' option in the Account Editor)")); g_error_free(err); } return utf8; } /* XXX tag closings are not necessarily correctly nested here! If we * get a ^O or reach the end of the string and there are open * tags, they are closed in a fixed order ... this means, for * example, you might see <FONT COLOR="blue">some text <B>with * various attributes</FONT></B> (notice that B and FONT overlap * and are not cleanly nested). This is imminently fixable but * I am not fixing it right now. */ char *irc_mirc2html(const char *string) { const char *cur, *end; char fg[3] = "\0\0", bg[3] = "\0\0"; int fgnum, bgnum; int font = 0, bold = 0, underline = 0; GString *decoded = g_string_sized_new(strlen(string)); cur = string; do { end = strpbrk(cur, "\002\003\007\017\026\037"); decoded = g_string_append_len(decoded, cur, end ? end - cur : strlen(cur)); cur = end ? end : cur + strlen(cur); switch (*cur) { case '\002': cur++; if (!bold) { decoded = g_string_append(decoded, "<B>"); bold = TRUE; } else { decoded = g_string_append(decoded, "</B>"); bold = FALSE; } break; case '\003': cur++; fg[0] = fg[1] = bg[0] = bg[1] = '\0'; if (isdigit(*cur)) fg[0] = *cur++; if (isdigit(*cur)) fg[1] = *cur++; if (*cur == ',') { cur++; if (isdigit(*cur)) bg[0] = *cur++; if (isdigit(*cur)) bg[1] = *cur++; } if (font) { decoded = g_string_append(decoded, "</FONT>"); font = FALSE; } if (fg[0]) { fgnum = atoi(fg); if (fgnum < 0 || fgnum > 15) continue; font = TRUE; g_string_append_printf(decoded, "<FONT COLOR=\"%s\"", irc_mirc_colors[fgnum]); if (bg[0]) { bgnum = atoi(bg); if (bgnum >= 0 && bgnum < 16) g_string_append_printf(decoded, " BACK=\"%s\"", irc_mirc_colors[bgnum]); } decoded = g_string_append_c(decoded, '>'); } break; case '\037': cur++; if (!underline) { decoded = g_string_append(decoded, "<U>"); underline = TRUE; } else { decoded = g_string_append(decoded, "</U>"); underline = TRUE; } break; case '\007': case '\026': cur++; break; case '\017': cur++; /* fallthrough */ case '\000': if (bold) decoded = g_string_append(decoded, "</B>"); if (underline) decoded = g_string_append(decoded, "</U>"); if (font) decoded = g_string_append(decoded, "</FONT>"); break; default: gaim_debug(GAIM_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur); } } while (*cur); return g_string_free(decoded, FALSE); } char *irc_mirc2txt (const char *string) { char *result = g_strdup (string); int i, j; for (i = 0, j = 0; result[i]; i++) { switch (result[i]) { case '\002': case '\003': case '\007': case '\017': case '\026': case '\037': continue; default: result[j++] = result[i]; } } result[i] = '\0'; return result; } char *irc_parse_ctcp(struct irc_conn *irc, const char *from, const char *to, const char *msg, int notice) { GaimConnection *gc; const char *cur = msg + 1; char *buf, *ctcp; time_t timestamp; /* Note that this is NOT correct w.r.t. multiple CTCPs in one * message and low-level quoting ... but if you want that crap, * use a real IRC client. */ if (msg[0] != '\001' || msg[strlen(msg) - 1] != '\001') return g_strdup(msg); if (!strncmp(cur, "ACTION ", 7)) { cur += 7; buf = g_strdup_printf("/me %s", cur); buf[strlen(buf) - 1] = '\0'; return buf; } else if (!strncmp(cur, "PING ", 5)) { if (notice) { /* reply */ sscanf(cur, "PING %lu", ×tamp); gc = gaim_account_get_connection(irc->account); if (!gc) return NULL; buf = g_strdup_printf(_("Reply time from %s: %lu seconds"), from, time(NULL) - timestamp); gaim_notify_info(gc, _("PONG"), _("CTCP PING reply"), buf); g_free(buf); return NULL; } else { buf = irc_format(irc, "vt:", "NOTICE", from, msg); irc_send(irc, buf); g_free(buf); gc = gaim_account_get_connection(irc->account); } } else if (!strncmp(cur, "VERSION", 7) && !notice) { buf = irc_format(irc, "vt:", "NOTICE", from, "\001VERSION Gaim IRC\001"); irc_send(irc, buf); g_free(buf); } else if (!strncmp(cur, "DCC SEND ", 9)) { irc_dccsend_recv(irc, from, msg + 10); return NULL; } ctcp = g_strdup(msg + 1); ctcp[strlen(ctcp) - 1] = '\0'; buf = g_strdup_printf("Received CTCP '%s' (to %s) from %s", ctcp, to, from); g_free(ctcp); return buf; } void irc_msg_table_build(struct irc_conn *irc) { int i; if (!irc || !irc->msgs) { gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a message table on a bogus structure\n"); return; } for (i = 0; _irc_msgs[i].name; i++) { g_hash_table_insert(irc->msgs, (gpointer)_irc_msgs[i].name, (gpointer)&_irc_msgs[i]); } } void irc_cmd_table_build(struct irc_conn *irc) { int i; if (!irc || !irc->cmds) { gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a command table on a bogus structure\n"); return; } for (i = 0; _irc_cmds[i].name ; i++) { g_hash_table_insert(irc->cmds, (gpointer)_irc_cmds[i].name, (gpointer)&_irc_cmds[i]); } } char *irc_format(struct irc_conn *irc, const char *format, ...) { GString *string = g_string_new(""); char *tok, *tmp; const char *cur; va_list ap; va_start(ap, format); for (cur = format; *cur; cur++) { if (cur != format) g_string_append_c(string, ' '); tok = va_arg(ap, char *); switch (*cur) { case 'v': g_string_append(string, tok); break; case ':': g_string_append_c(string, ':'); /* no break! */ case 't': case 'n': case 'c': tmp = irc_send_convert(irc, tok); g_string_append(string, tmp); g_free(tmp); break; default: gaim_debug(GAIM_DEBUG_ERROR, "irc", "Invalid format character '%c'\n", *cur); break; } } va_end(ap); g_string_append(string, "\r\n"); return (g_string_free(string, FALSE)); } void irc_parse_msg(struct irc_conn *irc, char *input) { struct _irc_msg *msgent; char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg; guint i; if (!strncmp(input, "PING ", 5)) { msg = irc_format(irc, "vv", "PONG", input + 5); irc_send(irc, msg); g_free(msg); return; } else if (!strncmp(input, "ERROR ", 6)) { gaim_connection_error(gaim_account_get_connection(irc->account), _("Disconnected.")); return; } if (input[0] != ':' || (cur = strchr(input, ' ')) == NULL) { irc_parse_error_cb(irc, input); return; } from = g_strndup(&input[1], cur - &input[1]); cur++; end = strchr(cur, ' '); if (!end) end = cur + strlen(cur); tmp = g_strndup(cur, end - cur); msgname = g_ascii_strdown(tmp, -1); g_free(tmp); if ((msgent = g_hash_table_lookup(irc->msgs, msgname)) == NULL) { irc_msg_default(irc, "", from, &input); g_free(msgname); g_free(from); return; } g_free(msgname); args = g_new0(char *, strlen(msgent->format)); for (cur = end, fmt = msgent->format, i = 0; fmt[i] && *cur++; i++) { switch (fmt[i]) { case 'v': if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); args[i] = g_strndup(cur, end - cur); cur += end - cur; break; case 't': case 'n': case 'c': if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); tmp = g_strndup(cur, end - cur); args[i] = irc_recv_convert(irc, tmp); g_free(tmp); cur += end - cur; break; case ':': if (*cur == ':') cur++; args[i] = irc_recv_convert(irc, cur); cur = cur + strlen(cur); break; case '*': args[i] = g_strdup(cur); cur = cur + strlen(cur); break; default: gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid message format character '%c'\n", fmt[i]); break; } } tmp = irc_recv_convert(irc, from); (msgent->cb)(irc, msgent->name, tmp, args); g_free(tmp); for (i = 0; i < strlen(msgent->format); i++) { g_free(args[i]); } g_free(args); g_free(from); } int irc_parse_cmd(struct irc_conn *irc, const char *target, const char *cmdstr) { const char *cur, *end, *fmt; char *tmp, *cmd, **args; struct _irc_user_cmd *cmdent; guint i; int ret; cur = cmdstr; end = strchr(cmdstr, ' '); if (!end) end = cur + strlen(cur); tmp = g_strndup(cur, end - cur); cmd = g_utf8_strdown(tmp, -1); g_free(tmp); if ((cmdent = g_hash_table_lookup(irc->cmds, cmd)) == NULL) { ret = irc_cmd_default(irc, cmd, target, &cmdstr); g_free(cmd); return ret; } args = g_new0(char *, strlen(cmdent->format)); for (cur = end, fmt = cmdent->format, i = 0; fmt[i] && *cur++; i++) { switch (fmt[i]) { case 'v': if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); args[i] = g_strndup(cur, end - cur); cur += end - cur; break; case 't': case 'n': case 'c': if (!(end = strchr(cur, ' '))) end = cur + strlen(cur); args[i] = g_strndup(cur, end - cur); cur += end - cur; break; case ':': case '*': args[i] = g_strdup(cur); cur = cur + strlen(cur); break; default: gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid command format character '%c'\n", fmt[i]); break; } } ret = (cmdent->cb)(irc, cmd, target, (const char **)args); for (i = 0; i < strlen(cmdent->format); i++) g_free(args[i]); g_free(args); g_free(cmd); return ret; } static void irc_parse_error_cb(struct irc_conn *irc, char *input) { gaim_debug(GAIM_DEBUG_WARNING, "irc", "Unrecognized string: %s\n", input); }
