pupgram 0.1.3 → 0.2.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 +51 -0
- package/dist/actions/create-post/index.d.ts +1 -1
- package/dist/actions/create-post/index.js +1 -1
- package/dist/actions/create-post/select-files.action.js +15 -11
- package/dist/actions/create-post/wait-for-configmation.action.js +13 -13
- package/dist/actions/create-post/wait-for-post-configure.action.d.ts +3 -0
- package/dist/actions/create-post/wait-for-post-configure.action.js +32 -0
- package/dist/actions/create-reel/index.d.ts +3 -0
- package/dist/actions/create-reel/index.js +19 -0
- package/dist/actions/create-reel/select-ratio.action.d.ts +2 -0
- package/dist/actions/create-reel/select-ratio.action.js +49 -0
- package/dist/actions/create-reel/try-close-reel-info.action.d.ts +2 -0
- package/dist/actions/create-reel/try-close-reel-info.action.js +21 -0
- package/dist/actions/create-reel/wait-for-reel-configurate.action.d.ts +3 -0
- package/dist/actions/create-reel/wait-for-reel-configurate.action.js +26 -0
- package/dist/configs/en.config.js +3 -0
- package/dist/configs/pt-br.config.js +3 -0
- package/dist/instagram.d.ts +1 -0
- package/dist/instagram.js +25 -3
- package/dist/interfaces/action.interface.d.ts +1 -1
- package/dist/interfaces/config.interface.d.ts +3 -0
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -57,6 +57,42 @@ async function main() {
|
|
|
57
57
|
main()
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### Creating a Reel
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { Instagram, enConfig } from "pupgram"
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
// 1. Initialize the Instagram instance
|
|
67
|
+
const instagram = await Instagram.create({
|
|
68
|
+
puppeteer: {
|
|
69
|
+
userDataDir: ".user-data",
|
|
70
|
+
headless: "shell",
|
|
71
|
+
},
|
|
72
|
+
config: enConfig,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// 2. Ensure you are logged in
|
|
76
|
+
await instagram.ensureLoggedIn({
|
|
77
|
+
username: "your-username",
|
|
78
|
+
password: "your-password",
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// 3. Create a Reel
|
|
82
|
+
const reelData = await instagram.createReel({
|
|
83
|
+
filePaths: ["./path/to/video.mp4"],
|
|
84
|
+
caption: "My awesome Reel! 🎥",
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
console.log(`Reel created! URL: https://www.instagram.com/reel/${reelData.code}`)
|
|
88
|
+
|
|
89
|
+
// 4. Close the instance
|
|
90
|
+
await instagram.close()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main()
|
|
94
|
+
```
|
|
95
|
+
|
|
60
96
|
---
|
|
61
97
|
|
|
62
98
|
### Features
|
|
@@ -76,10 +112,25 @@ Automate content publishing with support for:
|
|
|
76
112
|
- **Captions**: Add rich text captions to your posts.
|
|
77
113
|
- **Confirmation**: Waiting for server confirmation to ensure the post is live before proceeding.
|
|
78
114
|
|
|
115
|
+
#### 🎞️ Reel Creation
|
|
116
|
+
|
|
117
|
+
Automate reel publishing with support for:
|
|
118
|
+
- **Video Upload**: Upload video files.
|
|
119
|
+
- **Aspect Ratio**: Automatically handles ratio selection for Reels (Original/9:16).
|
|
120
|
+
- **Captions**: Add rich text captions.
|
|
121
|
+
|
|
79
122
|
#### ⚙️ Configurable
|
|
80
123
|
|
|
81
124
|
Pupgram is designed to be flexible. You can provide custom configurations for different locales or UI variations using the `config` option in `Instagram.create`.
|
|
82
125
|
|
|
126
|
+
#### 🐞 Debugging
|
|
127
|
+
|
|
128
|
+
When things go wrong, Pupgram helps you diagnose the issue. If `screenshotOnError` or `htmlContentOnError` are enabled (default: `true`), Pupgram creates an `errors` directory in your project root.
|
|
129
|
+
|
|
130
|
+
Each error generates a timestamped folder containing:
|
|
131
|
+
- **error.png**: A screenshot of the page at the time of the error.
|
|
132
|
+
- **error.html**: The HTML content of the page.
|
|
133
|
+
|
|
83
134
|
---
|
|
84
135
|
|
|
85
136
|
## 🧪 Tests (Not included yet, CONTRIBUTE! :D)
|
|
@@ -4,4 +4,4 @@ export * from "./select-files.action";
|
|
|
4
4
|
export * from "./put-post-caption.action";
|
|
5
5
|
export * from "./click-on-next-button.action";
|
|
6
6
|
export * from "./click-on-share-button.action";
|
|
7
|
-
export * from "./wait-for-
|
|
7
|
+
export * from "./wait-for-post-configure.action";
|
|
@@ -20,4 +20,4 @@ __exportStar(require("./select-files.action"), exports);
|
|
|
20
20
|
__exportStar(require("./put-post-caption.action"), exports);
|
|
21
21
|
__exportStar(require("./click-on-next-button.action"), exports);
|
|
22
22
|
__exportStar(require("./click-on-share-button.action"), exports);
|
|
23
|
-
__exportStar(require("./wait-for-
|
|
23
|
+
__exportStar(require("./wait-for-post-configure.action"), exports);
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.selectFilesAction = void 0;
|
|
4
|
-
const
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
5
8
|
const error_1 = require("../../error");
|
|
6
9
|
const selectFilesAction = (filePaths) => {
|
|
7
10
|
return async ({ page, config, logger, defaultTimeout }) => {
|
|
8
11
|
logger.info("Selecting files");
|
|
9
|
-
const
|
|
12
|
+
const fileInput = (await page.waitForSelector(config.fileInputSelector, {
|
|
10
13
|
timeout: defaultTimeout,
|
|
11
14
|
}));
|
|
12
|
-
if (!
|
|
15
|
+
if (!fileInput) {
|
|
13
16
|
throw new error_1.InstagramError("File input element not found");
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
const absolutePaths = filePaths.map((filePath) => node_path_1.default.resolve(filePath));
|
|
19
|
+
try {
|
|
20
|
+
await fileInput.uploadFile(...absolutePaths);
|
|
21
|
+
logger.debug("Files uploaded via uploadFile");
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
logger.error(`Failed to upload files: ${error.message}`);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
23
27
|
logger.debug("Files selected");
|
|
24
28
|
};
|
|
25
29
|
};
|
|
@@ -3,25 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.waitForConfirmationAction = void 0;
|
|
4
4
|
const error_1 = require("../../error");
|
|
5
5
|
const waitForConfirmationAction = async ({ page, config, logger, defaultTimeout }) => {
|
|
6
|
-
var _a;
|
|
6
|
+
var _a, _b;
|
|
7
7
|
logger.info("Waiting for confirmation");
|
|
8
8
|
let result;
|
|
9
|
-
const
|
|
9
|
+
const configureEndpoints = ["/api/v1/media/configure/", "/api/v1/media/configure_sidecar/"];
|
|
10
10
|
logger.debug("Waiting for response");
|
|
11
|
-
const response = await page.waitForResponse((res) =>
|
|
11
|
+
const response = await page.waitForResponse((res) => configureEndpoints.some((endpoint) => res.url().includes(endpoint)) &&
|
|
12
|
+
res.status() === 200 &&
|
|
13
|
+
res.request().method() === "POST", { timeout: 60000 * 2 });
|
|
12
14
|
const json = await response.json();
|
|
13
|
-
if (json.status
|
|
14
|
-
const media = json.media;
|
|
15
|
-
result = {
|
|
16
|
-
id: media.id,
|
|
17
|
-
pk: media.pk,
|
|
18
|
-
code: media.code,
|
|
19
|
-
caption: ((_a = media.caption) === null || _a === void 0 ? void 0 : _a.text) || "",
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
15
|
+
if (json.status !== "ok" || !json.media) {
|
|
23
16
|
throw new error_1.InstagramError("Failed to create post");
|
|
24
17
|
}
|
|
18
|
+
const media = json.media;
|
|
19
|
+
result = {
|
|
20
|
+
id: (_a = media.id) !== null && _a !== void 0 ? _a : media.pk,
|
|
21
|
+
pk: media.pk,
|
|
22
|
+
code: media.code,
|
|
23
|
+
caption: ((_b = media.caption) === null || _b === void 0 ? void 0 : _b.text) || "",
|
|
24
|
+
};
|
|
25
25
|
logger.debug("Response received. Waiting for confirmation text message");
|
|
26
26
|
await page.waitForSelector(`xpath///*[contains(text(), "${config.confirmationText}")]`, {
|
|
27
27
|
timeout: defaultTimeout,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.waitForPostConfigureAction = void 0;
|
|
4
|
+
const error_1 = require("../../error");
|
|
5
|
+
const waitForPostConfigureAction = async ({ page, config, logger, defaultTimeout }) => {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
logger.info("Waiting for confirmation");
|
|
8
|
+
let result;
|
|
9
|
+
const configureEndpoints = ["/api/v1/media/configure/", "/api/v1/media/configure_sidecar/"];
|
|
10
|
+
logger.debug("Waiting for response");
|
|
11
|
+
const response = await page.waitForResponse((res) => configureEndpoints.some((endpoint) => res.url().includes(endpoint)) &&
|
|
12
|
+
res.status() === 200 &&
|
|
13
|
+
res.request().method() === "POST", { timeout: 60000 * 5 });
|
|
14
|
+
const json = await response.json();
|
|
15
|
+
if (json.status !== "ok" || !json.media) {
|
|
16
|
+
throw new error_1.InstagramError("Failed to create post");
|
|
17
|
+
}
|
|
18
|
+
const media = json.media;
|
|
19
|
+
result = {
|
|
20
|
+
id: (_a = media.id) !== null && _a !== void 0 ? _a : media.pk,
|
|
21
|
+
pk: media.pk,
|
|
22
|
+
code: media.code,
|
|
23
|
+
caption: ((_b = media.caption) === null || _b === void 0 ? void 0 : _b.text) || "",
|
|
24
|
+
};
|
|
25
|
+
logger.debug("Response received. Waiting for confirmation text message");
|
|
26
|
+
await page.waitForSelector(`xpath///*[contains(text(), "${config.confirmationText}")]`, {
|
|
27
|
+
timeout: defaultTimeout,
|
|
28
|
+
});
|
|
29
|
+
logger.debug("Confirmation text message found");
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
exports.waitForPostConfigureAction = waitForPostConfigureAction;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./select-ratio.action"), exports);
|
|
18
|
+
__exportStar(require("./try-close-reel-info.action"), exports);
|
|
19
|
+
__exportStar(require("./wait-for-reel-configurate.action"), exports);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectRatioAction = void 0;
|
|
4
|
+
const error_1 = require("../../error");
|
|
5
|
+
const selectRatioAction = async ({ page, config, logger, defaultTimeout }) => {
|
|
6
|
+
logger.info("Selecting ratio");
|
|
7
|
+
const selectRatioSelector = `xpath///button[.//*[name()='svg']//*[name()='title' and text()='${config.reelSelectRatioText}']]`;
|
|
8
|
+
const videoElement = await page.waitForSelector("video", { timeout: defaultTimeout });
|
|
9
|
+
if (!videoElement) {
|
|
10
|
+
throw new error_1.InstagramError("Video element not found");
|
|
11
|
+
}
|
|
12
|
+
logger.debug("Video element found");
|
|
13
|
+
await page.evaluate((videoElement) => {
|
|
14
|
+
const container = videoElement.closest("div");
|
|
15
|
+
if (container instanceof HTMLElement) {
|
|
16
|
+
container.dataset.__oldDisplay = container.style.display;
|
|
17
|
+
container.style.display = "none";
|
|
18
|
+
}
|
|
19
|
+
}, videoElement);
|
|
20
|
+
logger.debug("Video container display hidden");
|
|
21
|
+
try {
|
|
22
|
+
const selectRatioElement = await page.waitForSelector(selectRatioSelector, {
|
|
23
|
+
timeout: defaultTimeout,
|
|
24
|
+
});
|
|
25
|
+
if (!selectRatioElement) {
|
|
26
|
+
throw new error_1.InstagramError("Select ratio element not found");
|
|
27
|
+
}
|
|
28
|
+
await selectRatioElement.click();
|
|
29
|
+
logger.debug("Select ratio element clicked");
|
|
30
|
+
const originalRatioElement = await page.waitForSelector(`xpath///div[./span[contains(text(), '${config.reelSelectOriginalRatioText}')]]`, { timeout: defaultTimeout });
|
|
31
|
+
if (!originalRatioElement) {
|
|
32
|
+
throw new error_1.InstagramError("Original ratio element not found");
|
|
33
|
+
}
|
|
34
|
+
await originalRatioElement.click();
|
|
35
|
+
logger.debug("Original ratio element clicked");
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
logger.info("Restoring video container display");
|
|
39
|
+
await page.evaluate(() => {
|
|
40
|
+
document.querySelectorAll("div[data-__old-display]").forEach((el) => {
|
|
41
|
+
const oldDisplay = el.getAttribute("data-__old-display");
|
|
42
|
+
el.style.display = oldDisplay || "";
|
|
43
|
+
el.removeAttribute("data-__old-display");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
logger.debug("Original ratio element clicked");
|
|
48
|
+
};
|
|
49
|
+
exports.selectRatioAction = selectRatioAction;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryCloseReelInfoAction = void 0;
|
|
4
|
+
const tryCloseReelInfoAction = async ({ page, config, logger, defaultTimeout }) => {
|
|
5
|
+
logger.info("Closing reel info");
|
|
6
|
+
try {
|
|
7
|
+
const closeButton = await page.waitForSelector(`xpath///button[text()='${config.reelCloseInfoText}']`, {
|
|
8
|
+
timeout: defaultTimeout * 2,
|
|
9
|
+
});
|
|
10
|
+
if (!closeButton) {
|
|
11
|
+
logger.debug("Close button not found");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await closeButton.click();
|
|
15
|
+
logger.debug("Close button clicked");
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
logger.debug("Close button not found");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.tryCloseReelInfoAction = tryCloseReelInfoAction;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.waitForReelConfigureAction = void 0;
|
|
4
|
+
const waitForReelConfigureAction = async ({ page }) => {
|
|
5
|
+
var _a;
|
|
6
|
+
const start = Date.now();
|
|
7
|
+
const timeout = 5 * 60000;
|
|
8
|
+
while (Date.now() - start < timeout) {
|
|
9
|
+
const response = await page.waitForResponse((res) => res.url().includes("/api/v1/media/configure_to_clips/") && res.request().method() === "POST", { timeout: timeout });
|
|
10
|
+
const json = await response.json();
|
|
11
|
+
if (json.media) {
|
|
12
|
+
return {
|
|
13
|
+
caption: (_a = json.caption.text) !== null && _a !== void 0 ? _a : "",
|
|
14
|
+
id: json.media.id,
|
|
15
|
+
pk: json.media.pk,
|
|
16
|
+
code: json.media.code,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (json.status === "fail" && json.message === "Transcode not finished yet.") {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unexpected response: ${JSON.stringify(json)}`);
|
|
23
|
+
}
|
|
24
|
+
throw new Error("Timeout waiting for Reel configure");
|
|
25
|
+
};
|
|
26
|
+
exports.waitForReelConfigureAction = waitForReelConfigureAction;
|
package/dist/instagram.d.ts
CHANGED
|
@@ -49,4 +49,5 @@ export declare class Instagram {
|
|
|
49
49
|
logIn({ username, password }: LogInOptions): Promise<void>;
|
|
50
50
|
ensureLoggedIn({ username, password }: LogInOptions): Promise<void>;
|
|
51
51
|
createPost({ caption, filePaths }: CreatePostOptions): Promise<PostData>;
|
|
52
|
+
createReel({ caption, filePaths }: CreatePostOptions): Promise<PostData>;
|
|
52
53
|
}
|
package/dist/instagram.js
CHANGED
|
@@ -6,13 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.Instagram = exports.defaultTimeout = void 0;
|
|
7
7
|
const puppeteer_extra_plugin_stealth_1 = __importDefault(require("puppeteer-extra-plugin-stealth"));
|
|
8
8
|
const puppeteer_extra_1 = __importDefault(require("puppeteer-extra"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
11
|
const create_post_1 = require("./actions/create-post");
|
|
11
12
|
const login_1 = require("./actions/login");
|
|
13
|
+
const create_reel_1 = require("./actions/create-reel");
|
|
12
14
|
const needs_login_action_1 = require("./actions/needs-login.action");
|
|
13
15
|
const logger_helper_1 = require("./helpers/logger.helper");
|
|
14
16
|
const delay_action_1 = require("./actions/delay.action");
|
|
15
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
16
17
|
puppeteer_extra_1.default.use((0, puppeteer_extra_plugin_stealth_1.default)());
|
|
17
18
|
exports.defaultTimeout = 10000;
|
|
18
19
|
class Instagram {
|
|
@@ -60,7 +61,10 @@ class Instagram {
|
|
|
60
61
|
async initalizePage() {
|
|
61
62
|
this.logger.info("Initalizing page");
|
|
62
63
|
const page = await this.browser.newPage();
|
|
63
|
-
await page.setViewport({
|
|
64
|
+
await page.setViewport({
|
|
65
|
+
width: 1920,
|
|
66
|
+
height: 919,
|
|
67
|
+
});
|
|
64
68
|
await page.goto(this.baseUrl, { waitUntil: "domcontentloaded" });
|
|
65
69
|
this.logger.debug("Page initalized");
|
|
66
70
|
return page;
|
|
@@ -147,10 +151,28 @@ class Instagram {
|
|
|
147
151
|
create_post_1.clickOnNextButtonAction,
|
|
148
152
|
(0, create_post_1.putPostCaptionAction)(caption),
|
|
149
153
|
create_post_1.clickOnShareButtonAction,
|
|
150
|
-
create_post_1.
|
|
154
|
+
create_post_1.waitForPostConfigureAction,
|
|
151
155
|
]);
|
|
152
156
|
this.logger.debug("Post created");
|
|
153
157
|
return result;
|
|
154
158
|
}
|
|
159
|
+
async createReel({ caption, filePaths }) {
|
|
160
|
+
this.logger.info("Creating reel");
|
|
161
|
+
const result = await this.executeWithDiagnostics([
|
|
162
|
+
(0, delay_action_1.delayAction)(1500),
|
|
163
|
+
create_post_1.clickOnCreateButtonAction,
|
|
164
|
+
create_post_1.clickOnPostButtonAction,
|
|
165
|
+
(0, create_post_1.selectFilesAction)(filePaths),
|
|
166
|
+
create_reel_1.tryCloseReelInfoAction,
|
|
167
|
+
create_reel_1.selectRatioAction,
|
|
168
|
+
create_post_1.clickOnNextButtonAction,
|
|
169
|
+
create_post_1.clickOnNextButtonAction,
|
|
170
|
+
(0, create_post_1.putPostCaptionAction)(caption),
|
|
171
|
+
create_post_1.clickOnShareButtonAction,
|
|
172
|
+
create_reel_1.waitForReelConfigureAction,
|
|
173
|
+
]);
|
|
174
|
+
this.logger.debug("Reel created");
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
155
177
|
}
|
|
156
178
|
exports.Instagram = Instagram;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pupgram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"typescript": "^5.9.3"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"clipboardy": "^5.1.0",
|
|
43
42
|
"puppeteer": "^24.36.1",
|
|
44
43
|
"puppeteer-extra": "^3.3.6",
|
|
45
44
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|