irssi/src/irc/core/irc-nicklist.c
Timo Sirainen f354fe54c7 Moved some stuff from irc to core. Added command_bind_proto() function to
bind protocol-specific commands. Added #define command_bind_irc() for easier
access. CMD_IRC_SERVER(server) check should be done at the beginning of each
command requiring IRC server as active server, it handles it correctly the
cases when it is not. Did some other cleanups as well.


git-svn-id: http://svn.irssi.org/repos/irssi/trunk@1955 dbcabf3a-b0e7-0310-adc4-f8d773084564
2001-11-02 01:05:14 +00:00

405 lines
11 KiB
C

/*
irc-nicklist.c : irssi
Copyright (C) 1999-2000 Timo Sirainen
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 "module.h"
#include "signals.h"
#include "misc.h"
#include "irc-servers.h"
#include "irc-channels.h"
#include "irc-masks.h"
#include "irc-nicklist.h"
#include "modes.h"
#include "servers.h"
/* Add new nick to list */
NICK_REC *irc_nicklist_insert(IRC_CHANNEL_REC *channel, const char *nick,
int op, int voice, int send_massjoin)
{
NICK_REC *rec;
g_return_val_if_fail(IS_IRC_CHANNEL(channel), NULL);
g_return_val_if_fail(nick != NULL, NULL);
rec = g_new0(NICK_REC, 1);
rec->nick = g_strdup(nick);
if (op) rec->op = TRUE;
if (voice) rec->voice = TRUE;
rec->send_massjoin = send_massjoin;
nicklist_insert(CHANNEL(channel), rec);
return rec;
}
#define isnickchar(a) \
(isalnum((int) (a)) || (a) == '`' || (a) == '-' || (a) == '_' || \
(a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \
(a) == '|' || (a) == '\\' || (a) == '^')
/* Remove all "extra" characters from `nick'. Like _nick_ -> nick */
char *irc_nick_strip(const char *nick)
{
char *stripped, *spos;
g_return_val_if_fail(nick != NULL, NULL);
spos = stripped = g_strdup(nick);
while (isnickchar(*nick)) {
if (isalnum((int) *nick))
*spos++ = *nick;
nick++;
}
if ((unsigned char) *nick >= 128)
*spos++ = *nick; /* just add it so that nicks won't match.. */
*spos = '\0';
return stripped;
}
static void event_names_list(IRC_SERVER_REC *server, const char *data)
{
IRC_CHANNEL_REC *chanrec;
char *params, *type, *channel, *names, *ptr;
g_return_if_fail(data != NULL);
params = event_get_params(data, 4, NULL, &type, &channel, &names);
chanrec = irc_channel_find(server, channel);
if (chanrec == NULL || chanrec->names_got) {
/* unknown channel / names list already read */
g_free(params);
return;
}
/* type = '=' = public, '*' = private, '@' = secret.
This is actually pretty useless to check here, but at least we
get to know if the channel is +p or +s a few seconds before
we receive the MODE reply...
If the channel key is set, assume the channel is +k also until
we know better, so parse_channel_modes() won't clear the key */
if (*type == '*') {
parse_channel_modes(chanrec, NULL,
chanrec->key ? "+kp" : "+p");
} else if (*type == '@') {
parse_channel_modes(chanrec, NULL,
chanrec->key ? "+ks" : "+s");
}
while (*names != '\0') {
while (*names == ' ') names++;
ptr = names;
while (*names != '\0' && *names != ' ') names++;
if (*names != '\0') *names++ = '\0';
irc_nicklist_insert(chanrec, ptr+isnickflag(*ptr),
*ptr == '@', *ptr == '+', FALSE);
}
g_free(params);
}
static void event_end_of_names(IRC_SERVER_REC *server, const char *data)
{
char *params, *channel;
IRC_CHANNEL_REC *chanrec;
NICK_REC *ownnick;
int nicks;
g_return_if_fail(server != NULL);
params = event_get_params(data, 2, NULL, &channel);
chanrec = irc_channel_find(server, channel);
if (chanrec != NULL && !chanrec->names_got) {
ownnick = nicklist_find(CHANNEL(chanrec), server->nick);
if (ownnick == NULL) {
/* stupid server - assume we have ops
if channel is empty */
nicks = g_hash_table_size(chanrec->nicks);
ownnick = irc_nicklist_insert(chanrec, server->nick,
nicks == 0, FALSE,
FALSE);
}
nicklist_set_own(CHANNEL(chanrec), ownnick);
chanrec->chanop = chanrec->ownnick->op;
chanrec->names_got = TRUE;
signal_emit("channel joined", 1, chanrec);
}
g_free(params);
}
static void event_who(SERVER_REC *server, const char *data)
{
char *params, *nick, *channel, *user, *host, *stat, *realname, *hops;
CHANNEL_REC *chanrec;
NICK_REC *nickrec;
g_return_if_fail(data != NULL);
params = event_get_params(data, 8, NULL, &channel, &user, &host,
NULL, &nick, &stat, &realname);
/* get hop count */
hops = realname;
while (*realname != '\0' && *realname != ' ') realname++;
*realname++ = '\0';
while (*realname == ' ') realname++;
/* update host, realname, hopcount */
chanrec = channel_find(server, channel);
nickrec = chanrec == NULL ? NULL :
nicklist_find(chanrec, nick);
if (nickrec != NULL) {
if (nickrec->host == NULL) {
char *str = g_strdup_printf("%s@%s", user, host);
nicklist_set_host(chanrec, nickrec, str);
g_free(str);
}
if (nickrec->realname == NULL)
nickrec->realname = g_strdup(realname);
sscanf(hops, "%d", &nickrec->hops);
}
nicklist_update_flags(server, nick,
strchr(stat, 'G') != NULL, /* gone */
strchr(stat, '*') != NULL); /* ircop */
g_free(params);
}
static void event_whois(IRC_SERVER_REC *server, const char *data)
{
char *params, *nick, *realname;
GSList *nicks, *tmp;
NICK_REC *rec;
g_return_if_fail(data != NULL);
server->whois_coming = TRUE;
/* first remove the gone-flag, if user is gone
it will be set later.. */
params = event_get_params(data, 6, NULL, &nick, NULL,
NULL, NULL, &realname);
nicks = nicklist_get_same(SERVER(server), nick);
for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
rec = tmp->next->data;
if (rec->realname == NULL)
rec->realname = g_strdup(realname);
}
g_slist_free(nicks);
/* reset gone and ircop status, we'll handle them in the following
WHOIS replies */
nicklist_update_flags(SERVER(server), nick, FALSE, FALSE);
g_free(params);
}
static void event_whois_away(SERVER_REC *server, const char *data)
{
char *params, *nick, *awaymsg;
g_return_if_fail(data != NULL);
/* set user's gone flag.. */
params = event_get_params(data, 3, NULL, &nick, &awaymsg);
nicklist_update_flags(server, nick, TRUE, -1);
g_free(params);
}
static void event_whois_ircop(SERVER_REC *server, const char *data)
{
char *params, *nick, *awaymsg;
g_return_if_fail(data != NULL);
/* set user's gone flag.. */
params = event_get_params(data, 3, NULL, &nick, &awaymsg);
nicklist_update_flags(server, nick, -1, TRUE);
g_free(params);
}
static void event_end_of_whois(IRC_SERVER_REC *server, const char *data)
{
server->whois_coming = FALSE;
}
static void event_nick_in_use(IRC_SERVER_REC *server, const char *data)
{
char *str;
int n;
g_return_if_fail(data != NULL);
if (server->connected) {
/* Already connected, no need to handle this anymore. */
return;
}
/* nick already in use - need to change it .. */
if (strcmp(server->nick, server->connrec->nick) == 0 &&
server->connrec->alternate_nick != NULL) {
/* first try, so try the alternative nick.. */
g_free(server->nick);
server->nick = g_strdup(server->connrec->alternate_nick);
}
else if (strlen(server->nick) < 9) {
/* keep adding '_' to end of nick.. */
str = g_strdup_printf("%s_", server->nick);
g_free(server->nick);
server->nick = str;
} else {
/* nick full, keep adding number at the end */
for (n = 8; n > 0; n--) {
if (server->nick[n] < '0' || server->nick[n] > '9') {
server->nick[n] = '1';
break;
}
if (server->nick[n] < '9') {
server->nick[n]++;
break;
}
server->nick[n] = '0';
}
}
irc_send_cmdv(server, "NICK %s", server->nick);
}
static void event_target_unavailable(IRC_SERVER_REC *server, const char *data)
{
char *params, *channel;
g_return_if_fail(data != NULL);
params = event_get_params(data, 2, NULL, &channel);
if (!ischannel(*channel)) {
/* nick is unavailable. */
event_nick_in_use(server, data);
}
g_free(params);
}
static void event_nick(SERVER_REC *server, const char *data,
const char *orignick)
{
char *params, *nick;
g_return_if_fail(data != NULL);
params = event_get_params(data, 1, &nick);
if (g_strcasecmp(orignick, server->nick) == 0) {
/* You changed your nick */
server_change_nick(server, nick);
}
nicklist_rename(server, orignick, nick);
g_free(params);
}
static void event_userhost(SERVER_REC *server, const char *data)
{
char *params, *hosts, **phosts, **pos, *ptr;
g_return_if_fail(data != NULL);
/* set user's gone flag.. */
params = event_get_params(data, 2, NULL, &hosts);
phosts = g_strsplit(hosts, " ", -1);
for (pos = phosts; *pos != NULL; pos++) {
ptr = strchr(*pos, '=');
if (ptr == NULL) continue;
*ptr++ = '\0';
nicklist_update_flags(server, *pos, *ptr == '-', -1);
}
g_strfreev(phosts);
g_free(params);
}
static void sig_usermode(SERVER_REC *server)
{
g_return_if_fail(IS_SERVER(server));
nicklist_update_flags(server, server->nick, server->usermode_away, -1);
}
static const char *get_nick_flags(void)
{
return "@+%";
}
static void sig_connected(IRC_SERVER_REC *server)
{
if (IS_IRC_SERVER(server)) {
server->get_nick_flags =
(const char *(*)(void)) get_nick_flags;
}
}
void irc_nicklist_init(void)
{
signal_add_first("event nick", (SIGNAL_FUNC) event_nick);
signal_add_first("event 352", (SIGNAL_FUNC) event_who);
signal_add("silent event who", (SIGNAL_FUNC) event_who);
signal_add("silent event whois", (SIGNAL_FUNC) event_whois);
signal_add_first("event 311", (SIGNAL_FUNC) event_whois);
signal_add_first("event 301", (SIGNAL_FUNC) event_whois_away);
signal_add_first("event 313", (SIGNAL_FUNC) event_whois_ircop);
signal_add("event 318", (SIGNAL_FUNC) event_end_of_whois);
signal_add("event 353", (SIGNAL_FUNC) event_names_list);
signal_add("event 366", (SIGNAL_FUNC) event_end_of_names);
signal_add("event 433", (SIGNAL_FUNC) event_nick_in_use);
signal_add("event 437", (SIGNAL_FUNC) event_target_unavailable);
signal_add("event 302", (SIGNAL_FUNC) event_userhost);
signal_add("userhost event", (SIGNAL_FUNC) event_userhost);
signal_add("user mode changed", (SIGNAL_FUNC) sig_usermode);
signal_add("server connected", (SIGNAL_FUNC) sig_connected);
}
void irc_nicklist_deinit(void)
{
signal_remove("event nick", (SIGNAL_FUNC) event_nick);
signal_remove("event 352", (SIGNAL_FUNC) event_who);
signal_remove("silent event who", (SIGNAL_FUNC) event_who);
signal_remove("silent event whois", (SIGNAL_FUNC) event_whois);
signal_remove("event 311", (SIGNAL_FUNC) event_whois);
signal_remove("event 301", (SIGNAL_FUNC) event_whois_away);
signal_remove("event 313", (SIGNAL_FUNC) event_whois_ircop);
signal_remove("event 318", (SIGNAL_FUNC) event_end_of_whois);
signal_remove("event 353", (SIGNAL_FUNC) event_names_list);
signal_remove("event 366", (SIGNAL_FUNC) event_end_of_names);
signal_remove("event 433", (SIGNAL_FUNC) event_nick_in_use);
signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable);
signal_remove("event 302", (SIGNAL_FUNC) event_userhost);
signal_remove("userhost event", (SIGNAL_FUNC) event_userhost);
signal_remove("user mode changed", (SIGNAL_FUNC) sig_usermode);
signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
}