daiso 1.0.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/LICENSE +21 -0
- package/README.md +741 -0
- package/dist/api/cgvHandlers.js +97 -0
- package/dist/api/handlers.js +208 -0
- package/dist/api/megaboxHandlers.js +145 -0
- package/dist/api/response.js +22 -0
- package/dist/api/routes/cgvRoutes.js +22 -0
- package/dist/api/routes/daisoRoutes.js +27 -0
- package/dist/api/routes/megaboxRoutes.js +22 -0
- package/dist/api/routes/oliveyoungRoutes.js +17 -0
- package/dist/cli.js +140 -0
- package/dist/core/interfaces.js +6 -0
- package/dist/core/registry.js +121 -0
- package/dist/core/types.js +6 -0
- package/dist/index.js +123 -0
- package/dist/pages/openapi.js +6 -0
- package/dist/pages/openapiResponse.js +32 -0
- package/dist/pages/openapiSpec.js +43 -0
- package/dist/pages/openapiSpecComponents.js +419 -0
- package/dist/pages/openapiSpecComponentsCgv.js +123 -0
- package/dist/pages/openapiSpecPathsCgv.js +149 -0
- package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +381 -0
- package/dist/pages/openapiSpecPathsMegabox.js +184 -0
- package/dist/pages/openapiYaml.js +58 -0
- package/dist/pages/privacy.js +262 -0
- package/dist/pages/prompt.js +425 -0
- package/dist/services/cgv/api.js +15 -0
- package/dist/services/cgv/client.js +146 -0
- package/dist/services/cgv/index.js +30 -0
- package/dist/services/cgv/timetable.js +16 -0
- package/dist/services/cgv/tools/findTheaters.js +43 -0
- package/dist/services/cgv/tools/getTimetable.js +47 -0
- package/dist/services/cgv/tools/searchMovies.js +40 -0
- package/dist/services/cgv/transport.js +91 -0
- package/dist/services/cgv/types.js +4 -0
- package/dist/services/daiso/api.js +53 -0
- package/dist/services/daiso/client.js +35 -0
- package/dist/services/daiso/index.js +42 -0
- package/dist/services/daiso/tools/checkInventory.js +122 -0
- package/dist/services/daiso/tools/findStores.js +145 -0
- package/dist/services/daiso/tools/getPriceInfo.js +90 -0
- package/dist/services/daiso/tools/searchProducts.js +74 -0
- package/dist/services/daiso/types.js +4 -0
- package/dist/services/megabox/api.js +8 -0
- package/dist/services/megabox/client.js +140 -0
- package/dist/services/megabox/index.js +26 -0
- package/dist/services/megabox/tools/findNearbyTheaters.js +74 -0
- package/dist/services/megabox/tools/getRemainingSeats.js +57 -0
- package/dist/services/megabox/tools/listNowShowing.js +51 -0
- package/dist/services/megabox/types.js +4 -0
- package/dist/services/oliveyoung/api.js +8 -0
- package/dist/services/oliveyoung/client.js +90 -0
- package/dist/services/oliveyoung/index.js +28 -0
- package/dist/services/oliveyoung/tools/checkInventory.js +87 -0
- package/dist/services/oliveyoung/tools/findNearbyStores.js +53 -0
- package/dist/services/oliveyoung/types.js +4 -0
- package/dist/tools/checkInventory.js +98 -0
- package/dist/tools/findStores.js +125 -0
- package/dist/tools/getPriceInfo.js +77 -0
- package/dist/tools/searchProducts.js +61 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/cache.js +54 -0
- package/dist/utils/fetch.js +14 -0
- package/dist/utils/format.js +31 -0
- package/dist/utils/http.js +35 -0
- package/dist/utils/zyte.js +76 -0
- package/openapi.json +1938 -0
- package/openapi.yaml +1338 -0
- package/package.json +79 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Edge Cache 유틸리티
|
|
3
|
+
*
|
|
4
|
+
* caches.default를 사용할 수 있는 런타임에서만 동작하며,
|
|
5
|
+
* 로컬(Node) 테스트 환경에서는 자동으로 캐시를 건너뜁니다.
|
|
6
|
+
*/
|
|
7
|
+
function getCacheStorage() {
|
|
8
|
+
const globalObject = globalThis;
|
|
9
|
+
return globalObject.caches;
|
|
10
|
+
}
|
|
11
|
+
function createCacheKey(urlString, keyPrefix) {
|
|
12
|
+
const url = new URL(urlString);
|
|
13
|
+
const normalized = new URL(`${url.origin}${url.pathname}`);
|
|
14
|
+
const sortedEntries = Array.from(url.searchParams.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
15
|
+
for (const [key, value] of sortedEntries) {
|
|
16
|
+
normalized.searchParams.append(key, value);
|
|
17
|
+
}
|
|
18
|
+
if (keyPrefix) {
|
|
19
|
+
normalized.searchParams.append('__cache_prefix', keyPrefix);
|
|
20
|
+
}
|
|
21
|
+
return new Request(normalized.toString(), { method: 'GET' });
|
|
22
|
+
}
|
|
23
|
+
function buildCacheControl(options) {
|
|
24
|
+
const { ttlSeconds, staleWhileRevalidateSeconds = 30 } = options;
|
|
25
|
+
return [
|
|
26
|
+
`public`,
|
|
27
|
+
`max-age=${ttlSeconds}`,
|
|
28
|
+
`s-maxage=${ttlSeconds}`,
|
|
29
|
+
`stale-while-revalidate=${staleWhileRevalidateSeconds}`,
|
|
30
|
+
].join(', ');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* GET 응답을 Cloudflare Edge Cache에 저장/조회합니다.
|
|
34
|
+
*/
|
|
35
|
+
export async function withEdgeCache(requestUrl, options, fetcher) {
|
|
36
|
+
const cacheStorage = getCacheStorage();
|
|
37
|
+
const cache = cacheStorage?.default;
|
|
38
|
+
if (!cache) {
|
|
39
|
+
return fetcher();
|
|
40
|
+
}
|
|
41
|
+
const cacheKey = createCacheKey(requestUrl, options.keyPrefix);
|
|
42
|
+
const cachedResponse = await cache.match(cacheKey);
|
|
43
|
+
if (cachedResponse) {
|
|
44
|
+
return cachedResponse;
|
|
45
|
+
}
|
|
46
|
+
const response = await fetcher();
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
const cacheControl = buildCacheControl(options);
|
|
51
|
+
response.headers.set('Cache-Control', cacheControl);
|
|
52
|
+
await cache.put(cacheKey, response.clone());
|
|
53
|
+
return response;
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 요청 유틸리티
|
|
3
|
+
*/
|
|
4
|
+
import { fetchJson as fetchJsonByHttp, fetchText as fetchTextByHttp, fetchWithTimeout, } from './http.js';
|
|
5
|
+
// JSON 응답을 위한 fetch
|
|
6
|
+
export async function fetchJson(url, options = {}) {
|
|
7
|
+
return fetchJsonByHttp(url, options);
|
|
8
|
+
}
|
|
9
|
+
// HTML 응답을 위한 fetch
|
|
10
|
+
export async function fetchHtml(url, options = {}) {
|
|
11
|
+
return fetchTextByHttp(url, options);
|
|
12
|
+
}
|
|
13
|
+
// 하위 호환을 위한 별칭 (기존 이름 유지)
|
|
14
|
+
export const daisoFetch = fetchWithTimeout;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 숫자/시간/날짜 포맷 공용 유틸리티
|
|
3
|
+
*/
|
|
4
|
+
export function toNumber(value) {
|
|
5
|
+
if (typeof value === 'number') {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === 'string') {
|
|
9
|
+
const parsed = parseInt(value, 10);
|
|
10
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
11
|
+
}
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
export function formatTime(raw) {
|
|
15
|
+
if (!raw) {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
if (raw.includes(':')) {
|
|
19
|
+
return raw;
|
|
20
|
+
}
|
|
21
|
+
if (raw.length === 4) {
|
|
22
|
+
return `${raw.slice(0, 2)}:${raw.slice(2)}`;
|
|
23
|
+
}
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
export function toYyyymmdd(value = new Date()) {
|
|
27
|
+
const year = value.getFullYear();
|
|
28
|
+
const month = `${value.getMonth() + 1}`.padStart(2, '0');
|
|
29
|
+
const day = `${value.getDate()}`.padStart(2, '0');
|
|
30
|
+
return `${year}${month}${day}`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 공용 HTTP 유틸리티
|
|
3
|
+
*/
|
|
4
|
+
export function createTimeoutController(timeout) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
7
|
+
return { controller, timeoutId };
|
|
8
|
+
}
|
|
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
|
+
});
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
clearTimeout(timeoutId);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function fetchJson(url, options = {}) {
|
|
23
|
+
const response = await fetchWithTimeout(url, options);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`API 요청 실패: ${response.status} ${response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
return response.json();
|
|
28
|
+
}
|
|
29
|
+
export async function fetchText(url, options = {}) {
|
|
30
|
+
const response = await fetchWithTimeout(url, options);
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`API 요청 실패: ${response.status} ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
return response.text();
|
|
35
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zyte Extract API 공통 유틸리티
|
|
3
|
+
*/
|
|
4
|
+
import { createTimeoutController } from './http.js';
|
|
5
|
+
export function resolveZyteApiKey(apiKey) {
|
|
6
|
+
if (apiKey && apiKey.trim().length > 0) {
|
|
7
|
+
return apiKey;
|
|
8
|
+
}
|
|
9
|
+
/* c8 ignore start */
|
|
10
|
+
if (typeof process !== 'undefined' && process.env?.ZYTE_API_KEY) {
|
|
11
|
+
return process.env.ZYTE_API_KEY;
|
|
12
|
+
}
|
|
13
|
+
/* c8 ignore end */
|
|
14
|
+
throw new Error('ZYTE_API_KEY가 설정되지 않았습니다. .env 또는 Cloudflare Worker Secret을 확인해주세요.');
|
|
15
|
+
}
|
|
16
|
+
export function encodeBasicAuth(apiKey) {
|
|
17
|
+
if (typeof btoa === 'function') {
|
|
18
|
+
return btoa(`${apiKey}:`);
|
|
19
|
+
}
|
|
20
|
+
/* c8 ignore start */
|
|
21
|
+
if (typeof Buffer !== 'undefined') {
|
|
22
|
+
return Buffer.from(`${apiKey}:`).toString('base64');
|
|
23
|
+
}
|
|
24
|
+
/* c8 ignore end */
|
|
25
|
+
throw new Error('Basic 인증 인코딩을 지원하지 않는 런타임입니다.');
|
|
26
|
+
}
|
|
27
|
+
export function decodeBase64(value) {
|
|
28
|
+
if (typeof atob === 'function') {
|
|
29
|
+
const binary = atob(value);
|
|
30
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
31
|
+
return new TextDecoder().decode(bytes);
|
|
32
|
+
}
|
|
33
|
+
/* c8 ignore start */
|
|
34
|
+
if (typeof Buffer !== 'undefined') {
|
|
35
|
+
return Buffer.from(value, 'base64').toString('utf8');
|
|
36
|
+
}
|
|
37
|
+
/* c8 ignore end */
|
|
38
|
+
throw new Error('Base64 디코딩을 지원하지 않는 런타임입니다.');
|
|
39
|
+
}
|
|
40
|
+
export async function requestByZyte(options) {
|
|
41
|
+
const { timeout = 15000, url, method = 'GET', headers = [], bodyText, apiKey } = options;
|
|
42
|
+
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());
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function decodeZyteHttpBody(result) {
|
|
72
|
+
if (!result.httpResponseBody) {
|
|
73
|
+
throw new Error('Zyte HTTP 응답 본문이 비어 있습니다.');
|
|
74
|
+
}
|
|
75
|
+
return JSON.parse(decodeBase64(result.httpResponseBody));
|
|
76
|
+
}
|