@herowcode/utils 1.0.2 → 1.1.1
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 +236 -81
- package/dist/files/compress-image.d.ts +10 -0
- package/dist/files/compress-image.d.ts.map +1 -0
- package/dist/files/compress-image.esm.js +57 -0
- package/dist/files/compress-image.js +57 -0
- package/dist/files/compress-image.js.map +1 -0
- package/dist/files/download-url.d.ts +2 -0
- package/dist/files/download-url.d.ts.map +1 -0
- package/dist/files/download-url.esm.js +24 -0
- package/dist/files/download-url.js +24 -0
- package/dist/files/download-url.js.map +1 -0
- package/dist/files/format-bytes.d.ts +2 -0
- package/dist/files/format-bytes.d.ts.map +1 -0
- package/dist/files/format-bytes.esm.js +13 -0
- package/dist/files/format-bytes.js +13 -0
- package/dist/files/format-bytes.js.map +1 -0
- package/dist/files/index.d.ts +4 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.esm.js +3 -0
- package/dist/files/index.js +3 -0
- package/dist/files/index.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +6 -10
- package/dist/index.js +6 -10
- package/dist/index.js.map +1 -1
- package/dist/string/format-hms-to-seconds.d.ts +2 -0
- package/dist/string/format-hms-to-seconds.d.ts.map +1 -0
- package/dist/string/format-hms-to-seconds.esm.js +25 -0
- package/dist/string/format-hms-to-seconds.js +25 -0
- package/dist/string/format-hms-to-seconds.js.map +1 -0
- package/dist/string/format-seconds-to-fragment.d.ts +3 -0
- package/dist/string/format-seconds-to-fragment.d.ts.map +1 -0
- package/dist/string/format-seconds-to-fragment.esm.js +15 -0
- package/dist/string/format-seconds-to-fragment.js +15 -0
- package/dist/string/format-seconds-to-fragment.js.map +1 -0
- package/dist/string/format-seconds-to-hms.d.ts +2 -0
- package/dist/string/format-seconds-to-hms.d.ts.map +1 -0
- package/dist/string/format-seconds-to-hms.esm.js +13 -0
- package/dist/string/format-seconds-to-hms.js +13 -0
- package/dist/string/format-seconds-to-hms.js.map +1 -0
- package/dist/string/format-string-to-time.d.ts +2 -0
- package/dist/string/format-string-to-time.d.ts.map +1 -0
- package/dist/string/format-string-to-time.esm.js +10 -0
- package/dist/string/format-string-to-time.js +10 -0
- package/dist/string/format-string-to-time.js.map +1 -0
- package/dist/string/index.d.ts +4 -0
- package/dist/string/index.d.ts.map +1 -1
- package/dist/string/index.esm.js +4 -0
- package/dist/string/index.js +4 -0
- package/dist/string/index.js.map +1 -1
- package/dist/youtube/extract-youtube-video-id.d.ts +2 -0
- package/dist/youtube/extract-youtube-video-id.d.ts.map +1 -0
- package/dist/youtube/extract-youtube-video-id.esm.js +26 -0
- package/dist/youtube/extract-youtube-video-id.js +26 -0
- package/dist/youtube/extract-youtube-video-id.js.map +1 -0
- package/dist/youtube/generate-youtube-url.d.ts +20 -0
- package/dist/youtube/generate-youtube-url.d.ts.map +1 -0
- package/dist/youtube/generate-youtube-url.esm.js +81 -0
- package/dist/youtube/generate-youtube-url.js +81 -0
- package/dist/youtube/generate-youtube-url.js.map +1 -0
- package/dist/youtube/index.d.ts +5 -0
- package/dist/youtube/index.d.ts.map +1 -0
- package/dist/youtube/index.esm.js +4 -0
- package/dist/youtube/index.js +4 -0
- package/dist/youtube/index.js.map +1 -0
- package/dist/youtube/use-get-video-duration.d.ts +7 -0
- package/dist/youtube/use-get-video-duration.d.ts.map +1 -0
- package/dist/youtube/use-get-video-duration.esm.js +150 -0
- package/dist/youtube/use-get-video-duration.js +150 -0
- package/dist/youtube/use-get-video-duration.js.map +1 -0
- package/dist/youtube/validate-youtube-link.d.ts +2 -0
- package/dist/youtube/validate-youtube-link.d.ts.map +1 -0
- package/dist/youtube/validate-youtube-link.esm.js +40 -0
- package/dist/youtube/validate-youtube-link.js +40 -0
- package/dist/youtube/validate-youtube-link.js.map +1 -0
- package/package.json +33 -17
- package/dist/number.d.ts +0 -8
- package/dist/number.d.ts.map +0 -1
- package/dist/number.esm.js +0 -15
- package/dist/number.js +0 -15
- package/dist/number.js.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: Window is any */
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { formatSecondsToHMS } from "../string";
|
|
4
|
+
import { extractYouTubeId } from "./extract-youtube-video-id";
|
|
5
|
+
import { validateYoutubeLink } from "./validate-youtube-link";
|
|
6
|
+
let YtApiLoading = null;
|
|
7
|
+
function loadYouTubeIFrameAPI() {
|
|
8
|
+
var _a;
|
|
9
|
+
if ((_a = window.YT) === null || _a === void 0 ? void 0 : _a.Player)
|
|
10
|
+
return Promise.resolve();
|
|
11
|
+
if (YtApiLoading)
|
|
12
|
+
return YtApiLoading;
|
|
13
|
+
YtApiLoading = new Promise((resolve) => {
|
|
14
|
+
const existing = document.querySelector('script[src="https://www.youtube.com/iframe_api"]');
|
|
15
|
+
if (existing) {
|
|
16
|
+
// Poll until the API is ready
|
|
17
|
+
const poll = setInterval(() => {
|
|
18
|
+
var _a;
|
|
19
|
+
if ((_a = window.YT) === null || _a === void 0 ? void 0 : _a.Player) {
|
|
20
|
+
clearInterval(poll);
|
|
21
|
+
resolve();
|
|
22
|
+
}
|
|
23
|
+
}, 50);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const tag = document.createElement("script");
|
|
27
|
+
tag.src = "https://www.youtube.com/iframe_api";
|
|
28
|
+
tag.async = true;
|
|
29
|
+
document.body.appendChild(tag);
|
|
30
|
+
window.onYouTubeIframeAPIReady = () => {
|
|
31
|
+
resolve();
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
return YtApiLoading;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* useGetYoutubeVideoDuration
|
|
38
|
+
* Returns a function that accepts a YouTube URL (full or short) and returns a Promise
|
|
39
|
+
* resolving to the video duration formatted as "HH:MM:SS" or null on failure.
|
|
40
|
+
*/
|
|
41
|
+
export function useGetYoutubeVideoDuration() {
|
|
42
|
+
const getYoutubeVideoDuration = useCallback(async (videoUrl) => {
|
|
43
|
+
const videoId = extractYouTubeId(videoUrl);
|
|
44
|
+
if (!videoId)
|
|
45
|
+
return null;
|
|
46
|
+
const videoIsValid = await validateYoutubeLink(videoUrl);
|
|
47
|
+
if (!videoIsValid)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
await loadYouTubeIFrameAPI();
|
|
51
|
+
}
|
|
52
|
+
catch (_a) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return await new Promise((resolve) => {
|
|
56
|
+
const iframeId = `yt-duration-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
57
|
+
const iframe = document.createElement("iframe");
|
|
58
|
+
// create a minimal offscreen iframe for the player
|
|
59
|
+
iframe.id = iframeId;
|
|
60
|
+
iframe.style.position = "fixed";
|
|
61
|
+
iframe.style.left = "-9999px";
|
|
62
|
+
iframe.style.width = "1px";
|
|
63
|
+
iframe.style.height = "1px";
|
|
64
|
+
iframe.style.opacity = "0";
|
|
65
|
+
iframe.style.pointerEvents = "none";
|
|
66
|
+
// embed URL with enablejsapi so we can construct YT.Player
|
|
67
|
+
const origin = window.location.origin;
|
|
68
|
+
iframe.src = `https://www.youtube.com/embed/${encodeURIComponent(videoId)}?enablejsapi=1&origin=${encodeURIComponent(origin)}`;
|
|
69
|
+
let resolved = false;
|
|
70
|
+
let player = null;
|
|
71
|
+
let cleanupTimeout = null;
|
|
72
|
+
function cleanupAndResolve(result) {
|
|
73
|
+
if (resolved)
|
|
74
|
+
return;
|
|
75
|
+
resolved = true;
|
|
76
|
+
try {
|
|
77
|
+
if (player && typeof player.destroy === "function")
|
|
78
|
+
player.destroy();
|
|
79
|
+
}
|
|
80
|
+
catch (_a) {
|
|
81
|
+
/* ignore */
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
if (iframe.parentNode)
|
|
85
|
+
iframe.parentNode.removeChild(iframe);
|
|
86
|
+
}
|
|
87
|
+
catch (_b) {
|
|
88
|
+
/* ignore */
|
|
89
|
+
}
|
|
90
|
+
if (cleanupTimeout)
|
|
91
|
+
window.clearTimeout(cleanupTimeout);
|
|
92
|
+
resolve(result);
|
|
93
|
+
}
|
|
94
|
+
// timeout fallback
|
|
95
|
+
cleanupTimeout = window.setTimeout(() => {
|
|
96
|
+
cleanupAndResolve(null);
|
|
97
|
+
}, 10000); // 10s timeout
|
|
98
|
+
document.body.appendChild(iframe);
|
|
99
|
+
// Construct player
|
|
100
|
+
try {
|
|
101
|
+
player = new window.YT.Player(iframeId, {
|
|
102
|
+
events: {
|
|
103
|
+
onReady: (e) => {
|
|
104
|
+
try {
|
|
105
|
+
let duration = e.target.getDuration();
|
|
106
|
+
if (!duration || duration === 0) {
|
|
107
|
+
// Sometimes duration is 0 immediately; try a few short retries
|
|
108
|
+
let attempts = 0;
|
|
109
|
+
const tryInterval = setInterval(() => {
|
|
110
|
+
attempts += 1;
|
|
111
|
+
try {
|
|
112
|
+
duration = e.target.getDuration();
|
|
113
|
+
if (duration && duration > 0) {
|
|
114
|
+
clearInterval(tryInterval);
|
|
115
|
+
cleanupAndResolve(formatSecondsToHMS(duration));
|
|
116
|
+
}
|
|
117
|
+
else if (attempts >= 8) {
|
|
118
|
+
clearInterval(tryInterval);
|
|
119
|
+
cleanupAndResolve(null);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (_a) {
|
|
123
|
+
if (attempts >= 8) {
|
|
124
|
+
clearInterval(tryInterval);
|
|
125
|
+
cleanupAndResolve(null);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, 300);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
cleanupAndResolve(formatSecondsToHMS(duration));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (_a) {
|
|
135
|
+
cleanupAndResolve(null);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
onError: () => {
|
|
139
|
+
cleanupAndResolve(null);
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch (_a) {
|
|
145
|
+
cleanupAndResolve(null);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}, []);
|
|
149
|
+
return getYoutubeVideoDuration;
|
|
150
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: Window is any */
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { formatSecondsToHMS } from "../string";
|
|
4
|
+
import { extractYouTubeId } from "./extract-youtube-video-id";
|
|
5
|
+
import { validateYoutubeLink } from "./validate-youtube-link";
|
|
6
|
+
let YtApiLoading = null;
|
|
7
|
+
function loadYouTubeIFrameAPI() {
|
|
8
|
+
var _a;
|
|
9
|
+
if ((_a = window.YT) === null || _a === void 0 ? void 0 : _a.Player)
|
|
10
|
+
return Promise.resolve();
|
|
11
|
+
if (YtApiLoading)
|
|
12
|
+
return YtApiLoading;
|
|
13
|
+
YtApiLoading = new Promise((resolve) => {
|
|
14
|
+
const existing = document.querySelector('script[src="https://www.youtube.com/iframe_api"]');
|
|
15
|
+
if (existing) {
|
|
16
|
+
// Poll until the API is ready
|
|
17
|
+
const poll = setInterval(() => {
|
|
18
|
+
var _a;
|
|
19
|
+
if ((_a = window.YT) === null || _a === void 0 ? void 0 : _a.Player) {
|
|
20
|
+
clearInterval(poll);
|
|
21
|
+
resolve();
|
|
22
|
+
}
|
|
23
|
+
}, 50);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const tag = document.createElement("script");
|
|
27
|
+
tag.src = "https://www.youtube.com/iframe_api";
|
|
28
|
+
tag.async = true;
|
|
29
|
+
document.body.appendChild(tag);
|
|
30
|
+
window.onYouTubeIframeAPIReady = () => {
|
|
31
|
+
resolve();
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
return YtApiLoading;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* useGetYoutubeVideoDuration
|
|
38
|
+
* Returns a function that accepts a YouTube URL (full or short) and returns a Promise
|
|
39
|
+
* resolving to the video duration formatted as "HH:MM:SS" or null on failure.
|
|
40
|
+
*/
|
|
41
|
+
export function useGetYoutubeVideoDuration() {
|
|
42
|
+
const getYoutubeVideoDuration = useCallback(async (videoUrl) => {
|
|
43
|
+
const videoId = extractYouTubeId(videoUrl);
|
|
44
|
+
if (!videoId)
|
|
45
|
+
return null;
|
|
46
|
+
const videoIsValid = await validateYoutubeLink(videoUrl);
|
|
47
|
+
if (!videoIsValid)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
await loadYouTubeIFrameAPI();
|
|
51
|
+
}
|
|
52
|
+
catch (_a) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return await new Promise((resolve) => {
|
|
56
|
+
const iframeId = `yt-duration-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
57
|
+
const iframe = document.createElement("iframe");
|
|
58
|
+
// create a minimal offscreen iframe for the player
|
|
59
|
+
iframe.id = iframeId;
|
|
60
|
+
iframe.style.position = "fixed";
|
|
61
|
+
iframe.style.left = "-9999px";
|
|
62
|
+
iframe.style.width = "1px";
|
|
63
|
+
iframe.style.height = "1px";
|
|
64
|
+
iframe.style.opacity = "0";
|
|
65
|
+
iframe.style.pointerEvents = "none";
|
|
66
|
+
// embed URL with enablejsapi so we can construct YT.Player
|
|
67
|
+
const origin = window.location.origin;
|
|
68
|
+
iframe.src = `https://www.youtube.com/embed/${encodeURIComponent(videoId)}?enablejsapi=1&origin=${encodeURIComponent(origin)}`;
|
|
69
|
+
let resolved = false;
|
|
70
|
+
let player = null;
|
|
71
|
+
let cleanupTimeout = null;
|
|
72
|
+
function cleanupAndResolve(result) {
|
|
73
|
+
if (resolved)
|
|
74
|
+
return;
|
|
75
|
+
resolved = true;
|
|
76
|
+
try {
|
|
77
|
+
if (player && typeof player.destroy === "function")
|
|
78
|
+
player.destroy();
|
|
79
|
+
}
|
|
80
|
+
catch (_a) {
|
|
81
|
+
/* ignore */
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
if (iframe.parentNode)
|
|
85
|
+
iframe.parentNode.removeChild(iframe);
|
|
86
|
+
}
|
|
87
|
+
catch (_b) {
|
|
88
|
+
/* ignore */
|
|
89
|
+
}
|
|
90
|
+
if (cleanupTimeout)
|
|
91
|
+
window.clearTimeout(cleanupTimeout);
|
|
92
|
+
resolve(result);
|
|
93
|
+
}
|
|
94
|
+
// timeout fallback
|
|
95
|
+
cleanupTimeout = window.setTimeout(() => {
|
|
96
|
+
cleanupAndResolve(null);
|
|
97
|
+
}, 10000); // 10s timeout
|
|
98
|
+
document.body.appendChild(iframe);
|
|
99
|
+
// Construct player
|
|
100
|
+
try {
|
|
101
|
+
player = new window.YT.Player(iframeId, {
|
|
102
|
+
events: {
|
|
103
|
+
onReady: (e) => {
|
|
104
|
+
try {
|
|
105
|
+
let duration = e.target.getDuration();
|
|
106
|
+
if (!duration || duration === 0) {
|
|
107
|
+
// Sometimes duration is 0 immediately; try a few short retries
|
|
108
|
+
let attempts = 0;
|
|
109
|
+
const tryInterval = setInterval(() => {
|
|
110
|
+
attempts += 1;
|
|
111
|
+
try {
|
|
112
|
+
duration = e.target.getDuration();
|
|
113
|
+
if (duration && duration > 0) {
|
|
114
|
+
clearInterval(tryInterval);
|
|
115
|
+
cleanupAndResolve(formatSecondsToHMS(duration));
|
|
116
|
+
}
|
|
117
|
+
else if (attempts >= 8) {
|
|
118
|
+
clearInterval(tryInterval);
|
|
119
|
+
cleanupAndResolve(null);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (_a) {
|
|
123
|
+
if (attempts >= 8) {
|
|
124
|
+
clearInterval(tryInterval);
|
|
125
|
+
cleanupAndResolve(null);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, 300);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
cleanupAndResolve(formatSecondsToHMS(duration));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (_a) {
|
|
135
|
+
cleanupAndResolve(null);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
onError: () => {
|
|
139
|
+
cleanupAndResolve(null);
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch (_a) {
|
|
145
|
+
cleanupAndResolve(null);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}, []);
|
|
149
|
+
return getYoutubeVideoDuration;
|
|
150
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-get-video-duration.js","sourceRoot":"","sources":["../../src/youtube/use-get-video-duration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-youtube-link.d.ts","sourceRoot":"","sources":["../../src/youtube/validate-youtube-link.ts"],"names":[],"mappings":"AAAA,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0C5E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export async function validateYoutubeLink(videoUrl) {
|
|
2
|
+
const { extractYouTubeId } = await import("./extract-youtube-video-id");
|
|
3
|
+
const videoId = extractYouTubeId(videoUrl);
|
|
4
|
+
if (!videoId)
|
|
5
|
+
return false;
|
|
6
|
+
// Try loading YouTube thumbnail images — avoids CORS problems because
|
|
7
|
+
// creating an Image and listening for load/error is not blocked by CORS.
|
|
8
|
+
const thumbs = [
|
|
9
|
+
`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`,
|
|
10
|
+
`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
|
|
11
|
+
`https://img.youtube.com/vi/${videoId}/mqdefault.jpg`,
|
|
12
|
+
`https://img.youtube.com/vi/${videoId}/default.jpg`,
|
|
13
|
+
];
|
|
14
|
+
const loadImage = (src) => new Promise((resolve) => {
|
|
15
|
+
const img = new Image();
|
|
16
|
+
img.onload = () => resolve(true);
|
|
17
|
+
img.onerror = () => resolve(false);
|
|
18
|
+
img.src = src;
|
|
19
|
+
});
|
|
20
|
+
for (const url of thumbs) {
|
|
21
|
+
try {
|
|
22
|
+
const ok = await loadImage(url);
|
|
23
|
+
if (ok)
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// ignore and try next thumbnail
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Fallback: try oEmbed endpoint (may be subject to CORS in some environments)
|
|
31
|
+
try {
|
|
32
|
+
const watchUrl = `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`;
|
|
33
|
+
const oembedUrl = `https://www.youtube.com/oembed?url=${encodeURIComponent(watchUrl)}&format=json`;
|
|
34
|
+
const res = await fetch(oembedUrl, { method: "GET" });
|
|
35
|
+
return res.ok;
|
|
36
|
+
}
|
|
37
|
+
catch (_b) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export async function validateYoutubeLink(videoUrl) {
|
|
2
|
+
const { extractYouTubeId } = await import("./extract-youtube-video-id");
|
|
3
|
+
const videoId = extractYouTubeId(videoUrl);
|
|
4
|
+
if (!videoId)
|
|
5
|
+
return false;
|
|
6
|
+
// Try loading YouTube thumbnail images — avoids CORS problems because
|
|
7
|
+
// creating an Image and listening for load/error is not blocked by CORS.
|
|
8
|
+
const thumbs = [
|
|
9
|
+
`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`,
|
|
10
|
+
`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
|
|
11
|
+
`https://img.youtube.com/vi/${videoId}/mqdefault.jpg`,
|
|
12
|
+
`https://img.youtube.com/vi/${videoId}/default.jpg`,
|
|
13
|
+
];
|
|
14
|
+
const loadImage = (src) => new Promise((resolve) => {
|
|
15
|
+
const img = new Image();
|
|
16
|
+
img.onload = () => resolve(true);
|
|
17
|
+
img.onerror = () => resolve(false);
|
|
18
|
+
img.src = src;
|
|
19
|
+
});
|
|
20
|
+
for (const url of thumbs) {
|
|
21
|
+
try {
|
|
22
|
+
const ok = await loadImage(url);
|
|
23
|
+
if (ok)
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// ignore and try next thumbnail
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Fallback: try oEmbed endpoint (may be subject to CORS in some environments)
|
|
31
|
+
try {
|
|
32
|
+
const watchUrl = `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`;
|
|
33
|
+
const oembedUrl = `https://www.youtube.com/oembed?url=${encodeURIComponent(watchUrl)}&format=json`;
|
|
34
|
+
const res = await fetch(oembedUrl, { method: "GET" });
|
|
35
|
+
return res.ok;
|
|
36
|
+
}
|
|
37
|
+
catch (_b) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-youtube-link.js","sourceRoot":"","sources":["../../src/youtube/validate-youtube-link.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDA0CC;AA1CM,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACxD,MAAM,EAAE,gBAAgB,EAAE,GAAG,wDAAa,4BAA4B,GAAC,CAAA;IACvE,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC1C,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1B,sEAAsE;IACtE,yEAAyE;IACzE,MAAM,MAAM,GAAG;QACb,8BAA8B,OAAO,oBAAoB;QACzD,8BAA8B,OAAO,gBAAgB;QACrD,8BAA8B,OAAO,gBAAgB;QACrD,8BAA8B,OAAO,cAAc;KACpD,CAAA;IAED,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,EAAE,CAChC,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QAC/B,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAA;QACvB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAChC,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAClC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAA;IACf,CAAC,CAAC,CAAA;IAEJ,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAA;YAC/B,IAAI,EAAE;gBAAE,OAAO,IAAI,CAAA;QACrB,CAAC;QAAC,WAAM,CAAC;YACP,gCAAgC;QAClC,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,mCAAmC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAA;QACjF,MAAM,SAAS,GAAG,sCAAsC,kBAAkB,CACxE,QAAQ,CACT,cAAc,CAAA;QACf,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QACrD,OAAO,GAAG,CAAC,EAAE,CAAA;IACf,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herowcode/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A lightweight collection of utility functions for everyday JavaScript/TypeScript development",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -26,15 +26,20 @@
|
|
|
26
26
|
"import": "./dist/string/index.esm.js",
|
|
27
27
|
"require": "./dist/string/index.js"
|
|
28
28
|
},
|
|
29
|
-
"./
|
|
30
|
-
"types": "./dist/
|
|
31
|
-
"import": "./dist/
|
|
32
|
-
"require": "./dist/
|
|
29
|
+
"./files": {
|
|
30
|
+
"types": "./dist/files/index.d.ts",
|
|
31
|
+
"import": "./dist/files/index.esm.js",
|
|
32
|
+
"require": "./dist/files/index.js"
|
|
33
33
|
},
|
|
34
34
|
"./function": {
|
|
35
35
|
"types": "./dist/function.d.ts",
|
|
36
36
|
"import": "./dist/function.esm.js",
|
|
37
37
|
"require": "./dist/function.js"
|
|
38
|
+
},
|
|
39
|
+
"./youtube": {
|
|
40
|
+
"types": "./dist/youtube.d.ts",
|
|
41
|
+
"import": "./dist/youtube.esm.js",
|
|
42
|
+
"require": "./dist/youtube.js"
|
|
38
43
|
}
|
|
39
44
|
},
|
|
40
45
|
"files": [
|
|
@@ -47,14 +52,15 @@
|
|
|
47
52
|
"build:types": "tsc --project tsconfig.types.json",
|
|
48
53
|
"lint": "biome check --write --unsafe",
|
|
49
54
|
"clean": "rm -rf dist",
|
|
50
|
-
"test": "
|
|
51
|
-
"test:
|
|
52
|
-
"test:
|
|
55
|
+
"test": "vitest",
|
|
56
|
+
"test:ui": "vitest --ui",
|
|
57
|
+
"test:run": "vitest run",
|
|
58
|
+
"test:coverage": "vitest run --coverage",
|
|
53
59
|
"prepare": "yarn build",
|
|
54
|
-
"prepublishOnly": "yarn lint && yarn test && yarn build",
|
|
55
|
-
"version:patch": "yarn lint && yarn test && yarn build && npm version patch && git push && git push --tags",
|
|
56
|
-
"version:minor": "yarn lint && yarn test && yarn build && npm version minor && git push && git push --tags",
|
|
57
|
-
"version:major": "yarn lint && yarn test && yarn build && npm version major && git push && git push --tags"
|
|
60
|
+
"prepublishOnly": "yarn lint && yarn test:run && yarn build",
|
|
61
|
+
"version:patch": "yarn lint && yarn test:run && yarn build && npm version patch && git push && git push --tags",
|
|
62
|
+
"version:minor": "yarn lint && yarn test:run && yarn build && npm version minor && git push && git push --tags",
|
|
63
|
+
"version:major": "yarn lint && yarn test:run && yarn build && npm version major && git push && git push --tags"
|
|
58
64
|
},
|
|
59
65
|
"keywords": [
|
|
60
66
|
"utils",
|
|
@@ -72,14 +78,24 @@
|
|
|
72
78
|
},
|
|
73
79
|
"license": "MIT",
|
|
74
80
|
"dependencies": {
|
|
75
|
-
"dayjs": "^1.11.10"
|
|
81
|
+
"dayjs": "^1.11.10",
|
|
82
|
+
"react": "19.1.1"
|
|
76
83
|
},
|
|
77
84
|
"devDependencies": {
|
|
78
85
|
"@biomejs/biome": "2.2.3",
|
|
79
|
-
"@
|
|
80
|
-
"jest": "
|
|
81
|
-
"
|
|
82
|
-
"
|
|
86
|
+
"@testing-library/dom": "10.4.1",
|
|
87
|
+
"@testing-library/jest-dom": "6.8.0",
|
|
88
|
+
"@testing-library/react": "16.3.0",
|
|
89
|
+
"@testing-library/react-hooks": "8.0.1",
|
|
90
|
+
"@types/react": "19.1.12",
|
|
91
|
+
"@types/react-dom": "19.1.9",
|
|
92
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
93
|
+
"@vitest/ui": "3.2.4",
|
|
94
|
+
"jest-environment-jsdom": "30.1.2",
|
|
95
|
+
"jsdom": "26.1.0",
|
|
96
|
+
"react-dom": "19.1.1",
|
|
97
|
+
"typescript": "^5.2.2",
|
|
98
|
+
"vitest": "3.2.4"
|
|
83
99
|
},
|
|
84
100
|
"repository": {
|
|
85
101
|
"type": "git",
|
package/dist/number.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a random integer between min and max (inclusive)
|
|
3
|
-
* @param min - The minimum value (inclusive)
|
|
4
|
-
* @param max - The maximum value (inclusive)
|
|
5
|
-
* @returns A random integer between min and max
|
|
6
|
-
*/
|
|
7
|
-
export declare function randomInt(min: number, max: number): number;
|
|
8
|
-
//# sourceMappingURL=number.d.ts.map
|
package/dist/number.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"number.d.ts","sourceRoot":"","sources":["../src/number.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAU1D"}
|
package/dist/number.esm.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a random integer between min and max (inclusive)
|
|
3
|
-
* @param min - The minimum value (inclusive)
|
|
4
|
-
* @param max - The maximum value (inclusive)
|
|
5
|
-
* @returns A random integer between min and max
|
|
6
|
-
*/
|
|
7
|
-
export function randomInt(min, max) {
|
|
8
|
-
if (!Number.isInteger(min) || !Number.isInteger(max)) {
|
|
9
|
-
throw new Error("Both min and max must be integers");
|
|
10
|
-
}
|
|
11
|
-
if (min > max) {
|
|
12
|
-
throw new Error("Min value cannot be greater than max value");
|
|
13
|
-
}
|
|
14
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
15
|
-
}
|
package/dist/number.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a random integer between min and max (inclusive)
|
|
3
|
-
* @param min - The minimum value (inclusive)
|
|
4
|
-
* @param max - The maximum value (inclusive)
|
|
5
|
-
* @returns A random integer between min and max
|
|
6
|
-
*/
|
|
7
|
-
export function randomInt(min, max) {
|
|
8
|
-
if (!Number.isInteger(min) || !Number.isInteger(max)) {
|
|
9
|
-
throw new Error("Both min and max must be integers");
|
|
10
|
-
}
|
|
11
|
-
if (min > max) {
|
|
12
|
-
throw new Error("Min value cannot be greater than max value");
|
|
13
|
-
}
|
|
14
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
15
|
-
}
|
package/dist/number.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"number.js","sourceRoot":"","sources":["../src/number.ts"],"names":[],"mappings":";;AAMA,8BAUC;AAhBD;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,GAAW,EAAE,GAAW;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IACtD,CAAC;IAED,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAA;AAC1D,CAAC"}
|