omoggle live psl + video loop + drag

real-time psl slider, video cam loop, draggable menu, api hook

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         omoggle live psl + video loop + drag
// @namespace    lol
// @version      9.0
// @description  real-time psl slider, video cam loop, draggable menu, api hook
// @match        https://omoggle.com/*
// @match        https://www.omoggle.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    let current_psl = 9.9;
    let videoStream = null;

    // -------- API INTERCEPTORS (fetch + xhr) ----------
    const oldFetch = window.fetch;
    window.fetch = function(...args) {
        let url = args[0];
        let opts = args[1] || {};
        if (typeof url === 'string' && (url.includes('/api/match/finalize') || url.includes('/api/ranked/finalize'))) {
            if (opts.body) {
                try {
                    let body = JSON.parse(opts.body);
                    if (body.psl !== undefined) body.psl = current_psl;
                    if (body.score !== undefined) body.score = current_psl;
                    if (body.rating !== undefined) body.rating = current_psl;
                    if (body.yourScore !== undefined) body.yourScore = current_psl;
                    opts.body = JSON.stringify(body);
                } catch(e) {}
            }
        }
        return oldFetch.apply(this, [url, opts]);
    };

    const XHR = XMLHttpRequest.prototype;
    const oldOpen = XHR.open;
    const oldSend = XHR.send;
    XHR.open = function(method, url, ...rest) {
        this._mog_url = url;
        return oldOpen.apply(this, [method, url, ...rest]);
    };
    XHR.send = function(body) {
        if (this._mog_url && (this._mog_url.includes('/api/match/finalize') || this._mog_url.includes('/api/ranked/finalize'))) {
            if (body && typeof body === 'string') {
                try {
                    let parsed = JSON.parse(body);
                    if (parsed.psl !== undefined) parsed.psl = current_psl;
                    if (parsed.score !== undefined) parsed.score = current_psl;
                    if (parsed.rating !== undefined) parsed.rating = current_psl;
                    body = JSON.stringify(parsed);
                } catch(e) {}
            }
        }
        return oldSend.call(this, body);
    };

    // -------- WAIT FOR PAGE THEN ADD MENU ----------
    window.addEventListener('DOMContentLoaded', () => {
        // create menu div
        let menu = document.createElement('div');
        menu.id = 'mogmenu';
        menu.style.cssText = 'position:fixed; bottom:20px; right:20px; background:#111; border:1px solid #f0f; color:#eee; font-family:monospace; font-size:13px; padding:10px; z-index:99999; width:240px; cursor:default;';
        menu.innerHTML = `
            <div id="mogHeader" style="font-weight:bold; margin-bottom:10px; text-align:center; cursor:move;">OMOGGLE TOOL (drag me)</div>
            <div>PSL: <span id="pslValue">9.9</span></div>
            <input type="range" id="pslSlider" min="0.1" max="9.9" step="0.01" value="9.9" style="width:100%; margin:5px 0;">
            <div style="margin-top:10px;">📹 fake cam (video loop)</div>
            <input type="file" id="camFile" accept="video/*" style="width:100%; margin:5px 0;">
            <div id="camStatus" style="color:#aaa; font-size:11px;">no video</div>
            <div id="apiLog" style="border-top:1px solid #333; margin-top:8px; padding-top:5px; font-size:10px;">api: idle</div>
        `;
        document.body.appendChild(menu);

        // draggable functionality
        let header = document.getElementById('mogHeader');
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        header.onmousedown = dragMouseDown;
        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDrag;
            document.onmousemove = elementDrag;
        }
        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            let top = menu.offsetTop - pos2;
            let left = menu.offsetLeft - pos1;
            if (top > 0) menu.style.top = top + 'px';
            if (left > 0) menu.style.left = left + 'px';
            menu.style.bottom = 'auto';
            menu.style.right = 'auto';
        }
        function closeDrag() {
            document.onmouseup = null;
            document.onmousemove = null;
        }

        // psl slider -> update display and global value
        let slider = document.getElementById('pslSlider');
        let pslSpan = document.getElementById('pslValue');
        slider.addEventListener('input', (e) => {
            let val = parseFloat(e.target.value).toFixed(2);
            pslSpan.innerText = val;
            current_psl = parseFloat(val);
            // instantly spoof any visible score on page
            spoofAllScores(current_psl);
            document.getElementById('apiLog').innerHTML = `api: set to ${current_psl}`;
        });

        // function to find all score elements and change them live
        function spoofAllScores(score) {
            // common selectors
            let selectors = ['.score-value', '.psl-score', '.your-score', '[data-score]', '.rating-number', '.mog-score'];
            for (let sel of selectors) {
                document.querySelectorAll(sel).forEach(el => {
                    if (el.innerText && !isNaN(parseFloat(el.innerText))) el.innerText = score;
                });
            }
            // aggressive: any element with a single decimal number between 0.1-9.9
            document.querySelectorAll('*').forEach(el => {
                if (el.children.length === 0 && el.innerText && el.innerText.trim().match(/^\d+\.\d$/)) {
                    let num = parseFloat(el.innerText);
                    if (num >= 0.1 && num <= 9.9) el.innerText = score;
                }
            });
        }

        // run spoof repeatedly in case omoggle updates DOM
        setInterval(() => {
            spoofAllScores(current_psl);
        }, 500);

        // camera: video file upload (loops)
        let fileInput = document.getElementById('camFile');
        let camStatus = document.getElementById('camStatus');
        fileInput.addEventListener('change', (e) => {
            let f = e.target.files[0];
            if (f && f.type.startsWith('video/')) {
                let url = URL.createObjectURL(f);
                let video = document.createElement('video');
                video.src = url;
                video.loop = true;
                video.muted = true;   // required for autoplay
                video.onloadeddata = () => {
                    video.play();
                    // replace camera stream
                    let stream = video.captureStream(); // works in Chrome/Edge
                    // find user's video element
                    let userVideo = null;
                    document.querySelectorAll('video').forEach(v => {
                        if (v.srcObject && v.srcObject.getVideoTracks) userVideo = v;
                    });
                    if (userVideo && userVideo.srcObject) {
                        let oldTracks = userVideo.srcObject.getTracks();
                        oldTracks.forEach(t => t.stop());
                        userVideo.srcObject = stream;
                        camStatus.innerText = 'video loaded (looping)';
                        camStatus.style.color = '#8f8';
                    } else {
                        // fallback: store stream for later when video appears
                        videoStream = stream;
                        camStatus.innerText = 'waiting for cam...';
                        let checkInterval = setInterval(() => {
                            let uv = document.querySelector('video[srcObject]');
                            if (uv) {
                                uv.srcObject = stream;
                                camStatus.innerText = 'video applied';
                                clearInterval(checkInterval);
                            }
                        }, 500);
                    }
                };
            } else {
                camStatus.innerText = 'not a video file';
                camStatus.style.color = '#f88';
            }
        });

        // initial spoof
        spoofAllScores(9.9);
    });
})();