podcast-dl 7.0.0 → 7.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/README.md +1 -2
- package/bin/async.js +17 -2
- package/bin/bin.js +7 -2
- package/bin/util.js +52 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
## A CLI for downloading podcasts with a focus on archiving.
|
|
4
4
|
|
|
5
|
-

|
|
6
|
-
|
|
7
5
|
## How to Use
|
|
8
6
|
|
|
9
7
|
### npx
|
|
@@ -39,6 +37,7 @@ Type values surrounded in square brackets (`[]`) can be used as used as boolean
|
|
|
39
37
|
| --list | [String] | false | Print episode list instead of downloading. Defaults to "table" when used as a boolean option. "json" is also supported. |
|
|
40
38
|
| --exec | String | false | Execute a command after each episode is downloaded. |
|
|
41
39
|
| --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
|
|
40
|
+
| --filter-url-tacking | | false | Attempts to extract the direct download link of an episode if detected (**experimental**). |
|
|
42
41
|
| --version | | false | Output the version number. |
|
|
43
42
|
| --help | | false | Output usage information. |
|
|
44
43
|
|
package/bin/async.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
runExec,
|
|
21
21
|
writeItemMeta,
|
|
22
22
|
writeToArchive,
|
|
23
|
+
getUrlEmbed,
|
|
23
24
|
} from "./util.js";
|
|
24
25
|
|
|
25
26
|
const pipeline = promisify(stream.pipeline);
|
|
@@ -34,6 +35,7 @@ const download = async ({
|
|
|
34
35
|
archive,
|
|
35
36
|
override,
|
|
36
37
|
onAfterDownload,
|
|
38
|
+
filterUrlTracking,
|
|
37
39
|
}) => {
|
|
38
40
|
const logMessage = getLogMessageWithMarker(marker);
|
|
39
41
|
if (!override && fs.existsSync(outputPath)) {
|
|
@@ -41,7 +43,18 @@ const download = async ({
|
|
|
41
43
|
return;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
let embeddedUrl = null;
|
|
47
|
+
if (filterUrlTracking) {
|
|
48
|
+
logMessage("Attempting to find embedded URL...");
|
|
49
|
+
embeddedUrl = await getUrlEmbed(url);
|
|
50
|
+
|
|
51
|
+
if (!embeddedUrl) {
|
|
52
|
+
logMessage("Unable to find embedded URL. Defaulting to full address");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const finalUrl = embeddedUrl || url;
|
|
57
|
+
const headResponse = await got(finalUrl, {
|
|
45
58
|
timeout: 5000,
|
|
46
59
|
method: "HEAD",
|
|
47
60
|
responseType: "json",
|
|
@@ -87,7 +100,7 @@ const download = async ({
|
|
|
87
100
|
});
|
|
88
101
|
|
|
89
102
|
await pipeline(
|
|
90
|
-
got.stream(
|
|
103
|
+
got.stream(finalUrl).on("downloadProgress", onDownloadProgress),
|
|
91
104
|
fs.createWriteStream(outputPath)
|
|
92
105
|
);
|
|
93
106
|
} catch (error) {
|
|
@@ -140,6 +153,7 @@ let downloadItemsAsync = async ({
|
|
|
140
153
|
episodeTemplate,
|
|
141
154
|
exec,
|
|
142
155
|
feed,
|
|
156
|
+
filterUrlTracking,
|
|
143
157
|
includeEpisodeMeta,
|
|
144
158
|
mono,
|
|
145
159
|
override,
|
|
@@ -177,6 +191,7 @@ let downloadItemsAsync = async ({
|
|
|
177
191
|
archive,
|
|
178
192
|
override,
|
|
179
193
|
marker,
|
|
194
|
+
filterUrlTracking,
|
|
180
195
|
key: getArchiveKey({
|
|
181
196
|
prefix: archiveUrl,
|
|
182
197
|
name: getArchiveFilename({
|
package/bin/bin.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import _path from "path";
|
|
5
|
-
import _url from "url";
|
|
6
5
|
import commander from "commander";
|
|
7
6
|
import { createRequire } from "module";
|
|
8
7
|
import pluralize from "pluralize";
|
|
@@ -119,6 +118,10 @@ commander
|
|
|
119
118
|
createParseNumber({ min: 1, max: 32, name: "threads" }),
|
|
120
119
|
1
|
|
121
120
|
)
|
|
121
|
+
.option(
|
|
122
|
+
"--filter-url-tracking",
|
|
123
|
+
"attempts to extract the direct download link of an episode if detected (experimental)"
|
|
124
|
+
)
|
|
122
125
|
.parse(process.argv);
|
|
123
126
|
|
|
124
127
|
const {
|
|
@@ -140,6 +143,7 @@ const {
|
|
|
140
143
|
exec,
|
|
141
144
|
mono,
|
|
142
145
|
threads,
|
|
146
|
+
filterUrlTracking,
|
|
143
147
|
addMp3Metadata: addMp3MetadataFlag,
|
|
144
148
|
adjustBitrate: bitrate,
|
|
145
149
|
} = commander;
|
|
@@ -151,7 +155,7 @@ const main = async () => {
|
|
|
151
155
|
logErrorAndExit("No URL provided");
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
const { hostname, pathname } =
|
|
158
|
+
const { hostname, pathname } = new URL(url);
|
|
155
159
|
const archiveUrl = `${hostname}${pathname}`;
|
|
156
160
|
const feed = await getFeed(url);
|
|
157
161
|
const basePath = _path.resolve(
|
|
@@ -286,6 +290,7 @@ const main = async () => {
|
|
|
286
290
|
override,
|
|
287
291
|
targetItems,
|
|
288
292
|
threads,
|
|
293
|
+
filterUrlTracking,
|
|
289
294
|
});
|
|
290
295
|
|
|
291
296
|
if (numEpisodesDownloaded === 0) {
|
package/bin/util.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import _url from "url";
|
|
2
1
|
import rssParser from "rss-parser";
|
|
3
2
|
import path from "path";
|
|
4
3
|
import fs from "fs";
|
|
5
4
|
import dayjs from "dayjs";
|
|
5
|
+
import got from "got";
|
|
6
6
|
import util from "util";
|
|
7
7
|
import { exec } from "child_process";
|
|
8
8
|
|
|
@@ -45,6 +45,54 @@ const getIsInArchive = ({ key, archive }) => {
|
|
|
45
45
|
return archiveResult.includes(key);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
const getPossibleUrlEmbeds = (url, maxAmount = 5) => {
|
|
49
|
+
const fullUrl = new URL(url);
|
|
50
|
+
const possibleStartIndexes = [];
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < fullUrl.pathname.length; i++) {
|
|
53
|
+
if (fullUrl.pathname[i] === "/") {
|
|
54
|
+
possibleStartIndexes.push(i);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const possibleEmbedChoices = possibleStartIndexes.map((startIndex) => {
|
|
59
|
+
let possibleEmbed = fullUrl.pathname.slice(startIndex + 1);
|
|
60
|
+
|
|
61
|
+
if (!possibleEmbed.startsWith("http")) {
|
|
62
|
+
possibleEmbed = `https://${possibleEmbed}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return decodeURIComponent(possibleEmbed);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return possibleEmbedChoices
|
|
69
|
+
.slice(Math.max(possibleEmbedChoices.length - maxAmount, 0))
|
|
70
|
+
.reverse();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getUrlEmbed = async (url) => {
|
|
74
|
+
const possibleUrlEmbeds = getPossibleUrlEmbeds(url);
|
|
75
|
+
for (const possibleUrl of possibleUrlEmbeds) {
|
|
76
|
+
try {
|
|
77
|
+
const embeddedUrl = new URL(possibleUrl);
|
|
78
|
+
await got(embeddedUrl.href, {
|
|
79
|
+
timeout: 3000,
|
|
80
|
+
method: "HEAD",
|
|
81
|
+
responseType: "json",
|
|
82
|
+
headers: {
|
|
83
|
+
accept: "*/*",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return embeddedUrl;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// do nothing
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
|
|
48
96
|
const getLoopControls = ({ limit, offset, length, reverse }) => {
|
|
49
97
|
if (reverse) {
|
|
50
98
|
const startIndex = length - 1 - offset;
|
|
@@ -341,7 +389,7 @@ const writeItemMeta = ({
|
|
|
341
389
|
};
|
|
342
390
|
|
|
343
391
|
const getUrlExt = (url) => {
|
|
344
|
-
const { pathname } =
|
|
392
|
+
const { pathname } = new URL(url);
|
|
345
393
|
|
|
346
394
|
if (!pathname) {
|
|
347
395
|
return "";
|
|
@@ -408,7 +456,7 @@ const getImageUrl = ({ image, itunes }) => {
|
|
|
408
456
|
};
|
|
409
457
|
|
|
410
458
|
const getFeed = async (url) => {
|
|
411
|
-
const { href } =
|
|
459
|
+
const { href } = new URL(url);
|
|
412
460
|
|
|
413
461
|
let feed;
|
|
414
462
|
try {
|
|
@@ -522,6 +570,7 @@ export {
|
|
|
522
570
|
getImageUrl,
|
|
523
571
|
getItemsToDownload,
|
|
524
572
|
getUrlExt,
|
|
573
|
+
getUrlEmbed,
|
|
525
574
|
logFeedInfo,
|
|
526
575
|
ITEM_LIST_FORMATS,
|
|
527
576
|
logItemsList,
|