daiso 1.2.7 → 1.2.9

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 (70) hide show
  1. package/README.md +17 -17
  2. package/dist/api/actionsProxy.js +0 -10
  3. package/dist/api/cgvHandlers.js +4 -3
  4. package/dist/api/configStatus.js +7 -2
  5. package/dist/api/gs25Handlers.js +4 -3
  6. package/dist/api/handlers.js +11 -7
  7. package/dist/api/healthCheckDefinitions.js +0 -9
  8. package/dist/api/healthChecks.js +0 -1
  9. package/dist/api/lottecinemaHandlers.js +4 -3
  10. package/dist/api/lottemartHandlers.js +5 -142
  11. package/dist/api/megaboxHandlers.js +4 -3
  12. package/dist/api/opinetHandlers.js +2 -1
  13. package/dist/api/routes/cgvRoutes.js +3 -3
  14. package/dist/api/routes/cuRoutes.js +2 -2
  15. package/dist/api/routes/gs25Routes.js +3 -3
  16. package/dist/api/routes/lottecinemaRoutes.js +3 -3
  17. package/dist/api/routes/lottemartRoutes.js +4 -16
  18. package/dist/api/routes/megaboxRoutes.js +3 -3
  19. package/dist/api/routes/opinetRoutes.js +4 -4
  20. package/dist/cli.js +5 -5
  21. package/dist/cliHelp.js +0 -3
  22. package/dist/cliHelpDefinitions.js +2 -19
  23. package/dist/index.js +10 -11
  24. package/dist/middleware/dailyRateLimit.js +0 -1
  25. package/dist/pages/openapiFullSpec.js +0 -2
  26. package/dist/pages/openapiSpecActionParameters.js +4 -4
  27. package/dist/pages/openapiSpecComponents.js +0 -2
  28. package/dist/pages/openapiSpecPathsMegabox.js +1 -1
  29. package/dist/pages/promptReferenceText.js +0 -5
  30. package/dist/pages/promptRetailCinemaText.js +0 -26
  31. package/dist/services/cgv/index.js +3 -6
  32. package/dist/services/cgv/location.js +38 -125
  33. package/dist/services/cgv/tools/findTheaters.js +5 -4
  34. package/dist/services/cgv/tools/getTimetable.js +5 -4
  35. package/dist/services/cgv/tools/searchMovies.js +5 -4
  36. package/dist/services/cu/client.js +3 -35
  37. package/dist/services/cu/index.js +1 -0
  38. package/dist/services/cu/tools/checkInventory.js +3 -1
  39. package/dist/services/gs25/client.js +3 -33
  40. package/dist/services/gs25/index.js +2 -2
  41. package/dist/services/gs25/tools/checkInventory.js +3 -1
  42. package/dist/services/gs25/tools/findNearbyStores.js +3 -1
  43. package/dist/services/lottecinema/index.js +3 -6
  44. package/dist/services/lottecinema/location.js +13 -100
  45. package/dist/services/lottecinema/tools/findNearbyTheaters.js +7 -6
  46. package/dist/services/lottecinema/tools/getRemainingSeats.js +5 -4
  47. package/dist/services/lottecinema/tools/listNowShowing.js +5 -4
  48. package/dist/services/lottemart/client.js +5 -32
  49. package/dist/services/lottemart/geocode.js +3 -46
  50. package/dist/services/megabox/index.js +9 -8
  51. package/dist/services/megabox/location.js +19 -84
  52. package/dist/services/megabox/tools/findNearbyTheaters.js +7 -7
  53. package/dist/services/megabox/tools/getRemainingSeats.js +5 -5
  54. package/dist/services/megabox/tools/listNowShowing.js +5 -5
  55. package/dist/services/oliveyoung/tools/checkInventory.js +5 -5
  56. package/dist/services/oliveyoung/tools/findNearbyStores.js +3 -3
  57. package/dist/services/oliveyoung/tools/searchProducts.js +3 -3
  58. package/dist/services/oliveyoung/transport.js +49 -33
  59. package/dist/services/opinet/client.js +1 -1
  60. package/dist/services/opinet/index.js +1 -1
  61. package/dist/services/opinet/location.js +5 -56
  62. package/dist/services/opinet/tools.js +2 -2
  63. package/dist/utils/geocode.js +155 -0
  64. package/dist/utils/http.js +2 -2
  65. package/dist/utils/relayQuota.js +4 -2
  66. package/openapi.json +4 -183
  67. package/openapi.yaml +4 -131
  68. package/package.json +1 -2
  69. package/dist/pages/openapiSpecComponentsLotteMart.js +0 -85
  70. package/dist/pages/openapiSpecPathsLotteMart.js +0 -157
@@ -4,7 +4,7 @@
4
4
  import * as z from 'zod';
5
5
  import { fetchCgvMovies, toYyyymmdd } from '../client.js';
6
6
  import { resolveCgvNearestTheater } from '../location.js';
7
- async function searchMovies(args, apiKey, googleMapsApiKey) {
7
+ async function searchMovies(args, apiKey, googleMapsApiKey, geocodeOptions = {}) {
8
8
  const { playDate = toYyyymmdd(), theaterCode, keyword, latitude, longitude, timeoutMs = 15000 } = args;
9
9
  let resolvedTheater = null;
10
10
  let targetTheaterCode = theaterCode;
@@ -18,7 +18,8 @@ async function searchMovies(args, apiKey, googleMapsApiKey) {
18
18
  }, {
19
19
  timeout: timeoutMs,
20
20
  zyteApiKey: apiKey,
21
- googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
21
+ ...geocodeOptions,
22
+ googleMapsApiKey,
22
23
  });
23
24
  resolvedTheater = resolved.theater;
24
25
  targetTheaterCode = resolved.theater?.theaterCode;
@@ -48,7 +49,7 @@ async function searchMovies(args, apiKey, googleMapsApiKey) {
48
49
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
49
50
  };
50
51
  }
51
- export function createSearchMoviesTool(apiKey, googleMapsApiKey) {
52
+ export function createSearchMoviesTool(apiKey, googleMapsApiKey, geocodeOptions = {}) {
52
53
  return {
53
54
  name: 'cgv_search_movies',
54
55
  metadata: {
@@ -63,6 +64,6 @@ export function createSearchMoviesTool(apiKey, googleMapsApiKey) {
63
64
  timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
64
65
  },
65
66
  },
66
- handler: ((args) => searchMovies(args, apiKey, googleMapsApiKey)),
67
+ handler: ((args) => searchMovies(args, apiKey, googleMapsApiKey, geocodeOptions)),
67
68
  };
68
69
  }
@@ -1,3 +1,4 @@
1
+ import { geocodeLocation } from '../../utils/geocode.js';
1
2
  /**
2
3
  * CU API 클라이언트
3
4
  */
@@ -74,41 +75,8 @@ async function requestCuWebHtml(path, body, _apiKey, timeout = 15000) {
74
75
  throw new Error(`API 요청 실패: ${response.status} ${response.statusText}`);
75
76
  }
76
77
  export async function geocodeCuAddress(address, options = {}) {
77
- const keyword = address.trim();
78
- if (keyword.length === 0) {
79
- return null;
80
- }
81
- const { timeout = 15000, googleMapsApiKey } = options;
82
- const apiKey = (googleMapsApiKey || '').trim();
83
- if (apiKey.length === 0) {
84
- return null;
85
- }
86
- const endpoint = new URL('https://maps.googleapis.com/maps/api/geocode/json');
87
- endpoint.searchParams.set('address', keyword);
88
- endpoint.searchParams.set('key', apiKey);
89
- const body = await fetchJson(endpoint.toString(), {
90
- method: 'GET',
91
- timeout,
92
- headers: {
93
- Accept: 'application/json',
94
- },
95
- });
96
- if (body.status !== 'OK') {
97
- return null;
98
- }
99
- const firstLocation = body.results?.[0]?.geometry?.location;
100
- if (!firstLocation) {
101
- return null;
102
- }
103
- const latitude = toNumber(firstLocation.lat);
104
- const longitude = toNumber(firstLocation.lng);
105
- if (latitude === 0 || longitude === 0) {
106
- return null;
107
- }
108
- return {
109
- latitude,
110
- longitude,
111
- };
78
+ const location = await geocodeLocation(address, options);
79
+ return location ? { latitude: location.latitude, longitude: location.longitude } : null;
112
80
  }
113
81
  function decodeHtmlEntities(value) {
114
82
  return value
@@ -20,6 +20,7 @@ class CuService {
20
20
  createFindNearbyStoresTool(this.options.zyteApiKey),
21
21
  createCheckInventoryTool({
22
22
  zyteApiKey: this.options.zyteApiKey,
23
+ ...this.options,
23
24
  googleMapsApiKey: this.options.googleMapsApiKey,
24
25
  }),
25
26
  ];
@@ -36,7 +36,8 @@ async function checkInventory(args) {
36
36
  if (firstAddress.length > 0) {
37
37
  const geocoded = await geocodeCuAddress(firstAddress, {
38
38
  timeout: timeoutMs,
39
- googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
39
+ ...args,
40
+ googleMapsApiKey,
40
41
  });
41
42
  if (geocoded) {
42
43
  const hasStockSeed = !!firstStockItem?.itemCode;
@@ -136,6 +137,7 @@ export function createCheckInventoryTool(options = {}) {
136
137
  },
137
138
  handler: ((args) => checkInventory({
138
139
  ...args,
140
+ ...options,
139
141
  zyteApiKey: options.zyteApiKey ?? args.zyteApiKey,
140
142
  googleMapsApiKey: options.googleMapsApiKey ?? args.googleMapsApiKey,
141
143
  })),
@@ -1,3 +1,4 @@
1
+ import { geocodeLocation } from '../../utils/geocode.js';
1
2
  /**
2
3
  * GS25 API 클라이언트
3
4
  */
@@ -57,39 +58,8 @@ function buildCacheKey(params) {
57
58
  ].join(':');
58
59
  }
59
60
  export async function geocodeGs25Address(address, options = {}) {
60
- const keyword = address.trim();
61
- if (keyword.length === 0) {
62
- return null;
63
- }
64
- const { timeout = 15000, googleMapsApiKey } = options;
65
- const apiKey = (googleMapsApiKey || '').trim();
66
- if (apiKey.length === 0) {
67
- return null;
68
- }
69
- const endpoint = new URL('https://maps.googleapis.com/maps/api/geocode/json');
70
- endpoint.searchParams.set('address', keyword);
71
- endpoint.searchParams.set('key', apiKey);
72
- const body = await fetchJson(endpoint.toString(), {
73
- ...GS25_DEFAULT_FETCH_OPTIONS,
74
- method: 'GET',
75
- timeout,
76
- headers: {
77
- Accept: 'application/json',
78
- },
79
- });
80
- if (body.status !== 'OK') {
81
- return null;
82
- }
83
- const location = body.results?.[0]?.geometry?.location;
84
- if (!location) {
85
- return null;
86
- }
87
- const latitude = toNumber(location.lat);
88
- const longitude = toNumber(location.lng);
89
- if (latitude === 0 || longitude === 0) {
90
- return null;
91
- }
92
- return { latitude, longitude };
61
+ const location = await geocodeLocation(address, options);
62
+ return location ? { latitude: location.latitude, longitude: location.longitude } : null;
93
63
  }
94
64
  export async function fetchGs25WebStores(keyword, options = {}) {
95
65
  const query = keyword.trim();
@@ -18,9 +18,9 @@ class Gs25Service {
18
18
  }
19
19
  getTools() {
20
20
  return [
21
- createFindNearbyStoresTool(this.options.googleMapsApiKey, this.options.zyteApiKey, this.options.apiKey),
21
+ createFindNearbyStoresTool(this.options.googleMapsApiKey, this.options.zyteApiKey, this.options.apiKey, this.options),
22
22
  createSearchProductsTool(this.options.zyteApiKey),
23
- createCheckInventoryTool(this.options.googleMapsApiKey, this.options.zyteApiKey, this.options.apiKey),
23
+ createCheckInventoryTool(this.options.googleMapsApiKey, this.options.zyteApiKey, this.options.apiKey, this.options),
24
24
  ];
25
25
  }
26
26
  }
@@ -31,6 +31,7 @@ async function checkInventory(args) {
31
31
  if (firstAddress.length > 0) {
32
32
  const geocoded = await geocodeGs25Address(firstAddress, {
33
33
  timeout: timeoutMs,
34
+ ...args,
34
35
  googleMapsApiKey,
35
36
  });
36
37
  if (geocoded) {
@@ -151,7 +152,7 @@ async function checkInventory(args) {
151
152
  ],
152
153
  };
153
154
  }
154
- export function createCheckInventoryTool(googleMapsApiKey, zyteApiKey, apiKey) {
155
+ export function createCheckInventoryTool(googleMapsApiKey, zyteApiKey, apiKey, geocodeOptions = {}) {
155
156
  return {
156
157
  name: 'gs25_check_inventory',
157
158
  metadata: {
@@ -186,6 +187,7 @@ export function createCheckInventoryTool(googleMapsApiKey, zyteApiKey, apiKey) {
186
187
  },
187
188
  handler: ((args) => checkInventory({
188
189
  ...args,
190
+ ...geocodeOptions,
189
191
  googleMapsApiKey: args.googleMapsApiKey || googleMapsApiKey,
190
192
  zyteApiKey: args.zyteApiKey || zyteApiKey,
191
193
  apiKey: args.apiKey || apiKey,
@@ -18,6 +18,7 @@ async function findNearbyStores(args) {
18
18
  keyword.trim().length > 0) {
19
19
  const geocoded = await geocodeGs25Address(keyword, {
20
20
  timeout: timeoutMs,
21
+ ...args,
21
22
  googleMapsApiKey,
22
23
  });
23
24
  if (geocoded) {
@@ -103,7 +104,7 @@ async function findNearbyStores(args) {
103
104
  ],
104
105
  };
105
106
  }
106
- export function createFindNearbyStoresTool(googleMapsApiKey, zyteApiKey, apiKey) {
107
+ export function createFindNearbyStoresTool(googleMapsApiKey, zyteApiKey, apiKey, geocodeOptions = {}) {
107
108
  return {
108
109
  name: 'gs25_find_nearby_stores',
109
110
  metadata: {
@@ -124,6 +125,7 @@ export function createFindNearbyStoresTool(googleMapsApiKey, zyteApiKey, apiKey)
124
125
  },
125
126
  handler: ((args) => findNearbyStores({
126
127
  ...args,
128
+ ...geocodeOptions,
127
129
  googleMapsApiKey: args.googleMapsApiKey || googleMapsApiKey,
128
130
  zyteApiKey: args.zyteApiKey || zyteApiKey,
129
131
  apiKey: args.apiKey || apiKey,
@@ -1,6 +1,3 @@
1
- /**
2
- * 롯데시네마 서비스 프로바이더
3
- */
4
1
  import { createFindNearbyTheatersTool } from './tools/findNearbyTheaters.js';
5
2
  import { createGetRemainingSeatsTool } from './tools/getRemainingSeats.js';
6
3
  import { createListNowShowingTool } from './tools/listNowShowing.js';
@@ -18,9 +15,9 @@ class LotteCinemaService {
18
15
  metadata = LOTTECINEMA_METADATA;
19
16
  getTools() {
20
17
  return [
21
- createFindNearbyTheatersTool(this.options.googleMapsApiKey),
22
- createListNowShowingTool(this.options.googleMapsApiKey),
23
- createGetRemainingSeatsTool(this.options.googleMapsApiKey),
18
+ createFindNearbyTheatersTool(this.options.googleMapsApiKey, this.options),
19
+ createListNowShowingTool(this.options.googleMapsApiKey, this.options),
20
+ createGetRemainingSeatsTool(this.options.googleMapsApiKey, this.options),
24
21
  ];
25
22
  }
26
23
  }
@@ -1,26 +1,9 @@
1
1
  /**
2
2
  * 롯데시네마 위치 해석 및 근처 극장 조회 보조 모듈
3
3
  */
4
- import { fetchJson } from '../../utils/http.js';
4
+ import { geocodeLocation, validKoreanCoordinates } from '../../utils/geocode.js';
5
5
  import { fetchLotteCinemaTicketingPage } from './client.js';
6
6
  const DEFAULT_TIMEOUT_MS = 15000;
7
- const GEOCODE_CACHE_TTL_MS = 60 * 60 * 1000;
8
- const LOCATION_STOPWORDS = new Set([
9
- '롯데시네마',
10
- '영화',
11
- '극장',
12
- '주변',
13
- '근처',
14
- '오늘',
15
- '상영',
16
- '영화목록',
17
- '시간표',
18
- '좌석',
19
- '잔여',
20
- '찾아',
21
- '주세요',
22
- ]);
23
- const geocodeCache = new Map();
24
7
  function calculateDistanceKm(lat1, lng1, lat2, lng2) {
25
8
  const toRad = (deg) => (deg * Math.PI) / 180;
26
9
  const dLat = toRad(lat2 - lat1);
@@ -33,77 +16,17 @@ function calculateDistanceKm(lat1, lng1, lat2, lng2) {
33
16
  function isValidCoordinate(value) {
34
17
  return typeof value === 'number' && Number.isFinite(value);
35
18
  }
36
- function normalizeText(text) {
37
- return text.replace(/\s+/g, '').toLowerCase();
38
- }
39
- function extractLocationTokens(...values) {
40
- const tokens = new Set();
41
- for (const value of values) {
42
- const matches = (value || '').match(/[0-9A-Za-z가-힣]+/g) || [];
43
- for (const match of matches) {
44
- const normalized = normalizeText(match);
45
- if (normalized.length < 2 || LOCATION_STOPWORDS.has(normalized)) {
46
- continue;
47
- }
48
- tokens.add(normalized);
49
- if (normalized.endsWith('역') && normalized.length > 2) {
50
- tokens.add(normalized.slice(0, -1));
51
- }
52
- }
53
- }
54
- return [...tokens];
55
- }
56
- function scoreTheater(theater, tokens) {
57
- const normalizedName = normalizeText(theater.theaterName);
58
- const normalizedAddress = normalizeText(theater.address || '');
59
- return tokens.reduce((score, token) => {
60
- if (normalizedName.includes(token)) {
61
- return score + 2;
62
- }
63
- if (normalizedAddress.includes(token)) {
64
- return score + 1;
65
- }
66
- return score;
67
- }, 0);
68
- }
69
19
  async function geocodeAddress(query, options) {
70
- const apiKey = (options.googleMapsApiKey || '').trim();
71
20
  const trimmedQuery = query.trim();
72
- if (apiKey.length === 0 || trimmedQuery.length === 0) {
73
- return null;
74
- }
75
- const cacheKey = `keyword:${trimmedQuery}`;
76
- const cached = geocodeCache.get(cacheKey);
77
- if (cached && cached.expiresAt > Date.now()) {
78
- return cached.value;
79
- }
80
- const endpoint = new URL('https://maps.googleapis.com/maps/api/geocode/json');
81
- endpoint.searchParams.set('address', trimmedQuery);
82
- endpoint.searchParams.set('key', apiKey);
83
- const body = await fetchJson(endpoint.toString(), {
84
- method: 'GET',
85
- timeout: options.timeout || DEFAULT_TIMEOUT_MS,
86
- headers: { Accept: 'application/json' },
87
- });
88
- const firstResult = body.status === 'OK' ? body.results?.[0] : null;
89
- const latitude = firstResult?.geometry?.location?.lat;
90
- const longitude = firstResult?.geometry?.location?.lng;
91
- const value = typeof latitude === 'number' && typeof longitude === 'number'
92
- ? {
93
- latitude,
94
- longitude,
95
- formattedAddress: firstResult?.formatted_address || null,
96
- }
97
- : null;
98
- geocodeCache.set(cacheKey, {
99
- expiresAt: Date.now() + GEOCODE_CACHE_TTL_MS,
100
- value,
101
- });
21
+ const value = await geocodeLocation(trimmedQuery, options);
102
22
  return value;
103
23
  }
104
24
  export async function resolveLotteCinemaLocation(params, options = {}) {
105
25
  const keyword = (params.keyword || '').trim();
106
26
  const hasCoordinates = isValidCoordinate(params.latitude) && isValidCoordinate(params.longitude);
27
+ if (hasCoordinates &&
28
+ !validKoreanCoordinates(params.latitude, params.longitude))
29
+ throw new Error('유효한 위도/경도를 입력해주세요.');
107
30
  if (hasCoordinates) {
108
31
  return {
109
32
  keyword: keyword.length > 0 ? keyword : null,
@@ -123,12 +46,14 @@ export async function resolveLotteCinemaLocation(params, options = {}) {
123
46
  };
124
47
  }
125
48
  const geocoded = await geocodeAddress(keyword, options);
49
+ if (!geocoded)
50
+ throw new Error(`위치를 좌표로 변환하지 못했습니다: ${keyword}`);
126
51
  return {
127
52
  keyword,
128
- latitude: geocoded?.latitude ?? null,
129
- longitude: geocoded?.longitude ?? null,
130
- geocodeUsed: Boolean(geocoded),
131
- formattedAddress: geocoded?.formattedAddress ?? null,
53
+ latitude: geocoded.latitude,
54
+ longitude: geocoded.longitude,
55
+ geocodeUsed: true,
56
+ formattedAddress: geocoded.formattedAddress,
132
57
  };
133
58
  }
134
59
  export async function fetchLotteCinemaNearbyTheaters(params, options = {}) {
@@ -148,19 +73,7 @@ export async function fetchLotteCinemaNearbyTheaters(params, options = {}) {
148
73
  .slice(0, limit);
149
74
  }
150
75
  else {
151
- const tokens = extractLocationTokens(location.keyword, location.formattedAddress);
152
- nearby = theaters
153
- .map((theater) => ({
154
- theater,
155
- score: scoreTheater(theater, tokens),
156
- }))
157
- .filter((item) => item.score > 0)
158
- .sort((left, right) => right.score - left.score || left.theater.theaterName.localeCompare(right.theater.theaterName))
159
- .slice(0, limit)
160
- .map((item) => ({
161
- ...item.theater,
162
- distanceKm: null,
163
- }));
76
+ nearby = [];
164
77
  }
165
78
  return {
166
79
  ...location,
@@ -187,5 +100,5 @@ export async function resolveLotteCinemaNearestTheater(params, options = {}) {
187
100
  };
188
101
  }
189
102
  export function __testOnlyClearLotteCinemaLocationCaches() {
190
- geocodeCache.clear();
103
+ // 캐시를 유지하지 않으므로 초기화할 상태가 없습니다.
191
104
  }
@@ -4,7 +4,7 @@
4
4
  import * as z from 'zod';
5
5
  import { toYyyymmdd } from '../client.js';
6
6
  import { fetchLotteCinemaNearbyTheaters } from '../location.js';
7
- async function findNearbyTheaters(args, googleMapsApiKey) {
7
+ async function findNearbyTheaters(args, googleMapsApiKey, geocodeOptions = {}) {
8
8
  const { keyword, latitude, longitude, playDate = toYyyymmdd(), limit = 10, timeoutMs = 15000 } = args;
9
9
  const hasCoordinates = typeof latitude === 'number' && typeof longitude === 'number';
10
10
  const nearby = await fetchLotteCinemaNearbyTheaters({
@@ -16,7 +16,8 @@ async function findNearbyTheaters(args, googleMapsApiKey) {
16
16
  timeout: timeoutMs,
17
17
  }, {
18
18
  timeout: timeoutMs,
19
- googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
19
+ ...geocodeOptions,
20
+ googleMapsApiKey,
20
21
  });
21
22
  return {
22
23
  content: [
@@ -27,7 +28,7 @@ async function findNearbyTheaters(args, googleMapsApiKey) {
27
28
  ],
28
29
  };
29
30
  }
30
- export function createFindNearbyTheatersTool(googleMapsApiKey) {
31
+ export function createFindNearbyTheatersTool(googleMapsApiKey, geocodeOptions = {}) {
31
32
  return {
32
33
  name: 'lottecinema_find_nearby_theaters',
33
34
  metadata: {
@@ -35,13 +36,13 @@ export function createFindNearbyTheatersTool(googleMapsApiKey) {
35
36
  description: '위치 키워드 또는 좌표 기준으로 롯데시네마 지점을 거리순으로 조회합니다.',
36
37
  inputSchema: {
37
38
  keyword: z.string().optional().describe('위치 키워드 (예: 안산 중앙역, 잠실역)'),
38
- latitude: z.number().optional().default(37.5665).describe('위도 (기본값: 서울 시청 37.5665)'),
39
- longitude: z.number().optional().default(126.978).describe('경도 (기본값: 서울 시청 126.978)'),
39
+ latitude: z.number().optional().describe('위도 (좌표 또는 위치 키워드 입력)'),
40
+ longitude: z.number().optional().describe('경도 (좌표 또는 위치 키워드 입력)'),
40
41
  playDate: z.string().optional().describe('조회 날짜(YYYYMMDD, 기본값: 오늘)'),
41
42
  limit: z.number().optional().default(10).describe('반환할 최대 지점 수 (기본값: 10)'),
42
43
  timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
43
44
  },
44
45
  },
45
- handler: ((args) => findNearbyTheaters(args, googleMapsApiKey)),
46
+ handler: ((args) => findNearbyTheaters(args, googleMapsApiKey, geocodeOptions)),
46
47
  };
47
48
  }
@@ -4,7 +4,7 @@
4
4
  import * as z from 'zod';
5
5
  import { fetchLotteCinemaNowShowing, toYyyymmdd } from '../client.js';
6
6
  import { resolveLotteCinemaNearestTheater } from '../location.js';
7
- async function getRemainingSeats(args, googleMapsApiKey) {
7
+ async function getRemainingSeats(args, googleMapsApiKey, geocodeOptions = {}) {
8
8
  const { playDate = toYyyymmdd(), theaterId, movieId, keyword, latitude, longitude, limit = 50, timeoutMs = 15000, } = args;
9
9
  let resolvedTheater = null;
10
10
  let targetTheaterId = theaterId;
@@ -17,7 +17,8 @@ async function getRemainingSeats(args, googleMapsApiKey) {
17
17
  timeout: timeoutMs,
18
18
  }, {
19
19
  timeout: timeoutMs,
20
- googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
20
+ ...geocodeOptions,
21
+ googleMapsApiKey,
21
22
  });
22
23
  resolvedTheater = resolved.theater;
23
24
  targetTheaterId = resolved.theater?.theaterId;
@@ -63,7 +64,7 @@ async function getRemainingSeats(args, googleMapsApiKey) {
63
64
  ],
64
65
  };
65
66
  }
66
- export function createGetRemainingSeatsTool(googleMapsApiKey) {
67
+ export function createGetRemainingSeatsTool(googleMapsApiKey, geocodeOptions = {}) {
67
68
  return {
68
69
  name: 'lottecinema_get_remaining_seats',
69
70
  metadata: {
@@ -80,6 +81,6 @@ export function createGetRemainingSeatsTool(googleMapsApiKey) {
80
81
  timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
81
82
  },
82
83
  },
83
- handler: ((args) => getRemainingSeats(args, googleMapsApiKey)),
84
+ handler: ((args) => getRemainingSeats(args, googleMapsApiKey, geocodeOptions)),
84
85
  };
85
86
  }
@@ -4,7 +4,7 @@
4
4
  import * as z from 'zod';
5
5
  import { fetchLotteCinemaNowShowing, toYyyymmdd } from '../client.js';
6
6
  import { resolveLotteCinemaNearestTheater } from '../location.js';
7
- async function listNowShowing(args, googleMapsApiKey) {
7
+ async function listNowShowing(args, googleMapsApiKey, geocodeOptions = {}) {
8
8
  const { playDate = toYyyymmdd(), theaterId, movieId, keyword, latitude, longitude, timeoutMs = 15000 } = args;
9
9
  let resolvedTheater = null;
10
10
  let targetTheaterId = theaterId;
@@ -17,7 +17,8 @@ async function listNowShowing(args, googleMapsApiKey) {
17
17
  timeout: timeoutMs,
18
18
  }, {
19
19
  timeout: timeoutMs,
20
- googleMapsApiKey: googleMapsApiKey || process.env.GOOGLE_MAPS_API_KEY,
20
+ ...geocodeOptions,
21
+ googleMapsApiKey,
21
22
  });
22
23
  resolvedTheater = resolved.theater;
23
24
  targetTheaterId = resolved.theater?.theaterId;
@@ -58,7 +59,7 @@ async function listNowShowing(args, googleMapsApiKey) {
58
59
  ],
59
60
  };
60
61
  }
61
- export function createListNowShowingTool(googleMapsApiKey) {
62
+ export function createListNowShowingTool(googleMapsApiKey, geocodeOptions = {}) {
62
63
  return {
63
64
  name: 'lottecinema_list_now_showing',
64
65
  metadata: {
@@ -74,6 +75,6 @@ export function createListNowShowingTool(googleMapsApiKey) {
74
75
  timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
75
76
  },
76
77
  },
77
- handler: ((args) => listNowShowing(args, googleMapsApiKey)),
78
+ handler: ((args) => listNowShowing(args, googleMapsApiKey, geocodeOptions)),
78
79
  };
79
80
  }
@@ -87,22 +87,13 @@ async function fetchLotteMartStoresByAreaKeyword(area, keyword, options) {
87
87
  }
88
88
  export async function fetchLotteMartStores(params = {}, options = {}) {
89
89
  const { area, keyword = '', brandVariant = '', latitude, longitude, limit = 20 } = params;
90
- const { timeout = DEFAULT_LOTTEMART_TIMEOUT_MS, googleMapsApiKey } = options;
90
+ const { timeout = DEFAULT_LOTTEMART_TIMEOUT_MS } = options;
91
91
  if (area && !normalizeArea(area)) {
92
92
  throw new Error(`지원하지 않는 지역입니다: ${area}`);
93
93
  }
94
- let resolvedLatitude = latitude;
95
- let resolvedLongitude = longitude;
96
- let geocodeUsed = false;
97
- if ((typeof resolvedLatitude !== 'number' || typeof resolvedLongitude !== 'number') &&
98
- keyword.trim().length > 0) {
99
- const geocoded = await geocodeLotteMartAddress(keyword, { timeout, googleMapsApiKey });
100
- if (geocoded) {
101
- resolvedLatitude = geocoded.latitude;
102
- resolvedLongitude = geocoded.longitude;
103
- geocodeUsed = true;
104
- }
105
- }
94
+ const resolvedLatitude = latitude;
95
+ const resolvedLongitude = longitude;
96
+ const geocodeUsed = false;
106
97
  const sessionCookie = await getLotteMartSessionCookie({ timeout });
107
98
  const targetAreas = getTargetAreas(area);
108
99
  const hasKeyword = keyword.trim().length > 0;
@@ -125,26 +116,8 @@ export async function fetchLotteMartStores(params = {}, options = {}) {
125
116
  : stores
126
117
  .filter((store) => matchesKeyword(store, keyword))
127
118
  .filter((store) => matchesBrandVariant(store, brandVariant)), resolvedLatitude, resolvedLongitude);
128
- const enriched = await Promise.all(filtered.map(async (store) => {
129
- if (typeof resolvedLatitude !== 'number' ||
130
- typeof resolvedLongitude !== 'number' ||
131
- store.latitude !== 0 ||
132
- store.longitude !== 0) {
133
- return store;
134
- }
135
- const geocoded = await geocodeLotteMartAddress(store.address, { timeout, googleMapsApiKey });
136
- if (!geocoded) {
137
- return store;
138
- }
139
- return {
140
- ...store,
141
- latitude: geocoded.latitude,
142
- longitude: geocoded.longitude,
143
- distanceM: calculateDistanceM(resolvedLatitude, resolvedLongitude, geocoded.latitude, geocoded.longitude),
144
- };
145
- }));
146
119
  return {
147
- stores: sortStores(enriched).slice(0, limit),
120
+ stores: sortStores(filtered).slice(0, limit),
148
121
  location: typeof resolvedLatitude === 'number' && typeof resolvedLongitude === 'number'
149
122
  ? { latitude: resolvedLatitude, longitude: resolvedLongitude }
150
123
  : null,
@@ -1,49 +1,6 @@
1
- /**
2
- * 롯데마트 매장 주소 지오코딩
3
- */
4
- import { fetchJson } from '../../utils/http.js';
5
- import { DEFAULT_LOTTEMART_TIMEOUT_MS } from './config.js';
6
- const GEOCODE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
7
- const geocodeCache = new Map();
8
- export async function geocodeLotteMartAddress(address, options = {}) {
9
- const keyword = address.trim();
10
- if (keyword.length === 0) {
11
- return null;
12
- }
13
- const cached = geocodeCache.get(keyword);
14
- if (cached && cached.expiresAt > Date.now()) {
15
- return cached.value;
16
- }
17
- const apiKey = (options.googleMapsApiKey || '').trim();
18
- if (apiKey.length === 0) {
19
- return null;
20
- }
21
- const endpoint = new URL('https://maps.googleapis.com/maps/api/geocode/json');
22
- endpoint.searchParams.set('address', keyword);
23
- endpoint.searchParams.set('key', apiKey);
24
- const body = await fetchJson(endpoint.toString(), {
25
- method: 'GET',
26
- timeout: options.timeout || DEFAULT_LOTTEMART_TIMEOUT_MS,
27
- headers: {
28
- Accept: 'application/json',
29
- },
30
- });
31
- if (body.status !== 'OK') {
32
- geocodeCache.set(keyword, {
33
- expiresAt: Date.now() + GEOCODE_CACHE_TTL_MS,
34
- value: null,
35
- });
36
- return null;
37
- }
38
- const latitude = body.results?.[0]?.geometry?.location?.lat;
39
- const longitude = body.results?.[0]?.geometry?.location?.lng;
40
- const value = typeof latitude === 'number' && typeof longitude === 'number' ? { latitude, longitude } : null;
41
- geocodeCache.set(keyword, {
42
- expiresAt: Date.now() + GEOCODE_CACHE_TTL_MS,
43
- value,
44
- });
45
- return value;
1
+ export async function geocodeLotteMartAddress(_address, _options = {}) {
2
+ return null;
46
3
  }
47
4
  export function __testOnlyClearLotteMartGeocodeCache() {
48
- geocodeCache.clear();
5
+ // 지원 종료 후에는 캐시나 외부 요청을 유지하지 않습니다.
49
6
  }
@@ -1,6 +1,3 @@
1
- /**
2
- * 메가박스 서비스 프로바이더
3
- */
4
1
  import { createFindNearbyTheatersTool } from './tools/findNearbyTheaters.js';
5
2
  import { createListNowShowingTool } from './tools/listNowShowing.js';
6
3
  import { createGetRemainingSeatsTool } from './tools/getRemainingSeats.js';
@@ -11,16 +8,20 @@ const MEGABOX_METADATA = {
11
8
  description: '메가박스 주변 지점 탐색, 영화 목록 조회, 잔여 좌석 조회 서비스',
12
9
  };
13
10
  class MegaboxService {
11
+ options;
12
+ constructor(options = {}) {
13
+ this.options = options;
14
+ }
14
15
  metadata = MEGABOX_METADATA;
15
16
  getTools() {
16
17
  return [
17
- createFindNearbyTheatersTool(),
18
- createListNowShowingTool(),
19
- createGetRemainingSeatsTool(),
18
+ createFindNearbyTheatersTool(this.options),
19
+ createListNowShowingTool(this.options),
20
+ createGetRemainingSeatsTool(this.options),
20
21
  ];
21
22
  }
22
23
  }
23
- export function createMegaboxService() {
24
- return new MegaboxService();
24
+ export function createMegaboxService(options = {}) {
25
+ return new MegaboxService(options);
25
26
  }
26
27
  export * from './types.js';