tail-fin-xhs 0.7.8

Xiaohongshu adapter for tail-fin: search, notes, comments, feed
Documentation
pub const JS: &str = r#"(async () => {
    const loginPattern = /登录后查看搜索结果|登录后查看|请登录/;
    if (loginPattern.test(document.body.innerText)) {
        return { loginWall: true, notFound: false, notes: [] };
    }

    // Wait for content via MutationObserver (section.note-item or __INITIAL_STATE__.search.notes)
    await new Promise((resolve) => {
        const timeout = setTimeout(resolve, 5000);

        function check() {
            if (document.querySelector('section.note-item')) {
                clearTimeout(timeout);
                resolve();
                return true;
            }
            const state = window.__INITIAL_STATE__;
            if (state && state.search && state.search.notes && state.search.notes.length > 0) {
                clearTimeout(timeout);
                resolve();
                return true;
            }
            return false;
        }

        if (check()) return;

        const observer = new MutationObserver(() => {
            if (check()) observer.disconnect();
        });
        observer.observe(document.body, { childList: true, subtree: true });
    });

    if (loginPattern.test(document.body.innerText)) {
        return { loginWall: true, notFound: false, notes: [] };
    }

    const state = window.__INITIAL_STATE__;
    const searchState = state && state.search;

    if (!searchState) {
        return { loginWall: false, notFound: false, notes: [] };
    }

    function decodeObjectIdDate(id) {
        try {
            const hex = String(id).substring(0, 8);
            const ts = parseInt(hex, 16) * 1000;
            if (!isNaN(ts) && ts > 0) {
                return new Date(ts).toISOString().split('T')[0];
            }
        } catch (_) {}
        return '';
    }

    function extractNote(item) {
        const card = item.noteCard || item.note_card || item;
        const noteId = card.noteId || card.note_id || card.id || '';
        const title = card.displayTitle || card.title || '';
        const user = card.user || card.author || {};
        const author = user.nickname || user.name || '';
        const interactInfo = card.interactInfo || card.interact_info || {};
        const likes = parseInt(interactInfo.likedCount || interactInfo.liked_count || '0', 10) || 0;
        const cover = card.cover || {};
        const coverImage = cover.urlDefault || cover.url || null;
        const publishedAt = decodeObjectIdDate(noteId);
        const url = noteId ? `https://www.xiaohongshu.com/explore/${noteId}` : '';
        return { id: noteId, title, author, likes, coverImage, publishedAt, url };
    }

    let rawNotes = [];

    if (Array.isArray(searchState.notes) && searchState.notes.length > 0) {
        rawNotes = searchState.notes;
    } else if (searchState.feedsMap && typeof searchState.feedsMap === 'object') {
        for (const key of Object.keys(searchState.feedsMap)) {
            const feed = searchState.feedsMap[key];
            const items = feed.items || feed.feeds || [];
            rawNotes = rawNotes.concat(items);
        }
    }

    const notes = rawNotes.map(extractNote).filter(n => n.id);

    return { loginWall: false, notFound: false, notes };
})()
"#;