ebag 0.0.2 → 0.1.1
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 +26 -10
- package/dist/cli/format.d.ts +1 -1
- package/dist/cli/format.js +136 -115
- package/dist/cli/index.js +138 -79
- package/dist/lib/auth.d.ts +1 -1
- package/dist/lib/auth.js +5 -5
- package/dist/lib/cart.d.ts +1 -1
- package/dist/lib/cart.js +11 -11
- package/dist/lib/client.d.ts +1 -1
- package/dist/lib/client.js +30 -20
- package/dist/lib/config.d.ts +2 -1
- package/dist/lib/config.js +22 -13
- package/dist/lib/cookies.js +10 -7
- package/dist/lib/index.d.ts +9 -9
- package/dist/lib/lists.d.ts +1 -1
- package/dist/lib/lists.js +4 -4
- package/dist/lib/log.d.ts +8 -0
- package/dist/lib/log.js +115 -0
- package/dist/lib/orders.d.ts +1 -1
- package/dist/lib/orders.js +47 -29
- package/dist/lib/products.d.ts +1 -1
- package/dist/lib/search.d.ts +1 -1
- package/dist/lib/search.js +26 -20
- package/dist/lib/slots.d.ts +1 -1
- package/dist/lib/slots.js +3 -3
- package/dist/lib/types.d.ts +1 -1
- package/package.json +5 -2
package/dist/lib/config.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.getConfigDir = getConfigDir;
|
|
|
7
7
|
exports.getConfigPath = getConfigPath;
|
|
8
8
|
exports.getSessionPath = getSessionPath;
|
|
9
9
|
exports.getCachePath = getCachePath;
|
|
10
|
+
exports.getLogPath = getLogPath;
|
|
10
11
|
exports.loadConfig = loadConfig;
|
|
11
12
|
exports.saveConfig = saveConfig;
|
|
12
13
|
exports.loadSession = loadSession;
|
|
@@ -17,11 +18,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
17
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
19
|
const node_os_1 = __importDefault(require("node:os"));
|
|
19
20
|
const DEFAULT_CONFIG = {
|
|
20
|
-
baseUrl:
|
|
21
|
+
baseUrl: "https://www.ebag.bg",
|
|
21
22
|
algolia: {
|
|
22
|
-
appId:
|
|
23
|
-
apiKey:
|
|
24
|
-
host:
|
|
23
|
+
appId: "JMJMDQ9HHX",
|
|
24
|
+
apiKey: "42ca9458d9354298c7016ce9155d8481",
|
|
25
|
+
host: "jmjmdq9hhx-dsn.algolia.net",
|
|
25
26
|
},
|
|
26
27
|
};
|
|
27
28
|
function ensureDir(dir) {
|
|
@@ -32,11 +33,11 @@ function getConfigDir() {
|
|
|
32
33
|
if (override) {
|
|
33
34
|
return override;
|
|
34
35
|
}
|
|
35
|
-
return node_path_1.default.join(node_os_1.default.homedir(),
|
|
36
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".config", "ebag");
|
|
36
37
|
}
|
|
37
38
|
function readJsonFile(filePath, fallback) {
|
|
38
39
|
try {
|
|
39
|
-
const raw = node_fs_1.default.readFileSync(filePath,
|
|
40
|
+
const raw = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
40
41
|
return JSON.parse(raw);
|
|
41
42
|
}
|
|
42
43
|
catch {
|
|
@@ -45,26 +46,34 @@ function readJsonFile(filePath, fallback) {
|
|
|
45
46
|
}
|
|
46
47
|
function writeJsonFile(filePath, data) {
|
|
47
48
|
ensureDir(node_path_1.default.dirname(filePath));
|
|
48
|
-
node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2),
|
|
49
|
+
node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
49
50
|
}
|
|
50
51
|
function writeSessionFile(filePath, data) {
|
|
51
52
|
ensureDir(node_path_1.default.dirname(filePath));
|
|
52
|
-
node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), {
|
|
53
|
+
node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), {
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
mode: 0o600,
|
|
56
|
+
});
|
|
53
57
|
}
|
|
54
58
|
function getConfigPath() {
|
|
55
|
-
return node_path_1.default.join(getConfigDir(),
|
|
59
|
+
return node_path_1.default.join(getConfigDir(), "config.json");
|
|
56
60
|
}
|
|
57
61
|
function getSessionPath() {
|
|
58
|
-
return node_path_1.default.join(getConfigDir(),
|
|
62
|
+
return node_path_1.default.join(getConfigDir(), "session.json");
|
|
59
63
|
}
|
|
60
64
|
function getCachePath() {
|
|
61
|
-
return node_path_1.default.join(getConfigDir(),
|
|
65
|
+
return node_path_1.default.join(getConfigDir(), "cache.json");
|
|
66
|
+
}
|
|
67
|
+
function getLogPath() {
|
|
68
|
+
return node_path_1.default.join(getConfigDir(), "ebag.log");
|
|
62
69
|
}
|
|
63
70
|
function loadConfig() {
|
|
64
71
|
const stored = readJsonFile(getConfigPath(), {});
|
|
65
72
|
const algolia = {
|
|
66
|
-
appId: stored.algolia?.appId ?? DEFAULT_CONFIG.algolia?.appId ??
|
|
67
|
-
apiKey: stored.algolia?.apiKey ??
|
|
73
|
+
appId: stored.algolia?.appId ?? DEFAULT_CONFIG.algolia?.appId ?? "JMJMDQ9HHX",
|
|
74
|
+
apiKey: stored.algolia?.apiKey ??
|
|
75
|
+
DEFAULT_CONFIG.algolia?.apiKey ??
|
|
76
|
+
"42ca9458d9354298c7016ce9155d8481",
|
|
68
77
|
host: stored.algolia?.host ?? DEFAULT_CONFIG.algolia?.host,
|
|
69
78
|
};
|
|
70
79
|
return {
|
package/dist/lib/cookies.js
CHANGED
|
@@ -4,24 +4,27 @@ exports.normalizeCookieInput = normalizeCookieInput;
|
|
|
4
4
|
exports.validateCookieInput = validateCookieInput;
|
|
5
5
|
function normalizeCookieInput(value) {
|
|
6
6
|
const trimmed = value.trim();
|
|
7
|
-
if (trimmed.toLowerCase().startsWith(
|
|
8
|
-
return trimmed.slice(
|
|
7
|
+
if (trimmed.toLowerCase().startsWith("cookie:")) {
|
|
8
|
+
return trimmed.slice("cookie:".length).trim();
|
|
9
9
|
}
|
|
10
10
|
return trimmed;
|
|
11
11
|
}
|
|
12
12
|
function validateCookieInput(value) {
|
|
13
13
|
if (!value) {
|
|
14
|
-
return
|
|
14
|
+
return "Cookie value is empty.";
|
|
15
15
|
}
|
|
16
|
-
if (value.includes(
|
|
17
|
-
return
|
|
16
|
+
if (value.includes("\n") || value.includes("\r")) {
|
|
17
|
+
return "Cookie value should be a single header line without newlines.";
|
|
18
18
|
}
|
|
19
|
-
const parts = value
|
|
19
|
+
const parts = value
|
|
20
|
+
.split(";")
|
|
21
|
+
.map((part) => part.trim())
|
|
22
|
+
.filter(Boolean);
|
|
20
23
|
if (parts.length === 0) {
|
|
21
24
|
return 'Cookie value should look like "name=value" pairs from the Cookie header.';
|
|
22
25
|
}
|
|
23
26
|
for (const part of parts) {
|
|
24
|
-
const eqIndex = part.indexOf(
|
|
27
|
+
const eqIndex = part.indexOf("=");
|
|
25
28
|
if (eqIndex <= 0 || eqIndex === part.length - 1) {
|
|
26
29
|
return 'Cookie value should look like "name=value" pairs from the Cookie header.';
|
|
27
30
|
}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
1
|
+
export * from "./auth";
|
|
2
|
+
export * from "./cart";
|
|
3
|
+
export * from "./client";
|
|
4
|
+
export * from "./config";
|
|
5
|
+
export * from "./lists";
|
|
6
|
+
export * from "./orders";
|
|
7
|
+
export * from "./products";
|
|
8
|
+
export * from "./search";
|
|
9
|
+
export * from "./types";
|
package/dist/lib/lists.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Config, ListSummary, Session } from
|
|
1
|
+
import type { Config, ListSummary, Session } from "./types";
|
|
2
2
|
export declare function getLists(config: Config, session: Session): Promise<ListSummary[]>;
|
|
3
3
|
export declare function addToList(config: Config, session: Session, listId: number, productId: number, quantity: number): Promise<unknown>;
|
|
4
4
|
export declare function getListItems(config: Config, session: Session, listId: number, page?: number): Promise<Record<string, unknown>>;
|
package/dist/lib/lists.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.addToList = addToList;
|
|
|
5
5
|
exports.getListItems = getListItems;
|
|
6
6
|
const client_1 = require("./client");
|
|
7
7
|
async function getLists(config, session) {
|
|
8
|
-
const result = await (0, client_1.requestEbag)(config, session,
|
|
8
|
+
const result = await (0, client_1.requestEbag)(config, session, "/lists/json");
|
|
9
9
|
return result.data.map((list) => ({
|
|
10
10
|
id: list.id,
|
|
11
11
|
name: list.name,
|
|
@@ -19,15 +19,15 @@ async function getLists(config, session) {
|
|
|
19
19
|
}));
|
|
20
20
|
}
|
|
21
21
|
async function addToList(config, session, listId, productId, quantity) {
|
|
22
|
-
const baseUrl = config.baseUrl ||
|
|
22
|
+
const baseUrl = config.baseUrl || "https://www.ebag.bg";
|
|
23
23
|
const body = new URLSearchParams({
|
|
24
24
|
product_id: String(productId),
|
|
25
25
|
quantity: String(quantity),
|
|
26
26
|
});
|
|
27
27
|
const result = await (0, client_1.requestEbag)(config, session, `/lists/${listId}/items/update`, {
|
|
28
|
-
method:
|
|
28
|
+
method: "POST",
|
|
29
29
|
headers: {
|
|
30
|
-
|
|
30
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
31
31
|
origin: baseUrl,
|
|
32
32
|
referer: `${baseUrl}/search/`,
|
|
33
33
|
},
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type LogEntry = {
|
|
2
|
+
event: string;
|
|
3
|
+
level?: "info" | "error";
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
export declare function redactHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined;
|
|
7
|
+
export declare function sanitizeEntry(entry: Record<string, unknown>): Record<string, unknown>;
|
|
8
|
+
export declare function appendLog(entry: LogEntry): void;
|
package/dist/lib/log.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.redactHeaders = redactHeaders;
|
|
7
|
+
exports.sanitizeEntry = sanitizeEntry;
|
|
8
|
+
exports.appendLog = appendLog;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
const SENSITIVE_KEYS = ["cookie", "authorization", "x-csrftoken"];
|
|
13
|
+
function ensureDir(dir) {
|
|
14
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
function isSensitiveKey(key) {
|
|
17
|
+
const lower = key.toLowerCase();
|
|
18
|
+
return SENSITIVE_KEYS.some((token) => lower === token || lower.includes(token));
|
|
19
|
+
}
|
|
20
|
+
function looksLikeCookie(value) {
|
|
21
|
+
if (value.includes("csrftoken=") ||
|
|
22
|
+
value.includes("sessionid=") ||
|
|
23
|
+
value.includes("cookie=")) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return value.includes("=") && value.includes(";");
|
|
27
|
+
}
|
|
28
|
+
function redactHeaders(headers) {
|
|
29
|
+
if (!headers)
|
|
30
|
+
return undefined;
|
|
31
|
+
const redacted = {};
|
|
32
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
33
|
+
redacted[key] = isSensitiveKey(key) ? "[redacted]" : value;
|
|
34
|
+
}
|
|
35
|
+
return redacted;
|
|
36
|
+
}
|
|
37
|
+
function sanitizeValue(value, seen) {
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
return looksLikeCookie(value) ? "[redacted]" : value;
|
|
40
|
+
}
|
|
41
|
+
if (!value || typeof value !== "object") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
if (seen.has(value)) {
|
|
45
|
+
return "[circular]";
|
|
46
|
+
}
|
|
47
|
+
seen.add(value);
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return value.map((item) => sanitizeValue(item, seen));
|
|
50
|
+
}
|
|
51
|
+
const output = {};
|
|
52
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
53
|
+
if (isSensitiveKey(key)) {
|
|
54
|
+
output[key] = "[redacted]";
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
output[key] = sanitizeValue(entry, seen);
|
|
58
|
+
}
|
|
59
|
+
return output;
|
|
60
|
+
}
|
|
61
|
+
function sanitizeEntry(entry) {
|
|
62
|
+
const seen = new WeakSet();
|
|
63
|
+
return sanitizeValue(entry, seen);
|
|
64
|
+
}
|
|
65
|
+
function formatValue(value) {
|
|
66
|
+
return JSON.stringify(value);
|
|
67
|
+
}
|
|
68
|
+
function formatEntry(entry) {
|
|
69
|
+
const base = {
|
|
70
|
+
ts: new Date().toISOString(),
|
|
71
|
+
...entry,
|
|
72
|
+
};
|
|
73
|
+
const orderedKeys = [
|
|
74
|
+
"ts",
|
|
75
|
+
"level",
|
|
76
|
+
"event",
|
|
77
|
+
"command",
|
|
78
|
+
"args",
|
|
79
|
+
"json",
|
|
80
|
+
"pid",
|
|
81
|
+
"ppid",
|
|
82
|
+
"cwd",
|
|
83
|
+
"node",
|
|
84
|
+
"configDir",
|
|
85
|
+
"exitCode",
|
|
86
|
+
"durationMs",
|
|
87
|
+
];
|
|
88
|
+
const seen = new Set(orderedKeys);
|
|
89
|
+
const pairs = [];
|
|
90
|
+
for (const key of orderedKeys) {
|
|
91
|
+
if (base[key] !== undefined) {
|
|
92
|
+
pairs.push(`${key}=${formatValue(base[key])}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const key of Object.keys(base).sort()) {
|
|
96
|
+
if (seen.has(key))
|
|
97
|
+
continue;
|
|
98
|
+
const value = base[key];
|
|
99
|
+
if (value !== undefined) {
|
|
100
|
+
pairs.push(`${key}=${formatValue(value)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return pairs.join(" ");
|
|
104
|
+
}
|
|
105
|
+
function appendLog(entry) {
|
|
106
|
+
try {
|
|
107
|
+
const logPath = (0, config_1.getLogPath)();
|
|
108
|
+
ensureDir(node_path_1.default.dirname(logPath));
|
|
109
|
+
const line = formatEntry(sanitizeEntry(entry));
|
|
110
|
+
node_fs_1.default.appendFileSync(logPath, `${line}\n`, "utf8");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Logging must never crash the CLI.
|
|
114
|
+
}
|
|
115
|
+
}
|
package/dist/lib/orders.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Config, OrderDetail, OrderSummary, Session } from
|
|
1
|
+
import type { Config, OrderDetail, OrderSummary, Session } from "./types";
|
|
2
2
|
export declare function getTimeSlots(config: Config, session: Session): Promise<Record<string, unknown>>;
|
|
3
3
|
export declare function listOrders(config: Config, session: Session, options?: {
|
|
4
4
|
limit?: number;
|
package/dist/lib/orders.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.getOrderDetail = getOrderDetail;
|
|
|
6
6
|
const config_1 = require("./config");
|
|
7
7
|
const client_1 = require("./client");
|
|
8
8
|
async function getTimeSlots(config, session) {
|
|
9
|
-
const result = await (0, client_1.requestEbag)(config, session,
|
|
9
|
+
const result = await (0, client_1.requestEbag)(config, session, "/orders/get-time-slots");
|
|
10
10
|
return result.data;
|
|
11
11
|
}
|
|
12
12
|
function normalizeDate(value) {
|
|
@@ -24,25 +24,27 @@ function normalizeDate(value) {
|
|
|
24
24
|
const parsed = new Date(value);
|
|
25
25
|
if (Number.isNaN(parsed.getTime()))
|
|
26
26
|
return undefined;
|
|
27
|
-
const pad = (n) => String(n).padStart(2,
|
|
27
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
28
28
|
return `${parsed.getFullYear()}-${pad(parsed.getMonth() + 1)}-${pad(parsed.getDate())}`;
|
|
29
29
|
}
|
|
30
30
|
function normalizeOrderSummary(entry) {
|
|
31
31
|
const parseNumber = (value) => {
|
|
32
|
-
if (typeof value ===
|
|
32
|
+
if (typeof value === "number")
|
|
33
33
|
return value;
|
|
34
|
-
if (typeof value ===
|
|
34
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
35
35
|
const parsed = Number(value);
|
|
36
36
|
return Number.isNaN(parsed) ? undefined : parsed;
|
|
37
37
|
}
|
|
38
38
|
return undefined;
|
|
39
39
|
};
|
|
40
40
|
return {
|
|
41
|
-
id: entry.encrypted_id ? String(entry.encrypted_id) :
|
|
41
|
+
id: entry.encrypted_id ? String(entry.encrypted_id) : "",
|
|
42
42
|
shippingDate: entry.shipping_date ? String(entry.shipping_date) : undefined,
|
|
43
43
|
timeSlotStart: parseNumber(entry.time_slot_start),
|
|
44
44
|
timeSlotEnd: parseNumber(entry.time_slot_end),
|
|
45
|
-
timeSlotDisplay: entry.time_slot_display
|
|
45
|
+
timeSlotDisplay: entry.time_slot_display
|
|
46
|
+
? String(entry.time_slot_display)
|
|
47
|
+
: undefined,
|
|
46
48
|
status: parseNumber(entry.order_status),
|
|
47
49
|
statusText: entry.order_status_pharmacy
|
|
48
50
|
? String(entry.order_status_pharmacy)
|
|
@@ -50,8 +52,10 @@ function normalizeOrderSummary(entry) {
|
|
|
50
52
|
? String(entry.order_status_text)
|
|
51
53
|
: null,
|
|
52
54
|
finalAmount: entry.final_amount ? String(entry.final_amount) : undefined,
|
|
53
|
-
finalAmountEur: entry.final_amount_eur
|
|
54
|
-
|
|
55
|
+
finalAmountEur: entry.final_amount_eur
|
|
56
|
+
? String(entry.final_amount_eur)
|
|
57
|
+
: undefined,
|
|
58
|
+
additionalOrdersCount: typeof entry.additional_orders_count === "number"
|
|
55
59
|
? entry.additional_orders_count
|
|
56
60
|
: entry.additional_orders_count !== undefined
|
|
57
61
|
? Number(entry.additional_orders_count)
|
|
@@ -64,26 +68,27 @@ function normalizeGroupedItems(groupedItems) {
|
|
|
64
68
|
return [];
|
|
65
69
|
const items = [];
|
|
66
70
|
for (const group of groupedItems) {
|
|
67
|
-
if (!group || typeof group !==
|
|
71
|
+
if (!group || typeof group !== "object")
|
|
68
72
|
continue;
|
|
69
73
|
const groupName = group.group_name;
|
|
70
74
|
const groupItems = group.group_items || [];
|
|
71
75
|
for (const entry of groupItems) {
|
|
72
|
-
if (!entry || typeof entry !==
|
|
76
|
+
if (!entry || typeof entry !== "object")
|
|
73
77
|
continue;
|
|
74
78
|
const item = entry;
|
|
75
79
|
const product = item.product || undefined;
|
|
76
|
-
const productSaved = item.product_saved ||
|
|
80
|
+
const productSaved = item.product_saved ||
|
|
81
|
+
undefined;
|
|
77
82
|
const name = product?.name ||
|
|
78
83
|
item.product_saved_name ||
|
|
79
84
|
productSaved?.name_bg ||
|
|
80
85
|
productSaved?.name_en ||
|
|
81
|
-
|
|
82
|
-
const id = typeof product?.id ===
|
|
86
|
+
"Unknown";
|
|
87
|
+
const id = typeof product?.id === "number"
|
|
83
88
|
? product.id
|
|
84
89
|
: product?.id
|
|
85
90
|
? Number(product.id)
|
|
86
|
-
: typeof productSaved?.id ===
|
|
91
|
+
: typeof productSaved?.id === "number"
|
|
87
92
|
? productSaved.id
|
|
88
93
|
: productSaved?.id
|
|
89
94
|
? Number(productSaved.id)
|
|
@@ -92,11 +97,17 @@ function normalizeGroupedItems(groupedItems) {
|
|
|
92
97
|
id,
|
|
93
98
|
name,
|
|
94
99
|
quantity: item.quantity ? String(item.quantity) : undefined,
|
|
95
|
-
unit: product?.unit_weight_text
|
|
100
|
+
unit: product?.unit_weight_text
|
|
101
|
+
? String(product.unit_weight_text)
|
|
102
|
+
: undefined,
|
|
96
103
|
price: item.price ? String(item.price) : undefined,
|
|
97
104
|
priceEur: item.price_eur ? String(item.price_eur) : undefined,
|
|
98
|
-
regularPrice: item.regular_price
|
|
99
|
-
|
|
105
|
+
regularPrice: item.regular_price
|
|
106
|
+
? String(item.regular_price)
|
|
107
|
+
: undefined,
|
|
108
|
+
regularPriceEur: item.regular_price_eur
|
|
109
|
+
? String(item.regular_price_eur)
|
|
110
|
+
: undefined,
|
|
100
111
|
group: groupName ? String(groupName) : undefined,
|
|
101
112
|
});
|
|
102
113
|
}
|
|
@@ -106,24 +117,27 @@ function normalizeGroupedItems(groupedItems) {
|
|
|
106
117
|
function normalizeOrderDetail(payload) {
|
|
107
118
|
const order = payload.order || {};
|
|
108
119
|
const parseNumber = (value) => {
|
|
109
|
-
if (typeof value ===
|
|
120
|
+
if (typeof value === "number")
|
|
110
121
|
return value;
|
|
111
|
-
if (typeof value ===
|
|
122
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
112
123
|
const parsed = Number(value);
|
|
113
124
|
return Number.isNaN(parsed) ? undefined : parsed;
|
|
114
125
|
}
|
|
115
126
|
return undefined;
|
|
116
127
|
};
|
|
117
|
-
const addressSerialized = order.address_serialized
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const
|
|
128
|
+
const addressSerialized = order.address_serialized
|
|
129
|
+
? String(order.address_serialized)
|
|
130
|
+
: "";
|
|
131
|
+
const city = order.city ? String(order.city) : "";
|
|
132
|
+
const neighbourhood = order.neighbourhood ? String(order.neighbourhood) : "";
|
|
133
|
+
const street = order.street ? String(order.street) : "";
|
|
134
|
+
const address = addressSerialized ||
|
|
135
|
+
[city, neighbourhood, street].filter(Boolean).join(", ");
|
|
122
136
|
const additionalOrdersRaw = Array.isArray(order.additional_orders)
|
|
123
137
|
? order.additional_orders
|
|
124
138
|
: [];
|
|
125
139
|
return {
|
|
126
|
-
id: order.encrypted_id ? String(order.encrypted_id) :
|
|
140
|
+
id: order.encrypted_id ? String(order.encrypted_id) : "",
|
|
127
141
|
status: parseNumber(order.order_status),
|
|
128
142
|
statusText: order.order_status_pharmacy
|
|
129
143
|
? String(order.order_status_pharmacy)
|
|
@@ -142,8 +156,12 @@ function normalizeOrderDetail(payload) {
|
|
|
142
156
|
totals: {
|
|
143
157
|
total: order.total ? String(order.total) : undefined,
|
|
144
158
|
totalEur: order.total_eur ? String(order.total_eur) : undefined,
|
|
145
|
-
totalPaid: order.total_price_paid
|
|
146
|
-
|
|
159
|
+
totalPaid: order.total_price_paid
|
|
160
|
+
? String(order.total_price_paid)
|
|
161
|
+
: undefined,
|
|
162
|
+
totalPaidEur: order.total_price_paid_eur
|
|
163
|
+
? String(order.total_price_paid_eur)
|
|
164
|
+
: undefined,
|
|
147
165
|
discount: order.discount ? String(order.discount) : undefined,
|
|
148
166
|
discountEur: order.discount_eur ? String(order.discount_eur) : undefined,
|
|
149
167
|
tip: order.tip ? String(order.tip) : undefined,
|
|
@@ -205,11 +223,11 @@ async function listOrders(config, session, options = {}) {
|
|
|
205
223
|
let page = options.page ?? 1;
|
|
206
224
|
let pagesFetched = 0;
|
|
207
225
|
while (results.length < limit) {
|
|
208
|
-
const result = await (0, client_1.requestEbag)(config, session,
|
|
226
|
+
const result = await (0, client_1.requestEbag)(config, session, "/orders/list/json", {
|
|
209
227
|
query: {
|
|
210
228
|
page,
|
|
211
229
|
year: hasRange ? year : undefined,
|
|
212
|
-
exclude_additional_order:
|
|
230
|
+
exclude_additional_order: "true",
|
|
213
231
|
},
|
|
214
232
|
});
|
|
215
233
|
const payload = result.data;
|
package/dist/lib/products.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Config, ProductDetail, Session } from
|
|
1
|
+
import type { Config, ProductDetail, Session } from "./types";
|
|
2
2
|
export declare function getProductById(config: Config, session: Session, productId: number): Promise<ProductDetail>;
|
package/dist/lib/search.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Config, ProductCacheEntry, SearchResult, Session } from
|
|
1
|
+
import type { Config, ProductCacheEntry, SearchResult, Session } from "./types";
|
|
2
2
|
export declare const PRODUCT_CACHE_TTL_MS: number;
|
|
3
3
|
export declare function isProductCacheFresh(entry: ProductCacheEntry, now?: number, ttlMs?: number): boolean;
|
|
4
4
|
export declare function searchProducts(config: Config, session: Session, query: string, options?: {
|
package/dist/lib/search.js
CHANGED
|
@@ -7,9 +7,9 @@ const config_1 = require("./config");
|
|
|
7
7
|
const client_1 = require("./client");
|
|
8
8
|
const lists_1 = require("./lists");
|
|
9
9
|
const DEFAULT_FACETS = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
"brand_name_bg",
|
|
11
|
+
"country_of_origin_bg",
|
|
12
|
+
"hierarchical_categories_bg.lv1",
|
|
13
13
|
];
|
|
14
14
|
exports.PRODUCT_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
15
15
|
function isProductCacheFresh(entry, now = Date.now(), ttlMs = exports.PRODUCT_CACHE_TTL_MS) {
|
|
@@ -20,12 +20,16 @@ function isProductCacheFresh(entry, now = Date.now(), ttlMs = exports.PRODUCT_CA
|
|
|
20
20
|
}
|
|
21
21
|
function normalizeProductFromDetail(data) {
|
|
22
22
|
const id = Number(data.id);
|
|
23
|
-
const name = String(data.name ||
|
|
23
|
+
const name = String(data.name || "");
|
|
24
24
|
const nameEn = data.name_en ? String(data.name_en) : undefined;
|
|
25
25
|
const price = data.price ? String(data.price) : undefined;
|
|
26
26
|
const pricePromo = data.price_promo ? String(data.price_promo) : undefined;
|
|
27
|
-
const currentPrice = data.current_price
|
|
28
|
-
|
|
27
|
+
const currentPrice = data.current_price
|
|
28
|
+
? String(data.current_price)
|
|
29
|
+
: undefined;
|
|
30
|
+
const mainImageId = data.main_image_id
|
|
31
|
+
? String(data.main_image_id)
|
|
32
|
+
: undefined;
|
|
29
33
|
const imageUrl = mainImageId
|
|
30
34
|
? `https://www.ebag.bg/products/images/${mainImageId}/200/webp`
|
|
31
35
|
: undefined;
|
|
@@ -43,11 +47,13 @@ function normalizeProductFromDetail(data) {
|
|
|
43
47
|
}
|
|
44
48
|
function normalizeProductFromAlgolia(hit) {
|
|
45
49
|
const id = Number(hit.id);
|
|
46
|
-
const name = String(hit.name_bg ||
|
|
50
|
+
const name = String(hit.name_bg || "");
|
|
47
51
|
const nameEn = hit.name_en ? String(hit.name_en) : undefined;
|
|
48
52
|
const price = hit.price ? String(hit.price) : undefined;
|
|
49
53
|
const pricePromo = hit.price_promo ? String(hit.price_promo) : undefined;
|
|
50
|
-
const currentPrice = hit.current_price
|
|
54
|
+
const currentPrice = hit.current_price
|
|
55
|
+
? String(hit.current_price)
|
|
56
|
+
: undefined;
|
|
51
57
|
const imageUrl = hit.product_image_absolute_url
|
|
52
58
|
? String(hit.product_image_absolute_url)
|
|
53
59
|
: undefined;
|
|
@@ -61,11 +67,11 @@ function normalizeProductFromAlgolia(hit) {
|
|
|
61
67
|
currentPrice,
|
|
62
68
|
imageUrl,
|
|
63
69
|
urlSlug,
|
|
64
|
-
source:
|
|
70
|
+
source: "algolia",
|
|
65
71
|
};
|
|
66
72
|
}
|
|
67
73
|
function safeLower(value) {
|
|
68
|
-
return value.toLocaleLowerCase(
|
|
74
|
+
return value.toLocaleLowerCase("bg-BG");
|
|
69
75
|
}
|
|
70
76
|
async function fetchProductDetail(config, session, productId) {
|
|
71
77
|
const result = await (0, client_1.requestEbag)(config, session, `/products/${productId}/json`);
|
|
@@ -88,7 +94,7 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
88
94
|
async function getProductWithCache(config, session, cache, productId) {
|
|
89
95
|
const cached = cache.products[String(productId)];
|
|
90
96
|
if (cached) {
|
|
91
|
-
if (
|
|
97
|
+
if ("product" in cached) {
|
|
92
98
|
const entry = cached;
|
|
93
99
|
if (isProductCacheFresh(entry)) {
|
|
94
100
|
return entry.product;
|
|
@@ -123,23 +129,23 @@ async function searchInLists(config, session, query) {
|
|
|
123
129
|
return products
|
|
124
130
|
.map((product) => ({
|
|
125
131
|
...product,
|
|
126
|
-
source:
|
|
132
|
+
source: "list",
|
|
127
133
|
listNames: productIdToLists.get(product.id) || [],
|
|
128
134
|
}))
|
|
129
135
|
.filter((product) => {
|
|
130
|
-
const name = safeLower(product.name ||
|
|
131
|
-
const nameEn = product.nameEn ? safeLower(product.nameEn) :
|
|
136
|
+
const name = safeLower(product.name || "");
|
|
137
|
+
const nameEn = product.nameEn ? safeLower(product.nameEn) : "";
|
|
132
138
|
return name.includes(needle) || nameEn.includes(needle);
|
|
133
139
|
});
|
|
134
140
|
}
|
|
135
141
|
async function searchAlgolia(config, query, page, hitsPerPage) {
|
|
136
142
|
const params = new URLSearchParams({
|
|
137
|
-
clickAnalytics:
|
|
143
|
+
clickAnalytics: "true",
|
|
138
144
|
facets: JSON.stringify(DEFAULT_FACETS),
|
|
139
|
-
filters:
|
|
140
|
-
highlightPostTag:
|
|
141
|
-
highlightPreTag:
|
|
142
|
-
maxValuesPerFacet:
|
|
145
|
+
filters: "",
|
|
146
|
+
highlightPostTag: "__/ais-highlight__",
|
|
147
|
+
highlightPreTag: "__ais-highlight__",
|
|
148
|
+
maxValuesPerFacet: "50",
|
|
143
149
|
page: String(page),
|
|
144
150
|
query,
|
|
145
151
|
hitsPerPage: String(hitsPerPage),
|
|
@@ -147,7 +153,7 @@ async function searchAlgolia(config, query, page, hitsPerPage) {
|
|
|
147
153
|
const body = {
|
|
148
154
|
requests: [
|
|
149
155
|
{
|
|
150
|
-
indexName:
|
|
156
|
+
indexName: "products",
|
|
151
157
|
params: params.toString(),
|
|
152
158
|
},
|
|
153
159
|
],
|
package/dist/lib/slots.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DeliverySlot } from
|
|
1
|
+
import type { DeliverySlot } from "./types";
|
|
2
2
|
export declare function normalizeSlots(data: Record<string, unknown>): DeliverySlot[];
|
|
3
3
|
export declare function formatSlotTime(value: number): string;
|
|
4
4
|
export declare function formatSlotRange(start: number, end: number): string;
|
package/dist/lib/slots.js
CHANGED
|
@@ -11,7 +11,7 @@ function normalizeSlots(data) {
|
|
|
11
11
|
if (!Array.isArray(entries))
|
|
12
12
|
continue;
|
|
13
13
|
for (const entry of entries) {
|
|
14
|
-
if (!entry || typeof entry !==
|
|
14
|
+
if (!entry || typeof entry !== "object")
|
|
15
15
|
continue;
|
|
16
16
|
const slot = entry;
|
|
17
17
|
const start = Number(slot.start);
|
|
@@ -34,7 +34,7 @@ function normalizeSlots(data) {
|
|
|
34
34
|
return slots;
|
|
35
35
|
}
|
|
36
36
|
function formatSlotTime(value) {
|
|
37
|
-
const padded = String(Math.trunc(value)).padStart(4,
|
|
37
|
+
const padded = String(Math.trunc(value)).padStart(4, "0");
|
|
38
38
|
const hours = padded.slice(0, 2);
|
|
39
39
|
const minutes = padded.slice(2);
|
|
40
40
|
return `${hours}:${minutes}`;
|
|
@@ -44,7 +44,7 @@ function formatSlotRange(start, end) {
|
|
|
44
44
|
}
|
|
45
45
|
function formatLoadPercent(value) {
|
|
46
46
|
if (!Number.isFinite(value))
|
|
47
|
-
return
|
|
47
|
+
return "0%";
|
|
48
48
|
const rounded = Math.round(value * 10) / 10;
|
|
49
49
|
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
|
|
50
50
|
}
|
package/dist/lib/types.d.ts
CHANGED