diff --git a/Makefile b/Makefile index d731d5d..2e3973a 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,17 @@ all: gcc -o fedi_post fedi_post.c $(shell pkg-config --cflags --libs r_util r_io libcurl) + gcc -o fedi_notifs fedi_notifs.c $(shell pkg-config --cflags --libs r_util r_io libcurl libjson) clean: rm fedi_post + rm fedi_notifs install: cp fedi_post /usr/bin + cp fedi_notifs /usr/bin chmod 755 /usr/bin/fedi_post + chmod 755 /usr/bin/fedi_notifs uninstall: rm /usr/bin/fedi_post + rm /usr/bin/fedi_notifs diff --git a/README.md b/README.md index ff22880..570cec6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ * libcurl * radare2 (build and install it from https://github.com/radareorg/radare2) +* libjson (https://github.com/Cebtenzzre/libjson) # Build @@ -37,8 +38,9 @@ sdb ~/.fedi_tools.sdb password= # The tools -So far there is only `fedi_post` which let's you make a post on fedi. -You can use it like this: +So far there are only `fedi_post` and `fedi_notifs`. +The latter fetches your notifications for you, nothing too exciting. +The former let's you make a post on fedi. You can use it like this: ```sh fedi_post diff --git a/fedi_notifs.c b/fedi_notifs.c new file mode 100644 index 0000000..bf8fb30 --- /dev/null +++ b/fedi_notifs.c @@ -0,0 +1,273 @@ +/* fedi_tools - GPL - Copyright 2023 - condret */ + +#include +#include +#include +#include +#include +#include + +__attribute__((constructor)) void foo(void) { + curl_global_init (CURL_GLOBAL_ALL); +} + +__attribute__((destructor)) void bla(void) { + curl_global_cleanup (); +} + +typedef struct fedi_notifs_config_t { + char *instance; + char *creds; +} FediNotifsCFG; + +// workaround for a bug in sdb +static Sdb *load_sdb (char *path) { + Sdb *ret = sdb_new0 (); + Sdb *on_disk = sdb_new0 (); + if (sdb_open (on_disk, path) < 0) { + sdb_free (ret); + sdb_free (on_disk); + return NULL; + } + sdb_copy (on_disk, ret); + sdb_close (on_disk); + sdb_free (on_disk); + return ret; +} + +static FediNotifsCFG *get_cfg () { + FediNotifsCFG *fncfg; + if (!(fncfg = R_NEW0 (FediNotifsCFG))) { + return NULL; + } + char *config_path = r_file_home (".fedi_tools.sdb"); + if (!config_path) { + free (fncfg); + return NULL; + } + Sdb *db = load_sdb (config_path); + if (!db) { + eprintf ("couldn't open %s :(\n", config_path); + sdb_free (db); + free (config_path); + free (fncfg); + return NULL; + } + bool fail = false; + if (!sdb_exists (db, "instance")) { + eprintf ("'instance' is not set in %s :(\n", config_path); + fail = true; + } + if (!sdb_exists (db, "user")) { + eprintf ("'user' is not set in %s :(\n", config_path); + fail = true; + } + if (!sdb_exists (db, "password")) { + eprintf ("'password' is not set in %s :(\n", config_path); + fail = true; + } + R_FREE (config_path); + if (fail) { + sdb_free (db); + free (fncfg); + return NULL; + } + fncfg->instance = sdb_get (db, "instance", NULL); + fncfg->creds = r_str_newf ("%s:%s", sdb_const_get (db, "user", NULL), + sdb_const_get (db, "password", NULL)); + sdb_free (db); + if (!fncfg->instance || !fncfg->creds) { + free (fncfg->instance); + free (fncfg->creds); + free (fncfg); + return NULL; + } + return fncfg; +} + +static void cfg_free (FediNotifsCFG *cfg) { + if (cfg) { + free (cfg->instance); + free (cfg->creds); + free (cfg); + } +} + +typedef struct fedi_notif_t { + char *acct; + char *display_name; + char *type; +} FediNotif; + +typedef struct json_notif_parser_t { + ut32 depth; + RQueue *notif_queue; + RStack *code_stack; + FediNotif *cur; +} JsonNParser; + +typedef int (*HandleJson)(JsonNParser *jnp, int type, const char *data, ut32 len); + +static int handle_account_acct (JsonNParser *jnp, int type, const char *data, ut32 len) { + jnp->cur->acct = R_NEWS0 (char, len + 1); + memcpy (jnp->cur->acct, data, len); + r_stack_pop (jnp->code_stack); + return 0; +} + +static int handle_account_display_name (JsonNParser *jnp, int type, const char *data, ut32 len) { + jnp->cur->display_name = R_NEWS0 (char, len + 1); + memcpy (jnp->cur->display_name, data, len); + r_stack_pop (jnp->code_stack); + return 0; +} + +static int handle_account (JsonNParser *jnp, int type, const char *data, ut32 len) { + if (type == JSON_OBJECT_BEGIN) { + jnp->depth++; + return 0; + } + if (type == JSON_KEY && len == 4 && !memcmp (data, "acct", 4)) { + r_stack_push (jnp->code_stack, handle_account_acct); + return 0; + } + if (type == JSON_KEY && len == 12 && !memcmp (data, "display_name", 12)) { + r_stack_push (jnp->code_stack, handle_account_display_name); + return 0; + } + if (type == JSON_OBJECT_END) { + if (2 == jnp->depth--) { + r_stack_pop (jnp->code_stack); + } + return 0; + } + return 0; +} + +static int handle_status (JsonNParser *jnp, int type, const char *data, ut32 len) { + if (type == JSON_OBJECT_BEGIN) { + jnp->depth++; + return 0; + } + if (type == JSON_OBJECT_END) { + if (2 == jnp->depth--) { + r_stack_pop (jnp->code_stack); + } + return 0; + } + return 0; +} + +static int handle_type (JsonNParser *jnp, int type, const char *data, ut32 len) { + jnp->cur->type = R_NEWS0 (char, len + 1); + memcpy (jnp->cur->type, data, len); + r_stack_pop (jnp->code_stack); + return 0; +} + +static int parser_cb (void *user, int type, const char *data, uint32_t length) { + JsonNParser *jnp = (JsonNParser *)user; + HandleJson h = (HandleJson)r_stack_peek (jnp->code_stack); + if (h) { + return h(jnp, type, data, length); + } + if (type == JSON_OBJECT_BEGIN) { + if (jnp->depth == 0) { + jnp->cur = R_NEW0 (FediNotif); + } + jnp->depth++; + } + if (type == JSON_KEY) { + if (length == 7 && !memcmp (data, "account", 7)) { + r_stack_push (jnp->code_stack, handle_account); + return 0; + } + if (length == 6 && !memcmp (data, "status", 6)) { + r_stack_push (jnp->code_stack, handle_status); + return 0; + } + if (length == 4 && !memcmp (data, "type", 4)) { + r_stack_push (jnp->code_stack, handle_type); + return 0; + } + } + if (type == JSON_OBJECT_END) { + if (jnp->depth == 1) { + r_queue_enqueue (jnp->notif_queue, jnp->cur); + jnp->cur = NULL; + } + jnp->depth--; + } + return 0; +} + +static size_t write_data(void *ptr, size_t size, size_t nmemb, void *user) { + RIODesc *desc = (RIODesc *)user; + size_t dlen = size * nmemb; + while (dlen > 0x7fffffff) { + r_io_desc_write (desc, (const ut8 *)ptr, 0x7fffffff); + ptr += 0x7fffffff; + dlen -= 0x7fffffff; + } + r_io_desc_write (desc, (const ut8 *)ptr, (int)dlen); + return size * nmemb; +} + +int main (int argc, char *argv[]) { + FediNotifsCFG *cfg = get_cfg (); + if (!cfg) { + return -1; + } + char *notifs_url = r_str_newf ("%s/api/v1/notifications", cfg->instance); + if (!notifs_url) { + cfg_free (cfg); + return -1; + } + RIO *io = r_io_new (); + if (!io) { + free (notifs_url); + cfg_free (cfg); + return -1; + } + int tree_fd = r_io_fd_open (io, "treebuf://", R_PERM_RW, 0); + CURL *curl = curl_easy_init(); + if(!curl) { + r_io_free (io); + free (notifs_url); + cfg_free (cfg); + return -1; + } + curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt (curl, CURLOPT_URL, notifs_url); + curl_easy_setopt (curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_BASIC); + curl_easy_setopt (curl, CURLOPT_USERPWD, cfg->creds); + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, r_io_desc_get (io, tree_fd)); + curl_easy_perform (curl); + ut64 rec_len = r_io_fd_seek (io, tree_fd, 0LL, R_IO_SEEK_CUR); + curl_easy_cleanup (curl); + free (notifs_url); + cfg_free (cfg); + char *data = malloc (rec_len + 1); + r_io_fd_read_at (io, tree_fd, 0ULL, data, (int)rec_len); + data[rec_len] = '\x00'; + r_io_free (io); + JsonNParser jnp = {false, r_queue_new (2), r_stack_new (8)}; + json_parser parser; + json_parser_init (&parser, NULL, parser_cb, &jnp); + json_parser_string (&parser, data, rec_len, NULL); + free (data); + json_parser_free (&parser); + r_stack_free (jnp.code_stack); + while (!r_queue_is_empty (jnp.notif_queue)) { + FediNotif *notif = (FediNotif *)r_queue_dequeue (jnp.notif_queue); + printf ("%s: @%s\n%s\n", notif->display_name, notif->acct, notif->type); + puts ("==========================================\n"); + free (notif->acct); + free (notif->display_name); + free (notif->type); + free (notif); + } + r_queue_free (jnp.notif_queue); + return 0; +}