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 CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  ## A CLI for downloading podcasts with a focus on archiving.
4
4
 
5
- ![podcast-dl example gif](./docs/podcast-dl-example.gif)
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
- const headResponse = await got(url, {
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(url).on("downloadProgress", onDownloadProgress),
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 } = _url.parse(url);
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 } = _url.parse(url);
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 } = _url.parse(url);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podcast-dl",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "A CLI for downloading podcasts.",
5
5
  "type": "module",
6
6
  "bin": "./bin/bin.js",