commit 8277809e4da8a38d1688c2f7f8917797a1bf8ed2 Author: lost+skunk Date: Wed Jan 8 17:24:48 2025 +0300 anfjtjsdrhgu diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..932cb35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.dub +docs.json +__dummy.html +docs/ +/skunkybot-d +skunkybot-d.so +skunkybot-d.dylib +skunkybot-d.dll +skunkybot-d.a +skunkybot-d.lib +skunkybot-d-test-* +*.exe +*.pdb +*.o +*.obj +*.lst diff --git a/README.md b/README.md new file mode 100644 index 0000000..756a99f --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# TODO +- [ ] get rid of indian code +- [ ] implement shared-library modules +- [ ] implement config +- [ ] implement forgejo/gitea webhooks +# INFO +говнобот \ No newline at end of file diff --git a/dub.sdl b/dub.sdl new file mode 100644 index 0000000..b6dc7a0 --- /dev/null +++ b/dub.sdl @@ -0,0 +1,6 @@ +name "skunkybot-d" +description "Matrix bot" +authors "skunk" +copyright "Copyright © 2025, skunk" +license "AGPL-3.0-only" +dependency "asdf" version="~>0.7.17" diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..9d35756 --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,9 @@ +{ + "fileVersion": 1, + "versions": { + "asdf": "0.7.17", + "mir-algorithm": "3.22.3", + "mir-core": "1.7.1", + "silly": "1.1.1" + } +} diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..46d52a6 --- /dev/null +++ b/source/app.d @@ -0,0 +1,86 @@ +import std.stdio: writeln; +import std.net.curl; +import asdf: deserialize, serializeToJson; + +import util; + +void main() { + init("syt_c29mdHBpZ2VvbmVz_QqNnkNjuuAffMKfGfUEO_0SqrAx", "hot-chilli.im"); + sync(); +} + +HTTP http; +string homeserver; +void init(string token, string hs) { + http = HTTP(); + http.addRequestHeader("Authorization", "Bearer " ~ token); + homeserver = "https://" ~ hs; + get(homeserver~"/_matrix/federation/v1/version"); +} + +void send(T)(T content, string roomid, string type = "m.room.message") { + import std.random: uniform; + put(homeserver~"/_matrix/client/v3/rooms/" ~ roomid ~ "/send/" ~ type ~ + "/skunky-" ~ intToStr(uniform(1111_1111, 9999_9999)), content.serializeToJson, http); +} + +// void join(string roomid) { +// post(homeserver~"/_matrix/client/v3/rooms/"~roomid~"/join", http); +// } + +void sync() { + import core.memory; + import object: destroy; + string str = homeserver ~ "/_matrix/client/v3/sync?set_presence=online"; + Sync content = get(str, http).deserialize!Sync; str ~= "&since="; + for (;;) { + string bthUrl = str ~ content.next_batch; + writeln(content.next_batch); + destroy(content); + GC.free(&content); + content = get(bthUrl, http).deserialize!Sync; + + // 🇮🇳🇮🇳🇮🇳 + + foreach (room, _; content.rooms.invite) { + writeln("Joining to room: ", room); + post(homeserver~"/_matrix/client/v3/rooms/"~room~"/join", null, http); + send(MSG("Yasno << Ponyatno"), 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); + writeln(room, ": ", evt.body); + switch (evt.body) { + case "!huy": + send(MSG(":orehussmile:", + `:orehussmile:`), room); + break; + case "!версия": send(MSG("Compiler version: "~intToStr(__VERSION__)), room); break; + case "скунс": send(MSG("еблан"), room); break; + case "!avatar": + string url = cast(string)get(homeserver~"/_matrix/client/v3/profile/"~event.sender~"/avatar_url"); + if (url == "{}") {send(MSG("Avatar is empty"), room); break;} + send(MSG(event.sender, ``), room); + break; + default: break; + } + } catch (Exception e) { + writeln(e.msg); + send(MSG(e.msg), room); + } + } + } + } + } +} + +string intToStr(T)(T num) { + char[] buf; + for(short i; num > 0; ++i) { + buf = (num % 10 + '0')~buf; + num /= 10; + } return cast(string)buf; +} \ No newline at end of file diff --git a/source/util.d b/source/util.d new file mode 100644 index 0000000..88c248d --- /dev/null +++ b/source/util.d @@ -0,0 +1,73 @@ +module util; +import asdf; + +struct JsonObject { + import std.conv: to; + string data; + + SerdeException deserializeFromAsdf(Asdf data) { + this.data = data.to!string; + return null; + } + + void serialize(S)(ref S serializer) { + try serializer.sink.putSmallEscaped(data.parseJson.to!string); + catch (Exception) serializer.sink.putSmallEscaped("{}"); + } +} + +struct Sync { + struct StrippedStateEvent { + JsonObject content; + string sender, state_key, type; + } + + struct Unsigned { + int age; + string membership, transaction_id; + } + + struct Rooms { + struct Invited { + struct IS { StrippedStateEvent[] events; } + IS invite_state; + } + 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; + } + + struct UNC { + int highlight_count; + int notification_count; + } + + Timeline timeline; + UNC unread_notifications; + } + + @serdeOptional Invited[string] invite; + @serdeOptional Joined[string] join; + } + + @serdeOptional Rooms rooms; + string next_batch; +} + +// EVENTS +struct MSG { + string body; + @serdeOptional @serdeIgnoreDefault string formatted_body; + string msgtype = "m.notice"; + @serdeOptional string format = "org.matrix.custom.html"; +} \ No newline at end of file