shithub: masto9

ref: 22b180670881db7c4850e7fd9786ffcb70498057
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"

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

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;

  if(after != nil) {
    endpoint = esmprint("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 = estrdup((char *)id->s);

		if(reblog->s == nil) {
			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;

			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;

		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 = estrdup((char *)type->s);

        if(strcmp(type->s, "image") == 0){
          attachment->url = estrdup((char *)preview_url->s);
        } else {
          attachment->url = estrdup((char *)remote_url->s);
        }

				toot->media_attachments[j] = attachment;
				toot->attachments_count++;
				j++;
			}
		}
		toots[i] = *toot;
		i++;
	}
	jsonfree(obj);
}

void
getnotifications(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 = estrdup((char *)id->s);

    notif->type = estrdup((char *)type->s);
    if(strcmp(type->s, "follow") != 0) {
      notif->content = estrdup((char *)content->s);
    }
		notif->display_name = estrdup((char *)display_name->s);
		notif->handle = estrdup((char *)handle->s);

		notifs[i] = *notif;
		i++;
	}
	jsonfree(obj);
}

void
posttoot(char *token, char *host, char *text)
{
  char *url;
  url = esmprint("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, *response;

  endpoint = concat("statuses/", 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;
}

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;
  url = esmprint("%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;
  int fd, wait;
  char *s, *t, *u, *url;
  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 = esmprint("%s", s);
    free(s);
    if(t != nil){
      url = esmprint("%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");
  }
}

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;

	url = esmprint("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 = esmprint("%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 = esmprint("%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);
}

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


//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, *filepath;

  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, "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);
  } 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, "debug") == 0) {
    id = argv[3];
    debug(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];
    getnotifications(token, host, notifs);
    displaynotifications(notifs);
  }

  free(p);
	exits(nil);
}