Added a chat history plugin.

The mod_chat_history plugin provides chat history for all public chat messages.

Can be configured in the following ways:

- history_max: max number of messages stored in history
- history_default: the default number of messages to be returned when invoking !history
- history_connect: if > 0, then this number of messages is automatically sent when connecting to the hub

Removed the built-in !history command in favour of the mod_chat_history plug-in.

Make sure we unescape all chat messages before forwarding any of them to plugins.

Update example plugins.conf in documentation directory.
This commit is contained in:
Jan Vidar Krey 2012-01-03 23:02:28 +01:00
parent a9ed03cf38
commit 875f55a401
6 changed files with 313 additions and 110 deletions

View File

@ -1,16 +1,63 @@
# ATTENTION! # ATTENTION!
# Plugins are invoked in the order of listing in the plugin config file. # Plugins are invoked in the order of listing in the plugin config file.
# auth user
# file={path for DB file with user auth information} # Sqlite based user authentication.
#
# This plugin provides a Sqlite based authentication database for
# registered users.
# Use the uhub-passwd utility to create the database and add/remove users.
#
# Parameters:
# file: path/filename for database.
#
plugin /var/lib/uhub/mod_auth_sqlite.so "file=/etc/uhub/users.db" plugin /var/lib/uhub/mod_auth_sqlite.so "file=/etc/uhub/users.db"
# log subsystem.
# file={/path/to/logfile} # Log file writer
#
# Parameters:
# file: path/filename for log file.
# syslog: if true then syslog is used instead of writing to a file (Unix only)
plugin /var/lib/uhub/mod_logging.so "file=/var/log/uhub.log" plugin /var/lib/uhub/mod_logging.so "file=/var/log/uhub.log"
# # A simple example plugin
# plugin /var/lib/uhub/mod_auth_simple.so
#
# plugin /var/lib/uhub/mod_example.so # plugin /var/lib/uhub/mod_example.so
# Load the chat history plugin.
#
# This plugin provides chat history when users are connecting, or
# when users invoke the !history command.
# The history command can optionally take a parameter to indicate how many lines of history is requested.
#
# Parameters:
# history_max: the maximum number of messages to keep in history
# history_default: when !history is provided without arguments, then this default number of messages are returned.
# history_connect: the number of chat history messages to send when users connect (0 = do not send any history)
plugin /var/lib/uhub/mod_chat_history.so "history_max=200 history_default=10 history_connect=5"
# A plugin sending a welcome message.
#
# This plugin provides the following commands:
# !motd - Message of the day
# !rules - Show hub rules.
#
# Parameters:
# motd: path/filename for the welcome message (message of the day)
# rules: path/filenam for the rules file
#
# NOTE: The files MUST exist, however if you do not wish to provide one then these parameters can be omitted.
#
# The motd/rules files can do the following substitutions:
# %n - Nickname of the user who entered the hub or issued the command.
# %a - IP address of the user
# %% - Becomes '%'
# %H - Hour 24-hour format (00-23) (Hub local time)
# %I - Hour 12-hour format (01-12) (Hub local time)
# %P - 'AM' or 'PM'
# %p - 'am' or 'pm'
# %M - Minutes (00-59) (Hub local time)
# %S - Seconds (00-60) (Hub local time)
plugin /var/lib/uhub/mod_welcome.so "motd=/etc/uhub/motd.txt rules=/etc/uhub/rules.txt"

View File

@ -685,49 +685,6 @@ static int command_broadcast(struct command_base* cbase, struct hub_user* user,
return 0; return 0;
} }
static int command_history(struct command_base* cbase, struct hub_user* user, struct hub_command* cmd)
{
struct cbuffer* buf;
struct linked_list* messages = cbase->hub->chat_history;
char* message;
char* maxlines_str = list_get_first(cmd->args);
int maxlines = 0;
int skiplines = 0;
int total = list_size(messages);
if (total == 0)
return command_status(cbase, user, cmd, cbuf_create_const("No messages."));
buf = cbuf_create(MAX_HELP_MSG);
if (maxlines_str)
maxlines = uhub_atoi(maxlines_str);
if (maxlines <= 0 || maxlines > total)
maxlines = total;
if (maxlines != total)
{
skiplines = total - maxlines;
cbuf_append_format(buf, "*** %s: Displaying %d of %d message%s:", cmd->prefix, maxlines, total, ((total != 1) ? "s" : ""));
}
else
{
cbuf_append_format(buf, "*** %s: Displaying %d message%s:", cmd->prefix, total, ((total != 1) ? "s" : ""));
}
cbuf_append(buf, "\n");
message = (char*) list_get_first(messages);
while (message)
{
if (--skiplines < 0)
cbuf_append(buf, message);
message = (char*) list_get_next(messages);
}
cbuf_append(buf, "\n");
send_message(cbase, user, buf);
return 0;
}
static int command_log(struct command_base* cbase, struct hub_user* user, struct hub_command* cmd) static int command_log(struct command_base* cbase, struct hub_user* user, struct hub_command* cmd)
{ {
struct cbuffer* buf; struct cbuffer* buf;
@ -948,7 +905,6 @@ void commands_builtin_add(struct command_base* cbase)
#endif #endif
ADD_COMMAND("getip", 5, "n", auth_cred_operator, command_getip, "Show IP address for a user" ); ADD_COMMAND("getip", 5, "n", auth_cred_operator, command_getip, "Show IP address for a user" );
ADD_COMMAND("help", 4, "?c",auth_cred_guest, command_help, "Show this help message." ); ADD_COMMAND("help", 4, "?c",auth_cred_guest, command_help, "Show this help message." );
ADD_COMMAND("history", 7, "?N",auth_cred_guest, command_history, "Show the last chat messages." );
ADD_COMMAND("kick", 4, "n", auth_cred_operator, command_kick, "Kick a user" ); ADD_COMMAND("kick", 4, "n", auth_cred_operator, command_kick, "Kick a user" );
ADD_COMMAND("log", 3, "", auth_cred_operator, command_log, "Display log" ); ADD_COMMAND("log", 3, "", auth_cred_operator, command_log, "Display log" );
ADD_COMMAND("mute", 4, "n", auth_cred_operator, command_mute, "Mute user" ); ADD_COMMAND("mute", 4, "n", auth_cred_operator, command_mute, "Mute user" );

View File

@ -54,10 +54,10 @@ enum command_parse_status
*/ */
struct hub_command struct hub_command
{ {
enum command_parse_status status; /**<<< "Status of the hub_command." */
const char* message; /**<<< "The complete message." */ const char* message; /**<<< "The complete message." */
char* prefix; /**<<< "The prefix extracted from the message." */ char* prefix; /**<<< "The prefix extracted from the message." */
struct linked_list* args; /**<<< "List of all parsed arguments from the message. Type depends on expectations." */ struct linked_list* args; /**<<< "List of all parsed arguments from the message. Type depends on expectations." */
enum command_parse_status status; /**<<< "Status of the hub_command." */
command_handler handler; /**<<< "The function handler to call in order to invoke this command." */ command_handler handler; /**<<< "The function handler to call in order to invoke this command." */
const struct hub_user* user; /**<<< "The user who invoked this command." */ const struct hub_user* user; /**<<< "The user who invoked this command." */
void* ptr; /**<<< "A pointer of data which came from struct command_handler" */ void* ptr; /**<<< "A pointer of data which came from struct command_handler" */

View File

@ -265,6 +265,13 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc
if (!message) if (!message)
return 0; return 0;
message_decoded = adc_msg_unescape(message);
if (!message_decoded)
{
hub_free(message);
return 0;
}
if (!user_is_logged_in(u)) if (!user_is_logged_in(u))
{ {
hub_free(message); hub_free(message);
@ -290,9 +297,7 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc
} }
else else
{ {
message_decoded = adc_msg_unescape(message);
relay = command_invoke(hub->commands, u, message_decoded); relay = command_invoke(hub->commands, u, message_decoded);
hub_free(message_decoded);
} }
} }
@ -307,13 +312,13 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc
plugin_st status = st_default; plugin_st status = st_default;
if (broadcast) if (broadcast)
{ {
status = plugin_handle_chat_message(hub, u, message, 0); status = plugin_handle_chat_message(hub, u, message_decoded, 0);
} }
else if (private_msg) else if (private_msg)
{ {
struct hub_user* target = uman_get_user_by_sid(hub, cmd->target); struct hub_user* target = uman_get_user_by_sid(hub, cmd->target);
if (target) if (target)
status = plugin_handle_private_message(hub, u, target, message, 0); status = plugin_handle_private_message(hub, u, target, message_decoded, 0);
else else
relay = 0; relay = 0;
} }
@ -327,39 +332,15 @@ int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc
/* adc_msg_remove_named_argument(cmd, "PM"); */ /* adc_msg_remove_named_argument(cmd, "PM"); */
if (broadcast) if (broadcast)
{ {
hub_chat_history_add(hub, u, cmd); plugin_log_chat_message(hub, u, message_decoded, 0);
plugin_log_chat_message(hub, u, message, 0);
} }
ret = route_message(hub, u, cmd); ret = route_message(hub, u, cmd);
} }
hub_free(message); hub_free(message);
hub_free(message_decoded);
return ret; return ret;
} }
void hub_chat_history_add(struct hub_info* hub, struct hub_user* user, struct adc_message* cmd)
{
char* msg_esc = adc_msg_get_argument(cmd, 0);
char* message = adc_msg_unescape(msg_esc);
size_t loglen = strlen(message) + strlen(user->id.nick) + 13;
char* log = hub_malloc(loglen + 1);
snprintf(log, loglen, "%s <%s> %s\n", get_timestamp(time(NULL)), user->id.nick, message);
log[loglen] = '\0';
list_append(hub->chat_history, log);
while (list_size(hub->chat_history) > (size_t) hub->config->max_chat_history)
{
char* msg = list_get_first(hub->chat_history);
list_remove(hub->chat_history, msg);
hub_free(msg);
}
hub_free(message);
hub_free(msg_esc);
}
void hub_chat_history_clear(struct hub_info* hub)
{
list_clear(hub->chat_history, &hub_free);
}
void hub_send_support(struct hub_info* hub, struct hub_user* u) void hub_send_support(struct hub_info* hub, struct hub_user* u)
{ {
if (user_is_connecting(u) || user_is_logged_in(u)) if (user_is_connecting(u) || user_is_logged_in(u))
@ -803,20 +784,7 @@ struct hub_info* hub_start_service(struct hub_config* config)
return 0; return 0;
} }
hub->chat_history = (struct linked_list*) list_create();
hub->logout_info = (struct linked_list*) list_create(); hub->logout_info = (struct linked_list*) list_create();
if (!hub->chat_history)
{
net_con_close(hub->server);
list_destroy(hub->chat_history);
list_destroy(hub->logout_info);
hub_free(hub->recvbuf);
hub_free(hub->sendbuf);
uman_shutdown(hub);
hub_free(hub);
return 0;
}
server_alt_port_start(hub, config); server_alt_port_start(hub, config);
hub->status = hub_status_running; hub->status = hub_status_running;
@ -844,8 +812,6 @@ void hub_shutdown_service(struct hub_info* hub)
hub->status = hub_status_stopped; hub->status = hub_status_stopped;
hub_free(hub->sendbuf); hub_free(hub->sendbuf);
hub_free(hub->recvbuf); hub_free(hub->recvbuf);
hub_chat_history_clear(hub);
list_destroy(hub->chat_history);
list_clear(hub->logout_info, &hub_free); list_clear(hub->logout_info, &hub_free);
list_destroy(hub->logout_info); list_destroy(hub->logout_info);
command_shutdown(hub->commands); command_shutdown(hub->commands);

View File

@ -109,7 +109,6 @@ struct hub_info
char* recvbuf; /* Global receive buffer */ char* recvbuf; /* Global receive buffer */
char* sendbuf; /* Global send buffer */ char* sendbuf; /* Global send buffer */
struct linked_list* chat_history; /* Chat history */
struct linked_list* logout_info; /* Log of people logging out. */ struct linked_list* logout_info; /* Log of people logging out. */
struct command_base* commands; /* Hub command handler */ struct command_base* commands; /* Hub command handler */
@ -151,16 +150,6 @@ extern int hub_handle_password(struct hub_info* hub, struct hub_user* u, struct
*/ */
extern int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc_message* cmd); extern int hub_handle_chat_message(struct hub_info* hub, struct hub_user* u, struct adc_message* cmd);
/**
* Add a chat message to the chat history
*/
extern void hub_chat_history_add(struct hub_info* hub, struct hub_user* user, struct adc_message* cmd);
/**
* Clear the chat history.
*/
extern void hub_chat_history_clear(struct hub_info* hub);
/** /**
* Used internally by hub_handle_info * Used internally by hub_handle_info
* @return 1 if nickname is OK, or 0 if nickname is not accepted. * @return 1 if nickname is OK, or 0 if nickname is not accepted.

View File

@ -0,0 +1,245 @@
/*
* uhub - A tiny ADC p2p connection hub
* Copyright (C) 2007-2012, Jan Vidar Krey
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "plugin_api/handle.h"
#include "plugin_api/command_api.h"
#include "util/config_token.h"
#include "util/memory.h"
#include "util/misc.h"
#include "util/list.h"
#include "util/cbuffer.h"
#define MAX_HISTORY_SIZE 16384
struct chat_history_data
{
size_t history_max; ///<<< "the maximum number of chat messages kept in history."
size_t history_default; ///<<< "the default number of chat messages returned if no limit was provided"
size_t history_connect; ///<<< "the number of chat messages provided when users connect to the hub."
struct linked_list* chat_history; ///<<< "The chat history storage."
struct plugin_command_handle* command_history_handle; ///<<< "A handle to the !history command."
};
/**
* Add a chat message to history.
*/
static void history_add(struct plugin_handle* plugin, struct plugin_user* from, const char* message, int flags)
{
size_t loglen = strlen(message) + strlen(from->nick) + 13;
struct chat_history_data* data = (struct chat_history_data*) plugin->ptr;
char* log = hub_malloc(loglen + 1);
snprintf(log, loglen, "%s <%s> %s\n", get_timestamp(time(NULL)), from->nick, message);
log[loglen] = '\0';
list_append(data->chat_history, log);
while (list_size(data->chat_history) > data->history_max)
{
char* msg = list_get_first(data->chat_history);
list_remove(data->chat_history, msg);
hub_free(msg);
}
}
/**
* Obtain 'num' messages from the chat history and append them to outbuf.
*
* @return the number of messages added to the buffer.
*/
static size_t get_messages(struct chat_history_data* data, size_t num, struct cbuffer* outbuf)
{
struct linked_list* messages = data->chat_history;
char* message;
int skiplines = 0;
size_t lines = 0;
int total = list_size(messages);
if (total == 0)
return 0;
if (num <= 0 || num > total)
num = total;
if (num != total)
skiplines = total - num;
cbuf_append(outbuf, "\n");
message = (char*) list_get_first(messages);
while (message)
{
if (--skiplines < 0)
{
cbuf_append(outbuf, message);
lines++;
}
message = (char*) list_get_next(messages);
}
cbuf_append(outbuf, "\n");
return lines;
}
void user_login(struct plugin_handle* plugin, struct plugin_user* user)
{
struct chat_history_data* data = (struct chat_history_data*) plugin->ptr;
struct cbuffer* buf = NULL;
// size_t messages = 0;
if (data->history_connect > 0 && list_size(data->chat_history) > 0)
{
buf = cbuf_create(MAX_HISTORY_SIZE);
cbuf_append(buf, "Chat history:\n");
get_messages(data, data->history_connect, buf);
plugin->hub.send_message(plugin, user, cbuf_get(buf));
cbuf_destroy(buf);
}
}
/**
* Send a status message back to the user who issued the !history command.
*/
static int command_status(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_command* cmd, struct cbuffer* buf)
{
struct cbuffer* msg = cbuf_create(cbuf_size(buf) + strlen(cmd->prefix) + 8);
cbuf_append_format(msg, "*** %s: %s", cmd->prefix, cbuf_get(buf));
plugin->hub.send_message(plugin, user, cbuf_get(msg));
cbuf_destroy(msg);
cbuf_destroy(buf);
return 0;
}
/**
* The callback function for handling the !history command.
*/
static int command_history(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_command* cmd)
{
struct cbuffer* buf;
struct chat_history_data* data = (struct chat_history_data*) plugin->ptr;
int maxlines = 0;
if (!list_size(data->chat_history))
return command_status(plugin, user, cmd, cbuf_create_const("No messages."));
if (list_size(cmd->args) > 0)
maxlines = (int) (intptr_t) ((intptr_t*) (void*) list_get_first(cmd->args));
else
maxlines = data->history_default;
buf = cbuf_create(MAX_HISTORY_SIZE);
cbuf_append_format(buf, "*** %s: Chat History:\n", cmd->prefix);
get_messages(data, maxlines, buf);
plugin->hub.send_message(plugin, user, cbuf_get(buf));
cbuf_destroy(buf);
return 0;
}
static void set_error_message(struct plugin_handle* plugin, const char* msg)
{
plugin->error_msg = msg;
}
static struct chat_history_data* parse_config(const char* line, struct plugin_handle* plugin)
{
struct chat_history_data* data = (struct chat_history_data*) hub_malloc_zero(sizeof(struct chat_history_data));
struct cfg_tokens* tokens = cfg_tokenize(line);
char* token = cfg_token_get_first(tokens);
assert(data != NULL);
data->history_max = 200;
data->history_default = 25;
data->history_connect = 5;
data->chat_history = list_create();
while (token)
{
struct cfg_settings* setting = cfg_settings_split(token);
if (!setting)
{
set_error_message(plugin, "Unable to parse startup parameters");
cfg_tokens_free(tokens);
hub_free(data);
return 0;
}
if (strcmp(cfg_settings_get_key(setting), "history_max") == 0)
{
data->history_max = (size_t) uhub_atoi(cfg_settings_get_value(setting));
}
else if (strcmp(cfg_settings_get_key(setting), "history_default") == 0)
{
data->history_default = (size_t) uhub_atoi(cfg_settings_get_value(setting));
}
else if (strcmp(cfg_settings_get_key(setting), "history_connect") == 0)
{
data->history_connect = (size_t) uhub_atoi(cfg_settings_get_value(setting));
}
else
{
set_error_message(plugin, "Unknown startup parameters given");
cfg_tokens_free(tokens);
cfg_settings_free(setting);
hub_free(data);
return 0;
}
cfg_settings_free(setting);
token = cfg_token_get_next(tokens);
}
cfg_tokens_free(tokens);
return data;
}
int plugin_register(struct plugin_handle* plugin, const char* config)
{
struct chat_history_data* data;
PLUGIN_INITIALIZE(plugin, "Chat history plugin", "1.0", "Provide a global chat history log.");
plugin->funcs.on_user_chat_message = history_add;
plugin->funcs.on_user_login = user_login;
data = parse_config(config, plugin);
if (!data)
return -1;
plugin->ptr = data;
data->command_history_handle = (struct plugin_command_handle*) hub_malloc(sizeof(struct plugin_command_handle));
PLUGIN_COMMAND_INITIALIZE(data->command_history_handle, plugin, "history", "?N", auth_cred_guest, &command_history, "Show chat message history.");
plugin->hub.command_add(plugin, data->command_history_handle);
return 0;
}
int plugin_unregister(struct plugin_handle* plugin)
{
struct chat_history_data* data = (struct chat_history_data*) plugin->ptr;
if (data)
{
list_clear(data->chat_history, &hub_free);
list_destroy(data->chat_history);
plugin->hub.command_del(plugin, data->command_history_handle);
hub_free(data->command_history_handle);
hub_free(data);
}
return 0;
}