podcast-dl 11.6.0 → 11.6.2

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/bin/naming.js CHANGED
@@ -1,136 +1,144 @@
1
- import dayjs from "dayjs";
2
- import filenamify from "filenamify";
3
- import path from "path";
4
-
5
- const INVALID_CHAR_REPLACE = "_";
6
-
7
- const FILTER_FUNCTIONS = {
8
- strip: (val) => val.replace(/\s+/g, ""),
9
- strip_special: (val) => val.replace(/[^a-zA-Z0-9\s]/g, ""),
10
- underscore: (val) => val.replace(/\s+/g, "_"),
11
- dash: (val) => val.replace(/\s+/g, "-"),
12
- camelcase: (val) =>
13
- val
14
- .split(/\s+/)
15
- .map((w) =>
16
- w ? w.charAt(0).toUpperCase() + w.slice(1).toLowerCase() : ""
17
- )
18
- .join(""),
19
- lowercase: (val) => val.toLowerCase(),
20
- uppercase: (val) => val.toUpperCase(),
21
- trim: (val) => val.trim(),
22
- };
23
-
24
- const applyFilters = (value, filterStr) => {
25
- if (!filterStr) {
26
- return value;
27
- }
28
-
29
- const filters = filterStr.slice(1).split("|");
30
- return filters.reduce((val, filter) => {
31
- const filterFn = FILTER_FUNCTIONS[filter];
32
- return filterFn ? filterFn(val) : val;
33
- }, value);
34
- };
35
-
36
- const MAX_LENGTH_FILENAME = process.env.MAX_LENGTH_FILENAME
37
- ? parseInt(process.env.MAX_LENGTH_FILENAME)
38
- : 255;
39
-
40
- export const getSafeName = (name, maxLength = MAX_LENGTH_FILENAME) => {
41
- return filenamify(name, {
42
- replacement: INVALID_CHAR_REPLACE,
43
- maxLength,
44
- });
45
- };
46
-
47
- export const getSimpleFilename = (name, ext = "") => {
48
- return `${getSafeName(name, MAX_LENGTH_FILENAME - (ext?.length ?? 0))}${ext}`;
49
- };
50
-
51
- export const getItemFilename = ({
52
- item,
53
- ext,
54
- url,
55
- feed,
56
- template,
57
- width,
58
- customTemplateOptions = [],
59
- offset = 0,
60
- }) => {
61
- const episodeNum = feed.items.length - item._originalIndex + offset;
62
- const title = item.title || "";
63
-
64
- const releaseYear = item.pubDate
65
- ? dayjs(new Date(item.pubDate)).format("YYYY")
66
- : null;
67
-
68
- const releaseMonth = item.pubDate
69
- ? dayjs(new Date(item.pubDate)).format("MM")
70
- : null;
71
-
72
- const releaseDay = item.pubDate
73
- ? dayjs(new Date(item.pubDate)).format("DD")
74
- : null;
75
-
76
- const releaseDate = item.pubDate
77
- ? dayjs(new Date(item.pubDate)).format("YYYYMMDD")
78
- : null;
79
-
80
- const customReplacementTuples = customTemplateOptions.map((option, i) => {
81
- const matchRegex = new RegExp(option);
82
- const match = title.match(matchRegex);
83
-
84
- return match && match[0] ? [`custom_${i}`, match[0]] : [`custom_${i}`, ""];
85
- });
86
-
87
- const templateReplacementsTuples = [
88
- ["title", title],
89
- ["release_date", releaseDate || ""],
90
- ["release_year", releaseYear || ""],
91
- ["release_month", releaseMonth || ""],
92
- ["release_day", releaseDay || ""],
93
- ["episode_num", `${episodeNum}`.padStart(width, "0")],
94
- ["url", url],
95
- ["podcast_title", feed.title || ""],
96
- ["podcast_link", feed.link || ""],
97
- ["duration", item.itunes?.duration || ""],
98
- ["guid", item.guid],
99
- ...customReplacementTuples,
100
- ];
101
-
102
- const replacementsMap = Object.fromEntries(templateReplacementsTuples);
103
- const templateSegments = template.trim().split(path.sep);
104
- const nameSegments = templateSegments.map((segment) => {
105
- const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
106
- const name = segment.replace(replaceRegex, (match, varName, filterStr) => {
107
- const replacement = replacementsMap[varName] || "";
108
- return applyFilters(replacement, filterStr);
109
- });
110
-
111
- return getSimpleFilename(name);
112
- });
113
-
114
- nameSegments[nameSegments.length - 1] = getSimpleFilename(
115
- nameSegments[nameSegments.length - 1],
116
- ext
117
- );
118
-
119
- return nameSegments.join(path.sep);
120
- };
121
-
122
- export const getFolderName = ({ feed, template }) => {
123
- const replacementsMap = {
124
- podcast_title: feed.title || "",
125
- podcast_link: feed.link || "",
126
- };
127
-
128
- const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
129
- const name = template.replace(replaceRegex, (_, varName, filterStr) => {
130
- const replacement = replacementsMap[varName] || "";
131
- const filtered = applyFilters(replacement, filterStr);
132
- return getSafeName(filtered);
133
- });
134
-
135
- return name;
136
- };
1
+ import dayjs from "dayjs";
2
+ import filenamify from "filenamify";
3
+ import path from "path";
4
+
5
+ const INVALID_CHAR_REPLACE = "_";
6
+
7
+ const FILTER_FUNCTIONS = {
8
+ strip: (val) => val.replace(/\s+/g, ""),
9
+ strip_special: (val) => val.replace(/[^a-zA-Z0-9\s]/g, ""),
10
+ underscore: (val) => val.replace(/\s+/g, "_"),
11
+ dash: (val) => val.replace(/\s+/g, "-"),
12
+ camelcase: (val) =>
13
+ val
14
+ .split(/\s+/)
15
+ .map((w) =>
16
+ w ? w.charAt(0).toUpperCase() + w.slice(1).toLowerCase() : ""
17
+ )
18
+ .join(""),
19
+ lowercase: (val) => val.toLowerCase(),
20
+ uppercase: (val) => val.toUpperCase(),
21
+ trim: (val) => val.trim(),
22
+ };
23
+
24
+ const applyFilters = (value, filterStr) => {
25
+ if (!filterStr) {
26
+ return value;
27
+ }
28
+
29
+ const filters = filterStr.slice(1).split("|");
30
+ return filters.reduce((val, filter) => {
31
+ const filterFn = FILTER_FUNCTIONS[filter];
32
+ return filterFn ? filterFn(val) : val;
33
+ }, value);
34
+ };
35
+
36
+ const MAX_LENGTH_FILENAME = process.env.MAX_LENGTH_FILENAME
37
+ ? parseInt(process.env.MAX_LENGTH_FILENAME)
38
+ : 255;
39
+
40
+ export const getSafeName = (name, maxLength = MAX_LENGTH_FILENAME) => {
41
+ // Replace periods with underscores BEFORE filenamify truncation.
42
+ // filenamify treats periods as extension delimiters and preserves content
43
+ // after the last period while truncating from the START, which destroys
44
+ // dates and other important prefix content in podcast titles.
45
+ const sanitized = name.replace(/\./g, INVALID_CHAR_REPLACE);
46
+ return filenamify(sanitized, {
47
+ replacement: INVALID_CHAR_REPLACE,
48
+ maxLength,
49
+ });
50
+ };
51
+
52
+ export const getSimpleFilename = (name, ext = "") => {
53
+ return `${getSafeName(name, MAX_LENGTH_FILENAME - (ext?.length ?? 0))}${ext}`;
54
+ };
55
+
56
+ export const getItemFilename = ({
57
+ item,
58
+ ext,
59
+ url,
60
+ feed,
61
+ template,
62
+ width,
63
+ customTemplateOptions = [],
64
+ offset = 0,
65
+ }) => {
66
+ const episodeNum = feed.items.length - item._originalIndex + offset;
67
+ const title = item.title || "";
68
+
69
+ const releaseYear = item.pubDate
70
+ ? dayjs(new Date(item.pubDate)).format("YYYY")
71
+ : null;
72
+
73
+ const releaseMonth = item.pubDate
74
+ ? dayjs(new Date(item.pubDate)).format("MM")
75
+ : null;
76
+
77
+ const releaseDay = item.pubDate
78
+ ? dayjs(new Date(item.pubDate)).format("DD")
79
+ : null;
80
+
81
+ const releaseDate = item.pubDate
82
+ ? dayjs(new Date(item.pubDate)).format("YYYYMMDD")
83
+ : null;
84
+
85
+ const customReplacementTuples = customTemplateOptions.map((option, i) => {
86
+ const matchRegex = new RegExp(option);
87
+ const match = title.match(matchRegex);
88
+
89
+ return match && match[0] ? [`custom_${i}`, match[0]] : [`custom_${i}`, ""];
90
+ });
91
+
92
+ const templateReplacementsTuples = [
93
+ ["title", title],
94
+ ["release_date", releaseDate || ""],
95
+ ["release_year", releaseYear || ""],
96
+ ["release_month", releaseMonth || ""],
97
+ ["release_day", releaseDay || ""],
98
+ ["episode_num", `${episodeNum}`.padStart(width, "0")],
99
+ ["url", url],
100
+ ["podcast_title", feed.title || ""],
101
+ ["podcast_link", feed.link || ""],
102
+ ["duration", item.itunes?.duration || ""],
103
+ ["guid", item.guid],
104
+ ...customReplacementTuples,
105
+ ];
106
+
107
+ const replacementsMap = Object.fromEntries(templateReplacementsTuples);
108
+ const templateSegments = template.trim().split(path.sep);
109
+ const nameSegments = templateSegments.map((segment, index) => {
110
+ const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
111
+ const name = segment.replace(replaceRegex, (match, varName, filterStr) => {
112
+ const replacement = replacementsMap[varName] || "";
113
+ return applyFilters(replacement, filterStr);
114
+ });
115
+
116
+ // Only truncate non-final segments here (they don't get an extension)
117
+ // Final segment is truncated below with the extension accounted for
118
+ const isLastSegment = index === templateSegments.length - 1;
119
+ return isLastSegment ? name : getSimpleFilename(name);
120
+ });
121
+
122
+ nameSegments[nameSegments.length - 1] = getSimpleFilename(
123
+ nameSegments[nameSegments.length - 1],
124
+ ext
125
+ );
126
+
127
+ return nameSegments.join(path.sep);
128
+ };
129
+
130
+ export const getFolderName = ({ feed, template }) => {
131
+ const replacementsMap = {
132
+ podcast_title: feed.title || "",
133
+ podcast_link: feed.link || "",
134
+ };
135
+
136
+ const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
137
+ const name = template.replace(replaceRegex, (_, varName, filterStr) => {
138
+ const replacement = replacementsMap[varName] || "";
139
+ const filtered = applyFilters(replacement, filterStr);
140
+ return getSafeName(filtered);
141
+ });
142
+
143
+ return name;
144
+ };
@@ -0,0 +1,3 @@
1
+ # rss-parser (local fork)
2
+
3
+ This is a local fork of [rss-parser](https://github.com/rbren/rss-parser) by Bobby Brennan.
@@ -0,0 +1,66 @@
1
+ export const feed = [
2
+ ["author", "creator"],
3
+ ["dc:publisher", "publisher"],
4
+ ["dc:creator", "creator"],
5
+ ["dc:source", "source"],
6
+ ["dc:title", "title"],
7
+ ["dc:type", "type"],
8
+ "title",
9
+ "description",
10
+ "author",
11
+ "pubDate",
12
+ "webMaster",
13
+ "managingEditor",
14
+ "generator",
15
+ "link",
16
+ "language",
17
+ "copyright",
18
+ "lastBuildDate",
19
+ "docs",
20
+ "generator",
21
+ "ttl",
22
+ "rating",
23
+ "skipHours",
24
+ "skipDays",
25
+ ];
26
+
27
+ export const item = [
28
+ ["author", "creator"],
29
+ ["dc:creator", "creator"],
30
+ ["dc:date", "date"],
31
+ ["dc:language", "language"],
32
+ ["dc:rights", "rights"],
33
+ ["dc:source", "source"],
34
+ ["dc:title", "title"],
35
+ "title",
36
+ "link",
37
+ "pubDate",
38
+ "author",
39
+ "summary",
40
+ ["content:encoded", "content:encoded", { includeSnippet: true }],
41
+ "enclosure",
42
+ "dc:creator",
43
+ "dc:date",
44
+ "comments",
45
+ ["podcast:transcript", "podcastTranscripts", { keepArray: true }],
46
+ ];
47
+
48
+ const mapItunesField = (f) => ["itunes:" + f, f];
49
+
50
+ export const podcastFeed = ["author", "subtitle", "summary", "explicit"].map(
51
+ mapItunesField
52
+ );
53
+
54
+ export const podcastItem = [
55
+ "author",
56
+ "subtitle",
57
+ "summary",
58
+ "explicit",
59
+ "duration",
60
+ "image",
61
+ "episode",
62
+ "image",
63
+ "season",
64
+ "keywords",
65
+ "episodeType",
66
+ ].map(mapItunesField);