ebag 0.0.2
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 +89 -0
- package/dist/cli/format.d.ts +12 -0
- package/dist/cli/format.js +354 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +447 -0
- package/dist/lib/auth.d.ts +3 -0
- package/dist/lib/auth.js +17 -0
- package/dist/lib/cart.d.ts +4 -0
- package/dist/lib/cart.js +46 -0
- package/dist/lib/client.d.ts +13 -0
- package/dist/lib/client.js +96 -0
- package/dist/lib/config.d.ts +11 -0
- package/dist/lib/config.js +90 -0
- package/dist/lib/cookies.d.ts +2 -0
- package/dist/lib/cookies.js +30 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.js +25 -0
- package/dist/lib/lists.d.ts +4 -0
- package/dist/lib/lists.js +41 -0
- package/dist/lib/orders.d.ts +14 -0
- package/dist/lib/orders.js +266 -0
- package/dist/lib/products.d.ts +2 -0
- package/dist/lib/products.js +8 -0
- package/dist/lib/search.d.ts +7 -0
- package/dist/lib/search.js +181 -0
- package/dist/lib/slots.d.ts +6 -0
- package/dist/lib/slots.js +55 -0
- package/dist/lib/types.d.ts +115 -0
- package/dist/lib/types.js +2 -0
- package/package.json +53 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const commander_1 = require("commander");
|
|
10
|
+
const cart_1 = require("../lib/cart");
|
|
11
|
+
const auth_1 = require("../lib/auth");
|
|
12
|
+
const cookies_1 = require("../lib/cookies");
|
|
13
|
+
const config_1 = require("../lib/config");
|
|
14
|
+
const lists_1 = require("../lib/lists");
|
|
15
|
+
const orders_1 = require("../lib/orders");
|
|
16
|
+
const products_1 = require("../lib/products");
|
|
17
|
+
const search_1 = require("../lib/search");
|
|
18
|
+
const slots_1 = require("../lib/slots");
|
|
19
|
+
const format_1 = require("./format");
|
|
20
|
+
function requireSessionCookie() {
|
|
21
|
+
const session = (0, config_1.loadSession)();
|
|
22
|
+
if (!session.cookies) {
|
|
23
|
+
throw new Error('No session cookie found. Run `ebag login --cookie "<cookie>"` first.');
|
|
24
|
+
}
|
|
25
|
+
return session;
|
|
26
|
+
}
|
|
27
|
+
function formatError(err) {
|
|
28
|
+
const error = err;
|
|
29
|
+
const details = { message: error.message };
|
|
30
|
+
if (error.status)
|
|
31
|
+
details.status = error.status;
|
|
32
|
+
if (error.body)
|
|
33
|
+
details.body = error.body;
|
|
34
|
+
return details;
|
|
35
|
+
}
|
|
36
|
+
async function main() {
|
|
37
|
+
const program = new commander_1.Command();
|
|
38
|
+
function getPackageVersion() {
|
|
39
|
+
try {
|
|
40
|
+
const packagePath = node_path_1.default.resolve(__dirname, '../../package.json');
|
|
41
|
+
const raw = node_fs_1.default.readFileSync(packagePath, 'utf8');
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
return parsed.version || 'unknown';
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return 'unknown';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
program
|
|
50
|
+
.name('ebag')
|
|
51
|
+
.description('CLI for interacting with ebag.bg')
|
|
52
|
+
.version(getPackageVersion(), '-v, --version', 'Show CLI version')
|
|
53
|
+
.option('--json', 'Output JSON');
|
|
54
|
+
program
|
|
55
|
+
.command('login')
|
|
56
|
+
.description('Store session cookie and validate it')
|
|
57
|
+
.option('--cookie <cookie>', 'Cookie header value from browser')
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
const config = (0, config_1.loadConfig)();
|
|
60
|
+
const json = program.opts().json;
|
|
61
|
+
if (!options.cookie) {
|
|
62
|
+
if (json) {
|
|
63
|
+
(0, format_1.outputJson)({ instructions: (0, auth_1.getLoginInstructions)() });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
process.stdout.write(`${(0, auth_1.getLoginInstructions)()}\n`);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const normalizedCookie = (0, cookies_1.normalizeCookieInput)(options.cookie);
|
|
71
|
+
const cookieError = (0, cookies_1.validateCookieInput)(normalizedCookie);
|
|
72
|
+
if (cookieError) {
|
|
73
|
+
throw new Error(`${cookieError} Run \`ebag login --cookie "<cookie>"\` with a Cookie header value.`);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const session = {
|
|
77
|
+
cookies: normalizedCookie,
|
|
78
|
+
updatedAt: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
const user = await (0, auth_1.validateSession)(config, session);
|
|
81
|
+
const email = user.email ||
|
|
82
|
+
user.username;
|
|
83
|
+
if (!email) {
|
|
84
|
+
throw new Error('Session validated but no user email was returned.');
|
|
85
|
+
}
|
|
86
|
+
(0, config_1.saveSession)(session);
|
|
87
|
+
if (json) {
|
|
88
|
+
(0, format_1.outputJson)({ status: 'ok', user, email });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
process.stdout.write('Login session validated and saved.\n');
|
|
92
|
+
process.stdout.write(`Logged in as: ${email}\n`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (json) {
|
|
97
|
+
(0, format_1.outputJson)({ status: 'error', message: err.message });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
process.stderr.write(`Login failed: ${err.message}\n`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
program
|
|
105
|
+
.command('status')
|
|
106
|
+
.description('Show current login status')
|
|
107
|
+
.action(async () => {
|
|
108
|
+
const config = (0, config_1.loadConfig)();
|
|
109
|
+
const json = program.opts().json;
|
|
110
|
+
const session = (0, config_1.loadSession)();
|
|
111
|
+
if (!session.cookies) {
|
|
112
|
+
if (json) {
|
|
113
|
+
(0, format_1.outputJson)({ status: 'logged_out' });
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
process.stdout.write('Logged out (no session cookie).\n');
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const user = await (0, auth_1.validateSession)(config, session);
|
|
122
|
+
const email = user.email || user.username;
|
|
123
|
+
if (json) {
|
|
124
|
+
(0, format_1.outputJson)({ status: 'logged_in', user, email });
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
process.stdout.write('Logged in.\n');
|
|
128
|
+
if (email) {
|
|
129
|
+
process.stdout.write(`Email: ${email}\n`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const error = err;
|
|
135
|
+
if (error.status && [401, 403].includes(error.status)) {
|
|
136
|
+
if (json) {
|
|
137
|
+
(0, format_1.outputJson)({ status: 'logged_out' });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
process.stdout.write('Logged out.\n');
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
program
|
|
148
|
+
.command('slots')
|
|
149
|
+
.description('Show the next available delivery slots')
|
|
150
|
+
.option('--limit <n>', 'Limit number of slots', '10')
|
|
151
|
+
.action(async (options) => {
|
|
152
|
+
const config = (0, config_1.loadConfig)();
|
|
153
|
+
const session = requireSessionCookie();
|
|
154
|
+
const json = program.opts().json;
|
|
155
|
+
const limit = Number(options.limit);
|
|
156
|
+
const slotsPayload = await (0, orders_1.getTimeSlots)(config, session);
|
|
157
|
+
const slots = (0, slots_1.normalizeSlots)(slotsPayload).filter((slot) => slot.isAvailable);
|
|
158
|
+
slots.sort(slots_1.sortSlots);
|
|
159
|
+
const limited = slots.slice(0, Number.isFinite(limit) ? limit : 10);
|
|
160
|
+
if (json) {
|
|
161
|
+
(0, format_1.outputJson)({ slots: limited });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!limited.length) {
|
|
165
|
+
process.stdout.write('No available delivery slots.\n');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const today = new Date();
|
|
169
|
+
const todayDate = today.toISOString().slice(0, 10);
|
|
170
|
+
const tomorrow = new Date(today);
|
|
171
|
+
tomorrow.setDate(today.getDate() + 1);
|
|
172
|
+
const tomorrowDate = tomorrow.toISOString().slice(0, 10);
|
|
173
|
+
let currentDate = '';
|
|
174
|
+
let printedHeader = false;
|
|
175
|
+
for (const slot of limited) {
|
|
176
|
+
if (slot.date !== currentDate) {
|
|
177
|
+
currentDate = slot.date;
|
|
178
|
+
let label = currentDate;
|
|
179
|
+
if (currentDate === todayDate) {
|
|
180
|
+
label = `Today (${currentDate})`;
|
|
181
|
+
}
|
|
182
|
+
else if (currentDate === tomorrowDate) {
|
|
183
|
+
label = `Tomorrow (${currentDate})`;
|
|
184
|
+
}
|
|
185
|
+
const headerPrefix = printedHeader ? '\n' : '';
|
|
186
|
+
process.stdout.write(`${headerPrefix}${(0, format_1.formatHeading)(`# ${label}`)}\n`);
|
|
187
|
+
printedHeader = true;
|
|
188
|
+
}
|
|
189
|
+
process.stdout.write(`${(0, slots_1.formatSlotRange)(slot.start, slot.end)} (${(0, slots_1.formatLoadPercent)(slot.loadPercent)})\n`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
program
|
|
193
|
+
.command('search')
|
|
194
|
+
.description('Search for products')
|
|
195
|
+
.argument('<query>', 'Search query')
|
|
196
|
+
.option('--limit <n>', 'Limit number of results', '20')
|
|
197
|
+
.option('--page <n>', 'Algolia page number (0-based)', '0')
|
|
198
|
+
.action(async (query, options) => {
|
|
199
|
+
const config = (0, config_1.loadConfig)();
|
|
200
|
+
const session = (0, config_1.loadSession)();
|
|
201
|
+
const json = program.opts().json;
|
|
202
|
+
const limit = Number(options.limit);
|
|
203
|
+
const page = Number(options.page);
|
|
204
|
+
const result = await (0, search_1.searchProducts)(config, session, query, { limit, page });
|
|
205
|
+
if (json) {
|
|
206
|
+
(0, format_1.outputJson)(result);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
(0, format_1.outputProducts)(result.results);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
program
|
|
213
|
+
.command('product')
|
|
214
|
+
.description('Get product details by ID')
|
|
215
|
+
.argument('<productId>', 'Product ID')
|
|
216
|
+
.action(async (productId) => {
|
|
217
|
+
const config = (0, config_1.loadConfig)();
|
|
218
|
+
const session = (0, config_1.loadSession)();
|
|
219
|
+
const json = program.opts().json;
|
|
220
|
+
const result = await (0, products_1.getProductById)(config, session, Number(productId));
|
|
221
|
+
if (json) {
|
|
222
|
+
(0, format_1.outputJson)(result);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
(0, format_1.outputProductDetail)(result);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const order = program.command('order').description('Order operations');
|
|
229
|
+
order
|
|
230
|
+
.command('list')
|
|
231
|
+
.description('List recent orders')
|
|
232
|
+
.option('--limit <n>', 'Limit number of results', '10')
|
|
233
|
+
.option('--page <n>', 'Page number')
|
|
234
|
+
.option('--from <date>', 'Filter from date (YYYY-MM-DD)')
|
|
235
|
+
.option('--to <date>', 'Filter to date (YYYY-MM-DD)')
|
|
236
|
+
.action(async (options) => {
|
|
237
|
+
const config = (0, config_1.loadConfig)();
|
|
238
|
+
const session = requireSessionCookie();
|
|
239
|
+
const json = program.opts().json;
|
|
240
|
+
const limit = Number(options.limit);
|
|
241
|
+
const page = options.page ? Number(options.page) : undefined;
|
|
242
|
+
const from = options.from;
|
|
243
|
+
const to = options.to;
|
|
244
|
+
const result = await (0, orders_1.listOrders)(config, session, {
|
|
245
|
+
limit: Number.isFinite(limit) ? limit : 10,
|
|
246
|
+
page: page !== undefined && Number.isFinite(page) ? page : undefined,
|
|
247
|
+
from,
|
|
248
|
+
to,
|
|
249
|
+
});
|
|
250
|
+
if (json) {
|
|
251
|
+
(0, format_1.outputJson)(result);
|
|
252
|
+
}
|
|
253
|
+
else if (!result.results.length) {
|
|
254
|
+
process.stdout.write('No orders found.\n');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
(0, format_1.outputOrdersList)(result.results);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
order
|
|
261
|
+
.command('show')
|
|
262
|
+
.description('Show order details')
|
|
263
|
+
.argument('<orderId>', 'Order ID')
|
|
264
|
+
.action(async (orderId) => {
|
|
265
|
+
const config = (0, config_1.loadConfig)();
|
|
266
|
+
const session = requireSessionCookie();
|
|
267
|
+
const json = program.opts().json;
|
|
268
|
+
const detail = await (0, orders_1.getOrderDetail)(config, session, String(orderId));
|
|
269
|
+
if (json) {
|
|
270
|
+
(0, format_1.outputJson)(detail);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
(0, format_1.outputOrderDetail)(detail);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
const cart = program.command('cart').description('Cart operations');
|
|
277
|
+
cart
|
|
278
|
+
.command('add')
|
|
279
|
+
.argument('<productId>', 'Product ID')
|
|
280
|
+
.option('--qty <n>', 'Quantity', '1')
|
|
281
|
+
.action(async (productId, options) => {
|
|
282
|
+
const config = (0, config_1.loadConfig)();
|
|
283
|
+
const session = requireSessionCookie();
|
|
284
|
+
const json = program.opts().json;
|
|
285
|
+
const qty = Number(options.qty);
|
|
286
|
+
const result = await (0, cart_1.addToCart)(config, session, Number(productId), qty);
|
|
287
|
+
if (json) {
|
|
288
|
+
(0, format_1.outputJson)(result);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
process.stdout.write('Added to cart.\n');
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
cart
|
|
295
|
+
.command('update')
|
|
296
|
+
.argument('<productId>', 'Product ID')
|
|
297
|
+
.option('--qty <n>', 'Quantity', '1')
|
|
298
|
+
.action(async (productId, options) => {
|
|
299
|
+
const config = (0, config_1.loadConfig)();
|
|
300
|
+
const session = requireSessionCookie();
|
|
301
|
+
const json = program.opts().json;
|
|
302
|
+
const qty = Number(options.qty);
|
|
303
|
+
const result = await (0, cart_1.updateCart)(config, session, Number(productId), qty);
|
|
304
|
+
if (json) {
|
|
305
|
+
(0, format_1.outputJson)(result);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
process.stdout.write('Cart updated.\n');
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
cart
|
|
312
|
+
.command('show')
|
|
313
|
+
.description('Show cart contents')
|
|
314
|
+
.action(async () => {
|
|
315
|
+
const config = (0, config_1.loadConfig)();
|
|
316
|
+
const session = requireSessionCookie();
|
|
317
|
+
const json = program.opts().json;
|
|
318
|
+
const cartData = await (0, cart_1.getCart)(config, session);
|
|
319
|
+
if (json) {
|
|
320
|
+
(0, format_1.outputJson)(cartData);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const items = Array.isArray(cartData.items)
|
|
324
|
+
? cartData.items
|
|
325
|
+
: [];
|
|
326
|
+
const listItems = items
|
|
327
|
+
.map((item) => {
|
|
328
|
+
const entry = item;
|
|
329
|
+
const id = entry.product?.id ?? entry.product_id ?? entry.productId ?? entry.id;
|
|
330
|
+
const name = entry.product?.name ?? entry.name ?? 'Unknown';
|
|
331
|
+
const count = entry.quantity ?? entry.qty;
|
|
332
|
+
if (!id)
|
|
333
|
+
return null;
|
|
334
|
+
return { id: Number(id), name, count };
|
|
335
|
+
})
|
|
336
|
+
.filter(Boolean);
|
|
337
|
+
if (listItems.length) {
|
|
338
|
+
(0, format_1.outputList)(listItems);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
process.stdout.write('Cart is empty.\n');
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
const list = program.command('list').description('List operations');
|
|
345
|
+
list
|
|
346
|
+
.command('show')
|
|
347
|
+
.description('Show your lists')
|
|
348
|
+
.argument('[listId]', 'List ID')
|
|
349
|
+
.action(async (listId) => {
|
|
350
|
+
const config = (0, config_1.loadConfig)();
|
|
351
|
+
const session = requireSessionCookie();
|
|
352
|
+
const json = program.opts().json;
|
|
353
|
+
const lists = await (0, lists_1.getLists)(config, session);
|
|
354
|
+
if (listId) {
|
|
355
|
+
const listEntry = lists.find((item) => Number(item.id) === Number(listId));
|
|
356
|
+
if (!listEntry) {
|
|
357
|
+
throw new Error(`List ${listId} not found.`);
|
|
358
|
+
}
|
|
359
|
+
const listItems = await (0, lists_1.getListItems)(config, session, Number(listId));
|
|
360
|
+
const results = Array.isArray(listItems.results)
|
|
361
|
+
? listItems.results
|
|
362
|
+
: [];
|
|
363
|
+
const count = typeof listItems.count === 'number'
|
|
364
|
+
? listItems.count
|
|
365
|
+
: results.length;
|
|
366
|
+
if (json) {
|
|
367
|
+
(0, format_1.outputJson)({
|
|
368
|
+
...listEntry,
|
|
369
|
+
count,
|
|
370
|
+
items: results,
|
|
371
|
+
next: listItems.next ?? null,
|
|
372
|
+
previous: listItems.previous ?? null,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
process.stdout.write(`${listEntry.name} (${listEntry.id})`);
|
|
377
|
+
if (!count) {
|
|
378
|
+
process.stdout.write(' - empty\n');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
process.stdout.write(` - ${count} items\n`);
|
|
382
|
+
const outputItems = results
|
|
383
|
+
.map((item) => {
|
|
384
|
+
const entry = item;
|
|
385
|
+
const product = entry.product;
|
|
386
|
+
const id = product?.id ?? entry.product_id ?? entry.productId ?? entry.id;
|
|
387
|
+
const name = product?.name ?? entry.name ?? 'Unknown';
|
|
388
|
+
const itemCount = entry.quantity ?? entry.qty;
|
|
389
|
+
if (!id)
|
|
390
|
+
return null;
|
|
391
|
+
return { id: Number(id), name, count: itemCount };
|
|
392
|
+
})
|
|
393
|
+
.filter(Boolean);
|
|
394
|
+
(0, format_1.outputList)(outputItems);
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (json) {
|
|
399
|
+
(0, format_1.outputJson)(lists);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
(0, format_1.outputList)(lists.map((item) => ({
|
|
403
|
+
id: item.id,
|
|
404
|
+
name: item.name,
|
|
405
|
+
count: item.products?.length,
|
|
406
|
+
})));
|
|
407
|
+
});
|
|
408
|
+
list
|
|
409
|
+
.command('add')
|
|
410
|
+
.argument('<listId>', 'List ID')
|
|
411
|
+
.argument('<productId>', 'Product ID')
|
|
412
|
+
.option('--qty <n>', 'Quantity', '1')
|
|
413
|
+
.action(async (listId, productId, options) => {
|
|
414
|
+
const config = (0, config_1.loadConfig)();
|
|
415
|
+
const session = requireSessionCookie();
|
|
416
|
+
const json = program.opts().json;
|
|
417
|
+
const qty = Number(options.qty);
|
|
418
|
+
const result = await (0, lists_1.addToList)(config, session, Number(listId), Number(productId), qty);
|
|
419
|
+
if (json) {
|
|
420
|
+
(0, format_1.outputJson)(result);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
process.stdout.write('Added to list.\n');
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
try {
|
|
427
|
+
await program.parseAsync(process.argv);
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
const json = program.opts().json;
|
|
431
|
+
if (json) {
|
|
432
|
+
(0, format_1.outputJson)({ status: 'error', ...formatError(err) });
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const details = formatError(err);
|
|
436
|
+
process.stderr.write(`${details.message}\n`);
|
|
437
|
+
if (details.status) {
|
|
438
|
+
process.stderr.write(`Status: ${details.status}\n`);
|
|
439
|
+
}
|
|
440
|
+
if (details.body) {
|
|
441
|
+
process.stderr.write(`Body: ${details.body}\n`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
main();
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLoginInstructions = getLoginInstructions;
|
|
4
|
+
exports.validateSession = validateSession;
|
|
5
|
+
const client_1 = require("./client");
|
|
6
|
+
function getLoginInstructions() {
|
|
7
|
+
return [
|
|
8
|
+
'1) Log in to https://www.ebag.bg in your browser.',
|
|
9
|
+
'2) Open DevTools > Network and click any request to https://www.ebag.bg (not a static asset).',
|
|
10
|
+
'3) In the request headers, copy the full Cookie header value (semicolon-separated).',
|
|
11
|
+
'4) Run: ebag login --cookie "<cookie>"',
|
|
12
|
+
].join('\n');
|
|
13
|
+
}
|
|
14
|
+
async function validateSession(config, session) {
|
|
15
|
+
const result = await (0, client_1.requestEbag)(config, session, '/user/json');
|
|
16
|
+
return result.data;
|
|
17
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Config, Session } from './types';
|
|
2
|
+
export declare function addToCart(config: Config, session: Session, productId: number, quantity: number, unitTypeOverride?: string): Promise<unknown>;
|
|
3
|
+
export declare function updateCart(config: Config, session: Session, productId: number, quantity: number, unitTypeOverride?: string): Promise<unknown>;
|
|
4
|
+
export declare function getCart(config: Config, session: Session): Promise<unknown>;
|
package/dist/lib/cart.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addToCart = addToCart;
|
|
4
|
+
exports.updateCart = updateCart;
|
|
5
|
+
exports.getCart = getCart;
|
|
6
|
+
const client_1 = require("./client");
|
|
7
|
+
async function addToCart(config, session, productId, quantity, unitTypeOverride = 'false') {
|
|
8
|
+
const baseUrl = config.baseUrl || 'https://www.ebag.bg';
|
|
9
|
+
const body = new URLSearchParams({
|
|
10
|
+
product_id: String(productId),
|
|
11
|
+
quantity: String(quantity),
|
|
12
|
+
unit_type_override: unitTypeOverride,
|
|
13
|
+
});
|
|
14
|
+
const result = await (0, client_1.requestEbag)(config, session, '/cart/add', {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
18
|
+
origin: baseUrl,
|
|
19
|
+
referer: `${baseUrl}/search/`,
|
|
20
|
+
},
|
|
21
|
+
body,
|
|
22
|
+
});
|
|
23
|
+
return result.data;
|
|
24
|
+
}
|
|
25
|
+
async function updateCart(config, session, productId, quantity, unitTypeOverride = 'false') {
|
|
26
|
+
const baseUrl = config.baseUrl || 'https://www.ebag.bg';
|
|
27
|
+
const body = new URLSearchParams({
|
|
28
|
+
product_id: String(productId),
|
|
29
|
+
quantity: String(quantity),
|
|
30
|
+
unit_type_override: unitTypeOverride,
|
|
31
|
+
});
|
|
32
|
+
const result = await (0, client_1.requestEbag)(config, session, '/cart/update', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
36
|
+
origin: baseUrl,
|
|
37
|
+
referer: `${baseUrl}/search/`,
|
|
38
|
+
},
|
|
39
|
+
body,
|
|
40
|
+
});
|
|
41
|
+
return result.data;
|
|
42
|
+
}
|
|
43
|
+
async function getCart(config, session) {
|
|
44
|
+
const result = await (0, client_1.requestEbag)(config, session, '/cart/json');
|
|
45
|
+
return result.data;
|
|
46
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config, Session } from './types';
|
|
2
|
+
export type RequestResult<T> = {
|
|
3
|
+
status: number;
|
|
4
|
+
data: T;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
export declare function requestEbag<T>(config: Config, session: Session, path: string, options?: {
|
|
8
|
+
method?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
body?: string | URLSearchParams;
|
|
11
|
+
query?: Record<string, string | number | undefined>;
|
|
12
|
+
}): Promise<RequestResult<T>>;
|
|
13
|
+
export declare function requestAlgolia<T>(config: Config, body: unknown): Promise<RequestResult<T>>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestEbag = requestEbag;
|
|
4
|
+
exports.requestAlgolia = requestAlgolia;
|
|
5
|
+
function normalizeHeaders(headers) {
|
|
6
|
+
const out = {};
|
|
7
|
+
headers.forEach((value, key) => {
|
|
8
|
+
out[key] = value;
|
|
9
|
+
});
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
function getCookieValue(cookies, name) {
|
|
13
|
+
if (!cookies)
|
|
14
|
+
return undefined;
|
|
15
|
+
const parts = cookies.split(';').map((part) => part.trim());
|
|
16
|
+
for (const part of parts) {
|
|
17
|
+
if (part.startsWith(`${name}=`)) {
|
|
18
|
+
return part.slice(name.length + 1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
async function requestEbag(config, session, path, options = {}) {
|
|
24
|
+
const baseUrl = config.baseUrl || 'https://www.ebag.bg';
|
|
25
|
+
const url = new URL(path, baseUrl);
|
|
26
|
+
if (options.query) {
|
|
27
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
28
|
+
if (value !== undefined) {
|
|
29
|
+
url.searchParams.set(key, String(value));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const headers = {
|
|
34
|
+
accept: 'application/json, text/plain, */*',
|
|
35
|
+
...options.headers,
|
|
36
|
+
};
|
|
37
|
+
if (session.cookies) {
|
|
38
|
+
headers.cookie = session.cookies;
|
|
39
|
+
}
|
|
40
|
+
const method = (options.method || 'GET').toUpperCase();
|
|
41
|
+
if (!headers['x-csrftoken'] && method !== 'GET') {
|
|
42
|
+
const token = getCookieValue(session.cookies, 'csrftoken');
|
|
43
|
+
if (token) {
|
|
44
|
+
headers['x-csrftoken'] = token;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const response = await fetch(url.toString(), {
|
|
48
|
+
method,
|
|
49
|
+
headers,
|
|
50
|
+
body: options.body instanceof URLSearchParams ? options.body.toString() : options.body,
|
|
51
|
+
});
|
|
52
|
+
const contentType = response.headers.get('content-type') || '';
|
|
53
|
+
const raw = await response.text();
|
|
54
|
+
const data = contentType.includes('application/json') ? JSON.parse(raw) : raw;
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const err = new Error(`Request failed ${response.status} ${url}`);
|
|
57
|
+
err.status = response.status;
|
|
58
|
+
err.body = raw;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
status: response.status,
|
|
63
|
+
data,
|
|
64
|
+
headers: normalizeHeaders(response.headers),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function requestAlgolia(config, body) {
|
|
68
|
+
const host = config.algolia?.host || 'jmjmdq9hhx-dsn.algolia.net';
|
|
69
|
+
const appId = config.algolia?.appId || 'JMJMDQ9HHX';
|
|
70
|
+
const apiKey = config.algolia?.apiKey || '42ca9458d9354298c7016ce9155d8481';
|
|
71
|
+
const url = new URL(`/1/indexes/*/queries`, `https://${host}`);
|
|
72
|
+
url.searchParams.set('x-algolia-agent', 'Algolia for JavaScript (4.24.0); Browser; instantsearch.js (4.80.0)');
|
|
73
|
+
const response = await fetch(url.toString(), {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'x-algolia-application-id': appId,
|
|
77
|
+
'x-algolia-api-key': apiKey,
|
|
78
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify(body),
|
|
81
|
+
});
|
|
82
|
+
const contentType = response.headers.get('content-type') || '';
|
|
83
|
+
const raw = await response.text();
|
|
84
|
+
const data = contentType.includes('application/json') ? JSON.parse(raw) : raw;
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
const err = new Error(`Algolia request failed ${response.status}`);
|
|
87
|
+
err.status = response.status;
|
|
88
|
+
err.body = raw;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
status: response.status,
|
|
93
|
+
data,
|
|
94
|
+
headers: normalizeHeaders(response.headers),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Cache, Config, Session } from './types';
|
|
2
|
+
export declare function getConfigDir(): string;
|
|
3
|
+
export declare function getConfigPath(): string;
|
|
4
|
+
export declare function getSessionPath(): string;
|
|
5
|
+
export declare function getCachePath(): string;
|
|
6
|
+
export declare function loadConfig(): Config;
|
|
7
|
+
export declare function saveConfig(config: Config): void;
|
|
8
|
+
export declare function loadSession(): Session;
|
|
9
|
+
export declare function saveSession(session: Session): void;
|
|
10
|
+
export declare function loadCache(): Cache;
|
|
11
|
+
export declare function saveCache(cache: Cache): void;
|