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.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +151 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +210 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +145 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -0
- package/src/client.ts +231 -0
- package/src/errors.ts +23 -0
- package/src/index.ts +14 -0
- package/src/types.ts +171 -0
package/dist/types.d.ts
ADDED
|
@@ -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":"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"}
|
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
|
+
}
|