pl-api: untested imethods for mitra subscriptions

Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
Nicole Mikołajczyk
2025-04-09 13:41:32 +02:00
parent 6e6698a4b5
commit 3fb0dfb617
6 changed files with 271 additions and 52 deletions

View File

@@ -70,6 +70,9 @@ import {
statusSchema,
statusSourceSchema,
streamingEventSchema,
subscriptionDetailsSchema,
subscriptionInvoiceSchema,
subscriptionOptionSchema,
suggestionSchema,
tagSchema,
tokenSchema,
@@ -2859,58 +2862,6 @@ class PlApiClient {
},
};
public readonly shoutbox = {
connect: (token: string, { onMessage, onMessages }: {
onMessages: (messages: Array<ShoutMessage>) => void;
onMessage: (message: ShoutMessage) => void;
}) => {
let counter = 2;
let intervalId: NodeJS.Timeout;
if (this.#shoutSocket) return this.#shoutSocket;
const path = buildFullPath('/socket/websocket', this.baseURL, { token, vsn: '2.0.0' });
const ws = new WebSocket(path);
ws.onmessage = (event) => {
const [_, __, ___, type, payload] = JSON.parse(event.data as string);
if (type === 'new_msg') {
const message = v.parse(shoutMessageSchema, payload);
onMessage(message);
} else if (type === 'messages') {
const messages = v.parse(filteredArray(shoutMessageSchema), payload.messages);
onMessages(messages);
}
};
ws.onopen = () => {
ws.send(JSON.stringify(['3', `${++counter}`, 'chat:public', 'phx_join', {}]));
intervalId = setInterval(() => {
ws.send(JSON.stringify([null, `${++counter}`, 'phoenix', 'heartbeat', {}]));
}, 5000);
};
ws.onclose = () => {
clearInterval(intervalId);
};
this.#shoutSocket = {
message: (text: string) => {
// guess this is meant to be incremented on each call but idk
ws.send(JSON.stringify(['3', `${++counter}`, 'chat:public', 'new_msg', { 'text': text }]));
},
close: () => {
ws.close();
this.#shoutSocket = undefined;
clearInterval(intervalId);
},
};
return this.#shoutSocket;
},
};
public readonly notifications = {
/**
* Get all notifications
@@ -4739,6 +4690,58 @@ class PlApiClient {
},
};
public readonly shoutbox = {
connect: (token: string, { onMessage, onMessages }: {
onMessages: (messages: Array<ShoutMessage>) => void;
onMessage: (message: ShoutMessage) => void;
}) => {
let counter = 2;
let intervalId: NodeJS.Timeout;
if (this.#shoutSocket) return this.#shoutSocket;
const path = buildFullPath('/socket/websocket', this.baseURL, { token, vsn: '2.0.0' });
const ws = new WebSocket(path);
ws.onmessage = (event) => {
const [_, __, ___, type, payload] = JSON.parse(event.data as string);
if (type === 'new_msg') {
const message = v.parse(shoutMessageSchema, payload);
onMessage(message);
} else if (type === 'messages') {
const messages = v.parse(filteredArray(shoutMessageSchema), payload.messages);
onMessages(messages);
}
};
ws.onopen = () => {
ws.send(JSON.stringify(['3', `${++counter}`, 'chat:public', 'phx_join', {}]));
intervalId = setInterval(() => {
ws.send(JSON.stringify([null, `${++counter}`, 'phoenix', 'heartbeat', {}]));
}, 5000);
};
ws.onclose = () => {
clearInterval(intervalId);
};
this.#shoutSocket = {
message: (text: string) => {
// guess this is meant to be incremented on each call but idk
ws.send(JSON.stringify(['3', `${++counter}`, 'chat:public', 'new_msg', { 'text': text }]));
},
close: () => {
ws.close();
this.#shoutSocket = undefined;
clearInterval(intervalId);
},
};
return this.#shoutSocket;
},
};
public readonly events = {
/**
* Creates an event
@@ -5250,6 +5253,106 @@ class PlApiClient {
},
};
public readonly subscriptions = {
/**
* Add subscriber or extend existing subscription.
*
* Requires features{@link Features['subscriptions']}.
* @param subscriberId - The subscriber ID.
* @param duration - The subscription duration (in seconds).
*/
createSubscription: async(subscriberId: string, duration: number) => {
const response = await this.request('/api/v1/subscriptions', { method: 'POST', body: { subscriber_id: subscriberId, duration } });
return v.parse(subscriptionDetailsSchema, response.json);
},
/**
* Get list of subscription options
*
* Requires features{@link Features['subscriptions']}.
*/
getSubscriptionOptions: async () => {
const response = await this.request('/api/v1/subscriptions/options');
return v.parse(filteredArray(subscriptionOptionSchema), response.json);
},
/**
* Enable subscriptions or update subscription settings
*
* Requires features{@link Features['subscriptions']}.
* @param type - Subscription type
* @param chainId - CAIP-2 chain ID.
* @param price - Subscription price (only for Monero)
* @param payoutAddress - Payout address (only for Monero)
*/
updateSubscription: async(type: 'monero', chainId?: string, price?: number, payoutAddress?: string) => {
const response = await this.request('/api/v1/subscriptions/options', { method: 'POST', body: { type, chain_id: chainId, price, payout_address: payoutAddress } });
return v.parse(accountSchema, response.json);
},
/**
* Find subscription by sender and recipient
*
* Requires features{@link Features['subscriptions']}.
* @param senderId - Sender ID.
* @param recipientId - Recipient ID.
*/
findSubscription: async(senderId: string, recipientId: string) => {
const response = await this.request('/api/v1/subscriptions/find', { method: 'POST', body: { sender_id: senderId, recipient_id: recipientId } });
return v.parse(subscriptionDetailsSchema, response.json);
},
/**
* Create invoice
*
* Requires features{@link Features['subscriptions']}.
* @param senderId - Sender ID.
* @param recipientId - Recipient ID.
* @param chainId - CAIP-2 chain ID.
* @param amount - Requested payment amount (in atomic units).
*/
createInvoice: async(senderId: string, recipientId: string, chainId: string, amount: number) => {
const response = await this.request('/api/v1/subscriptions/invoices', {
method: 'POST',
body: {
sender_id: senderId, recipient_id: recipientId, chain_id: chainId, amount,
},
});
return v.parse(subscriptionInvoiceSchema, response.json);
},
/**
* View information about an invoice.
*
* Requires features{@link Features['invoices']}.
* @param invoiceId - Invoice ID
*/
getInvoice: async(invoiceId: string) => {
const response = await this.request(`/api/v1/subscriptions/invoices/${invoiceId}`);
return v.parse(subscriptionInvoiceSchema, response.json);
},
/**
* Cancel invoice.
*
* Requires features{@link Features['invoices']}.
* @param invoiceId - Invoice ID
*/
cancelInvoice: async(invoiceId: string) => {
const response = await this.request(`/api/v1/subscriptions/invoices/${invoiceId}`, {
method: 'DELETE',
});
return v.parse(subscriptionInvoiceSchema, response.json);
},
};
/** Routes that are not part of any stable release */
public readonly experimental = {
admin: {

View File

@@ -158,10 +158,38 @@ const baseAccountSchema = v.object({
pronouns: v.fallback(v.array(v.string()), []),
/** Mention policy */
mention_policy: v.fallback(v.picklist(['none', 'only_known', 'only_followers']), 'none'),
/** The reported subscribers of this user */
subscribers_count: v.fallback(v.number(), 0),
/** Identity proofs */
identity_proofs: filteredArray(v.object({
/** The key of a given field's key-value pair */
name: v.fallback(v.string(), ''),
/** The value associated with the name key */
value: v.fallback(v.string(), ''),
/** Timestamp of when the server verified the field value */
verified_at: v.fallback(datetimeSchema, new Date().toISOString()),
})),
/** Payment options */
payment_options: filteredArray(v.object({
/** Payment type */
type: v.picklist(['link', 'monero-subscription']),
/** Link name (only for link type) */
name: v.fallback(v.nullable(v.string()), null),
/** Link URL (only for link type) */
href: v.fallback(v.nullable(v.string()), null),
/** CAIP-2 chain ID (only for monero-subscription type) */
chain_id: v.fallback(v.nullable(v.string()), null),
/** Subscription price (only for monero-subscription type) */
price: v.fallback(v.nullable(v.string()), null),
/** Unique identifier of a proposal object */
object_id: v.fallback(v.nullable(v.string()), null),
})),
/** Whether the user is a cat */
is_cat: v.fallback(v.boolean(), false),
/** Whether the user's posts should be nyanified */
speak_as_cat: v.fallback(v.boolean(), false),
__meta: coerceObject({

View File

@@ -72,6 +72,9 @@ export * from './status';
export * from './status-edit';
export * from './status-source';
export * from './streaming-event';
export * from './subscription-details';
export * from './subscription-invoice';
export * from './subscription-option';
export * from './suggestion';
export * from './tag';
export * from './token';

View File

@@ -0,0 +1,23 @@
import * as v from 'valibot';
import { datetimeSchema } from './utils';
/**
* @category Schemas
*/
const subscriptionDetailsSchema = v.object({
/** Subscription ID. */
id: v.number(),
/** The date when subscription expires. */
expires_at: v.fallback(datetimeSchema, new Date().toISOString()),
});
/**
* @category Entity types
*/
type SubscriptionDetails = v.InferOutput<typeof subscriptionDetailsSchema>;
export {
subscriptionDetailsSchema,
type SubscriptionDetails,
};

View File

@@ -0,0 +1,37 @@
import * as v from 'valibot';
import { datetimeSchema } from './utils';
/**
* @category Schemas
*/
const subscriptionInvoiceSchema = v.object({
/** Invoice ID. */
invoice_id: v.string(),
/** The ID of the sender. */
sender_id: v.string(),
/** The ID of the recipient. */
recipient_id: v.string(),
/** CAIP-2 chain ID. */
chain_id: v.string(),
/** Payment address. */
payment_address: v.string(),
/** Requested payment amount (in atomic units). */
amount: v.number(),
/** Invoice status. */
status: v.picklist(['open', 'paid', 'forwarded', 'timeout', 'cancelled', 'underpaid', 'completed', 'failed']),
/** The date when invoice was created. */
created_at: v.fallback(datetimeSchema, new Date().toISOString()),
/** The date when invoice times out. */
invoice_expires_at: v.fallback(datetimeSchema, new Date().toISOString()),
});
/**
* @category Entity types
*/
type SubscriptionInvoice = v.InferOutput<typeof subscriptionInvoiceSchema>;
export {
subscriptionInvoiceSchema,
type SubscriptionInvoice,
};

View File

@@ -0,0 +1,25 @@
import * as v from 'valibot';
/**
* @category Schemas
*/
const subscriptionOptionSchema = v.object({
/** Subscription type */
type: v.picklist(['monero']),
/** CAIP-2 chain ID. */
chain_id: v.fallback(v.string(), ''),
/** Subscription price (only for Monero) */
price: v.fallback(v.nullable(v.number()), null),
/** Payout address (only for Monero) */
payout_address: v.fallback(v.string(), ''),
});
/**
* @category Entity types
*/
type SubscriptionOption = v.InferOutput<typeof subscriptionOptionSchema>;
export {
subscriptionOptionSchema,
type SubscriptionOption,
};