daiso 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -537
- package/dist/api/actionsProxy.js +82 -0
- package/dist/api/cgvHandlers.js +103 -16
- package/dist/api/daisoHandlers.js +127 -0
- package/dist/api/emart24Handlers.js +176 -0
- package/dist/api/gs25Handlers.js +256 -0
- package/dist/api/handlers.js +184 -108
- package/dist/api/healthChecks.js +332 -0
- package/dist/api/lottecinemaHandlers.js +155 -0
- package/dist/api/lottemartHandlers.js +128 -0
- package/dist/api/megaboxHandlers.js +81 -50
- package/dist/api/routes/cuRoutes.js +17 -0
- package/dist/api/routes/daisoRoutes.js +6 -1
- package/dist/api/routes/emart24Routes.js +22 -0
- package/dist/api/routes/gs25Routes.js +22 -0
- package/dist/api/routes/healthRoutes.js +44 -0
- package/dist/api/routes/lottecinemaRoutes.js +22 -0
- package/dist/api/routes/lottemartRoutes.js +18 -0
- package/dist/api/routes/oliveyoungRoutes.js +6 -1
- package/dist/api/routes/sevenelevenRoutes.js +32 -0
- package/dist/api/sevenelevenHandlers.js +200 -0
- package/dist/cli/args.js +54 -0
- package/dist/cli/commands/convenience.js +241 -0
- package/dist/cli/commands/daiso.js +96 -0
- package/dist/cli/constants.js +5 -0
- package/dist/cli/deps.js +48 -0
- package/dist/cli/http.js +98 -0
- package/dist/cli/interactiveFetch.js +31 -0
- package/dist/cli/interactiveItemSearch.js +259 -0
- package/dist/cli/interactivePrompt.js +49 -0
- package/dist/cli/interactiveTypes.js +4 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli.js +66 -181
- package/dist/cliHelp.js +216 -9
- package/dist/cliInteractive.js +174 -0
- package/dist/cliPicker.js +56 -0
- package/dist/cliRenderer.js +139 -0
- package/dist/index.js +147 -8
- package/dist/pages/openapi.js +2 -1
- package/dist/pages/openapiFullSpec.js +58 -0
- package/dist/pages/openapiResponse.js +28 -0
- package/dist/pages/openapiSpec.js +2 -41
- package/dist/pages/openapiSpecActions.js +109 -0
- package/dist/pages/openapiSpecComponents.js +30 -391
- package/dist/pages/openapiSpecComponentsCgv.js +24 -0
- package/dist/pages/openapiSpecComponentsCu.js +112 -0
- package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
- package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
- package/dist/pages/openapiSpecComponentsGs25.js +109 -0
- package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
- package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
- package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
- package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
- package/dist/pages/openapiSpecPathsCgv.js +66 -3
- package/dist/pages/openapiSpecPathsCu.js +138 -0
- package/dist/pages/openapiSpecPathsDaiso.js +302 -0
- package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
- package/dist/pages/openapiSpecPathsEmart24.js +223 -0
- package/dist/pages/openapiSpecPathsGs25.js +166 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +135 -0
- package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
- package/dist/pages/openapiSpecPathsMegabox.js +52 -3
- package/dist/pages/openapiSpecPathsOliveyoung.js +231 -0
- package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
- package/dist/pages/prompt.js +8 -402
- package/dist/pages/promptCoreText.js +278 -0
- package/dist/pages/promptReferenceText.js +131 -0
- package/dist/pages/promptRetailCinemaText.js +273 -0
- package/dist/services/cgv/client.js +6 -1
- package/dist/services/cgv/index.js +3 -3
- package/dist/services/cgv/location.js +235 -0
- package/dist/services/cgv/tools/findTheaters.js +30 -5
- package/dist/services/cgv/tools/getTimetable.js +42 -14
- package/dist/services/cgv/tools/searchMovies.js +40 -12
- package/dist/services/cgv/transport.js +0 -3
- package/dist/services/cu/api.js +11 -0
- package/dist/services/cu/client.js +297 -0
- package/dist/services/cu/index.js +21 -0
- package/dist/services/cu/tools/checkInventory.js +123 -0
- package/dist/services/cu/tools/findNearbyStores.js +49 -0
- package/dist/services/cu/types.js +4 -0
- package/dist/services/daiso/api.js +22 -2
- package/dist/services/daiso/client.js +59 -0
- package/dist/services/daiso/index.js +2 -0
- package/dist/services/daiso/product.js +17 -0
- package/dist/services/daiso/tools/checkInventory.js +76 -41
- package/dist/services/daiso/tools/findStores.js +17 -7
- package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
- package/dist/services/emart24/api.js +11 -0
- package/dist/services/emart24/client.js +173 -0
- package/dist/services/emart24/index.js +22 -0
- package/dist/services/emart24/inventoryLookup.js +134 -0
- package/dist/services/emart24/tools/checkInventory.js +54 -0
- package/dist/services/emart24/tools/findNearbyStores.js +72 -0
- package/dist/services/emart24/tools/searchProducts.js +63 -0
- package/dist/services/emart24/types.js +4 -0
- package/dist/services/gs25/api.js +9 -0
- package/dist/services/gs25/client.js +198 -0
- package/dist/services/gs25/index.js +22 -0
- package/dist/services/gs25/storeUtils.js +168 -0
- package/dist/services/gs25/tools/checkInventory.js +163 -0
- package/dist/services/gs25/tools/findNearbyStores.js +103 -0
- package/dist/services/gs25/tools/searchProducts.js +50 -0
- package/dist/services/gs25/types.js +4 -0
- package/dist/services/lottecinema/api.js +12 -0
- package/dist/services/lottecinema/client.js +161 -0
- package/dist/services/lottecinema/index.js +30 -0
- package/dist/services/lottecinema/location.js +191 -0
- package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
- package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
- package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
- package/dist/services/lottecinema/types.js +4 -0
- package/dist/services/lottemart/api.js +31 -0
- package/dist/services/lottemart/client.js +245 -0
- package/dist/services/lottemart/clientTypes.js +4 -0
- package/dist/services/lottemart/config.js +4 -0
- package/dist/services/lottemart/debug.js +92 -0
- package/dist/services/lottemart/geocode.js +49 -0
- package/dist/services/lottemart/index.js +30 -0
- package/dist/services/lottemart/keyword.js +29 -0
- package/dist/services/lottemart/parser.js +185 -0
- package/dist/services/lottemart/session.js +331 -0
- package/dist/services/lottemart/storeSearch.js +52 -0
- package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
- package/dist/services/lottemart/tools/searchProducts.js +62 -0
- package/dist/services/lottemart/types.js +4 -0
- package/dist/services/lottemart/zetta.js +105 -0
- package/dist/services/megabox/location.js +219 -0
- package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
- package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
- package/dist/services/megabox/tools/listNowShowing.js +31 -3
- package/dist/services/oliveyoung/api.js +2 -0
- package/dist/services/oliveyoung/client.js +150 -9
- package/dist/services/oliveyoung/index.js +3 -1
- package/dist/services/oliveyoung/tools/checkInventory.js +15 -4
- package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
- package/dist/services/seveneleven/api.js +15 -0
- package/dist/services/seveneleven/client.js +279 -0
- package/dist/services/seveneleven/index.js +30 -0
- package/dist/services/seveneleven/inventory.js +302 -0
- package/dist/services/seveneleven/productKeyword.js +141 -0
- package/dist/services/seveneleven/tools/checkInventory.js +82 -0
- package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
- package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
- package/dist/services/seveneleven/tools/searchProducts.js +56 -0
- package/dist/services/seveneleven/tools/searchStores.js +45 -0
- package/dist/services/seveneleven/types.js +4 -0
- package/dist/utils/cliInteractiveHelpers.js +63 -0
- package/dist/utils/daisoKeyword.js +30 -0
- package/dist/utils/http.js +16 -2
- package/openapi.json +1977 -734
- package/openapi.yaml +1430 -543
- package/package.json +28 -15
- package/dist/tools/checkInventory.js +0 -98
- package/dist/tools/findStores.js +0 -125
- package/dist/tools/getPriceInfo.js +0 -77
- package/dist/tools/searchProducts.js +0 -61
- package/dist/types/index.js +0 -4
|
@@ -18,9 +18,9 @@ class CgvService {
|
|
|
18
18
|
metadata = CGV_METADATA;
|
|
19
19
|
getTools() {
|
|
20
20
|
return [
|
|
21
|
-
createFindTheatersTool(this.options.zyteApiKey),
|
|
22
|
-
createSearchMoviesTool(this.options.zyteApiKey),
|
|
23
|
-
createGetTimetableTool(this.options.zyteApiKey),
|
|
21
|
+
createFindTheatersTool(this.options.zyteApiKey, this.options.googleMapsApiKey),
|
|
22
|
+
createSearchMoviesTool(this.options.zyteApiKey, this.options.googleMapsApiKey),
|
|
23
|
+
createGetTimetableTool(this.options.zyteApiKey, this.options.googleMapsApiKey),
|
|
24
24
|
];
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CGV 위치 해석 및 근처 극장 조회 보조 모듈
|
|
3
|
+
*/
|
|
4
|
+
import { fetchJson } from '../../utils/http.js';
|
|
5
|
+
import { fetchCgvTheaters } from './client.js';
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
7
|
+
const USER_GEOCODE_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
8
|
+
const THEATER_GEOCODE_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
9
|
+
const MAX_CANDIDATE_THEATERS = 12;
|
|
10
|
+
const LOCATION_STOPWORDS = new Set([
|
|
11
|
+
'cgv',
|
|
12
|
+
'극장',
|
|
13
|
+
'영화',
|
|
14
|
+
'주변',
|
|
15
|
+
'근처',
|
|
16
|
+
'오늘',
|
|
17
|
+
'상영',
|
|
18
|
+
'시간표',
|
|
19
|
+
'좌석',
|
|
20
|
+
'잔여',
|
|
21
|
+
'찾아',
|
|
22
|
+
'주세요',
|
|
23
|
+
'주세요요',
|
|
24
|
+
'역',
|
|
25
|
+
]);
|
|
26
|
+
const BRAND_PATTERN = /\b(?:cgv|씨지브이)\b/giu;
|
|
27
|
+
const TRAILING_INTENT_PATTERN = /\s+(?:극장|영화관|영화|상영작|시간표|좌석|잔여좌석|남은좌석|목록|찾고|찾아|찾아서|알려|보여|추천|조회|확인|해주세요|해줘).*/u;
|
|
28
|
+
const userGeocodeCache = new Map();
|
|
29
|
+
const theaterGeocodeCache = new Map();
|
|
30
|
+
function calculateDistanceKm(lat1, lng1, lat2, lng2) {
|
|
31
|
+
const toRad = (deg) => (deg * Math.PI) / 180;
|
|
32
|
+
const dLat = toRad(lat2 - lat1);
|
|
33
|
+
const dLng = toRad(lng2 - lng1);
|
|
34
|
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
35
|
+
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
|
36
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
37
|
+
return 6371 * c;
|
|
38
|
+
}
|
|
39
|
+
function isValidCoordinate(value) {
|
|
40
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
41
|
+
}
|
|
42
|
+
function normalizeWhitespace(text) {
|
|
43
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
44
|
+
}
|
|
45
|
+
function normalizeText(text) {
|
|
46
|
+
return text.replace(/\s+/g, '').toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
function sanitizeLocationKeyword(keyword) {
|
|
49
|
+
const trimmed = normalizeWhitespace(keyword);
|
|
50
|
+
if (trimmed.length === 0) {
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
let sanitized = normalizeWhitespace(trimmed.replace(BRAND_PATTERN, ' '));
|
|
54
|
+
const nearbyMatch = sanitized.match(/^(.+?)\s*(?:근처|주변)\s*(?:극장|영화관)?(?:\s.*)?$/u);
|
|
55
|
+
if (nearbyMatch?.[1]) {
|
|
56
|
+
sanitized = normalizeWhitespace(nearbyMatch[1]);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
sanitized = normalizeWhitespace(sanitized.replace(TRAILING_INTENT_PATTERN, ''));
|
|
60
|
+
}
|
|
61
|
+
return sanitized || trimmed;
|
|
62
|
+
}
|
|
63
|
+
function extractLocationTokens(...values) {
|
|
64
|
+
const tokens = new Set();
|
|
65
|
+
for (const value of values) {
|
|
66
|
+
const matches = (value || '').match(/[0-9A-Za-z가-힣]+/g) || [];
|
|
67
|
+
for (const match of matches) {
|
|
68
|
+
const normalized = normalizeText(match);
|
|
69
|
+
if (normalized.length < 2 || LOCATION_STOPWORDS.has(normalized)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
tokens.add(normalized);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...tokens];
|
|
76
|
+
}
|
|
77
|
+
function scoreTheaterName(theaterName, tokens) {
|
|
78
|
+
const normalized = normalizeText(theaterName);
|
|
79
|
+
return tokens.reduce((score, token) => (normalized.includes(token) ? score + 1 : score), 0);
|
|
80
|
+
}
|
|
81
|
+
async function geocodeAddress(query, options, cacheKey, ttlMs, cache) {
|
|
82
|
+
const apiKey = (options.googleMapsApiKey || '').trim();
|
|
83
|
+
const trimmedQuery = query.trim();
|
|
84
|
+
if (apiKey.length === 0 || trimmedQuery.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const cached = cache.get(cacheKey);
|
|
88
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
89
|
+
return cached.value;
|
|
90
|
+
}
|
|
91
|
+
const endpoint = new URL('https://maps.googleapis.com/maps/api/geocode/json');
|
|
92
|
+
endpoint.searchParams.set('address', trimmedQuery);
|
|
93
|
+
endpoint.searchParams.set('key', apiKey);
|
|
94
|
+
const body = await fetchJson(endpoint.toString(), {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
timeout: options.timeout || DEFAULT_TIMEOUT_MS,
|
|
97
|
+
headers: { Accept: 'application/json' },
|
|
98
|
+
});
|
|
99
|
+
const firstResult = body.status === 'OK' ? body.results?.[0] : null;
|
|
100
|
+
const latitude = firstResult?.geometry?.location?.lat;
|
|
101
|
+
const longitude = firstResult?.geometry?.location?.lng;
|
|
102
|
+
const value = typeof latitude === 'number' && typeof longitude === 'number'
|
|
103
|
+
? {
|
|
104
|
+
latitude,
|
|
105
|
+
longitude,
|
|
106
|
+
formattedAddress: firstResult?.formatted_address || null,
|
|
107
|
+
}
|
|
108
|
+
: null;
|
|
109
|
+
cache.set(cacheKey, {
|
|
110
|
+
expiresAt: Date.now() + ttlMs,
|
|
111
|
+
value,
|
|
112
|
+
});
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
export async function resolveCgvLocation(params, options = {}) {
|
|
116
|
+
const keyword = sanitizeLocationKeyword((params.keyword || '').trim());
|
|
117
|
+
const hasCoordinates = isValidCoordinate(params.latitude) && isValidCoordinate(params.longitude);
|
|
118
|
+
if (hasCoordinates) {
|
|
119
|
+
return {
|
|
120
|
+
keyword: keyword.length > 0 ? keyword : null,
|
|
121
|
+
latitude: params.latitude,
|
|
122
|
+
longitude: params.longitude,
|
|
123
|
+
geocodeUsed: false,
|
|
124
|
+
formattedAddress: null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (keyword.length === 0) {
|
|
128
|
+
return {
|
|
129
|
+
keyword: null,
|
|
130
|
+
latitude: null,
|
|
131
|
+
longitude: null,
|
|
132
|
+
geocodeUsed: false,
|
|
133
|
+
formattedAddress: null,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const geocoded = await geocodeAddress(keyword, options, `user:${keyword}`, USER_GEOCODE_CACHE_TTL_MS, userGeocodeCache);
|
|
137
|
+
return {
|
|
138
|
+
keyword,
|
|
139
|
+
latitude: geocoded?.latitude ?? null,
|
|
140
|
+
longitude: geocoded?.longitude ?? null,
|
|
141
|
+
geocodeUsed: Boolean(geocoded),
|
|
142
|
+
formattedAddress: geocoded?.formattedAddress ?? null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function geocodeCgvTheater(theater, options) {
|
|
146
|
+
const cleanName = theater.theaterName.replace(/^CGV\s*/i, '').trim();
|
|
147
|
+
const geocoded = await geocodeAddress(`대한민국 CGV ${cleanName}`, options, `theater:${theater.theaterCode}:${cleanName}`, THEATER_GEOCODE_CACHE_TTL_MS, theaterGeocodeCache);
|
|
148
|
+
return geocoded
|
|
149
|
+
? {
|
|
150
|
+
latitude: geocoded.latitude,
|
|
151
|
+
longitude: geocoded.longitude,
|
|
152
|
+
address: geocoded.formattedAddress,
|
|
153
|
+
}
|
|
154
|
+
: null;
|
|
155
|
+
}
|
|
156
|
+
function pickCandidateTheaters(theaters, tokens) {
|
|
157
|
+
if (tokens.length === 0) {
|
|
158
|
+
return theaters.slice(0, MAX_CANDIDATE_THEATERS);
|
|
159
|
+
}
|
|
160
|
+
const scored = theaters
|
|
161
|
+
.map((theater) => ({ theater, score: scoreTheaterName(theater.theaterName, tokens) }))
|
|
162
|
+
.filter((item) => item.score > 0)
|
|
163
|
+
.sort((left, right) => right.score - left.score || left.theater.theaterName.localeCompare(right.theater.theaterName))
|
|
164
|
+
.map((item) => item.theater);
|
|
165
|
+
return scored.slice(0, MAX_CANDIDATE_THEATERS);
|
|
166
|
+
}
|
|
167
|
+
export async function fetchCgvNearbyTheaters(params, options = {}) {
|
|
168
|
+
const limit = Math.max(1, params.limit || 10);
|
|
169
|
+
const timeout = params.timeout || DEFAULT_TIMEOUT_MS;
|
|
170
|
+
const theaters = await fetchCgvTheaters({
|
|
171
|
+
playDate: params.playDate,
|
|
172
|
+
regionCode: params.regionCode,
|
|
173
|
+
timeout,
|
|
174
|
+
zyteApiKey: options.zyteApiKey,
|
|
175
|
+
});
|
|
176
|
+
const location = await resolveCgvLocation(params, options);
|
|
177
|
+
const tokens = extractLocationTokens(location.keyword, location.formattedAddress);
|
|
178
|
+
const hasGoogleKey = (options.googleMapsApiKey || '').trim().length > 0;
|
|
179
|
+
if (tokens.length === 0 && !hasGoogleKey) {
|
|
180
|
+
return {
|
|
181
|
+
...location,
|
|
182
|
+
playDate: params.playDate,
|
|
183
|
+
regionCode: params.regionCode || null,
|
|
184
|
+
count: 0,
|
|
185
|
+
theaters: [],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const candidates = pickCandidateTheaters(theaters, tokens);
|
|
189
|
+
const resolvedCandidates = await Promise.all(candidates.map(async (theater) => {
|
|
190
|
+
const geocoded = await geocodeCgvTheater(theater, options);
|
|
191
|
+
const distanceKm = geocoded && location.latitude !== null && location.longitude !== null
|
|
192
|
+
? Number(calculateDistanceKm(location.latitude, location.longitude, geocoded.latitude, geocoded.longitude).toFixed(2))
|
|
193
|
+
: null;
|
|
194
|
+
return {
|
|
195
|
+
...theater,
|
|
196
|
+
latitude: geocoded?.latitude ?? null,
|
|
197
|
+
longitude: geocoded?.longitude ?? null,
|
|
198
|
+
distanceKm,
|
|
199
|
+
address: geocoded?.address ?? null,
|
|
200
|
+
};
|
|
201
|
+
}));
|
|
202
|
+
const sorted = resolvedCandidates.sort((left, right) => {
|
|
203
|
+
const leftDistance = left.distanceKm ?? Number.POSITIVE_INFINITY;
|
|
204
|
+
const rightDistance = right.distanceKm ?? Number.POSITIVE_INFINITY;
|
|
205
|
+
return leftDistance - rightDistance || left.theaterName.localeCompare(right.theaterName);
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
...location,
|
|
209
|
+
playDate: params.playDate,
|
|
210
|
+
regionCode: params.regionCode || null,
|
|
211
|
+
count: sorted.slice(0, limit).length,
|
|
212
|
+
theaters: sorted.slice(0, limit),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export async function resolveCgvNearestTheater(params, options = {}) {
|
|
216
|
+
const nearby = await fetchCgvNearbyTheaters({
|
|
217
|
+
...params,
|
|
218
|
+
limit: 1,
|
|
219
|
+
timeout: params.timeout,
|
|
220
|
+
}, options);
|
|
221
|
+
return {
|
|
222
|
+
location: {
|
|
223
|
+
keyword: nearby.keyword,
|
|
224
|
+
latitude: nearby.latitude,
|
|
225
|
+
longitude: nearby.longitude,
|
|
226
|
+
geocodeUsed: nearby.geocodeUsed,
|
|
227
|
+
formattedAddress: nearby.formattedAddress,
|
|
228
|
+
},
|
|
229
|
+
theater: nearby.theaters[0] || null,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
export function __testOnlyClearCgvLocationCaches() {
|
|
233
|
+
userGeocodeCache.clear();
|
|
234
|
+
theaterGeocodeCache.clear();
|
|
235
|
+
}
|
|
@@ -3,8 +3,27 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import { fetchCgvTheaters, toYyyymmdd } from '../client.js';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { fetchCgvNearbyTheaters } from '../location.js';
|
|
7
|
+
async function findTheaters(args, apiKey, googleMapsApiKey) {
|
|
8
|
+
const { playDate = toYyyymmdd(), regionCode, keyword, latitude, longitude, limit = 30, timeoutMs = 15000, } = args;
|
|
9
|
+
if (keyword || typeof latitude === 'number' || typeof longitude === 'number') {
|
|
10
|
+
const result = await fetchCgvNearbyTheaters({
|
|
11
|
+
playDate,
|
|
12
|
+
regionCode,
|
|
13
|
+
keyword,
|
|
14
|
+
latitude,
|
|
15
|
+
longitude,
|
|
16
|
+
limit,
|
|
17
|
+
timeout: timeoutMs,
|
|
18
|
+
}, {
|
|
19
|
+
timeout: timeoutMs,
|
|
20
|
+
zyteApiKey: apiKey,
|
|
21
|
+
googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
8
27
|
const theaters = await fetchCgvTheaters({
|
|
9
28
|
playDate,
|
|
10
29
|
regionCode,
|
|
@@ -16,6 +35,9 @@ async function findTheaters(args, apiKey) {
|
|
|
16
35
|
playDate,
|
|
17
36
|
filters: {
|
|
18
37
|
regionCode: regionCode || null,
|
|
38
|
+
keyword: keyword || null,
|
|
39
|
+
latitude: latitude ?? null,
|
|
40
|
+
longitude: longitude ?? null,
|
|
19
41
|
limit,
|
|
20
42
|
},
|
|
21
43
|
count: sliced.length,
|
|
@@ -25,19 +47,22 @@ async function findTheaters(args, apiKey) {
|
|
|
25
47
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
26
48
|
};
|
|
27
49
|
}
|
|
28
|
-
export function createFindTheatersTool(apiKey) {
|
|
50
|
+
export function createFindTheatersTool(apiKey, googleMapsApiKey) {
|
|
29
51
|
return {
|
|
30
52
|
name: 'cgv_find_theaters',
|
|
31
53
|
metadata: {
|
|
32
54
|
title: 'CGV 극장 검색',
|
|
33
|
-
description: '지역 코드 기준으로 CGV 극장 목록을 조회합니다.',
|
|
55
|
+
description: '지역 코드 또는 위치 키워드 기준으로 CGV 극장 목록을 조회합니다.',
|
|
34
56
|
inputSchema: {
|
|
35
57
|
playDate: z.string().optional().describe('조회 날짜(YYYYMMDD, 기본값: 오늘)'),
|
|
36
58
|
regionCode: z.string().optional().describe('지역 코드 (예: 01 서울)'),
|
|
59
|
+
keyword: z.string().optional().describe('위치 키워드 (예: 안산 중앙역, 강남역)'),
|
|
60
|
+
latitude: z.number().optional().describe('위도'),
|
|
61
|
+
longitude: z.number().optional().describe('경도'),
|
|
37
62
|
limit: z.number().optional().default(30).describe('최대 결과 수 (기본값: 30)'),
|
|
38
63
|
timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
|
|
39
64
|
},
|
|
40
65
|
},
|
|
41
|
-
handler: ((args) => findTheaters(args, apiKey)),
|
|
66
|
+
handler: ((args) => findTheaters(args, apiKey, googleMapsApiKey)),
|
|
42
67
|
};
|
|
43
68
|
}
|
|
@@ -3,24 +3,49 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import { fetchCgvTimetable, toYyyymmdd } from '../client.js';
|
|
6
|
+
import { resolveCgvNearestTheater } from '../location.js';
|
|
6
7
|
import { filterAndSortTimetable } from '../timetable.js';
|
|
7
|
-
async function getTimetable(args, apiKey) {
|
|
8
|
-
const { playDate = toYyyymmdd(), theaterCode, movieCode, limit = 50, timeoutMs = 15000, } = args;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
async function getTimetable(args, apiKey, googleMapsApiKey) {
|
|
9
|
+
const { playDate = toYyyymmdd(), theaterCode, movieCode, keyword, latitude, longitude, limit = 50, timeoutMs = 15000, } = args;
|
|
10
|
+
let resolvedTheater = null;
|
|
11
|
+
let targetTheaterCode = theaterCode;
|
|
12
|
+
if (!targetTheaterCode && (keyword || typeof latitude === 'number' || typeof longitude === 'number')) {
|
|
13
|
+
const resolved = await resolveCgvNearestTheater({
|
|
14
|
+
playDate,
|
|
15
|
+
keyword,
|
|
16
|
+
latitude,
|
|
17
|
+
longitude,
|
|
18
|
+
timeout: timeoutMs,
|
|
19
|
+
}, {
|
|
20
|
+
timeout: timeoutMs,
|
|
21
|
+
zyteApiKey: apiKey,
|
|
22
|
+
googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
|
|
23
|
+
});
|
|
24
|
+
resolvedTheater = resolved.theater;
|
|
25
|
+
targetTheaterCode = resolved.theater?.theaterCode;
|
|
26
|
+
}
|
|
27
|
+
const shouldReturnEmpty = !targetTheaterCode && (keyword || typeof latitude === 'number' || typeof longitude === 'number');
|
|
28
|
+
const timetable = shouldReturnEmpty
|
|
29
|
+
? []
|
|
30
|
+
: await fetchCgvTimetable({
|
|
31
|
+
playDate,
|
|
32
|
+
theaterCode: targetTheaterCode,
|
|
33
|
+
movieCode,
|
|
34
|
+
timeout: timeoutMs,
|
|
35
|
+
zyteApiKey: apiKey,
|
|
36
|
+
});
|
|
37
|
+
const filtered = filterAndSortTimetable(timetable, { theaterCode: targetTheaterCode, movieCode, limit });
|
|
17
38
|
const result = {
|
|
18
39
|
playDate,
|
|
19
40
|
filters: {
|
|
20
|
-
theaterCode:
|
|
41
|
+
theaterCode: targetTheaterCode || null,
|
|
21
42
|
movieCode: movieCode || null,
|
|
43
|
+
keyword: keyword || null,
|
|
44
|
+
latitude: latitude ?? null,
|
|
45
|
+
longitude: longitude ?? null,
|
|
22
46
|
limit,
|
|
23
47
|
},
|
|
48
|
+
resolvedTheater,
|
|
24
49
|
count: filtered.length,
|
|
25
50
|
timetable: filtered,
|
|
26
51
|
};
|
|
@@ -28,20 +53,23 @@ async function getTimetable(args, apiKey) {
|
|
|
28
53
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
29
54
|
};
|
|
30
55
|
}
|
|
31
|
-
export function createGetTimetableTool(apiKey) {
|
|
56
|
+
export function createGetTimetableTool(apiKey, googleMapsApiKey) {
|
|
32
57
|
return {
|
|
33
58
|
name: 'cgv_get_timetable',
|
|
34
59
|
metadata: {
|
|
35
60
|
title: 'CGV 시간표 조회',
|
|
36
|
-
description: '날짜/극장/영화 조건으로 CGV 상영 시간표를 조회합니다.',
|
|
61
|
+
description: '날짜/극장/영화 조건으로 CGV 상영 시간표를 조회합니다. 극장 코드가 없으면 위치 키워드 기준 최근접 극장을 선택할 수 있습니다.',
|
|
37
62
|
inputSchema: {
|
|
38
63
|
playDate: z.string().optional().describe('조회 날짜(YYYYMMDD, 기본값: 오늘)'),
|
|
39
64
|
theaterCode: z.string().optional().describe('CGV 극장 코드 (예: 0056)'),
|
|
40
65
|
movieCode: z.string().optional().describe('CGV 영화 코드'),
|
|
66
|
+
keyword: z.string().optional().describe('위치 키워드 (예: 안산 중앙역, 강남역)'),
|
|
67
|
+
latitude: z.number().optional().describe('위도'),
|
|
68
|
+
longitude: z.number().optional().describe('경도'),
|
|
41
69
|
limit: z.number().optional().default(50).describe('최대 결과 수 (기본값: 50)'),
|
|
42
70
|
timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
|
|
43
71
|
},
|
|
44
72
|
},
|
|
45
|
-
handler: ((args) => getTimetable(args, apiKey)),
|
|
73
|
+
handler: ((args) => getTimetable(args, apiKey, googleMapsApiKey)),
|
|
46
74
|
};
|
|
47
75
|
}
|
|
@@ -3,19 +3,44 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import { fetchCgvMovies, toYyyymmdd } from '../client.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
import { resolveCgvNearestTheater } from '../location.js';
|
|
7
|
+
async function searchMovies(args, apiKey, googleMapsApiKey) {
|
|
8
|
+
const { playDate = toYyyymmdd(), theaterCode, keyword, latitude, longitude, timeoutMs = 15000 } = args;
|
|
9
|
+
let resolvedTheater = null;
|
|
10
|
+
let targetTheaterCode = theaterCode;
|
|
11
|
+
if (!targetTheaterCode && (keyword || typeof latitude === 'number' || typeof longitude === 'number')) {
|
|
12
|
+
const resolved = await resolveCgvNearestTheater({
|
|
13
|
+
playDate,
|
|
14
|
+
keyword,
|
|
15
|
+
latitude,
|
|
16
|
+
longitude,
|
|
17
|
+
timeout: timeoutMs,
|
|
18
|
+
}, {
|
|
19
|
+
timeout: timeoutMs,
|
|
20
|
+
zyteApiKey: apiKey,
|
|
21
|
+
googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
|
|
22
|
+
});
|
|
23
|
+
resolvedTheater = resolved.theater;
|
|
24
|
+
targetTheaterCode = resolved.theater?.theaterCode;
|
|
25
|
+
}
|
|
26
|
+
const shouldReturnEmpty = !targetTheaterCode && (keyword || typeof latitude === 'number' || typeof longitude === 'number');
|
|
27
|
+
const movies = shouldReturnEmpty
|
|
28
|
+
? []
|
|
29
|
+
: await fetchCgvMovies({
|
|
30
|
+
playDate,
|
|
31
|
+
theaterCode: targetTheaterCode,
|
|
32
|
+
timeout: timeoutMs,
|
|
33
|
+
zyteApiKey: apiKey,
|
|
34
|
+
});
|
|
14
35
|
const result = {
|
|
15
36
|
playDate,
|
|
16
37
|
filters: {
|
|
17
|
-
theaterCode:
|
|
38
|
+
theaterCode: targetTheaterCode || null,
|
|
39
|
+
keyword: keyword || null,
|
|
40
|
+
latitude: latitude ?? null,
|
|
41
|
+
longitude: longitude ?? null,
|
|
18
42
|
},
|
|
43
|
+
resolvedTheater,
|
|
19
44
|
count: movies.length,
|
|
20
45
|
movies,
|
|
21
46
|
};
|
|
@@ -23,18 +48,21 @@ async function searchMovies(args, apiKey) {
|
|
|
23
48
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
24
49
|
};
|
|
25
50
|
}
|
|
26
|
-
export function createSearchMoviesTool(apiKey) {
|
|
51
|
+
export function createSearchMoviesTool(apiKey, googleMapsApiKey) {
|
|
27
52
|
return {
|
|
28
53
|
name: 'cgv_search_movies',
|
|
29
54
|
metadata: {
|
|
30
55
|
title: 'CGV 영화 검색',
|
|
31
|
-
description: 'CGV 상영 영화 목록을 조회합니다.',
|
|
56
|
+
description: 'CGV 상영 영화 목록을 조회합니다. 극장 코드가 없으면 위치 키워드 기준 최근접 극장을 선택할 수 있습니다.',
|
|
32
57
|
inputSchema: {
|
|
33
58
|
playDate: z.string().optional().describe('조회 날짜(YYYYMMDD, 기본값: 오늘)'),
|
|
34
59
|
theaterCode: z.string().optional().describe('CGV 극장 코드 (예: 0056)'),
|
|
60
|
+
keyword: z.string().optional().describe('위치 키워드 (예: 안산 중앙역, 강남역)'),
|
|
61
|
+
latitude: z.number().optional().describe('위도'),
|
|
62
|
+
longitude: z.number().optional().describe('경도'),
|
|
35
63
|
timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
|
|
36
64
|
},
|
|
37
65
|
},
|
|
38
|
-
handler: ((args) => searchMovies(args, apiKey)),
|
|
66
|
+
handler: ((args) => searchMovies(args, apiKey, googleMapsApiKey)),
|
|
39
67
|
};
|
|
40
68
|
}
|
|
@@ -7,11 +7,9 @@ import { createTimeoutController } from '../../utils/http.js';
|
|
|
7
7
|
import { decodeZyteHttpBody, requestByZyte } from '../../utils/zyte.js';
|
|
8
8
|
import { CGV_API } from './api.js';
|
|
9
9
|
function toBase64(bytes) {
|
|
10
|
-
/* c8 ignore start */
|
|
11
10
|
if (typeof Buffer !== 'undefined') {
|
|
12
11
|
return Buffer.from(bytes).toString('base64');
|
|
13
12
|
}
|
|
14
|
-
/* c8 ignore end */
|
|
15
13
|
let binary = '';
|
|
16
14
|
for (const byte of bytes) {
|
|
17
15
|
binary += String.fromCharCode(byte);
|
|
@@ -19,7 +17,6 @@ function toBase64(bytes) {
|
|
|
19
17
|
if (typeof btoa === 'function') {
|
|
20
18
|
return btoa(binary);
|
|
21
19
|
}
|
|
22
|
-
/* c8 ignore next */
|
|
23
20
|
throw new Error('Base64 인코딩을 지원하지 않는 런타임입니다.');
|
|
24
21
|
}
|
|
25
22
|
async function createSignature(pathname, bodyText, timestamp) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CU API 엔드포인트 중앙 관리
|
|
3
|
+
*/
|
|
4
|
+
export const CU_API = {
|
|
5
|
+
BASE_URL: 'https://www.pocketcu.co.kr',
|
|
6
|
+
STORE_PATH: '/api/store',
|
|
7
|
+
STOCK_DISPLAY_PATH: '/api/search/display/stock',
|
|
8
|
+
STOCK_MAIN_PATH: '/api/search/rest/stock/main',
|
|
9
|
+
WEB_BASE_URL: 'https://cu.bgfretail.com',
|
|
10
|
+
WEB_STORE_LIST_PATH: '/store/list_Ajax.do',
|
|
11
|
+
};
|