diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 0055257c5..19034c763 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -750,7 +750,7 @@ class PlApiClient { * Requires features{@link Features.antennas}. */ getAccountExcludeAntennas: async (accountId: string) => { - const response = await this.request(`/api/v1/accounts/${accountId}/circles`); + const response = await this.request(`/api/v1/accounts/${accountId}/exclude_antennas`); return v.parse(filteredArray(circleSchema), response.json); }, @@ -761,7 +761,7 @@ class PlApiClient { * Requires features{@link Features.circles}. */ getAccountCircles: async (accountId: string) => { - const response = await this.request(`/api/v1/accounts/${accountId}/exclude_antennas`); + const response = await this.request(`/api/v1/accounts/${accountId}/circles`); return v.parse(filteredArray(antennaSchema), response.json); }, @@ -1371,7 +1371,7 @@ class PlApiClient { const response = await this.request('/api/v1/accounts/update_credentials', { method: 'PATCH', - contentType: (this.features.version.software === GOTOSOCIAL || params.avatar || params.header) ? '' : undefined, + contentType: '', body: params, }); @@ -5445,19 +5445,16 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - getAntennaAccounts: async (antennaId: string) => { - const response = await this.request(`/api/v1/antennas/${antennaId}/accounts`); - - return v.parse(filteredArray(accountSchema), response.json); - }, + getAntennaAccounts: async (antennaId: string) => + this.#paginatedGet(`/api/v1/antennas/${antennaId}/accounts`, {}, accountSchema), /** * Requires features{@link Features.antennas}. */ - addAntennaAccount: async (antennaId: string, accountId: string) => { + addAntennaAccounts: async (antennaId: string, accountIds: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/accounts`, { method: 'POST', - body: { account_ids: [accountId] }, + body: { account_ids: accountIds }, }); return response.json; @@ -5466,10 +5463,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaAccount: async (antennaId: string, accountId: string) => { + removeAntennaAccounts: async (antennaId: string, accountIds: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/accounts`, { method: 'DELETE', - body: { account_ids: [accountId] }, + body: { account_ids: accountIds }, }); return response.json; @@ -5478,19 +5475,16 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - getAntennaExcludeAccounts: async (antennaId: string) => { - const response = await this.request(`/api/v1/antennas/${antennaId}/exclude_accounts`); - - return v.parse(filteredArray(accountSchema), response.json); - }, + getAntennaExcludedAccounts: async (antennaId: string) => + this.#paginatedGet(`/api/v1/antennas/${antennaId}/exclude_accounts`, {}, accountSchema), /** * Requires features{@link Features.antennas}. */ - addAntennaExcludeAccount: async (antennaId: string, accountId: string) => { + addAntennaExcludedAccounts: async (antennaId: string, accountIds: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_accounts`, { method: 'POST', - body: { account_ids: [accountId] }, + body: { account_ids: accountIds }, }); return response.json; @@ -5499,10 +5493,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaExcludeAccount: async (antennaId: string, accountId: string) => { + removeAntennaExcludedAccounts: async (antennaId: string, accountIds: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_accounts`, { method: 'DELETE', - body: { account_ids: [accountId] }, + body: { account_ids: accountIds }, }); return response.json; @@ -5523,10 +5517,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - addAntennaDomain: async (antennaId: string, domain: string) => { + addAntennaDomains: async (antennaId: string, domains: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/domains`, { method: 'POST', - body: { domains: [domain] }, + body: { domains }, }); return response.json; @@ -5535,10 +5529,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaDomain: async (antennaId: string, domain: string) => { + removeAntennaDomains: async (antennaId: string, domains: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/domains`, { method: 'DELETE', - body: { domains: [domain] }, + body: { domains }, }); return response.json; @@ -5547,10 +5541,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - addAntennaExcludeDomain: async (antennaId: string, domain: string) => { + addAntennaExcludedDomains: async (antennaId: string, domains: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_domains`, { method: 'POST', - body: { domains: [domain] }, + body: { domains }, }); return response.json; @@ -5559,10 +5553,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaExcludeDomain: async (antennaId: string, domain: string) => { + removeAntennaExcludedDomains: async (antennaId: string, domains: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_domains`, { method: 'DELETE', - body: { domains: [domain] }, + body: { domains }, }); return response.json; @@ -5583,10 +5577,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - addAntennaKeyword: async (antennaId: string, keyword: string) => { + addAntennaKeywords: async (antennaId: string, keywords: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/keywords`, { method: 'POST', - body: { keywords: [keyword] }, + body: { keywords }, }); return response.json; @@ -5595,10 +5589,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaKeyword: async (antennaId: string, keyword: string) => { + removeAntennaKeywords: async (antennaId: string, keywords: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/keywords`, { method: 'DELETE', - body: { keywords: [keyword] }, + body: { keywords }, }); return response.json; @@ -5607,10 +5601,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - addAntennaExcludeKeyword: async (antennaId: string, keyword: string) => { + addAntennaExcludedKeywords: async (antennaId: string, keywords: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_keywords`, { method: 'POST', - body: { keywords: [keyword] }, + body: { keywords }, }); return response.json; @@ -5619,10 +5613,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaExcludeKeyword: async (antennaId: string, keyword: string) => { + removeAntennaExcludedKeywords: async (antennaId: string, keywords: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_keywords`, { method: 'DELETE', - body: { keywords: [keyword] }, + body: { keywords }, }); return response.json; @@ -5667,10 +5661,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - addAntennaExcludeTag: async (antennaId: string, tag: string) => { + addAntennaExcludedTags: async (antennaId: string, tags: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_tags`, { method: 'POST', - body: { tags: [tag] }, + body: { tags }, }); return response.json; @@ -5679,10 +5673,10 @@ class PlApiClient { /** * Requires features{@link Features.antennas}. */ - removeAntennaExcludeTag: async (antennaId: string, tag: string) => { + removeAntennaExcludedTags: async (antennaId: string, tags: Array) => { const response = await this.request<{}>(`/api/v1/antennas/${antennaId}/exclude_tags`, { method: 'DELETE', - body: { tags: [tag] }, + body: { tags }, }); return response.json; @@ -5849,7 +5843,7 @@ class PlApiClient { * @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 } }); + const response = await this.request('/api/v1/subscriptions/find', { params: { sender_id: senderId, recipient_id: recipientId } }); return v.parse(subscriptionDetailsSchema, response.json); }, diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 3be5bc522..28c2e58e2 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -14,6 +14,31 @@ const filterBadges = (tags?: string[]) => const MKLJCZK_ACCOUNTS = ['https://pl.fediverse.pl/users/mkljczk', 'https://gts.mkljczk.pl/users/mkljczk', 'https://gts.mkljczk.pl/@mkljczk']; +const paymentOptionSchema = v.variant('type', [ + v.object({ + /** Payment type */ + type: v.literal('link'), + /** 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), + /** Unique identifier of a proposal object */ + object_id: v.fallback(v.nullable(v.string()), null), + }), + v.object({ + /** Payment type */ + type: v.literal('monero-subscription'), + /** 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.number()), null), + /** Minimum payment amount (only for monero-subscription type) */ + amount_min: v.fallback(v.nullable(v.number()), null), + /** Unique identifier of a proposal object */ + object_id: v.fallback(v.nullable(v.string()), null), + }), +]); + const preprocessAccount = v.transform((account: any) => { if (!account?.acct) return null; @@ -166,20 +191,7 @@ const baseAccountSchema = v.object({ 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), - })), + payment_options: filteredArray(paymentOptionSchema), /** Whether the user is a cat */ is_cat: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 4945ba9c0..cf03caf22 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -423,6 +423,17 @@ const instanceSchema = v.pipe( title: v.fallback(v.string(), ''), usage: usageSchema, version: v.pipe(v.fallback(v.string(), '0.0.0'), v.transform(fixVersion)), + blockchains: v.fallback(v.optional(filteredArray(v.object({ + chain_id: v.fallback(v.nullable(v.string()), null), + chain_metadata: coerceObject({ + is_forwarding_required: v.fallback(v.boolean(), false), + description: v.fallback(v.string(), ''), + payment_amount_min: v.number(), + }), + features: coerceObject({ + subscriptions: v.fallback(v.boolean(), false), + }), + }))), undefined), }), ); diff --git a/packages/pl-api/lib/entities/subscription-invoice.ts b/packages/pl-api/lib/entities/subscription-invoice.ts index 6646de27e..47aa4e283 100644 --- a/packages/pl-api/lib/entities/subscription-invoice.ts +++ b/packages/pl-api/lib/entities/subscription-invoice.ts @@ -7,7 +7,7 @@ import { datetimeSchema } from './utils'; */ const subscriptionInvoiceSchema = v.object({ /** Invoice ID. */ - invoice_id: v.string(), + id: v.string(), /** The ID of the sender. */ sender_id: v.string(), /** The ID of the recipient. */ @@ -21,9 +21,9 @@ const subscriptionInvoiceSchema = v.object({ /** 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()), + created_at: datetimeSchema, /** The date when invoice times out. */ - invoice_expires_at: v.fallback(datetimeSchema, new Date().toISOString()), + invoice_expires_at: datetimeSchema, }); /** diff --git a/packages/pl-api/lib/entities/subscription-option.ts b/packages/pl-api/lib/entities/subscription-option.ts index 156a994fc..9b1ba02a4 100644 --- a/packages/pl-api/lib/entities/subscription-option.ts +++ b/packages/pl-api/lib/entities/subscription-option.ts @@ -3,16 +3,18 @@ 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(), ''), -}); +const subscriptionOptionSchema = v.variant('type', [ + v.object({ + /** Subscription type */ + type: v.literal('monero'), + /** CAIP-2 chain ID. */ + chain_id: v.string(), + /** Subscription price */ + price: v.nullable(v.number()), + /** Payout address */ + payout_address: v.string(), + }), +]); /** * @category Entity types diff --git a/packages/pl-api/lib/params/index.ts b/packages/pl-api/lib/params/index.ts index 8ed232252..8516faf41 100644 --- a/packages/pl-api/lib/params/index.ts +++ b/packages/pl-api/lib/params/index.ts @@ -1,5 +1,6 @@ export * from './accounts'; export * from './admin'; +export * from './antennas'; export * from './apps'; export * from './chats'; export * from './circles'; diff --git a/packages/pl-fe/src/components/blurhash.tsx b/packages/pl-fe/src/components/blurhash.tsx index 9ba2cc727..084e542a2 100644 --- a/packages/pl-fe/src/components/blurhash.tsx +++ b/packages/pl-fe/src/components/blurhash.tsx @@ -40,10 +40,12 @@ const Blurhash: React.FC = React.memo(({ try { const pixels = decode(hash, width, height); const ctx = canvas.getContext('2d'); - const imageData = new ImageData(new Uint8ClampedArray(pixels), width, height); + const imageData = ctx?.createImageData(width, height); + imageData?.data.set(pixels); - if (!ctx) return; - ctx.putImageData(imageData, 0, 0); + if (imageData) { + ctx?.putImageData(imageData, 0, 0); + } } catch (err) { console.error('Blurhash decoding failure', { err, hash }); } diff --git a/packages/pl-fe/src/components/media-gallery.tsx b/packages/pl-fe/src/components/media-gallery.tsx index ce00867ca..d4d6c4bee 100644 --- a/packages/pl-fe/src/components/media-gallery.tsx +++ b/packages/pl-fe/src/components/media-gallery.tsx @@ -40,8 +40,15 @@ interface SizeData { itemsDimensions: Dimensions[]; } -const getAspectRatio = (attachment: MediaAttachment) => - (attachment.type === 'gifv' || attachment.type === 'image' || attachment.type === 'video') && attachment.meta.original?.aspect || null; +const getAspectRatio = (attachment: MediaAttachment) => { + if ((attachment.type === 'gifv' || attachment.type === 'image' || attachment.type === 'video') && attachment.meta.original) { + if (attachment.meta.original.aspect) { + return attachment.meta.original.aspect; + } + return attachment.meta.original.width / attachment.meta.original.height; + } + return null; +}; const withinLimits = (aspectRatio: number) => aspectRatio >= minimumAspectRatio && aspectRatio <= maximumAspectRatio; diff --git a/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx b/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx index e566d42b6..898a7cd40 100644 --- a/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx +++ b/packages/pl-fe/src/components/statuses/sensitive-content-overlay.tsx @@ -105,7 +105,7 @@ const SensitiveContentOverlay = React.forwardRef