Add views/mastodon/accounts.ts, views/mastodon/emojis.ts
This commit is contained in:
parent
cb1141784e
commit
0b77e7d888
|
@ -11,7 +11,8 @@ import { isFollowing, lookupAccount, nostrNow, Time } from '@/utils.ts';
|
|||
import { paginated, paginationSchema, parseBody } from '@/utils/web.ts';
|
||||
import { createEvent } from '@/utils/web.ts';
|
||||
import { renderEventAccounts } from '@/views.ts';
|
||||
import { accountFromPubkey, toAccount, toRelationship, toStatus } from '@/views/nostr-to-mastoapi.ts';
|
||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { accountFromPubkey, toRelationship, toStatus } from '@/views/nostr-to-mastoapi.ts';
|
||||
|
||||
const usernameSchema = z
|
||||
.string().min(1).max(30)
|
||||
|
@ -60,7 +61,7 @@ const verifyCredentialsController: AppController = async (c) => {
|
|||
|
||||
const event = await getAuthor(pubkey);
|
||||
if (event) {
|
||||
return c.json(await toAccount(event, { withSource: true }));
|
||||
return c.json(await renderAccount(event, { withSource: true }));
|
||||
} else {
|
||||
return c.json(await accountFromPubkey(pubkey, { withSource: true }));
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ const accountController: AppController = async (c) => {
|
|||
|
||||
const event = await getAuthor(pubkey);
|
||||
if (event) {
|
||||
return c.json(await toAccount(event));
|
||||
return c.json(await renderAccount(event));
|
||||
}
|
||||
|
||||
return c.json({ error: 'Could not find user.' }, 404);
|
||||
|
@ -86,7 +87,7 @@ const accountLookupController: AppController = async (c) => {
|
|||
|
||||
const event = await lookupAccount(decodeURIComponent(acct));
|
||||
if (event) {
|
||||
return c.json(await toAccount(event));
|
||||
return c.json(await renderAccount(event));
|
||||
}
|
||||
|
||||
return c.json({ error: 'Could not find user.' }, 404);
|
||||
|
@ -101,7 +102,7 @@ const accountSearchController: AppController = async (c) => {
|
|||
|
||||
const event = await lookupAccount(decodeURIComponent(q));
|
||||
if (event) {
|
||||
return c.json([await toAccount(event)]);
|
||||
return c.json([await renderAccount(event)]);
|
||||
}
|
||||
|
||||
return c.json([]);
|
||||
|
@ -199,7 +200,7 @@ const updateCredentialsController: AppController = async (c) => {
|
|||
tags: [],
|
||||
}, c);
|
||||
|
||||
const account = await toAccount(event);
|
||||
const account = await renderAccount(event);
|
||||
return c.json(account);
|
||||
};
|
||||
|
||||
|
@ -237,7 +238,7 @@ const followingController: AppController = async (c) => {
|
|||
// TODO: pagination by offset.
|
||||
const accounts = await Promise.all(pubkeys.map(async (pubkey) => {
|
||||
const event = await getAuthor(pubkey);
|
||||
return event ? await toAccount(event) : undefined;
|
||||
return event ? await renderAccount(event) : undefined;
|
||||
}));
|
||||
|
||||
return c.json(accounts.filter(Boolean));
|
||||
|
|
|
@ -6,7 +6,8 @@ import { booleanParamSchema } from '@/schema.ts';
|
|||
import { nostrIdSchema } from '@/schemas/nostr.ts';
|
||||
import { dedupeEvents, Time } from '@/utils.ts';
|
||||
import { lookupNip05Cached } from '@/utils/nip05.ts';
|
||||
import { toAccount, toStatus } from '@/views/nostr-to-mastoapi.ts';
|
||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { toStatus } from '@/views/nostr-to-mastoapi.ts';
|
||||
|
||||
/** Matches NIP-05 names with or without an @ in front. */
|
||||
const ACCT_REGEX = /^@?(?:([\w.+-]+)@)?([\w.-]+)$/;
|
||||
|
@ -44,7 +45,7 @@ const searchController: AppController = async (c) => {
|
|||
Promise.all(
|
||||
results
|
||||
.filter((event): event is Event<0> => event.kind === 0)
|
||||
.map((event) => toAccount(event)),
|
||||
.map((event) => renderAccount(event)),
|
||||
),
|
||||
Promise.all(
|
||||
results
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AppContext } from '@/app.ts';
|
|||
import { type Filter } from '@/deps.ts';
|
||||
import * as mixer from '@/mixer.ts';
|
||||
import { getAuthor } from '@/queries.ts';
|
||||
import { toAccount } from '@/views/nostr-to-mastoapi.ts';
|
||||
import { renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { paginated } from '@/utils/web.ts';
|
||||
|
||||
/** Render account objects for the author of each event. */
|
||||
|
@ -17,7 +17,7 @@ async function renderEventAccounts(c: AppContext, filters: Filter[]) {
|
|||
const accounts = await Promise.all([...pubkeys].map(async (pubkey) => {
|
||||
const author = await getAuthor(pubkey);
|
||||
if (author) {
|
||||
return toAccount(author);
|
||||
return renderAccount(author);
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import { Conf } from '@/config.ts';
|
||||
import * as eventsDB from '@/db/events.ts';
|
||||
import { findUser } from '@/db/users.ts';
|
||||
import { lodash, nip19, type UnsignedEvent } from '@/deps.ts';
|
||||
import { getFollowedPubkeys } from '@/queries.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { verifyNip05Cached } from '@/utils/nip05.ts';
|
||||
import { Nip05, nostrDate, nostrNow, parseNip05 } from '@/utils.ts';
|
||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||
|
||||
interface ToAccountOpts {
|
||||
withSource?: boolean;
|
||||
}
|
||||
|
||||
async function renderAccount(event: UnsignedEvent<0>, opts: ToAccountOpts = {}) {
|
||||
const { withSource = false } = opts;
|
||||
const { pubkey } = event;
|
||||
|
||||
const {
|
||||
name,
|
||||
nip05,
|
||||
picture = Conf.local('/images/avi.png'),
|
||||
banner = Conf.local('/images/banner.png'),
|
||||
about,
|
||||
} = jsonMetaContentSchema.parse(event.content);
|
||||
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
const [user, parsed05, followersCount, followingCount, statusesCount] = await Promise.all([
|
||||
findUser({ pubkey }),
|
||||
parseAndVerifyNip05(nip05, pubkey),
|
||||
eventsDB.countFilters([{ kinds: [3], '#p': [pubkey] }]),
|
||||
getFollowedPubkeys(pubkey).then((pubkeys) => pubkeys.length),
|
||||
eventsDB.countFilters([{ kinds: [1], authors: [pubkey] }]),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: pubkey,
|
||||
acct: parsed05?.handle || npub,
|
||||
avatar: picture,
|
||||
avatar_static: picture,
|
||||
bot: false,
|
||||
created_at: event ? nostrDate(event.created_at).toISOString() : new Date().toISOString(),
|
||||
discoverable: true,
|
||||
display_name: name,
|
||||
emojis: renderEmojis(event),
|
||||
fields: [],
|
||||
follow_requests_count: 0,
|
||||
followers_count: followersCount,
|
||||
following_count: followingCount,
|
||||
fqn: parsed05?.handle || npub,
|
||||
header: banner,
|
||||
header_static: banner,
|
||||
last_status_at: null,
|
||||
locked: false,
|
||||
note: lodash.escape(about),
|
||||
roles: [],
|
||||
source: withSource
|
||||
? {
|
||||
fields: [],
|
||||
language: '',
|
||||
note: about || '',
|
||||
privacy: 'public',
|
||||
sensitive: false,
|
||||
follow_requests_count: 0,
|
||||
}
|
||||
: undefined,
|
||||
statuses_count: statusesCount,
|
||||
url: Conf.local(`/users/${pubkey}`),
|
||||
username: parsed05?.nickname || npub.substring(0, 8),
|
||||
pleroma: {
|
||||
is_admin: user?.admin || false,
|
||||
is_moderator: user?.admin || false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function accountFromPubkey(pubkey: string, opts: ToAccountOpts = {}) {
|
||||
const event: UnsignedEvent<0> = {
|
||||
kind: 0,
|
||||
pubkey,
|
||||
content: '',
|
||||
tags: [],
|
||||
created_at: nostrNow(),
|
||||
};
|
||||
|
||||
return renderAccount(event, opts);
|
||||
}
|
||||
|
||||
async function parseAndVerifyNip05(nip05: string | undefined, pubkey: string): Promise<Nip05 | undefined> {
|
||||
if (nip05 && await verifyNip05Cached(nip05, pubkey)) {
|
||||
return parseNip05(nip05);
|
||||
}
|
||||
}
|
||||
|
||||
export { accountFromPubkey, renderAccount };
|
|
@ -0,0 +1,19 @@
|
|||
import { UnsignedEvent } from '@/deps.ts';
|
||||
import { EmojiTag, emojiTagSchema } from '@/schemas/nostr.ts';
|
||||
import { filteredArray } from '@/schema.ts';
|
||||
|
||||
function renderEmoji([_, shortcode, url]: EmojiTag) {
|
||||
return {
|
||||
shortcode,
|
||||
static_url: url,
|
||||
url,
|
||||
};
|
||||
}
|
||||
|
||||
function renderEmojis({ tags }: UnsignedEvent) {
|
||||
return filteredArray(emojiTagSchema)
|
||||
.parse(tags)
|
||||
.map(renderEmoji);
|
||||
}
|
||||
|
||||
export { renderEmojis };
|
|
@ -2,108 +2,19 @@ import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ad
|
|||
|
||||
import { Conf } from '@/config.ts';
|
||||
import * as eventsDB from '@/db/events.ts';
|
||||
import { findUser } from '@/db/users.ts';
|
||||
import { type Event, findReplyTag, lodash, nip19, type UnsignedEvent } from '@/deps.ts';
|
||||
import { type Event, findReplyTag, nip19 } from '@/deps.ts';
|
||||
import { getMediaLinks, parseNoteContent } from '@/note.ts';
|
||||
import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts';
|
||||
import { filteredArray } from '@/schema.ts';
|
||||
import { emojiTagSchema, jsonMediaDataSchema, jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { isFollowing, type Nip05, nostrDate, nostrNow, parseNip05 } from '@/utils.ts';
|
||||
import { verifyNip05Cached } from '@/utils/nip05.ts';
|
||||
import { getAuthor, getFollows } from '@/queries.ts';
|
||||
import { jsonMediaDataSchema } from '@/schemas/nostr.ts';
|
||||
import { isFollowing, nostrDate } from '@/utils.ts';
|
||||
import { unfurlCardCached } from '@/utils/unfurl.ts';
|
||||
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
|
||||
import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts';
|
||||
|
||||
const defaultAvatar = () => Conf.local('/images/avi.png');
|
||||
const defaultBanner = () => Conf.local('/images/banner.png');
|
||||
|
||||
interface ToAccountOpts {
|
||||
withSource?: boolean;
|
||||
}
|
||||
|
||||
async function toAccount(event: UnsignedEvent<0>, opts: ToAccountOpts = {}) {
|
||||
const { withSource = false } = opts;
|
||||
const { pubkey } = event;
|
||||
|
||||
const {
|
||||
name,
|
||||
nip05,
|
||||
picture = defaultAvatar(),
|
||||
banner = defaultBanner(),
|
||||
about,
|
||||
} = jsonMetaContentSchema.parse(event.content);
|
||||
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
const [user, parsed05, followersCount, followingCount, statusesCount] = await Promise.all([
|
||||
findUser({ pubkey }),
|
||||
parseAndVerifyNip05(nip05, pubkey),
|
||||
eventsDB.countFilters([{ kinds: [3], '#p': [pubkey] }]),
|
||||
getFollowedPubkeys(pubkey).then((pubkeys) => pubkeys.length),
|
||||
eventsDB.countFilters([{ kinds: [1], authors: [pubkey] }]),
|
||||
]);
|
||||
|
||||
return {
|
||||
id: pubkey,
|
||||
acct: parsed05?.handle || npub,
|
||||
avatar: picture,
|
||||
avatar_static: picture,
|
||||
bot: false,
|
||||
created_at: event ? nostrDate(event.created_at).toISOString() : new Date().toISOString(),
|
||||
discoverable: true,
|
||||
display_name: name,
|
||||
emojis: toEmojis(event),
|
||||
fields: [],
|
||||
follow_requests_count: 0,
|
||||
followers_count: followersCount,
|
||||
following_count: followingCount,
|
||||
fqn: parsed05?.handle || npub,
|
||||
header: banner,
|
||||
header_static: banner,
|
||||
last_status_at: null,
|
||||
locked: false,
|
||||
note: lodash.escape(about),
|
||||
roles: [],
|
||||
source: withSource
|
||||
? {
|
||||
fields: [],
|
||||
language: '',
|
||||
note: about || '',
|
||||
privacy: 'public',
|
||||
sensitive: false,
|
||||
follow_requests_count: 0,
|
||||
}
|
||||
: undefined,
|
||||
statuses_count: statusesCount,
|
||||
url: Conf.local(`/users/${pubkey}`),
|
||||
username: parsed05?.nickname || npub.substring(0, 8),
|
||||
pleroma: {
|
||||
is_admin: user?.admin || false,
|
||||
is_moderator: user?.admin || false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function accountFromPubkey(pubkey: string, opts: ToAccountOpts = {}) {
|
||||
const event: UnsignedEvent<0> = {
|
||||
kind: 0,
|
||||
pubkey,
|
||||
content: '',
|
||||
tags: [],
|
||||
created_at: nostrNow(),
|
||||
};
|
||||
|
||||
return toAccount(event, opts);
|
||||
}
|
||||
|
||||
async function parseAndVerifyNip05(nip05: string | undefined, pubkey: string): Promise<Nip05 | undefined> {
|
||||
if (nip05 && await verifyNip05Cached(nip05, pubkey)) {
|
||||
return parseNip05(nip05);
|
||||
}
|
||||
}
|
||||
import { renderEmojis } from '@/views/mastodon/emojis.ts';
|
||||
|
||||
async function toMention(pubkey: string) {
|
||||
const profile = await getAuthor(pubkey);
|
||||
const account = profile ? await toAccount(profile) : undefined;
|
||||
const account = profile ? await renderAccount(profile) : undefined;
|
||||
|
||||
if (account) {
|
||||
return {
|
||||
|
@ -125,7 +36,7 @@ async function toMention(pubkey: string) {
|
|||
|
||||
async function toStatus(event: Event<1>, viewerPubkey?: string) {
|
||||
const profile = await getAuthor(event.pubkey);
|
||||
const account = profile ? await toAccount(profile) : await accountFromPubkey(event.pubkey);
|
||||
const account = profile ? await renderAccount(profile) : await accountFromPubkey(event.pubkey);
|
||||
|
||||
const replyTag = findReplyTag(event);
|
||||
|
||||
|
@ -191,7 +102,7 @@ async function toStatus(event: Event<1>, viewerPubkey?: string) {
|
|||
media_attachments: media.map(renderAttachment),
|
||||
mentions,
|
||||
tags: [],
|
||||
emojis: toEmojis(event),
|
||||
emojis: renderEmojis(event),
|
||||
poll: null,
|
||||
uri: Conf.local(`/posts/${event.id}`),
|
||||
url: Conf.local(`/posts/${event.id}`),
|
||||
|
@ -212,17 +123,6 @@ function buildInlineRecipients(mentions: Mention[]): string {
|
|||
return `<span class="recipients-inline">${elements.join(' ')} </span>`;
|
||||
}
|
||||
|
||||
function toEmojis(event: UnsignedEvent) {
|
||||
const emojiTags = event.tags.filter((tag) => tag[0] === 'emoji');
|
||||
|
||||
return filteredArray(emojiTagSchema).parse(emojiTags)
|
||||
.map((tag) => ({
|
||||
shortcode: tag[1],
|
||||
static_url: tag[2],
|
||||
url: tag[2],
|
||||
}));
|
||||
}
|
||||
|
||||
async function toRelationship(sourcePubkey: string, targetPubkey: string) {
|
||||
const [source, target] = await Promise.all([
|
||||
getFollows(sourcePubkey),
|
||||
|
@ -265,4 +165,4 @@ async function toNotificationMention(event: Event<1>, viewerPubkey?: string) {
|
|||
};
|
||||
}
|
||||
|
||||
export { accountFromPubkey, toAccount, toNotification, toRelationship, toStatus };
|
||||
export { accountFromPubkey, toNotification, toRelationship, toStatus };
|
||||
|
|
Loading…
Reference in New Issue