CHZZK - Recorder (HLS)

You can record a live stream.

// ==UserScript==
// @name         CHZZK - Recorder (HLS)
// @name:en      CHZZK - Recorder (HLS)
// @name:ko      치지직 - 레코더 (HLS)
// @namespace    https://greasyfork.org/ja/users/941284-ぐらんぴ
// @version      2025-08-03
// @description  You can record a live stream.
// @description:en You can record a live stream.
// @description:ko 라이브 스트림을 녹화할 수 있습니다.
// @author       ぐらんぴ
// @match        https://*.naver.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=naver.com
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

let m3u8, lastHref, recorder, chunks = [], isRecording = false, log = console.log;

const origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (){
    this.addEventListener("load", function (){
        try{
            const url = this.responseURL;
            const currHref = location.href;

            // detect location changed
            if(currHref !== lastHref){
                lastHref = currHref;
                m3u8 = undefined;
            }

            // reassign
            if((!m3u8 || currHref !== lastHref) && url?.includes(".m3u8") && currHref.startsWith('https://chzzk.naver.com/live/')){
                m3u8 = url;
                log("m3u8:", m3u8);
            }
        }catch(e){//log("err", e)
        }
    });
    return origSend.apply(this, arguments);
};

const origAppendChild = Element.prototype.appendChild;
Element.prototype.appendChild = function (...args){
    const el = args[0];

    if(el?.className === "video_information_control__UTm8Z"){
        if(!location.href.startsWith('https://chzzk.naver.com/live/')) return;
        if(el.querySelector(".GRMP")) return;

        const button = document.createElement("button");
        button.className = "GRMP";
        button.textContent = "RECORD";

        button.addEventListener("click", () => {
            if(!m3u8){
                alert("No m3u8 URL found.");
                return;
            }

            const video = document.querySelector("video");
            if(!video){
                alert("Video element not found.");
                return;
            }

            if(video.paused || video.readyState < 3){
                video.play().catch(err => console.warn("Video play failed:", err));
            }

            if(!isRecording){
                try{
                    const stream = video.captureStream();
                    if(!stream){
                        alert("Failed to capture stream.");
                        return;
                    }

                    recorder = new MediaRecorder(stream);
                    chunks = [];

                    recorder.ondataavailable = e => chunks.push(e.data);
                    recorder.onstop = () => {
                        const blob = new Blob(chunks, { type: 'video/webm' });
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = document.querySelector(".name_text__yQG50").textContent.trim() + " - " +
                            document.querySelector(".video_information_title__jrLfG").textContent.trim() + ".webm";
                        a.click();
                    };

                    recorder.start();
                    isRecording = true;
                    button.textContent = "SAVE";
                }catch(e){ alert("Recording failed: " + e);
                         }
            }else{
                recorder.stop();
                isRecording = false;
                button.textContent = "RECORD";
            }
        });
        el.appendChild(button);
    }
    return origAppendChild.apply(this, args);
};