-
Notifications
You must be signed in to change notification settings - Fork 0
/
grabUberEats.js
275 lines (248 loc) · 9.12 KB
/
grabUberEats.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
Classes and their info:
[Uber18_text_p1 black]
regular order items such as item name, price, quantity, servuce fee, delivery fee, tax, tip
[Uber18_text_p3]
"subtotal", subtotal, amount paid
[Uber18_p3 total_head]
Total
[Uber18_text_p1 header_Uber18_text_p1]
"Here's your receipt from <RESTAURANT> and Uber Eats."
[Uber18_text_p1 green]
any type of discounts
[Uber18_p3 header_h3]
"Thanks for ordering, <NAME>"
*/
import puppeteer from "puppeteer";
import exceljs from "exceljs";
import fs from "fs";
import { setTimeout } from "timers/promises";
import { TimeoutError } from "puppeteer";
main();
async function main() {
// Launch the browser and open a new blank page
const browser = await puppeteer.launch({
headless: false,
userDataDir: "./user_data"
});
let page = await browser.newPage();
// Set screen size
await page.setViewport({width: 1080, height: 1080});
const user = "";
const pw = "";
// Navigate the page to the login URL
await page.goto('https://www.ubereats.com/ca/orders/#', {
waitUntil: 'domcontentloaded'
});
await login(user, pw, page);
// }
let lastDateInExcel = await getLastDateInExcel("UberEats");
let lastDateToGet = lastDateInExcel ? lastDateInExcel : new Date("2022-06-20 23:59:59");
console.log("Getting orders starting from: " + lastDateToGet);
let receipts = await getOrders(page, lastDateToGet);
await uploadToSheets(receipts);
await page.close();
await browser.close();
}
async function login(username, password, page) {
try {
// input phone number and click continue
let inputBox = await page.$("#phone-number-or-email-input-container");
if (inputBox) await inputBox.type(username, {delay: 50});
await page.click("#forward-button");
await page.waitForSelector("#alt-PASSWORD", {visible: true, timeout: 3000});
console.log("clicked on password")
await page.click("#alt-PASSWORD");
await page
.waitForSelector('#PASSWORD', {visible: true})
.then(async () => {
console.log('found pw input');
await page.type("#PASSWORD", password);
await page.keyboard.press('Enter');
})
} catch (e) {
return;
}
}
function textBetween(text, start, end) {
return text.substring(text.indexOf(start) + start.length, text.indexOf(end));
}
async function getOrders(page, lastDateToGet = new Date()) {
await page.waitForSelector('#main-content').then(() => console.log("main-content loaded"));
await setTimeout(1000);
// selectors
const viewReceiptSelector = "//a[contains(., 'View receipt')]";
const showMoreSelector = "#main-content > div > button";
const headerSelector = ".Uber18_p3.header_h3";
const restaurantSelector = ".Uber18_text_p1.header_Uber18_text_p1";
const totalsSelector = ".Uber18_text_p3";
const costsSelector = ".Uber18_text_p1.black";
const discountsSelector = ".Uber18_text_p1.green";
const dateSelector = "span.Uber18_text_p1";
const showMoreItemsSelectorXPath = "//div[contains(@class, 'bo bp d1 di al aq if bc') and contains(text(), 'Show more')]";
const ordersSelector = "#main-content > div > div.al"; // [0] is text header, [1] is first order
const itemsOrderSelector = "div.al.am.bh > div > div.dh > div > ul > li > div > div" // can contain "Show more"
// get receipts
let receipts = [];
let counter = 0;
let finished = false;
while (!finished) {
// wait for "Show more" button to show up, means page is loaded
await page.waitForSelector(showMoreSelector, {visible: true});
// Click on any "Show more"s to expand the order
// let orders = (await page.$$(ordersSelector)).slice(1);
// let showMoreItems = await page.$x(showMoreItemsSelectorXPath);
// showMoreItems.forEach(async (showMoreItem, i) => await showMoreItem.click());
let viewReceiptLinks = await page.$x(viewReceiptSelector);
console.log("# receipts found: " + viewReceiptLinks.length);
// loop through each receipt
for (let i = counter; i < viewReceiptLinks.length; i++) {
await viewReceiptLinks[i].click();
// wait for view receipt iframe to load
const iframeHandle = await page.waitForSelector("iframe", {visible: true});
const frame = await iframeHandle.contentFrame();
// wait for header to show
await frame.waitForSelector(headerSelector, {visible: true});
// operations
console.log("receipt counter: " + counter);
let receipt = {"service": "UberEats"};
// grab id
let url = await page.url();
receipt["id"] = textBetween(url, "modctx=", "&ps=1");
// grab date
receipt["date"] = await frame.$eval(dateSelector, el => el.textContent);
// restaurant
const restaurantText = await frame.$eval(restaurantSelector, el => el.textContent);
receipt["restaurant"] = textBetween(restaurantText, "from ", " and ");
// price totals
const totals = await frame.$$(totalsSelector);
receipt["subtotal"] = await totals[1].evaluate(el => el.textContent);
receipt["payments"] = [];
for (let i = 2; i < totals.length; i++) {
let totalText = await totals[i].evaluate(el => el.textContent);
receipt["payments"].push(totalText);
}
// costs
const costs = await frame.$$(costsSelector);
receipt["costItems"] = {};
for (let i = 0; i < costs.length; i++) {
let costText = await costs[i].evaluate(el => el.textContent);
if (costText.indexOf("CA$") === 0) {
let costItem = await costs[i-1].evaluate(el => el.textContent);
receipt["costItems"][costItem.toUpperCase()] = costText;
}
}
// discounts
receipt["discounts"] = [];
const discounts = await frame.$$(discountsSelector);
for (var discount of discounts) {
let discountText = await discount.evaluate(el => el.textContent);
receipt["discounts"].push(discountText);
}
// go back
await page.goBack();
await page.waitForSelector("iframe", {hidden: true});
// set finished flag
if (new Date(receipt["date"]) <= lastDateToGet) {
console.log("FINISHED");
finished = true;
break;
}
counter++;
receipts.push(receipt);
// logging
console.log(receipt["restaurant"]);
console.log(receipt["date"]);
}
let showmore = await page.$(showMoreSelector);
await showmore.click();
}
return receipts;
}
function cadToNum(cad) {
return parseFloat(cad.replace('CA$', ''));
}
function formatReceipts(receipts) {
receipts.forEach(receipt => {
receipt["date"] = new Date(receipt["date"]);
receipt["subtotal"] = cadToNum(receipt["subtotal"]);
receipt["payments"] = receipt["payments"].map(p => cadToNum(p));
receipt["discounts"] = receipt["discounts"].map(d => cadToNum(d));
receipt["costItems"] = Object.fromEntries(
Object.entries(receipt["costItems"]).map(([key, value]) => [key, cadToNum(value)])
);
});
return receipts;
}
function getItems(receipt) {
let items = Object.entries(receipt["costItems"])
.filter(([k, v]) => k !== "SERVICE FEE" && k !== "DELIVERY FEE" && k !== "TAX" && k !== "TIP");
items.forEach((item, i, a) => {
a[i] = [receipt["service"], receipt["id"], item[0], item[1]]
});
return items;
}
/**
*
* @param {*} receipt
*/
async function uploadToSheets(receipts) {
// open excel
const workbook = new exceljs.Workbook();
await workbook.xlsx.readFile('C:/Users/murph/OneDrive/Shared Expenses.xlsx');
const ws = workbook.getWorksheet('Orders');
const wsItems = workbook.getWorksheet('Orders.Items');
// add rows
console.log(ws.lastRow.number);
formatReceipts(receipts);
ws.addRows(receipts.map(receipt => {
return [
receipt["service"],
receipt["id"],
receipt["restaurant"],
receipt["date"],
receipt["payments"].reduce((a, b) => a + b, 0),
receipt["subtotal"],
receipt["costItems"]["SERVICE FEE"],
receipt["costItems"]["DELIVERY FEE"],
receipt["costItems"]["TAX"],
receipt["costItems"]["TIP"],
receipt["discounts"].reduce((a, b) => a + b, 0),
];
}));
wsItems.addRows(
receipts
.map(receipt => getItems(receipt))
.flat()
);
// write to excel
console.log("writing to excel");
workbook.xlsx.writeFile('C:/Users/murph/OneDrive/Shared Expenses.xlsx')
.then(() => {console.log("done");});
return 0;
}
async function getLastDateInExcel(service = 'UberEats') {
// open excel
const workbook = new exceljs.Workbook();
await workbook.xlsx.readFile('C:/Users/murph/OneDrive/Shared Expenses.xlsx');
const orders = workbook.getWorksheet('Orders');
// get service
let serviceCol = orders.getColumn(1).values.slice(2);
let dateCol = orders.getColumn(4).values.slice(2).map(v => new Date(v));
let largestDate = Math.min(...dateCol);
let largestIdx = -1;
for (let i = 0; i < serviceCol.length; i++) {
if (serviceCol[i] === service && dateCol[i] > largestDate) {
largestDate = dateCol[i];
largestIdx = i + 2; // off by 2 in excel structure
}
}
console.log({
largestDate: largestDate,
largestIdx: largestIdx,
});
// get last date
return largestIdx > 0 ?
new Date(orders.getRow(largestIdx).getCell(4).value) :
null;
}