npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

stihirus-reader

v1.6.1

Published

stihirus.ru parser

Downloads

23

Readme

StihiRus Reader / Парсер StihiRus

English | Русский


English

Node.js module (ESM) to fetch author profile information (description, stats, collections), available filters (rubrics, dates), poems (all, specific page, or filtered), specific poem data, and homepage data (recommended authors, promo poems, etc.) from the Russian poetry website stihirus.ru.

It handles various author identifiers (ID, username, URL) and retrieves data by parsing HTML pages and querying internal APIs. Functions return a structured response indicating success or failure.

This library is intended for use in Node.js environments.

Installation

# From npm registry (recommended)
npm install @opexdevelop/stihirus-reader

# Or from GitHub Packages registry
# (Requires authentication or .npmrc configuration for the @opexdevelop scope)
# npm install @opexdevelop/stihirus-reader --registry=https://npm.pkg.github.com

Requires Node.js v16 or higher.

Usage (Node.js)

// example.js
import {
    getAuthorData,
    getAuthorFilters,
    getRecommendedAuthors,
    getPromoPoems,
    getWeeklyRatedAuthors,
    getActiveAuthors,
    getPoemById
} from '@opexdevelop/stihirus-reader';

async function runNodeExample(identifier, page = null, delay = 500) {
    console.log(`\n--- [Node.js] Fetching data for: ${identifier} | Page: ${page === null ? 'All' : page} ---`);
    try {
        // Fetch main author data (e.g., page 1)
        const response = await getAuthorData(identifier, page, delay);

        console.log("\n=== Author Data Response ===");
        if (response.status === 'success') {
            const d = response.data;
            console.log(`Status: ${response.status}`);
            console.log(`Author: ${d.username} (ID: ${d.authorId})`);
            console.log(`Premium: ${d.isPremium}`);
            console.log(`Profile URL: ${d.profileUrl}`);
            console.log(`Stats: ${JSON.stringify(d.stats)}`);
            console.log(`Collections (${d.collections.length}):`);
            console.log(d.collections.length > 0 ? JSON.stringify(d.collections.slice(0,2), null, 2) + (d.collections.length > 2 ? '\n...' : '') : '(No collections)');
            console.log(`Poems received: ${d.poems.length}`);
            if (d.poems.length > 0) {
                console.log(`--- First Poem Details ---`);
                console.log(JSON.stringify(d.poems[0], null, 2));
            }
        } else {
            console.error("Status: error");
            console.error("Error:", JSON.stringify(response.error, null, 2));
        }

        // Fetch available filters
        console.log(`\n--- [Node.js] Fetching filters for: ${identifier} ---`);
        const filtersResponse = await getAuthorFilters(identifier);

        console.log("\n=== Filters Response ===");
        if (filtersResponse.status === 'success') {
            const f = filtersResponse.data;
            console.log(`Status: ${filtersResponse.status}`);
            console.log(`Available Rubric Filters (${f.rubrics.length}):`);
            console.log(f.rubrics.length > 0 ? JSON.stringify(f.rubrics.slice(0,3), null, 2) + (f.rubrics.length > 3 ? '\n...' : '') : '(No rubric filters)');
            console.log(`\nAvailable Date Filters (${f.dates.length}):`);
            console.log(f.dates.length > 0 ? JSON.stringify(f.dates.slice(0,3), null, 2) + (f.dates.length > 3 ? '\n...' : '') : '(No date filters)');

            // Example: Fetch filtered poems using the first available rubric filter
            if (f.rubrics.length > 0) {
                const firstRubric = f.rubrics[0];
                console.log(`\n--- Fetching Filtered Poems (Rubric: ${firstRubric.name}) ---`);
                const filteredResponse = await getAuthorData(identifier, null, delay, { rubricId: firstRubric.id });
                 if (filteredResponse.status === 'success') {
                     console.log(`Status: success`);
                     console.log(`Found ${filteredResponse.data.poems.length} poems in rubric ${firstRubric.id}.`);
                     if (filteredResponse.data.poems.length > 0) {
                         console.log("First filtered poem:", JSON.stringify(filteredResponse.data.poems[0], null, 2));
                     }
                 } else {
                     console.error("Status: error");
                     console.error("Error fetching filtered poems:", JSON.stringify(filteredResponse.error, null, 2));
                 }
            }

        } else {
             console.error("Status: error");
             console.error("Error fetching filters:", JSON.stringify(filtersResponse.error, null, 2));
        }

        // Fetch Homepage Data
        console.log(`\n--- Fetching Homepage Data ---`);
        const [recAuthors, promoPoems, weeklyAuthors, activeAuthors] = await Promise.all([
            getRecommendedAuthors(),
            getPromoPoems(),
            getWeeklyRatedAuthors(),
            getActiveAuthors()
        ]);

        console.log("\n=== Recommended Authors ===");
        if(recAuthors.status === 'success') console.log(JSON.stringify(recAuthors.data.slice(0,3), null, 2) + (recAuthors.data.length > 3 ? '\n...' : '')); else console.error(recAuthors.error);
        console.log("\n=== Promo Poems ===");
        if(promoPoems.status === 'success') console.log(JSON.stringify(promoPoems.data.slice(0,3), null, 2) + (promoPoems.data.length > 3 ? '\n...' : '')); else console.error(promoPoems.error);
        console.log("\n=== Weekly Rated Authors ===");
        if(weeklyAuthors.status === 'success') console.log(JSON.stringify(weeklyAuthors.data.slice(0,3), null, 2) + (weeklyAuthors.data.length > 3 ? '\n...' : '')); else console.error(weeklyAuthors.error);
        console.log("\n=== Active Authors ===");
        if(activeAuthors.status === 'success') console.log(JSON.stringify(activeAuthors.data.slice(0,3), null, 2) + (activeAuthors.data.length > 3 ? '\n...' : '')); else console.error(activeAuthors.error);

        // Fetch Single Poem
        const poemIdToFetch = 317868; // Example ID
        console.log(`\n--- Fetching Single Poem (ID: ${poemIdToFetch}) ---`);
        const poemResp = await getPoemById(poemIdToFetch);
        console.log("\n=== Single Poem Response ===");
        if(poemResp.status === 'success') console.log(JSON.stringify(poemResp.data, null, 2)); else console.error(poemResp.error);


    } catch (error) {
        console.error(`\n!!! UNEXPECTED SCRIPT ERROR for ${identifier}:`, error);
    } finally {
        console.log(`\n--- Fetching finished for: ${identifier} ---`);
    }
}

async function main() {
    const AUTHOR_IDENTIFIER = 'oreh-orehov'; // Or 14260, or URL like 'https://...'
    const PAGE_TO_FETCH = 1; // null = all pages, 0 = profile only, N > 0 = page N
    const REQUEST_DELAY_MS = 200; // Delay between requests when fetching all pages

    await runNodeExample(AUTHOR_IDENTIFIER, PAGE_TO_FETCH, REQUEST_DELAY_MS);
}

main();

API

getAuthorData(identifier, [page], [requestDelayMs], [filterOptions])

Asynchronously fetches author profile and poem data.

  • identifier: string | number - Author ID, username, subdomain URL, or path URL.
  • page: number | null (Optional) - Controls poem fetching: null (all), 0 (profile only), N > 0 (page N). Default: null.
  • requestDelayMs: number (Optional) - Delay between API requests when fetching all pages (page = null) sequentially (used only when filters are active, otherwise parallel fetching is used). Default: 200.
  • filterOptions: StihirusFilterOptions | null (Optional) - Object to filter poems: { rubricId?: number, year?: number, month?: number }. Get available IDs/dates from getAuthorFilters. Default: null.
  • Returns: Promise<StihirusResponse> - Check status field ('success' or 'error').

getAuthorFilters(identifier)

Asynchronously fetches available filter options (rubrics and dates) for an author's poems.

  • identifier: string | number - Author ID, username, subdomain URL, or path URL.
  • Returns: Promise<StihirusFiltersResponse> - Check status field ('success' or 'error').

getPoemById(poemId)

Asynchronously fetches data for a single poem by its ID using the API.

  • poemId: number - The unique ID of the poem.
  • Returns: Promise<StihirusSinglePoemResponse> - Check status field ('success' or 'error').

Homepage Functions

These functions fetch data by parsing the stihirus.ru homepage.

  • getRecommendedAuthors(): Gets recommended authors.
  • getPromoPoems(): Gets promo poems.
  • getWeeklyRatedAuthors(): Gets weekly rated authors.
  • getActiveAuthors(): Gets active authors.
  • Returns: Promise<StihirusHomepageResponse<DataStructure>> - Where DataStructure is StihirusHomepageAuthor or StihirusHomepagePoem. Check status field.

Returned Object Structures:

// See index.d.ts for detailed interface definitions.

// Example StihirusPoem structure:
interface StihirusPoem {
  id: number;
  title: string;
  text: string;
  created: string; // "DD.MM.YYYY HH:MM"
  rubric: { name: string; url: string | null };
  collection: string | null;
  rating: number;
  commentsCount: number;
  imageUrl: string | null;
  hasCertificate: boolean;
  gifts: string[]; // e.g. [] or ["proizv_podarok1"]
  uniquenessStatus: -1 | 0 | 1; // -1: checking, 0: not unique, 1: unique/certified
  contest?: { id: number; name: string } | null;
  holidaySection?: { id: number; url: string | null; title: string } | null;
  author?: {
      id: number;
      username: string;
      uri?: string;
      profileUrl: string;
      avatarUrl?: string | null;
  } | null;
}

// Example StihirusAuthorData structure:
interface StihirusAuthorData {
  authorId: number;
  username: string;
  profileUrl: string;
  canonicalUsername: string;
  description: string;
  avatarUrl: string | null;
  headerUrl: string | null;
  status: string;
  lastVisit: string;
  stats: { poems: number; reviewsSent: number; reviewsReceived: number; subscribers: number };
  collections: { name: string; url: string }[];
  isPremium: boolean;
  poems: StihirusPoem[];
}

Important Notes

  • Unofficial: Uses web scraping and internal APIs. Site changes may break it.
  • Rate Limiting: Default 200ms delay is used when fetching multiple pages sequentially (with filters). Parallel fetching is used for unrestricted downloads. Use responsibly.
  • Error Handling: Returns { status: 'error', error: {...} } for operational errors, does not throw them. Always check status.
  • Environment: This library is designed for Node.js.

License

MIT


Русский

Node.js модуль (ESM) для получения информации профиля автора (описание, статистика, сборники), доступных фильтров (рубрики, даты), стихов (всех, конкретной страницы или отфильтрованных), данных конкретного стихотворения и данных с главной страницы (рекомендуемые авторы, промо-стихи и т.д.) с сайта русской поэзии stihirus.ru.

Обрабатывает различные идентификаторы автора (ID, имя пользователя, URL) и извлекает данные путем парсинга HTML-страниц и запросов к внутренним API. Функции возвращают структурированный ответ, указывающий на успех или неудачу.

Эта библиотека предназначена для использования в среде Node.js.

Установка

# Из реестра npm (рекомендуется)
npm install @opexdevelop/stihirus-reader

# Или из реестра GitHub Packages
# (Требует аутентификации или настройки .npmrc для области @opexdevelop)
# npm install @opexdevelop/stihirus-reader --registry=https://npm.pkg.github.com

Требуется Node.js v16 или выше.

Использование (Node.js)

// example.js
import {
    getAuthorData,
    getAuthorFilters,
    getRecommendedAuthors,
    getPromoPoems,
    getWeeklyRatedAuthors,
    getActiveAuthors,
    getPoemById
} from '@opexdevelop/stihirus-reader';

async function runNodeExample(identifier, page = null, delay = 500) {
    console.log(`\n--- [Node.js] Получение данных для: ${identifier} | Страница: ${page === null ? 'Все' : page} ---`);
    try {
        // Получаем основные данные автора (например, стр. 1)
        const response = await getAuthorData(identifier, page, delay);

        console.log("\n=== Ответ с данными автора ===");
        if (response.status === 'success') {
            const d = response.data;
            console.log(`Статус: ${response.status}`);
            console.log(`Автор: ${d.username} (ID: ${d.authorId})`);
            console.log(`Премиум: ${d.isPremium}`);
            console.log(`URL профиля: ${d.profileUrl}`);
            console.log(`Статистика: ${JSON.stringify(d.stats)}`);
            console.log(`Сборники (${d.collections.length}):`);
            console.log(d.collections.length > 0 ? JSON.stringify(d.collections.slice(0,2), null, 2) + (d.collections.length > 2 ? '\n...' : '') : '(Нет сборников)');
            console.log(`Получено стихов: ${d.poems.length}`);
            if (d.poems.length > 0) {
                console.log(`--- Детали первого стихотворения ---`);
                console.log(JSON.stringify(d.poems[0], null, 2));
            }
        } else {
            console.error("Статус: error");
            console.error("Ошибка:", JSON.stringify(response.error, null, 2));
        }

        // Получаем доступные фильтры
        console.log(`\n--- [Node.js] Получение фильтров для: ${identifier} ---`);
        const filtersResponse = await getAuthorFilters(identifier);

        console.log("\n=== Ответ с фильтрами ===");
        if (filtersResponse.status === 'success') {
            const f = filtersResponse.data;
            console.log(`Статус: ${filtersResponse.status}`);
            console.log(`Доступные фильтры по рубрикам (${f.rubrics.length}):`);
            console.log(f.rubrics.length > 0 ? JSON.stringify(f.rubrics.slice(0,3), null, 2) + (f.rubrics.length > 3 ? '\n...' : '') : '(Нет фильтров по рубрикам)');
            console.log(`\nДоступные фильтры по датам (${f.dates.length}):`);
            console.log(f.dates.length > 0 ? JSON.stringify(f.dates.slice(0,3), null, 2) + (f.dates.length > 3 ? '\n...' : '') : '(Нет фильтров по датам)');

            // Пример: Получение отфильтрованных стихов по первому доступному фильтру рубрики
            if (f.rubrics.length > 0) {
                const firstRubric = f.rubrics[0];
                console.log(`\n--- Получение отфильтрованных стихов (Рубрика: ${firstRubric.name}) ---`);
                const filteredResponse = await getAuthorData(identifier, null, delay, { rubricId: firstRubric.id });
                 if (filteredResponse.status === 'success') {
                     console.log(`Статус: success`);
                     console.log(`Найдено ${filteredResponse.data.poems.length} стихов в рубрике ${firstRubric.id}.`);
                     if (filteredResponse.data.poems.length > 0) {
                         console.log("Первое отфильтрованное стихотворение:", JSON.stringify(filteredResponse.data.poems[0], null, 2));
                     }
                 } else {
                     console.error("Статус: error");
                     console.error("Ошибка получения отфильтрованных стихов:", JSON.stringify(filteredResponse.error, null, 2));
                 }
            }

        } else {
             console.error("Статус: error");
             console.error("Ошибка получения фильтров:", JSON.stringify(filtersResponse.error, null, 2));
        }

        // Получение данных с главной страницы
        console.log(`\n--- Получение данных с главной страницы ---`);
        const [recAuthors, promoPoems, weeklyAuthors, activeAuthors] = await Promise.all([
            getRecommendedAuthors(),
            getPromoPoems(),
            getWeeklyRatedAuthors(),
            getActiveAuthors()
        ]);

        console.log("\n=== Рекомендуемые авторы ===");
        if(recAuthors.status === 'success') console.log(JSON.stringify(recAuthors.data.slice(0,3), null, 2) + (recAuthors.data.length > 3 ? '\n...' : '')); else console.error(recAuthors.error);
        console.log("\n=== Промо стихи ===");
        if(promoPoems.status === 'success') console.log(JSON.stringify(promoPoems.data.slice(0,3), null, 2) + (promoPoems.data.length > 3 ? '\n...' : '')); else console.error(promoPoems.error);
        console.log("\n=== Рейтинг недели ===");
        if(weeklyAuthors.status === 'success') console.log(JSON.stringify(weeklyAuthors.data.slice(0,3), null, 2) + (weeklyAuthors.data.length > 3 ? '\n...' : '')); else console.error(weeklyAuthors.error);
        console.log("\n=== Активные авторы ===");
        if(activeAuthors.status === 'success') console.log(JSON.stringify(activeAuthors.data.slice(0,3), null, 2) + (activeAuthors.data.length > 3 ? '\n...' : '')); else console.error(activeAuthors.error);

        // Получение одного стихотворения
        const poemIdToFetch = 317868; // Пример ID
        console.log(`\n--- Получение стихотворения (ID: ${poemIdToFetch}) ---`);
        const poemResp = await getPoemById(poemIdToFetch);
        console.log("\n=== Ответ по стихотворению ===");
        if(poemResp.status === 'success') console.log(JSON.stringify(poemResp.data, null, 2)); else console.error(poemResp.error);


    } catch (error) {
        console.error(`\n!!! НЕПРЕДВИДЕННАЯ ОШИБКА СКРИПТА для ${identifier}:`, error);
    } finally {
        console.log(`\n--- Получение данных завершено для: ${identifier} ---`);
    }
}

async function main() {
    const AUTHOR_IDENTIFIER = 'oreh-orehov'; // Или 14260, или URL
    const PAGE_TO_FETCH = 1; // null = все страницы, 0 = только профиль, N > 0 = страница N
    const REQUEST_DELAY_MS = 200; // Задержка между запросами при получении всех страниц

    await runNodeExample(AUTHOR_IDENTIFIER, PAGE_TO_FETCH, REQUEST_DELAY_MS);
}

main();

API

getAuthorData(identifier, [page], [requestDelayMs], [filterOptions])

Асинхронно получает данные профиля и стихи автора.

  • identifier: string | number - ID автора, имя пользователя, URL поддомена или URL пути.
  • page: number | null (Опционально) - Управляет загрузкой стихов: null (все), 0 (только профиль), N > 0 (страница N). По умолчанию: null.
  • requestDelayMs: number (Опционально) - Задержка между запросами API при загрузке всех страниц (page = null) последовательно (используется только при активных фильтрах, иначе используется параллельная загрузка). По умолчанию: 200.
  • filterOptions: StihirusFilterOptions | null (Опционально) - Объект для фильтрации стихов: { rubricId?: number, year?: number, month?: number }. Доступные ID/даты можно получить через getAuthorFilters. По умолчанию: null.
  • Возвращает: Promise<StihirusResponse> - Проверьте поле status ('success' или 'error').

getAuthorFilters(identifier)

Асинхронно получает доступные опции фильтрации (рубрики и даты) для стихов автора.

  • identifier: string | number - ID автора, имя пользователя, URL поддомена или URL пути.
  • Возвращает: Promise<StihirusFiltersResponse> - Проверьте поле status ('success' или 'error').

getPoemById(poemId)

Асинхронно получает данные одного стихотворения по его ID используя API.

  • poemId: number - Уникальный ID стихотворения.
  • Возвращает: Promise<StihirusSinglePoemResponse> - Проверьте поле status ('success' или 'error').

Функции для главной страницы

Эти функции получают данные путем парсинга главной страницы stihirus.ru.

  • getRecommendedAuthors(): Получает рекомендуемых авторов.
  • getPromoPoems(): Получает промо-стихи.
  • getWeeklyRatedAuthors(): Получает авторов из рейтинга недели.
  • getActiveAuthors(): Получает активных авторов.
  • Возвращает: Promise<StihirusHomepageResponse<DataStructure>> - Где DataStructure это StihirusHomepageAuthor или StihirusHomepagePoem. Проверьте поле status.

Структуры возвращаемых объектов:

// Смотрите index.d.ts для детальных определений интерфейсов.

// Пример структуры StihirusPoem:
interface StihirusPoem {
  id: number;
  title: string;
  text: string;
  created: string; // "DD.MM.YYYY HH:MM"
  rubric: { name: string; url: string | null };
  collection: string | null;
  rating: number;
  commentsCount: number;
  imageUrl: string | null;
  hasCertificate: boolean;
  gifts: string[]; // напр. [] или ["proizv_podarok1"]
  uniquenessStatus: -1 | 0 | 1; // -1: проверка, 0: неуникально, 1: уникально/сертификат
  contest?: { id: number; name: string } | null;
  holidaySection?: { id: number; url: string | null; title: string } | null;
  author?: {
      id: number;
      username: string;
      uri?: string;
      profileUrl: string;
      avatarUrl?: string | null;
  } | null;
}

// Пример структуры StihirusAuthorData:
interface StihirusAuthorData {
  authorId: number;
  username: string;
  profileUrl: string;
  canonicalUsername: string;
  description: string;
  avatarUrl: string | null;
  headerUrl: string | null;
  status: string;
  lastVisit: string;
  stats: { poems: number; reviewsSent: number; reviewsReceived: number; subscribers: number };
  collections: { name: string; url: string }[];
  isPremium: boolean;
  poems: StihirusPoem[];
}

Важные замечания

  • Неофициальный: Использует веб-скрапинг и внутренние API. Изменения на сайте могут сломать модуль.
  • Ограничение запросов: Задержка 200мс по умолчанию используется при последовательной загрузке всех страниц (с фильтрами). Параллельная загрузка используется для скачивания без ограничений. Используйте ответственно.
  • Обработка ошибок: Возвращает { status: 'error', error: {...} } при операционных ошибках, не выбрасывает их. Всегда проверяйте status.
  • Среда выполнения: Библиотека предназначена для Node.js.

Лицензия

MIT