ssb: Add a get_contacts command to enumerate follows, blocks, and friends.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
This commit is contained in:
parent
287c6c06e1
commit
f28e409ea5
99
src/main.c
99
src/main.c
@ -152,6 +152,7 @@ static int _tf_command_store_blob(const char* file, int argc, char* argv[]);
|
|||||||
static int _tf_command_get_sequence(const char* file, int argc, char* argv[]);
|
static int _tf_command_get_sequence(const char* file, int argc, char* argv[]);
|
||||||
static int _tf_command_get_identity(const char* file, int argc, char* argv[]);
|
static int _tf_command_get_identity(const char* file, int argc, char* argv[]);
|
||||||
static int _tf_command_get_profile(const char* file, int argc, char* argv[]);
|
static int _tf_command_get_profile(const char* file, int argc, char* argv[]);
|
||||||
|
static int _tf_command_get_contacts(const char* file, int argc, char* argv[]);
|
||||||
static int _tf_command_test(const char* file, int argc, char* argv[]);
|
static int _tf_command_test(const char* file, int argc, char* argv[]);
|
||||||
static int _tf_command_verify(const char* file, int argc, char* argv[]);
|
static int _tf_command_verify(const char* file, int argc, char* argv[]);
|
||||||
static int _tf_command_usage(const char* file);
|
static int _tf_command_usage(const char* file);
|
||||||
@ -173,6 +174,7 @@ const command_t k_commands[] = {
|
|||||||
{ "get_sequence", _tf_command_get_sequence, "Get the last sequence number for a feed." },
|
{ "get_sequence", _tf_command_get_sequence, "Get the last sequence number for a feed." },
|
||||||
{ "get_identity", _tf_command_get_identity, "Get the server account identity." },
|
{ "get_identity", _tf_command_get_identity, "Get the server account identity." },
|
||||||
{ "get_profile", _tf_command_get_profile, "Get profile information for the given identity." },
|
{ "get_profile", _tf_command_get_profile, "Get profile information for the given identity." },
|
||||||
|
{ "get_contacts", _tf_command_get_contacts, "Get information about followed, blocked, and friend identities." },
|
||||||
{ "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." },
|
{ "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." },
|
||||||
{ "store_blob", _tf_command_store_blob, "Write a file to the blob store." },
|
{ "store_blob", _tf_command_store_blob, "Write a file to the blob store." },
|
||||||
{ "verify", _tf_command_verify, "Verify a feed." },
|
{ "verify", _tf_command_verify, "Verify a feed." },
|
||||||
@ -944,7 +946,7 @@ static int _tf_command_get_profile(const char* file, int argc, char* argv[])
|
|||||||
tf_printf("\n%s get_profile [options]\n\n", file);
|
tf_printf("\n%s get_profile [options]\n\n", file);
|
||||||
tf_printf("options:\n");
|
tf_printf("options:\n");
|
||||||
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
|
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
|
||||||
tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n");
|
tf_printf(" -i, --identity identity Account for which to get profile information.\n");
|
||||||
tf_printf(" -h, --help Show this usage information.\n");
|
tf_printf(" -h, --help Show this usage information.\n");
|
||||||
tf_free((void*)default_db_path);
|
tf_free((void*)default_db_path);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
@ -961,6 +963,101 @@ static int _tf_command_get_profile(const char* file, int argc, char* argv[])
|
|||||||
return profile != NULL;
|
return profile != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _tf_command_get_contacts(const char* file, int argc, char* argv[])
|
||||||
|
{
|
||||||
|
const char* default_db_path = _get_db_path();
|
||||||
|
const char* db_path = default_db_path;
|
||||||
|
const char* identity = NULL;
|
||||||
|
bool show_usage = false;
|
||||||
|
|
||||||
|
while (!show_usage)
|
||||||
|
{
|
||||||
|
static const struct option k_options[] = {
|
||||||
|
{ "db-path", required_argument, NULL, 'd' },
|
||||||
|
{ "id", required_argument, NULL, 'i' },
|
||||||
|
{ "help", no_argument, NULL, 'h' },
|
||||||
|
{ 0 },
|
||||||
|
};
|
||||||
|
int c = getopt_long(argc, argv, "d:i:h", k_options, NULL);
|
||||||
|
if (c == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '?':
|
||||||
|
case 'h':
|
||||||
|
default:
|
||||||
|
show_usage = true;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
db_path = optarg;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
identity = optarg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_usage || !identity)
|
||||||
|
{
|
||||||
|
tf_printf("\n%s get_contacts [options]\n\n", file);
|
||||||
|
tf_printf("options:\n");
|
||||||
|
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
|
||||||
|
tf_printf(" -i, --identity identity Account from which to get contact information.\n");
|
||||||
|
tf_printf(" -h, --help Show this usage information.\n");
|
||||||
|
tf_free((void*)default_db_path);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue contacts = JS_NewObject(context);
|
||||||
|
JSValue follows = JS_NewObject(context);
|
||||||
|
JSValue blocks = JS_NewObject(context);
|
||||||
|
JSValue friends = JS_NewObject(context);
|
||||||
|
tf_ssb_following_t* following = tf_ssb_db_following_deep(ssb, &identity, 1, 1, true);
|
||||||
|
tf_ssb_following_t* following2 = tf_ssb_db_following_deep(ssb, &identity, 1, 2, false);
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
|
for (int i = 0; *following[i].id; i++)
|
||||||
|
{
|
||||||
|
if (following[i].followed_by_count)
|
||||||
|
{
|
||||||
|
const char* name = tf_ssb_db_get_profile_name(db, following[i].id);
|
||||||
|
JS_SetPropertyStr(context, follows, following[i].id, name ? JS_NewString(context, name) : JS_NULL);
|
||||||
|
tf_free((void*)name);
|
||||||
|
}
|
||||||
|
if (following[i].blocked_by_count)
|
||||||
|
{
|
||||||
|
const char* name = tf_ssb_db_get_profile_name(db, following[i].id);
|
||||||
|
JS_SetPropertyStr(context, blocks, following[i].id, name ? JS_NewString(context, name) : JS_NULL);
|
||||||
|
tf_free((void*)name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; *following2[i].id; i++)
|
||||||
|
{
|
||||||
|
const char* name = tf_ssb_db_get_profile_name(db, following2[i].id);
|
||||||
|
JS_SetPropertyStr(context, friends, following2[i].id, name ? JS_NewString(context, name) : JS_NULL);
|
||||||
|
tf_free((void*)name);
|
||||||
|
}
|
||||||
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
|
JS_SetPropertyStr(context, contacts, "follows", follows);
|
||||||
|
JS_SetPropertyStr(context, contacts, "blocks", blocks);
|
||||||
|
JS_SetPropertyStr(context, contacts, "friends", friends);
|
||||||
|
tf_free(following2);
|
||||||
|
tf_free(following);
|
||||||
|
JSValue json = JS_JSONStringify(context, contacts, JS_NULL, JS_NewInt32(context, 2));
|
||||||
|
const char* json_str = JS_ToCString(context, json);
|
||||||
|
tf_printf("%s\n", json_str);
|
||||||
|
JS_FreeCString(context, json_str);
|
||||||
|
JS_FreeValue(context, json);
|
||||||
|
JS_FreeValue(context, contacts);
|
||||||
|
tf_ssb_destroy(ssb);
|
||||||
|
tf_free((void*)default_db_path);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
static int _tf_command_verify(const char* file, int argc, char* argv[])
|
static int _tf_command_verify(const char* file, int argc, char* argv[])
|
||||||
{
|
{
|
||||||
const char* identity = NULL;
|
const char* identity = NULL;
|
||||||
|
58
src/ssb.db.c
58
src/ssb.db.c
@ -1304,9 +1304,9 @@ static bool _is_blocked_by_active_blocks(const char* id, const block_node_t* blo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static following_t* _make_following_node(const char* id, following_t*** following, int* following_count, block_node_t* blocks)
|
static following_t* _make_following_node(const char* id, following_t*** following, int* following_count, block_node_t* blocks, bool include_blocks)
|
||||||
{
|
{
|
||||||
if (_is_blocked_by_active_blocks(id, blocks))
|
if (!include_blocks && _is_blocked_by_active_blocks(id, blocks))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -1333,7 +1333,7 @@ static following_t* _make_following_node(const char* id, following_t*** followin
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, block_node_t* active_blocks)
|
static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, block_node_t* active_blocks, bool include_blocks)
|
||||||
{
|
{
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
sqlite3_stmt* statement = NULL;
|
sqlite3_stmt* statement = NULL;
|
||||||
@ -1352,7 +1352,7 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
|
|||||||
if (sqlite3_column_type(statement, 1) != SQLITE_NULL)
|
if (sqlite3_column_type(statement, 1) != SQLITE_NULL)
|
||||||
{
|
{
|
||||||
bool is_following = sqlite3_column_int(statement, 1) != 0;
|
bool is_following = sqlite3_column_int(statement, 1) != 0;
|
||||||
following_t* next = _make_following_node(contact, following, following_count, active_blocks);
|
following_t* next = _make_following_node(contact, following, following_count, active_blocks, include_blocks);
|
||||||
if (next)
|
if (next)
|
||||||
{
|
{
|
||||||
if (is_following)
|
if (is_following)
|
||||||
@ -1374,7 +1374,7 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
|
|||||||
if (sqlite3_column_type(statement, 2) != SQLITE_NULL)
|
if (sqlite3_column_type(statement, 2) != SQLITE_NULL)
|
||||||
{
|
{
|
||||||
bool is_blocking = sqlite3_column_int(statement, 2) != 0;
|
bool is_blocking = sqlite3_column_int(statement, 2) != 0;
|
||||||
following_t* next = _make_following_node(contact, following, following_count, active_blocks);
|
following_t* next = _make_following_node(contact, following, following_count, active_blocks, include_blocks);
|
||||||
if (next)
|
if (next)
|
||||||
{
|
{
|
||||||
if (is_blocking)
|
if (is_blocking)
|
||||||
@ -1400,13 +1400,14 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
|
|||||||
tf_ssb_release_db_reader(ssb, db);
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _get_following(tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks)
|
static void _get_following(
|
||||||
|
tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks, bool include_blocks)
|
||||||
{
|
{
|
||||||
entry->depth = tf_min(depth, entry->depth);
|
entry->depth = tf_min(depth, entry->depth);
|
||||||
if (depth < max_depth && !entry->populated && !_is_blocked_by_active_blocks(entry->id, active_blocks))
|
if (depth < max_depth && !entry->populated && !_is_blocked_by_active_blocks(entry->id, active_blocks))
|
||||||
{
|
{
|
||||||
entry->populated = true;
|
entry->populated = true;
|
||||||
_populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks);
|
_populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks, include_blocks);
|
||||||
|
|
||||||
if (depth < max_depth)
|
if (depth < max_depth)
|
||||||
{
|
{
|
||||||
@ -1415,28 +1416,28 @@ static void _get_following(tf_ssb_t* ssb, following_t* entry, following_t*** fol
|
|||||||
{
|
{
|
||||||
if (!_has_following_entry(entry->following[i]->id, entry->blocking, entry->blocking_count))
|
if (!_has_following_entry(entry->following[i]->id, entry->blocking, entry->blocking_count))
|
||||||
{
|
{
|
||||||
_get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks);
|
_get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth)
|
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks)
|
||||||
{
|
{
|
||||||
following_t** following = NULL;
|
following_t** following = NULL;
|
||||||
int following_count = 0;
|
int following_count = 0;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL);
|
following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL, include_blocks);
|
||||||
_get_following(ssb, entry, &following, &following_count, 0, depth, NULL);
|
_get_following(ssb, entry, &following, &following_count, 0, depth, NULL, include_blocks);
|
||||||
entry->ref_count++;
|
entry->ref_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int actual_following_count = 0;
|
int actual_following_count = 0;
|
||||||
for (int i = 0; i < following_count; i++)
|
for (int i = 0; i < following_count; i++)
|
||||||
{
|
{
|
||||||
if (following[i]->ref_count > 0)
|
if (following[i]->ref_count > 0 || include_blocks)
|
||||||
{
|
{
|
||||||
actual_following_count++;
|
actual_following_count++;
|
||||||
}
|
}
|
||||||
@ -1448,7 +1449,7 @@ tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, in
|
|||||||
int write_index = 0;
|
int write_index = 0;
|
||||||
for (int i = 0; i < following_count; i++)
|
for (int i = 0; i < following_count; i++)
|
||||||
{
|
{
|
||||||
if (following[i]->ref_count > 0)
|
if (following[i]->ref_count > 0 || include_blocks)
|
||||||
{
|
{
|
||||||
snprintf(result[write_index].id, sizeof(result[write_index].id), "%s", following[i]->id);
|
snprintf(result[write_index].id, sizeof(result[write_index].id), "%s", following[i]->id);
|
||||||
result[write_index].following_count = following[i]->following_count;
|
result[write_index].following_count = following[i]->following_count;
|
||||||
@ -1477,8 +1478,8 @@ const char** tf_ssb_db_following_deep_ids(tf_ssb_t* ssb, const char** ids, int c
|
|||||||
int following_count = 0;
|
int following_count = 0;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL);
|
following_t* entry = _make_following_node(ids[i], &following, &following_count, NULL, false);
|
||||||
_get_following(ssb, entry, &following, &following_count, 0, depth, NULL);
|
_get_following(ssb, entry, &following, &following_count, 0, depth, NULL, false);
|
||||||
entry->ref_count++;
|
entry->ref_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2109,3 +2110,30 @@ const char* tf_ssb_db_get_profile(sqlite3* db, const char* id)
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id)
|
||||||
|
{
|
||||||
|
const char* result = NULL;
|
||||||
|
sqlite3_stmt* statement;
|
||||||
|
if (sqlite3_prepare(db,
|
||||||
|
"SELECT name FROM (SELECT messages.author, RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, "
|
||||||
|
"messages.content ->> 'name' AS name FROM messages WHERE messages.author = ? "
|
||||||
|
"AND json_extract(messages.content, '$.type') = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL) "
|
||||||
|
"WHERE author_rank = 1",
|
||||||
|
-1, &statement, NULL) == SQLITE_OK)
|
||||||
|
{
|
||||||
|
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
|
||||||
|
{
|
||||||
|
if (sqlite3_step(statement) == SQLITE_ROW)
|
||||||
|
{
|
||||||
|
result = tf_strdup((const char*)sqlite3_column_text(statement, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
13
src/ssb.db.h
13
src/ssb.db.h
@ -308,9 +308,10 @@ const char** tf_ssb_db_following_deep_ids(tf_ssb_t* ssb, const char** ids, int c
|
|||||||
** @param ids An array of identities.
|
** @param ids An array of identities.
|
||||||
** @param count The number of identities.
|
** @param count The number of identities.
|
||||||
** @param depth The following depth to use (prefer 2).
|
** @param depth The following depth to use (prefer 2).
|
||||||
** @return An array of information about visible accounts. Fere with tf_free().
|
** @param include_blocks Whether to include blocked identities in results.
|
||||||
|
** @return An array of information about visible accounts. Free with tf_free().
|
||||||
*/
|
*/
|
||||||
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth);
|
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Get all visible identities from all local accounts.
|
** Get all visible identities from all local accounts.
|
||||||
@ -493,6 +494,14 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou
|
|||||||
*/
|
*/
|
||||||
const char* tf_ssb_db_get_profile(sqlite3* db, const char* id);
|
const char* tf_ssb_db_get_profile(sqlite3* db, const char* id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Get the latest profile name for the given identity.
|
||||||
|
** @param db The database.
|
||||||
|
** @param id The identity.
|
||||||
|
** @return The name. Free with tf_free().
|
||||||
|
*/
|
||||||
|
const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
|
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
|
||||||
** @param user_data User data registered with the authorizer.
|
** @param user_data User data registered with the authorizer.
|
||||||
|
@ -2170,7 +2170,7 @@ typedef struct _following_t
|
|||||||
static void _tf_ssb_following_work(tf_ssb_t* ssb, void* user_data)
|
static void _tf_ssb_following_work(tf_ssb_t* ssb, void* user_data)
|
||||||
{
|
{
|
||||||
following_t* following = user_data;
|
following_t* following = user_data;
|
||||||
following->out_following = tf_ssb_db_following_deep(ssb, following->ids, following->ids_count, following->depth);
|
following->out_following = tf_ssb_db_following_deep(ssb, following->ids, following->ids_count, following->depth, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _tf_ssb_following_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
static void _tf_ssb_following_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user