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