pocketcasts-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,210 @@
1
+ import { z } from 'zod';
2
+ export declare const PlayingStatusSchema: z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<"unplayed" | "playing" | "played", number>>, z.ZodEnum<{
3
+ unplayed: "unplayed";
4
+ playing: "playing";
5
+ played: "played";
6
+ }>]>;
7
+ export type PlayingStatus = 'unplayed' | 'playing' | 'played';
8
+ export declare const PLAYING_STATUS_TO_API: Record<PlayingStatus, number>;
9
+ export declare const PodcastSchema: z.ZodPipe<z.ZodObject<{
10
+ uuid: z.ZodString;
11
+ title: z.ZodString;
12
+ author: z.ZodString;
13
+ description: z.ZodDefault<z.ZodString>;
14
+ url: z.ZodString;
15
+ thumbnail_url: z.ZodOptional<z.ZodString>;
16
+ language: z.ZodOptional<z.ZodString>;
17
+ categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
+ media_type: z.ZodOptional<z.ZodString>;
19
+ sort_order: z.ZodOptional<z.ZodNumber>;
20
+ }, z.core.$loose>, z.ZodTransform<{
21
+ thumbnailUrl: string | undefined;
22
+ mediaType: string | undefined;
23
+ sortOrder: number | undefined;
24
+ uuid: string;
25
+ title: string;
26
+ author: string;
27
+ description: string;
28
+ url: string;
29
+ language?: string | undefined;
30
+ categories?: string[] | undefined;
31
+ }, {
32
+ [x: string]: unknown;
33
+ uuid: string;
34
+ title: string;
35
+ author: string;
36
+ description: string;
37
+ url: string;
38
+ thumbnail_url?: string | undefined;
39
+ language?: string | undefined;
40
+ categories?: string[] | undefined;
41
+ media_type?: string | undefined;
42
+ sort_order?: number | undefined;
43
+ }>>;
44
+ export type Podcast = z.output<typeof PodcastSchema>;
45
+ export declare const EpisodeSchema: z.ZodPipe<z.ZodObject<{
46
+ uuid: z.ZodString;
47
+ title: z.ZodString;
48
+ url: z.ZodDefault<z.ZodString>;
49
+ podcastUuid: z.ZodOptional<z.ZodString>;
50
+ podcast_uuid: z.ZodOptional<z.ZodString>;
51
+ podcastTitle: z.ZodOptional<z.ZodString>;
52
+ podcast_title: z.ZodOptional<z.ZodString>;
53
+ duration: z.ZodDefault<z.ZodNumber>;
54
+ published: z.ZodOptional<z.ZodString>;
55
+ publishedAt: z.ZodOptional<z.ZodString>;
56
+ published_at: z.ZodOptional<z.ZodString>;
57
+ fileType: z.ZodOptional<z.ZodString>;
58
+ file_type: z.ZodOptional<z.ZodString>;
59
+ size: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
60
+ playingStatus: z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<"unplayed" | "playing" | "played", number>>, z.ZodEnum<{
61
+ unplayed: "unplayed";
62
+ playing: "playing";
63
+ played: "played";
64
+ }>]>>;
65
+ playing_status: z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodNumber, z.ZodTransform<"unplayed" | "playing" | "played", number>>, z.ZodEnum<{
66
+ unplayed: "unplayed";
67
+ playing: "playing";
68
+ played: "played";
69
+ }>]>>;
70
+ playedUpTo: z.ZodOptional<z.ZodNumber>;
71
+ played_up_to: z.ZodOptional<z.ZodNumber>;
72
+ starred: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodNumber, z.ZodTransform<boolean, number>>]>>;
73
+ isDeleted: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodNumber, z.ZodTransform<boolean, number>>]>>;
74
+ is_deleted: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodNumber, z.ZodTransform<boolean, number>>]>>;
75
+ isVideo: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodNumber, z.ZodTransform<boolean, number>>]>>;
76
+ is_video: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodNumber, z.ZodTransform<boolean, number>>]>>;
77
+ }, z.core.$loose>, z.ZodTransform<{
78
+ podcastUuid: string;
79
+ podcastTitle: string | undefined;
80
+ publishedAt: string;
81
+ fileType: string | undefined;
82
+ playingStatus: "unplayed" | "playing" | "played";
83
+ playedUpTo: number;
84
+ isDeleted: boolean;
85
+ isVideo: boolean;
86
+ uuid: string;
87
+ title: string;
88
+ url: string;
89
+ duration: number;
90
+ starred: boolean;
91
+ is_deleted: boolean;
92
+ is_video: boolean;
93
+ podcast_uuid?: string | undefined;
94
+ podcast_title?: string | undefined;
95
+ published?: string | undefined;
96
+ published_at?: string | undefined;
97
+ file_type?: string | undefined;
98
+ size?: number | undefined;
99
+ playing_status?: "unplayed" | "playing" | "played" | undefined;
100
+ played_up_to?: number | undefined;
101
+ }, {
102
+ [x: string]: unknown;
103
+ uuid: string;
104
+ title: string;
105
+ url: string;
106
+ duration: number;
107
+ starred: boolean;
108
+ isDeleted: boolean;
109
+ is_deleted: boolean;
110
+ isVideo: boolean;
111
+ is_video: boolean;
112
+ podcastUuid?: string | undefined;
113
+ podcast_uuid?: string | undefined;
114
+ podcastTitle?: string | undefined;
115
+ podcast_title?: string | undefined;
116
+ published?: string | undefined;
117
+ publishedAt?: string | undefined;
118
+ published_at?: string | undefined;
119
+ fileType?: string | undefined;
120
+ file_type?: string | undefined;
121
+ size?: number | undefined;
122
+ playingStatus?: "unplayed" | "playing" | "played" | undefined;
123
+ playing_status?: "unplayed" | "playing" | "played" | undefined;
124
+ playedUpTo?: number | undefined;
125
+ played_up_to?: number | undefined;
126
+ }>>;
127
+ export type Episode = z.output<typeof EpisodeSchema>;
128
+ export declare const SearchResultSchema: z.ZodPipe<z.ZodObject<{
129
+ uuid: z.ZodString;
130
+ title: z.ZodString;
131
+ author: z.ZodDefault<z.ZodString>;
132
+ description: z.ZodOptional<z.ZodString>;
133
+ thumbnail_url: z.ZodOptional<z.ZodString>;
134
+ }, z.core.$loose>, z.ZodTransform<{
135
+ thumbnailUrl: string | undefined;
136
+ uuid: string;
137
+ title: string;
138
+ author: string;
139
+ description?: string | undefined;
140
+ }, {
141
+ [x: string]: unknown;
142
+ uuid: string;
143
+ title: string;
144
+ author: string;
145
+ description?: string | undefined;
146
+ thumbnail_url?: string | undefined;
147
+ }>>;
148
+ export type SearchResult = z.output<typeof SearchResultSchema>;
149
+ export declare const ListeningStatsSchema: z.ZodPipe<z.ZodObject<{
150
+ timeSilenceRemoval: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
151
+ timeListened: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
152
+ timeSkipping: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
153
+ timeIntroSkipping: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
154
+ timeVariableSpeed: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
155
+ totalPodcasts: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
156
+ totalEpisodes: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
157
+ timesStartedAt: z.ZodOptional<z.ZodString>;
158
+ startedAt: z.ZodOptional<z.ZodString>;
159
+ time_silence_removal: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
160
+ time_listened: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
161
+ time_skipping: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
162
+ time_intro_skipping: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
163
+ time_variable_speed: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
164
+ total_podcasts: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
165
+ total_episodes: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
166
+ started_at: z.ZodOptional<z.ZodString>;
167
+ }, z.core.$loose>, z.ZodTransform<{
168
+ timeSilenceRemoval: number;
169
+ timeListened: number;
170
+ timeSkipping: number;
171
+ timeIntroSkipping: number;
172
+ timeVariableSpeed: number;
173
+ totalPodcasts: number;
174
+ totalEpisodes: number;
175
+ startedAt: string;
176
+ time_silence_removal: number;
177
+ time_listened: number;
178
+ time_skipping: number;
179
+ time_intro_skipping: number;
180
+ time_variable_speed: number;
181
+ total_podcasts: number;
182
+ total_episodes: number;
183
+ timesStartedAt?: string | undefined;
184
+ started_at?: string | undefined;
185
+ }, {
186
+ [x: string]: unknown;
187
+ timeSilenceRemoval: number;
188
+ timeListened: number;
189
+ timeSkipping: number;
190
+ timeIntroSkipping: number;
191
+ timeVariableSpeed: number;
192
+ totalPodcasts: number;
193
+ totalEpisodes: number;
194
+ time_silence_removal: number;
195
+ time_listened: number;
196
+ time_skipping: number;
197
+ time_intro_skipping: number;
198
+ time_variable_speed: number;
199
+ total_podcasts: number;
200
+ total_episodes: number;
201
+ timesStartedAt?: string | undefined;
202
+ startedAt?: string | undefined;
203
+ started_at?: string | undefined;
204
+ }>>;
205
+ export type ListeningStats = z.output<typeof ListeningStatsSchema>;
206
+ export declare const LoginResponseSchema: z.ZodObject<{
207
+ token: z.ZodString;
208
+ uuid: z.ZodString;
209
+ }, z.core.$strip>;
210
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB,eAAO,MAAM,mBAAmB;;;;IAG9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9D,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAI/D,CAAC;AAIF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBrB,CAAC;AAEN,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAarD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCrB,CAAC;AAEN,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAIrD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;GAW1B,CAAC;AAEN,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAS/D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgC5B,CAAC;AAEN,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAInE,eAAO,MAAM,mBAAmB;;;iBAG9B,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,145 @@
1
+ import { z } from 'zod';
2
+ // --- Playing Status ---
3
+ // The API returns playing status as either:
4
+ // - a number: 0=unplayed, 1=not played (same as unplayed), 2=playing, 3=played
5
+ // - a string: "unplayed", "playing", "played"
6
+ const PLAYING_STATUS_NUM_MAP = {
7
+ 0: 'unplayed',
8
+ 1: 'unplayed', // value 1 appears in the wild, treat as unplayed
9
+ 2: 'playing',
10
+ 3: 'played',
11
+ };
12
+ const PLAYING_STATUS_STRINGS = ['unplayed', 'playing', 'played'];
13
+ export const PlayingStatusSchema = z.union([
14
+ z.number().transform((n) => PLAYING_STATUS_NUM_MAP[n] ?? 'unplayed'),
15
+ z.enum(PLAYING_STATUS_STRINGS),
16
+ ]);
17
+ export const PLAYING_STATUS_TO_API = {
18
+ unplayed: 0,
19
+ playing: 2,
20
+ played: 3,
21
+ };
22
+ // --- Podcast ---
23
+ export const PodcastSchema = z
24
+ .looseObject({
25
+ uuid: z.string(),
26
+ title: z.string(),
27
+ author: z.string(),
28
+ description: z.string().default(''),
29
+ url: z.string(),
30
+ thumbnail_url: z.string().optional(),
31
+ language: z.string().optional(),
32
+ categories: z.array(z.string()).optional(),
33
+ media_type: z.string().optional(),
34
+ sort_order: z.number().optional(),
35
+ })
36
+ .transform(({ thumbnail_url, media_type, sort_order, ...rest }) => ({
37
+ ...rest,
38
+ thumbnailUrl: thumbnail_url,
39
+ mediaType: media_type,
40
+ sortOrder: sort_order,
41
+ }));
42
+ // --- Episode ---
43
+ // The real API returns camelCase fields (podcastUuid, playingStatus, etc.)
44
+ // We accept both camelCase and snake_case for flexibility.
45
+ const boolOrNum = z
46
+ .union([z.boolean(), z.number().transform((n) => n === 1)])
47
+ .default(false);
48
+ const stringOrNum = z
49
+ .union([z.number(), z.string().transform((s) => parseInt(s, 10) || 0)])
50
+ .optional();
51
+ export const EpisodeSchema = z
52
+ .looseObject({
53
+ uuid: z.string(),
54
+ title: z.string(),
55
+ url: z.string().default(''),
56
+ // Accept both camelCase (real API) and snake_case
57
+ podcastUuid: z.string().optional(),
58
+ podcast_uuid: z.string().optional(),
59
+ podcastTitle: z.string().optional(),
60
+ podcast_title: z.string().optional(),
61
+ duration: z.number().default(0),
62
+ published: z.string().optional(),
63
+ publishedAt: z.string().optional(),
64
+ published_at: z.string().optional(),
65
+ fileType: z.string().optional(),
66
+ file_type: z.string().optional(),
67
+ size: stringOrNum,
68
+ playingStatus: PlayingStatusSchema.optional(),
69
+ playing_status: PlayingStatusSchema.optional(),
70
+ playedUpTo: z.number().optional(),
71
+ played_up_to: z.number().optional(),
72
+ starred: boolOrNum,
73
+ isDeleted: boolOrNum,
74
+ is_deleted: boolOrNum,
75
+ isVideo: boolOrNum,
76
+ is_video: boolOrNum,
77
+ })
78
+ .transform((e) => ({
79
+ ...e,
80
+ podcastUuid: e.podcastUuid ?? e.podcast_uuid ?? '',
81
+ podcastTitle: e.podcastTitle ?? e.podcast_title,
82
+ publishedAt: e.publishedAt || e.published_at || e.published || '',
83
+ fileType: e.fileType ?? e.file_type,
84
+ playingStatus: e.playingStatus ?? e.playing_status ?? 'unplayed',
85
+ playedUpTo: e.playedUpTo ?? e.played_up_to ?? 0,
86
+ isDeleted: e.isDeleted || e.is_deleted || false,
87
+ isVideo: e.isVideo || e.is_video || false,
88
+ }));
89
+ // --- Search Result ---
90
+ export const SearchResultSchema = z
91
+ .looseObject({
92
+ uuid: z.string(),
93
+ title: z.string(),
94
+ author: z.string().default(''),
95
+ description: z.string().optional(),
96
+ thumbnail_url: z.string().optional(),
97
+ })
98
+ .transform(({ thumbnail_url, ...rest }) => ({
99
+ ...rest,
100
+ thumbnailUrl: thumbnail_url,
101
+ }));
102
+ // --- Listening Stats ---
103
+ // Real API returns camelCase with some values as strings (not numbers).
104
+ const numOrStr = z
105
+ .union([z.number(), z.string().transform((s) => parseFloat(s) || 0)])
106
+ .default(0);
107
+ export const ListeningStatsSchema = z
108
+ .looseObject({
109
+ // camelCase (real API)
110
+ timeSilenceRemoval: numOrStr,
111
+ timeListened: numOrStr,
112
+ timeSkipping: numOrStr,
113
+ timeIntroSkipping: numOrStr,
114
+ timeVariableSpeed: numOrStr,
115
+ totalPodcasts: numOrStr,
116
+ totalEpisodes: numOrStr,
117
+ timesStartedAt: z.string().optional(),
118
+ startedAt: z.string().optional(),
119
+ // snake_case fallback
120
+ time_silence_removal: numOrStr,
121
+ time_listened: numOrStr,
122
+ time_skipping: numOrStr,
123
+ time_intro_skipping: numOrStr,
124
+ time_variable_speed: numOrStr,
125
+ total_podcasts: numOrStr,
126
+ total_episodes: numOrStr,
127
+ started_at: z.string().optional(),
128
+ })
129
+ .transform((s) => ({
130
+ ...s,
131
+ timeSilenceRemoval: s.timeSilenceRemoval || s.time_silence_removal || 0,
132
+ timeListened: s.timeListened || s.time_listened || 0,
133
+ timeSkipping: s.timeSkipping || s.time_skipping || 0,
134
+ timeIntroSkipping: s.timeIntroSkipping || s.time_intro_skipping || 0,
135
+ timeVariableSpeed: s.timeVariableSpeed || s.time_variable_speed || 0,
136
+ totalPodcasts: s.totalPodcasts || s.total_podcasts || 0,
137
+ totalEpisodes: s.totalEpisodes || s.total_episodes || 0,
138
+ startedAt: s.timesStartedAt ?? s.startedAt ?? s.started_at ?? '',
139
+ }));
140
+ // --- Login Response ---
141
+ export const LoginResponseSchema = z.object({
142
+ token: z.string(),
143
+ uuid: z.string(),
144
+ });
145
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,yBAAyB;AACzB,4CAA4C;AAC5C,iFAAiF;AACjF,gDAAgD;AAEhD,MAAM,sBAAsB,GAAkC;IAC5D,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,UAAU,EAAE,iDAAiD;IAChE,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,QAAQ;CACZ,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAC;AAE1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC;IACzC,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;IACpE,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC;CAC/B,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,qBAAqB,GAAkC;IAClE,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,kBAAkB;AAElB,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,WAAW,CAAC;IACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC1C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC;KACD,SAAS,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAClE,GAAG,IAAI;IACP,YAAY,EAAE,aAAa;IAC3B,SAAS,EAAE,UAAU;IACrB,SAAS,EAAE,UAAU;CACtB,CAAC,CAAC,CAAC;AAIN,kBAAkB;AAClB,2EAA2E;AAC3E,2DAA2D;AAE3D,MAAM,SAAS,GAAG,CAAC;KAChB,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KAC1D,OAAO,CAAC,KAAK,CAAC,CAAC;AAClB,MAAM,WAAW,GAAG,CAAC;KAClB,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACtE,QAAQ,EAAE,CAAC;AAEd,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,WAAW,CAAC;IACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3B,kDAAkD;IAClD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,WAAW;IACjB,aAAa,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IAC7C,cAAc,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IAC9C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,SAAS;IACpB,UAAU,EAAE,SAAS;IACrB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,SAAS;CACpB,CAAC;KACD,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjB,GAAG,CAAC;IACJ,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,YAAY,IAAI,EAAE;IAClD,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,aAAa;IAC/C,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE;IACjE,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS;IACnC,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,cAAc,IAAK,UAAoB;IAC3E,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC;IAC/C,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU,IAAI,KAAK;IAC/C,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK;CAC1C,CAAC,CAAC,CAAC;AAIN,wBAAwB;AAExB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KAChC,WAAW,CAAC;IACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC9B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC;KACD,SAAS,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC1C,GAAG,IAAI;IACP,YAAY,EAAE,aAAa;CAC5B,CAAC,CAAC,CAAC;AAIN,0BAA0B;AAC1B,wEAAwE;AAExE,MAAM,QAAQ,GAAG,CAAC;KACf,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACpE,OAAO,CAAC,CAAC,CAAC,CAAC;AAEd,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,WAAW,CAAC;IACX,uBAAuB;IACvB,kBAAkB,EAAE,QAAQ;IAC5B,YAAY,EAAE,QAAQ;IACtB,YAAY,EAAE,QAAQ;IACtB,iBAAiB,EAAE,QAAQ;IAC3B,iBAAiB,EAAE,QAAQ;IAC3B,aAAa,EAAE,QAAQ;IACvB,aAAa,EAAE,QAAQ;IACvB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,sBAAsB;IACtB,oBAAoB,EAAE,QAAQ;IAC9B,aAAa,EAAE,QAAQ;IACvB,aAAa,EAAE,QAAQ;IACvB,mBAAmB,EAAE,QAAQ;IAC7B,mBAAmB,EAAE,QAAQ;IAC7B,cAAc,EAAE,QAAQ;IACxB,cAAc,EAAE,QAAQ;IACxB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC;KACD,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjB,GAAG,CAAC;IACJ,kBAAkB,EAAE,CAAC,CAAC,kBAAkB,IAAI,CAAC,CAAC,oBAAoB,IAAI,CAAC;IACvE,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC;IACpD,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,mBAAmB,IAAI,CAAC;IACpE,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,mBAAmB,IAAI,CAAC;IACpE,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC;IACvD,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC;IACvD,SAAS,EAAE,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU,IAAI,EAAE;CACjE,CAAC,CAAC,CAAC;AAIN,yBAAyB;AAEzB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "pocketcasts-api",
3
+ "version": "0.1.0",
4
+ "description": "Unofficial TypeScript client for the Pocket Casts API",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "sideEffects": false,
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:integration": "RUN_INTEGRATION=true vitest run test/integration.test.ts",
27
+ "lint": "eslint . && tsc --noEmit",
28
+ "format": "prettier --write .",
29
+ "format:check": "prettier --check .",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "pocketcasts",
34
+ "pocket-casts",
35
+ "podcast",
36
+ "api"
37
+ ],
38
+ "license": "MIT",
39
+ "author": "Madison Rickert",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/madisonrickert/pocketcasts-api.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/madisonrickert/pocketcasts-api/issues"
46
+ },
47
+ "homepage": "https://github.com/madisonrickert/pocketcasts-api#readme",
48
+ "dependencies": {
49
+ "zod": "^4.4"
50
+ },
51
+ "devDependencies": {
52
+ "@eslint/js": "^10.0.1",
53
+ "eslint": "^10.4.0",
54
+ "prettier": "^3.8.3",
55
+ "typescript": "^6.0",
56
+ "typescript-eslint": "^8.60.0",
57
+ "vitest": "^4.1"
58
+ }
59
+ }
package/src/client.ts ADDED
@@ -0,0 +1,231 @@
1
+ import { PocketCastsAuthError, PocketCastsAPIError } from './errors.js';
2
+ import {
3
+ LoginResponseSchema,
4
+ PodcastSchema,
5
+ EpisodeSchema,
6
+ SearchResultSchema,
7
+ ListeningStatsSchema,
8
+ type Podcast,
9
+ type Episode,
10
+ type SearchResult,
11
+ type ListeningStats,
12
+ type PlayingStatus,
13
+ } from './types.js';
14
+ import { z } from 'zod';
15
+
16
+ const API_BASE = 'https://api.pocketcasts.com';
17
+ const LISTS_BASE = 'https://lists.pocketcasts.com';
18
+
19
+ type FetchFn = typeof globalThis.fetch;
20
+
21
+ export interface PocketCastsOptions {
22
+ fetch?: FetchFn;
23
+ }
24
+
25
+ export class PocketCasts {
26
+ private token: string | null = null;
27
+ private fetch: FetchFn;
28
+
29
+ constructor(options: PocketCastsOptions = {}) {
30
+ this.fetch = options.fetch ?? globalThis.fetch;
31
+ }
32
+
33
+ async login(email: string, password: string): Promise<void> {
34
+ const res = await this.fetch(`${API_BASE}/user/login`, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({ email, password, scope: 'webplayer' }),
38
+ });
39
+
40
+ if (!res.ok) {
41
+ const text = await res.text();
42
+ throw new PocketCastsAuthError(`Login failed (${res.status}): ${text}`);
43
+ }
44
+
45
+ const data = LoginResponseSchema.parse(await res.json());
46
+ this.token = data.token;
47
+ }
48
+
49
+ private async postAuth<T>(path: string, body: object = {}): Promise<T> {
50
+ if (!this.token) {
51
+ throw new PocketCastsAuthError('Not logged in. Call login() first.');
52
+ }
53
+
54
+ const res = await this.fetch(`${API_BASE}${path}`, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ Authorization: `Bearer ${this.token}`,
59
+ },
60
+ body: JSON.stringify(body),
61
+ });
62
+
63
+ if (!res.ok) {
64
+ const text = await res.text();
65
+ throw new PocketCastsAPIError(
66
+ `API error (${res.status}): ${text}`,
67
+ res.status,
68
+ );
69
+ }
70
+
71
+ return (await res.json()) as T;
72
+ }
73
+
74
+ private async getPublic<T>(path: string): Promise<T> {
75
+ const res = await this.fetch(`${LISTS_BASE}${path}`);
76
+
77
+ if (!res.ok) {
78
+ const text = await res.text();
79
+ throw new PocketCastsAPIError(
80
+ `API error (${res.status}): ${text}`,
81
+ res.status,
82
+ );
83
+ }
84
+
85
+ return (await res.json()) as T;
86
+ }
87
+
88
+ async getSubscriptions(): Promise<{ podcasts: Podcast[] }> {
89
+ const data = await this.postAuth<{ podcasts: unknown[] }>(
90
+ '/user/podcast/list',
91
+ );
92
+ return { podcasts: z.array(PodcastSchema).parse(data.podcasts) };
93
+ }
94
+
95
+ async getNewReleases(): Promise<{ episodes: Episode[] }> {
96
+ const data = await this.postAuth<{ episodes: unknown[] }>(
97
+ '/user/new_releases',
98
+ );
99
+ return { episodes: z.array(EpisodeSchema).parse(data.episodes) };
100
+ }
101
+
102
+ async getInProgress(): Promise<{ episodes: Episode[] }> {
103
+ const data = await this.postAuth<{ episodes: unknown[] }>(
104
+ '/user/in_progress',
105
+ );
106
+ return { episodes: z.array(EpisodeSchema).parse(data.episodes) };
107
+ }
108
+
109
+ async getStarred(): Promise<{ episodes: Episode[] }> {
110
+ const data = await this.postAuth<{ episodes: unknown[] }>('/user/starred');
111
+ return { episodes: z.array(EpisodeSchema).parse(data.episodes) };
112
+ }
113
+
114
+ async getHistory(): Promise<{ episodes: Episode[] }> {
115
+ const data = await this.postAuth<{ episodes: unknown[] }>('/user/history');
116
+ return { episodes: z.array(EpisodeSchema).parse(data.episodes) };
117
+ }
118
+
119
+ async getStats(): Promise<ListeningStats> {
120
+ const data = await this.postAuth<unknown>('/user/stats/summary');
121
+ return ListeningStatsSchema.parse(data);
122
+ }
123
+
124
+ async getPodcast(uuid: string): Promise<Podcast> {
125
+ const data = await this.postAuth<unknown>('/podcasts/podcast', { uuid });
126
+ return PodcastSchema.parse(data);
127
+ }
128
+
129
+ async getEpisode(uuid: string): Promise<Episode> {
130
+ const data = await this.postAuth<unknown>('/episodes/episode', { uuid });
131
+ return EpisodeSchema.parse(data);
132
+ }
133
+
134
+ async getEpisodeNotes(uuid: string): Promise<string> {
135
+ const data = await this.postAuth<{ show_notes: string }>(
136
+ '/episodes/show_notes',
137
+ { uuid },
138
+ );
139
+ return data.show_notes;
140
+ }
141
+
142
+ async search(term: string): Promise<{ podcasts: SearchResult[] }> {
143
+ const data = await this.postAuth<{ podcasts: unknown[] }>(
144
+ '/discover/search',
145
+ { term },
146
+ );
147
+ return { podcasts: z.array(SearchResultSchema).parse(data.podcasts) };
148
+ }
149
+
150
+ async getTrending(): Promise<{ podcasts: SearchResult[] }> {
151
+ const data = await this.getPublic<{ podcasts: unknown[] }>(
152
+ '/trending.json',
153
+ );
154
+ return { podcasts: z.array(SearchResultSchema).parse(data.podcasts) };
155
+ }
156
+
157
+ async getPopular(): Promise<{ podcasts: SearchResult[] }> {
158
+ const data = await this.getPublic<{ podcasts: unknown[] }>('/popular.json');
159
+ return { podcasts: z.array(SearchResultSchema).parse(data.podcasts) };
160
+ }
161
+
162
+ async getFeatured(): Promise<{ podcasts: SearchResult[] }> {
163
+ const data = await this.getPublic<{ podcasts: unknown[] }>(
164
+ '/featured.json',
165
+ );
166
+ return { podcasts: z.array(SearchResultSchema).parse(data.podcasts) };
167
+ }
168
+
169
+ async subscribe(podcastUuid: string): Promise<void> {
170
+ await this.postAuth('/user/podcast/subscribe', { uuid: podcastUuid });
171
+ }
172
+
173
+ async unsubscribe(podcastUuid: string): Promise<void> {
174
+ await this.postAuth('/user/podcast/unsubscribe', { uuid: podcastUuid });
175
+ }
176
+
177
+ // Episode mutations use /sync/update_episode.
178
+ // The endpoint expects: uuid, podcast, and either status (1-4) or position/duration.
179
+ // Status values: 1=unplayed, 2=playing, 3=played, 4=? (validation says 1..4)
180
+
181
+ async starEpisode(podcastUuid: string, episodeUuid: string): Promise<void> {
182
+ // /sync/update_episode requires status or position alongside star
183
+ await this.postAuth('/sync/update_episode', {
184
+ uuid: episodeUuid,
185
+ podcast: podcastUuid,
186
+ star: 1,
187
+ status: 1, // required by validation, 1=unplayed (preserves current state)
188
+ });
189
+ }
190
+
191
+ async unstarEpisode(podcastUuid: string, episodeUuid: string): Promise<void> {
192
+ await this.postAuth('/sync/update_episode', {
193
+ uuid: episodeUuid,
194
+ podcast: podcastUuid,
195
+ star: 0,
196
+ status: 1,
197
+ });
198
+ }
199
+
200
+ async updatePlayingStatus(
201
+ podcastUuid: string,
202
+ episodeUuid: string,
203
+ status: PlayingStatus,
204
+ ): Promise<void> {
205
+ // The sync endpoint uses 1-based status: 1=unplayed, 2=playing, 3=played
206
+ const SYNC_STATUS_MAP: Record<PlayingStatus, number> = {
207
+ unplayed: 1,
208
+ playing: 2,
209
+ played: 3,
210
+ };
211
+ await this.postAuth('/sync/update_episode', {
212
+ uuid: episodeUuid,
213
+ podcast: podcastUuid,
214
+ status: SYNC_STATUS_MAP[status],
215
+ });
216
+ }
217
+
218
+ async updatePlaybackPosition(
219
+ podcastUuid: string,
220
+ episodeUuid: string,
221
+ position: number,
222
+ duration: number,
223
+ ): Promise<void> {
224
+ await this.postAuth('/sync/update_episode', {
225
+ uuid: episodeUuid,
226
+ podcast: podcastUuid,
227
+ position,
228
+ duration,
229
+ });
230
+ }
231
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,23 @@
1
+ export class PocketCastsError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'PocketCastsError';
5
+ }
6
+ }
7
+
8
+ export class PocketCastsAuthError extends PocketCastsError {
9
+ constructor(message: string) {
10
+ super(message);
11
+ this.name = 'PocketCastsAuthError';
12
+ }
13
+ }
14
+
15
+ export class PocketCastsAPIError extends PocketCastsError {
16
+ statusCode: number;
17
+
18
+ constructor(message: string, statusCode: number) {
19
+ super(message);
20
+ this.name = 'PocketCastsAPIError';
21
+ this.statusCode = statusCode;
22
+ }
23
+ }