aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-05-12 20:33:57 +0200
committerLinnea Gräf <nea@nea.moe>2025-05-12 20:33:57 +0200
commit4a6ddab6da2bfce1c97a1138ba529f9b9691d998 (patch)
treed2eb64f2b12af7a268cfde5685080db00aef3941 /source
downloadboobbot-4a6ddab6da2bfce1c97a1138ba529f9b9691d998.tar.gz
boobbot-4a6ddab6da2bfce1c97a1138ba529f9b9691d998.tar.bz2
boobbot-4a6ddab6da2bfce1c97a1138ba529f9b9691d998.zip
Initial commit
Diffstat (limited to 'source')
-rw-r--r--source/app.d18
-rw-r--r--source/backronyms.d81
-rw-r--r--source/misskey.d133
3 files changed, 232 insertions, 0 deletions
diff --git a/source/app.d b/source/app.d
new file mode 100644
index 0000000..b60e192
--- /dev/null
+++ b/source/app.d
@@ -0,0 +1,18 @@
+import std.stdio;
+import backronyms;
+import misskey;
+import std.process;
+import std.logger;
+void main()
+{
+ globalLogLevel = LogLevel.trace;
+ info("Hello world");
+ auto generator = BackronymGenerator.load;
+ auto misskey = new MisskeyClient(
+ environment.get("MISSKEY_INSTANCE"),
+ environment.get("MISSKEY_TOKEN")
+ );
+
+ auto boobFormat = "boob is short for\n\n" ~ generator.formatBackronymList("boob");
+ writefln("%s", misskey.createNote(new BasicNote("public", boobFormat)));
+}
diff --git a/source/backronyms.d b/source/backronyms.d
new file mode 100644
index 0000000..7b3c9e6
--- /dev/null
+++ b/source/backronyms.d
@@ -0,0 +1,81 @@
+module backronyms;
+import std.stdio;
+import std.algorithm;
+import std.random;
+import std.conv;
+import std.range;
+import std.typecons;
+
+struct BackronymGenerator
+{
+ WordList nouns;
+ WordList adjectives;
+
+ static BackronymGenerator load()
+ {
+ return BackronymGenerator(
+ WordList.fromFile("SimpleWordlists/Wordlist-Nouns-Common-Audited-Len-3-6.txt"),
+ WordList.fromFile("SimpleWordlists/Wordlist-Adjectives-All.txt"),
+ );
+ }
+
+ string formatBackronymList(string word)
+ {
+ string ret;
+ for (int i = 0; i < word.length; i++)
+ {
+ ret ~= (
+ i + 1 == word.length
+ ? this.nouns : this.adjectives
+ ).findRandomWord(word[i]);
+ ret ~= '\n';
+ }
+ return ret;
+ }
+}
+
+struct WordList
+{
+
+ string[][char] words;
+
+ string[] findBackronym(string word)
+ {
+ string[] ret;
+
+ foreach (c; word)
+ {
+ ret ~= this.findRandomWord(c);
+ }
+
+ return ret;
+ }
+
+ string findRandomWord(char start)
+ {
+ return this.words[start].choice;
+ }
+
+ static WordList fromFile(string filePath)
+ {
+ auto file = File(filePath);
+ return fromLines(file.byLine.map!(to!string));
+ }
+
+ static WordList fromLines(R)(R lines)
+ if (isInputRange!R && is(ElementType!R == string))
+ {
+ WordList ret;
+
+ foreach (line; lines)
+ {
+ if (line.length == 0)
+ continue;
+
+ ret.words[line[0]] ~= line;
+ }
+
+ return ret;
+ }
+
+}
diff --git a/source/misskey.d b/source/misskey.d
new file mode 100644
index 0000000..823dbe0
--- /dev/null
+++ b/source/misskey.d
@@ -0,0 +1,133 @@
+module misskey;
+import requests;
+import std.logger;
+import std.format;
+import std.conv;
+import jsonizer;
+
+class MisskeyException : Exception
+{
+
+ string path;
+ Response response;
+ this(
+ Response response,
+ string path,
+ string msg,
+ string file = __FILE__, size_t line = __LINE__)
+ {
+ super("at path " ~ path ~ ": " ~ msg, file, line);
+ this.response = response;
+ this.path = path;
+ }
+}
+
+class MisskeyClient
+{
+ string instanceUrl;
+ string token;
+ this(string instanceUrl, string token)
+ {
+ this.instanceUrl = instanceUrl;
+ this.token = token;
+ assert(token.length != 0);
+ assert(instanceUrl.length != 0);
+ }
+
+ T request(string method, string path, T, R)(R body)
+ {
+ static assert(path[0] == '/');
+ auto req = Request();
+ req.addHeaders([
+ "user-agent": "boobbot",
+ "authorization": format("Bearer %s", this.token)
+ ]);
+ auto fullPath = format("%s/api%s", this.instanceUrl, path);
+ auto reqBody = toJSONString!R(body);
+ infof("Executing request to %s with headers %s", fullPath, req.headers);
+ Response response = mixin("req." ~ method)(
+ fullPath,
+ reqBody,
+ "application/json"
+ );
+ if (response.code / 100 != 2)
+ throw new MisskeyException(response, path, format("non 200 status code: %s", response
+ .code));
+ return fromJSONString!T(to!string(response.responseBody));
+ }
+
+ CreateNoteResponse createNote(BasicNote note)
+ {
+ return this.request!("post", "/notes/create", CreateNoteResponse, BasicNote)(note);
+ }
+
+}
+
+class BasicNote
+{
+ mixin JsonizeMe;
+
+ @jsonize(Jsonize.opt)
+ {
+ string visibility;
+ string text;
+ string id;
+ }
+
+ this()
+ {
+ }
+
+ this(string visibility, string text)
+ {
+ this.visibility = visibility;
+ this.text = text;
+ this.id = "";
+ }
+
+}
+
+class CreateNoteResponse
+{
+ mixin JsonizeMe;
+
+ @jsonize
+ BasicNote createdNote;
+}
+
+/*
+curl https://sk.amy.rip/api/notes/create \
+ --request POST \
+ --header 'Content-Type: application/json' \
+ --header 'Authorization: Bearer aaaaaaaaaa' \
+ --data '{
+ "visibility": "public",
+ "visibleUserIds": [
+ ""
+ ],
+ "cw": null,
+ "localOnly": false,
+ "reactionAcceptance": null,
+ "noExtractMentions": false,
+ "noExtractHashtags": false,
+ "noExtractEmojis": false,
+ "replyId": null,
+ "renoteId": null,
+ "channelId": null,
+ "text": null,
+ "fileIds": [
+ ""
+ ],
+ "mediaIds": [
+ ""
+ ],
+ "poll": {
+ "choices": [
+ ""
+ ],
+ "multiple": true,
+ "expiresAt": null,
+ "expiredAfter": null
+ }
+}'
+*/