yt-grabber 1.3.0 → 1.5.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 +4 -3
- package/package.json +4 -2
- package/public/screenshots/main-downloading.png +0 -0
- package/public/screenshots/main-info.png +0 -0
- package/public/screenshots/main.png +0 -0
- package/public/screenshots/settings.png +0 -0
- package/src/automations/Selectors.ts +2 -0
- package/src/automations/Youtube.ts +78 -82
- package/src/automations/YoutubeAlbums.ts +76 -76
- package/src/automations/YoutubeArtists.ts +98 -89
- package/src/automations/YoutubeTracks.ts +131 -0
- package/src/common/FileSystem.ts +15 -0
- package/src/common/Helpers.ts +6 -0
- package/src/common/Media.ts +22 -1
- package/src/common/Messaging.ts +4 -29
- package/src/common/Store.ts +10 -5
- package/src/common/YtdplUtils.ts +162 -28
- package/src/components/fileField/FileField.tsx +5 -5
- package/src/components/modals/detailsModal/DetailsModal.tsx +6 -2
- package/src/components/modals/imageModal/ImageModal.styl +24 -18
- package/src/components/youtube/inputModePicker/InputModePicker.tsx +23 -6
- package/src/components/youtube/inputPanel/InputPanel.tsx +22 -17
- package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.styl +135 -90
- package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.tsx +26 -34
- package/src/components/youtube/playlistTabs/PlaylistTabs.styl +15 -0
- package/src/components/youtube/playlistTabs/PlaylistTabs.tsx +63 -28
- package/src/components/youtube/trackList/TrackList.styl +13 -0
- package/src/components/youtube/trackList/TrackList.tsx +309 -155
- package/src/i18next.ts +1 -1
- package/src/index.ts +4 -148
- package/src/messaging/MessageBus.ts +14 -0
- package/src/messaging/MessageChannel.ts +67 -0
- package/src/messaging/Messages.ts +31 -0
- package/src/messaging/MessagingService.ts +25 -0
- package/src/messaging/MultiMessageChannel.ts +42 -0
- package/src/messaging/channels/SystemMessageChannel.ts +58 -0
- package/src/messaging/channels/YoutubeAlbumsMessageChannel.ts +18 -0
- package/src/messaging/channels/YoutubeArtistsMessageChannel.ts +18 -0
- package/src/messaging/channels/YoutubeTracksMessageChannel.ts +18 -0
- package/src/messaging/channels/YoutubeUrlsMessageChannel.ts +18 -0
- package/src/react/contexts/DataContext.tsx +3 -3
- package/src/resources/bin/ffmpeg.exe +0 -0
- package/src/resources/locales/de-DE/help.json +7 -3
- package/src/resources/locales/de-DE/translation.json +7 -3
- package/src/resources/locales/en-GB/help.json +7 -3
- package/src/resources/locales/en-GB/translation.json +7 -3
- package/src/resources/locales/pl-PL/help.json +7 -3
- package/src/resources/locales/pl-PL/translation.json +7 -3
- package/src/styles/fonts.styl +0 -23
- package/src/theme/ColorThemes.ts +0 -16
- package/src/views/home/HomeView.tsx +303 -217
- package/src/views/settings/SettingsView.styl +6 -0
- package/src/views/settings/SettingsView.tsx +49 -9
- package/src/automations/YoutubeSongs.ts +0 -130
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ It provides support for downloading:
|
|
|
10
10
|
|
|
11
11
|
- videos
|
|
12
12
|
- audio tracks
|
|
13
|
-
-
|
|
13
|
+
- playlists
|
|
14
|
+
- music albums
|
|
14
15
|
- full discographies
|
|
15
16
|
|
|
16
17
|
Various formats and quality options are available for both - audio and video.
|
|
@@ -32,13 +33,13 @@ Each download can be customized to your needs for easy workflow automation.
|
|
|
32
33
|
|
|
33
34
|
## Features
|
|
34
35
|
|
|
35
|
-
* Download video, audio, playlists and complete artist discographies
|
|
36
|
+
* Download video, audio, playlists, songs, albums and complete artist discographies
|
|
36
37
|
|
|
37
38
|
* Multiple output formats (mp3, m4u, flac, wav, mp4, mkv)
|
|
38
39
|
|
|
39
40
|
* Customizable audio and video quality
|
|
40
41
|
|
|
41
|
-
* Trimming video and audio
|
|
42
|
+
* Trimming video and audio (multiple parts)
|
|
42
43
|
|
|
43
44
|
* Batch multimedia download
|
|
44
45
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yt-grabber",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Youtube Grabber",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"clean": "rimraf dist",
|
|
11
11
|
"start": "concurrently \"cross-env NODE_ENV=development yarn run watch\" \"yarn run electron\"",
|
|
12
|
+
"start:debug": "concurrently \"cross-env NODE_ENV=development yarn run watch\" \"yarn run electron-debug\"",
|
|
12
13
|
"build": "yarn clean && \"cross-env NODE_ENV=development webpack\" --config webpack.config.ts",
|
|
13
14
|
"build:prod": "yarn clean && \"cross-env NODE_ENV=production webpack\" --config webpack.config.ts",
|
|
14
15
|
"electron": "wait-on dist/index.js && ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
|
|
16
|
+
"electron-debug": "wait-on dist/index.js && ELECTRON_DISABLE_SECURITY_WARNINGS=true electron --inspect-brk=9229 .",
|
|
15
17
|
"watch": "webpack --watch --config webpack.config.ts",
|
|
16
18
|
"pack": "yarn build:prod && electron-builder --dir",
|
|
17
19
|
"dist": "yarn build:prod && electron-builder"
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
"win": {
|
|
29
31
|
"target": "nsis",
|
|
30
32
|
"icon": "dist/resources/icons/logo.ico",
|
|
31
|
-
"artifactName": "${
|
|
33
|
+
"artifactName": "${name}-${version}-setup.${ext}"
|
|
32
34
|
},
|
|
33
35
|
"nsis": {
|
|
34
36
|
"oneClick": false,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -10,6 +10,8 @@ export const YtMusicSearchInputSelector = "//ytmusic-app-layout//ytmusic-search-
|
|
|
10
10
|
|
|
11
11
|
export const YtMusicArtistsChipSelector = "//ytmusic-app-layout//ytmusic-search-page//div[contains(@class, 'content')]//ytmusic-section-list-renderer//ytmusic-chip-cloud-renderer//iron-selector[@id='chips']//a[contains(@class, 'ytmusic-chip-cloud-chip-renderer')]//yt-formatted-string[contains(text(), 'Wykonawcy') or contains(text(), 'Artists')]//ancestor::a";
|
|
12
12
|
|
|
13
|
+
export const YtMusicArtistBestResultLinkSelector = "//ytmusic-app-layout//div[@id='content']/ytmusic-search-page//ytmusic-section-list-renderer//div[@id='contents']//ytmusic-card-shelf-renderer//div[contains(@class, 'card-container')]//div[contains(@class, 'card-content-container')]//a[contains(@class, 'yt-simple-endpoint') and contains(@class, 'thumbnail-link')]";
|
|
14
|
+
|
|
13
15
|
export const YtMusicAlbumsChipSelector = "//ytmusic-app-layout//ytmusic-search-page//div[contains(@class, 'content')]//ytmusic-section-list-renderer//ytmusic-chip-cloud-renderer//iron-selector[@id='chips']//a[contains(@class, 'ytmusic-chip-cloud-chip-renderer')]//yt-formatted-string[contains(text(), 'Albumy') or contains(text(), 'Albums')]//ancestor::a";
|
|
14
16
|
|
|
15
17
|
export const YtMusicSongsChipSelector = "//ytmusic-app-layout//ytmusic-search-page//div[contains(@class, 'content')]//ytmusic-section-list-renderer//ytmusic-chip-cloud-renderer//iron-selector[@id='chips']//a[contains(@class, 'ytmusic-chip-cloud-chip-renderer')]//yt-formatted-string[contains(text(), 'Utwory') or contains(text(), 'Songs')]//ancestor::a";
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
|
-
import {i18n as i18next} from "i18next";
|
|
3
2
|
import _forEach from "lodash/forEach";
|
|
4
3
|
import _includes from "lodash/includes";
|
|
5
4
|
import _isEmpty from "lodash/isEmpty";
|
|
6
5
|
import _map from "lodash/map";
|
|
7
6
|
import _merge from "lodash/merge";
|
|
8
7
|
import _replace from "lodash/replace";
|
|
9
|
-
import {Browser,
|
|
8
|
+
import {Browser, Page, TimeoutError} from "puppeteer";
|
|
10
9
|
import puppeteer from "puppeteer-extra";
|
|
11
10
|
import StealthPlugin from "puppeteer-extra-plugin-stealth";
|
|
12
11
|
|
|
13
12
|
import {getProfilePath} from "../common/FileSystem";
|
|
14
13
|
import {waitFor} from "../common/Helpers";
|
|
15
|
-
import {
|
|
14
|
+
import {GetYoutubeResult} from "../common/Messaging";
|
|
16
15
|
import puppeteerOptions from "../common/PuppeteerOptions";
|
|
17
|
-
import {IReporter,
|
|
16
|
+
import {IReporter, Reporter} from "../common/Reporter";
|
|
17
|
+
import {MessageHandlerParams} from "../messaging/MessageChannel";
|
|
18
18
|
import {navigateToPage} from "./Helpers";
|
|
19
19
|
import {
|
|
20
20
|
AlbumFilterSelector, AlbumLinkSelector, AlbumsDirectLinkSelector, AlbumsHrefSelector
|
|
@@ -22,92 +22,93 @@ import {
|
|
|
22
22
|
|
|
23
23
|
let page: Page;
|
|
24
24
|
let browser: Browser;
|
|
25
|
-
let reporter: IReporter<
|
|
25
|
+
let reporter: IReporter<GetYoutubeResult>;
|
|
26
26
|
|
|
27
27
|
puppeteer.use(StealthPlugin());
|
|
28
28
|
|
|
29
|
+
export const execute = async (parameters: MessageHandlerParams) => {
|
|
30
|
+
const {params, options, i18n, onUpdate, signal} = parameters;
|
|
31
|
+
const abortPromise = new Promise((_, reject) => {
|
|
32
|
+
signal.addEventListener("abort", () => reject(new Error("aborted")));
|
|
33
|
+
});
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
export const execute = async (
|
|
32
|
-
params: GetYoutubeUrlParams,
|
|
33
|
-
options: LaunchOptions,
|
|
34
|
-
i18n: i18next,
|
|
35
|
-
onProgress: (data: ProgressInfo<GetYoutubeUrlResult>) => void,
|
|
36
|
-
) => {
|
|
37
35
|
try {
|
|
38
|
-
const result:
|
|
36
|
+
const result: GetYoutubeResult = {warnings: [], errors: [], values: []};
|
|
39
37
|
const userAgent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.3";
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
reporter = new Reporter(onProgress);
|
|
44
|
-
reporter.start(i18n.t("starting"));
|
|
45
|
-
browser = await puppeteer.launch(_merge(puppeteerOptions, options));
|
|
46
|
-
[page] = await browser.pages();
|
|
47
|
-
|
|
48
|
-
await page.setUserAgent(userAgent);
|
|
49
|
-
|
|
50
|
-
const cachedCookies = fs.readJSONSync(getProfilePath() + "/cookies.json", {throws: false});
|
|
51
|
-
|
|
52
|
-
if (_isEmpty(cachedCookies)) {
|
|
53
|
-
await waitFor(3000);
|
|
54
|
-
const pageCookies = await page.cookies();
|
|
55
|
-
|
|
56
|
-
fs.writeJSONSync(getProfilePath() + "/cookies.json", pageCookies, {spaces: 2});
|
|
57
|
-
|
|
58
|
-
await page.setCookie(...pageCookies);
|
|
59
|
-
} else {
|
|
60
|
-
await page.setCookie(...cachedCookies);
|
|
61
|
-
}
|
|
38
|
+
await Promise.race([
|
|
39
|
+
(async () => {
|
|
40
|
+
await i18n.changeLanguage(params.lang);
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const albumFilterButton = await page.waitForSelector(`::-p-xpath(${AlbumFilterSelector})`, {timeout: 1000});
|
|
76
|
-
|
|
77
|
-
albumFilterButton.click();
|
|
78
|
-
await page.waitForNetworkIdle();
|
|
79
|
-
|
|
80
|
-
// await page.screenshot({path: './profile/debug.png', fullPage: true});
|
|
81
|
-
// fs.writeFileSync("./profile/debug.html", await page.content());
|
|
82
|
-
|
|
83
|
-
const items = await page.$$eval(`xpath/${AlbumLinkSelector}`, (elements) => elements.map((el) => el.getAttribute("href")));
|
|
84
|
-
|
|
85
|
-
for (const item of items) {
|
|
86
|
-
results.push(`${params.url}/${item}`);
|
|
87
|
-
}
|
|
42
|
+
reporter = new Reporter(onUpdate);
|
|
43
|
+
reporter.start(i18n.t("starting"));
|
|
44
|
+
browser = await puppeteer.launch(_merge(puppeteerOptions, options));
|
|
45
|
+
[page] = await browser.pages();
|
|
46
|
+
|
|
47
|
+
await page.setUserAgent(userAgent);
|
|
48
|
+
|
|
49
|
+
const cachedCookies = fs.readJSONSync(getProfilePath() + "/cookies.json", {throws: false});
|
|
50
|
+
|
|
51
|
+
if (_isEmpty(cachedCookies)) {
|
|
52
|
+
await waitFor(3000);
|
|
53
|
+
const pageCookies = await page.cookies();
|
|
88
54
|
|
|
89
|
-
|
|
90
|
-
} catch (error) {
|
|
91
|
-
const items = await page.$$eval(`xpath/${AlbumsDirectLinkSelector}`, (elements) => elements.map((el) => el.getAttribute("href")));
|
|
55
|
+
fs.writeJSONSync(getProfilePath() + "/cookies.json", pageCookies, {spaces: 2});
|
|
92
56
|
|
|
93
|
-
|
|
94
|
-
|
|
57
|
+
await page.setCookie(...pageCookies);
|
|
58
|
+
} else {
|
|
59
|
+
await page.setCookie(...cachedCookies);
|
|
95
60
|
}
|
|
96
61
|
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
};
|
|
62
|
+
await navigateToPage(params.url, page);
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
result.urls.push(...data);
|
|
105
|
-
}
|
|
64
|
+
const process = async (urlToProcess: string) => {
|
|
65
|
+
const results: string[] = [];
|
|
106
66
|
|
|
107
|
-
|
|
67
|
+
try {
|
|
68
|
+
await navigateToPage(urlToProcess, page);
|
|
69
|
+
|
|
70
|
+
const element = await page.waitForSelector(`::-p-xpath(${AlbumsHrefSelector})`, {timeout: 1000});
|
|
71
|
+
const albumsUrl = await element.evaluate((el) => el.getAttribute("href"));
|
|
72
|
+
|
|
73
|
+
await navigateToPage(`${params.url}/${albumsUrl}`, page);
|
|
74
|
+
const albumFilterButton = await page.waitForSelector(`::-p-xpath(${AlbumFilterSelector})`, {timeout: 1000});
|
|
75
|
+
|
|
76
|
+
albumFilterButton.click();
|
|
77
|
+
await page.waitForNetworkIdle();
|
|
78
|
+
|
|
79
|
+
const items = await page.$$eval(`xpath/${AlbumLinkSelector}`, (elements) => elements.map((el) => el.getAttribute("href")));
|
|
80
|
+
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
results.push(`${params.url}/${item}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return results;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const items = await page.$$eval(`xpath/${AlbumsDirectLinkSelector}`, (elements) => elements.map((el) => el.getAttribute("href")));
|
|
88
|
+
|
|
89
|
+
for (const item of items) {
|
|
90
|
+
results.push(`${params.url}/${item}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
for (const u of params.values) {
|
|
98
|
+
const data = await process(u);
|
|
99
|
+
|
|
100
|
+
result.values.push(...data);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
reporter.finish("done", result);
|
|
104
|
+
})(),
|
|
105
|
+
abortPromise,
|
|
106
|
+
]);
|
|
108
107
|
} catch (error: any) {
|
|
109
|
-
const result:
|
|
110
|
-
|
|
108
|
+
const result: GetYoutubeResult = {errors: []};
|
|
109
|
+
if (error.message === "aborted") {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
111
112
|
if (error instanceof TimeoutError) {
|
|
112
113
|
result.errors.push({title: i18n.t("exceptionTimeout"), description: i18n.t("exceptionTimeoutText")});
|
|
113
114
|
} else {
|
|
@@ -117,7 +118,7 @@ export const execute = async (
|
|
|
117
118
|
result.errors.push({title: i18n.t("exceptionGetYoutubeUrls"), description: i18n.t("exceptionGetYoutubeUrlsText", {error: error.name})});
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
reporter.finish("done", result);
|
|
122
123
|
console.error("Execution failed at stage: ", error.stack);
|
|
123
124
|
} finally {
|
|
@@ -130,9 +131,4 @@ const closeResources = async () => {
|
|
|
130
131
|
if (browser) await browser.close();
|
|
131
132
|
};
|
|
132
133
|
|
|
133
|
-
export const cancel = async () => {
|
|
134
|
-
await browser.close();
|
|
135
|
-
await browser.disconnect();
|
|
136
|
-
};
|
|
137
|
-
|
|
138
134
|
export default execute;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
|
-
import {i18n as i18next} from "i18next";
|
|
3
2
|
import _forEach from "lodash/forEach";
|
|
4
3
|
import _includes from "lodash/includes";
|
|
5
4
|
import _isEmpty from "lodash/isEmpty";
|
|
6
5
|
import _map from "lodash/map";
|
|
7
6
|
import _merge from "lodash/merge";
|
|
8
7
|
import _replace from "lodash/replace";
|
|
9
|
-
import {Browser,
|
|
8
|
+
import {Browser, Page, TimeoutError} from "puppeteer";
|
|
10
9
|
import puppeteer from "puppeteer-extra";
|
|
11
10
|
import StealthPlugin from "puppeteer-extra-plugin-stealth";
|
|
12
11
|
|
|
13
12
|
import {getProfilePath} from "../common/FileSystem";
|
|
14
13
|
import {waitFor} from "../common/Helpers";
|
|
15
|
-
import {
|
|
14
|
+
import {GetYoutubeResult} from "../common/Messaging";
|
|
16
15
|
import puppeteerOptions from "../common/PuppeteerOptions";
|
|
17
|
-
import {IReporter,
|
|
16
|
+
import {IReporter, Reporter} from "../common/Reporter";
|
|
17
|
+
import {MessageHandlerParams} from "../messaging/MessageChannel";
|
|
18
18
|
import {clearInput, navigateToPage} from "./Helpers";
|
|
19
19
|
import {
|
|
20
20
|
YtMusicAlbumsChipSelector, YtMusicSearchInputSelector, YtMusicSearchResultsSelector
|
|
@@ -22,82 +22,87 @@ import {
|
|
|
22
22
|
|
|
23
23
|
let page: Page;
|
|
24
24
|
let browser: Browser;
|
|
25
|
-
let reporter: IReporter<
|
|
25
|
+
let reporter: IReporter<GetYoutubeResult>;
|
|
26
26
|
|
|
27
27
|
puppeteer.use(StealthPlugin());
|
|
28
28
|
|
|
29
|
-
export const execute = async (
|
|
30
|
-
params
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
) => {
|
|
35
|
-
try {
|
|
36
|
-
const result: GetYoutubeUrlResult = {warnings: [], errors: [], urls: []};
|
|
37
|
-
const userAgent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.3";
|
|
38
|
-
|
|
39
|
-
await i18n.changeLanguage(params.lang);
|
|
40
|
-
|
|
41
|
-
reporter = new Reporter(onProgress);
|
|
42
|
-
reporter.start(i18n.t("starting"));
|
|
43
|
-
browser = await puppeteer.launch(_merge(puppeteerOptions, options));
|
|
44
|
-
[page] = await browser.pages();
|
|
45
|
-
|
|
46
|
-
await page.setUserAgent(userAgent);
|
|
47
|
-
|
|
48
|
-
const cachedCookies = fs.readJSONSync(getProfilePath() + "/cookies.json", {throws: false});
|
|
49
|
-
|
|
50
|
-
if (_isEmpty(cachedCookies)) {
|
|
51
|
-
await waitFor(3000);
|
|
52
|
-
const pageCookies = await page.cookies();
|
|
53
|
-
|
|
54
|
-
fs.writeJSONSync(getProfilePath() + "/cookies.json", pageCookies, {spaces: 2});
|
|
55
|
-
|
|
56
|
-
await page.setCookie(...pageCookies);
|
|
57
|
-
} else {
|
|
58
|
-
await page.setCookie(...cachedCookies);
|
|
59
|
-
}
|
|
29
|
+
export const execute = async (parameters: MessageHandlerParams) => {
|
|
30
|
+
const {params, options, i18n, onUpdate, signal} = parameters;
|
|
31
|
+
const abortPromise = new Promise((_, reject) => {
|
|
32
|
+
signal.addEventListener("abort", () => reject(new Error("aborted")));
|
|
33
|
+
});
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const searchInput = await page.waitForSelector(`::-p-xpath(${YtMusicSearchInputSelector})`, {timeout: 1000});
|
|
68
|
-
await clearInput(searchInput, page);
|
|
69
|
-
await searchInput.type(album);
|
|
70
|
-
page.keyboard.press("Enter");
|
|
71
|
-
await page.waitForNetworkIdle();
|
|
72
|
-
|
|
73
|
-
const albumsChip = await page.waitForSelector(`::-p-xpath(${YtMusicAlbumsChipSelector})`, {timeout: 1000});
|
|
74
|
-
|
|
75
|
-
albumsChip.click();
|
|
76
|
-
await page.waitForNetworkIdle();
|
|
77
|
-
await page.waitForSelector(`::-p-xpath(${YtMusicSearchResultsSelector})`, {timeout: 1000});
|
|
35
|
+
try {
|
|
36
|
+
await Promise.race([
|
|
37
|
+
(async () => {
|
|
38
|
+
const result: GetYoutubeResult = {warnings: [], errors: [], values: []};
|
|
39
|
+
const userAgent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.3";
|
|
78
40
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
41
|
+
reporter = new Reporter(onUpdate);
|
|
42
|
+
reporter.start(i18n.t("starting"));
|
|
43
|
+
browser = await puppeteer.launch(_merge(puppeteerOptions, options));
|
|
44
|
+
[page] = await browser.pages();
|
|
82
45
|
|
|
83
|
-
|
|
46
|
+
await page.setUserAgent(userAgent);
|
|
84
47
|
|
|
85
|
-
|
|
86
|
-
} catch (error) {
|
|
87
|
-
return results;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
48
|
+
const cachedCookies = fs.readJSONSync(getProfilePath() + "/cookies.json", {throws: false});
|
|
90
49
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
result.urls.push(...data);
|
|
95
|
-
}
|
|
50
|
+
if (_isEmpty(cachedCookies)) {
|
|
51
|
+
await waitFor(3000);
|
|
52
|
+
const pageCookies = await page.cookies();
|
|
96
53
|
|
|
97
|
-
|
|
54
|
+
fs.writeJSONSync(getProfilePath() + "/cookies.json", pageCookies, {spaces: 2});
|
|
55
|
+
|
|
56
|
+
await page.setCookie(...pageCookies);
|
|
57
|
+
} else {
|
|
58
|
+
await page.setCookie(...cachedCookies);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await navigateToPage(params.url, page);
|
|
62
|
+
|
|
63
|
+
const process = async (album: string) => {
|
|
64
|
+
const results: string[] = [];
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const searchInput = await page.waitForSelector(`::-p-xpath(${YtMusicSearchInputSelector})`, {timeout: 1000});
|
|
68
|
+
await clearInput(searchInput, page);
|
|
69
|
+
await searchInput.type(album);
|
|
70
|
+
page.keyboard.press("Enter");
|
|
71
|
+
await page.waitForNetworkIdle();
|
|
72
|
+
|
|
73
|
+
const albumsChip = await page.waitForSelector(`::-p-xpath(${YtMusicAlbumsChipSelector})`, {timeout: 1000});
|
|
74
|
+
|
|
75
|
+
albumsChip.click();
|
|
76
|
+
await page.waitForNetworkIdle();
|
|
77
|
+
await page.waitForSelector(`::-p-xpath(${YtMusicSearchResultsSelector})`, {timeout: 1000});
|
|
78
|
+
|
|
79
|
+
const albumsElements = await page.$$(`::-p-xpath(${YtMusicSearchResultsSelector})`);
|
|
80
|
+
const albumEl = albumsElements[0];
|
|
81
|
+
const albumUrl = await albumEl.evaluate((el) => el.getAttribute("href"));
|
|
82
|
+
|
|
83
|
+
results.push(`${params.url}/${albumUrl}`);
|
|
84
|
+
|
|
85
|
+
return results;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return results;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
for (const a of params.values) {
|
|
92
|
+
const data = await process(a);
|
|
93
|
+
|
|
94
|
+
result.values.push(...data);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
reporter.finish("done", result);
|
|
98
|
+
})(),
|
|
99
|
+
abortPromise,
|
|
100
|
+
]);
|
|
98
101
|
} catch (error: any) {
|
|
99
|
-
const result:
|
|
100
|
-
|
|
102
|
+
const result: GetYoutubeResult = {errors: []};
|
|
103
|
+
if (error.message === "aborted") {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
101
106
|
if (error instanceof TimeoutError) {
|
|
102
107
|
result.errors.push({title: i18n.t("exceptionTimeout"), description: i18n.t("exceptionTimeoutText")});
|
|
103
108
|
} else {
|
|
@@ -107,7 +112,7 @@ export const execute = async (
|
|
|
107
112
|
result.errors.push({title: i18n.t("exceptionGetYoutubeUrls"), description: i18n.t("exceptionGetYoutubeUrlsText", {error: error.name})});
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
|
-
|
|
115
|
+
|
|
111
116
|
reporter.finish("done", result);
|
|
112
117
|
console.error("Execution failed at stage: ", error.stack);
|
|
113
118
|
} finally {
|
|
@@ -120,9 +125,4 @@ const closeResources = async () => {
|
|
|
120
125
|
if (browser) await browser.close();
|
|
121
126
|
};
|
|
122
127
|
|
|
123
|
-
export const cancel = async () => {
|
|
124
|
-
await browser.close();
|
|
125
|
-
await browser.disconnect();
|
|
126
|
-
};
|
|
127
|
-
|
|
128
128
|
export default execute;
|