projang / Chess.com 기보 페이지 편의성 개선

// ==UserScript==
// @name         Chess.com 기보 페이지 편의성 개선
// @version      1.1
// @description  Chess.com 사이트에서 Embed 기보를 익명으로 복사하는 버튼과 Gif 저장버튼을 추가합니다.
// @match        https://www.chess.com/game/*
// @match        https://www.chess.com/*/game/*
// @author       projang
// @license MIT
// @copyright 2021, projang (https://openuserjs.org/users/projang)
// @updateURL https://openuserjs.org/meta/projang/Chess.com_기보_페이지_편의성_개선.meta.js
// ==/UserScript==

document.querySelector("#sb > div:nth-child(3)").insertAdjacentHTML(
    "beforeend",
    `<a class="nav-link-component" id="copyembed">
<span class="nav-link-text">익명으로 복사</span>
</a>`
);

const copyElement = document.querySelector("#copyembed");
copyElement.onclick = copyAnonEmbed;

function copyAnonEmbed() {
    copyElement.style.color = "orangered";
    copyElement.text = "복사중";

    const game = document.querySelector("chess-board").game;
    const flipped = document
    .querySelector("chess-board")
    .game.getOptions().flipped;
    let pgn = game.getPGN();
    if (!pgn.includes("Termination")) {
        copyElement.text = "다시 시도하세요";
        return;
    }
    pgn = editPgnData(pgn, "White", "Anon");
    pgn = editPgnData(pgn, "Black", "Anon");
    pgn = editPgnData(pgn, "WhiteElo", "");
    pgn = editPgnData(pgn, "BlackElo", "");

    getEmbed(flipped, pgn).then((id) => {
        copyElement.style.color = "yellowgreen";
        const text = `<iframe id="${id}" allowtransparency="true" frameborder="0" style="width:100%;border:none;" src="//www.chess.com/emboard?id=${id}">`;
        copyToClipboard(text);
        copyElement.text = "복사 완료";
    });
}

async function getEmbed(flipped, pgnbody) {
    const data = JSON.stringify({
        "textSetup": "&-diagramtype:\nchessGame\n&-colorscheme:\ngreen\n&-piecestyle:\nneo\n&-float:\nleft\n&-flip:\n" +
        flipped +
        "\n&-prompt:\nfalse\n&-coords:\ntrue\n&-size:\n45\n&-hideglobalbuttons:\nfalse\n&-pgnbody:\n" +
        pgnbody,
    });
    return await fetch("https://www.chess.com/tinymce/api/get_diagram?id=new", {
        "headers": {
            "content-type": "application/json;charset=UTF-8",
        },
        "body": data,
        "method": "POST",
    }).then((res) => res.text());
}

function editPgnData(pgn, key, newValue) {
    const i = pgn.indexOf(key) + key.length + 2;
    return pgn.slice(0, i) + newValue + pgn.slice(pgn.indexOf('"', i));
}

function copyToClipboard(text) {
    var area = document.createElement("textarea");
    area.value = text;
    document.body.appendChild(area);

    area.select();
    document.execCommand("copy");
    document.body.removeChild(area);
}

document.querySelector("#sb > div:nth-child(3)").insertAdjacentHTML(
    "beforeend",
    `<a class="nav-link-component" id="savegif">
<span class="nav-link-text">Gif 저장</span>
</a>`
);

const gifElement = document.querySelector("#savegif");
gifElement.onclick = saveGif;

async function saveGif() {
    gifElement.style.color = "orangered";
    gifElement.text = "이동 준비중";

    const game = document.querySelector("chess-board").game;
    const flipped = game.getOptions().flipped;

    const token = await fetch("https://www.chess.com/gifs")
    .then((res) => res.text())
    .then(
        (body) =>
        new DOMParser()
        .parseFromString(body, "text/html")
        .querySelector("#animated_gif__token").value
    );

    const data = {
        "animated_gif[data]": location.href,
        "animated_gif[board_texture]": "green",
        "animated_gif[piece_theme]": "neo",
        "animated_gif[_token]": token,
    };
    if (flipped) data["animated_gif[flip]"] = 1;

    const url = await fetch("https://www.chess.com/gifs", {
        "headers": {
            "content-type": "application/x-www-form-urlencoded",
        },
        "body": new URLSearchParams(data),
        "method": "POST",
    })
    .then((res) => res.text())
    .then((body) =>
          new DOMParser()
          .parseFromString(body, "text/html")
          .querySelector("#animated-gif")
          .getAttribute("url")
         );

    if (!url) {
        gifElement.text = "불가능한 페이지입니다";
        return
    }
    await waitUntilStatus200(url);
    gifElement.style.color = "yellowgreen";
    gifElement.text = "이동 완료";
    window.open(url, "_blank")
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function waitUntilStatus200(url) {
    while (true) {
        const status = await fetch(url).then((res) => res.status);
        if (status == 200) break;
        await sleep(500);
    }
}