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.
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -3
- package/dist/services/podcast-index/index.d.ts +14 -5
- package/dist/services/podcast-index/index.d.ts.map +1 -1
- package/dist/services/podcast-index/index.js +120 -59
- package/package.json +3 -3
package/dist/config/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;;;;;;;;CAalB,CAAA"}
|
package/dist/config/index.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
156
|
+
if (lang) {
|
|
157
|
+
url += `&lang=${encodeURIComponent(lang)}`;
|
|
74
158
|
}
|
|
75
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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": "^
|
|
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.
|
|
37
|
+
"podverse-helpers": "^5.1.2-alpha.5",
|
|
38
38
|
"web-push": "^3.6.3"
|
|
39
39
|
}
|
|
40
40
|
}
|