ref: b8d0e8f2692a2e0809d72cd621159e4ed4a6051d
dir: /masto9.c/
#include <u.h>
#include <libc.h>
#include <stdio.h>
#include <json.h>
#include <auth.h>
#include <bio.h>
#include "masto9.h"
static UserPasswd *
getcredentials(char *host)
{
UserPasswd *p;
if((p = auth_getuserpasswd(auth_getkey, "proto=pass service=mastodon server=%s", host)) == nil)
sysfatal("getcredentials: failed to retrieve token: %r");
return p;
}
static JSON *
mastodonget(char *token, char *host, char *endpoint)
{
JSON *obj;
char *response, *url;
url = esmprint("https://%s/api/v1/%s", host, endpoint);
response = httpget(token, url);
if((obj = jsonparse(response)) == nil)
sysfatal("mastodonget: jsonparse: not json");
return (obj);
}
static void
perform(char *token, char *host, char *id, char *action)
{
char *url;
url = esmprint("https://%s/api/v1/statuses/%s/%s", host, id, action);
httppost(token, url, "");
}
static char *
tootauthor(char *token, char *host, char *id)
{
JSON *obj, *account, *reblog;
char *endpoint, *response;
endpoint = esmprint("statuses/%s", id);
obj = mastodonget(token, host, endpoint);
reblog = getjsonkey(obj, "reblog");
if(reblog->s != nil) {
account = getjsonkey(reblog, "account");
} else {
account = getjsonkey(obj, "account");
}
response = estrdup((char *)getjsonkey(account, "acct")->s);
free(account);
free(reblog);
free(obj);
return response;
}
static void
gethome(char *token, char *host, Toot toots[], char *beforeid)
{
JSON *obj, *id, *content, *reblogcontent, *account, *reblogaccount, *handle,
*rebloghandle, *displayname, *avatar, *reblog, *mediaattachments, *type,
*previewurl, *remoteurl, *baseurl;
char *endpoint;
int i = 0;
if(beforeid != nil) {
endpoint = esmprint("timelines/home?max_id=%s", beforeid);
} else {
endpoint = "timelines/home";
}
obj = mastodonget(token, host, endpoint);
if(obj->t != JSONArray)
sysfatal("gethome: jsonparse: not an array");
for(JSONEl *p = obj->first; p != nil && i < TOOTSCOUNT; p = p->next) {
JSON *tootjson = p->val;
id = getjsonkey(tootjson, "id");
content = getjsonkey(tootjson, "content");
account = getjsonkey(tootjson, "account");
handle = getjsonkey(account, "acct");
displayname = getjsonkey(account, "display_name");
avatar = getjsonkey(account, "avatar_static");
reblog = getjsonkey(tootjson, "reblog");
mediaattachments = getjsonkey(tootjson, "media_attachments");
Toot *toot = emalloc(sizeof(Toot));
toot->id = estrdup((char *)id->s);
if(reblog->s == nil) {
toot->reblogged = 0;
toot->content = estrdup((char *)content->s);
} else {
reblogcontent = getjsonkey(reblog, "content");
reblogaccount = getjsonkey(reblog, "account");
rebloghandle = getjsonkey(reblogaccount, "acct");
toot->content = estrdup((char *)reblogcontent->s);
toot->rebloggedhandle = estrdup((char *)rebloghandle->s);
toot->reblogged = 1;
mediaattachments = getjsonkey(reblog, "media_attachments");
};
toot->handle = estrdup((char *)handle->s);
toot->displayname = estrdup((char *)displayname->s);
toot->avatarurl = estrdup((char *)avatar->s);
toot->attachmentscount = 0;
if(mediaattachments->s != nil) {
int j = 0;
for(JSONEl *at = mediaattachments->first; at != nil; at = at->next) {
JSON *attachmentjson = at->val;
Attachment *attachment = emalloc(sizeof(Attachment));
type = getjsonkey(attachmentjson, "type");
previewurl = getjsonkey(attachmentjson, "preview_url");
remoteurl = getjsonkey(attachmentjson, "remote_url");
baseurl = getjsonkey(attachmentjson, "url");
attachment->type = estrdup((char *)type->s);
if(strcmp(type->s, "image") == 0) {
attachment->url = estrdup((char *)previewurl->s);
} else if(remoteurl->s != 0) {
attachment->url = estrdup((char *)remoteurl->s);
} else {
attachment->url = estrdup((char*)baseurl->s);
}
toot->mediaattachments[j] = attachment;
toot->attachmentscount++;
j++;
}
}
toots[i] = *toot;
i++;
}
jsonfree(obj);
}
static void
getnotifications(char *token, char *host, Notification *notifs, char *filter)
{
JSON *obj, *id, *content, *displayname, *handle, *type, *account, *status, *statusid, *reblog;
char *endpoint;
int i = 0;
if(strlen(filter) > 0) {
endpoint = esmprint("notifications?types[]=%s", filter);
} else {
endpoint = "notifications";
}
obj = mastodonget(token, host, endpoint);
if(obj->t != JSONArray)
sysfatal("getnotifications: jsonparse: not an array");
for(JSONEl *p = obj->first; p != nil && i < NOTIFSCOUNT; p = p->next) {
JSON *notifjson = p->val;
id = getjsonkey(notifjson, "id");
type = getjsonkey(notifjson, "type");
account = getjsonkey(notifjson, "account");
displayname = getjsonkey(account, "display_name");
handle = getjsonkey(account, "acct");
Notification *notif = emalloc(sizeof(Notification));
notif->id = estrdup((char *)id->s);
notif->type = estrdup((char *)type->s);
notif->displayname = estrdup((char *)displayname->s);
notif->handle = estrdup((char *)handle->s);
if(strcmp(type->s, "follow") != 0) {
status = getjsonkey(notifjson, "status");
reblog = getjsonkey(status, "reblog");
if(strcmp(type->s, "favourite") == 0 && reblog->s != nil) {
// Weird case with snac where a favourite is on a reblog
// so the "content" key is inside the reblog object as "text"
content = getjsonkey(reblog, "text");
} else if (strcmp(type->s, "reblog") == 0 && reblog->s != nil) {
content = getjsonkey(reblog, "content");
} else {
content = getjsonkey(status, "content");
}
statusid = getjsonkey(status, "id");
notif->content = estrdup((char *)content->s);
notif->statusid = estrdup((char *)statusid->s);
}
notifs[i] = *notif;
i++;
}
jsonfree(obj);
}
static void
posttoot(char *token, char *host, char *text)
{
char *url;
url = esmprint("https://%s/api/v1/statuses", host);
httppost(token, url, esmprint("status=%s", text));
print("Posted:\n %s\n", text);
}
static 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);
if((obj = jsonparse(response)) == nil)
sysfatal("postattachment: jsonparse: not json");
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");
}
static void
boost(char *token, char *host, char *id)
{
perform(token, host, id, "reblog");
print("Boosted toot.");
}
static void
unboost(char *token, char *host, char *id)
{
perform(token, host, id, "unreblog");
print("Unboosted toot.");
}
static void
fav(char *token, char *host, char *id)
{
perform(token, host, id, "favourite");
print("Favorited toot.");
}
static void
unfav(char *token, char *host, char *id)
{
perform(token, host, id, "unfavourite");
print("Unfavorited toot.");
}
static void
reply(char *token, char *host, char *id)
{
char *content;
int fd;
char *s, *t, *u, *url;
Biobuf body;
t = nil;
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 = esmprint("%s", s);
free(s);
if(t != nil) {
url = esmprint("https://%s/api/v1/statuses", host);
content = esmprint("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");
}
}
static void
debug(char *token, char *host, char *id)
{
JSON *obj;
char *endpoint;
endpoint = esmprint("statuses/%s", id);
obj = mastodonget(token, host, endpoint);
print("%J\n", obj);
jsonfree(obj);
}
static void
usage(void)
{
sysfatal("usage: masto9 DOMAIN [COMMAND] [DATA]");
}
void
main(int argc, char **argv)
{
UserPasswd *p;
char *token, *host, *cmd, *text, *id, *filepath;
if(argc < 2)
usage();
JSONfmtinstall();
host = argv[1];
cmd = argv[2];
p = getcredentials(host);
token = p->passwd;
if(cmd == nil || strcmp(cmd, "home") == 0 || strcmp(cmd, "h") == 0) {
Toot toots[TOOTSCOUNT];
gethome(token, host, toots, nil);
displaytoots(toots, host);
} else if(strcmp(cmd, "toot") == 0 || strcmp(cmd, "t") == 0) {
text = argv[3];
posttoot(token, host, text);
} else if(strcmp(cmd, "tootwithfile") == 0 || strcmp(cmd, "tf") == 0) {
if(argc > 4) {
text = argv[3];
filepath = argv[4];
} else {
text = "";
filepath = argv[3];
}
postattachment(token, host, text, filepath);
} else if(strcmp(cmd, "fav") == 0 || strcmp(cmd, "f") == 0) {
id = argv[3];
fav(token, host, id);
} else if(strcmp(cmd, "unfav") == 0 || strcmp(cmd, "uf") == 0) {
id = argv[3];
unfav(token, host, id);
} else if(strcmp(cmd, "boost") == 0 || strcmp(cmd, "b") == 0) {
id = argv[3];
boost(token, host, id);
} else if(strcmp(cmd, "unboost") == 0 || strcmp(cmd, "ub") == 0) {
id = argv[3];
unboost(token, host, id);
} else if(strcmp(cmd, "reply") == 0 || strcmp(cmd, "r") == 0) {
id = argv[3];
reply(token, host, id);
} else if(strcmp(cmd, "debug") == 0) {
id = argv[3];
debug(token, host, id);
} else if(strcmp(cmd, "more") == 0) {
id = argv[3];
Toot toots[TOOTSCOUNT];
gethome(token, host, toots, id);
displaytoots(toots, host);
} else if(strcmp(cmd, "notifications") == 0 || strcmp(cmd, "n") == 0) {
Notification notifs[NOTIFSCOUNT];
getnotifications(token, host, notifs, "");
displaynotifications(notifs);
} else if(strcmp(cmd, "mentions") == 0 || strcmp(cmd, "m") == 0) {
Notification notifs[NOTIFSCOUNT];
getnotifications(token, host, notifs, "mention");
displaynotifications(notifs);
} else {
print("Unknown command %s.\n", cmd);
usage();
}
free(p);
exits(nil);
}