347 lines
12 KiB
JavaScript
347 lines
12 KiB
JavaScript
const express = require('express');
|
||
const router = express.Router();
|
||
const axios = require('axios');
|
||
const { authenticate } = require('../middleware/auth');
|
||
const config = require('../config');
|
||
|
||
// Функция для создания прокси URL
|
||
function createProxyUrl(originalUrl) {
|
||
if (!originalUrl) return null;
|
||
|
||
// Кодируем URL в base64
|
||
const encodedUrl = Buffer.from(originalUrl).toString('base64');
|
||
return `/api/search/proxy/${encodedUrl}`;
|
||
}
|
||
|
||
// Эндпоинт для проксирования изображений
|
||
router.get('/proxy/:encodedUrl', async (req, res) => {
|
||
try {
|
||
const { encodedUrl } = req.params;
|
||
|
||
// Декодируем URL
|
||
const originalUrl = Buffer.from(encodedUrl, 'base64').toString('utf-8');
|
||
|
||
// Проверяем, что URL от разрешенных доменов
|
||
const allowedDomains = [
|
||
'e621.net',
|
||
'static1.e621.net',
|
||
'gelbooru.com',
|
||
'img3.gelbooru.com',
|
||
'img2.gelbooru.com',
|
||
'img1.gelbooru.com',
|
||
'simg3.gelbooru.com',
|
||
'simg4.gelbooru.com'
|
||
];
|
||
const urlObj = new URL(originalUrl);
|
||
|
||
if (!allowedDomains.some(domain => urlObj.hostname.includes(domain))) {
|
||
return res.status(403).json({ error: 'Запрещенный домен' });
|
||
}
|
||
|
||
// Запрашиваем изображение
|
||
const response = await axios.get(originalUrl, {
|
||
responseType: 'stream',
|
||
headers: {
|
||
'User-Agent': 'NakamaHost/1.0',
|
||
'Referer': urlObj.origin
|
||
},
|
||
timeout: 30000 // 30 секунд таймаут
|
||
});
|
||
|
||
// Копируем заголовки
|
||
res.setHeader('Content-Type', response.headers['content-type']);
|
||
res.setHeader('Cache-Control', 'public, max-age=86400'); // Кешируем на 24 часа
|
||
|
||
if (response.headers['content-length']) {
|
||
res.setHeader('Content-Length', response.headers['content-length']);
|
||
}
|
||
|
||
// Стримим изображение
|
||
response.data.pipe(res);
|
||
} catch (error) {
|
||
console.error('Ошибка проксирования изображения:', error.message);
|
||
res.status(500).json({ error: 'Ошибка загрузки изображения' });
|
||
}
|
||
});
|
||
|
||
// e621 API поиск
|
||
router.get('/furry', authenticate, async (req, res) => {
|
||
try {
|
||
const { query, limit = 320, page = 1 } = req.query; // e621 поддерживает до 320 постов на страницу
|
||
|
||
if (!query) {
|
||
return res.status(400).json({ error: 'Параметр query обязателен' });
|
||
}
|
||
|
||
// Поддержка множественных тегов через пробел
|
||
// e621 API автоматически обрабатывает теги через пробел в параметре tags
|
||
|
||
try {
|
||
const response = await axios.get('https://e621.net/posts.json', {
|
||
params: {
|
||
tags: query.trim(), // Множественные теги через пробел
|
||
limit: Math.min(parseInt(limit) || 320, 320), // Максимум 320
|
||
page: parseInt(page) || 1
|
||
},
|
||
headers: {
|
||
'User-Agent': 'NakamaHost/1.0'
|
||
},
|
||
timeout: 30000,
|
||
validateStatus: (status) => status < 500 // Не бросать ошибку для 429
|
||
});
|
||
|
||
// Обработка 429 (Too Many Requests)
|
||
if (response.status === 429) {
|
||
console.warn('⚠️ e621 rate limit (429)');
|
||
return res.json({ posts: [] });
|
||
}
|
||
|
||
// Проверка на наличие данных
|
||
if (!response.data || !response.data.posts || !Array.isArray(response.data.posts)) {
|
||
console.warn('⚠️ e621 вернул неверный формат данных');
|
||
return res.json({ posts: [] });
|
||
}
|
||
|
||
const posts = response.data.posts.map(post => ({
|
||
id: post.id,
|
||
url: createProxyUrl(post.file.url),
|
||
preview: createProxyUrl(post.preview.url),
|
||
tags: post.tags.general,
|
||
rating: post.rating,
|
||
score: post.score.total,
|
||
source: 'e621'
|
||
}));
|
||
|
||
return res.json({ posts });
|
||
} catch (error) {
|
||
// Обработка 429 ошибок
|
||
if (error.response && error.response.status === 429) {
|
||
console.warn('⚠️ e621 rate limit (429)');
|
||
return res.json({ posts: [] });
|
||
}
|
||
|
||
console.error('Ошибка e621 API:', error.message);
|
||
return res.json({ posts: [] }); // Возвращаем пустой массив вместо ошибки
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка поиска e621:', error);
|
||
return res.json({ posts: [] }); // Возвращаем пустой массив вместо ошибки
|
||
}
|
||
});
|
||
|
||
// Gelbooru API поиск
|
||
router.get('/anime', authenticate, async (req, res) => {
|
||
try {
|
||
const { query, limit = 320, page = 1 } = req.query; // Gelbooru поддерживает до 320 постов на страницу
|
||
|
||
if (!query) {
|
||
return res.status(400).json({ error: 'Параметр query обязателен' });
|
||
}
|
||
|
||
// Поддержка множественных тегов через пробел
|
||
// Gelbooru API автоматически обрабатывает теги через пробел в параметре tags
|
||
|
||
try {
|
||
const response = await axios.get('https://gelbooru.com/index.php', {
|
||
params: {
|
||
page: 'dapi',
|
||
s: 'post',
|
||
q: 'index',
|
||
json: 1,
|
||
tags: query.trim(), // Множественные теги через пробел
|
||
limit: Math.min(parseInt(limit) || 320, 320), // Максимум 320
|
||
pid: parseInt(page) || 1,
|
||
api_key: config.gelbooruApiKey,
|
||
user_id: config.gelbooruUserId
|
||
},
|
||
headers: {
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||
},
|
||
timeout: 30000,
|
||
validateStatus: (status) => status < 500 // Не бросать ошибку для 429
|
||
});
|
||
|
||
// Обработка 429 (Too Many Requests)
|
||
if (response.status === 429) {
|
||
console.warn('⚠️ Gelbooru rate limit (429)');
|
||
return res.json({ posts: [] });
|
||
}
|
||
|
||
// Обработка разных форматов ответа Gelbooru
|
||
let postsData = [];
|
||
if (Array.isArray(response.data)) {
|
||
postsData = response.data;
|
||
} else if (response.data && response.data.post) {
|
||
postsData = Array.isArray(response.data.post) ? response.data.post : [response.data.post];
|
||
} else if (response.data && Array.isArray(response.data)) {
|
||
postsData = response.data;
|
||
}
|
||
|
||
const posts = postsData.map(post => ({
|
||
id: post.id,
|
||
url: createProxyUrl(post.file_url),
|
||
preview: createProxyUrl(post.preview_url || post.thumbnail_url || post.file_url),
|
||
tags: post.tags ? (typeof post.tags === 'string' ? post.tags.split(' ') : post.tags) : [],
|
||
rating: post.rating || 'unknown',
|
||
score: post.score || 0,
|
||
source: 'gelbooru'
|
||
}));
|
||
|
||
return res.json({ posts });
|
||
} catch (error) {
|
||
// Обработка 429 ошибок
|
||
if (error.response && error.response.status === 429) {
|
||
console.warn('⚠️ Gelbooru rate limit (429)');
|
||
return res.json({ posts: [] });
|
||
}
|
||
|
||
console.error('Ошибка Gelbooru API:', error.message);
|
||
if (error.response) {
|
||
console.error('Gelbooru ответ:', error.response.status, error.response.data);
|
||
}
|
||
return res.json({ posts: [] }); // Возвращаем пустой массив вместо ошибки
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка поиска Gelbooru:', error);
|
||
return res.json({ posts: [] }); // Возвращаем пустой массив вместо ошибки
|
||
}
|
||
});
|
||
|
||
// Автокомплит тегов для e621
|
||
router.get('/furry/tags', authenticate, async (req, res) => {
|
||
try {
|
||
const { query } = req.query;
|
||
|
||
if (!query || query.length < 2) {
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
try {
|
||
const response = await axios.get('https://e621.net/tags.json', {
|
||
params: {
|
||
'search[name_matches]': `${query}*`,
|
||
'search[order]': 'count',
|
||
limit: 10
|
||
},
|
||
headers: {
|
||
'User-Agent': 'NakamaHost/1.0'
|
||
},
|
||
timeout: 10000,
|
||
validateStatus: (status) => status < 500 // Не бросать ошибку для 429
|
||
});
|
||
|
||
// Обработка 429 (Too Many Requests)
|
||
if (response.status === 429) {
|
||
console.warn('⚠️ e621 rate limit (429)');
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
// Проверка на массив
|
||
if (!response.data || !Array.isArray(response.data)) {
|
||
console.warn('⚠️ e621 вернул не массив:', typeof response.data);
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
const tags = response.data.map(tag => ({
|
||
name: tag.name,
|
||
count: tag.post_count
|
||
}));
|
||
|
||
return res.json({ tags });
|
||
} catch (error) {
|
||
// Обработка 429 ошибок
|
||
if (error.response && error.response.status === 429) {
|
||
console.warn('⚠️ e621 rate limit (429)');
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
console.error('Ошибка получения тегов e621:', error.message);
|
||
return res.json({ tags: [] }); // Возвращаем пустой массив вместо ошибки
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка получения тегов:', error);
|
||
return res.json({ tags: [] }); // Возвращаем пустой массив вместо ошибки
|
||
}
|
||
});
|
||
|
||
// Автокомплит тегов для Gelbooru
|
||
router.get('/anime/tags', authenticate, async (req, res) => {
|
||
try {
|
||
const { query } = req.query;
|
||
|
||
if (!query || query.length < 2) {
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
try {
|
||
const response = await axios.get('https://gelbooru.com/index.php', {
|
||
params: {
|
||
page: 'dapi',
|
||
s: 'tag',
|
||
q: 'index',
|
||
json: 1,
|
||
name_pattern: `${query}%`,
|
||
orderby: 'count',
|
||
limit: 10,
|
||
api_key: config.gelbooruApiKey,
|
||
user_id: config.gelbooruUserId
|
||
},
|
||
headers: {
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||
},
|
||
timeout: 30000,
|
||
validateStatus: (status) => status < 500 // Не бросать ошибку для 429
|
||
});
|
||
|
||
// Обработка 429 (Too Many Requests)
|
||
if (response.status === 429) {
|
||
console.warn('⚠️ Gelbooru rate limit (429)');
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
// Обработка разных форматов ответа Gelbooru
|
||
let tagsData = [];
|
||
if (Array.isArray(response.data)) {
|
||
tagsData = response.data;
|
||
} else if (response.data && response.data.tag) {
|
||
tagsData = Array.isArray(response.data.tag) ? response.data.tag : [response.data.tag];
|
||
} else if (response.data && Array.isArray(response.data)) {
|
||
tagsData = response.data;
|
||
}
|
||
|
||
// Проверка на массив перед map
|
||
if (!Array.isArray(tagsData)) {
|
||
console.warn('⚠️ Gelbooru вернул не массив тегов');
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
const tags = tagsData.map(tag => ({
|
||
name: tag.name || tag.tag || '',
|
||
count: tag.count || tag.post_count || 0
|
||
})).filter(tag => tag.name);
|
||
|
||
return res.json({ tags });
|
||
} catch (error) {
|
||
// Обработка 429 ошибок
|
||
if (error.response && error.response.status === 429) {
|
||
console.warn('⚠️ Gelbooru rate limit (429)');
|
||
return res.json({ tags: [] });
|
||
}
|
||
|
||
console.error('Ошибка получения тегов Gelbooru:', error.message);
|
||
if (error.response) {
|
||
console.error('Gelbooru ответ:', error.response.status, error.response.data);
|
||
}
|
||
// В случае ошибки возвращаем пустой массив вместо ошибки
|
||
return res.json({ tags: [] });
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка получения тегов Gelbooru:', error);
|
||
// В случае ошибки возвращаем пустой массив вместо ошибки
|
||
return res.json({ tags: [] });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|
||
|