ref: 416e913d695eea696560b129c98cd3038efa103a
dir: /masto9.c/
#include <u.h>
#include <libc.h>
#include <stdio.h>
#include <json.h>
#include <auth.h>
#include "masto9.h"
char *URL = "https://fedi.9til.de/api/v1/timelines/home";
char *POSTURL = "https://fedi.9til.de/api/v1/statuses";
char *NOTIFICATIONSURL = "https://fedi.9til.de/api/v1/notifications";
UserPasswd *
getcredentials(char *host)
{
UserPasswd* p;
p = auth_getuserpasswd(auth_getkey,
"proto=pass service=mastodon server=%s", host);
if(p == nil)
sysfatal("mastofs: failed to retrieve token: %r");
return p;
}
static JSON *
get_json_key(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
get_timeline(char *token, Toot toots[])
{
JSON *obj, *id, *content, *reblog_content, *account, *reblog_account, *username, *reblog_username, *display_name, *avatar, *reblog, *media_attachments, *type, *url;
char *response;
int i = 0;
response = httpget(token, URL);
obj = jsonparse(response);
if (obj == nil)
sysfatal("jsonparse: not json");
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 = get_json_key(toot_json, "id");
content = get_json_key(toot_json, "content");
account = get_json_key(toot_json, "account");
username = get_json_key(account, "username");
display_name = get_json_key(account, "display_name");
avatar = get_json_key(account, "avatar_static");
reblog = get_json_key(toot_json, "reblog");
media_attachments = get_json_key(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 {
//print("reblog: %J", toot_json);
reblog_content = get_json_key(reblog, "content");
reblog_account = get_json_key(reblog, "account");
reblog_username = get_json_key(reblog_account, "username");
toot.content = strdup((char *)reblog_content->s);
toot.reblogged = 1;
toot.reblogged_username = strdup((char *)reblog_username->s);
media_attachments = get_json_key(reblog, "media_attachments");
};
toot.username = strdup((char *)username->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;
//print("att: %J", attachment_json);
Attachment attachment = emalloc(sizeof(Attachment));
type = get_json_key(attachment_json, "type");
url = get_json_key(attachment_json, "preview_url");
attachment.type = strdup((char *)type->s);
attachment.url = strdup((char *)url->s);
toot.media_attachments[j] = attachment;
toot.attachments_count++;
j++;
}
}
toots[i] = toot;
i++;
}
jsonfree(obj);
}
void
get_notifications(char *token, Notification toots[])
{
JSON *obj, *id, *content, *username, *type, *account, *status;
char *response;
int i = 0;
response = httpget(token, NOTIFICATIONSURL);
obj = jsonparse(response);
if (obj == nil)
sysfatal("jsonparse: not json");
if (obj->t != JSONArray)
sysfatal("jsonparse: not an array");
for(JSONEl *p = obj->first; p != nil; p = p->next) {
JSON *toot_json = p->val;
content = emalloc(JSONNull);
id = get_json_key(toot_json, "id");
type = get_json_key(toot_json, "type");
if(strcmp(type->s, "follow") != 0) {
status = get_json_key(toot_json, "status");
content = get_json_key(status, "content");
}
account = get_json_key(toot_json, "account");
username = get_json_key(account, "username");
Notification toot = emalloc(sizeof(Notification));
toot.id = strdup((char *)id->s);
toot.type = strdup((char *)type->s);
toot.content = strdup((char *)content->s);
toot.username = strdup((char *)username->s);
toots[i] = toot;
i++;
}
jsonfree(obj);
}
void
post_toot(char *token, char *text)
{
char buf[TOOTBUFSIZE];
httppost(token, POSTURL, text);
print("RESPONSE %s", buf);
}
void
action_toot(char *token, char *id, char *action)
{
char *response;
char url[1024];
snprintf(url, sizeof(url), "%s/%s/%s", POSTURL, id, action);
print("URL %s\n", url);
response = httppost(token, url, "");
print("Response:\n %s\n", response);
}
void
boost_toot(char *token, char *id)
{
action_toot(token, id, "reblog");
}
void
unboost_toot(char *token, char *id)
{
action_toot(token, id, "unreblog");
}
void
fav_toot(char *token, char *id)
{
action_toot(token, id, "favourite");
}
void
unfav_toot(char *token, char *id)
{
action_toot(token, id, "unfavourite");
}
void
reply_toot(char *token, char *id, char *text)
{
char content[TOOTBUFSIZE];
snprintf(content, TOOTBUFSIZE, "in_reply_to_id=%s&status=%s", id, text);
httppost(token, POSTURL, content);
}
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;
}
void
remove_substring(char *str, char *sub)
{
int len = strlen(sub);
while ((str = strstr(str, sub))) {
memmove(str, str + len, strlen(str + len) + 1);
}
}
void
remove_tag(char *str, char *tag)
{
char *start = strstr(str, tag);
while (start) {
char *end = strchr(start, '>');
if (end) {
memmove(start, end + 1, strlen(end + 1) + 1);
} else {
*start = '\0';
break;
}
start = strstr(start, tag);
}
}
char *
cleanup(char *str)
{
remove_tag(str, "<span");
remove_substring(str, "</span>");
return str;
}
/* static void* */
/* slurp(int fd, int *n) */
/* { */
/* char *b; */
/* int r, sz; */
/* *n = 0; */
/* sz = 32; */
/* if((b = malloc(sz)) == nil) */
/* abort(); */
/* while(1){ */
/* if(*n + 1 == sz){ */
/* sz *= 2; */
/* if((b = realloc(b, sz)) == nil) */
/* abort(); */
/* } */
/* r = read(fd, b + *n, sz - *n - 1); */
/* if(r == 0) */
/* break; */
/* if(r == -1){ */
/* free(b); */
/* return nil; */
/* } */
/* *n += r; */
/* } */
/* b[*n] = 0; */
/* return b; */
/* } */
/* static int */
/* webopen(char *url, char *token, char *dir, int ndir) */
/* { */
/* char buf[16]; */
/* int n, cfd, conn; */
/* if((cfd = open("/mnt/web/clone", ORDWR)) == -1) */
/* return -1; */
/* if((n = read(cfd, buf, sizeof(buf)-1)) == -1) */
/* return -1; */
/* buf[n] = 0; */
/* conn = atoi(buf); */
/* bearer_token = concat("Authorization: Bearer ", token); */
/* if(fprint(cfd, "headers %s", bearer_token) <= 0) */
/* goto Error; */
/* if(fprint(cfd, "url %s", url) == -1) */
/* goto Error; */
/* snprint(dir, ndir, "/mnt/web/%d", conn); */
/* return cfd; */
/* Error: */
/* close(cfd); */
/* return -1; */
/* } */
/* static char* */
/* get(char *url, char *token, int *n) */
/* { */
/* char *r, dir[64], path[80]; */
/* int cfd, dfd; */
/* r = nil; */
/* dfd = -1; */
/* if((cfd = webopen(url, token, dir, sizeof(dir))) == -1) */
/* goto Error; */
/* snprint(path, sizeof(path), "%s/%s", dir, "body"); */
/* if((dfd = open(path, OREAD)) == -1) */
/* goto Error; */
/* r = slurp(dfd, n); */
/* Error: */
/* if(dfd != -1) close(dfd); */
/* if(cfd != -1) close(cfd); */
/* return r; */
/* } */
/* static char* */
/* post(char *url, char *buf, int nbuf, int *nret, Hdr *h) */
/* { */
/* char *r, dir[64], path[80]; */
/* int cfd, dfd, hfd, ok; */
/* r = nil; */
/* ok = 0; */
/* dfd = -1; */
/* if((cfd = webopen(url, dir, sizeof(dir))) == -1) */
/* goto Error; */
/* if(write(cfd, Contenttype, strlen(Contenttype)) == -1) */
/* goto Error; */
/* snprint(path, sizeof(path), "%s/%s", dir, "postbody"); */
/* if((dfd = open(path, OWRITE)) == -1) */
/* goto Error; */
/* if(write(dfd, buf, nbuf) != nbuf) */
/* goto Error; */
/* close(dfd); */
/* snprint(path, sizeof(path), "%s/%s", dir, "body"); */
/* if((dfd = open(path, OREAD)) == -1) */
/* goto Error; */
/* if((r = slurp(dfd, nret)) == nil) */
/* goto Error; */
/* if(h != nil){ */
/* snprint(path, sizeof(path), "%s/%s", dir, h->name); */
/* if((hfd = open(path, OREAD)) == -1) */
/* goto Error; */
/* if((h->val = slurp(hfd, &h->nval)) == nil) */
/* goto Error; */
/* close(hfd); */
/* } */
/* ok = 1; */
/* Error: */
/* if(dfd != -1) close(dfd); */
/* if(cfd != -1) close(cfd); */
/* if(!ok && h != nil) */
/* free(h->val); */
/* return r; */
/* } */
JSON *
mastodonget(char *token, char *host, char *endpoint)
{
JSON *obj;
char *response, url[MAX_URL];
snprintf(url, MAX_URL, "https://%s/api/v1/%s", host, endpoint);
response = httpget(token, url);
obj = jsonparse(response);
if (obj == nil)
sysfatal("jsonparse: not json");
if (obj->t != JSONArray)
sysfatal("jsonparse: not an array");
return(obj);
}
void
usage(void)
{
sysfatal("usage: masto9 url");
}
//echo 'proto=pass service=mastodon server=fedi.9til.de pass=aStT5pM-8RThsxzlZ140YKFZLDmIXSbn4Y6cbIpntDg user=julienxx' > /mnt/factotum/ctl
void
main(int argc, char**argv)
{
UserPasswd *p;
char *token, *server, *command, *text, *id;
if(argc < 2)
usage();
JSONfmtinstall();
server = argv[1];
command = argv[2];
p = getcredentials(server);
token = p->passwd;
if(command == nil) {
get_timeline(token, toots);
for (int i=0;i<TOOTS_COUNT;i++) {
Toot toot = toots[i];
print("\n\n——————————————————————————————————————————————————\n");
if(toot.reblogged == 1) {
print("⊙ %s retooted %s:\n", toot.username, toot.reblogged_username);
} else {
print("⊙ %s:\n", toot.username);
}
print("\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];
print("\n[%s] %s", attachment.type, attachment.url);
}
print("\n");
}
print("\nReply[%s] | Boost[%s] | Favorite[%s]", toot.id, toot.id, toot.id);
}
print("\n");
} else if(strcmp(command, "toot") == 0) {
text = argv[3];
post_toot(token, text);
} else if(strcmp(command, "fav") == 0) {
id = argv[3];
fav_toot(token, id);
} else if(strcmp(command, "unfav") == 0) {
id = argv[3];
unfav_toot(token, id);
} else if(strcmp(command, "boost") == 0) {
id = argv[3];
boost_toot(token, id);
} else if(strcmp(command, "unboost") == 0) {
id = argv[3];
unboost_toot(token, id);
} else if(strcmp(command, "reply") == 0) {
id = argv[3];
reply_toot(token, id, "@sirjofri@mastodon.sdf.org now this should be a proper reply");
} else if(strcmp(command, "notifications") == 0) {
get_notifications(token, notifs);
for (int i=0;i<NOTIFS_COUNT;i++) {
Notification notif = notifs[i];
if (strcmp(notif.type, "reblog") == 0) {
print("⊙ %s retooted\n %s\n", notif.username, fmthtml(cleanup(notif.content)));
} else if (strcmp(notif.type, "favourite") == 0) {
print("⊙ %s favorited\n %s\n", notif.username, fmthtml(cleanup(notif.content)));
} else if (strcmp(notif.type, "mention") == 0) {
print("⊙ %s mentioned you\n %s\n", notif.username, fmthtml(cleanup(notif.content)));
} else if (strcmp(notif.type, "follow") == 0) {
print("⊙ %s followed you\n", notif.username);
} else if (strcmp(notif.type, "poll") == 0) {
print("⊙ %s poll ended\n %s\n", notif.username, fmthtml(cleanup(notif.content)));
}
}
}
exits(nil);
}