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