daiso 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -9
- package/dist/api/actionsProxy.js +3 -0
- package/dist/api/cgvHandlers.js +15 -3
- package/dist/api/configStatus.js +1 -1
- package/dist/api/dtryxHandlers.js +113 -0
- package/dist/api/emart24Handlers.js +6 -2
- package/dist/api/gs25Handlers.js +22 -19
- package/dist/api/handlers.js +3 -0
- package/dist/api/healthCheckDefinitions.js +56 -8
- package/dist/api/healthCheckShape.js +24 -8
- package/dist/api/healthChecks.js +48 -20
- package/dist/api/operationalAuth.js +32 -0
- package/dist/api/routes/dtryxRoutes.js +22 -0
- package/dist/api/routes/healthRoutes.js +19 -11
- package/dist/api/routes/rateLimitStatsRoutes.js +274 -0
- package/dist/api/routes/registerServiceRoutes.js +37 -0
- package/dist/api/sevenelevenHandlers.js +7 -3
- package/dist/cli/commandOptions.js +24 -0
- package/dist/cli/commands/cinema.js +27 -0
- package/dist/cli/commands/convenience.js +2 -2
- package/dist/cli.js +7 -1
- package/dist/cliHelp.js +3 -337
- package/dist/cliHelpDefinitions.js +369 -0
- package/dist/cliInteractive.js +8 -5
- package/dist/core/errors.js +17 -2
- package/dist/core/registry.js +13 -15
- package/dist/durableObjects/dailyRateLimiter.js +185 -0
- package/dist/durableObjects/rateLimitMetricsStore.js +144 -0
- package/dist/index.js +16 -30
- package/dist/middleware/dailyRateLimit.js +176 -0
- package/dist/pages/openapiFullSpec.js +2 -0
- package/dist/pages/openapiSpecActionParameters.js +429 -0
- package/dist/pages/openapiSpecActions.js +1 -391
- package/dist/pages/openapiSpecComponentsLottecinema.js +2 -2
- package/dist/pages/openapiSpecPathsDtryx.js +148 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +10 -2
- package/dist/pages/promptRetailCinemaText.js +9 -1
- package/dist/services/cgv/client.js +7 -4
- package/dist/services/cgv/errors.js +12 -0
- package/dist/services/cgv/transport.js +19 -2
- package/dist/services/cu/client.js +42 -12
- package/dist/services/cu/index.js +13 -3
- package/dist/services/cu/tools/checkInventory.js +65 -45
- package/dist/services/cu/tools/findNearbyStores.js +4 -3
- package/dist/services/cu/upstreamError.js +15 -0
- package/dist/services/dtryx/api.js +25 -0
- package/dist/services/dtryx/client.js +133 -0
- package/dist/services/dtryx/index.js +24 -0
- package/dist/services/dtryx/location.js +216 -0
- package/dist/services/dtryx/tools/getRemainingSeats.js +117 -0
- package/dist/services/dtryx/tools/listCinemas.js +46 -0
- package/dist/services/dtryx/tools/listNowShowing.js +77 -0
- package/dist/services/dtryx/types.js +4 -0
- package/dist/services/dtryx/validation.js +21 -0
- package/dist/services/emart24/tools/searchProducts.js +37 -19
- package/dist/services/feedback/tools/submitDeveloperRequest.js +15 -1
- package/dist/services/gs25/api.js +2 -2
- package/dist/services/gs25/client.js +16 -123
- package/dist/services/gs25/errors.js +12 -0
- package/dist/services/gs25/index.js +2 -2
- package/dist/services/gs25/productSearch.js +1 -0
- package/dist/services/gs25/storeStockTransport.js +55 -0
- package/dist/services/gs25/tools/checkInventory.js +7 -2
- package/dist/services/gs25/tools/findNearbyStores.js +20 -5
- package/dist/services/lottecinema/client.js +4 -2
- package/dist/services/lottemart/config.js +1 -1
- package/dist/services/lottemart/session.js +29 -160
- package/dist/services/lottemart/socketTransport.js +149 -0
- package/dist/services/oliveyoung/client.js +1 -0
- package/dist/services/places/client.js +29 -8
- package/dist/services/seveneleven/client.js +18 -8
- package/dist/services/seveneleven/index.js +11 -7
- package/dist/services/seveneleven/inventory.js +5 -2
- package/dist/services/seveneleven/productKeyword.js +3 -2
- package/dist/services/seveneleven/tools/checkInventory.js +4 -3
- package/dist/services/seveneleven/tools/getCatalogSnapshot.js +4 -3
- package/dist/services/seveneleven/tools/getSearchPopwords.js +4 -3
- package/dist/services/seveneleven/tools/searchProducts.js +40 -20
- package/dist/services/seveneleven/tools/searchStores.js +4 -3
- package/dist/utils/format.js +10 -3
- package/dist/utils/http.js +38 -10
- package/dist/utils/zyte.js +13 -2
- package/dist/utils/zyteJsonFallback.js +67 -0
- package/openapi.json +51 -3
- package/openapi.yaml +36 -3
- package/package.json +28 -24
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import { fetchSevenElevenSearchPopwords } from '../client.js';
|
|
6
|
-
async function getSearchPopwords(args) {
|
|
6
|
+
async function getSearchPopwords(args, zyteApiKey) {
|
|
7
7
|
const { label = 'home', timeoutMs = 15000 } = args;
|
|
8
8
|
const keywords = await fetchSevenElevenSearchPopwords(label, {
|
|
9
9
|
timeout: timeoutMs,
|
|
10
|
+
zyteApiKey,
|
|
10
11
|
});
|
|
11
12
|
return {
|
|
12
13
|
content: [
|
|
@@ -25,7 +26,7 @@ async function getSearchPopwords(args) {
|
|
|
25
26
|
],
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
|
-
export function createGetSearchPopwordsTool() {
|
|
29
|
+
export function createGetSearchPopwordsTool(zyteApiKey) {
|
|
29
30
|
return {
|
|
30
31
|
name: 'seveneleven_get_search_popwords',
|
|
31
32
|
metadata: {
|
|
@@ -36,6 +37,6 @@ export function createGetSearchPopwordsTool() {
|
|
|
36
37
|
timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
|
|
37
38
|
},
|
|
38
39
|
},
|
|
39
|
-
handler: getSearchPopwords,
|
|
40
|
+
handler: (args) => getSearchPopwords(args, zyteApiKey),
|
|
40
41
|
};
|
|
41
42
|
}
|
|
@@ -9,28 +9,46 @@ function buildTextResponse(payload) {
|
|
|
9
9
|
structuredContent: payload,
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
async function searchProducts(args) {
|
|
12
|
+
async function searchProducts(args, zyteApiKey) {
|
|
13
13
|
const { query, page = 1, size = 20, sort = 'recommend', timeoutMs = 15000 } = args;
|
|
14
14
|
if (!query || query.trim().length === 0) {
|
|
15
15
|
throw new Error('상품 검색어(query)를 입력해주세요.');
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
try {
|
|
18
|
+
const result = await searchSevenElevenProductsWithVariants(query, {
|
|
19
|
+
page,
|
|
20
|
+
size,
|
|
21
|
+
sort,
|
|
22
|
+
timeout: timeoutMs,
|
|
23
|
+
zyteApiKey,
|
|
24
|
+
});
|
|
25
|
+
return buildTextResponse({
|
|
26
|
+
query: result.query,
|
|
27
|
+
page,
|
|
28
|
+
size,
|
|
29
|
+
sort,
|
|
30
|
+
totalCount: result.totalCount,
|
|
31
|
+
count: result.products.length,
|
|
32
|
+
collectionIds: result.collectionIds,
|
|
33
|
+
appliedQueries: result.appliedQueries,
|
|
34
|
+
products: result.products,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return buildTextResponse({
|
|
39
|
+
query,
|
|
40
|
+
page,
|
|
41
|
+
size,
|
|
42
|
+
sort,
|
|
43
|
+
totalCount: 0,
|
|
44
|
+
count: 0,
|
|
45
|
+
collectionIds: [],
|
|
46
|
+
appliedQueries: [query],
|
|
47
|
+
products: [],
|
|
48
|
+
status: 'degraded',
|
|
49
|
+
message: error instanceof Error ? error.message : '세븐일레븐 상품 검색에 실패했습니다.',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
34
52
|
}
|
|
35
53
|
const searchProductsOutputSchema = {
|
|
36
54
|
query: z.string().describe('적용된 검색어'),
|
|
@@ -42,8 +60,10 @@ const searchProductsOutputSchema = {
|
|
|
42
60
|
collectionIds: z.array(z.string()).describe('검색 컬렉션 ID 목록'),
|
|
43
61
|
appliedQueries: z.array(z.string()).describe('실제로 시도한 검색어 목록'),
|
|
44
62
|
products: z.array(z.unknown()).describe('세븐일레븐 상품 검색 결과'),
|
|
63
|
+
status: z.string().optional().describe('외부 API 차단 등으로 결과가 제한된 경우 degraded'),
|
|
64
|
+
message: z.string().optional().describe('degraded 상태의 원인 메시지'),
|
|
45
65
|
};
|
|
46
|
-
export function createSearchProductsTool() {
|
|
66
|
+
export function createSearchProductsTool(zyteApiKey) {
|
|
47
67
|
return {
|
|
48
68
|
name: 'seveneleven_search_products',
|
|
49
69
|
metadata: {
|
|
@@ -62,6 +82,6 @@ export function createSearchProductsTool() {
|
|
|
62
82
|
},
|
|
63
83
|
outputSchema: searchProductsOutputSchema,
|
|
64
84
|
},
|
|
65
|
-
handler: searchProducts,
|
|
85
|
+
handler: (args) => searchProducts(args, zyteApiKey),
|
|
66
86
|
};
|
|
67
87
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import { fetchSevenElevenStoresByKeyword } from '../client.js';
|
|
6
|
-
async function searchStores(args) {
|
|
6
|
+
async function searchStores(args, zyteApiKey) {
|
|
7
7
|
const { keyword, limit = 20, timeoutMs = 15000 } = args;
|
|
8
8
|
if (!keyword || keyword.trim().length === 0) {
|
|
9
9
|
throw new Error('매장 검색어(keyword)를 입력해주세요.');
|
|
@@ -13,6 +13,7 @@ async function searchStores(args) {
|
|
|
13
13
|
limit,
|
|
14
14
|
}, {
|
|
15
15
|
timeout: timeoutMs,
|
|
16
|
+
zyteApiKey,
|
|
16
17
|
});
|
|
17
18
|
return {
|
|
18
19
|
content: [
|
|
@@ -28,7 +29,7 @@ async function searchStores(args) {
|
|
|
28
29
|
],
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
|
-
export function createSearchStoresTool() {
|
|
32
|
+
export function createSearchStoresTool(zyteApiKey) {
|
|
32
33
|
return {
|
|
33
34
|
name: 'seveneleven_search_stores',
|
|
34
35
|
metadata: {
|
|
@@ -40,6 +41,6 @@ export function createSearchStoresTool() {
|
|
|
40
41
|
timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
|
|
41
42
|
},
|
|
42
43
|
},
|
|
43
|
-
handler: searchStores,
|
|
44
|
+
handler: (args) => searchStores(args, zyteApiKey),
|
|
44
45
|
};
|
|
45
46
|
}
|
package/dist/utils/format.js
CHANGED
|
@@ -24,8 +24,15 @@ export function formatTime(raw) {
|
|
|
24
24
|
return raw;
|
|
25
25
|
}
|
|
26
26
|
export function toYyyymmdd(value = new Date()) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
28
|
+
timeZone: 'Asia/Seoul',
|
|
29
|
+
year: 'numeric',
|
|
30
|
+
month: '2-digit',
|
|
31
|
+
day: '2-digit',
|
|
32
|
+
}).formatToParts(value);
|
|
33
|
+
const byType = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
34
|
+
const year = byType.year;
|
|
35
|
+
const month = byType.month;
|
|
36
|
+
const day = byType.day;
|
|
30
37
|
return `${year}${month}${day}`;
|
|
31
38
|
}
|
package/dist/utils/http.js
CHANGED
|
@@ -15,6 +15,17 @@ export class HttpError extends Error {
|
|
|
15
15
|
this.bodyText = bodyText;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
/** HTML 원문을 노출하지 않고 예상하지 못한 응답 형식을 구분합니다. */
|
|
19
|
+
export class UnexpectedHtmlResponseError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
contentType;
|
|
22
|
+
constructor(status, contentType) {
|
|
23
|
+
super(`JSON 대신 HTML 응답을 받았습니다 (HTTP ${status}).`);
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.contentType = contentType;
|
|
26
|
+
this.name = 'UnexpectedHtmlResponseError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
18
29
|
export function createTimeoutController(timeout) {
|
|
19
30
|
const controller = new AbortController();
|
|
20
31
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -54,19 +65,22 @@ function parseRetryAfterDelayMs(response) {
|
|
|
54
65
|
}
|
|
55
66
|
return null;
|
|
56
67
|
}
|
|
57
|
-
|
|
68
|
+
async function requestWithTimeout(url, options, readResponse) {
|
|
58
69
|
const { timeout = 10000, retries = 0, retryDelayMs = 250, retryStatusCodes = DEFAULT_RETRY_STATUS_CODES, retryUnsafeMethods = false, retryMethods = DEFAULT_RETRY_METHODS, onRetry, ...restOptions } = options;
|
|
59
70
|
const maxAttempts = Math.max(1, Math.trunc(retries) + 1);
|
|
60
71
|
const method = normalizeMethod(restOptions.method);
|
|
61
72
|
const retryAllowed = canRetryMethod(method, retryMethods, retryUnsafeMethods);
|
|
62
73
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
63
74
|
const { controller, timeoutId } = createTimeoutController(timeout);
|
|
75
|
+
let readingResponse = false;
|
|
64
76
|
try {
|
|
65
77
|
const response = await fetch(url, {
|
|
66
78
|
...restOptions,
|
|
67
79
|
signal: controller.signal,
|
|
68
80
|
});
|
|
69
|
-
if (retryAllowed &&
|
|
81
|
+
if (retryAllowed &&
|
|
82
|
+
attempt < maxAttempts &&
|
|
83
|
+
isRetryableStatus(response.status, retryStatusCodes)) {
|
|
70
84
|
const delayMs = parseRetryAfterDelayMs(response) ?? retryDelayMs;
|
|
71
85
|
onRetry?.({
|
|
72
86
|
attempt,
|
|
@@ -81,10 +95,11 @@ export async function fetchWithTimeout(url, options = {}) {
|
|
|
81
95
|
await wait(delayMs);
|
|
82
96
|
continue;
|
|
83
97
|
}
|
|
84
|
-
|
|
98
|
+
readingResponse = true;
|
|
99
|
+
return await readResponse(response);
|
|
85
100
|
}
|
|
86
101
|
catch (error) {
|
|
87
|
-
if (!retryAllowed || attempt >= maxAttempts) {
|
|
102
|
+
if (readingResponse || !retryAllowed || attempt >= maxAttempts) {
|
|
88
103
|
throw error;
|
|
89
104
|
}
|
|
90
105
|
onRetry?.({
|
|
@@ -105,17 +120,30 @@ export async function fetchWithTimeout(url, options = {}) {
|
|
|
105
120
|
/* v8 ignore next */
|
|
106
121
|
throw new Error('API 요청 재시도 처리 중 알 수 없는 오류가 발생했습니다.');
|
|
107
122
|
}
|
|
123
|
+
export async function fetchWithTimeout(url, options = {}) {
|
|
124
|
+
return requestWithTimeout(url, options, async (response) => response);
|
|
125
|
+
}
|
|
126
|
+
async function readTextResponse(url, options) {
|
|
127
|
+
// 헤더 수신뿐 아니라 본문 읽기에도 같은 요청 제한 시간을 적용합니다.
|
|
128
|
+
return requestWithTimeout(url, options, async (response) => ({
|
|
129
|
+
response,
|
|
130
|
+
body: await response.text(),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
108
133
|
export async function fetchJson(url, options = {}) {
|
|
109
|
-
const response = await
|
|
134
|
+
const { response, body } = await readTextResponse(url, options);
|
|
110
135
|
if (!response.ok) {
|
|
111
|
-
throw new HttpError(response.status, response.statusText,
|
|
136
|
+
throw new HttpError(response.status, response.statusText, body);
|
|
137
|
+
}
|
|
138
|
+
if (/^\s*(?:<!doctype\s+html\b|<html\b)/i.test(body)) {
|
|
139
|
+
throw new UnexpectedHtmlResponseError(response.status, response.headers.get('content-type') || '');
|
|
112
140
|
}
|
|
113
|
-
return
|
|
141
|
+
return JSON.parse(body);
|
|
114
142
|
}
|
|
115
143
|
export async function fetchText(url, options = {}) {
|
|
116
|
-
const response = await
|
|
144
|
+
const { response, body } = await readTextResponse(url, options);
|
|
117
145
|
if (!response.ok) {
|
|
118
|
-
throw new HttpError(response.status, response.statusText,
|
|
146
|
+
throw new HttpError(response.status, response.statusText, body);
|
|
119
147
|
}
|
|
120
|
-
return
|
|
148
|
+
return body;
|
|
121
149
|
}
|
package/dist/utils/zyte.js
CHANGED
|
@@ -51,6 +51,16 @@ function isRetryableTargetStatus(statusCode) {
|
|
|
51
51
|
function isRetryableError(error) {
|
|
52
52
|
return isAbortError(error) || error instanceof RetryableZyteError;
|
|
53
53
|
}
|
|
54
|
+
function parseZyteResponseBody(text) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(text);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {
|
|
60
|
+
detail: text.trim().replace(/\s+/g, ' ').slice(0, 300),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
function wait(ms) {
|
|
55
65
|
if (ms <= 0) {
|
|
56
66
|
return Promise.resolve();
|
|
@@ -60,7 +70,7 @@ function wait(ms) {
|
|
|
60
70
|
});
|
|
61
71
|
}
|
|
62
72
|
export async function requestByZyte(options) {
|
|
63
|
-
const { timeout = 15000, retries = 1, retryDelayMs = 250, url, method = 'GET', headers = [], bodyText, apiKey, } = options;
|
|
73
|
+
const { timeout = 15000, retries = 1, retryDelayMs = 250, url, method = 'GET', headers = [], bodyText, apiKey, tags, } = options;
|
|
64
74
|
const auth = encodeBasicAuth(resolveZyteApiKey(apiKey));
|
|
65
75
|
const maxAttempts = Math.max(1, Math.trunc(retries) + 1);
|
|
66
76
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
@@ -81,6 +91,7 @@ export async function requestByZyte(options) {
|
|
|
81
91
|
customHttpRequestHeaders: headers,
|
|
82
92
|
httpRequestText: bodyText,
|
|
83
93
|
httpResponseBody: true,
|
|
94
|
+
...(tags ? { tags } : {}),
|
|
84
95
|
}),
|
|
85
96
|
signal: controller.signal,
|
|
86
97
|
});
|
|
@@ -91,7 +102,7 @@ export async function requestByZyte(options) {
|
|
|
91
102
|
}
|
|
92
103
|
throw error;
|
|
93
104
|
}
|
|
94
|
-
const result = (await response.
|
|
105
|
+
const result = parseZyteResponseBody(await response.text());
|
|
95
106
|
if (!response.ok) {
|
|
96
107
|
const message = `Zyte API 호출 실패: ${response.status} ${result.detail || result.title || ''}`.trim();
|
|
97
108
|
if (isRetryableZyteStatus(response.status)) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 차단 응답에만 Zyte를 사용하는 JSON 전송 유틸리티
|
|
3
|
+
*/
|
|
4
|
+
import { fetchJson, HttpError } from './http.js';
|
|
5
|
+
import { decodeZyteHttpBody, requestByZyte, } from './zyte.js';
|
|
6
|
+
const ZYTE_FALLBACK_STATUSES = new Set([400, 403, 429]);
|
|
7
|
+
const ZYTE_METHODS = new Set([
|
|
8
|
+
'GET',
|
|
9
|
+
'POST',
|
|
10
|
+
'PUT',
|
|
11
|
+
'PATCH',
|
|
12
|
+
'DELETE',
|
|
13
|
+
]);
|
|
14
|
+
function normalizeMethod(method) {
|
|
15
|
+
const normalized = (method || 'GET').toUpperCase();
|
|
16
|
+
if (!ZYTE_METHODS.has(normalized)) {
|
|
17
|
+
throw new Error(`Zyte에서 지원하지 않는 HTTP 메서드입니다: ${normalized}`);
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
function toZyteHeaders(headers) {
|
|
22
|
+
const normalized = new Headers(headers);
|
|
23
|
+
const result = [];
|
|
24
|
+
normalized.forEach((value, name) => {
|
|
25
|
+
result.push({ name, value });
|
|
26
|
+
});
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
function assertSuccessfulTarget(result) {
|
|
30
|
+
const status = result.statusCode;
|
|
31
|
+
if (typeof status !== 'number' || status < 200 || status >= 300) {
|
|
32
|
+
throw new Error(`Zyte 대상 응답 실패: ${status ?? '알 수 없음'}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function hasZyteApiKey(apiKey) {
|
|
36
|
+
if (apiKey?.trim()) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/* c8 ignore next */
|
|
40
|
+
return typeof process !== 'undefined' && Boolean(process.env?.ZYTE_API_KEY?.trim());
|
|
41
|
+
}
|
|
42
|
+
export async function fetchJsonWithZyteFallback(url, options = {}) {
|
|
43
|
+
const { zyteApiKey, zyteTags, ...directOptions } = options;
|
|
44
|
+
try {
|
|
45
|
+
return await fetchJson(url, directOptions);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (!(error instanceof HttpError) || !ZYTE_FALLBACK_STATUSES.has(error.status)) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
if (!hasZyteApiKey(zyteApiKey)) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const result = await requestByZyte({
|
|
56
|
+
apiKey: zyteApiKey,
|
|
57
|
+
url,
|
|
58
|
+
timeout: directOptions.timeout,
|
|
59
|
+
retries: 0,
|
|
60
|
+
method: normalizeMethod(directOptions.method),
|
|
61
|
+
headers: toZyteHeaders(directOptions.headers),
|
|
62
|
+
bodyText: typeof directOptions.body === 'string' ? directOptions.body : undefined,
|
|
63
|
+
tags: zyteTags,
|
|
64
|
+
});
|
|
65
|
+
assertSuccessfulTarget(result);
|
|
66
|
+
return decodeZyteHttpBody(result);
|
|
67
|
+
}
|
package/openapi.json
CHANGED
|
@@ -22,6 +22,51 @@
|
|
|
22
22
|
"summary": "공통 GET 액션 실행",
|
|
23
23
|
"description": "action 값에 따라 기존 GET API 엔드포인트로 프록시합니다. OpenAI Actions 등록 시 이 단일 엔드포인트를 사용하세요.",
|
|
24
24
|
"parameters": [
|
|
25
|
+
{
|
|
26
|
+
"name": "cinemaCode",
|
|
27
|
+
"in": "query",
|
|
28
|
+
"required": false,
|
|
29
|
+
"description": "디트릭스 극장 코드",
|
|
30
|
+
"schema": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "brandCode",
|
|
36
|
+
"in": "query",
|
|
37
|
+
"required": false,
|
|
38
|
+
"description": "디트릭스 브랜드 코드",
|
|
39
|
+
"schema": {
|
|
40
|
+
"type": "string"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "region",
|
|
45
|
+
"in": "query",
|
|
46
|
+
"required": false,
|
|
47
|
+
"description": "디트릭스 광역 지역명",
|
|
48
|
+
"schema": {
|
|
49
|
+
"type": "string"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "movieName",
|
|
54
|
+
"in": "query",
|
|
55
|
+
"required": false,
|
|
56
|
+
"description": "디트릭스 영화명 부분 일치",
|
|
57
|
+
"schema": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "includePlayDates",
|
|
63
|
+
"in": "query",
|
|
64
|
+
"required": false,
|
|
65
|
+
"description": "디트릭스 상영 가능 날짜 포함",
|
|
66
|
+
"schema": {
|
|
67
|
+
"type": "boolean"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
25
70
|
{
|
|
26
71
|
"name": "action",
|
|
27
72
|
"in": "query",
|
|
@@ -30,6 +75,9 @@
|
|
|
30
75
|
"schema": {
|
|
31
76
|
"type": "string",
|
|
32
77
|
"enum": [
|
|
78
|
+
"dtryxListCinemas",
|
|
79
|
+
"dtryxListNowShowing",
|
|
80
|
+
"dtryxGetRemainingSeats",
|
|
33
81
|
"daisoSearchProducts",
|
|
34
82
|
"daisoGetProduct",
|
|
35
83
|
"daisoFindStores",
|
|
@@ -551,7 +599,7 @@
|
|
|
551
599
|
"name": "timeoutMs",
|
|
552
600
|
"in": "query",
|
|
553
601
|
"required": false,
|
|
554
|
-
"description": "
|
|
602
|
+
"description": "원본 요청 타임아웃(ms), 디트릭스는 1~60000",
|
|
555
603
|
"schema": {
|
|
556
604
|
"type": "integer"
|
|
557
605
|
}
|
|
@@ -2726,11 +2774,11 @@
|
|
|
2726
2774
|
},
|
|
2727
2775
|
"bookedSeats": {
|
|
2728
2776
|
"type": "integer",
|
|
2729
|
-
"example":
|
|
2777
|
+
"example": 4
|
|
2730
2778
|
},
|
|
2731
2779
|
"remainingSeats": {
|
|
2732
2780
|
"type": "integer",
|
|
2733
|
-
"example":
|
|
2781
|
+
"example": 28
|
|
2734
2782
|
}
|
|
2735
2783
|
}
|
|
2736
2784
|
},
|
package/openapi.yaml
CHANGED
|
@@ -24,6 +24,36 @@ paths:
|
|
|
24
24
|
summary: 공통 GET 액션 실행
|
|
25
25
|
description: action 값에 따라 기존 GET API 엔드포인트로 프록시합니다. OpenAI Actions 등록 시 이 단일 엔드포인트를 사용하세요.
|
|
26
26
|
parameters:
|
|
27
|
+
- name: cinemaCode
|
|
28
|
+
in: query
|
|
29
|
+
required: false
|
|
30
|
+
description: 디트릭스 극장 코드
|
|
31
|
+
schema:
|
|
32
|
+
type: string
|
|
33
|
+
- name: brandCode
|
|
34
|
+
in: query
|
|
35
|
+
required: false
|
|
36
|
+
description: 디트릭스 브랜드 코드
|
|
37
|
+
schema:
|
|
38
|
+
type: string
|
|
39
|
+
- name: region
|
|
40
|
+
in: query
|
|
41
|
+
required: false
|
|
42
|
+
description: 디트릭스 광역 지역명
|
|
43
|
+
schema:
|
|
44
|
+
type: string
|
|
45
|
+
- name: movieName
|
|
46
|
+
in: query
|
|
47
|
+
required: false
|
|
48
|
+
description: 디트릭스 영화명 부분 일치
|
|
49
|
+
schema:
|
|
50
|
+
type: string
|
|
51
|
+
- name: includePlayDates
|
|
52
|
+
in: query
|
|
53
|
+
required: false
|
|
54
|
+
description: 디트릭스 상영 가능 날짜 포함
|
|
55
|
+
schema:
|
|
56
|
+
type: boolean
|
|
27
57
|
- name: action
|
|
28
58
|
in: query
|
|
29
59
|
required: true
|
|
@@ -31,6 +61,9 @@ paths:
|
|
|
31
61
|
schema:
|
|
32
62
|
type: string
|
|
33
63
|
enum:
|
|
64
|
+
- dtryxListCinemas
|
|
65
|
+
- dtryxListNowShowing
|
|
66
|
+
- dtryxGetRemainingSeats
|
|
34
67
|
- daisoSearchProducts
|
|
35
68
|
- daisoGetProduct
|
|
36
69
|
- daisoFindStores
|
|
@@ -389,7 +422,7 @@ paths:
|
|
|
389
422
|
- name: timeoutMs
|
|
390
423
|
in: query
|
|
391
424
|
required: false
|
|
392
|
-
description:
|
|
425
|
+
description: 원본 요청 타임아웃(ms), 디트릭스는 1~60000
|
|
393
426
|
schema:
|
|
394
427
|
type: integer
|
|
395
428
|
responses:
|
|
@@ -1920,10 +1953,10 @@ components:
|
|
|
1920
1953
|
example: 32
|
|
1921
1954
|
bookedSeats:
|
|
1922
1955
|
type: integer
|
|
1923
|
-
example:
|
|
1956
|
+
example: 4
|
|
1924
1957
|
remainingSeats:
|
|
1925
1958
|
type: integer
|
|
1926
|
-
example:
|
|
1959
|
+
example: 28
|
|
1927
1960
|
LotteCinemaTheaterSearchResponse:
|
|
1928
1961
|
type: object
|
|
1929
1962
|
properties:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daiso",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Korean retail and cinema MCP server, CLI, and Codex Skill for Daiso, convenience stores, marts, Olive Young, and theaters",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"release:dry-run": "npm pack --dry-run",
|
|
32
32
|
"publish:public": "npm publish --access public",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
|
+
"check:source-lines": "npx tsx scripts/quality/check-source-line-limit.ts",
|
|
34
35
|
"lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"scripts/**/*.ts\" \"vitest.config.ts\"",
|
|
35
36
|
"lint:fix": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"scripts/**/*.ts\" \"vitest.config.ts\" --fix",
|
|
36
37
|
"format": "prettier .github/**/*.md .github/**/*.yml README.md SECURITY.md AGENTS.md CONTRIBUTING.md package.json tsconfig.json .prettierrc.json .mcp.json eslint.config.ts --write",
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
"test": "vitest run",
|
|
40
41
|
"test:watch": "vitest",
|
|
41
42
|
"test:coverage": "vitest run --coverage",
|
|
42
|
-
"check": "npm run format:check && npm run lint && npm run lint:biome && npm run typecheck && npm test",
|
|
43
|
+
"check": "npm run format:check && npm run lint && npm run lint:biome && npm run typecheck && npm run check:source-lines && npm test",
|
|
43
44
|
"lint:biome": "biome lint"
|
|
44
45
|
},
|
|
45
46
|
"keywords": [
|
|
@@ -76,37 +77,40 @@
|
|
|
76
77
|
"url": "https://github.com/hmmhmmhm/daiso-mcp/issues"
|
|
77
78
|
},
|
|
78
79
|
"engines": {
|
|
79
|
-
"node": ">=
|
|
80
|
+
"node": ">=22"
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
|
-
"@biomejs/biome": "^2.
|
|
83
|
-
"@cloudflare/workers-types": "^
|
|
83
|
+
"@biomejs/biome": "^2.5.13",
|
|
84
|
+
"@cloudflare/workers-types": "^5.20260914.1",
|
|
84
85
|
"@eslint/js": "^10.0.1",
|
|
85
|
-
"@types/node": "^
|
|
86
|
-
"@vitest/coverage-v8": "^4.1.
|
|
87
|
-
"eslint": "^10.
|
|
86
|
+
"@types/node": "^26.5.1",
|
|
87
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
88
|
+
"eslint": "^10.8.0",
|
|
88
89
|
"eslint-config-prettier": "^10.1.8",
|
|
89
|
-
"globals": "^17.
|
|
90
|
+
"globals": "^17.12.0",
|
|
90
91
|
"jiti": "^2.7.0",
|
|
91
|
-
"playwright": "^1.
|
|
92
|
-
"prettier": "^3.
|
|
93
|
-
"sharp": "^0.
|
|
94
|
-
"tsx": "^4.
|
|
95
|
-
"typescript": "
|
|
96
|
-
"typescript-eslint": "^8.
|
|
97
|
-
"vitest": "^4.1.
|
|
98
|
-
"wrangler": "^4.
|
|
92
|
+
"playwright": "^1.63.0",
|
|
93
|
+
"prettier": "^3.9.6",
|
|
94
|
+
"sharp": "^0.35.4",
|
|
95
|
+
"tsx": "^4.23.1",
|
|
96
|
+
"typescript": "6.0.3",
|
|
97
|
+
"typescript-eslint": "^8.65.0",
|
|
98
|
+
"vitest": "^4.1.11",
|
|
99
|
+
"wrangler": "^4.131.2"
|
|
99
100
|
},
|
|
100
101
|
"overrides": {
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"
|
|
102
|
+
"@modelcontextprotocol/sdk": {
|
|
103
|
+
"@hono/node-server": "2.0.11"
|
|
104
|
+
},
|
|
105
|
+
"miniflare": {
|
|
106
|
+
"sharp": "0.35.4",
|
|
107
|
+
"undici": "7.29.1"
|
|
108
|
+
}
|
|
105
109
|
},
|
|
106
110
|
"dependencies": {
|
|
107
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
108
|
-
"hono": "^4.
|
|
109
|
-
"proj4": "^2.
|
|
111
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
112
|
+
"hono": "^4.13.7",
|
|
113
|
+
"proj4": "^2.22.0",
|
|
110
114
|
"workers-mcp": "^0.0.13"
|
|
111
115
|
}
|
|
112
116
|
}
|