tachibanaa710 commited on
Commit
646f386
·
verified ·
1 Parent(s): a746907

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +304 -38
server.js CHANGED
@@ -1,58 +1,324 @@
1
- 'use strict';
2
-
3
  import express from 'express';
4
- import fetch from 'node-fetch';
5
- import sharp from 'sharp';
6
- import Lens from 'chrome-lens-ocr';
7
-
8
- // Constants
9
- const PORT = 7860;
10
- const HOST = '0.0.0.0';
11
 
12
- // App
13
  const app = express();
14
- const lens = new Lens();
15
 
16
- app.get('/', (req, res) => {
17
- res.send('Hello World from ExpressJS! This example is from the NodeJS Docs: https://nodejs.org/en/docs/guides/nodejs-docker-webapp/');
18
- });
19
 
20
- app.get('/scanByUrl', async (req, res) => {
21
- const { url } = req.query;
22
 
23
- if (!url) {
24
- return res.status(400).json({ error: 'Image URL is required' });
 
 
 
 
 
 
 
 
 
25
  }
 
 
 
26
 
 
27
  try {
28
- // Fetch the image
29
- const response = await fetch(url);
30
- if (!response.ok) throw new Error('Failed to fetch image');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- const buffer = await response.arrayBuffer();
33
- const imageBuffer = Buffer.from(buffer);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- // Get image metadata
36
- const metadata = await sharp(imageBuffer).metadata();
 
37
 
38
- // Resize if necessary
39
- let processedImage = imageBuffer;
40
- if (metadata.width > 1000 || metadata.height > 1000) {
41
- processedImage = await sharp(imageBuffer)
42
- .resize(1000, 1000, { fit: 'inside' })
43
- .toBuffer();
44
  }
45
 
46
- // Scan with Chrome Lens OCR
47
- const data = await lens.scanByBuffer(processedImage);
48
- const combinedText = data.segments.map(segment => segment.text).join('\n\n');
49
 
50
- res.json({ combinedText, detailedData: data });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  } catch (error) {
52
- res.status(500).json({ error: error.message });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
  });
55
 
56
- app.listen(PORT, HOST, () => {
57
- console.log(`Running on http://${HOST}:${PORT}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import express from 'express';
2
+ import axios from 'axios';
3
+ import cheerio from 'cheerio';
4
+ import cors from 'cors';
 
 
 
 
5
 
 
6
  const app = express();
7
+ const PORT = process.env.PORT || 3000;
8
 
 
 
 
9
 
10
+ app.use(cors());
11
+ app.use(express.json());
12
 
13
+
14
+ function extractPixivId(url) {
15
+ const patterns = [
16
+ /pixiv\.net\/(?:en\/)?artworks\/(\d+)/,
17
+ /pixiv\.net\/member_illust\.php\?.*illust_id=(\d+)/,
18
+ /pixiv\.net\/(?:en\/)?users\/\d+\/artworks\/(\d+)/
19
+ ];
20
+
21
+ for (let pattern of patterns) {
22
+ const match = url.match(pattern);
23
+ if (match) return match[1];
24
  }
25
+ return null;
26
+ }
27
+
28
 
29
+ async function getPixivMetadata(artworkId, req) {
30
  try {
31
+ const apiUrl = `https://www.pixiv.net/ajax/illust/${artworkId}`;
32
+ const pageUrl = `https://www.pixiv.net/en/artworks/${artworkId}`;
33
+
34
+ // Set headers to mimic a browser request
35
+ const headers = {
36
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
37
+ 'Accept': 'application/json',
38
+ 'Accept-Language': 'en-US,en;q=0.9',
39
+ 'Referer': 'https://www.pixiv.net/'
40
+ };
41
+
42
+
43
+ try {
44
+ const response = await axios.get(apiUrl, { headers });
45
+ if (response.data && response.data.body) {
46
+ const data = response.data.body;
47
+ const baseUrl = req.protocol + '://' + req.get('host');
48
+ return {
49
+ id: data.id,
50
+ title: data.title,
51
+ description: data.description,
52
+ artist: {
53
+ id: data.userId,
54
+ name: data.userName,
55
+ account: data.userAccount
56
+ },
57
+ tags: data.tags ? data.tags.tags.map(tag => ({
58
+ tag: tag.tag,
59
+ translation: tag.translation ? tag.translation.en : null
60
+ })) : [],
61
+ images: {
62
+ thumbnail: data.urls ? data.urls.thumb : null,
63
+ small: data.urls ? data.urls.small : null,
64
+ regular: data.urls ? data.urls.regular : null,
65
+ original: data.urls ? data.urls.original : null
66
+ },
67
+ proxiedImages: {
68
+ thumbnail: data.urls?.thumb ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(data.urls.thumb)}` : null,
69
+ small: data.urls?.small ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(data.urls.small)}` : null,
70
+ regular: data.urls?.regular ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(data.urls.regular)}` : null,
71
+ original: data.urls?.original ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(data.urls.original)}` : null
72
+ },
73
+ stats: {
74
+ views: data.viewCount,
75
+ bookmarks: data.bookmarkCount,
76
+ likes: data.likeCount,
77
+ comments: data.commentCount
78
+ },
79
+ pageCount: data.pageCount,
80
+ width: data.width,
81
+ height: data.height,
82
+ createDate: data.createDate,
83
+ uploadDate: data.uploadDate,
84
+ type: data.illustType === 0 ? 'illustration' : data.illustType === 1 ? 'manga' : 'ugoira',
85
+ isR18: data.xRestrict === 1,
86
+ isAI: data.aiType === 2
87
+ };
88
+ }
89
+ } catch (apiError) {
90
+ console.log('API endpoint failed, trying page scraping...');
91
+ }
92
+
93
+
94
+ const pageResponse = await axios.get(pageUrl, { headers });
95
+ const $ = cheerio.load(pageResponse.data);
96
+
97
 
98
+ let metadata = null;
99
+ $('script').each((i, elem) => {
100
+ const text = $(elem).html();
101
+ if (text && text.includes('meta-preload-data')) {
102
+ try {
103
+ const match = text.match(/{"timestamp".*?}(?=<\/script>)/);
104
+ if (match) {
105
+ const data = JSON.parse(match[0]);
106
+ if (data.illust && data.illust[artworkId]) {
107
+ const illust = data.illust[artworkId];
108
+ const baseUrl = req.protocol + '://' + req.get('host');
109
+ metadata = {
110
+ id: illust.id,
111
+ title: illust.title,
112
+ description: illust.description,
113
+ artist: {
114
+ id: illust.userId,
115
+ name: illust.userName,
116
+ account: illust.userAccount || null
117
+ },
118
+ tags: illust.tags ? illust.tags.tags.map(tag => ({
119
+ tag: tag.tag,
120
+ translation: tag.translation ? tag.translation.en : null
121
+ })) : [],
122
+ images: {
123
+ thumbnail: illust.urls ? illust.urls.thumb : null,
124
+ small: illust.urls ? illust.urls.small : null,
125
+ regular: illust.urls ? illust.urls.regular : null,
126
+ original: illust.urls ? illust.urls.original : null
127
+ },
128
+ proxiedImages: {
129
+ thumbnail: illust.urls?.thumb ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(illust.urls.thumb)}` : null,
130
+ small: illust.urls?.small ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(illust.urls.small)}` : null,
131
+ regular: illust.urls?.regular ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(illust.urls.regular)}` : null,
132
+ original: illust.urls?.original ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(illust.urls.original)}` : null
133
+ },
134
+ stats: {
135
+ views: illust.viewCount || 0,
136
+ bookmarks: illust.bookmarkCount || 0,
137
+ likes: illust.likeCount || 0,
138
+ comments: illust.commentCount || 0
139
+ },
140
+ pageCount: illust.pageCount || 1,
141
+ width: illust.width,
142
+ height: illust.height,
143
+ createDate: illust.createDate,
144
+ uploadDate: illust.uploadDate,
145
+ type: illust.illustType === 0 ? 'illustration' : illust.illustType === 1 ? 'manga' : 'ugoira',
146
+ isR18: illust.xRestrict === 1,
147
+ isAI: illust.aiType === 2
148
+ };
149
+ }
150
+ }
151
+ } catch (e) {
152
 
153
+ }
154
+ }
155
+ });
156
 
157
+ if (metadata) {
158
+ return metadata;
 
 
 
 
159
  }
160
 
 
 
 
161
 
162
+ const baseUrl = req.protocol + '://' + req.get('host');
163
+ const ogImage = $('meta[property="og:image"]').attr('content');
164
+ return {
165
+ id: artworkId,
166
+ title: $('meta[property="og:title"]').attr('content') || 'Unknown',
167
+ description: $('meta[property="og:description"]').attr('content') || '',
168
+ artist: {
169
+ name: $('meta[name="twitter:creator"]').attr('content') || 'Unknown'
170
+ },
171
+ images: {
172
+ thumbnail: ogImage || null
173
+ },
174
+ proxiedImages: {
175
+ thumbnail: ogImage ? `${baseUrl}/api/image-proxy?url=${encodeURIComponent(ogImage)}` : null
176
+ },
177
+ tags: [],
178
+ stats: {},
179
+ pageCount: 1,
180
+ type: 'illustration'
181
+ };
182
+
183
  } catch (error) {
184
+ throw new Error(`Failed to fetch metadata: ${error.message}`);
185
+ }
186
+ }
187
+
188
+
189
+ app.get('/api/image-proxy', async (req, res) => {
190
+ try {
191
+ const { url } = req.query;
192
+
193
+ if (!url) {
194
+ return res.status(400).json({
195
+ error: 'Please provide an image URL',
196
+ example: '/api/image-proxy?url=https://i.pximg.net/...'
197
+ });
198
+ }
199
+
200
+
201
+ if (!url.includes('pximg.net')) {
202
+ return res.status(400).json({
203
+ error: 'Only Pixiv image URLs are supported'
204
+ });
205
+ }
206
+
207
+ const headers = {
208
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
209
+ 'Referer': 'https://www.pixiv.net/',
210
+ 'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
211
+ 'Accept-Language': 'en-US,en;q=0.9'
212
+ };
213
+
214
+ const response = await axios.get(url, {
215
+ headers,
216
+ responseType: 'stream',
217
+ timeout: 30000
218
+ });
219
+
220
+
221
+ const contentType = response.headers['content-type'] || 'image/jpeg';
222
+ res.setHeader('Content-Type', contentType);
223
+ res.setHeader('Cache-Control', 'public, max-age=3600'); // Cache for 1 hour
224
+
225
+
226
+ response.data.pipe(res);
227
+
228
+ } catch (error) {
229
+ if (error.response && error.response.status === 403) {
230
+ res.status(403).json({
231
+ error: 'Access forbidden - image may be restricted or URL invalid'
232
+ });
233
+ } else {
234
+ res.status(500).json({
235
+ error: `Failed to fetch image: ${error.message}`
236
+ });
237
+ }
238
  }
239
  });
240
 
241
+
242
+ app.get('/api/pixiv', async (req, res) => {
243
+ try {
244
+ const { url, id } = req.query;
245
+
246
+ if (!url && !id) {
247
+ return res.status(400).json({
248
+ error: 'Please provide either a Pixiv URL or artwork ID',
249
+ example: '/api/pixiv?url=https://www.pixiv.net/en/artworks/123456789'
250
+ });
251
+ }
252
+
253
+ let artworkId = id;
254
+ if (url) {
255
+ artworkId = extractPixivId(url);
256
+ if (!artworkId) {
257
+ return res.status(400).json({
258
+ error: 'Invalid Pixiv URL format',
259
+ supportedFormats: [
260
+ 'https://www.pixiv.net/en/artworks/{id}',
261
+ 'https://www.pixiv.net/artworks/{id}',
262
+ 'https://www.pixiv.net/member_illust.php?illust_id={id}'
263
+ ]
264
+ });
265
+ }
266
+ }
267
+
268
+ const metadata = await getPixivMetadata(artworkId, req);
269
+
270
+ res.json({
271
+ success: true,
272
+ data: metadata
273
+ });
274
+
275
+ } catch (error) {
276
+ res.status(500).json({
277
+ error: error.message,
278
+ success: false
279
+ });
280
+ }
281
  });
282
+
283
+
284
+ app.get('/health', (req, res) => {
285
+ res.json({ status: 'OK', timestamp: new Date().toISOString() });
286
+ });
287
+
288
+
289
+ app.get('/', (req, res) => {
290
+ res.json({
291
+ name: 'Pixiv Metadata API',
292
+ version: '1.0.0',
293
+ endpoints: {
294
+ '/api/pixiv': {
295
+ method: 'GET',
296
+ description: 'Get metadata for a Pixiv artwork',
297
+ parameters: {
298
+ url: 'Pixiv artwork URL (optional if id is provided)',
299
+ id: 'Pixiv artwork ID (optional if url is provided)'
300
+ },
301
+ example: '/api/pixiv?url=https://www.pixiv.net/en/artworks/123456789'
302
+ },
303
+ '/api/image-proxy': {
304
+ method: 'GET',
305
+ description: 'Proxy Pixiv images to bypass hotlink protection',
306
+ parameters: {
307
+ url: 'Pixiv image URL (pximg.net)'
308
+ },
309
+ example: '/api/image-proxy?url=https://i.pximg.net/...'
310
+ },
311
+ '/health': {
312
+ method: 'GET',
313
+ description: 'Health check endpoint'
314
+ }
315
+ }
316
+ });
317
+ });
318
+
319
+ app.listen(PORT, () => {
320
+ console.log(`Pixiv Metadata API running on port ${PORT}`);
321
+ console.log(`Access the API at: http://localhost:${PORT}/api/pixiv`);
322
+ });
323
+
324
+ module.exports = app;