Rewrite of ESH-Media v1 with separated main/renderer/shared architecture (vite-plugin-electron, React 18, react-router-dom). Includes NeDB storage, electron-store config, proxy manager with FoxyProxy/uBlock extensions, custom server-checked updater, NSIS installer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
272 lines
8.5 KiB
Markdown
272 lines
8.5 KiB
Markdown
# Система Поисковых Скриптов
|
||
|
||
## Обзор
|
||
|
||
Media Center использует систему кастомных JavaScript скриптов для поиска контента на различных сайтах. Каждый сайт имеет свой собственный скрипт, который знает, как выполнять поиск на этом конкретном сайте.
|
||
|
||
## Преимущества подхода
|
||
|
||
1. **Гибкость**: Каждый сайт может иметь уникальную логику поиска
|
||
2. **Расширяемость**: Легко добавлять новые сайты без изменения основного кода
|
||
3. **Обновляемость**: Скрипты можно обновлять с сервера
|
||
4. **Нет зависимости от Node.js**: Скрипты выполняются в контексте Electron
|
||
|
||
## Структура скрипта
|
||
|
||
Каждый скрипт - это JavaScript файл, который экспортирует функцию `search`:
|
||
|
||
```javascript
|
||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||
// Ваша логика поиска
|
||
return [
|
||
{
|
||
name: "Название фильма",
|
||
url: "https://site.com/movie/123",
|
||
image: "https://site.com/poster.jpg", // опционально
|
||
year: "2023", // опционально
|
||
description: "Описание фильма", // опционально
|
||
rating: "8.5" // опционально
|
||
}
|
||
];
|
||
}
|
||
```
|
||
|
||
### Параметры функции
|
||
|
||
- `query` (string) - поисковый запрос пользователя
|
||
- `siteUrl` (string) - базовый URL сайта
|
||
- `useProxy` (boolean) - флаг использования прокси
|
||
- `axios` - HTTP клиент для запросов
|
||
- `cheerio` - библиотека для парсинга HTML
|
||
- `proxyConfig` - объект с настройками прокси `{host, port}`
|
||
|
||
### Возвращаемое значение
|
||
|
||
Массив объектов с результатами. Обязательные поля:
|
||
- `name` - название фильма/сериала
|
||
- `url` - ссылка на страницу
|
||
|
||
Опциональные поля:
|
||
- `image` - URL постера
|
||
- `year` - год выпуска
|
||
- `description` - краткое описание
|
||
- `rating` - рейтинг
|
||
|
||
## Доступные инструменты
|
||
|
||
### axios
|
||
|
||
HTTP клиент для выполнения запросов:
|
||
|
||
```javascript
|
||
// GET запрос
|
||
const response = await axios.get('https://site.com/search', {
|
||
params: { q: query },
|
||
timeout: 15000,
|
||
headers: {
|
||
'User-Agent': 'Mozilla/5.0...'
|
||
}
|
||
});
|
||
|
||
// POST запрос
|
||
const response = await axios.post('https://site.com/search', {
|
||
query: query
|
||
}, {
|
||
timeout: 15000
|
||
});
|
||
|
||
// С прокси
|
||
if (useProxy && proxyConfig) {
|
||
const response = await axios.get(url, {
|
||
proxy: {
|
||
host: proxyConfig.host,
|
||
port: proxyConfig.port
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
### cheerio
|
||
|
||
jQuery-подобная библиотека для парсинга HTML:
|
||
|
||
```javascript
|
||
const $ = cheerio.load(html);
|
||
|
||
// Поиск элементов
|
||
const title = $('.movie-title').text();
|
||
const link = $('a.movie-link').attr('href');
|
||
|
||
// Итерация по элементам
|
||
$('.movie-card').each((index, element) => {
|
||
const $el = $(element);
|
||
const name = $el.find('.title').text().trim();
|
||
const url = $el.find('a').attr('href');
|
||
});
|
||
```
|
||
|
||
## Примеры реализаций
|
||
|
||
### Пример 1: HTML парсинг (Kinogo)
|
||
|
||
```javascript
|
||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||
const config = {
|
||
params: { do: 'search', subaction: 'search', story: query },
|
||
timeout: 15000,
|
||
headers: { 'User-Agent': 'Mozilla/5.0...' }
|
||
};
|
||
|
||
if (useProxy && proxyConfig) {
|
||
config.proxy = { host: proxyConfig.host, port: proxyConfig.port };
|
||
}
|
||
|
||
const response = await axios.get(`${siteUrl}/index.php`, config);
|
||
const $ = cheerio.load(response.data);
|
||
const results = [];
|
||
|
||
$('.shortstory').each((i, el) => {
|
||
const $item = $(el);
|
||
const name = $item.find('.title a').text().trim();
|
||
const url = $item.find('.title a').attr('href');
|
||
const image = $item.find('img').attr('src');
|
||
|
||
if (name && url) {
|
||
results.push({ name, url: siteUrl + url, image });
|
||
}
|
||
});
|
||
|
||
return results;
|
||
}
|
||
```
|
||
|
||
### Пример 2: JSON API (Rutube)
|
||
|
||
```javascript
|
||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||
const config = {
|
||
params: { query, limit: 20 },
|
||
timeout: 15000
|
||
};
|
||
|
||
const response = await axios.get(`${siteUrl}/api/search/video/`, config);
|
||
const data = response.data;
|
||
|
||
if (!data.results) return [];
|
||
|
||
return data.results.map(item => ({
|
||
name: item.title,
|
||
url: item.video_url || `${siteUrl}/video/${item.id}`,
|
||
image: item.thumbnail_url,
|
||
description: item.description
|
||
}));
|
||
}
|
||
```
|
||
|
||
## Хранение скриптов
|
||
|
||
Скрипты хранятся в двух местах:
|
||
|
||
1. **Встроенные скрипты**: `<app>/search-scripts/`
|
||
- Поставляются с приложением
|
||
- Только для чтения
|
||
- Используются по умолчанию
|
||
|
||
2. **Пользовательские скрипты**: `<userData>/search-scripts/`
|
||
- Загружаются с сервера
|
||
- Можно обновлять
|
||
- Имеют приоритет над встроенными
|
||
|
||
## Конфигурация сайта
|
||
|
||
В `config/sites.json`:
|
||
|
||
```json
|
||
{
|
||
"id": "kinogo",
|
||
"name": "Kinogo",
|
||
"url": "https://kinogo.biz",
|
||
"logo": "https://kinogo.biz/favicon.ico",
|
||
"enabled": true,
|
||
"useProxy": true,
|
||
"searchScript": "kinogo.js"
|
||
}
|
||
```
|
||
|
||
## Создание нового скрипта
|
||
|
||
1. Скопируйте `SCRIPT_TEMPLATE.js`
|
||
2. Назовите файл по имени сайта (например, `mysite.js`)
|
||
3. Реализуйте функцию `search()`
|
||
4. Протестируйте с разными запросами
|
||
5. Добавьте сайт в конфигурацию
|
||
|
||
## Обновление скриптов с сервера
|
||
|
||
Пользователи могут обновлять скрипты через настройки приложения:
|
||
|
||
1. Настройки → Конфигурация
|
||
2. Кнопка "Обновить конфигурацию"
|
||
3. Скачиваются новые скрипты и конфигурация
|
||
4. Применяются автоматически
|
||
|
||
## Безопасность
|
||
|
||
- Скрипты выполняются в изолированном контексте
|
||
- Таймаут выполнения: 30 секунд
|
||
- Нет доступа к файловой системе
|
||
- Нет доступа к другим модулям Node.js
|
||
- Только axios и cheerio
|
||
|
||
## Отладка
|
||
|
||
Для отладки скриптов:
|
||
|
||
1. Откройте DevTools (F12 в приложении)
|
||
2. Выполните поиск
|
||
3. Проверьте консоль на наличие ошибок
|
||
4. Логи включают:
|
||
- Найденное количество результатов
|
||
- Ошибки выполнения
|
||
- Невалидные результаты
|
||
|
||
## Требования к серверу
|
||
|
||
Сервер должен предоставлять:
|
||
|
||
### GET /api/config/sites
|
||
|
||
Возвращает конфигурацию со списком сайтов и ссылками на скрипты:
|
||
|
||
```json
|
||
{
|
||
"version": "1.0.0",
|
||
"lastUpdated": "2025-10-14T12:00:00Z",
|
||
"sites": [
|
||
{
|
||
"id": "kinogo",
|
||
"name": "Kinogo",
|
||
"url": "https://kinogo.biz",
|
||
"logo": "https://kinogo.biz/favicon.ico",
|
||
"enabled": true,
|
||
"useProxy": true,
|
||
"searchScript": "kinogo.js",
|
||
"scriptUrl": "https://server.com/scripts/kinogo.js"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### GET /scripts/{scriptName}
|
||
|
||
Возвращает содержимое скрипта (JavaScript файл).
|
||
|
||
## Best Practices
|
||
|
||
1. **Обработка ошибок**: Всегда оборачивайте код в try-catch
|
||
2. **Таймауты**: Устанавливайте разумные таймауты для запросов
|
||
3. **Валидация**: Проверяйте наличие обязательных полей
|
||
4. **User-Agent**: Используйте реалистичный User-Agent
|
||
5. **Относительные URL**: Преобразуйте в абсолютные
|
||
6. **Пустые результаты**: Возвращайте `[]` при ошибке, а не `null`
|