kopia lustrzana https://github.com/cloudflare/wildebeest
MOW-101: hide DM from public
rodzic
5ce31c4d75
commit
196d6fa896
|
@ -1,4 +1,5 @@
|
|||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
|
||||
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
|
||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
|
||||
|
@ -131,11 +132,17 @@ export async function handle(
|
|||
// FIXME: download any attachment Objects
|
||||
|
||||
let recipients: Array<string> = []
|
||||
let target = PUBLIC_GROUP
|
||||
|
||||
if (Array.isArray(activity.to)) {
|
||||
if (Array.isArray(activity.to) && activity.to.length > 0) {
|
||||
recipients = [...recipients, ...activity.to]
|
||||
|
||||
if (activity.to.length !== 1) {
|
||||
console.warn("multiple `Activity.to` isn't supported")
|
||||
}
|
||||
if (Array.isArray(activity.cc)) {
|
||||
target = activity.to[0]
|
||||
}
|
||||
if (Array.isArray(activity.cc) && activity.cc.length > 0) {
|
||||
recipients = [...recipients, ...activity.cc]
|
||||
}
|
||||
|
||||
|
@ -172,7 +179,7 @@ export async function handle(
|
|||
const fromActor = await actors.getAndCache(getActorAsId(), db)
|
||||
// Add the object in the originating actor's outbox, allowing other
|
||||
// actors on this instance to see the note in their timelines.
|
||||
await addObjectInOutbox(db, fromActor, obj, activity.published)
|
||||
await addObjectInOutbox(db, fromActor, obj, activity.published, target)
|
||||
|
||||
for (let i = 0, len = recipients.length; i < len; i++) {
|
||||
const handle = parseHandle(extractID(domain, recipients[i]))
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export type Activity = any
|
||||
|
||||
export const PUBLIC_GROUP = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
|
||||
// Generate a unique ID. Note that currently the generated URL aren't routable.
|
||||
export function uri(domain: string): URL {
|
||||
const id = crypto.randomUUID()
|
||||
|
|
|
@ -2,20 +2,27 @@ import type { Object } from 'wildebeest/backend/src/activitypub/objects'
|
|||
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
|
||||
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { OrderedCollection, OrderedCollectionPage } from 'wildebeest/backend/src/activitypub/core'
|
||||
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
|
||||
|
||||
export async function addObjectInOutbox(db: D1Database, actor: Actor, obj: Object, published_date?: string) {
|
||||
export async function addObjectInOutbox(
|
||||
db: D1Database,
|
||||
actor: Actor,
|
||||
obj: Object,
|
||||
published_date?: string,
|
||||
target: string = PUBLIC_GROUP
|
||||
) {
|
||||
const id = crypto.randomUUID()
|
||||
let out: any = null
|
||||
|
||||
if (published_date !== undefined) {
|
||||
out = await db
|
||||
.prepare('INSERT INTO outbox_objects(id, actor_id, object_id, published_date) VALUES(?, ?, ?, ?)')
|
||||
.bind(id, actor.id.toString(), obj.id.toString(), published_date)
|
||||
.prepare('INSERT INTO outbox_objects(id, actor_id, object_id, published_date, target) VALUES(?, ?, ?, ?, ?)')
|
||||
.bind(id, actor.id.toString(), obj.id.toString(), published_date, target)
|
||||
.run()
|
||||
} else {
|
||||
out = await db
|
||||
.prepare('INSERT INTO outbox_objects(id, actor_id, object_id) VALUES(?, ?, ?)')
|
||||
.bind(id, actor.id.toString(), obj.id.toString())
|
||||
.prepare('INSERT INTO outbox_objects(id, actor_id, object_id, target) VALUES(?, ?, ?, ?)')
|
||||
.bind(id, actor.id.toString(), obj.id.toString(), target)
|
||||
.run()
|
||||
}
|
||||
if (!out.success) {
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { Document } from 'wildebeest/backend/src/activitypub/objects'
|
||||
import { followersURL } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
|
||||
import * as objects from '.'
|
||||
|
||||
const NOTE = 'Note'
|
||||
export const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
|
||||
export interface Note extends objects.Object {
|
||||
|
@ -34,7 +34,7 @@ export async function createPublicNote(
|
|||
const properties = {
|
||||
attributedTo: actorId,
|
||||
content,
|
||||
to: [PUBLIC],
|
||||
to: [PUBLIC_GROUP],
|
||||
cc: [followersURL(actorId)],
|
||||
|
||||
// FIXME: stub values
|
||||
|
@ -50,3 +50,34 @@ export async function createPublicNote(
|
|||
|
||||
return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note
|
||||
}
|
||||
|
||||
export async function createPrivateNote(
|
||||
domain: string,
|
||||
db: D1Database,
|
||||
content: string,
|
||||
actor: Actor,
|
||||
targetActor: Actor,
|
||||
attachment: Array<Document> = [],
|
||||
extraProperties: any = {}
|
||||
): Promise<Note> {
|
||||
const actorId = new URL(actor.id)
|
||||
|
||||
const properties = {
|
||||
attributedTo: actorId,
|
||||
content,
|
||||
to: [targetActor.id.toString()],
|
||||
cc: [],
|
||||
|
||||
// FIXME: stub values
|
||||
inReplyTo: null,
|
||||
replies: null,
|
||||
sensitive: false,
|
||||
summary: null,
|
||||
tag: [],
|
||||
attachment,
|
||||
|
||||
...extraProperties,
|
||||
}
|
||||
|
||||
return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
|
|||
import { getFollowingId } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import type { Actor } from 'wildebeest/backend/src/activitypub/actors/'
|
||||
import { toMastodonStatusFromRow } from './status'
|
||||
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
|
||||
|
||||
export async function pregenerateTimelines(domain: string, db: D1Database, cache: KVNamespace, actor: Actor) {
|
||||
const timeline = await getHomeTimeline(domain, db, actor)
|
||||
|
@ -31,6 +32,7 @@ WHERE
|
|||
objects.type = 'Note'
|
||||
AND outbox_objects.actor_id IN (SELECT value FROM json_each(?))
|
||||
AND json_extract(objects.properties, '$.inReplyTo') IS NULL
|
||||
AND outbox_objects.target = '${PUBLIC_GROUP}'
|
||||
ORDER by outbox_objects.published_date DESC
|
||||
LIMIT ?
|
||||
`
|
||||
|
@ -97,6 +99,7 @@ INNER JOIN actors ON actors.id=outbox_objects.actor_id
|
|||
WHERE objects.type='Note'
|
||||
AND ${localPreferenceQuery(localPreference)}
|
||||
AND json_extract(objects.properties, '$.inReplyTo') IS NULL
|
||||
AND outbox_objects.target = '${PUBLIC_GROUP}'
|
||||
ORDER by outbox_objects.published_date DESC
|
||||
LIMIT ?1 OFFSET ?2
|
||||
`
|
||||
|
|
|
@ -2,7 +2,7 @@ import { makeDB, isUrlValid } from './utils'
|
|||
import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
|
||||
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||
import { strict as assert } from 'node:assert/strict'
|
||||
import { cacheObject } from 'wildebeest/backend/src/activitypub/objects/'
|
||||
|
@ -74,7 +74,7 @@ describe('ActivityPub', () => {
|
|||
await sleep(10)
|
||||
await addObjectInOutbox(db, actor, await createPublicNote(domain, db, 'my second status', actor))
|
||||
|
||||
const res = await ap_outbox_page.handleRequest(domain, db, 'sven', userKEK)
|
||||
const res = await ap_outbox_page.handleRequest(domain, db, 'sven')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
|
@ -83,6 +83,46 @@ describe('ActivityPub', () => {
|
|||
assert.equal(data.orderedItems[0].object.content, 'my second status')
|
||||
assert.equal(data.orderedItems[1].object.content, 'my first status')
|
||||
})
|
||||
|
||||
test("doesn't show private notes to anyone", async () => {
|
||||
const db = await makeDB()
|
||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
|
||||
|
||||
const note = await createPrivateNote(domain, db, 'DM', actorA, actorB)
|
||||
await addObjectInOutbox(db, actorA, note, undefined, actorB.id.toString())
|
||||
|
||||
{
|
||||
const res = await ap_outbox_page.handleRequest(domain, db, 'a')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
assert.equal(data.orderedItems.length, 0)
|
||||
}
|
||||
|
||||
{
|
||||
const res = await ap_outbox_page.handleRequest(domain, db, 'b')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
assert.equal(data.orderedItems.length, 0)
|
||||
}
|
||||
})
|
||||
|
||||
test("should show private notes to target but doesn't yet", async () => {
|
||||
const db = await makeDB()
|
||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||
const actorB = await createPerson(domain, db, userKEK, 'target@cloudflare.com')
|
||||
|
||||
const note = await createPrivateNote(domain, db, 'DM', actorA, actorB)
|
||||
await addObjectInOutbox(db, actorA, note)
|
||||
|
||||
const res = await ap_outbox_page.handleRequest(domain, db, 'target')
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
assert.equal(data.orderedItems.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Objects', () => {
|
||||
|
|
|
@ -14,136 +14,6 @@ const vapidKeys = {} as JWK
|
|||
|
||||
describe('ActivityPub', () => {
|
||||
describe('handle Activity', () => {
|
||||
test('Note to inbox stores in DB', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: [actor.id.toString()],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const entry = await db
|
||||
.prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id')
|
||||
.first()
|
||||
const properties = JSON.parse(entry.properties)
|
||||
assert.equal(properties.content, 'test note')
|
||||
})
|
||||
|
||||
test("Note adds in remote actor's outbox", async () => {
|
||||
const remoteActorId = 'https://example.com/actor'
|
||||
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
if (input.toString() === remoteActorId) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: remoteActorId,
|
||||
type: 'Person',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error('unexpected request to ' + input)
|
||||
}
|
||||
|
||||
const db = await makeDB()
|
||||
await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: remoteActorId,
|
||||
to: [],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first()
|
||||
assert.equal(entry.actor_id, remoteActorId)
|
||||
})
|
||||
|
||||
test('local actor sends Note with mention create notification', async () => {
|
||||
const db = await makeDB()
|
||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actorB.id.toString(),
|
||||
to: [actorA.id.toString()],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note2',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
|
||||
assert(entry)
|
||||
assert.equal(entry.type, 'mention')
|
||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||
assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
|
||||
})
|
||||
|
||||
test('Note records reply', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
{
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: [actor.id.toString()],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'post',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
}
|
||||
|
||||
{
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: [actor.id.toString()],
|
||||
object: {
|
||||
inReplyTo: 'https://example.com/note1',
|
||||
id: 'https://example.com/note2',
|
||||
type: 'Note',
|
||||
content: 'reply',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
}
|
||||
|
||||
const entry = await db.prepare('SELECT * FROM actor_replies').first()
|
||||
assert.equal(entry.actor_id, actor.id.toString().toString())
|
||||
|
||||
const obj: any = await getObjectById(db, entry.object_id)
|
||||
assert(obj)
|
||||
assert.equal(obj.originalObjectId, 'https://example.com/note2')
|
||||
|
||||
const inReplyTo: any = await getObjectById(db, entry.in_reply_to_object_id)
|
||||
assert(inReplyTo)
|
||||
assert.equal(inReplyTo.originalObjectId, 'https://example.com/note1')
|
||||
})
|
||||
|
||||
describe('Announce', () => {
|
||||
test('records reblog in db', async () => {
|
||||
const db = await makeDB()
|
||||
|
@ -314,6 +184,157 @@ describe('ActivityPub', () => {
|
|||
message: '`activity.object` must be of type object',
|
||||
})
|
||||
})
|
||||
|
||||
test('Note to inbox stores in DB', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: [actor.id.toString()],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const entry = await db
|
||||
.prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id')
|
||||
.first()
|
||||
const properties = JSON.parse(entry.properties)
|
||||
assert.equal(properties.content, 'test note')
|
||||
})
|
||||
|
||||
test("Note adds in remote actor's outbox", async () => {
|
||||
const remoteActorId = 'https://example.com/actor'
|
||||
|
||||
globalThis.fetch = async (input: RequestInfo) => {
|
||||
if (input.toString() === remoteActorId) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: remoteActorId,
|
||||
type: 'Person',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error('unexpected request to ' + input)
|
||||
}
|
||||
|
||||
const db = await makeDB()
|
||||
await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: remoteActorId,
|
||||
to: [],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first()
|
||||
assert.equal(entry.actor_id, remoteActorId)
|
||||
})
|
||||
|
||||
test('local actor sends Note with mention create notification', async () => {
|
||||
const db = await makeDB()
|
||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actorB.id.toString(),
|
||||
to: [actorA.id.toString()],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note2',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
|
||||
assert(entry)
|
||||
assert.equal(entry.type, 'mention')
|
||||
assert.equal(entry.actor_id.toString(), actorA.id.toString())
|
||||
assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
|
||||
})
|
||||
|
||||
test('Note records reply', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
{
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: [actor.id.toString()],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'post',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
}
|
||||
|
||||
{
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: [actor.id.toString()],
|
||||
object: {
|
||||
inReplyTo: 'https://example.com/note1',
|
||||
id: 'https://example.com/note2',
|
||||
type: 'Note',
|
||||
content: 'reply',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
}
|
||||
|
||||
const entry = await db.prepare('SELECT * FROM actor_replies').first()
|
||||
assert.equal(entry.actor_id, actor.id.toString().toString())
|
||||
|
||||
const obj: any = await getObjectById(db, entry.object_id)
|
||||
assert(obj)
|
||||
assert.equal(obj.originalObjectId, 'https://example.com/note2')
|
||||
|
||||
const inReplyTo: any = await getObjectById(db, entry.in_reply_to_object_id)
|
||||
assert(inReplyTo)
|
||||
assert.equal(inReplyTo.originalObjectId, 'https://example.com/note1')
|
||||
})
|
||||
|
||||
test('preserve Note sent with `to`', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
||||
const activity: any = {
|
||||
type: 'Create',
|
||||
actor: actor.id.toString(),
|
||||
to: ['some actor'],
|
||||
cc: [],
|
||||
object: {
|
||||
id: 'https://example.com/note1',
|
||||
type: 'Note',
|
||||
content: 'test note',
|
||||
},
|
||||
}
|
||||
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
|
||||
|
||||
const row = await db.prepare('SELECT * FROM outbox_objects').first()
|
||||
assert.equal(row.target, 'some actor')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Update', () => {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
|||
import { insertLike } from 'wildebeest/backend/src/mastodon/like'
|
||||
import { insertReblog } from 'wildebeest/backend/src/mastodon/reblog'
|
||||
import { isUrlValid, makeDB, assertJSON, streamToArrayBuffer, makeQueue } from '../utils'
|
||||
import * as note from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import * as activities from 'wildebeest/backend/src/activitypub/activities'
|
||||
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||
|
||||
|
@ -220,7 +220,7 @@ describe('Mastodon APIs', () => {
|
|||
assert.equal(deliveredNote.actor, `https://${domain}/ap/users/sven`)
|
||||
assert.equal(deliveredNote.object.attributedTo, `https://${domain}/ap/users/sven`)
|
||||
assert.equal(deliveredNote.object.type, 'Note')
|
||||
assert(deliveredNote.object.to.includes(note.PUBLIC))
|
||||
assert(deliveredNote.object.to.includes(activities.PUBLIC_GROUP))
|
||||
assert.equal(deliveredNote.object.cc.length, 1)
|
||||
})
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict'
|
|||
import { insertReply } from 'wildebeest/backend/src/mastodon/reply'
|
||||
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
|
||||
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import { makeDB, assertCORS, assertJSON } from '../utils'
|
||||
|
@ -53,6 +53,40 @@ describe('Mastodon APIs', () => {
|
|||
assert.equal(data[1].reblogs_count, 1)
|
||||
})
|
||||
|
||||
test("home doesn't show private Notes from followed actors", async () => {
|
||||
const db = await makeDB()
|
||||
const actor1 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
|
||||
const actor3 = await createPerson(domain, db, userKEK, 'sven3@cloudflare.com')
|
||||
|
||||
// actor3 follows actor1 and actor2
|
||||
await addFollowing(db, actor3, actor1, 'not needed')
|
||||
await acceptFollowing(db, actor3, actor1)
|
||||
await addFollowing(db, actor3, actor2, 'not needed')
|
||||
await acceptFollowing(db, actor3, actor2)
|
||||
|
||||
// actor2 sends a DM to actor1
|
||||
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
|
||||
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
|
||||
|
||||
// actor3 shouldn't see the private note
|
||||
const data = await timelines.getHomeTimeline(domain, db, actor3)
|
||||
assert.equal(data.length, 0)
|
||||
})
|
||||
|
||||
test("public doesn't show private Notes", async () => {
|
||||
const db = await makeDB()
|
||||
const actor1 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
|
||||
|
||||
// actor2 sends a DM to actor1
|
||||
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
|
||||
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
|
||||
|
||||
const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
|
||||
assert.equal(data.length, 0)
|
||||
})
|
||||
|
||||
test('home returns Notes from ourself', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
|
|
|
@ -7,10 +7,11 @@ import type { ContextData } from 'wildebeest/backend/src/types/context'
|
|||
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import * as activityCreate from 'wildebeest/backend/src/activitypub/activities/create'
|
||||
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
|
||||
|
||||
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
|
||||
const domain = new URL(request.url).hostname
|
||||
return handleRequest(domain, env.DATABASE, params.id as string, env.userKEK)
|
||||
return handleRequest(domain, env.DATABASE, params.id as string)
|
||||
}
|
||||
|
||||
const headers = {
|
||||
|
@ -21,8 +22,7 @@ const headers = {
|
|||
|
||||
const DEFAULT_LIMIT = 20
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use userKEK
|
||||
export async function handleRequest(domain: string, db: D1Database, id: string, userKEK: string): Promise<Response> {
|
||||
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
|
||||
const handle = parseHandle(id)
|
||||
|
||||
if (handle.domain !== null) {
|
||||
|
@ -42,9 +42,11 @@ export async function handleRequest(domain: string, db: D1Database, id: string,
|
|||
SELECT objects.*
|
||||
FROM outbox_objects
|
||||
INNER JOIN objects ON objects.id = outbox_objects.object_id
|
||||
WHERE outbox_objects.actor_id = ? AND objects.type = 'Note'
|
||||
WHERE outbox_objects.actor_id = ?1
|
||||
AND objects.type = 'Note'
|
||||
AND outbox_objects.target = '${PUBLIC_GROUP}'
|
||||
ORDER by outbox_objects.cdate DESC
|
||||
LIMIT ?
|
||||
LIMIT ?2
|
||||
`
|
||||
|
||||
const { success, error, results } = await db.prepare(QUERY).bind(actorId.toString(), DEFAULT_LIMIT).all()
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
-- Migration number: 0002 2023-01-16T13:46:54.975Z
|
||||
|
||||
ALTER TABLE outbox_objects
|
||||
ADD target TEXT NOT NULL DEFAULT 'https://www.w3.org/ns/activitystreams#Public';
|
Ładowanie…
Reference in New Issue