Compare commits
24 Commits
v0.2025.11
...
f4c6e2db1f
| Author | SHA1 | Date | |
|---|---|---|---|
| f4c6e2db1f | |||
| 48406bfe38 | |||
| 07c879f5f5 | |||
| cd34c127d1 | |||
| fb6e554e59 | |||
| d200e361f7 | |||
| bc3fd57d7a | |||
| fa4ef3b082 | |||
| 0827718d68 | |||
| 0ec862eaac | |||
| 7e1621dfb4 | |||
| c4d4e3822d | |||
| d2e5015eac | |||
| 510c2f81bd | |||
| 4f2e0245d3 | |||
| eecdbf6852 | |||
| ddc4603f13 | |||
| 759b522cd1 | |||
| 7ecb4a192d | |||
| d84626ac31 | |||
| 9c36e0db7b | |||
| fcd26bac1c | |||
| e8e7c98705 | |||
| b5af5cc223 |
@@ -16,14 +16,14 @@ 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
|
||||||
|
|
||||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3510000.zip
|
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3510100.zip
|
||||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.18.2/bundletool-all-1.18.2.jar
|
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.18.2/bundletool-all-1.18.2.jar
|
||||||
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🎛",
|
"emoji": "🎛",
|
||||||
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
|
"previous": "&bRhS1LQIH8WQjbBfQqdhjLv7tqDdHT7IEPyCmj39b+4=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,20 @@ tfrpc.register(function global_settings_set(key, value) {
|
|||||||
return core.globalSettingsSet(key, value);
|
return core.globalSettingsSet(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tfrpc.register(function addBlock(id) {
|
||||||
|
return ssb.addBlock(id);
|
||||||
|
});
|
||||||
|
tfrpc.register(function removeBlock(id) {
|
||||||
|
return ssb.removeBlock(id);
|
||||||
|
});
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
let data = {
|
let data = {
|
||||||
users: {},
|
users: {},
|
||||||
granted: await core.allPermissionsGranted(),
|
granted: await core.allPermissionsGranted(),
|
||||||
settings: await core.globalSettingsDescriptions(),
|
settings: await core.globalSettingsDescriptions(),
|
||||||
|
blocks: await ssb.getBlocks(),
|
||||||
};
|
};
|
||||||
for (let user of await core.users()) {
|
for (let user of await core.users()) {
|
||||||
data.users[user] = await core.permissionsForUser(user);
|
data.users[user] = await core.permissionsForUser(user);
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ function delete_user(user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function add_block() {
|
||||||
|
await tfrpc.rpc.addBlock(document.getElementById('add_block').value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove_block(id) {
|
||||||
|
await tfrpc.rpc.removeBlock(id);
|
||||||
|
}
|
||||||
|
|
||||||
function global_settings_set(key, value) {
|
function global_settings_set(key, value) {
|
||||||
tfrpc.rpc
|
tfrpc.rpc
|
||||||
.global_settings_set(key, value)
|
.global_settings_set(key, value)
|
||||||
@@ -94,11 +102,32 @@ ${description.value}</textarea
|
|||||||
${user}: ${permissions.map((x) => permission_template(x))}
|
${user}: ${permissions.map((x) => permission_template(x))}
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
|
const block_template = (block) => html`
|
||||||
|
<li class="w3-card w3-margin">
|
||||||
|
<button
|
||||||
|
class="w3-button w3-theme-action"
|
||||||
|
@click=${(e) => remove_block(block.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<code>${block.id}</code>
|
||||||
|
${new Date(block.timestamp)}
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
const users_template = (users) =>
|
const users_template = (users) =>
|
||||||
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
|
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
|
||||||
<ul class="w3-ul">
|
<ul class="w3-ul">
|
||||||
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
|
const blocks_template = (blocks) =>
|
||||||
|
html` <header class="w3-container w3-theme-l2"><h2>Blocks</h2></header>
|
||||||
|
<div class="w3-row w3-margin">
|
||||||
|
<input type="text" class="w3-threequarter w3-input" id="add_block"></input>
|
||||||
|
<button class="w3-quarter w3-button w3-theme-action" @click=${add_block}>Add</button>
|
||||||
|
</div>
|
||||||
|
<ul class="w3-ul">
|
||||||
|
${blocks.map((b) => block_template(b))}
|
||||||
|
</ul>`;
|
||||||
const page_template = (data) =>
|
const page_template = (data) =>
|
||||||
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
|
||||||
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
|
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
|
||||||
@@ -109,7 +138,7 @@ ${description.value}</textarea
|
|||||||
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
.map((x) => html`${input_template(x, data.settings[x])}`)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
${users_template(data.users)}
|
${users_template(data.users)} ${blocks_template(data.blocks)}
|
||||||
</div> `;
|
</div> `;
|
||||||
render(page_template(g_data), document.body);
|
render(page_template(g_data), document.body);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&7dPNAI4sffljUTiwGr3XEUeB8sBD72CFkWMk/o0Z2pw=.sha256"
|
"previous": "&QPdP4BaztX9l3BBQNVbFAu8t5iN5NTqzhIOr+8bNhb0=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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,12 +519,16 @@ class TfMessageElement extends LitElement {
|
|||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: undefined}
|
: undefined}
|
||||||
<button
|
<button class="w3-button w3-bar-item" @click=${this.react}>
|
||||||
class="w3-button w3-bar-item w3-border-bottom"
|
|
||||||
@click=${this.react}
|
|
||||||
>
|
|
||||||
👍 React
|
👍 React
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
id="button_flag"
|
||||||
|
class="w3-button w3-bar-item w3-border-bottom"
|
||||||
|
@click=${this.flag}
|
||||||
|
>
|
||||||
|
🚩 Flag
|
||||||
|
</button>
|
||||||
${formats.map(
|
${formats.map(
|
||||||
([format, name]) => html`
|
([format, name]) => html`
|
||||||
<button
|
<button
|
||||||
@@ -575,9 +599,11 @@ class TfMessageElement extends LitElement {
|
|||||||
let self = this;
|
let self = this;
|
||||||
return this.render_frame(html`
|
return this.render_frame(html`
|
||||||
${self.render_header()}
|
${self.render_header()}
|
||||||
|
<div class="w3-container">
|
||||||
${self.format == 'raw'
|
${self.format == 'raw'
|
||||||
? html`<div class="w3-container">${self.render_raw()}</div>`
|
? html`${self.render_raw()}`
|
||||||
: inner}
|
: self.render_flagged(inner)}
|
||||||
|
</div>
|
||||||
${self.render_votes()}
|
${self.render_votes()}
|
||||||
${(self.message.child_messages || []).map(
|
${(self.message.child_messages || []).map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
@@ -703,6 +729,38 @@ class TfMessageElement extends LitElement {
|
|||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_flagged(inner) {
|
||||||
|
if (this.message.flags) {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="w3-panel w3-round-xlarge w3-theme-l4 w3"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@click=${(x) => this.toggle_expanded(':cw')}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
${this.message.flags
|
||||||
|
? html`<p>
|
||||||
|
Caution: This message has been flagged
|
||||||
|
${this.message.flags.length}
|
||||||
|
time${this.message.flags.length == 1 ? '' : 's'}.
|
||||||
|
</p>`
|
||||||
|
: undefined}
|
||||||
|
</p>
|
||||||
|
<p class="w3-small">
|
||||||
|
${inner !== undefined
|
||||||
|
? this.is_expanded(':cw')
|
||||||
|
? 'Show less'
|
||||||
|
: 'Show more'
|
||||||
|
: undefined}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
${this.is_expanded(':cw') ? inner : undefined}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_render() {
|
_render() {
|
||||||
let content = this.message?.content;
|
let content = this.message?.content;
|
||||||
if (this.message?.decrypted?.type == 'post') {
|
if (this.message?.decrypted?.type == 'post') {
|
||||||
@@ -850,6 +908,7 @@ class TfMessageElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w3-container">${this.render_flagged(undefined)}</div>
|
||||||
<div>${this.render_votes()}</div>
|
<div>${this.render_votes()}</div>
|
||||||
${(this.message.child_messages || []).map(
|
${(this.message.child_messages || []).map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
@@ -965,7 +1024,19 @@ 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
|
||||||
|
? html`<p>
|
||||||
|
Caution: This message has been flagged
|
||||||
|
${this.message.flags.length}
|
||||||
|
time${this.message.flags.length == 1 ? '' : 's'}.
|
||||||
|
</p>`
|
||||||
|
: undefined}
|
||||||
|
${content.contentWarning
|
||||||
|
? html`<p>${content.contentWarning}</p>`
|
||||||
|
: undefined}
|
||||||
|
</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 +1047,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
|
||||||
@@ -999,15 +1071,13 @@ class TfMessageElement extends LitElement {
|
|||||||
`);
|
`);
|
||||||
} else if (content.type === 'blog') {
|
} else if (content.type === 'blog') {
|
||||||
let self = this;
|
let self = this;
|
||||||
tfrpc.rpc.get_blob(content.blog).then(function (data) {
|
self.blog_data = tfrpc.rpc.get_blob(content.blog).then(function (data) {
|
||||||
self.blog_data = data;
|
return data
|
||||||
|
? unsafeHTML(tfutils.markdown(data))
|
||||||
|
: html`Blog post content unavailable.`;
|
||||||
});
|
});
|
||||||
let payload = this.expanded[(this.message.id || '') + ':blog']
|
let payload = this.expanded[(this.message.id || '') + ':blog']
|
||||||
? html`<div>
|
? until(this.blog_data, 'Loading...')
|
||||||
${this.blog_data
|
|
||||||
? unsafeHTML(tfutils.markdown(this.blog_data))
|
|
||||||
: 'Loading...'}
|
|
||||||
</div>`
|
|
||||||
: undefined;
|
: undefined;
|
||||||
let body;
|
let body;
|
||||||
switch (this.format) {
|
switch (this.format) {
|
||||||
@@ -1020,15 +1090,24 @@ class TfMessageElement extends LitElement {
|
|||||||
case 'message':
|
case 'message':
|
||||||
body = html`
|
body = html`
|
||||||
<div
|
<div
|
||||||
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
|
class="w3-border w3-theme-l4 w3-round-xlarge"
|
||||||
@click=${(x) => self.toggle_expanded(':blog')}>
|
style="padding: 8px; margin: 4px; cursor: pointer"
|
||||||
|
@click=${(x) => self.toggle_expanded(':blog')}
|
||||||
|
>
|
||||||
<h2>${content.title}</h2>
|
<h2>${content.title}</h2>
|
||||||
<div style="display: flex; flex-direction: row">
|
<div style="display: flex; flex-direction: row; gap: 8px">
|
||||||
<img src=/${content.thumbnail}/view></img>
|
${content.thumbnail
|
||||||
|
? html`<img src=/${content.thumbnail}/view style="max-width: 25vw; max-height: 25vw"></img>`
|
||||||
|
: undefined}
|
||||||
<span>${content.summary}</span>
|
<span>${content.summary}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="w3-small">
|
||||||
|
${this.expanded[(this.message.id || '') + ':blog']
|
||||||
|
? 'Show less'
|
||||||
|
: 'Show more'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
${payload}
|
<div class="w3-container">${payload}</div>
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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(
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
11
core/core.js
11
core/core.js
@@ -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,16 @@ 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);
|
||||||
|
};
|
||||||
|
imports.ssb.getBlocks = ssb_internal.getBlocks.bind(ssb_internal);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.credentials &&
|
process.credentials &&
|
||||||
|
|||||||
@@ -34,12 +34,18 @@
|
|||||||
<p>
|
<p>
|
||||||
If you encounter objectionable content, you can filter it from your view
|
If you encounter objectionable content, you can filter it from your view
|
||||||
by blocking the user who posted it. This also makes it so that users
|
by blocking the user who posted it. This also makes it so that users
|
||||||
following you will not see it as a consequence of following you.
|
following you will not see it as a consequence of following you. You
|
||||||
|
moderate for your friends and vice versa.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You can also flag a message to report to the operators of services with
|
||||||
|
which you interact that the it should be considered for removal.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The <code>admin</code> app contains a variety of settings that control the
|
The <code>admin</code> app contains a variety of settings that control the
|
||||||
types of connections Tilde Friends will make or accept, including whether
|
types of connections Tilde Friends will make or accept, including whether
|
||||||
the app will even accept or make connections at all.
|
the app will even accept or make connections at all. This is also where a
|
||||||
|
local blocklist can be managed.
|
||||||
</p>
|
</p>
|
||||||
<h2>This app is not a service</h2>
|
<h2>This app is not a service</h2>
|
||||||
<p>
|
<p>
|
||||||
@@ -47,12 +53,6 @@
|
|||||||
has no more ability to see or filter what you post or read than any other
|
has no more ability to see or filter what you post or read than any other
|
||||||
user of the network.
|
user of the network.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
If you believe objectionable content obtained from a service that is
|
|
||||||
running as part of the Secure Scuttlebutt network should be flagged,
|
|
||||||
report it to the operator of the service, generally by contacting the
|
|
||||||
email address listed when visiting the server address in a web browser.
|
|
||||||
</p>
|
|
||||||
<h2>Agreement</h2>
|
<h2>Agreement</h2>
|
||||||
<p>
|
<p>
|
||||||
If you do not accept these terms, do not use this app. You may close and
|
If you do not accept these terms, do not use this app. You may close and
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
12
deps/codemirror_src/package-lock.json
generated
vendored
12
deps/codemirror_src/package-lock.json
generated
vendored
@@ -217,9 +217,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/common": {
|
"node_modules/@lezer/common": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz",
|
||||||
"integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==",
|
"integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/css": {
|
"node_modules/@lezer/css": {
|
||||||
@@ -276,9 +276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/lr": {
|
"node_modules/@lezer/lr": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.4.tgz",
|
||||||
"integrity": "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==",
|
"integrity": "sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
|
|||||||
2
deps/sqlite/shell.c
vendored
2
deps/sqlite/shell.c
vendored
@@ -14944,6 +14944,7 @@ static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){
|
|||||||
sqlite3_free(zRet);
|
sqlite3_free(zRet);
|
||||||
zRet = 0;
|
zRet = 0;
|
||||||
}
|
}
|
||||||
|
va_end(ap);
|
||||||
return zRet;
|
return zRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29053,6 +29054,7 @@ static int do_meta_command(char *zLine, ShellState *p){
|
|||||||
}
|
}
|
||||||
p->showHeader = savedShowHeader;
|
p->showHeader = savedShowHeader;
|
||||||
p->shellFlgs = savedShellFlags;
|
p->shellFlgs = savedShellFlags;
|
||||||
|
rc = p->nErr>0;
|
||||||
}else
|
}else
|
||||||
|
|
||||||
if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
|
if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
|
||||||
|
|||||||
58
deps/sqlite/sqlite3.c
vendored
58
deps/sqlite/sqlite3.c
vendored
@@ -1,6 +1,6 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
** This file is an amalgamation of many separate C source files from SQLite
|
** This file is an amalgamation of many separate C source files from SQLite
|
||||||
** version 3.51.0. By combining all the individual C code files into this
|
** version 3.51.1. By combining all the individual C code files into this
|
||||||
** single large file, the entire code can be compiled as a single translation
|
** single large file, the entire code can be compiled as a single translation
|
||||||
** unit. This allows many compilers to do optimizations that would not be
|
** unit. This allows many compilers to do optimizations that would not be
|
||||||
** possible if the files were compiled separately. Performance improvements
|
** possible if the files were compiled separately. Performance improvements
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
** separate file. This file contains only code for the core SQLite library.
|
** separate file. This file contains only code for the core SQLite library.
|
||||||
**
|
**
|
||||||
** The content in this amalgamation comes from Fossil check-in
|
** The content in this amalgamation comes from Fossil check-in
|
||||||
** fb2c931ae597f8d00a37574ff67aeed3eced with changes in files:
|
** 281fc0e9afc38674b9b0991943b9e9d1e64c with changes in files:
|
||||||
**
|
**
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
@@ -467,12 +467,12 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.51.0"
|
#define SQLITE_VERSION "3.51.1"
|
||||||
#define SQLITE_VERSION_NUMBER 3051000
|
#define SQLITE_VERSION_NUMBER 3051001
|
||||||
#define SQLITE_SOURCE_ID "2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b"
|
#define SQLITE_SOURCE_ID "2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88"
|
||||||
#define SQLITE_SCM_BRANCH "trunk"
|
#define SQLITE_SCM_BRANCH "branch-3.51"
|
||||||
#define SQLITE_SCM_TAGS "release major-release version-3.51.0"
|
#define SQLITE_SCM_TAGS "release version-3.51.1"
|
||||||
#define SQLITE_SCM_DATETIME "2025-11-04T19:38:17.314Z"
|
#define SQLITE_SCM_DATETIME "2025-11-28T17:28:25.933Z"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@@ -10747,7 +10747,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
|||||||
** ){
|
** ){
|
||||||
** // do something with pVal
|
** // do something with pVal
|
||||||
** }
|
** }
|
||||||
** if( rc!=SQLITE_OK ){
|
** if( rc!=SQLITE_DONE ){
|
||||||
** // an error has occurred
|
** // an error has occurred
|
||||||
** }
|
** }
|
||||||
** </pre></blockquote>)^
|
** </pre></blockquote>)^
|
||||||
@@ -38004,6 +38004,7 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/************** End of hash.c ************************************************/
|
/************** End of hash.c ************************************************/
|
||||||
/************** Begin file opcodes.c *****************************************/
|
/************** Begin file opcodes.c *****************************************/
|
||||||
/* Automatically generated. Do not edit */
|
/* Automatically generated. Do not edit */
|
||||||
@@ -130655,6 +130656,7 @@ SQLITE_PRIVATE void sqlite3SchemaClear(void *p){
|
|||||||
for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
|
for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
|
||||||
sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem));
|
sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem));
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3HashClear(&temp2);
|
sqlite3HashClear(&temp2);
|
||||||
sqlite3HashInit(&pSchema->tblHash);
|
sqlite3HashInit(&pSchema->tblHash);
|
||||||
for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
|
for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
|
||||||
@@ -160976,9 +160978,12 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
|
|||||||
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
|
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
|
||||||
addModuleArgument(pParse, pTab, 0);
|
addModuleArgument(pParse, pTab, 0);
|
||||||
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
|
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
|
||||||
|
db->nSchemaLock++;
|
||||||
rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr);
|
rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr);
|
||||||
|
db->nSchemaLock--;
|
||||||
if( rc ){
|
if( rc ){
|
||||||
sqlite3ErrorMsg(pParse, "%s", zErr);
|
sqlite3ErrorMsg(pParse, "%s", zErr);
|
||||||
|
pParse->rc = rc;
|
||||||
sqlite3DbFree(db, zErr);
|
sqlite3DbFree(db, zErr);
|
||||||
sqlite3VtabEponymousTableClear(db, pMod);
|
sqlite3VtabEponymousTableClear(db, pMod);
|
||||||
}
|
}
|
||||||
@@ -174040,8 +174045,22 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
|
|||||||
sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
|
sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
|
||||||
}
|
}
|
||||||
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
|
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
|
||||||
if( pTabList->a[pLevel->iFrom].fg.fromExists ){
|
if( pTabList->a[pLevel->iFrom].fg.fromExists && i==pWInfo->nLevel-1 ){
|
||||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
|
/* If the EXISTS-to-JOIN optimization was applied, then the EXISTS
|
||||||
|
** loop(s) will be the inner-most loops of the join. There might be
|
||||||
|
** multiple EXISTS loops, but they will all be nested, and the join
|
||||||
|
** order will not have been changed by the query planner. If the
|
||||||
|
** inner-most EXISTS loop sees a single successful row, it should
|
||||||
|
** break out of *all* EXISTS loops. But only the inner-most of the
|
||||||
|
** nested EXISTS loops should do this breakout. */
|
||||||
|
int nOuter = 0; /* Nr of outer EXISTS that this one is nested within */
|
||||||
|
while( nOuter<i ){
|
||||||
|
if( !pTabList->a[pLevel[-nOuter-1].iFrom].fg.fromExists ) break;
|
||||||
|
nOuter++;
|
||||||
|
}
|
||||||
|
testcase( nOuter>0 );
|
||||||
|
sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk);
|
||||||
|
VdbeComment((v, "EXISTS break"));
|
||||||
}
|
}
|
||||||
/* The common case: Advance to the next row */
|
/* The common case: Advance to the next row */
|
||||||
if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
|
if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
|
||||||
@@ -186225,6 +186244,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
|
|||||||
/* Clear the TEMP schema separately and last */
|
/* Clear the TEMP schema separately and last */
|
||||||
if( db->aDb[1].pSchema ){
|
if( db->aDb[1].pSchema ){
|
||||||
sqlite3SchemaClear(db->aDb[1].pSchema);
|
sqlite3SchemaClear(db->aDb[1].pSchema);
|
||||||
|
assert( db->aDb[1].pSchema->trigHash.count==0 );
|
||||||
}
|
}
|
||||||
sqlite3VtabUnlockList(db);
|
sqlite3VtabUnlockList(db);
|
||||||
|
|
||||||
@@ -187553,7 +187573,7 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){
|
|||||||
*/
|
*/
|
||||||
SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){
|
SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){
|
||||||
int rc = SQLITE_OK;
|
int rc = SQLITE_OK;
|
||||||
if( !sqlite3SafetyCheckSickOrOk(db) ){
|
if( !sqlite3SafetyCheckOk(db) ){
|
||||||
return SQLITE_MISUSE_BKPT;
|
return SQLITE_MISUSE_BKPT;
|
||||||
}
|
}
|
||||||
sqlite3_mutex_enter(db->mutex);
|
sqlite3_mutex_enter(db->mutex);
|
||||||
@@ -249220,6 +249240,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
|
|||||||
while( 1 ){
|
while( 1 ){
|
||||||
u64 iDelta = 0;
|
u64 iDelta = 0;
|
||||||
|
|
||||||
|
if( i>=n ) break;
|
||||||
if( eDetail==FTS5_DETAIL_NONE ){
|
if( eDetail==FTS5_DETAIL_NONE ){
|
||||||
/* todo */
|
/* todo */
|
||||||
if( i<n && a[i]==0 ){
|
if( i<n && a[i]==0 ){
|
||||||
@@ -260283,7 +260304,7 @@ static void fts5SourceIdFunc(
|
|||||||
){
|
){
|
||||||
assert( nArg==0 );
|
assert( nArg==0 );
|
||||||
UNUSED_PARAM2(nArg, apUnused);
|
UNUSED_PARAM2(nArg, apUnused);
|
||||||
sqlite3_result_text(pCtx, "fts5: 2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b", -1, SQLITE_TRANSIENT);
|
sqlite3_result_text(pCtx, "fts5: 2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88", -1, SQLITE_TRANSIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -265104,7 +265125,12 @@ static int fts5VocabOpenMethod(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Restore cursor pCsr to the state it was in immediately after being
|
||||||
|
** created by the xOpen() method.
|
||||||
|
*/
|
||||||
static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
|
static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
|
||||||
|
int nCol = pCsr->pFts5->pConfig->nCol;
|
||||||
pCsr->rowid = 0;
|
pCsr->rowid = 0;
|
||||||
sqlite3Fts5IterClose(pCsr->pIter);
|
sqlite3Fts5IterClose(pCsr->pIter);
|
||||||
sqlite3Fts5StructureRelease(pCsr->pStruct);
|
sqlite3Fts5StructureRelease(pCsr->pStruct);
|
||||||
@@ -265114,6 +265140,12 @@ static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
|
|||||||
pCsr->nLeTerm = -1;
|
pCsr->nLeTerm = -1;
|
||||||
pCsr->zLeTerm = 0;
|
pCsr->zLeTerm = 0;
|
||||||
pCsr->bEof = 0;
|
pCsr->bEof = 0;
|
||||||
|
pCsr->iCol = 0;
|
||||||
|
pCsr->iInstPos = 0;
|
||||||
|
pCsr->iInstOff = 0;
|
||||||
|
pCsr->colUsed = 0;
|
||||||
|
memset(pCsr->aCnt, 0, sizeof(i64)*nCol);
|
||||||
|
memset(pCsr->aDoc, 0, sizeof(i64)*nCol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
14
deps/sqlite/sqlite3.h
vendored
14
deps/sqlite/sqlite3.h
vendored
@@ -146,12 +146,12 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.51.0"
|
#define SQLITE_VERSION "3.51.1"
|
||||||
#define SQLITE_VERSION_NUMBER 3051000
|
#define SQLITE_VERSION_NUMBER 3051001
|
||||||
#define SQLITE_SOURCE_ID "2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b"
|
#define SQLITE_SOURCE_ID "2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88"
|
||||||
#define SQLITE_SCM_BRANCH "trunk"
|
#define SQLITE_SCM_BRANCH "branch-3.51"
|
||||||
#define SQLITE_SCM_TAGS "release major-release version-3.51.0"
|
#define SQLITE_SCM_TAGS "release version-3.51.1"
|
||||||
#define SQLITE_SCM_DATETIME "2025-11-04T19:38:17.314Z"
|
#define SQLITE_SCM_DATETIME "2025-11-28T17:28:25.933Z"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@@ -10426,7 +10426,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
|||||||
** ){
|
** ){
|
||||||
** // do something with pVal
|
** // do something with pVal
|
||||||
** }
|
** }
|
||||||
** if( rc!=SQLITE_OK ){
|
** if( rc!=SQLITE_DONE ){
|
||||||
** // an error has occurred
|
** // an error has occurred
|
||||||
** }
|
** }
|
||||||
** </pre></blockquote>)^
|
** </pre></blockquote>)^
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#define YELLOW "\e[1;33m"
|
#define YELLOW "\e[1;33m"
|
||||||
#define RESET "\e[0m"
|
#define RESET "\e[0m"
|
||||||
|
|
||||||
static const int k_eula_version = 1;
|
static const int k_eula_version = 2;
|
||||||
|
|
||||||
static JSClassID _httpd_request_class_id;
|
static JSClassID _httpd_request_class_id;
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
93
src/ssb.db.c
93
src/ssb.db.c
@@ -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')"))
|
||||||
@@ -890,7 +894,7 @@ bool tf_ssb_db_blob_has(sqlite3* db, const char* id)
|
|||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
sqlite3_stmt* statement;
|
sqlite3_stmt* statement;
|
||||||
const char* query = "SELECT COUNT(*) FROM blobs WHERE id = ?1";
|
const char* query = "SELECT COUNT(*) FROM blobs LEFT OUTER JOIN blocks ON blobs.id = blocks.id WHERE blobs.id = ?1 AND blocks.id IS NULL";
|
||||||
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||||
@@ -907,7 +911,7 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
|
|||||||
bool result = false;
|
bool result = false;
|
||||||
sqlite3_stmt* statement;
|
sqlite3_stmt* statement;
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
const char* query = "SELECT content FROM blobs WHERE id = ?1";
|
const char* query = "SELECT content FROM blobs LEFT OUTER JOIN blocks ON blobs.id = blocks.id WHERE blobs.id = ?1 AND blocks.id IS NULL";
|
||||||
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||||
@@ -1165,7 +1169,8 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
|
|||||||
|
|
||||||
if (out_message_id)
|
if (out_message_id)
|
||||||
{
|
{
|
||||||
const char* query = "SELECT id, sequence FROM messages WHERE author = ?1 ORDER BY sequence DESC LIMIT 1";
|
const char* query = "SELECT messages.id, messages.sequence FROM messages LEFT OUTER JOIN blocks ON messages.id = blocks.id WHERE author = ?1 AND blocks.id IS NULL ORDER "
|
||||||
|
"BY messages.sequence DESC LIMIT 1";
|
||||||
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||||
@@ -1189,7 +1194,8 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const char* query = "SELECT max_sequence FROM messages_stats WHERE author = ?1";
|
const char* query = "SELECT messages_stats.max_sequence FROM messages_stats LEFT OUTER JOIN blocks ON messages_stats.author = blocks.id WHERE messages_stats.author = ?1 "
|
||||||
|
"AND blocks.id IS NULL";
|
||||||
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||||
@@ -2868,3 +2874,80 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data)
|
||||||
|
{
|
||||||
|
sqlite3_stmt* statement = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db, "SELECT id, timestamp FROM blocks ORDER BY timestamp", -1, &statement, NULL) == SQLITE_OK)
|
||||||
|
{
|
||||||
|
while (sqlite3_step(statement) == SQLITE_ROW)
|
||||||
|
{
|
||||||
|
callback((const char*)sqlite3_column_text(statement, 0), sqlite3_column_double(statement, 1), user_data);
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
30
src/ssb.db.h
30
src/ssb.db.h
@@ -617,4 +617,34 @@ 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Get block list.
|
||||||
|
** @param db The database.
|
||||||
|
** @param callback Called for each entry.
|
||||||
|
** @param user_data User data to be passed to the callback.
|
||||||
|
*/
|
||||||
|
void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|||||||
126
src/ssb.js.c
126
src/ssb.js.c
@@ -2172,6 +2172,129 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _block_t
|
||||||
|
{
|
||||||
|
char id[k_id_base64_len];
|
||||||
|
double timestamp;
|
||||||
|
} block_t;
|
||||||
|
|
||||||
|
typedef struct _get_blocks_t
|
||||||
|
{
|
||||||
|
block_t* blocks;
|
||||||
|
int count;
|
||||||
|
JSValue promise[2];
|
||||||
|
} get_blocks_t;
|
||||||
|
|
||||||
|
static void _get_blocks_callback(const char* id, double timestamp, void* user_data)
|
||||||
|
{
|
||||||
|
get_blocks_t* work = user_data;
|
||||||
|
work->blocks = tf_resize_vec(work->blocks, sizeof(block_t) * (work->count + 1));
|
||||||
|
work->blocks[work->count] = (block_t) { .timestamp = timestamp };
|
||||||
|
tf_string_set(work->blocks[work->count].id, sizeof(work->blocks[work->count].id), id);
|
||||||
|
work->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_get_blocks_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
|
tf_ssb_db_get_blocks(db, _get_blocks_callback, user_data);
|
||||||
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_get_blocks_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
get_blocks_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
|
||||||
|
JSValue result = JS_NewArray(context);
|
||||||
|
for (int i = 0; i < work->count; i++)
|
||||||
|
{
|
||||||
|
JSValue entry = JS_NewObject(context);
|
||||||
|
JS_SetPropertyStr(context, entry, "id", JS_NewString(context, work->blocks[i].id));
|
||||||
|
JS_SetPropertyStr(context, entry, "timestamp", JS_NewFloat64(context, work->blocks[i].timestamp));
|
||||||
|
JS_SetPropertyUint32(context, result, i, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
tf_free(work->blocks);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_get_blocks(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
|
{
|
||||||
|
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||||
|
get_blocks_t* work = tf_malloc(sizeof(get_blocks_t));
|
||||||
|
*work = (get_blocks_t) { 0 };
|
||||||
|
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
tf_ssb_run_work(ssb, _tf_ssb_get_blocks_work, _tf_ssb_get_blocks_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 +2350,9 @@ 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_SetPropertyStr(context, object_internal, "getBlocks", JS_NewCFunction(context, _tf_ssb_get_blocks, "getBlocks", 0));
|
||||||
|
|
||||||
JS_FreeValue(context, global);
|
JS_FreeValue(context, global);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1851,4 +1851,81 @@ 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);
|
||||||
|
|
||||||
|
assert(tf_ssb_db_blob_get(ssb, blob_id, NULL, NULL));
|
||||||
|
|
||||||
|
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);
|
||||||
|
assert(!tf_ssb_db_blob_get(ssb, blob_id, NULL, NULL));
|
||||||
|
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);
|
||||||
|
assert(tf_ssb_db_blob_get(ssb, blob_id, NULL, NULL));
|
||||||
|
|
||||||
|
tf_ssb_destroy(ssb);
|
||||||
|
|
||||||
|
uv_run(&loop, UV_RUN_DEFAULT);
|
||||||
|
uv_loop_close(&loop);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def select(driver, path, action = None, keep_trying = False):
|
|||||||
elif node == 'shadow_root':
|
elif node == 'shadow_root':
|
||||||
context = context.shadow_root
|
context = context.shadow_root
|
||||||
else:
|
else:
|
||||||
context = context.find_element(By.TAG_NAME, node)
|
context = context.find_element(By.CSS_SELECTOR, node)
|
||||||
if action is not None:
|
if action is not None:
|
||||||
if action[0] == 'click':
|
if action[0] == 'click':
|
||||||
context.click()
|
context.click()
|
||||||
@@ -89,6 +89,16 @@ try:
|
|||||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'We made it to the ssb app.'))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'We made it to the ssb app.'))
|
||||||
|
|
||||||
|
driver.get('http://localhost:8888/')
|
||||||
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'Hello, world!'))
|
||||||
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
||||||
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
|
driver.get('http://localhost:8888/')
|
||||||
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', 'tf-tab-news-feed', 'shadow_root', 'tf-news', 'shadow_root', 'tf-message', 'shadow_root', 'button'], ('click',))
|
||||||
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', 'tf-tab-news-feed', 'shadow_root', 'tf-news', 'shadow_root', 'tf-message', 'shadow_root', '#button_flag'], ('click',))
|
||||||
|
wait.until(expected_conditions.alert_is_present()).send_keys('cusswords')
|
||||||
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
|
|
||||||
driver.get('http://localhost:8888/~core/admin/')
|
driver.get('http://localhost:8888/~core/admin/')
|
||||||
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
|
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
|
||||||
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
|
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
|
||||||
@@ -179,14 +189,14 @@ try:
|
|||||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
words = select(driver, ['#document', 'frame', '//li//textarea']).get_attribute('value')
|
words = select(driver, ['#document', 'frame', '//li//textarea']).get_attribute('value')
|
||||||
select(driver, ['#document', 'frame', '//li/button[text()="Delete Identity"]'], ('click',))
|
select(driver, ['#document', 'frame', '//li/button[text()="Delete Identity"]'], ('click',))
|
||||||
driver.switch_to.alert.send_keys('DELETE')
|
wait.until(expected_conditions.alert_is_present()).send_keys('DELETE')
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
words = select(driver, ['#document', 'frame', '//textarea'], ('send_keys', words))
|
words = select(driver, ['#document', 'frame', '//textarea'], ('send_keys', words))
|
||||||
select(driver, ['#document', 'frame', '//button[text()="Import Identity"]'], ('click',))
|
select(driver, ['#document', 'frame', '//button[text()="Import Identity"]'], ('click',))
|
||||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
driver.switch_to.frame(wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))))
|
driver.switch_to.frame(wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))))
|
||||||
id1 = select(driver, ['#document', 'frame', 'li']).text.split(' ')[-1]
|
id1 = select(driver, ['#document', 'frame', 'li']).text.split(' ')[-1]
|
||||||
assert id0 == id1
|
assert id0 == id1
|
||||||
@@ -197,16 +207,16 @@ try:
|
|||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
||||||
select(driver, ['//label[text()="Remember this decision."]'], ('click',))
|
select(driver, ['//label[text()="Remember this decision."]'], ('click',))
|
||||||
select(driver, ['//button[text()="❌ Deny"]'], ('click',))
|
select(driver, ['//button[text()="❌ Deny"]'], ('click',))
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
||||||
select(driver, ['tf-navigation', 'shadow_root', '=🎛️'], ('click',))
|
select(driver, ['tf-navigation', 'shadow_root', '=🎛️'], ('click',))
|
||||||
select(driver, ['tf-navigation', 'shadow_root', '#permission_reset:ssb_append'], ('click',))
|
select(driver, ['tf-navigation', 'shadow_root', '#permission_reset:ssb_append'], ('click',))
|
||||||
select(driver, ['tf-navigation', 'shadow_root', '#permissions_close'], ('click',))
|
select(driver, ['tf-navigation', 'shadow_root', '#permissions_close'], ('click',))
|
||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
||||||
select(driver, ['//button[text()="❌ Deny"]'], ('click',))
|
select(driver, ['//button[text()="❌ Deny"]'], ('click',))
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
|
||||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'Hello, world 2!'))
|
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'Hello, world 2!'))
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user