Add interfaces for Mastodon entity types, hide deactivated accounts

This commit is contained in:
Alex Gleason 2024-06-15 19:36:34 -05:00
parent 06db5d2b1d
commit e63ee9b5a3
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
7 changed files with 143 additions and 27 deletions

View file

@ -0,0 +1,58 @@
/** Mastodon account entity, including supported extensions from Pleroma, etc. */
export interface MastodonAccount {
id: string;
acct: string;
avatar: string;
avatar_static: string;
bot: boolean;
created_at: string;
discoverable: boolean;
display_name: string;
emojis: {
shortcode: string;
static_url: string;
url: string;
}[];
fields: unknown[];
follow_requests_count: number;
followers_count: number;
following_count: number;
fqn: string;
header: string;
header_static: string;
last_status_at: string | null;
locked: boolean;
note: string;
roles: unknown[];
source?: {
fields: unknown[];
language: string;
note: string;
privacy: string;
sensitive: boolean;
follow_requests_count: number;
nostr: {
nip05?: string;
};
};
statuses_count: number;
url: string;
username: string;
ditto: {
accepts_zaps: boolean;
};
pleroma: {
deactivated: boolean;
is_admin: boolean;
is_moderator: boolean;
is_suggested: boolean;
is_local: boolean;
settings_store: unknown;
tags: string[];
};
nostr: {
pubkey: string;
lud16?: string;
};
website?: string;
}

View file

@ -0,0 +1,6 @@
export interface MastodonMention {
acct: string;
id: string;
url: string;
username: string;
}

View file

@ -0,0 +1,42 @@
import { MastodonAccount } from '@/entities/MastodonAccount.ts';
import { PreviewCard } from '@/entities/PreviewCard.ts';
export interface MastodonStatus {
id: string;
account: MastodonAccount;
card: PreviewCard | null;
content: string;
created_at: string;
in_reply_to_id: string | null;
in_reply_to_account_id: string | null;
sensitive: boolean;
spoiler_text: string;
visibility: string;
language: string | null;
replies_count: number;
reblogs_count: number;
favourites_count: number;
zaps_amount: number;
favourited: boolean;
reblogged: boolean;
muted: boolean;
bookmarked: boolean;
pinned: boolean;
reblog: MastodonStatus | null;
application: unknown;
media_attachments: unknown[];
mentions: unknown[];
tags: unknown[];
emojis: unknown[];
poll: unknown;
quote?: MastodonStatus | null;
quote_id: string | null;
uri: string;
url: string;
zapped: boolean;
pleroma: {
emoji_reactions: { name: string; count: number; me: boolean }[];
expires_at?: string;
quotes_count: number;
};
}

View file

@ -0,0 +1,16 @@
export interface PreviewCard {
url: string;
title: string;
description: string;
type: 'link' | 'photo' | 'video' | 'rich';
author_name: string;
author_url: string;
provider_name: string;
provider_url: string;
html: string;
width: number;
height: number;
image: string | null;
embed_url: string;
blurhash: string | null;
}

View file

@ -3,28 +3,12 @@ import Debug from '@soapbox/stickynotes/debug';
import DOMPurify from 'isomorphic-dompurify';
import { unfurl } from 'unfurl.js';
import { PreviewCard } from '@/entities/PreviewCard.ts';
import { Time } from '@/utils/time.ts';
import { fetchWorker } from '@/workers/fetch.ts';
const debug = Debug('ditto:unfurl');
interface PreviewCard {
url: string;
title: string;
description: string;
type: 'link' | 'photo' | 'video' | 'rich';
author_name: string;
author_url: string;
provider_name: string;
provider_url: string;
html: string;
width: number;
height: number;
image: string | null;
embed_url: string;
blurhash: string | null;
}
async function unfurlCard(url: string, signal: AbortSignal): Promise<PreviewCard | null> {
debug(`Unfurling ${url}...`);
try {

View file

@ -3,6 +3,7 @@ import { escape } from 'entities';
import { nip19, UnsignedEvent } from 'nostr-tools';
import { Conf } from '@/config.ts';
import { MastodonAccount } from '@/entities/MastodonAccount.ts';
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
import { getLnurl } from '@/utils/lnurl.ts';
import { nip05Cache } from '@/utils/nip05.ts';
@ -17,10 +18,17 @@ interface ToAccountOpts {
async function renderAccount(
event: Omit<DittoEvent, 'id' | 'sig'>,
opts: ToAccountOpts = {},
) {
): Promise<MastodonAccount> {
const { withSource = false } = opts;
const { pubkey } = event;
const names = getTagSet(event.user?.tags ?? [], 'n');
if (names.has('disabled') || names.has('suspended')) {
const account = await accountFromPubkey(pubkey, opts);
account.pleroma.deactivated = true;
return account;
}
const {
name,
nip05,
@ -34,7 +42,6 @@ async function renderAccount(
const npub = nip19.npubEncode(pubkey);
const parsed05 = await parseAndVerifyNip05(nip05, pubkey);
const names = getTagSet(event.user?.tags ?? [], 'n');
return {
id: pubkey,
@ -77,6 +84,7 @@ async function renderAccount(
accepts_zaps: Boolean(getLnurl({ lud06, lud16 })),
},
pleroma: {
deactivated: names.has('disabled') || names.has('suspended'),
is_admin: names.has('admin'),
is_moderator: names.has('admin') || names.has('moderator'),
is_suggested: names.has('suggested'),
@ -92,7 +100,7 @@ async function renderAccount(
};
}
function accountFromPubkey(pubkey: string, opts: ToAccountOpts = {}) {
function accountFromPubkey(pubkey: string, opts: ToAccountOpts = {}): Promise<MastodonAccount> {
const event: UnsignedEvent = {
kind: 0,
pubkey,

View file

@ -2,6 +2,8 @@ import { NostrEvent } from '@nostrify/nostrify';
import { nip19 } from 'nostr-tools';
import { Conf } from '@/config.ts';
import { MastodonMention } from '@/entities/MastodonMention.ts';
import { MastodonStatus } from '@/entities/MastodonStatus.ts';
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
import { Storages } from '@/storages.ts';
import { nostrDate } from '@/utils.ts';
@ -17,7 +19,7 @@ interface RenderStatusOpts {
depth?: number;
}
async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<any> {
async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<MastodonStatus | undefined> {
const { viewerPubkey, depth = 1 } = opts;
if (depth > 2 || depth < 0) return;
@ -130,12 +132,14 @@ async function renderStatus(event: DittoEvent, opts: RenderStatusOpts): Promise<
};
}
async function renderReblog(event: DittoEvent, opts: RenderStatusOpts) {
async function renderReblog(event: DittoEvent, opts: RenderStatusOpts): Promise<MastodonStatus | undefined> {
const { viewerPubkey } = opts;
if (!event.repost) return;
const status = await renderStatus(event, {}); // omit viewerPubkey intentionally
const reblog = await renderStatus(event.repost, { viewerPubkey });
if (!status) return;
const reblog = await renderStatus(event.repost, { viewerPubkey }) ?? null;
return {
...status,
@ -145,7 +149,7 @@ async function renderReblog(event: DittoEvent, opts: RenderStatusOpts) {
};
}
async function toMention(pubkey: string, event?: NostrEvent) {
async function toMention(pubkey: string, event?: NostrEvent): Promise<MastodonMention> {
const account = event ? await renderAccount(event) : undefined;
if (account) {
@ -166,9 +170,7 @@ async function toMention(pubkey: string, event?: NostrEvent) {
}
}
type Mention = Awaited<ReturnType<typeof toMention>>;
function buildInlineRecipients(mentions: Mention[]): string {
function buildInlineRecipients(mentions: MastodonMention[]): string {
if (!mentions.length) return '';
const elements = mentions.reduce<string[]>((acc, { url, username }) => {