/* Do not remove the headers from this file! see /USAGE for more info. */
//
// BOARD.C 970414 Fritti@TiamaT, Vette@Lima
//
// Generic bulletin board, with switching group capability.
//
// Added security checks using NEWS_D internal security
// Vette April 14, 1997
//:MODULE
// Boards are simply set up from rooms by using
//
// set_objects( ([
// BOARD_OB : ({ "name", "linked newsgroup", int switchable })
// ]) );
// TODO
// Something to show more than the MAX_HEADERS amount of posts
// The query_message_lines() returns an array which is then imploded,
// would it not be faster to send a string and not have to implode?
#include <edit.h>
inherit OBJ;
inherit M_READABLE;
inherit M_INPUT;
inherit CLASS_NEWSMSG;
// The maximum nr of headers to show.
#define MAX_HEADERS 10
// Default to no switchable boards.
#define DEFAULT_SWITCHABLE 0
// The default mud newsgroup
#define DEFAULT_GROUP NEWS_D->get_groups()[1]
// The default Name of the Board Object
#define DEFAULT_NAME "Bulletin Board"
// The group to which this board is linked. Set by setup().
private nosave string linked_group;
// The name of this board. Set by setup().
private nosave string board_name;
// Is this board switchable? Set by create().
private int can_switch;
// Function prototypes.
varargs private nomask int get_current_id(string group_name);
int set_group(string new_group);
private nomask string list_headers();
string do_desc();
int get_group_last_id(string group);
private nomask string switch_to_next_unread_group();
// Filter removed messages from ids array.
private nomask int filter_removed(int elem) {
class news_msg msg = NEWS_D->get_message(linked_group, elem);
if (!msg || !msg->body)
return 0;
return 1;
}
// Create a new board.
varargs void mudlib_setup( string name, string group, int switchable )
{
if (!group || group == "")
group = DEFAULT_GROUP;
if (!name || name == "")
name = DEFAULT_NAME;
if (!switchable)
switchable = DEFAULT_SWITCHABLE;
can_switch = switchable;
set_group(group);
board_name = name;
set_id("board", "bulletin board");
set_in_room_desc( (: do_desc :) );
set_long( (: list_headers :) );
set_size( SMALL );
}
// A check to test whether the current user is allowed to read
// from this group.
// Added by Vette April 14, 1997
private nomask int check_group(string g) {
if (member_array(g, NEWS_D->get_groups()) == -1) {
return 0;
}
return 1;
}
// Set and query functions.
// Moved the group check into its own function and called that fn
// Vette April 14, 1997
private nomask int set_group(string new_group) {
if (!check_group(new_group)) return 0;
linked_group = new_group;
return 1;
}
// Added Valid group check
nomask string query_group() {
if (!check_group(linked_group)) return DEFAULT_GROUP;
return linked_group;
}
// Used for switchable boards.
nomask void set_switchable(int flag) {
can_switch = flag;
}
nomask int query_switchable() {
return can_switch;
}
// Format one message line
private nomask varargs string format_message_line(int id) {
class news_msg msg;
string subject;
msg = NEWS_D->get_message(linked_group, id);
if (!msg || !msg->body)
return 0; // Do not display removed messages.
else
subject = msg->subject;
return sprintf(
"%-48s [%-10s on %s]", // Display message subject/poster/time
subject[0..47],
msg->poster,
intp(msg->time) ? ctime(msg->time)[4..9] : msg->time);
}
// Format all message lines.
// Added some changes to support groups with less than MAX_HEADERS posts
// to them...it used to make negative ID's
// Added a * to unread posts
private nomask string array query_message_lines() {
int array ids = sort_array(filter_array(
NEWS_D->get_messages(linked_group), (: filter_removed :)), 1);
int i,k, j = sizeof(ids) - MAX_HEADERS;
string array tmp;
if (j<0) j = 0;
//### This can probably be removed, but will wait for now --Vette--
//### ids = ids[<MAX_HEADERS..];
ids = ids[j..];
tmp = map_array(ids, (: format_message_line($1) :));
if( sizeof(ids) == 0 )
return ({ "The board is bereft of posts. Feel free to change this!\n" });
k = ids[<1]-this_body()->get_news_group_id(linked_group);
for (i = 1; i < sizeof(tmp) + 1; i++) {
tmp[i-1] = sprintf("%4d%s ", i + j, k>(sizeof(tmp)-i-1) ? "*" : " ") + tmp[i-1];
}
return tmp;
}
// Make the long description.
private nomask string list_headers() {
if (!check_group(linked_group))
return "You do not have permission.\n";
return (can_switch ? "Group: " + linked_group + "\n" : "") +
"ID TITLE POSTER TIME\n" +
repeat_string("-", 78) + "\n" + implode(query_message_lines(), "\n");
}
// Needed for M_READABLE
nomask int has_entries() {
return 1;
}
// Read about an entry.
// Added a couple of changes to beef up security
// Vette April 14, 1997
nomask mixed read_entry(string str) {
class news_msg msg;
int id;
int array ids;
// Before we do anything, need to make sure this user can read the group
if (!check_group(linked_group))
return "You do not have access.\n";
ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)), 1);
if (str == "next") {
id = this_body()->get_news_group_id(linked_group) + 0;
while(member_array(id,ids) == -1 && id < ids[sizeof(ids)-1])
id++;
/* This ID already a NEWS_D ID, no translation needed */
if (id > ids[sizeof(ids)-1])
return "No more messages.\n";
if (id < ids[0] || id > ids[sizeof(ids)-1])
return "You can only read posts that are on the bulletin board!\n";
}
else {
if (sscanf(str, "%d", id) != 1)
return "Specify the post number you want to read.";
if (id < 0 || id > sizeof(ids))
return "You can only read posts that are on the bulletin board!\n";
id = ids[id - 1]; // Translate to NEWS_D id
}
if (this_body()->get_news_group_id(linked_group) < (id+1))
this_body()->set_news_group_id(linked_group, id+1);
msg = NEWS_D->get_message(linked_group, id);
return format_message_line(id) + "\n\n" + msg->body;
}
// Helpful text for those used to type 'read 1'.
nomask mixed direct_read_obj(object ob) {
return "Use 'read about <postnr>' to read the bulletin board.\n";
}
mixed direct_read_about_str_from_obj(string str, object ob)
{
return 1;
}
// Short description.
private nomask string do_desc() {
int new_id;
int array ids;
int curr_id = this_body()->get_news_group_id(linked_group);
if ( !NEWS_D->get_messages(linked_group) ) ids = ({ });
else
ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)),
1);
/* Slight fix here, lest see if this solves our problems. */
if (curr_id < 0) curr_id=1;
/*
if (curr_id <= 0)
this_body()->set_news_group_id(linked_group, curr_id = 1);
if (curr_id > sizeof(ids))
this_body()->set_news_group_id(linked_group, curr_id = sizeof(ids));
*/
new_id = member_array(curr_id,ids);
if (curr_id > 0 && new_id < 0) new_id = sizeof(ids);
else if (new_id < 0) new_id = 1;
return "A " + board_name + (can_switch ? " set to " + linked_group : "") +
" [at " + new_id + ", max " + sizeof(ids) + "].";
}
// For board verbs like post etc.
nomask int is_bulletin_board() {
return 1;
}
// Check if id is in range, for board verbs can_* functions.
nomask int valid_id(int id) {
int array ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)), 1);
if (id <= 0 || id > sizeof(ids))
return 0;
return 1;
}
// Check if this_body() can post.
nomask int valid_post() {
return !NEWS_D->is_write_restricted(linked_group);
}
// Verb interface. These get called by their respective verbs.
// Remove a note.
nomask void do_remove(int id) {
int array ids;
class news_msg msg;
ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)), 1);
msg = NEWS_D->get_message(linked_group, ids[id - 1]);
if (msg->poster != this_body()->query_name() && !check_privilege(1)) {
write("You can only delete your own posts.\n");
return;
}
NEWS_D->remove_post(linked_group, ids[id - 1]);
write("Post number " + id + " removed.\n");
return;
}
mixed direct_remove_wrd_from_obj(string wrd, object ob) {
int id;
if (!valid_post())
return "You aren't permitted to remove posts from this board.\n";
if (sscanf(wrd, "%d", id) != 1)
return "Please specify a post number.\n";
if (!valid_id(id))
return "No such note.\n";
return 1;
}
// Post a note.
private void receive_post(string subj, string array body) {
if (!body) {
write("Post aborted.\n");
return;
}
NEWS_D->post(linked_group, subj, implode(body, "\n"));
write("Posted.\n");
}
private void receive_subject(mixed subj) {
if (subj == -1)
return;
if (!subj || subj == "") {
write("No subject, post aborted.\n");
modal_pop();
return;
}
if (sizeof(subj) > 50) {
write("Subject too long. Please try again.\n");
return;
}
modal_pop();
new(EDIT_OB, EDIT_TEXT, 0, (: receive_post, subj :));
}
nomask void do_post(string subj) {
if (subj == "" || !subj)
modal_push( (: receive_subject :), "Subject: ");
else
new(EDIT_OB, EDIT_TEXT, 0, (: receive_post, subj :));
}
mixed direct_post_on_obj_about_str(string str, object ob) {
if (!valid_post())
return "You aren't permitted to post on this board.\n";
if (!check_group(linked_group))
return "You lack the permissions.\n";
if (sizeof(str) > 50)
return "Subject too long.\n";
return 1;
}
// Followup a note.
private void receive_followup(int followup_id, string array body) {
if (!body) {
write("Followup aborted.\n");
return;
}
NEWS_D->followup(linked_group, followup_id, implode(body, "\n"));
write("Posted.\n");
}
nomask void do_followup(int id) {
int array ids;
ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)), 1);
new(EDIT_OB, EDIT_TEXT, 0, (: receive_followup, ids[id - 1] :));
}
nomask void do_followup_with_message(int id) {
int array ids;
class news_msg msg;
string * lines;
ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)), 1);
msg = NEWS_D->get_message(linked_group,ids[id-1]);
if ( !msg ) {
write("You may not followup to that message -- it was removed.\n");
return;
}
lines = ({ sprintf("On %s %s wrote post #%d:",
intp(msg->time) ? ctime(msg->time) : msg->time,
msg->poster, get_current_id()) });
lines += map_array(explode(msg->body, "\n"), (: "> " + $1 :) );
new(EDIT_OB, EDIT_TEXT, lines, (: receive_followup, ids[id - 1] :));
}
mixed direct_post_wrd_to_wrd_on_obj(string wrd1, string wrd2, object ob) {
int id;
if (!valid_post())
return "You're not allowed to post on this board.\n";
if (!check_group(linked_group))
return "You lack the permissions.\n";
if (sscanf(wrd2, "%d", id) != 1)
return "Please specify a post number.\n";
if (!valid_id(id))
return "No such note.\n";
return 1;
}
mixed direct_post_wrd_with_wrd_on_obj(string wrd1, string wrd2, object ob) {
return direct_post_wrd_to_wrd_on_obj(wrd1,wrd2,ob);
}
// Reply to a user about a note.
private void receive_reply(int reply_id, string array body) {
class news_msg msg = NEWS_D->get_message(linked_group, reply_id);
if (!body) {
write("Reply aborted.\n");
return;
}
// This line required a change in the mailer
// (/obj/secure/mailers/mailer.c) because it checked whether the
// previous object was the newsreader.
this_body()->query_mailer()->send_news_reply("Re: " + msg->subject,
body, lower_case(msg->poster));
write("Replied to "+lower_case(msg->poster)+".\n");
}
nomask void do_reply(int id) {
int array ids;
ids = sort_array(filter_array(NEWS_D->get_messages(linked_group),
(: filter_removed :)), 1);
new(EDIT_OB, EDIT_TEXT, 0, (: receive_reply, ids[id - 1] :));
}
mixed direct_answer_to_wrd_on_obj(string wrd, object ob) {
int id;
if (!check_group(linked_group))
return "You lack the permissions.\n";
if (sscanf(wrd, "%d", id) != 1)
return "Please specify a post number to reply to.\n";
if (!valid_id(id))
return "No such note.\n";
return 1;
}
// Switch board newsgroup.
nomask void do_switch(string group) {
if (group == "next")
if ((group = switch_to_next_unread_group()) == "" || !group) {
write("No new news.\n");
return;
}
if (!set_group(group)) {
write("Couldn't change to group <" + group + ">.\n");
return;
}
this_body()->simple_action("$N $vchange " + the_short() + " to group <" + group + ">.\n");
}
mixed direct_switch_obj_to_str(object ob, string str) {
if (!can_switch)
return 0;
if (!valid_post())
return "You aren't permitted to switch this board.\n";
if ((member_array(str, NEWS_D->get_groups(str)) == -1) && (str != "next"))
return "That is not a valid newsgroup name.\n";
return 1;
}
/*
=============================================================================
*/
/* Functions to Support Switching to Next Unread and Listing Unread Groups */
/* Lifted from newsreader.c */
varargs private nomask int get_current_id(string group_name)
{
if ( !group_name )
group_name = linked_group;
/*
** -1 to get the "current" message. The body records the _next_
** message to read.
*/
return this_body()->get_news_group_id(group_name) - 1;
}
/* Lifted from newsreader.c */
private int count_unread_messages(string group, int all_messages)
{
int * ids = NEWS_D->get_messages(group);
int id;
class news_msg msg;
int count = 0;
int read_thru_id;
read_thru_id = get_current_id( group );
if ( !all_messages )
{
ids = filter_array(ids, (: $1 > $(read_thru_id):));
}
foreach ( id in ids )
{
msg = NEWS_D->get_message(group, id);
if ( msg->body )
{
count++;
}
}
return count;
}
/* Lifted from newsreader.c */
private nomask int test_for_new(string group)
{
if( count_unread_messages( group, 0 )) return 1;
return 0;
}
/* Lifted from newsreader.c */
private nomask string format_group_line(string group)
{
int last_id;
int unread = count_unread_messages(group, 1);
last_id = NEWS_D->get_group_last_id(group);
return sprintf(" %-40s (%d %s, %d unread)",
group,
unread,
unread == 1 ? "message" : "messages",
count_unread_messages(group, 0));
}
/* Lifted from newsreader.c (slightly modified) */
private nomask void display_groups_with_new()
{
string * groups;
groups = filter_array(this_body()->subscribed_groups(), (: test_for_new :));
if(!sizeof(groups))
{
write("No new news.\n");
}
else
{
string * list;
list = ({ "Groups with new messages:", "" }) +
map_array(groups, (: format_group_line :));
more(list);
}
}
private nomask string switch_to_next_unread_group()
{
string * groups;
groups = filter_array(this_body()->subscribed_groups(), (: test_for_new :));
if(!sizeof(groups)) {
return "";
}
else {
return groups[0];
}
}