diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 3be4524933..453179bd6f 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -28,7 +28,7 @@ import { import { db } from "@/db/postgre.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; -async function populatePoll(note: Note, meId: User["id"] | null) { +export async function populatePoll(note: Note, meId: User["id"] | null) { const poll = await Polls.findOneByOrFail({ noteId: note.id }); const choices = poll.choices.map((c) => ({ text: c, diff --git a/packages/backend/src/queue/processors/ended-poll-notification.ts b/packages/backend/src/queue/processors/ended-poll-notification.ts index 9fe57d8da3..e3d860ac82 100644 --- a/packages/backend/src/queue/processors/ended-poll-notification.ts +++ b/packages/backend/src/queue/processors/ended-poll-notification.ts @@ -1,9 +1,9 @@ import type Bull from "bull"; -import { In } from "typeorm"; -import { Notes, Polls, PollVotes } from "@/models/index.js"; +import { Notes, PollVotes } from "@/models/index.js"; import { queueLogger } from "../logger.js"; import type { EndedPollNotificationJobData } from "@/queue/types.js"; import { createNotification } from "@/services/create-notification.js"; +import { deliverQuestionUpdate } from "@/services/note/polls/update.js"; const logger = queueLogger.createSubLogger("ended-poll-notification"); @@ -32,5 +32,8 @@ export async function endedPollNotification( }); } + // Broadcast the poll result once it ends + await deliverQuestionUpdate(note.id); + done(); } diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 73589125b4..26aa5bf544 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -13,11 +13,10 @@ import type { import { htmlToMfm } from "../misc/html-to-mfm.js"; import { extractApHashtags } from "./tag.js"; import { unique, toArray, toSingle } from "@/prelude/array.js"; -import { extractPollFromQuestion, updateQuestion } from "./question.js"; +import { extractPollFromQuestion } from "./question.js"; import vote from "@/services/note/polls/vote.js"; import { apLogger } from "../logger.js"; import { DriveFile } from "@/models/entities/drive-file.js"; -import { deliverQuestionUpdate } from "@/services/note/polls/update.js"; import { extractDbHost, toPuny } from "@/misc/convert-host.js"; import { Emojis, @@ -334,9 +333,6 @@ export async function createNote( `vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`, ); await vote(actor, reply, index); - - // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(reply.id); } return null; }; diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index 520b9fee94..f5855c3e7c 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -1,7 +1,7 @@ import config from "@/config/index.js"; import Resolver from "../resolver.js"; import type { IObject, IQuestion } from "../type.js"; -import { isQuestion } from "../type.js"; +import { getApId, isQuestion } from "../type.js"; import { apLogger } from "../logger.js"; import { Notes, Polls } from "@/models/index.js"; import type { IPoll } from "@/models/entities/poll.js"; @@ -47,11 +47,14 @@ export async function extractPollFromQuestion( /** * Update votes of Question - * @param uri URI of AP Question object + * @param value URI of AP Question object or object itself * @returns true if updated */ -export async function updateQuestion(value: any, resolver?: Resolver) { - const uri = typeof value === "string" ? value : value.id; +export async function updateQuestion( + value: string | IQuestion, + resolver?: Resolver, +): Promise { + const uri = typeof value === "string" ? value : getApId(value); // Skip if URI points to this server if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local"); @@ -65,22 +68,23 @@ export async function updateQuestion(value: any, resolver?: Resolver) { //#endregion // resolve new Question object - if (resolver == null) resolver = new Resolver(); - const question = (await resolver.resolve(value)) as IQuestion; + const _resolver = resolver ?? new Resolver(); + const question = (await _resolver.resolve(value)) as IQuestion; apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); if (question.type !== "Question") throw new Error("object is not a Question"); const apChoices = question.oneOf || question.anyOf; + if (!apChoices) return false; let changed = false; for (const choice of poll.choices) { const oldCount = poll.votes[poll.choices.indexOf(choice)]; - const newCount = apChoices!.filter((ap) => ap.name === choice)[0].replies! - .totalItems; + const newCount = apChoices.filter((ap) => ap.name === choice)[0].replies + ?.totalItems; - if (oldCount !== newCount) { + if (newCount !== undefined && oldCount !== newCount) { changed = true; poll.votes[poll.choices.indexOf(choice)] = newCount; } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index fa804802eb..0bd3414ee3 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -1,5 +1,4 @@ import define from "../../define.js"; -import config from "@/config/index.js"; import { createPerson } from "@/remote/activitypub/models/person.js"; import { createNote } from "@/remote/activitypub/models/note.js"; import DbResolver from "@/remote/activitypub/db-resolver.js"; @@ -9,11 +8,13 @@ import { extractDbHost } from "@/misc/convert-host.js"; import { Users, Notes } from "@/models/index.js"; import type { Note } from "@/models/entities/note.js"; import type { CacheableLocalUser, User } from "@/models/entities/user.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; import { isActor, isPost, getApId } from "@/remote/activitypub/type.js"; import type { SchemaType } from "@/misc/schema.js"; import { HOUR } from "@/const.js"; import { shouldBlockInstance } from "@/misc/should-block-instance.js"; +import { updateQuestion } from "@/remote/activitypub/models/question.js"; +import { populatePoll } from "@/models/repositories/note.js"; +import { redisClient } from "@/db/redis.js"; export const meta = { tags: ["federation"], @@ -104,18 +105,29 @@ async function fetchAny( const dbResolver = new DbResolver(); - let local = await mergePack( - me, - ...(await Promise.all([ - dbResolver.getUserFromApId(uri), - dbResolver.getNoteFromApId(uri), - ])), - ); - if (local != null) return local; + const [user, note] = await Promise.all([ + dbResolver.getUserFromApId(uri), + dbResolver.getNoteFromApId(uri), + ]); + let local = await mergePack(me, user, note); + if (local) { + if (local.type === "Note" && note?.uri && note.hasPoll) { + // Update questions if the stored (remote) note contains the poll + const key = `pollFetched:${note.uri}`; + if ((await redisClient.exists(key)) === 0) { + if (await updateQuestion(note.uri)) { + local.object.poll = await populatePoll(note, me?.id ?? null); + } + // Allow fetching the poll again after 1 minute + await redisClient.set(key, 1, "EX", 60); + } + } + return local; + } // fetching Object once from remote const resolver = new Resolver(); - const object = (await resolver.resolve(uri)) as any; + const object = await resolver.resolve(uri); // /@user If a URI other than the id is specified, // the URI is determined here @@ -123,8 +135,8 @@ async function fetchAny( local = await mergePack( me, ...(await Promise.all([ - dbResolver.getUserFromApId(object.id), - dbResolver.getNoteFromApId(object.id), + dbResolver.getUserFromApId(getApId(object)), + dbResolver.getNoteFromApId(getApId(object)), ])), ); if (local != null) return local; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 0558aa1b8f..40535d3401 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -4,7 +4,6 @@ import { createNotification } from "@/services/create-notification.js"; import { deliver } from "@/queue/index.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderVote from "@/remote/activitypub/renderer/vote.js"; -import { deliverQuestionUpdate } from "@/services/note/polls/update.js"; import { PollVotes, NoteWatchings, @@ -178,7 +177,4 @@ export default define(meta, paramDef, async (ps, user) => { pollOwner.inbox, ); } - - // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(note.id); }); diff --git a/packages/client/src/components/MkPoll.vue b/packages/client/src/components/MkPoll.vue index db4c3350ff..1fdbc7a422 100644 --- a/packages/client/src/components/MkPoll.vue +++ b/packages/client/src/components/MkPoll.vue @@ -34,23 +34,25 @@

{{ i18n.t("_poll.totalVotes", { n: total }) }} - · - {{ + + · + {{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult - }} - {{ i18n.ts._poll.voted }} - {{ i18n.ts._poll.closed }} + }} + + + · + {{ i18n.ts.reload }} + + · {{ i18n.ts._poll.voted }} + · {{ i18n.ts._poll.closed }} · {{ timer }}