342 lines
10 KiB
Nix
342 lines
10 KiB
Nix
with import <stockholm/lib>;
|
||
{ config, pkgs, ... }:
|
||
|
||
let
|
||
#for shared state directory
|
||
stateDir = config.krebs.reaktor2.r.stateDir;
|
||
|
||
generators = pkgs.reaktor2-plugins.generators;
|
||
hooks = pkgs.reaktor2-plugins.hooks;
|
||
commands = pkgs.reaktor2-plugins.commands;
|
||
|
||
taskRcFile = builtins.toFile "taskrc" ''
|
||
confirmation=no
|
||
'';
|
||
|
||
task = name: {
|
||
pattern = "^${name}-([a-z]+)(?::?\\s*(.*))?";
|
||
activate = "match";
|
||
command = 1;
|
||
arguments = [2];
|
||
env.TASKDATA = "${stateDir}/${name}";
|
||
commands = {
|
||
add.filename = pkgs.writeDash "${name}-task-add" ''
|
||
${pkgs.taskwarrior}/bin/task rc:${taskRcFile} add "$1"
|
||
'';
|
||
list.filename = pkgs.writeDash "${name}-task-list" ''
|
||
${pkgs.taskwarrior}/bin/task rc:${taskRcFile} export \
|
||
| ${pkgs.jq}/bin/jq -r '
|
||
.[] | select(.id != 0) | "\(.id) \(.description)"
|
||
'
|
||
'';
|
||
delete.filename = pkgs.writeDash "${name}-task-delete" ''
|
||
${pkgs.taskwarrior}/bin/task rc:${taskRcFile} delete "$1"
|
||
'';
|
||
done.filename = pkgs.writeDash "${name}-task-done" ''
|
||
${pkgs.taskwarrior}/bin/task rc:${taskRcFile} done "$1"
|
||
'';
|
||
};
|
||
};
|
||
|
||
systemPlugin = {
|
||
plugin = "system";
|
||
config = {
|
||
workdir = stateDir;
|
||
hooks.JOIN = [
|
||
{
|
||
activate = "always";
|
||
command = {
|
||
filename =
|
||
<stockholm/krebs/5pkgs/simple/Reaktor/scripts/tell-on_join.sh>;
|
||
env = {
|
||
PATH = makeBinPath [
|
||
pkgs.coreutils # XXX env, touch
|
||
pkgs.jq # XXX sed
|
||
pkgs.util-linux # XXX flock
|
||
];
|
||
state_file = "${stateDir}/tell.json";
|
||
};
|
||
};
|
||
}
|
||
];
|
||
hooks.PRIVMSG = [
|
||
{
|
||
pattern = "^bier (ballern|bal(an(ce)?)?)$";
|
||
activate = "match";
|
||
command = {
|
||
env = {
|
||
state_file = "${stateDir}/ledger";
|
||
};
|
||
filename = pkgs.writeDash "bier-balance" ''
|
||
${pkgs.hledger}/bin/hledger -f $state_file bal -N -O csv \
|
||
| ${pkgs.coreutils}/bin/tail +2 \
|
||
| ${pkgs.miller}/bin/mlr --icsv --opprint cat \
|
||
| ${pkgs.gnused}/bin/sed 's/^\(.\)/\1/'
|
||
'';
|
||
};
|
||
}
|
||
{
|
||
pattern = "18@p";
|
||
activate = "match";
|
||
command = {
|
||
env = {
|
||
radius = toString 500; # metres around c-base to search
|
||
age_threshold = toString (24 * 60 * 60);
|
||
state_file = "${stateDir}/krebsfood";
|
||
};
|
||
filename = pkgs.writeDash "krebsfood" ''
|
||
set -efu
|
||
expected_max_results=1024 # the upper bound on the number of restaurants
|
||
|
||
file_age_seconds() {
|
||
expr "$(date +%s)" - "$(date +%s -r "$1")"
|
||
}
|
||
|
||
get_restaurants() {
|
||
if [ -f "$state_file" ]; then
|
||
[ "$(file_age_seconds "$state_file")" -lt "$age_threshold" ] && cat "$state_file"
|
||
else
|
||
echo '[out:json];node(id:260050809)->.cbase;
|
||
(
|
||
node(around.cbase:'$radius')[amenity=fast_food];
|
||
node(around.cbase:'$radius')[amenity=restaurant];
|
||
);out;' \
|
||
| ${pkgs.curl}/bin/curl -sSL -d @- -X POST http://overpass-api.de/api/interpreter \
|
||
| tee "$state_file"
|
||
fi
|
||
}
|
||
|
||
get_restaurants \
|
||
| ${pkgs.jq}/bin/jq -r --argjson random "$(shuf -i 0-$expected_max_results -n 1)" '
|
||
.elements
|
||
| length as $length
|
||
| .[$random % $length]
|
||
| "How about \(.tags.name) (https://www.openstreetmap.org/\(.type)/\(.id)), open \(.tags.opening_hours)?"
|
||
'
|
||
'';
|
||
};
|
||
}
|
||
{
|
||
pattern = ''^([\H-]*?):?\s+([+-][1-9][0-9]*)\s+(\S+)$'';
|
||
activate = "match";
|
||
arguments = [1 2 3];
|
||
command = {
|
||
env = {
|
||
# TODO; get state as argument
|
||
state_file = "${stateDir}/ledger";
|
||
};
|
||
filename = pkgs.writeDash "ledger-add" ''
|
||
set -x
|
||
tonick=$1
|
||
amt=$2
|
||
unit=$3
|
||
printf '%s\n %s %d %s\n %s %d %s\n' "$(date -Id)" "$tonick" "$amt" "$unit" "$_from" "$(expr 0 - "''${amt#+}")" "$unit" >> $state_file
|
||
${pkgs.hledger}/bin/hledger -f $state_file bal -N -O csv \
|
||
| ${pkgs.coreutils}/bin/tail +2 \
|
||
| ${pkgs.miller}/bin/mlr --icsv --opprint cat \
|
||
| ${pkgs.gnugrep}/bin/grep "$_from"
|
||
'';
|
||
};
|
||
}
|
||
hooks.sed
|
||
(generators.command_hook {
|
||
inherit (commands) dance random-emoji nixos-version;
|
||
tell = {
|
||
filename =
|
||
<stockholm/krebs/5pkgs/simple/Reaktor/scripts/tell-on_privmsg.sh>;
|
||
env = {
|
||
PATH = makeBinPath [
|
||
pkgs.coreutils # XXX date, env
|
||
pkgs.jq # XXX sed
|
||
pkgs.util-linux # XXX flock
|
||
];
|
||
state_file = "${stateDir}/tell.txt";
|
||
};
|
||
};
|
||
})
|
||
(task "agenda")
|
||
];
|
||
};
|
||
};
|
||
|
||
in {
|
||
|
||
users.users.reaktor2 = {
|
||
uid = genid_uint31 "reaktor2";
|
||
home = stateDir;
|
||
isSystemUser = true;
|
||
extraGroups = [ "reaktor2" ];
|
||
};
|
||
|
||
users.groups.reaktor2 = {};
|
||
|
||
systemd.services.htgen-agenda.serviceConfig.StateDirectory = "reaktor2";
|
||
krebs.htgen.agenda = {
|
||
port = 8009;
|
||
user = {
|
||
name = "reaktor2";
|
||
home = stateDir;
|
||
};
|
||
script = ''. ${pkgs.writeDash "agenda" ''
|
||
echo "$Method $Request_URI" >&2
|
||
case "$Method" in
|
||
"GET")
|
||
printf 'HTTP/1.1 200 OK\r\n'
|
||
printf 'Connection: close\r\n'
|
||
printf '\r\n'
|
||
TASKDATA=/var/lib/reaktor2/agenda ${pkgs.taskwarrior}/bin/task rc:${taskRcFile} export
|
||
exit
|
||
;;
|
||
esac
|
||
''}'';
|
||
};
|
||
|
||
services.nginx = {
|
||
virtualHosts."agenda.r" = {
|
||
serverAliases = [ "kri.r" ];
|
||
locations."= /index.html".extraConfig = ''
|
||
alias ${pkgs.writeText "agenda.html" ''
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Agenda</title>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<style>
|
||
html {
|
||
font-family: monospace;
|
||
}
|
||
|
||
dt {
|
||
float: left;
|
||
clear: left;
|
||
width: 30px;
|
||
text-align: right;
|
||
font-weight: bold;
|
||
}
|
||
|
||
dd {
|
||
margin: 0 0 0 40px;
|
||
padding: 0 0 0.5em 0;
|
||
}
|
||
|
||
.date {
|
||
color: grey;
|
||
font-style: italic;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<dl id="agenda"></dl>
|
||
<script>
|
||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||
const params = Object.fromEntries(urlSearchParams.entries());
|
||
|
||
if (params.hasOwnProperty("style")) {
|
||
const cssUrls = params["style"].split(" ").filter((x) => x.length > 0);
|
||
for (const cssUrl of cssUrls)
|
||
fetch(cssUrl)
|
||
.then((response) =>
|
||
response.text().then((css) => {
|
||
const title = document.getElementsByTagName("head")[0];
|
||
const style = document.createElement("style");
|
||
style.appendChild(document.createTextNode(css));
|
||
title.appendChild(style);
|
||
})
|
||
)
|
||
.catch(console.log);
|
||
}
|
||
|
||
fetch("/agenda.json")
|
||
.then((response) => {
|
||
response.json().then((agenda) => {
|
||
const dl = document.getElementById("agenda");
|
||
for (const agendaItem of agenda) {
|
||
if (agendaItem.status !== "pending") continue;
|
||
// task warrior date format to ISO
|
||
const entryDate = agendaItem.entry.replace(
|
||
/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/,
|
||
"$1-$2-$3T$4:$5:$6Z"
|
||
);
|
||
|
||
const dt = document.createElement("dt");
|
||
dt.className = "id";
|
||
dt.appendChild(document.createTextNode(agendaItem.id.toString()));
|
||
dl.appendChild(dt);
|
||
|
||
const spanDate = document.createElement("span");
|
||
spanDate.className = "date";
|
||
spanDate.title = new Date(entryDate).toString();
|
||
spanDate.appendChild(document.createTextNode(entryDate));
|
||
|
||
const link = document.createElement("a");
|
||
link.href = "http://wiki.r/agenda/" + encodeURIComponent(agendaItem.description.replaceAll("/", "\u29F8")); // we use big solidus instead of slash because gollum will create directories
|
||
link.appendChild(document.createTextNode(agendaItem.description));
|
||
|
||
const dd = document.createElement("dd");
|
||
dd.className = "description";
|
||
dd.appendChild(link);
|
||
dd.appendChild(document.createTextNode(" "));
|
||
dd.appendChild(spanDate);
|
||
|
||
dl.appendChild(dd);
|
||
}
|
||
});
|
||
})
|
||
.then((data) => console.log(data));
|
||
</script>
|
||
</body>
|
||
</html>
|
||
''};
|
||
'';
|
||
locations."/agenda.json".extraConfig = ''
|
||
proxy_set_header Host $host;
|
||
proxy_pass http://localhost:8009;
|
||
'';
|
||
extraConfig = ''
|
||
add_header 'Access-Control-Allow-Origin' '*';
|
||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||
'';
|
||
};
|
||
};
|
||
|
||
systemd.services.reaktor2-r.serviceConfig.DynamicUser = mkForce false;
|
||
systemd.services.reaktor2-hackint.serviceConfig.DynamicUser = mkForce false;
|
||
krebs.reaktor2 = {
|
||
hackint = {
|
||
hostname = "irc.hackint.org";
|
||
nick = "reaktor2|krebs";
|
||
plugins = [
|
||
{
|
||
plugin = "register";
|
||
config = {
|
||
channels = [
|
||
"#krebs"
|
||
];
|
||
};
|
||
}
|
||
systemPlugin
|
||
];
|
||
username = "reaktor2";
|
||
port = "6697";
|
||
};
|
||
r = {
|
||
nick = "reaktor2|krebs";
|
||
sendDelaySec = null;
|
||
plugins = [
|
||
{
|
||
plugin = "register";
|
||
config = {
|
||
channels = [
|
||
"#noise"
|
||
"#xxx"
|
||
];
|
||
};
|
||
}
|
||
systemPlugin
|
||
];
|
||
username = "reaktor2";
|
||
};
|
||
};
|
||
}
|