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:",
``];
-@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