ADTの問題URLを検知してABCで開く
当前为
// ==UserScript==
// @name ADT→ABC Converter Button
// @namespace http://mogobon.github.io/
// @version 1.1
// @description ADTの問題URLを検知してABCで開く
// @author もごぼん
// @match https://atcoder.jp/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// スタイルを追加する関数
function addStyles() {
const style = document.createElement('style');
style.textContent = `
.adt-converter-button {
position: fixed;
top: 80px;
right: 20px;
background-color: #4CAF50;
color: white;
font-weight: bold;
font-size: 14px;
border: none;
border-radius: 8px;
padding: 10px 15px;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
z-index: 9999;
transition: all 0.3s ease;
/* デフォルトで浮き上がった状態 */
transform: translateY(-3px);
}
.adt-converter-button:hover {
/* ホバー時に沈む */
background-color: #3c9040;
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.adt-converter-button:active {
/* クリック時さらに沈む */
transform: translateY(1px);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.adt-converter-notification {
position: fixed;
top: 130px;
right: 20px;
background: #4CAF50;
color: white;
padding: 10px 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
z-index: 10000;
font-size: 14px;
animation: adtFadeIn 0.3s, adtFadeOut 0.3s 1.7s forwards;
}
@keyframes adtFadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes adtFadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-20px); }
}
@media (max-width: 768px) {
.adt-converter-button {
top: 70px;
right: 10px;
font-size: 12px;
padding: 8px 12px;
}
.adt-converter-notification {
top: 120px;
right: 10px;
font-size: 12px;
max-width: 80%;
}
}
`;
document.head.appendChild(style);
}
// URL変換ロジック
function convertUrl(adtUrl) {
const parts = adtUrl.split("/tasks/", 2);
if (parts.length < 2) return adtUrl;
const [prefix, taskPart] = parts;
// 問題一覧ページの場合はそのまま返す
if (!taskPart || taskPart === "") return adtUrl;
const abcId = taskPart.split("_", 1)[0];
return `https://atcoder.jp/contests/${abcId}/tasks/${taskPart}`;
}
// 通知を表示する関数
function showNotification(message) {
// 既存の通知を削除
const existingNotifications = document.querySelectorAll('.adt-converter-notification');
existingNotifications.forEach(notif => {
if (document.body.contains(notif)) {
document.body.removeChild(notif);
}
});
const notif = document.createElement('div');
notif.className = 'adt-converter-notification';
notif.textContent = message;
document.body.appendChild(notif);
// 自動的に削除
setTimeout(() => {
if (document.body.contains(notif)) {
document.body.removeChild(notif);
}
}, 2000);
}
// AtCoder公式サイトで開く
function openInAtCoder() {
try {
const currentUrl = window.location.href;
const convertedUrl = convertUrl(currentUrl);
// URLが変換されなかった場合
if (convertedUrl === currentUrl) {
showNotification('このページは変換できません');
return;
}
// 新しいタブで開く
window.open(convertedUrl, '_blank');
showNotification('AtCoder公式サイトで開きました!');
} catch (error) {
showNotification('エラー: ' + error.message);
}
}
// ボタンを作成して追加
function addButton() {
// 既存のボタンを確認(重複防止)
if (document.querySelector('.adt-converter-button')) {
return;
}
const button = document.createElement('button');
button.className = 'adt-converter-button';
button.textContent = 'ABCで開く';
button.title = 'この問題をAtCoder公式サイトで開く';
button.addEventListener('click', openInAtCoder);
document.body.appendChild(button);
}
// URLがADTの個別問題URLかどうかを判定する関数
function isAdtProblemUrl() {
const url = window.location.href.toLowerCase();
// 基本的にはADTのURLを含む
const isAdtUrl = (url.includes('atcoder-tools') || url.includes('adt')) && url.includes('tasks');
// 問題一覧ページは除外する(/tasks で終わるか、/tasks/ で終わる場合)
const isProblemListPage = url.match(/\/tasks\/?$/);
// 問題一覧ページでなく、ADTのURLを含む場合のみtrue
return isAdtUrl && !isProblemListPage;
}
// ページ初期化
function init() {
// 個別問題のURLの場合のみボタンを表示
if (isAdtProblemUrl()) {
addStyles();
addButton();
}
}
// ページロード完了時に実行
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init);
}
// ページ変更を監視(SPAサイト対応)
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
setTimeout(() => {
// 既存のボタンを削除
const existingButton = document.querySelector('.adt-converter-button');
if (existingButton) {
existingButton.remove();
}
// 個別問題のURLの場合のみボタンを再表示
if (isAdtProblemUrl()) {
addButton();
}
}, 300);
}
}).observe(document, {subtree: true, childList: true});
})();