#include "irc_chan.h"
#include "irc_proto.h"
#include "log.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
const char* irc_chan_name (struct irc_chan *chan)
{
return chan ? chan->info.channel : "NULL";
}
/**
* Invoke the named irc_chan_callbacks field with the given args
*/
#define IRC_CHAN_INVOKE_CALLBACK(chan, _cb_name_, ...) \
do { \
struct irc_chan_callback_item *item; \
\
CHAIN_FOREACH_SAFE(&(chan)->callbacks, item) { \
if (item->callbacks->_cb_name_) \
item->callbacks->_cb_name_((chan), ## __VA_ARGS__, item->arg); \
} \
} while (0)
/**
* Add or update a nickname to the irc_chan.users list.
*
* If the given nickname already exists in our users list, this does nothing. Otherwise, an irc_user is aquired using
* irc_net_get_user, and a new irc_chan_user struct added to our users list.
*/
static err_t irc_chan_add_user (struct irc_chan *chan, const char *nickname)
{
struct irc_user *user;
struct irc_chan_user *chan_user;
err_t err;
// skip if already listed
if (irc_chan_get_user(chan, nickname))
return SUCCESS;
// lookup/create the irc_user state, incrementing the refcount
if ((err = irc_net_get_user(chan->net, &user, nickname)))
return err;
// alloc the new irc_chan_user
if ((chan_user = calloc(1, sizeof(*chan_user))) == NULL) {
// XXX: release irc_user
return ERR_CALLOC;
}
// store
chan_user->user = user;
// add to users list
LIST_INSERT_HEAD(&chan->users, chan_user, chan_users);
// ok
return SUCCESS;
}
/**
* Remove an irc_chan_user previously added by irc_chan_add_user().
*/
static void irc_chan_remove_user (struct irc_chan *chan, struct irc_chan_user *chan_user)
{
// put the irc_user reference back, decrementing refcount
irc_net_put_user(chan->net, chan_user->user);
// remove from list
LIST_REMOVE(chan_user, chan_users);
// free
free(chan_user);
}
/**
* :nm JOIN <channel>
*/
static void irc_chan_on_JOIN (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
err_t err;
if (irc_conn_self(chan->net->conn, line->source->nickname)) {
// twiddle state
chan->joining = false;
chan->joined = true;
// invoke callback
IRC_CHAN_INVOKE_CALLBACK(chan, on_self_join);
} else {
// add them
if ((err = irc_chan_add_user(chan, line->source->nickname)))
return log_warn("irc_chan_add_user(%s, %s): %s", irc_chan_name(chan), line->source->nickname, error_name(err));
// invoke callback (source)
IRC_CHAN_INVOKE_CALLBACK(chan, on_join, line->source);
}
}
/**
* :nm PRIVMSG <channel> <message>
*/
static void irc_chan_on_PRIVMSG (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
const char *msg = line->args[1];
// invoke callback (source, message)
IRC_CHAN_INVOKE_CALLBACK(chan, on_msg, line->source, msg);
}
/**
* Add/update nicknames to users list using irc_chan_add_user
*
* @see IRC_RPL_NAMREPLY
*/
static void irc_chan_on_RPL_NAMREPLY (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
char chanflags[IRC_CHANFLAGS_MAX];
const char *nickname;
err_t err;
// the args
const char *arg_names = line->args[3];
// copy the nicklist to a mutable buffer
char names_buf[strlen(arg_names) + 1], *names = names_buf;
strcpy(names, arg_names);
// iterate over each name
while ((nickname = strsep(&names, " "))) {
// skip empty token at end
if (strlen(nickname) == 0)
continue;
// parse off the channel flags
nickname = irc_nick_chanflags(nickname, chanflags);
// add/update
// XXX: do something with chanflags
if ((err = irc_chan_add_user(chan, nickname)))
log_warn("irc_chan_add_user(%s, %s): %s", irc_chan_name(chan), nickname, error_name(err));
}
}
/**
* Channel join sequence complete
*
* @see IRC_RPL_ENDOFNAMES
*/
static void irc_chan_on_RPL_ENDOFNAMES (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
(void) line;
(void) chan;
// XXX: update state
log_info("channel join sync complete");
}
/**
* Someone, or us ourselves, left a channel
*
* :nm PART <channel> [<message>]
*/
static void irc_chan_on_PART (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
const char *msg = line->args[1];
if (irc_conn_self(chan->net->conn, line->source->nickname)) {
// twiddle state
chan->joined = false;
chan->parted = true;
// invoke callback
IRC_CHAN_INVOKE_CALLBACK(chan, on_self_part);
// XXX: cleanup
} else {
// someone else
struct irc_chan_user *chan_user;
// invoke callback (source, msg)
IRC_CHAN_INVOKE_CALLBACK(chan, on_part, line->source, msg);
// look up the irc_chan_user
if ((chan_user = irc_chan_get_user(chan, line->source->nickname)) == NULL)
return log_warn("PART'd user not on channel: %s, %s", irc_chan_name(chan), line->source->nickname);
// remove them
irc_chan_remove_user(chan, chan_user);
}
}
/**
* :nm KICK <channel> <target> [<reason>]
*
* User kicked some other user, might be us
*/
static void irc_chan_on_KICK (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
struct irc_chan_user *target_user;
const char *target = line->args[1];
const char *msg = line->args[2];
if (irc_conn_self(chan->net->conn, target)) {
// twiddle state
chan->joined = false;
chan->kicked = true;
// invoke callback (source, msg)
IRC_CHAN_INVOKE_CALLBACK(chan, on_self_kicked, line->source, msg);
// XXX: cleanup
} else {
// look up the target irc_chan_user
if ((target_user = irc_chan_get_user(chan, target)) == NULL)
return log_warn("KICK'd user not on channel: %s, %s", irc_chan_name(chan), target);
// invoke callback (source, target_user, msg)
IRC_CHAN_INVOKE_CALLBACK(chan, on_kick, line->source, target_user, msg);
// remove them
irc_chan_remove_user(chan, target_user);
}
}
/**
* :nm QUIT [<message>]
*
* User quit, so remove them from our users list
*/
static void irc_chan_on_QUIT (const struct irc_line *line, void *arg)
{
struct irc_chan *chan = arg;
struct irc_chan_user *chan_user;
const char *msg = line->args[1];
// invoke callback (source, msg)
IRC_CHAN_INVOKE_CALLBACK(chan, on_quit, line->source, msg);
// look up the irc_chan_user
if ((chan_user = irc_chan_get_user(chan, line->source->nickname)) == NULL)
return log_warn("QUIT'd user not on channel: %s, %s", irc_chan_name(chan), line->source->nickname);
// remove them
irc_chan_remove_user(chan, chan_user);
}
/**
* Core command handlers
*/
struct irc_cmd_handler _cmd_handlers[] = {
{ "JOIN", &irc_chan_on_JOIN },
{ IRC_RPL_NAMREPLY, &irc_chan_on_RPL_NAMREPLY },
{ IRC_RPL_ENDOFNAMES, &irc_chan_on_RPL_ENDOFNAMES },
{ "PRIVMSG", &irc_chan_on_PRIVMSG },
{ "PART", &irc_chan_on_PART },
{ "KICK", &irc_chan_on_KICK },
{ "QUIT", &irc_chan_on_QUIT },
{ NULL, NULL }
};
err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, struct error_info *err)
{
struct irc_chan *chan;
// allocate
if ((chan = calloc(1, sizeof(*chan))) == NULL)
return SET_ERROR(err, ERR_CALLOC);
// store
chan->net = net;
// copy info
if ((chan->info.channel = strdup(info->channel)) == NULL)
JUMP_SET_ERROR(err, ERR_STRDUP);
// init
LIST_INIT(&chan->users);
irc_cmd_init(&chan->handlers);
CHAIN_INIT(&chan->callbacks);
// add handlers
if ((ERROR_CODE(err) = irc_cmd_add(&chan->handlers, _cmd_handlers, chan)))
goto error;
// ok
*chan_ptr = chan;
return SUCCESS;
error:
// cleanup
irc_chan_destroy(chan);
return ERROR_CODE(err);
}
void irc_chan_destroy (struct irc_chan *chan)
{
struct irc_chan_user *chan_user;
// free users
while ((chan_user = LIST_FIRST(&chan->users))) {
irc_chan_remove_user(chan, chan_user);
}
// free chan itself
irc_cmd_clear(&chan->handlers);
CHAIN_CLEAR(&chan->callbacks);
free((char *) chan->info.channel);
free(chan);
}
err_t irc_chan_add_callbacks (struct irc_chan *chan, const struct irc_chan_callbacks *callbacks, void *arg)
{
struct irc_chan_callback_item *item;
// create a new item
if ((item = CHAIN_ADD_TAIL(&chan->callbacks)) == NULL)
return ERR_MEM;
// store
item->callbacks = callbacks;
item->arg = arg;
// ok
return SUCCESS;
}
void irc_chan_remove_callbacks (struct irc_chan *chan, const struct irc_chan_callbacks *callbacks, void *arg)
{
struct irc_chan_callback_item *item;
// remove all matching callback_items
CHAIN_DELETE_WHICH(&chan->callbacks, item, item->callbacks == callbacks && item->arg == arg);
}
struct irc_chan_user* irc_chan_get_user (struct irc_chan *chan, const char *nickname)
{
struct irc_chan_user *chan_user = NULL;
// look for it...
LIST_FOREACH(chan_user, &chan->users, chan_users) {
if (irc_cmp_nick(nickname, chan_user->user->nickname) == 0) {
// found
return chan_user;
}
}
// not found
return NULL;
}
err_t irc_chan_join (struct irc_chan *chan)
{
err_t err;
// correct state
if (chan->joining || chan->joined)
return ERR_IRC_CHAN_STATE;
if (!chan->net->conn)
return ERR_IRC_NET_STATE;
// send JOIN message on the appropriate connection
if ((err = irc_conn_JOIN(chan->net->conn, chan->info.channel)))
// XXX: error state?
return err;
// ok
chan->joining = true;
return SUCCESS;
}
err_t irc_chan_PRIVMSG (struct irc_chan *chan, const char *message)
{
err_t err;
// correct state
if (!chan->joined)
return ERR_IRC_CHAN_STATE;
if (!chan->net->conn)
return ERR_IRC_NET_STATE;
// send the PRIVMSG message
if ((err = irc_conn_PRIVMSG(chan->net->conn, chan->info.channel, message)))
return err;
// invoke callback (msg)
IRC_CHAN_INVOKE_CALLBACK(chan, on_self_msg, message);
// ok
return SUCCESS;
}
err_t irc_chan_NOTICE (struct irc_chan *chan, const char *message)
{
err_t err;
// correct state
if (!chan->joined)
return ERR_IRC_CHAN_STATE;
if (!chan->net->conn)
return ERR_IRC_NET_STATE;
// send the PRIVMSG message
if ((err = irc_conn_NOTICE(chan->net->conn, chan->info.channel, message)))
return err;
// invoke callback (msg)
IRC_CHAN_INVOKE_CALLBACK(chan, on_self_notice, message);
// ok
return SUCCESS;
}