daydreamer-json commited on
Commit
954ec7f
·
verified ·
1 Parent(s): fcebd54

Add /api/meta_web endpoint

Browse files
Files changed (4) hide show
  1. bun.lock +47 -0
  2. package.json +1 -0
  3. src/main.ts +2 -0
  4. src/routes/metaWeb.ts +80 -0
bun.lock CHANGED
@@ -4,6 +4,7 @@
4
  "": {
5
  "name": "line-stamp-api-proxy",
6
  "dependencies": {
 
7
  "hono": "^4.9.6",
8
  "ky": "^1.9.1",
9
  "log4js": "^6.9.1",
@@ -24,14 +25,36 @@
24
 
25
  "@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
26
 
 
 
27
  "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
28
 
 
 
 
 
 
 
 
 
29
  "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
30
 
31
  "date-format": ["[email protected]", "", {}, "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg=="],
32
 
33
  "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
34
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  "flatted": ["[email protected]", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
36
 
37
  "fs-extra": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
@@ -40,6 +63,10 @@
40
 
41
  "hono": ["[email protected]", "", {}, "sha512-doVjXhSFvYZ7y0dNokjwwSahcrAfdz+/BCLvAMa/vHLzjj8+CFyV5xteThGUsKdkaasgN+gF2mUxao+SGLpUeA=="],
42
 
 
 
 
 
43
  "jsonfile": ["[email protected]", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
44
 
45
  "ky": ["[email protected]", "", {}, "sha512-WGzpBn57klhxsqRTEABAqF4tqTtqCuxoTIv9m6nIZtMMFTVcrHp7bRDWblzFIfqkb47+OhTztOgHn6A4xItmqg=="],
@@ -48,16 +75,36 @@
48
 
49
  "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
50
 
 
 
 
 
 
 
 
 
51
  "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
52
 
 
 
53
  "streamroller": ["[email protected]", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "fs-extra": "^8.1.0" } }, "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw=="],
54
 
55
  "typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
56
 
 
 
57
  "undici-types": ["[email protected]", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
58
 
59
  "universalify": ["[email protected]", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
60
 
 
 
 
 
61
  "yaml": ["[email protected]", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
 
 
 
 
62
  }
63
  }
 
4
  "": {
5
  "name": "line-stamp-api-proxy",
6
  "dependencies": {
7
+ "cheerio": "^1.1.2",
8
  "hono": "^4.9.6",
9
  "ky": "^1.9.1",
10
  "log4js": "^6.9.1",
 
25
 
26
  "@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
27
 
28
+ "boolbase": ["[email protected]", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
29
+
30
  "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
31
 
32
+ "cheerio": ["[email protected]", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="],
33
+
34
+ "cheerio-select": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
35
+
36
+ "css-select": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
37
+
38
+ "css-what": ["[email protected]", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
39
+
40
  "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
41
 
42
  "date-format": ["[email protected]", "", {}, "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg=="],
43
 
44
  "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
45
 
46
+ "dom-serializer": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
47
+
48
+ "domelementtype": ["[email protected]", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
49
+
50
+ "domhandler": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
51
+
52
+ "domutils": ["[email protected]", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
53
+
54
+ "encoding-sniffer": ["[email protected]", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
55
+
56
+ "entities": ["[email protected]", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
57
+
58
  "flatted": ["[email protected]", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
59
 
60
  "fs-extra": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
 
63
 
64
  "hono": ["[email protected]", "", {}, "sha512-doVjXhSFvYZ7y0dNokjwwSahcrAfdz+/BCLvAMa/vHLzjj8+CFyV5xteThGUsKdkaasgN+gF2mUxao+SGLpUeA=="],
65
 
66
+ "htmlparser2": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
67
+
68
+ "iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
69
+
70
  "jsonfile": ["[email protected]", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
71
 
72
  "ky": ["[email protected]", "", {}, "sha512-WGzpBn57klhxsqRTEABAqF4tqTtqCuxoTIv9m6nIZtMMFTVcrHp7bRDWblzFIfqkb47+OhTztOgHn6A4xItmqg=="],
 
75
 
76
  "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
77
 
78
+ "nth-check": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
79
+
80
+ "parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
81
+
82
+ "parse5-htmlparser2-tree-adapter": ["[email protected]", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
83
+
84
+ "parse5-parser-stream": ["[email protected]", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
85
+
86
  "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
87
 
88
+ "safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
89
+
90
  "streamroller": ["[email protected]", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "fs-extra": "^8.1.0" } }, "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw=="],
91
 
92
  "typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
93
 
94
+ "undici": ["[email protected]", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="],
95
+
96
  "undici-types": ["[email protected]", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
97
 
98
  "universalify": ["[email protected]", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
99
 
100
+ "whatwg-encoding": ["[email protected]", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
101
+
102
+ "whatwg-mimetype": ["[email protected]", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
103
+
104
  "yaml": ["[email protected]", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
105
+
106
+ "htmlparser2/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
107
+
108
+ "parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
109
  }
110
  }
package.json CHANGED
@@ -15,6 +15,7 @@
15
  "typescript": "^5"
16
  },
17
  "dependencies": {
 
18
  "hono": "^4.9.6",
19
  "ky": "^1.9.1",
20
  "log4js": "^6.9.1",
 
15
  "typescript": "^5"
16
  },
17
  "dependencies": {
18
+ "cheerio": "^1.1.2",
19
  "hono": "^4.9.6",
20
  "ky": "^1.9.1",
21
  "log4js": "^6.9.1",
src/main.ts CHANGED
@@ -78,12 +78,14 @@ const apiRoutes = app.basePath('/api');
78
  // ルートを登録
79
  import searchRoutes from './routes/search';
80
  import metaRoutes from './routes/meta';
 
81
  import stickerRoutes from './routes/download/sticker';
82
  import emojiRoutes from './routes/download/emoji';
83
  import docsRoutes from './routes/docs';
84
 
85
  apiRoutes.route('/search', searchRoutes);
86
  apiRoutes.route('/meta', metaRoutes);
 
87
  apiRoutes.route('/download/sticker', stickerRoutes);
88
  apiRoutes.route('/download/emoji', emojiRoutes);
89
  app.route('/docs', docsRoutes);
 
78
  // ルートを登録
79
  import searchRoutes from './routes/search';
80
  import metaRoutes from './routes/meta';
81
+ import metaWebRoutes from './routes/metaWeb';
82
  import stickerRoutes from './routes/download/sticker';
83
  import emojiRoutes from './routes/download/emoji';
84
  import docsRoutes from './routes/docs';
85
 
86
  apiRoutes.route('/search', searchRoutes);
87
  apiRoutes.route('/meta', metaRoutes);
88
+ apiRoutes.route('/meta_web', metaWebRoutes);
89
  apiRoutes.route('/download/sticker', stickerRoutes);
90
  apiRoutes.route('/download/emoji', emojiRoutes);
91
  app.route('/docs', docsRoutes);
src/routes/metaWeb.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Hono } from 'hono';
2
+ import ky from 'ky';
3
+ import * as cheerio from 'cheerio';
4
+ import { logger } from '../utils/logger';
5
+ import { loadConfig } from '../utils/config';
6
+
7
+ const config = loadConfig();
8
+ const requestTimeout = config.request_timeout || 30000;
9
+
10
+ const app = new Hono();
11
+
12
+ app.get('/sticker/:productId', async (c) => {
13
+ const productId: number = parseInt(c.req.param('productId') || '-1');
14
+ if (productId === -1) return c.text('Invalid productId', 400);
15
+ const preferredLanguage = c.req.header('Accept-Language') || 'ja';
16
+
17
+ try {
18
+ const response = await ky(`https://store.line.me/stickershop/product/${productId}/${preferredLanguage}`, {
19
+ headers: { 'User-Agent': config.userAgent.chromeWindows },
20
+ timeout: requestTimeout,
21
+ });
22
+ const html = await response.text();
23
+ const $ = cheerio.load(html);
24
+ const jsonLdScript = $('head script[type="application/ld+json"]').first().html();
25
+
26
+ if (jsonLdScript) {
27
+ try {
28
+ const data = JSON.parse(jsonLdScript);
29
+ return c.json(data, 200);
30
+ } catch (parseError) {
31
+ logger.error('JSON-LD parse error', parseError);
32
+ return c.text('Invalid JSON-LD', 500);
33
+ }
34
+ } else {
35
+ return c.text('No JSON-LD found', 404);
36
+ }
37
+ } catch (error) {
38
+ if ((error as any).response?.status === 404) {
39
+ return c.text('Not Found', 404);
40
+ }
41
+ logger.error('Sticker meta_web download error', error);
42
+ return c.text('Internal Server Error', 500);
43
+ }
44
+ });
45
+
46
+ app.get('/emoji/:productId', async (c) => {
47
+ const productId: string = c.req.param('productId') || 'null';
48
+ if (!/^[0-9a-f]+$/.test(productId)) return c.text('Invalid productId', 400);
49
+ const preferredLanguage = c.req.header('Accept-Language') || 'ja';
50
+
51
+ try {
52
+ const response = await ky(`https://store.line.me/emojishop/product/${productId}/${preferredLanguage}`, {
53
+ headers: { 'User-Agent': config.userAgent.lineIos },
54
+ timeout: requestTimeout,
55
+ });
56
+ const html = await response.text();
57
+ const $ = cheerio.load(html);
58
+ const jsonLdScript = $('head script[type="application/ld+json"]').first().html();
59
+
60
+ if (jsonLdScript) {
61
+ try {
62
+ const data = JSON.parse(jsonLdScript);
63
+ return c.json(data, 200);
64
+ } catch (parseError) {
65
+ logger.error('JSON-LD parse error', parseError);
66
+ return c.text('Invalid JSON-LD', 500);
67
+ }
68
+ } else {
69
+ return c.text('No JSON-LD found', 404);
70
+ }
71
+ } catch (error) {
72
+ if ((error as any).response?.status === 404) {
73
+ return c.text('Not Found', 404);
74
+ }
75
+ logger.error('Emoji meta_web download error', error);
76
+ return c.text('Internal Server Error', 500);
77
+ }
78
+ });
79
+
80
+ export default app;