patchy IRC server?????
## How to compile and use
+Required dependencies:
+- libuv
+- lua5.4
+- sqlite3
+
+Optional dependencies:
+- libtls
+
Basic compilation:
```
autoreconf -iv # if you're compiling from source control
You can set these to whatever you want, though setting a contact email is
recommended.
-When the server starts, it reads the `init.txt` file. A fake oper connection
-performs the actions specified in the file. This allows initializing permanent
-channels. If you specify source (i.e. `:nick!user@host` before the command),
-the oper's nick will change accordingly. You can also fake the current unix time
-by prefixing the line with `$123` (`$0` sets the time back to real time). Faking
-nick and time is useful for JOIN and TOPIC commands. `chanlog.txt` records all
-channels changes in this format.
-(NOTE: this feature is deprecated. We only use it for bootstrapping lua:
-`LUA eval :dofile(getluafile"init.lua")`. Then in `lua/local.lua` we set up the
-channels. Also, be sure to PART any `--autojoin` channels if you want to give
-those a specific creation timestamp.)
+When the server starts, it initialized the lua subsystem by running the
+`init.lua` file. You can put your own code into `local.lua`. In particular, this
+allows automatically initializing permanent channels.
+```lua
+channel_new("#anime", 1652016295) -- name, creation time
+local chan = channel_map["#anime"]
+channel_mode(chan, "+b", "*!~*@*") -- varargs, like MODE command
+-- note that default modes are +ntP
+channel_topic(chan, "Anime!", "root", 1751023162) -- topic, who set, when set
+```
+The scripts can be reloaded by opers with `/quote LUA reload`, so you probably
+want to wrap some of your code like so:
+```lua
+if not local_processed then
+ local_processed = true
+ -- code here
+end
+```
+
+Channel state changes are logged in `chanlog.txt`, in a format similar to IRC
+protocol. Lines are prefixed with `$123`, which indicates the unix timestamp for
+when the action has occured.
## Command-line options
Add TCP listeners with `--listenPlain ip port` and `--listenTls ip port`. If
PKG_CHECK_MODULES(LIBUV, [libuv])
PKG_HAVE_DEFINE_WITH_MODULES(LIBTLS, [libtls])
-PKG_HAVE_DEFINE_WITH_MODULES(LUA, [lua5.4])
+PKG_CHECK_MODULES(LUA, [lua5.4])
PKG_CHECK_MODULES(SQLITE3, [sqlite3])
AC_OUTPUT
// Copyright (c) 2023-2026 Egor
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common.hpp"
-#ifdef HAVE_LUA
#include "admin.hpp"
#include "irc.hpp"
#include "ircfmt.hpp"
GL = 0;
}
}
+void admin_dostring(const char *command)
+{
+ lua_State *L = GL;
+ if (luaL_dostring(L, command) != LUA_OK)
+ fprintf(stderr, "%s\n", lua_tostring(L, -1));
+ lua_settop(L, 0);
+}
int admin_command(Client *client, char *source, char *command, int argc, char *args[])
{
if (!is_registered(client))
lua_pop(L, 1);
return rv;
}
-#endif
-// Copyright (c) 2023-2025 Egor
+// Copyright (c) 2023-2026 Egor
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef ADMIN_H_
#define ADMIN_H_
-#ifdef HAVE_LUA
#include "ircdata.hpp"
void admin_init(void);
void admin_fini(void);
+void admin_dostring(const char *command);
int admin_command(Client *client, char *source, char *command, int argc, char *args[]);
int admin_hook(const char *hook, Client *cl1, Client *cl2, Channel *chan, char *txt);
#endif
-#endif
return err_needmoreparams(client, command), sendbuf(client), 0;
client->lastactivity = get_time();
if (!strcmp(args[0], "cia")) {
-#ifdef HAVE_LUA
admin_hook(command[0] == 'P' ? "privmsgtoservice" : "noticetoservice", client, 0, 0, args[1]);
-#endif
return 0;
}
(command[0] == 'P' ? msg_privmsg : msg_notice)(client, args[0], args[1]);
return err_cannotsendtochan(client, chan->name), sendbuf(client), 0;
if (chan->dmode & DMODE_MODERATED && !chan->v.has(client) && !chan->o.has(client))
return err_cannotsendtochan(client, chan->name), sendbuf(client), 0;
-#ifdef HAVE_LUA
switch (admin_hook("sendtochannel", client, 0, chan, args[1])) {
case -1:
msg_quit(client, "Quit: eel bearing atlantean", "");
case 1:
return err_cannotsendtochan(client, chan->name), sendbuf(client), 0;
}
-#endif
if (!is_modmsg) {
sendbuf_list_sans_self(client, chan->members, 0);
} else if (memchr(args[0], '+', is_modmsg)) {
}
} else {
Client *cl = findClient(args[0]);
-#ifdef HAVE_LUA
if (!cl)
return err_nosuchnick(client, args[0]), sendbuf(client), 0;
switch (admin_hook("sendtoclient", client, cl, 0, args[1])) {
case 1:
return err_cantsendtouser(client, cl), sendbuf(client), 0;
}
-#endif
sendbuf(cl);
if (cl->away)
rpl_away(client, cl), sendbuf(client);
rpl_whoismodes(client, cl), sendbuf(client);
if (cl->umode & UMODE_BOT)
rpl_whoisbot(client, cl), sendbuf(client);
-#ifdef HAVE_LUA
admin_hook("whois", client, cl, 0, 0);
-#endif
rpl_endofwhois(client, cl), sendbuf(client);
} else {
err_nosuchnick(client, nick);
sendbuf_monitor(client->key, cookie, CAP_SETNAME|CAP_EXTENDEDMONITOR, 0);
if (client->cookie != cookie)
sendbuf(client);
-#ifdef HAVE_LUA
} MATCH_COMMAND("LUA") {
if (argc < 1)
return err_needmoreparams(client, command), sendbuf(client), 0;
return admin_command(client, source, args[0], argc-1, args+1);
-#endif
} MATCH_ELSE {
unknown:
err_unknowncommand(client, command), sendbuf(client);
sendLUSERS(client);
rpl_umodeis(client), sendbuf(client);
sendMOTD(client);
-#ifdef HAVE_LUA
admin_hook("newuser", client, 0, 0, 0);
-#endif
for (auto key : autojoin)
channel_join(client, key, NULL);
}
}
// Update nickname
- if(client->nick) {
-#ifdef HAVE_LUA
+ if (client->nick) {
admin_hook("nickchange", client, 0, 0, key);
-#endif
msg_nick(client, nickname);
sendbuf(client);
sendbuf_visible(client);
delete[] client->nick;
delete[] client->key;
} else {
-#ifdef HAVE_LUA
admin_hook("nickadd", client, 0, 0, key);
-#endif
}
client->nick = mystrdup(nickname);
auto dblk = db::init();
netpoll::init();
job_init();
-#ifdef HAVE_LUA
admin_init();
-#endif
struct tls *tls_ctx = NULL;
#ifdef HAVE_LIBTLS
netpoll::reg(listener.fd, UV_READABLE);
}
- FILE *f = fopen(getdatafile("init.txt"), "rbe");
- if (f) {
- Client *fake = new Client();
- fake->fd = -1;
- fake->umode |= UMODE_OPER | UMODE_SECURE;
- fake->hostname = mystrdup("localhost");
- if (!fake->hostname)
- abortf("strdup fail (hostname)\n");
- strcpy_lit(fake->rbuf, "NICK :root\r\n");
- irc_parse_line(fake, fake->rbuf, 0);
- strcpy_lit(fake->rbuf, "USER root 0 * :Patchouli Knowledge\r\n");
- irc_parse_line(fake, fake->rbuf, 0);
- while (fgets(fake->rbuf, sizeof(fake->rbuf), f)) {
- char *p = fake->rbuf;
- size_t len = strlen(fake->rbuf);
- if (!len || fake->rbuf[len-1] != '\n')
- break;
- unsigned long faketime;
- if (sscanf(p, "$%lu", &faketime)) {
- set_time((time_t)faketime);
- p = strchr(fake->rbuf, ' ');
- if (!p)
- continue;
- len -= ++p - fake->rbuf;
- memmove(fake->rbuf, p, len);
- }
- if (*p == ':') {
- char *sp = strchr(p, ' ');
- char *bang = strchr(p, '!');
- if (sp && bang && bang < sp) {
- size_t nlen = bang - p - 1;
- if (memcmp(&p[1], fake->nick, nlen) != 0) {
- char *nnick = new char[nlen+1];
- memcpy(nnick, &p[1], nlen);
- nnick[nlen] = '\0';
- delete[] fake->nick;
- fake->nick = nnick;
- }
- }
- }
- irc_parse_line(fake, p, 0);
- }
- strcpy_lit(fake->rbuf, "QUIT :bye\r\n");
- irc_parse_line(fake, fake->rbuf, 0);
- set_time(0);
- fclose(f);
- }
+ admin_dostring("dofile(getluafile'init.lua')");
+
fchanlog = fopen(getdatafile("chanlog.txt"), "abe");
if (fchanlog) {
fprintf(fchanlog, "$0 AAA :server starting %lu\r\n", (unsigned long)get_time());
}
}
-#ifdef HAVE_LUA
admin_fini();
-#endif
job_fini();
netpoll::fini();
#ifdef HAVE_LIBTLS
// Copyright (c) 2022 Sam Smith
-// Copyright (c) 2022-2025 Egor
+// Copyright (c) 2022-2026 Egor
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common.hpp"
#include "net.hpp"
channel_part(cl, chan, NULL, NULL);
/* Remove from client_map and get rid of the pending invites */
if (cl->key) {
-#ifdef HAVE_LUA
admin_hook("nickdel", cl, 0, 0, cl->key);
-#endif
client_map.remove(cl);
rpl_monoffline(0, cl->key), sendbuf_monitor(cl->key, 0, 0, 0);
for (auto chan : cl->invites)