Skrypt do kopiowania opisów nagrań z sesji w MS Clarity

Strona główna » Skrypty » Skrypt do kopiowania opisów nagrań z sesji w MS Clarity
Instrukcja użycia

→ Ściągnij wtyczkę Tampermonkey do przeglądarki
→ W opcjach wtyczki włącz opcję „Allow User Scripts” (dzięki temu będzie można korzystać z własnych skryptów)
→ Wklej w Tampermonkey poniższy skrypt widgetu
→ Wejdź do Microsoft Clarity, do sekcji nagrań, i pobieraj opisy

Skrypt widgeta

// ==UserScript==
// @name         MS Clarity Recorder Helper - Raport Błędów
// @namespace    http://tampermonkey.net/
// @version      2026-02-17
// @description  Widget z raportem wygenerowanych opisów i błędów (np. zbyt krótka sesja)
// @author       Paweł Piekarski
// @match        https://clarity.microsoft.com/*
// @icon         https://clarity.microsoft.com/blog/wp-content/uploads/2025/02/siteIcon.png
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const clarityIconSvg = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M4 4L20 12L4 20V4Z" fill="#1960bf"/>
    </svg>`;

    let statystyki = { pominiete: 0, sukcesy: 0, oczekiwane: 0 };

    // --- LOGIKA WYDOBYWANIA DANYCH ---

    function pobierzDostepneNagrania() {
        const countEl = document.querySelector('.sessionCount strong');
        if (!countEl) return '...';
        return countEl.textContent.replace(/\D/g, '');
    }

    function pobierzUserId(card) {
        const badges = Array.from(card.querySelectorAll('div[class*="recordings_cardBadge"]'));
        const userBadge = badges.find(el => el.textContent.includes('User ID:'));
        if (userBadge) {
            const rawText = userBadge.textContent.replace('User ID:', '').trim();
            return rawText.split(/\s+/)[0];
        }
        return '';
    }

    function startProcess() {
        const limit = parseInt(document.getElementById('helper-limit').value) || 0;
        const cards = Array.from(document.querySelectorAll('div[data-testid="sessionCard"]')).slice(0, limit || undefined);

        statystyki = { pominiete: 0, sukcesy: 0, oczekiwane: cards.length };

        let index = 0;
        updateStatus(`Analizuję sesje...`);

        const procesuj = () => {
            if (index < cards.length) {
                const card = cards[index];
                const btn = card.querySelector('button[data-testid="sessionGenerateInsightsButton"]');

                if (!btn || btn.disabled) {
                    statystyki.pominiete++;
                } else {
                    btn.click();
                    statystyki.sukcesy++;
                }

                index++;
                updateStatus(`Klikanie: ${index}/${cards.length}`);
                setTimeout(procesuj, 800);
            } else {
                nasluchujOpisy();
            }
        };
        procesuj();
    }

    const BLAD_AI_TEKST = "I'm sorry, something went wrong";

    function czyBladAI(card) {
        const content = card.querySelector('div.insightsContent');
        return content && content.textContent.includes(BLAD_AI_TEKST);
    }

    function nasluchujOpisy() {
        let proba = 0;
        const intervalId = setInterval(() => {
            proba++;
            const cards = Array.from(document.querySelectorAll('div[data-testid="sessionCard"]')).slice(0, statystyki.oczekiwane);

            // Liczymy tylko karty z treścią ORAZ bez komunikatu błędu AI
            const aktualneSukcesy = cards.filter(card =>
                card.querySelector('div.insightsContent') && !czyBladAI(card)
            ).length;

            // Ile już "odpowiedziało" (sukces lub błąd AI) - do detekcji końca
            const juzOdpowiedziane = cards.filter(card => card.querySelector('div.insightsContent')).length;

            updateStatus(`Opisy: ${aktualneSukcesy}/${statystyki.oczekiwane}`);

            if (juzOdpowiedziane === statystyki.sukcesy || proba >= 40) {
                clearInterval(intervalId);
                pokazRaport(aktualneSukcesy, juzOdpowiedziane);
            }
        }, 1000);
    }

    // --- UI & RAPORT ---

    function updateStatus(msg) {
        document.getElementById('helper-status').textContent = `Status: ${msg}`;
        document.getElementById('helper-report').style.display = 'none';
    }

    function pokazRaport(finalneSukcesy, juzOdpowiedziane) {
        const reportEl = document.getElementById('helper-report');
        const statusEl = document.getElementById('helper-status');

        statusEl.textContent = `Status: Gotowe (${finalneSukcesy}/${statystyki.oczekiwane})`;

        const bledyAI = juzOdpowiedziane - finalneSukcesy;
        const linie = [];

        if (statystyki.pominiete > 0) {
            linie.push(`• ${statystyki.pominiete} pominięto — zbyt krótka sesja`);
        }
        if (bledyAI > 0) {
            linie.push(`• ${bledyAI} błąd generowania AI`);
        }

        if (linie.length > 0) {
            reportEl.innerHTML = linie.join('<br>');
            reportEl.style.display = 'block';
        }
    }

    function refreshTotalCount() {
        const el = document.getElementById('helper-max-val');
        if (el) el.textContent = pobierzDostepneNagrania();
    }

    const container = document.createElement('div');
    container.id = 'clarity-helper-widget';
    container.innerHTML = `
        <div style="background: #ffffff; border: 1px solid #ced4da; border-radius: 10px; padding: 15px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); width: 220px; font-family: sans-serif; color: #333;">
            <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; border-bottom: 1px solid #f0f0f0; padding-bottom: 8px;">
                <div style="display: flex; align-items: center;">
                    <div style="margin-right: 8px; display: flex; align-items: center;">${clarityIconSvg}</div>
                    <h3 style="margin: 0; font-size: 14px; color: #000;">Ściągnij opisy nagrań</h3>
                </div>
                <div style="display: flex; align-items: center;">
                    <button id="btn-toggle" title="Zwiń / Rozwiń" style="background: none; border: none; cursor: pointer; font-size: 14px; color: #888; line-height: 1; padding: 0 2px;">▼</button>
                </div>
            </div>

            <div id="helper-body">
                <label style="font-size: 11px; color: #888; font-weight: bold;">LICZBA NAGRAŃ (MAX: <span id="helper-max-val">...</span>):</label>
                <input type="number" id="helper-limit" value="10" style="width: 100%; padding: 6px; margin: 4px 0 12px 0; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box;">

                <div id="helper-status" style="font-size: 12px; margin-bottom: 4px; color: #1960bf; font-weight: 500; background: #f0f7ff; padding: 4px 8px; border-radius: 4px;">Status: Gotowy</div>
                <div id="helper-report" style="font-size: 10px; color: #d93025; margin-bottom: 12px; padding: 0 8px; display: none; line-height: 1.2;"></div>

                <button id="btn-generate" style="width: 100%; background: #1960bf; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-weight: bold; margin-bottom: 8px; font-size: 12px;">GENERUJ OPISY</button>
                <button id="btn-copy" style="width: 100%; background: #444; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">KOPIUJ JSON</button>
                
                <div style="margin-top: 15px; text-align: center; border-top: 1px solid #f0f0f0; padding-top: 12px;">
                    <a href="https://pawelpiekarski.pl" target="_blank" title="Odwiedź stronę">
                        <img src="https://pawelpiekarski.pl/wp-content/uploads/2024/02/Logo-pawelpiekarski.pl-v850.png" alt="Paweł Piekarski" style="max-width: 130px; height: auto; opacity: 0.8; transition: opacity 0.2s ease-in-out;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.8'">
                    </a>
                </div>
            </div>
        </div>
    `;

    Object.assign(container.style, { position: 'fixed', bottom: '20px', right: '20px', zIndex: '10000' });
    document.body.appendChild(container);

    // --- Toggle minimalizacji ---
    let zwiniety = false;
    document.getElementById('btn-toggle').addEventListener('click', () => {
        zwiniety = !zwiniety;
        const body = document.getElementById('helper-body');
        const toggleBtn = document.getElementById('btn-toggle');
        body.style.display = zwiniety ? 'none' : 'block';
        toggleBtn.textContent = zwiniety ? '▲' : '▼';
    });

    document.getElementById('btn-generate').addEventListener('click', startProcess);
    document.getElementById('btn-copy').addEventListener('click', () => {
        const limit = parseInt(document.getElementById('helper-limit').value) || 0;
        const cards = Array.from(document.querySelectorAll('div[data-testid="sessionCard"]')).slice(0, limit || undefined);
        const dane = cards.map(card => ({
            userId: pobierzUserId(card),
            description: (card.querySelector('div.insightsContent') && !czyBladAI(card))
                ? card.querySelector('div.insightsContent').textContent.trim()
                : 'BRAK OPISU'
        }));
        navigator.clipboard.writeText(JSON.stringify(dane, null, 2)).then(() => {
            const btn = document.getElementById('btn-copy');
            btn.textContent = "SKOPIOWANO!";
            setTimeout(() => btn.textContent = "KOPIUJ JSON", 2000);
        });
    });

    setInterval(refreshTotalCount, 3000);
    refreshTotalCount();
})();

Dodaj komentarz

Napisz do mnie

Paweł Piekarski
Paweł Piekarski PhD, Analityk eCommerce Marketing oparty na danych

Dane kontaktowe

+48 725 473 745

poczta@pawelpiekarski.pl

linkedin.com/in/pawelpiekarskipl

    Wysyłając wiadomość, zgadzasz się na kontakt i przetwarzanie danych zgodnie z polityką prywatności.