Compare commits
	
		
			72 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						3c048f0d56
	
				 | 
					
					
						|||
| 
						
						
							
						
						8e668ff31a
	
				 | 
					
					
						|||
| 
						
						
							
						
						eb70bf3fbc
	
				 | 
					
					
						|||
| 
						
						
							
						
						d86a68f510
	
				 | 
					
					
						|||
| 
						
						
							
						
						d6be22291d
	
				 | 
					
					
						|||
| 
						
						
							
						
						a813babb89
	
				 | 
					
					
						|||
| 
						
						
							
						
						b666ce6926
	
				 | 
					
					
						|||
| 
						
						
							
						
						e2bb051bd3
	
				 | 
					
					
						|||
| 
						
						
							
						
						52d1ded7df
	
				 | 
					
					
						|||
| 
						
						
							
						
						cb9f187f80
	
				 | 
					
					
						|||
| 
						
						
							
						
						0247c4667a
	
				 | 
					
					
						|||
| 
						
						
							
						
						572f4e2ea3
	
				 | 
					
					
						|||
| 
						
						
							
						
						50599e09bd
	
				 | 
					
					
						|||
| 
						
						
							
						
						b24bb0aded
	
				 | 
					
					
						|||
| 
						
						
							
						
						7c6cf42075
	
				 | 
					
					
						|||
| 
						
						
							
						
						414a525c4d
	
				 | 
					
					
						|||
| 
						
						
							
						
						6cee7159f2
	
				 | 
					
					
						|||
| 
						
						
							
						
						568f9b7123
	
				 | 
					
					
						|||
| 
						
						
							
						
						0d499dd125
	
				 | 
					
					
						|||
| 
						
						
							
						
						37e49b54cf
	
				 | 
					
					
						|||
| 
						
						
							
						
						742d590b8d
	
				 | 
					
					
						|||
| 
						
						
							
						
						b6528c73e3
	
				 | 
					
					
						|||
| 
						
						
							
						
						1e79aaec26
	
				 | 
					
					
						|||
| 
						
						
							
						
						0995da3900
	
				 | 
					
					
						|||
| 
						
						
							
						
						c8a826f016
	
				 | 
					
					
						|||
| 
						
						
							
						
						95c7ababc3
	
				 | 
					
					
						|||
| 
						
						
							
						
						a0d733fdb9
	
				 | 
					
					
						|||
| 
						
						
							
						
						557a39c6c8
	
				 | 
					
					
						|||
| 
						
						
							
						
						745e758394
	
				 | 
					
					
						|||
| 
						
						
							
						
						b60bdf119a
	
				 | 
					
					
						|||
| 
						
						
							
						
						278e2b236b
	
				 | 
					
					
						|||
| 
						
						
							
						
						2f758bbdb9
	
				 | 
					
					
						|||
| 
						
						
							
						
						911276b263
	
				 | 
					
					
						|||
| 
						
						
							
						
						cb5ad675a6
	
				 | 
					
					
						|||
| 
						
						
							
						
						9408dfc67c
	
				 | 
					
					
						|||
| 
						
						
							
						
						fed8b06aff
	
				 | 
					
					
						|||
| 
						
						
							
						
						7e64fd9886
	
				 | 
					
					
						|||
| 
						
						
							
						
						6928184a3d
	
				 | 
					
					
						|||
| 
						
						
							
						
						f7155f3919
	
				 | 
					
					
						|||
| 
						
						
							
						
						f032466307
	
				 | 
					
					
						|||
| 
						
						
							
						
						c0f4b554ef
	
				 | 
					
					
						|||
| 
						
						
							
						
						639da7a9a7
	
				 | 
					
					
						|||
| 
						
						
							
						
						230b04014f
	
				 | 
					
					
						|||
| 
						
						
							
						
						4848354bb9
	
				 | 
					
					
						|||
| 
						
						
							
						
						8028c7fa47
	
				 | 
					
					
						|||
| 
						
						
							
						
						43de836b91
	
				 | 
					
					
						|||
| 
						
						
							
						
						16d10f574b
	
				 | 
					
					
						|||
| 
						
						
							
						
						4cefa5ab1b
	
				 | 
					
					
						|||
| 
						
						
							
						
						92a4d4b5a7
	
				 | 
					
					
						|||
| 
						
						
							
						
						26f94d2459
	
				 | 
					
					
						|||
| 
						
						
							
						
						0be43691d0
	
				 | 
					
					
						|||
| 
						
						
							
						
						483ab39e3c
	
				 | 
					
					
						|||
| 
						
						
							
						
						beaf1a1f82
	
				 | 
					
					
						|||
| 
						
						
							
						
						5613c326c9
	
				 | 
					
					
						|||
| 
						
						
							
						
						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.5")
 | 
			
		||||
 | 
			
		||||
# 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.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								NEWS
									
									
									
									
									
								
							@@ -1,3 +1,50 @@
 | 
			
		||||
0.9.5 (2016-12-30) "It's Time"
 | 
			
		||||
 | 
			
		||||
 * Better support for the KILL command
 | 
			
		||||
 | 
			
		||||
 * degesch: export many more fields to the Lua API, add a prompt hook
 | 
			
		||||
 | 
			
		||||
 * degesch: show channel user count in the prompt
 | 
			
		||||
 | 
			
		||||
 * degesch: allow hiding join/part messages and other noise (Meta-Shift-H)
 | 
			
		||||
 | 
			
		||||
 * degesch: allow autojoining channels with keys
 | 
			
		||||
 | 
			
		||||
 * degesch: rejoin channels with keys on reconnect
 | 
			
		||||
 | 
			
		||||
 * degesch: make /query without arguments just open the buffer
 | 
			
		||||
 | 
			
		||||
 * degesch: add a censor plugin
 | 
			
		||||
 | 
			
		||||
 * degesch: die on configuration parse errors
 | 
			
		||||
 | 
			
		||||
 * degesch: request channel modes also on rejoin
 | 
			
		||||
 | 
			
		||||
 * degesch: don't show remembered channel modes on parted channels
 | 
			
		||||
 | 
			
		||||
 * degesch: fix highlight detection in colored text
 | 
			
		||||
 | 
			
		||||
 * degesch: fix CTCP handling for the real world and don't decode X-QUOTEs
 | 
			
		||||
 | 
			
		||||
 * degesch: add support for OpenSSL 1.1.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.adoc
									
									
									
									
									
								
							@@ -20,11 +20,13 @@ The IRC client.  It is largely defined by being built on top of GNU Readline
 | 
			
		||||
that has been hacked to death.  Its interface should feel somewhat familiar for
 | 
			
		||||
weechat or irssi users.
 | 
			
		||||
 | 
			
		||||
image::degesch.png[align="center"]
 | 
			
		||||
 | 
			
		||||
This is the largest application within the project.  It has most of the stuff
 | 
			
		||||
you'd expect of an IRC client, such as being able to set up multiple servers,
 | 
			
		||||
a powerful configuration system, integrated help, text formatting, CTCP queries,
 | 
			
		||||
automatic splitting of overlong messages, autocomplete, logging to file,
 | 
			
		||||
auto-away, command aliases and rudimentary support for Lua scripting.
 | 
			
		||||
auto-away, command aliases and basic support for Lua scripting.
 | 
			
		||||
 | 
			
		||||
kike
 | 
			
		||||
----
 | 
			
		||||
@@ -95,9 +97,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.
 | 
			
		||||
@@ -145,6 +144,35 @@ Consult the source code and the GNU Readline manual for a list of available
 | 
			
		||||
functions.  Also refer to the latter for the exact syntax of this file.
 | 
			
		||||
Beware that you can easily break the program if you're not careful.
 | 
			
		||||
 | 
			
		||||
How do I make degesch look like the screenshot?
 | 
			
		||||
-----------------------------------------------
 | 
			
		||||
First of all, you must build it with Lua support.  With the defaults, degesch
 | 
			
		||||
doesn't look very fancy because some things are rather hackish, and I also don't
 | 
			
		||||
want to depend on UTF-8 or 256color terminals in the code.  In addition to that,
 | 
			
		||||
I appear to be one of the few people who use black on white terminals.
 | 
			
		||||
 | 
			
		||||
 /set behaviour.date_change_line = "%a %e %b %Y"
 | 
			
		||||
 /set behaviour.plugin_autoload += "fancy-prompt.lua,thin-cursor.lua"
 | 
			
		||||
 /set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
 | 
			
		||||
 /set behaviour.backlog_helper_strip_formatting = off
 | 
			
		||||
 /set attributes.reset = "\x1b[0m"
 | 
			
		||||
 /set attributes.userhost = "\x1b[38;5;109m"
 | 
			
		||||
 /set attributes.join = "\x1b[38;5;108m"
 | 
			
		||||
 /set attributes.part = "\x1b[38;5;138m"
 | 
			
		||||
 /set attributes.external = "\x1b[38;5;248m"
 | 
			
		||||
 /set attributes.timestamp = "\x1b[48;5;255m\x1b[38;5;250m"
 | 
			
		||||
 | 
			
		||||
Configuration profiles
 | 
			
		||||
----------------------
 | 
			
		||||
Even though the applications don't directly support configuration profiles,
 | 
			
		||||
they conform to the XDG standard, and thus you can change the location they
 | 
			
		||||
load configuration from via XDG_CONFIG_HOME (normally '~/.config') and the
 | 
			
		||||
location where store their data via XDG_DATA_HOME (normally '~/.local/share').
 | 
			
		||||
 | 
			
		||||
It would be relatively easy to make the applications assume whatever name you
 | 
			
		||||
run them under (for example by using symbolic links), and load different
 | 
			
		||||
configurations accordingly, but I consider it rather messy and unnecessary.
 | 
			
		||||
 | 
			
		||||
Contributing and Support
 | 
			
		||||
------------------------
 | 
			
		||||
Use this project's GitHub to report any bugs, request features, or submit pull
 | 
			
		||||
@@ -165,3 +193,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.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								common.c
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								common.c
									
									
									
									
									
								
							@@ -34,12 +34,20 @@
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <netinet/tcp.h>
 | 
			
		||||
 | 
			
		||||
/// Shorthand to set an error and return failure from the function
 | 
			
		||||
#define FAIL(...)                                                              \
 | 
			
		||||
	BLOCK_START                                                                \
 | 
			
		||||
		error_set (e, __VA_ARGS__);                                            \
 | 
			
		||||
		return 0;                                                              \
 | 
			
		||||
	BLOCK_END
 | 
			
		||||
static void
 | 
			
		||||
init_openssl (void)
 | 
			
		||||
{
 | 
			
		||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
 | 
			
		||||
	SSL_library_init ();
 | 
			
		||||
	// XXX: this list is probably not complete
 | 
			
		||||
	atexit (EVP_cleanup);
 | 
			
		||||
	SSL_load_error_strings ();
 | 
			
		||||
	atexit (ERR_free_strings);
 | 
			
		||||
#else
 | 
			
		||||
	// Cleanup is done automatically via atexit()
 | 
			
		||||
	OPENSSL_init_ssl (0, NULL);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- To be moved to liberty --------------------------------------------------
 | 
			
		||||
 | 
			
		||||
@@ -108,79 +116,11 @@ xwrite (int fd, const char *data, size_t len, struct error **e)
 | 
			
		||||
		if (res >= 0)
 | 
			
		||||
			written += res;
 | 
			
		||||
		else if (errno != EINTR)
 | 
			
		||||
			FAIL ("%s", strerror (errno));
 | 
			
		||||
			return error_set (e, "%s", strerror (errno));
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Simple network I/O ------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// TODO: move to liberty and remove from dwmstatus.c as well
 | 
			
		||||
 | 
			
		||||
#define SOCKET_IO_OVERFLOW (8 << 20)    ///< How large a read buffer can be
 | 
			
		||||
 | 
			
		||||
enum socket_io_result
 | 
			
		||||
{
 | 
			
		||||
	SOCKET_IO_OK,                       ///< Completed successfully
 | 
			
		||||
	SOCKET_IO_EOF,                      ///< Connection shut down by peer
 | 
			
		||||
	SOCKET_IO_ERROR                     ///< Connection error
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static enum socket_io_result
 | 
			
		||||
socket_io_try_read (int socket_fd, struct str *rb, struct error **e)
 | 
			
		||||
{
 | 
			
		||||
	// We allow buffering of a fair amount of data, however within reason,
 | 
			
		||||
	// so that it's not so easy to flood us and cause an allocation failure
 | 
			
		||||
	ssize_t n_read;
 | 
			
		||||
	while (rb->len < SOCKET_IO_OVERFLOW)
 | 
			
		||||
	{
 | 
			
		||||
		str_ensure_space (rb, 4096);
 | 
			
		||||
		n_read = recv (socket_fd, rb->str + rb->len,
 | 
			
		||||
			rb->alloc - rb->len - 1 /* null byte */, 0);
 | 
			
		||||
 | 
			
		||||
		if (n_read > 0)
 | 
			
		||||
		{
 | 
			
		||||
			rb->str[rb->len += n_read] = '\0';
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (n_read == 0)
 | 
			
		||||
			return SOCKET_IO_EOF;
 | 
			
		||||
 | 
			
		||||
		if (errno == EAGAIN)
 | 
			
		||||
			return SOCKET_IO_OK;
 | 
			
		||||
		if (errno == EINTR)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		error_set (e, "%s", strerror (errno));
 | 
			
		||||
		return SOCKET_IO_ERROR;
 | 
			
		||||
	}
 | 
			
		||||
	return SOCKET_IO_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static enum socket_io_result
 | 
			
		||||
socket_io_try_write (int socket_fd, struct str *wb, struct error **e)
 | 
			
		||||
{
 | 
			
		||||
	ssize_t n_written;
 | 
			
		||||
	while (wb->len)
 | 
			
		||||
	{
 | 
			
		||||
		n_written = send (socket_fd, wb->str, wb->len, 0);
 | 
			
		||||
		if (n_written >= 0)
 | 
			
		||||
		{
 | 
			
		||||
			str_remove_slice (wb, 0, n_written);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (errno == EAGAIN)
 | 
			
		||||
			return SOCKET_IO_OK;
 | 
			
		||||
		if (errno == EINTR)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		error_set (e, "%s", strerror (errno));
 | 
			
		||||
		return SOCKET_IO_ERROR;
 | 
			
		||||
	}
 | 
			
		||||
	return SOCKET_IO_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Logging -----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
@@ -1023,6 +963,13 @@ ctcp_intra_decode (const char *chunk, size_t len, struct str *output)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// According to the original CTCP specification we should use
 | 
			
		||||
// ctcp_intra_decode() on all parts, however no one seems to use that
 | 
			
		||||
// and it breaks normal text with backslashes
 | 
			
		||||
#ifndef SUPPORT_CTCP_X_QUOTES
 | 
			
		||||
#define ctcp_intra_decode(s, len, output) str_append_data (output, s, len)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
ctcp_parse_tagged (const char *chunk, size_t len, struct ctcp_chunk *output)
 | 
			
		||||
{
 | 
			
		||||
@@ -1051,9 +998,6 @@ ctcp_parse (const char *message)
 | 
			
		||||
 | 
			
		||||
	struct ctcp_chunk *result = NULL, *result_tail = NULL;
 | 
			
		||||
 | 
			
		||||
	// According to the original CTCP specification we should use
 | 
			
		||||
	// ctcp_intra_decode() on all parts, however no one seems to
 | 
			
		||||
	// use that and it breaks normal text with backslashes
 | 
			
		||||
	size_t start = 0;
 | 
			
		||||
	bool in_ctcp = false;
 | 
			
		||||
	for (size_t i = 0; i < m.len; i++)
 | 
			
		||||
@@ -1077,7 +1021,7 @@ ctcp_parse (const char *message)
 | 
			
		||||
		if (my_is_ctcp)
 | 
			
		||||
			ctcp_parse_tagged (m.str + my_start, i - my_start, chunk);
 | 
			
		||||
		else
 | 
			
		||||
			str_append_data (&chunk->text, m.str + my_start, i - my_start);
 | 
			
		||||
			ctcp_intra_decode (m.str + my_start, i - my_start, &chunk->text);
 | 
			
		||||
		LIST_APPEND_WITH_TAIL (result, result_tail, chunk);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1091,7 +1035,7 @@ ctcp_parse (const char *message)
 | 
			
		||||
			chunk->is_partial = true;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			str_append_data (&chunk->text, m.str + start, m.len - start);
 | 
			
		||||
			ctcp_intra_decode (m.str + start, m.len - start, &chunk->text);
 | 
			
		||||
		LIST_APPEND_WITH_TAIL (result, result_tail, chunk);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								degesch.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								degesch.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 8.9 KiB  | 
							
								
								
									
										41
									
								
								kike.c
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								kike.c
									
									
									
									
									
								
							@@ -1403,7 +1403,7 @@ irc_handle_cap (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	if (msg->params.len > 1)
 | 
			
		||||
	{
 | 
			
		||||
		args.full_params = msg->params.vector[1];
 | 
			
		||||
		cstr_split_ignore_empty (args.full_params, ' ', &args.params);
 | 
			
		||||
		cstr_split (args.full_params, " ", true, &args.params);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct irc_cap_command *cmd =
 | 
			
		||||
@@ -2186,7 +2186,7 @@ irc_handle_list (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	{
 | 
			
		||||
		struct str_vector channels;
 | 
			
		||||
		str_vector_init (&channels);
 | 
			
		||||
		cstr_split_ignore_empty (msg->params.vector[0], ',', &channels);
 | 
			
		||||
		cstr_split (msg->params.vector[0], ",", true, &channels);
 | 
			
		||||
		for (size_t i = 0; i < channels.len; i++)
 | 
			
		||||
			if ((chan = str_map_find (&c->ctx->channels, channels.vector[i]))
 | 
			
		||||
			 && (!(chan->modes & IRC_CHAN_MODE_SECRET)
 | 
			
		||||
@@ -2317,7 +2317,7 @@ irc_handle_names (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	{
 | 
			
		||||
		struct str_vector channels;
 | 
			
		||||
		str_vector_init (&channels);
 | 
			
		||||
		cstr_split_ignore_empty (msg->params.vector[0], ',', &channels);
 | 
			
		||||
		cstr_split (msg->params.vector[0], ",", true, &channels);
 | 
			
		||||
		for (size_t i = 0; i < channels.len; i++)
 | 
			
		||||
			if ((chan = str_map_find (&c->ctx->channels, channels.vector[i]))
 | 
			
		||||
			 && (!(chan->modes & IRC_CHAN_MODE_SECRET)
 | 
			
		||||
@@ -2480,7 +2480,7 @@ irc_handle_whois (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	struct str_vector masks;
 | 
			
		||||
	str_vector_init (&masks);
 | 
			
		||||
	const char *masks_str = msg->params.vector[msg->params.len > 1];
 | 
			
		||||
	cstr_split_ignore_empty (masks_str, ',', &masks);
 | 
			
		||||
	cstr_split (masks_str, ",", true, &masks);
 | 
			
		||||
	for (size_t i = 0; i < masks.len; i++)
 | 
			
		||||
	{
 | 
			
		||||
		const char *mask = masks.vector[i];
 | 
			
		||||
@@ -2521,7 +2521,7 @@ irc_handle_whowas (const struct irc_message *msg, struct client *c)
 | 
			
		||||
 | 
			
		||||
	struct str_vector nicks;
 | 
			
		||||
	str_vector_init (&nicks);
 | 
			
		||||
	cstr_split_ignore_empty (msg->params.vector[0], ',', &nicks);
 | 
			
		||||
	cstr_split (msg->params.vector[0], ",", true, &nicks);
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < nicks.len; i++)
 | 
			
		||||
	{
 | 
			
		||||
@@ -2641,7 +2641,7 @@ irc_handle_part (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	const char *reason = msg->params.len > 1 ? msg->params.vector[1] : NULL;
 | 
			
		||||
	struct str_vector channels;
 | 
			
		||||
	str_vector_init (&channels);
 | 
			
		||||
	cstr_split_ignore_empty (msg->params.vector[0], ',', &channels);
 | 
			
		||||
	cstr_split (msg->params.vector[0], ",", true, &channels);
 | 
			
		||||
	for (size_t i = 0; i < channels.len; i++)
 | 
			
		||||
		irc_try_part (c, channels.vector[i], reason);
 | 
			
		||||
	str_vector_free (&channels);
 | 
			
		||||
@@ -2692,8 +2692,8 @@ irc_handle_kick (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	struct str_vector users;
 | 
			
		||||
	str_vector_init (&channels);
 | 
			
		||||
	str_vector_init (&users);
 | 
			
		||||
	cstr_split_ignore_empty (msg->params.vector[0], ',', &channels);
 | 
			
		||||
	cstr_split_ignore_empty (msg->params.vector[1], ',', &users);
 | 
			
		||||
	cstr_split (msg->params.vector[0], ",", true, &channels);
 | 
			
		||||
	cstr_split (msg->params.vector[1], ",", true, &users);
 | 
			
		||||
 | 
			
		||||
	if (channels.len == 1)
 | 
			
		||||
		for (size_t i = 0; i < users.len; i++)
 | 
			
		||||
@@ -2821,9 +2821,9 @@ irc_handle_join (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	struct str_vector keys;
 | 
			
		||||
	str_vector_init (&channels);
 | 
			
		||||
	str_vector_init (&keys);
 | 
			
		||||
	cstr_split_ignore_empty (msg->params.vector[0], ',', &channels);
 | 
			
		||||
	cstr_split (msg->params.vector[0], ",", true, &channels);
 | 
			
		||||
	if (msg->params.len > 1)
 | 
			
		||||
		cstr_split_ignore_empty (msg->params.vector[1], ',', &keys);
 | 
			
		||||
		cstr_split (msg->params.vector[1], ",", true, &keys);
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < channels.len; i++)
 | 
			
		||||
		irc_try_join (c, channels.vector[i],
 | 
			
		||||
@@ -2991,6 +2991,11 @@ irc_handle_kill (const struct irc_message *msg, struct client *c)
 | 
			
		||||
	struct client *target;
 | 
			
		||||
	if (!(target = str_map_find (&c->ctx->users, msg->params.vector[0])))
 | 
			
		||||
		RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHNICK, msg->params.vector[0]);
 | 
			
		||||
 | 
			
		||||
	client_send (target, ":%s!%s@%s KILL %s :%s",
 | 
			
		||||
		c->nickname, c->username, c->hostname,
 | 
			
		||||
		target->nickname, msg->params.vector[1]);
 | 
			
		||||
 | 
			
		||||
	char *reason = xstrdup_printf ("Killed by %s: %s",
 | 
			
		||||
		c->nickname, msg->params.vector[1]);
 | 
			
		||||
	client_close_link (target, reason);
 | 
			
		||||
@@ -3739,7 +3744,7 @@ irc_parse_config (struct server_context *ctx, struct error **e)
 | 
			
		||||
	str_vector_init (&fingerprints);
 | 
			
		||||
	const char *operators = str_map_find (&ctx->config, "operators");
 | 
			
		||||
	if (operators)
 | 
			
		||||
		cstr_split_ignore_empty (operators, ',', &fingerprints);
 | 
			
		||||
		cstr_split (operators, ",", true, &fingerprints);
 | 
			
		||||
	for (size_t i = 0; i < fingerprints.len; i++)
 | 
			
		||||
	{
 | 
			
		||||
		const char *key = fingerprints.vector[i];
 | 
			
		||||
@@ -3900,7 +3905,7 @@ irc_setup_listen_fds (struct server_context *ctx, struct error **e)
 | 
			
		||||
 | 
			
		||||
	struct str_vector ports;
 | 
			
		||||
	str_vector_init (&ports);
 | 
			
		||||
	cstr_split_ignore_empty (bind_port, ',', &ports);
 | 
			
		||||
	cstr_split (bind_port, ",", true, &ports);
 | 
			
		||||
	ctx->listen_fds = xcalloc (ports.len, sizeof *ctx->listen_fds);
 | 
			
		||||
	ctx->listen_events = xcalloc (ports.len, sizeof *ctx->listen_events);
 | 
			
		||||
	for (size_t i = 0; i < ports.len; i++)
 | 
			
		||||
@@ -4038,11 +4043,7 @@ main (int argc, char *argv[])
 | 
			
		||||
 | 
			
		||||
	print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
 | 
			
		||||
	setup_signal_handlers ();
 | 
			
		||||
 | 
			
		||||
	SSL_library_init ();
 | 
			
		||||
	atexit (EVP_cleanup);
 | 
			
		||||
	SSL_load_error_strings ();
 | 
			
		||||
	atexit (ERR_free_strings);
 | 
			
		||||
	init_openssl ();
 | 
			
		||||
 | 
			
		||||
	struct server_context ctx;
 | 
			
		||||
	server_context_init (&ctx);
 | 
			
		||||
@@ -4076,6 +4077,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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								liberty
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								liberty
									
									
									
									
									
								
							 Submodule liberty updated: 365aed456e...f53b717f3b
									
								
							
							
								
								
									
										49
									
								
								plugins/degesch/auto-rejoin.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								plugins/degesch/auto-rejoin.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
--
 | 
			
		||||
-- 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,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async, await = degesch.async, coroutine.yield
 | 
			
		||||
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
 | 
			
		||||
		async.go (function ()
 | 
			
		||||
			await (async.timer_ms (timeout * 1000))
 | 
			
		||||
			server:send ("JOIN " .. channel)
 | 
			
		||||
		end)
 | 
			
		||||
	end
 | 
			
		||||
	return line
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										74
									
								
								plugins/degesch/censor.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								plugins/degesch/censor.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
--
 | 
			
		||||
-- censor.lua: black out certain users' messages
 | 
			
		||||
--
 | 
			
		||||
-- 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 to_pattern = function (mask)
 | 
			
		||||
	if not mask:match ("!") then mask = mask .. "!*" end
 | 
			
		||||
	if not mask:match ("@") then mask = mask .. "@*" end
 | 
			
		||||
 | 
			
		||||
	-- That is, * acts like a wildcard, otherwise everything is escaped
 | 
			
		||||
	return "^" .. mask:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0")
 | 
			
		||||
		:gsub ("%*", ".*") .. "$"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local patterns = {}
 | 
			
		||||
local read_masks = function (v)
 | 
			
		||||
	patterns = {}
 | 
			
		||||
	local add = function (who, where)
 | 
			
		||||
		local channels = patterns[who] or {}
 | 
			
		||||
		table.insert (channels, where)
 | 
			
		||||
		patterns[who] = channels
 | 
			
		||||
	end
 | 
			
		||||
	for item in v:lower ():gmatch ("[^,]+") do
 | 
			
		||||
		local who, where = item:match ("^([^/]+)/*(.*)")
 | 
			
		||||
		if who then add (to_pattern (who), where == "" or where) end
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
degesch.setup_config {
 | 
			
		||||
	masks = {
 | 
			
		||||
		type = "string_array",
 | 
			
		||||
		default = "\"\"",
 | 
			
		||||
		comment = "user masks (optionally \"/#channel\") to censor",
 | 
			
		||||
		on_change = read_masks
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
local censor = function (line)
 | 
			
		||||
	-- Taking a shortcut to avoid lengthy message reassembly
 | 
			
		||||
	local start, text = line:match ("^(.- PRIVMSG .-:)(.*)$")
 | 
			
		||||
	local ctcp, rest = text:match ("^(\x01%g+ )(.*)")
 | 
			
		||||
	text = ctcp and ctcp .. "\x0301,01" .. rest or "\x0301,01" .. text
 | 
			
		||||
	return start .. text
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
degesch.hook_irc (function (hook, server, line)
 | 
			
		||||
	local msg = degesch.parse (line)
 | 
			
		||||
	if msg.command ~= "PRIVMSG" then return line end
 | 
			
		||||
 | 
			
		||||
	local channel = msg.params[1]:lower ()
 | 
			
		||||
	for who, where in pairs (patterns) do
 | 
			
		||||
		if msg.prefix:lower ():match (who) then
 | 
			
		||||
			for _, x in pairs (where) do
 | 
			
		||||
				if x == true or x == channel then
 | 
			
		||||
					return censor (line)
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	return line
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										101
									
								
								plugins/degesch/fancy-prompt.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								plugins/degesch/fancy-prompt.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
--
 | 
			
		||||
-- fancy-prompt.lua: the fancy multiline prompt you probably want
 | 
			
		||||
--
 | 
			
		||||
-- 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.
 | 
			
		||||
--
 | 
			
		||||
-- Beware that it is a hack and only goes about 90% of the way, which is why
 | 
			
		||||
-- this functionality is only available as a plugin in the first place
 | 
			
		||||
-- (well, and also for customizability).
 | 
			
		||||
--
 | 
			
		||||
-- The biggest problem is that the way we work with Readline is incompatible
 | 
			
		||||
-- with multiline prompts, and normal newlines just don't work.  This is being
 | 
			
		||||
-- circumvented by using an overflowing single-line prompt with a specially
 | 
			
		||||
-- crafted character in the rightmost column that prevents the bar's background
 | 
			
		||||
-- from spilling all over the last line.
 | 
			
		||||
--
 | 
			
		||||
-- There is also a problem with C-r search rendering not clearing out the
 | 
			
		||||
-- background but to really fix that mode, we'd have to fully reimplement it
 | 
			
		||||
-- since its alternative prompt very often gets overriden by accident anyway.
 | 
			
		||||
 | 
			
		||||
local prompt = degesch.hook_prompt (function (hook)
 | 
			
		||||
	local current = degesch.current_buffer
 | 
			
		||||
	local chan = current.channel
 | 
			
		||||
	local s = current.server
 | 
			
		||||
 | 
			
		||||
	local bg_color = "255"
 | 
			
		||||
	local current_n = 0
 | 
			
		||||
	local active = ""
 | 
			
		||||
	for i, buffer in ipairs (degesch.buffers) do
 | 
			
		||||
		if buffer == current then
 | 
			
		||||
			current_n = i
 | 
			
		||||
		elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
 | 
			
		||||
			if active ~= "" then active = active .. "," end
 | 
			
		||||
			if buffer.highlighted then
 | 
			
		||||
				active = active .. "!"
 | 
			
		||||
				bg_color = "224"
 | 
			
		||||
			end
 | 
			
		||||
			active = active .. i
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	if active ~= "" then active = "(" .. active .. ")" end
 | 
			
		||||
	local x = current_n .. ":" .. current.name
 | 
			
		||||
	if chan and chan.users_len ~= 0 then
 | 
			
		||||
		local params = ""
 | 
			
		||||
		for mode, param in pairs (chan.param_modes) do
 | 
			
		||||
			params = params .. " +" .. mode .. " " .. param
 | 
			
		||||
		end
 | 
			
		||||
		local modes = chan.no_param_modes .. params:sub (3)
 | 
			
		||||
		if modes ~= "" then x = x .. "(+" .. modes .. ")" end
 | 
			
		||||
		x = x .. "{" .. chan.users_len .. "}"
 | 
			
		||||
	end
 | 
			
		||||
	if current.hide_unimportant then x = x .. "<H>" end
 | 
			
		||||
 | 
			
		||||
	local lines, cols = degesch.get_screen_size ()
 | 
			
		||||
	x = x .. " " .. active .. string.rep (" ", cols)
 | 
			
		||||
 | 
			
		||||
	-- Cut off extra characters and apply formatting, including the hack.
 | 
			
		||||
	-- Note that this doesn't count with full-width or zero-width characters.
 | 
			
		||||
	local overflow = utf8.offset (x, cols - 1)
 | 
			
		||||
	if overflow then x = x:sub (1, overflow) end
 | 
			
		||||
	x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" ..
 | 
			
		||||
		x ..  "\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02 \x01\x1b[0;1m\x02"
 | 
			
		||||
 | 
			
		||||
	local user_prefix = function (chan, user)
 | 
			
		||||
		for i, chan_user in ipairs (chan.users) do
 | 
			
		||||
			if chan_user.user == user then return chan_user.prefixes end
 | 
			
		||||
		end
 | 
			
		||||
		return ""
 | 
			
		||||
	end
 | 
			
		||||
	if s then
 | 
			
		||||
		x = x .. "["
 | 
			
		||||
		local state = s.state
 | 
			
		||||
		if state == "disconnected" or state == "connecting" then
 | 
			
		||||
			x = x .. "(" .. state .. ")"
 | 
			
		||||
		elseif state ~= "registered" then
 | 
			
		||||
			x = x .. "(unregistered)"
 | 
			
		||||
		else
 | 
			
		||||
			local user, modes = s.user, s.user_mode
 | 
			
		||||
			if chan then x = x .. user_prefix (chan, user) end
 | 
			
		||||
			x = x .. user.nickname
 | 
			
		||||
			if modes ~= "" then x = x .. "(" .. modes .. ")" end
 | 
			
		||||
		end
 | 
			
		||||
		x = x .. "] "
 | 
			
		||||
	else
 | 
			
		||||
		-- There needs to be at least one character so that the cursor
 | 
			
		||||
		-- doesn't get damaged by our hack in that last column
 | 
			
		||||
		x = x .. "> "
 | 
			
		||||
	end
 | 
			
		||||
	return x
 | 
			
		||||
end)
 | 
			
		||||
@@ -118,24 +118,23 @@ end
 | 
			
		||||
local running
 | 
			
		||||
 | 
			
		||||
-- Initiate a connection to last.fm servers
 | 
			
		||||
async, await = degesch.async, coroutine.yield
 | 
			
		||||
local make_request = function (buffer, action)
 | 
			
		||||
	if not user or not api_key then
 | 
			
		||||
		report_error (buffer, "configuration is incomplete")
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if running then running.abort () end
 | 
			
		||||
 | 
			
		||||
	running = degesch.connect ("ws.audioscrobbler.com", 80, {
 | 
			
		||||
		on_success = function (c, host)
 | 
			
		||||
			on_connected (buffer, c, host, action)
 | 
			
		||||
			running = nil
 | 
			
		||||
		end,
 | 
			
		||||
		on_error   = function (e)
 | 
			
		||||
	if running then running:cancel () end
 | 
			
		||||
	running = async.go (function ()
 | 
			
		||||
		local c, host, e = await (async.dial ("ws.audioscrobbler.com", 80))
 | 
			
		||||
		if e then
 | 
			
		||||
			report_error (buffer, e)
 | 
			
		||||
			running = nil
 | 
			
		||||
		else
 | 
			
		||||
			on_connected (buffer, c, host, action)
 | 
			
		||||
		end
 | 
			
		||||
	})
 | 
			
		||||
		running = nil
 | 
			
		||||
	end)
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								plugins/degesch/thin-cursor.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								plugins/degesch/thin-cursor.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
--
 | 
			
		||||
-- thin-cursor.lua: set a thin cursor
 | 
			
		||||
--
 | 
			
		||||
-- 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.
 | 
			
		||||
--
 | 
			
		||||
-- If tmux doesn't work, add the following to its configuration:
 | 
			
		||||
--   set -as terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[2 q'
 | 
			
		||||
-- Change the "2" as per http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 | 
			
		||||
 | 
			
		||||
local out = io.output ()
 | 
			
		||||
out:write ("\x1b[6 q"):flush ()
 | 
			
		||||
 | 
			
		||||
-- By registering a global variable, we get notified about plugin unload
 | 
			
		||||
x = setmetatable ({}, { __gc = function ()
 | 
			
		||||
	out:write ("\x1b[2 q"):flush ()
 | 
			
		||||
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";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								zyklonb.c
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								zyklonb.c
									
									
									
									
									
								
							@@ -310,7 +310,7 @@ irc_get_boolean_from_config
 | 
			
		||||
	if (set_boolean_if_valid (value, str))
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
	FAIL ("invalid configuration value for `%s'", name);
 | 
			
		||||
	return error_set (e, "invalid configuration value for `%s'", name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
@@ -324,12 +324,14 @@ irc_initialize_ca_set (SSL_CTX *ssl_ctx, const char *file, const char *path,
 | 
			
		||||
		if (SSL_CTX_load_verify_locations (ssl_ctx, file, path))
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
		FAIL ("%s: %s", "failed to set locations for the CA certificate bundle",
 | 
			
		||||
		return error_set (e, "%s: %s",
 | 
			
		||||
			"failed to set locations for the CA certificate bundle",
 | 
			
		||||
			ERR_reason_error_string (ERR_get_error ()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!SSL_CTX_set_default_verify_paths (ssl_ctx))
 | 
			
		||||
		FAIL ("%s: %s", "couldn't load the default CA certificate bundle",
 | 
			
		||||
		return error_set (e, "%s: %s",
 | 
			
		||||
			"couldn't load the default CA certificate bundle",
 | 
			
		||||
			ERR_reason_error_string (ERR_get_error ()));
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
@@ -442,7 +444,7 @@ error_ssl_1:
 | 
			
		||||
	//   multiple errors on the OpenSSL stack.
 | 
			
		||||
	if (!error_info)
 | 
			
		||||
		error_info = ERR_error_string (ERR_get_error (), NULL);
 | 
			
		||||
	FAIL ("%s: %s", "could not initialize TLS", error_info);
 | 
			
		||||
	return error_set (e, "%s: %s", "could not initialize TLS", error_info);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool
 | 
			
		||||
@@ -455,7 +457,7 @@ irc_establish_connection (struct bot_context *ctx,
 | 
			
		||||
 | 
			
		||||
	int err = getaddrinfo (host, port, &gai_hints, &gai_result);
 | 
			
		||||
	if (err)
 | 
			
		||||
		FAIL ("%s: %s: %s", "connection failed",
 | 
			
		||||
		return error_set (e, "%s: %s: %s", "connection failed",
 | 
			
		||||
			"getaddrinfo", gai_strerror (err));
 | 
			
		||||
 | 
			
		||||
	int sockfd;
 | 
			
		||||
@@ -497,7 +499,7 @@ irc_establish_connection (struct bot_context *ctx,
 | 
			
		||||
	freeaddrinfo (gai_result);
 | 
			
		||||
 | 
			
		||||
	if (!gai_iter)
 | 
			
		||||
		FAIL ("connection failed");
 | 
			
		||||
		return error_set (e, "connection failed");
 | 
			
		||||
 | 
			
		||||
	ctx->irc_fd = sockfd;
 | 
			
		||||
	return true;
 | 
			
		||||
@@ -1026,11 +1028,17 @@ plugin_launch (struct bot_context *ctx, const char *name, struct error **e)
 | 
			
		||||
{
 | 
			
		||||
	const char *plugin_dir = str_map_find (&ctx->config, "plugin_dir");
 | 
			
		||||
	if (!plugin_dir)
 | 
			
		||||
		FAIL ("plugin directory not set");
 | 
			
		||||
	{
 | 
			
		||||
		error_set (e, "plugin directory not set");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int stdin_pipe[2];
 | 
			
		||||
	if (pipe (stdin_pipe) == -1)
 | 
			
		||||
		FAIL ("%s: %s", "pipe", strerror (errno));
 | 
			
		||||
	{
 | 
			
		||||
		error_set (e, "%s: %s", "pipe", strerror (errno));
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int stdout_pipe[2];
 | 
			
		||||
	if (pipe (stdout_pipe) == -1)
 | 
			
		||||
@@ -1117,9 +1125,9 @@ static bool
 | 
			
		||||
plugin_load (struct bot_context *ctx, const char *name, struct error **e)
 | 
			
		||||
{
 | 
			
		||||
	if (!is_valid_plugin_name (name))
 | 
			
		||||
		FAIL ("invalid plugin name");
 | 
			
		||||
		return error_set (e, "invalid plugin name");
 | 
			
		||||
	if (str_map_find (&ctx->plugins_by_name, name))
 | 
			
		||||
		FAIL ("the plugin has already been loaded");
 | 
			
		||||
		return error_set (e, "the plugin has already been loaded");
 | 
			
		||||
 | 
			
		||||
	struct plugin *plugin;
 | 
			
		||||
	if (!(plugin = plugin_launch (ctx, name, e)))
 | 
			
		||||
@@ -1149,7 +1157,7 @@ plugin_unload (struct bot_context *ctx, const char *name, struct error **e)
 | 
			
		||||
	struct plugin *plugin = str_map_find (&ctx->plugins_by_name, name);
 | 
			
		||||
 | 
			
		||||
	if (!plugin)
 | 
			
		||||
		FAIL ("no such plugin is loaded");
 | 
			
		||||
		return error_set (e, "no such plugin is loaded");
 | 
			
		||||
 | 
			
		||||
	plugin_zombify (plugin);
 | 
			
		||||
 | 
			
		||||
@@ -1168,7 +1176,7 @@ plugin_load_all_from_config (struct bot_context *ctx)
 | 
			
		||||
	struct str_vector plugins;
 | 
			
		||||
	str_vector_init (&plugins);
 | 
			
		||||
 | 
			
		||||
	cstr_split_ignore_empty (plugin_list, ',', &plugins);
 | 
			
		||||
	cstr_split (plugin_list, ",", true, &plugins);
 | 
			
		||||
	for (size_t i = 0; i < plugins.len; i++)
 | 
			
		||||
	{
 | 
			
		||||
		char *name = cstr_strip_in_place (plugins.vector[i], " ");
 | 
			
		||||
@@ -1208,7 +1216,7 @@ parse_bot_command (const char *s, const char *command, const char **following)
 | 
			
		||||
static void
 | 
			
		||||
split_bot_command_argument_list (const char *arguments, struct str_vector *out)
 | 
			
		||||
{
 | 
			
		||||
	cstr_split_ignore_empty (arguments, ',', out);
 | 
			
		||||
	cstr_split (arguments, ",", true, out);
 | 
			
		||||
	for (size_t i = 0; i < out->len; )
 | 
			
		||||
	{
 | 
			
		||||
		if (!*cstr_strip_in_place (out->vector[i], " \t"))
 | 
			
		||||
@@ -1778,7 +1786,7 @@ irc_connect (struct bot_context *ctx, struct error **e)
 | 
			
		||||
	// TODO: again, get rid of `struct error' in here.  The question is: how
 | 
			
		||||
	//   do we tell our caller that he should not try to reconnect?
 | 
			
		||||
	if (!irc_host)
 | 
			
		||||
		FAIL ("no hostname specified in configuration");
 | 
			
		||||
		return error_set (e, "no hostname specified in configuration");
 | 
			
		||||
 | 
			
		||||
	bool use_tls;
 | 
			
		||||
	if (!irc_get_boolean_from_config (ctx, "tls", &use_tls, e))
 | 
			
		||||
@@ -1823,7 +1831,10 @@ parse_config (struct bot_context *ctx, struct error **e)
 | 
			
		||||
	const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");
 | 
			
		||||
	hard_assert (delay_str != NULL);  // We have a default value for this
 | 
			
		||||
	if (!xstrtoul (&ctx->reconnect_delay, delay_str, 10))
 | 
			
		||||
		FAIL ("invalid configuration value for `%s'", "reconnect_delay");
 | 
			
		||||
	{
 | 
			
		||||
		return error_set (e,
 | 
			
		||||
			"invalid configuration value for `%s'", "reconnect_delay");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hard_assert (!ctx->admin_re);
 | 
			
		||||
	const char *admin = str_map_find (&ctx->config, "admin");
 | 
			
		||||
@@ -1999,12 +2010,7 @@ main (int argc, char *argv[])
 | 
			
		||||
 | 
			
		||||
	print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
 | 
			
		||||
	setup_signal_handlers ();
 | 
			
		||||
 | 
			
		||||
	SSL_library_init ();
 | 
			
		||||
	atexit (EVP_cleanup);
 | 
			
		||||
	SSL_load_error_strings ();
 | 
			
		||||
	// XXX: ERR_load_BIO_strings()?  Anything else?
 | 
			
		||||
	atexit (ERR_free_strings);
 | 
			
		||||
	init_openssl ();
 | 
			
		||||
 | 
			
		||||
	struct bot_context ctx;
 | 
			
		||||
	bot_context_init (&ctx);
 | 
			
		||||
@@ -2023,6 +2029,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