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.
Files changed (54) hide show
  1. package/README.md +4 -3
  2. package/package.json +4 -2
  3. package/public/screenshots/main-downloading.png +0 -0
  4. package/public/screenshots/main-info.png +0 -0
  5. package/public/screenshots/main.png +0 -0
  6. package/public/screenshots/settings.png +0 -0
  7. package/src/automations/Selectors.ts +2 -0
  8. package/src/automations/Youtube.ts +78 -82
  9. package/src/automations/YoutubeAlbums.ts +76 -76
  10. package/src/automations/YoutubeArtists.ts +98 -89
  11. package/src/automations/YoutubeTracks.ts +131 -0
  12. package/src/common/FileSystem.ts +15 -0
  13. package/src/common/Helpers.ts +6 -0
  14. package/src/common/Media.ts +22 -1
  15. package/src/common/Messaging.ts +4 -29
  16. package/src/common/Store.ts +10 -5
  17. package/src/common/YtdplUtils.ts +162 -28
  18. package/src/components/fileField/FileField.tsx +5 -5
  19. package/src/components/modals/detailsModal/DetailsModal.tsx +6 -2
  20. package/src/components/modals/imageModal/ImageModal.styl +24 -18
  21. package/src/components/youtube/inputModePicker/InputModePicker.tsx +23 -6
  22. package/src/components/youtube/inputPanel/InputPanel.tsx +22 -17
  23. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.styl +135 -90
  24. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.tsx +26 -34
  25. package/src/components/youtube/playlistTabs/PlaylistTabs.styl +15 -0
  26. package/src/components/youtube/playlistTabs/PlaylistTabs.tsx +63 -28
  27. package/src/components/youtube/trackList/TrackList.styl +13 -0
  28. package/src/components/youtube/trackList/TrackList.tsx +309 -155
  29. package/src/i18next.ts +1 -1
  30. package/src/index.ts +4 -148
  31. package/src/messaging/MessageBus.ts +14 -0
  32. package/src/messaging/MessageChannel.ts +67 -0
  33. package/src/messaging/Messages.ts +31 -0
  34. package/src/messaging/MessagingService.ts +25 -0
  35. package/src/messaging/MultiMessageChannel.ts +42 -0
  36. package/src/messaging/channels/SystemMessageChannel.ts +58 -0
  37. package/src/messaging/channels/YoutubeAlbumsMessageChannel.ts +18 -0
  38. package/src/messaging/channels/YoutubeArtistsMessageChannel.ts +18 -0
  39. package/src/messaging/channels/YoutubeTracksMessageChannel.ts +18 -0
  40. package/src/messaging/channels/YoutubeUrlsMessageChannel.ts +18 -0
  41. package/src/react/contexts/DataContext.tsx +3 -3
  42. package/src/resources/bin/ffmpeg.exe +0 -0
  43. package/src/resources/locales/de-DE/help.json +7 -3
  44. package/src/resources/locales/de-DE/translation.json +7 -3
  45. package/src/resources/locales/en-GB/help.json +7 -3
  46. package/src/resources/locales/en-GB/translation.json +7 -3
  47. package/src/resources/locales/pl-PL/help.json +7 -3
  48. package/src/resources/locales/pl-PL/translation.json +7 -3
  49. package/src/styles/fonts.styl +0 -23
  50. package/src/theme/ColorThemes.ts +0 -16
  51. package/src/views/home/HomeView.tsx +303 -217
  52. package/src/views/settings/SettingsView.styl +6 -0
  53. package/src/views/settings/SettingsView.tsx +49 -9
  54. 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
- - complete playlists
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.0",
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": "${productName}-setup-${version}.${ext}"
33
+ "artifactName": "${name}-${version}-setup.${ext}"
32
34
  },
33
35
  "nsis": {
34
36
  "oneClick": false,
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, LaunchOptions, Page, TimeoutError} from "puppeteer";
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 {GetYoutubeUrlParams, GetYoutubeUrlResult} from "../common/Messaging";
14
+ import {GetYoutubeResult} from "../common/Messaging";
16
15
  import puppeteerOptions from "../common/PuppeteerOptions";
17
- import {IReporter, ProgressInfo, Reporter} from "../common/Reporter";
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<GetYoutubeUrlResult>;
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: GetYoutubeUrlResult = {warnings: [], errors: [], urls: []};
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
- await i18n.changeLanguage(params.lang);
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
- await navigateToPage(params.url, page);
64
-
65
- const process = async (urlToProcess: string) => {
66
- const results: string[] = [];
67
-
68
- try {
69
- await navigateToPage(urlToProcess, page);
70
-
71
- const element = await page.waitForSelector(`::-p-xpath(${AlbumsHrefSelector})`, {timeout: 1000});
72
- const albumsUrl = await element.evaluate((el) => el.getAttribute("href"));
73
-
74
- await navigateToPage(`${params.url}/${albumsUrl}`, page);
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
- return results;
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
- for (const item of items) {
94
- results.push(`${params.url}/${item}`);
57
+ await page.setCookie(...pageCookies);
58
+ } else {
59
+ await page.setCookie(...cachedCookies);
95
60
  }
96
61
 
97
- return results;
98
- }
99
- };
62
+ await navigateToPage(params.url, page);
100
63
 
101
- for (const u of params.artistUrls) {
102
- const data = await process(u);
103
-
104
- result.urls.push(...data);
105
- }
64
+ const process = async (urlToProcess: string) => {
65
+ const results: string[] = [];
106
66
 
107
- reporter.finish("done", result);
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: GetYoutubeUrlResult = {errors: []};
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, LaunchOptions, Page, TimeoutError} from "puppeteer";
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 {GetYoutubeAlbumsParams, GetYoutubeUrlResult} from "../common/Messaging";
14
+ import {GetYoutubeResult} from "../common/Messaging";
16
15
  import puppeteerOptions from "../common/PuppeteerOptions";
17
- import {IReporter, ProgressInfo, Reporter} from "../common/Reporter";
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<GetYoutubeUrlResult>;
25
+ let reporter: IReporter<GetYoutubeResult>;
26
26
 
27
27
  puppeteer.use(StealthPlugin());
28
28
 
29
- export const execute = async (
30
- params: GetYoutubeAlbumsParams,
31
- options: LaunchOptions,
32
- i18n: i18next,
33
- onProgress: (data: ProgressInfo<GetYoutubeUrlResult>) => void,
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
- 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});
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
- const albumsElements = await page.$$(`::-p-xpath(${YtMusicSearchResultsSelector})`);
80
- const albumEl = albumsElements[0];
81
- const albumUrl = await albumEl.evaluate((el) => el.getAttribute("href"));
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
- results.push(`${params.url}/${albumUrl}`);
46
+ await page.setUserAgent(userAgent);
84
47
 
85
- return results;
86
- } catch (error) {
87
- return results;
88
- }
89
- };
48
+ const cachedCookies = fs.readJSONSync(getProfilePath() + "/cookies.json", {throws: false});
90
49
 
91
- for (const a of params.albums) {
92
- const data = await process(a);
93
-
94
- result.urls.push(...data);
95
- }
50
+ if (_isEmpty(cachedCookies)) {
51
+ await waitFor(3000);
52
+ const pageCookies = await page.cookies();
96
53
 
97
- reporter.finish("done", result);
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: GetYoutubeUrlResult = {errors: []};
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;