-
Notifications
You must be signed in to change notification settings - Fork 2
/
checkout.js
166 lines (154 loc) · 4.97 KB
/
checkout.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
const puppeteer = require("puppeteer");
(async () => {
const emailer = await getEmailer();
const { browserWSEndpoint, kill } = await launchChrome();
const browser = await puppeteer.connect({
browserWSEndpoint,
defaultViewport: null,
});
const page = await browser.newPage();
const { goto, has, click, clickAndWait, content, screenshot } = api(page);
const start =
"https://www.amazon.com/gp/buy/shipoptionselect/handlers/display.html?hasWorkingJavascript=1";
let attempts = 0;
while (true) {
await goto(start);
if (await has("No delivery windows available")) {
console.log(`[${attempts}] No delivery windows available. Trying again.`);
} else if (await has("Checkout Whole Foods Market Cart")) {
console.log(`Checkout Whole Foods Market Cart`);
await clickAndWait('input[name^="proceedToALMCheckout"]');
await clickAndWait('a[name="proceedToCheckout"]');
await clickAndWait('input[type="submit"]');
} else if (
(await has("Recommended for you")) ||
(await has("An error occurred when we tried to process your request"))
) {
console.log("Recommended for you || An error occurred...");
await clickAndWait("a#nav-cart");
await clickAndWait('input[name^="proceedToALMCheckout"]');
await clickAndWait('a[name="proceedToCheckout"]');
await clickAndWait('input[type="submit"]');
} else if (
await has("We're sorry we are unable to fulfill your entire order")
) {
console.log("We're sorry we are unable to fulfill your entire order");
await clickAndWait('input[value="Continue"]');
} else {
// Found slot!
console.log("Found a slot!");
await screenshot("slot-found-1.png");
await emailer.mail(`slot found`, await content());
// click on first FREE slot
await page.evaluate(() =>
[...document.querySelectorAll("span")]
.filter((s) => s.innerText === "FREE")[0]
.click()
);
await screenshot("slot-found-1-selected.png");
// click through checkout pages
await clickAndWait("input.a-button-input");
await screenshot("slot-found-2.png");
// sometimes this page doesn't exist; try clicking anyway...
try {
await clickAndWait("input#continue-top");
await screenshot("slot-found-3.png");
} catch (e) {
console.log(`click input#continue-top failed; continuing...`);
}
await clickAndWait("input.place-your-order-button");
await screenshot("slot-found-4.png");
break;
}
attempts++;
}
// kill();
})();
function api(page) {
return {
goto: async (url) => {
return Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.goto(url),
]);
},
has: async (content) => {
const innerText = await page.evaluate(() => document.body.innerText);
return innerText.includes(content);
},
click: async (selector) => page.click(selector),
clickAndWait: async (selector) => {
return await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.click(selector),
]);
},
content: async () => page.content(),
screenshot: async (path) => page.screenshot({ path }),
};
}
async function getEmailer() {
const fs = require("fs");
if (!fs.existsSync("config.json")) {
console.warn(
"Emailer not configured. To send notification emails, configure `config.json` with email smtp settings"
);
return { mail: async () => {} };
}
const { smtpConfig: nodemailerConfig, emailRecipients } = JSON.parse(
fs.readFileSync("config.json", "utf8")
);
const nodemailer = require("nodemailer");
const transport = nodemailer.createTransport({
...nodemailerConfig,
});
return {
mail: async (subject, text) => {
return new Promise((resolve, reject) => {
transport.sendMail(
{
from: nodemailerConfig.auth.user,
to: emailRecipients,
subject,
text,
},
(err, info) => {
if (err) {
reject(err);
} else {
resolve(info);
}
}
);
});
},
};
}
async function delay(ms) {
return new Promise((res) => setTimeout(res, ms));
}
async function launchChrome() {
return new Promise((resolve, reject) => {
const { spawn } = require("child_process");
const cp = spawn(
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', // prettier-ignore
[
"--remote-debugging-port=9222",
"--no-first-run",
"--no-default-browser-check",
`--user-data-dir=${__dirname}/chrome-data-dir`,
]
);
let stderr = "";
cp.stderr.on("data", (data) => {
stderr += data;
if (stderr.includes("XXX Init()")) {
const [_, browserWSEndpoint] = stderr.match(/listening on ([^\s]+)/);
resolve({
browserWSEndpoint,
kill: () => cp.kill(),
});
}
});
});
}