Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
db17223df0
|
|||
|
2474b5f3f5
|
|||
|
d97f28e7f7
|
|||
|
d6a9e1dca1
|
|||
|
c8e4833086
|
|||
|
99595c0d81
|
|||
|
75c4645f10
|
|||
|
fa5e005728
|
|||
|
a9b77b3206
|
|||
|
29418e5e55
|
|||
|
4665807d09
|
|||
| 1180255e7b | |||
| 6f85490fa3 | |||
| e97c60245c | |||
| 3a8d70de66 | |||
| 695d615225 | |||
| 8a3144f0ac | |||
| 48423aa4af |
@@ -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
16
NEWS
@@ -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
|
||||
|
||||
@@ -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
599
degesch.c
@@ -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", ¤t)))
|
||||
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, ¤t))
|
||||
{
|
||||
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", ¤t))
|
||||
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, ¤t))
|
||||
print_error ("%s: %s", "localtime_r", strerror (errno));
|
||||
else if (!strftime (buf, sizeof buf, "%F %T", ¤t))
|
||||
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
6
kike.c
@@ -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);
|
||||
|
||||
47
plugins/degesch/auto-rejoin.lua
Normal file
47
plugins/degesch/auto-rejoin.lua
Normal 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)
|
||||
@@ -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
241
plugins/zyklonb/calc
Executable 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
160
plugins/zyklonb/seen
Executable 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
|
||||
39
plugins/zyklonb/seen-import-degesch.pl
Executable file
39
plugins/zyklonb/seen-import-degesch.pl
Executable 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";
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user