NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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); } }