pl-api: pixelfed stories
Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -76,6 +76,9 @@ import {
|
||||
statusEditSchema,
|
||||
statusSchema,
|
||||
statusSourceSchema,
|
||||
storyCarouselItemSchema,
|
||||
storyMediaSchema,
|
||||
storyProfileSchema,
|
||||
streamingEventSchema,
|
||||
subscriptionDetailsSchema,
|
||||
subscriptionInvoiceSchema,
|
||||
@ -248,6 +251,7 @@ import type {
|
||||
GetStatusQuotesParams,
|
||||
GetStatusReferencesParams,
|
||||
} from './params/statuses';
|
||||
import type { CreateStoryParams, CreateStoryPollParams, CropStoryPhotoParams, StoryReportType } from './params/stories';
|
||||
import type {
|
||||
AntennaTimelineParams,
|
||||
BubbleTimelineParams,
|
||||
@ -5767,6 +5771,132 @@ class PlApiClient {
|
||||
},
|
||||
};
|
||||
|
||||
public readonly stories = {
|
||||
getRecentStories: async () => {
|
||||
const response = await this.request('/api/web/stories/v1/recent');
|
||||
|
||||
return v.parse(filteredArray(storyCarouselItemSchema), response.json);
|
||||
},
|
||||
|
||||
getStoryViewers: async (storyId: string) => {
|
||||
const response = await this.request('/api/web/stories/v1/viewers', {
|
||||
params: { sid: storyId },
|
||||
});
|
||||
|
||||
return v.parse(filteredArray(accountSchema), response.json);
|
||||
},
|
||||
|
||||
getStoriesForProfile: async (accountId: string) => {
|
||||
const response = await this.request(`/api/web/stories/v1/profile/${accountId}`);
|
||||
|
||||
return v.parse(filteredArray(storyProfileSchema), response.json);
|
||||
},
|
||||
|
||||
storyExists: async (accountId: string) => {
|
||||
const response = await this.request(`/api/web/stories/v1/exists/${accountId}`);
|
||||
|
||||
return v.parse(v.boolean(), response.json);
|
||||
},
|
||||
|
||||
getStoryPollResults: async (storyId: string) => {
|
||||
const response = await this.request('/api/web/stories/v1/poll/results', {
|
||||
params: { sid: storyId },
|
||||
});
|
||||
|
||||
return v.parse(v.array(v.number()), response.json);
|
||||
},
|
||||
|
||||
markStoryAsViewed: async (storyId: string) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/viewed', {
|
||||
method: 'POST',
|
||||
body: { id: storyId },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStoryReaction: async (storyId: string, emoji: string) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/react', {
|
||||
method: 'POST',
|
||||
body: { sid: storyId, reaction: emoji },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStoryComment: async (storyId: string, comment: string) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/comment', {
|
||||
method: 'POST',
|
||||
body: { sid: storyId, caption: comment },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStoryPoll: async (params: CreateStoryPollParams) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/publish/poll', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
storyPollVote: async (storyId: string, choiceId: number) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/publish/poll', {
|
||||
method: 'POST',
|
||||
body: { sid: storyId, ci: choiceId },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
reportStory: async (storyId: string, type: StoryReportType) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/report', {
|
||||
method: 'POST',
|
||||
body: { id: storyId, type },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
addMedia: async (file: File) => {
|
||||
const response = await this.request('/api/web/stories/v1/add', {
|
||||
method: 'POST',
|
||||
body: { file },
|
||||
contentType: '',
|
||||
});
|
||||
|
||||
return v.parse(storyMediaSchema, response.json);
|
||||
},
|
||||
|
||||
cropPhoto: async (mediaId: string, params: CropStoryPhotoParams) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/crop', {
|
||||
method: 'POST',
|
||||
body: { media_id: mediaId, ...params },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
createStory: async (mediaId: string, params: CreateStoryParams) => {
|
||||
const response = await this.request<{}>('/api/web/stories/v1/publish', {
|
||||
method: 'POST',
|
||||
body: { media_id: mediaId, ...params },
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
|
||||
deleteStory: async (storyId: string) => {
|
||||
const response = await this.request<{}>(`/api/web/stories/v1/delete/${storyId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return response.json;
|
||||
},
|
||||
};
|
||||
|
||||
/** Routes that are not part of any stable release */
|
||||
public readonly experimental = {
|
||||
admin: {
|
||||
|
||||
@ -76,6 +76,9 @@ export * from './shout-message';
|
||||
export * from './status';
|
||||
export * from './status-edit';
|
||||
export * from './status-source';
|
||||
export * from './story-carousel-item';
|
||||
export * from './story-media';
|
||||
export * from './story-profile';
|
||||
export * from './streaming-event';
|
||||
export * from './subscription-details';
|
||||
export * from './subscription-invoice';
|
||||
|
||||
30
packages/pl-api/lib/entities/story-carousel-item.ts
Normal file
30
packages/pl-api/lib/entities/story-carousel-item.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
/**
|
||||
* @category Schemas
|
||||
*/
|
||||
const storyCarouselItemSchema = v.pipe(v.any(), v.transform((item) => ({
|
||||
account_id: item.pid,
|
||||
story_id: item.sid,
|
||||
...item,
|
||||
})), v.object({
|
||||
account_id: v.string(),
|
||||
avatar: v.string(),
|
||||
local: v.boolean(),
|
||||
username: v.string(),
|
||||
latest: v.object({
|
||||
id: v.pipe(v.unknown(), v.transform(String)),
|
||||
type: v.string(),
|
||||
preview_url: v.string(),
|
||||
}),
|
||||
url: v.string(),
|
||||
seen: v.boolean(),
|
||||
story_id: v.pipe(v.unknown(), v.transform(String)),
|
||||
}));
|
||||
|
||||
/**
|
||||
* @category Entity types
|
||||
*/
|
||||
type StoryCarouselItem = v.InferOutput<typeof storyCarouselItemSchema>;
|
||||
|
||||
export { storyCarouselItemSchema, type StoryCarouselItem };
|
||||
21
packages/pl-api/lib/entities/story-media.ts
Normal file
21
packages/pl-api/lib/entities/story-media.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
/**
|
||||
* @category Schemas
|
||||
*/
|
||||
const storyMediaSchema = v.pipe(v.any(), v.transform((media) => ({
|
||||
id: media.media_id,
|
||||
url: media.media_url,
|
||||
type: media.media_type,
|
||||
})), v.object({
|
||||
id: v.string(),
|
||||
url: v.string(),
|
||||
type: v.picklist(['photo', 'video']),
|
||||
}));
|
||||
|
||||
/**
|
||||
* @category Entity types
|
||||
*/
|
||||
type StoryMedia = v.InferOutput<typeof storyMediaSchema>;
|
||||
|
||||
export { storyMediaSchema, type StoryMedia };
|
||||
45
packages/pl-api/lib/entities/story-profile.ts
Normal file
45
packages/pl-api/lib/entities/story-profile.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { accountSchema } from './account';
|
||||
import { datetimeSchema, filteredArray } from './utils';
|
||||
|
||||
/**
|
||||
* @category Schemas
|
||||
*/
|
||||
const storyNodeSchema = v.object({
|
||||
id: v.string(),
|
||||
type: v.string(),
|
||||
duration: v.number(),
|
||||
src: v.string(),
|
||||
created_at: datetimeSchema,
|
||||
expires_at: datetimeSchema,
|
||||
view_count: v.fallback(v.nullable(v.number()), null),
|
||||
seen: v.boolean(),
|
||||
progress: v.number(),
|
||||
can_reply: v.boolean(),
|
||||
can_react: v.boolean(),
|
||||
question: v.optional(v.string(), undefined),
|
||||
options: v.optional(v.array(v.string()), undefined),
|
||||
voted: v.optional(v.boolean(), undefined),
|
||||
voted_index: v.optional(v.number(), undefined),
|
||||
});
|
||||
|
||||
/**
|
||||
* @category Entity types
|
||||
*/
|
||||
type StoryNode = v.InferOutput<typeof storyNodeSchema>;
|
||||
|
||||
/**
|
||||
* @category Schemas
|
||||
*/
|
||||
const storyProfileSchema = v.object({
|
||||
account: accountSchema,
|
||||
nodes: filteredArray(storyNodeSchema),
|
||||
});
|
||||
|
||||
/**
|
||||
* @category Entity types
|
||||
*/
|
||||
type StoryProfile = v.InferOutput<typeof storyProfileSchema>;
|
||||
|
||||
export { storyNodeSchema, type StoryNode, storyProfileSchema, type StoryProfile };
|
||||
@ -1556,6 +1556,25 @@ const getFeatures = (instance: Instance) => {
|
||||
*/
|
||||
statusDislikes: v.software === FRIENDICA && gte(v.version, '2023.3.0'),
|
||||
|
||||
/**
|
||||
* @see GET /api/web/stories/v1/recent
|
||||
* @see GET /api/web/stories/v1/viewers
|
||||
* @see GET /api/web/stories/v1/profile/:id
|
||||
* @see GET /api/web/stories/v1/exists/:id
|
||||
* @see GET /api/web/stories/v1/poll/results
|
||||
* @see POST /api/web/stories/v1/viewed
|
||||
* @see POST /api/web/stories/v1/react
|
||||
* @see POST /api/web/stories/v1/comment
|
||||
* @see POST /api/web/stories/v1/publish/poll
|
||||
* @see POST /api/web/stories/v1/poll/vote
|
||||
* @see POST /api/web/stories/v1/report
|
||||
* @see POST /api/web/stories/v1/add
|
||||
* @see POST /api/web/stories/v1/crop
|
||||
* @see POST /api/web/stories/v1/publish
|
||||
* @see DELETE /api/web/stories/v1/delete/:id
|
||||
*/
|
||||
stories: v.software === PIXELFED,
|
||||
|
||||
/**
|
||||
* @see GET /api/v1/accounts/:id/subscribers
|
||||
* @see POST /api/v1/subscriptions
|
||||
|
||||
26
packages/pl-api/lib/params/stories.ts
Normal file
26
packages/pl-api/lib/params/stories.ts
Normal file
@ -0,0 +1,26 @@
|
||||
interface CreateStoryPollParams {
|
||||
/** From 6 to 140 characters. */
|
||||
question: string;
|
||||
/** Between 2 and 4 answers. */
|
||||
answers: string;
|
||||
can_reply: boolean;
|
||||
can_react: boolean;
|
||||
}
|
||||
|
||||
type StoryReportType = 'spam' | 'sensitive' | 'abusive' | 'underage' | 'copyright' | 'impersonation' | 'scam' | 'terrorism';
|
||||
|
||||
interface CropStoryPhotoParams {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface CreateStoryParams {
|
||||
/** Between 3 and 120 (in seconds). */
|
||||
duration: number;
|
||||
can_reply: boolean;
|
||||
can_react: boolean;
|
||||
}
|
||||
|
||||
export type { CreateStoryPollParams, StoryReportType, CropStoryPhotoParams, CreateStoryParams };
|
||||
Reference in New Issue
Block a user