podverse-external-services 5.1.1-alpha.1 → 5.1.1-alpha.11

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.
@@ -11,8 +11,5 @@ export declare const config: {
11
11
  clientSecret: string;
12
12
  mode: string;
13
13
  };
14
- podcastIndex: {
15
- recentlyUpdatedDataInterval: number;
16
- };
17
14
  };
18
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;CAgBlB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;;;;;;;;CAalB,CAAA"}
@@ -13,8 +13,5 @@ exports.config = {
13
13
  clientId: process.env.PAYPAL_CLIENT_ID || '',
14
14
  clientSecret: process.env.PAYPAL_CLIENT_SECRET || '',
15
15
  mode: process.env.PAYPAL_MODE || 'sandbox'
16
- },
17
- podcastIndex: {
18
- recentlyUpdatedDataInterval: parseInt(process.env.PODCAST_INDEX_RECENTLY_UPDATED_DATA_INTERVAL || '1800', 10)
19
16
  }
20
17
  };
@@ -10,11 +10,20 @@ export declare class PodcastIndexService {
10
10
  secretKey: string;
11
11
  constructor({ authKey, baseUrl, secretKey }: Constructor);
12
12
  podcastIndexAPIRequest: (url: string, config?: any) => Promise<any>;
13
- getRecentlyUpdatedData: () => Promise<any[]>;
14
- getPodcastByGuid: (podcastGuid: string) => Promise<PodcastByGuidResponse | null>;
15
- getValueTagEnabledPodcastIdsRecursively: (accumulatedPodcastIndexIds: number[], startAt?: number) => Promise<number[]>;
16
- getValueTagEnabledPodcastIds: () => Promise<number[]>;
17
- downloadAndExtractCSV: () => Promise<any[]>;
13
+ deadFeedsDownloadAndExtractCSV: (resolveHandler: (row: string[]) => void) => Promise<void>;
14
+ deadFeedsExtractRow: (row: string[]) => {
15
+ id_to_archive: number;
16
+ duplicate_id_to_keep: number | null;
17
+ };
18
+ podcastGetById: (podcastIndexId: number) => Promise<any | null>;
19
+ podcastGetByGuid: (podcastGuid: string) => Promise<PodcastByGuidResponse | null>;
20
+ recentGetData: (sinceRange: number) => Promise<any[]>;
21
+ trendingGetPodcasts: (max?: number, since?: number, lang?: string, cat?: string) => Promise<{
22
+ feeds: any[];
23
+ nextSince?: number;
24
+ }>;
25
+ valueGetByPodcastIds: () => Promise<number[]>;
26
+ valueGetByPodcastIdsRecursively: (accumulatedPodcastIndexIds: number[], startAt?: number) => Promise<number[]>;
18
27
  }
19
28
  export {};
20
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/podcast-index/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AASD,qBAAa,mBAAmB;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;gBAEZ,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,WAAW;IAMzD,sBAAsB,GAAU,KAAK,MAAM,EAAE,SAAS,GAAG,kBAcxD;IAED,sBAAsB,uBA6BrB;IAED,gBAAgB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAYpF;IAED,uCAAuC,GACrC,4BAA4B,MAAM,EAAE,EAAE,gBAAW,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAatE;IAED,4BAA4B,QAAa,OAAO,CAAC,MAAM,EAAE,CAAC,CAMzD;IAED,qBAAqB,QAAa,OAAO,CAAC,GAAG,EAAE,CAAC,CAwC/C;CACF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/podcast-index/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AASD,qBAAa,mBAAmB;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;gBAEZ,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,WAAW;IAQzD,sBAAsB,GAAU,KAAK,MAAM,EAAE,SAAS,GAAG,kBA+BxD;IAID,8BAA8B,GAAU,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,KAAG,OAAO,CAAC,IAAI,CAAC,CAsC9F;IAED,mBAAmB,GAAI,KAAK,MAAM,EAAE;;;MAOnC;IAID,cAAc,GAAU,gBAAgB,MAAM,KAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAQnE;IAED,gBAAgB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAYpF;IAID,aAAa,GAAU,YAAY,MAAM,oBA4BxC;IAID,mBAAmB,GACjB,MAAK,MAAW,EAChB,QAAQ,MAAM,EACd,OAAO,MAAM,EACb,MAAM,MAAM,KACX,OAAO,CAAC;QAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAoB/C;IAID,oBAAoB,QAAa,OAAO,CAAC,MAAM,EAAE,CAAC,CAMjD;IAED,+BAA+B,GAC7B,4BAA4B,MAAM,EAAE,EAAE,gBAAW,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAatE;CACF"}
@@ -19,7 +19,6 @@ const csv_parser_1 = __importDefault(require("csv-parser"));
19
19
  const fs_1 = __importDefault(require("fs"));
20
20
  const path_1 = __importDefault(require("path"));
21
21
  const podverse_helpers_1 = require("podverse-helpers");
22
- const config_1 = require("@external-services/config");
23
22
  /*
24
23
  NOTE!!!
25
24
  The episodeGuid needs to be encoded both on the client-side and server side if it is an http url guid.
@@ -28,19 +27,104 @@ const config_1 = require("@external-services/config");
28
27
  */
29
28
  class PodcastIndexService {
30
29
  constructor({ authKey, baseUrl, secretKey }) {
30
+ // Request handler
31
31
  this.podcastIndexAPIRequest = (url, config) => __awaiter(this, void 0, void 0, function* () {
32
- const apiHeaderTime = new Date().getTime() / 1000;
32
+ var _a, _b, _c;
33
+ const apiHeaderTime = Math.floor(Date.now() / 1000);
33
34
  const hash = (0, sha1_1.default)(this.authKey + this.secretKey + apiHeaderTime).toString(enc_hex_1.default);
34
- return (0, podverse_helpers_1.request)(url, Object.assign({ headers: {
35
- 'X-Auth-Key': this.authKey,
36
- 'X-Auth-Date': apiHeaderTime,
37
- Authorization: hash
38
- } }, config));
35
+ const shouldPreventHeaders = (config === null || config === void 0 ? void 0 : config.preventHeaders) || false;
36
+ config === null || config === void 0 ? true : delete config.preventHeaders;
37
+ try {
38
+ const response = yield (0, podverse_helpers_1.request)(url, Object.assign(Object.assign({}, (shouldPreventHeaders ? {} : {
39
+ headers: {
40
+ 'X-Auth-Key': this.authKey,
41
+ 'X-Auth-Date': apiHeaderTime,
42
+ Authorization: hash
43
+ }
44
+ })), config));
45
+ return response;
46
+ }
47
+ catch (error) {
48
+ podverse_helpers_1.logger.error('[PodcastIndex] Request failed', {
49
+ url,
50
+ errorMessage: error === null || error === void 0 ? void 0 : error.message,
51
+ errorStack: error === null || error === void 0 ? void 0 : error.stack,
52
+ errorResponse: (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data,
53
+ errorStatus: (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status,
54
+ errorHeaders: (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.headers
55
+ });
56
+ throw error;
57
+ }
58
+ });
59
+ // Dead Feeds
60
+ this.deadFeedsDownloadAndExtractCSV = (resolveHandler) => __awaiter(this, void 0, void 0, function* () {
61
+ const url = 'https://public.podcastindex.org/podcastindex_dead_feeds.csv';
62
+ const tmpDir = path_1.default.join(__dirname, 'tmp');
63
+ const filePath = path_1.default.join(tmpDir, 'podcastindex_dead_feeds.csv');
64
+ if (!fs_1.default.existsSync(tmpDir)) {
65
+ fs_1.default.mkdirSync(tmpDir);
66
+ }
67
+ const data = yield this.podcastIndexAPIRequest(url, { preventHeaders: true, responseType: 'stream' });
68
+ const writer = fs_1.default.createWriteStream(filePath);
69
+ data.pipe(writer);
70
+ yield new Promise((resolve, reject) => {
71
+ writer.on('finish', () => resolve());
72
+ writer.on('error', reject);
73
+ });
74
+ yield new Promise((resolve, reject) => {
75
+ const stream = fs_1.default.createReadStream(filePath)
76
+ .pipe((0, csv_parser_1.default)({ headers: false, skipLines: 0 }));
77
+ stream.on('data', (row) => __awaiter(this, void 0, void 0, function* () {
78
+ stream.pause();
79
+ try {
80
+ yield resolveHandler(row);
81
+ stream.resume();
82
+ }
83
+ catch (err) {
84
+ stream.destroy(err instanceof Error ? err : new Error(String(err)));
85
+ }
86
+ }));
87
+ stream.on('end', () => resolve());
88
+ stream.on('error', reject);
89
+ stream.on('close', () => resolve());
90
+ });
91
+ fs_1.default.unlinkSync(filePath);
92
+ });
93
+ this.deadFeedsExtractRow = (row) => {
94
+ const id_to_archive = row[0];
95
+ const duplicate_id_to_keep = row[1] || null;
96
+ return {
97
+ id_to_archive: parseInt(id_to_archive, 10),
98
+ duplicate_id_to_keep: duplicate_id_to_keep ? parseInt(duplicate_id_to_keep, 10) : null
99
+ };
100
+ };
101
+ // Podcast
102
+ this.podcastGetById = (podcastIndexId) => __awaiter(this, void 0, void 0, function* () {
103
+ const url = `${this.baseUrl}/podcasts/byfeedid?id=${podcastIndexId}`;
104
+ try {
105
+ const response = yield this.podcastIndexAPIRequest(url);
106
+ return response || null;
107
+ }
108
+ catch (error) {
109
+ return null;
110
+ }
111
+ });
112
+ this.podcastGetByGuid = (podcastGuid) => __awaiter(this, void 0, void 0, function* () {
113
+ const url = `${this.baseUrl}/podcasts/byguid?guid=${podcastGuid}`;
114
+ let podcastIndexPodcast = null;
115
+ try {
116
+ const data = yield this.podcastIndexAPIRequest(url);
117
+ podcastIndexPodcast = data;
118
+ }
119
+ catch (error) {
120
+ // assume a 404
121
+ }
122
+ return podcastIndexPodcast || null;
39
123
  });
40
- this.getRecentlyUpdatedData = () => __awaiter(this, void 0, void 0, function* () {
41
- podverse_helpers_1.logger.info('getRecentlyUpdatedData beginning...');
124
+ // Recent
125
+ this.recentGetData = (sinceRange) => __awaiter(this, void 0, void 0, function* () {
126
+ podverse_helpers_1.logger.info('recentGetData beginning...');
42
127
  const currentTimeInSeconds = Math.floor(Date.now() / 1000);
43
- const sinceRange = config_1.config.podcastIndex.recentlyUpdatedDataInterval;
44
128
  const sinceTimeInSeconds = currentTimeInSeconds - sinceRange;
45
129
  const fetchData = (since_1, ...args_1) => __awaiter(this, [since_1, ...args_1], void 0, function* (since, allData = []) {
46
130
  podverse_helpers_1.logger.info(`fetchData since: ${since}, allData.length: ${allData.length}`);
@@ -62,67 +146,44 @@ class PodcastIndexService {
62
146
  });
63
147
  return fetchData(sinceTimeInSeconds);
64
148
  });
65
- this.getPodcastByGuid = (podcastGuid) => __awaiter(this, void 0, void 0, function* () {
66
- const url = `${this.baseUrl}/podcasts/byguid?guid=${podcastGuid}`;
67
- let podcastIndexPodcast = null;
68
- try {
69
- const data = yield this.podcastIndexAPIRequest(url);
70
- podcastIndexPodcast = data;
149
+ // Trending
150
+ this.trendingGetPodcasts = (...args_1) => __awaiter(this, [...args_1], void 0, function* (max = 25, since, lang, cat) {
151
+ const safeMax = Math.min(max, 1000);
152
+ let url = `${this.baseUrl}/podcasts/trending?max=${safeMax}`;
153
+ if (since) {
154
+ url += `&since=${since}`;
71
155
  }
72
- catch (error) {
73
- // assume a 404
156
+ if (lang) {
157
+ url += `&lang=${encodeURIComponent(lang)}`;
74
158
  }
75
- return podcastIndexPodcast || null;
159
+ if (cat) {
160
+ url += `&cat=${encodeURIComponent(cat)}`;
161
+ }
162
+ podverse_helpers_1.logger.info(`[PodcastIndex] Fetching trending feeds (max: ${safeMax}, since: ${since}, lang: ${lang}, cat: ${cat})`);
163
+ const response = yield this.podcastIndexAPIRequest(url);
164
+ return {
165
+ feeds: response.feeds || [],
166
+ nextSince: response.nextSince
167
+ };
76
168
  });
77
- this.getValueTagEnabledPodcastIdsRecursively = (accumulatedPodcastIndexIds_1, ...args_1) => __awaiter(this, [accumulatedPodcastIndexIds_1, ...args_1], void 0, function* (accumulatedPodcastIndexIds, startAt = 1) {
169
+ // Value
170
+ this.valueGetByPodcastIds = () => __awaiter(this, void 0, void 0, function* () {
171
+ const accumulatedPodcastIndexIds = [];
172
+ const nextStartAt = 1;
173
+ const podcastIndexIds = yield this.valueGetByPodcastIdsRecursively(accumulatedPodcastIndexIds, nextStartAt);
174
+ return podcastIndexIds;
175
+ });
176
+ this.valueGetByPodcastIdsRecursively = (accumulatedPodcastIndexIds_1, ...args_1) => __awaiter(this, [accumulatedPodcastIndexIds_1, ...args_1], void 0, function* (accumulatedPodcastIndexIds, startAt = 1) {
78
177
  const url = `${this.baseUrl}/podcasts/bytag?podcast-valueTimeSplit=true&max=5000&start_at=${startAt}`;
79
178
  const data = yield this.podcastIndexAPIRequest(url);
80
179
  for (const feed of data.feeds) {
81
180
  accumulatedPodcastIndexIds.push(feed.id);
82
181
  }
83
182
  if (data.nextStartAt) {
84
- return yield this.getValueTagEnabledPodcastIdsRecursively(accumulatedPodcastIndexIds, data.nextStartAt);
183
+ return yield this.valueGetByPodcastIdsRecursively(accumulatedPodcastIndexIds, data.nextStartAt);
85
184
  }
86
185
  return accumulatedPodcastIndexIds;
87
186
  });
88
- this.getValueTagEnabledPodcastIds = () => __awaiter(this, void 0, void 0, function* () {
89
- const accumulatedPodcastIndexIds = [];
90
- const nextStartAt = 1;
91
- const podcastIndexIds = yield this.getValueTagEnabledPodcastIdsRecursively(accumulatedPodcastIndexIds, nextStartAt);
92
- return podcastIndexIds;
93
- });
94
- this.downloadAndExtractCSV = () => __awaiter(this, void 0, void 0, function* () {
95
- const url = 'https://public.podcastindex.org/podcastindex_dead_feeds.csv';
96
- const tmpDir = path_1.default.join(__dirname, 'tmp');
97
- const filePath = path_1.default.join(tmpDir, 'podcastindex_dead_feeds.csv');
98
- if (!fs_1.default.existsSync(tmpDir)) {
99
- fs_1.default.mkdirSync(tmpDir);
100
- }
101
- const data = yield this.podcastIndexAPIRequest(url, { responseType: 'stream' });
102
- const writer = fs_1.default.createWriteStream(filePath);
103
- data.pipe(writer);
104
- yield new Promise((resolve, reject) => {
105
- writer.on('finish', () => resolve());
106
- writer.on('error', reject);
107
- });
108
- const results = [];
109
- yield new Promise((resolve, reject) => {
110
- fs_1.default.createReadStream(filePath)
111
- .pipe((0, csv_parser_1.default)())
112
- .on('data', (data) => results.push(data))
113
- .on('end', resolve)
114
- .on('error', reject);
115
- });
116
- fs_1.default.unlinkSync(filePath);
117
- const parsedResults = results.map((row) => {
118
- const [id, duplicateOf] = Object.values(row).map((value) => value.trim());
119
- return {
120
- podcast_index_id: parseInt(id, 10),
121
- duplicateOf: duplicateOf ? parseInt(duplicateOf, 10) : null
122
- };
123
- });
124
- return parsedResults;
125
- });
126
187
  this.authKey = authKey;
127
188
  this.baseUrl = baseUrl;
128
189
  this.secretKey = secretKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podverse-external-services",
3
- "version": "5.1.1-alpha.1",
3
+ "version": "5.1.1-alpha.11",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "devDependencies": {
19
19
  "@types/crypto-js": "^4.2.1",
20
20
  "@types/http-errors": "^2.0.4",
21
- "@types/node": "^20.10.4",
21
+ "@types/node": "^22.0.0",
22
22
  "@types/web-push": "^3.3.2",
23
23
  "@typescript-eslint/eslint-plugin": "^6.13.0",
24
24
  "@typescript-eslint/parser": "^6.13.0",
@@ -34,7 +34,7 @@
34
34
  "module-alias": "^2.2.3",
35
35
  "paypal-rest-sdk": "2.0.0-rc.2",
36
36
  "podcast-partytime": "^4.8.3",
37
- "podverse-helpers": "^5.1.2-alpha.2",
37
+ "podverse-helpers": "^5.1.2-alpha.5",
38
38
  "web-push": "^3.6.3"
39
39
  }
40
40
  }