Display detailed usercounts in !peek (ops/voices/regulars)
[srvx.git] / src / chanserv.c
index 2780729b7c2e4d52b5b6865688da4d86cf9daea5..e0ec5200d9a980cce6b453ee1ec062b25f48a85e 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";
     }
@@ -4494,13 +4509,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 +4524,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");
@@ -6498,11 +6524,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)
     {
@@ -6553,7 +6577,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;
@@ -6621,7 +6645,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))
@@ -6708,17 +6732,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);
     }
 }
@@ -6735,7 +6768,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);
     }
 
@@ -7507,7 +7540,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);