continente-mcp 3.1.0 → 4.0.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/.env.example +19 -11
- package/README.md +53 -276
- package/continente-auto-login.js +232 -0
- package/package.json +6 -8
- package/skills/groceries/SKILL.md +3 -0
- package/src/auth-session.js +37 -0
- package/src/cart-utils.js +76 -0
- package/src/index.js +291 -88
- package/check-change.js +0 -157
- package/continente-backup.js +0 -166
- package/continente-cookie-reader.py +0 -164
- package/continente-keepalive.py +0 -135
- package/src/auth.js +0 -180
- package/src/changedetector.js +0 -158
|
@@ -40,6 +40,8 @@ If confidence is high (clear favourite or obvious top result), add silently and
|
|
|
40
40
|
|
|
41
41
|
Call `add_to_cart` with `product_id`. Default quantity is 1 unless specified (e.g. "2 pacotes de leite").
|
|
42
42
|
|
|
43
|
+
After adding products with a requested quantity greater than 1, call `get_cart` and verify the displayed quantity. Some Continente products use internal weighted units, so the quantity returned by `add_to_cart` can differ from the requested display quantity. If the cart quantity is wrong, call `update_cart_item` with the same `product_id` and the desired displayed quantity, then call `get_cart` again.
|
|
44
|
+
|
|
43
45
|
## Output format
|
|
44
46
|
|
|
45
47
|
After all items are processed, print a compact summary:
|
|
@@ -47,6 +49,7 @@ After all items are processed, print a compact summary:
|
|
|
47
49
|
```
|
|
48
50
|
✓ Queijo Flamengo Mil Vacas 400g — added (from favourites)
|
|
49
51
|
✓ Leite Mimosa Meio-Gordo 1L — added
|
|
52
|
+
✓ Banana Continente — quantity set to 12
|
|
50
53
|
✗ Pão de Forma — unclear match, please check manually
|
|
51
54
|
```
|
|
52
55
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
import { autoLogin, getCredentialStatus, loadCredentialEnv, resolveStatePaths } from '../continente-auto-login.js';
|
|
4
|
+
|
|
5
|
+
export { resolveStatePaths };
|
|
6
|
+
|
|
7
|
+
export function canAutoLogin(env = process.env) {
|
|
8
|
+
return getCredentialStatus(env).ready;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function refreshAuthCookies({
|
|
12
|
+
stateDir,
|
|
13
|
+
closeBrowser,
|
|
14
|
+
log = console.error,
|
|
15
|
+
env = process.env,
|
|
16
|
+
} = {}) {
|
|
17
|
+
if (!canAutoLogin(env)) {
|
|
18
|
+
log('Automatic login skipped: CONTINENTE_EMAIL and CONTINENTE_PASSWORD are not both set.');
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const loginEnv = loadCredentialEnv(env);
|
|
23
|
+
if (stateDir) loginEnv.CONTINENTE_STATE_DIR = stateDir;
|
|
24
|
+
const { cookieFile } = resolveStatePaths(loginEnv);
|
|
25
|
+
loginEnv.CONTINENTE_COOKIES_PATH = cookieFile;
|
|
26
|
+
|
|
27
|
+
if (closeBrowser) {
|
|
28
|
+
await closeBrowser();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = await autoLogin({
|
|
32
|
+
env: loginEnv,
|
|
33
|
+
log: (message) => log(`[auth] ${message}`),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return Boolean(result.success && existsSync(cookieFile));
|
|
37
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function normalizeCartProductId(productId) {
|
|
2
|
+
const value = String(productId || '').trim();
|
|
3
|
+
if (!value) return value;
|
|
4
|
+
|
|
5
|
+
const withoutHtml = value.replace(/\.html$/, '');
|
|
6
|
+
const masterMatch = withoutHtml.match(/^(\d+)-master$/);
|
|
7
|
+
if (masterMatch) return masterMatch[1];
|
|
8
|
+
|
|
9
|
+
const numericTail = withoutHtml.match(/(\d+)$/);
|
|
10
|
+
return numericTail ? numericTail[1] : withoutHtml;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function quantityForCartUpdate(displayQuantity, measureOptions = {}) {
|
|
14
|
+
const quantity = Number(displayQuantity);
|
|
15
|
+
if (!Number.isFinite(quantity) || quantity < 0) {
|
|
16
|
+
throw new Error('Quantity must be a non-negative number');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const conversion = Number(measureOptions.primaryToSecondary || measureOptions.unitConversionRate);
|
|
20
|
+
const hasAlternativeUnit = measureOptions.hasAlternativeSaleUnit || measureOptions.hasConversionRate;
|
|
21
|
+
const cartQuantity = hasAlternativeUnit && conversion > 0 ? quantity * conversion : quantity;
|
|
22
|
+
|
|
23
|
+
return Number(cartQuantity.toFixed(3)).toString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function summarizeCartState(payload = {}) {
|
|
27
|
+
const customerAuthenticated = payload?.resources?.customerAuthenticated === true;
|
|
28
|
+
// The storefront uses {} when this session has not created a basket yet.
|
|
29
|
+
// Require the known response envelope; arbitrary/malformed objects still fail.
|
|
30
|
+
if (payload?.action === 'Cart-MiniCartShow' && payload.basket &&
|
|
31
|
+
typeof payload.basket === 'object' && !Array.isArray(payload.basket) &&
|
|
32
|
+
Object.keys(payload.basket).length === 0) {
|
|
33
|
+
return { customerAuthenticated, items: [], total: 0 };
|
|
34
|
+
}
|
|
35
|
+
const addItems = Array.isArray(payload?.cart?.items) ? payload.cart.items : null;
|
|
36
|
+
const miniCartGroups = Array.isArray(payload?.basket?.itemsSortedByBrand)
|
|
37
|
+
? payload.basket.itemsSortedByBrand
|
|
38
|
+
: null;
|
|
39
|
+
|
|
40
|
+
const unknown = { customerAuthenticated, error: 'cart_shape_unknown' };
|
|
41
|
+
if (!addItems && !miniCartGroups) return unknown;
|
|
42
|
+
if (!addItems && miniCartGroups.some(group => !Array.isArray(group?.items))) return unknown;
|
|
43
|
+
|
|
44
|
+
const sourceItems = addItems || (miniCartGroups
|
|
45
|
+
? miniCartGroups.flatMap(group => Array.isArray(group?.items) ? group.items : [])
|
|
46
|
+
: []);
|
|
47
|
+
|
|
48
|
+
if (sourceItems.some(item => !item?.id || !item.productName ||
|
|
49
|
+
!Number.isFinite(Number(item.secondaryQuantity ?? item.quantity)) ||
|
|
50
|
+
Number(item.secondaryQuantity ?? item.quantity) < 0)) return unknown;
|
|
51
|
+
|
|
52
|
+
const items = sourceItems.map(item => ({
|
|
53
|
+
id: normalizeCartProductId(item.id),
|
|
54
|
+
name: item.productName,
|
|
55
|
+
qty: Number(item.secondaryQuantity ?? item.quantity ?? 1),
|
|
56
|
+
price: moneyOrNull(item?.price?.sales?.value ?? item?.priceTotal?.basePriceValue)
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const total = moneyOrNull(
|
|
60
|
+
payload?.cart?.totalProductsValueNumber ??
|
|
61
|
+
payload?.basket?.totals?.productsTotalPriceOnly ??
|
|
62
|
+
payload?.basket?.totals?.productsTotalPriceOnlyWithSDR
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
customerAuthenticated,
|
|
67
|
+
items,
|
|
68
|
+
total
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function moneyOrNull(value) {
|
|
73
|
+
if (value == null || value === '') return null;
|
|
74
|
+
const amount = Number(value);
|
|
75
|
+
return Number.isFinite(amount) && amount >= 0 ? amount : null;
|
|
76
|
+
}
|