vilgain-mcp 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +46 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/parsers/cart.d.ts +7 -0
  7. package/dist/parsers/cart.js +27 -0
  8. package/dist/parsers/cart.js.map +1 -0
  9. package/dist/parsers/common.d.ts +22 -0
  10. package/dist/parsers/common.js +98 -0
  11. package/dist/parsers/common.js.map +1 -0
  12. package/dist/parsers/orders.d.ts +7 -0
  13. package/dist/parsers/orders.js +91 -0
  14. package/dist/parsers/orders.js.map +1 -0
  15. package/dist/parsers/product.d.ts +5 -0
  16. package/dist/parsers/product.js +140 -0
  17. package/dist/parsers/product.js.map +1 -0
  18. package/dist/parsers/search.d.ts +3 -0
  19. package/dist/parsers/search.js +66 -0
  20. package/dist/parsers/search.js.map +1 -0
  21. package/dist/tools/cart.d.ts +99 -0
  22. package/dist/tools/cart.js +103 -0
  23. package/dist/tools/cart.js.map +1 -0
  24. package/dist/tools/helpers.d.ts +12 -0
  25. package/dist/tools/helpers.js +21 -0
  26. package/dist/tools/helpers.js.map +1 -0
  27. package/dist/tools/order-history.d.ts +46 -0
  28. package/dist/tools/order-history.js +93 -0
  29. package/dist/tools/order-history.js.map +1 -0
  30. package/dist/tools/product-detail.d.ts +27 -0
  31. package/dist/tools/product-detail.js +64 -0
  32. package/dist/tools/product-detail.js.map +1 -0
  33. package/dist/tools/product-variants.d.ts +25 -0
  34. package/dist/tools/product-variants.js +41 -0
  35. package/dist/tools/product-variants.js.map +1 -0
  36. package/dist/tools/search-products.d.ts +27 -0
  37. package/dist/tools/search-products.js +43 -0
  38. package/dist/tools/search-products.js.map +1 -0
  39. package/dist/types.d.ts +124 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/validate-api.d.ts +9 -0
  43. package/dist/validate-api.js +97 -0
  44. package/dist/validate-api.js.map +1 -0
  45. package/dist/vilgain-api.d.ts +60 -0
  46. package/dist/vilgain-api.js +238 -0
  47. package/dist/vilgain-api.js.map +1 -0
  48. package/package.json +54 -0
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { errorResult, textResult } from './helpers.js';
3
+ export function createProductVariantsTool(api) {
4
+ return {
5
+ name: 'get_product_variants',
6
+ definition: {
7
+ title: 'Get Product Variants',
8
+ description: 'List all purchasable variants (flavors/sizes) of a Vilgain product with prices, availability and variant IDs. ' +
9
+ 'Use this to pick a variant before add_to_cart. For ingredients and nutrition facts use get_product_detail instead.',
10
+ inputSchema: {
11
+ product_url: z
12
+ .string()
13
+ .min(1)
14
+ .describe('Product URL from search_products, e.g. "https://vilgain.cz/vilgain-whey-protein-2"'),
15
+ },
16
+ annotations: {
17
+ readOnlyHint: true,
18
+ openWorldHint: true,
19
+ },
20
+ },
21
+ handler: async ({ product_url }) => {
22
+ try {
23
+ const detail = await api.getProductDetail(product_url);
24
+ if (detail.variants.length === 0) {
25
+ return textResult(`No variants found for ${detail.name} - the product may be unavailable.`);
26
+ }
27
+ const variants = detail.variants
28
+ .map((v) => {
29
+ const stock = v.inStock ? '' : ' | OUT OF STOCK';
30
+ return `• ${v.name} – ${v.price} ${v.currency}${stock}\n Variant ID: ${v.variantId}`;
31
+ })
32
+ .join('\n');
33
+ return textResult(`# ${detail.name}\n\nVariants (use Variant ID with add_to_cart):\n${variants}`);
34
+ }
35
+ catch (error) {
36
+ return errorResult(error);
37
+ }
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=product-variants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-variants.js","sourceRoot":"","sources":["../../src/tools/product-variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,UAAU,yBAAyB,CAAC,GAAe;IACvD,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,UAAU,EAAE;YACV,KAAK,EAAE,sBAAsB;YAC7B,WAAW,EACT,gHAAgH;gBAChH,oHAAoH;YACtH,WAAW,EAAE;gBACX,WAAW,EAAE,CAAC;qBACX,MAAM,EAAE;qBACR,GAAG,CAAC,CAAC,CAAC;qBACN,QAAQ,CAAC,oFAAoF,CAAC;aAClG;YACD,WAAW,EAAE;gBACX,YAAY,EAAE,IAAI;gBAClB,aAAa,EAAE,IAAI;aACpB;SACF;QACD,OAAO,EAAE,KAAK,EAAE,EAAE,WAAW,EAA2B,EAAE,EAAE;YAC1D,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;gBACvD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACjC,OAAO,UAAU,CAAC,yBAAyB,MAAM,CAAC,IAAI,oCAAoC,CAAC,CAAC;gBAC9F,CAAC;gBACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ;qBAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBACT,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC;oBACjD,OAAO,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,GAAG,KAAK,mBAAmB,CAAC,CAAC,SAAS,EAAE,CAAC;gBACxF,CAAC,CAAC;qBACD,IAAI,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO,UAAU,CAAC,KAAK,MAAM,CAAC,IAAI,oDAAoD,QAAQ,EAAE,CAAC,CAAC;YACpG,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import { VilgainAPI } from '../vilgain-api.js';
3
+ export declare function createSearchProductsTool(api: VilgainAPI): {
4
+ name: string;
5
+ definition: {
6
+ title: string;
7
+ description: string;
8
+ inputSchema: {
9
+ query: z.ZodString;
10
+ limit: z.ZodDefault<z.ZodNumber>;
11
+ };
12
+ annotations: {
13
+ readOnlyHint: boolean;
14
+ openWorldHint: boolean;
15
+ };
16
+ };
17
+ handler: ({ query, limit }: {
18
+ query: string;
19
+ limit?: number;
20
+ }) => Promise<{
21
+ content: Array<{
22
+ type: "text";
23
+ text: string;
24
+ }>;
25
+ isError?: boolean;
26
+ }>;
27
+ };
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ import { errorResult, textResult } from './helpers.js';
3
+ export function createSearchProductsTool(api) {
4
+ return {
5
+ name: 'search_products',
6
+ definition: {
7
+ title: 'Search Products',
8
+ description: 'Search for products on Vilgain by name or keyword. Returns product names, prices, ratings, product URLs and ' +
9
+ 'the Variant ID of the displayed variant, which can be passed directly to add_to_cart. ' +
10
+ 'To pick a different flavor/size, call get_product_variants with the product URL first.',
11
+ inputSchema: {
12
+ query: z.string().min(1).describe('Search term, e.g. "whey protein" or "arašídové máslo"'),
13
+ limit: z.number().int().min(1).max(30).default(10).describe('Maximum number of results (default 10)'),
14
+ },
15
+ annotations: {
16
+ readOnlyHint: true,
17
+ openWorldHint: true,
18
+ },
19
+ },
20
+ handler: async ({ query, limit }) => {
21
+ try {
22
+ const products = await api.searchProducts(query, limit ?? 10);
23
+ if (products.length === 0) {
24
+ return textResult(`No products found for "${query}".`);
25
+ }
26
+ const output = products
27
+ .map((p) => {
28
+ const price = p.price !== undefined ? `${p.price} ${p.currency}` : 'price unavailable';
29
+ const rating = p.rating ? ` | Rating: ${p.rating} (${p.reviewCount} reviews)` : '';
30
+ const variant = p.defaultVariant ? ` | Shown variant: ${p.defaultVariant}` : '';
31
+ const variantId = p.variantId !== undefined ? `\n Variant ID (shown variant): ${p.variantId}` : '';
32
+ return `• ${p.name}${p.subtitle ? ` – ${p.subtitle}` : ''}\n Price: ${price}${variant}${rating}${variantId}\n URL: ${p.url}`;
33
+ })
34
+ .join('\n\n');
35
+ return textResult(`Found ${products.length} products for "${query}":\n\n${output}`);
36
+ }
37
+ catch (error) {
38
+ return errorResult(error);
39
+ }
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=search-products.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-products.js","sourceRoot":"","sources":["../../src/tools/search-products.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,UAAU,wBAAwB,CAAC,GAAe;IACtD,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,UAAU,EAAE;YACV,KAAK,EAAE,iBAAiB;YACxB,WAAW,EACT,8GAA8G;gBAC9G,wFAAwF;gBACxF,wFAAwF;YAC1F,WAAW,EAAE;gBACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,uDAAuD,CAAC;gBAC1F,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;aACtG;YACD,WAAW,EAAE;gBACX,YAAY,EAAE,IAAI;gBAClB,aAAa,EAAE,IAAI;aACpB;SACF;QACD,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAqC,EAAE,EAAE;YACrE,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;gBAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC1B,OAAO,UAAU,CAAC,0BAA0B,KAAK,IAAI,CAAC,CAAC;gBACzD,CAAC;gBACD,MAAM,MAAM,GAAG,QAAQ;qBACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBACT,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC;oBACvF,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,WAAW,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;oBACnF,MAAM,OAAO,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAChF,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACpG,OAAO,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,YAAY,CAAC,CAAC,GAAG,EAAE,CAAC;gBACjI,CAAC,CAAC;qBACD,IAAI,CAAC,MAAM,CAAC,CAAC;gBAChB,OAAO,UAAU,CAAC,SAAS,QAAQ,CAAC,MAAM,kBAAkB,KAAK,SAAS,MAAM,EAAE,CAAC,CAAC;YACtF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,124 @@
1
+ export interface VilgainCredentials {
2
+ email: string;
3
+ password: string;
4
+ }
5
+ /** Product as listed in search results. One product groups multiple variants (flavors, sizes). */
6
+ export interface SearchProduct {
7
+ /** Product group id (identifies the product family, not a purchasable variant) */
8
+ productId: number;
9
+ name: string;
10
+ /** Short marketing description shown under the product name */
11
+ subtitle?: string;
12
+ /** Name of the variant the listed price refers to, e.g. "Čokoláda 1 000 g" */
13
+ defaultVariant?: string;
14
+ /** Variant id of the displayed variant, usable directly with add_to_cart (best-effort) */
15
+ variantId?: number;
16
+ /** Absolute URL of the product detail page */
17
+ url: string;
18
+ /** Current price of the default variant (VAT included) */
19
+ price?: number;
20
+ currency?: string;
21
+ /** Rating like "4.6/5" */
22
+ rating?: string;
23
+ reviewCount?: number;
24
+ }
25
+ /** A purchasable product variant (specific flavor + size). */
26
+ export interface ProductVariant {
27
+ /** Variant id used by cart operations */
28
+ variantId: number;
29
+ /** Variant name, e.g. "Čokoláda 1 000 g" */
30
+ name: string;
31
+ url: string;
32
+ price: number;
33
+ currency: string;
34
+ inStock: boolean;
35
+ gtin?: string;
36
+ }
37
+ export interface NutritionRow {
38
+ /** e.g. "Energetická hodnota", "Bílkoviny" */
39
+ label: string;
40
+ /** Values in the same order as NutritionTable.columns */
41
+ values: string[];
42
+ }
43
+ export interface NutritionTable {
44
+ /** Column headers, e.g. ["100 g", "30 g"] */
45
+ columns: string[];
46
+ rows: NutritionRow[];
47
+ }
48
+ export interface ProductDetail {
49
+ name: string;
50
+ url: string;
51
+ /** Marketing description from page metadata */
52
+ shortDescription?: string;
53
+ /** Full description text from the "Popis" tab */
54
+ description?: string;
55
+ /** Ingredients text including allergen notes (for the variant selected by the URL) */
56
+ ingredients?: string;
57
+ /** Dosage / usage instructions */
58
+ dosage?: string;
59
+ /** Nutrition facts tables (values per 100 g and per serving) */
60
+ nutrition: NutritionTable[];
61
+ rating?: string;
62
+ reviewCount?: number;
63
+ variants: ProductVariant[];
64
+ /** Id of the variant the page content (ingredients, nutrition) refers to */
65
+ selectedVariantId?: number;
66
+ }
67
+ export interface CartItem {
68
+ variantId: number;
69
+ productName: string;
70
+ /** Variant name, e.g. "1 000 g" or "jahoda 1 000 g" */
71
+ variant?: string;
72
+ quantity: number;
73
+ /** Unit price with VAT */
74
+ price: number;
75
+ currency: string;
76
+ }
77
+ export interface CartContent {
78
+ items: CartItem[];
79
+ /** Cart total with VAT */
80
+ totalWithVat?: number;
81
+ currency?: string;
82
+ }
83
+ export interface OrderSummary {
84
+ /** Order id/number as displayed by the shop */
85
+ id: string;
86
+ url?: string;
87
+ /** ISO date (YYYY-MM-DD) */
88
+ date?: string;
89
+ /** e.g. "Vyřízeno", "Stornováno" */
90
+ state?: string;
91
+ total?: number;
92
+ currency?: string;
93
+ /** Product names in the order (from the list page thumbnails) */
94
+ productNames: string[];
95
+ }
96
+ export interface OrderItem {
97
+ name: string;
98
+ /** e.g. "Příchuť: křupavé mandle" */
99
+ variant?: string;
100
+ /** Variant id usable with add_to_cart (to reorder the item) */
101
+ variantId?: number;
102
+ quantity?: number;
103
+ /** Line price with VAT */
104
+ price?: number;
105
+ currency?: string;
106
+ /** Product page URL */
107
+ url?: string;
108
+ }
109
+ export interface OrderTimelineEvent {
110
+ /** e.g. "Zásilka byla doručena" */
111
+ event: string;
112
+ /** e.g. "11. 08. 2026 16:14" */
113
+ date?: string;
114
+ }
115
+ export interface OrderDetail {
116
+ orderNumber: string;
117
+ items: OrderItem[];
118
+ totalWithVat?: number;
119
+ currency?: string;
120
+ /** Shipment status history, most recent first */
121
+ timeline: OrderTimelineEvent[];
122
+ /** Delivery destination (pickup point or address) */
123
+ deliveryAddress?: string;
124
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Live smoke test against the real Vilgain website.
4
+ * Run with `npm run validate-api` (requires credentials in .env).
5
+ *
6
+ * Exercises every API method end to end and cleans up after itself
7
+ * (anything it adds to the cart is removed again).
8
+ */
9
+ export {};
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Live smoke test against the real Vilgain website.
4
+ * Run with `npm run validate-api` (requires credentials in .env).
5
+ *
6
+ * Exercises every API method end to end and cleans up after itself
7
+ * (anything it adds to the cart is removed again).
8
+ */
9
+ import { VilgainAPI } from './vilgain-api.js';
10
+ const email = process.env.VILGAIN_EMAIL;
11
+ const password = process.env.VILGAIN_PASSWORD;
12
+ if (!email || !password) {
13
+ console.error('VILGAIN_EMAIL and VILGAIN_PASSWORD are required (put them in .env)');
14
+ process.exit(1);
15
+ }
16
+ const api = new VilgainAPI({ email, password }, process.env.VILGAIN_BASE_URL || 'https://vilgain.cz');
17
+ let failures = 0;
18
+ async function step(name, fn) {
19
+ try {
20
+ const result = await fn();
21
+ console.log(`✓ ${name}`);
22
+ return result;
23
+ }
24
+ catch (error) {
25
+ failures++;
26
+ console.error(`✗ ${name}: ${error instanceof Error ? error.message : String(error)}`);
27
+ return undefined;
28
+ }
29
+ }
30
+ function assert(condition, message) {
31
+ if (!condition)
32
+ throw new Error(message);
33
+ }
34
+ await step('login', () => api.login());
35
+ const products = await step('searchProducts("protein")', async () => {
36
+ const results = await api.searchProducts('protein', 5);
37
+ assert(results.length > 0, 'no search results');
38
+ assert(results[0].url.startsWith('http'), 'missing product url');
39
+ assert(results[0].price !== undefined, 'missing price');
40
+ assert(results[0].variantId !== undefined, 'missing displayed variant id');
41
+ return results;
42
+ });
43
+ const detail = products
44
+ ? await step(`getProductDetail(${products[0].name})`, async () => {
45
+ const d = await api.getProductDetail(products[0].url);
46
+ assert(d.variants.length > 0, 'no variants parsed');
47
+ assert(d.ingredients, 'no ingredients parsed');
48
+ assert(d.nutrition.length > 0, 'no nutrition table parsed');
49
+ return d;
50
+ })
51
+ : undefined;
52
+ if (detail && products) {
53
+ const otherVariant = detail.variants.find((v) => v.inStock && v.variantId !== detail.selectedVariantId);
54
+ if (otherVariant) {
55
+ await step(`getProductDetail(variant ${otherVariant.variantId})`, async () => {
56
+ const variantDetail = await api.getProductDetail(products[0].url, otherVariant.variantId);
57
+ assert(variantDetail.selectedVariantId === otherVariant.variantId, 'wrong variant selected');
58
+ assert(variantDetail.ingredients, 'no ingredients for variant');
59
+ });
60
+ }
61
+ }
62
+ const variant = detail?.variants.find((v) => v.inStock);
63
+ if (variant) {
64
+ await step(`addToCart(${variant.variantId} = ${variant.name}, quantity 2)`, async () => {
65
+ const cart = await api.addToCart(variant.variantId, 2);
66
+ const item = cart.items.find((i) => i.variantId === variant.variantId);
67
+ assert(item, 'item not in cart after add');
68
+ });
69
+ await step(`setCartItemQuantity(${variant.variantId}, 1)`, async () => {
70
+ const cart = await api.setCartItemQuantity(variant.variantId, 1);
71
+ const item = cart.items.find((i) => i.variantId === variant.variantId);
72
+ assert(item?.quantity === 1, `expected quantity 1, got ${item?.quantity}`);
73
+ });
74
+ await step(`removeFromCart(${variant.variantId})`, async () => {
75
+ const cart = await api.removeFromCart(variant.variantId);
76
+ assert(!cart.items.some((i) => i.variantId === variant.variantId), 'item still in cart after remove');
77
+ });
78
+ }
79
+ await step('getCart', async () => {
80
+ await api.getCart();
81
+ });
82
+ const history = await step('getOrderHistory', async () => api.getOrderHistory());
83
+ if (history && history.orders.length > 0) {
84
+ await step(`getOrderDetail(${history.orders[0].id})`, async () => {
85
+ const detail = await api.getOrderDetail(history.orders[0].id);
86
+ assert(detail.orderNumber === history.orders[0].id, 'order number mismatch');
87
+ assert(detail.items.length > 0, 'no items parsed');
88
+ assert(detail.totalWithVat !== undefined, 'no total parsed');
89
+ assert(detail.timeline.length > 0, 'no timeline parsed');
90
+ });
91
+ }
92
+ else {
93
+ console.log('- getOrderDetail skipped (account has no orders)');
94
+ }
95
+ console.log(failures === 0 ? '\nAll checks passed.' : `\n${failures} check(s) failed.`);
96
+ process.exit(failures === 0 ? 0 : 1);
97
+ //# sourceMappingURL=validate-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-api.js","sourceRoot":"","sources":["../src/validate-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { CartContent, OrderDetail, OrderSummary, ProductDetail, SearchProduct, VilgainCredentials } from './types.js';
2
+ export declare class VilgainAPIError extends Error {
3
+ status?: number | undefined;
4
+ constructor(message: string, status?: number | undefined);
5
+ }
6
+ /**
7
+ * Client for the reverse engineered Vilgain.cz web endpoints.
8
+ *
9
+ * Vilgain has no public JSON API; the website is a server-rendered Nette
10
+ * application. This client talks to the same endpoints the website's own
11
+ * frontend uses (Nette "signals" via the `_do` form field) and parses data
12
+ * out of the returned HTML/JSON. See docs/api.md for the endpoint map.
13
+ */
14
+ export declare class VilgainAPI {
15
+ private credentials;
16
+ readonly baseUrl: string;
17
+ private cookies;
18
+ private loggedIn;
19
+ private lastRequestTime;
20
+ private readonly minRequestInterval;
21
+ constructor(credentials: VilgainCredentials, baseUrl?: string);
22
+ private rateLimit;
23
+ private storeCookies;
24
+ private request;
25
+ /** Log in and store the session cookie. Called automatically when needed. */
26
+ login(): Promise<void>;
27
+ private ensureLoggedIn;
28
+ /**
29
+ * Fetch a page that requires authentication. If the session has expired
30
+ * (the server redirects to the login page), re-login and retry once.
31
+ */
32
+ private getAuthenticatedPage;
33
+ /** Full-text product search. */
34
+ searchProducts(query: string, limit?: number): Promise<SearchProduct[]>;
35
+ /**
36
+ * Fetch and parse a product detail page. Accepts absolute or relative product URLs.
37
+ * With `variantId`, the page of that specific variant is fetched instead of the
38
+ * default one (the router resolves `/<product-slug>/<anything>-<variantId>`).
39
+ */
40
+ getProductDetail(productUrl: string, variantId?: number): Promise<ProductDetail>;
41
+ /** Get current cart content. */
42
+ getCart(): Promise<CartContent>;
43
+ /**
44
+ * Add a product variant to the cart. Each call adds `quantity` more pieces
45
+ * (the shop's add-to-cart adds one; larger quantities are set explicitly
46
+ * afterwards). Returns the updated cart.
47
+ */
48
+ addToCart(variantId: number, quantity?: number): Promise<CartContent>;
49
+ /** Set the absolute quantity of a cart item. Returns the updated cart. */
50
+ setCartItemQuantity(variantId: number, quantity: number): Promise<CartContent>;
51
+ /** Remove an item from the cart. Returns the updated cart. */
52
+ removeFromCart(variantId: number): Promise<CartContent>;
53
+ /** List past orders (most recent first, as displayed by the shop). */
54
+ getOrderHistory(): Promise<{
55
+ orders: OrderSummary[];
56
+ empty: boolean;
57
+ }>;
58
+ /** Get one order's items, total, delivery destination and shipment timeline. */
59
+ getOrderDetail(orderId: string): Promise<OrderDetail>;
60
+ }
@@ -0,0 +1,238 @@
1
+ import { parseCart } from './parsers/cart.js';
2
+ import { isOrderHistoryEmpty, parseOrderDetail, parseOrderHistory } from './parsers/orders.js';
3
+ import { parseProductDetail } from './parsers/product.js';
4
+ import { parseSearchResults } from './parsers/search.js';
5
+ export class VilgainAPIError extends Error {
6
+ status;
7
+ constructor(message, status) {
8
+ super(message);
9
+ this.status = status;
10
+ this.name = 'VilgainAPIError';
11
+ }
12
+ }
13
+ const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
14
+ /**
15
+ * Client for the reverse engineered Vilgain.cz web endpoints.
16
+ *
17
+ * Vilgain has no public JSON API; the website is a server-rendered Nette
18
+ * application. This client talks to the same endpoints the website's own
19
+ * frontend uses (Nette "signals" via the `_do` form field) and parses data
20
+ * out of the returned HTML/JSON. See docs/api.md for the endpoint map.
21
+ */
22
+ export class VilgainAPI {
23
+ credentials;
24
+ baseUrl;
25
+ cookies = new Map();
26
+ loggedIn = false;
27
+ lastRequestTime = 0;
28
+ minRequestInterval = 100;
29
+ constructor(credentials, baseUrl = 'https://vilgain.cz') {
30
+ this.credentials = credentials;
31
+ this.baseUrl = baseUrl;
32
+ }
33
+ async rateLimit() {
34
+ const elapsed = Date.now() - this.lastRequestTime;
35
+ if (elapsed < this.minRequestInterval) {
36
+ await new Promise((resolve) => setTimeout(resolve, this.minRequestInterval - elapsed));
37
+ }
38
+ this.lastRequestTime = Date.now();
39
+ }
40
+ storeCookies(response) {
41
+ for (const cookie of response.headers.getSetCookie()) {
42
+ const [pair] = cookie.split(';');
43
+ const eq = pair.indexOf('=');
44
+ if (eq > 0) {
45
+ this.cookies.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
46
+ }
47
+ }
48
+ }
49
+ async request(path, options = {}) {
50
+ await this.rateLimit();
51
+ const url = new URL(path, this.baseUrl).href;
52
+ const headers = {
53
+ 'User-Agent': USER_AGENT,
54
+ 'Accept-Language': 'cs,en;q=0.8',
55
+ // The site's own AJAX layer sends this header; without it some
56
+ // requests can be challenged by the WAF.
57
+ 'x-vilgain-waf-bypass': 'naja-shop',
58
+ };
59
+ if (options.ajax) {
60
+ headers['X-Requested-With'] = 'XMLHttpRequest';
61
+ headers['Accept'] = '*/*';
62
+ }
63
+ if (this.cookies.size > 0) {
64
+ headers['Cookie'] = [...this.cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
65
+ }
66
+ let body;
67
+ if (options.form) {
68
+ body = new FormData();
69
+ for (const [key, value] of Object.entries(options.form)) {
70
+ body.append(key, value);
71
+ }
72
+ }
73
+ let response;
74
+ try {
75
+ response = await fetch(url, {
76
+ method: options.method ?? (options.form ? 'POST' : 'GET'),
77
+ headers,
78
+ body,
79
+ });
80
+ }
81
+ catch (error) {
82
+ throw new VilgainAPIError(`Request to ${url} failed: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ this.storeCookies(response);
85
+ if (response.status === 202) {
86
+ throw new VilgainAPIError('Request was challenged by the site\'s bot protection (HTTP 202). Try again later.', 202);
87
+ }
88
+ if (!response.ok) {
89
+ throw new VilgainAPIError(`Request to ${url} failed with HTTP ${response.status}`, response.status);
90
+ }
91
+ return response;
92
+ }
93
+ /** Log in and store the session cookie. Called automatically when needed. */
94
+ async login() {
95
+ // Initial GET establishes the session cookies the login form expects.
96
+ await this.request('/prihlaseni');
97
+ const response = await this.request('/prihlaseni', {
98
+ ajax: true,
99
+ form: {
100
+ email: this.credentials.email,
101
+ password: this.credentials.password,
102
+ _do: 'modalStack-loginModal-form-submit',
103
+ ok: 'Přihlásit',
104
+ },
105
+ });
106
+ const result = (await response.json());
107
+ // On success the server redirects away from the login page;
108
+ // on bad credentials it redirects back to it.
109
+ if (!result.redirect || result.redirect.includes('/prihlaseni')) {
110
+ throw new VilgainAPIError('Login failed - check VILGAIN_EMAIL and VILGAIN_PASSWORD');
111
+ }
112
+ this.loggedIn = true;
113
+ }
114
+ async ensureLoggedIn() {
115
+ if (!this.loggedIn) {
116
+ await this.login();
117
+ }
118
+ }
119
+ /**
120
+ * Fetch a page that requires authentication. If the session has expired
121
+ * (the server redirects to the login page), re-login and retry once.
122
+ */
123
+ async getAuthenticatedPage(path) {
124
+ await this.ensureLoggedIn();
125
+ let response = await this.request(path);
126
+ if (new URL(response.url).pathname === '/prihlaseni') {
127
+ this.loggedIn = false;
128
+ await this.ensureLoggedIn();
129
+ response = await this.request(path);
130
+ }
131
+ return response.text();
132
+ }
133
+ /** Full-text product search. */
134
+ async searchProducts(query, limit = 10) {
135
+ const response = await this.request(`/vyhledavani?s=${encodeURIComponent(query)}`);
136
+ const html = await response.text();
137
+ return parseSearchResults(html, this.baseUrl).slice(0, limit);
138
+ }
139
+ /**
140
+ * Fetch and parse a product detail page. Accepts absolute or relative product URLs.
141
+ * With `variantId`, the page of that specific variant is fetched instead of the
142
+ * default one (the router resolves `/<product-slug>/<anything>-<variantId>`).
143
+ */
144
+ async getProductDetail(productUrl, variantId) {
145
+ let url = new URL(productUrl, this.baseUrl);
146
+ if (url.host !== new URL(this.baseUrl).host) {
147
+ throw new VilgainAPIError(`Product URL must be on ${this.baseUrl}`);
148
+ }
149
+ if (variantId !== undefined) {
150
+ const productSlug = url.pathname.split('/').filter(Boolean)[0];
151
+ if (!productSlug) {
152
+ throw new VilgainAPIError('Product URL is missing the product slug');
153
+ }
154
+ url = new URL(`/${productSlug}/v-${variantId}`, this.baseUrl);
155
+ }
156
+ const response = await this.request(url.href);
157
+ const html = await response.text();
158
+ const detail = parseProductDetail(html, response.url);
159
+ if (variantId !== undefined && detail.selectedVariantId !== variantId) {
160
+ throw new VilgainAPIError(`Variant ${variantId} not found on ${productUrl}`);
161
+ }
162
+ return detail;
163
+ }
164
+ /** Get current cart content. */
165
+ async getCart() {
166
+ const html = await this.getAuthenticatedPage('/objednavka');
167
+ return parseCart(html);
168
+ }
169
+ /**
170
+ * Add a product variant to the cart. Each call adds `quantity` more pieces
171
+ * (the shop's add-to-cart adds one; larger quantities are set explicitly
172
+ * afterwards). Returns the updated cart.
173
+ */
174
+ async addToCart(variantId, quantity = 1) {
175
+ await this.ensureLoggedIn();
176
+ await this.request('/', {
177
+ ajax: true,
178
+ form: {
179
+ _do: `buyVariant-${variantId}-addForm-submit`,
180
+ add: 'add_to_cart_button',
181
+ },
182
+ });
183
+ let cart = await this.getCart();
184
+ const item = cart.items.find((i) => i.variantId === variantId);
185
+ if (!item) {
186
+ throw new VilgainAPIError(`Adding variant ${variantId} to the cart had no effect - the variant id is probably invalid or out of stock`);
187
+ }
188
+ if (quantity > 1) {
189
+ cart = await this.setCartItemQuantity(variantId, item.quantity + quantity - 1);
190
+ }
191
+ return cart;
192
+ }
193
+ /** Set the absolute quantity of a cart item. Returns the updated cart. */
194
+ async setCartItemQuantity(variantId, quantity) {
195
+ await this.ensureLoggedIn();
196
+ await this.request('/objednavka', {
197
+ ajax: true,
198
+ form: {
199
+ _do: `mainBox-productVariant_${variantId}-quantityForm-submit`,
200
+ quantity: String(quantity),
201
+ },
202
+ });
203
+ return this.getCart();
204
+ }
205
+ /** Remove an item from the cart. Returns the updated cart. */
206
+ async removeFromCart(variantId) {
207
+ await this.ensureLoggedIn();
208
+ await this.request('/objednavka', {
209
+ ajax: true,
210
+ form: {
211
+ _do: `mainBox-productVariant_${variantId}-removeForm-submit`,
212
+ remove: '',
213
+ },
214
+ });
215
+ return this.getCart();
216
+ }
217
+ /** List past orders (most recent first, as displayed by the shop). */
218
+ async getOrderHistory() {
219
+ const html = await this.getAuthenticatedPage('/muj-ucet/objednavky');
220
+ return {
221
+ orders: parseOrderHistory(html, this.baseUrl),
222
+ empty: isOrderHistoryEmpty(html),
223
+ };
224
+ }
225
+ /** Get one order's items, total, delivery destination and shipment timeline. */
226
+ async getOrderDetail(orderId) {
227
+ if (!/^\d+$/.test(orderId)) {
228
+ throw new VilgainAPIError(`Invalid order id "${orderId}" - expected a number from get_order_history`);
229
+ }
230
+ const html = await this.getAuthenticatedPage(`/muj-ucet/objednavka/${orderId}`);
231
+ const detail = parseOrderDetail(html, this.baseUrl);
232
+ if (!detail.orderNumber) {
233
+ throw new VilgainAPIError(`Order ${orderId} not found on this account`);
234
+ }
235
+ return detail;
236
+ }
237
+ }
238
+ //# sourceMappingURL=vilgain-api.js.map