ref: d7047b022e63c014469877be1faa54a977e51e89
dir: /masto9.c/
#include <u.h> #include <libc.h> #include <stdio.h> #include <json.h> #include <auth.h> #include <bio.h> #include "masto9.h" UserPasswd * getcredentials(char *host) { UserPasswd* p; p = auth_getuserpasswd(auth_getkey, "proto=pass service=mastodon server=%s", host); if(p == nil) sysfatal("masto9: failed to retrieve token: %r"); return p; } static JSON * getjsonkey(JSON *obj, char *key) { JSON *value = jsonbyname(obj, key); if (value == nil) sysfatal("jsonbyname: key %s not found in %J", key, obj); return value; } void gethome(char *token, char *host, Toot toots[], char *after) { JSON *obj, *id, *content, *reblog_content, *account, *reblog_account, *handle, *reblog_handle, *display_name, *avatar, *reblog, *media_attachments, *type, *preview_url, *remote_url; char *endpoint; int i = 0; endpoint = emalloc(sizeof(char)*1024); if(after != nil) { snprintf(endpoint, 1024, "timelines/home?max_id=%s", after); } else { endpoint = "timelines/home"; } obj = mastodonget(token, host, endpoint); if (obj->t != JSONArray) sysfatal("jsonparse: not an array"); for(JSONEl *p = obj->first; p != nil; p = p->next) { JSON *toot_json = p->val; id = getjsonkey(toot_json, "id"); content = getjsonkey(toot_json, "content"); account = getjsonkey(toot_json, "account"); handle = getjsonkey(account, "acct"); display_name = getjsonkey(account, "display_name"); avatar = getjsonkey(account, "avatar_static"); reblog = getjsonkey(toot_json, "reblog"); media_attachments = getjsonkey(toot_json, "media_attachments"); Toot toot = emalloc(sizeof(Toot)); toot.id = strdup((char *)id->s); if(reblog->s == nil) { toot.reblogged = 0; toot.content = strdup((char *)content->s); } else { reblog_content = getjsonkey(reblog, "content"); reblog_account = getjsonkey(reblog, "account"); reblog_handle = getjsonkey(reblog_account, "acct"); toot.content = strdup((char *)reblog_content->s); toot.reblogged_handle = strdup((char *)reblog_handle->s); toot.reblogged = 1; media_attachments = getjsonkey(reblog, "media_attachments"); }; toot.handle = strdup((char *)handle->s); toot.display_name = strdup((char *)display_name->s); toot.avatar_url = strdup((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)); type = getjsonkey(attachment_json, "type"); preview_url = getjsonkey(attachment_json, "preview_url"); remote_url = getjsonkey(attachment_json, "remote_url"); attachment.type = strdup((char *)type->s); if(strcmp(type->s, "image") == 0){ attachment.url = strdup((char *)preview_url->s); } else { attachment.url = strdup((char *)remote_url->s); } toot.media_attachments[j] = attachment; toot.attachments_count++; j++; } } toots[i] = toot; i++; } jsonfree(obj); } void notifications(char *token, char *host, Notification notifs[]) { JSON *obj, *id, *content, *display_name, *handle, *type, *account, *status; int i = 0; obj = mastodonget(token, host, "notifications"); if (obj->t != JSONArray) sysfatal("jsonparse: not an array"); for(JSONEl *p = obj->first; p != nil; p = p->next) { JSON *notif_json = p->val; id = getjsonkey(notif_json, "id"); type = getjsonkey(notif_json, "type"); if(strcmp(type->s, "follow") != 0) { status = getjsonkey(notif_json, "status"); content = getjsonkey(status, "content"); } else { content = jsonparse(""); } account = getjsonkey(notif_json, "account"); display_name = getjsonkey(account, "display_name"); handle = getjsonkey(account, "acct"); Notification notif = emalloc(sizeof(Notification)); notif.id = strdup((char *)id->s); notif.type = strdup((char *)type->s); if(strcmp(type->s, "follow") != 0) { notif.content = strdup((char *)content->s); } notif.display_name = strdup((char *)display_name->s); notif.handle = strdup((char *)handle->s); notifs[i] = notif; i++; } jsonfree(obj); } void posttoot(char *token, char *host, char *text) { char url[MAXURL]; snprintf(url, MAXURL, "https://%s/api/v1/statuses", host); httppost(token, url, concat("status=", text)); print("Posted:\n %s\n", text); } char * tootauthor(char *token, char *host, char *id) { JSON *obj, *account, *reblog; char *endpoint; endpoint = concat("statuses/", id); obj = mastodonget(token, host, endpoint); reblog = getjsonkey(obj, "reblog"); if(reblog != nil) { account = getjsonkey(reblog, "account"); } else { account = getjsonkey(obj, "account"); } return getjsonkey(account, "acct")->s; } void perform(char *token, char *host, char *id, char *action) { char url[MAXURL]; snprintf(url, sizeof(url), "%s/api/v1/statuses/%s/%s", host, id, action); httppost(token, url, ""); } void boost(char *token, char *host, char *id) { perform(token, host, id, "reblog"); print("Boosted toot."); } void unboost(char *token, char *host, char *id) { perform(token, host, id, "unreblog"); print("Unboosted toot."); } void fav(char *token, char *host, char *id) { perform(token, host, id, "favourite"); print("Favorited toot."); } void unfav(char *token, char *host, char *id) { perform(token, host, id, "unfavourite"); print("Unfavorited toot."); } void reply(char *token, char *host, char *id) { char content[TOOTBUFSIZE]; int fd, wait; char *s, *t, *u, url[MAXURL]; Biobuf body; wait = 0; t = nil; if(wait) close(open("/dev/text", OWRITE|OTRUNC|OCEXEC)); if((fd = open("/dev/consctl", OWRITE|OCEXEC)) >= 0){ write(fd, "holdon", 6); u = tootauthor(token, host, id); print("Reply to %s\n", u); Binit(&body, 0, OREAD); if((s = Brdstr(&body, 0, 1)) != nil) t = smprint("%s", s); free(s); if(t != nil){ snprintf(url, MAXURL, "%s/api/v1/statuses", host); snprintf(content, TOOTBUFSIZE, "in_reply_to_id=%s&status=@%s %s", id, u, t); httppost(token, url, content); print("\nReply sent.\n"); free(t); }else{ fprint(2, "%r\n"); } close(fd); }else{ fprint(2, "%r\n"); } } char * fmthtml(char *msg) { int wr[2], rd[2], n; char buf[TOOTBUFSIZE]; if(pipe(wr) == -1 || pipe(rd) == -1) sysfatal("pipe: %r"); switch(fork()){ case -1: sysfatal("fork: %r"); break; case 0: close(wr[0]); close(rd[1]); dup(wr[1], 0); dup(rd[0], 1); execl("/bin/htmlfmt", "htmlfmt -cutf-8 -j", nil); sysfatal("exec: %r"); break; default: close(wr[1]); close(rd[0]); write(wr[0], msg, strlen(msg)); close(wr[0]); n = readn(rd[1], buf, sizeof(buf)); close(rd[1]); if(n == -1) sysfatal("read: %r\n"); buf[n] = 0; return buf; } return buf; } char * cleanup(char *str) { removetag(str, "<span"); removesubstring(str, "</span>"); return str; } JSON * mastodonget(char *token, char *host, char *endpoint) { JSON *obj; char *response, url[MAXURL]; snprintf(url, MAXURL, "https://%s/api/v1/%s", host, endpoint); response = httpget(token, url); obj = jsonparse(response); if (obj == nil) sysfatal("jsonparse: not json"); return(obj); } void usage(void) { sysfatal("usage: masto9 url"); } void displaytoots(Toot toots[], char *server) { Biobuf out; Binit(&out, 1, OWRITE); for (int i=0;i<TOOTSCOUNT;i++) { Toot toot = toots[i]; char *username; username = emalloc(sizeof(char)*256); snprintf(username, 256, "%s (%s)", toot.display_name, toot.handle); Bprint(&out, "\n\n——————————\n"); if(toot.reblogged == 1) { Bprint(&out, "⊙ %s retooted %s:\n", username, toot.reblogged_handle); } else { Bprint(&out, "⊙ %s:\n", username); } 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); } Bprint(&out, "\n"); } Bprint(&out, "\nReply[%s] | Boost[%s] | Favorite[%s]", toot.id, toot.id, toot.id); } Bprint(&out, "\n\n\n⇒ Send the next line to load more"); Bprint(&out, "\n6.out %s more %s\n\n", server, toots[19].id); Bflush(&out); } void displaynotifications(Notification notifs[]) { Biobuf out; Binit(&out, 1, OWRITE); for (int i=0;i<NOTIFSCOUNT;i++) { Notification notif = notifs[i]; char *username; username = emalloc(sizeof(char)*256); snprintf(username, 256, "%s (%s)", notif.display_name, notif.handle); if (strcmp(notif.type, "reblog") == 0) { Bprint(&out, "\n⊙ %s retooted\n %s", username, fmthtml(cleanup(notif.content))); } else if (strcmp(notif.type, "favourite") == 0) { Bprint(&out, "\n⊙ %s favorited\n %s", username, fmthtml(cleanup(notif.content))); } else if (strcmp(notif.type, "mention") == 0) { Bprint(&out, "\n⊙ %s mentioned you\n %s", username, fmthtml(cleanup(notif.content))); } else if (strcmp(notif.type, "follow") == 0) { Bprint(&out, "\n⊙ %s followed you\n", username); } else if (strcmp(notif.type, "poll") == 0) { Bprint(&out, "\n⊙ %s poll ended\n %s", username, fmthtml(cleanup(notif.content))); } } Bprint(&out, "\n"); Bflush(&out); } //echo 'proto=pass service=mastodon server=instanceHostName pass=yourToken user=yourUsername' > /mnt/factotum/ctl void main(int argc, char**argv) { UserPasswd *p; char *token, *host, *command, *text, *id; if(argc < 2) usage(); JSONfmtinstall(); host = argv[1]; command = argv[2]; p = getcredentials(host); token = p->passwd; if(command == nil) { Toot toots[TOOTSCOUNT]; gethome(token, host, toots, nil); displaytoots(toots, host); } else if(strcmp(command, "toot") == 0) { text = argv[3]; posttoot(token, host, text); } else if(strcmp(command, "fav") == 0) { id = argv[3]; fav(token, host, id); } else if(strcmp(command, "unfav") == 0) { id = argv[3]; unfav(token, host, id); } else if(strcmp(command, "boost") == 0) { id = argv[3]; boost(token, host, id); } else if(strcmp(command, "unboost") == 0) { id = argv[3]; unboost(token, host, id); } else if(strcmp(command, "reply") == 0) { id = argv[3]; reply(token, host, id); } else if(strcmp(command, "more") == 0) { id = argv[3]; Toot toots[TOOTSCOUNT]; gethome(token, host, toots, id); displaytoots(toots, host); } else if(strcmp(command, "notifications") == 0) { Notification notifs[NOTIFSCOUNT]; notifications(token, host, notifs); displaynotifications(notifs); } free(p); exits(nil); }