18 Commits

Author SHA1 Message Date
db17223df0 Bump version; update NEWS, README 2016-04-28 23:46:08 +02:00
2474b5f3f5 calc: fix usage of (substring) 2016-04-28 23:25:29 +02:00
d97f28e7f7 ZyklonB: add a seen plugin 2016-04-24 21:05:53 +02:00
d6a9e1dca1 degesch: customizable date change messages
Now also in the backlog.
2016-04-21 23:50:05 +02:00
c8e4833086 degesch: add a NOWRAP flag to formatter_flush()
--format should work as before now.

It is now also possible to rebind PageUp to show a wrapped backlog.
2016-04-21 23:50:05 +02:00
99595c0d81 degesch: update comments 2016-04-21 23:50:05 +02:00
75c4645f10 degesch: add an auto-rejoin.lua plugin 2016-04-21 22:12:33 +02:00
fa5e005728 degesch: refactor Lua weak objects 2016-04-21 22:09:35 +02:00
a9b77b3206 degesch: expose channels and users to Lua 2016-04-21 22:09:35 +02:00
29418e5e55 ping-timeout.lua: fix message parsing 2016-04-21 22:09:35 +02:00
4665807d09 degesch: expose message parsing to Lua 2016-04-21 22:09:35 +02:00
1180255e7b calc: comment updates, import fixes 2016-04-20 22:55:40 +02:00
6f85490fa3 Update NEWS 2016-04-16 20:11:11 +02:00
e97c60245c ZyklonB: add a calc plugin 2016-04-16 20:11:11 +02:00
3a8d70de66 degesch: fix crash on invalid cp1252 characters
We don't even really need iconv here.
2016-04-03 04:05:04 +02:00
695d615225 ZyklonB, kike: Use pledge(2) in OpenBSD
degesch has something like "stdio wpath cpath inet tty proc exec"
but given that it's user-extensible and very annoying for users to
have it crash, I'm leaving it unrestricted for now.
2016-03-30 00:50:44 +02:00
8a3144f0ac degesch: update program logo
I've noticed that the old one wasn't very pleasant to look at.
2016-03-28 21:08:04 +02:00
48423aa4af Update README 2016-03-28 21:07:56 +02:00
11 changed files with 937 additions and 193 deletions

View File

@@ -13,7 +13,7 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
# Version
set (project_version "0.9.3")
set (project_version "0.9.4")
# Try to append commit ID if it follows a version tag. It might be nicer if
# we could also detect dirty worktrees but that's very hard to get right.

16
NEWS
View File

@@ -1,3 +1,19 @@
0.9.4 (2016-04-28) "Oops"
* degesch: fix crash on characters invalid in Windows-1252
* degesch: add an auto-rejoin plugin
* degesch: better date change messages with customizable formatting;
now also used in the backlog, so it looks closer to regular output
* ZyklonB: add a calc plugin providing a basic Scheme REPL
* ZyklonB: add a seen plugin
* kike, ZyklonB: use pledge(2) on OpenBSD
0.9.3 (2016-03-27) "Doesn't Even Suck"
* Use TLS Server Name Indication when connecting to servers

View File

@@ -95,9 +95,6 @@ Or you can try telling CMake to make a package for you. For Debian it is:
$ cpack -G DEB
# dpkg -i uirc3-*.deb
Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with
`fakeroot` or file ownership will end up wrong.
Usage
-----
'degesch' has in-program configuration. Just run it and read the instructions.
@@ -165,3 +162,6 @@ is included within the package, or, at your option, you may relicense the work
under the MIT or the Modified BSD License, as listed at the following site:
http://www.gnu.org/licenses/license-list.html
Note that 'degesch' technically becomes GPL-licensed when you compile it against
GNU Readline, but that is not a concern of this source package.

599
degesch.c
View File

@@ -22,6 +22,7 @@
#define ATTR_TABLE(XX) \
XX( PROMPT, "prompt", "Terminal attrs for the prompt" ) \
XX( RESET, "reset", "String to reset terminal attributes" ) \
XX( DATE_CHANGE, "date_change", "Terminal attrs for date change" ) \
XX( READ_MARKER, "read_marker", "Terminal attrs for the read marker" ) \
XX( WARNING, "warning", "Terminal attrs for warnings" ) \
XX( ERROR, "error", "Terminal attrs for errors" ) \
@@ -1328,8 +1329,6 @@ channel_user_destroy (struct channel_user *self)
// We keep references to channels in their buffers,
// and weak references in their users and the name lookup table.
// XXX: this doesn't really have to be reference countable
struct channel
{
REF_COUNTABLE_HEADER
@@ -1973,7 +1972,6 @@ struct app_context
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
iconv_t term_from_utf8; ///< UTF-8 to terminal encoding
iconv_t latin1_to_utf8; ///< ISO Latin 1 to UTF-8
struct input *input; ///< User interface
@@ -2054,12 +2052,9 @@ app_context_init (struct app_context *self)
self->backlog_limit = 1000;
self->last_displayed_msg_time = time (NULL);
// Windows 1252 redefines several silly control characters as glyphs
char *native = nl_langinfo (CODESET);
if (!app_iconv_open (&self->term_from_utf8, native, "UTF-8")
|| !app_iconv_open (&self->term_to_utf8, "UTF-8", native)
|| (!app_iconv_open (&self->latin1_to_utf8, "UTF-8", "WINDOWS-1252")
&& !app_iconv_open (&self->latin1_to_utf8, "UTF-8", "ISO-8859-1")))
|| !app_iconv_open (&self->term_to_utf8, "UTF-8", native))
exit_fatal ("creating the UTF-8 conversion object failed: %s",
strerror (errno));
@@ -2100,7 +2095,6 @@ app_context_free (struct app_context *self)
str_map_free (&self->servers);
poller_free (&self->poller);
iconv_close (self->latin1_to_utf8);
iconv_close (self->term_from_utf8);
iconv_close (self->term_to_utf8);
@@ -2324,6 +2318,10 @@ static struct config_schema g_config_behaviour[] =
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on",
.on_change = on_config_word_wrapping_change },
{ .name = "date_change_line",
.comment = "Input to strftime(3) for the date change line",
.type = CONFIG_ITEM_STRING,
.default_ = "\"%F\"" },
{ .name = "logging",
.comment = "Log buffer contents to file",
.type = CONFIG_ITEM_BOOLEAN,
@@ -2619,6 +2617,7 @@ init_colors (struct app_context *ctx)
INIT_ATTR (PROMPT, enter_bold_mode);
INIT_ATTR (RESET, exit_attribute_mode);
INIT_ATTR (DATE_CHANGE, enter_bold_mode);
INIT_ATTR (READ_MARKER, g_terminal.color_set_fg[COLOR_MAGENTA]);
INIT_ATTR (WARNING, g_terminal.color_set_fg[COLOR_YELLOW]);
INIT_ATTR (ERROR, g_terminal.color_set_fg[COLOR_RED]);
@@ -2915,14 +2914,41 @@ irc_skip_statusmsg (struct server *s, const char *target)
// As of 2015, everything should be in UTF-8. And if it's not, we'll decode it
// as ISO Latin 1. This function should not be called on the whole message.
static char *
irc_to_utf8 (struct app_context *ctx, const char *text)
irc_to_utf8 (const char *text)
{
if (!text)
return NULL;
size_t len = strlen (text) + 1;
if (utf8_validate (text, len))
return xstrdup (text);
return iconv_xstrdup (ctx->latin1_to_utf8, (char *) text, len, NULL);
// Windows 1252 redefines several silly C1 control characters as glyphs
static const char *c1[32] =
{
"\xe2\x82\xac", "\xc2\x81", "\xe2\x80\x9a", "\xc6\x92",
"\xe2\x80\x9e", "\xe2\x80\xa6", "\xe2\x80\xa0", "\xe2\x80\xa1",
"\xcb\x86", "\xe2\x80\xb0", "\xc5\xa0", "\xe2\x80\xb9",
"\xc5\x92", "\xc2\x8d", "\xc5\xbd", "\xc2\x8f",
"\xc2\x90", "\xe2\x80\x98", "\xe2\x80\x99", "\xe2\x80\x9c",
"\xe2\x80\x9d", "\xe2\x80\xa2", "\xe2\x80\x93", "\xe2\x80\x94",
"\xcb\x9c", "\xe2\x84\xa2", "\xc5\xa1", "\xe2\x80\xba",
"\xc5\x93", "\xc2\x9d", "\xc5\xbe", "\xc5\xb8",
};
struct str s;
str_init (&s);
for (const char *p = text; *p; p++)
{
int c = *(unsigned char *) p;
if (c < 0x80)
str_append_c (&s, c);
else if (c < 0xA0)
str_append (&s, c1[c & 0x1f]);
else
str_append_data (&s,
(char[]) {0xc0 | (c >> 6), 0x80 | (c & 0x3f)}, 2);
}
return str_steal (&s);
}
// This function is used to output debugging IRC traffic to the terminal.
@@ -2931,7 +2957,7 @@ irc_to_utf8 (struct app_context *ctx, const char *text)
static char *
irc_to_term (struct app_context *ctx, const char *text)
{
char *utf8 = irc_to_utf8 (ctx, text);
char *utf8 = irc_to_utf8 (text);
char *term = iconv_xstrdup (ctx->term_from_utf8, utf8, -1, NULL);
free (utf8);
return term;
@@ -3096,7 +3122,7 @@ formatter_parse_nick (struct formatter *self, char *s)
// which would also make us not cut off the userhost part, ever
if (irc_is_channel (self->s, irc_skip_statusmsg (self->s, s)))
{
char *tmp = irc_to_utf8 (self->ctx, s);
char *tmp = irc_to_utf8 (s);
FORMATTER_ADD_TEXT (self, tmp);
free (tmp);
return;
@@ -3120,7 +3146,7 @@ formatter_parse_nick (struct formatter *self, char *s)
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = color);
char *x = irc_to_utf8 (self->ctx, nick);
char *x = irc_to_utf8 (nick);
free (nick);
FORMATTER_ADD_TEXT (self, x);
free (x);
@@ -3141,7 +3167,7 @@ formatter_parse_nick_full (struct formatter *self, char *s)
FORMATTER_ADD_TEXT (self, " (");
FORMATTER_ADD_ITEM (self, ATTR, .attribute = ATTR_USERHOST);
char *x = irc_to_utf8 (self->ctx, userhost);
char *x = irc_to_utf8 (userhost);
FORMATTER_ADD_TEXT (self, x);
free (x);
@@ -3181,12 +3207,12 @@ restart:
break;
case 'S':
tmp = irc_to_utf8 (self->ctx, (s = va_arg (*ap, char *)));
tmp = irc_to_utf8 ((s = va_arg (*ap, char *)));
str_append (buf, tmp);
free (tmp);
break;
case 'm':
tmp = irc_to_utf8 (self->ctx, (s = va_arg (*ap, char *)));
tmp = irc_to_utf8 ((s = va_arg (*ap, char *)));
formatter_parse_mirc (self, tmp);
free (tmp);
break;
@@ -3344,11 +3370,14 @@ line_wrap_flush (struct line_wrap_state *s, bool force_split)
else if (force_split || s->chunk.used > s->line_max)
{
#ifdef WRAP_UNNECESSARILY
// Use the entire line and split the chunk in the middle
// When the line wraps at the end of the screen and a background colour
// is set, the terminal paints the entire new line with that colour.
// Explicitly inserting a newline with the default attributes fixes it.
line_wrap_flush_split (s, &s->overflow);
#else
// We don't actually _need_ to split here, and doing so will break
// link searching mechanisms in some terminals
// Splitting here breaks link searching mechanisms in some terminals,
// though, so we make a trade-off and let the chunk wrap naturally.
// Fuck terminals, really.
s->line_used = s->overflow.used;
#endif
}
@@ -3542,12 +3571,19 @@ formatter_to_chars (struct formatter *formatter)
return self.result;
}
enum
{
FLUSH_OPT_RAW = (1 << 0), ///< Print raw attributes
FLUSH_OPT_NOWRAP = (1 << 1) ///< Do not wrap
};
static void
formatter_flush (struct formatter *self, FILE *stream, bool raw_attributes)
formatter_flush (struct formatter *self, FILE *stream, int flush_opts)
{
struct line_char *line = formatter_to_chars (self);
if (!get_attribute_printer (stream) && !raw_attributes)
bool is_tty = !!get_attribute_printer (stream);
if (!is_tty && !(flush_opts & FLUSH_OPT_RAW))
{
LIST_FOR_EACH (struct line_char, c, line)
{
@@ -3557,7 +3593,7 @@ formatter_flush (struct formatter *self, FILE *stream, bool raw_attributes)
return;
}
if (get_attribute_printer (stream) && self->ctx->word_wrapping)
if (self->ctx->word_wrapping && !(flush_opts & FLUSH_OPT_NOWRAP))
line = line_wrap (line, g_terminal.columns);
// TODO: rewrite the sloppily hacked mess around attribute_printer;
@@ -3620,7 +3656,8 @@ on_config_backlog_limit_change (struct config_item *item)
}
static void
buffer_update_time (struct app_context *ctx, time_t now)
buffer_update_time (struct app_context *ctx, time_t now, FILE *stream,
int flush_opts)
{
struct tm last, current;
if (!localtime_r (&ctx->last_displayed_msg_time, &last)
@@ -3637,15 +3674,27 @@ buffer_update_time (struct app_context *ctx, time_t now)
&& last.tm_mday == current.tm_mday)
return;
char buf[32] = "";
if (soft_assert (strftime (buf, sizeof buf, "%F", &current)))
print_status ("%s", buf);
// Else the buffer was too small, which is pretty weird
char buf[64] = "";
const char *format =
get_config_string (ctx->config.root, "behaviour.date_change_line");
if (!strftime (buf, sizeof buf, format, &current))
{
print_error ("%s: %s", "strftime", strerror (errno));
return;
}
struct formatter f;
formatter_init (&f, ctx, NULL);
formatter_add (&f, "#a#s\n", ATTR_DATE_CHANGE, buf);
formatter_flush (&f, stream, flush_opts);
// Flush the trailing formatting reset item
fflush (stream);
formatter_free (&f);
}
static void
buffer_line_flush (struct buffer_line *line, struct formatter *f, FILE *output,
bool raw_attributes)
int flush_opts)
{
int flags = line->flags;
if (flags & BUFFER_LINE_INDENT) formatter_add (f, " ");
@@ -3656,20 +3705,17 @@ buffer_line_flush (struct buffer_line *line, struct formatter *f, FILE *output,
formatter_add_item (f, *iter);
formatter_add (f, "\n");
formatter_flush (f, output, raw_attributes);
formatter_flush (f, output, flush_opts);
formatter_free (f);
}
static void
buffer_line_display (struct app_context *ctx,
struct buffer_line *line, bool is_external)
buffer_line_write_time (struct formatter *f, struct buffer_line *line,
FILE *stream, int flush_opts)
{
// Normal timestamps don't include the date, this way the user won't be
// confused as to when an event has happened
buffer_update_time (ctx, line->when);
struct formatter f;
formatter_init (&f, ctx, NULL);
buffer_update_time (f->ctx, line->when, stream, flush_opts);
struct tm current;
char buf[9];
@@ -3678,7 +3724,18 @@ buffer_line_display (struct app_context *ctx,
else if (!strftime (buf, sizeof buf, "%T", &current))
print_error ("%s: %s", "strftime", "buffer too small");
else
formatter_add (&f, "#a#s#r ", ATTR_TIMESTAMP, buf);
formatter_add (f, "#a#s#r ", ATTR_TIMESTAMP, buf);
}
static void
buffer_line_display (struct app_context *ctx,
struct buffer_line *line, bool is_external)
{
CALL (ctx->input, hide);
struct formatter f;
formatter_init (&f, ctx, NULL);
buffer_line_write_time (&f, line, stdout, 0);
// Ignore all formatting for messages coming from other buffers, that is
// either from the global or server buffer. Instead print them in grey.
@@ -3687,31 +3744,21 @@ buffer_line_display (struct app_context *ctx,
formatter_add (&f, "#a", ATTR_EXTERNAL);
FORMATTER_ADD_ITEM (&f, IGNORE_ATTR, .attribute = 1);
}
CALL (ctx->input, hide);
buffer_line_flush (line, &f, stdout, false);
buffer_line_flush (line, &f, stdout, 0);
// Flush the trailing formatting reset item
fflush (stdout);
CALL (ctx->input, show);
}
static void
buffer_line_write_to_backlog (struct app_context *ctx,
struct buffer_line *line, FILE *log_file, bool raw_attributes)
struct buffer_line *line, FILE *log_file, int flush_opts)
{
struct formatter f;
formatter_init (&f, ctx, NULL);
struct tm current;
char buf[20];
if (!localtime_r (&line->when, &current))
print_error ("%s: %s", "localtime_r", strerror (errno));
else if (!strftime (buf, sizeof buf, "%F %T", &current))
print_error ("%s: %s", "strftime", "buffer too small");
else
formatter_add (&f, "#a#s#r ", ATTR_TIMESTAMP, buf);
buffer_line_flush (line, &f, log_file, raw_attributes);
buffer_line_write_time (&f, line, log_file, flush_opts);
buffer_line_flush (line, &f, log_file, flush_opts);
}
static void
@@ -3733,7 +3780,8 @@ buffer_line_write_to_log (struct app_context *ctx,
else
formatter_add (&f, "#s ", buf);
buffer_line_flush (line, &f, log_file, false);
// The target is not a terminal, thus it won't wrap in spite of the 0
buffer_line_flush (line, &f, log_file, 0);
}
static void
@@ -3983,12 +4031,12 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
}
static void
buffer_print_read_marker (struct app_context *ctx, FILE *stream, bool raw_attrs)
buffer_print_read_marker (struct app_context *ctx, FILE *stream, int flush_opts)
{
struct formatter f;
formatter_init (&f, ctx, NULL);
formatter_add (&f, "#a-- -- -- ---\n", ATTR_READ_MARKER);
formatter_flush (&f, stream, raw_attrs);
formatter_flush (&f, stream, flush_opts);
// Flush the trailing formatting reset item
fflush (stream);
formatter_free (&f);
@@ -4017,6 +4065,10 @@ buffer_print_backlog (struct app_context *ctx, struct buffer *buffer)
tputs (tparm (cursor_address, g_terminal.lines - 1,
0, 0, 0, 0, 0, 0, 0, 0), 1, printer);
fflush (stdout);
// We should update "last_displayed_msg_time" here just to be sure
// that the first date marker, if necessary, is shown, but in practice
// the value should always be from today when this function is called
}
else
{
@@ -4037,12 +4089,12 @@ buffer_print_backlog (struct app_context *ctx, struct buffer *buffer)
{
if (until_marker-- == 0
&& buffer->new_messages_count != buffer->lines_count)
buffer_print_read_marker (ctx, stdout, false);
buffer_line_display (ctx, line, false);
buffer_print_read_marker (ctx, stdout, 0);
buffer_line_display (ctx, line, 0);
}
// So that it is obvious if the last line in the buffer is not from today
buffer_update_time (ctx, time (NULL));
buffer_update_time (ctx, time (NULL), stdout, 0);
refresh_prompt (ctx);
CALL (ctx->input, show);
@@ -8255,114 +8307,270 @@ lua_plugin_log_error
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#define XLUA_BUFFER_METATABLE "buffer" ///< Identifier for the Lua metatable
struct lua_buffer
{
struct lua_plugin *plugin; ///< The plugin we belong to
struct buffer *buffer; ///< The buffer
struct weak_ref_link *weak_ref; ///< A weak reference link
};
static void
lua_buffer_invalidate (void *object, void *user_data)
lua_plugin_kv (lua_State *L, const char *key, const char *value)
{
struct lua_buffer *wrapper = user_data;
wrapper->buffer = NULL;
wrapper->weak_ref = NULL;
// This can in theory call the GC, order isn't arbitrary here
lua_cache_invalidate (wrapper->plugin->L, object);
lua_pushstring (L, value);
lua_setfield (L, -2, key);
}
static void
lua_plugin_push_buffer (struct lua_plugin *plugin, struct buffer *buffer)
lua_plugin_push_message (lua_State *L, const struct irc_message *msg)
{
lua_State *L = plugin->L;
if (lua_cache_get (L, buffer))
return;
lua_createtable (L, 0, 4);
struct lua_buffer *wrapper = lua_newuserdata (L, sizeof *wrapper);
luaL_setmetatable (L, XLUA_BUFFER_METATABLE);
wrapper->plugin = plugin;
wrapper->buffer = buffer;
wrapper->weak_ref = buffer_weak_ref
(buffer, lua_buffer_invalidate, wrapper);
lua_cache_store (L, buffer, -1);
lua_createtable (L, msg->tags.len, 0);
struct str_map_iter iter;
str_map_iter_init (&iter, &msg->tags);
const char *value;
while ((value = str_map_iter_next (&iter)))
lua_plugin_kv (L, iter.link->key, value);
lua_setfield (L, -2, "tags");
// TODO: parse the prefix further?
if (msg->prefix) lua_plugin_kv (L, "prefix", msg->prefix);
if (msg->command) lua_plugin_kv (L, "command", msg->command);
lua_createtable (L, msg->params.len, 0);
for (size_t i = 0; i < msg->params.len; i++)
{
lua_pushstring (L, msg->params.vector[i]);
lua_rawseti (L, -2, i + 1);
}
lua_setfield (L, -2, "params");
}
static int
lua_plugin_parse (lua_State *L)
{
struct irc_message msg;
irc_parse_message (&msg, luaL_checkstring (L, 1));
lua_plugin_push_message (L, &msg);
irc_free_message (&msg);
return 1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#define XLUA_SERVER_METATABLE "server" ///< Identifier for the Lua metatable
// Lua code can use weakly referenced wrappers for internal objects.
struct lua_server
typedef struct weak_ref_link *
(*lua_weak_ref_fn) (void *object, destroy_cb_fn cb, void *user_data);
typedef void (*lua_weak_unref_fn) (void *object, struct weak_ref_link **link);
struct lua_weak_info
{
const char *name; ///< Metatable name
lua_weak_ref_fn ref; ///< Weak link invalidator
lua_weak_unref_fn unref; ///< Weak link generator
};
struct lua_weak
{
struct lua_plugin *plugin; ///< The plugin we belong to
struct server *server; ///< The server
void *object; ///< The object
struct weak_ref_link *weak_ref; ///< A weak reference link
};
static void
lua_server_invalidate (void *object, void *user_data)
lua_weak_invalidate (void *object, void *user_data)
{
struct lua_server *wrapper = user_data;
wrapper->server = NULL;
struct lua_weak *wrapper = user_data;
wrapper->object = NULL;
wrapper->weak_ref = NULL;
// This can in theory call the GC, order isn't arbitrary here
lua_cache_invalidate (wrapper->plugin->L, object);
}
static void
lua_plugin_push_server (struct lua_plugin *plugin, struct server *server)
lua_weak_push (struct lua_plugin *plugin, void *object,
struct lua_weak_info *info)
{
lua_State *L = plugin->L;
if (lua_cache_get (L, server))
if (!object)
{
lua_pushnil (L);
return;
}
if (lua_cache_get (L, object))
return;
struct lua_server *wrapper = lua_newuserdata (L, sizeof *wrapper);
luaL_setmetatable (L, XLUA_SERVER_METATABLE);
struct lua_weak *wrapper = lua_newuserdata (L, sizeof *wrapper);
luaL_setmetatable (L, info->name);
wrapper->plugin = plugin;
wrapper->server = server;
wrapper->weak_ref = server_weak_ref
(server, lua_server_invalidate, wrapper);
lua_cache_store (L, server, -1);
wrapper->object = object;
wrapper->weak_ref = info->ref (object, lua_weak_invalidate, wrapper);
lua_cache_store (L, object, -1);
}
static int
lua_weak_gc (lua_State *L, const struct lua_weak_info *info)
{
struct lua_weak *wrapper = luaL_checkudata (L, 1, info->name);
if (wrapper->object)
{
lua_cache_invalidate (L, wrapper->object);
info->unref (wrapper->object, &wrapper->weak_ref);
wrapper->object = NULL;
}
return 0;
}
static struct lua_weak *
lua_weak_deref (lua_State *L, const struct lua_weak_info *info)
{
struct lua_weak *weak = luaL_checkudata (L, 1, info->name);
luaL_argcheck (L, weak->object, 1, "dead reference used");
return weak;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#define LUA_WEAK_DECLARE(id, metatable_id) \
static struct lua_weak_info lua_ ## id ## _info = \
{ \
.name = metatable_id, \
.ref = (lua_weak_ref_fn) id ## _weak_ref, \
.unref = (lua_weak_unref_fn) id ## _weak_unref, \
};
#define XLUA_USER_METATABLE "user" ///< Identifier for Lua metatable
#define XLUA_CHANNEL_METATABLE "channel" ///< Identifier for Lua metatable
#define XLUA_BUFFER_METATABLE "buffer" ///< Identifier for Lua metatable
#define XLUA_SERVER_METATABLE "server" ///< Identifier for Lua metatable
LUA_WEAK_DECLARE (user, XLUA_USER_METATABLE)
LUA_WEAK_DECLARE (channel, XLUA_CHANNEL_METATABLE)
LUA_WEAK_DECLARE (buffer, XLUA_BUFFER_METATABLE)
LUA_WEAK_DECLARE (server, XLUA_SERVER_METATABLE)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static int
lua_user_gc (lua_State *L)
{
return lua_weak_gc (L, &lua_user_info);
}
static int
lua_user_get_nickname (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_user_info);
struct user *user = wrapper->object;
lua_pushstring (L, user->nickname);
return 1;
}
static int
lua_user_get_channels (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_user_info);
struct user *user = wrapper->object;
int i = 1;
lua_newtable (L);
LIST_FOR_EACH (struct user_channel, iter, user->channels)
{
lua_weak_push (wrapper->plugin, iter->channel, &lua_channel_info);
lua_rawseti (L, -2, i++);
}
return 1;
}
static luaL_Reg lua_user_table[] =
{
{ "__gc", lua_user_gc },
{ "get_nickname", lua_user_get_nickname },
{ "get_channels", lua_user_get_channels },
{ NULL, NULL }
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static int
lua_channel_gc (lua_State *L)
{
return lua_weak_gc (L, &lua_channel_info);
}
static int
lua_channel_get_name (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_channel_info);
struct channel *channel = wrapper->object;
lua_pushstring (L, channel->name);
return 1;
}
static int
lua_channel_get_users (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_channel_info);
struct channel *channel = wrapper->object;
int i = 1;
lua_newtable (L);
LIST_FOR_EACH (struct channel_user, iter, channel->users)
{
lua_createtable (L, 0, 2);
lua_weak_push (wrapper->plugin, iter->user, &lua_user_info);
lua_setfield (L, -2, "user");
lua_plugin_kv (L, "prefixes", iter->prefixes.str);
lua_rawseti (L, -2, i++);
}
return 1;
}
static luaL_Reg lua_channel_table[] =
{
{ "__gc", lua_channel_gc },
{ "get_name", lua_channel_get_name },
{ "get_users", lua_channel_get_users },
{ NULL, NULL }
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static int
lua_buffer_gc (lua_State *L)
{
struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
if (wrapper->buffer)
{
lua_cache_invalidate (L, wrapper->buffer);
buffer_weak_unref (wrapper->buffer, &wrapper->weak_ref);
wrapper->buffer = NULL;
}
return 0;
return lua_weak_gc (L, &lua_buffer_info);
}
static int
lua_buffer_get_user (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
struct buffer *buffer = wrapper->object;
lua_weak_push (wrapper->plugin, buffer->user, &lua_user_info);
return 1;
}
static int
lua_buffer_get_channel (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
struct buffer *buffer = wrapper->object;
lua_weak_push (wrapper->plugin, buffer->channel, &lua_channel_info);
return 1;
}
static int
lua_buffer_get_server (lua_State *L)
{
struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
luaL_argcheck (L, wrapper->buffer, 1, "dead reference used");
if (wrapper->buffer->server)
lua_plugin_push_server (wrapper->plugin, wrapper->buffer->server);
else
lua_pushnil (L);
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
struct buffer *buffer = wrapper->object;
lua_weak_push (wrapper->plugin, buffer->server, &lua_server_info);
return 1;
}
static int
lua_buffer_log (lua_State *L)
{
struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
luaL_argcheck (L, wrapper->buffer, 1, "dead reference used");
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
struct buffer *buffer = wrapper->object;
const char *message = lua_plugin_check_utf8 (L, 2);
struct buffer *buffer = wrapper->buffer;
log_full (wrapper->plugin->ctx, buffer->server, buffer,
BUFFER_LINE_STATUS, "#s", message);
return 0;
@@ -8371,11 +8579,9 @@ lua_buffer_log (lua_State *L)
static int
lua_buffer_execute (lua_State *L)
{
struct lua_buffer *wrapper = luaL_checkudata (L, 1, XLUA_BUFFER_METATABLE);
luaL_argcheck (L, wrapper->buffer, 1, "dead reference used");
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
struct buffer *buffer = wrapper->object;
const char *line = lua_plugin_check_utf8 (L, 2);
struct buffer *buffer = wrapper->buffer;
process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
return 0;
}
@@ -8383,11 +8589,13 @@ lua_buffer_execute (lua_State *L)
static luaL_Reg lua_buffer_table[] =
{
// TODO: some useful methods or values
{ "__gc", lua_buffer_gc },
{ "get_server", lua_buffer_get_server },
{ "log", lua_buffer_log },
{ "execute", lua_buffer_execute },
{ NULL, NULL }
{ "__gc", lua_buffer_gc },
{ "get_user", lua_buffer_get_user },
{ "get_channel", lua_buffer_get_channel },
{ "get_server", lua_buffer_get_server },
{ "log", lua_buffer_log },
{ "execute", lua_buffer_execute },
{ NULL, NULL }
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -8395,37 +8603,33 @@ static luaL_Reg lua_buffer_table[] =
static int
lua_server_gc (lua_State *L)
{
struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
if (wrapper->server)
{
lua_cache_invalidate (L, wrapper->server);
server_weak_unref (wrapper->server, &wrapper->weak_ref);
wrapper->server = NULL;
}
return 0;
return lua_weak_gc (L, &lua_server_info);
}
static int
lua_server_get_user (lua_State *L)
{
struct lua_weak *wrapper = lua_weak_deref (L, &lua_server_info);
struct server *server = wrapper->object;
lua_weak_push (wrapper->plugin, server->irc_user, &lua_user_info);
return 1;
}
static int
lua_server_get_buffer (lua_State *L)
{
struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
luaL_argcheck (L, wrapper->server, 1, "dead reference used");
if (wrapper->server->buffer)
lua_plugin_push_buffer (wrapper->plugin, wrapper->server->buffer);
else
lua_pushnil (L);
struct lua_weak *wrapper = lua_weak_deref (L, &lua_server_info);
struct server *server = wrapper->object;
lua_weak_push (wrapper->plugin, server->buffer, &lua_buffer_info);
return 1;
}
static int
lua_server_send (lua_State *L)
{
struct lua_server *wrapper = luaL_checkudata (L, 1, XLUA_SERVER_METATABLE);
luaL_argcheck (L, wrapper->server, 1, "dead reference used");
const char *line = luaL_checkstring (L, 2);
irc_send (wrapper->server, "%s", line);
struct lua_weak *wrapper = lua_weak_deref (L, &lua_server_info);
struct server *server = wrapper->object;
irc_send (server, "%s", luaL_checkstring (L, 2));
return 0;
}
@@ -8433,6 +8637,7 @@ static luaL_Reg lua_server_table[] =
{
// TODO: some useful methods or values
{ "__gc", lua_server_gc },
{ "get_user", lua_server_get_user },
{ "get_buffer", lua_server_get_buffer },
{ "send", lua_server_send },
{ NULL, NULL }
@@ -8521,9 +8726,9 @@ lua_input_hook_filter (struct input_hook *self, struct buffer *buffer,
lua_State *L = plugin->L;
lua_rawgeti (L, LUA_REGISTRYINDEX, hook->ref_callback);
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
lua_plugin_push_buffer (plugin, buffer); // 2: buffer
lua_pushstring (L, input); // 3: input
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
lua_weak_push (plugin, buffer, &lua_buffer_info); // 2: buffer
lua_pushstring (L, input); // 3: input
struct error *e = NULL;
if (lua_plugin_call (plugin, 3, 1, &e))
@@ -8552,9 +8757,9 @@ lua_irc_hook_filter (struct irc_hook *self, struct server *s, char *message)
lua_State *L = plugin->L;
lua_rawgeti (L, LUA_REGISTRYINDEX, hook->ref_callback);
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
lua_plugin_push_server (plugin, s); // 2: server
lua_pushstring (L, message); // 3: message
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
lua_weak_push (plugin, s, &lua_server_info); // 2: server
lua_pushstring (L, message); // 3: message
struct error *e = NULL;
if (lua_plugin_call (plugin, 3, 1, &e))
@@ -8641,7 +8846,7 @@ lua_completion_hook_complete (struct completion_hook *self,
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
lua_plugin_push_completion (L, data); // 2: data
lua_plugin_push_buffer (plugin, plugin->ctx->current_buffer);
lua_weak_push (plugin, plugin->ctx->current_buffer, &lua_buffer_info);
lua_setfield (L, -2, "buffer");
lua_pushstring (L, word); // 3: word
@@ -9429,6 +9634,7 @@ lua_plugin_connect (lua_State *L)
static luaL_Reg lua_plugin_library[] =
{
{ "parse", lua_plugin_parse },
{ "hook_input", lua_plugin_hook_input },
{ "hook_irc", lua_plugin_hook_irc },
{ "hook_completion", lua_plugin_hook_completion },
@@ -9563,6 +9769,8 @@ lua_plugin_load (struct app_context *ctx, const char *filename,
// Create metatables for our objects
lua_plugin_create_meta (L, XLUA_HOOK_METATABLE, lua_hook_table);
lua_plugin_create_meta (L, XLUA_USER_METATABLE, lua_user_table);
lua_plugin_create_meta (L, XLUA_CHANNEL_METATABLE, lua_channel_table);
lua_plugin_create_meta (L, XLUA_BUFFER_METATABLE, lua_buffer_table);
lua_plugin_create_meta (L, XLUA_SERVER_METATABLE, lua_server_table);
lua_plugin_create_meta (L, XLUA_SCHEMA_METATABLE, lua_schema_table);
@@ -9958,7 +10166,6 @@ handle_command_buffer (struct handler_args *a)
else if (!strcasecmp_ascii (action, "clear"))
{
buffer_clear (a->buffer);
// XXX: clear screen?
buffer_print_backlog (ctx, a->buffer);
}
else if (!strcasecmp_ascii (action, "move"))
@@ -11968,12 +12175,8 @@ launch_backlog_helper (struct app_context *ctx, int backlog_fd)
}
static bool
on_display_backlog (int count, int key, void *user_data)
display_backlog (struct app_context *ctx, int flush_opts)
{
(void) count;
(void) key;
struct app_context *ctx = user_data;
FILE *backlog = tmpfile ();
if (!backlog)
{
@@ -11981,8 +12184,10 @@ on_display_backlog (int count, int key, void *user_data)
"Failed to create a temporary file", strerror (errno));
return false;
}
bool raw_attributes = !get_config_boolean
(ctx->config.root, "behaviour.backlog_helper_strip_formatting");
if (!get_config_boolean (ctx->config.root,
"behaviour.backlog_helper_strip_formatting"))
flush_opts |= FLUSH_OPT_RAW;
struct buffer *buffer = ctx->current_buffer;
int until_marker =
@@ -11991,10 +12196,13 @@ on_display_backlog (int count, int key, void *user_data)
{
if (until_marker-- == 0
&& buffer->new_messages_count != buffer->lines_count)
buffer_print_read_marker (ctx, backlog, raw_attributes);
buffer_line_write_to_backlog (ctx, line, backlog, raw_attributes);
buffer_print_read_marker (ctx, backlog, flush_opts);
buffer_line_write_to_backlog (ctx, line, backlog, flush_opts);
}
// So that it is obvious if the last line in the buffer is not from today
buffer_update_time (ctx, time (NULL), backlog, flush_opts);
rewind (backlog);
set_cloexec (fileno (backlog));
launch_backlog_helper (ctx, fileno (backlog));
@@ -12002,6 +12210,22 @@ on_display_backlog (int count, int key, void *user_data)
return true;
}
static bool
on_display_backlog (int count, int key, void *user_data)
{
(void) count;
(void) key;
return display_backlog (user_data, 0);
}
static bool
on_display_backlog_nowrap (int count, int key, void *user_data)
{
(void) count;
(void) key;
return display_backlog (user_data, FLUSH_OPT_NOWRAP);
}
static bool
on_display_full_log (int count, int key, void *user_data)
{
@@ -12178,20 +12402,21 @@ input_add_functions (void *user_data)
{
struct app_context *ctx = user_data;
#define XX(...) CALL_ (ctx->input, register_fn, __VA_ARGS__, ctx);
XX ("previous-buffer", "Previous buffer", on_previous_buffer)
XX ("next-buffer", "Next buffer", on_next_buffer)
XX ("goto-buffer", "Go to buffer", on_goto_buffer)
XX ("switch-buffer", "Switch buffer", on_switch_buffer)
XX ("goto-highlight", "Go to highlight", on_goto_highlight)
XX ("goto-activity", "Go to activity", on_goto_activity)
XX ("move-buffer-left", "Move buffer left", on_move_buffer_left)
XX ("move-buffer-right", "Move buffer right", on_move_buffer_right)
XX ("display-backlog", "Show backlog", on_display_backlog)
XX ("display-full-log", "Show full log", on_display_full_log)
XX ("edit-input", "Edit input", on_edit_input)
XX ("redraw-screen", "Redraw screen", on_redraw_screen)
XX ("insert-attribute", "mIRC formatting", on_insert_attribute)
XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode)
XX ("previous-buffer", "Previous buffer", on_previous_buffer)
XX ("next-buffer", "Next buffer", on_next_buffer)
XX ("goto-buffer", "Go to buffer", on_goto_buffer)
XX ("switch-buffer", "Switch buffer", on_switch_buffer)
XX ("goto-highlight", "Go to highlight", on_goto_highlight)
XX ("goto-activity", "Go to activity", on_goto_activity)
XX ("move-buffer-left", "Move buffer left", on_move_buffer_left)
XX ("move-buffer-right", "Move buffer right", on_move_buffer_right)
XX ("display-backlog", "Show backlog", on_display_backlog)
XX ("display-backlog-nw", "Non-wrapped log", on_display_backlog_nowrap)
XX ("display-full-log", "Show full log", on_display_full_log)
XX ("edit-input", "Edit input", on_edit_input)
XX ("redraw-screen", "Redraw screen", on_redraw_screen)
XX ("insert-attribute", "mIRC formatting", on_insert_attribute)
XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode)
#undef XX
}
@@ -12215,7 +12440,7 @@ bind_common_keys (struct app_context *ctx)
if (key_f5) CALL_ (self, bind, key_f5, "previous-buffer");
if (key_f6) CALL_ (self, bind, key_f6, "next-buffer");
if (key_ppage) CALL_ (self, bind, key_ppage, "display-backlog");
if (key_ppage) CALL_ (self, bind, key_ppage, "display-backlog-nw");
if (clear_screen)
CALL_ (self, bind_control, 'l', "redraw-screen");
@@ -12925,7 +13150,11 @@ static void
on_date_change_timer (struct app_context *ctx)
{
if (ctx->terminal_suspended <= 0)
buffer_update_time (ctx, time (NULL));
{
CALL (ctx->input, hide);
buffer_update_time (ctx, time (NULL), stdout, 0);
CALL (ctx->input, show);
}
rearm_date_change_timer (ctx);
}
@@ -13098,12 +13327,12 @@ main (int argc, char *argv[])
static const char *g_logo[] =
{
" __ __ ",
" __/ / ____ ____ ____ ____ ____ / /_ ",
" / / / , / / / / , / / __/ / __/ / __ \\ ",
" / / / / __/ / / / / __/ /_ / / /_ / / / / ",
"/___/ /___/ /_ / /___/ /___/ /___/ /_/ /_/ " PROGRAM_VERSION,
" /___/ ",
" __ __ ",
" __/ /___________________/ / ",
" / / , / / , / __/ __/ _ \\ ",
"/ / / __/ / / __/_ / /_/ // / ",
"\\__/\\__/_ /\\__/___/\\__/_//_/ " PROGRAM_VERSION,
" /___/",
""
};
@@ -13123,7 +13352,7 @@ format_input_and_die (struct app_context *ctx)
struct formatter f;
formatter_init (&f, ctx, NULL);
formatter_add (&f, "#m", buf);
formatter_flush (&f, stdout, false);
formatter_flush (&f, stdout, FLUSH_OPT_NOWRAP);
formatter_free (&f);
}
exit (EXIT_SUCCESS);

6
kike.c
View File

@@ -4076,6 +4076,12 @@ main (int argc, char *argv[])
else if (!irc_lock_pid_file (&ctx, &e))
exit_fatal ("%s", e->message);
#if OpenBSD >= 201605
// This won't be as simple once we decide to implement REHASH
if (pledge ("stdio inet dns", NULL))
exit_fatal ("%s: %s", "pledge", strerror (errno));
#endif
ctx.polling = true;
while (ctx.polling)
poller_run (&ctx.poller);

View File

@@ -0,0 +1,47 @@
--
-- auto-rejoin.lua: join back automatically when someone kicks you
--
-- Copyright (c) 2016, Přemysl Janouch <p.janouch@gmail.com>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local timeout
degesch.setup_config {
timeout = {
type = "integer",
comment = "auto rejoin timeout",
default = "0",
on_change = function (v)
timeout = v
end,
validate = function (v)
if v < 0 then error ("timeout must not be negative", 0) end
end,
},
}
degesch.hook_irc (function (hook, server, line)
local msg = degesch.parse (line)
if msg.command ~= "KICK" then return line end
local who = msg.prefix:match ("^[^!]*")
local channel, whom = table.unpack (msg.params)
if who ~= whom and whom == server.user.nickname then
degesch.hook_timer (function (hook)
server:send ("JOIN " .. channel)
end, timeout * 1000)
end
return line
end)

View File

@@ -1,7 +1,7 @@
--
-- ping-timeout.lua: ping timeout readability enhancement plugin
--
-- Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
-- Copyright (c) 2015 - 2016, Přemysl Janouch <p.janouch@gmail.com>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,9 @@
--
degesch.hook_irc (function (hook, server, line)
local start, timeout =
line:match ("^(:[^ ]* QUIT :Ping timeout:) (%d+) seconds$")
if not start then
local msg = degesch.parse (line)
local start, timeout = line:match ("^(.* :Ping timeout:) (%d+) seconds$")
if msg.command ~= "QUIT" or not start then
return line
end

241
plugins/zyklonb/calc Executable file
View File

@@ -0,0 +1,241 @@
#!/usr/bin/env guile
ZyklonB calc plugin, basic Scheme evaluator
Copyright 2016 Přemysl Janouch
See the file LICENSE for licensing information.
!#
(import (rnrs (6)))
(use-modules ((rnrs) :version (6)))
; --- Message parsing ----------------------------------------------------------
(define-record-type message (fields prefix command params))
(define (parse-message line)
(let f ([parts '()] [chars (string->list line)])
(define (take-word w chars)
(if (or (null? chars) (eqv? (car chars) #\x20))
(f (cons (list->string (reverse w)) parts)
(if (null? chars) chars (cdr chars)))
(take-word (cons (car chars) w) (cdr chars))))
(if (null? chars)
(let ([data (reverse parts)])
(when (< (length data) 2)
(error 'parse-message "invalid message"))
(make-message (car data) (cadr data) (cddr data)))
(if (null? parts)
(if (eqv? (car chars) #\:)
(take-word '() (cdr chars))
(f (cons #f parts) chars))
(if (eqv? (car chars) #\:)
(f (cons (list->string (cdr chars)) parts) '())
(take-word '() chars))))))
; --- Utilities ----------------------------------------------------------------
(define (display-exception e port)
(define (puts . x)
(for-all (lambda (a) (display a port)) x)
(newline port))
(define (record-fields rec)
(let* ([rtd (record-rtd rec)]
[v (record-type-field-names rtd)]
[len (vector-length v)])
(map (lambda (k i) (cons k ((record-accessor rtd i) rec)))
(vector->list v)
(let c ([i len] [ls '()])
(if (= i 0) ls (c (- i 1) (cons (- i 1) ls)))))))
(puts "Caught " (record-type-name (record-rtd e)))
(for-all
(lambda (subtype)
(puts " " (record-type-name (record-rtd subtype)))
(for-all
(lambda (field) (puts " " (car field) ": " (cdr field)))
(record-fields subtype)))
(simple-conditions e)))
; XXX - we have to work around Guile's lack of proper eol-style support
(define xc (make-transcoder (latin-1-codec) 'lf 'replace))
(define irc-input-port (transcoded-port (standard-input-port) xc))
(define irc-output-port (transcoded-port (standard-output-port) xc))
(define (send . message)
(for-all (lambda (x) (display x irc-output-port)) message)
(display #\return irc-output-port)
(newline irc-output-port)
(flush-output-port irc-output-port))
(define (get-line-crlf port)
(define line (get-line port))
(if (eof-object? line) line
(let ([len (string-length line)])
(if (and (> len 0) (eqv? (string-ref line (- len 1)) #\return))
(substring line 0 (- len 1)) line))))
(define (get-config name)
(send "ZYKLONB get_config :" name)
(car (message-params (parse-message (get-line-crlf irc-input-port)))))
(define (extract-nick prefix)
(do ([i 0 (+ i 1)] [len (string-length prefix)])
([or (= i len) (char=? #\! (string-ref prefix i))]
[substring prefix 0 i])))
(define (string-after s start)
(let ([s-len (string-length s)] [with-len (string-length start)])
(and (>= s-len with-len)
(string=? (substring s 0 with-len) start)
(substring s with-len s-len))))
; --- Calculator ---------------------------------------------------------------
; Evaluator derived from the example in The Scheme Programming Language.
;
; Even though EVAL with a carefully crafted environment would also do a good
; job at sandboxing, it would probably be impossible to limit execution time...
(define (env-new formals actuals env)
(cond [(null? formals) env]
[(symbol? formals) (cons (cons formals actuals) env)]
[else (cons (cons (car formals) (car actuals))
(env-new (cdr formals) (cdr actuals) env))]))
(define (env-lookup var env) (cdr (assq var env)))
(define (env-assign var val env) (set-cdr! (assq var env) val))
(define (check-reductions r)
(if (= (car r) 0)
(error 'check-reductions "reduction limit exceeded")
(set-car! r (- (car r) 1))))
; TODO - think about implementing more syntactical constructs,
; however there's not much point in having anything else in a calculator...
(define (exec expr r env)
(check-reductions r)
(cond [(symbol? expr) (env-lookup expr env)]
[(pair? expr)
(case (car expr)
[(quote) (cadr expr)]
[(lambda) (lambda vals
(let ([env (env-new (cadr expr) vals env)])
(let loop ([exprs (cddr expr)])
(if (null? (cdr exprs))
(exec (car exprs) r env)
(begin (exec (car exprs) r env)
(loop (cdr exprs)))))))]
[(if) (if (exec (cadr expr) r env)
(exec (caddr expr) r env)
(exec (cadddr expr) r env))]
[(set!) (env-assign (cadr expr) (exec (caddr expr) r env) env)]
[else (apply (exec (car expr) r env)
(map (lambda (x) (exec x r env)) (cdr expr)))])]
[else expr]))
(define-syntax forward
(syntax-rules ()
[(_) '()]
[(_ a b ...) (cons (cons (quote a) a) (forward b ...))]))
; ...which can't prevent me from simply importing most of the standard library
(define base-library
(forward
; Equivalence, procedure predicate, booleans
eqv? eq? equal? procedure? boolean? boolean=? not
; numbers, numerical input and output
number? complex? real? rational? integer? exact? inexact? exact inexact
real-valued? rational-valued? integer-valued? number->string string->number
; Arithmetic
= < > <= >= zero? positive? negative? odd? even? finite? infinite? nan?
min max + * - / abs div-and-mod div mod div0-and-mod0 div0 mod0
gcd lcm numerator denominator floor ceiling truncate round
rationalize exp log sin cos tan asin acos atan sqrt expt
make-rectangular make-polar real-part imag-part magnitude angle
; Pairs and lists
map for-each cons car cdr caar cadr cdar cddr
caaar caadr cadar caddr cdaar cdadr cddar cdddr
caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
pair? null? list? list length append reverse list-tail list-ref
; Symbols
symbol? symbol=? symbol->string string->symbol
; Characters
char? char=? char<? char>? char<=? char>=? char->integer integer->char
; Strings; XXX - omitted make-string - can cause OOM
string? string=? string<? string>? string<=? string>=?
string string-length string-ref substring
string-append string->list list->string string-for-each string-copy
; Vectors; XXX - omitted make-vector - can cause OOM
vector? vector vector-length vector-ref vector-set!
vector->list list->vector vector-fill! vector-map vector-for-each
; Control features
apply call/cc values call-with-values dynamic-wind))
(define extended-library
(forward
char-upcase char-downcase char-titlecase char-foldcase
char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=?
char-alphabetic? char-numeric? char-whitespace?
char-upper-case? char-lower-case? char-title-case?
string-upcase string-downcase string-titlecase string-foldcase
string-ci=? string-ci<? string-ci>? string-ci<=? string-ci>=?
find for-all exists filter partition fold-left fold-right
remp remove remv remq memp member memv memq assp assoc assv assq cons*
list-sort vector-sort vector-sort!
bitwise-not bitwise-and bitwise-ior bitwise-xor bitwise-if
bitwise-bit-count bitwise-length bitwise-first-bit-set bitwise-bit-set?
bitwise-copy-bit bitwise-bit-field bitwise-copy-bit-field
bitwise-arithmetic-shift bitwise-rotate-bit-field bitwise-reverse-bit-field
bitwise-arithmetic-shift-left bitwise-arithmetic-shift-right
set-car! set-cdr! string-set! string-fill!))
(define (interpret expr)
(exec expr '(2000) (append base-library extended-library)))
; We could show something a bit nicer but it would be quite Guile-specific
(define (error-string e)
(map (lambda (x) (string-append " " (symbol->string x)))
(filter (lambda (x) (not (member x '(&who &message &irritants &guile))))
(map (lambda (x) (record-type-name (record-rtd x)))
(simple-conditions e)))))
(define (calc input respond)
(define (stringify x)
(call-with-string-output-port (lambda (port) (write x port))))
(guard (e [else (display-exception e (current-error-port))
(apply respond "caught" (error-string e))])
(let* ([input (open-string-input-port input)]
[data (let loop ()
(define datum (get-datum input))
(if (eof-object? datum) '() (cons datum (loop))))])
(call-with-values
(lambda () (interpret (list (append '(lambda ()) data))))
(lambda message
(for-all (lambda (x) (respond (stringify x))) message))))))
; --- Main loop ----------------------------------------------------------------
(define prefix (get-config "prefix"))
(send "ZYKLONB register")
(define (process msg)
(when (string-ci=? (message-command msg) "PRIVMSG")
(let* ([nick (extract-nick (message-prefix msg))]
[target (car (message-params msg))]
[response-begin
(apply string-append "PRIVMSG "
(if (memv (string-ref target 0) (string->list "#&!+"))
`(,target " :" ,nick ": ") `(,nick " :")))]
[respond (lambda args (apply send response-begin args))]
[text (cadr (message-params msg))]
[input (or (string-after text (string-append prefix "calc "))
(string-after text (string-append prefix "= ")))])
(when input (calc input respond)))))
(let main-loop ()
(define line (get-line-crlf irc-input-port))
(unless (eof-object? line)
(guard (e [else (display-exception e (current-error-port))])
(unless (string=? "" line)
(process (parse-message line))))
(main-loop)))

160
plugins/zyklonb/seen Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env lua
--
-- ZyklonB seen plugin
--
-- Copyright 2016 Přemysl Janouch <p.janouch@gmail.com>
-- See the file LICENSE for licensing information.
--
function parse (line)
local msg = { params = {} }
line = line:match ("[^\r]*")
for start, word in line:gmatch ("()([^ ]+)") do
local colon = word:match ("^:(.*)")
if start == 1 and colon then
msg.prefix = colon
elseif not msg.command then
msg.command = word
elseif colon then
table.insert (msg.params, line:sub (start + 1))
break
elseif start ~= #line then
table.insert (msg.params, word)
end
end
return msg
end
function get_config (name)
io.write ("ZYKLONB get_config :", name, "\r\n")
return parse (io.read ()).params[1]
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
io.output ():setvbuf ('line')
local prefix = get_config ('prefix')
io.write ("ZYKLONB register\r\n")
local db = {}
local db_filename = "seen.db"
local db_garbage = 0
function remember (who, where, when, what)
if not db[who] then db[who] = {} end
if db[who][where] then db_garbage = db_garbage + 1 end
db[who][where] = { tonumber (when), what }
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local db_file, e = io.open (db_filename, "a+")
if not db_file then error ("cannot open database: " .. e, 0) end
function db_store (who, where, when, what)
db_file:write (string.format
(":%s %s %s %s :%s\n", who, "PRIVMSG", where, when, what))
end
function db_compact ()
db_file:close ()
-- Unfortunately, default Lua doesn't have anything like mkstemp()
local db_tmpname = db_filename .. "." .. os.time ()
db_file, e = io.open (db_tmpname, "a+")
if not db_file then error ("cannot save database: " .. e, 0) end
for who, places in pairs (db) do
for where, data in pairs (places) do
db_store (who, where, data[1], data[2])
end
end
db_file:flush ()
local ok, e = os.rename (db_tmpname, db_filename)
if not ok then error ("cannot save database: " .. e, 0) end
db_garbage = 0
end
for line in db_file:lines () do
local msg = parse (line)
remember (msg.prefix, table.unpack (msg.params))
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function seen (who, where, args)
local respond = function (...)
local privmsg = function (target, ...)
io.write ("PRIVMSG ", target, " :", table.concat { ... }, "\r\n")
end
if where:match ("^[#&!+]") then
privmsg (where, who, ": ", ...)
else
privmsg (who, ...)
end
end
local whom, e, garbage = args:match ("^(%S+)()%s*(.*)")
if not whom or #garbage ~= 0 then
return respond ("usage: <name>")
elseif who:lower () == whom:lower () then
return respond ("I can see you right now.")
end
local top = {}
-- That is, * acts like a wildcard, otherwise everything is escaped
local pattern = "^" .. whom:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0")
:gsub ("%*", ".*"):lower () .. "$"
for name, places in pairs (db) do
if places[where] and name:lower ():match (pattern) then
local when, what = table.unpack (places[where])
table.insert (top, { name = name, when = when, what = what })
end
end
if #top == 0 then
return respond ("I have not seen \x02" .. whom .. "\x02 here.")
end
-- Get all matching nicknames ordered from the most recently active
-- and make the list case insensitive (remove older duplicates)
table.sort (top, function (a, b) return a.when > b.when end)
for i = #top, 2, -1 do
if top[i - 1].name:lower () == top[i].name:lower () then
table.remove (top, i)
end
end
-- Hopefully the formatting mess will disrupt highlights in clients
for i = 1, math.min (#top, 3) do
local name = top[i].name:gsub ("^.", "%0\x02\x02")
respond (string.format ("\x02%s\x02 -> %s -> %s",
name, os.date ("%c", top[i].when), top[i].what))
end
end
function handle (msg)
local who = msg.prefix:match ("^[^!@]*")
local where, what = table.unpack (msg.params)
local when = os.time ()
local what_log = what:gsub ("^\x01ACTION", "*"):gsub ("\x01$", "")
remember (who, where, when, what_log)
db_store (who, where, when, what_log)
-- Comment out to reduce both disk load and reliability
db_file:flush ()
if db_garbage > 5000 then db_compact () end
if what:sub (1, #prefix) == prefix then
local command = what:sub (#prefix + 1)
local name, e = command:match ("^(%S+)%s*()")
if name == 'seen' then seen (who, where, command:sub (e)) end
end
end
for line in io.lines () do
local msg = parse (line)
if msg.command == "PRIVMSG" then handle (msg) end
end

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env perl
# Creates a database for the "seen" plugin from logs for degesch.
# The results may not be completely accurate but are good for jumpstarting.
# Usage: ./seen-import-degesch.pl LOG-FILE... > seen.db
use strict;
use warnings;
use File::Basename;
use Time::Piece;
my $db = {};
for (@ARGV) {
my $where = (basename($_) =~ /\.(.*).log/)[0];
unless ($where) {
print STDERR "Invalid filename: $_\n";
next;
}
open my $fh, '<', $_ or die "Failed to open log file: $!";
while (<$fh>) {
my ($when, $who, $who_action, $what) =
/^(.{19}) (?:<[~&@%+]*(.*?)>| \* (\S+)) (.*)/;
next unless $when;
if ($who_action) {
$who = $who_action;
$what = "* $what";
}
$db->{$who}->{$where} =
[Time::Piece->strptime($when, "%Y-%m-%d %T")->epoch, $what];
}
}
while (my ($who, $places) = each %$db) {
while (my ($where, $data) = each %$places) {
my ($when, $what) = @$data;
print ":$who PRIVMSG $where $when :$what\n";
}
}

View File

@@ -2023,6 +2023,12 @@ main (int argc, char *argv[])
ctx.signal_event.user_data = &ctx;
poller_fd_set (&ctx.signal_event, POLLIN);
#if OpenBSD >= 201605
// cpath is for creating the plugin home directory
if (pledge ("stdio rpath cpath inet proc exec", NULL))
exit_fatal ("%s: %s", "pledge", strerror (errno));
#endif
plugin_load_all_from_config (&ctx);
if (!parse_config (&ctx, &e)
|| !irc_connect (&ctx, &e))