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