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;
+}
--
⑨