New feature: channel logs
authorAri Johnson <ari@theari.com>
Mon, 19 Jan 2015 17:22:46 +0000 (12:22 -0500)
committerAri Johnson <ari@theari.com>
Mon, 19 Jan 2015 17:22:46 +0000 (12:22 -0500)
.gitignore
game/log/chat/README [new file with mode: 0644]
game/mushcnf.dst
hdrs/conf.h
hdrs/extchat.h
src/conf.c
src/extchat.c

index f19caa36d406848a7818d89d3095766b90634c01..429ffdcbd720b5f70fa48363ef55549f693afd24 100644 (file)
@@ -62,6 +62,7 @@ game/data/indb.*
 game/data/PANIC.db
 game/save/*
 game/log/*.log
+game/log/chat/*.log
 game/txt/compose.sh
 game/txt/connect.txt
 game/txt/events.txt
diff --git a/game/log/chat/README b/game/log/chat/README
new file mode 100644 (file)
index 0000000..fa0291f
--- /dev/null
@@ -0,0 +1,2 @@
+This is the default location for chat logs. Files here are named according to
+the channel name with .log appended.
index 39801bb37613480bedc62c383cd46d6d08eba11d..968146174fcf56e4386b841fcc59e317441ee3a7 100644 (file)
@@ -535,6 +535,9 @@ log_commands        no
 # log forces done by wizards
 log_forces     yes
 
+# Directory to store chat logs
+chatlog_dir log/chat/
+
 ###
 ### Logins
 ###
index feca962b845545b181ff48893ea3d30d1a29fae8..31220eb8e24442be4c6f27c51ab41e93b7ac00e2 100644 (file)
@@ -306,6 +306,7 @@ struct options_table {
   char command_log[256]; /**< File to log suspect commands */
   char trace_log[256]; /**< File to log trace data */
   char checkpt_log[256]; /**< File to log checkpoint data */
+  char chatlog_dir[256]; /**< Directory to store chat logs */
 #ifdef HAS_MYSQL
   char sql_platform[256]; /**< Type of SQL server, or "disabled" */
   char sql_host[256]; /**< Hostname of sql server */
@@ -475,6 +476,7 @@ int cf_time(const char *opt, const char *val, void *loc, int maxval,
 #define CMDLOG (options.command_log)
 #define TRACELOG (options.trace_log)
 #define CHECKLOG (options.checkpt_log)
+#define CHATLOGDIR (options.chatlog_dir)
 #ifdef HAS_MYSQL
 #define SQL_PLATFORM (options.sql_platform)
 #define SQL_HOST (options.sql_host)
index 6e09fe683c8a0c87d88f05e2303d19eeabc94ef4..b005bb236485f5a910402697661d3d164079c756 100644 (file)
@@ -103,6 +103,7 @@ struct channel {
   boolexp hidelock;    /**< Who may hide from view */
   struct channel *next;                /**< Next channel in linked list */
   BUFFERQ *bufferq;            /**< Pointer to channel recall buffer queue */
+  FILE *logfile;       /**< File to log channel in */
 };
 
 /** A list of channels on an object.
@@ -138,6 +139,7 @@ struct na_cpass {
 #define CHANNEL_NOCEMIT 0x400  /* Disallow @cemit */
 #define CHANNEL_COBJ   0x800  /* Channel with a channel object */
 #define CHANNEL_INTERACT       0x1000  /* Filter channel output through interactions */
+#define CHANNEL_LOG    0x2000  /* Log the channel to a file */
 #define CHANNEL_DEFAULT_FLAGS   (CHANNEL_PLAYER)
 #define CL_JOIN 0x1
 #define CL_SPEAK 0x2
@@ -168,6 +170,7 @@ int ChanObjCheck _((CHAN *c));
 #define ChanSeeLock(c) ((c)->seelock)
 #define ChanHideLock(c) ((c)->hidelock)
 #define ChanBufferQ(c) ((c)->bufferq)
+#define ChanLogFile(c) ((c)->logfile)
 #define Channel_Quiet(c)        (ChanType(c) & CHANNEL_QUIET)
 #define Channel_Open(c) (ChanType(c) & CHANNEL_OPEN)
 #define Channel_Object(c) (ChanType(c) & CHANNEL_OBJECT)
@@ -180,6 +183,7 @@ int ChanObjCheck _((CHAN *c));
 #define Channel_NoNames(c) (ChanType(c) & CHANNEL_NONAMES)
 #define Channel_NoCemit(c) (ChanType(c) & CHANNEL_NOCEMIT)
 #define Channel_Interact(c) (ChanType(c) & CHANNEL_INTERACT)
+#define Channel_Log(c) (ChanType(c) & CHANNEL_LOG)
 #define Chan_Ok_Type(c,o) \
         ((ChanObj(c) == o) || (IsPlayer(o) && Channel_Player(c)) || \
          (IsThing(o) && Channel_Object(c)))
index fa65768074554dce4b638289053d90a5ee0a4d87..0e71f63ff564203282ec683bd9ffd4391f55e43d 100644 (file)
@@ -376,6 +376,9 @@ COBRA_CONF conftable[] = {
   {"connect_log", cf_str, options.connect_log, sizeof options.connect_log, 0,
    "log"}
   ,
+  {"chatlog_dir", cf_str, options.chatlog_dir, sizeof options.chatlog_dir, 0,
+   "log"}
+  ,
 
   {"player_flags", cf_flag, options.player_flags, sizeof options.player_flags,
    0, "flags"}
@@ -1122,6 +1125,7 @@ conf_default_set(void)
   strcpy(options.trace_log, "");
   strcpy(options.wizard_log, "");
   strcpy(options.checkpt_log, "");
+  strcpy(options.chatlog_dir, "");
   options.log_commands = 0;
   options.log_forces = 1;
   options.support_pueblo = 0;
index 7fba5d272515395337a703437afc7fedc537e350..edec670f3ae6c2706d01c0c7f3a218c4a6e1d31f 100644 (file)
@@ -15,6 +15,8 @@
 #include <sys/types.h>
 #endif
 #include <stdarg.h>
+#include <sys/stat.h>
+#include <errno.h>
 #include "conf.h"
 #include "externs.h"
 #include "attrib.h"
@@ -103,6 +105,9 @@ static void format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim,
                                     const char *extra);
 static void list_partial_matches(dbref player, const char *name,
                                 enum chan_match_type type);
+static void begin_chat_log(CHAN *chan);
+static void end_chat_log(CHAN *chan);
+static void write_chat_log(CHAN *chan, char *buf);
 
 const char *chan_speak_lock = "ChanSpeakLock"; /**< Name of speak lock */
 const char *chan_join_lock = "ChanJoinLock";   /**< Name of join lock */
@@ -140,6 +145,7 @@ static PRIV priv_table[] = {
   {"NoCemit", 'C', CHANNEL_NOCEMIT, CHANNEL_NOCEMIT},
   {"Interact", 'I', CHANNEL_INTERACT, CHANNEL_INTERACT},
   {"ChanObj", 'Z', CHANNEL_COBJ, CHANNEL_COBJ},
+  {"LogChannel", 'L', CHANNEL_LOG, CHANNEL_LOG},
   {NULL, '\0', 0, 0}
 };
 
@@ -232,6 +238,8 @@ load_chatdb_oldstyle(FILE * fp)
       return 0;
     }
     insert_channel(&ch);
+    if(Channel_Log(ch))
+      begin_chat_log(ch);
   }
   num_channels = i;
 
@@ -305,6 +313,8 @@ load_chatdb(FILE * fp)
       return 0;
     }
     insert_channel(&ch);
+    if(Channel_Log(ch))
+      begin_chat_log(ch);
   }
   num_channels = i;
 
@@ -346,6 +356,7 @@ new_channel(void)
   ChanMaxUsers(ch) = 0;
   ChanUsers(ch) = NULL;
   ChanBufferQ(ch) = NULL;
+  ch->logfile = NULL;
   return ch;
 }
 
@@ -373,6 +384,8 @@ free_channel(CHAN *c)
   CHANUSER *u, *unext;
   if (!c)
     return;
+  if(c->logfile)
+    fclose(c->logfile);
   free_boolexp(ChanJoinLock(c));
   free_boolexp(ChanSpeakLock(c));
   free_boolexp(ChanHideLock(c));
@@ -1867,6 +1880,8 @@ do_chan_admin(dbref player, char *name, const char *perms, int flag)
     ChanCreator(chan) = Owner(player);
     strcpy(ChanName(chan), name);
     insert_channel(&chan);
+    if(Channel_Log(chan))
+      begin_chat_log(chan);
     notify_format(player, T("CHAT: Channel <%s> created."), ChanName(chan));
     break;
   case 1:
@@ -1876,6 +1891,8 @@ do_chan_admin(dbref player, char *name, const char *perms, int flag)
       notify(player, T("Permission denied."));
       return;
     }
+    if(Channel_Log(chan))
+      end_chat_log(chan);
     /* remove everyone from the channel */
     channel_wipe(player, chan);
     /* refund the owner's money */
@@ -1909,9 +1926,13 @@ do_chan_admin(dbref player, char *name, const char *perms, int flag)
     }
     /* When we rename a channel, we actually remove it and re-insert it */
     strcpy(old, ChanName(chan));
+    if(Channel_Log(chan))
+      end_chat_log(chan);
     remove_channel(chan);
     strcpy(ChanName(chan), perms);
     insert_channel(&chan);
+    if(Channel_Log(chan))
+      begin_chat_log(chan);
     channel_broadcast(chan, player, 0,
                      "<%s> %s has renamed channel %s to %s.",
                      ChanName(chan), Name(player), old, ChanName(chan));
@@ -1938,6 +1959,10 @@ do_chan_admin(dbref player, char *name, const char *perms, int flag)
                    ("Invalid or same permissions on channel <%s>. No changes made."),
                    ChanName(chan));
     } else {
+      if(Channel_Log(chan) && !(type & CHANNEL_LOG))
+        end_chat_log(chan);
+      else if(!Channel_Log(chan) && (type & CHANNEL_LOG))
+        begin_chat_log(chan);
       ChanType(chan) = type;
       notify_format(player,
                    T("Permissions on channel <%s> changed."), ChanName(chan));
@@ -3490,6 +3515,8 @@ channel_broadcast(CHAN *channel, dbref player, int flags, const char *fmt, ...)
   if (ChanBufferQ(channel))
     add_to_bufferq(ChanBufferQ(channel), 0,
                   (flags & CB_NOSPOOF) ? player : NOTHING, tbuf1);
+  if(Channel_Log(channel))
+    write_chat_log(channel, tbuf1);
 }
 
 
@@ -3692,6 +3719,87 @@ format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim, int flags,
     channel_broadcast(chan, victim, flags, msg, ChanObjName(chan), Name(victim));
 }
 
+static void
+begin_chat_log(CHAN *chan)
+{
+  struct stat st;
+  char filename[512];
+  int i, j;
+
+  if(!*CHATLOGDIR)
+    return;
+
+  if(stat(CHATLOGDIR, &st)) {
+    do_rawlog(LT_ERR, T("Unable to stat() chatlog_dir: %s"), strerror(errno));
+    return;
+  }
+
+  if(!S_ISDIR(st.st_mode)) {
+    do_rawlog(LT_ERR, T("chatlog_dir is not a directory"));
+    return;
+  }
+
+  strcpy(filename, CHATLOGDIR); /* N.B.: CHATLOGDIR is at most 256 bytes */
+  i = strlen(filename);
+  if(filename[i-1] != '/') {
+    filename[i] = '/';
+    i++;
+  }
+
+  j = strlen(ChanName(chan));
+  if((i + j) > 507) {
+    do_rawlog(LT_ERR,
+              T("Buffer length exceeded for chat log filename for channel %s"),
+              ChanName(chan));
+    return;
+  }
+
+  strcpy(filename + i, ChanName(chan));
+  i += j;
+
+  strcpy(filename + i, ".log");
+  filename[511] = '\0';
+
+  ChanLogFile(chan) = fopen(filename, "a");
+  if(!ChanLogFile(chan)) {
+    do_rawlog(LT_ERR, T("Unable to open log file for channel %s: %s"),
+              ChanName(chan), strerror(errno));
+    return;
+  }
+
+  write_chat_log(chan, "Beginning of log.");
+}
+
+static void
+end_chat_log(CHAN *chan)
+{
+  if(!ChanLogFile(chan))
+    return;
+
+  write_chat_log(chan, "End of log.");
+  fclose(ChanLogFile(chan));
+  ChanLogFile(chan) = NULL;
+}
+
+static void
+write_chat_log(CHAN *chan, char *buf)
+{
+  char tbuf[BUFFER_LEN];
+  struct tm *when;
+  char *clean;
+
+  if(!ChanLogFile(chan))
+    return;
+
+  when = localtime(&mudtime);
+  strftime(tbuf, sizeof tbuf, "[%m/%d %H:%M:%S] ", when);
+  fputs(tbuf, ChanLogFile(chan));
+
+  clean = remove_markup(buf, NULL);
+  fputs(clean, ChanLogFile(chan));
+  fputc('\n', ChanLogFile(chan));
+  fflush(ChanLogFile(chan));
+}
 
 static void
 do_reset_cobj(player, name)