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.
- package/.github/workflows/publish.yml +0 -0
- package/dist/eslint.config.d.ts +3 -0
- package/dist/eslint.config.d.ts.map +1 -0
- package/dist/eslint.config.js +20 -0
- package/dist/eslint.config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/src/index.d.ts +116 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +210 -0
- package/dist/src/index.js.map +1 -0
- package/eslint.config.ts +20 -0
- package/package.json +35 -0
- package/src/index.ts +257 -0
- package/tsconfig.json +43 -0
|
File without changes
|
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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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"}
|
package/eslint.config.ts
ADDED
|
@@ -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
|
+
}
|