AtCoderの問題文中の .zip リンクの横にコピーボタンを追加します。
// ==UserScript==
// @name AtCoder Zip Copy
// @namespace https://github.com/e6nlaq/atcoder-zip-copy
// @version 1.0.1
// @author e6nlaq
// @description AtCoderの問題文中の .zip リンクの横にコピーボタンを追加します。
// @license MIT
// @copyright (C) 2026 e6nlaq
// @match https://atcoder.jp/contests/*/tasks/*
// ==/UserScript==
(function () {
'use strict';
function removeSearchParams(href) {
try {
const url = new URL(href);
url.search = "";
return url.toString();
} catch (_e) {
const dummyBase = "https://example.com";
const url = new URL(href, dummyBase);
url.search = "";
return url.pathname + url.hash;
}
}
function init() {
const taskStatement = document.getElementById("task-statement");
if (!taskStatement) return;
const links = taskStatement.querySelectorAll("a");
for (const link of links) {
const url = new URL(link.href, window.location.href);
if (url.pathname.toLowerCase().endsWith(".zip")) {
const btn = document.createElement("button");
btn.type = "button";
btn.textContent = "Copy";
btn.className = "btn btn-default btn-sm";
btn.style.marginLeft = "0.5em";
btn.style.verticalAlign = "middle";
btn.addEventListener("click", (e) => {
e.preventDefault();
navigator.clipboard.writeText(removeSearchParams(link.href)).then(() => {
const originalText = btn.textContent;
btn.textContent = "Copied!";
setTimeout(() => {
btn.textContent = originalText;
}, 1e3);
}).catch((err) => {
console.error("Failed to copy text: ", err);
});
});
link.parentNode?.insertBefore(btn, link.nextSibling);
}
}
}
init();
})();