72 Commits

Author SHA1 Message Date
3c048f0d56 Bump version 2016-12-30 08:15:44 +01:00
8e668ff31a Various fixes related to channel modes
Bugs unnoticed for so long.
2016-12-30 08:08:34 +01:00
eb70bf3fbc Cleanup 2016-12-28 12:44:27 +01:00
d86a68f510 Add support for OpenSSL 1.1.0 2016-12-28 12:40:47 +01:00
d6be22291d degesch: /query w/o arguments just opens the query 2016-12-06 13:51:16 +01:00
a813babb89 fancy-prompt.lua: fix parametrized modes 2016-12-02 12:28:55 +01:00
b666ce6926 fancy-prompt.lua: change background on highlight 2016-12-02 12:28:55 +01:00
e2bb051bd3 degesch: replace degesch.connect with async.dial
Halfway there, looks much saner.
2016-11-04 22:02:26 +01:00
52d1ded7df degesch: move the Lua async code within the file 2016-11-04 20:44:23 +01:00
cb9f187f80 degesch: get rid of Lua timer hooks
Since they were the exception and have been replaced with the async API.
2016-11-04 20:21:46 +01:00
0247c4667a degesch: Lua coroutine safety 2016-11-04 20:12:28 +01:00
572f4e2ea3 degesch: implement Lua coroutine async basics 2016-11-04 20:11:59 +01:00
50599e09bd Update README, add a screenshot for degesch 2016-10-30 18:52:20 +01:00
b24bb0aded degesch: fix join/part hiding in the backlog 2016-10-30 16:24:23 +01:00
7c6cf42075 thin-cursor.lua: update comments 2016-10-30 01:50:21 +02:00
414a525c4d degesch: add a thin-cursor plugin 2016-10-30 00:00:48 +02:00
6cee7159f2 degesch: clean up
Caught by Coverity, however it is quite harmless.
2016-10-29 21:08:15 +02:00
568f9b7123 degesch: tiny fixes for the prompt hook
It should return valid UTF-8.

Also remember to refresh the prompt upon hook removal.
2016-10-29 20:03:31 +02:00
0d499dd125 degesch: avoid senseless indirection in hooks
It's always been one function call only this far.
2016-10-29 19:51:54 +02:00
37e49b54cf degesch: rename things around terminal attributes 2016-10-29 18:07:28 +02:00
742d590b8d degesch: simplify "attribute_printer"
Now that the line wrapper took over some of the state.
2016-10-29 17:53:06 +02:00
b6528c73e3 degesch: microoptimization 2016-10-28 18:16:21 +02:00
1e79aaec26 degesch: refresh the prompt when a hook is set 2016-10-28 13:58:37 +02:00
0995da3900 degesch: don't consider all mode changes important 2016-10-28 13:32:29 +02:00
c8a826f016 degesch: optimize Lua weak refs 2016-10-28 13:09:50 +02:00
95c7ababc3 degesch: add a "fancy-prompt" plugin
So that the client looks at least a tiny bit decent if needed.
2016-10-28 12:53:18 +02:00
a0d733fdb9 Update NEWS, README 2016-10-28 12:47:11 +02:00
557a39c6c8 degesch: export server state as a string to Lua 2016-10-28 12:47:11 +02:00
745e758394 degesch: add Lua API for screen size retrieval 2016-10-28 04:12:06 +02:00
b60bdf119a degesch: add a prompt hook 2016-10-28 04:12:06 +02:00
278e2b236b degesch: add introspection for refs within str_maps
This required some fixes to the design.
2016-10-28 04:12:05 +02:00
2f758bbdb9 degesch: allow lists of refs in introspection 2016-10-28 04:12:05 +02:00
911276b263 degesch: add introspection for "app_context" 2016-10-28 04:12:05 +02:00
cb5ad675a6 degesch: add introspection for "str" and "str_map" 2016-10-28 04:12:05 +02:00
9408dfc67c degesch: create Lua refs through introspection 2016-10-28 04:12:05 +02:00
fed8b06aff degesch: begin work on direct introspection 2016-10-28 04:12:05 +02:00
7e64fd9886 degesch: cleanup 2016-10-28 04:12:05 +02:00
6928184a3d degesch: defer prompt refreshing
Now that we do it each time we receive a message from the server.
2016-10-23 17:34:52 +02:00
f7155f3919 degesch: allow hiding join/part messages 2016-10-23 17:14:24 +02:00
f032466307 degesch: comments, no functional change 2016-10-23 17:14:24 +02:00
c0f4b554ef degesch: show channel user count in the status 2016-10-23 17:14:24 +02:00
639da7a9a7 degesch: accept Word shortcuts for formatting
Because why not.
2016-10-23 13:40:04 +02:00
230b04014f Bump liberty, add consts to some arguments 2016-10-23 13:38:46 +02:00
4848354bb9 Get rid of the remaining FAILs 2016-10-11 12:05:17 +02:00
8028c7fa47 Bump liberty 2016-10-11 10:52:49 +02:00
43de836b91 degesch: exit with error when arguments are given 2016-09-29 13:40:15 +02:00
16d10f574b degesch: simplify highlight detection 2016-09-25 14:11:30 +02:00
4cefa5ab1b degesch: fix highlight detection in colored text 2016-09-23 23:46:26 +02:00
92a4d4b5a7 Better support for the KILL command 2016-09-23 22:50:30 +02:00
26f94d2459 degesch: add a "censor" plugin
So far this approach screws up highlights, which is actually a bug.
2016-09-23 18:59:37 +02:00
0be43691d0 Update README 2016-07-23 20:29:25 +02:00
483ab39e3c degesch: die on configuration parse errors
Seems more sensible.
2016-07-23 20:00:40 +02:00
beaf1a1f82 degesch: fix Ctrl-J in Readline 2016-07-23 19:13:55 +02:00
5613c326c9 degesch: fix CTCP handling
In `/me :\` practically no client bothers to escape the backslash but we
used to interpret it as the start of an escape sequence anyway.

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

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

View File

@@ -13,7 +13,7 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
# Version
set (project_version "0.9.3")
set (project_version "0.9.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
View File

@@ -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

View File

@@ -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
View File

@@ -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);
}

2012
degesch.c

File diff suppressed because it is too large Load Diff

BIN
degesch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

41
kike.c
View File

@@ -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);

Submodule liberty updated: 365aed456e...f53b717f3b

View 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)

View 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)

View 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)

View File

@@ -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
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

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

View 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
View File

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

160
plugins/zyklonb/seen Executable file
View File

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

View File

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

View File

@@ -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))