podverse-external-services 5.1.1-alpha.4 → 5.1.1-alpha.5
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.
|
@@ -10,11 +10,15 @@ 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: () => Promise<any[]>;
|
|
14
|
+
podcastGetByGuid: (podcastGuid: string) => Promise<PodcastByGuidResponse | null>;
|
|
15
|
+
recentGetData: () => Promise<any[]>;
|
|
16
|
+
trendingGetPodcasts: (max?: number, since?: number, lang?: string, cat?: string) => Promise<{
|
|
17
|
+
feeds: any[];
|
|
18
|
+
nextSince?: number;
|
|
19
|
+
}>;
|
|
20
|
+
valueGetByPodcastIds: () => Promise<number[]>;
|
|
21
|
+
valueGetByPodcastIdsRecursively: (accumulatedPodcastIndexIds: number[], startAt?: number) => Promise<number[]>;
|
|
18
22
|
}
|
|
19
23
|
export {};
|
|
20
24
|
//# 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,kBA0BxD;IAID,8BAA8B,QAAa,OAAO,CAAC,GAAG,EAAE,CAAC,CAwCxD;IAID,gBAAgB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAYpF;IAID,aAAa,uBA6BZ;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"}
|
|
@@ -28,39 +28,17 @@ const config_1 = require("@external-services/config");
|
|
|
28
28
|
*/
|
|
29
29
|
class PodcastIndexService {
|
|
30
30
|
constructor({ authKey, baseUrl, secretKey }) {
|
|
31
|
+
// Request handler
|
|
31
32
|
this.podcastIndexAPIRequest = (url, config) => __awaiter(this, void 0, void 0, function* () {
|
|
32
33
|
var _a, _b, _c;
|
|
33
34
|
const apiHeaderTime = Math.floor(Date.now() / 1000);
|
|
34
35
|
const hash = (0, sha1_1.default)(this.authKey + this.secretKey + apiHeaderTime).toString(enc_hex_1.default);
|
|
35
|
-
console.log('[PodcastIndex] Request details', {
|
|
36
|
-
url,
|
|
37
|
-
apiHeaderTime,
|
|
38
|
-
authKey: this.authKey,
|
|
39
|
-
baseUrl: this.baseUrl,
|
|
40
|
-
secretKeyPresent: !!this.secretKey,
|
|
41
|
-
headers: {
|
|
42
|
-
'X-Auth-Key': this.authKey,
|
|
43
|
-
'X-Auth-Date': apiHeaderTime,
|
|
44
|
-
Authorization: hash
|
|
45
|
-
},
|
|
46
|
-
config
|
|
47
|
-
});
|
|
48
|
-
// Log system time for drift debugging
|
|
49
|
-
console.log('[PodcastIndex] System time (UTC)', {
|
|
50
|
-
iso: new Date().toISOString(),
|
|
51
|
-
epoch: Math.floor(Date.now() / 1000)
|
|
52
|
-
});
|
|
53
36
|
try {
|
|
54
37
|
const response = yield (0, podverse_helpers_1.request)(url, Object.assign({ headers: {
|
|
55
38
|
'X-Auth-Key': this.authKey,
|
|
56
39
|
'X-Auth-Date': apiHeaderTime,
|
|
57
40
|
Authorization: hash
|
|
58
41
|
} }, config));
|
|
59
|
-
console.log('[PodcastIndex] Response received', {
|
|
60
|
-
status: response === null || response === void 0 ? void 0 : response.status,
|
|
61
|
-
statusText: response === null || response === void 0 ? void 0 : response.statusText,
|
|
62
|
-
dataKeys: (response === null || response === void 0 ? void 0 : response.data) ? Object.keys(response.data) : undefined
|
|
63
|
-
});
|
|
64
42
|
return response;
|
|
65
43
|
}
|
|
66
44
|
catch (error) {
|
|
@@ -75,8 +53,55 @@ class PodcastIndexService {
|
|
|
75
53
|
throw error;
|
|
76
54
|
}
|
|
77
55
|
});
|
|
78
|
-
|
|
79
|
-
|
|
56
|
+
// Dead Feeds
|
|
57
|
+
this.deadFeedsDownloadAndExtractCSV = () => __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const url = 'https://public.podcastindex.org/podcastindex_dead_feeds.csv';
|
|
59
|
+
const tmpDir = path_1.default.join(__dirname, 'tmp');
|
|
60
|
+
const filePath = path_1.default.join(tmpDir, 'podcastindex_dead_feeds.csv');
|
|
61
|
+
if (!fs_1.default.existsSync(tmpDir)) {
|
|
62
|
+
fs_1.default.mkdirSync(tmpDir);
|
|
63
|
+
}
|
|
64
|
+
const data = yield this.podcastIndexAPIRequest(url, { responseType: 'stream' });
|
|
65
|
+
const writer = fs_1.default.createWriteStream(filePath);
|
|
66
|
+
data.pipe(writer);
|
|
67
|
+
yield new Promise((resolve, reject) => {
|
|
68
|
+
writer.on('finish', () => resolve());
|
|
69
|
+
writer.on('error', reject);
|
|
70
|
+
});
|
|
71
|
+
const results = [];
|
|
72
|
+
yield new Promise((resolve, reject) => {
|
|
73
|
+
fs_1.default.createReadStream(filePath)
|
|
74
|
+
.pipe((0, csv_parser_1.default)())
|
|
75
|
+
.on('data', (data) => results.push(data))
|
|
76
|
+
.on('end', resolve)
|
|
77
|
+
.on('error', reject);
|
|
78
|
+
});
|
|
79
|
+
fs_1.default.unlinkSync(filePath);
|
|
80
|
+
const parsedResults = results.map((row) => {
|
|
81
|
+
const [id, duplicateOf] = Object.values(row).map((value) => value.trim());
|
|
82
|
+
return {
|
|
83
|
+
podcast_index_id: parseInt(id, 10),
|
|
84
|
+
duplicateOf: duplicateOf ? parseInt(duplicateOf, 10) : null
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return parsedResults;
|
|
88
|
+
});
|
|
89
|
+
// Podcast
|
|
90
|
+
this.podcastGetByGuid = (podcastGuid) => __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
const url = `${this.baseUrl}/podcasts/byguid?guid=${podcastGuid}`;
|
|
92
|
+
let podcastIndexPodcast = null;
|
|
93
|
+
try {
|
|
94
|
+
const data = yield this.podcastIndexAPIRequest(url);
|
|
95
|
+
podcastIndexPodcast = data;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// assume a 404
|
|
99
|
+
}
|
|
100
|
+
return podcastIndexPodcast || null;
|
|
101
|
+
});
|
|
102
|
+
// Recent
|
|
103
|
+
this.recentGetData = () => __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
podverse_helpers_1.logger.info('recentGetData beginning...');
|
|
80
105
|
const currentTimeInSeconds = Math.floor(Date.now() / 1000);
|
|
81
106
|
const sinceRange = config_1.config.podcastIndex.recentlyUpdatedDataInterval;
|
|
82
107
|
const sinceTimeInSeconds = currentTimeInSeconds - sinceRange;
|
|
@@ -100,67 +125,45 @@ class PodcastIndexService {
|
|
|
100
125
|
});
|
|
101
126
|
return fetchData(sinceTimeInSeconds);
|
|
102
127
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
128
|
+
// Trending
|
|
129
|
+
this.trendingGetPodcasts = (...args_1) => __awaiter(this, [...args_1], void 0, function* (max = 25, since, lang, cat) {
|
|
130
|
+
const safeMax = Math.min(max, 1000);
|
|
131
|
+
let url = `${this.baseUrl}/podcasts/trending?max=${safeMax}`;
|
|
132
|
+
if (since) {
|
|
133
|
+
url += `&since=${since}`;
|
|
109
134
|
}
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
if (lang) {
|
|
136
|
+
url += `&lang=${encodeURIComponent(lang)}`;
|
|
112
137
|
}
|
|
113
|
-
|
|
138
|
+
if (cat) {
|
|
139
|
+
url += `&cat=${encodeURIComponent(cat)}`;
|
|
140
|
+
}
|
|
141
|
+
podverse_helpers_1.logger.info(`[PodcastIndex] Fetching trending feeds (max: ${safeMax}, since: ${since}, lang: ${lang}, cat: ${cat})`);
|
|
142
|
+
const response = yield this.podcastIndexAPIRequest(url);
|
|
143
|
+
console.log('Trending response:', response);
|
|
144
|
+
return {
|
|
145
|
+
feeds: response.feeds || [],
|
|
146
|
+
nextSince: response.nextSince
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
// Value
|
|
150
|
+
this.valueGetByPodcastIds = () => __awaiter(this, void 0, void 0, function* () {
|
|
151
|
+
const accumulatedPodcastIndexIds = [];
|
|
152
|
+
const nextStartAt = 1;
|
|
153
|
+
const podcastIndexIds = yield this.valueGetByPodcastIdsRecursively(accumulatedPodcastIndexIds, nextStartAt);
|
|
154
|
+
return podcastIndexIds;
|
|
114
155
|
});
|
|
115
|
-
this.
|
|
156
|
+
this.valueGetByPodcastIdsRecursively = (accumulatedPodcastIndexIds_1, ...args_1) => __awaiter(this, [accumulatedPodcastIndexIds_1, ...args_1], void 0, function* (accumulatedPodcastIndexIds, startAt = 1) {
|
|
116
157
|
const url = `${this.baseUrl}/podcasts/bytag?podcast-valueTimeSplit=true&max=5000&start_at=${startAt}`;
|
|
117
158
|
const data = yield this.podcastIndexAPIRequest(url);
|
|
118
159
|
for (const feed of data.feeds) {
|
|
119
160
|
accumulatedPodcastIndexIds.push(feed.id);
|
|
120
161
|
}
|
|
121
162
|
if (data.nextStartAt) {
|
|
122
|
-
return yield this.
|
|
163
|
+
return yield this.valueGetByPodcastIdsRecursively(accumulatedPodcastIndexIds, data.nextStartAt);
|
|
123
164
|
}
|
|
124
165
|
return accumulatedPodcastIndexIds;
|
|
125
166
|
});
|
|
126
|
-
this.getValueTagEnabledPodcastIds = () => __awaiter(this, void 0, void 0, function* () {
|
|
127
|
-
const accumulatedPodcastIndexIds = [];
|
|
128
|
-
const nextStartAt = 1;
|
|
129
|
-
const podcastIndexIds = yield this.getValueTagEnabledPodcastIdsRecursively(accumulatedPodcastIndexIds, nextStartAt);
|
|
130
|
-
return podcastIndexIds;
|
|
131
|
-
});
|
|
132
|
-
this.downloadAndExtractCSV = () => __awaiter(this, void 0, void 0, function* () {
|
|
133
|
-
const url = 'https://public.podcastindex.org/podcastindex_dead_feeds.csv';
|
|
134
|
-
const tmpDir = path_1.default.join(__dirname, 'tmp');
|
|
135
|
-
const filePath = path_1.default.join(tmpDir, 'podcastindex_dead_feeds.csv');
|
|
136
|
-
if (!fs_1.default.existsSync(tmpDir)) {
|
|
137
|
-
fs_1.default.mkdirSync(tmpDir);
|
|
138
|
-
}
|
|
139
|
-
const data = yield this.podcastIndexAPIRequest(url, { responseType: 'stream' });
|
|
140
|
-
const writer = fs_1.default.createWriteStream(filePath);
|
|
141
|
-
data.pipe(writer);
|
|
142
|
-
yield new Promise((resolve, reject) => {
|
|
143
|
-
writer.on('finish', () => resolve());
|
|
144
|
-
writer.on('error', reject);
|
|
145
|
-
});
|
|
146
|
-
const results = [];
|
|
147
|
-
yield new Promise((resolve, reject) => {
|
|
148
|
-
fs_1.default.createReadStream(filePath)
|
|
149
|
-
.pipe((0, csv_parser_1.default)())
|
|
150
|
-
.on('data', (data) => results.push(data))
|
|
151
|
-
.on('end', resolve)
|
|
152
|
-
.on('error', reject);
|
|
153
|
-
});
|
|
154
|
-
fs_1.default.unlinkSync(filePath);
|
|
155
|
-
const parsedResults = results.map((row) => {
|
|
156
|
-
const [id, duplicateOf] = Object.values(row).map((value) => value.trim());
|
|
157
|
-
return {
|
|
158
|
-
podcast_index_id: parseInt(id, 10),
|
|
159
|
-
duplicateOf: duplicateOf ? parseInt(duplicateOf, 10) : null
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
return parsedResults;
|
|
163
|
-
});
|
|
164
167
|
this.authKey = authKey;
|
|
165
168
|
this.baseUrl = baseUrl;
|
|
166
169
|
this.secretKey = secretKey;
|