Add a button in the profile editor to ask the server to follow you. I'm hoping this helps replicating accounts that are otherwise difficult to discover.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4558 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
6dae2f0749
commit
6db1a097aa
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🐌"
|
"emoji": "🐌",
|
||||||
|
"previous": "&Vm+p2yc+lC+wd71VVNRmEGpaePRsnwyknk+qI9hphjQ=.sha256"
|
||||||
}
|
}
|
@ -18,6 +18,12 @@ tfrpc.register(async function databaseSet(key, value) {
|
|||||||
tfrpc.register(async function createIdentity() {
|
tfrpc.register(async function createIdentity() {
|
||||||
return ssb.createIdentity();
|
return ssb.createIdentity();
|
||||||
});
|
});
|
||||||
|
tfrpc.register(async function getServerIdentity() {
|
||||||
|
return ssb.getServerIdentity();
|
||||||
|
});
|
||||||
|
tfrpc.register(async function setServerFollowingMe(id, following) {
|
||||||
|
return ssb.setServerFollowingMe(id, following);
|
||||||
|
});
|
||||||
tfrpc.register(async function getIdentities() {
|
tfrpc.register(async function getIdentities() {
|
||||||
return ssb.getIdentities();
|
return ssb.getIdentities();
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,7 @@ class TfProfileElement extends LitElement {
|
|||||||
id: {type: String},
|
id: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
size: {type: Number},
|
size: {type: Number},
|
||||||
|
server_follows_me: {type: Boolean},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +25,23 @@ class TfProfileElement extends LitElement {
|
|||||||
this.id = null;
|
this.id = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
this.size = 0;
|
this.size = 0;
|
||||||
|
this.server_follows_me = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initial_load() {
|
||||||
|
this.server_follows_me = undefined;
|
||||||
|
let server_id = await tfrpc.rpc.getServerIdentity();
|
||||||
|
let followed = await tfrpc.rpc.query(`
|
||||||
|
SELECT json_extract(content, '$.following') AS following FROM messages
|
||||||
|
WHERE author = ? AND
|
||||||
|
json_extract(content, '$.type') = 'contact' AND
|
||||||
|
json_extract(content, '$.contact') = ? ORDER BY sequence DESC LIMIT 1
|
||||||
|
`, [server_id, this.whoami]);
|
||||||
|
let is_followed = false;
|
||||||
|
for (let row of followed) {
|
||||||
|
is_followed = row.following != 0;
|
||||||
|
}
|
||||||
|
this.server_follows_me = is_followed;
|
||||||
}
|
}
|
||||||
|
|
||||||
modify(change) {
|
modify(change) {
|
||||||
@ -103,7 +121,23 @@ class TfProfileElement extends LitElement {
|
|||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async server_follow_me(follow) {
|
||||||
|
try {
|
||||||
|
await tfrpc.rpc.setServerFollowingMe(this.whoami, follow);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.initial_load();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.id == this.whoami && this.editing && this.server_follows_me === undefined) {
|
||||||
|
this.initial_load();
|
||||||
|
}
|
||||||
let self = this;
|
let self = this;
|
||||||
let profile = this.users[this.id] || {};
|
let profile = this.users[this.id] || {};
|
||||||
tfrpc.rpc.query(
|
tfrpc.rpc.query(
|
||||||
@ -116,9 +150,16 @@ class TfProfileElement extends LitElement {
|
|||||||
let block;
|
let block;
|
||||||
if (this.id === this.whoami) {
|
if (this.id === this.whoami) {
|
||||||
if (this.editing) {
|
if (this.editing) {
|
||||||
|
let server_follow;
|
||||||
|
if (this.server_follows_me === true) {
|
||||||
|
server_follow = html`<input type="button" value="Server, Stop Following Me" @click=${() => this.server_follow_me(false)}></input>`;
|
||||||
|
} else if (this.server_follows_me === false) {
|
||||||
|
server_follow = html`<input type="button" value="Server, Follow Me" @click=${() => this.server_follow_me(true)}></input>`;
|
||||||
|
}
|
||||||
edit = html`
|
edit = html`
|
||||||
<input type="button" value="Save Profile" @click=${this.save_edits}></input>
|
<input type="button" value="Save Profile" @click=${this.save_edits}></input>
|
||||||
<input type="button" value="Discard" @click=${this.discard_edits}></input>
|
<input type="button" value="Discard" @click=${this.discard_edits}></input>
|
||||||
|
${server_follow}
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
edit = html`<input type="button" value="Edit Profile" @click=${this.edit}></input>`;
|
edit = html`<input type="button" value="Edit Profile" @click=${this.edit}></input>`;
|
||||||
|
@ -421,6 +421,13 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
return ssb.privateMessageDecrypt(process.credentials.session.name, id, message);
|
return ssb.privateMessageDecrypt(process.credentials.session.name, id, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
imports.ssb.setServerFollowingMe = function(id, following) {
|
||||||
|
if (process.credentials &&
|
||||||
|
process.credentials.session &&
|
||||||
|
process.credentials.session.name) {
|
||||||
|
return ssb.setServerFollowingMe(process.credentials.session.name, id, following);
|
||||||
|
}
|
||||||
|
};
|
||||||
imports.fetch = function(url, options) {
|
imports.fetch = function(url, options) {
|
||||||
return http.fetch(url, options, gGlobalSettings.fetch_hosts);
|
return http.fetch(url, options, gGlobalSettings.fetch_hosts);
|
||||||
}
|
}
|
||||||
@ -683,7 +690,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
|
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + blobId + '/', "Content-Length": "0"});
|
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + blobId + '/', "Content-Length": "0"});
|
||||||
response.end(data);
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
77
src/ssb.db.c
77
src/ssb.db.c
@ -1258,7 +1258,10 @@ void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* id
|
|||||||
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size)
|
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
memset(out_private_key, 0, crypto_sign_SECRETKEYBYTES);
|
if (out_private_key)
|
||||||
|
{
|
||||||
|
memset(out_private_key, 0, private_key_size);
|
||||||
|
}
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
sqlite3_stmt* statement = NULL;
|
sqlite3_stmt* statement = NULL;
|
||||||
if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
|
||||||
@ -1269,8 +1272,15 @@ bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const c
|
|||||||
if (sqlite3_step(statement) == SQLITE_ROW)
|
if (sqlite3_step(statement) == SQLITE_ROW)
|
||||||
{
|
{
|
||||||
const char* key = (const char*)sqlite3_column_text(statement, 0);
|
const char* key = (const char*)sqlite3_column_text(statement, 0);
|
||||||
int r = tf_base64_decode(key, sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), out_private_key, private_key_size);
|
if (out_private_key && private_key_size)
|
||||||
success = r > 0;
|
{
|
||||||
|
int r = tf_base64_decode(key, sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), out_private_key, private_key_size);
|
||||||
|
success = r > 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
@ -1289,6 +1299,7 @@ typedef struct _following_t
|
|||||||
int following_count;
|
int following_count;
|
||||||
int blocking_count;
|
int blocking_count;
|
||||||
int depth;
|
int depth;
|
||||||
|
int ref_count;
|
||||||
} following_t;
|
} following_t;
|
||||||
|
|
||||||
static int _following_compare(const void* a, const void* b)
|
static int _following_compare(const void* a, const void* b)
|
||||||
@ -1301,7 +1312,7 @@ static int _following_compare(const void* a, const void* b)
|
|||||||
static void _add_following_entry(following_t*** list, int* count, following_t* add)
|
static void _add_following_entry(following_t*** list, int* count, following_t* add)
|
||||||
{
|
{
|
||||||
int index = tf_util_insert_index(add->id, *list, *count, sizeof(following_t*), _following_compare);
|
int index = tf_util_insert_index(add->id, *list, *count, sizeof(following_t*), _following_compare);
|
||||||
if (index >= *count || strcmp(add->id, (*list)[index]->id) == 0)
|
if (index >= *count || strcmp(add->id, (*list)[index]->id) != 0)
|
||||||
{
|
{
|
||||||
*list = tf_resize_vec(*list, sizeof(**list) * (*count + 1));
|
*list = tf_resize_vec(*list, sizeof(**list) * (*count + 1));
|
||||||
if (*count - index)
|
if (*count - index)
|
||||||
@ -1309,6 +1320,21 @@ static void _add_following_entry(following_t*** list, int* count, following_t* a
|
|||||||
memmove(*list + index + 1, *list + index, sizeof(following_t*) * (*count - index));
|
memmove(*list + index + 1, *list + index, sizeof(following_t*) * (*count - index));
|
||||||
}
|
}
|
||||||
(*list)[index] = add;
|
(*list)[index] = add;
|
||||||
|
(*count)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _remove_following_entry(following_t*** list, int* count, following_t* remove)
|
||||||
|
{
|
||||||
|
int index = tf_util_insert_index(remove->id, *list, *count, sizeof(following_t*), _following_compare);
|
||||||
|
if (index < *count && strcmp(remove->id, (*list)[index]->id) == 0)
|
||||||
|
{
|
||||||
|
if (*count - index > 1)
|
||||||
|
{
|
||||||
|
memmove(*list + index, *list + index + 1, sizeof(following_t*) * (*count - index));
|
||||||
|
}
|
||||||
|
*list = tf_resize_vec(*list, sizeof(**list) * (*count - 1));
|
||||||
|
(*count)--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1354,21 +1380,31 @@ static following_t* _get_following(tf_ssb_t* ssb, const char* id, following_t***
|
|||||||
const char* contact = (const char*)sqlite3_column_text(statement, 0);
|
const char* contact = (const char*)sqlite3_column_text(statement, 0);
|
||||||
if (sqlite3_column_type(statement, 1) != SQLITE_NULL)
|
if (sqlite3_column_type(statement, 1) != SQLITE_NULL)
|
||||||
{
|
{
|
||||||
bool is_following = sqlite3_column_int(statement, 1);
|
bool is_following = sqlite3_column_int(statement, 1) != 0;
|
||||||
following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, max_depth);
|
following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, max_depth);
|
||||||
if (is_following)
|
if (is_following)
|
||||||
{
|
{
|
||||||
_add_following_entry(&entry->following, &entry->following_count, next);
|
_add_following_entry(&entry->following, &entry->following_count, next);
|
||||||
|
next->ref_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_remove_following_entry(&entry->following, &entry->following_count, next);
|
||||||
|
next->ref_count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sqlite3_column_type(statement, 2) != SQLITE_NULL)
|
if (sqlite3_column_type(statement, 2) != SQLITE_NULL)
|
||||||
{
|
{
|
||||||
bool is_blocking = sqlite3_column_int(statement, 2);
|
bool is_blocking = sqlite3_column_int(statement, 2 != 0);
|
||||||
following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, 0 /* don't dig deeper into blocked users */);
|
following_t* next = _get_following(ssb, contact, following, following_count, depth + 1, 0 /* don't dig deeper into blocked users */);
|
||||||
if (is_blocking)
|
if (is_blocking)
|
||||||
{
|
{
|
||||||
_add_following_entry(&entry->blocking, &entry->blocking_count, next);
|
_add_following_entry(&entry->blocking, &entry->blocking_count, next);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_remove_following_entry(&entry->blocking, &entry->blocking_count, next);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1385,18 +1421,33 @@ const char** tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count
|
|||||||
int following_count = 0;
|
int following_count = 0;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
_get_following(ssb, ids[i], &following, &following_count, 0, depth);
|
following_t* entry = _get_following(ssb, ids[i], &following, &following_count, 0, depth);
|
||||||
|
entry->ref_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
char** result = tf_malloc(sizeof(char*) * (following_count + 1) + k_id_base64_len * following_count);
|
int actual_following_count = 0;
|
||||||
char* result_ids = (char*)result + sizeof(char*) * (following_count + 1);
|
|
||||||
|
|
||||||
for (int i = 0; i < following_count; i++)
|
for (int i = 0; i < following_count; i++)
|
||||||
{
|
{
|
||||||
result[i] = result_ids + k_id_base64_len * i;
|
if (following[i]->ref_count > 0)
|
||||||
snprintf(result[i], k_id_base64_len, "%s", following[i]->id);
|
{
|
||||||
|
actual_following_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result[following_count] = NULL;
|
|
||||||
|
char** result = tf_malloc(sizeof(char*) * (actual_following_count + 1) + k_id_base64_len * actual_following_count);
|
||||||
|
char* result_ids = (char*)result + sizeof(char*) * (actual_following_count + 1);
|
||||||
|
|
||||||
|
int write_index = 0;
|
||||||
|
for (int i = 0; i < following_count; i++)
|
||||||
|
{
|
||||||
|
if (following[i]->ref_count > 0)
|
||||||
|
{
|
||||||
|
result[write_index] = result_ids + k_id_base64_len * write_index;
|
||||||
|
snprintf(result[write_index], k_id_base64_len, "%s", following[i]->id);
|
||||||
|
write_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[actual_following_count] = NULL;
|
||||||
|
|
||||||
for (int i = 0; i < following_count; i++)
|
for (int i = 0; i < following_count; i++)
|
||||||
{
|
{
|
||||||
|
67
src/ssb.js.c
67
src/ssb.js.c
@ -29,6 +29,7 @@ static const int k_sql_async_timeout_ms = 60 * 1000;
|
|||||||
static JSClassID _tf_ssb_classId;
|
static JSClassID _tf_ssb_classId;
|
||||||
|
|
||||||
void _tf_ssb_on_rpc(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data);
|
void _tf_ssb_on_rpc(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data);
|
||||||
|
static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||||
|
|
||||||
static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
@ -63,6 +64,71 @@ static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JSValue _set_server_following_internal(tf_ssb_t* ssb, JSValueConst this_val, JSValue id, JSValue following)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue message = JS_NewObject(context);
|
||||||
|
JSValue server_user = JS_NewString(context, ":admin");
|
||||||
|
char server_id_buffer[k_id_base64_len] = { 0 };
|
||||||
|
tf_ssb_whoami(ssb, server_id_buffer, sizeof(server_id_buffer));
|
||||||
|
JSValue server_id = JS_NewString(context, server_id_buffer);
|
||||||
|
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "contact"));
|
||||||
|
JS_SetPropertyStr(context, message, "contact", JS_DupValue(context, id));
|
||||||
|
JS_SetPropertyStr(context, message, "following", JS_DupValue(context, following));
|
||||||
|
JSValue args[] =
|
||||||
|
{
|
||||||
|
server_user,
|
||||||
|
server_id,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
JSValue result = _tf_ssb_appendMessageWithIdentity(context, this_val, _countof(args), args);
|
||||||
|
JS_FreeValue(context, server_id);
|
||||||
|
JS_FreeValue(context, server_user);
|
||||||
|
JS_FreeValue(context, message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_set_server_following_me(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
|
{
|
||||||
|
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
if (ssb)
|
||||||
|
{
|
||||||
|
char server_id[k_id_base64_len];
|
||||||
|
tf_ssb_whoami(ssb, server_id, sizeof(server_id));
|
||||||
|
const char* user = JS_ToCString(context, argv[0]);
|
||||||
|
const char* key = JS_ToCString(context, argv[1]);
|
||||||
|
if (!tf_ssb_db_identity_get_private_key(ssb, user, key, NULL, 0))
|
||||||
|
{
|
||||||
|
result = JS_ThrowInternalError(context, "User %s does not own key %s.", user, key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* server_id_ptr = server_id;
|
||||||
|
const char** current_following = tf_ssb_db_following_deep(ssb, &server_id_ptr, 1, 1);
|
||||||
|
bool is_following = false;
|
||||||
|
for (const char** it = current_following; *it; it++)
|
||||||
|
{
|
||||||
|
if (strcmp(key, *it) == 0)
|
||||||
|
{
|
||||||
|
is_following = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tf_free(current_following);
|
||||||
|
|
||||||
|
bool want_following = JS_ToBool(context, argv[2]);
|
||||||
|
if ((want_following && !is_following) || (!want_following && is_following))
|
||||||
|
{
|
||||||
|
result = _set_server_following_internal(ssb, this_val, argv[1], argv[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, key);
|
||||||
|
JS_FreeCString(context, user);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct _identities_visit_t
|
typedef struct _identities_visit_t
|
||||||
{
|
{
|
||||||
JSContext* context;
|
JSContext* context;
|
||||||
@ -1465,6 +1531,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
|||||||
|
|
||||||
/* Requires an identity. */
|
/* Requires an identity. */
|
||||||
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
|
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
|
||||||
|
JS_SetPropertyStr(context, object, "setServerFollowingMe", JS_NewCFunction(context, _tf_ssb_set_server_following_me, "setServerFollowingMe", 3));
|
||||||
JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1));
|
JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1));
|
||||||
JS_SetPropertyStr(context, object, "hmacsha256sign", JS_NewCFunction(context, _tf_ssb_hmacsha256_sign, "hmacsha256sign", 3));
|
JS_SetPropertyStr(context, object, "hmacsha256sign", JS_NewCFunction(context, _tf_ssb_hmacsha256_sign, "hmacsha256sign", 3));
|
||||||
JS_SetPropertyStr(context, object, "hmacsha256verify", JS_NewCFunction(context, _tf_ssb_hmacsha256_verify, "hmacsha256verify", 3));
|
JS_SetPropertyStr(context, object, "hmacsha256verify", JS_NewCFunction(context, _tf_ssb_hmacsha256_verify, "hmacsha256verify", 3));
|
||||||
|
Loading…
Reference in New Issue
Block a user