wetherspoons-api 0.0.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.
File without changes
@@ -0,0 +1,3 @@
1
+ declare const _default: import("@eslint/core", { with: { "resolution-mode": "require" } }).ConfigObject<import("@eslint/core", { with: { "resolution-mode": "require" } }).RulesConfig>[];
2
+ export default _default;
3
+ //# sourceMappingURL=eslint.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint.config.d.ts","sourceRoot":"","sources":["../eslint.config.ts"],"names":[],"mappings":";AAMA,wBAaG"}
@@ -0,0 +1,20 @@
1
+ import js from '@eslint/js';
2
+ import globals from 'globals';
3
+ import tseslint from 'typescript-eslint';
4
+ import { defineConfig } from 'eslint/config';
5
+ import stylistic from '@stylistic/eslint-plugin';
6
+ export default defineConfig([
7
+ {
8
+ ignores: ['dist/**', 'node_modules/**'],
9
+ },
10
+ { files: ['**/*.{js,mjs,cjs,ts,mts,cts}'], plugins: { js, '@stylistic': stylistic }, extends: ['js/recommended'], languageOptions: { globals: { ...globals.browser, ...globals.node } } },
11
+ tseslint.configs.recommended,
12
+ stylistic.configs['recommended'],
13
+ {
14
+ rules: {
15
+ '@stylistic/semi': ['error', 'always'],
16
+ '@stylistic/brace-style': ['error', '1tbs'],
17
+ },
18
+ },
19
+ ]);
20
+ //# sourceMappingURL=eslint.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint.config.js","sourceRoot":"","sources":["../eslint.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,SAAS,MAAM,0BAA0B,CAAC;AAEjD,eAAe,YAAY,CAAC;IAC1B;QACE,OAAO,EAAE,CAAC,SAAS,EAAE,iBAAiB,CAAC;KACxC;IACD,EAAE,KAAK,EAAE,CAAC,8BAA8B,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,gBAAgB,CAAC,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE;IACzL,QAAQ,CAAC,OAAO,CAAC,WAAW;IAC5B,SAAS,CAAC,OAAO,CAAC,aAAa,CAAC;IAChC;QACE,KAAK,EAAE;YACL,iBAAiB,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;YACtC,wBAAwB,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;SAC5C;KACF;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,116 @@
1
+ import { z } from 'zod';
2
+ export declare const highLevelVenueSchema: z.ZodObject<{
3
+ franchise: z.ZodString;
4
+ id: z.ZodNumber;
5
+ isClosed: z.ZodBoolean;
6
+ name: z.ZodString;
7
+ venueRef: z.ZodNumber;
8
+ }, z.core.$strip>;
9
+ export type HighLevelVenue = z.infer<typeof highLevelVenueSchema>;
10
+ export declare function venues(): Promise<HighLevelVenue[]>;
11
+ declare const detailedVenueSchema: z.ZodObject<{
12
+ canPlaceOrder: z.ZodBoolean;
13
+ franchise: z.ZodString;
14
+ id: z.ZodNumber;
15
+ isClosed: z.ZodOptional<z.ZodBoolean>;
16
+ name: z.ZodString;
17
+ salesAreas: z.ZodArray<z.ZodObject<{
18
+ id: z.ZodNumber;
19
+ }, z.core.$strip>>;
20
+ venueCanOrder: z.ZodBoolean;
21
+ venueRef: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
22
+ }, z.core.$strip>;
23
+ export type DetailedVenue = z.infer<typeof detailedVenueSchema>;
24
+ export declare function getVenue(venue: HighLevelVenue): Promise<DetailedVenue>;
25
+ export declare const highLevelMenuSchema: z.ZodObject<{
26
+ canOrder: z.ZodBoolean;
27
+ franchise: z.ZodString;
28
+ id: z.ZodNumber;
29
+ name: z.ZodString;
30
+ salesAreaId: z.ZodNumber;
31
+ venueRef: z.ZodNumber;
32
+ }, z.core.$strip>;
33
+ export type HighLevelMenu = z.infer<typeof highLevelMenuSchema>;
34
+ export declare function getMenus({ venue, salesAreaId }: {
35
+ venue: DetailedVenue;
36
+ salesAreaId: number;
37
+ }): Promise<HighLevelMenu[]>;
38
+ declare const detailedMenuProductSchema: z.ZodObject<{
39
+ id: z.ZodNumber;
40
+ isOutOfStock: z.ZodBoolean;
41
+ itemType: z.ZodLiteral<"product">;
42
+ name: z.ZodString;
43
+ description: z.ZodString;
44
+ options: z.ZodObject<{
45
+ portion: z.ZodObject<{
46
+ options: z.ZodArray<z.ZodObject<{
47
+ label: z.ZodString;
48
+ value: z.ZodObject<{
49
+ price: z.ZodObject<{
50
+ currency: z.ZodString;
51
+ discount: z.ZodNumber;
52
+ initialValue: z.ZodNumber;
53
+ value: z.ZodNumber;
54
+ }, z.core.$strip>;
55
+ }, z.core.$strip>;
56
+ }, z.core.$strip>>;
57
+ }, z.core.$strip>;
58
+ }, z.core.$strip>;
59
+ }, z.core.$strip>;
60
+ export type DetailedMenuProduct = z.infer<typeof detailedMenuProductSchema>;
61
+ declare const detailedMenuSchema: z.ZodObject<{
62
+ data: z.ZodObject<{
63
+ canOrder: z.ZodBoolean;
64
+ categories: z.ZodArray<z.ZodObject<{
65
+ itemGroups: z.ZodArray<z.ZodObject<{
66
+ items: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
67
+ itemType: z.ZodLiteral<"text">;
68
+ text: z.ZodString;
69
+ }, z.core.$strip>, z.ZodObject<{
70
+ itemType: z.ZodLiteral<"divider">;
71
+ }, z.core.$strip>, z.ZodObject<{
72
+ itemType: z.ZodLiteral<"ale">;
73
+ }, z.core.$strip>, z.ZodObject<{
74
+ id: z.ZodNumber;
75
+ isOutOfStock: z.ZodBoolean;
76
+ itemType: z.ZodLiteral<"product">;
77
+ name: z.ZodString;
78
+ description: z.ZodString;
79
+ options: z.ZodObject<{
80
+ portion: z.ZodObject<{
81
+ options: z.ZodArray<z.ZodObject<{
82
+ label: z.ZodString;
83
+ value: z.ZodObject<{
84
+ price: z.ZodObject<{
85
+ currency: z.ZodString;
86
+ discount: z.ZodNumber;
87
+ initialValue: z.ZodNumber;
88
+ value: z.ZodNumber;
89
+ }, z.core.$strip>;
90
+ }, z.core.$strip>;
91
+ }, z.core.$strip>>;
92
+ }, z.core.$strip>;
93
+ }, z.core.$strip>;
94
+ }, z.core.$strip>]>>;
95
+ name: z.ZodNullable<z.ZodString>;
96
+ }, z.core.$strip>>;
97
+ name: z.ZodString;
98
+ }, z.core.$strip>>;
99
+ franchise: z.ZodString;
100
+ id: z.ZodNumber;
101
+ salesAreaId: z.ZodNumber;
102
+ venueRef: z.ZodNumber;
103
+ }, z.core.$strip>;
104
+ }, z.core.$strip>;
105
+ export type DetailedMenu = z.infer<typeof detailedMenuSchema>;
106
+ export declare function getMenu(highLevelMenu: HighLevelMenu): Promise<DetailedMenu>;
107
+ export interface Drink {
108
+ name: string;
109
+ units: number;
110
+ productId: number;
111
+ price: number;
112
+ ppu: number;
113
+ }
114
+ export declare function getDrinks(highLevelVenue: HighLevelVenue): Promise<Drink[]>;
115
+ export {};
116
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB,eAAO,MAAM,oBAAoB;;;;;;iBAM/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,wBAAsB,MAAM,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAIxD;AAED,QAAA,MAAM,mBAAmB;;;;;;;;;;;iBAWvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAsB,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAI5E;AAED,eAAO,MAAM,mBAAmB;;;;;;;iBAO9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAsB,QAAQ,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAI9H;AAED,QAAA,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;iBAqB7B,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,wBAAsB,OAAO,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAIjF;AAMD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAsB,SAAS,CAAC,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CA+GhF"}
@@ -0,0 +1,210 @@
1
+ import { z } from 'zod';
2
+ const API_ENDPOINT = 'https://ca.jdw-apps.net/api/v0.1';
3
+ const API_HEADERS = {
4
+ Authorization: 'Bearer 1|SFS9MMnn5deflq0BMcUTSijwSMBB4mc7NSG2rOhqb2765466',
5
+ };
6
+ async function request(path) {
7
+ const response = await fetch(`${API_ENDPOINT}${path}`, {
8
+ headers: API_HEADERS,
9
+ });
10
+ const json = await response.json();
11
+ return json;
12
+ }
13
+ export const highLevelVenueSchema = z.object({
14
+ franchise: z.string(),
15
+ id: z.number(),
16
+ isClosed: z.boolean(),
17
+ name: z.string(),
18
+ venueRef: z.number(),
19
+ });
20
+ export async function venues() {
21
+ const response = await request('/venues');
22
+ const venues = z.object({ data: z.array(highLevelVenueSchema) }).parse(response);
23
+ return venues.data;
24
+ }
25
+ const detailedVenueSchema = z.object({
26
+ canPlaceOrder: z.boolean(),
27
+ franchise: z.string(),
28
+ id: z.number(),
29
+ isClosed: z.boolean().optional(),
30
+ name: z.string(),
31
+ salesAreas: z.array(z.object({
32
+ id: z.number(),
33
+ })),
34
+ venueCanOrder: z.boolean(),
35
+ venueRef: z.union([z.string(), z.number()]),
36
+ });
37
+ export async function getVenue(venue) {
38
+ const response = await request(`/venues/${venue.venueRef}`);
39
+ const venueDetails = z.object({ data: detailedVenueSchema }).parse(response);
40
+ return venueDetails.data;
41
+ }
42
+ export const highLevelMenuSchema = z.object({
43
+ canOrder: z.boolean(),
44
+ franchise: z.string(),
45
+ id: z.number(),
46
+ name: z.string(),
47
+ salesAreaId: z.number(),
48
+ venueRef: z.number(),
49
+ });
50
+ export async function getMenus({ venue, salesAreaId }) {
51
+ const response = await request(`/${venue.franchise}/venues/${venue.venueRef}/sales-areas/${salesAreaId}/menus`);
52
+ const menus = z.object({ data: z.array(highLevelMenuSchema) }).parse(response);
53
+ return menus.data;
54
+ }
55
+ const detailedMenuProductSchema = z.object({
56
+ id: z.number(),
57
+ isOutOfStock: z.boolean(),
58
+ itemType: z.literal('product'),
59
+ name: z.string(),
60
+ description: z.string(),
61
+ options: z.object({
62
+ portion: z.object({
63
+ options: z.array(z.object({
64
+ label: z.string(),
65
+ value: z.object({
66
+ price: z.object({
67
+ currency: z.string(),
68
+ discount: z.number(),
69
+ initialValue: z.number(),
70
+ value: z.number(),
71
+ }),
72
+ }),
73
+ })),
74
+ }),
75
+ }),
76
+ });
77
+ const detailedMenuSchema = z.object({
78
+ data: z.object({
79
+ canOrder: z.boolean(),
80
+ categories: z.array(z.object({
81
+ itemGroups: z.array(z.object({
82
+ items: z.array(z.union([
83
+ z.object({
84
+ itemType: z.literal('text'),
85
+ text: z.string(),
86
+ }),
87
+ z.object({
88
+ itemType: z.literal('divider'),
89
+ }),
90
+ z.object({
91
+ itemType: z.literal('ale'),
92
+ }),
93
+ detailedMenuProductSchema,
94
+ ])),
95
+ name: z.string().nullable(),
96
+ })),
97
+ name: z.string(),
98
+ })),
99
+ franchise: z.string(),
100
+ id: z.number(),
101
+ salesAreaId: z.number(),
102
+ venueRef: z.number(),
103
+ }),
104
+ });
105
+ export async function getMenu(highLevelMenu) {
106
+ const response = await request(`/${highLevelMenu.franchise}/venues/${highLevelMenu.venueRef}/sales-areas/${highLevelMenu.salesAreaId}/menus/${highLevelMenu.id}`);
107
+ const detailedMenu = detailedMenuSchema.parse(response);
108
+ return detailedMenu;
109
+ }
110
+ function strengthAndVolumeToUnits(strength, volume) {
111
+ return (strength * volume) / 1000;
112
+ }
113
+ export async function getDrinks(highLevelVenue) {
114
+ const detailedVenue = await getVenue(highLevelVenue);
115
+ const salesArea = detailedVenue.salesAreas[0];
116
+ if (!salesArea)
117
+ return [];
118
+ const menus = await getMenus({ venue: detailedVenue, salesAreaId: salesArea.id });
119
+ let drinksMenu;
120
+ for (const menu of menus) {
121
+ if (menu.name === 'Drinks') {
122
+ drinksMenu = menu;
123
+ break;
124
+ }
125
+ }
126
+ if (!drinksMenu)
127
+ return [];
128
+ const res = await getMenu(drinksMenu);
129
+ // Convert menu to flat array of drinks
130
+ const hash_map = new Map();
131
+ for (const categories of res.data.categories) {
132
+ for (const itemGroup of categories.itemGroups) {
133
+ for (const item of itemGroup.items) {
134
+ if (item.itemType == 'product') {
135
+ // Skip out of stock
136
+ if (item.isOutOfStock) {
137
+ continue;
138
+ }
139
+ hash_map.set(item.id, item);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ const drinks = [];
145
+ for (const product of hash_map.values()) {
146
+ const strengthMatches = product.description.match(/(\d?\d?\.?\d?\d%)\s?ABV/);
147
+ const volumeDescriptionMatches = product.description.match(/(\d?\d\d)ml/);
148
+ let strength;
149
+ if (strengthMatches) {
150
+ strength = parseFloat(strengthMatches[0]);
151
+ }
152
+ let volumeDescription;
153
+ if (volumeDescriptionMatches) {
154
+ volumeDescription = parseFloat(volumeDescriptionMatches[0]);
155
+ }
156
+ let bestPortion;
157
+ let bestPPU = Infinity;
158
+ let bestUnits = 0;
159
+ for (const portion of product.options.portion.options) {
160
+ let units;
161
+ const volumeMatches = portion.label.match(/(\d?\d\d)ml/);
162
+ let volume;
163
+ if (volumeMatches && volumeMatches[1])
164
+ volume = parseFloat(volumeMatches[1]);
165
+ const unitsMatches = portion.label.match(/(\d?\.?\d?\d) unit/);
166
+ if (unitsMatches && unitsMatches[1])
167
+ units = parseFloat(unitsMatches[1]);
168
+ if (portion.label === 'Pint' && strength) {
169
+ units = strengthAndVolumeToUnits(strength, 568);
170
+ }
171
+ else if (['Half pint', 'Half Pint', 'Half'].includes(portion.label) && typeof strength !== 'undefined') {
172
+ units = strengthAndVolumeToUnits(strength, 284);
173
+ }
174
+ else if (typeof strength !== 'undefined' && volume) {
175
+ units = strengthAndVolumeToUnits(strength, volume);
176
+ }
177
+ else if (typeof strength !== 'undefined' && volumeDescription) {
178
+ units = strengthAndVolumeToUnits(strength, volumeDescription);
179
+ }
180
+ else if (typeof strength !== 'undefined' && portion.label === 'Single') {
181
+ units = strengthAndVolumeToUnits(strength, 25);
182
+ }
183
+ else if (typeof strength !== 'undefined' && portion.label === 'Double') {
184
+ units = strengthAndVolumeToUnits(strength, 50);
185
+ }
186
+ if (typeof units !== 'undefined') {
187
+ const ppu = portion.value.price.value / units;
188
+ if (ppu < bestPPU) {
189
+ bestPPU = ppu;
190
+ bestPortion = portion;
191
+ bestUnits = units;
192
+ }
193
+ }
194
+ }
195
+ if (typeof bestPortion !== 'undefined') {
196
+ drinks.push({
197
+ name: product.name,
198
+ units: bestUnits,
199
+ ppu: bestPPU,
200
+ productId: product.id,
201
+ price: bestPortion?.value.price.value,
202
+ });
203
+ }
204
+ }
205
+ drinks.sort((a, b) => {
206
+ return a.ppu - b.ppu;
207
+ });
208
+ return drinks;
209
+ }
210
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import js from '@eslint/js';
2
+ import globals from 'globals';
3
+ import tseslint from 'typescript-eslint';
4
+ import { defineConfig } from 'eslint/config';
5
+ import stylistic from '@stylistic/eslint-plugin';
6
+
7
+ export default defineConfig([
8
+ {
9
+ ignores: ['dist/**', 'node_modules/**'],
10
+ },
11
+ { files: ['**/*.{js,mjs,cjs,ts,mts,cts}'], plugins: { js, '@stylistic': stylistic }, extends: ['js/recommended'], languageOptions: { globals: { ...globals.browser, ...globals.node } } },
12
+ tseslint.configs.recommended,
13
+ stylistic.configs['recommended'],
14
+ {
15
+ rules: {
16
+ '@stylistic/semi': ['error', 'always'],
17
+ '@stylistic/brace-style': ['error', '1tbs'],
18
+ },
19
+ },
20
+ ]);
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "wetherspoons-api",
3
+ "version": "0.0.1",
4
+ "description": "A simple package to get data from the JD Wetherspoons API",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "lint": "eslint .",
10
+ "lint:fix": "eslint . --fix",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/slack2450/wetherspoons-api.git"
16
+ },
17
+ "author": "Joss Bird",
18
+ "license": "MIT",
19
+ "bugs": {
20
+ "url": "https://github.com/slack2450/wetherspoons-api/issues"
21
+ },
22
+ "homepage": "https://github.com/slack2450/wetherspoons-api#readme",
23
+ "devDependencies": {
24
+ "@eslint/js": "^9.37.0",
25
+ "@stylistic/eslint-plugin": "^5.4.0",
26
+ "eslint": "^9.37.0",
27
+ "globals": "^16.4.0",
28
+ "jiti": "^2.6.1",
29
+ "typescript": "^5.9.3",
30
+ "typescript-eslint": "^8.46.0"
31
+ },
32
+ "dependencies": {
33
+ "zod": "^4.1.12"
34
+ }
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,257 @@
1
+ import { z } from 'zod';
2
+
3
+ const API_ENDPOINT = 'https://ca.jdw-apps.net/api/v0.1';
4
+ const API_HEADERS = {
5
+ Authorization: 'Bearer 1|SFS9MMnn5deflq0BMcUTSijwSMBB4mc7NSG2rOhqb2765466',
6
+ };
7
+
8
+ async function request(path: string): Promise<unknown> {
9
+ const response = await fetch(`${API_ENDPOINT}${path}`,
10
+ {
11
+ headers: API_HEADERS,
12
+ },
13
+ );
14
+ const json = await response.json();
15
+ return json;
16
+ }
17
+
18
+ export const highLevelVenueSchema = z.object({
19
+ franchise: z.string(),
20
+ id: z.number(),
21
+ isClosed: z.boolean(),
22
+ name: z.string(),
23
+ venueRef: z.number(),
24
+ });
25
+
26
+ export type HighLevelVenue = z.infer<typeof highLevelVenueSchema>;
27
+
28
+ export async function venues(): Promise<HighLevelVenue[]> {
29
+ const response = await request('/venues');
30
+ const venues = z.object({ data: z.array(highLevelVenueSchema) }).parse(response);
31
+ return venues.data;
32
+ }
33
+
34
+ const detailedVenueSchema = z.object({
35
+ canPlaceOrder: z.boolean(),
36
+ franchise: z.string(),
37
+ id: z.number(),
38
+ isClosed: z.boolean().optional(),
39
+ name: z.string(),
40
+ salesAreas: z.array(z.object({
41
+ id: z.number(),
42
+ })),
43
+ venueCanOrder: z.boolean(),
44
+ venueRef: z.union([z.string(), z.number()]),
45
+ });
46
+
47
+ export type DetailedVenue = z.infer<typeof detailedVenueSchema>;
48
+
49
+ export async function getVenue(venue: HighLevelVenue): Promise<DetailedVenue> {
50
+ const response = await request(`/venues/${venue.venueRef}`);
51
+ const venueDetails = z.object({ data: detailedVenueSchema }).parse(response);
52
+ return venueDetails.data;
53
+ }
54
+
55
+ export const highLevelMenuSchema = z.object({
56
+ canOrder: z.boolean(),
57
+ franchise: z.string(),
58
+ id: z.number(),
59
+ name: z.string(),
60
+ salesAreaId: z.number(),
61
+ venueRef: z.number(),
62
+ });
63
+
64
+ export type HighLevelMenu = z.infer<typeof highLevelMenuSchema>;
65
+
66
+ export async function getMenus({ venue, salesAreaId }: { venue: DetailedVenue, salesAreaId: number }): Promise<HighLevelMenu[]> {
67
+ const response = await request(`/${venue.franchise}/venues/${venue.venueRef}/sales-areas/${salesAreaId}/menus`);
68
+ const menus = z.object({ data: z.array(highLevelMenuSchema) }).parse(response);
69
+ return menus.data;
70
+ }
71
+
72
+ const detailedMenuProductSchema = z.object({
73
+ id: z.number(),
74
+ isOutOfStock: z.boolean(),
75
+ itemType: z.literal('product'),
76
+ name: z.string(),
77
+ description: z.string(),
78
+ options: z.object({
79
+ portion: z.object({
80
+ options: z.array(z.object({
81
+ label: z.string(),
82
+ value: z.object({
83
+ price: z.object({
84
+ currency: z.string(),
85
+ discount: z.number(),
86
+ initialValue: z.number(),
87
+ value: z.number(),
88
+ }),
89
+ }),
90
+ })),
91
+ }),
92
+ }),
93
+ });
94
+
95
+ export type DetailedMenuProduct = z.infer<typeof detailedMenuProductSchema>;
96
+
97
+ const detailedMenuSchema = z.object({
98
+ data: z.object({
99
+ canOrder: z.boolean(),
100
+ categories: z.array(z.object({
101
+ itemGroups: z.array(z.object({
102
+ items: z.array(z.union([
103
+ z.object({
104
+ itemType: z.literal('text'),
105
+ text: z.string(),
106
+ }),
107
+ z.object({
108
+ itemType: z.literal('divider'),
109
+ }),
110
+ z.object({
111
+ itemType: z.literal('ale'),
112
+ }),
113
+ detailedMenuProductSchema,
114
+ ])),
115
+ name: z.string().nullable(),
116
+ })),
117
+ name: z.string(),
118
+ })),
119
+ franchise: z.string(),
120
+ id: z.number(),
121
+ salesAreaId: z.number(),
122
+ venueRef: z.number(),
123
+ }),
124
+ });
125
+
126
+ export type DetailedMenu = z.infer<typeof detailedMenuSchema>;
127
+
128
+ export async function getMenu(highLevelMenu: HighLevelMenu): Promise<DetailedMenu> {
129
+ const response = await request(`/${highLevelMenu.franchise}/venues/${highLevelMenu.venueRef}/sales-areas/${highLevelMenu.salesAreaId}/menus/${highLevelMenu.id}`);
130
+ const detailedMenu = detailedMenuSchema.parse(response);
131
+ return detailedMenu;
132
+ }
133
+
134
+ function strengthAndVolumeToUnits(strength: number, volume: number) {
135
+ return (strength * volume) / 1000;
136
+ }
137
+
138
+ export interface Drink {
139
+ name: string
140
+ units: number
141
+ productId: number
142
+ price: number
143
+ ppu: number
144
+ }
145
+
146
+ export async function getDrinks(highLevelVenue: HighLevelVenue): Promise<Drink[]> {
147
+ const detailedVenue = await getVenue(highLevelVenue);
148
+
149
+ const salesArea = detailedVenue.salesAreas[0];
150
+
151
+ if (!salesArea) return [];
152
+
153
+ const menus = await getMenus({ venue: detailedVenue, salesAreaId: salesArea.id });
154
+
155
+ let drinksMenu;
156
+ for (const menu of menus) {
157
+ if (menu.name === 'Drinks') {
158
+ drinksMenu = menu;
159
+ break;
160
+ }
161
+ }
162
+
163
+ if (!drinksMenu) return [];
164
+ const res = await getMenu(drinksMenu);
165
+
166
+ // Convert menu to flat array of drinks
167
+ const hash_map = new Map<number, DetailedMenuProduct>();
168
+
169
+ for (const categories of res.data.categories) {
170
+ for (const itemGroup of categories.itemGroups) {
171
+ for (const item of itemGroup.items) {
172
+ if (item.itemType == 'product') {
173
+ // Skip out of stock
174
+ if (item.isOutOfStock) {
175
+ continue;
176
+ }
177
+ hash_map.set(item.id, item);
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ const drinks: Drink[] = [];
184
+
185
+ for (const product of hash_map.values()) {
186
+ const strengthMatches = product.description.match(/(\d?\d?\.?\d?\d%)\s?ABV/);
187
+ const volumeDescriptionMatches = product.description.match(/(\d?\d\d)ml/);
188
+
189
+ let strength;
190
+ if (strengthMatches) {
191
+ strength = parseFloat(strengthMatches[0]);
192
+ }
193
+
194
+ let volumeDescription;
195
+ if (volumeDescriptionMatches) {
196
+ volumeDescription = parseFloat(volumeDescriptionMatches[0]);
197
+ }
198
+
199
+ let bestPortion;
200
+ let bestPPU = Infinity;
201
+ let bestUnits = 0;
202
+
203
+ for (const portion of product.options.portion.options) {
204
+ let units;
205
+
206
+ const volumeMatches = portion.label.match(/(\d?\d\d)ml/);
207
+
208
+ let volume;
209
+ if (volumeMatches && volumeMatches[1])
210
+ volume = parseFloat(volumeMatches[1]);
211
+
212
+ const unitsMatches = portion.label.match(/(\d?\.?\d?\d) unit/);
213
+ if (unitsMatches && unitsMatches[1])
214
+ units = parseFloat(unitsMatches[1]);
215
+
216
+ if (portion.label === 'Pint' && strength) {
217
+ units = strengthAndVolumeToUnits(strength, 568);
218
+ } else if (['Half pint', 'Half Pint', 'Half'].includes(portion.label) && typeof strength !== 'undefined') {
219
+ units = strengthAndVolumeToUnits(strength, 284);
220
+ } else if (typeof strength !== 'undefined' && volume) {
221
+ units = strengthAndVolumeToUnits(strength, volume);
222
+ } else if (typeof strength !== 'undefined' && volumeDescription) {
223
+ units = strengthAndVolumeToUnits(strength, volumeDescription);
224
+ } else if (typeof strength !== 'undefined' && portion.label === 'Single') {
225
+ units = strengthAndVolumeToUnits(strength, 25);
226
+ } else if (typeof strength !== 'undefined' && portion.label === 'Double') {
227
+ units = strengthAndVolumeToUnits(strength, 50);
228
+ }
229
+
230
+ if (typeof units !== 'undefined') {
231
+ const ppu = portion.value.price.value / units;
232
+
233
+ if (ppu < bestPPU) {
234
+ bestPPU = ppu;
235
+ bestPortion = portion;
236
+ bestUnits = units;
237
+ }
238
+ }
239
+ }
240
+
241
+ if (typeof bestPortion !== 'undefined') {
242
+ drinks.push({
243
+ name: product.name,
244
+ units: bestUnits,
245
+ ppu: bestPPU,
246
+ productId: product.id,
247
+ price: bestPortion?.value.price.value,
248
+ });
249
+ }
250
+ }
251
+
252
+ drinks.sort((a, b) => {
253
+ return a.ppu - b.ppu;
254
+ });
255
+
256
+ return drinks;
257
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "outDir": "./dist",
6
+
7
+ // Environment Settings
8
+ // See also https://aka.ms/tsconfig/module
9
+ "module": "node20",
10
+ "target": "es2024",
11
+ "types": [],
12
+ // For nodejs:
13
+ // "lib": ["esnext"],
14
+ // "types": ["node"],
15
+ // and npm install -D @types/node
16
+
17
+ // Other Outputs
18
+ "sourceMap": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+
22
+ // Stricter Typechecking Options
23
+ "noUncheckedIndexedAccess": true,
24
+ "exactOptionalPropertyTypes": true,
25
+
26
+ // Style Options
27
+ // "noImplicitReturns": true,
28
+ // "noImplicitOverride": true,
29
+ // "noUnusedLocals": true,
30
+ // "noUnusedParameters": true,
31
+ // "noFallthroughCasesInSwitch": true,
32
+ // "noPropertyAccessFromIndexSignature": true,
33
+
34
+ // Recommended Options
35
+ "strict": true,
36
+ "jsx": "react-jsx",
37
+ "verbatimModuleSyntax": true,
38
+ "isolatedModules": true,
39
+ "noUncheckedSideEffectImports": true,
40
+ "moduleDetection": "force",
41
+ "skipLibCheck": true,
42
+ }
43
+ }