Test-English Simple Progress Tracker

Test-English egzersizlerini basit şekilde takip eder.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Test-English Simple Progress Tracker
// @namespace    https://test-english.com/
// @version      1.0.0
// @description  Test-English egzersizlerini basit şekilde takip eder.
// @match        https://test-english.com/*
// @match        https://www.test-english.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // ============================================================
    // AYARLAR
    // ============================================================

    const STORAGE_KEY = 'testEnglishSimpleProgress';


    // ============================================================
    // VERİ
    // ============================================================

    function getData() {
        return GM_getValue(STORAGE_KEY, {});
    }

    function saveData(data) {
        GM_setValue(STORAGE_KEY, data);
    }


    // ============================================================
    // SAYFA BİLGİSİ
    // ============================================================

    function getPageKey() {
        return window.location.pathname
            .replace(/\/+$/, '');
    }


    function getExerciseNumber() {

        const path = getPageKey();

        // Örnek:
        // /grammar-points/a1/present-simple-forms-of-to-be/
        // = Exercise 1

        // Örnek:
        // /grammar-points/a1/present-simple-forms-of-to-be/2/
        // = Exercise 2

        const match = path.match(/\/(\d+)$/);

        if (match) {
            return parseInt(match[1], 10);
        }

        return 1;
    }


    function getTitle() {

        const h1 = document.querySelector('h1');

        if (h1) {
            return h1.textContent
                .trim()
                .replace(/\s+/g, ' ');
        }

        return document.title
            .replace(/\s*\|\s*Test-English.*$/i, '')
            .trim();
    }


    // ============================================================
    // SAYFANIN DAHA ÖNCE ÇÖZÜLÜP ÇÖZÜLMEDİĞİNİ KONTROL ET
    // ============================================================

    function getCurrentResult() {

        const data = getData();

        const pageKey = getPageKey();

        return data[pageKey] || null;
    }
    function getTotalStatistics() {

        const data = getData();

        let totalSolved = 0;
        let totalCorrect = 0;
        let totalWrong = 0;

        Object.values(data).forEach(function (result) {

            if (!result || !result.completed) {
                return;
            }

            /*
         * Doğru ve yanlış bilgisi girilmişse
         * bu exercise'in sorularını toplama dahil et.
         */

            if (
                typeof result.correct === 'number' &&
                typeof result.wrong === 'number'
            ) {

                totalCorrect += result.correct;

                totalWrong += result.wrong;

                totalSolved +=
                    result.correct +
                    result.wrong;
            }

        });

        return {
            totalSolved: totalSolved,
            totalCorrect: totalCorrect,
            totalWrong: totalWrong
        };
    }

    // ============================================================
    // EGZERSİZİ TAMAMLANDI OLARAK KAYDET
    // ============================================================

    function markCompleted() {

        const data = getData();

        const pageKey = getPageKey();

        const exercise = getExerciseNumber();

        /*
     * Test-English sonuç ekranındaki
     * "Correct answers: 9/10" bilgisini yakala.
     */

        const text =
              document.body.innerText || '';

        const match =
              text.match(
                  /Correct answers:\s*(\d+)\s*\/\s*(\d+)/i
              );

        let correct = null;
        let wrong = null;

        if (match) {

            const total =
                  parseInt(match[2], 10);

            correct =
                parseInt(match[1], 10);

            wrong =
                total - correct;
        }


        /*
     * Skoru da yakala.
     */

        const scoreMatch =
              text.match(
                  /Your score is\s*(\d+(?:\.\d+)?)\s*%/i
              );

        let score = null;

        if (scoreMatch) {

            score =
                parseFloat(
                scoreMatch[1]
            );
        }


        /*
     * Sonucu kaydet.
     */

        data[pageKey] = {

            title: getTitle(),

            exercise: exercise,

            completed: true,

            correct: correct,

            wrong: wrong,

            score: score,

            completedAt:
            new Date().toISOString()

        };


        saveData(data);

        showCompletedMessage();

        createManualScorePanel();
    }

    // ============================================================
    // CHECK ANSWERS BUTONUNU BUL
    // ============================================================

    function findCheckAnswersButton() {

        const elements =
              document.querySelectorAll(
                  'button, input[type="button"], input[type="submit"], a'
              );

        for (const element of elements) {

            const text =
                  (
                      element.innerText ||
                      element.value ||
                      ''
                  )
            .trim()
            .toLowerCase();

            if (
                text.includes('check answers')
            ) {
                return element;
            }
        }

        return null;
    }


    // ============================================================
    // BUTONA TIKLAMAYI DİNLE
    // ============================================================

    function setupCheckAnswers() {

        const button =
              findCheckAnswersButton();

        if (!button) {
            return;
        }


        if (
            button.dataset.teTrackerAttached ===
            'true'
        ) {
            return;
        }


        button.dataset.teTrackerAttached =
            'true';


        button.addEventListener(
            'click',
            function () {

                /*
             * Test-English'in sonucu oluşturması
             * için kısa bir süre bekle.
             */

                const delays = [
                    300,
                    700,
                    1200,
                    2000
                ];


                delays.forEach(
                    function (delay) {

                        setTimeout(
                            function () {

                                const text =
                                      document.body.innerText ||
                                      '';

                                if (
                                    /Test completed!/i.test(
                                        text
                                    ) &&
                                    /Correct answers:/i.test(
                                        text
                                    )
                                ) {

                                    markCompleted();

                                }

                            },
                            delay
                        );

                    }
                );

            }
        );
    }

    // ============================================================
    // TAMAMLANDI MESAJI
    // ============================================================

    function showCompletedMessage() {

        const old =
              document.getElementById(
                  'te-completed-message'
              );

        if (old) {
            old.remove();
        }


        const statistics =
              getTotalStatistics();


        const message =
              document.createElement('div');

        message.id =
            'te-completed-message';


        message.innerHTML = `

        <div class="te-completed-icon">
            ✓
        </div>

        <div class="te-completed-content">

            <strong>
                Bu test daha önce çözüldü
            </strong>

            <div class="te-completed-small">
                Exercise ${getExerciseNumber()}
            </div>

            <div class="te-total-statistics">

                📊 Toplam çözülen:
                <strong>
                    ${statistics.totalSolved}
                </strong>

                <br>

                <span>
                    Doğru:
                    ${statistics.totalCorrect}
                </span>

                ·

                <span>
                    Yanlış:
                    ${statistics.totalWrong}
                </span>

            </div>

        </div>
    `;


        const header =
              document.querySelector('header');


        if (header) {

            header.insertAdjacentElement(
                'afterend',
                message
            );

        } else {

            document.body.prepend(
                message
            );

        }
    }
    // ============================================================
    // MANUEL DOĞRU / YANLIŞ GİRİŞİ
    // ============================================================

    function createManualScorePanel() {

        const result =
              getCurrentResult();

        if (
            !result ||
            !result.completed
        ) {
            return;
        }


        const old =
              document.getElementById(
                  'te-score-panel'
              );

        if (old) {
            old.remove();
        }


        const panel =
              document.createElement(
                  'div'
              );

        panel.id =
            'te-score-panel';


        panel.innerHTML = `

        <div class="te-score-title">
            Test sonucu
        </div>

        <div class="te-score-row">

            <label>
                Doğru
            </label>

            <input
                id="te-correct-input"
                type="number"
                min="0"
                value="${
                    result.correct !== null
            ? result.correct
        : ''
    }"
            >

        </div>


        <div class="te-score-row">

            <label>
                Yanlış
            </label>

            <input
                id="te-wrong-input"
                type="number"
                min="0"
                value="${
                    result.wrong !== null
            ? result.wrong
        : ''
    }"
            >

        </div>


        <div
            id="te-score-display"
            class="te-score-display"
        >
            ${
                result.score !== null
            ? 'Skor: ' + result.score + '%'
        : ''
    }
        </div>


        <button
            id="te-save-score"
        >
            Sonucu Kaydet
        </button>

    `;


        /*
     * Tamamlandı mesajının hemen altına koy.
     */

        const completedMessage =
              document.getElementById(
                  'te-completed-message'
              );


        if (completedMessage) {

            completedMessage.insertAdjacentElement(
                'afterend',
                panel
            );

        } else {

            document.body.prepend(
                panel
            );

        }


        document
            .getElementById(
            'te-save-score'
        )
            .addEventListener(
            'click',
            saveManualScore
        );
    }

    function saveManualScore() {

        const data =
              getData();

        const pageKey =
              getPageKey();

        if (
            !data[pageKey]
        ) {
            return;
        }


        const correctInput =
              document.getElementById(
                  'te-correct-input'
              );

        const wrongInput =
              document.getElementById(
                  'te-wrong-input'
              );


        const correct =
              correctInput.value === ''
        ? null
        : parseInt(
            correctInput.value,
            10
        );


        const wrong =
              wrongInput.value === ''
        ? null
        : parseInt(
            wrongInput.value,
            10
        );


        data[pageKey].correct =
            correct;

        data[pageKey].wrong =
            wrong;


        /*
     * Eğer doğru + yanlış varsa
     * toplamı hesaplayabiliriz.
     */

        if (
            correct !== null &&
            wrong !== null &&
            correct + wrong > 0
        ) {

            data[pageKey].total =
                correct + wrong;

            data[pageKey].score =
                Math.round(
                (
                    correct /
                    (correct + wrong)
                ) * 100
            );

        }


        saveData(data);


        const button =
              document.getElementById(
                  'te-save-score'
              );

        button.textContent =
            '✓ Kaydedildi';


        setTimeout(
            function () {

                button.textContent =
                    'Sonucu Kaydet';

            },
            1500
        );
    }

    // ============================================================
    // UI
    // ============================================================

    function createUI() {

        const result =
              getCurrentResult();

        if (
            result &&
            result.completed
        ) {

            showCompletedMessage();

            createManualScorePanel();

        }

    }


    // ============================================================
    // CSS
    // ============================================================

    GM_addStyle(`

       #te-completed-message {

    position: relative;

    z-index: 9999;

    width: calc(100% - 20px);

    box-sizing: border-box;

    margin: 10px;

    padding: 12px 16px;

    display: flex;

    align-items: center;

    gap: 10px;

    background: #e9f7ec;

    color: #222222;

    border: 1px solid #b8dfc0;

    border-radius: 8px;

    font-family:
        Arial,
        Helvetica,
        sans-serif;

    font-size: 13px;
}

        .te-completed-icon {

            width: 30px;

            height: 30px;

            border-radius: 50%;

            background: #35a854;

            color: white;

            display: flex;

            align-items: center;

            justify-content: center;

            font-size: 18px;

            font-weight: bold;
        }


        .te-completed-small {

            margin-top: 3px;

            color: #777777;

            font-size: 11px;
        }


        #te-score-panel {

            position: fixed;

            top: 85px;

            right: 20px;

            z-index: 999999;

            width: 220px;

            padding: 14px;

            background: #ffffff;

            border: 1px solid #dddddd;

            border-radius: 10px;

            box-shadow:
                0 5px 20px
                rgba(0, 0, 0, 0.18);

            font-family:
                Arial,
                Helvetica,
                sans-serif;
        }


        .te-score-title {

            font-size: 13px;

            font-weight: bold;

            margin-bottom: 10px;
        }


        .te-score-row {

            display: flex;

            align-items: center;

            justify-content:
                space-between;

            margin-bottom: 7px;

            font-size: 12px;
        }


        .te-score-row input {

            width: 100px;

            padding: 5px;

            border:
                1px solid #cccccc;

            border-radius: 5px;
        }


        #te-save-score {

            width: 100%;

            margin-top: 5px;

            padding: 7px;

            border: none;

            border-radius: 5px;

            background: #222222;

            color: white;

            cursor: pointer;
        }


        #te-save-score:hover {

            background: #444444;
        }

.te-total-statistics {

    margin-top: 8px;

    padding-top: 8px;

    border-top:
        1px solid #cfe3d2;

    color: #555555;

    font-size: 12px;

    line-height: 1.7;
}


.te-total-statistics strong {

    color: #222222;

    font-weight: bold;
}


.te-total-statistics span {

    color: #666666;
}

    `);


    // ============================================================
    // BAŞLAT
    // ============================================================

    function init() {

        /*
         * Sadece bir kez çalışıyoruz.
         *
         * MutationObserver yok.
         * Sürekli tarama yok.
         * Sayfayı yenileme yok.
         */

        setupCheckAnswers();

        createUI();

    }


    if (
        document.readyState ===
        'loading'
    ) {

        document.addEventListener(
            'DOMContentLoaded',
            init
        );

    } else {

        init();

    }

})();