// File: channels.c (A daemon)
// Author: Inspiral@Tabor, et al
// Created: 93-05-10
// Purpose: To efficiently handle player communication.
//
// REVISON HISTORY:
// 930725 - Fixed problem with creating spurious channels! -- Inspiral
// 930801 - Added scan_config() -- Inspiral
// 930803 - Reformatted data structure to use
// more arrays than mappings -- Inspiral
// 930811 - Removed all reliances on query() and set() lfuns. -- Inspiral
// 930818 - Added log(), tell_user(), secure() -- Inspiral
// 930826 - fixed bug in del user -- Pallando
// 931004 - added emotes -- Rust
// 940322 - Rewritten to be faster, and to index users by
// object -- Inspiral
// 940911 - Added missing prototpye -- Leto
// 950312 - cleaned up format & changed channels variable set
// in the player to a mapping type instead of an
// array since Earthmud uses the mapping version -- Zifi@earthmud
// 951001 - Added parse_amcp_channel -- Leto@Earthmud
// 960325 - Added backlog for each channel -- Perrin@Earthmud
// 960429 - Fixed channel emotes and formatting problems -- Emeradii@Earthmud
// 960501 - Added support for I3 channels -- Brainstorm@SILKE
#include <channels.h>
#include <commands.h>
#include <net/i3modules.h>
#include <emoted.h>
#include <daemons.h>
#include <net/daemons.h>
#include <driver/origin.h>
varargs mixed display_channel(string chan, object viewer);
varargs
int register_channel(string chan, object user, int state, mixed *action);
int log(string log_mesg);
void add_chan_backlog(string chan,string mesg);
void process_alias();
mapping channels; // contains all our channel stats
mapping alias; // a rude alias mechanism
#define BACKLOG_MAX 15
private static int
tell_user(object user, string mesg)
{
tell_object(user, wrap(sprintf("CHANNELS:\t%s\n", mesg)));
return 1;
}
#define NO_NEW_CHANNELS
// Begin admin_channel() - only applies to I3 at the moment but who knows
private static
int admin_channel( string chan, object user, string* add_list,
string* rem_list )
{ string ext_ob, err;
if( !channels[chan] ) return 0;
// send admin command to router
if( !undefinedp( ( ext_ob = channels[chan]["object"] ) ) ) {
sscanf( ext_ob, "%s#%s", chan, ext_ob );
if( err = catch( ext_ob->
daemon_apply( user, ADM, ({ user->query("name"), chan, add_list,
rem_list }) ) ) )
log( sprintf( "parse_channel:\tError in %s:\n%s",
ext_ob, err ) );
}
return 1;
}
private static varargs int
create_channel(string chan, object user, string group, int mode,
object ref )
{
if (channels[chan])
return 1;
if( living(user) && ref ) {
// Create channel came from a real user
I3_CHANNEL->daemon_apply( user, CRE,
({ user->query("name"), chan, mode }) );
return 1;
}
channels[chan] = ([ ]);
channels[chan]["vis"] = ({ });
channels[chan]["backlog"] = ({ });
if( group )
channels[chan]["priv"]=group;
if( ref )
channels[chan]["object"]=chan+"#"+base_name(ref)+".c";
return 1;
}
varargs private static int
kill_user(object user, string chan, int onquit)
{
string ext_ob, err;
string *usrchanlist;
if (undefinedp(channels[chan]))
return 0;
channels[chan]["vis"] -= ({user});
// Delete the users from the channel
if( user && !onquit) {
usrchanlist = user->query("channels/toggle") - ({ chan });
user->set("channels/toggle", usrchanlist );
}
if( channels[chan]["vis"] == ({ }) ) {
// deregister channel with router
if( !undefinedp( ( ext_ob = channels[chan]["object"] ) ) ) {
sscanf( ext_ob, "%s#%s", chan, ext_ob );
if( err = catch( ext_ob->
daemon_apply( this_object(), LIS, ({ chan, 0 })) ) )
log( sprintf( "parse_channel:\tError in %s:\n%s",
ext_ob, err ) );
}
}
return 1;
}
varargs int
delete_channel(string chan, object user)
{
int i;
object *chuser;
if( alias[chan] ) chan=alias[chan];
if (undefinedp(channels[chan]))
return 0;
chuser=channels[chan]["vis"];
for( i=0; i<sizeof(chuser); i++ ) {
kill_user( chuser[i], chan );
}
if( channels[chan]["object"] && user && living(user) ) {
I3_CHANNEL->daemon_apply( user, DEL, ({ user->query("name"), chan }) );
}
log( "Deleting channel "+chan+" by "+identify(user) );
map_delete(channels, chan);
return 1;
}
private static varargs int
add_user(string chan, object user)
{
string *toggle_list, priv, ext_ob, err, pchan;
if (!chan || !user)
return 0;
if (!pointerp(toggle_list = (string *) user->query("channels/toggle")))
{
user->set("channels/toggle",({ }));
toggle_list = ({ });
}
// chan doesn't exist...do we have permission to create it?
if (undefinedp(channels[chan]))
{
#ifdef NO_NEW_CHANNELS
return 0;
#else
create_channel(chan, user);
#endif
}
if (!undefinedp(priv = channels[chan]["priv"]) && !member_group(geteuid(user), priv))
return 0;
if (member_array(chan,toggle_list) == -1)
user->add("channels/toggle",({chan}));
if( channels[chan]["vis"] == ({ }) ) {
// Send a channel listen if we are the first in this channel
if( !undefinedp( ( ext_ob = channels[chan]["object"] ) ) ) {
sscanf( ext_ob, "%s#%s", pchan, ext_ob );
if( err = catch( ext_ob->
daemon_apply( user, LIS, ({ pchan, 1 })) ) )
log( sprintf( "parse_channel:\tError in %s:\n%s",
ext_ob, err ) );
}
}
channels[chan]["vis"] = uniq_array(channels[chan]["vis"] + ({user}));
return 1;
}
private static int
kill_users(mixed chan)
{
int i;
if (!chan) chan = keys(channels);
else chan = ({chan});
i = sizeof(chan);
while(i--)
channels[chan[i]]["vis"] -= ({ 0 });
return 1;
}
// Clean up the channel list on quit
// So we are able to deregister unused channels with the
// Router - Brainstorm
int
cleanup_user( object whom )
{ string *current_list;
int i;
if( !whom || previous_object() != whom ) return 0;
current_list=whom->query("channels/toggle");
i=sizeof(current_list);
while(i--)
kill_user( whom, current_list[i], 1);
return 1;
}
varargs int
initialize_user(object whom)
{
int i, j;
object *list;
string priv;
string *current_list;
if (!whom || !living(whom)) list = users();
else list = ({whom}); // We have one user.
i = sizeof(list);
while(i--)
{
current_list = (string *) list[i]->query("channels/toggle");
if (!pointerp(current_list))
{
list[i]->set("channels/toggle",({ }));
current_list = ({ });
}
j = sizeof(current_list);
while(j--)
register_channel(current_list[j], list[i], ADD);
}
return 1;
}
varargs int
parse_channel(string chan, string mesg, string name, int emote)
{
int i;
string *ignore;
object *list; // Holds the list of all players currently on chan
string ext_ob, err;
string plural; // Holds the plural form of the channel name, chan
string verb, cmd;
if (alias[chan]) chan = alias[chan];
if (undefinedp(channels[chan]))
return 0;
kill_users(chan);
if (undefinedp((list = channels[chan]["vis"])))
return 0;
i = sizeof(list); // Optimize is.
// Channel format is wrong in the daemon..Attempt a fix, but log.
if (!pointerp(list))
{
channels[chan]["vis"] = ({ });
initialize_user(); // Shall we reconfigure?
return log(sprintf("parse_channel: channel %s is not a pointer! Fixing.", identify(chan)));
}
// Is the name flag not given? or, is name not even in the list?
if (this_player() && member_array(this_player(),list) == -1)
return 0;
if (this_player() && !name)
name = (string) this_player()->query("cap_name");
if (!mesg || mesg == "")
{
CMD_SHOW->cmd_show(chan);
return 1;
}
// Output backlog stuff here
if (mesg[0..4]=="/last")
{
if (!sizeof(channels[chan]["backlog"]))
message("channels","No backlog yet, sorry.\n",this_player());
else
message("channels",sprintf("Backlog of channel %s:\n%s",chan,
implode(channels[chan]["backlog"],"")+"\n"),this_player());
return 1;
}
if (undefinedp((plural = channels[chan]["plural"])))
plural = pluralize_verb(chan);
if (mesg[0] == ';')
{
if (!sscanf(mesg[1..],"%s %s",verb,cmd))
verb = mesg[1..];
mesg = EMOTE_D->return_parse(verb,cmd);
if (!mesg)
{
if (!cmd) cmd = "";
mesg = name + " " + verb + " " + cmd;
}
emote = 1;
}
else if (mesg[0..5] == ":emote")
{
if (!sscanf(mesg[7..],"%s %s",verb,cmd))
verb = mesg[7..];
mesg = EMOTE_D->return_parse(verb,cmd);
if (!mesg)
{
if (!cmd) cmd = "";
mesg = name + " " + verb + " " + cmd;
}
emote = 1;
}
else if (emote)
{
mesg = name + " " + mesg;
}
if( emote )
add_chan_backlog(chan,iwrap(capitalize(chan)+": "+mesg));
else
add_chan_backlog(chan,iwrap(name+" "+plural+": "+mesg));
while(i--)
{
if (!objectp(list[i]))
continue;
if (list[i] == this_player())
{
if (emote)
message("channels",iwrap(capitalize(chan)+": "+mesg),list[i]);
else
message("channels",iwrap("You "+chan+": "+mesg),list[i]);
}
else
{
ignore = (string *) list[i]->query("ignore");
if (!pointerp(ignore))
ignore = ({ });
if (member_array(lower_case(name), ignore) != -1)
continue;
if (list[i]->getenv("add_newline"))
tell_object(list[i], "\n");
if (emote) message("channels",iwrap(capitalize(chan)+": "+mesg),list[i]);
else message("channels",iwrap(name+" "+plural+": "+mesg),list[i]);
continue;
}
} // while()
// Do we have an apply to an external object?
chan = lower_case(chan);
if (!undefinedp((ext_ob = channels[chan]["object"])))
{
// format of object flag: alias#object
sscanf(ext_ob,"%s#%s", chan, ext_ob);
// Attempt the apply, log all errors
if (err = catch(ext_ob->daemon_apply(this_player(), MSG,
({ chan, mesg, emote}) )))
log(sprintf("parse_channel:\tError in %s:\n%s", ext_ob, err));
}
return 1;
}
//
int display_i3channel( string chan, string mud, object viewer )
{ string ext_ob, err;
if( !channels[chan] ) return 0;
if( !undefinedp( ( ext_ob = channels[chan]["object"] ) ) ) {
// format of object flag: alias#object
sscanf( ext_ob, "%s#%s", chan, ext_ob );
// Attempt the apply, log all errors
if( err = catch( ext_ob->
daemon_apply( this_player(), WHOREQ,
({ this_player()->query("name"), mud, chan })) ) )
log( sprintf( "parse_channel:\tError in %s:\n%s",
ext_ob, err ) );
}
}
varargs mixed display_channel(string chan, object viewer) {
mixed list;
if(!chan || chan == "") list = keys(channels);
else {
if( alias[chan] ) chan=alias[chan];
if(undefinedp(list = channels[chan]))
return sprintf("There is no such channel: %s.",identify(chan));
list = channels[chan]["vis"];
}
if(!pointerp(list) || !sizeof(list)) {
channels[chan]["vis"] = ({ });
// Return something printable for a user, and an empty set for a
// daemon.
return viewer && living(viewer) ?
sprintf("CHANNELS:\tNo Users Subscribed to %s", identify(chan)) : "";
}
if(chan && chan != "")
list = filter_array(list, "flush_list", this_object(), viewer);
list = map_array(list, "get_cap", this_object());
list = (implode(list,",")); // Leto.. +" <End>");
return list;
}
// Bootflag is set if this is the first channellist after
// a startupreq from the I3 DAEMON. In that case we need
// to tune the channels in again ;)
varargs
int add_i3channels( mapping chanlist, int bootflag )
{ string *i3list, line;
int j;
if( sizeof(chanlist) ) {
i3list=keys( chanlist );
for( j=0; j<sizeof(i3list); j++ ) {
line=i3list[j];
if( !alias[line] && channels[line] &&
channels[line]["object"] ) continue;
if( alias[line] ) line=alias[line];
if( ! ( channels[line] && !channels[line]["object"]) ) {
// Its not there so lets add it or we get an
// information update on it in which case we dont
// want to boot our users from the channel
if( !channels[line] )
channels[line] = allocate_mapping( 7 );
if( !channels[line]["vis"] || bootflag )
channels[line]["vis"] = ({ });
// channels[line]["priv"] = "wiz";
channels[line]["object"] = i3list[j]+"#"+I3_CHANNEL+".c";
channels[line]["creator"] = chanlist[i3list[j]][0];
channels[line]["mode"] = chanlist[i3list[j]][1];
}
}
}
// call process_alias again in case we got new channels
process_alias();
if(bootflag)
call_out( "initialize_user", 2);
}
private static int scan_config() {
string *list, *split_line, line;
int i, j;
list = update_file(CHANNELS_CONFIG);
i = sizeof(list);
// Once through the i for each non-"#" line in config file
while(i--) {
line = list[i];
if(line) {
split_line = explode(line, ":");
j = sizeof(split_line);
channels[split_line[0]] = allocate_mapping(6);
channels[split_line[0]]["vis"] = ({ });
while(j--) {
if((split_line[j] != "X") && (CHANNELS_INFO[j] != ""))
channels[split_line[0]][CHANNELS_INFO[j]] = split_line[j];
} // while(j)
} // IF (LINE)
} // while(i)
return 1;
}
int log(string log_mesg) {
if(!log_mesg || log_mesg == "") return 0;
log_file(CHANNELS_LOG, sprintf("%s %O - %s\n", intl_date_stamp(1),
previous_object(), log_mesg));
return 0;
}
mapping q() {
return copy(channels);
}
mapping a() {
return copy(alias);
}
void
process_alias() {
int i;
string *list, temp1;
list = keys(channels);
i = sizeof(list);
while(i--) {
if(!undefinedp(temp1 = channels[list[i]]["object"]))
if(sscanf(temp1, "%s#%*s", temp1) == 2)
alias += ([temp1 : list[i]]);
}
}
string get_cap(object ob) {
return (string) ob->query("cap_name");
}
int flush_list(object user, object viewer) {
return (objectp(user) && visible(user, viewer) && interactive(user));
}
void create() {
channels = allocate_mapping(20);
alias = allocate_mapping(20);
seteuid(getuid());
scan_config();
process_alias();
add_i3channels(I3_DAEMON->query_chanlist());
call_out("initialize_user", 2);
}
varargs int
register_channel(string chan, object user, int state, mixed *action )
{
int ret, mode;
string group;
object ref;
if( origin() != ORIGIN_LOCAL && previous_object(1) &&
!adminp(previous_object(1)) && previous_object(1) != user) return 0;
if (nullp(state))
return notify_fail("register_channel: no state given!");
switch(state)
{
case DEL: ret = delete_channel(chan, user);
break;
case ADM: if( sizeof( action ) != 2 ) ret = 0;
else
ret=admin_channel( chan, user, action[0], action[1] );
break;
case CRE: switch( sizeof( action ) ) {
case 3: ref = action[2];
case 2: mode = action[1];
case 1: group = action[0];
}
ret = create_channel( chan, user, group, mode, ref );
break;
case ADD: ret = add_user(chan, user);
break;
case KIL: ret = kill_user(user, chan, sizeof(action) ? action[0] : 0 );
break;
default : ret = 0;
break;
}
return ret;
}
// If all parsing of <foo>bar fails, we arrive at this function to see
// if the request might be a valid message class. We return 0 if it
// wasn't, and 1 if it was. We're assuming all strings are valid.
int parse_amcp_channel(string command, string mesg) {
string *channels, control, channelname;
channels = previous_object()->query("channels/toggle");
command = lower_case(command);
if(member_array(command,channels)!=-1)
{
if (!mesg) this_player()->receive_message(command,display_channel(
command,this_player() ));
return parse_channel(command,mesg); // channel message sent.
}
// is it a control message then ?
if(sscanf(command,"channel&%s",control)!=1) return 0; // We don't get it.
//write("CHAN:"+control);
if(control=="list")
{
this_player()->dump_amcp_channels();
return 1;
}
if(sscanf(control,"%s&%s",control,channelname)!=2)
{
this_player()->receive_message("error","Unknown channel control message.\n");
return 1;
}
if(control=="subscribe") return CMD_TUNE->cmd_tune(channelname+" in");
if(control=="unsubscribe") return CMD_TUNE->cmd_tune(channelname+" out");
// Guess it's an unknown control message.
this_player()->receive_message("error","Unknown channel control message.\n");
return 1;
}
void add_chan_backlog(string chan,string mesg)
{
string *backlog = channels[chan]["backlog"];
int count = sizeof(channels[chan]["backlog"]);
// if(undefinedp(channels[chan]["backlog"]))
if(!count)
channels[chan]["backlog"] = ({});
if(count >= BACKLOG_MAX) backlog = backlog[1..];
if (!backlog)
backlog = ({ mesg });
else
backlog = backlog + ({ mesg });
channels[chan]["backlog"] = backlog;
}