mirror of
https://github.com/aljazceru/ditto.git
synced 2026-01-22 23:04:23 +01:00
Merge remote-tracking branch 'origin/main' into feat-persist-user-preference
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { getPublicKeyPem } from '@/utils/rsa.ts';
|
||||
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
@@ -7,7 +8,7 @@ import type { Actor } from '@/schemas/activitypub.ts';
|
||||
|
||||
/** Nostr metadata event to ActivityPub actor. */
|
||||
async function renderActor(event: NostrEvent, username: string): Promise<Actor | undefined> {
|
||||
const content = jsonMetaContentSchema.parse(event.content);
|
||||
const content = n.json().pipe(n.metadata()).catch({}).parse(event.content);
|
||||
|
||||
return {
|
||||
type: 'Person',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
import { nip19, UnsignedEvent } from 'nostr-tools';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { lodash, nip19, type UnsignedEvent } from '@/deps.ts';
|
||||
import { lodash } from '@/deps.ts';
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { getLnurl } from '@/utils/lnurl.ts';
|
||||
import { nip05Cache } from '@/utils/nip05.ts';
|
||||
import { Nip05, nostrDate, nostrNow, parseNip05 } from '@/utils.ts';
|
||||
@@ -26,7 +28,7 @@ async function renderAccount(
|
||||
about,
|
||||
lud06,
|
||||
lud16,
|
||||
} = jsonMetaContentSchema.parse(event.content);
|
||||
} = n.json().pipe(n.metadata()).catch({}).parse(event.content);
|
||||
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const parsed05 = await parseAndVerifyNip05(nip05, pubkey);
|
||||
@@ -77,6 +79,7 @@ async function renderAccount(
|
||||
is_admin: role === 'admin',
|
||||
is_moderator: ['admin', 'moderator'].includes(role),
|
||||
is_local: parsed05?.domain === Conf.url.host,
|
||||
settings_store: undefined as unknown,
|
||||
},
|
||||
nostr: {
|
||||
pubkey,
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { nostrDate } from '@/utils.ts';
|
||||
|
||||
import { accountFromPubkey, renderAccount } from './accounts.ts';
|
||||
|
||||
/** Expects a kind 0 fully hydrated */
|
||||
async function renderAdminAccount(event: DittoEvent) {
|
||||
const d = event.tags.find(([name]) => name === 'd')?.[1]!;
|
||||
const account = event.d_author ? await renderAccount({ ...event.d_author, user: event }) : await accountFromPubkey(d);
|
||||
const account = await renderAccount(event);
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
username: event.tags.find(([name]) => name === 'name')?.[1]!,
|
||||
username: account.username,
|
||||
domain: account.acct.split('@')[1] || null,
|
||||
created_at: nostrDate(event.created_at).toISOString(),
|
||||
created_at: account.created_at,
|
||||
email: '',
|
||||
ip: null,
|
||||
ips: [],
|
||||
locale: '',
|
||||
invite_request: null,
|
||||
role: event.tags.find(([name]) => name === 'role')?.[1] || 'user',
|
||||
role: event.tags.find(([name]) => name === 'role')?.[1],
|
||||
confirmed: true,
|
||||
approved: true,
|
||||
disabled: false,
|
||||
@@ -27,4 +25,28 @@ async function renderAdminAccount(event: DittoEvent) {
|
||||
};
|
||||
}
|
||||
|
||||
export { renderAdminAccount };
|
||||
/** Expects a target pubkey */
|
||||
async function renderAdminAccountFromPubkey(pubkey: string) {
|
||||
const account = await accountFromPubkey(pubkey);
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
username: account.username,
|
||||
domain: account.acct.split('@')[1] || null,
|
||||
created_at: account.created_at,
|
||||
email: '',
|
||||
ip: null,
|
||||
ips: [],
|
||||
locale: '',
|
||||
invite_request: null,
|
||||
role: 'user',
|
||||
confirmed: true,
|
||||
approved: true,
|
||||
disabled: false,
|
||||
silenced: false,
|
||||
suspended: false,
|
||||
account,
|
||||
};
|
||||
}
|
||||
|
||||
export { renderAdminAccount, renderAdminAccountFromPubkey };
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
import { UnattachedMedia } from '@/db/unattached-media.ts';
|
||||
import { type TypeFest } from '@/deps.ts';
|
||||
import { getUrlMediaType } from '@/utils/media.ts';
|
||||
|
||||
type DittoAttachment = TypeFest.SetOptional<UnattachedMedia, 'id' | 'pubkey' | 'uploaded_at'>;
|
||||
/** Render Mastodon media attachment. */
|
||||
function renderAttachment(media: { id?: string; data: string[][] }) {
|
||||
const { id, data: tags } = media;
|
||||
|
||||
const url = tags.find(([name]) => name === 'url')?.[1];
|
||||
|
||||
const m = tags.find(([name]) => name === 'm')?.[1] ?? getUrlMediaType(url!);
|
||||
const alt = tags.find(([name]) => name === 'alt')?.[1];
|
||||
const cid = tags.find(([name]) => name === 'cid')?.[1];
|
||||
const dim = tags.find(([name]) => name === 'dim')?.[1];
|
||||
const blurhash = tags.find(([name]) => name === 'blurhash')?.[1];
|
||||
|
||||
if (!url) return;
|
||||
|
||||
const [width, height] = dim?.split('x').map(Number) ?? [null, null];
|
||||
|
||||
const meta = (typeof width === 'number' && typeof height === 'number')
|
||||
? {
|
||||
original: {
|
||||
width,
|
||||
height,
|
||||
aspect: width / height,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
function renderAttachment(media: DittoAttachment) {
|
||||
const { id, data, url } = media;
|
||||
return {
|
||||
id: id ?? url ?? data.cid,
|
||||
type: getAttachmentType(data.mime ?? ''),
|
||||
id: id ?? url,
|
||||
type: getAttachmentType(m ?? ''),
|
||||
url,
|
||||
preview_url: url,
|
||||
remote_url: null,
|
||||
description: data.description ?? '',
|
||||
blurhash: data.blurhash || null,
|
||||
cid: data.cid,
|
||||
description: alt ?? '',
|
||||
blurhash: blurhash || null,
|
||||
meta,
|
||||
cid: cid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,4 +53,4 @@ function getAttachmentType(mime: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export { type DittoAttachment, renderAttachment };
|
||||
export { renderAttachment };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { UnsignedEvent } from '@/deps.ts';
|
||||
import { UnsignedEvent } from 'nostr-tools';
|
||||
|
||||
import { EmojiTag, emojiTagSchema } from '@/schemas/nostr.ts';
|
||||
import { filteredArray } from '@/schema.ts';
|
||||
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { getAuthor } from '@/queries.ts';
|
||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { nostrDate } from '@/utils.ts';
|
||||
import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
|
||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
function renderNotification(event: NostrEvent, viewerPubkey?: string) {
|
||||
switch (event.kind) {
|
||||
case 1:
|
||||
return renderNotificationMention(event, viewerPubkey);
|
||||
interface RenderNotificationOpts {
|
||||
viewerPubkey: string;
|
||||
}
|
||||
|
||||
function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
const mentioned = !!event.tags.find(([name, value]) => name === 'p' && value === opts.viewerPubkey);
|
||||
|
||||
if (event.kind === 1 && mentioned) {
|
||||
return renderMention(event, opts);
|
||||
}
|
||||
|
||||
if (event.kind === 6) {
|
||||
return renderReblog(event, opts);
|
||||
}
|
||||
|
||||
if (event.kind === 7 && event.content === '+') {
|
||||
return renderFavourite(event, opts);
|
||||
}
|
||||
|
||||
if (event.kind === 7) {
|
||||
return renderReaction(event, opts);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderNotificationMention(event: NostrEvent, viewerPubkey?: string) {
|
||||
const author = await getAuthor(event.pubkey);
|
||||
const status = await renderStatus({ ...event, author }, { viewerPubkey: viewerPubkey });
|
||||
async function renderMention(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
const status = await renderStatus(event, opts);
|
||||
if (!status) return;
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
id: notificationId(event),
|
||||
type: 'mention',
|
||||
created_at: nostrDate(event.created_at).toISOString(),
|
||||
account: status.account,
|
||||
@@ -25,4 +41,55 @@ async function renderNotificationMention(event: NostrEvent, viewerPubkey?: strin
|
||||
};
|
||||
}
|
||||
|
||||
export { accountFromPubkey, renderNotification };
|
||||
async function renderReblog(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
if (event.repost?.kind !== 1) return;
|
||||
const status = await renderStatus(event.repost, opts);
|
||||
if (!status) return;
|
||||
const account = event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey);
|
||||
|
||||
return {
|
||||
id: notificationId(event),
|
||||
type: 'reblog',
|
||||
created_at: nostrDate(event.created_at).toISOString(),
|
||||
account,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
async function renderFavourite(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
if (event.reacted?.kind !== 1) return;
|
||||
const status = await renderStatus(event.reacted, opts);
|
||||
if (!status) return;
|
||||
const account = event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey);
|
||||
|
||||
return {
|
||||
id: notificationId(event),
|
||||
type: 'favourite',
|
||||
created_at: nostrDate(event.created_at).toISOString(),
|
||||
account,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
async function renderReaction(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
if (event.reacted?.kind !== 1) return;
|
||||
const status = await renderStatus(event.reacted, opts);
|
||||
if (!status) return;
|
||||
const account = event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey);
|
||||
|
||||
return {
|
||||
id: notificationId(event),
|
||||
type: 'pleroma:emoji_reaction',
|
||||
emoji: event.content,
|
||||
created_at: nostrDate(event.created_at).toISOString(),
|
||||
account,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
/** This helps notifications be sorted in the correct order. */
|
||||
function notificationId({ id, created_at }: NostrEvent): string {
|
||||
return `${created_at}-${id}`;
|
||||
}
|
||||
|
||||
export { renderNotification };
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { hasTag } from '@/tags.ts';
|
||||
|
||||
async function renderRelationship(sourcePubkey: string, targetPubkey: string) {
|
||||
const events = await eventsDB.query([
|
||||
const db = await Storages.db();
|
||||
|
||||
const events = await db.query([
|
||||
{ kinds: [3], authors: [sourcePubkey], limit: 1 },
|
||||
{ kinds: [3], authors: [targetPubkey], limit: 1 },
|
||||
{ kinds: [10000], authors: [sourcePubkey], limit: 1 },
|
||||
{ kinds: [10000], authors: [targetPubkey], limit: 1 },
|
||||
]);
|
||||
|
||||
const event3 = events.find((event) => event.kind === 3 && event.pubkey === sourcePubkey);
|
||||
const target3 = events.find((event) => event.kind === 3 && event.pubkey === targetPubkey);
|
||||
const event10000 = events.find((event) => event.kind === 10000 && event.pubkey === sourcePubkey);
|
||||
const target10000 = events.find((event) => event.kind === 10000 && event.pubkey === targetPubkey);
|
||||
|
||||
return {
|
||||
id: targetPubkey,
|
||||
@@ -20,9 +20,9 @@ async function renderRelationship(sourcePubkey: string, targetPubkey: string) {
|
||||
showing_reblogs: true,
|
||||
notifying: false,
|
||||
followed_by: target3 ? hasTag(target3?.tags, ['p', sourcePubkey]) : false,
|
||||
blocking: event10000 ? hasTag(event10000.tags, ['p', targetPubkey]) : false,
|
||||
blocked_by: target10000 ? hasTag(target10000.tags, ['p', sourcePubkey]) : false,
|
||||
muting: false,
|
||||
blocking: false,
|
||||
blocked_by: false,
|
||||
muting: event10000 ? hasTag(event10000.tags, ['p', targetPubkey]) : false,
|
||||
muting_notifications: false,
|
||||
requested: false,
|
||||
domain_blocking: false,
|
||||
|
||||
78
src/views/mastodon/reports.ts
Normal file
78
src/views/mastodon/reports.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { nostrDate } from '@/utils.ts';
|
||||
import { renderAdminAccount, renderAdminAccountFromPubkey } from '@/views/mastodon/admin-accounts.ts';
|
||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||
|
||||
/** Expects a `reportEvent` of kind 1984 and a `profile` of kind 0 of the person being reported */
|
||||
async function renderReport(event: DittoEvent) {
|
||||
// The category is present in both the 'e' and 'p' tag, however, it is possible to report a user without reporting a note, so it's better to get the category from the 'p' tag
|
||||
const category = event.tags.find(([name]) => name === 'p')?.[2];
|
||||
const statusIds = event.tags.filter(([name]) => name === 'e').map((tag) => tag[1]) ?? [];
|
||||
const reportedPubkey = event.tags.find(([name]) => name === 'p')?.[1];
|
||||
if (!reportedPubkey) return;
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
action_taken: false,
|
||||
action_taken_at: null,
|
||||
category,
|
||||
comment: event.content,
|
||||
forwarded: false,
|
||||
created_at: nostrDate(event.created_at).toISOString(),
|
||||
status_ids: statusIds,
|
||||
rules_ids: null,
|
||||
target_account: event.reported_profile
|
||||
? await renderAccount(event.reported_profile)
|
||||
: await accountFromPubkey(reportedPubkey),
|
||||
};
|
||||
}
|
||||
|
||||
interface RenderAdminReportOpts {
|
||||
viewerPubkey?: string;
|
||||
actionTaken?: boolean;
|
||||
}
|
||||
|
||||
/** Admin-level information about a filed report.
|
||||
* Expects an event of kind 1984 fully hydrated.
|
||||
* https://docs.joinmastodon.org/entities/Admin_Report */
|
||||
async function renderAdminReport(reportEvent: DittoEvent, opts: RenderAdminReportOpts) {
|
||||
const { viewerPubkey, actionTaken = false } = opts;
|
||||
|
||||
// The category is present in both the 'e' and 'p' tag, however, it is possible to report a user without reporting a note, so it's better to get the category from the 'p' tag
|
||||
const category = reportEvent.tags.find(([name]) => name === 'p')?.[2];
|
||||
|
||||
const statuses = [];
|
||||
if (reportEvent.reported_notes) {
|
||||
for (const status of reportEvent.reported_notes) {
|
||||
statuses.push(await renderStatus(status, { viewerPubkey }));
|
||||
}
|
||||
}
|
||||
|
||||
const reportedPubkey = reportEvent.tags.find(([name]) => name === 'p')?.[1];
|
||||
if (!reportedPubkey) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
id: reportEvent.id,
|
||||
action_taken: actionTaken,
|
||||
action_taken_at: null,
|
||||
category,
|
||||
comment: reportEvent.content,
|
||||
forwarded: false,
|
||||
created_at: nostrDate(reportEvent.created_at).toISOString(),
|
||||
account: reportEvent.author
|
||||
? await renderAdminAccount(reportEvent.author)
|
||||
: await renderAdminAccountFromPubkey(reportEvent.pubkey),
|
||||
target_account: reportEvent.reported_profile
|
||||
? await renderAdminAccount(reportEvent.reported_profile)
|
||||
: await renderAdminAccountFromPubkey(reportedPubkey),
|
||||
assigned_account: null,
|
||||
action_taken_by_account: null,
|
||||
statuses,
|
||||
rule: [],
|
||||
};
|
||||
}
|
||||
|
||||
export { renderAdminReport, renderReport };
|
||||
@@ -1,28 +1,27 @@
|
||||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { nip19 } from '@/deps.ts';
|
||||
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||
import { getMediaLinks, parseNoteContent } from '@/note.ts';
|
||||
import { getAuthor } from '@/queries.ts';
|
||||
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
|
||||
import { eventsDB } from '@/storages.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { findReplyTag } from '@/tags.ts';
|
||||
import { nostrDate } from '@/utils.ts';
|
||||
import { getMediaLinks, parseNoteContent, stripimeta } from '@/utils/note.ts';
|
||||
import { unfurlCardCached } from '@/utils/unfurl.ts';
|
||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
|
||||
import { renderAttachment } from '@/views/mastodon/attachments.ts';
|
||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||
|
||||
interface statusOpts {
|
||||
interface RenderStatusOpts {
|
||||
viewerPubkey?: string;
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||
async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<any> {
|
||||
const { viewerPubkey, depth = 1 } = opts;
|
||||
|
||||
if (depth > 2 || depth < 0) return null;
|
||||
if (depth > 2 || depth < 0) return;
|
||||
|
||||
const note = nip19.noteEncode(event.id);
|
||||
|
||||
@@ -40,14 +39,23 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||
),
|
||||
];
|
||||
|
||||
const { html, links, firstUrl } = parseNoteContent(event.content);
|
||||
const db = await Storages.db();
|
||||
const optimizer = await Storages.optimizer();
|
||||
|
||||
const mentionedProfiles = await optimizer.query(
|
||||
[{ kinds: [0], authors: mentionedPubkeys, limit: mentionedPubkeys.length }],
|
||||
);
|
||||
|
||||
const { html, links, firstUrl } = parseNoteContent(stripimeta(event.content, event.tags));
|
||||
|
||||
const [mentions, card, relatedEvents] = await Promise
|
||||
.all([
|
||||
Promise.all(mentionedPubkeys.map(toMention)),
|
||||
Promise.all(
|
||||
mentionedPubkeys.map((pubkey) => toMention(pubkey, mentionedProfiles.find((event) => event.pubkey === pubkey))),
|
||||
),
|
||||
firstUrl ? unfurlCardCached(firstUrl) : null,
|
||||
viewerPubkey
|
||||
? await eventsDB.query([
|
||||
? await db.query([
|
||||
{ kinds: [6], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
||||
{ kinds: [7], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
||||
{ kinds: [9734], '#e': [event.id], authors: [viewerPubkey], limit: 1 },
|
||||
@@ -68,13 +76,11 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||
const cw = event.tags.find(isCWTag);
|
||||
const subject = event.tags.find((tag) => tag[0] === 'subject');
|
||||
|
||||
const mediaLinks = getMediaLinks(links);
|
||||
const imeta: string[][][] = event.tags
|
||||
.filter(([name]) => name === 'imeta')
|
||||
.map(([_, ...entries]) => entries.map((entry) => entry.split(' ')));
|
||||
|
||||
const mediaTags: DittoAttachment[] = event.tags
|
||||
.filter((tag) => tag[0] === 'media')
|
||||
.map(([_, url, json]) => ({ url, data: jsonMediaDataSchema.parse(json) }));
|
||||
|
||||
const media = [...mediaLinks, ...mediaTags];
|
||||
const media = imeta.length ? imeta : getMediaLinks(links);
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
@@ -98,12 +104,12 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||
pinned: Boolean(pinEvent),
|
||||
reblog: null,
|
||||
application: null,
|
||||
media_attachments: media.map(renderAttachment),
|
||||
media_attachments: media.map((m) => renderAttachment({ data: m })).filter(Boolean),
|
||||
mentions,
|
||||
tags: [],
|
||||
emojis: renderEmojis(event),
|
||||
poll: null,
|
||||
quote: !event.quote_repost ? null : await renderStatus(event.quote_repost, { depth: depth + 1 }),
|
||||
quote: !event.quote ? null : await renderStatus(event.quote, { depth: depth + 1 }),
|
||||
quote_id: event.tags.find(([name]) => name === 'q')?.[1] ?? null,
|
||||
uri: Conf.external(note),
|
||||
url: Conf.external(note),
|
||||
@@ -111,11 +117,9 @@ async function renderStatus(event: DittoEvent, opts: statusOpts): Promise<any> {
|
||||
};
|
||||
}
|
||||
|
||||
async function renderReblog(event: DittoEvent, opts: statusOpts) {
|
||||
async function renderReblog(event: DittoEvent, opts: RenderStatusOpts) {
|
||||
const { viewerPubkey } = opts;
|
||||
|
||||
if (!event.author) return;
|
||||
|
||||
const repostId = event.tags.find(([name]) => name === 'e')?.[1];
|
||||
if (!repostId) return;
|
||||
|
||||
@@ -125,15 +129,14 @@ async function renderReblog(event: DittoEvent, opts: statusOpts) {
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
account: await renderAccount(event.author),
|
||||
account: event.author ? await renderAccount(event.author) : await accountFromPubkey(event.pubkey),
|
||||
reblogged: true,
|
||||
reblog,
|
||||
};
|
||||
}
|
||||
|
||||
async function toMention(pubkey: string) {
|
||||
const author = await getAuthor(pubkey);
|
||||
const account = author ? await renderAccount(author) : undefined;
|
||||
async function toMention(pubkey: string, event?: NostrEvent) {
|
||||
const account = event ? await renderAccount(event) : undefined;
|
||||
|
||||
if (account) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user