#include "conf.h"
#include "global.h"
#include "modcmd.h"
-#include "opserv.h" /* for opserv_bad_channel() */
+#include "opserv.h" /* for opserv_bad_channel() and devnull management */
#include "nickserv.h" /* for oper_outranks() */
#include "saxdb.h"
#include "spamserv.h"
#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
#define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
#define KEY_INVITED_INTERVAL "invite_timeout"
+#define KEY_REVOKE_MODE_A "revoke_mode_a"
+#define KEY_NEW_CHANNEL_AUTHED "new_channel_authed_join"
+#define KEY_NEW_CHANNEL_UNAUTHED "new_channel_unauthed_join"
+#define KEY_NEW_CHANNEL_MSG "new_channel_message"
/* ChanServ database */
#define KEY_CHANNELS "channels"
#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_INFO "info"
#define KEY_SEEN "seen"
+/* Votes */
+#define KEY_VOTE "vote"
+#define KEY_VOTE_START "votestart"
+#define KEY_VOTE_OPTIONS "voptions"
+#define KEY_VOTE_OPTION_NAME "voptionname"
+#define KEY_VOTE_VOTED "vvoted"
+#define KEY_VOTE_VOTEDFOR "vvotefor"
+#define KEY_VOTE_OPTION_ID "voptionid"
+#define KEY_VOTE_OPTION_VOTED "voptionvoted"
+
/* Ban data */
#define KEY_OWNER "owner"
#define KEY_REASON "reason"
/* User management */
{ "CSMSG_ADDED_USER", "Added %s to the %s user list with access %d." },
{ "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." },
- { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." },
+ { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be lower than maximum (%d)." },
{ "CSMSG_DELETED_USERS", "Deleted accounts matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list." },
{ "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." },
{ "CSMSG_INCORRECT_ACCESS", "%s has access $b%d$b, not %s." },
{ "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_SET_USERINFO", "$bUserInfo $b %d" },
{ "CSMSG_SET_GIVE_VOICE", "$bGiveVoice $b %d" },
{ "CSMSG_SET_TOPICSNARF", "$bTopicSnarf $b %d" },
+ { "CSMSG_SET_VOTE", "$bVote $b %d" },
{ "CSMSG_SET_INVITEME", "$bInviteMe $b %d" },
{ "CSMSG_SET_ENFOPS", "$bEnfOps $b %d" },
{ "CSMSG_SET_GIVE_OPS", "$bGiveOps $b %d" },
{ "CSMSG_ACCESS_SEARCH_HEADER", "%s users from level %d to %d matching %s:" },
{ "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
{ "CSMSG_CHANGED_ACCESS", "%s now has access $b%d$b in %s." },
+ { "CSMSG_TOTAL_USERS", "There are $b%d$b users in %s." },
/* Channel note list */
{ "CSMSG_NOTELIST_HEADER", "Notes for $b%s$b:" },
{ "CSMSG_UC_H_TITLE", "network helper" },
{ "CSMSG_LC_H_TITLE", "support helper" },
{ "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." },
+ { "CSMSG_MYACCESS_COUNT", "%s has access in $b%d$b channels and is owner of $b%d$b channel(s)." },
+ { "CSMSG_MYACCESS_COUNT_1", "%s has access in $b%d$b channel and is owner of $b%d$b channel(s)." },
+
/* Seen information */
{ "CSMSG_NEVER_SEEN", "%s has never been seen in $b%s$b." },
{ "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" },
{ "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
{ "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
+/* Vote */
+ { "CSMSG_ADDVOTE_DONE", "Vote added. Use $baddoption$b to add at least 2 vote options and then $bstartvote$b to start the voting." },
+ { "CSMSG_ADDVOTE_FULL", "There is already a vote in this channel. Use $bdelvote$b to delete it." },
+ { "CSMSG_DELVOTE_DONE", "Vote deleted." },
+ { "CSMSG_NO_VOTE", "There is no vote in this channel." },
+ { "CSMSG_ADDOPTION_DONE", "Vote option added with id %i (%i - %i)." },
+ { "CSMSG_DELOPTION_DONE", "Vote option deleted." },
+ { "CSMSG_DELOPTION_NONE", "Vote option does not exist." },
+ { "CSMSG_VOTE_NEED_OPTIONS", "There must be at least 2 options in a vote." },
+ { "CSMSG_VOTE_NOT_STARTED", "The vote is not started. Use $bstartvote$b to allow voting." },
+ { "CSMSG_VOTE_QUESTION", "Question: %s" },
+ { "CSMSG_VOTE_OPTION", "$b%i$b: %s ($b%i$b votes)" },
+ { "CSMSG_VOTERES_QUESTION", "Question: %s" },
+ { "CSMSG_VOTERES_OPTION", "\ 2%i\ 2: %s (\ 2%i\ 2 votes)" },
+ { "CSMSG_STARTVOTE_TOP", "\ 2%s\ 2 has started a voting:" },
+ { "CSMSG_STARTVOTE_QUESTION", "\ 2Question:\ 2 %s" },
+ { "CSMSG_STARTVOTE_OPTION", "\ 2%i:\ 2 %s" },
+ { "CSMSG_STARTVOTE_ACCESS", "All channel users with at least \ 2%i\ 2 access can vote." },
+ { "CSMSG_STARTVOTE_HOWTO", "To vote for an option, use \ 2vote ID\ 2. To see the available options and the current votes, use \ 2vote\ 2 without parameters." },
+ { "CSMSG_STARTVOTE_RUNNING", "The vote is already running." },
+ { "CSMSG_VOTE_VOTED", "You have already voted." },
+ { "CSMSG_VOTE_INVALID", "Vote option does not exist." },
+ { "CSMSG_VOTE_DONE", "You voted for $b%s$b." },
+ { "CSMSG_ENDVOTE_DONE", "The vote has been finished." },
+ { "CSMSG_ENDVOTE_STOPPED", "The vote is not running." },
+
/* Other things */
{ "CSMSG_EVENT_SEARCH_RESULTS", "The following channel events were found:" },
{ NULL, NULL }
unsigned int max_chan_users;
unsigned int max_chan_bans;
unsigned int max_userinfo_length;
+
+ unsigned int revoke_mode_a;
struct string_list *set_shows;
struct string_list *eightball;
const char *irc_operator_epithet;
const char *network_helper_epithet;
const char *support_helper_epithet;
+
+ const char *new_channel_authed;
+ const char *new_channel_unauthed;
+ const char *new_channel_msg;
} chanserv_conf;
struct listData
{ "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9, 0, 0 },
{ "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 },
{ "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 },
- { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }
+ { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 },
+ { "CSMSG_SET_VOTE", "vote", 100, ~0, 0, 0 }
};
struct charOptionValues {
#define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0)
#define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1)
+static void unregister_channel(struct chanData *channel, const char *reason);
unsigned short
user_level_from_name(const char *name, unsigned short clamp_level)
return ntype;
}
+static void
+free_vote_options(void *data)
+{
+ struct vote_option *vOpt = data;
+ free(vOpt->name);
+ free(vOpt->option_str);
+ free(vOpt);
+}
+
static void
chanserv_deref_note_type(void *data)
{
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;
channel->channel = cNode;
LockChannel(cNode);
cNode->channel_info = channel;
+
+ channel->vote = NULL;
return channel;
}
{
struct mod_chanmode change;
char msgbuf[MAXLEN];
+ int i;
/* After channel unregistration, the following must be cleaned
up:
timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
- if(off_channel > 0)
- {
- mod_chanmode_init(&change);
- change.modes_clear |= MODE_REGISTERED;
- mod_chanmode_announce(chanserv, channel->channel, &change);
+ if(off_channel > 0 || chanserv_conf.revoke_mode_a) {
+ mod_chanmode_init(&change);
+ if(off_channel > 0)
+ change.modes_clear |= MODE_REGISTERED;
+ if(chanserv_conf.revoke_mode_a)
+ change.modes_clear |= MODE_ACCESS;
+ mod_chanmode_announce(chanserv, channel->channel, &change);
}
while(channel->users)
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;
}
static void
-expire_channels(UNUSED_ARG(void *data))
+expire_channels(void *data)
{
struct chanData *channel, *next;
struct userData *user;
unregister_channel(channel, "registration expired.");
}
- if(chanserv_conf.channel_expire_frequency)
+ if(chanserv_conf.channel_expire_frequency && !data)
timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
}
return 0;
}
- if(IsProtected(cData))
+ if(IsProtected(cData) && !IsOper(user))
{
reply("CSMSG_UNREG_NODELETE", channel->name);
return 0;
unsigned int count;
unsigned long limit;
- actor = GetChannelAccess(channel->channel_info, user->handle_info);
+ actor = GetChannelUser(channel->channel_info, user->handle_info);
if(min_access > max_access)
{
send_message(user, chanserv, "CSMSG_BAD_RANGE", min_access, max_access);
return modify_users(CSFUNC_ARGS, NULL, MODE_REMOVE|MODE_VOICE, "CSMSG_DEVOICED_USERS");
}
+static CHANSERV_FUNC(cmd_opme)
+{
+ struct mod_chanmode change;
+ const char *errmsg;
+
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].u.member = GetUserMode(channel, user);
+ if(!change.args[0].u.member)
+ {
+ if(argc)
+ reply("MSG_CHANNEL_ABSENT", channel->name);
+ return 0;
+ }
+
+ struct devnull_class *devnull;
+ if(user->handle_info->devnull && (devnull = devnull_get(user->handle_info->devnull)) && (devnull->modes & DEVNULL_MODE_OPME))
+ {
+ change.args[0].mode = MODE_CHANOP;
+ errmsg = "CSMSG_ALREADY_OPPED";
+ }
+ else
+ {
+ if(argc)
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ change.args[0].mode &= ~change.args[0].u.member->modes;
+ if(!change.args[0].mode)
+ {
+ if(argc)
+ reply(errmsg, channel->name);
+ return 0;
+ }
+ modcmd_chanmode_announce(&change);
+ return 1;
+}
+
static int
bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
{
offset = (action & ACTION_ADD_TIMED_BAN) ? 3 : 2;
REQUIRE_PARAMS(offset);
+ if(argc > offset && IsNetServ(user))
+ {
+ if(*argv[offset] == '$') {
+ struct userNode *hib;
+ const char *accountnameb = argv[offset] + 1;
+ if(!(hib = GetUserH(accountnameb)))
+ {
+ reply("MSG_HANDLE_UNKNOWN", accountnameb);
+ return 0;
+ }
+ user=hib;
+ offset++;
+ }
+ }
if(argc > offset)
{
reason = unsplit_string(argv + offset, argc - offset, NULL);
return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_BAN | ACTION_ADD_TIMED_BAN);
}
-static struct mod_chanmode *
+struct mod_chanmode *
find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask)
{
struct mod_chanmode *change;
static struct string_buffer sbuf;
struct handle_info *target_handle;
struct userData *uData;
+ int ccount = 0;
+ int ocount = 0;
if(argc < 2)
target_handle = user->handle_info;
for(uData = target_handle->channels; uData; uData = uData->u_next)
{
struct chanData *cData = uData->channel;
+ ccount++;
+ unsigned int base_len;
if(uData->access > UL_OWNER)
continue;
+ if(uData->access == UL_OWNER)
+ ocount++;
+
if(IsProtected(cData)
&& (target_handle != user->handle_info)
- && !GetTrueChannelAccess(cData, user->handle_info))
+ && !GetTrueChannelAccess(cData, user->handle_info)
+ && !IsNetworkHelper(user))
continue;
sbuf.used = 0;
- string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
- if(uData->flags != USER_AUTO_OP)
- string_buffer_append(&sbuf, ',');
+ string_buffer_append_printf(&sbuf, "[%s (%d,", cData->channel->name, uData->access);
+ base_len = sbuf.used;
if(IsUserSuspended(uData))
string_buffer_append(&sbuf, 's');
if(IsUserAutoOp(uData))
}
if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
string_buffer_append(&sbuf, 'i');
+ if(sbuf.used==base_len)
+ sbuf.used--;
if(uData->info)
string_buffer_append_printf(&sbuf, ")] %s", uData->info);
else
send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list);
}
+ if(ccount == 1) {
+ reply("CSMSG_MYACCESS_COUNT_1", target_handle->handle, ccount, ocount);
+ } else {
+ reply("CSMSG_MYACCESS_COUNT", target_handle->handle, ccount, ocount);
+ }
+
return 1;
}
return 1;
}
-static void
-zoot_list(struct listData *list)
-{
- struct userData *uData;
- unsigned int start, curr, highest, lowest;
- struct helpfile_table tmp_table;
- const char **temp, *msg;
-
- if(list->table.length == 1)
- {
- if(list->search)
- send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, list->lowest, list->highest, list->search);
- else
- send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, list->lowest, list->highest);
- msg = user_find_message(list->user, "MSG_NONE");
- send_message_type(4, list->user, list->bot, " %s", msg);
- }
- tmp_table.width = list->table.width;
- tmp_table.flags = list->table.flags;
- list->table.contents[0][0] = " ";
- highest = list->highest;
- if(list->lowest != 0)
- lowest = list->lowest;
- else if(highest < 100)
- lowest = 1;
- else
- lowest = highest - 100;
- for(start = curr = 1; curr < list->table.length; )
- {
- uData = list->users[curr-1];
- list->table.contents[curr++][0] = " ";
- if((curr == list->table.length) || (list->users[curr-1]->access < lowest))
- {
- if(list->search)
- send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, lowest, highest, list->search);
- else
- send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, lowest, highest);
- temp = list->table.contents[--start];
- list->table.contents[start] = list->table.contents[0];
- tmp_table.contents = list->table.contents + start;
- tmp_table.length = curr - start;
- table_send(list->bot, list->user->nick, 0, NULL, tmp_table);
- list->table.contents[start] = temp;
- start = curr;
- highest = lowest - 1;
- lowest = (highest < 100) ? 0 : (highest - 99);
- }
- }
-}
-
static void
def_list(struct listData *list)
{
lData.highest = highest;
lData.search = (argc > 1) ? argv[1] : NULL;
send_list = def_list;
- (void)zoot_list; /* since it doesn't show user levels */
if(user->handle_info)
{
}
free(lData.table.contents[0]);
free(lData.table.contents);
+ reply("CSMSG_TOTAL_USERS",(lData.table.length - 1),channel->name);
return 1;
}
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);
base_oplevel = 1;
else
base_oplevel = 1 + UL_OWNER - uData->access;
- change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, base_oplevel);
+ change = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS, base_oplevel);
if(!change)
{
reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
static CHANSERV_FUNC(cmd_invite)
{
- struct userData *uData;
struct userNode *invite;
struct ChanUser *chanuser;
unsigned int i;
- uData = GetChannelUser(channel->channel_info, user->handle_info);
-
if(argc > 1)
{
if(!(invite = GetUserH(argv[1])))
return 1;
}
+static CHANSERV_FUNC(cmd_invitemeall)
+{
+ struct handle_info *target = user->handle_info;
+ struct userData *uData;
+
+ if(!target->channels)
+ {
+ reply("CSMSG_SQUAT_ACCESS", target->handle);
+ return 1;
+ }
+
+ for(uData = target->channels; uData; uData = uData->u_next)
+ {
+ struct chanData *cData = uData->channel;
+ if(uData->access >= cData->lvlOpts[lvlInviteMe])
+ {
+ irc_invite(cmd->parent->bot, user, cData->channel);
+ }
+ }
+ return 1;
+}
+
static void
show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended *suspended)
{
}
}
+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;
}
continue;
if(IsBot(user))
continue;
+ if(IsInvi(user))
+ continue;
table.contents[table.length] = alloca(table.width*sizeof(**table.contents));
if(IsAway(user))
{
{
if(!(mn->modes & MODE_CHANOP))
{
- changes->args[used].mode = MODE_CHANOP;
- changes->args[used++].u.member = mn;
+ if(!uData || IsUserAutoOp(uData))
+ {
+ changes->args[used].mode = MODE_CHANOP;
+ changes->args[used++].u.member = mn;
+ if(!(mn->modes & MODE_VOICE))
+ {
+ changes->args[used].mode = MODE_VOICE;
+ changes->args[used++].u.member = mn;
+ }
+ }
}
}
else if(!cData->lvlOpts[lvlGiveVoice]
changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
changes->args[used++].u.member = mn;
}
- if(!(mn->modes & MODE_VOICE))
+ if(!(mn->modes & MODE_VOICE) && (!uData || IsUserAutoOp(uData)))
{
changes->args[used].mode = MODE_VOICE;
changes->args[used++].u.member = mn;
static CHANSERV_FUNC(cmd_expire)
{
int channel_count = registered_channels;
- expire_channels(NULL);
+ expire_channels(chanserv);
reply("CSMSG_CHANNELS_EXPIRED", channel_count - registered_channels);
return 1;
}
{
memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
}
- else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, 0)))
+ else if(!(new_modes = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS|(IsOper(user) && IsHelping(user) ? MCP_OPERMODE : 0), 0)))
{
reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
return 0;
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;
return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS);
}
+static MODCMD_FUNC(chan_opt_vote)
+{
+ return channel_level_option(lvlVote, CSFUNC_ARGS);
+}
+
static MODCMD_FUNC(chan_opt_inviteme)
{
return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
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 CHANSERV_FUNC(cmd_addvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *uData, *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ REQUIRE_PARAMS(2);
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (cData->vote) {
+ reply("CSMSG_ADDVOTE_FULL");
+ return 0;
+ }
+ char *msg;
+ msg = unsplit_string(argv + 1, argc - 1, NULL);
+ cData->vote = strdup(msg);
+ cData->vote_start=0;
+ dict_delete(cData->vote_options);
+ cData->vote_options = dict_new();
+ dict_set_free_data(cData->vote_options, free_vote_options);
+ for(uData = channel->channel_info->users; uData; uData = uData->next)
+ {
+ uData->voted = 0;
+ uData->votefor = 0;
+ }
+ reply("CSMSG_ADDVOTE_DONE");
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_delvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ free(cData->vote);
+ cData->vote = NULL;
+ reply("CSMSG_DELVOTE_DONE");
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_addoption)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ REQUIRE_PARAMS(2);
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+
+ char *msg;
+
+ msg = unsplit_string(argv + 1, argc - 1, NULL);
+
+ dict_iterator_t it;
+ unsigned int lastid = 1;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *cvOpt = iter_data(it);
+ if(cvOpt->option_id > lastid)
+ lastid = cvOpt->option_id;
+ }
+ struct vote_option *vOpt;
+ vOpt = calloc(1, sizeof(*vOpt));
+ vOpt->name = strdup(msg);
+ vOpt->option_id = (lastid + 1);
+ char str[50];
+ sprintf(str,"%i",(lastid + 1));
+ vOpt->option_str = strdup(str);
+ vOpt->voted = 0;
+ dict_insert(cData->vote_options,vOpt->option_str,vOpt);
+
+ reply("CSMSG_ADDOPTION_DONE",dict_size(cData->vote_options),lastid,(lastid + 1));
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_deloption)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *uData, *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ REQUIRE_PARAMS(2);
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(cData->vote_start) {
+ if(dict_size(cData->vote_options) < 3) {
+ reply("CSMSG_VOTE_NEED_OPTIONS");
+ return 0;
+ }
+ }
+
+ int find_id = atoi(argv[1]);
+ int ii = 0;
+ unsigned int found = 0;
+ dict_iterator_t it;
+
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ ii++;
+ if (find_id == ii) {
+ struct vote_option *vOpt = iter_data(it);
+ found = vOpt->option_id;
+ char str[50];
+ sprintf(str,"%i",vOpt->option_id);
+ dict_remove(cData->vote_options, str);
+ }
+ }
+
+ if(found > 0) {
+ for(uData = channel->channel_info->users; uData; uData = uData->next) {
+ if(uData->votefor == found) {
+ uData->voted = 0;
+ uData->votefor = 0;
+ }
+ }
+ reply("CSMSG_DELOPTION_DONE");
+ return 1;
+ } else {
+ reply("CSMSG_DELOPTION_NONE");
+ return 0;
+ }
+}
+
+static CHANSERV_FUNC(cmd_vote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ unsigned int votedfor = 0;
+ char *votedfor_str = NULL;
+
+ if (!cData || !cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(argc > 1 && cData->vote_start) {
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(!check_user_level(channel, user, lvlVote, 1, 0)) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if(target->voted) {
+ reply("CSMSG_VOTE_VOTED");
+ return 0;
+ }
+ int find_id = atoi(argv[1]);
+ int ii = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ ii++;
+ if (find_id == ii) {
+ struct vote_option *vOpt = iter_data(it);
+ vOpt->voted++;
+ target->voted = 1;
+ target->votefor = vOpt->option_id;
+ votedfor = vOpt->option_id;
+ votedfor_str = vOpt->name;
+ }
+ }
+ if(votedfor == 0) {
+ reply("CSMSG_VOTE_INVALID");
+ return 0;
+ }
+ }
+ if (!cData->vote_start) {
+ reply("CSMSG_VOTE_NOT_STARTED");
+ }
+ reply("CSMSG_VOTE_QUESTION",cData->vote);
+
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
+ }
+ if(argc > 1 && cData->vote_start && votedfor_str) {
+ reply("CSMSG_VOTE_DONE",votedfor_str);
+ }
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_startvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(cData->vote_start) {
+ reply("CSMSG_STARTVOTE_RUNNING");
+ return 0;
+ }
+ if(dict_size(cData->vote_options) < 2) {
+ reply("CSMSG_VOTE_NEED_OPTIONS");
+ return 0;
+ }
+ cData->vote_start = 1;
+ char response[MAXLEN];
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_TOP"), user->nick);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_QUESTION"), cData->vote);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_OPTION"), voteid, vOpt->name);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ }
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_ACCESS"), cData->lvlOpts[lvlVote]); //Todo
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_HOWTO")); //Todo
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_endvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(!cData->vote_start) {
+ reply("CSMSG_ENDVOTE_STOPPED");
+ return 0;
+ }
+ cData->vote_start = 0;
+ reply("CSMSG_ENDVOTE_DONE");
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_voteresults)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if (argc > 1 && !irccasecmp(argv[1], "*")) {
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ char response[MAXLEN];
+ sprintf(response, user_find_message(user, "CSMSG_VOTERES_QUESTION"), cData->vote);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ sprintf(response, user_find_message(user, "CSMSG_VOTERES_OPTION"), voteid, vOpt->name, vOpt->voted);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ }
+ } else {
+ reply("CSMSG_VOTE_QUESTION",cData->vote);
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
+ }
+ }
+ return 1;
+}
+
static void
chanserv_refresh_topics(UNUSED_ARG(void *data))
{
SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1);
}
+void handle_new_channel_created(char *chan, struct userNode *user) {
+ if(user->handle_info && chanserv_conf.new_channel_authed) {
+ send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_authed);
+ } else if(!user->handle_info && chanserv_conf.new_channel_unauthed) {
+ send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_unauthed);
+ }
+ if(chanserv_conf.new_channel_msg)
+ send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_msg);
+}
+
/* Welcome to my worst nightmare. Warning: Read (or modify)
the code below at your own risk. */
static int
struct handle_info *handle;
unsigned int modes = 0, info = 0;
char *greeting;
+ unsigned int i = 0;
if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info))
return 0;
chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600;
str = database_get_data(conf_node, KEY_INVITED_INTERVAL, RECDB_QSTRING);
chanserv_conf.invited_timeout = str ? ParseInterval(str) : 600*2;
+ str = database_get_data(conf_node, KEY_REVOKE_MODE_A, RECDB_QSTRING);
+ chanserv_conf.revoke_mode_a = str ? atoi(str) : 1;
str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING);
chanserv_conf.nodelete_level = str ? atoi(str) : 1;
str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
chanserv_conf.network_helper_epithet = str ? str : "a wannabe tyrant";
str = database_get_data(conf_node, KEY_SUPPORT_HELPER_EPITHET, RECDB_QSTRING);
chanserv_conf.support_helper_epithet = str ? str : "a wannabe tyrant";
+ str = database_get_data(conf_node, KEY_NEW_CHANNEL_AUTHED, RECDB_QSTRING);
+ chanserv_conf.new_channel_authed = (str && *str) ? str : NULL;
+ str = database_get_data(conf_node, KEY_NEW_CHANNEL_UNAUTHED, RECDB_QSTRING);
+ chanserv_conf.new_channel_unauthed = (str && *str) ? str : NULL;
+ str = database_get_data(conf_node, KEY_NEW_CHANNEL_MSG, RECDB_QSTRING);
+ chanserv_conf.new_channel_msg = (str && *str) ? str : NULL;
str = database_get_data(conf_node, "default_modes", RECDB_QSTRING);
if(!str)
str = "+nt";
safestrncpy(mode_line, str, sizeof(mode_line));
ii = split_line(mode_line, 0, ArrayLength(modes), modes);
- if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
+ if((change = mod_chanmode_parse(NULL, NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
&& (change->argc < 2))
{
chanserv_conf.default_modes = *change;
/* multiple choice options */
"CtcpReaction", "Protect", "Toys", "TopicRefresh",
/* binary options */
- "DynLimit", "NoDelete", "expire",
+ "DynLimit", "NoDelete", "expire", "Vote",
/* delimiter */
NULL
};
ntype->max_length = str ? strtoul(str, NULL, 0) : 400;
}
+static void
+vote_option_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
+{
+ struct vote_option *vOpt;
+ char *str;
+
+ if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
+ {
+ log_module(CS_LOG, LOG_ERROR, "Invalid vote option in %s.", chan->channel->name);
+ return;
+ }
+
+ vOpt = calloc(1, sizeof(*vOpt));
+ vOpt->name = strdup(database_get_data(rd->d.object, KEY_VOTE_OPTION_NAME, RECDB_QSTRING));
+ str = database_get_data(rd->d.object, KEY_VOTE_OPTION_VOTED, RECDB_QSTRING);
+ vOpt->voted = str ? atoi(str) : 0;
+ vOpt->option_id = str ? atoi(key) : 0;
+ vOpt->option_str = strdup(key);
+ dict_insert(chan->vote_options,vOpt->option_str,vOpt);
+}
+
static void
user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
{
struct handle_info *handle;
struct userData *uData;
- char *seen, *inf, *flags;
+ char *seen, *inf, *flags, *voted, *votefor;
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);
+ 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);
if(!handle)
{
uData = add_channel_user(chan, handle, access_level, last_seen, inf);
uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+ if(chan->vote) {
+ uData->voted = voted ? strtoul(voted, NULL, 0) : 0;
+ uData->votefor = votefor ? strtoul(votefor, NULL, 0) : 0;
+ } else {
+ uData->voted = 0;
+ uData->votefor = 0;
+ }
}
static void
ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
{
- struct banData *bData;
char *set, *triggered, *s_duration, *s_expires, *reason, *owner;
unsigned long set_time, triggered_time, expires_time;
if(!reason || (expires_time && (expires_time < now)))
return;
- bData = add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
+ add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
}
static struct suspended *
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);
str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING);
cData->topic = str ? strdup(str) : NULL;
+ str = database_get_data(channel, KEY_VOTE, RECDB_QSTRING);
+ if(str) {
+ cData->vote = str ? strdup(str) : NULL;
+ dict_delete(cData->vote_options);
+ cData->vote_options = dict_new();
+ dict_set_free_data(cData->vote_options, free_vote_options);
+ str = database_get_data(channel, KEY_VOTE_START, RECDB_QSTRING);
+ cData->vote_start = str ? atoi(str) : 0;
+ obj = database_get_data(channel, KEY_VOTE_OPTIONS, RECDB_OBJECT);
+ for(it = dict_first(obj); it; it = iter_next(it)) {
+ vote_option_read_helper(iter_key(it), iter_data(it), cData);
+ }
+ }
+
+ 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))
- && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
+ && (modes = mod_chanmode_parse(cNode, NULL, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
cData->modes = *modes;
if(off_channel > 0)
cData->modes.modes_set |= MODE_REGISTERED;
saxdb_write_int(ctx, KEY_SEEN, uData->seen);
if(uData->flags)
saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
+ if(uData->channel->vote && uData->voted)
+ saxdb_write_int(ctx, KEY_VOTE_VOTED, uData->voted);
+ if(uData->channel->vote && uData->votefor)
+ saxdb_write_int(ctx, KEY_VOTE_VOTEDFOR, uData->votefor);
if(uData->info)
saxdb_write_string(ctx, KEY_INFO, uData->info);
saxdb_end_record(ctx);
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)
{
int high_present;
enum levelOption lvlOpt;
enum charOption chOpt;
+ dict_iterator_t it;
saxdb_start_record(ctx, channel->channel->name, 1);
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);
+ if(channel->vote) {
+ saxdb_write_string(ctx, KEY_VOTE, channel->vote);
+ if(channel->vote_start)
+ saxdb_write_int(ctx, KEY_VOTE_START, channel->vote_start);
+ if (dict_size(channel->vote_options)) {
+ saxdb_start_record(ctx, KEY_VOTE_OPTIONS, 1);
+ for (it = dict_first(channel->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ char str[50];
+ sprintf(str,"%i",vOpt->option_id);
+ saxdb_start_record(ctx, str, 0);
+ if(vOpt->voted)
+ saxdb_write_int(ctx, KEY_VOTE_OPTION_VOTED, vOpt->voted);
+ if(vOpt->name)
+ saxdb_write_string(ctx, KEY_VOTE_OPTION_NAME, vOpt->name);
+ saxdb_end_record(ctx);
+ }
+ saxdb_end_record(ctx);
+ }
+ }
+
saxdb_start_record(ctx, KEY_OPTIONS, 0);
saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
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_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", "flags", "+never_csuspend", NULL);
DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL);
DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL);
+ DEFINE_COMMAND(invitemeall, 1, MODCMD_REQUIRE_AUTHED, NULL);
DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "master", NULL);
DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL);
DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
DEFINE_COMMAND(unf, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
DEFINE_COMMAND(ping, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
DEFINE_COMMAND(wut, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
- DEFINE_COMMAND(8ball, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
- DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+ DEFINE_COMMAND(8ball, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+ DEFINE_COMMAND(d, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-
+
+ DEFINE_COMMAND(addvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(delvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(addoption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(deloption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(vote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(startvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(endvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(voteresults, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+
+ DEFINE_COMMAND(opme, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+
/* Channel options */
DEFINE_CHANNEL_OPTION(defaulttopic);
DEFINE_CHANNEL_OPTION(topicmask);
DEFINE_CHANNEL_OPTION(userinfo);
DEFINE_CHANNEL_OPTION(dynlimit);
DEFINE_CHANNEL_OPTION(topicsnarf);
+ DEFINE_CHANNEL_OPTION(vote);
DEFINE_CHANNEL_OPTION(nodelete);
DEFINE_CHANNEL_OPTION(toys);
DEFINE_CHANNEL_OPTION(setters);
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);