mirror of
https://github.com/aljazceru/ditto.git
synced 2026-01-14 11:04:18 +01:00
Merge branch 'main' into translate-status
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { NConnectSigner, NSchema as n, NSecSigner } from '@nostrify/nostrify';
|
||||
import { bech32 } from '@scure/base';
|
||||
import { escape } from 'entities';
|
||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||
import { generateSecretKey } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { nostrNow } from '@/utils.ts';
|
||||
import { parseBody } from '@/utils/api.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { encryptSecretKey, generateToken } from '@/utils/auth.ts';
|
||||
|
||||
const passwordGrantSchema = z.object({
|
||||
grant_type: z.literal('password'),
|
||||
@@ -82,38 +82,30 @@ async function getToken(
|
||||
{ pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
||||
): Promise<`token1${string}`> {
|
||||
const kysely = await Storages.kysely();
|
||||
const token = generateToken();
|
||||
const { token, hash } = await generateToken();
|
||||
|
||||
const serverSeckey = generateSecretKey();
|
||||
const serverPubkey = getPublicKey(serverSeckey);
|
||||
const nip46Seckey = generateSecretKey();
|
||||
|
||||
const signer = new NConnectSigner({
|
||||
pubkey,
|
||||
signer: new NSecSigner(serverSeckey),
|
||||
signer: new NSecSigner(nip46Seckey),
|
||||
relay: await Storages.pubsub(), // TODO: Use the relays from the request.
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
await signer.connect(secret);
|
||||
|
||||
await kysely.insertInto('nip46_tokens').values({
|
||||
api_token: token,
|
||||
user_pubkey: pubkey,
|
||||
server_seckey: serverSeckey,
|
||||
server_pubkey: serverPubkey,
|
||||
relays: JSON.stringify(relays),
|
||||
connected_at: new Date(),
|
||||
await kysely.insertInto('auth_tokens').values({
|
||||
token_hash: hash,
|
||||
pubkey,
|
||||
nip46_sk_enc: await encryptSecretKey(Conf.seckey, nip46Seckey),
|
||||
nip46_relays: relays,
|
||||
created_at: new Date(),
|
||||
}).execute();
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/** Generate a bech32 token for the API. */
|
||||
function generateToken(): `token1${string}` {
|
||||
const words = bech32.toWords(generateSecretKey());
|
||||
return bech32.encode('token', words);
|
||||
}
|
||||
|
||||
/** Display the OAuth form. */
|
||||
const oauthController: AppController = (c) => {
|
||||
const encodedUri = c.req.query('redirect_uri');
|
||||
|
||||
@@ -88,14 +88,28 @@ const createStatusController: AppController = async (c) => {
|
||||
return c.json({ error: 'Original post not found.' }, 404);
|
||||
}
|
||||
|
||||
const root = ancestor.tags.find((tag) => tag[0] === 'e' && tag[3] === 'root')?.[1] ?? ancestor.id;
|
||||
const rootId = ancestor.tags.find((tag) => tag[0] === 'e' && tag[3] === 'root')?.[1] ?? ancestor.id;
|
||||
const root = rootId === ancestor.id ? ancestor : await getEvent(rootId);
|
||||
|
||||
tags.push(['e', root, Conf.relay, 'root']);
|
||||
tags.push(['e', data.in_reply_to_id, Conf.relay, 'reply']);
|
||||
if (root) {
|
||||
tags.push(['e', root.id, Conf.relay, 'root', root.pubkey]);
|
||||
} else {
|
||||
tags.push(['e', rootId, Conf.relay, 'root']);
|
||||
}
|
||||
|
||||
tags.push(['e', ancestor.id, Conf.relay, 'reply', ancestor.pubkey]);
|
||||
}
|
||||
|
||||
let quoted: DittoEvent | undefined;
|
||||
|
||||
if (data.quote_id) {
|
||||
tags.push(['q', data.quote_id]);
|
||||
quoted = await getEvent(data.quote_id);
|
||||
|
||||
if (!quoted) {
|
||||
return c.json({ error: 'Quoted post not found.' }, 404);
|
||||
}
|
||||
|
||||
tags.push(['q', quoted.id, Conf.relay, '', quoted.pubkey]);
|
||||
}
|
||||
|
||||
if (data.sensitive && data.spoiler_text) {
|
||||
@@ -143,7 +157,7 @@ const createStatusController: AppController = async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
return `nostr:${nip19.npubEncode(pubkey)}`;
|
||||
return `nostr:${nip19.nprofileEncode({ pubkey, relays: [Conf.relay] })}`;
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
@@ -159,7 +173,7 @@ const createStatusController: AppController = async (c) => {
|
||||
}
|
||||
|
||||
for (const pubkey of pubkeys) {
|
||||
tags.push(['p', pubkey]);
|
||||
tags.push(['p', pubkey, Conf.relay]);
|
||||
}
|
||||
|
||||
for (const link of linkify.find(data.status ?? '')) {
|
||||
@@ -175,10 +189,16 @@ const createStatusController: AppController = async (c) => {
|
||||
.map(({ url }) => url)
|
||||
.filter((url): url is string => Boolean(url));
|
||||
|
||||
const quoteCompat = data.quote_id ? `\n\nnostr:${nip19.noteEncode(data.quote_id)}` : '';
|
||||
const quoteCompat = quoted
|
||||
? `\n\nnostr:${
|
||||
nip19.neventEncode({ id: quoted.id, kind: quoted.kind, author: quoted.pubkey, relays: [Conf.relay] })
|
||||
}`
|
||||
: '';
|
||||
|
||||
const mediaCompat = mediaUrls.length ? `\n\n${mediaUrls.join('\n')}` : '';
|
||||
|
||||
const author = await getAuthor(await c.get('signer')?.getPublicKey()!);
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const author = pubkey ? await getAuthor(pubkey) : undefined;
|
||||
|
||||
if (Conf.zapSplitsEnabled) {
|
||||
const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content);
|
||||
@@ -191,7 +211,7 @@ const createStatusController: AppController = async (c) => {
|
||||
tags.push(['zap', pubkey, Conf.relay, dittoZapSplit[pubkey].weight.toString(), dittoZapSplit[pubkey].message]);
|
||||
}
|
||||
if (totalSplit) {
|
||||
tags.push(['zap', author?.pubkey as string, Conf.relay, Math.max(0, 100 - totalSplit).toString()]);
|
||||
tags.push(['zap', pubkey, Conf.relay, Math.max(0, 100 - totalSplit).toString()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,7 +243,7 @@ const deleteStatusController: AppController = async (c) => {
|
||||
if (event.pubkey === pubkey) {
|
||||
await createEvent({
|
||||
kind: 5,
|
||||
tags: [['e', id, Conf.relay]],
|
||||
tags: [['e', id, Conf.relay, '', pubkey]],
|
||||
}, c);
|
||||
|
||||
const author = await getAuthor(event.pubkey);
|
||||
@@ -281,7 +301,7 @@ const favouriteController: AppController = async (c) => {
|
||||
kind: 7,
|
||||
content: '+',
|
||||
tags: [
|
||||
['e', target.id, Conf.relay],
|
||||
['e', target.id, Conf.relay, '', target.pubkey],
|
||||
['p', target.pubkey, Conf.relay],
|
||||
],
|
||||
}, c);
|
||||
@@ -324,7 +344,7 @@ const reblogStatusController: AppController = async (c) => {
|
||||
const reblogEvent = await createEvent({
|
||||
kind: 6,
|
||||
tags: [
|
||||
['e', event.id, Conf.relay],
|
||||
['e', event.id, Conf.relay, '', event.pubkey],
|
||||
['p', event.pubkey, Conf.relay],
|
||||
],
|
||||
}, c);
|
||||
@@ -361,7 +381,7 @@ const unreblogStatusController: AppController = async (c) => {
|
||||
|
||||
await createEvent({
|
||||
kind: 5,
|
||||
tags: [['e', repostEvent.id, Conf.relay]],
|
||||
tags: [['e', repostEvent.id, Conf.relay, '', repostEvent.pubkey]],
|
||||
}, c);
|
||||
|
||||
return c.json(await renderStatus(event, { viewerPubkey: pubkey }));
|
||||
@@ -413,7 +433,7 @@ const bookmarkController: AppController = async (c) => {
|
||||
if (event) {
|
||||
await updateListEvent(
|
||||
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
||||
(tags) => addTag(tags, ['e', eventId, Conf.relay]),
|
||||
(tags) => addTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
||||
c,
|
||||
);
|
||||
|
||||
@@ -440,7 +460,7 @@ const unbookmarkController: AppController = async (c) => {
|
||||
if (event) {
|
||||
await updateListEvent(
|
||||
{ kinds: [10003], authors: [pubkey], limit: 1 },
|
||||
(tags) => deleteTag(tags, ['e', eventId, Conf.relay]),
|
||||
(tags) => deleteTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
||||
c,
|
||||
);
|
||||
|
||||
@@ -467,7 +487,7 @@ const pinController: AppController = async (c) => {
|
||||
if (event) {
|
||||
await updateListEvent(
|
||||
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
||||
(tags) => addTag(tags, ['e', eventId, Conf.relay]),
|
||||
(tags) => addTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
||||
c,
|
||||
);
|
||||
|
||||
@@ -496,7 +516,7 @@ const unpinController: AppController = async (c) => {
|
||||
if (event) {
|
||||
await updateListEvent(
|
||||
{ kinds: [10001], authors: [pubkey], limit: 1 },
|
||||
(tags) => deleteTag(tags, ['e', eventId, Conf.relay]),
|
||||
(tags) => deleteTag(tags, ['e', event.id, Conf.relay, '', event.pubkey]),
|
||||
c,
|
||||
);
|
||||
|
||||
@@ -540,8 +560,8 @@ const zapController: AppController = async (c) => {
|
||||
lnurl = getLnurl(meta);
|
||||
if (target && lnurl) {
|
||||
tags.push(
|
||||
['e', target.id, Conf.relay],
|
||||
['p', target.pubkey],
|
||||
['e', target.id, Conf.relay, '', target.pubkey],
|
||||
['p', target.pubkey, Conf.relay],
|
||||
['amount', amount.toString()],
|
||||
['relays', Conf.relay],
|
||||
['lnurl', lnurl],
|
||||
@@ -553,7 +573,7 @@ const zapController: AppController = async (c) => {
|
||||
lnurl = getLnurl(meta);
|
||||
if (target && lnurl) {
|
||||
tags.push(
|
||||
['p', target.pubkey],
|
||||
['p', target.pubkey, Conf.relay],
|
||||
['amount', amount.toString()],
|
||||
['relays', Conf.relay],
|
||||
['lnurl', lnurl],
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MuteListPolicy } from '@/policies/MuteListPolicy.ts';
|
||||
import { getFeedPubkeys } from '@/queries.ts';
|
||||
import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { getTokenHash } from '@/utils/auth.ts';
|
||||
import { bech32ToPubkey, Time } from '@/utils.ts';
|
||||
import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
||||
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
||||
@@ -233,14 +234,15 @@ async function topicToFilter(
|
||||
async function getTokenPubkey(token: string): Promise<string | undefined> {
|
||||
if (token.startsWith('token1')) {
|
||||
const kysely = await Storages.kysely();
|
||||
const tokenHash = await getTokenHash(token as `token1${string}`);
|
||||
|
||||
const { user_pubkey } = await kysely
|
||||
.selectFrom('nip46_tokens')
|
||||
.select(['user_pubkey', 'server_seckey', 'relays'])
|
||||
.where('api_token', '=', token)
|
||||
const { pubkey } = await kysely
|
||||
.selectFrom('auth_tokens')
|
||||
.select('pubkey')
|
||||
.where('token_hash', '=', tokenHash)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return user_pubkey;
|
||||
return pubkey;
|
||||
} else {
|
||||
return bech32ToPubkey(token);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user