diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts index a356ca7abf..1cd7642ba5 100644 --- a/packages/backend/src/queue/processors/db/delete-account.ts +++ b/packages/backend/src/queue/processors/db/delete-account.ts @@ -17,9 +17,7 @@ export async function deleteAccount( logger.info(`Deleting account of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); - if (user == null) { - return; - } + if (!user) return; { // Delete notes diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 3571135aa5..83c6442dde 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -15,9 +15,11 @@ export async function deleteActor( return `skip: delete actor ${actor.uri} !== ${uri}`; } - const user = await Users.findOneByOrFail({ id: actor.id }); - if (user.isDeleted) { - logger.info("skip: already deleted"); + const user = await Users.findOneBy({ id: actor.id }); + if (!user) { + return `skip: actor ${actor.id} not found in the local database`; + } else if (user.isDeleted) { + return `skip: user ${user.id} already deleted`; } const job = await createDeleteAccountJob(actor); diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index d362d1b9e5..c68a09585f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -12,7 +12,7 @@ export async function getInstance(response: Entity.Instance) { uri: response.uri, title: response.title || "Calckey", short_description: - response.description.substring(0, 50) || "See real server website", + response.description?.substring(0, 50) || "See real server website", description: response.description || "This is a vanilla Calckey Instance. It doesnt seem to have a description. BTW you are using the Mastodon api to access this server :)", diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index ab3f365705..9696c3ccaa 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -56,7 +56,7 @@ import { checkHitAntenna } from "@/misc/check-hit-antenna.js"; import { getWordHardMute } from "@/misc/check-word-mute.js"; import { addNoteToAntenna } from "../add-note-to-antenna.js"; import { countSameRenotes } from "@/misc/count-same-renotes.js"; -import { deliverToRelays } from "../relay.js"; +import { deliverToRelays, getCachedRelays } from "../relay.js"; import type { Channel } from "@/models/entities/channel.js"; import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { getAntennas } from "@/misc/antenna-cache.js"; @@ -68,6 +68,7 @@ import { db } from "@/db/postgre.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; import meilisearch from "../../db/meilisearch.js"; +import { redisClient } from "@/db/redis.js"; const mutedWordsCache = new Cache< { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] @@ -165,6 +166,7 @@ export default async ( isSilenced: User["isSilenced"]; createdAt: User["createdAt"]; isBot: User["isBot"]; + inbox?: User["inbox"]; }, data: Option, silent = false, @@ -453,7 +455,37 @@ export default async ( } if (!dontFederateInitially) { - publishNotesStream(note); + const relays = await getCachedRelays(); + // Some relays (e.g., aode-relay) deliver posts by boosting them as + // Announce activities. In that case, user is the relay's actor. + const boostedByRelay = + !!user.inbox && + relays.map((relay) => relay.inbox).includes(user.inbox); + + if (!note.uri) { + // Publish if the post is local + publishNotesStream(note); + } else if ( + boostedByRelay && + data.renote?.uri && + (await redisClient.exists(`publishedNote:${data.renote.uri}`)) === 0 + ) { + // Publish if the post was boosted by a relay and not yet published. + publishNotesStream(data.renote); + const key = `publishedNote:${data.renote.uri}`; + await redisClient.set(key, 1, "EX", 30); + } else if ( + !boostedByRelay && + note.uri && + (await redisClient.exists(`publishedNote:${note.uri}`)) === 0 + ) { + // Publish if the post came directly from a remote server, or from a + // relay that doesn't boost the post (e.g, YUKIMOCHI Activity-Relay), + // and not yet published. + const key = `publishedNote:${note.uri}`; + publishNotesStream(note); + await redisClient.set(key, 1, "EX", 30); + } } if (note.replyId != null) { // Only provide the reply note id here as the recipient may not be authorized to see the note. @@ -524,7 +556,6 @@ export default async ( nm.push(data.renote.userId, type); } } - // Fetch watchers nmRelatedPromises.push( notifyToWatchersOfRenotee(data.renote, user, nm, type), @@ -537,8 +568,9 @@ export default async ( }); publishMainStream(data.renote.userId, "renote", packedRenote); + const renote = data.renote; const webhooks = (await getActiveWebhooks()).filter( - (x) => x.userId === data.renote!.userId && x.on.includes("renote"), + (x) => x.userId === renote.userId && x.on.includes("renote"), ); for (const webhook of webhooks) { webhookDeliver(webhook, "renote", { diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 244e05c030..bec4b1f86b 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -37,7 +37,7 @@ export async function addRelay(inbox: string) { }).then((x) => Relays.findOneByOrFail(x.identifiers[0])); const relayActor = await getRelayActor(); - const follow = await renderFollowRelay(relay, relayActor); + const follow = renderFollowRelay(relay, relayActor); const activity = renderActivity(follow); deliver(relayActor, activity, relay.inbox); @@ -60,6 +60,7 @@ export async function removeRelay(inbox: string) { deliver(relayActor, activity, relay.inbox); await Relays.delete(relay.id); + await updateRelaysCache(); } export async function listRelay() { @@ -67,14 +68,31 @@ export async function listRelay() { return relays; } +export async function getCachedRelays(): Promise { + return await relaysCache.fetch(null, () => + Relays.findBy({ + status: "accepted", + }), + ); +} + export async function relayAccepted(id: string) { const result = await Relays.update(id, { status: "accepted", }); + await updateRelaysCache(); + return JSON.stringify(result); } +async function updateRelaysCache() { + const relays = await Relays.findBy({ + status: "accepted", + }); + relaysCache.set(null, relays); +} + export async function relayRejected(id: string) { const result = await Relays.update(id, { status: "rejected", @@ -89,11 +107,7 @@ export async function deliverToRelays( ) { if (activity == null) return; - const relays = await relaysCache.fetch(null, () => - Relays.findBy({ - status: "accepted", - }), - ); + const relays = await getCachedRelays(); if (relays.length === 0) return; // TODO diff --git a/packages/client/package.json b/packages/client/package.json index 9816f69631..d024277ddf 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -87,7 +87,7 @@ "vue-isyourpasswordsafe": "^2.0.0", "vue-plyr": "^7.0.0", "vue-prism-editor": "2.0.0-alpha.2", - "vue3-otp-input": "^0.4.1", + "vue3-otp-input": "github:thatonecalculator/vue3-otp-input", "vuedraggable": "4.1.0" } } diff --git a/packages/client/src/components/MkSignin.vue b/packages/client/src/components/MkSignin.vue index 0c8fffcaf0..3d2e05dfae 100644 --- a/packages/client/src/components/MkSignin.vue +++ b/packages/client/src/components/MkSignin.vue @@ -101,7 +101,7 @@ { - token = value; + token = value.toString(); }; const meta = $computed(() => instance); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2eef2ca45..c91365d22b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -904,8 +904,8 @@ importers: specifier: 2.0.0-alpha.2 version: 2.0.0-alpha.2(vue@3.3.4) vue3-otp-input: - specifier: ^0.4.1 - version: 0.4.1(vue@3.3.4) + specifier: github:thatonecalculator/vue3-otp-input + version: github.com/thatonecalculator/vue3-otp-input/098b24b224661884983a5035ef3b245a519dff14(vue@3.3.4) vuedraggable: specifier: 4.1.0 version: 4.1.0(vue@3.3.4) @@ -15209,15 +15209,6 @@ packages: vue: 3.3.4 dev: true - /vue3-otp-input@0.4.1(vue@3.3.4): - resolution: {integrity: sha512-wVl9i3DcWlO0C7fBI9V+RIP3crm/1tY72fuhvb3YM2JfbLoYofB96aPl5AgFhA0Cse5bQEMYtIvOeiqW3rfbAw==} - engines: {node: '>=16.0.0', npm: '>=8.0.0'} - peerDependencies: - vue: ^3.0.* - dependencies: - vue: 3.3.4 - dev: true - /vue@2.7.14: resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==} dependencies: @@ -15904,3 +15895,15 @@ packages: rangetouch: 2.0.1 url-polyfill: 1.1.12 dev: true + + github.com/thatonecalculator/vue3-otp-input/098b24b224661884983a5035ef3b245a519dff14(vue@3.3.4): + resolution: {tarball: https://codeload.github.com/thatonecalculator/vue3-otp-input/tar.gz/098b24b224661884983a5035ef3b245a519dff14} + id: github.com/thatonecalculator/vue3-otp-input/098b24b224661884983a5035ef3b245a519dff14 + name: vue3-otp-input + version: 0.4.1 + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + vue: ^3.0.* + dependencies: + vue: 3.3.4 + dev: true