chore: translate Japanese comments to English for better code readability
Browse files- src/main.ts +6 -6
- src/routes/download/emoji.ts +5 -5
- src/routes/download/sticker.ts +9 -9
- src/routes/meta.ts +2 -0
- src/routes/metaWeb.ts +2 -0
- src/routes/search.ts +6 -6
src/main.ts
CHANGED
|
@@ -8,7 +8,7 @@ const requestTimeout = config.request_timeout || 30000;
|
|
| 8 |
|
| 9 |
const app = new Hono();
|
| 10 |
|
| 11 |
-
// CORS
|
| 12 |
app.use(
|
| 13 |
'*',
|
| 14 |
cors({
|
|
@@ -20,7 +20,7 @@ app.use(
|
|
| 20 |
}),
|
| 21 |
);
|
| 22 |
|
| 23 |
-
//
|
| 24 |
app.use('*', async (c, next) => {
|
| 25 |
const start = Date.now();
|
| 26 |
await next();
|
|
@@ -43,7 +43,7 @@ app.use('*', async (c, next) => {
|
|
| 43 |
c.header('Server', 'THIS SERVER IS POWERED BY PENGUINS');
|
| 44 |
});
|
| 45 |
|
| 46 |
-
//
|
| 47 |
app.get('/', (c) => {
|
| 48 |
const baseDomain = new URL(c.req.url).host;
|
| 49 |
return c.text(
|
|
@@ -72,10 +72,10 @@ More info: /docs`,
|
|
| 72 |
);
|
| 73 |
});
|
| 74 |
|
| 75 |
-
// API
|
| 76 |
const apiRoutes = app.basePath('/api');
|
| 77 |
|
| 78 |
-
//
|
| 79 |
import searchRoutes from './routes/search';
|
| 80 |
import metaRoutes from './routes/meta';
|
| 81 |
import metaWebRoutes from './routes/metaWeb';
|
|
@@ -90,7 +90,7 @@ apiRoutes.route('/download/sticker', stickerRoutes);
|
|
| 90 |
apiRoutes.route('/download/emoji', emojiRoutes);
|
| 91 |
app.route('/docs', docsRoutes);
|
| 92 |
|
| 93 |
-
//
|
| 94 |
const port = process.env.PORT || 3000;
|
| 95 |
logger.info(`Server running on port ${port}`);
|
| 96 |
Bun.serve({
|
|
|
|
| 8 |
|
| 9 |
const app = new Hono();
|
| 10 |
|
| 11 |
+
// CORS middleware: Disable all restrictions
|
| 12 |
app.use(
|
| 13 |
'*',
|
| 14 |
cors({
|
|
|
|
| 20 |
}),
|
| 21 |
);
|
| 22 |
|
| 23 |
+
// Request log middleware
|
| 24 |
app.use('*', async (c, next) => {
|
| 25 |
const start = Date.now();
|
| 26 |
await next();
|
|
|
|
| 43 |
c.header('Server', 'THIS SERVER IS POWERED BY PENGUINS');
|
| 44 |
});
|
| 45 |
|
| 46 |
+
// Base route
|
| 47 |
app.get('/', (c) => {
|
| 48 |
const baseDomain = new URL(c.req.url).host;
|
| 49 |
return c.text(
|
|
|
|
| 72 |
);
|
| 73 |
});
|
| 74 |
|
| 75 |
+
// API route group
|
| 76 |
const apiRoutes = app.basePath('/api');
|
| 77 |
|
| 78 |
+
// Register routes
|
| 79 |
import searchRoutes from './routes/search';
|
| 80 |
import metaRoutes from './routes/meta';
|
| 81 |
import metaWebRoutes from './routes/metaWeb';
|
|
|
|
| 90 |
apiRoutes.route('/download/emoji', emojiRoutes);
|
| 91 |
app.route('/docs', docsRoutes);
|
| 92 |
|
| 93 |
+
// Start server
|
| 94 |
const port = process.env.PORT || 3000;
|
| 95 |
logger.info(`Server running on port ${port}`);
|
| 96 |
Bun.serve({
|
src/routes/download/emoji.ts
CHANGED
|
@@ -10,7 +10,7 @@ const requestTimeout = config.request_timeout || 30000;
|
|
| 10 |
|
| 11 |
const app = new Hono();
|
| 12 |
|
| 13 |
-
// ZIP
|
| 14 |
app.get('/zip/:productId', async (c) => {
|
| 15 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 16 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -64,7 +64,7 @@ app.get('/zip/:productId', async (c) => {
|
|
| 64 |
}
|
| 65 |
});
|
| 66 |
|
| 67 |
-
//
|
| 68 |
app.get('/single/:productId/:iconIndex', async (c) => {
|
| 69 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 70 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -98,7 +98,7 @@ app.get('/single/:productId/:iconIndex', async (c) => {
|
|
| 98 |
},
|
| 99 |
);
|
| 100 |
|
| 101 |
-
// GIF
|
| 102 |
if (gifFlag && !isStaticFlag) {
|
| 103 |
const buffer = await response.arrayBuffer();
|
| 104 |
const prefix = Math.random().toString(36).substring(2);
|
|
@@ -120,7 +120,7 @@ app.get('/single/:productId/:iconIndex', async (c) => {
|
|
| 120 |
|
| 121 |
const gifBuffer = fs.readFileSync(outputPath);
|
| 122 |
|
| 123 |
-
//
|
| 124 |
fs.unlink(inputPath, () => {});
|
| 125 |
fs.unlink(outputPath, () => {});
|
| 126 |
|
|
@@ -156,7 +156,7 @@ app.get('/single/:productId/:iconIndex', async (c) => {
|
|
| 156 |
}
|
| 157 |
});
|
| 158 |
|
| 159 |
-
//
|
| 160 |
app.get('/thumb/:productId', async (c) => {
|
| 161 |
const productId: string = c.req.param('productId') || 'null';
|
| 162 |
if (!/^[0-9a-f]+$/.test(productId)) return c.text('Invalid productId', 400);
|
|
|
|
| 10 |
|
| 11 |
const app = new Hono();
|
| 12 |
|
| 13 |
+
// ZIP download endpoint
|
| 14 |
app.get('/zip/:productId', async (c) => {
|
| 15 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 16 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 64 |
}
|
| 65 |
});
|
| 66 |
|
| 67 |
+
// Individual emoji download endpoint
|
| 68 |
app.get('/single/:productId/:iconIndex', async (c) => {
|
| 69 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 70 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 98 |
},
|
| 99 |
);
|
| 100 |
|
| 101 |
+
// If GIF conversion is needed (only for animations)
|
| 102 |
if (gifFlag && !isStaticFlag) {
|
| 103 |
const buffer = await response.arrayBuffer();
|
| 104 |
const prefix = Math.random().toString(36).substring(2);
|
|
|
|
| 120 |
|
| 121 |
const gifBuffer = fs.readFileSync(outputPath);
|
| 122 |
|
| 123 |
+
// Clean up temporary files
|
| 124 |
fs.unlink(inputPath, () => {});
|
| 125 |
fs.unlink(outputPath, () => {});
|
| 126 |
|
|
|
|
| 156 |
}
|
| 157 |
});
|
| 158 |
|
| 159 |
+
// Emoji thumbnail download endpoint
|
| 160 |
app.get('/thumb/:productId', async (c) => {
|
| 161 |
const productId: string = c.req.param('productId') || 'null';
|
| 162 |
if (!/^[0-9a-f]+$/.test(productId)) return c.text('Invalid productId', 400);
|
src/routes/download/sticker.ts
CHANGED
|
@@ -10,7 +10,7 @@ const requestTimeout = config.request_timeout || 30000;
|
|
| 10 |
|
| 11 |
const app = new Hono();
|
| 12 |
|
| 13 |
-
// ZIP
|
| 14 |
app.get('/zip/:productId', async (c) => {
|
| 15 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 16 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -62,7 +62,7 @@ app.get('/zip/:productId', async (c) => {
|
|
| 62 |
}
|
| 63 |
});
|
| 64 |
|
| 65 |
-
//
|
| 66 |
app.get('/single/:stickerId', async (c) => {
|
| 67 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 68 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -89,7 +89,7 @@ app.get('/single/:stickerId', async (c) => {
|
|
| 89 |
},
|
| 90 |
);
|
| 91 |
|
| 92 |
-
// GIF
|
| 93 |
if (gifFlag && !isStaticFlag) {
|
| 94 |
const buffer = await response.arrayBuffer();
|
| 95 |
const prefix = Math.random().toString(36).substring(2);
|
|
@@ -111,7 +111,7 @@ app.get('/single/:stickerId', async (c) => {
|
|
| 111 |
|
| 112 |
const gifBuffer = fs.readFileSync(outputPath);
|
| 113 |
|
| 114 |
-
//
|
| 115 |
fs.unlink(inputPath, () => {});
|
| 116 |
fs.unlink(outputPath, () => {});
|
| 117 |
|
|
@@ -125,7 +125,7 @@ app.get('/single/:stickerId', async (c) => {
|
|
| 125 |
});
|
| 126 |
}
|
| 127 |
|
| 128 |
-
//
|
| 129 |
const allowedHeaders = ['Content-Type', 'ETag', 'Cache-Control', 'Last-Modified'];
|
| 130 |
const filteredHeaders = new Headers();
|
| 131 |
allowedHeaders.forEach((header) => {
|
|
@@ -152,7 +152,7 @@ app.get('/single/:stickerId', async (c) => {
|
|
| 152 |
}
|
| 153 |
});
|
| 154 |
|
| 155 |
-
//
|
| 156 |
app.get('/thumb/:productId', async (c) => {
|
| 157 |
const productId: number = parseInt(c.req.param('productId') || '-1');
|
| 158 |
if (productId === -1) return c.text('Invalid productId', 400);
|
|
@@ -189,7 +189,7 @@ app.get('/thumb/:productId', async (c) => {
|
|
| 189 |
}
|
| 190 |
});
|
| 191 |
|
| 192 |
-
//
|
| 193 |
app.get('/sound/single/:stickerId', async (c) => {
|
| 194 |
const validDeviceTypes = ['ios', 'android', 'pc'] as const;
|
| 195 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -240,7 +240,7 @@ app.get('/sound/single/:stickerId', async (c) => {
|
|
| 240 |
}
|
| 241 |
});
|
| 242 |
|
| 243 |
-
//
|
| 244 |
app.get('/sound/thumb/:productId', async (c) => {
|
| 245 |
const validDeviceTypes = ['ios', 'android', 'pc'] as const;
|
| 246 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -288,7 +288,7 @@ app.get('/sound/thumb/:productId', async (c) => {
|
|
| 288 |
}
|
| 289 |
});
|
| 290 |
|
| 291 |
-
// SOUND+(ANIMATION or STATIC)
|
| 292 |
app.get('/mp4/single/:stickerId', async (c) => {
|
| 293 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 294 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 10 |
|
| 11 |
const app = new Hono();
|
| 12 |
|
| 13 |
+
// ZIP download endpoint
|
| 14 |
app.get('/zip/:productId', async (c) => {
|
| 15 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 16 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 62 |
}
|
| 63 |
});
|
| 64 |
|
| 65 |
+
// Individual sticker download endpoint
|
| 66 |
app.get('/single/:stickerId', async (c) => {
|
| 67 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 68 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 89 |
},
|
| 90 |
);
|
| 91 |
|
| 92 |
+
// If GIF conversion is needed (only for animations)
|
| 93 |
if (gifFlag && !isStaticFlag) {
|
| 94 |
const buffer = await response.arrayBuffer();
|
| 95 |
const prefix = Math.random().toString(36).substring(2);
|
|
|
|
| 111 |
|
| 112 |
const gifBuffer = fs.readFileSync(outputPath);
|
| 113 |
|
| 114 |
+
// Clean up temporary files
|
| 115 |
fs.unlink(inputPath, () => {});
|
| 116 |
fs.unlink(outputPath, () => {});
|
| 117 |
|
|
|
|
| 125 |
});
|
| 126 |
}
|
| 127 |
|
| 128 |
+
// Normal case
|
| 129 |
const allowedHeaders = ['Content-Type', 'ETag', 'Cache-Control', 'Last-Modified'];
|
| 130 |
const filteredHeaders = new Headers();
|
| 131 |
allowedHeaders.forEach((header) => {
|
|
|
|
| 152 |
}
|
| 153 |
});
|
| 154 |
|
| 155 |
+
// Thumbnail download endpoint
|
| 156 |
app.get('/thumb/:productId', async (c) => {
|
| 157 |
const productId: number = parseInt(c.req.param('productId') || '-1');
|
| 158 |
if (productId === -1) return c.text('Invalid productId', 400);
|
|
|
|
| 189 |
}
|
| 190 |
});
|
| 191 |
|
| 192 |
+
// Individual sticker sound download endpoint
|
| 193 |
app.get('/sound/single/:stickerId', async (c) => {
|
| 194 |
const validDeviceTypes = ['ios', 'android', 'pc'] as const;
|
| 195 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 240 |
}
|
| 241 |
});
|
| 242 |
|
| 243 |
+
// Thumbnail sound download endpoint
|
| 244 |
app.get('/sound/thumb/:productId', async (c) => {
|
| 245 |
const validDeviceTypes = ['ios', 'android', 'pc'] as const;
|
| 246 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 288 |
}
|
| 289 |
});
|
| 290 |
|
| 291 |
+
// SOUND+(ANIMATION or STATIC) video download endpoint
|
| 292 |
app.get('/mp4/single/:stickerId', async (c) => {
|
| 293 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 294 |
type DeviceType = (typeof validDeviceTypes)[number];
|
src/routes/meta.ts
CHANGED
|
@@ -8,6 +8,7 @@ const requestTimeout = config.request_timeout || 30000;
|
|
| 8 |
|
| 9 |
const app = new Hono();
|
| 10 |
|
|
|
|
| 11 |
app.get('/sticker/:productId', async (c) => {
|
| 12 |
const validDeviceTypes = ['ios', 'android', 'pc'] as const;
|
| 13 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
@@ -52,6 +53,7 @@ app.get('/sticker/:productId', async (c) => {
|
|
| 52 |
}
|
| 53 |
});
|
| 54 |
|
|
|
|
| 55 |
app.get('/emoji/:productId', async (c) => {
|
| 56 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 57 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 8 |
|
| 9 |
const app = new Hono();
|
| 10 |
|
| 11 |
+
// Sticker metadata endpoint
|
| 12 |
app.get('/sticker/:productId', async (c) => {
|
| 13 |
const validDeviceTypes = ['ios', 'android', 'pc'] as const;
|
| 14 |
type DeviceType = (typeof validDeviceTypes)[number];
|
|
|
|
| 53 |
}
|
| 54 |
});
|
| 55 |
|
| 56 |
+
// Emoji metadata endpoint
|
| 57 |
app.get('/emoji/:productId', async (c) => {
|
| 58 |
const validDeviceTypes = ['ios', 'android'] as const;
|
| 59 |
type DeviceType = (typeof validDeviceTypes)[number];
|
src/routes/metaWeb.ts
CHANGED
|
@@ -9,6 +9,7 @@ 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);
|
|
@@ -43,6 +44,7 @@ app.get('/sticker/:productId', async (c) => {
|
|
| 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);
|
|
|
|
| 9 |
|
| 10 |
const app = new Hono();
|
| 11 |
|
| 12 |
+
// Sticker web metadata endpoint
|
| 13 |
app.get('/sticker/:productId', async (c) => {
|
| 14 |
const productId: number = parseInt(c.req.param('productId') || '-1');
|
| 15 |
if (productId === -1) return c.text('Invalid productId', 400);
|
|
|
|
| 44 |
}
|
| 45 |
});
|
| 46 |
|
| 47 |
+
// Emoji web metadata endpoint
|
| 48 |
app.get('/emoji/:productId', async (c) => {
|
| 49 |
const productId: string = c.req.param('productId') || 'null';
|
| 50 |
if (!/^[0-9a-f]+$/.test(productId)) return c.text('Invalid productId', 400);
|
src/routes/search.ts
CHANGED
|
@@ -8,11 +8,11 @@ const requestTimeout = config.request_timeout || 30000;
|
|
| 8 |
|
| 9 |
const app = new Hono();
|
| 10 |
|
| 11 |
-
//
|
| 12 |
const validCategories = ['sticker', 'emoji', 'theme', 'family'] as const;
|
| 13 |
type Category = (typeof validCategories)[number];
|
| 14 |
|
| 15 |
-
//
|
| 16 |
const validTypes = ['ALL', 'OFFICIAL', 'CREATORS', 'SUBSCRIPTION'] as const;
|
| 17 |
type SearchType = (typeof validTypes)[number];
|
| 18 |
|
|
@@ -24,7 +24,7 @@ app.get('/', async (c) => {
|
|
| 24 |
return c.text('Missing query parameter', 400);
|
| 25 |
}
|
| 26 |
|
| 27 |
-
//
|
| 28 |
if (!validCategories.includes(queryParams.category as Category)) {
|
| 29 |
return c.text('Invalid category', 400);
|
| 30 |
}
|
|
@@ -36,9 +36,9 @@ app.get('/', async (c) => {
|
|
| 36 |
const preferredLanguage = 'lang' in queryParams ? queryParams.lang : c.req.header('Accept-Language') || 'ja';
|
| 37 |
|
| 38 |
try {
|
| 39 |
-
// LINE Store API
|
| 40 |
const response = await ky(`https://store.line.me/api/search/${queryParams.category}`, {
|
| 41 |
-
searchParams: queryParams, //
|
| 42 |
headers: {
|
| 43 |
'Accept-Language': preferredLanguage,
|
| 44 |
'User-Agent': config.userAgent.chromeWindows,
|
|
@@ -46,7 +46,7 @@ app.get('/', async (c) => {
|
|
| 46 |
timeout: requestTimeout,
|
| 47 |
});
|
| 48 |
|
| 49 |
-
//
|
| 50 |
const allowedHeaders = ['Date', 'Content-Type', 'ETag', 'Cache-Control'];
|
| 51 |
const filteredHeaders = new Headers();
|
| 52 |
|
|
|
|
| 8 |
|
| 9 |
const app = new Hono();
|
| 10 |
|
| 11 |
+
// Category type validation
|
| 12 |
const validCategories = ['sticker', 'emoji', 'theme', 'family'] as const;
|
| 13 |
type Category = (typeof validCategories)[number];
|
| 14 |
|
| 15 |
+
// Type validation
|
| 16 |
const validTypes = ['ALL', 'OFFICIAL', 'CREATORS', 'SUBSCRIPTION'] as const;
|
| 17 |
type SearchType = (typeof validTypes)[number];
|
| 18 |
|
|
|
|
| 24 |
return c.text('Missing query parameter', 400);
|
| 25 |
}
|
| 26 |
|
| 27 |
+
// Category validation
|
| 28 |
if (!validCategories.includes(queryParams.category as Category)) {
|
| 29 |
return c.text('Invalid category', 400);
|
| 30 |
}
|
|
|
|
| 36 |
const preferredLanguage = 'lang' in queryParams ? queryParams.lang : c.req.header('Accept-Language') || 'ja';
|
| 37 |
|
| 38 |
try {
|
| 39 |
+
// Request to LINE Store API
|
| 40 |
const response = await ky(`https://store.line.me/api/search/${queryParams.category}`, {
|
| 41 |
+
searchParams: queryParams, // Pass query parameters as-is
|
| 42 |
headers: {
|
| 43 |
'Accept-Language': preferredLanguage,
|
| 44 |
'User-Agent': config.userAgent.chromeWindows,
|
|
|
|
| 46 |
timeout: requestTimeout,
|
| 47 |
});
|
| 48 |
|
| 49 |
+
// Filter allowed headers (add Content-Length)
|
| 50 |
const allowedHeaders = ['Date', 'Content-Type', 'ETag', 'Cache-Control'];
|
| 51 |
const filteredHeaders = new Headers();
|
| 52 |
|