tail-fin-xhs 0.7.8

Xiaohongshu adapter for tail-fin: search, notes, comments, feed
Documentation
pub const INJECT_FEED: &str = r#"(() => {
    try {
        window.__TF_CAPTURED = [];
        const app = document.querySelector('#app')?.__vue_app__;
        if (!app) return { injected: false, error: 'no Vue app found' };
        const pinia = app.config.globalProperties.$pinia;
        if (!pinia) return { injected: false, error: 'no Pinia instance found' };

        let feedStore = null;
        for (const [id, store] of pinia._s) {
            if (id.includes('feed') || id.includes('home') || id.includes('explore')) {
                feedStore = store;
                break;
            }
        }
        if (!feedStore) return { injected: false, error: 'no feed/home/explore store found' };

        // Read existing state
        const state = feedStore.$state || feedStore;
        const existing = state.feeds || state.items || state.list || [];
        if (Array.isArray(existing) && existing.length > 0) {
            window.__TF_CAPTURED.push(...existing);
        }

        // Hook $onAction for future updates
        feedStore.$onAction(({ after }) => {
            after((result) => {
                if (!result) return;
                let items = null;
                if (Array.isArray(result)) {
                    items = result;
                } else if (result.items && Array.isArray(result.items)) {
                    items = result.items;
                } else if (result.data && result.data.items && Array.isArray(result.data.items)) {
                    items = result.data.items;
                }
                if (items) {
                    window.__TF_CAPTURED.push(...items);
                }
            });
        });

        return { injected: true, count: window.__TF_CAPTURED.length };
    } catch (e) {
        return { injected: false, error: String(e) };
    }
})()"#;

pub const COLLECT_FEED: &str = r#"(() => {
    const captured = window.__TF_CAPTURED || [];
    return captured.map((raw) => {
        const item = raw.note_card || raw.noteCard || raw;
        return {
            id: item.id || item.note_id || '',
            title: item.title || item.display_title || '',
            author: item.author || (item.user && (item.user.nickname || item.user.name)) || '',
            likes: item.likes || item.liked_count || item.interact_info?.liked_count || 0,
            noteType: item.noteType || item.note_type || item.type || 'normal',
            url: item.url || (item.id ? `https://www.xiaohongshu.com/explore/${item.id}` : ''),
        };
    });
})()"#;

pub const INJECT_NOTIFICATIONS: &str = r#"(() => {
    try {
        window.__TF_CAPTURED_NOTIF = [];
        const app = document.querySelector('#app')?.__vue_app__;
        if (!app) return { injected: false, error: 'no Vue app found' };
        const pinia = app.config.globalProperties.$pinia;
        if (!pinia) return { injected: false, error: 'no Pinia instance found' };

        let notifStore = null;
        for (const [id, store] of pinia._s) {
            if (id.includes('notification') || id.includes('message') || id.includes('notice')) {
                notifStore = store;
                break;
            }
        }
        if (!notifStore) return { injected: false, error: 'no notification/message/notice store found' };

        // Read existing state
        const state = notifStore.$state || notifStore;
        const existing = state.notifications || state.messages || state.notices || state.list || state.items || [];
        if (Array.isArray(existing) && existing.length > 0) {
            window.__TF_CAPTURED_NOTIF.push(...existing);
        }

        // Hook $onAction for future updates
        notifStore.$onAction(({ after }) => {
            after((result) => {
                if (!result) return;
                let items = null;
                if (Array.isArray(result)) {
                    items = result;
                } else if (result.items && Array.isArray(result.items)) {
                    items = result.items;
                } else if (result.data && result.data.items && Array.isArray(result.data.items)) {
                    items = result.data.items;
                }
                if (items) {
                    window.__TF_CAPTURED_NOTIF.push(...items);
                }
            });
        });

        return { injected: true, count: window.__TF_CAPTURED_NOTIF.length };
    } catch (e) {
        return { injected: false, error: String(e) };
    }
})()"#;

pub const COLLECT_NOTIFICATIONS: &str = r#"(() => {
    const captured = window.__TF_CAPTURED_NOTIF || [];
    return captured.map((raw) => {
        return {
            notificationType: raw.notificationType || raw.notification_type || raw.type || 'unknown',
            user: raw.user || (raw.user_info && (raw.user_info.nickname || raw.user_info.name)) || '',
            content: raw.content || raw.note_content || raw.body || '',
            time: raw.time || raw.created_at || raw.timestamp || '',
            targetNoteId: raw.targetNoteId || raw.target_note_id || raw.note_id || null,
        };
    });
})()"#;