shithub: masto9

Download patch

ref: 22b180670881db7c4850e7fd9786ffcb70498057
parent: 454fd94f7db58e8bef6fb5123a9ed260271d3c06
author: Julien Blanchard <jblanchard@makemusic.com>
date: Thu Apr 6 14:39:38 EDT 2023

Post with attachment

--- a/http.c
+++ b/http.c
@@ -2,46 +2,54 @@
 #include <libc.h>
 #include <stdio.h>
 #include <json.h>
+#include <bio.h>
 
 #include "masto9.h"
 
+#define BOUNDARY "---------------------------328018649918767126933410246249"
+
+int
+webclone(int *c)
+{
+	char buf[128];
+	int n, fd;
+
+	if((fd = open("/mnt/web/clone", ORDWR)) < 0)
+		sysfatal("couldn't open %s: %r", buf);
+	if((n = read(fd, buf, sizeof buf-1)) < 0)
+		sysfatal("reading clone: %r");
+	if(n == 0)
+		sysfatal("short read on clone");
+	buf[n] = '\0';
+	*c = atoi(buf);
+
+	return fd;
+}
+
 char *
 httpget(char *token, char *url)
 {
 	int	ctlfd, bodyfd, conn, n;
 	char buf[1024];
-	char *body;
+	char body[TLBUFSIZE];
   char *bearer_token;
 
-	ctlfd = open("/mnt/web/clone", ORDWR);
-	if (ctlfd < 0)
-		sysfatal("open: %r");
-	n = read(ctlfd, buf, sizeof(buf));
-	if (n < 0)
-		sysfatal("read: %r");
-	buf[n] = 0;
-	conn = atoi(buf);
+  ctlfd = webclone(&conn);
 
-	/* the write(2) syscall (used by fprint) is considered
-	 * failed if it returns -1 or N != Nwritten. to check for
-	 * error here you'd have to snprint the url to a temporary
-	 * buffer, get its length, then write it out and check the
-	 * amount written against the length */
-	if (fprint(ctlfd, "url %s", url) <= 0)
-		sysfatal("write ctl failed 'url %s': %r", url);
+	snprint(buf, sizeof buf, "url %s", url);
+	if(write(ctlfd, buf, n = strlen(buf)) != n)
+		sysfatal("post: write: %r");
 
+  /* Request */
   bearer_token = concat("Authorization: Bearer ", token);
-
 	if (fprint(ctlfd, "headers %s", bearer_token) <= 0)
 		sysfatal("write ctl failed 'headers'");
 
 	snprint(buf, sizeof(buf), "/mnt/web/%d/body", conn);
 
-	bodyfd = open(buf, OREAD);
-	if (bodyfd < 0)
+  /* Response */
+	if ((bodyfd = open(buf, OREAD)) < 0)
 		sysfatal("open %s: %r", buf);
-
-  body = emalloc(TLBUFSIZE * sizeof(char));
 	if (readn(bodyfd, body, TLBUFSIZE) <= 0)
 		sysfatal("readn: %r");
 
@@ -54,31 +62,18 @@
 char *
 httppost(char *token, char *url, char *text)
 {
-	int	ctlfd, bodyfd, conn;
-	char *buf;
+	int n, ctlfd, bodyfd, conn;
+	char buf[TOOTBUFSIZE];
   char *bearer_token;
 
-  buf = emalloc(TOOTBUFSIZE * sizeof(char));
+  ctlfd = webclone(&conn);
 
-	ctlfd = open("/mnt/web/clone", ORDWR);
-	if (ctlfd < 0)
-		sysfatal("open ctlfd: %r");
-	/* n = write(ctlfd, Contenttype, sizeof(Contenttype)); */
-	/* if (n < 0) */
-	/* 	sysfatal("write: %r"); */
-	/* buf[n] = 0; */
-	conn = atoi(buf);
+	snprint(buf, sizeof buf, "url %s", url);
+	if(write(ctlfd, buf, n = strlen(buf)) != n)
+		sysfatal("post: write: %r");
 
-	/* the write(2) syscall (used by fprint) is considered
-	 * failed if it returns -1 or N != Nwritten. to check for
-	 * error here you'd have to snprint the url to a temporary
-	 * buffer, get its length, then write it out and check the
-	 * amount written against the length */
-	if (fprint(ctlfd, "url %s", url) <= 0)
-		sysfatal("write ctl failed 'url %s': %r", url);
-
+  /* Request */
   bearer_token = concat("Authorization: Bearer ", token);
-
 	if (fprint(ctlfd, "headers %s", bearer_token) <= 0)
 		sysfatal("write ctl failed 'headers'");
 
@@ -91,17 +86,14 @@
     sysfatal("write: %r");
 
 	close(bodyfd);
-	snprint(buf, TOOTBUFSIZE, "/mnt/web/%d/body", conn);
 
-  bodyfd = open(buf, OREAD);
-	if (bodyfd < 0)
-		sysfatal("open %s: %r", buf);
-
+  /* Response */
+  snprint(buf, TOOTBUFSIZE, "/mnt/web/%d/body", conn);
+	if((bodyfd = open(buf, OREAD)) < 0)
+		sysfatal("post: opening body: %r");
 	if (readn(bodyfd, buf, TOOTBUFSIZE) <= 0)
 		sysfatal("readn: %r");
 
-  /* print("BUF %s", buf); */
-
 	close(bodyfd);
 	close(ctlfd);
 
@@ -108,119 +100,82 @@
   return buf;
 }
 
+char
+*mime_type(char *filename)
+{
+    char *ext = strrchr(filename, '.');
+    if (!ext) return "application/octet-stream";
+    if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg";
+    if (strcmp(ext, ".gif") == 0) return "image/gif";
+    if (strcmp(ext, ".png") == 0) return "image/png";
+    if (strcmp(ext, ".mp3") == 0) return "audio/mp3";
+    if (strcmp(ext, ".mp4") == 0) return "video/mp4";
+    if (strcmp(ext, ".webm") == 0) return "video/webm";
+    return "application/octet-stream";
+}
 
-/* static void* */
-/* slurp(int fd, int *n) */
-/* { */
-/* 	char *b; */
-/* 	int r, sz; */
+char *
+upload(char *token, char *url, char *filepath)
+{
+	char buf[BUFSIZE];
+	int n, conn, ctlfd, bodyfd;
+  char *bearer_token, *multipart_header;
+  FileAttachment *fa;
 
-/* 	*n = 0; */
-/* 	sz = 32; */
-/* 	if((b = malloc(sz)) == nil) */
-/* 		abort(); */
-/* 	while(1){ */
-/* 		if(*n + 1 == sz){ */
-/* 			sz *= 2; */
-/* 			if((b = realloc(b, sz)) == nil) */
-/* 				abort(); */
-/* 		} */
-/* 		r = read(fd, b + *n, sz - *n - 1); */
-/* 		if(r == 0) */
-/* 			break; */
-/* 		if(r == -1){ */
-/* 			free(b); */
-/* 			return nil; */
-/* 		} */
-/* 		*n += r; */
-/* 	} */
-/* 	b[*n] = 0; */
-/* 	return b; */
-/* } */
+	ctlfd = open("/mnt/web/clone", ORDWR);
+	if (ctlfd < 0)
+		sysfatal("open: %r");
+	n = read(ctlfd, buf, sizeof(buf));
+	if (n < 0)
+		sysfatal("read: %r");
+	buf[n] = '\0';
+	conn = atoi(buf);
 
-/* static int */
-/* webopen(char *url, char *token, char *dir, int ndir) */
-/* { */
-/* 	char buf[16]; */
-/* 	int n, cfd, conn; */
+  fa = readfile(filepath);
 
-/* 	if((cfd = open("/mnt/web/clone", ORDWR)) == -1) */
-/* 		return -1; */
-/* 	if((n = read(cfd, buf, sizeof(buf)-1)) == -1) */
-/* 		return -1; */
-/* 	buf[n] = 0; */
-/* 	conn = atoi(buf); */
+	snprint(buf, sizeof buf, "url %s", url);
+	if(write(ctlfd, buf, n = strlen(buf)) != n)
+		sysfatal("post: write: %r");
+  bearer_token = esmprint("Authorization: Bearer %s", token);
+	snprint(buf, sizeof buf, "headers %s", bearer_token);
+	if(write(ctlfd, buf, n = strlen(buf)) != n)
+		sysfatal("post: write headers: %r");
+	snprint(buf, sizeof buf, "contenttype multipart/form-data; boundary=%s", BOUNDARY);
+	if(write(ctlfd, buf, n = strlen(buf)) != n)
+		sysfatal("post: write contenttype: %r");
 
-/*     bearer_token = concat("Authorization: Bearer ", token); */
+  snprint(buf, sizeof buf, "/mnt/web/%d/postbody", conn);
+	if ((bodyfd = open(buf, OWRITE)) < 0)
+		sysfatal("open bodyfd %s: %r", buf);
 
-/* 	if(fprint(cfd, "headers %s", bearer_token) <= 0) */
-/* 		goto Error; */
-/* 	if(fprint(cfd, "url %s", url) == -1) */
-/* 		goto Error; */
-/* 	snprint(dir, ndir, "/mnt/web/%d", conn); */
-/* 	return cfd; */
-/* Error: */
-/* 	close(cfd); */
-/* 	return -1; */
-/* } */
+  /* Write multipart body */
+  write(bodyfd, "--", 2);
+  write(bodyfd, BOUNDARY, strlen(BOUNDARY));
+  write(bodyfd, "\r\n", 2);
 
-/* static char* */
-/* get(char *url, char *token, int *n) */
-/* { */
-/* 	char *r, dir[64], path[80]; */
-/* 	int cfd, dfd; */
+  multipart_header = esmprint("Content-Disposition: form-data; \
+                               name=\"file\"; \
+                               filename=\"blob\"\r\nContent-Type: %s\r\n\r\n", mime_type(basename(filepath)));
+  write(bodyfd, multipart_header, strlen(multipart_header));
 
-/* 	r = nil; */
-/* 	dfd = -1; */
-/* 	if((cfd = webopen(url, token, dir, sizeof(dir))) == -1) */
-/* 		goto Error; */
-/* 	snprint(path, sizeof(path), "%s/%s", dir, "body"); */
-/* 	if((dfd = open(path, OREAD)) == -1) */
-/* 		goto Error; */
-/* 	r = slurp(dfd, n); */
-/* Error: */
-/* 	if(dfd != -1) close(dfd); */
-/* 	if(cfd != -1) close(cfd); */
-/* 	return r; */
-/* } */
+  write(bodyfd, fa->buf, fa->size);
 
-/* static char* */
-/* post(char *url, char *buf, int nbuf, int *nret, Hdr *h) */
-/* { */
-/* 	char *r, dir[64], path[80]; */
-/* 	int cfd, dfd, hfd, ok; */
+  write(bodyfd, "\r\n", 2);
+  write(bodyfd, "--", 2);
+  write(bodyfd, BOUNDARY, strlen(BOUNDARY));
+  write(bodyfd, "--\r\n", 4);
 
-/* 	r = nil; */
-/* 	ok = 0; */
-/* 	dfd = -1; */
-/* 	if((cfd = webopen(url, dir, sizeof(dir))) == -1) */
-/* 		goto Error; */
-/* 	if(write(cfd, Contenttype, strlen(Contenttype)) == -1) */
-/* 		goto Error; */
-/* 	snprint(path, sizeof(path), "%s/%s", dir, "postbody"); */
-/* 	if((dfd = open(path, OWRITE)) == -1) */
-/* 		goto Error; */
-/* 	if(write(dfd, buf, nbuf) != nbuf) */
-/* 		goto Error; */
-/* 	close(dfd); */
-/* 	snprint(path, sizeof(path), "%s/%s", dir, "body"); */
-/* 	if((dfd = open(path, OREAD)) == -1) */
-/* 		goto Error; */
-/* 	if((r = slurp(dfd, nret)) == nil) */
-/* 		goto Error; */
-/* 	if(h != nil){ */
-/* 		snprint(path, sizeof(path), "%s/%s", dir, h->name); */
-/* 		if((hfd = open(path, OREAD)) == -1) */
-/* 			goto Error; */
-/* 		if((h->val = slurp(hfd, &h->nval)) == nil) */
-/* 			goto Error; */
-/* 		close(hfd); */
-/* 	} */
-/* 	ok = 1; */
-/* Error: */
-/* 	if(dfd != -1) close(dfd); */
-/* 	if(cfd != -1) close(cfd); */
-/* 	if(!ok && h != nil) */
-/* 		free(h->val); */
-/* 	return r; */
-/* } */
+  close(bodyfd);
+
+	/* Response */
+	snprint(buf, sizeof buf, "/mnt/web/%d/body", conn);
+	if((bodyfd = open(buf, OREAD)) < 0)
+		sysfatal("post: opening body: %r");
+	if (readn(bodyfd, buf, BUFSIZE) <= 0)
+		sysfatal("readn: %r");
+
+	close(bodyfd);
+	close(ctlfd);
+
+  return buf;
+}
--- a/masto9.c
+++ b/masto9.c
@@ -48,50 +48,50 @@
 		reblog = getjsonkey(toot_json, "reblog");
 		media_attachments = getjsonkey(toot_json, "media_attachments");
 
-		Toot toot = emalloc(sizeof(Toot));
-		toot.id = estrdup((char *)id->s);
+		Toot *toot = emalloc(sizeof(Toot));
+		toot->id = estrdup((char *)id->s);
 
 		if(reblog->s == nil) {
-			toot.reblogged = 0;
-			toot.content = estrdup((char *)content->s);
+			toot->reblogged = 0;
+			toot->content = estrdup((char *)content->s);
 		} else {
 			reblog_content = getjsonkey(reblog, "content");
 			reblog_account = getjsonkey(reblog, "account");
 			reblog_handle = getjsonkey(reblog_account, "acct");
 
-			toot.content = estrdup((char *)reblog_content->s);
-			toot.reblogged_handle = estrdup((char *)reblog_handle->s);
-			toot.reblogged = 1;
+			toot->content = estrdup((char *)reblog_content->s);
+			toot->reblogged_handle = estrdup((char *)reblog_handle->s);
+			toot->reblogged = 1;
 
 			media_attachments = getjsonkey(reblog, "media_attachments");
 		};
-		toot.handle = estrdup((char *)handle->s);
-		toot.display_name = estrdup((char *)display_name->s);
-		toot.avatar_url = estrdup((char *)avatar->s);
-		toot.attachments_count = 0;
+		toot->handle = estrdup((char *)handle->s);
+		toot->display_name = estrdup((char *)display_name->s);
+		toot->avatar_url = estrdup((char *)avatar->s);
+		toot->attachments_count = 0;
 
 		if(media_attachments->s != nil) {
 			int j = 0;
 			for(JSONEl *at = media_attachments->first; at != nil; at = at->next) {
 				JSON *attachment_json = at->val;
-				Attachment attachment = emalloc(sizeof(Attachment));
+				Attachment *attachment = emalloc(sizeof(Attachment));
 				type = getjsonkey(attachment_json, "type");
 				preview_url = getjsonkey(attachment_json, "preview_url");
 				remote_url = getjsonkey(attachment_json, "remote_url");
-				attachment.type = estrdup((char *)type->s);
+				attachment->type = estrdup((char *)type->s);
 
         if(strcmp(type->s, "image") == 0){
-          attachment.url = estrdup((char *)preview_url->s);
+          attachment->url = estrdup((char *)preview_url->s);
         } else {
-          attachment.url = estrdup((char *)remote_url->s);
+          attachment->url = estrdup((char *)remote_url->s);
         }
 
-				toot.media_attachments[j] = attachment;
-				toot.attachments_count++;
+				toot->media_attachments[j] = attachment;
+				toot->attachments_count++;
 				j++;
 			}
 		}
-		toots[i] = toot;
+		toots[i] = *toot;
 		i++;
 	}
 	jsonfree(obj);
@@ -98,7 +98,7 @@
 }
 
 void
-getnotifications(char *token, char *host, Notification notifs[])
+getnotifications(char *token, char *host, Notification *notifs)
 {
 	JSON *obj, *id, *content, *display_name, *handle, *type, *account, *status;
 	int i = 0;
@@ -123,17 +123,17 @@
     display_name = getjsonkey(account, "display_name");
     handle = getjsonkey(account, "acct");
 
-		Notification notif = emalloc(sizeof(Notification));
-		notif.id = estrdup((char *)id->s);
+		Notification *notif = emalloc(sizeof(Notification));
+		notif->id = estrdup((char *)id->s);
 
-    notif.type = estrdup((char *)type->s);
+    notif->type = estrdup((char *)type->s);
     if(strcmp(type->s, "follow") != 0) {
-      notif.content = estrdup((char *)content->s);
+      notif->content = estrdup((char *)content->s);
     }
-		notif.display_name = estrdup((char *)display_name->s);
-		notif.handle = estrdup((char *)handle->s);
+		notif->display_name = estrdup((char *)display_name->s);
+		notif->handle = estrdup((char *)handle->s);
 
-		notifs[i] = notif;
+		notifs[i] = *notif;
 		i++;
 	}
 	jsonfree(obj);
@@ -175,6 +175,33 @@
 }
 
 void
+postattachment(char *token, char *host, char *text, char *filepath)
+{
+  JSON *obj, *id;
+  char *url, *response, *body;
+
+	url = esmprint("https://%s/api/v1/media", host);
+	response = upload(token, url, filepath);
+
+  obj = jsonparse(response);
+	if (obj == nil)
+		sysfatal("jsonparse: not json");
+
+  print("%J\n", obj);
+  id = getjsonkey(obj, "id");
+
+  url = esmprint("https://%s/api/v1/statuses", host);
+
+  if (strlen(text) >0) {
+    body = esmprint("status=%s&media_ids[]=%s", text, id->s);
+  } else {
+    body = esmprint("media_ids[]=%s", id->s);
+  }
+	httppost(token, url, body);
+  print("Posted toot\n");
+}
+
+void
 perform(char *token, char *host, char *id, char *action)
 {
   char *url;
@@ -334,8 +361,8 @@
     Bprint(&out, "\n%s", fmthtml(cleanup(toot.content)));
     if(toot.attachments_count>0) {
       for (int j=0;j<toot.attachments_count;j++) {
-        Attachment attachment = toot.media_attachments[j];
-        Bprint(&out, "\n[%s] %s", attachment.type, attachment.url);
+        Attachment *attachment = toot.media_attachments[j];
+        Bprint(&out, "\n[%s] %s", attachment->type, attachment->url);
       }
       Bprint(&out, "\n");
     }
@@ -392,7 +419,7 @@
 main(int argc, char**argv)
 {
   UserPasswd *p;
-  char *token, *host, *command, *text, *id;
+  char *token, *host, *command, *text, *id, *filepath;
 
   if(argc < 2)
 		usage();
@@ -412,6 +439,15 @@
   } else if(strcmp(command, "toot") == 0) {
     text = argv[3];
     posttoot(token, host, text);
+  } else if(strcmp(command, "tootfile") == 0) {
+    if (argc > 4) {
+      text = argv[3];
+      filepath = argv[4];
+    } else {
+      text = "";
+      filepath = argv[3];
+    }
+    postattachment(token, host, text, filepath);
   } else if(strcmp(command, "fav") == 0) {
     id = argv[3];
     fav(token, host, id);
--- a/masto9.h
+++ b/masto9.h
@@ -1,10 +1,13 @@
-#define Contenttype	"contenttype application/json"
-
 typedef struct Attachment {
 	char *type;
 	char *url;
 } Attachment;
 
+typedef struct FileAttachment {
+	char *buf;
+	int size;
+} FileAttachment;
+
 typedef struct Notification {
   char *id;
   char *type;
@@ -22,18 +25,12 @@
 	char *in_reply_to_account_id;
 	int reblogged;
 	char *reblogged_handle;
-	Attachment media_attachments[10];
+	Attachment *media_attachments[10];
 	int attachments_count;
 } Toot;
 
-typedef struct {
-	char *s1;
-	char *s2;
-} Str2;
-
-#pragma varargck type "E" Str2
-
 enum {
+	BUFSIZE = 2056,
 	TOOTBUFSIZE = 8192,
 	TLBUFSIZE = 512000,
 	MAXURL = 1024,
@@ -47,6 +44,7 @@
 /* http */
 char *httpget(char *token, char *url);
 char *httppost(char *token, char *url, char *text);
+char *upload(char *token, char *url, char *filename);
 
 /* utils */
 char *concat(char *s1, char *s2);
@@ -59,3 +57,5 @@
 void removesubstring(char *, char *);
 void removetag(char *, char *);
 JSON *getjsonkey(JSON *, char *);
+FileAttachment *readfile(char *filename);
+char *basename(char *filepath);
--- a/util.c
+++ b/util.c
@@ -134,3 +134,44 @@
 		sysfatal("jsonbyname: key %s not found in %J", key, obj);
 	return value;
 }
+
+FileAttachment *
+readfile(char *filename)
+{
+    int fd, nread, size = 0, bufsize = 1024;
+    FileAttachment *fa = emalloc(sizeof(FileAttachment));
+    char *buf = malloc(bufsize);
+    if (!buf)
+        sysfatal("malloc failed");
+
+    fd = open(filename, OREAD);
+    if (fd < 0)
+        sysfatal("open %s: %r", filename);
+
+    while ((nread = read(fd, buf + size, bufsize - size)) > 0) {
+        size += nread;
+        if (size == bufsize) {
+            bufsize *= 2;
+            buf = realloc(buf, bufsize);
+            if (!buf)
+                sysfatal("realloc failed");
+        }
+    }
+    close(fd);
+
+    if (nread < 0)
+        sysfatal("read %s: %r", filename);
+
+    buf[size] = '\0';
+
+    fa->buf = buf;
+    fa->size = size;
+    return fa;
+}
+
+char *
+basename(char *path)
+{
+  char *base = strrchr(path, '/');
+  return base ? base + 1 : path;
+}