ADT→ABC Converter Button

ADTの問題URLを検知してABCで開く

目前为 2025-03-28 提交的版本。查看 最新版本

// ==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});
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址