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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +741 -0
  3. package/dist/api/cgvHandlers.js +97 -0
  4. package/dist/api/handlers.js +208 -0
  5. package/dist/api/megaboxHandlers.js +145 -0
  6. package/dist/api/response.js +22 -0
  7. package/dist/api/routes/cgvRoutes.js +22 -0
  8. package/dist/api/routes/daisoRoutes.js +27 -0
  9. package/dist/api/routes/megaboxRoutes.js +22 -0
  10. package/dist/api/routes/oliveyoungRoutes.js +17 -0
  11. package/dist/cli.js +140 -0
  12. package/dist/core/interfaces.js +6 -0
  13. package/dist/core/registry.js +121 -0
  14. package/dist/core/types.js +6 -0
  15. package/dist/index.js +123 -0
  16. package/dist/pages/openapi.js +6 -0
  17. package/dist/pages/openapiResponse.js +32 -0
  18. package/dist/pages/openapiSpec.js +43 -0
  19. package/dist/pages/openapiSpecComponents.js +419 -0
  20. package/dist/pages/openapiSpecComponentsCgv.js +123 -0
  21. package/dist/pages/openapiSpecPathsCgv.js +149 -0
  22. package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +381 -0
  23. package/dist/pages/openapiSpecPathsMegabox.js +184 -0
  24. package/dist/pages/openapiYaml.js +58 -0
  25. package/dist/pages/privacy.js +262 -0
  26. package/dist/pages/prompt.js +425 -0
  27. package/dist/services/cgv/api.js +15 -0
  28. package/dist/services/cgv/client.js +146 -0
  29. package/dist/services/cgv/index.js +30 -0
  30. package/dist/services/cgv/timetable.js +16 -0
  31. package/dist/services/cgv/tools/findTheaters.js +43 -0
  32. package/dist/services/cgv/tools/getTimetable.js +47 -0
  33. package/dist/services/cgv/tools/searchMovies.js +40 -0
  34. package/dist/services/cgv/transport.js +91 -0
  35. package/dist/services/cgv/types.js +4 -0
  36. package/dist/services/daiso/api.js +53 -0
  37. package/dist/services/daiso/client.js +35 -0
  38. package/dist/services/daiso/index.js +42 -0
  39. package/dist/services/daiso/tools/checkInventory.js +122 -0
  40. package/dist/services/daiso/tools/findStores.js +145 -0
  41. package/dist/services/daiso/tools/getPriceInfo.js +90 -0
  42. package/dist/services/daiso/tools/searchProducts.js +74 -0
  43. package/dist/services/daiso/types.js +4 -0
  44. package/dist/services/megabox/api.js +8 -0
  45. package/dist/services/megabox/client.js +140 -0
  46. package/dist/services/megabox/index.js +26 -0
  47. package/dist/services/megabox/tools/findNearbyTheaters.js +74 -0
  48. package/dist/services/megabox/tools/getRemainingSeats.js +57 -0
  49. package/dist/services/megabox/tools/listNowShowing.js +51 -0
  50. package/dist/services/megabox/types.js +4 -0
  51. package/dist/services/oliveyoung/api.js +8 -0
  52. package/dist/services/oliveyoung/client.js +90 -0
  53. package/dist/services/oliveyoung/index.js +28 -0
  54. package/dist/services/oliveyoung/tools/checkInventory.js +87 -0
  55. package/dist/services/oliveyoung/tools/findNearbyStores.js +53 -0
  56. package/dist/services/oliveyoung/types.js +4 -0
  57. package/dist/tools/checkInventory.js +98 -0
  58. package/dist/tools/findStores.js +125 -0
  59. package/dist/tools/getPriceInfo.js +77 -0
  60. package/dist/tools/searchProducts.js +61 -0
  61. package/dist/types/index.js +4 -0
  62. package/dist/utils/cache.js +54 -0
  63. package/dist/utils/fetch.js +14 -0
  64. package/dist/utils/format.js +31 -0
  65. package/dist/utils/http.js +35 -0
  66. package/dist/utils/zyte.js +76 -0
  67. package/openapi.json +1938 -0
  68. package/openapi.yaml +1338 -0
  69. package/package.json +79 -0
package/dist/cli.js ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * daiso CLI 엔트리
4
+ *
5
+ * npx daiso 명령으로 원격 MCP 서버 정보를 확인하고 상태를 점검합니다.
6
+ */
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { spawn } from 'node:child_process';
11
+ const DEFAULT_BASE_URL = 'https://mcp.aka.page';
12
+ const DEFAULT_MCP_URL = `${DEFAULT_BASE_URL}/mcp`;
13
+ function loadVersion() {
14
+ const cliPath = fileURLToPath(import.meta.url);
15
+ const packagePath = path.resolve(path.dirname(cliPath), '../package.json');
16
+ if (!existsSync(packagePath)) {
17
+ return '0.0.0';
18
+ }
19
+ const raw = readFileSync(packagePath, 'utf8');
20
+ const parsed = JSON.parse(raw);
21
+ return parsed.version ?? '0.0.0';
22
+ }
23
+ function printHelp(writeOut) {
24
+ writeOut('daiso CLI');
25
+ writeOut('');
26
+ writeOut('사용법:');
27
+ writeOut(' daiso <command> [options]');
28
+ writeOut('');
29
+ writeOut('명령어:');
30
+ writeOut(' help 도움말 출력');
31
+ writeOut(' version CLI 버전 출력');
32
+ writeOut(' url MCP 엔드포인트 URL 출력');
33
+ writeOut(' health 원격 서버 상태 확인');
34
+ writeOut(' claude Claude Code MCP 등록 명령 출력');
35
+ writeOut(' claude --exec Claude Code MCP 등록 명령 실행');
36
+ writeOut('');
37
+ writeOut('예시:');
38
+ writeOut(' npx daiso url');
39
+ writeOut(' npx daiso health');
40
+ writeOut(' npx daiso claude');
41
+ }
42
+ async function execCommand(command, args) {
43
+ return await new Promise((resolve, reject) => {
44
+ const child = spawn(command, args, {
45
+ stdio: 'inherit',
46
+ shell: false,
47
+ });
48
+ child.on('error', (error) => {
49
+ reject(error);
50
+ });
51
+ child.on('close', (code) => {
52
+ resolve(code ?? 1);
53
+ });
54
+ });
55
+ }
56
+ function createDefaultDeps() {
57
+ return {
58
+ fetchImpl: fetch,
59
+ writeOut: (message) => {
60
+ process.stdout.write(`${message}\n`);
61
+ },
62
+ writeErr: (message) => {
63
+ process.stderr.write(`${message}\n`);
64
+ },
65
+ getVersion: loadVersion,
66
+ nowIso: () => new Date().toISOString(),
67
+ runCommand: execCommand,
68
+ };
69
+ }
70
+ export async function runCli(argv, deps) {
71
+ const resolvedDeps = {
72
+ ...createDefaultDeps(),
73
+ ...deps,
74
+ };
75
+ const [command, ...options] = argv;
76
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
77
+ printHelp(resolvedDeps.writeOut);
78
+ return 0;
79
+ }
80
+ if (command === 'version' || command === '--version' || command === '-v') {
81
+ resolvedDeps.writeOut(resolvedDeps.getVersion());
82
+ return 0;
83
+ }
84
+ if (command === 'url') {
85
+ resolvedDeps.writeOut(DEFAULT_MCP_URL);
86
+ return 0;
87
+ }
88
+ if (command === 'health') {
89
+ try {
90
+ const response = await resolvedDeps.fetchImpl(`${DEFAULT_BASE_URL}/health`);
91
+ if (!response.ok) {
92
+ resolvedDeps.writeErr(`서버 상태 확인 실패: HTTP ${response.status}`);
93
+ return 1;
94
+ }
95
+ const payload = (await response.json());
96
+ resolvedDeps.writeOut(JSON.stringify({
97
+ status: payload.status ?? 'unknown',
98
+ endpoint: DEFAULT_MCP_URL,
99
+ checkedAt: resolvedDeps.nowIso(),
100
+ }, null, 2));
101
+ return 0;
102
+ }
103
+ catch (error) {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ resolvedDeps.writeErr(`서버 상태 확인 중 오류 발생: ${message}`);
106
+ return 1;
107
+ }
108
+ }
109
+ if (command === 'claude') {
110
+ const cliArgs = ['mcp', 'add', 'daiso', DEFAULT_BASE_URL, '--transport', 'sse'];
111
+ if (options.includes('--exec')) {
112
+ try {
113
+ return await resolvedDeps.runCommand('claude', cliArgs);
114
+ }
115
+ catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ resolvedDeps.writeErr(`Claude CLI 실행 실패: ${message}`);
118
+ return 1;
119
+ }
120
+ }
121
+ resolvedDeps.writeOut(`claude ${cliArgs.join(' ')}`);
122
+ return 0;
123
+ }
124
+ resolvedDeps.writeErr(`알 수 없는 명령어: ${command}`);
125
+ resolvedDeps.writeErr('도움말: daiso help');
126
+ return 1;
127
+ }
128
+ export function isDirectExecution(entryPath = process.argv[1], moduleUrl = import.meta.url) {
129
+ if (!entryPath) {
130
+ return false;
131
+ }
132
+ return path.resolve(entryPath) === fileURLToPath(moduleUrl);
133
+ }
134
+ /* c8 ignore start */
135
+ if (isDirectExecution()) {
136
+ runCli(process.argv.slice(2)).then((exitCode) => {
137
+ process.exit(exitCode);
138
+ });
139
+ }
140
+ /* c8 ignore stop */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 서비스 프로바이더 인터페이스
3
+ *
4
+ * 모든 서비스(다이소, CU, CGV 등)가 구현해야 하는 계약입니다.
5
+ */
6
+ export {};
@@ -0,0 +1,121 @@
1
+ /**
2
+ * 서비스 레지스트리
3
+ *
4
+ * 서비스 프로바이더를 등록하고 MCP 서버에 도구를 연결합니다.
5
+ */
6
+ /**
7
+ * 서비스 레지스트리 클래스
8
+ *
9
+ * 여러 서비스 프로바이더를 관리하고 MCP 서버에 등록합니다.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const registry = new ServiceRegistry();
14
+ * registry.register(createDaisoService);
15
+ * registry.register(createCuService);
16
+ * await registry.applyToServer(mcpServer);
17
+ * ```
18
+ */
19
+ export class ServiceRegistry {
20
+ services = new Map();
21
+ /**
22
+ * 서비스 팩토리 등록
23
+ * @param factory 서비스 인스턴스를 생성하는 팩토리 함수
24
+ */
25
+ register(factory) {
26
+ const service = factory();
27
+ const { id } = service.metadata;
28
+ if (this.services.has(id)) {
29
+ throw new Error(`서비스 '${id}'가 이미 등록되어 있습니다.`);
30
+ }
31
+ this.services.set(id, service);
32
+ }
33
+ /**
34
+ * 여러 서비스 팩토리를 한 번에 등록
35
+ * @param factories 서비스 팩토리 함수 배열
36
+ */
37
+ registerAll(factories) {
38
+ for (const factory of factories) {
39
+ this.register(factory);
40
+ }
41
+ }
42
+ /**
43
+ * 모든 서비스 초기화
44
+ */
45
+ async initializeAll() {
46
+ const initPromises = Array.from(this.services.values())
47
+ .filter((service) => service.initialize)
48
+ .map((service) => service.initialize());
49
+ await Promise.all(initPromises);
50
+ }
51
+ /**
52
+ * 모든 서비스 정리
53
+ */
54
+ async cleanupAll() {
55
+ const cleanupPromises = Array.from(this.services.values())
56
+ .filter((service) => service.cleanup)
57
+ .map((service) => service.cleanup());
58
+ await Promise.all(cleanupPromises);
59
+ }
60
+ /**
61
+ * 모든 도구를 MCP 서버에 등록
62
+ * @param server MCP 서버 인스턴스
63
+ */
64
+ applyToServer(server) {
65
+ for (const service of this.services.values()) {
66
+ const tools = service.getTools();
67
+ for (const tool of tools) {
68
+ this.registerTool(server, tool);
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * 단일 도구를 MCP 서버에 등록
74
+ */
75
+ registerTool(server, tool) {
76
+ server.registerTool(tool.name, tool.metadata, async (args) => {
77
+ const result = await tool.handler(args);
78
+ return {
79
+ content: result.content.map((item) => ({
80
+ type: item.type,
81
+ text: item.text,
82
+ })),
83
+ };
84
+ });
85
+ }
86
+ /**
87
+ * 등록된 서비스 정보 목록 반환
88
+ * API 응답용으로 사용됩니다.
89
+ */
90
+ getServicesInfo() {
91
+ return Array.from(this.services.values()).map((service) => ({
92
+ id: service.metadata.id,
93
+ name: service.metadata.name,
94
+ version: service.metadata.version,
95
+ description: service.metadata.description,
96
+ tools: service.getTools().map((tool) => tool.name),
97
+ }));
98
+ }
99
+ /**
100
+ * 등록된 모든 도구 이름 반환
101
+ */
102
+ getAllToolNames() {
103
+ const toolNames = [];
104
+ for (const service of this.services.values()) {
105
+ toolNames.push(...service.getTools().map((tool) => tool.name));
106
+ }
107
+ return toolNames;
108
+ }
109
+ /**
110
+ * 서비스 개수 반환
111
+ */
112
+ get size() {
113
+ return this.services.size;
114
+ }
115
+ /**
116
+ * 특정 서비스 조회
117
+ */
118
+ getService(id) {
119
+ return this.services.get(id);
120
+ }
121
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 공통 타입 정의
3
+ *
4
+ * 모든 서비스 프로바이더가 사용하는 공통 타입입니다.
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 다중 서비스 MCP 서버
3
+ *
4
+ * Cloudflare Workers에서 실행되는 플러그인 기반 MCP 서버입니다.
5
+ * 다이소, 편의점, 백화점 등 다양한 서비스를 확장할 수 있습니다.
6
+ */
7
+ import { Hono } from 'hono';
8
+ import { cors } from 'hono/cors';
9
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
11
+ import { ServiceRegistry } from './core/registry.js';
12
+ import { createDaisoService } from './services/daiso/index.js';
13
+ import { createOliveyoungService } from './services/oliveyoung/index.js';
14
+ import { createMegaboxService } from './services/megabox/index.js';
15
+ import { createCgvService } from './services/cgv/index.js';
16
+ import { createPromptResponse } from './pages/prompt.js';
17
+ import { createOpenApiJsonResponse, createOpenApiYamlResponse } from './pages/openapi.js';
18
+ import { createPrivacyResponse } from './pages/privacy.js';
19
+ import { registerDaisoRoutes } from './api/routes/daisoRoutes.js';
20
+ import { registerOliveyoungRoutes } from './api/routes/oliveyoungRoutes.js';
21
+ import { registerMegaboxRoutes } from './api/routes/megaboxRoutes.js';
22
+ import { registerCgvRoutes } from './api/routes/cgvRoutes.js';
23
+ // 서버 메타데이터
24
+ const SERVER_NAME = 'multi-service-mcp';
25
+ const SERVER_VERSION = '1.0.0';
26
+ /**
27
+ * 요청 컨텍스트 기반 서비스 레지스트리 생성
28
+ */
29
+ const createRegistry = (bindings) => {
30
+ const registry = new ServiceRegistry();
31
+ registry.registerAll([
32
+ createDaisoService,
33
+ createMegaboxService,
34
+ () => createCgvService({
35
+ zyteApiKey: bindings?.ZYTE_API_KEY,
36
+ }),
37
+ () => createOliveyoungService({
38
+ zyteApiKey: bindings?.ZYTE_API_KEY,
39
+ }),
40
+ ]);
41
+ return registry;
42
+ };
43
+ /**
44
+ * MCP 서버 생성 함수
45
+ */
46
+ const createMcpServer = (bindings) => {
47
+ const registry = createRegistry(bindings);
48
+ const server = new McpServer({
49
+ name: SERVER_NAME,
50
+ version: SERVER_VERSION,
51
+ });
52
+ registry.applyToServer(server);
53
+ return server;
54
+ };
55
+ // Hono 앱 생성
56
+ const app = new Hono();
57
+ // CORS 설정
58
+ app.use('*', cors({
59
+ origin: '*',
60
+ allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
61
+ allowHeaders: ['Content-Type', 'mcp-session-id', 'Last-Event-ID', 'mcp-protocol-version'],
62
+ exposeHeaders: ['mcp-session-id', 'mcp-protocol-version'],
63
+ }));
64
+ // 기본 정보 엔드포인트 (GET 요청만)
65
+ app.get('/', (c) => {
66
+ const registry = createRegistry(c.env);
67
+ const services = registry.getServicesInfo();
68
+ const allTools = registry.getAllToolNames();
69
+ return c.json({
70
+ name: SERVER_NAME,
71
+ version: SERVER_VERSION,
72
+ description: 'Multi-Service MCP Server for Cloudflare Workers',
73
+ endpoints: {
74
+ mcp: '/ 또는 /mcp (POST) - MCP 프로토콜 엔드포인트',
75
+ health: '/health (GET) - 헬스 체크',
76
+ },
77
+ services,
78
+ tools: allTools,
79
+ totalServices: services.length,
80
+ totalTools: allTools.length,
81
+ });
82
+ });
83
+ // 루트 경로에서 MCP 요청 처리 (POST, DELETE, OPTIONS)
84
+ app.on(['POST', 'DELETE', 'OPTIONS'], '/', async (c) => {
85
+ const transport = new WebStandardStreamableHTTPServerTransport();
86
+ const server = createMcpServer(c.env);
87
+ await server.connect(transport);
88
+ return transport.handleRequest(c.req.raw);
89
+ });
90
+ // 헬스 체크 엔드포인트
91
+ app.get('/health', (c) => c.json({ status: 'ok' }));
92
+ // 프롬프트 페이지 (MCP 미지원 에이전트용)
93
+ app.get('/prompt', (c) => {
94
+ const baseUrl = new URL(c.req.url).origin;
95
+ return createPromptResponse(baseUrl);
96
+ });
97
+ // OpenAPI 스펙 엔드포인트 (ChatGPT GPTs 등록용)
98
+ app.get('/openapi.json', (c) => {
99
+ const baseUrl = new URL(c.req.url).origin;
100
+ return createOpenApiJsonResponse(baseUrl);
101
+ });
102
+ app.get('/openapi.yaml', (c) => {
103
+ const baseUrl = new URL(c.req.url).origin;
104
+ return createOpenApiYamlResponse(baseUrl);
105
+ });
106
+ // 개인정보 처리방침 페이지
107
+ app.get('/privacy', (c) => {
108
+ const baseUrl = new URL(c.req.url).origin;
109
+ return createPrivacyResponse(baseUrl);
110
+ });
111
+ // GET API 엔드포인트 (MCP 미지원 에이전트용)
112
+ registerDaisoRoutes(app);
113
+ registerOliveyoungRoutes(app);
114
+ registerMegaboxRoutes(app);
115
+ registerCgvRoutes(app);
116
+ // MCP 엔드포인트
117
+ app.all('/mcp', async (c) => {
118
+ const transport = new WebStandardStreamableHTTPServerTransport();
119
+ const server = createMcpServer(c.env);
120
+ await server.connect(transport);
121
+ return transport.handleRequest(c.req.raw);
122
+ });
123
+ export default app;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * OpenAPI 페이지 엔트리
3
+ */
4
+ export { generateOpenApiSpec } from './openapiSpec.js';
5
+ export { createOpenApiJsonResponse, createOpenApiYamlResponse } from './openapiResponse.js';
6
+ export { __testOnlyJsonToYaml } from './openapiYaml.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * OpenAPI 응답 생성 모듈
3
+ */
4
+ import { generateOpenApiSpec } from './openapiSpec.js';
5
+ import { jsonToYaml } from './openapiYaml.js';
6
+ /**
7
+ * OpenAPI 스펙 응답 생성 (JSON)
8
+ */
9
+ export function createOpenApiJsonResponse(baseUrl) {
10
+ const spec = generateOpenApiSpec(baseUrl);
11
+ return new Response(JSON.stringify(spec, null, 2), {
12
+ headers: {
13
+ 'Content-Type': 'application/json; charset=utf-8',
14
+ 'Cache-Control': 'public, max-age=3600',
15
+ 'Access-Control-Allow-Origin': '*',
16
+ },
17
+ });
18
+ }
19
+ /**
20
+ * OpenAPI 스펙 응답 생성 (YAML)
21
+ */
22
+ export function createOpenApiYamlResponse(baseUrl) {
23
+ const spec = generateOpenApiSpec(baseUrl);
24
+ const yaml = jsonToYaml(spec);
25
+ return new Response(yaml, {
26
+ headers: {
27
+ 'Content-Type': 'text/yaml; charset=utf-8',
28
+ 'Cache-Control': 'public, max-age=3600',
29
+ 'Access-Control-Allow-Origin': '*',
30
+ },
31
+ });
32
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * OpenAPI 스펙 조합 모듈
3
+ */
4
+ import { OPENAPI_PATHS_DAISO_OLIVEYOUNG } from './openapiSpecPathsDaisoOliveyoung.js';
5
+ import { OPENAPI_PATHS_MEGABOX } from './openapiSpecPathsMegabox.js';
6
+ import { OPENAPI_PATHS_CGV } from './openapiSpecPathsCgv.js';
7
+ import { OPENAPI_COMPONENTS } from './openapiSpecComponents.js';
8
+ /**
9
+ * OpenAPI 스펙 생성
10
+ */
11
+ export function generateOpenApiSpec(baseUrl) {
12
+ return {
13
+ openapi: '3.1.0',
14
+ info: {
15
+ title: '다이소 MCP API',
16
+ description: `다이소 제품 검색, 매장 찾기, 재고 확인을 위한 API입니다.
17
+
18
+ ## 주요 기능
19
+ - 🔍 **제품 검색**: 키워드로 다이소 제품 검색
20
+ - 🏪 **매장 찾기**: 지역/키워드로 매장 검색
21
+ - 📦 **재고 확인**: 온라인 및 오프라인 매장 재고 조회
22
+ - 🎬 **메가박스 지점/영화 조회**: 주변 지점, 상영 목록, 잔여 좌석 조회
23
+ - 🎥 **CGV 지점/영화 조회**: 극장 목록, 영화 목록, 시간표 조회
24
+
25
+ ## 사용 팁
26
+ 1. 한글 검색어는 URL 인코딩이 자동 처리됩니다
27
+ 2. 재고 확인 시 먼저 제품 검색으로 제품 ID를 확인하세요
28
+ 3. 위치 기반 재고 조회 시 lat, lng 파라미터를 활용하세요`,
29
+ version: '1.0.0',
30
+ contact: {
31
+ name: 'GitHub Repository',
32
+ url: 'https://github.com/hmmhmmhm/daiso-mcp',
33
+ },
34
+ },
35
+ servers: [{ url: baseUrl, description: 'Production Server' }],
36
+ paths: {
37
+ ...OPENAPI_PATHS_DAISO_OLIVEYOUNG,
38
+ ...OPENAPI_PATHS_MEGABOX,
39
+ ...OPENAPI_PATHS_CGV,
40
+ },
41
+ components: OPENAPI_COMPONENTS,
42
+ };
43
+ }