Fix MIN_ARGC for ChanServ's !8ball and !d commands.
[srvx.git] / src / chanserv.c
index 8ff71ab5796816dfcd9eb94b76cffdcc7505b295..06a3d41e20b08a2eaa2afec2dee822f2cd552ad5 100644 (file)
@@ -404,7 +404,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_PEEK_INFO", "$b%s$b Status:" },
     { "CSMSG_PEEK_TOPIC", "$bTopic:          $b%s" },
     { "CSMSG_PEEK_MODES", "$bModes:          $b%s" },
-    { "CSMSG_PEEK_USERS", "$bTotal users:    $b%d" },
+    { "CSMSG_PEEK_USERS", "$bTotal users:    $b%d (%d ops, %d voices, %d regulars)" },
     { "CSMSG_PEEK_OPS", "$bOps:$b" },
     { "CSMSG_PEEK_NO_OPS", "$bOps:            $bNone present" },
 
@@ -438,7 +438,7 @@ static const struct message_entry msgtab[] = {
 
 /* User settings */
     { "CSMSG_USER_OPTIONS", "User Options:" },
-    { "CSMSG_USER_PROTECTED", "That user is protected." },
+    { "CSMSG_USER_PROTECTED_2", "That user is protected." },
 
 /* Toys */
     { "CSMSG_UNF_RESPONSE", "I don't want to be part of your sick fantasies!" },
@@ -1382,7 +1382,7 @@ expire_channels(UNUSED_ARG(void *data))
 
         /* Make sure there are no high-ranking users still in the channel. */
         for(user=channel->users; user; user=user->next)
-            if(user->present && (user->access >= UL_PRESENT))
+            if(user->present && (user->access >= UL_PRESENT) && !HANDLE_FLAGGED(user->handle, BOT))
                 break;
         if(user)
             continue;
@@ -2265,6 +2265,10 @@ static CHANSERV_FUNC(cmd_move)
     target->channel_info->channel = target;
     channel->channel_info = NULL;
 
+    /* Check whether users are present in the new channel. */
+    for(uData = target->channel_info->users; uData; uData = uData->next)
+        scan_user_presence(uData, NULL);
+
     reply("CSMSG_MOVE_SUCCESS", target->name);
 
     sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
@@ -2315,6 +2319,11 @@ merge_users(struct chanData *source, struct chanData *target)
             choice = (suData->seen > tuData->seen) ? suData : tuData;
         else /* Otherwise, keep the higher access level. */
             choice = (suData->access > tuData->access) ? suData : tuData;
+        /* Use the later seen time. */
+        if(suData->seen < tuData->seen)
+            suData->seen = tuData->seen;
+        else
+            tuData->seen = suData->seen;
 
         /* Remove the user that wasn't picked. */
         if(choice == tuData)
@@ -2343,6 +2352,9 @@ merge_users(struct chanData *source, struct chanData *target)
         /* Update the user counts for the target channel; the
            source counts are left alone. */
         target->userCount++;
+
+        /* Check whether the user is in the target channel. */
+        scan_user_presence(suData, NULL);
     }
 
     /* Possible to assert (source->users == NULL) here. */
@@ -2727,7 +2739,7 @@ cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short mi
         return 0;
     }
 
-    if((actor->access <= max_access) && !IsHelping(user))
+    if(actor->access <= max_access)
     {
         reply("CSMSG_NO_ACCESS");
         return 0;
@@ -3139,6 +3151,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     else if(!is_ircmask(argv[1]) && (*argv[1] == '*'))
     {
         struct handle_info *hi;
+        extern const char *titlehost_suffix;
         char banmask[NICKLEN + USERLEN + HOSTLEN + 3];
         const char *accountname = argv[1] + 1;
 
@@ -3148,7 +3161,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             return 0;
         }
 
-        snprintf(banmask, sizeof(banmask), "*!*@%s.*", hi->handle);
+        snprintf(banmask, sizeof(banmask), "*!*@%s.*.%s", hi->handle, titlehost_suffix);
         victims = alloca(sizeof(victims[0]) * channel->members.used);
 
         if(bad_channel_ban(channel, user, banmask, &victimCount, victims))
@@ -3951,6 +3964,8 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
             ary[3] = "Suspended";
         else if(HANDLE_FLAGGED(uData->handle, FROZEN))
             ary[3] = "Vacation";
+        else if(HANDLE_FLAGGED(uData->handle, BOT))
+            ary[3] = "Bot";
         else
             ary[3] = "Normal";
     }
@@ -4196,6 +4211,7 @@ static CHANSERV_FUNC(cmd_mode)
     struct userData *uData;
     struct mod_chanmode *change;
     short base_oplevel;
+    char fmt[MAXLEN];
 
     if(argc < 2)
     {
@@ -4215,7 +4231,7 @@ static CHANSERV_FUNC(cmd_mode)
         base_oplevel = 1;
     else
         base_oplevel = 1 + UL_OWNER - uData->access;
-    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED, base_oplevel);
+    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, base_oplevel);
     if(!change)
     {
         reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
@@ -4232,8 +4248,9 @@ static CHANSERV_FUNC(cmd_mode)
     }
 
     modcmd_chanmode_announce(change);
+    mod_chanmode_format(change, fmt);
     mod_chanmode_free(change);
-    reply("CSMSG_MODES_SET", unsplit_string(argv+1, argc-1, NULL));
+    reply("CSMSG_MODES_SET", fmt);
     return 1;
 }
 
@@ -4494,13 +4511,13 @@ static CHANSERV_FUNC(cmd_peek)
     char modes[MODELEN];
     unsigned int n;
     struct helpfile_table table;
+    int opcount = 0, voicecount = 0, srvcount = 0;
 
     irc_make_chanmode(channel, modes);
 
     reply("CSMSG_PEEK_INFO", channel->name);
     reply("CSMSG_PEEK_TOPIC", channel->topic);
     reply("CSMSG_PEEK_MODES", modes);
-    reply("CSMSG_PEEK_USERS", channel->members.used);
 
     table.length = 0;
     table.width = 1;
@@ -4509,12 +4526,23 @@ static CHANSERV_FUNC(cmd_peek)
     for(n = 0; n < channel->members.used; n++)
     {
         mn = channel->members.list[n];
+        if(IsLocal(mn->user))
+            srvcount++;
+        else if(mn->modes & MODE_CHANOP)
+            opcount++;
+        else if(mn->modes & MODE_VOICE)
+            voicecount++;
+
         if(!(mn->modes & MODE_CHANOP) || IsLocal(mn->user))
             continue;
         table.contents[table.length] = alloca(sizeof(**table.contents));
         table.contents[table.length][0] = mn->user->nick;
         table.length++;
     }
+
+    reply("CSMSG_PEEK_USERS", channel->members.used, opcount, voicecount,
+          (channel->members.used - opcount - voicecount - srvcount));
+
     if(table.length)
     {
         reply("CSMSG_PEEK_OPS");
@@ -4942,12 +4970,16 @@ chanserv_expire_suspension(void *data)
 {
     struct suspended *suspended = data;
     struct chanNode *channel;
+    unsigned int ii;
 
+    /* Update the channel registration data structure. */
     if(!suspended->expires || (now < suspended->expires))
         suspended->revoked = now;
     channel = suspended->cData->channel;
     suspended->cData->channel = channel;
     suspended->cData->flags &= ~CHANNEL_SUSPENDED;
+
+    /* If appropriate, re-join ChanServ to the channel. */
     if(!IsOffChannel(suspended->cData))
     {
         struct mod_chanmode change;
@@ -4957,6 +4989,17 @@ chanserv_expire_suspension(void *data)
         change.args[0].u.member = AddChannelUser(chanserv, channel);
         mod_chanmode_announce(chanserv, channel, &change);
     }
+
+    /* Mark everyone currently in the channel as present. */
+    for(ii = 0; ii < channel->members.used; ++ii)
+    {
+        struct userData *uData = GetChannelAccess(suspended->cData, channel->members.list[ii]->user->handle_info);
+        if(uData)
+        {
+            uData->present = 1;
+            uData->seen = now;
+        }
+    }
 }
 
 static CHANSERV_FUNC(cmd_csuspend)
@@ -5366,7 +5409,7 @@ static MODCMD_FUNC(chan_opt_usergreeting)
 static MODCMD_FUNC(chan_opt_modes)
 {
     struct mod_chanmode *new_modes;
-    char modes[MODELEN];
+    char modes[MAXLEN];
 
     if(argc > 1)
     {
@@ -5379,7 +5422,7 @@ static MODCMD_FUNC(chan_opt_modes)
         {
             memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
         }
-        else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED, 0)))
+        else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, 0)))
         {
             reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
             return 0;
@@ -6483,11 +6526,9 @@ handle_join(struct modeNode *mNode)
         }
     }
 
-    /* ChanServ will not modify the limits in join-flooded channels.
-       It will also skip DynLimit processing when the user (or srvx)
-       is bursting in, because there are likely more incoming. */
+    /* ChanServ will not modify the limits in join-flooded channels,
+       or when there are enough slots left below the limit. */
     if((cData->flags & CHANNEL_DYNAMIC_LIMIT)
-       && !user->uplink->burst
        && !channel->join_flooded
        && (channel->limit - channel->members.used) < chanserv_conf.adjust_threshold)
     {
@@ -6538,7 +6579,7 @@ handle_join(struct modeNode *mNode)
                 else if(uData->access >= cData->lvlOpts[lvlGiveVoice])
                     modes |= MODE_VOICE;
             }
-            if(uData->access >= UL_PRESENT)
+            if(uData->access >= UL_PRESENT && !HANDLE_FLAGGED(uData->handle, BOT))
                 cData->visited = now;
             if(cData->user_greeting)
                 greeting = cData->user_greeting;
@@ -6567,7 +6608,7 @@ handle_join(struct modeNode *mNode)
         }
         if(greeting)
             send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting);
-        if(uData && info)
+        if(uData && info && (modes || !(channel->modes & MODE_DELAYJOINS)))
             send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
     }
     return 0;
@@ -6606,7 +6647,7 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
             continue;
         }
 
-        if(channel->access >= UL_PRESENT)
+        if(channel->access >= UL_PRESENT && !HANDLE_FLAGGED(channel->handle, BOT))
             channel->channel->visited = now;
 
         if(IsUserAutoOp(channel))
@@ -6693,17 +6734,26 @@ handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
     {
         scan_user_presence(uData, mn->user);
         uData->seen = now;
-        if (uData->access >= UL_PRESENT)
+        if (uData->access >= UL_PRESENT && !HANDLE_FLAGGED(uData->handle, BOT))
             cData->visited = now;
     }
 
     if(IsHelping(mn->user) && IsSupportHelper(mn->user))
     {
         unsigned int ii;
-        for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
-             if(find_handle_in_channel(chanserv_conf.support_channels.list[ii], mn->user->handle_info, mn->user))
+        for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii) {
+            struct chanNode *channel;
+            struct userNode *exclude;
+            /* When looking at the channel that is being /part'ed, we
+             * have to skip over the client that is leaving.  For
+             * other channels, we must not do that.
+             */
+            channel = chanserv_conf.support_channels.list[ii];
+            exclude = (channel == mn->channel) ? mn->user : NULL;
+            if(find_handle_in_channel(channel, mn->user->handle_info, exclude))
                 break;
-         if(ii == chanserv_conf.support_channels.used)
+        }
+        if(ii == chanserv_conf.support_channels.used)
             HANDLE_CLEAR_FLAG(mn->user->handle_info, HELPING);
     }
 }
@@ -6720,7 +6770,7 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c
 
     if(protect_user(victim, kicker, channel->channel_info))
     {
-        const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED");
+        const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED_2");
         KickChannelUser(kicker, channel, chanserv, reason);
     }
 
@@ -7009,7 +7059,7 @@ chanserv_conf_read(void)
         str = "+nt";
     safestrncpy(mode_line, str, sizeof(mode_line));
     ii = split_line(mode_line, 0, ArrayLength(modes), modes);
-    if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE, 0))
+    if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
        && (change->argc < 2))
     {
         chanserv_conf.default_modes = *change;
@@ -7376,7 +7426,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     if(!IsSuspended(cData)
        && (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
        && (argc = split_line(str, 0, ArrayLength(argv), argv))
-       && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE, 0))) {
+       && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
         cData->modes = *modes;
         if(off_channel > 0)
           cData->modes.modes_set |= MODE_REGISTERED;
@@ -7492,7 +7542,7 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
     saxdb_start_record(ctx, KEY_USERS, 1);
     for(; uData; uData = uData->next)
     {
-        if((uData->access >= UL_PRESENT) && uData->present)
+        if((uData->access >= UL_PRESENT) && uData->present && !HANDLE_FLAGGED(uData->handle, BOT))
             high_present = 1;
         saxdb_start_record(ctx, uData->handle->handle, 0);
         saxdb_write_int(ctx, KEY_LEVEL, uData->access);
@@ -7858,8 +7908,8 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(unf, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(ping, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(wut, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-    DEFINE_COMMAND(8ball, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-    DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(8ball, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(d, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
 
     /* Channel options */