10 Commits

Author SHA1 Message Date
4f2e0245d3 ssb: Make blocks begin to do something.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m21s
2025-11-27 16:43:15 -05:00
eecdbf6852 ssb: Exercise at least calling adding/removing blocks in a test.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m12s
2025-11-27 16:06:33 -05:00
ddc4603f13 ssb: Add some plausible API and a table for storing instance-wide blocks. 2025-11-27 14:33:57 -05:00
759b522cd1 ssb: Preliminary view of flagged messages. Seems a bit counter-productive, but here we are.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m3s
2025-11-27 13:32:37 -05:00
7ecb4a192d buttfeed: Add SoapDog's PatchWork.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 11m18s
2025-11-27 13:05:19 -05:00
d84626ac31 ssb: Fix showing flags if we see the messages in the other order.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-11-26 12:16:23 -05:00
9c36e0db7b ssb: Show flagged messages similar to a message with a content warning.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m19s
2025-11-25 19:20:49 -05:00
fcd26bac1c ssb: Add some UI for posting 'flag' messages.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m8s
2025-11-25 19:04:52 -05:00
e8e7c98705 build: wip.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m14s
2025-11-25 18:31:25 -05:00
b5af5cc223 build: Let's start work on the December build. 2025-11-25 18:29:50 -05:00
22 changed files with 377 additions and 48 deletions

View File

@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 48 VERSION_CODE := 49
VERSION_CODE_IOS := 26 VERSION_CODE_IOS := 27
VERSION_NUMBER := 0.2025.11 VERSION_NUMBER := 0.2025.12-wip
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.5 IPHONEOS_VERSION_MIN=14.5

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&7dPNAI4sffljUTiwGr3XEUeB8sBD72CFkWMk/o0Z2pw=.sha256" "previous": "&t4hk+Y6NB+TftzDLhJ9gUKLPU5YcFYvTEbJuTT2qPCQ=.sha256"
} }

View File

@@ -185,6 +185,7 @@ class TfElement extends LitElement {
'', '',
'@', '@',
'👍', '👍',
'🚩',
...Object.keys(this.visible_private()) ...Object.keys(this.visible_private())
.sort() .sort()
.map((x) => '🔐' + JSON.parse(x).join(',')), .map((x) => '🔐' + JSON.parse(x).join(',')),
@@ -491,6 +492,13 @@ class TfElement extends LitElement {
`, `,
k_args k_args
), ),
tfrpc.rpc.query(
`
SELECT '🚩' AS channel, MAX(messages.rowid) AS rowid FROM messages
WHERE messages.content ->> 'type' = 'flag'
`,
k_args
),
]) ])
).flat(); ).flat();
let latest = {'🔐': undefined}; let latest = {'🔐': undefined};

View File

@@ -196,6 +196,26 @@ class TfMessageElement extends LitElement {
); );
} }
flag(event) {
let reason = prompt(
'What is the reason for reporting this content (spam, nsfw, ...)?',
'offensive'
);
if (reason !== undefined) {
tfrpc.rpc
.appendMessage(this.whoami, {
type: 'flag',
flag: {
link: this.message.id,
reason: reason.length ? reason : undefined,
},
})
.catch(function (error) {
alert(error?.message);
});
}
}
show_image(link) { show_image(link) {
let div = document.createElement('div'); let div = document.createElement('div');
div.style.left = 0; div.style.left = 0;
@@ -499,11 +519,14 @@ class TfMessageElement extends LitElement {
</button> </button>
` `
: undefined} : undefined}
<button class="w3-button w3-bar-item" @click=${this.react}>
👍 React
</button>
<button <button
class="w3-button w3-bar-item w3-border-bottom" class="w3-button w3-bar-item w3-border-bottom"
@click=${this.react} @click=${this.flag}
> >
👍 React ⚠️ Flag
</button> </button>
${formats.map( ${formats.map(
([format, name]) => html` ([format, name]) => html`
@@ -965,7 +988,11 @@ class TfMessageElement extends LitElement {
style="cursor: pointer" style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')} @click=${(x) => this.toggle_expanded(':cw')}
> >
<p>${content.contentWarning}</p> <p>
${this.message.flags
? `Caution: This message has been flagged ${this.message.flags.length} time${this.message.flags.length == 1 ? '' : 's'}.`
: content.contentWarning}
</p>
<p class="w3-small"> <p class="w3-small">
${this.is_expanded(':cw') ? 'Show less' : 'Show more'} ${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
</p> </p>
@@ -976,7 +1003,8 @@ class TfMessageElement extends LitElement {
<div @click=${this.body_click}>${body}</div> <div @click=${this.body_click}>${body}</div>
${this.render_mentions()} ${this.render_mentions()}
`; `;
let payload = content.contentWarning let payload =
this.message.flags || content.contentWarning
? self.expanded[(this.message.id || '') + ':cw'] ? self.expanded[(this.message.id || '') + ':cw']
? html` ${content_warning} ${content_html} ` ? html` ${content_warning} ${content_html} `
: content_warning : content_warning

View File

@@ -66,6 +66,16 @@ class TfNewsElement extends LitElement {
} }
parent.votes.push(message); parent.votes.push(message);
message.parent_message = message.content.vote.link; message.parent_message = message.content.vote.link;
} else if (message.content.type == 'flag') {
let parent = ensure_message(message.content.flag.link, message.rowid);
if (!parent.flags) {
parent.flags = [];
}
parent.flags.push(message);
parent.flags = Object.values(
Object.fromEntries(parent.flags.map((x) => [x.id, x]))
);
message.parent_message = message.content.flag.link;
} else if (message.content.type == 'post') { } else if (message.content.type == 'post') {
if (message.content.root) { if (message.content.root) {
if (typeof message.content.root === 'string') { if (typeof message.content.root === 'string') {
@@ -106,6 +116,7 @@ class TfNewsElement extends LitElement {
message.parent_message = placeholder.parent_message; message.parent_message = placeholder.parent_message;
message.child_messages = placeholder.child_messages; message.child_messages = placeholder.child_messages;
message.votes = placeholder.votes; message.votes = placeholder.votes;
message.flags = placeholder.flags;
if ( if (
placeholder.parent_message && placeholder.parent_message &&
messages_by_id[placeholder.parent_message] messages_by_id[placeholder.parent_message]

View File

@@ -84,7 +84,6 @@ class TfTabNewsFeedElement extends LitElement {
`, `,
[JSON.stringify(combined.map((x) => x.id))] [JSON.stringify(combined.map((x) => x.id))]
); );
let t0 = new Date();
let result = [].concat( let result = [].concat(
combined, combined,
await tfrpc.rpc.query( await tfrpc.rpc.query(
@@ -101,8 +100,7 @@ class TfTabNewsFeedElement extends LitElement {
] ]
) )
); );
let t1 = new Date(); console.log(result);
console.log((t1 - t0) / 1000);
return result; return result;
} }
@@ -227,7 +225,8 @@ class TfTabNewsFeedElement extends LitElement {
k_max_results, k_max_results,
] ]
); );
result = (await this.decrypt(result)).filter((x) => x.decrypted); let decrypted = (await this.decrypt(result)).filter((x) => x.decrypted);
result = await this._fetch_related_messages(decrypted);
} else if (this.hash == '#👍') { } else if (this.hash == '#👍') {
result = await tfrpc.rpc.query( result = await tfrpc.rpc.query(
` `
@@ -246,6 +245,23 @@ class TfTabNewsFeedElement extends LitElement {
`, `,
[JSON.stringify(this.following), start_time, end_time, k_max_results] [JSON.stringify(this.following), start_time, end_time, k_max_results]
); );
} else if (this.hash == '#🚩') {
result = await tfrpc.rpc.query(
`
WITH flags AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
WHERE
messages.content ->> 'type' = 'flag' AND
(?1 IS NULL OR messages.timestamp >= ?1) AND messages.timestamp < ?2
ORDER BY timestamp DESC limit ?3)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM flags
JOIN messages ON messages.id = flags.content ->> '$.flag.link'
UNION
SELECT TRUE AS is_primary, * FROM flags
`,
[start_time, end_time, k_max_results]
);
} else { } else {
let initial_messages = await tfrpc.rpc.query( let initial_messages = await tfrpc.rpc.query(
` `

View File

@@ -243,6 +243,12 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined} style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a >${this.unread_status('👍')}👍votes</a
> >
<a
href="#🚩"
class="w3-bar-item w3-button"
style=${this.hash == '#🚩' ? 'font-weight: bold' : undefined}
>${this.unread_status('🚩')}🚩flagged</a
>
${Object.keys(this?.visible_private_messages ?? []) ${Object.keys(this?.visible_private_messages ?? [])
?.sort() ?.sort()
?.map( ?.map(

View File

@@ -494,6 +494,7 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
}; };
if (process.credentials?.permissions?.administration) {
imports.ssb.swapWithServerIdentity = function (id) { imports.ssb.swapWithServerIdentity = function (id) {
if ( if (
process.credentials && process.credentials &&
@@ -506,6 +507,15 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
}; };
imports.ssb.addBlock = async function (id) {
await imports.core.permissionTest('modify_blocks', `Block ${id}.`);
await ssb_internal.addBlock(id);
};
imports.ssb.removeBlock = async function (id) {
await imports.core.permissionTest('modify_blocks', `Unblock ${id}.`);
await ssb_internal.removeBlock(id);
};
}
if ( if (
process.credentials && process.credentials &&

View File

@@ -25,14 +25,14 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.2025.9"; version = "0.2025.11";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";
owner = "cory"; owner = "cory";
repo = "tildefriends"; repo = "tildefriends";
rev = "v${version}"; rev = "v${version}";
hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk="; hash = "sha256-z4v4ghKOBTMv+agTUKg+HU8zfE4imluXFsozQCT4qX8=";
fetchSubmodules = true; fetchSubmodules = true;
}; };

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1758589230, "lastModified": 1763948260,
"narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=", "narHash": "sha256-dY9qLD0H0zOUgU3vWacPY6Qc421BeQAfm8kBuBtPVE0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0", "rev": "1c8ba8d3f7634acac4a2094eef7c32ad9106532c",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends" package="com.unprompted.tildefriends"
android:versionCode="48" android:versionCode="49"
android:versionName="0.2025.11"> android:versionName="0.2025.12-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application

View File

@@ -13,13 +13,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2025.11</string> <string>0.2025.12</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>26</string> <string>27</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@@ -271,7 +271,6 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
")"); ")");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS identities_user ON identities (user, public_key)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS identities_user ON identities (user, public_key)");
_tf_ssb_db_exec(db, "DELETE FROM identities WHERE user = ':auth'"); _tf_ssb_db_exec(db, "DELETE FROM identities WHERE user = ':auth'");
_tf_ssb_db_exec(db, _tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS invites (" "CREATE TABLE IF NOT EXISTS invites ("
" invite_public_key TEXT PRIMARY KEY," " invite_public_key TEXT PRIMARY KEY,"
@@ -279,6 +278,11 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
" use_count INTEGER," " use_count INTEGER,"
" expires INTEGER" " expires INTEGER"
")"); ")");
_tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS blocks ("
" id TEXT PRIMARY KEY,"
" timestamp REAL"
")");
bool populate_fts = false; bool populate_fts = false;
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_fts')")) if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_fts')"))
@@ -2868,3 +2872,67 @@ tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* u
tf_free(info); tf_free(info);
return copy; return copy;
} }
void tf_ssb_db_add_block(sqlite3* db, const char* id)
{
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare_v2(db, "INSERT INTO blocks (id, timestamp) VALUES (?, unixepoch() * 1000) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("add block: %s\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
if (sqlite3_prepare_v2(db,
"INSERT INTO blocks (id, timestamp) SELECT messages_refs.ref AS id, unixepoch() * 1000 AS timestamp FROM messages_refs WHERE messages_refs.message = ? ON CONFLICT DO "
"NOTHING",
-1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("add block messages ref: %s\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
}
void tf_ssb_db_remove_block(sqlite3* db, const char* id)
{
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare_v2(db, "DELETE FROM blocks WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("remove block: %s\n", sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
}
bool tf_ssb_db_is_blocked(sqlite3* db, const char* id)
{
bool is_blocked = false;
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare_v2(db, "SELECT 1 FROM blocks WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
is_blocked = true;
}
}
sqlite3_finalize(statement);
}
return is_blocked;
}

View File

@@ -617,4 +617,26 @@ tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* u
*/ */
void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id); void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id);
/**
** Add an instance-wide block.
** @param db The database.
** @param id The account, message, or blob ID to block.
*/
void tf_ssb_db_add_block(sqlite3* db, const char* id);
/**
** Remove an instance-wide block.
** @param db The database.
** @param id The account, message, or blob ID to unblock.
*/
void tf_ssb_db_remove_block(sqlite3* db, const char* id);
/**
** Check if an ID is blocked on this instance.
** @param db The database.
** @param id The account, message, or blob ID to check.
** @return true if the id is blocked.
*/
bool tf_ssb_db_is_blocked(sqlite3* db, const char* id);
/** @} */ /** @} */

View File

@@ -221,6 +221,14 @@ static void _ebt_add_to_clock(ebt_get_clock_t* work, const char* id, int64_t val
} }
} }
static bool _is_blocked(tf_ssb_t* ssb, const char* id)
{
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
bool blocked = tf_ssb_db_is_blocked(db, id);
tf_ssb_release_db_reader(ssb, db);
return blocked;
}
static void _tf_ssb_ebt_get_send_clock_work(tf_ssb_connection_t* connection, void* user_data) static void _tf_ssb_ebt_get_send_clock_work(tf_ssb_connection_t* connection, void* user_data)
{ {
ebt_get_clock_t* work = user_data; ebt_get_clock_t* work = user_data;
@@ -239,7 +247,10 @@ static void _tf_ssb_ebt_get_send_clock_work(tf_ssb_connection_t* connection, voi
for (int i = 0; visible[i]; i++) for (int i = 0; visible[i]; i++)
{ {
int32_t sequence = 0; int32_t sequence = 0;
if (!_is_blocked(ssb, visible[i]))
{
tf_ssb_db_get_latest_message_by_author(ssb, visible[i], &sequence, NULL, 0); tf_ssb_db_get_latest_message_by_author(ssb, visible[i], &sequence, NULL, 0);
}
sequences = tf_resize_vec(sequences, (i + 1) * sizeof(int32_t)); sequences = tf_resize_vec(sequences, (i + 1) * sizeof(int32_t));
sequences[i] = sequence; sequences[i] = sequence;
} }
@@ -258,6 +269,8 @@ static void _tf_ssb_ebt_get_send_clock_work(tf_ssb_connection_t* connection, voi
/* Ask about the incoming connection, too. */ /* Ask about the incoming connection, too. */
char id[k_id_base64_len] = ""; char id[k_id_base64_len] = "";
if (tf_ssb_connection_get_id(connection, id, sizeof(id))) if (tf_ssb_connection_get_id(connection, id, sizeof(id)))
{
if (!_is_blocked(ssb, id))
{ {
int32_t sequence = 0; int32_t sequence = 0;
tf_ssb_db_get_latest_message_by_author(ssb, id, &sequence, NULL, 0); tf_ssb_db_get_latest_message_by_author(ssb, id, &sequence, NULL, 0);
@@ -265,6 +278,7 @@ static void _tf_ssb_ebt_get_send_clock_work(tf_ssb_connection_t* connection, voi
_ebt_add_to_clock(work, id, sequence, true, true); _ebt_add_to_clock(work, id, sequence, true, true);
uv_mutex_unlock(&work->ebt->mutex); uv_mutex_unlock(&work->ebt->mutex);
} }
}
/* Also respond with what we know about all requested identities. */ /* Also respond with what we know about all requested identities. */
tf_ssb_ebt_clock_entry_t* requested = NULL; tf_ssb_ebt_clock_entry_t* requested = NULL;
@@ -286,9 +300,12 @@ static void _tf_ssb_ebt_get_send_clock_work(tf_ssb_connection_t* connection, voi
if (requested_count) if (requested_count)
{ {
for (int i = 0; i < requested_count; i++) for (int i = 0; i < requested_count; i++)
{
if (!_is_blocked(ssb, requested[i].id))
{ {
tf_ssb_db_get_latest_message_by_author(ssb, requested[i].id, &requested[i].value, NULL, 0); tf_ssb_db_get_latest_message_by_author(ssb, requested[i].id, &requested[i].value, NULL, 0);
} }
}
uv_mutex_lock(&work->ebt->mutex); uv_mutex_lock(&work->ebt->mutex);
for (int i = 0; i < requested_count; i++) for (int i = 0; i < requested_count; i++)

View File

@@ -2172,6 +2172,66 @@ static JSValue _tf_ssb_port(JSContext* context, JSValueConst this_val, int argc,
return JS_NewInt32(context, tf_ssb_server_get_port(ssb)); return JS_NewInt32(context, tf_ssb_server_get_port(ssb));
} }
typedef struct _modify_block_t
{
char id[k_id_base64_len];
bool add;
JSValue promise[2];
} modify_block_t;
static void _tf_ssb_modify_block_work(tf_ssb_t* ssb, void* user_data)
{
modify_block_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (work->add)
{
tf_ssb_db_add_block(db, work->id);
}
else
{
tf_ssb_db_remove_block(db, work->id);
}
tf_ssb_release_db_writer(ssb, db);
}
static void _tf_ssb_modify_block_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
modify_block_t* request = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 0, NULL);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, request->promise[0]);
JS_FreeValue(context, request->promise[1]);
tf_free(request);
}
static JSValue _tf_ssb_add_block(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
const char* id = JS_ToCString(context, argv[0]);
modify_block_t* work = tf_malloc(sizeof(modify_block_t));
*work = (modify_block_t) { .add = true };
tf_string_set(work->id, sizeof(work->id), id);
JSValue result = JS_NewPromiseCapability(context, work->promise);
JS_FreeCString(context, id);
tf_ssb_run_work(ssb, _tf_ssb_modify_block_work, _tf_ssb_modify_block_after_work, work);
return result;
}
static JSValue _tf_ssb_remove_block(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
const char* id = JS_ToCString(context, argv[0]);
modify_block_t* work = tf_malloc(sizeof(modify_block_t));
*work = (modify_block_t) { .add = false };
tf_string_set(work->id, sizeof(work->id), id);
JSValue result = JS_NewPromiseCapability(context, work->promise);
JS_FreeCString(context, id);
tf_ssb_run_work(ssb, _tf_ssb_modify_block_work, _tf_ssb_modify_block_after_work, work);
return result;
}
void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
{ {
JS_NewClassID(&_tf_ssb_classId); JS_NewClassID(&_tf_ssb_classId);
@@ -2227,6 +2287,8 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object_internal, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3)); JS_SetPropertyStr(context, object_internal, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3));
JS_SetPropertyStr(context, object_internal, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2)); JS_SetPropertyStr(context, object_internal, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2)); JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));
JS_SetPropertyStr(context, object_internal, "addBlock", JS_NewCFunction(context, _tf_ssb_add_block, "addBlock", 1));
JS_SetPropertyStr(context, object_internal, "removeBlock", JS_NewCFunction(context, _tf_ssb_remove_block, "removeBlock", 1));
JS_FreeValue(context, global); JS_FreeValue(context, global);
} }

View File

@@ -155,7 +155,7 @@ static void _tf_ssb_rpc_blobs_has_work(tf_ssb_connection_t* connection, void* us
blobs_has_work_t* work = user_data; blobs_has_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
work->found = tf_ssb_db_blob_has(db, work->id); work->found = tf_ssb_db_blob_has(db, work->id) && !tf_ssb_db_is_blocked(db, work->id);
tf_ssb_release_db_reader(ssb, db); tf_ssb_release_db_reader(ssb, db);
} }

View File

@@ -1851,4 +1851,77 @@ void tf_ssb_test_following_perf(const tf_test_options_t* options)
uv_run(&loop, UV_RUN_DEFAULT); uv_run(&loop, UV_RUN_DEFAULT);
uv_loop_close(&loop); uv_loop_close(&loop);
} }
static void _store_callback(const char* id, bool verified, bool is_new, void* user_data)
{
tf_string_set(user_data, k_id_base64_len, id);
}
void tf_ssb_test_blocks(const tf_test_options_t* options)
{
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
tf_printf("Testing blocks.\n");
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_generate_keys(ssb);
uint8_t priv[512] = { 0 };
tf_ssb_get_private_key(ssb, priv, sizeof(priv));
char id[k_id_base64_len] = { 0 };
tf_ssb_whoami(ssb, id, sizeof(id));
tf_printf("ID %s\n", id);
char blob_id[k_id_base64_len] = { 0 };
const char* k_blob = "Hello, blob!";
bool blob_stored = false;
tf_ssb_add_blob_stored_callback(ssb, _blob_stored, NULL, &blob_stored);
tf_ssb_db_blob_store(ssb, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
tf_ssb_notify_blob_stored(ssb, blob_id);
tf_ssb_remove_blob_stored_callback(ssb, _blob_stored, &blob_stored);
assert(blob_stored);
JSContext* context = tf_ssb_get_context(ssb);
JSValue obj = JS_NewObject(context);
JS_SetPropertyStr(context, obj, "type", JS_NewString(context, "post"));
JS_SetPropertyStr(context, obj, "text", JS_NewString(context, "First post."));
JSValue mentions = JS_NewArray(context);
JSValue mention = JS_NewObject(context);
JS_SetPropertyStr(context, mention, "link", JS_NewString(context, blob_id));
JS_SetPropertyUint32(context, mentions, 0, mention);
JS_SetPropertyStr(context, obj, "mentions", mentions);
JSValue signed_message = tf_ssb_sign_message(ssb, id, priv, obj, NULL, 0);
char message_id[k_id_base64_len] = { 0 };
tf_ssb_verify_strip_and_store_message(ssb, signed_message, _store_callback, message_id);
JS_FreeValue(context, signed_message);
while (!*message_id)
{
uv_run(tf_ssb_get_loop(ssb), UV_RUN_ONCE);
}
JS_FreeValue(context, obj);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
tf_ssb_db_add_block(db, message_id);
assert(tf_ssb_db_is_blocked(db, message_id));
/* Blocked already, because the blocked message references it. */
assert(tf_ssb_db_is_blocked(db, blob_id));
tf_ssb_db_add_block(db, blob_id);
tf_ssb_db_add_block(db, id);
assert(tf_ssb_db_is_blocked(db, id));
tf_ssb_db_remove_block(db, blob_id);
tf_ssb_db_remove_block(db, message_id);
tf_ssb_db_remove_block(db, id);
assert(!tf_ssb_db_is_blocked(db, message_id));
assert(!tf_ssb_db_is_blocked(db, blob_id));
assert(!tf_ssb_db_is_blocked(db, id));
tf_ssb_release_db_writer(ssb, db);
tf_ssb_destroy(ssb);
uv_run(&loop, UV_RUN_DEFAULT);
uv_loop_close(&loop);
}
#endif #endif

View File

@@ -107,4 +107,10 @@ void tf_ssb_test_cli(const tf_test_options_t* options);
*/ */
void tf_ssb_test_following_perf(const tf_test_options_t* options); void tf_ssb_test_following_perf(const tf_test_options_t* options);
/**
** Test blocks.
** @param options The test options.
*/
void tf_ssb_test_blocks(const tf_test_options_t* options);
/** @} */ /** @} */

View File

@@ -982,7 +982,6 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "b64", _test_b64, false); _tf_test_run(options, "b64", _test_b64, false);
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false); _tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
_tf_test_run(options, "bench", tf_ssb_test_bench, false); _tf_test_run(options, "bench", tf_ssb_test_bench, false);
_tf_test_run(options, "auto", _test_auto, false);
_tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true); _tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true);
_tf_test_run(options, "encrypt", tf_ssb_test_encrypt, false); _tf_test_run(options, "encrypt", tf_ssb_test_encrypt, false);
_tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false); _tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false);
@@ -994,6 +993,8 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "triggers", tf_ssb_test_triggers, false); _tf_test_run(options, "triggers", tf_ssb_test_triggers, false);
_tf_test_run(options, "cli", tf_ssb_test_cli, false); _tf_test_run(options, "cli", tf_ssb_test_cli, false);
_tf_test_run(options, "following_perf", tf_ssb_test_following_perf, true); _tf_test_run(options, "following_perf", tf_ssb_test_following_perf, true);
_tf_test_run(options, "blocks", tf_ssb_test_blocks, true);
_tf_test_run(options, "auto", _test_auto, false);
tf_printf("Tests completed.\n"); tf_printf("Tests completed.\n");
#endif #endif
} }

View File

@@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.2025.11" #define VERSION_NUMBER "0.2025.12-wip"
#define VERSION_NAME "This program kills fascists." #define VERSION_NAME "This program kills fascists."

View File

@@ -18,6 +18,7 @@ k_feeds = {
'manyverse': 'https://gitlab.com/staltz/manyverse/-/commits/master?format=atom', 'manyverse': 'https://gitlab.com/staltz/manyverse/-/commits/master?format=atom',
'ahau': 'https://gitlab.com/ahau/ahau/-/commits/master.atom', 'ahau': 'https://gitlab.com/ahau/ahau/-/commits/master.atom',
'patchfox': 'https://github.com/soapdog/patchfox/commits.atom', 'patchfox': 'https://github.com/soapdog/patchfox/commits.atom',
'ponchowonky': 'https://github.com/soapdog/patchwork/commits.atom',
} }
def fix_title(entry): def fix_title(entry):