#define KEY_DNR_EXPIRE_FREQ "dnr_expire_freq"
#define KEY_MAX_CHAN_USERS "max_chan_users"
#define KEY_MAX_CHAN_BANS "max_chan_bans"
+#define KEY_MIN_TIME_BANS "min_time_bans"
#define KEY_NICK "nick"
#define KEY_OLD_CHANSERV_NAME "old_chanserv_name"
#define KEY_8BALL_RESPONSES "8ball"
#define KEY_REVOKED "revoked"
#define KEY_SUSPEND_EXPIRES "suspend_expires"
#define KEY_SUSPEND_REASON "suspend_reason"
+#define KEY_GIVEOWNERSHIP "giveownership"
+#define KEY_STAFF_ISSUER "staff_issuer"
+#define KEY_OLD_OWNER "old_owner"
+#define KEY_TARGET "target"
+#define KEY_TARGET_ACCESS "target_access"
#define KEY_VISITED "visited"
#define KEY_TOPIC "topic"
#define KEY_GREETING "greeting"
#define KEY_MAX_TIME "max_time"
#define KEY_NOTES "notes"
#define KEY_TOPIC_MASK "topic_mask"
+#define KEY_ADVTOPIC_ENTRIES "adv_topic"
#define KEY_OWNER_TRANSFER "owner_transfer"
#define KEY_EXPIRE "expire"
#define KEY_EXPIRES "expires"
#define KEY_TRIGGERED "triggered"
-#define CHANNEL_DEFAULT_FLAGS (CHANNEL_OFFCHANNEL | CHANNEL_UNREVIEWED)
+#define CHANNEL_DEFAULT_FLAGS (CHANNEL_UNREVIEWED)
#define CHANNEL_PRESERVED_FLAGS (CHANNEL_UNREVIEWED)
#define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
{ "CSMSG_TOPIC_LOCKED", "The %s topic is locked." },
{ "CSMSG_MASK_BUT_NO_TOPIC", "Warning: $b%s$b does not have a default topic, but you just set the topic mask." },
{ "CSMSG_TOPIC_MISMATCH", "Warning: The default topic for $b%s$b does not match the topic mask; changing it anyway." },
+ { "CSMSG_ADVTOPIC_INVALID_ID", "%d is an invalid advtopic id." },
{ "CSMSG_MODES_SET", "Channel modes are now $b%s$b." },
{ "CSMSG_DEFAULTED_MODES", "Channel modes for $b%s$b are set to their defaults." },
{ "CSMSG_INVALID_NUMERIC", "$b%d$b is not a valid choice. Choose one:" },
{ "CSMSG_SET_DEFAULT_TOPIC", "$bDefaultTopic$b %s" },
{ "CSMSG_SET_TOPICMASK", "$bTopicMask $b %s" },
+ { "CSMSG_SET_ADVTOPIC", "$bAdvTopic $b %s" },
{ "CSMSG_SET_GREETING", "$bGreeting $b %s" },
{ "CSMSG_SET_USERGREETING", "$bUserGreeting$b %s" },
{ "CSMSG_SET_MODES", "$bModes $b %s" },
{ "CSMSG_CHANNEL_REGISTERED", "$bRegistered: $b%s ago." },
{ "CSMSG_CHANNEL_VISITED", "$bVisited: $b%s ago." },
+ { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for %s" },
+ { "CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", " from %s to %s (%d access) by %s on %s (Reason: %s)" },
+ { "CSMSG_CHANNEL_OWNERSHIP_STAFF", " from %s to %s (%d access) by %s on %s" },
+ { "CSMSG_CHANNEL_OWNERSHIP_NORMAL", " from %s to %s (%d access) on %s" },
+
{ "CSMSG_PEEK_INFO", "$b%s$b Status:" },
{ "CSMSG_PEEK_TOPIC", "$bTopic: $b%s" },
{ "CSMSG_PEEK_MODES", "$bModes: $b%s" },
{ NULL, NULL }
};
+#define CSMSG_ALERT_REGISTERED "%s registered to %s by %s."
+#define CSMSG_ALERT_UNREGISTERED "%s %s"
+
/* eject_user and unban_user flags */
#define ACTION_KICK 0x0001
#define ACTION_BAN 0x0002
DECLARE_LIST(dnrList, struct do_not_register *);
DEFINE_LIST(dnrList, struct do_not_register *)
+#define chanserv_notice(target, format...) send_message(target , chanserv , ## format)
+#define chanserv_oper_message(format...) do { if(chanserv_conf.oper_channel) send_channel_message(chanserv_conf.oper_channel , chanserv , ## format); } while(0)
+
static int eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action);
struct userNode *chanserv;
unsigned int max_owned;
unsigned int max_chan_users;
unsigned int max_chan_bans;
+ unsigned int min_time_bans;
unsigned int max_userinfo_length;
unsigned int revoke_mode_a;
const char *new_channel_authed;
const char *new_channel_unauthed;
const char *new_channel_msg;
+
+ struct chanNode *oper_channel;
} chanserv_conf;
struct listData
struct chanData *channel;
enum levelOption lvlOpt;
enum charOption chOpt;
+ int i;
channel = calloc(1, sizeof(struct chanData));
channel->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
channel->chOpts[chOpt] = charOptions[chOpt].default_value;
+ for(i = 0; i < MAXADVTOPICENTRIES; i++)
+ channel->advtopic[i] = NULL;
channel->prev = NULL;
channel->next = channelList;
{
struct mod_chanmode change;
char msgbuf[MAXLEN];
+ int i;
/* After channel unregistration, the following must be cleaned
up:
free(channel->greeting);
free(channel->user_greeting);
free(channel->topic_mask);
+
+ for(i = 0; i < MAXADVTOPICENTRIES; i++) {
+ if(channel->advtopic[i])
+ free(channel->advtopic[i]);
+ }
if(channel->prev)
channel->prev->next = channel->next;
channel->channel->channel_info = NULL;
dict_delete(channel->notes);
- sprintf(msgbuf, "%s %s", channel->channel->name, reason);
if(!IsSuspended(channel))
DelChannelUser(chanserv, channel->channel, msgbuf, 0);
- global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, msgbuf);
+
+ chanserv_oper_message(CSMSG_ALERT_UNREGISTERED, channel->channel->name, reason);
UnlockChannel(channel->channel);
free(channel);
registered_channels--;
else
reply("CSMSG_REG_SUCCESS", channel->name);
- sprintf(reason, "%s registered to %s by %s.", channel->name, handle->handle, user->handle_info->handle);
- global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+ chanserv_oper_message(CSMSG_ALERT_REGISTERED, channel->name, handle->handle, user->handle_info->handle);
return 1;
}
{
duration = ParseInterval(argv[2]);
- if(duration < 15)
+ if(duration < chanserv_conf.min_time_bans)
{
reply("CSMSG_DURATION_TOO_LOW");
free(ban);
ary[1] = uData->handle->handle;
if(uData->present)
ary[2] = "Here";
+ else if(HANDLE_FLAGGED(uData->handle, NETWORK))
+ ary[2] = "Here";
else if(!uData->seen)
ary[2] = "Never";
else
ary[2] = strdup(ary[2]);
if(IsUserSuspended(uData))
ary[3] = "Suspended";
+ else if(HANDLE_FLAGGED(uData->handle, OPER))
+ ary[3] = "Operator";
+ else if(HANDLE_FLAGGED(uData->handle, HELPING))
+ ary[3] = "Staff";
+ else if(HANDLE_FLAGGED(uData->handle, NETWORK))
+ ary[3] = "Network";
else if(HANDLE_FLAGGED(uData->handle, FROZEN))
ary[3] = "Vacation";
else if(HANDLE_FLAGGED(uData->handle, BOT))
struct chanData *cData = channel->channel_info;
if(check_user_level(channel, user, lvlEnfTopic, 1, 0))
return 0;
- if(cData->topic_mask)
- return !match_ircglob(new_topic, cData->topic_mask);
+ if(cData->topic_mask)
+ {
+ if(cData->flags & CHANNEL_ADVTOPIC)
+ {
+ //this implementation is a little bit dirty but i haven't found a better, faster solution, yet...
+ char topicmask[TOPICLEN];
+ int skipnum, topicpos = 0;
+ char *ptr = cData->topic_mask;
+ for(;*ptr;ptr++) { //replace all the %[0-9]* variables with *
+ switch(*ptr) {
+ case '%':
+ for(skipnum = 0; isdigit(ptr[1]) && skipnum < 3; ptr++, skipnum++) {} //skip up to 3 numbers
+ if(skipnum)
+ topicmask[topicpos++] = '*';
+ else
+ topicmask[topicpos++] = *ptr;
+ break;
+ default:
+ topicmask[topicpos++] = *ptr;
+ break;
+ }
+ }
+ topicmask[topicpos] = 0;
+ return !match_ircglob(new_topic, topicmask);
+ }
+ else
+ return !match_ircglob(new_topic, cData->topic_mask);
+ }
else if(cData->topic)
return irccasecmp(new_topic, cData->topic);
else
char new_topic[TOPICLEN+1], tchar;
int pos=0, starpos=-1, dpos=0, len;
- while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
+ if(cData->flags & CHANNEL_ADVTOPIC)
{
- switch(tchar)
+ //first check if there is a leading 'modifier id'
+ int advtopic_index = 0;
+ char numbuf[4];
+ int numpos;
+ for(; topic[pos]; pos++)
{
- case '*':
- if(starpos != -1)
- goto bad_mask;
- len = strlen(topic);
- if((dpos + len) > TOPICLEN)
- len = TOPICLEN + 1 - dpos;
- memcpy(new_topic+dpos, topic, len);
- dpos += len;
- starpos = pos;
- break;
- case '\\': tchar = topic_mask[pos++]; /* and fall through */
- default: new_topic[dpos++] = tchar; break;
+ if(topic[pos] == ' ')
+ {
+ //leading number found, cut off and store value in advtopic_index
+ topic[pos] = 0;
+ advtopic_index = atoi(topic) - 1; //no zerobase
+ topic = &topic[pos+1];
+ /* If they say "!topic 2 *", unset advtopic id 2. */
+ if((topic[0] == '*') && (topic[1] == 0))
+ topic[0] = 0;
+ }
+ if(!isdigit(topic[pos]))
+ break;
+ }
+ if(advtopic_index < 0 || advtopic_index >= MAXADVTOPICENTRIES)
+ {
+ //invalid id!
+ reply("CSMSG_ADVTOPIC_INVALID_ID", advtopic_index+1);
+ return 0;
+ }
+ if(cData->advtopic[advtopic_index])
+ free(cData->advtopic[advtopic_index]);
+ cData->advtopic[advtopic_index] = (topic[0] ? strdup(topic) : NULL);
+ char *ptr = topic_mask;
+ while(*ptr && (dpos <= TOPICLEN))
+ {
+ switch(*ptr)
+ {
+ case '%':
+ ptr++;
+ for(numpos = 0; isdigit(*ptr) && numpos < 3; ptr++) {
+ numbuf[numpos++] = *ptr;
+ }
+ numbuf[numpos] = 0;
+ if(!numpos || (advtopic_index = atoi(numbuf)) <= 0 || advtopic_index > MAXADVTOPICENTRIES) {
+ ptr -= numpos+1;
+ new_topic[dpos++] = *ptr; //is % again
+ break;
+ }
+ advtopic_index--; //no zero base
+ if(!cData->advtopic[advtopic_index])
+ break; //just leave it empty
+ len = strlen(cData->advtopic[advtopic_index]);
+ if((dpos + len) > TOPICLEN)
+ len = TOPICLEN + 1 - dpos;
+ memcpy(new_topic+dpos, cData->advtopic[advtopic_index], len);
+ dpos += len;
+ break;
+ case '\\':
+ ptr++; /* and fall through */
+ if(!*ptr) break;
+ default:
+ new_topic[dpos++] = *ptr;
+ ptr++;
+ break;
+ }
+ }
+ } else {
+ while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
+ {
+ switch(tchar)
+ {
+ case '*':
+ if(starpos != -1)
+ goto bad_mask;
+ len = strlen(topic);
+ if((dpos + len) > TOPICLEN)
+ len = TOPICLEN + 1 - dpos;
+ memcpy(new_topic+dpos, topic, len);
+ dpos += len;
+ starpos = pos;
+ break;
+ case '\\': tchar = topic_mask[pos++]; /* and fall through */
+ default: new_topic[dpos++] = tchar; break;
+ }
+ }
+ if((dpos > TOPICLEN) || tchar)
+ {
+ bad_mask:
+ reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
+ reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
+ return 0;
}
- }
- if((dpos > TOPICLEN) || tchar)
- {
- bad_mask:
- reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
- reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
- return 0;
}
new_topic[dpos] = 0;
SetChannelTopic(channel, chanserv, new_topic, 1);
}
}
+static void
+show_giveownership_info(struct svccmd *cmd, struct userNode *user, struct giveownership *giveownership)
+{
+ char buf[MAXLEN];
+ const char *fmt = "%a %b %d %H:%M %Y";
+ strftime(buf, sizeof(buf), fmt, localtime(&giveownership->issued));
+
+ if(giveownership->staff_issuer)
+ {
+ if(giveownership->reason)
+ reply("CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", giveownership->old_owner,
+ giveownership->target, giveownership->target_access,
+ giveownership->staff_issuer, buf, giveownership->reason);
+ else
+ reply("CSMSG_CHANNEL_OWNERSHIP_STAFF", giveownership->old_owner,
+ giveownership->target, giveownership->target_access,
+ giveownership->staff_issuer, buf);
+ }
+ else
+ {
+ reply("CSMSG_CHANNEL_OWNERSHIP_NORMAL", giveownership->old_owner, giveownership->target, giveownership->target_access, buf);
+ }
+}
+
static CHANSERV_FUNC(cmd_info)
{
char modes[MAXLEN], buffer[INTERVALLEN];
reply("CSMSG_CHANNEL_SUSPENDED", channel->name);
show_suspension_info(cmd, user, cData->suspended);
}
+
+ if(cData->giveownership && ((uData && (uData->access >= UL_COOWNER)) || IsStaff(user)))
+ {
+ struct giveownership *giveownership;
+ reply("CSMSG_CHANNEL_OWNERSHIP_HISTORY", channel->name);
+ for(giveownership = cData->giveownership; giveownership; giveownership = giveownership->previous)
+ show_giveownership_info(cmd, user, giveownership);
+ }
return 1;
}
CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
}
+static MODCMD_FUNC(chan_opt_advtopic)
+{
+ CHANNEL_BINARY_OPTION("CSMSG_SET_ADVTOPIC", CHANNEL_ADVTOPIC);
+}
+
static MODCMD_FUNC(chan_opt_offchannel)
{
struct chanData *cData = channel->channel_info;
struct chanData *cData = channel->channel_info;
struct do_not_register *dnr;
const char *confirm;
- unsigned int force;
- unsigned short co_access;
- char reason[MAXLEN];
+ struct giveownership *giveownership;
+ unsigned int force, override;
+ unsigned short co_access, new_owner_old_access;
+ char reason[MAXLEN], transfer_reason[MAXLEN];
REQUIRE_PARAMS(2);
curr_user = GetChannelAccess(cData, user->handle_info);
force = IsHelping(user) && (argc > 2) && !irccasecmp(argv[2], "force");
+
+ struct userData *uData = _GetChannelUser(channel->channel_info, user->handle_info, 1, 0);
+ override = ((cmd->effective_flags & MODCMD_REQUIRE_CHANUSER)
+ && (uData->access > 500)
+ && (!(uData = _GetChannelUser(channel->channel_info, user->handle_info, 0, 0))
+ || uData->access < 500));
+
if(!curr_user || (curr_user->access != UL_OWNER))
{
struct userData *owner = NULL;
return 0;
}
}
+ new_owner_old_access = new_owner->access;
if(new_owner->access >= UL_COOWNER)
co_access = new_owner->access;
else
if(curr_user)
curr_user->access = co_access;
cData->ownerTransfer = now;
+ giveownership = calloc(1, sizeof(*giveownership));
+ giveownership->issued = now;
+ giveownership->old_owner = curr_user->handle->handle;
+ giveownership->target = new_owner_hi->handle;
+ giveownership->target_access = new_owner_old_access;
+ if(override)
+ {
+ if(argc > (2 + force))
+ {
+ unsplit_string(argv + 2 + force, argc - 2 - force, transfer_reason);
+ giveownership->reason = strdup(transfer_reason);
+ }
+ giveownership->staff_issuer = strdup(user->handle_info->handle);
+ }
+
+ giveownership->previous = channel->channel_info->giveownership;
+ channel->channel_info->giveownership = giveownership;
reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
return 1;
}
+static void
+chanserv_expire_user_suspension(void *data)
+{
+ struct userData *target = data;
+
+ target->expires = 0;
+ target->flags &= ~USER_SUSPENDED;
+}
+
static CHANSERV_FUNC(cmd_suspend)
{
struct handle_info *hi;
struct userData *actor, *real_actor, *target;
unsigned int override = 0;
+ time_t expiry;
- REQUIRE_PARAMS(2);
+ REQUIRE_PARAMS(3);
if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
actor = GetChannelUser(channel->channel_info, user->handle_info);
real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
}
if(!real_actor || target->access >= real_actor->access)
override = CMD_LOG_OVERRIDE;
+ if(!strcmp(argv[2], "0"))
+ expiry = 0;
+ else
+ {
+ unsigned int duration;
+ if(!(duration = ParseInterval(argv[2])))
+ {
+ reply("MSG_INVALID_DURATION", argv[2]);
+ return 0;
+ }
+ expiry = now + duration;
+ }
+
+ target->expires = expiry;
+
+ if(target->expires)
+ timeq_add(target->expires, chanserv_expire_user_suspension, target);
+
target->flags |= USER_SUSPENDED;
reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
return 1 | override;
}
if(!real_actor || target->access >= real_actor->access)
override = CMD_LOG_OVERRIDE;
+ timeq_del(target->expires, chanserv_expire_user_suspension, target, 0);
target->flags &= ~USER_SUSPENDED;
scan_user_presence(target, NULL);
reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
chanserv_conf.max_chan_users = str ? atoi(str) : 512;
str = database_get_data(conf_node, KEY_MAX_CHAN_BANS, RECDB_QSTRING);
chanserv_conf.max_chan_bans = str ? atoi(str) : 512;
+ str = database_get_data(conf_node, KEY_MIN_TIME_BANS, RECDB_QSTRING);
+ chanserv_conf.min_time_bans = str ? atoi(str) : 5;
str = database_get_data(conf_node, KEY_MAX_USERINFO_LENGTH, RECDB_QSTRING);
chanserv_conf.max_userinfo_length = str ? atoi(str) : 400;
str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
chanserv_conf.old_ban_names = strlist;
str = database_get_data(conf_node, "off_channel", RECDB_QSTRING);
off_channel = str ? atoi(str) : 0;
+
+ str = database_get_data(conf_node, "oper_chan", RECDB_QSTRING);
+ if(str)
+ {
+ chanserv_conf.oper_channel = AddChannel(str, now, "+tinms", NULL);
+ }
+ else
+ {
+ chanserv_conf.oper_channel = NULL;
+ }
}
static void
{
struct handle_info *handle;
struct userData *uData;
- char *seen, *inf, *flags, *voted, *votefor;
+ char *seen, *inf, *flags, *voted, *votefor, *expires;
unsigned long last_seen;
unsigned short access_level;
seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
last_seen = seen ? strtoul(seen, NULL, 0) : now;
flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
+ expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
voted = database_get_data(rd->d.object, KEY_VOTE_VOTED, RECDB_QSTRING);
votefor = database_get_data(rd->d.object, KEY_VOTE_VOTEDFOR, RECDB_QSTRING);
handle = get_handle_info(key);
uData = add_channel_user(chan, handle, access_level, last_seen, inf);
uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+ uData->expires = expires ? (signed)strtoul(expires, NULL, 0) : 0;
+
+ if((uData->flags & USER_SUSPENDED) && uData->expires)
+ {
+ if(uData->expires > now)
+ timeq_add(uData->expires, chanserv_expire_user_suspension, uData);
+ else
+ uData->flags &= ~USER_SUSPENDED;
+ }
if(chan->vote) {
uData->voted = voted ? strtoul(voted, NULL, 0) : 0;
uData->votefor = votefor ? strtoul(votefor, NULL, 0) : 0;
return suspended;
}
+static struct giveownership *
+chanserv_read_giveownership(dict_t obj)
+{
+ struct giveownership *giveownership = calloc(1, sizeof(*giveownership));
+ char *str;
+ dict_t previous;
+
+ str = database_get_data(obj, KEY_STAFF_ISSUER, RECDB_QSTRING);
+ giveownership->staff_issuer = str ? strdup(str) : NULL;
+
+ giveownership->old_owner = strdup(database_get_data(obj, KEY_OLD_OWNER, RECDB_QSTRING));
+
+ giveownership->target = strdup(database_get_data(obj, KEY_TARGET, RECDB_QSTRING));
+ giveownership->target_access = atoi(database_get_data(obj, KEY_TARGET_ACCESS, RECDB_QSTRING));
+
+ str = database_get_data(obj, KEY_REASON, RECDB_QSTRING);
+ giveownership->reason = str ? strdup(str) : NULL;
+ str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
+ giveownership->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
+
+ previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
+ giveownership->previous = previous ? chanserv_read_giveownership(previous) : NULL;
+ return giveownership;
+}
+
static int
chanserv_channel_read(const char *key, struct record_data *hir)
{
struct suspended *suspended;
+ struct giveownership *giveownership;
struct mod_chanmode *modes;
struct chanNode *cNode;
struct chanData *cData;
cData->flags &= ~CHANNEL_SUSPENDED;
}
+ if((obj = database_get_data(hir->d.object, KEY_GIVEOWNERSHIP, RECDB_OBJECT)))
+ {
+ giveownership = chanserv_read_giveownership(obj);
+ cData->giveownership = giveownership;
+ }
+
if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) {
struct mod_chanmode change;
mod_chanmode_init(&change);
}
}
+ obj = database_get_data(channel, KEY_ADVTOPIC_ENTRIES, RECDB_OBJECT);
+ for(it = dict_first(obj); it; it = iter_next(it))
+ {
+ struct record_data *rd = iter_data(it);
+ if(rd->type != RECDB_QSTRING) continue;
+ int advtopic_index = atoi(iter_key(it));
+ if(advtopic_index < 0 || advtopic_index >= MAXADVTOPICENTRIES) continue;
+ cData->advtopic[advtopic_index] = (rd ? strdup(rd->d.qstring) : NULL);
+ }
+
if(!IsSuspended(cData)
&& (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
&& (argc = split_line(str, 0, ArrayLength(argv), argv))
saxdb_write_int(ctx, KEY_SEEN, uData->seen);
if(uData->flags)
saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
+ if(uData->expires)
+ saxdb_write_int(ctx, KEY_EXPIRES, uData->expires);
if(uData->channel->vote && uData->voted)
saxdb_write_int(ctx, KEY_VOTE_VOTED, uData->voted);
if(uData->channel->vote && uData->votefor)
saxdb_end_record(ctx);
}
+static void
+chanserv_write_giveownership(struct saxdb_context *ctx, const char *name, struct giveownership *giveownership)
+{
+ saxdb_start_record(ctx, name, 0);
+ if(giveownership->staff_issuer)
+ saxdb_write_string(ctx, KEY_STAFF_ISSUER, giveownership->staff_issuer);
+ if(giveownership->old_owner)
+ saxdb_write_string(ctx, KEY_OLD_OWNER, giveownership->old_owner);
+ if(giveownership->target)
+ saxdb_write_string(ctx, KEY_TARGET, giveownership->target);
+ if(giveownership->target_access)
+ saxdb_write_int(ctx, KEY_TARGET_ACCESS, giveownership->target_access);
+ if(giveownership->reason)
+ saxdb_write_string(ctx, KEY_REASON, giveownership->reason);
+ if(giveownership->issued)
+ saxdb_write_int(ctx, KEY_ISSUED, giveownership->issued);
+ if(giveownership->previous)
+ chanserv_write_giveownership(ctx, KEY_PREVIOUS, giveownership->previous);
+ saxdb_end_record(ctx);
+}
+
static void
chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
{
saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask);
if(channel->suspended)
chanserv_write_suspended(ctx, "suspended", channel->suspended);
+ if(channel->giveownership)
+ chanserv_write_giveownership(ctx, "giveownership", channel->giveownership);
if(channel->expiry)
saxdb_write_int(ctx, KEY_EXPIRE, channel->expiry);
high_present = chanserv_write_users(ctx, channel->users);
chanserv_write_bans(ctx, channel->bans);
+ if(channel->flags & CHANNEL_ADVTOPIC) {
+ saxdb_start_record(ctx, KEY_ADVTOPIC_ENTRIES, 0);
+ int advtopic_index;
+ for(advtopic_index = 0; advtopic_index < MAXADVTOPICENTRIES; advtopic_index++) {
+ if(channel->advtopic[advtopic_index])
+ saxdb_write_string(ctx, strtab(advtopic_index), channel->advtopic[advtopic_index]);
+ }
+ saxdb_end_record(ctx);
+ }
+
if(dict_size(channel->notes))
{
dict_iterator_t it;
DEFINE_CHANNEL_OPTION(ctcpusers);
DEFINE_CHANNEL_OPTION(ctcpreaction);
DEFINE_CHANNEL_OPTION(inviteme);
+ DEFINE_CHANNEL_OPTION(advtopic);
DEFINE_CHANNEL_OPTION(unreviewed);
modcmd_register(chanserv_module, "set expire", chan_opt_expire, 1, 0, "flags", "+helping", NULL);
modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL);