daiso 1.0.4 → 1.0.6

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 (166) hide show
  1. package/README.md +249 -533
  2. package/dist/api/actionsProxy.js +82 -0
  3. package/dist/api/cgvHandlers.js +103 -16
  4. package/dist/api/configStatus.js +19 -0
  5. package/dist/api/daisoHandlers.js +127 -0
  6. package/dist/api/emart24Handlers.js +176 -0
  7. package/dist/api/gs25Handlers.js +267 -0
  8. package/dist/api/handlers.js +209 -104
  9. package/dist/api/healthChecks.js +504 -0
  10. package/dist/api/lottecinemaHandlers.js +155 -0
  11. package/dist/api/lottemartHandlers.js +144 -0
  12. package/dist/api/megaboxHandlers.js +81 -50
  13. package/dist/api/response.js +2 -0
  14. package/dist/api/routes/cuRoutes.js +17 -0
  15. package/dist/api/routes/daisoRoutes.js +6 -1
  16. package/dist/api/routes/emart24Routes.js +22 -0
  17. package/dist/api/routes/gs25Routes.js +22 -0
  18. package/dist/api/routes/healthRoutes.js +61 -0
  19. package/dist/api/routes/lottecinemaRoutes.js +22 -0
  20. package/dist/api/routes/lottemartRoutes.js +18 -0
  21. package/dist/api/routes/oliveyoungRoutes.js +6 -1
  22. package/dist/api/routes/sevenelevenRoutes.js +32 -0
  23. package/dist/api/sevenelevenHandlers.js +200 -0
  24. package/dist/bin.js +0 -0
  25. package/dist/cli/args.js +75 -0
  26. package/dist/cli/commandOptions.js +38 -0
  27. package/dist/cli/commands/convenience.js +293 -0
  28. package/dist/cli/commands/daiso.js +112 -0
  29. package/dist/cli/constants.js +5 -0
  30. package/dist/cli/deps.js +48 -0
  31. package/dist/cli/http.js +156 -0
  32. package/dist/cli/interactiveFetch.js +31 -0
  33. package/dist/cli/interactiveItemSearch.js +261 -0
  34. package/dist/cli/interactivePrompt.js +50 -0
  35. package/dist/cli/interactiveTypes.js +4 -0
  36. package/dist/cli/types.js +4 -0
  37. package/dist/cli.js +87 -196
  38. package/dist/cliHelp.js +212 -1
  39. package/dist/cliInteractive.js +243 -0
  40. package/dist/cliPicker.js +57 -0
  41. package/dist/cliRenderer.js +90 -3
  42. package/dist/core/errors.js +66 -0
  43. package/dist/core/registry.js +227 -3
  44. package/dist/index.js +149 -9
  45. package/dist/pages/openapi.js +2 -1
  46. package/dist/pages/openapiFullSpec.js +58 -0
  47. package/dist/pages/openapiResponse.js +28 -0
  48. package/dist/pages/openapiSpec.js +2 -41
  49. package/dist/pages/openapiSpecActions.js +109 -0
  50. package/dist/pages/openapiSpecComponents.js +30 -391
  51. package/dist/pages/openapiSpecComponentsCgv.js +24 -0
  52. package/dist/pages/openapiSpecComponentsCu.js +112 -0
  53. package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
  54. package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
  55. package/dist/pages/openapiSpecComponentsGs25.js +109 -0
  56. package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
  57. package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
  58. package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
  59. package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
  60. package/dist/pages/openapiSpecPathsCgv.js +66 -3
  61. package/dist/pages/openapiSpecPathsCu.js +145 -0
  62. package/dist/pages/openapiSpecPathsDaiso.js +302 -0
  63. package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
  64. package/dist/pages/openapiSpecPathsEmart24.js +223 -0
  65. package/dist/pages/openapiSpecPathsGs25.js +166 -0
  66. package/dist/pages/openapiSpecPathsLotteMart.js +149 -0
  67. package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
  68. package/dist/pages/openapiSpecPathsMegabox.js +52 -3
  69. package/dist/pages/openapiSpecPathsOliveyoung.js +266 -0
  70. package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
  71. package/dist/pages/prompt.js +8 -402
  72. package/dist/pages/promptCoreText.js +278 -0
  73. package/dist/pages/promptReferenceText.js +137 -0
  74. package/dist/pages/promptRetailCinemaText.js +274 -0
  75. package/dist/services/cgv/client.js +6 -1
  76. package/dist/services/cgv/index.js +3 -3
  77. package/dist/services/cgv/location.js +235 -0
  78. package/dist/services/cgv/tools/findTheaters.js +30 -5
  79. package/dist/services/cgv/tools/getTimetable.js +42 -14
  80. package/dist/services/cgv/tools/searchMovies.js +40 -12
  81. package/dist/services/cgv/transport.js +0 -3
  82. package/dist/services/cu/api.js +11 -0
  83. package/dist/services/cu/client.js +297 -0
  84. package/dist/services/cu/index.js +21 -0
  85. package/dist/services/cu/tools/checkInventory.js +123 -0
  86. package/dist/services/cu/tools/findNearbyStores.js +49 -0
  87. package/dist/services/cu/types.js +4 -0
  88. package/dist/services/daiso/api.js +22 -2
  89. package/dist/services/daiso/client.js +67 -0
  90. package/dist/services/daiso/index.js +4 -0
  91. package/dist/services/daiso/product.js +17 -0
  92. package/dist/services/daiso/tools/checkInventory.js +80 -43
  93. package/dist/services/daiso/tools/findInventoryByName.js +222 -0
  94. package/dist/services/daiso/tools/findStores.js +17 -7
  95. package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
  96. package/dist/services/emart24/api.js +11 -0
  97. package/dist/services/emart24/client.js +182 -0
  98. package/dist/services/emart24/index.js +22 -0
  99. package/dist/services/emart24/inventoryLookup.js +134 -0
  100. package/dist/services/emart24/tools/checkInventory.js +54 -0
  101. package/dist/services/emart24/tools/findNearbyStores.js +72 -0
  102. package/dist/services/emart24/tools/searchProducts.js +73 -0
  103. package/dist/services/emart24/types.js +4 -0
  104. package/dist/services/gs25/api.js +9 -0
  105. package/dist/services/gs25/client.js +208 -0
  106. package/dist/services/gs25/index.js +22 -0
  107. package/dist/services/gs25/storeUtils.js +168 -0
  108. package/dist/services/gs25/tools/checkInventory.js +163 -0
  109. package/dist/services/gs25/tools/findNearbyStores.js +103 -0
  110. package/dist/services/gs25/tools/searchProducts.js +57 -0
  111. package/dist/services/gs25/types.js +4 -0
  112. package/dist/services/lottecinema/api.js +12 -0
  113. package/dist/services/lottecinema/client.js +161 -0
  114. package/dist/services/lottecinema/index.js +30 -0
  115. package/dist/services/lottecinema/location.js +191 -0
  116. package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
  117. package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
  118. package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
  119. package/dist/services/lottecinema/types.js +4 -0
  120. package/dist/services/lottemart/api.js +31 -0
  121. package/dist/services/lottemart/client.js +254 -0
  122. package/dist/services/lottemart/clientTypes.js +4 -0
  123. package/dist/services/lottemart/config.js +4 -0
  124. package/dist/services/lottemart/debug.js +92 -0
  125. package/dist/services/lottemart/geocode.js +49 -0
  126. package/dist/services/lottemart/index.js +30 -0
  127. package/dist/services/lottemart/keyword.js +29 -0
  128. package/dist/services/lottemart/parser.js +185 -0
  129. package/dist/services/lottemart/session.js +331 -0
  130. package/dist/services/lottemart/storeSearch.js +52 -0
  131. package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
  132. package/dist/services/lottemart/tools/searchProducts.js +69 -0
  133. package/dist/services/lottemart/types.js +4 -0
  134. package/dist/services/lottemart/zetta.js +105 -0
  135. package/dist/services/megabox/location.js +219 -0
  136. package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
  137. package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
  138. package/dist/services/megabox/tools/listNowShowing.js +31 -3
  139. package/dist/services/oliveyoung/api.js +2 -0
  140. package/dist/services/oliveyoung/client.js +225 -13
  141. package/dist/services/oliveyoung/index.js +3 -1
  142. package/dist/services/oliveyoung/tools/checkInventory.js +21 -5
  143. package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
  144. package/dist/services/seveneleven/api.js +15 -0
  145. package/dist/services/seveneleven/client.js +286 -0
  146. package/dist/services/seveneleven/index.js +30 -0
  147. package/dist/services/seveneleven/inventory.js +302 -0
  148. package/dist/services/seveneleven/productKeyword.js +141 -0
  149. package/dist/services/seveneleven/tools/checkInventory.js +82 -0
  150. package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
  151. package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
  152. package/dist/services/seveneleven/tools/searchProducts.js +67 -0
  153. package/dist/services/seveneleven/tools/searchStores.js +45 -0
  154. package/dist/services/seveneleven/types.js +4 -0
  155. package/dist/utils/cliInteractiveHelpers.js +63 -0
  156. package/dist/utils/daisoKeyword.js +30 -0
  157. package/dist/utils/http.js +98 -12
  158. package/dist/utils/zyte.js +47 -26
  159. package/openapi.json +1979 -736
  160. package/openapi.yaml +1428 -541
  161. package/package.json +49 -18
  162. package/dist/tools/checkInventory.js +0 -98
  163. package/dist/tools/findStores.js +0 -125
  164. package/dist/tools/getPriceInfo.js +0 -77
  165. package/dist/tools/searchProducts.js +0 -61
  166. package/dist/types/index.js +0 -4
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 세븐일레븐 매장 검색 도구
3
+ */
4
+ import * as z from 'zod';
5
+ import { fetchSevenElevenStoresByKeyword } from '../client.js';
6
+ async function searchStores(args) {
7
+ const { keyword, limit = 20, timeoutMs = 15000 } = args;
8
+ if (!keyword || keyword.trim().length === 0) {
9
+ throw new Error('매장 검색어(keyword)를 입력해주세요.');
10
+ }
11
+ const result = await fetchSevenElevenStoresByKeyword({
12
+ keyword,
13
+ limit,
14
+ }, {
15
+ timeout: timeoutMs,
16
+ });
17
+ return {
18
+ content: [
19
+ {
20
+ type: 'text',
21
+ text: JSON.stringify({
22
+ keyword: result.query,
23
+ totalCount: result.totalCount,
24
+ count: result.stores.length,
25
+ stores: result.stores,
26
+ }, null, 2),
27
+ },
28
+ ],
29
+ };
30
+ }
31
+ export function createSearchStoresTool() {
32
+ return {
33
+ name: 'seveneleven_search_stores',
34
+ metadata: {
35
+ title: '세븐일레븐 매장 검색',
36
+ description: '키워드로 세븐일레븐 매장을 검색합니다.',
37
+ inputSchema: {
38
+ keyword: z.string().describe('매장명/지역 키워드 (예: 안산 중앙역, 강남)'),
39
+ limit: z.number().optional().default(20).describe('반환할 최대 매장 수 (기본값: 20)'),
40
+ timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
41
+ },
42
+ },
43
+ handler: searchStores,
44
+ };
45
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 세븐일레븐 타입 정의
3
+ */
4
+ export {};
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 인터랙티브 CLI 보조 유틸
3
+ */
4
+ import { buildDaisoStoreKeywordVariants } from './daisoKeyword.js';
5
+ export function isRecord(value) {
6
+ return typeof value === 'object' && value !== null;
7
+ }
8
+ export function toText(value) {
9
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
10
+ return String(value);
11
+ }
12
+ return '';
13
+ }
14
+ export function parseStores(payload) {
15
+ if (!isRecord(payload) || payload.success !== true || !isRecord(payload.data)) {
16
+ return [];
17
+ }
18
+ const stores = payload.data.stores;
19
+ if (!Array.isArray(stores)) {
20
+ return [];
21
+ }
22
+ const result = [];
23
+ for (const store of stores) {
24
+ if (!isRecord(store)) {
25
+ continue;
26
+ }
27
+ const name = toText(store.name) || toText(store.storeName);
28
+ if (!name) {
29
+ continue;
30
+ }
31
+ result.push({
32
+ name,
33
+ address: toText(store.address),
34
+ phone: toText(store.phone),
35
+ });
36
+ }
37
+ return result;
38
+ }
39
+ export function parseDaisoProducts(payload) {
40
+ if (!isRecord(payload) || payload.success !== true || !isRecord(payload.data)) {
41
+ return [];
42
+ }
43
+ const products = payload.data.products;
44
+ if (!Array.isArray(products)) {
45
+ return [];
46
+ }
47
+ const result = [];
48
+ for (const product of products) {
49
+ if (!isRecord(product)) {
50
+ continue;
51
+ }
52
+ const id = toText(product.id);
53
+ const name = toText(product.name);
54
+ if (!id || !name) {
55
+ continue;
56
+ }
57
+ const priceRaw = product.price;
58
+ const price = typeof priceRaw === 'number' ? priceRaw : Number.parseInt(toText(priceRaw), 10) || 0;
59
+ result.push({ id, name, price });
60
+ }
61
+ return result;
62
+ }
63
+ export { buildDaisoStoreKeywordVariants };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 다이소 매장 검색 키워드 보정
3
+ */
4
+ /**
5
+ * 역/공백/서비스명이 섞인 검색어를 다이소 매장 검색용으로 보정합니다.
6
+ */
7
+ export function buildDaisoStoreKeywordVariants(rawKeyword) {
8
+ const base = rawKeyword.trim();
9
+ if (!base) {
10
+ return [];
11
+ }
12
+ const compact = base.replace(/\s+/g, '');
13
+ const withoutNoise = compact.replace(/(다이소|근처|주변)/g, '');
14
+ const withoutStation = withoutNoise.replace(/역$/g, '');
15
+ const candidates = [
16
+ base,
17
+ compact,
18
+ withoutNoise,
19
+ withoutStation,
20
+ withoutStation.replace(/(중앙)(역)?$/g, '$1'),
21
+ ].map((item) => item.trim());
22
+ const unique = [];
23
+ for (const item of candidates) {
24
+ if (!item || unique.includes(item)) {
25
+ continue;
26
+ }
27
+ unique.push(item);
28
+ }
29
+ return unique;
30
+ }
@@ -1,35 +1,121 @@
1
1
  /**
2
2
  * 공용 HTTP 유틸리티
3
3
  */
4
+ export class HttpError extends Error {
5
+ status;
6
+ statusText;
7
+ bodyText;
8
+ constructor(status, statusText, bodyText) {
9
+ const normalizedBody = bodyText.trim().replace(/\s+/g, ' ').slice(0, 300);
10
+ const detail = normalizedBody.length > 0 ? ` - ${normalizedBody}` : '';
11
+ super(`API 요청 실패: ${status} ${statusText}${detail}`);
12
+ this.name = 'HttpError';
13
+ this.status = status;
14
+ this.statusText = statusText;
15
+ this.bodyText = bodyText;
16
+ }
17
+ }
4
18
  export function createTimeoutController(timeout) {
5
19
  const controller = new AbortController();
6
20
  const timeoutId = setTimeout(() => controller.abort(), timeout);
7
21
  return { controller, timeoutId };
8
22
  }
9
- export async function fetchWithTimeout(url, options = {}) {
10
- const { timeout = 10000, ...restOptions } = options;
11
- const { controller, timeoutId } = createTimeoutController(timeout);
12
- try {
13
- return await fetch(url, {
14
- ...restOptions,
15
- signal: controller.signal,
16
- });
23
+ const DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504, 522, 524];
24
+ const DEFAULT_RETRY_METHODS = ['GET', 'HEAD', 'OPTIONS'];
25
+ function isRetryableStatus(status, retryStatusCodes) {
26
+ return retryStatusCodes.includes(status);
27
+ }
28
+ function wait(ms) {
29
+ if (ms <= 0) {
30
+ return Promise.resolve();
31
+ }
32
+ return new Promise((resolve) => {
33
+ setTimeout(resolve, ms);
34
+ });
35
+ }
36
+ function normalizeMethod(method) {
37
+ return (method || 'GET').toUpperCase();
38
+ }
39
+ function canRetryMethod(method, retryMethods, retryUnsafeMethods) {
40
+ return retryUnsafeMethods || retryMethods.map((item) => item.toUpperCase()).includes(method);
41
+ }
42
+ function parseRetryAfterDelayMs(response) {
43
+ const retryAfter = response.headers.get('retry-after');
44
+ if (!retryAfter) {
45
+ return null;
46
+ }
47
+ const seconds = Number.parseFloat(retryAfter);
48
+ if (Number.isFinite(seconds)) {
49
+ return Math.max(0, Math.trunc(seconds * 1000));
17
50
  }
18
- finally {
19
- clearTimeout(timeoutId);
51
+ const retryDate = Date.parse(retryAfter);
52
+ if (Number.isFinite(retryDate)) {
53
+ return Math.max(0, retryDate - Date.now());
54
+ }
55
+ return null;
56
+ }
57
+ export async function fetchWithTimeout(url, options = {}) {
58
+ const { timeout = 10000, retries = 0, retryDelayMs = 250, retryStatusCodes = DEFAULT_RETRY_STATUS_CODES, retryUnsafeMethods = false, retryMethods = DEFAULT_RETRY_METHODS, onRetry, ...restOptions } = options;
59
+ const maxAttempts = Math.max(1, Math.trunc(retries) + 1);
60
+ const method = normalizeMethod(restOptions.method);
61
+ const retryAllowed = canRetryMethod(method, retryMethods, retryUnsafeMethods);
62
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
63
+ const { controller, timeoutId } = createTimeoutController(timeout);
64
+ try {
65
+ const response = await fetch(url, {
66
+ ...restOptions,
67
+ signal: controller.signal,
68
+ });
69
+ if (retryAllowed && attempt < maxAttempts && isRetryableStatus(response.status, retryStatusCodes)) {
70
+ const delayMs = parseRetryAfterDelayMs(response) ?? retryDelayMs;
71
+ onRetry?.({
72
+ attempt,
73
+ maxAttempts,
74
+ delayMs,
75
+ reason: 'status',
76
+ status: response.status,
77
+ method,
78
+ url,
79
+ });
80
+ await response.body?.cancel();
81
+ await wait(delayMs);
82
+ continue;
83
+ }
84
+ return response;
85
+ }
86
+ catch (error) {
87
+ if (!retryAllowed || attempt >= maxAttempts) {
88
+ throw error;
89
+ }
90
+ onRetry?.({
91
+ attempt,
92
+ maxAttempts,
93
+ delayMs: retryDelayMs,
94
+ reason: 'error',
95
+ error,
96
+ method,
97
+ url,
98
+ });
99
+ await wait(retryDelayMs);
100
+ }
101
+ finally {
102
+ clearTimeout(timeoutId);
103
+ }
20
104
  }
105
+ /* v8 ignore next */
106
+ throw new Error('API 요청 재시도 처리 중 알 수 없는 오류가 발생했습니다.');
21
107
  }
22
108
  export async function fetchJson(url, options = {}) {
23
109
  const response = await fetchWithTimeout(url, options);
24
110
  if (!response.ok) {
25
- throw new Error(`API 요청 실패: ${response.status} ${response.statusText}`);
111
+ throw new HttpError(response.status, response.statusText, await response.text());
26
112
  }
27
113
  return response.json();
28
114
  }
29
115
  export async function fetchText(url, options = {}) {
30
116
  const response = await fetchWithTimeout(url, options);
31
117
  if (!response.ok) {
32
- throw new Error(`API 요청 실패: ${response.status} ${response.statusText}`);
118
+ throw new HttpError(response.status, response.statusText, await response.text());
33
119
  }
34
120
  return response.text();
35
121
  }
@@ -37,36 +37,57 @@ export function decodeBase64(value) {
37
37
  /* c8 ignore end */
38
38
  throw new Error('Base64 디코딩을 지원하지 않는 런타임입니다.');
39
39
  }
40
+ function isAbortError(error) {
41
+ return error instanceof Error && error.name === 'AbortError';
42
+ }
43
+ function wait(ms) {
44
+ if (ms <= 0) {
45
+ return Promise.resolve();
46
+ }
47
+ return new Promise((resolve) => {
48
+ setTimeout(resolve, ms);
49
+ });
50
+ }
40
51
  export async function requestByZyte(options) {
41
- const { timeout = 15000, url, method = 'GET', headers = [], bodyText, apiKey } = options;
52
+ const { timeout = 15000, retries = 1, retryDelayMs = 250, url, method = 'GET', headers = [], bodyText, apiKey, } = options;
42
53
  const auth = encodeBasicAuth(resolveZyteApiKey(apiKey));
43
- const { controller, timeoutId } = createTimeoutController(timeout);
44
- try {
45
- const response = await fetch('https://api.zyte.com/v1/extract', {
46
- method: 'POST',
47
- headers: {
48
- Authorization: `Basic ${auth}`,
49
- 'Content-Type': 'application/json',
50
- Accept: 'application/json',
51
- },
52
- body: JSON.stringify({
53
- url,
54
- httpRequestMethod: method,
55
- customHttpRequestHeaders: headers,
56
- httpRequestText: bodyText,
57
- httpResponseBody: true,
58
- }),
59
- signal: controller.signal,
60
- });
61
- const result = (await response.json());
62
- if (!response.ok) {
63
- throw new Error(`Zyte API 호출 실패: ${response.status} ${result.detail || result.title || ''}`.trim());
54
+ const maxAttempts = Math.max(1, Math.trunc(retries) + 1);
55
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
56
+ const { controller, timeoutId } = createTimeoutController(timeout);
57
+ try {
58
+ const response = await fetch('https://api.zyte.com/v1/extract', {
59
+ method: 'POST',
60
+ headers: {
61
+ Authorization: `Basic ${auth}`,
62
+ 'Content-Type': 'application/json',
63
+ Accept: 'application/json',
64
+ },
65
+ body: JSON.stringify({
66
+ url,
67
+ httpRequestMethod: method,
68
+ customHttpRequestHeaders: headers,
69
+ httpRequestText: bodyText,
70
+ httpResponseBody: true,
71
+ }),
72
+ signal: controller.signal,
73
+ });
74
+ const result = (await response.json());
75
+ if (!response.ok) {
76
+ throw new Error(`Zyte API 호출 실패: ${response.status} ${result.detail || result.title || ''}`.trim());
77
+ }
78
+ return result;
79
+ }
80
+ catch (error) {
81
+ if (!isAbortError(error) || attempt >= maxAttempts) {
82
+ throw error;
83
+ }
84
+ await wait(retryDelayMs);
85
+ }
86
+ finally {
87
+ clearTimeout(timeoutId);
64
88
  }
65
- return result;
66
- }
67
- finally {
68
- clearTimeout(timeoutId);
69
89
  }
90
+ throw new Error('Zyte API 호출 실패');
70
91
  }
71
92
  export function decodeZyteHttpBody(result) {
72
93
  if (!result.httpResponseBody) {