ソースを参照

Merge branch '21-botgroups'

* 21-botgroups: (31 commits)
  * Display unknown nodes as '(unknown)' and sort at top
  * Fix crash when there's no nodename
  * Sort cmd_bots output by nodename
  * Add '%group' support to 'bots' command.
  * Specify bot to cmd_groups to show groups for that bot
  * Fix cmd_channels showing 'inactive' on hub bots
  * Support group in cmd_channels
  * Const cleanup
  * Add command 'groups' to list all groups and which bots are in them.
  * Only match botcmd on non-hubs for groups
  * Remove unneeded botjoin/botpart
  * When overriding botset 'groups', auto include the global to avoid mistakingly making the bot part all channels immediately
  * Return the global set for default, then use the default
  * Fix groups not being 'main' if leaf had no local setting
  * Add botcmd support for groups
  * Update bdlib
  * Unpack all USERENTRY_SET regardless of being a hub
  * Share groups with all bots
  * Properly check the correct bots' groups in bot_shouldjoin
  * Remove obsolete code botshouldjoin()
  ...

Conflicts:
	doc/help.txt
Bryan Drewery 14 年 前
コミット
3ed8d7dd36

+ 8 - 0
doc/UPDATES

@@ -18,6 +18,14 @@
   * Add cmd_newhub for adding a new hub. All bots will add this to their config.
   * Add cmd_newhub for adding a new hub. All bots will add this to their config.
   * Hubs can now be overridden inside the botconfig (-C)
   * Hubs can now be overridden inside the botconfig (-C)
   * Optimize userfile writing by doing it asynchronously
   * Optimize userfile writing by doing it asynchronously
+  * Add [bot]set var 'groups' to configure what groups bots are in
+  * Add chanset 'groups' to take a list of groups that should join. 'chanset #chan groups { main backup }'
+  * Added group support to 'botcmd': 'botcmd %group cmd'
+  * 'botjoin' and 'botpart' have been removed. Just use .+chan, .chanset, .botset to control groups.
+  * Add command 'groups' to list all groups and which bots are in them.
+  * Command 'channels' now accepts a '%group' param to show which channels a group are in.
+  * Add '%group' support to 'bots' command.
+  * Improve output of 'bots' command by displaying and sorting nodename
 
 
 maint
 maint
   * Fix various compile warnings with newer GCC
   * Fix various compile warnings with newer GCC

+ 27 - 27
doc/help.txt

@@ -235,7 +235,7 @@ See also: -ban, +ban, console%{+m|m}, chanset, chaninfo%{-}, stick, unstick
    one. You can also specify a bot, and attempt to boot someone from another
    one. You can also specify a bot, and attempt to boot someone from another
    bot on the botnet. You can not boot a bot owner.
    bot on the botnet. You can not boot a bot owner.
 :hub:botcmd:
 :hub:botcmd:
-###  $bbotcmd$b <bot> <cmd> [params]
+###  $bbotcmd$b <bot|*|?|&|%%group> <cmd> [params]
    The specified cmd and optional parameters are executed on the specified bot, 
    The specified cmd and optional parameters are executed on the specified bot, 
    all results are displayed back on DCC. For example:
    all results are displayed back on DCC. For example:
      [19:29] #bryan# botcmd wtest whom ...
      [19:29] #bryan# botcmd wtest whom ...
@@ -253,6 +253,7 @@ See also: -ban, +ban, console%{+m|m}, chanset, chaninfo%{-}, stick, unstick
  
  
    Just using '?' for 'bot' will choose a random leaf bot.
    Just using '?' for 'bot' will choose a random leaf bot.
    Just using '&' for 'bot' will do the cmd on all localhub bots. (first bot in config).
    Just using '&' for 'bot' will do the cmd on all localhub bots. (first bot in config).
+   Use '%%group' to match a specific group. (see also: '%dhelp set')
  
  
    This cmd cannot be chained over the botnet, ie, no: '%dbotcmd [bot] botcmd [bot2] ...'
    This cmd cannot be chained over the botnet, ie, no: '%dbotcmd [bot] botcmd [bot2] ...'
  
  
@@ -261,15 +262,6 @@ See also: -ban, +ban, console%{+m|m}, chanset, chaninfo%{-}, stick, unstick
      bl -> botcmd ?
      bl -> botcmd ?
  
  
 See also: cmdpass
 See also: cmdpass
-::botjoin
-###  $bbotjoin$b <bot> <channel> [options]
-   Adds a channel to the bot's channel list. If options are specified, the 
-   channel will be configured with the options.
- 
-   $bTHIS CMD IS CURRENTLY EXPERIMENTAL AND WILL NOT ACTUALLY SAVE STATE$b
-   $bMEANING, ALL BOTS WILL JOIN EVENTUALLY AFTER DOING BOTJOIN$b
- 
-See also: botpart, +chan, -chan, chanset, chaninfo
 :hub:botjump:
 :hub:botjump:
 ###  $bbotjump$b <bot> [server [port [pass]]]
 ###  $bbotjump$b <bot> [server [port [pass]]]
    Makes the bot jump to another server. If you don't specify a
    Makes the bot jump to another server. If you don't specify a
@@ -291,13 +283,8 @@ See also: msg%{+n}
    The specified bot will display its current nick over DCC.
    The specified bot will display its current nick over DCC.
  
  
 See also: netnick, nick
 See also: netnick, nick
-::botpart
-###  $bbotpart$b <bot> <channel>
-   Removes bot from the specified channel
- 
-See also: botjoin, +chan, -chan, chanset, chaninfo
 :hub:bots
 :hub:bots
-###  $bbots$b [nodename]
+###  $bbots$b [nodename|%group]
    Shows the list of bots currently on the botnet.
    Shows the list of bots currently on the botnet.
    Example:
    Example:
       Bots: cEvin, ruthie, Killa1
       Bots: cEvin, ruthie, Killa1
@@ -305,9 +292,10 @@ See also: botjoin, +chan, -chan, chanset, chaninfo
    bot. %{+n}Use $b'%dwho'$b or $b'%dbottree'$b for that information.%{-}
    bot. %{+n}Use $b'%dwho'$b or $b'%dbottree'$b for that information.%{-}
  
  
    Specifying a nodename will display all bots up/down on that nodename.
    Specifying a nodename will display all bots up/down on that nodename.
-   Bots with a * preceeding its name is down.
+   Bots with a * preceeding its name are down.
+   Specifying a %%group will only show bots in that group.
  
  
-See also: downbots%{+n}, bottree%{-}
+See also: groups, downbots%{+n}, bottree%{-}
 :hub:botserver:
 :hub:botserver:
 ###  $bbotserver$b <bot>
 ###  $bbotserver$b <bot>
    The bot will display its current server and lag over DCC.
    The bot will display its current server and lag over DCC.
@@ -338,7 +326,7 @@ See also: set
    Red: localhubs (first bot in binary, see '%dhelp conf')
    Red: localhubs (first bot in binary, see '%dhelp conf')
    Other: normal leaf bots
    Other: normal leaf bots
  
  
-See also: bots, downbots
+See also: bots, downbots, groups
 ::botversion:
 ::botversion:
 ###  $bbotversion$b <bot>
 ###  $bbotversion$b <bot>
    The bot will display its pack version and uname.
    The bot will display its pack version and uname.
@@ -359,8 +347,12 @@ See also: link%{+a}, newhub%{-}
 ###  $bchaninfo$b <channel>
 ###  $bchaninfo$b <channel>
    This lists all the settings for the bot on the given channel.
    This lists all the settings for the bot on the given channel.
    It shows any of the following:
    It shows any of the following:
-        $bchanmode$b   These modes are enforced on the channel. Both + and -
-                   modes can be enforced.
+        $bchanmode$b         These modes are enforced on the channel.
+                          Both + and - modes can be enforced. Use {} to
+                          add a key. '%dchanset #chan chanmode { +nt key }'
+        $bgroups$b            List of groups that should join the channel.
+                          Use {} to add multiple groups. See also '%dhelp set'
+                          '%dchanset #chan groups { main alt }'
 %{+m|m}
 %{+m|m}
         $bauto-delay$b        Amount of seconds to wait before auto opping,
         $bauto-delay$b        Amount of seconds to wait before auto opping,
                               or auto voicing a client based on flags or
                               or auto voicing a client based on flags or
@@ -592,13 +584,14 @@ See also: %{-}%{+n}+chan, -chan%{-}%{+m|m}, chanset%{-}
  
  
 See also: status, whois
 See also: status, whois
 ::channels:
 ::channels:
-###  $bchannels$b %{+m}[user]%{-}
+###  $bchannels$b %{+m}[user|%group]%{-}
    Displays channels that you have access to, and any important 
    Displays channels that you have access to, and any important 
    flags that are set on them.
    flags that are set on them.
 %{+m}
 %{+m}
    Masters: You can specify other users to see what channels their 
    Masters: You can specify other users to see what channels their 
             flags grant them access to.%{-}
             flags grant them access to.%{-}
-See also: whois%{+m}, chattr, chaninfo%{-}
+   Special %%group to see what channels a particular group is in.
+See also: whois%{+m}, chattr, chaninfo, groups%{-}
 ::chanset
 ::chanset
 ###  $bchanset$b [#&!+channel|*|default] <settings>
 ###  $bchanset$b [#&!+channel|*|default] <settings>
    Allows you to change the channel settings (see $b'chaninfo'$b for the
    Allows you to change the channel settings (see $b'chaninfo'$b for the
@@ -649,8 +642,8 @@ See also: whois
 :hub:checkchannels
 :hub:checkchannels
 ###  $bcheckchannels$b
 ###  $bcheckchannels$b
    This will make all leaf bots display which channels they are currently
    This will make all leaf bots display which channels they are currently
-   not in but *should* be in.  This command will not show channels which
-   the bot is not active in due to '$bbotpart$b'.
+   not in but *should* be in.  This will only shows channels that contain
+   the groups the bot are in.
  
  
 See also: botcmd, channels, channel, status
 See also: botcmd, channels, channel, status
 :hub:chhandle
 :hub:chhandle
@@ -964,7 +957,7 @@ See also: cycle
    Example:
    Example:
       Down bots: cEvin, ruthie, Killa1
       Down bots: cEvin, ruthie, Killa1
  
  
-See also: bots
+See also: bots, groups
 :leaf:dump
 :leaf:dump
 ###  $bdump$b <text>
 ###  $bdump$b <text>
    dumps the text to the server.  keep in mind that this bot doesn't
    dumps the text to the server.  keep in mind that this bot doesn't
@@ -1061,6 +1054,13 @@ See also: -exempt, +exempt, console%{+m|m}, chanset, chaninfo%{-}, stick, unstic
    specified, your console channel is used.
    specified, your console channel is used.
  
  
 See also: console, channels%{+m}, status%{-}
 See also: console, channels%{+m}, status%{-}
+:hub:groups
+###  $bgroups$b [bot]
+   Shows the list of groups and which bots are in them.
+
+   Specify a bot to only show which groups it is in.
+ 
+See also: bots, downbots%{+n}, bottree%{-}
 ::handle
 ::handle
 ###  $bhandle$b <new-handle>
 ###  $bhandle$b <new-handle>
    Changes your handle on the bot. This is the handle (nickname) that the
    Changes your handle on the bot. This is the handle (nickname) that the
@@ -1672,7 +1672,7 @@ See also: reload, backup
 [N]  $bmsgburst$b        How many messages to burst at once to server. (Too high will excess flood)
 [N]  $bmsgburst$b        How many messages to burst at once to server. (Too high will excess flood)
 [N]  $bmsgrate$b         How often (msecs) to dequeue msgs to the server. Only used on
 [N]  $bmsgrate$b         How often (msecs) to dequeue msgs to the server. Only used on
                            non-ratbox servers. Too small a value can result in Excess Flood.
                            non-ratbox servers. Too small a value can result in Excess Flood.
- 
+[L]  $bgroups$b          List of groups that the affected bots are a part of.
 [L]  $brbl-servers$b     Servers to use for RBL checking in channels that are +rbl.
 [L]  $brbl-servers$b     Servers to use for RBL checking in channels that are +rbl.
  
  
 [S]  $brealname$b        The bot's "real name" when connecting. (supports '$n' expansion)
 [S]  $brealname$b        The bot's "real name" when connecting. (supports '$n' expansion)

+ 82 - 46
src/botnet.c

@@ -49,6 +49,7 @@
 #include "core_binds.h"
 #include "core_binds.h"
 #include <bdlib/src/String.h>
 #include <bdlib/src/String.h>
 #include <bdlib/src/Array.h>
 #include <bdlib/src/Array.h>
+#include <algorithm>
 
 
 tand_t			*tandbot = NULL;		/* Keep track of tandem bots on the
 tand_t			*tandbot = NULL;		/* Keep track of tandem bots on the
 							   botnet */
 							   botnet */
@@ -522,65 +523,100 @@ void answer_local_whom(int idx, int chan)
   dprintf(idx, "Total users: %d\n", total);
   dprintf(idx, "Total users: %d\n", total);
 }
 }
 
 
+bool sortNodes(const bd::String nodeA, const bd::String nodeB) {
+  const bd::String unknown("(unknown)");
+  if (nodeA == unknown) {
+    return true;
+  } else if (nodeB == unknown) {
+    return false;
+  }
+  // Reverse the domains
+  const bd::Array<bd::String> partsA(nodeA.split("."));
+  const bd::Array<bd::String> partsB(nodeB.split("."));
+  bd::Array<bd::String> reversedPartsA, reversedPartsB;
+  bd::String reversedNodeA, reversedNodeB;
+
+  if (partsA.length()) {
+    for (size_t i = partsA.length() - 1; i > 0; --i) {
+      reversedPartsA << partsA[i - 1];
+    }
+  }
+  if (partsB.length()) {
+    for (size_t i = partsB.length() - 1; i > 0; --i) {
+      reversedPartsB << partsB[i - 1];
+    }
+  }
+  reversedNodeA = reversedPartsA.join(".");
+  reversedNodeB = reversedPartsB.join(".");
+  return reversedNodeA < reversedNodeB;
+}
+
 /* Show z a list of all bots connected
 /* Show z a list of all bots connected
  */
  */
 void
 void
 tell_bots(int idx, int up, const char *nodename)
 tell_bots(int idx, int up, const char *nodename)
 {
 {
-  struct userrec *u = NULL;
-  int cnt = 0, tot = 0, total = 1, mtot = 0;
-  char work[151] = "", *node = NULL;
-
-  if (up) {
-    if (nodename)
-      node = (char *) get_user(&USERENTRY_NODENAME, conf.bot->u);    
-    if (!nodename || wild_match(nodename, node)) {
-      strlcat(work, conf.bot->nick, sizeof(work));
-      strlcat(work, " ", sizeof(work));
-      cnt++;
-      tot++;
-      if (nodename)
-        mtot++;
-    }
+  size_t total = 0, maxNodeNameLength = 0;;
+  bd::Array<bd::String> nodes;
+  bd::HashTable<bd::String, bd::Array<bd::String> > nodeBots;
+  bd::Array<bd::String> bots;
+  bd::String group;
+
+  if (nodename && nodename[0] == '%') {
+    group = nodename + 1;
   }
   }
 
 
-  for (u = userlist; u; u = u->next) {
+  // Gather a list of nodes and bots per node, as well as per domain
+  for (struct userrec* u = userlist; u; u = u->next) {
     if (u->bot) {
     if (u->bot) {
-      if (strcasecmp(u->handle, conf.bot->nick)) {
-        bool found = 0;
-        
-        if (findbot(u->handle))
-          found = 1;
-        total++;
-        if (nodename || (!nodename && ((!up && !found) || (up && found)))) {
-          if (nodename)
-            node = (char *) get_user(&USERENTRY_NODENAME, u);
-          if (!nodename || wild_match(nodename, node)) {
-            if (nodename && !found)
-              strlcat(work, "*", sizeof(work));
-            strlcat(work, u->handle, sizeof(work));
-            cnt++;
-            if (nodename)
-              mtot++;
-            if (!nodename || (nodename && found))
-              tot++;
-            if (cnt == 11) {
-              dprintf(idx, "%s bots: %s\n", nodename ? "Matching" : up ? "Up" : "Down", work);
-              work[0] = 0;
-              cnt = 0;
-            } else {
-              strlcat(work, " ", sizeof(work));
-            }
+      // If looking for groups, exclude hubs
+      if (group.length() && bot_hublevel(u) != 999) {
+        continue;
+      }
+      ++total;
+      bd::String botnick(u->handle);
+      const bd::Array<bd::String> botgroups(bd::String(var_get_bot_data(u, "groups", true)).split(","));
+
+      // Include this bot?
+      const bool group_match = group.length() && botgroups.find(group) != botgroups.npos;
+      const char *userNode = (const char*) get_user(&USERENTRY_NODENAME, u);
+      const bd::String node(userNode ? userNode : "(unknown)");
+      const bool node_match = ((nodename && node.length() && wild_match(nodename, node.c_str())) || !nodename);
+      const bool bot_found = findbot(u->handle);
+      const bool up_down_match = (nodename || (!nodename && ((up && bot_found) || (!up && !bot_found))));
+      if (group_match || (group.length() == 0 && node_match && up_down_match)) {
+        if (nodes.find(node) == nodes.npos) {
+          nodes << node;
+          if (node.length() > maxNodeNameLength) {
+            maxNodeNameLength = node.length();
           }
           }
         }
         }
+        if ((group.length() || nodename) && !bot_found) {
+          botnick = '*' + botnick;
+        }
+        nodeBots[node] << botnick;
+        bots << botnick;
       }
       }
     }
     }
   }
   }
-  if (work[0])
-    dprintf(idx, "%s bot%s: %s\n", nodename ? "Matching" : up ? "Up" : "Down", cnt > 1 ? "s" : "", work);
-  if (nodename)
-    dprintf(idx, "(Total Matching: %d/%d)\n", mtot, total);
-  dprintf(idx, "(Total %s: %d/%d)\n", nodename ? "up" : up ? "up" : "down", tot, nodename ? mtot : total);
+
+  if (group.length() == 0 && !nodename) {
+    dumplots(idx, nodename ? "Matching: " : (up ? "Up: " : "Down: "), static_cast<bd::String>(bots.join(" ")).c_str());
+  } else {
+    // Sort by nodes
+    std::sort(nodes.begin(), nodes.end(), sortNodes);
+    for (size_t i = 0; i < nodes.length(); ++i) {
+      const bd::String node(nodes[i]);
+      const bd::Array<bd::String> botsInNode(nodeBots[node]);
+      dumplots(idx, bd::String::printf("%*s: ", int(maxNodeNameLength), node.c_str()).c_str(), static_cast<bd::String>(botsInNode.join(" ")).c_str());
+    }
+  }
+
+  if (nodename || group.length()) {
+    dprintf(idx, "(Total Matching: %zu/%zu)\n", bots.length(), total);
+  } else {
+    dprintf(idx, "(Total %s: %zu/%zu)\n", up ? "up" : "down", bots.length(), total);
+  }
 }
 }
 
 
 /* Show a simpleton bot tree
 /* Show a simpleton bot tree

+ 5 - 1
src/chan.h

@@ -8,6 +8,9 @@
 #ifndef _EGG_CHAN_H
 #ifndef _EGG_CHAN_H
 #define _EGG_CHAN_H
 #define _EGG_CHAN_H
 
 
+#include <lib/bdlib/src/Array.h>
+#include <lib/bdlib/src/String.h>
+
 typedef struct memstruct {
 typedef struct memstruct {
   struct memstruct *next;
   struct memstruct *next;
   struct userrec *user;
   struct userrec *user;
@@ -210,6 +213,7 @@ struct chanset_t {
   char pls[21];			/* positive mode changes		*/
   char pls[21];			/* positive mode changes		*/
   char mns[21];			/* negative mode changes		*/
   char mns[21];			/* negative mode changes		*/
   char key_prot[121];		/* desired password			*/
   char key_prot[121];		/* desired password			*/
+  bd::Array<bd::String> *groups;/* groups that should join */
 /* Chanchar template
 /* Chanchar template
  *char temp[121];
  *char temp[121];
  */
  */
@@ -266,7 +270,7 @@ struct chanset_t {
 #define CHAN_STOP_CYCLE     BIT9	/* Some efnetservers have defined NO_CHANOPS_WHEN_SPLIT */
 #define CHAN_STOP_CYCLE     BIT9	/* Some efnetservers have defined NO_CHANOPS_WHEN_SPLIT */
 
 
 /* prototypes */
 /* prototypes */
-memberlist *ismember(struct chanset_t *, const char *);
+memberlist *ismember(const struct chanset_t *, const char *);
 struct chanset_t *findchan(const char *name);
 struct chanset_t *findchan(const char *name);
 struct chanset_t *findchan_by_dname(const char *name);
 struct chanset_t *findchan_by_dname(const char *name);
 
 

+ 27 - 69
src/chanprog.c

@@ -45,6 +45,7 @@
 #include "userrec.h"
 #include "userrec.h"
 #include "main.h"
 #include "main.h"
 #include "debug.h"
 #include "debug.h"
+#include "set.h"
 #include "dccutil.h"
 #include "dccutil.h"
 #include "botmsg.h"
 #include "botmsg.h"
 #if HAVE_GETRUSAGE
 #if HAVE_GETRUSAGE
@@ -54,8 +55,10 @@
 #endif
 #endif
 #endif
 #endif
 #include <sys/utsname.h>
 #include <sys/utsname.h>
+#include <bdlib/src/Array.h>
+#include <bdlib/src/String.h>
 
 
-char *def_chanset = "+enforcebans +dynamicbans +userbans -bitch +cycle -inactive +userexempts -dynamicexempts +userinvites -dynamicinvites -nodesynch -closed -take -voice -private -fastop +meankicks ban-type 3 protect-backup 1";
+char *def_chanset = "+enforcebans +dynamicbans +userbans -bitch +cycle -inactive +userexempts -dynamicexempts +userinvites -dynamicinvites -nodesynch -closed -take -voice -private -fastop +meankicks ban-type 3 protect-backup 1 groups { main }";
 struct chanset_t 	*chanset = NULL;	/* Channel list			*/
 struct chanset_t 	*chanset = NULL;	/* Channel list			*/
 struct chanset_t	*chanset_default = NULL;	/* Default channel list */
 struct chanset_t	*chanset_default = NULL;	/* Default channel list */
 char 			admin[121] = "";	/* Admin info			*/
 char 			admin[121] = "";	/* Admin info			*/
@@ -92,7 +95,7 @@ void rmspace(char *s)
 
 
 /* Returns memberfields if the nick is in the member list.
 /* Returns memberfields if the nick is in the member list.
  */
  */
-memberlist *ismember(struct chanset_t *chan, const char *nick)
+memberlist *ismember(const struct chanset_t *chan, const char *nick)
 {
 {
   register memberlist	*x = NULL;
   register memberlist	*x = NULL;
 
 
@@ -740,69 +743,7 @@ int isowner(char *name)
   }
   }
 }
 }
 
 
-/* this method is slow, but is only called when sharing and adding/removing chans */
-
-int 
-botshouldjoin(struct userrec *u, struct chanset_t *chan)
-{
-  /* just return 1 for now */
-  return 1;
-/*
-  char *chans = NULL;
-  struct userrec *u = NULL;
- 
-  u = get_user_by_handle(userlist, bot);
-  if (!u)
-    return;
-  chans = get_user(&USERENTRY_CHANS, u);
-*/
-}
-/* future use ?
-void
-chans_addbot(const char *bot, struct chanset_t *chan)
-{
-  char *chans = NULL;
-  struct userrec *u = NULL;
- 
-  u = get_user_by_handle(userlist, bot);
-  if (!u)
-    return;
-  chans = get_user(&USERENTRY_CHANS, u);
-  if (!botshouldjoin(u, chan)) {		
-    size_t size;
-    char *buf = NULL;
-   
-    size = strlen(chans) + strlen(chan->dname) + 2;
-    buf = (char *) my_calloc(1, size);
-    simple_snprintf(buf, size, "%s %s", chans, chan->dname);
-    set_user(&USERENTRY_CHANS, u, buf);
-    free(buf);
-  }
-}
-
-void 
-chans_delbot(const char *bot, struct chanset_t *chan)
-{
-  char *chans = NULL;
-  struct userrec *u = NULL;
- 
-  u = get_user_by_handle(userlist, bot);
-  if (!u)
-    return;
- 
-  if (botshouldjoin(u, chan)) {			
-    char *chans = NULL, *buf = NULL;
-    size_t size;
-
-    chans = get_user(&USERENTRY_CHANS, u);
-    size = strlen(chans) - strlen(chan->dname) + 2;
-
-    
-  }
-}
-*/
-
-bool bot_shouldjoin(struct userrec* u, struct flag_record* fr, struct chanset_t* chan, bool ignore_inactive)
+bool bot_shouldjoin(struct userrec* u, struct flag_record* fr, const struct chanset_t* chan, bool ignore_inactive)
 {
 {
   // If restarting, keep this channel.
   // If restarting, keep this channel.
   if (restarting && (reset_chans == 2) && (channel_active(chan) || channel_pending(chan))) return 1;
   if (restarting && (reset_chans == 2) && (channel_active(chan) || channel_pending(chan))) return 1;
@@ -823,13 +764,30 @@ bool bot_shouldjoin(struct userrec* u, struct flag_record* fr, struct chanset_t*
       return 0;
       return 0;
   }
   }
 #endif
 #endif
+
+  // Is this bot in the groups that this channel has?
+  const char *botgroups = u == conf.bot->u ? groups : var_get_bot_data(u, "groups", true);
+  bd::Array<bd::String> my_groupsArray(bd::String(botgroups).split(','));
+  bool group_match = 0;
+
+  if (chan->groups && chan->groups->length()) {
+    for (size_t i = 0; i < my_groupsArray.length(); ++i) {
+      if (chan->groups->find(my_groupsArray[i]) != chan->groups->npos) {
+        group_match = 1;
+        break;
+      }
+    }
+  }
+
   // Ignore +inactive during cmd_slowjoin to ensure that +backup bots join
   // Ignore +inactive during cmd_slowjoin to ensure that +backup bots join
-  return (!glob_kick(*fr) && !chan_kick(*fr) &&
-      ((ignore_inactive || !channel_inactive(chan)) &&
-       (channel_backup(chan) || (!glob_backup(*fr) && !chan_backup(*fr)))));
+  return (!glob_kick(*fr) && !chan_kick(*fr) && // Not being kicked
+      ((ignore_inactive || !channel_inactive(chan)) && // Not inactive
+      ((channel_backup(chan) && (glob_backup(*fr) || chan_backup(*fr) || group_match)) || // Is +backup and I'm a backup bot or my group matches
+       (!channel_backup(chan) && !glob_backup(*fr) && !chan_backup(*fr) && group_match))) // is -backup and I am not a backup bot and my group matches
+      );
 }
 }
 
 
-bool shouldjoin(struct chanset_t *chan)
+bool shouldjoin(const struct chanset_t *chan)
 {
 {
   struct flag_record fr = { FR_CHAN|FR_GLOBAL|FR_BOT, 0, 0, 0 };
   struct flag_record fr = { FR_CHAN|FR_GLOBAL|FR_BOT, 0, 0, 0 };
   get_user_flagrec(conf.bot->u, &fr, chan->dname, chan);
   get_user_flagrec(conf.bot->u, &fr, chan->dname, chan);

+ 2 - 3
src/chanprog.h

@@ -23,9 +23,8 @@ void rmspace(char *s);
 void set_chanlist(const char *host, struct userrec *rec);
 void set_chanlist(const char *host, struct userrec *rec);
 void clear_chanlist(void);
 void clear_chanlist(void);
 void clear_chanlist_member(const char *nick);
 void clear_chanlist_member(const char *nick);
-int botshouldjoin(struct userrec *u, struct chanset_t *);
-bool bot_shouldjoin(struct userrec* , struct flag_record *, struct chanset_t *, bool = 0);
-bool shouldjoin(struct chanset_t *);
+bool bot_shouldjoin(struct userrec* , struct flag_record *, const struct chanset_t *, bool = 0);
+bool shouldjoin(const struct chanset_t *);
 char *samechans(const char *, const char *);
 char *samechans(const char *, const char *);
 void add_myself_to_userlist();
 void add_myself_to_userlist();
 void add_child_bots();
 void add_child_bots();

+ 72 - 12
src/cmds.c

@@ -901,23 +901,74 @@ static void cmd_userlist(int idx, char *par)
   return;
   return;
 }
 }
 
 
+static void cmd_groups(int idx, char *par)
+{
+  struct userrec *u = NULL;
+
+  putlog(LOG_CMDS, "*", "#%s# groups %s", dcc[idx].nick, par);
+  bd::String botnick(newsplit(&par));
+
+  bd::Array<bd::String> globalgroups = bd::String(var_get_gdata("groups")).split(",");
+  bd::Array<bd::String> allgroups;
+  bd::HashTable<bd::String, bd::Array<bd::String> > groupBots;
+  bd::HashTable<bd::String, bd::Array<bd::String> > botGroups;
+  size_t maxGroupLen = 0;
+
+  // Need to loop over every bot and make a list of all groups and bots which are in those groups
+  for (u = userlist; u; u = u->next) {
+    if (u->bot && bot_hublevel(u) == 999) {
+      // Gather all the groups for this bot
+      botGroups[u->handle] = bd::String(var_get_bot_data(u, "groups", true)).split(",");
+      for (size_t i = 0; i < botGroups[u->handle].length(); ++i) {
+        const bd::String group(botGroups[u->handle][i]);
+        if (group.length() > maxGroupLen) {
+          maxGroupLen = group.length();
+        }
+        // Add their groups into the master list
+        if (allgroups.find(group) == allgroups.npos) {
+          allgroups << group;
+        }
+        // Add them to the list for this group
+        groupBots[group] << u->handle;
+
+      }
+    }
+  }
+
+  if (botnick.length()) {
+    dprintf(idx, "%s is in groups: %s\n", botnick.c_str(), static_cast<bd::String>(botGroups[botnick].join(" ")).c_str());
+  } else {
+    // Display all groups and which bots are in them
+    for (size_t i = 0; i < allgroups.length(); ++i) {
+      const bd::String group(allgroups[i]);
+      const bd::Array<bd::String> bots(groupBots[group]);
+      dumplots(idx, bd::String::printf("%-*s: ", int(maxGroupLen), group.c_str()).c_str(), static_cast<bd::String>(bots.join(" ")).c_str());
+    }
+  }
+
+  dprintf(idx, "Total groups: %zu\n", allgroups.length());
+  return;
+}
+
 static void cmd_channels(int idx, char *par) {
 static void cmd_channels(int idx, char *par) {
   putlog(LOG_CMDS, "*", "#%s# channels %s", dcc[idx].nick, par);
   putlog(LOG_CMDS, "*", "#%s# channels %s", dcc[idx].nick, par);
   if (par[0] && (dcc[idx].user->flags & USER_MASTER)) {
   if (par[0] && (dcc[idx].user->flags & USER_MASTER)) {
-    struct userrec *user = NULL;
-
-    user = get_user_by_handle(userlist, par);
-    if (user && whois_access(dcc[idx].user, user)) {
+    if (par[0] == '%') {
       show_channels(idx, par);
       show_channels(idx, par);
-    } else  {
-      dprintf(idx, "No such user.\n");
+    } else {
+      struct userrec *user = get_user_by_handle(userlist, par);
+      if (user && whois_access(dcc[idx].user, user)) {
+        show_channels(idx, par);
+      } else  {
+        dprintf(idx, "No such user.\n");
+      }
     }
     }
   } else {
   } else {
       show_channels(idx, NULL);
       show_channels(idx, NULL);
   }
   }
 
 
   if ((dcc[idx].user->flags & USER_MASTER) && !(par && par[0]))
   if ((dcc[idx].user->flags & USER_MASTER) && !(par && par[0]))
-    dprintf(idx, "You can also %schannels <user>\n", (dcc[idx].u.chat->channel >= 0) ? settings.dcc_prefix : "");
+    dprintf(idx, "You can also %schannels <user|%%group>\n", (dcc[idx].u.chat->channel >= 0) ? settings.dcc_prefix : "");
 }
 }
 
 
 
 
@@ -1335,7 +1386,7 @@ static void cmd_botcmd(int idx, char *par)
     cmd = newsplit(&par);
     cmd = newsplit(&par);
 
 
   if (!botm[0] || !cmd || (cmd && !cmd[0])) {
   if (!botm[0] || !cmd || (cmd && !cmd[0])) {
-    dprintf(idx, "Usage: botcmd <bot> <cmd> [params]\n");
+    dprintf(idx, "Usage: botcmd <bot|*|?|&|%%group> <cmd> [params]\n");
     return;
     return;
   }
   }
 
 
@@ -1347,7 +1398,7 @@ static void cmd_botcmd(int idx, char *par)
     putlog(LOG_CMDS, "*", "#%s# botcmd %s %s ...", dcc[idx].nick, botm, cmd);
     putlog(LOG_CMDS, "*", "#%s# botcmd %s %s ...", dcc[idx].nick, botm, cmd);
 
 
   // Restrict dangerous mass commands ('botcmd *' (any *) or 'botcmd &')
   // Restrict dangerous mass commands ('botcmd *' (any *) or 'botcmd &')
-  if ((strchr(botm, '*') && !findbot(botm)) || !strcmp(botm, "&")) {
+  if ((strchr(botm, '*') && !findbot(botm)) || !strcmp(botm, "&") || botm[0] == '%') {
     if (!strncasecmp(cmd, "di", 2) || (!strncasecmp(cmd, "res", 3) && strncasecmp(cmd, "reset", 5)) || !strncasecmp(cmd, "sui", 3) || !strncasecmp(cmd, "pl", 2) || !strncasecmp(cmd, "ac", 2) ||
     if (!strncasecmp(cmd, "di", 2) || (!strncasecmp(cmd, "res", 3) && strncasecmp(cmd, "reset", 5)) || !strncasecmp(cmd, "sui", 3) || !strncasecmp(cmd, "pl", 2) || !strncasecmp(cmd, "ac", 2) ||
         !strncasecmp(cmd, "j", 1) || (!strncasecmp(cmd, "dump", 4) && (!strncasecmp(par, "privmsg", 7) || !strncasecmp(par, "notice", 6) || !strncasecmp(par, "quit", 4)))) {
         !strncasecmp(cmd, "j", 1) || (!strncasecmp(cmd, "dump", 4) && (!strncasecmp(par, "privmsg", 7) || !strncasecmp(par, "notice", 6) || !strncasecmp(par, "quit", 4)))) {
       dprintf(idx, "Not a good idea.\n");
       dprintf(idx, "Not a good idea.\n");
@@ -1383,11 +1434,19 @@ static void cmd_botcmd(int idx, char *par)
   }
   }
   
   
   for (tbot = tandbot; tbot; tbot = tbot->next) {
   for (tbot = tandbot; tbot; tbot = tbot->next) {
-    if ((rand_leaf && bot_hublevel(get_user_by_handle(userlist, tbot->bot)) != 999) ||
-        (all_localhub && (bot_hublevel(get_user_by_handle(userlist, tbot->bot)) != 999 || !tbot->localhub)))
+    struct userrec *botu = get_user_by_handle(userlist, tbot->bot);
+    if ((rand_leaf && bot_hublevel(botu) != 999) ||
+        (all_localhub && (bot_hublevel(botu) != 999 || !tbot->localhub)))
       continue;
       continue;
     cnt++;
     cnt++;
-    if ((rleaf != -1 && cnt == rleaf) || (rleaf == -1 && (all_localhub || wild_match(botm, tbot->bot)))) {
+    bool group_match = false;
+    if (botm[0] == '%' && bot_hublevel(botu) == 999) {
+      bd::String botgroups = var_get_bot_data(botu, "groups", true);
+      if (botgroups.split(',').find(botm + 1) != bd::String::npos) {
+        group_match = true;
+      }
+    }
+    if (group_match || (rleaf != -1 && cnt == rleaf) || (rleaf == -1 && (all_localhub || wild_match(botm, tbot->bot)))) {
       if (rleaf != -1)
       if (rleaf != -1)
         putlog(LOG_CMDS, "*", "#%s# botcmd %s %s ...", dcc[idx].nick, tbot->bot, cmd);
         putlog(LOG_CMDS, "*", "#%s# botcmd %s %s ...", dcc[idx].nick, tbot->bot, cmd);
       send_remote_simul(idx, tbot->bot, cmd, par ? par : (char *) "");
       send_remote_simul(idx, tbot->bot, cmd, par ? par : (char *) "");
@@ -4721,6 +4780,7 @@ cmd_t C_dcc[] =
   {"version", 		"o", 	(Function) cmd_version, 	NULL, 0},
   {"version", 		"o", 	(Function) cmd_version, 	NULL, 0},
   {"netversion", 	"o", 	(Function) cmd_netversion, 	NULL, HUB},
   {"netversion", 	"o", 	(Function) cmd_netversion, 	NULL, HUB},
   {"userlist", 		"m", 	(Function) cmd_userlist, 	NULL, 0},
   {"userlist", 		"m", 	(Function) cmd_userlist, 	NULL, 0},
+  {"groups", 		"m", 	(Function) cmd_groups, 		NULL, HUB},
   {"ps", 		"n", 	(Function) cmd_ps, 		NULL, 0},
   {"ps", 		"n", 	(Function) cmd_ps, 		NULL, 0},
   {"last", 		"n", 	(Function) cmd_last, 		NULL, 0},
   {"last", 		"n", 	(Function) cmd_last, 		NULL, 0},
   {"exec", 		"a", 	(Function) cmd_exec, 		NULL, 0},
   {"exec", 		"a", 	(Function) cmd_exec, 		NULL, 0},

+ 1 - 1
src/flags.c

@@ -380,7 +380,7 @@ set_user_flagrec(struct userrec *u, struct flag_record *fr, const char *chname)
 /* Always pass the dname (display name) to this function for chname <cybah>
 /* Always pass the dname (display name) to this function for chname <cybah>
  */
  */
 void
 void
-get_user_flagrec(struct userrec *u, struct flag_record *fr, const char *chname, struct chanset_t* chan)
+get_user_flagrec(struct userrec *u, struct flag_record *fr, const char *chname, const struct chanset_t* chan)
 {
 {
   fr->bot = 0;
   fr->bot = 0;
   if (!u) {
   if (!u) {

+ 1 - 1
src/flags.h

@@ -142,7 +142,7 @@ struct flag_record {
 #define chan_doflood(x)				((x).chan & BOT_FLOODBOT)
 #define chan_doflood(x)				((x).chan & BOT_FLOODBOT)
 
 
 void init_flags(void);
 void init_flags(void);
-void get_user_flagrec(struct userrec *, struct flag_record *, const char *, struct chanset_t* = NULL);
+void get_user_flagrec(struct userrec *, struct flag_record *, const char *, const struct chanset_t* = NULL);
 void set_user_flagrec(struct userrec *, struct flag_record *, const char *);
 void set_user_flagrec(struct userrec *, struct flag_record *, const char *);
 void break_down_flags(const char *, struct flag_record *, struct flag_record *);
 void break_down_flags(const char *, struct flag_record *, struct flag_record *);
 int build_flags(char *, struct flag_record *, struct flag_record *);
 int build_flags(char *, struct flag_record *, struct flag_record *);

+ 44 - 27
src/misc.c

@@ -431,45 +431,62 @@ void show_motd(int idx)
 
 
 void show_channels(int idx, char *handle)
 void show_channels(int idx, char *handle)
 {
 {
-  struct chanset_t *chan = NULL;
-  struct flag_record fr = { FR_CHAN | FR_GLOBAL, 0, 0, 0 };
   struct userrec *u = NULL;
   struct userrec *u = NULL;
-  int first = 0, total = 0;
-  size_t l = 0;
-  char format[120] = "";
+  size_t maxChannelLength = 0;
+  bd::Array<bd::String> channelNames;
+  bd::HashTable<bd::String, struct chanset_t*> channels;
+  bd::String group;
 
 
-  if (handle)
+  if (handle && handle[0] != '%') {
     u = get_user_by_handle(userlist, handle);
     u = get_user_by_handle(userlist, handle);
-  else
+  } else {
     u = dcc[idx].user;
     u = dcc[idx].user;
+    if (handle && handle[0] == '%') {
+      group = handle + 1;
+    }
+  }
 
 
-  for (chan = chanset;chan ;chan = chan->next) {
+  for (struct chanset_t* chan = chanset; chan; chan = chan->next) {
+    struct flag_record fr = { FR_CHAN | FR_GLOBAL, 0, 0, 0 };
+    const bd::String chname(chan->dname);
+    // If a group was passed, ensure it matches
+    if (group.length() && chan->groups->find(group) == chan->groups->npos) {
+      continue;
+    }
     get_user_flagrec(u, &fr, chan->dname);
     get_user_flagrec(u, &fr, chan->dname);
-    if (l < strlen(chan->dname)) {
-      l = strlen(chan->dname);
+    if (group.length() || real_chk_op(fr, chan, 0)) {
+      if (maxChannelLength < chname.length()) {
+        maxChannelLength = chname.length();
+      }
+      channelNames << chname;
+      channels[chname] = chan;
     }
     }
-    if (real_chk_op(fr, chan, 0))
-      total++;
   }
   }
 
 
-  simple_snprintf(format, sizeof(format), "  %%c%%-%zus %%-s%%-s%%-s%%-s%%-s%%-s\n", (l+2));
+  if (channelNames.length()) {
+    char format[120] = "";
+    simple_snprintf(format, sizeof(format), "  %%c%%-%zus %%-s%%-s%%-s%%-s%%-s%%-s\n", (maxChannelLength+2));
+    if (group.length()) {
+      dprintf(idx, "group '%s' is in %zu channel%s:\n", group.c_str(), channelNames.length(), (channelNames.length() > 1) ? "s" : "");
+    } else {
+      dprintf(idx, "%s %s access to %zu channel%s:\n", handle ? u->handle : "You", handle ? "has" : "have", channelNames.length(), (channelNames.length() > 1) ? "s" : "");
+    }
 
 
-  for (chan = chanset;chan;chan = chan->next) {
-    get_user_flagrec(u, &fr, chan->dname);
-    if (real_chk_op(fr, chan, 0)) {
-        if (!first) { 
-          dprintf(idx, "%s %s access to %d channel%s:\n", handle ? u->handle : "You", handle ? "has" : "have", total, (total > 1) ? "s" : "");
-          
-          first = 1;
-        }
-        dprintf(idx, format, !conf.bot->hub && me_op(chan) ? '@' : ' ', chan->dname, !shouldjoin(chan) ? "(inactive) " : "", 
-           channel_privchan(chan) ? "(private)  " : "", chan->manop ? "(no manop) " : "", 
-           channel_bitch(chan) && !channel_botbitch(chan) ? "(bitch)    " : channel_botbitch(chan) ? "(botbitch) " : "",
-           channel_closed(chan) ?  "(closed) " : "", channel_backup(chan) ? "(backup)" : "");
+    for (size_t i = 0; i < channelNames.length(); ++i) {
+      const bd::String chname(channelNames[i]);
+      const struct chanset_t* chan = channels[chname];
+      dprintf(idx, format, !conf.bot->hub && me_op(chan) ? '@' : ' ', chan->dname, ((conf.bot->hub && channel_inactive(chan)) || (!conf.bot->hub && !shouldjoin(chan))) ? "(inactive) " : "",
+          channel_privchan(chan) ? "(private)  " : "", chan->manop ? "(no manop) " : "", 
+          channel_bitch(chan) && !channel_botbitch(chan) ? "(bitch)    " : channel_botbitch(chan) ? "(botbitch) " : "",
+          channel_closed(chan) ?  "(closed) " : "", channel_backup(chan) ? "(backup)" : "");
+    }
+  } else {
+    if (group.length()) {
+      dprintf(idx, "No channels found for group '%s'\n", group.c_str());
+    } else {
+      dprintf(idx, "%s %s not have access to any channels.\n", handle ? u->handle : "You", handle ? "does" : "do");
     }
     }
   }
   }
-  if (!first)
-    dprintf(idx, "%s %s not have access to any channels.\n", handle ? u->handle : "You", handle ? "does" : "do");
 }
 }
 
 
 /* Create a string with random letters and digits
 /* Create a string with random letters and digits

+ 20 - 7
src/mod/channels.mod/chanmisc.c

@@ -306,7 +306,7 @@ int SplitList(char *resultBuf, const char *list, int *argcPtr, const char ***arg
  */
  */
 int channel_modify(char *result, struct chanset_t *chan, int items, char **item, bool cmd)
 int channel_modify(char *result, struct chanset_t *chan, int items, char **item, bool cmd)
 {
 {
-  bool error = 0;
+  bool error = 0, changed_groups = false;
   int old_status = chan->status,
   int old_status = chan->status,
       old_mode_mns_prot = chan->mode_mns_prot,
       old_mode_mns_prot = chan->mode_mns_prot,
       old_mode_pls_prot = chan->mode_pls_prot;
       old_mode_pls_prot = chan->mode_pls_prot;
@@ -336,6 +336,20 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
       }
       }
       strlcpy(s, item[i], sizeof(s));
       strlcpy(s, item[i], sizeof(s));
       set_mode_protect(chan, s);
       set_mode_protect(chan, s);
+    } else if (!strcmp(item[i], "groups")) {
+      i++;
+      if (i >= items) {
+	if (result)
+	  strlcpy(result, "channel groups needs argument", RESULT_LEN);
+	return ERROR;
+      }
+      // Get string into right format
+      bd::String changroups(item[i]);
+      // Replace commas with spaces to be in proper format
+      changroups.sub(",", " ");
+      changroups.trim();
+      *(chan->groups) = changroups.split(" ");
+      changed_groups = true;
     } else if (!strcmp(item[i], "topic")) {
     } else if (!strcmp(item[i], "topic")) {
       char *p = NULL;
       char *p = NULL;
 
 
@@ -814,12 +828,9 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
     chan->status |= CHAN_BITCH;		// to avoid bots still mass opping from +take from not using cookies
     chan->status |= CHAN_BITCH;		// to avoid bots still mass opping from +take from not using cookies
 
 
   if (!conf.bot->hub && (chan != chanset_default)) {
   if (!conf.bot->hub && (chan != chanset_default)) {
-    if ((old_status ^ chan->status) & (CHAN_INACTIVE | CHAN_BACKUP)) {
-      if (!shouldjoin(chan) && (chan->ircnet_status & (CHAN_ACTIVE | CHAN_PEND))) {
-        putlog(LOG_DEBUG, "*", "In %s, but I shouldn't be, parting...", chan->dname);
-        dprintf(DP_SERVER, "PART %s\n", chan->name[0] ? chan->name : chan->dname);
-      } else if (shouldjoin(chan))
-        join_chan(chan);
+    // Check if groups changed or +/-backup set
+    if (!restarting && !loading && (changed_groups || ((old_status ^ chan->status) & (CHAN_INACTIVE | CHAN_BACKUP)))) {
+      check_shouldjoin(chan);
     }
     }
     if (me_op(chan)) {
     if (me_op(chan)) {
       if ((old_status ^ chan->status) & (CHAN_ENFORCEBANS|CHAN_NOUSERBANS|CHAN_DYNAMICBANS|CHAN_NOUSEREXEMPTS|CHAN_NOUSERINVITES|CHAN_DYNAMICEXEMPTS|CHAN_DYNAMICINVITES)) {
       if ((old_status ^ chan->status) & (CHAN_ENFORCEBANS|CHAN_NOUSERBANS|CHAN_DYNAMICBANS|CHAN_NOUSEREXEMPTS|CHAN_NOUSERINVITES|CHAN_DYNAMICEXEMPTS|CHAN_DYNAMICINVITES)) {
@@ -995,6 +1006,8 @@ int channel_add(char *result, const char *newname, char *options, bool isdefault
 /* Chanint template
 /* Chanint template
  *  chan->temp = 0;
  *  chan->temp = 0;
  */
  */
+    chan->groups = new bd::Array<bd::String>;
+    *(chan->groups) << "main";
     chan->protect_backup = 1;
     chan->protect_backup = 1;
     chan->knock_flags = 0;
     chan->knock_flags = 0;
     chan->flood_lock_time = 120;
     chan->flood_lock_time = 120;

+ 3 - 0
src/mod/channels.mod/channels.c

@@ -721,6 +721,9 @@ void remove_channel(struct chanset_t *chan)
      free(chan->key);
      free(chan->key);
    if (chan->rmkey)
    if (chan->rmkey)
      free(chan->rmkey);
      free(chan->rmkey);
+   if (chan->groups) {
+     delete(chan->groups);
+   }
    free(chan);
    free(chan);
 }
 }
 
 

+ 1 - 46
src/mod/channels.mod/cmdschan.c

@@ -1008,29 +1008,6 @@ static void cmd_pls_chan(int idx, char *par)
   pls_chan(idx, par, NULL);
   pls_chan(idx, par, NULL);
 }
 }
 
 
-static void cmd_botjoin(int idx, char *par)
-{
-  char *bot = NULL;
-  struct userrec *botu = NULL;
-
-  putlog(LOG_CMDS, "*", "#%s# botjoin %s", dcc[idx].nick, par);
-
-  if (!par[0]) {
-    dprintf(idx, "Usage: botjoin <bot> [%s]<channel> [options]\n", CHANMETA);
-    return;
-  }
-  bot = newsplit(&par);
-  botu = get_user_by_handle(userlist, bot);
-  if (botu && botu->bot) {
-    pls_chan(idx, par, bot);
-  } else {
-    dprintf(idx, "Error: '%s' is not a bot.\n", bot);
-  }
-
-  dprintf(idx, "!!!! Warning: botjoin is an unfinished feature which will pretty much make all bots join the channel.\n");
-  dprintf(idx, "!!!! Warning: You should probably -chan the channel now to avoid problems/confusion later.\n");
-}
-
 static void mns_chan(int idx, char *par, char *bot)
 static void mns_chan(int idx, char *par, char *bot)
 {
 {
   char *chname = NULL, buf2[1024] = "";
   char *chname = NULL, buf2[1024] = "";
@@ -1084,27 +1061,6 @@ static void cmd_mns_chan(int idx, char *par)
   mns_chan(idx, par, NULL);
   mns_chan(idx, par, NULL);
 }
 }
 
 
-static void cmd_botpart(int idx, char *par)
-{
-  char *bot = NULL;
-  struct userrec *botu = NULL;
-
-  putlog(LOG_CMDS, "*", "#%s# botpart %s", dcc[idx].nick, par);
-  
-  if (!par[0]) {
-    dprintf(idx, "Usage: botpart <bot> [%s]<channel> [options]\n", CHANMETA);
-    return;
-  }
-
-  bot = newsplit(&par);
-  botu = get_user_by_handle(userlist, bot);
-  if (botu && botu->bot) {
-    mns_chan(idx, par, bot);
-  } else {
-    dprintf(idx, "Error: '%s' is not a bot.\n", bot);
-  }
-}
-
 /* thanks Excelsior */
 /* thanks Excelsior */
 #define FLAG_COLS 4
 #define FLAG_COLS 4
 static void show_flag(int idx, char *work, int *cnt, const char *name, unsigned int state, size_t worksiz)
 static void show_flag(int idx, char *work, int *cnt, const char *name, unsigned int state, size_t worksiz)
@@ -1228,6 +1184,7 @@ static void cmd_chaninfo(int idx, char *par)
 /* FIXME: SHOW_CHAR() here */
 /* FIXME: SHOW_CHAR() here */
     get_mode_protect(chan, work, sizeof(work));
     get_mode_protect(chan, work, sizeof(work));
     dprintf(idx, "Protect modes (chanmode): %s\n", work[0] ? work : "None");
     dprintf(idx, "Protect modes (chanmode): %s\n", work[0] ? work : "None");
+    dprintf(idx, "Groups: %s\n", chan->groups && chan->groups->length() ? static_cast<bd::String>(chan->groups->join(" ")).c_str() : "None");
 //    dprintf(idx, "Protect topic (topic)   : %s\n", chan->topic[0] ? chan->topic : "");
 //    dprintf(idx, "Protect topic (topic)   : %s\n", chan->topic[0] ? chan->topic : "");
 /* Chanchar template
 /* Chanchar template
  *  dprintf(idx, "String temp: %s\n", chan->temp[0] ? chan->temp : "NULL");
  *  dprintf(idx, "String temp: %s\n", chan->temp[0] ? chan->temp : "NULL");
@@ -1419,8 +1376,6 @@ static cmd_t C_dcc_channels[] =
   {"-exempt",	"o|o",	(Function) cmd_mns_exempt,	NULL, AUTH},
   {"-exempt",	"o|o",	(Function) cmd_mns_exempt,	NULL, AUTH},
   {"-invite",	"o|o",	(Function) cmd_mns_invite,	NULL, AUTH},
   {"-invite",	"o|o",	(Function) cmd_mns_invite,	NULL, AUTH},
   {"bans",	"o|o",	(Function) cmd_bans,		NULL, 0},
   {"bans",	"o|o",	(Function) cmd_bans,		NULL, 0},
-  {"botjoin",	"n",	(Function) cmd_botjoin,		NULL, 0},
-  {"botpart",	"n",	(Function) cmd_botpart,		NULL, 0},
   {"exempts",	"o|o",	(Function) cmd_exempts,		NULL, 0},
   {"exempts",	"o|o",	(Function) cmd_exempts,		NULL, 0},
   {"invites",	"o|o",	(Function) cmd_invites,		NULL, 0},
   {"invites",	"o|o",	(Function) cmd_invites,		NULL, 0},
   {"chaninfo",	"m|m",	(Function) cmd_chaninfo,	NULL, 0},
   {"chaninfo",	"m|m",	(Function) cmd_chaninfo,	NULL, 0},

+ 3 - 10
src/mod/channels.mod/userchan.c

@@ -701,9 +701,6 @@ static void write_chan(bd::Stream& stream, int idx, struct chanset_t* chan)
   putlog(LOG_DEBUG, "*", "writing channel %s to userfile..", chan->dname);
   putlog(LOG_DEBUG, "*", "writing channel %s to userfile..", chan->dname);
 
 
   bool force_inactive = 0;
   bool force_inactive = 0;
-  /* if a bot should explicitly NOT join, just set it +inactive ... */
-  if (idx >= 0 && !botshouldjoin(dcc[idx].user, chan))
-    force_inactive = 1;
 
 
   stream << bd::String::printf("+ channel add %s { ", chan->dname);
   stream << bd::String::printf("+ channel add %s { ", chan->dname);
   if (chan != chanset_default)
   if (chan != chanset_default)
@@ -717,7 +714,7 @@ bd::String channel_to_string(struct chanset_t* chan, bool force_inactive) {
 
 
   get_mode_protect(chan, w, sizeof(w));
   get_mode_protect(chan, w, sizeof(w));
   return bd::String::printf("\
   return bd::String::printf("\
-chanmode { %s } bad-cookie %d manop %d mdop %d mop %d limit %d ban-type %d \
+chanmode { %s } groups { %s } bad-cookie %d manop %d mdop %d mop %d limit %d ban-type %d \
 flood-chan %d:%d flood-ctcp %d:%d flood-join %d:%d \
 flood-chan %d:%d flood-ctcp %d:%d flood-join %d:%d \
 flood-kick %d:%d flood-deop %d:%d flood-nick %d:%d flood-mjoin %d:%d \
 flood-kick %d:%d flood-deop %d:%d flood-nick %d:%d flood-mjoin %d:%d \
 closed-ban %d closed-invite %d closed-private %d ban-time %d \
 closed-ban %d closed-invite %d closed-private %d ban-time %d \
@@ -732,6 +729,7 @@ flood-exempt %d flood-lock-time %d knock %d \
  *      temp,
  *      temp,
  * also include temp %s in dprintf.
  * also include temp %s in dprintf.
  */
  */
+        chan->groups && chan->groups->length() ? static_cast<bd::String>(chan->groups->join(" ")).c_str() : "",
 	chan->bad_cookie,
 	chan->bad_cookie,
 	chan->manop,
 	chan->manop,
 	chan->mdop,
 	chan->mdop,
@@ -825,12 +823,7 @@ void write_chans_compat(bd::Stream& stream, int idx)
     putlog(LOG_DEBUG, "*", "writing channel %s to userfile..", chan->dname);
     putlog(LOG_DEBUG, "*", "writing channel %s to userfile..", chan->dname);
     get_mode_protect(chan, w, sizeof(w));
     get_mode_protect(chan, w, sizeof(w));
 
 
-    /* if a bot should explicitly NOT join, just set it +inactive ... */
-    if (idx >= 0 && !botshouldjoin(dcc[idx].user, chan))
-      inactive = '+';
-    /* ... otherwise give the bot the *actual* setting */
-    else
-      inactive = PLSMNS(channel_inactive(chan));
+    inactive = PLSMNS(channel_inactive(chan));
 
 
     stream << bd::String::printf("\
     stream << bd::String::printf("\
 + channel add %s { chanmode { %s } addedby %s addedts %li \
 + channel add %s { chanmode { %s } addedby %s addedts %li \

+ 15 - 8
src/mod/irc.mod/irc.c

@@ -1279,7 +1279,7 @@ killmember(struct chanset_t *chan, char *nick)
 /* Check if I am a chanop. Returns boolean 1 or 0.
 /* Check if I am a chanop. Returns boolean 1 or 0.
  */
  */
 bool
 bool
-me_op(struct chanset_t *chan)
+me_op(const struct chanset_t *chan)
 {
 {
   memberlist *mx = ismember(chan, botname);
   memberlist *mx = ismember(chan, botname);
 
 
@@ -1294,7 +1294,7 @@ me_op(struct chanset_t *chan)
 /* Check whether I'm voice. Returns boolean 1 or 0.
 /* Check whether I'm voice. Returns boolean 1 or 0.
  */
  */
 bool
 bool
-me_voice(struct chanset_t *chan)
+me_voice(const struct chanset_t *chan)
 {
 {
   memberlist *mx = ismember(chan, botname);
   memberlist *mx = ismember(chan, botname);
 
 
@@ -1553,13 +1553,21 @@ raise_limit(struct chanset_t *chan)
 
 
 }
 }
 
 
-static void
-check_expired_chanstuff(struct chanset_t *chan)
+void check_shouldjoin(struct chanset_t* chan)
 {
 {
   if ((channel_active(chan) || channel_pending(chan)) && !shouldjoin(chan)) {
   if ((channel_active(chan) || channel_pending(chan)) && !shouldjoin(chan)) {
     sdprintf("Active/Pending in %s but I shouldn't be there, parting...", chan->dname);
     sdprintf("Active/Pending in %s but I shouldn't be there, parting...", chan->dname);
-    dprintf(DP_MODE, "PART %s\n", chan->name[0] ? chan->name : chan->dname);
-  } else if (channel_active(chan)) {
+    dprintf(DP_SERVER, "PART %s\n", chan->name[0] ? chan->name : chan->dname);
+  } else if (shouldjoin(chan)) {
+    join_chan(chan);
+  }
+}
+
+static void
+check_expired_chanstuff(struct chanset_t *chan)
+{
+  check_shouldjoin(chan);
+  if (channel_active(chan) && shouldjoin(chan)) {
     masklist *b = NULL, *e = NULL;
     masklist *b = NULL, *e = NULL;
     memberlist *m = NULL, *n = NULL;
     memberlist *m = NULL, *n = NULL;
     char s[UHOSTLEN] = "";
     char s[UHOSTLEN] = "";
@@ -1670,8 +1678,7 @@ check_expired_chanstuff(struct chanset_t *chan)
     // Update minutely
     // Update minutely
     chan->channel.splitmembers = splitmembers;
     chan->channel.splitmembers = splitmembers;
     check_lonely_channel(chan);
     check_lonely_channel(chan);
-  } else if (shouldjoin(chan))
-    join_chan(chan);
+  }
 }
 }
 
 
 void
 void

+ 3 - 2
src/mod/irc.mod/irc.h

@@ -117,8 +117,8 @@ void notice_invite(struct chanset_t *, char *, char *, char *, bool);
 void real_add_mode(struct chanset_t *, const char, const char, const char *, bool);
 void real_add_mode(struct chanset_t *, const char, const char, const char *, bool);
 #define add_mode(chan, pls, mode, nick) real_add_mode(chan, pls, mode, nick, 0)
 #define add_mode(chan, pls, mode, nick) real_add_mode(chan, pls, mode, nick, 0)
 #define add_cookie(chan, nick) real_add_mode(chan, '+', 'o', nick, 1)
 #define add_cookie(chan, nick) real_add_mode(chan, '+', 'o', nick, 1)
-bool me_op(struct chanset_t *);
-bool me_voice(struct chanset_t *);
+bool me_op(const struct chanset_t *);
+bool me_voice(const struct chanset_t *);
 
 
 void check_this_ban(struct chanset_t *, char *, bool);
 void check_this_ban(struct chanset_t *, char *, bool);
 void check_this_exempt(struct chanset_t *, char *, bool);
 void check_this_exempt(struct chanset_t *, char *, bool);
@@ -145,6 +145,7 @@ void irc_report(int, int);
 void flush_modes();
 void flush_modes();
 void reset_chan_info(struct chanset_t *);
 void reset_chan_info(struct chanset_t *);
 char *getnick(const char *, struct chanset_t *);
 char *getnick(const char *, struct chanset_t *);
+void check_shouldjoin(struct chanset_t* chan);
 
 
 extern int		max_bans, max_exempts, max_invites, max_modes;
 extern int		max_bans, max_exempts, max_invites, max_modes;
 extern bool		use_354, include_lk;
 extern bool		use_354, include_lk;

+ 27 - 3
src/set.c

@@ -14,6 +14,7 @@
 #include "misc.h"
 #include "misc.h"
 #include "net.h"
 #include "net.h"
 #include "src/mod/server.mod/server.h"
 #include "src/mod/server.mod/server.h"
+#include "src/mod/irc.mod/irc.h"
 #include "src/mod/channels.mod/channels.h"
 #include "src/mod/channels.mod/channels.h"
 #include "src/mod/ctcp.mod/ctcp.h"
 #include "src/mod/ctcp.mod/ctcp.h"
 #include "users.h"
 #include "users.h"
@@ -30,6 +31,7 @@ static bool parsing_botset = 0;
 char altchars[50] = "";
 char altchars[50] = "";
 char alias[1024] = "";
 char alias[1024] = "";
 char rbl_servers[1024] = "";
 char rbl_servers[1024] = "";
+char groups[1024] = "";
 bool auth_chan;
 bool auth_chan;
 char auth_key[51] = "";
 char auth_key[51] = "";
 char auth_prefix[2] = "";
 char auth_prefix[2] = "";
@@ -94,6 +96,7 @@ static variable_t vars[] = {
  VAR("flood-ctcp",	&flood_ctcp,		VAR_RATE|VAR_NOLHUB,				0, 0, "3:60"),
  VAR("flood-ctcp",	&flood_ctcp,		VAR_RATE|VAR_NOLHUB,				0, 0, "3:60"),
  VAR("flood-msg",	&flood_msg,		VAR_RATE|VAR_NOLHUB,				0, 0, "5:60"),
  VAR("flood-msg",	&flood_msg,		VAR_RATE|VAR_NOLHUB,				0, 0, "5:60"),
  VAR("fork-interval",	&fork_interval,		VAR_INT,					10, 0, "0"),
  VAR("fork-interval",	&fork_interval,		VAR_INT,					10, 0, "0"),
+ VAR("groups",		groups,			VAR_STRING|VAR_LIST|VAR_NOLHUB,			0, 0, "main"),
  VAR("hijack",		&hijack,		VAR_INT|VAR_DETECTED|VAR_PERM,			0, 4, "die"),
  VAR("hijack",		&hijack,		VAR_INT|VAR_DETECTED|VAR_PERM,			0, 4, "die"),
  VAR("homechan",	homechan,		VAR_WORD|VAR_NOLOC|VAR_HIDE,			0, 0, NULL),
  VAR("homechan",	homechan,		VAR_WORD|VAR_NOLOC|VAR_HIDE,			0, 0, NULL),
  VAR("ident-botnick",   &ident_botnick,		VAR_INT|VAR_BOOL|VAR_NOLHUB,			0, 1, "0"),
  VAR("ident-botnick",   &ident_botnick,		VAR_INT|VAR_BOOL|VAR_NOLHUB,			0, 1, "0"),
@@ -410,6 +413,15 @@ sdprintf("var (mem): %s -> %s", var->name, datain ? datain : "(NULL)");
     var_set_mem(servers, servers->ldata ? servers->ldata : servers->gdata ? servers->gdata : NULL);
     var_set_mem(servers, servers->ldata ? servers->ldata : servers->gdata ? servers->gdata : NULL);
   }
   }
 
 
+  // Check if should part/join channels based on groups changing
+  if (!conf.bot->hub && !strcmp(var->name, "groups")) {
+    if (server_online && !restarting && !loading && !reset_chans) {
+      for (struct chanset_t* chan = chanset; chan; chan = chan->next) {
+        check_shouldjoin(chan);
+      }
+    }
+  }
+
   if (datap)
   if (datap)
     free(datap);
     free(datap);
 
 
@@ -516,6 +528,11 @@ static inline variable_t *var_get_var_by_name(const char *name)
   return (variable_t*) bsearch(&key, &vars, lengthof(vars) - 1, sizeof(variable_t), comp_variable_t);
   return (variable_t*) bsearch(&key, &vars, lengthof(vars) - 1, sizeof(variable_t), comp_variable_t);
 }
 }
 
 
+const char *var_get_gdata(const char *name) {
+  variable_t* var = var_get_var_by_name(name);
+  return var && var->gdata ? var->gdata : NULL;
+}
+
 void var_set(variable_t *var, const char *target, const char *datain)
 void var_set(variable_t *var, const char *target, const char *datain)
 {
 {
   /* Don't set locally if the variable doesn't permit it. */
   /* Don't set locally if the variable doesn't permit it. */
@@ -686,7 +703,7 @@ void var_userfile_share_line(char *line, int idx, bool share)
   set_noshare = 0;
   set_noshare = 0;
 }
 }
 
 
-static const char *var_get_bot_data(struct userrec *u, const char *name)
+const char *var_get_bot_data(struct userrec *u, const char *name, bool useDefault)
 {
 {
   if (!u)
   if (!u)
     return NULL;
     return NULL;
@@ -697,7 +714,14 @@ static const char *var_get_bot_data(struct userrec *u, const char *name)
   while (xk && strcmp(xk->key, name))
   while (xk && strcmp(xk->key, name))
     xk = xk->next;
     xk = xk->next;
 
 
-  return xk ? xk->data : NULL;
+  if (xk) {
+    return xk->data;
+  }
+  if (useDefault) {
+    variable_t *var = var_get_var_by_name(name);
+    return var->gdata ? var->gdata : var->def;
+  }
+  return NULL;
 }
 }
 
 
 
 
@@ -796,7 +820,7 @@ static int var_add_list(const char *botnick, variable_t *var, const char *elemen
   char *data = NULL, *olddata = NULL, *botdata = NULL;
   char *data = NULL, *olddata = NULL, *botdata = NULL;
 
 
   if (botnick) {                          //fetch data from bot's USERENTRY_SET
   if (botnick) {                          //fetch data from bot's USERENTRY_SET
-    botdata = (char *) var_get_bot_data(get_user_by_handle(userlist, (char *) botnick), var->name);
+    botdata = (char *) var_get_bot_data(get_user_by_handle(userlist, (char *) botnick), var->name, true);
     olddata = botdata ? botdata : NULL;
     olddata = botdata ? botdata : NULL;
   } else                                  //use global, no bot specified
   } else                                  //use global, no bot specified
     olddata = var->gdata ? var->gdata : NULL;
     olddata = var->gdata ? var->gdata : NULL;

+ 3 - 1
src/set.h

@@ -67,7 +67,7 @@ typedef struct rate_b {
  interval_t time;
  interval_t time;
 } rate_t;
 } rate_t;
 
 
-extern char		auth_key[], auth_prefix[2], motd[], alias[], rbl_servers[1024],
+extern char		auth_key[], auth_prefix[2], motd[], alias[], rbl_servers[1024], groups[1024],
 			msgident[], msginvite[], msgop[], msgpass[], msgrelease[],
 			msgident[], msginvite[], msgop[], msgpass[], msgrelease[],
                         homechan[], altchars[];
                         homechan[], altchars[];
 extern bool		dccauth, auth_obscure, manop_warn, auth_chan, oidentd, ident_botnick, irc_autoaway, link_cleartext, use_deaf, use_callerid;
 extern bool		dccauth, auth_obscure, manop_warn, auth_chan, oidentd, ident_botnick, irc_autoaway, link_cleartext, use_deaf, use_callerid;
@@ -86,6 +86,8 @@ void var_parse_my_botset();
 void init_vars();
 void init_vars();
 void var_set_by_name(const char *, const char *, const char *);
 void var_set_by_name(const char *, const char *, const char *);
 void var_set_userentry(const char *, const char *, const char *);
 void var_set_userentry(const char *, const char *, const char *);
+const char *var_get_bot_data(struct userrec *u, const char *name, bool useDefault = false);
+const char *var_get_gdata(const char *name);
 int cmd_set_real(const char *, int idx, char *);
 int cmd_set_real(const char *, int idx, char *);
 const char *var_get_str_by_name(const char *);
 const char *var_get_str_by_name(const char *);
 
 

+ 0 - 1
src/tandem.h

@@ -14,7 +14,6 @@ typedef struct tand_t_struct {
   time_t buildts;
   time_t buildts;
   int localhub;
   int localhub;
   struct userrec* u;
   struct userrec* u;
-  char *not_chans;
   char commit[10];
   char commit[10];
   char bot[HANDLEN + 1];
   char bot[HANDLEN + 1];
   char version[151];
   char version[151];

+ 20 - 21
src/userent.c

@@ -299,23 +299,21 @@ static bool set_unpack(struct userrec *u, struct user_entry *e)
   head = curr = e->u.list;
   head = curr = e->u.list;
   e->u.extra = NULL;
   e->u.extra = NULL;
 
 
-  if (conf.bot->hub || conf.bot->localhub || !strcasecmp(conf.bot->nick, u->handle)) {
-    struct xtra_key *t = NULL;
-    char *key = NULL, *data = NULL;
-
-    while (curr) {
-      t = (struct xtra_key *) my_calloc(1, sizeof(struct xtra_key));
-      data = curr->extra;
-      key = newsplit(&data);
-      if (data[0]) {
-        t->key = strdup(key);
-        t->data = strdup(data);
-        list_insert((struct xtra_key **) (&e->u.extra), t);
-      } else
-        free(t);
-      curr = curr->next;
-    }
-  } 
+  struct xtra_key *t = NULL;
+  char *key = NULL, *data = NULL;
+
+  while (curr) {
+    t = (struct xtra_key *) my_calloc(1, sizeof(struct xtra_key));
+    data = curr->extra;
+    key = newsplit(&data);
+    if (data[0]) {
+      t->key = strdup(key);
+      t->data = strdup(data);
+      list_insert((struct xtra_key **) (&e->u.extra), t);
+    } else
+      free(t);
+    curr = curr->next;
+  }
 
 
   list_type_kill(head);
   list_type_kill(head);
   return 1;
   return 1;
@@ -365,12 +363,13 @@ static bool set_gotshare(struct userrec *u, struct user_entry *e, char *buf, int
 static void set_write_userfile(bd::Stream& stream, const struct userrec *u, const struct user_entry *e, int idx)
 static void set_write_userfile(bd::Stream& stream, const struct userrec *u, const struct user_entry *e, int idx)
 {
 {
   int localhub = nextbot(u->handle);
   int localhub = nextbot(u->handle);
-  /* only write if saving local, or if sending to hub, or if sending to same user as entry, or the localhub in the chain */
-  if (idx == -1 || dcc[idx].hub || dcc[idx].user == u || (localhub != -1 && idx == localhub)) {
-    struct xtra_key *x = (struct xtra_key *) e->u.extra;
+  struct xtra_key *x = (struct xtra_key *) e->u.extra;
 
 
-    for (; x; x = x->next)
+  for (; x; x = x->next) {
+    /* only write if saving local, or if sending to hub, or if sending to same user as entry, or the localhub in the chain, or sending 'groups' */
+    if (idx == -1 || dcc[idx].hub || dcc[idx].user == u || (localhub != -1 && idx == localhub) || !strcmp(x->key, "groups")) {
       stream << bd::String::printf("--%s %s %s\n", e->type->name, x->key, x->data ? x->data : "");
       stream << bd::String::printf("--%s %s %s\n", e->type->name, x->key, x->data ? x->data : "");
+    }
   }
   }
 }
 }