shithub: masto9

ref: 0711761832f4be41bb103cee39cb91d766f67f33
dir: /masto9.c/

View raw version
#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
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;
	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; 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");
				attachment->type = estrdup((char *)type->s);

				if(strcmp(type->s, "image") == 0) {
					attachment->url = estrdup((char *)previewurl->s);
				} else {
					attachment->url = estrdup((char *)remoteurl->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;
	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; p = p->next) {
		JSON *notifjson = p->val;

		id = getjsonkey(notifjson, "id");
		type = getjsonkey(notifjson, "type");
		if(strcmp(type->s, "follow") != 0) {
			status = getjsonkey(notifjson, "status");
			content = getjsonkey(status, "content");
		} else {
			content = jsonparse("");
		}
		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);
		if(strcmp(type->s, "follow") != 0) {
			notif->content = estrdup((char *)content->s);
		}
		notif->displayname = estrdup((char *)displayname->s);
		notif->handle = estrdup((char *)handle->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 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
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
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 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
usage(void)
{
	sysfatal("usage: masto9 DOMAIN [COMMAND] [DATA]");
}

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

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