Allow posting a status to Nostr
This commit is contained in:
parent
73884c212f
commit
2acfecc1eb
|
@ -1,17 +1,15 @@
|
||||||
import { getPublicKey } from '@/deps.ts';
|
|
||||||
|
|
||||||
import { LOCAL_DOMAIN } from '../config.ts';
|
import { LOCAL_DOMAIN } from '../config.ts';
|
||||||
import { fetchUser } from '../client.ts';
|
import { fetchUser } from '../client.ts';
|
||||||
import { MetaContent, metaContentSchema } from '../schema.ts';
|
import { MetaContent, metaContentSchema } from '../schema.ts';
|
||||||
|
import { getKeys } from '../utils.ts';
|
||||||
|
|
||||||
import type { Context } from '@/deps.ts';
|
import type { Context } from '@/deps.ts';
|
||||||
|
|
||||||
async function credentialsController(c: Context) {
|
async function credentialsController(c: Context) {
|
||||||
const authHeader = c.req.headers.get('Authorization') || '';
|
const keys = getKeys(c);
|
||||||
|
|
||||||
if (authHeader.startsWith('Bearer ')) {
|
if (keys) {
|
||||||
const token = authHeader.split('Bearer ')[1];
|
const { pubkey } = keys;
|
||||||
const pubkey = getPublicKey(token);
|
|
||||||
const event = await fetchUser(pubkey);
|
const event = await fetchUser(pubkey);
|
||||||
const parsed = metaContentSchema.safeParse(JSON.parse(event?.content || ''));
|
const parsed = metaContentSchema.safeParse(JSON.parse(event?.content || ''));
|
||||||
const content: MetaContent = parsed.success ? parsed.data : {};
|
const content: MetaContent = parsed.success ? parsed.data : {};
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { validator, z } from '@/deps.ts';
|
||||||
|
|
||||||
|
import publish from '../publisher.ts';
|
||||||
|
import { toStatus } from '../transmute.ts';
|
||||||
|
import { getKeys } from '../utils.ts';
|
||||||
|
|
||||||
|
import type { Event } from '../event.ts';
|
||||||
|
|
||||||
|
const createStatusSchema = z.object({
|
||||||
|
status: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createStatusController = validator('json', (value, c) => {
|
||||||
|
const keys = getKeys(c);
|
||||||
|
const result = createStatusSchema.safeParse(value);
|
||||||
|
|
||||||
|
if (result.success && keys) {
|
||||||
|
const { data } = result;
|
||||||
|
const { pubkey, privatekey } = keys;
|
||||||
|
|
||||||
|
const event: Event<1> = {
|
||||||
|
kind: 1,
|
||||||
|
pubkey: pubkey,
|
||||||
|
content: data.status,
|
||||||
|
tags: [],
|
||||||
|
created_at: Math.floor(new Date().getTime() / 1000),
|
||||||
|
};
|
||||||
|
|
||||||
|
publish(event, privatekey);
|
||||||
|
return c.json(toStatus(event));
|
||||||
|
} else {
|
||||||
|
return c.json({ error: 'Bad request' }, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export { createStatusController };
|
|
@ -5,6 +5,7 @@ import { appCredentialsController, createAppController } from './api/apps.ts';
|
||||||
import { emptyArrayController } from './api/fallback.ts';
|
import { emptyArrayController } from './api/fallback.ts';
|
||||||
import instanceController from './api/instance.ts';
|
import instanceController from './api/instance.ts';
|
||||||
import { createTokenController } from './api/oauth.ts';
|
import { createTokenController } from './api/oauth.ts';
|
||||||
|
import { createStatusController } from './api/statuses.ts';
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
|
@ -19,6 +20,8 @@ app.post('/oauth/token', createTokenController);
|
||||||
|
|
||||||
app.get('/api/v1/accounts/verify_credentials', credentialsController);
|
app.get('/api/v1/accounts/verify_credentials', credentialsController);
|
||||||
|
|
||||||
|
app.post('/api/v1/statuses', createStatusController);
|
||||||
|
|
||||||
// Not (yet) implemented.
|
// Not (yet) implemented.
|
||||||
app.get('/api/v1/timelines/*', emptyArrayController);
|
app.get('/api/v1/timelines/*', emptyArrayController);
|
||||||
app.get('/api/v1/accounts/:id/statuses', emptyArrayController);
|
app.get('/api/v1/accounts/:id/statuses', emptyArrayController);
|
||||||
|
|
|
@ -34,4 +34,4 @@ const fetchFollows = (pubkey: string): Promise<Event<3> | null> => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { fetchEvent, fetchFollows, fetchUser };
|
export { fetchEvent, fetchFollows, fetchUser, pool };
|
||||||
|
|
|
@ -2,3 +2,4 @@ export const LOCAL_DOMAIN = Deno.env.get('LOCAL_DOMAIN') || 'http://localhost:80
|
||||||
export const POST_CHAR_LIMIT = Number(Deno.env.get('POST_CHAR_LIMIT') || 5000);
|
export const POST_CHAR_LIMIT = Number(Deno.env.get('POST_CHAR_LIMIT') || 5000);
|
||||||
|
|
||||||
export const poolRelays = (Deno.env.get('RELAY_POOL') || '').split(',').filter(Boolean);
|
export const poolRelays = (Deno.env.get('RELAY_POOL') || '').split(',').filter(Boolean);
|
||||||
|
export const publishRelays = ['wss://relay.mostr.pub'];
|
||||||
|
|
|
@ -3,5 +3,5 @@ export { Hono, validator };
|
||||||
export { cors } from 'https://deno.land/x/hono@v3.0.2/middleware.ts';
|
export { cors } from 'https://deno.land/x/hono@v3.0.2/middleware.ts';
|
||||||
export { z } from 'https://deno.land/x/zod@v3.20.5/mod.ts';
|
export { z } from 'https://deno.land/x/zod@v3.20.5/mod.ts';
|
||||||
export { Author, RelayPool } from 'https://dev.jspm.io/nostr-relaypool@0.5.3';
|
export { Author, RelayPool } from 'https://dev.jspm.io/nostr-relaypool@0.5.3';
|
||||||
export { getPublicKey } from 'https://dev.jspm.io/nostr-tools@1.6.0';
|
export { getEventHash, getPublicKey, signEvent } from 'https://dev.jspm.io/nostr-tools@1.6.0';
|
||||||
export type { Context };
|
export type { Context };
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { getEventHash, signEvent } from '@/deps.ts';
|
||||||
|
|
||||||
|
import { pool } from './client.ts';
|
||||||
|
import { publishRelays } from './config.ts';
|
||||||
|
|
||||||
|
import type { Event } from './event.ts';
|
||||||
|
|
||||||
|
/** Publish an event to the Nostr relay. */
|
||||||
|
function publish(event: Event, privatekey: string, relays = publishRelays): void {
|
||||||
|
event.id = getEventHash(event);
|
||||||
|
event.sig = signEvent(event, privatekey);
|
||||||
|
console.log('Publishing event', event);
|
||||||
|
try {
|
||||||
|
pool.publish(event, relays);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default publish;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { LOCAL_DOMAIN } from './config.ts';
|
||||||
|
|
||||||
|
import type { Event } from './event.ts';
|
||||||
|
|
||||||
|
function toStatus(event: Event<1>) {
|
||||||
|
return {
|
||||||
|
id: event.id,
|
||||||
|
account: {
|
||||||
|
id: event.pubkey,
|
||||||
|
},
|
||||||
|
content: event.content,
|
||||||
|
created_at: new Date(event.created_at * 1000).toISOString(),
|
||||||
|
in_reply_to_id: null,
|
||||||
|
in_reply_to_account_id: null,
|
||||||
|
sensitive: false,
|
||||||
|
spoiler_text: '',
|
||||||
|
visibility: 'public',
|
||||||
|
language: 'en',
|
||||||
|
replies_count: 0,
|
||||||
|
reblogs_count: 0,
|
||||||
|
favourites_count: 0,
|
||||||
|
favourited: false,
|
||||||
|
reblogged: false,
|
||||||
|
muted: false,
|
||||||
|
bookmarked: false,
|
||||||
|
reblog: null,
|
||||||
|
application: null,
|
||||||
|
media_attachments: [],
|
||||||
|
mentions: [],
|
||||||
|
tags: [],
|
||||||
|
emojis: [],
|
||||||
|
card: null,
|
||||||
|
poll: null,
|
||||||
|
uri: `${LOCAL_DOMAIN}/posts/${event.id}`,
|
||||||
|
url: `${LOCAL_DOMAIN}/posts/${event.id}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { toStatus };
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Context, getPublicKey } from '@/deps.ts';
|
||||||
|
|
||||||
|
function getKeys(c: Context) {
|
||||||
|
const auth = c.req.headers.get('Authorization') || '';
|
||||||
|
|
||||||
|
if (auth.startsWith('Bearer ')) {
|
||||||
|
const privatekey = auth.split('Bearer ')[1];
|
||||||
|
const pubkey = getPublicKey(privatekey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
privatekey,
|
||||||
|
pubkey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getKeys };
|
Loading…
Reference in New Issue