Browse Source
The IRC library can login and keep its connection alive by replying to PING messages. It also exposes a callback that handles PRIVMSG commands and can inject messages from outside, e.g., based on a timer or a lightning event.ppa-0.6.1
Christian Decker
9 years ago
2 changed files with 245 additions and 0 deletions
@ -0,0 +1,178 @@ |
|||
#include "irc.h" |
|||
#include "daemon/dns.h" |
|||
#include "daemon/log.h" |
|||
|
|||
void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *) = NULL; |
|||
void (*irc_disconnect_cb)(struct ircstate *) = NULL; |
|||
|
|||
static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state); |
|||
static void irc_disconnected(struct io_conn *conn, struct ircstate *state); |
|||
|
|||
bool irc_send_msg(struct ircstate *state, struct privmsg *m) |
|||
{ |
|||
return irc_send(state, "PRIVMSG", "%s :%s", m->channel, m->msg); |
|||
} |
|||
|
|||
/* Send a raw irccommand to the IRC server. */ |
|||
bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) |
|||
{ |
|||
va_list ap; |
|||
struct irccommand *c = tal(state, struct irccommand); |
|||
|
|||
c->prefix = NULL; |
|||
|
|||
if (!state->connected) |
|||
return false; |
|||
|
|||
va_start(ap, fmt); |
|||
c->command = tal_strdup(c, command); |
|||
c->params = tal_vfmt(c, fmt, ap); |
|||
va_end(ap); |
|||
|
|||
list_add_tail(&state->writequeue, &c->list); |
|||
io_wake(state); |
|||
return true; |
|||
} |
|||
|
|||
/* Write buffered irccommands to the IRC connection. Commands can be
|
|||
buffered using irc_send. */ |
|||
static struct io_plan *irc_write_loop(struct io_conn *conn, struct ircstate *state) |
|||
{ |
|||
state->writebuffer = tal_free(state->writebuffer); |
|||
|
|||
struct irccommand *m = list_pop(&state->writequeue, struct irccommand, list); |
|||
if (m == NULL) |
|||
return io_out_wait(conn, state, irc_write_loop, state); |
|||
|
|||
bool hasprefix = m->prefix == NULL; |
|||
state->writebuffer = tal_fmt( |
|||
state, "%s%s%s %s\r\n", |
|||
hasprefix ? "" : m->prefix, |
|||
hasprefix ? "" : " ", |
|||
m->command, |
|||
m->params); |
|||
|
|||
tal_free(m); |
|||
|
|||
log_debug(state->log, "Sending: \"%s\"", state->writebuffer); |
|||
|
|||
return io_write( |
|||
conn, |
|||
state->writebuffer, strlen(state->writebuffer), |
|||
irc_write_loop, state |
|||
); |
|||
} |
|||
|
|||
/*
|
|||
* Called by the read loop to handle individual lines. This splits the |
|||
* line into a struct irccommand and passes it on to the specific |
|||
* handlers for the irccommand type. It silently drops any irccommand |
|||
* that has an unhandled type. |
|||
*/ |
|||
static void handle_irc_command(struct ircstate *state, const char *line) |
|||
{ |
|||
log_debug(state->log, "Received: \"%s\"", line); |
|||
|
|||
struct irccommand *m = talz(state, struct irccommand); |
|||
char** splits = tal_strsplit(m, line, " ", STR_NO_EMPTY); |
|||
int numsplits = tal_count(splits) - 1; |
|||
|
|||
if (numsplits > 2 && strstarts(splits[0], ":")) { |
|||
m->prefix = splits[0]; |
|||
splits++; |
|||
} |
|||
m->command = splits[0]; |
|||
m->params = tal_strjoin(m, splits + 1, " ", STR_NO_TRAIL); |
|||
|
|||
if (streq(m->command, "PING")) { |
|||
irc_send(state, "PONG", "%s", m->params); |
|||
|
|||
} else if (streq(m->command, "PRIVMSG")) { |
|||
struct privmsg *pm = talz(m, struct privmsg); |
|||
pm->sender = m->prefix; |
|||
pm->channel = splits[1]; |
|||
pm->msg = tal_strjoin(m, splits + 2, " ", STR_NO_TRAIL); |
|||
irc_privmsg_cb(state, pm); |
|||
} |
|||
tal_free(m); |
|||
} |
|||
|
|||
/*
|
|||
* Read incoming data and split it along the newline boundaries. Takes |
|||
* care of buffering incomplete lines and passes the lines to the |
|||
* handle_irc_command handler. |
|||
*/ |
|||
static struct io_plan *irc_read_loop(struct io_conn *conn, struct ircstate *state) |
|||
{ |
|||
|
|||
size_t len = state->readlen + state->buffered; |
|||
char *start = state->buffer, *end; |
|||
|
|||
while ((end = memchr(start, '\n', len)) != NULL) { |
|||
/* Strip "\r\n" from lines. */ |
|||
const char *line = tal_strndup(state, start, end - 1 - start); |
|||
handle_irc_command(state, line); |
|||
tal_free(line); |
|||
len -= (end + 1 - start); |
|||
start = end + 1; |
|||
} |
|||
|
|||
/* Move any partial data back down. */ |
|||
memmove(state->buffer, start, len); |
|||
state->buffered = len; |
|||
|
|||
return io_read_partial(conn, state->buffer + state->buffered, |
|||
sizeof(state->buffer) - state->buffered, |
|||
&state->readlen, irc_read_loop, state); |
|||
} |
|||
|
|||
static void irc_failed(struct lightningd_state *dstate, struct ircstate *state) |
|||
{ |
|||
irc_disconnected(state->conn, state); |
|||
state->connected = false; |
|||
} |
|||
|
|||
static void irc_disconnected(struct io_conn *conn, struct ircstate *state) |
|||
{ |
|||
log_debug(state->log, "Lost connection to IRC server"); |
|||
state->connected = false; |
|||
state->conn = NULL; |
|||
state->readlen = 0; |
|||
state->buffered = 0; |
|||
memset(state->buffer, 0, sizeof(state->buffer)); |
|||
|
|||
/* Clear any pending commands, they're no longer useful */ |
|||
while (!list_empty(&state->writequeue)) |
|||
tal_free(list_pop(&state->writequeue, struct irccommand, list)); |
|||
|
|||
/* Same goes for partially written commands */ |
|||
state->writebuffer = tal_free(state->writebuffer); |
|||
|
|||
if (irc_disconnect_cb != NULL) |
|||
irc_disconnect_cb(state); |
|||
} |
|||
|
|||
void irc_connect(struct ircstate *state) |
|||
{ |
|||
state->connected = false; |
|||
list_head_init(&state->writequeue); |
|||
|
|||
log_debug(state->log, "Connecting to IRC server %s", state->server); |
|||
dns_resolve_and_connect(state->dstate, state->server, "6667", irc_connected, irc_failed, state); |
|||
} |
|||
|
|||
static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state) |
|||
{ |
|||
io_set_finish(conn, irc_disconnected, state); |
|||
state->conn = conn; |
|||
state->connected = true; |
|||
irc_send(state, "USER", "%s 0 * :A lightning node", state->nick); |
|||
irc_send(state, "NICK", "%s", state->nick); |
|||
irc_send(state, "JOIN", "#lightning-nodes"); |
|||
|
|||
return io_duplex(conn, |
|||
io_read_partial(conn, |
|||
state->buffer, sizeof(state->buffer), |
|||
&state->readlen, irc_read_loop, state), |
|||
irc_write_loop(conn, state)); |
|||
} |
@ -0,0 +1,67 @@ |
|||
#ifndef LIGHTNING_IRC_H |
|||
#define LIGHTNING_IRC_H |
|||
|
|||
#include <stdio.h> |
|||
#include <sys/socket.h> |
|||
#include <netdb.h> |
|||
#include <ccan/io/io.h> |
|||
#include <ccan/short_types/short_types.h> |
|||
#include <ccan/str/str.h> |
|||
#include <ccan/tal/str/str.h> |
|||
#include <ccan/time/time.h> |
|||
#include <ccan/timer/timer.h> |
|||
|
|||
#include "daemon/lightningd.h" |
|||
|
|||
struct irccommand { |
|||
struct list_node list; |
|||
const char *prefix; |
|||
const char *command; |
|||
const char *params; |
|||
}; |
|||
|
|||
struct privmsg { |
|||
const char *channel; |
|||
const char *sender; |
|||
const char *msg; |
|||
}; |
|||
|
|||
struct ircstate { |
|||
/* Meta information */ |
|||
const char *nick; |
|||
const char *server; |
|||
|
|||
/* Connection and reading */ |
|||
struct io_conn *conn; |
|||
char buffer[512]; |
|||
size_t readlen; |
|||
size_t buffered; |
|||
|
|||
/* Write queue related */ |
|||
struct list_head writequeue; |
|||
char *writebuffer; |
|||
|
|||
/* Pointer to external state, making it available to callbacks */ |
|||
struct lightningd_state *dstate; |
|||
|
|||
struct log *log; |
|||
|
|||
/* Are we currently connected? */ |
|||
bool connected; |
|||
|
|||
/* Time to wait after getting disconnected before reconnecting. */ |
|||
struct timerel reconnect_timeout; |
|||
}; |
|||
|
|||
/* Callback to register for incoming messages */ |
|||
extern void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *); |
|||
extern void (*irc_disconnect_cb)(struct ircstate *); |
|||
|
|||
/* Send messages to IRC */ |
|||
bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) PRINTF_FMT(3,4); |
|||
bool irc_send_msg(struct ircstate *state, struct privmsg *m); |
|||
|
|||
/* Register IRC connection with io */ |
|||
void irc_connect(struct ircstate *state); |
|||
|
|||
#endif /* LIGHTNING_IRC_H */ |
Loading…
Reference in new issue