init: media-center v2
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>
This commit is contained in:
135
search-scripts/README.md
Normal file
135
search-scripts/README.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Поисковые Скрипты для Media Center
|
||||
|
||||
Эта папка содержит кастомные JavaScript скрипты для поиска фильмов и сериалов на различных сайтах.
|
||||
|
||||
## Как работают скрипты
|
||||
|
||||
Каждый скрипт выполняется в защищенном контексте Electron и получает доступ к следующим инструментам:
|
||||
|
||||
- **axios** - для HTTP запросов
|
||||
- **cheerio** - для парсинга HTML (jQuery-подобный синтаксис)
|
||||
- **proxyConfig** - настройки прокси (если включен для сайта)
|
||||
|
||||
## Структура скрипта
|
||||
|
||||
Каждый скрипт должен экспортировать функцию `search` со следующей сигнатурой:
|
||||
|
||||
```javascript
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
// Ваш код поиска
|
||||
return [{name: "Название фильма", url: "https://..."}];
|
||||
}
|
||||
```
|
||||
|
||||
### Параметры:
|
||||
|
||||
- `query` (string) - поисковый запрос пользователя
|
||||
- `siteUrl` (string) - базовый URL сайта (например, "https://kinogo.biz")
|
||||
- `useProxy` (boolean) - нужно ли использовать прокси
|
||||
- `axios` (object) - экземпляр axios для HTTP запросов
|
||||
- `cheerio` (object) - библиотека для парсинга HTML
|
||||
- `proxyConfig` (object) - настройки прокси `{host: string, port: number}`
|
||||
|
||||
### Возвращаемое значение:
|
||||
|
||||
Массив объектов с результатами поиска. Каждый объект должен содержать:
|
||||
|
||||
**Обязательные поля:**
|
||||
- `name` (string) - название фильма/сериала
|
||||
- `url` (string) - ссылка на страницу фильма
|
||||
|
||||
**Опциональные поля:**
|
||||
- `image` (string) - URL постера
|
||||
- `year` (string) - год выпуска
|
||||
- `description` (string) - описание
|
||||
- `rating` (string) - рейтинг
|
||||
|
||||
## Примеры
|
||||
|
||||
### Пример 1: JSON API
|
||||
|
||||
```javascript
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
const config = {
|
||||
params: { q: query },
|
||||
timeout: 15000
|
||||
};
|
||||
|
||||
if (useProxy && proxyConfig) {
|
||||
config.proxy = { host: proxyConfig.host, port: proxyConfig.port };
|
||||
}
|
||||
|
||||
const response = await axios.get(`${siteUrl}/api/search`, config);
|
||||
|
||||
return response.data.results.map(item => ({
|
||||
name: item.title,
|
||||
url: item.link,
|
||||
image: item.poster
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
### Пример 2: HTML парсинг
|
||||
|
||||
```javascript
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
const config = {
|
||||
params: { q: query },
|
||||
timeout: 15000
|
||||
};
|
||||
|
||||
if (useProxy && proxyConfig) {
|
||||
config.proxy = { host: proxyConfig.host, port: proxyConfig.port };
|
||||
}
|
||||
|
||||
const response = await axios.get(`${siteUrl}/search`, config);
|
||||
const $ = cheerio.load(response.data);
|
||||
const results = [];
|
||||
|
||||
$('.movie-card').each((i, el) => {
|
||||
const $el = $(el);
|
||||
results.push({
|
||||
name: $el.find('.title').text().trim(),
|
||||
url: siteUrl + $el.find('a').attr('href'),
|
||||
image: $el.find('img').attr('src')
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
## Добавление нового скрипта
|
||||
|
||||
1. Создайте файл `sitename.js` в этой папке
|
||||
2. Используйте `SCRIPT_TEMPLATE.js` как основу
|
||||
3. Реализуйте функцию `search()`
|
||||
4. Обновите конфигурацию сайта в настройках приложения
|
||||
|
||||
## Обновление скриптов
|
||||
|
||||
Скрипты можно обновлять с сервера через настройки приложения. При обновлении конфигурации новые скрипты автоматически загружаются.
|
||||
|
||||
## Расположение скриптов
|
||||
|
||||
Скрипты хранятся в двух местах:
|
||||
|
||||
1. **Встроенные скрипты**: `<app>/search-scripts/` (только для чтения)
|
||||
2. **Пользовательские скрипты**: `<userData>/search-scripts/` (можно обновлять)
|
||||
|
||||
Пользовательские скрипты имеют приоритет над встроенными.
|
||||
|
||||
## Безопасность
|
||||
|
||||
- Скрипты выполняются в изолированном контексте
|
||||
- Таймаут выполнения: 30 секунд
|
||||
- Доступ только к axios и cheerio
|
||||
- Нет доступа к файловой системе или другим модулям Node.js
|
||||
|
||||
## Отладка
|
||||
|
||||
Ошибки скриптов логируются в консоль Electron DevTools. Для отладки:
|
||||
|
||||
1. Откройте DevTools (F12)
|
||||
2. Выполните поиск
|
||||
3. Проверьте консоль на наличие ошибок
|
||||
96
search-scripts/SCRIPT_TEMPLATE.js
Normal file
96
search-scripts/SCRIPT_TEMPLATE.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Search script template for creating custom site search scripts
|
||||
*
|
||||
* Copy this file and modify the search() function to work with your target site.
|
||||
*
|
||||
* Available tools:
|
||||
* - axios: For making HTTP requests
|
||||
* - cheerio: For parsing HTML (jQuery-like syntax)
|
||||
* - proxyConfig: Proxy settings if useProxy is true
|
||||
*
|
||||
* @param {string} query - Search query entered by user
|
||||
* @param {string} siteUrl - Base URL of the website (e.g., "https://example.com")
|
||||
* @param {boolean} useProxy - Whether to use proxy for this request
|
||||
* @param {object} axios - Axios HTTP client
|
||||
* @param {object} cheerio - Cheerio HTML parser
|
||||
* @param {object} proxyConfig - Proxy configuration {host: string, port: number}
|
||||
* @returns {Promise<Array>} Array of results in format: [{name: string, url: string, image?: string, year?: string, description?: string, rating?: string}]
|
||||
*/
|
||||
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
try {
|
||||
// 1. Build the search URL
|
||||
const searchUrl = `${siteUrl}/search?q=${encodeURIComponent(query)}`;
|
||||
|
||||
// 2. Configure the request
|
||||
const config = {
|
||||
timeout: 15000, // 15 seconds timeout
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Add proxy if needed
|
||||
if (useProxy && proxyConfig) {
|
||||
config.proxy = {
|
||||
host: proxyConfig.host,
|
||||
port: proxyConfig.port
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Make the HTTP request
|
||||
const response = await axios.get(searchUrl, config);
|
||||
|
||||
// 5a. If the response is JSON:
|
||||
// const data = response.data;
|
||||
// const results = data.items.map(item => ({
|
||||
// name: item.title,
|
||||
// url: item.link,
|
||||
// image: item.thumbnail,
|
||||
// year: item.year,
|
||||
// description: item.synopsis
|
||||
// }));
|
||||
|
||||
// 5b. If the response is HTML:
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
const results = [];
|
||||
|
||||
// Parse HTML and extract movie data
|
||||
$('.movie-card').each((index, element) => {
|
||||
const $item = $(element);
|
||||
|
||||
const name = $item.find('.movie-title').text().trim();
|
||||
const url = $item.find('a').attr('href');
|
||||
const image = $item.find('img').attr('src');
|
||||
const year = $item.find('.year').text().trim();
|
||||
const description = $item.find('.description').text().trim();
|
||||
|
||||
// Only add if name and url exist
|
||||
if (name && url) {
|
||||
results.push({
|
||||
name,
|
||||
url: url.startsWith('http') ? url : siteUrl + url,
|
||||
image: image ? (image.startsWith('http') ? image : siteUrl + image) : undefined,
|
||||
year: year || undefined,
|
||||
description: description || undefined
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Return the results
|
||||
return results;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Search error:', error.message);
|
||||
return []; // Return empty array on error
|
||||
}
|
||||
}
|
||||
|
||||
// Important notes:
|
||||
// 1. The function MUST be named 'search'
|
||||
// 2. It MUST return a Promise that resolves to an array
|
||||
// 3. Each result MUST have 'name' and 'url' fields
|
||||
// 4. Other fields (image, year, description, rating) are optional
|
||||
// 5. Handle errors gracefully and return [] on failure
|
||||
// 6. Test with different queries to ensure reliability
|
||||
87
search-scripts/hdrezka.js
Normal file
87
search-scripts/hdrezka.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* HDRezka.ag search script
|
||||
*
|
||||
* This script searches for movies/series on hdrezka.ag
|
||||
*
|
||||
* @param {string} query - Search query from user
|
||||
* @param {string} siteUrl - Base URL of the site
|
||||
* @param {boolean} useProxy - Whether to use proxy for requests
|
||||
* @param {object} axios - Axios instance for HTTP requests
|
||||
* @param {object} cheerio - Cheerio instance for HTML parsing
|
||||
* @param {object} proxyConfig - Proxy configuration {host, port}
|
||||
* @returns {Promise<Array>} Array of search results [{name, url, image?, year?, description?}]
|
||||
*/
|
||||
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
try {
|
||||
const searchUrl = `${siteUrl}/search/`;
|
||||
|
||||
// Prepare request config
|
||||
const config = {
|
||||
params: {
|
||||
do: 'search',
|
||||
subaction: 'search',
|
||||
q: query
|
||||
},
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8'
|
||||
}
|
||||
};
|
||||
|
||||
// Add proxy if needed
|
||||
if (useProxy && proxyConfig) {
|
||||
config.proxy = {
|
||||
host: proxyConfig.host,
|
||||
port: proxyConfig.port
|
||||
};
|
||||
}
|
||||
|
||||
// Make request
|
||||
const response = await axios.get(searchUrl, config);
|
||||
const html = response.data;
|
||||
|
||||
// Parse HTML
|
||||
const $ = cheerio.load(html);
|
||||
const results = [];
|
||||
|
||||
// Find all movie/series cards
|
||||
$('.b-content__inline_item').each((index, element) => {
|
||||
const $item = $(element);
|
||||
|
||||
// Extract title and URL
|
||||
const linkEl = $item.find('.b-content__inline_item-link a').first();
|
||||
const name = linkEl.text().trim();
|
||||
const url = linkEl.attr('href');
|
||||
|
||||
if (!name || !url) return;
|
||||
|
||||
// Extract image
|
||||
const imageEl = $item.find('.b-content__inline_item-cover img').first();
|
||||
const image = imageEl.attr('src') || imageEl.attr('data-src');
|
||||
|
||||
// Extract year
|
||||
const infoText = $item.find('.info').text();
|
||||
const yearMatch = infoText.match(/(\d{4})/);
|
||||
const year = yearMatch ? yearMatch[1] : '';
|
||||
|
||||
// Extract rating if available
|
||||
const ratingText = $item.find('.imdb').text().trim();
|
||||
|
||||
results.push({
|
||||
name,
|
||||
url: url.startsWith('http') ? url : siteUrl + url,
|
||||
image: image ? (image.startsWith('http') ? image : siteUrl + image) : undefined,
|
||||
year,
|
||||
rating: ratingText || undefined
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('HDRezka search error:', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
85
search-scripts/kinogo.js
Normal file
85
search-scripts/kinogo.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Kinogo.biz search script
|
||||
*
|
||||
* This script searches for movies/series on kinogo.biz
|
||||
*
|
||||
* @param {string} query - Search query from user
|
||||
* @param {string} siteUrl - Base URL of the site
|
||||
* @param {boolean} useProxy - Whether to use proxy for requests
|
||||
* @param {object} axios - Axios instance for HTTP requests
|
||||
* @param {object} cheerio - Cheerio instance for HTML parsing
|
||||
* @param {object} proxyConfig - Proxy configuration {host, port}
|
||||
* @returns {Promise<Array>} Array of search results [{name, url, image?, year?, description?}]
|
||||
*/
|
||||
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
try {
|
||||
const searchUrl = `${siteUrl}/index.php`;
|
||||
|
||||
// Prepare request config
|
||||
const config = {
|
||||
params: {
|
||||
do: 'search',
|
||||
subaction: 'search',
|
||||
story: query
|
||||
},
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
};
|
||||
|
||||
// Add proxy if needed
|
||||
if (useProxy && proxyConfig) {
|
||||
config.proxy = {
|
||||
host: proxyConfig.host,
|
||||
port: proxyConfig.port
|
||||
};
|
||||
}
|
||||
|
||||
// Make request
|
||||
const response = await axios.get(searchUrl, config);
|
||||
const html = response.data;
|
||||
|
||||
// Parse HTML
|
||||
const $ = cheerio.load(html);
|
||||
const results = [];
|
||||
|
||||
// Find all movie cards
|
||||
$('.shortstory').each((index, element) => {
|
||||
const $item = $(element);
|
||||
|
||||
// Extract title
|
||||
const titleEl = $item.find('.shortstory-title a, .title a, h2 a').first();
|
||||
const name = titleEl.text().trim();
|
||||
const url = titleEl.attr('href');
|
||||
|
||||
if (!name || !url) return;
|
||||
|
||||
// Extract image
|
||||
const imageEl = $item.find('.shortstory-img img, .poster img, img').first();
|
||||
const image = imageEl.attr('src') || imageEl.attr('data-src');
|
||||
|
||||
// Extract year
|
||||
const yearText = $item.find('.year, .shortstory-year').text().trim();
|
||||
const yearMatch = yearText.match(/(\d{4})/);
|
||||
const year = yearMatch ? yearMatch[1] : '';
|
||||
|
||||
// Extract description
|
||||
const description = $item.find('.shortstory-desc, .description').text().trim();
|
||||
|
||||
results.push({
|
||||
name,
|
||||
url: url.startsWith('http') ? url : siteUrl + url,
|
||||
image: image ? (image.startsWith('http') ? image : siteUrl + image) : undefined,
|
||||
year,
|
||||
description: description || undefined
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('Kinogo search error:', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
63
search-scripts/rutube.js
Normal file
63
search-scripts/rutube.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Rutube.ru search script
|
||||
*
|
||||
* This script searches for videos on rutube.ru using their API
|
||||
*
|
||||
* @param {string} query - Search query from user
|
||||
* @param {string} siteUrl - Base URL of the site
|
||||
* @param {boolean} useProxy - Whether to use proxy for requests
|
||||
* @param {object} axios - Axios instance for HTTP requests
|
||||
* @param {object} cheerio - Cheerio instance for HTML parsing (not used)
|
||||
* @param {object} proxyConfig - Proxy configuration {host, port}
|
||||
* @returns {Promise<Array>} Array of search results [{name, url, image?, description?}]
|
||||
*/
|
||||
|
||||
async function search(query, siteUrl, useProxy, axios, cheerio, proxyConfig) {
|
||||
try {
|
||||
const searchUrl = `${siteUrl}/api/search/video/`;
|
||||
|
||||
// Prepare request config
|
||||
const config = {
|
||||
params: {
|
||||
query: query,
|
||||
limit: 20
|
||||
},
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
};
|
||||
|
||||
// Add proxy if needed (Rutube usually doesn't require proxy in Russia)
|
||||
if (useProxy && proxyConfig) {
|
||||
config.proxy = {
|
||||
host: proxyConfig.host,
|
||||
port: proxyConfig.port
|
||||
};
|
||||
}
|
||||
|
||||
// Make request
|
||||
const response = await axios.get(searchUrl, config);
|
||||
const data = response.data;
|
||||
|
||||
// Check if results exist
|
||||
if (!data.results || !Array.isArray(data.results)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Map results to our format
|
||||
const results = data.results.map(item => {
|
||||
return {
|
||||
name: item.title || '',
|
||||
url: item.video_url || (siteUrl + '/video/' + item.id),
|
||||
image: item.thumbnail_url || undefined,
|
||||
description: item.description || undefined
|
||||
};
|
||||
});
|
||||
|
||||
return results.filter(r => r.name && r.url);
|
||||
} catch (error) {
|
||||
console.error('Rutube search error:', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user