stihirus-reader
Version:
Fetches author profile information and optionally poems from stihirus.ru
408 lines (343 loc) • 24.1 kB
Markdown
[](https://badge.fury.io/js/stihirus-reader)
[](https://opensource.org/licenses/MIT)
[](https://visitorbadge.io/status?path=https%3A%2F%2Fnpmjs.com%2Fstihirus-reader)
[](
---
Node.js module (ESM) to fetch author profile information (description, stats, collections, etc.) and optionally all their poems from the Russian poetry website stihirus.ru.
It intelligently handles different ways of identifying an author (ID, username, subdomain URL, path URL) and retrieves data by parsing the author's page and, if requested, querying an internal API used by the site. The function returns a structured response indicating success or failure.
### Installation
```bash
npm install stihirus-reader
```
Requires Node.js v16 or higher.
### Usage
```javascript
// example.js
import { getAuthorData } from 'stihirus-reader';
async function main() {
// Choose ONE identifier type:
const identifier = 'oreh-orehov'; // 1. Username
// const identifier = 14260; // 2. Author ID (number)
// const identifier = 'https://oreh-orehov.stihirus.ru/'; // 3. Subdomain URL
// const identifier = 'https://stihirus.ru/avtor/oreh-orehov'; // 4. Path URL
const customDelayMs = 750; // Optional: Custom delay between API requests (only used if fetchAllPoems is true)
const fetchAllPoems = true; // Optional: Set to false to fetch only profile info
try {
console.log(`Fetching data for: ${identifier} (Fetch poems: ${fetchAllPoems})`);
// Call getAuthorData, optionally passing delay and fetchAllPoems flag
const response = await getAuthorData(identifier, customDelayMs, fetchAllPoems);
// Or:
// const response = await getAuthorData(identifier); // Fetch profile + all poems with default delay
// const response = await getAuthorData(identifier, undefined, false); // Fetch profile only
// Check the status field to determine success or failure
if (response.status === 'success') {
const authorData = response.data; // Access data if successful
console.log(`\n--- Author: ${authorData.username} (ID: ${authorData.authorId}) ---`);
console.log(`Profile URL: ${authorData.profileUrl}`);
console.log(`Description: ${authorData.description.substring(0, 100)}...`);
console.log(`Status: ${authorData.status}`);
console.log(`Last Visit: ${authorData.lastVisit}`);
console.log(`Stats: Poems=${authorData.stats.poems}, Sent=${authorData.stats.reviewsSent}, Received=${authorData.stats.reviewsReceived}`);
console.log(`Collections: ${authorData.collections.map(c => c.name).join(', ') || 'None'}`);
console.log(`Poems array length: ${authorData.poems.length}`); // Will be 0 if fetchAllPoems was false
if (authorData.poems.length > 0) {
console.log('\n--- First 3 Poems ---');
authorData.poems.slice(0, 3).forEach(poem => {
console.log(` Title: ${poem.title}`);
console.log(` Rubric: ${poem.rubric.name}`);
console.log(` Collection: ${poem.collection || 'N/A'}`);
console.log(` Created: ${poem.created}`);
console.log(` Text Snippet: ${poem.text.substring(0, 70)}...`);
console.log(' ---');
});
} else if (fetchAllPoems) {
console.log('\n--- No poems found or fetched ---');
} else {
console.log('\n--- Poems were not requested (fetchAllPoems=false) ---');
}
// Full data available in authorData object
// console.log(JSON.stringify(authorData, null, 2));
} else if (response.status === 'error') {
const error = response.error; // Access error details if failed
console.error(`\n--- ERROR fetching data for ${identifier} ---`);
console.error(`Error Code: ${error.code}`);
console.error(`Message: ${error.message}`);
if (error.originalMessage) {
console.error(`Original Msg: ${error.originalMessage}`);
}
}
} catch (error) {
// Catch unexpected errors (e.g., programming errors in the calling code)
// Note: Operational errors (not found, API issues) are handled via response.status === 'error'
console.error(`\n--- UNEXPECTED ERROR for ${identifier} ---`);
console.error(error);
}
}
main();
```
Asynchronously fetches author data.
* **`identifier`**: `string | number` - The author identifier. Can be one of:
1. **Author ID** (e.g., `14260`)
2. **Username** (e.g., `'oreh-orehov'`)
3. **Subdomain URL** (e.g., `'https://oreh-orehov.stihirus.ru/'`)
4. **Path URL** (e.g., `'https://stihirus.ru/avtor/oreh-orehov'`)
* **`requestDelayMs`**: `number` (Optional) - The delay in milliseconds between API requests when fetching multiple pages of poems. Only relevant if `fetchAllPoems` is `true`. Defaults to `500`ms.
* **`fetchAllPoems`**: `boolean` (Optional) - Whether to fetch all poems via the API. If `false`, only profile information (description, stats from HTML, collections) is fetched. Defaults to `true`.
* **Returns**: `Promise<StihirusResponse>` - A promise that resolves to an object indicating the outcome. Check the `status` field:
* If `status` is `'success'`, the author's data is in the `data` property. The `poems` array will be empty if `fetchAllPoems` was `false`.
* If `status` is `'error'`, details about the error are in the `error` property.
* See the `StihirusResponse` structure below.
**Returned Object Structure (`StihirusResponse`):**
```typescript
// If the operation is successful:
{
status: 'success',
data: {
// StihirusAuthorData structure (see below)
authorId: number,
username: string,
profileUrl: string,
canonicalUsername: string,
description: string,
avatarUrl: string | null,
headerUrl: string | null,
status: string,
lastVisit: string,
stats: { /* StihirusAuthorStats */ },
collections: Array<{ /* StihirusCollectionInfo */ }>,
poems: Array<{ /* StihirusPoem */ }> // Will be empty if fetchAllPoems was false
}
}
// If the operation fails:
{
status: 'error',
error: {
// StihirusError structure (see below)
code: number,
message: string,
originalMessage?: string
}
}
// --- Detailed Structures ---
// StihirusAuthorData (inside response.data on success)
{
authorId: number, // Author's unique ID
username: string, // Author's display name (from HTML)
profileUrl: string, // The URL used to fetch the HTML profile page
canonicalUsername: string, // Username used in URLs/API calls
description: string, // Author's profile description
avatarUrl: string | null, // URL to the author's avatar image
headerUrl: string | null, // URL to the author's profile header image
status: string, // Author's status (e.g., 'новенький')
lastVisit: string, // Text indicating the last visit time (e.g., '5 минут назад')
stats: { // StihirusAuthorStats
poems: number, // Total number of poems (best estimate from HTML or API count if poems fetched)
reviewsSent: number, // Number of reviews sent by the author
reviewsReceived: number // Number of reviews received by the author
},
collections: Array<{ // StihirusCollectionInfo
name: string, // Name of the collection
url: string // Full URL to the collection page
}>,
poems: Array<{ // StihirusPoem (Array is empty if fetchAllPoems was false)
id: number, // Poem's unique ID
title: string, // Poem's title (or '***')
text: string, // Full text of the poem
created: string, // Creation date/time string (e.g., "27.03.2025 20:19")
rubric: { // StihirusPoemRubric
name: string, // Name of the general rubric (genre)
url: string | null // Full URL to the rubric page
},
collection: string | null, // Name of the author's collection, if any
rating: number, // Number of likes
commentsCount: number, // Number of comments
imageUrl: string | null, // URL to the poem's specific image, if any
hasCertificate: boolean // Whether the poem has a certificate
}>
}
// StihirusError (inside response.error on failure)
{
code: number, // HTTP status code or custom error code (e.g., 404, 500, 400)
message: string, // User-friendly error message
originalMessage?: string // Optional underlying error message (e.g., from fetch or API response)
}
```
* **Unofficial:** This module uses web scraping and interacts with internal site APIs. It is not an official API provided by stihirus.ru. Changes to the website may break this module.
* **Rate Limiting:** The module includes a default delay (**500ms**) between API calls during poem pagination (when `fetchAllPoems` is `true`) to avoid overloading the server. You can override this default by passing a second argument (in milliseconds) to `getAuthorData`. Aggressive use (very small delay) might still lead to temporary IP blocks from the website. Use responsibly.
* **Error Handling:** The `getAuthorData` function **does not throw** errors for operational failures (like invalid identifier, author not found, network issues, API errors). Instead, it **returns an object** with `status: 'error'` and an `error` object containing details (`code`, `message`). Always check the `status` field of the returned object to handle both success and error cases gracefully.
### License
MIT
---
## Русский
Node.js модуль (ESM) для получения информации профиля автора (описание, статистика, сборники и т.д.) и, опционально, всех его стихов с сайта русской поэзии stihirus.ru.
Модуль интеллектуально обрабатывает различные способы идентификации автора (ID, имя пользователя, URL поддомена, URL пути) и извлекает данные путем парсинга страницы автора и, если запрошено, запросов к внутреннему API, используемому сайтом. Функция возвращает структурированный ответ, указывающий на успех или неудачу операции.
### Установка
```bash
npm install stihirus-reader
```
Требуется Node.js v16 или выше.
### Использование
```javascript
// example.js
import { getAuthorData } from 'stihirus-reader';
async function main() {
// Выберите ОДИН тип идентификатора:
const identifier = 'oreh-orehov'; // 1. Имя пользователя
// const identifier = 14260; // 2. ID автора (число)
// const identifier = 'https://oreh-orehov.stihirus.ru/'; // 3. URL поддомена
// const identifier = 'https://stihirus.ru/avtor/oreh-orehov'; // 4. URL пути
const customDelayMs = 750; // Опционально: Пользовательская задержка между запросами к API (используется только если fetchAllPoems = true)
const fetchAllPoems = true; // Опционально: Установите false для получения только информации профиля
try {
console.log(`Получение данных для: ${identifier} (Загружать стихи: ${fetchAllPoems})`);
// Вызываем getAuthorData, опционально передавая задержку и флаг fetchAllPoems
const response = await getAuthorData(identifier, customDelayMs, fetchAllPoems);
// Или:
// const response = await getAuthorData(identifier); // Получить профиль + все стихи с задержкой по умолчанию
// const response = await getAuthorData(identifier, undefined, false); // Получить только профиль
// Проверяем поле status для определения успеха или неудачи
if (response.status === 'success') {
const authorData = response.data; // Получаем доступ к данным в случае успеха
console.log(`\n--- Автор: ${authorData.username} (ID: ${authorData.authorId}) ---`);
console.log(`URL профиля: ${authorData.profileUrl}`);
console.log(`Описание: ${authorData.description.substring(0, 100)}...`);
console.log(`Статус: ${authorData.status}`);
console.log(`Последний визит: ${authorData.lastVisit}`);
console.log(`Статистика: Стихи=${authorData.stats.poems}, Отправлено=${authorData.stats.reviewsSent}, Получено=${authorData.stats.reviewsReceived}`);
console.log(`Сборники: ${authorData.collections.map(c => c.name).join(', ') || 'Нет'}`);
console.log(`Длина массива стихов: ${authorData.poems.length}`); // Будет 0, если fetchAllPoems было false
if (authorData.poems.length > 0) {
console.log('\n--- Первые 3 стихотворения ---');
authorData.poems.slice(0, 3).forEach(poem => {
console.log(` Название: ${poem.title}`);
console.log(` Рубрика: ${poem.rubric.name}`);
console.log(` Сборник: ${poem.collection || 'N/A'}`);
console.log(` Создано: ${poem.created}`);
console.log(` Фрагмент: ${poem.text.substring(0, 70)}...`);
console.log(' ---');
});
} else if (fetchAllPoems) {
console.log('\n--- Стихи не найдены или не загружены ---');
} else {
console.log('\n--- Стихи не запрашивались (fetchAllPoems=false) ---');
}
// Полные данные доступны в объекте authorData
// console.log(JSON.stringify(authorData, null, 2));
} else if (response.status === 'error') {
const error = response.error; // Получаем доступ к деталям ошибки в случае неудачи
console.error(`\n--- ОШИБКА при получении данных для ${identifier} ---`);
console.error(`Код ошибки: ${error.code}`);
console.error(`Сообщение: ${error.message}`);
if (error.originalMessage) {
console.error(`Исходное сообщение: ${error.originalMessage}`);
}
}
} catch (error) {
// Ловим непредсказуемые ошибки (например, ошибки программирования в вызывающем коде)
// Примечание: Операционные ошибки (не найдено, проблемы с API) обрабатываются через response.status === 'error'
console.error(`\n--- НЕПРЕДВИДЕННАЯ ОШИБКА для ${identifier} ---`);
console.error(error);
}
}
main();
```
Асинхронно получает данные автора.
* **`identifier`**: `string | number` - Идентификатор автора. Может быть одним из:
1. **ID автора** (число, например, `14260`)
2. **Имя пользователя** (строка, например, `'oreh-orehov'`)
3. **URL поддомена** (строка, например, `'https://oreh-orehov.stihirus.ru/'`)
4. **URL пути** (строка, например, `'https://stihirus.ru/avtor/oreh-orehov'`)
* **`requestDelayMs`**: `number` (Опционально) - Задержка в миллисекундах между запросами к API при получении нескольких страниц стихов. Актуально только если `fetchAllPoems` равно `true`. По умолчанию `500` мс.
* **`fetchAllPoems`**: `boolean` (Опционально) - Загружать ли все стихи через API. Если `false`, будет получена только информация профиля (описание, статистика из HTML, сборники). По умолчанию `true`.
* **Возвращает**: `Promise<StihirusResponse>` - Promise, который разрешается объектом, указывающим результат операции. Проверьте поле `status`:
* Если `status` равен `'success'`, данные автора находятся в свойстве `data`. Массив `poems` будет пустым, если `fetchAllPoems` было `false`.
* Если `status` равен `'error'`, детали ошибки находятся в свойстве `error`.
* См. структуру `StihirusResponse` ниже.
**Структура возвращаемого объекта (`StihirusResponse`):**
```typescript
// Если операция прошла успешно:
{
status: 'success',
data: {
// Структура StihirusAuthorData (см. ниже)
authorId: number,
username: string,
profileUrl: string,
canonicalUsername: string,
description: string,
avatarUrl: string | null,
headerUrl: string | null,
status: string,
lastVisit: string,
stats: { /* StihirusAuthorStats */ },
collections: Array<{ /* StihirusCollectionInfo */ }>,
poems: Array<{ /* StihirusPoem */ }> // Будет пустым, если fetchAllPoems было false
}
}
// Если операция завершилась ошибкой:
{
status: 'error',
error: {
// Структура StihirusError (см. ниже)
code: number,
message: string,
originalMessage?: string
}
}
// --- Детальные структуры ---
// StihirusAuthorData (внутри response.data при успехе)
{
authorId: number, // Уникальный ID автора
username: string, // Отображаемое имя автора (из HTML)
profileUrl: string, // URL, использованный для получения HTML страницы профиля
canonicalUsername: string, // Имя пользователя, используемое в URL/API
description: string, // Описание профиля автора
avatarUrl: string | null, // URL аватара автора
headerUrl: string | null, // URL обложки профиля автора
status: string, // Статус автора (например, 'новенький')
lastVisit: string, // Текст о времени последнего визита (например, '5 минут назад')
stats: { // StihirusAuthorStats
poems: number, // Общее количество стихов (лучшая оценка из HTML или API, если стихи загружались)
reviewsSent: number, // Количество рецензий, отправленных автором
reviewsReceived: number // Количество рецензий, полученных автором
},
collections: Array<{ // StihirusCollectionInfo
name: string, // Название сборника
url: string // Полный URL страницы сборника
}>,
poems: Array<{ // StihirusPoem (Массив пуст, если fetchAllPoems было false)
id: number, // Уникальный ID стихотворения
title: string, // Название стихотворения (или '***')
text: string, // Полный текст стихотворения
created: string, // Строка с датой/временем создания (например, "27.03.2025 20:19")
rubric: { // StihirusPoemRubric
name: string, // Название общей рубрики (жанра)
url: string | null // Полный URL страницы рубрики
},
collection: string | null, // Название авторского сборника, если есть
rating: number, // Количество лайков
commentsCount: number, // Количество комментариев
imageUrl: string | null, // URL изображения для стихотворения, если есть
hasCertificate: boolean // Есть ли у стихотворения сертификат
}>
}
// StihirusError (внутри response.error при ошибке)
{
code: number, // HTTP статус или код ошибки (например, 404, 500, 400)
message: string, // Понятное сообщение об ошибке
originalMessage?: string // Опциональное исходное сообщение об ошибке (например, от fetch или ответа API)
}
```
* **Неофициальный:** Этот модуль использует веб-скрапинг и взаимодействует с внутренними API сайта. Это не официальный API, предоставляемый stihirus.ru. Изменения на веб-сайте могут нарушить работу модуля.
* **Ограничение запросов:** Модуль включает задержку по умолчанию (**500мс**) между вызовами API при постраничной загрузке стихов (когда `fetchAllPoems` равно `true`), чтобы избежать перегрузки сервера. Вы можете переопределить это значение, передав второй аргумент (в миллисекундах) в `getAuthorData`. Слишком агрессивное использование (очень маленькая задержка) все равно может привести к временной блокировке IP со стороны веб-сайта. Используйте ответственно.
* **Обработка ошибок:** Функция `getAuthorData` **не выбрасывает** ошибки при операционных сбоях (неверный идентификатор, автор не найден, проблемы с сетью, ошибки API). Вместо этого она **возвращает объект** с `status: 'error'` и объектом `error`, содержащим детали (`code`, `message`). Всегда проверяйте поле `status` возвращаемого объекта, чтобы корректно обрабатывать как успешные случаи, так и ошибки.
MIT