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