끄투 방 참가 알림

실시간으로 참여자 수 변화를 감지해 사인파 소리 재생 및 자동 시작/관전 버튼 클릭

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          끄투 방 참가 알림
// @namespace     끄투 방 참가 알림
// @match         https://kkutu.co.kr/*
// @version       1.3
// @description   실시간으로 참여자 수 변화를 감지해 사인파 소리 재생 및 자동 시작/관전 버튼 클릭
// @icon          https://www.google.com/s2/favicons?domain=kkutu.co.kr
// @grant         none
// @license       MIT
// ==/UserScript==

(function () {
    'use strict';

    let previousCount = 0;
    const autoStartCheckboxId = 'autoStartCheckbox';
    const autoSpectateCheckboxId = 'autoSpectateCheckbox';

    function setCookie(name, value, days) {
        const date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`;
    }

    function getCookie(name) {
        const cookies = document.cookie.split('; ').reduce((acc, cookie) => {
            const [key, value] = cookie.split('=');
            acc[key] = value;
            return acc;
        }, {});
        return cookies[name] || null;
    }

    function createCheckbox(id, labelText, topOffset) {
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = id;
        checkbox.style.position = 'fixed';
        checkbox.style.top = `${topOffset}px`;
        checkbox.style.right = '10px';
        document.body.appendChild(checkbox);

        const label = document.createElement('label');
        label.htmlFor = id;
        label.innerText = labelText;
        label.style.position = 'fixed';
        label.style.top = `${topOffset}px`;
        label.style.right = '30px';
        document.body.appendChild(label);

        return checkbox;
    }

    const autoStartCheckbox = createCheckbox(autoStartCheckboxId, '자동 시작', 10);
    const autoSpectateCheckbox = createCheckbox(autoSpectateCheckboxId, '자동 관전', 40);

    autoStartCheckbox.checked = getCookie(autoStartCheckboxId) === 'true';
    autoSpectateCheckbox.checked = getCookie(autoSpectateCheckboxId) === 'true';

    autoStartCheckbox.addEventListener('change', () => {
        setCookie(autoStartCheckboxId, autoStartCheckbox.checked, 7);
        console.log('자동 시작 설정:', autoStartCheckbox.checked);
    });

    autoSpectateCheckbox.addEventListener('change', () => {
        setCookie(autoSpectateCheckboxId, autoSpectateCheckbox.checked, 7);
        console.log('자동 관전 설정:', autoSpectateCheckbox.checked);
    });

    // 사인파 소리 재생 함수
    function playSineWave(frequency, duration) {
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const oscillator = audioContext.createOscillator();
        oscillator.type = 'sine';
        oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
        oscillator.connect(audioContext.destination);
        oscillator.start();
        oscillator.stop(audioContext.currentTime + duration);
    }

    function playTripleBeep() {
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const frequencies = [1220, 1220, 1220];
        const times = [0, 0.3, 0.6];

        frequencies.forEach((freq, index) => {
            const oscillator = audioContext.createOscillator();
            oscillator.type = 'sine';
            oscillator.frequency.setValueAtTime(freq, audioContext.currentTime + times[index]);
            oscillator.connect(audioContext.destination);
            oscillator.start(audioContext.currentTime + times[index]);
            oscillator.stop(audioContext.currentTime + times[index] + 0.2);
        });
    }


    function getUserCounts() {
        const spectators = document.querySelectorAll('.room-user-spectate');
        let readyCount = 0;
        let waitingCount = 0;
        let spectatorsCount = spectators.length; // 관전 인원 초기화

        document.querySelectorAll('.room-user-ready').forEach((user) => {
            if (user.textContent.includes('준비') && !user.classList.contains('room-user-master')) {
                readyCount++;
            } else if (user.textContent.includes('대기')) {
                waitingCount++;
            } else if (user.classList.contains('room-user-master')) {
                // 방장이 '준비'인 경우
                if (user.textContent.includes('관전')) {
                    // 방장이 관전 중이라면, 관전 인원에 추가
                    spectatorsCount++;
                } else {
                    // 방장이 '준비' 상태라면 준비 인원에 추가
                    readyCount++;
                }
            }
        });

        console.log('준비 인원:', readyCount);
        console.log('대기 인원:', waitingCount);
        console.log('관전 인원:', spectatorsCount);

        return { readyCount, waitingCount, spectatorsCount };
    }

    function getTeamCounts() {
        const teamCounts = { A: 0, B: 0, C: 0, D: 0, 개인: 0 };
        document.querySelectorAll('.room-user-team').forEach((user) => {
            if (user.classList.contains('team-1')) teamCounts.A++;
            else if (user.classList.contains('team-2')) teamCounts.B++;
            else if (user.classList.contains('team-3')) teamCounts.C++;
            else if (user.classList.contains('team-4')) teamCounts.D++;
            else if (user.classList.contains('room-user-ready') && user.classList.contains('room-user-spectate')) {
                return;
            } else {
                teamCounts.개인++;
            }
        });

        console.log('팀 인원:', teamCounts);
        return teamCounts;
    }

    function canStartGame() {
        const { readyCount, waitingCount } = getUserCounts();
        const { A, B, C, D, 개인 } = getTeamCounts();
        const teams = { A, B, C, D };

        if (waitingCount > 0) {
            console.log('대기 인원이 존재하여 게임 시작 불가');
            return false; // 대기 인원이 존재할 경우 게임 시작 불가
        }

        if (readyCount < 2) {
            console.log('준비 인원이 2인 미만으로 게임 시작 불가');
            return false; // 준비 인원이 2인 미만일 경우 시작 불가
        }

        // 팀 인원 수 체크
        const teamSizes = Object.values(teams).filter(count => count > 0);
        const allEqual = teamSizes.length > 0 && teamSizes.every(size => size === teamSizes[0]);

        // 팀 수가 0인 경우는 개인 인원이 2명 이상이면 게임 시작 가능
        if (teamSizes.length === 0 && 개인 >= 2) {
            console.log('팀이 없지만 개인 인원이 2명 이상이므로 게임 시작 가능');
            return true; // 개인 인원이 2명 이상인 경우 게임 시작 가능
        }

        // 팀 수가 불균형한 경우
        if (!allEqual) {
            console.log('팀 수 불균형 (실행 금지)');
            return false; // 팀 수 불균형
        }

        // 개인이 있을 경우
        if (개인 > 0) {
            const sameTeamCount = Object.values(teams).some(count => count >= 2);
            if (sameTeamCount) {
                console.log('팀이 2명 이상이며 개인이 존재하므로 게임 시작 불가');
                return false; // 팀이 2명 이상인 경우 게임 시작 불가
            }
        }

        // 두 팀 이상의 인원이 있어야 게임 시작 가능
        const totalTeams = Object.values(teams).filter(count => count > 0).length;
        if (totalTeams < 2) {
            console.log('상대팀이 없으므로 게임 시작 불가');
            return false; // 상대팀이 없는 경우 게임 시작 불가
        }

        console.log('게임 시작 가능');
        return true; // 조건을 모두 만족한 경우
    }

    function autoStartGame() {
        const startBtn = document.getElementById('StartBtn');
        if (!startBtn || startBtn.style.display === 'none') {
            return;
        }

        if (canStartGame()) {
            console.log('게임 시작 버튼 클릭');
            startBtn.click(); // 게임 시작 클릭
        }
    }

    function autoSpectate() {
        const spectateBtn = document.getElementById('SpectateBtn');
        if (spectateBtn && spectateBtn.style.display === 'block' && !spectateBtn.classList.contains('toggled')) {
            spectateBtn.click(); // 관전 클릭
            console.log('관전 버튼 클릭');
        }
    }

    const observer = new MutationObserver(() => {
        const element = document.querySelector('.room-head-limit');
        if (!element) return;

        const match = element.textContent.match(/참여자 (\d+) \/ (\d+)/);
        if (!match) return;

        const currentCount = parseInt(match[1], 10);
        const maxCount = parseInt(match[2], 10);

        if (currentCount !== previousCount) {
            if (currentCount > previousCount) {
                if (currentCount === maxCount) {
                    playTripleBeep();
                } else {
                    playSineWave(880, 0.2); // 참가자 수가 늘어났을 때
                }
            } else {
                playSineWave(440, 0.2); // 참가자 수가 줄어들었을 때
            }

            previousCount = currentCount;
        }

        if (autoStartCheckbox.checked) {
            autoStartGame();
        }

        if (autoSpectateCheckbox.checked) {
            autoSpectate();
        }
    });

    const config = { childList: true, subtree: true };
    observer.observe(document.body, config);
})();