From ea6fb882f1b2d2d258bd3debac546685285ed54d Mon Sep 17 00:00:00 2001 From: lost+skunk Date: Sun, 19 Jan 2025 00:55:34 +0300 Subject: [PATCH] =?UTF-8?q?=D1=85=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/app.d | 71 ++++++++++++++++++++++++++++++++--------------- source/commands.d | 55 +++++++++++++++++++++--------------- source/util.d | 62 +++++++++++++++++++++++++++++++++-------- 3 files changed, 133 insertions(+), 55 deletions(-) diff --git a/source/app.d b/source/app.d index 60b3cd2..0540ee0 100644 --- a/source/app.d +++ b/source/app.d @@ -5,18 +5,26 @@ import asdf: deserialize, serializeToJson; import util; void main() { - init("", "hot-chilli.im"); + import std.process: environment; + alias env = environment.get; + homeserver = env("SKUNKYBOT_HOMESERVER"); + init(env("SKUNKYBOT_TOKEN")); sync; } HTTP http; -string homeserver; -void init(string token, string hs) { +string syncUrl; +string initialBatch; +void init(string token) { http = HTTP(); http.addRequestHeader("Authorization", "Bearer " ~ token); http.tcpNoDelay = true; - homeserver = "https://" ~ hs; - get(homeserver~"/_matrix/federation/v1/version"); + + // initial sync + writeln("starting initial sync.."); + syncUrl = homeserver ~ "/_matrix/client/v3/sync?set_presence=online"; + initialBatch = (get(syncUrl, http).deserialize!Sync).next_batch; syncUrl ~= "&since="; + writeln("done!"); } void send(T)(T content, string roomid, string type = "m.room.message") { @@ -29,36 +37,55 @@ void send(T)(T content, string roomid, string type = "m.room.message") { // post(homeserver~"/_matrix/client/v3/rooms/"~roomid~"/join", http); // } -void sync() { - import commands; +import commands; +static MSG helpgen() { + string buf; + static foreach (member; __traits(allMembers, commands)) { + static foreach (attr; __traits(getAttributes, __traits(getMember, commands, member))) { + static if (is(typeof(attr) == Command)) buf ~= member ~ ": " ~ attr.description ~ '\n'; + } + } + return MSG(buf); +} - string str = homeserver ~ "/_matrix/client/v3/sync?set_presence=online"; - Sync content = get(str, http).deserialize!Sync; str ~= "&since="; +void sync() { + auto content = Sync(); + content.next_batch = initialBatch; for (;;) { - string bthUrl = str ~ content.next_batch; - content = get(bthUrl, http).deserialize!Sync; + string bthUrl = syncUrl ~ content.next_batch; + try content = get(bthUrl, http).deserialize!Sync; + catch (Exception e) { writeln("ERR: ", e.msg); continue; } foreach (room, _; content.rooms.invite) { writeln("Joining to room: ", room); post(homeserver~"/_matrix/client/v3/rooms/"~room~"/join", null, http); - send(MSG("Привет, я СканкиБот"), room); + send(MSG("Йа криведко"), room); } foreach (room, roomContent; content.rooms.join) { foreach(event; roomContent.timeline.events) { if (event.type == "m.room.message") { try { - auto evt = deserialize!MSG(event.content.data); + auto evt = deserialize!MSG(event.content.raw); + if (!evt.body.length) break; + auto argz = parseMsg(evt.body); + + if (argz.command == "hlp") { + send(helpgen, room); + break; + } + foreach (member; __traits(allMembers, commands)) { - alias mmbr = __traits(getMember, commands, member); - if (evt.body[1..$] == member) { - static foreach (attr; __traits(getAttributes, mmbr)) { - static if (is(attr == Command)) { - static if (is(mmbr == function)) - send(mmbr(), room); - else static if (__traits(isStaticArray, mmbr)) - send(MSG(mmbr[0], mmbr[1]), room); + if (argz.command == member) { + alias command = __traits(getMember, commands, member); + foreach (attr; __traits(getAttributes, command)) { + static if (is(typeof(attr) == Command)) { + static if (is(typeof(command) == function)) + auto content = command(argz, &event); + else static if (__traits(isStaticArray, command)) + auto content = MSG(command[0], command[1]); else - send(MSG(mmbr), room); + auto content = MSG(command); + send(content, room); } } } diff --git a/source/commands.d b/source/commands.d index 9b0537d..e3bb4a5 100644 --- a/source/commands.d +++ b/source/commands.d @@ -1,29 +1,40 @@ module commands; import util; -enum Command; // UDA -// auto avatar(string[] arguments) @Command { -// string url = cast(string)get(homeserver~"/_matrix/client/v3/profile/"~event.sender~"/avatar_url"); -// if (url == "{}") -// return MSG("User has no avatar"); -// return MSG(event.sender, ``); -// } +// UDA +struct Command { + string description = "No description provided"; + string name; +} -@Command string[2] huy = [":orehussmile:", +@Command("Отображает аватар пользователя") +auto avatar(Arguments argz, EventWithoutRoomID* evt) { + import std.net.curl: get; + string url = cast(string)get(homeserver~"/_matrix/client/v3/profile/" ~ + ((argz.parsed.length == 0) ? evt.sender : argz.parsed[0]) ~ "/avatar_url"); + if (url == "{}") return MSG("User has no avatar"); + return MSG(evt.sender, ``); +} + +@Command("", "пинг") +auto ping(Arguments, EventWithoutRoomID* evt) { + import std.datetime: Clock, SysTime, unixTimeToStdTime; + auto delay = (Clock.currTime() - SysTime(unixTimeToStdTime(0))).total!"msecs" - evt.origin_server_ts; + return MSG("PONG [" ~ intToStr(delay) ~ " ms]"); +} + +@Command("хз мне лень делать описание") +auto echo(Arguments argz, EventWithoutRoomID* evt) { + return MSG((argz.raw.length > 6) ? argz.raw[6..$] : "Too small MSG"); +} + +@Command() +string[2] huy = [":orehussmile:", `:orehussmile:`]; -@Command string ver = "SkunkyBot Pre-Alpha 0.1 :: https://git.bloat.cat/skunky/skunkybot-d"; -static @Command string compver = "Compiler version: "~intToStr(__VERSION__); -// switch (evt.body) { -// case "!huy": -// send(MSG(":orehussmile:", -// ), room); -// break; -// case "!версия": send(), room); break; -// case "скунс": send(MSG("еблан"), room); break; -// case "!avatar": -// -// break; -// default: break; -// } \ No newline at end of file +@Command("Версия бота", "версия") +string ver = "SkunkyBot Pre-Alpha 0.1 :: https://git.bloat.cat/skunky/skunkybot-d"; + +@Command("test") +static string compver = "Compiler version: "~intToStr(__VERSION__); \ No newline at end of file diff --git a/source/util.d b/source/util.d index 497ce86..6c58c08 100644 --- a/source/util.d +++ b/source/util.d @@ -1,6 +1,8 @@ module util; import asdf; +string homeserver; + string intToStr(T)(T num) { char[] buf; for(short i; num > 0; ++i) { @@ -11,10 +13,10 @@ string intToStr(T)(T num) { struct JsonObject { import mir.conv: to; - string data; + string raw; SerdeException deserializeFromAsdf(Asdf data) { - this.data = data.to!string; + this.raw = data.to!string; return null; } @@ -24,6 +26,14 @@ struct JsonObject { } } +struct EventWithoutRoomID { + JsonObject content; + string event_id; + ulong origin_server_ts; + @serdeOptional string sender, state_key, type; + // Unsigned unsigned; +} + struct Sync { struct StrippedStateEvent { JsonObject content; @@ -42,14 +52,6 @@ struct Sync { } struct Joined { struct Timeline { - struct EventWithoutRoomID { - JsonObject content; - string event_id; - ulong origin_server_ts; - @serdeOptional string sender, state_key, type; - // Unsigned unsigned; - } - EventWithoutRoomID[] events; bool limited; string prev_batch; @@ -60,7 +62,7 @@ struct Sync { int notification_count; } - Timeline timeline; + @serdeOptional Timeline timeline; UNC unread_notifications; } @@ -78,4 +80,42 @@ struct MSG { @serdeOptional @serdeIgnoreDefault string formatted_body; string msgtype = "m.notice"; @serdeOptional string format = "org.matrix.custom.html"; +} + +struct Arguments { + string raw; + string command; + string[] parsed; + string[string] options; +} + +auto parseMsg(string cmd) { + Arguments argz = Arguments(cmd); + if (argz.raw[0] == '#') { + string buf; + for (ulong i = 1; i < argz.raw.length; ++i) + if (argz.raw[i] != ' ' && argz.raw[i] != '=') { + buf ~= argz.raw[i]; + if (i+1 == argz.raw.length || (argz.raw[i+1] == ' ' || argz.raw[i+1] == '=')) { + argz.parsed ~= buf; + buf = null; + } + } + argz.command = argz.parsed[0]; argz.parsed = argz.parsed[1..$]; + + for (ulong i; i < argz.parsed.length; ++i) + if (argz.parsed[i][0] == '-') { + string key = argz.parsed[i]; + auto val = (i+1 < argz.parsed.length && argz.parsed[i+1][0] != '-') + ? argz.parsed[i+1] : null; + if (key[1] == '-') argz.options[key[2..$]] = val; + else for (ulong x = 1; x < key.length; ++x) + argz.options[key[x..x+1]] = val; + + auto x = (val) ? i + 2 : i + 1; + argz.parsed = argz.parsed[0..i] ~ argz.parsed[x..$]; + i = i - (x - i); + } + } + return argz; } \ No newline at end of file