podscan 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,630 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AlertsResource: () => AlertsResource,
24
+ EntitiesResource: () => EntitiesResource,
25
+ EpisodesResource: () => EpisodesResource,
26
+ ListsResource: () => ListsResource,
27
+ Paginator: () => Paginator,
28
+ PodcastsResource: () => PodcastsResource,
29
+ PodscanClient: () => PodscanClient,
30
+ PodscanError: () => PodscanError,
31
+ PublishersResource: () => PublishersResource,
32
+ TopicsResource: () => TopicsResource,
33
+ periods: () => periods
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/http.ts
38
+ var PodscanError = class extends Error {
39
+ code;
40
+ status;
41
+ details;
42
+ constructor({ code, message, status, details }) {
43
+ super(message);
44
+ this.name = "PodscanError";
45
+ this.code = code;
46
+ this.status = status;
47
+ this.details = details;
48
+ }
49
+ };
50
+ var HttpClient = class {
51
+ apiKey;
52
+ baseUrl;
53
+ timeout;
54
+ rateLimit = null;
55
+ constructor(options) {
56
+ this.apiKey = options.apiKey;
57
+ this.baseUrl = options.baseUrl ?? "https://podscan.fm/api/v1";
58
+ this.timeout = options.timeout ?? 3e4;
59
+ }
60
+ async get(path, params) {
61
+ const url = this.buildUrl(path, params);
62
+ return this.request(url, { method: "GET" });
63
+ }
64
+ async post(path, body) {
65
+ const url = this.buildUrl(path);
66
+ return this.request(url, {
67
+ method: "POST",
68
+ body: body ? JSON.stringify(body) : void 0
69
+ });
70
+ }
71
+ buildUrl(path, params) {
72
+ const url = new URL(`${this.baseUrl}${path}`);
73
+ if (params) {
74
+ for (const [key, value] of Object.entries(params)) {
75
+ if (value !== void 0 && value !== null) {
76
+ const serialized = typeof value === "object" ? JSON.stringify(value) : String(value);
77
+ url.searchParams.set(key, serialized);
78
+ }
79
+ }
80
+ }
81
+ return url.toString();
82
+ }
83
+ async request(url, init) {
84
+ const controller = new AbortController();
85
+ const timeoutId = setTimeout(() => {
86
+ controller.abort();
87
+ }, this.timeout);
88
+ try {
89
+ const response = await fetch(url, {
90
+ ...init,
91
+ signal: controller.signal,
92
+ headers: {
93
+ Authorization: `Bearer ${this.apiKey}`,
94
+ "Content-Type": "application/json",
95
+ Accept: "application/json"
96
+ }
97
+ });
98
+ this.parseRateLimitHeaders(response.headers);
99
+ if (!response.ok) {
100
+ const errorBody = await response.json().catch(() => null);
101
+ throw new PodscanError({
102
+ code: typeof errorBody?.code === "string" ? errorBody.code : "api_error",
103
+ message: typeof errorBody?.message === "string" ? errorBody.message : `HTTP ${String(response.status)}: ${response.statusText}`,
104
+ status: response.status,
105
+ details: errorBody?.details
106
+ });
107
+ }
108
+ return await response.json();
109
+ } catch (error) {
110
+ if (error instanceof PodscanError) throw error;
111
+ if (error instanceof Error && error.name === "AbortError") {
112
+ throw new PodscanError({
113
+ code: "timeout",
114
+ message: `Request timed out after ${String(this.timeout)}ms`,
115
+ status: 0
116
+ });
117
+ }
118
+ throw new PodscanError({
119
+ code: "network_error",
120
+ message: error instanceof Error ? error.message : "Unknown network error",
121
+ status: 0
122
+ });
123
+ } finally {
124
+ clearTimeout(timeoutId);
125
+ }
126
+ }
127
+ parseRateLimitHeaders(headers) {
128
+ const limit = headers.get("x-ratelimit-limit");
129
+ const remaining = headers.get("x-ratelimit-remaining");
130
+ const used = headers.get("x-ratelimit-used");
131
+ const resetsAt = headers.get("x-ratelimit-reset");
132
+ if (limit ?? remaining) {
133
+ this.rateLimit = {
134
+ limit: limit ? parseInt(limit, 10) : 0,
135
+ remaining: remaining ? parseInt(remaining, 10) : 0,
136
+ used: used ? parseInt(used, 10) : 0,
137
+ resetsAt: resetsAt ?? null
138
+ };
139
+ }
140
+ }
141
+ };
142
+
143
+ // src/paginator.ts
144
+ function extractTimestamp(item) {
145
+ if (item && typeof item === "object") {
146
+ const record = item;
147
+ if (typeof record.posted_at === "string") return record.posted_at;
148
+ if (typeof record.created_at === "string") return record.created_at;
149
+ }
150
+ return null;
151
+ }
152
+ function extractId(item) {
153
+ if (item && typeof item === "object") {
154
+ const record = item;
155
+ for (const key of [
156
+ "episode_id",
157
+ "podcast_id",
158
+ "topic_id",
159
+ "entity_id",
160
+ "alert_id",
161
+ "mention_id"
162
+ ]) {
163
+ if (typeof record[key] === "string") return record[key];
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+ var Paginator = class {
169
+ fetcher;
170
+ _totalSeen = 0;
171
+ _lastSeenAt = null;
172
+ _lastSeenId = null;
173
+ constructor(fetcher) {
174
+ this.fetcher = fetcher;
175
+ }
176
+ async *[Symbol.asyncIterator]() {
177
+ let currentPage = 1;
178
+ let lastPage = 1;
179
+ do {
180
+ const result = await this.fetcher(currentPage);
181
+ lastPage = result.pagination.last_page;
182
+ for (const item of result.items) {
183
+ this._totalSeen++;
184
+ const ts = extractTimestamp(item);
185
+ const id = extractId(item);
186
+ if (ts) this._lastSeenAt = ts;
187
+ if (id) this._lastSeenId = id;
188
+ yield item;
189
+ }
190
+ currentPage++;
191
+ } while (currentPage <= lastPage);
192
+ }
193
+ /**
194
+ * Returns a checkpoint representing the sync position after iteration.
195
+ * Save this and pass `checkpoint.lastSeenAt` as `since` on the next run
196
+ * to only fetch new items.
197
+ */
198
+ checkpoint() {
199
+ return {
200
+ lastSeenAt: this._lastSeenAt ?? "",
201
+ lastSeenId: this._lastSeenId ?? "",
202
+ totalSeen: this._totalSeen
203
+ };
204
+ }
205
+ /** Total number of items yielded so far. */
206
+ get totalSeen() {
207
+ return this._totalSeen;
208
+ }
209
+ };
210
+
211
+ // src/resources/episodes.ts
212
+ var EpisodesResource = class {
213
+ constructor(http) {
214
+ this.http = http;
215
+ }
216
+ /**
217
+ * Full-text search across podcast episode transcripts, titles, and descriptions.
218
+ */
219
+ async search(params) {
220
+ return this.http.get("/episodes/search", params);
221
+ }
222
+ /**
223
+ * Auto-paginating search that yields every matching episode across all pages.
224
+ *
225
+ * ```ts
226
+ * for await (const episode of client.episodes.searchAll({ query: 'AI', ...periods.thisWeek() })) {
227
+ * console.log(episode.episode_title);
228
+ * }
229
+ * ```
230
+ */
231
+ searchAll(params) {
232
+ return new Paginator(async (page) => {
233
+ const res = await this.search({ ...params, page });
234
+ return { items: res.episodes, pagination: res.pagination };
235
+ });
236
+ }
237
+ /**
238
+ * Get detailed information about a specific episode.
239
+ */
240
+ async get(params) {
241
+ const { episode_id, ...query } = params;
242
+ return this.http.get(`/episodes/${episode_id}`, query);
243
+ }
244
+ /**
245
+ * Get the most recently published podcast episodes.
246
+ */
247
+ async getRecent(params) {
248
+ return this.http.get("/episodes/recent", params);
249
+ }
250
+ /**
251
+ * List all episodes for a specific podcast.
252
+ */
253
+ async getByPodcast(params) {
254
+ const { podcast_id, ...query } = params;
255
+ return this.http.get(`/podcasts/${podcast_id}/episodes`, query);
256
+ }
257
+ /**
258
+ * Auto-paginating version of `getByPodcast()` that yields every episode.
259
+ *
260
+ * ```ts
261
+ * for await (const ep of client.episodes.getByPodcastAll({
262
+ * podcast_id: 'pd_abc',
263
+ * ...periods.thisMonth(),
264
+ * })) {
265
+ * console.log(ep.episode_title);
266
+ * }
267
+ * ```
268
+ */
269
+ getByPodcastAll(params) {
270
+ return new Paginator(async (page) => {
271
+ const res = await this.getByPodcast({ ...params, page });
272
+ return { items: res.episodes, pagination: res.pagination };
273
+ });
274
+ }
275
+ };
276
+
277
+ // src/resources/podcasts.ts
278
+ var PodcastsResource = class {
279
+ constructor(http) {
280
+ this.http = http;
281
+ }
282
+ /**
283
+ * Search podcasts by name, topic, or characteristics.
284
+ */
285
+ async search(params) {
286
+ return this.http.get("/podcasts/search", params);
287
+ }
288
+ /**
289
+ * Auto-paginating search that yields every matching podcast across all pages.
290
+ */
291
+ searchAll(params) {
292
+ return new Paginator(async (page) => {
293
+ const res = await this.search({ ...params, page });
294
+ return { items: res.podcasts, pagination: res.pagination };
295
+ });
296
+ }
297
+ /**
298
+ * Get detailed information about a specific podcast.
299
+ */
300
+ async get(params) {
301
+ const { podcast_id, ...query } = params;
302
+ return this.http.get(`/podcasts/${podcast_id}`, query);
303
+ }
304
+ };
305
+
306
+ // src/resources/alerts.ts
307
+ var AlertsResource = class {
308
+ constructor(http) {
309
+ this.http = http;
310
+ }
311
+ /**
312
+ * List your team's content monitoring alerts.
313
+ */
314
+ async list(params) {
315
+ return this.http.get("/alerts", params);
316
+ }
317
+ /**
318
+ * Get mentions found by a specific alert.
319
+ */
320
+ async getMentions(params) {
321
+ const { alert_id, ...query } = params;
322
+ return this.http.get(`/alerts/${alert_id}/mentions`, query);
323
+ }
324
+ /**
325
+ * Create a new content monitoring alert.
326
+ */
327
+ async create(params) {
328
+ return this.http.post("/alerts", params);
329
+ }
330
+ };
331
+
332
+ // src/resources/topics.ts
333
+ var TopicsResource = class {
334
+ constructor(http) {
335
+ this.http = http;
336
+ }
337
+ /**
338
+ * Discover topics and subjects discussed across podcasts.
339
+ */
340
+ async search(params) {
341
+ return this.http.get("/topics/search", params);
342
+ }
343
+ /**
344
+ * Auto-paginating search that yields every matching topic across all pages.
345
+ */
346
+ searchAll(params) {
347
+ return new Paginator(async (page) => {
348
+ const res = await this.search({ ...params, page });
349
+ return { items: res.topics, pagination: res.pagination };
350
+ });
351
+ }
352
+ /**
353
+ * Get detailed information about a specific topic.
354
+ */
355
+ async get(params) {
356
+ const { topic_id, ...query } = params;
357
+ return this.http.get(`/topics/${topic_id}`, query);
358
+ }
359
+ /**
360
+ * Get episodes where a specific topic was mentioned.
361
+ */
362
+ async getEpisodes(params) {
363
+ const { topic_id, ...query } = params;
364
+ return this.http.get(`/topics/${topic_id}/episodes`, query);
365
+ }
366
+ /**
367
+ * Auto-paginating version of `getEpisodes()` that yields every episode for a topic.
368
+ */
369
+ getEpisodesAll(params) {
370
+ return new Paginator(async (page) => {
371
+ const res = await this.getEpisodes({ ...params, page });
372
+ return { items: res.episodes, pagination: res.pagination };
373
+ });
374
+ }
375
+ /**
376
+ * Get currently trending topics across podcasts.
377
+ */
378
+ async getTrending(params) {
379
+ return this.http.get("/topics/trending", params);
380
+ }
381
+ };
382
+
383
+ // src/resources/entities.ts
384
+ var EntitiesResource = class {
385
+ constructor(http) {
386
+ this.http = http;
387
+ }
388
+ /**
389
+ * Search for people and organizations mentioned across podcasts.
390
+ */
391
+ async search(params) {
392
+ return this.http.get("/entities/search", params);
393
+ }
394
+ /**
395
+ * Auto-paginating search that yields every matching entity across all pages.
396
+ */
397
+ searchAll(params) {
398
+ return new Paginator(async (page) => {
399
+ const res = await this.search({ ...params, page });
400
+ return { items: res.entities, pagination: res.pagination };
401
+ });
402
+ }
403
+ /**
404
+ * Get detailed information about a person or organization.
405
+ */
406
+ async get(params) {
407
+ const { entity_id, ...query } = params;
408
+ return this.http.get(`/entities/${entity_id}`, query);
409
+ }
410
+ /**
411
+ * Get all podcast appearances for a specific entity.
412
+ */
413
+ async getAppearances(params) {
414
+ const { entity_id, ...query } = params;
415
+ return this.http.get(`/entities/${entity_id}/appearances`, query);
416
+ }
417
+ };
418
+
419
+ // src/resources/lists.ts
420
+ var ListsResource = class {
421
+ constructor(http) {
422
+ this.http = http;
423
+ }
424
+ /**
425
+ * Get all lists/collections for your team.
426
+ */
427
+ async list(params) {
428
+ return this.http.get("/lists", params);
429
+ }
430
+ /**
431
+ * Get contents of a specific list.
432
+ */
433
+ async getItems(params) {
434
+ const { list_id, ...query } = params;
435
+ return this.http.get(`/lists/${list_id}/items`, query);
436
+ }
437
+ /**
438
+ * Add items to a list. Accepts mixed item types (podcasts, episodes, entities, topics).
439
+ */
440
+ async addItems(params) {
441
+ const { list_id, ...body } = params;
442
+ return this.http.post(`/lists/${list_id}/items`, body);
443
+ }
444
+ };
445
+
446
+ // src/resources/publishers.ts
447
+ var PublishersResource = class {
448
+ constructor(http) {
449
+ this.http = http;
450
+ }
451
+ /**
452
+ * Get publisher information with their podcast portfolio.
453
+ */
454
+ async get(params) {
455
+ const { publisher_id, ...query } = params;
456
+ return this.http.get(`/publishers/${publisher_id}`, query);
457
+ }
458
+ };
459
+
460
+ // src/client.ts
461
+ var PodscanClient = class {
462
+ http;
463
+ /** Search and retrieve podcast episodes and recent content. */
464
+ episodes;
465
+ /** Search and retrieve podcast metadata. */
466
+ podcasts;
467
+ /** Create and manage keyword monitoring alerts and their mentions. */
468
+ alerts;
469
+ /** Search, retrieve, and track trending topics across podcasts. */
470
+ topics;
471
+ /** Search and retrieve people and organizations mentioned in podcasts. */
472
+ entities;
473
+ /** Manage curated collections of podcasts, episodes, entities, and topics. */
474
+ lists;
475
+ /** Retrieve publisher information and podcast portfolios. */
476
+ publishers;
477
+ constructor(options) {
478
+ this.http = new HttpClient(options);
479
+ this.episodes = new EpisodesResource(this.http);
480
+ this.podcasts = new PodcastsResource(this.http);
481
+ this.alerts = new AlertsResource(this.http);
482
+ this.topics = new TopicsResource(this.http);
483
+ this.entities = new EntitiesResource(this.http);
484
+ this.lists = new ListsResource(this.http);
485
+ this.publishers = new PublishersResource(this.http);
486
+ }
487
+ /** Current rate-limit info from the most recent API response, or null if no request has been made. */
488
+ get rateLimit() {
489
+ return this.http.rateLimit;
490
+ }
491
+ };
492
+
493
+ // src/periods.ts
494
+ function toISO(d) {
495
+ return d.toISOString();
496
+ }
497
+ function startOfDayUTC(d) {
498
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
499
+ }
500
+ function startOfWeekUTC(d) {
501
+ const day = d.getUTCDay();
502
+ const diff = day === 0 ? 6 : day - 1;
503
+ const monday = new Date(d);
504
+ monday.setUTCDate(d.getUTCDate() - diff);
505
+ return startOfDayUTC(monday);
506
+ }
507
+ function startOfMonthUTC(d) {
508
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1));
509
+ }
510
+ var periods = {
511
+ /**
512
+ * From midnight today (UTC) until now.
513
+ */
514
+ today() {
515
+ const now = /* @__PURE__ */ new Date();
516
+ return {
517
+ since: toISO(startOfDayUTC(now))
518
+ };
519
+ },
520
+ /**
521
+ * From midnight yesterday (UTC) to midnight today (UTC).
522
+ */
523
+ yesterday() {
524
+ const now = /* @__PURE__ */ new Date();
525
+ const todayStart = startOfDayUTC(now);
526
+ const yesterdayStart = new Date(todayStart);
527
+ yesterdayStart.setUTCDate(yesterdayStart.getUTCDate() - 1);
528
+ return {
529
+ since: toISO(yesterdayStart),
530
+ before: toISO(todayStart)
531
+ };
532
+ },
533
+ /**
534
+ * Rolling window: now minus 24 hours.
535
+ */
536
+ last24Hours() {
537
+ const now = /* @__PURE__ */ new Date();
538
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
539
+ return {
540
+ since: toISO(past)
541
+ };
542
+ },
543
+ /**
544
+ * From Monday 00:00 UTC of the current week until now.
545
+ */
546
+ thisWeek() {
547
+ const now = /* @__PURE__ */ new Date();
548
+ return {
549
+ since: toISO(startOfWeekUTC(now))
550
+ };
551
+ },
552
+ /**
553
+ * From Monday 00:00 UTC of last week to Monday 00:00 UTC of this week.
554
+ */
555
+ lastWeek() {
556
+ const now = /* @__PURE__ */ new Date();
557
+ const thisMonday = startOfWeekUTC(now);
558
+ const lastMonday = new Date(thisMonday);
559
+ lastMonday.setUTCDate(lastMonday.getUTCDate() - 7);
560
+ return {
561
+ since: toISO(lastMonday),
562
+ before: toISO(thisMonday)
563
+ };
564
+ },
565
+ /**
566
+ * From the 1st of the current month (UTC) until now.
567
+ */
568
+ thisMonth() {
569
+ const now = /* @__PURE__ */ new Date();
570
+ return {
571
+ since: toISO(startOfMonthUTC(now))
572
+ };
573
+ },
574
+ /**
575
+ * From the 1st of last month to the 1st of this month (UTC).
576
+ */
577
+ lastMonth() {
578
+ const now = /* @__PURE__ */ new Date();
579
+ const thisMonth = startOfMonthUTC(now);
580
+ const lastMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1));
581
+ return {
582
+ since: toISO(lastMonth),
583
+ before: toISO(thisMonth)
584
+ };
585
+ },
586
+ /**
587
+ * Rolling window: now minus N days.
588
+ */
589
+ lastNDays(n) {
590
+ const now = /* @__PURE__ */ new Date();
591
+ const past = new Date(now.getTime() - n * 24 * 60 * 60 * 1e3);
592
+ return {
593
+ since: toISO(past)
594
+ };
595
+ },
596
+ /**
597
+ * Rolling window: now minus N hours.
598
+ */
599
+ lastNHours(n) {
600
+ const now = /* @__PURE__ */ new Date();
601
+ const past = new Date(now.getTime() - n * 60 * 60 * 1e3);
602
+ return {
603
+ since: toISO(past)
604
+ };
605
+ },
606
+ /**
607
+ * Everything after a given date. Accepts an ISO string or Date object.
608
+ */
609
+ since(date) {
610
+ const d = typeof date === "string" ? new Date(date) : date;
611
+ return {
612
+ since: toISO(d)
613
+ };
614
+ }
615
+ };
616
+ // Annotate the CommonJS export names for ESM import in node:
617
+ 0 && (module.exports = {
618
+ AlertsResource,
619
+ EntitiesResource,
620
+ EpisodesResource,
621
+ ListsResource,
622
+ Paginator,
623
+ PodcastsResource,
624
+ PodscanClient,
625
+ PodscanError,
626
+ PublishersResource,
627
+ TopicsResource,
628
+ periods
629
+ });
630
+ //# sourceMappingURL=index.cjs.map