lambot/irc.lamb

254 lines
5.7 KiB
Plaintext

-- config constants
HOST = "127.0.0.1".
PORT = 6667.
NICK = "lambot".
-- nicks of administrators
ADMINS = ["darkf"].
-- channels to join
CHANS = ["#lobby"].
-- First we'll define some helper functions
map(f, []) -> [].
map(f, x::xs) -> f(x) :: map(f, xs).
fst((x, _)) -> x.
-- maybe stuff
is_just(("just", _)) -> true.
is_just(_) -> false.
is_nothing(("nothing")) -> true.
is_nothing(_) -> false.
unwrap_maybe(("just", x)) -> x.
-- list membership test
is_member([], _) -> false.
is_member(x::xs, member) ->
if x == member then true
else is_member(xs, member).
-- association list
-- insert a pair into a map
map_insert(assoc, key, value) -> (key, value) :: assoc.
-- lookup by key
map_lookup([], _) -> ("nothing").
map_lookup((k,v)::xs, key) ->
if k == key then ("just", v)
else map_lookup(xs, key).
-- remove a key from a map
map_remove([], key) -> [].
map_remove((k,v)::xs, key) ->
if k == key then xs
else (k,v) :: map_remove(xs, key).
-- irc stuff
-- Splits a string by spaces, or until it encounters a :, whereby the following is considered one element.
splitirc'("", stracc, acc) -> acc + [stracc].
splitirc'(" "::xs, stracc, acc) -> do
splitirc'(xs, "", acc + [stracc])
end.
-- prefix message
splitirc'(":"::xs, _, acc) -> do
acc + [xs]
end.
splitirc'(x::xs, stracc, acc) -> splitirc'(xs, stracc + x, acc).
-- helper function
splitirc(str) -> splitirc'(str, "", []).
-- (result, rest)
takeUntilSpace'("", acc) -> (acc, ""). -- no spaces
takeUntilSpace'(" "::xs, acc) -> (acc, xs).
takeUntilSpace'(x::xs, acc) -> takeUntilSpace'(xs, acc + x).
takeUntilSpace(str) -> takeUntilSpace'(str, "").
-- takes x!y and returns x
ircnick'("!"::xs, acc) -> acc.
ircnick'(x::xs, acc) -> ircnick'(xs, acc + x).
ircnick(str) -> ircnick'(str, "").
isAdmin(nick) -> do
is_member(ADMINS, nick)
end.
-- state getters
getFactoids(state) -> do
(factoids) = state;
factoids
end.
-- state setters
setFactoids(state, factoids) -> do
(factoids)
end.
-- factoid serialization
-- basic "key value" (space-separated) format
saveFactoids(factoids) -> do
file = fopen("factoids.txt", "w");
map(\(k,v) -> fputstr(file, k + " " + v + "\n"), factoids);
fclose(file)
end.
loadFactoids() -> do
file = fopen("factoids.txt", "r");
fact = loop(\pairs ->
if feof(file) != true then do
line = fgetline(file);
pair = takeUntilSpace(line);
pair :: pairs
end else false, []);
fclose(file);
fact
end.
-- event handling
say(chan, msg) -> fputstr(sock, "PRIVMSG " + chan + " :" + msg + "\r\n").
handleMessage(s, nick, chan, "$factoids") -> do
factoids = map(fst, getFactoids(s));
say(chan, nick + ": " + repr(factoids));
s
end.
handleMessage(s, nick, chan, "$defact "::line) -> do
(k,v) = takeUntilSpace(line);
factoids = getFactoids(s);
say(chan, nick + ": defined " + k);
setFactoids(s, map_insert(factoids, k, v))
end.
handleMessage(s, nick, chan, "$savefacts") -> do
if isAdmin(nick) then do
saveFactoids(getFactoids(s));
say(chan, "factoids saved.")
end
else say(chan, nick + ": you are not an admin");
s
end.
handleMessage(s, nick, chan, "$join "::j) -> do
if isAdmin(nick) then do
fputstr(sock, "JOIN " + j + "\r\n");
s
end else s
end.
handleMessage(s, nick, chan, "$ping") -> do say(chan, nick + ": pong"); s end.
handleMessage(s, nick, chan, "$quit") -> do
if isAdmin(nick) then do
say(chan, "bye!");
fputstr(sock, "QUIT\r\n");
fclose(sock)
end
else say(chan, nick + ": you are not an admin");
s
end.
-- unknown command, search factoids
handleMessage(s, nick, chan, "$"::line) -> do
(fact, rest) = takeUntilSpace(line);
if rest != "" then s -- it had spaces after it, might not want a factoid
else do
factoids = getFactoids(s);
factoid = map_lookup(factoids, fact);
if is_just(factoid) then do
say(chan, unwrap_maybe(factoid));
s
end
else s
end
end.
handleMessage(s, nick, chan, msg) -> s.
-- handleCommand(source, cmd, args)
handleCommand(s, _, "PING", [ping]) -> do
putstrln("ping: " + ping);
fputstr(sock, "PONG :" + ping);
s
end.
handleCommand(s, user, "JOIN", [chan]) -> do
putstrln("nick " + ircnick(user) + " joins " + chan);
s
end.
handleCommand(s, user, "PRIVMSG", [chan, msg]) -> do
nick = ircnick(user);
putstrln(chan + " " + "<" + nick + "> " + msg);
handleMessage(s, nick, chan, msg)
end.
-- nick list
handleCommand(s, _, "353", _::"="::chan::[nicks]) -> do
-- nicks is space-separated
putstrln("nicks in " + chan + ": " + nicks);
s
end.
handleCommand(s, _, "372", [_,msg]) -> do putstrln("MOTD: " + msg); s end.
handleCommand(s, _, "422", [_,msg]) -> do putstrln(msg); s end. -- MOTD is missing
handleCommand(s, _, "251", _) -> s. -- There are X users and Y services on Z server(s)
handleCommand(s, _, "331", _) -> s. -- No topic is set
handleCommand(s, _, "366", _) -> s. -- End of NAMES list
handleCommand(s, src, cmd, args) -> do
putstrln("Unhandled command: " + cmd + ", with args: " + repr(args) + " from " + src);
s
end.
handleLine(s, ":" :: line) -> do
-- sourced message
(source, rest) = takeUntilSpace(line);
command::args = splitirc(rest);
handleCommand(s, source, command, args)
end.
handleLine(s, line) -> do
-- non-sourced message
command::args = splitirc(line);
handleCommand(s, "", command, args)
end.
-- now for our actual program!
-- build our socket and connect to the server
sock = sockopen(HOST, PORT).
-- send introduction
fputstr(sock, "PASS " + NICK + "\r\n").
fputstr(sock, "NICK " + NICK + "\r\n").
fputstr(sock, "USER " + NICK + " 0 * :Lamb Da. Bot\r\n").
map(\chan -> fputstr(sock, "JOIN " + chan + "\r\n"), CHANS).
-- loop receiving lines
mainloop(state) ->
if feof(sock) != true then
do
line = fgetline(sock);
handleLine(state, line)
end
else
false.
putstrln("initializing").
initialState = (loadFactoids()).
putstrln("beginning mainloop").
loop(mainloop, initialState).
fclose(sock).
putstrln("done").