This commit is contained in:
lost+skunk 2025-01-19 00:55:34 +03:00
parent c22eba6f87
commit ea6fb882f1
3 changed files with 133 additions and 55 deletions

View File

@ -5,18 +5,26 @@ import asdf: deserialize, serializeToJson;
import util; import util;
void main() { void main() {
init("", "hot-chilli.im"); import std.process: environment;
alias env = environment.get;
homeserver = env("SKUNKYBOT_HOMESERVER");
init(env("SKUNKYBOT_TOKEN"));
sync; sync;
} }
HTTP http; HTTP http;
string homeserver; string syncUrl;
void init(string token, string hs) { string initialBatch;
void init(string token) {
http = HTTP(); http = HTTP();
http.addRequestHeader("Authorization", "Bearer " ~ token); http.addRequestHeader("Authorization", "Bearer " ~ token);
http.tcpNoDelay = true; 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") { 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); // 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"; void sync() {
Sync content = get(str, http).deserialize!Sync; str ~= "&since="; auto content = Sync();
content.next_batch = initialBatch;
for (;;) { for (;;) {
string bthUrl = str ~ content.next_batch; string bthUrl = syncUrl ~ content.next_batch;
content = get(bthUrl, http).deserialize!Sync; try content = get(bthUrl, http).deserialize!Sync;
catch (Exception e) { writeln("ERR: ", e.msg); continue; }
foreach (room, _; content.rooms.invite) { foreach (room, _; content.rooms.invite) {
writeln("Joining to room: ", room); writeln("Joining to room: ", room);
post(homeserver~"/_matrix/client/v3/rooms/"~room~"/join", null, http); post(homeserver~"/_matrix/client/v3/rooms/"~room~"/join", null, http);
send(MSG("Привет, я СканкиБот"), room); send(MSG("Йа криведко"), room);
} }
foreach (room, roomContent; content.rooms.join) { foreach (room, roomContent; content.rooms.join) {
foreach(event; roomContent.timeline.events) { foreach(event; roomContent.timeline.events) {
if (event.type == "m.room.message") { if (event.type == "m.room.message") {
try { 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)) { foreach (member; __traits(allMembers, commands)) {
alias mmbr = __traits(getMember, commands, member); if (argz.command == member) {
if (evt.body[1..$] == member) { alias command = __traits(getMember, commands, member);
static foreach (attr; __traits(getAttributes, mmbr)) { foreach (attr; __traits(getAttributes, command)) {
static if (is(attr == Command)) { static if (is(typeof(attr) == Command)) {
static if (is(mmbr == function)) static if (is(typeof(command) == function))
send(mmbr(), room); auto content = command(argz, &event);
else static if (__traits(isStaticArray, mmbr)) else static if (__traits(isStaticArray, command))
send(MSG(mmbr[0], mmbr[1]), room); auto content = MSG(command[0], command[1]);
else else
send(MSG(mmbr), room); auto content = MSG(command);
send(content, room);
} }
} }
} }

View File

@ -1,29 +1,40 @@
module commands; module commands;
import util; import util;
enum Command; // UDA
// auto avatar(string[] arguments) @Command { // UDA
// string url = cast(string)get(homeserver~"/_matrix/client/v3/profile/"~event.sender~"/avatar_url"); struct Command {
// if (url == "{}") string description = "No description provided";
// return MSG("User has no avatar"); string name;
// return MSG(event.sender, `<img src="`~url[15..$-2]~`">`); }
// }
@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, `<img src="`~url[15..$-2]~`">`);
}
@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:",
`<img data-mx-emoticon height="32" alt=":orehussmile:" title=":orehussmile:" `<img data-mx-emoticon height="32" alt=":orehussmile:" title=":orehussmile:"
src="mxc://4d2.org/XvWYAuhASYRHtYvtspsrWvtU" >`]; src="mxc://4d2.org/XvWYAuhASYRHtYvtspsrWvtU" >`];
@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) { @Command("Версия бота", "версия")
// case "!huy": string ver = "SkunkyBot Pre-Alpha 0.1 :: https://git.bloat.cat/skunky/skunkybot-d";
// send(MSG(":orehussmile:",
// ), room); @Command("test")
// break; static string compver = "Compiler version: "~intToStr(__VERSION__);
// case "!версия": send(), room); break;
// case "скунс": send(MSG("еблан"), room); break;
// case "!avatar":
//
// break;
// default: break;
// }

View File

@ -1,6 +1,8 @@
module util; module util;
import asdf; import asdf;
string homeserver;
string intToStr(T)(T num) { string intToStr(T)(T num) {
char[] buf; char[] buf;
for(short i; num > 0; ++i) { for(short i; num > 0; ++i) {
@ -11,10 +13,10 @@ string intToStr(T)(T num) {
struct JsonObject { struct JsonObject {
import mir.conv: to; import mir.conv: to;
string data; string raw;
SerdeException deserializeFromAsdf(Asdf data) { SerdeException deserializeFromAsdf(Asdf data) {
this.data = data.to!string; this.raw = data.to!string;
return null; 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 Sync {
struct StrippedStateEvent { struct StrippedStateEvent {
JsonObject content; JsonObject content;
@ -42,14 +52,6 @@ struct Sync {
} }
struct Joined { struct Joined {
struct Timeline { struct Timeline {
struct EventWithoutRoomID {
JsonObject content;
string event_id;
ulong origin_server_ts;
@serdeOptional string sender, state_key, type;
// Unsigned unsigned;
}
EventWithoutRoomID[] events; EventWithoutRoomID[] events;
bool limited; bool limited;
string prev_batch; string prev_batch;
@ -60,7 +62,7 @@ struct Sync {
int notification_count; int notification_count;
} }
Timeline timeline; @serdeOptional Timeline timeline;
UNC unread_notifications; UNC unread_notifications;
} }
@ -78,4 +80,42 @@ struct MSG {
@serdeOptional @serdeIgnoreDefault string formatted_body; @serdeOptional @serdeIgnoreDefault string formatted_body;
string msgtype = "m.notice"; string msgtype = "m.notice";
@serdeOptional string format = "org.matrix.custom.html"; @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;
} }