NicoSeigaCommentNG

ニコニコ静画のコメント欄でNGユーザー・NGワードを設定してコメントを削除する(改良版UI)

// ==UserScript==
// @name         NicoSeigaCommentNG
// @namespace    http://tampermonkey.net/
// @version      2025-06-24-5ch-integration-ui-improved
// @description  ニコニコ静画のコメント欄でNGユーザー・NGワードを設定してコメントを削除する(改良版UI)
// @author       You
// @match        https://seiga.nicovideo.jp/seiga/im*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM.getValue
// @grant        GM.setValue
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 設定データの管理クラス
    class Storage {
        constructor(storageName) {
            this.storageName = storageName;
        }

        async GetStorageData(defaultValue = null) {
            const text = await GM.getValue(this.storageName, null);
            return text != null ? JSON.parse(decodeURIComponent(text)) : defaultValue;
        }

        async SetStorageData(data) {
            await GM.setValue(this.storageName, encodeURIComponent(JSON.stringify(data)));
        }
    }

    // ストレージインスタンス
    const configStorage = new Storage("NICOSEIGA_COMMENT_NG_CONFIG_V3");

    // 初期化済みフラグ
    let isInitialized = false;
    let initAttempts = 0;
    const MAX_INIT_ATTEMPTS = 50;
    let commentsDataCache = null; // コメントデータキャッシュ
    let deletedComments = new Set(); // 削除されたコメントのIDを追跡
    let deletedTempIds = new Set(); // 削除された仮IDを追跡

    // 5ch風コメントスクリプトが有効かどうかをチェック
    function is5chStyleScriptActive() {
        // ComeCountクラスが存在するかチェック
        return document.querySelector('.ComeCount') !== null;
    }

    // 仮IDを取得(5ch風コメントスクリプトから)
    function getTempIdFromComment(commentElement) {
        const commentsData = parseCommentsData();
        const commentId = getCommentId(commentElement);
        if (!commentId) return null;

        const commentData = commentsData.find(c => c.id === commentId);
        return commentData ? commentData.user : null;
    }

    // 同じ仮IDのコメントを全て取得
    function getCommentsByTempId(tempId) {
        if (!tempId) return [];

        const allComments = document.querySelectorAll('.comment_list_item');
        const sameIdComments = [];

        allComments.forEach(comment => {
            const commentTempId = getTempIdFromComment(comment);
            if (commentTempId === tempId) {
                sameIdComments.push(comment);
            }
        });

        return sameIdComments;
    }

    // 設定データの取得
    async function getConfig() {
        const defaultConfig = {
            ngCommentIds: [],        // NGコメントID配列
            ngWords: [],             // NGワード配列(正規表現対応)
            ngUserHashes: [],        // NGユーザーハッシュ配列(公式と同じ形式)
            enable5chStyleIntegration: true // 5ch風コメントとの連携を有効にするか
        };
        return await configStorage.GetStorageData(defaultConfig);
    }

    // 設定データの保存
    async function saveConfig(config) {
        await configStorage.SetStorageData(config);
    }

    // コメントデータの解析とキャッシュ
    function parseCommentsData() {
        if (commentsDataCache) return commentsDataCache;

        const commentSection = document.getElementById('ko_comment');
        if (commentSection) {
            const dataInit = commentSection.getAttribute('data-initialize');
            if (dataInit) {
                try {
                    commentsDataCache = JSON.parse(dataInit);
                    console.log('コメントデータをキャッシュしました:', commentsDataCache.length + '件');
                    return commentsDataCache;
                } catch (e) {
                    console.error('コメントデータの解析に失敗:', e);
                }
            }
        }
        return [];
    }

    // 公式NG設定データの読み込み(参考用)
    function getOfficialNGData() {
        const ngSection = document.getElementById('ko_commentng');
        if (ngSection) {
            const clientNg = ngSection.getAttribute('data-client_ng');
            if (clientNg) {
                try {
                    const ngData = JSON.parse(clientNg);
                    console.log('公式NG設定:', ngData);
                    return ngData;
                } catch (e) {
                    console.error('公式NG設定の解析に失敗:', e);
                }
            }
        }
        return [];
    }

    // ユーザーハッシュを取得(公式と同じ形式)
    function getUserHash(commentElement) {
        const commentId = getCommentId(commentElement);
        if (!commentId) return null;

        const commentsData = parseCommentsData();
        const commentData = commentsData.find(c => c.id === commentId);
        return commentData ? commentData.user : null;
    }

    // コメントIDを取得
    function getCommentId(commentElement) {
        const idElement = commentElement.querySelector('.id span');
        return idElement ? idElement.textContent.trim() : null;
    }

    // コメントテキストを取得
    function getCommentText(commentElement) {
        const textElement = commentElement.querySelector('.text');
        return textElement ? textElement.textContent.trim() : '';
    }

    // NGアイテム削除関数
    async function removeNGItem(type, item) {
        const config = await getConfig();
        const index = config[type].indexOf(item);
        if (index > -1) {
            config[type].splice(index, 1);
            await saveConfig(config);
            await updateNGList();

            // 削除されたコメントをリセットして再フィルタリング
            restoreAllComments();
            deletedComments.clear();
            deletedTempIds.clear();
            filterComments();
        }
    }

    // 削除されたコメントを復元
    function restoreAllComments() {
        const deletedCommentsElements = document.querySelectorAll('.comment_list_item.ng-deleted');
        deletedCommentsElements.forEach(comment => {
            comment.classList.remove('ng-deleted');
            comment.style.display = '';

            // 元の位置に戻す
            const commentList = comment.closest('.comment_list');
            if (commentList && !comment.parentNode) {
                commentList.appendChild(comment);
            }
        });
    }

    // NGリストの表示更新
    async function updateNGList() {
        const config = await getConfig();

        // コメントIDリスト
        const commentIdList = document.getElementById('ng-commentid-list');
        if (commentIdList) {
            commentIdList.innerHTML = '';
            config.ngCommentIds.forEach(id => {
                const div = createNGListItem(`No.${id}`, () => removeNGItem('ngCommentIds', id));
                commentIdList.appendChild(div);
            });
            if (config.ngCommentIds.length === 0) {
                commentIdList.innerHTML = '<div style="color: #666; font-style: italic;">NGコメントIDはありません</div>';
            }
        }

        // ワードリスト
        const wordList = document.getElementById('ng-word-list');
        if (wordList) {
            wordList.innerHTML = '';
            config.ngWords.forEach(word => {
                const div = createNGListItem(word, () => removeNGItem('ngWords', word));
                wordList.appendChild(div);
            });
            if (config.ngWords.length === 0) {
                wordList.innerHTML = '<div style="color: #666; font-style: italic;">NGワードはありません</div>';
            }
        }

        // ユーザーハッシュリスト
        const userhashList = document.getElementById('ng-userhash-list');
        if (userhashList) {
            userhashList.innerHTML = '';
            config.ngUserHashes.forEach(hash => {
                const displayText = hash.length > 20 ? `${hash.substring(0, 20)}...` : hash;
                const div = createNGListItem(displayText, () => removeNGItem('ngUserHashes', hash), hash);
                userhashList.appendChild(div);
            });
            if (config.ngUserHashes.length === 0) {
                userhashList.innerHTML = '<div style="color: #666; font-style: italic;">NGユーザーハッシュはありません</div>';
            }
        }

        // 統計情報の更新
        updateStatistics(config);
    }

    // NGリストアイテムの作成
    function createNGListItem(displayText, removeCallback, fullText = null) {
        const div = document.createElement('div');
        div.className = 'ng-item';

        const span = document.createElement('span');
        span.textContent = displayText;
        if (fullText) {
            span.title = fullText; // ツールチップで完全なテキストを表示
        }

        const button = document.createElement('button');
        button.textContent = '削除';
        button.onclick = removeCallback;

        div.appendChild(span);
        div.appendChild(button);
        return div;
    }

    // 統計情報の更新
    function updateStatistics(config) {
        const statsElement = document.getElementById('ng-statistics');
        if (statsElement) {
            const total = config.ngCommentIds.length + config.ngWords.length + config.ngUserHashes.length;
            let statsText = `合計 ${total} 件のNG設定(コメント: ${config.ngCommentIds.length}, ワード: ${config.ngWords.length}, ユーザー: ${config.ngUserHashes.length})`;

            if (is5chStyleScriptActive()) {
                statsText += ' | 5ch風コメントスクリプト: ON';
            }

            statsElement.textContent = statsText;
        }
    }

    // 設定モーダルのHTML
    function createSettingsModal() {
        // 既存のモーダルがあれば削除
        const existingModal = document.getElementById('ng-settings-modal');
        if (existingModal) {
            existingModal.remove();
        }

        const modal = document.createElement('div');
        modal.id = 'ng-settings-modal';
        modal.style.cssText = `
            display: none;
            position: fixed;
            z-index: 10000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.6);
        `;

        const modalContent = document.createElement('div');
        modalContent.style.cssText = `
            background-color: #fefefe;
            margin: 3% auto;
            padding: 25px;
            border: 1px solid #888;
            width: 700px;
            max-height: 90%;
            overflow-y: auto;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
        `;

        const is5chActive = is5chStyleScriptActive();
        const integrationNote = is5chActive ?
            '<div style="background: #d4edda; border: 1px solid #c3e6cb; padding: 10px; border-radius: 5px; margin-bottom: 20px; color: #155724;"><strong>✅ 5ch風コメント連携:</strong> NGに該当する仮IDのコメントもまとめて削除されます</div>' :
            '<div style="background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 5px; margin-bottom: 20px; color: #856404;"><strong>ℹ️ 5ch風コメント連携:</strong> 5ch風コメントスクリプトが有効でないため、通常モードで動作します</div>';

        modalContent.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 2px solid #f0f0f0; padding-bottom: 15px;">
                <h2 style="margin: 0; color: #333;">NGコメント設定(削除モード)</h2>
                <span id="ng-close" style="color: #aaa; font-size: 28px; font-weight: bold; cursor: pointer; hover: color: #000;">&times;</span>
            </div>

            ${integrationNote}

            <div style="background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 5px; margin-bottom: 20px;">
                <strong>⚠️ 注意:</strong> NGに指定したコメントは完全に削除され、空きは詰められます。
            </div>

            <div id="ng-statistics" style="background: #e7f3ff; padding: 10px; border-radius: 5px; margin-bottom: 20px; font-weight: bold; color: #0066cc;"></div>

            <div id="ng-tabs" style="margin-bottom: 20px; border-bottom: 1px solid #ddd;">
                <button class="ng-tab-btn active" data-tab="comment-id">NGコメントID</button>
                <button class="ng-tab-btn" data-tab="word">NGワード</button>
                <button class="ng-tab-btn" data-tab="user-hash">NGユーザー</button>
                <button class="ng-tab-btn" data-tab="settings">設定</button>
            </div>

            <div id="ng-tab-content">
                <div id="tab-comment-id" class="ng-tab-panel active">
                    <h3>NGコメントID</h3>
                    <p style="color: #666; font-size: 14px;">特定のコメントを削除します。コメント番号(No.の後の数字)を指定してください。${is5chActive ? '<br><strong>※5ch風連携時: 該当コメントと同じ仮IDの全コメントが削除されます</strong>' : ''}</p>
                    <div style="margin-bottom: 15px; display: flex; gap: 10px;">
                        <input type="text" id="ng-commentid-input" placeholder="コメントID(例:47332405)" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                        <button id="ng-commentid-add" class="add-btn">追加</button>
                    </div>
                    <div id="ng-commentid-list" style="max-height: 200px; overflow-y: auto; border: 1px solid #eee; padding: 10px; border-radius: 4px;"></div>
                </div>

                <div id="tab-word" class="ng-tab-panel">
                    <h3>NGワード (正規表現対応)</h3>
                    <p style="color: #666; font-size: 14px;">コメント内容に含まれる文字列で削除します。正規表現も使用できます。${is5chActive ? '<br><strong>※5ch風連携時: 該当コメントと同じ仮IDの全コメントが削除されます</strong>' : ''}</p>
                    <div style="margin-bottom: 15px; display: flex; gap: 10px;">
                        <input type="text" id="ng-word-input" placeholder="NGワード(例:スパム、^.*広告.*$)" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                        <button id="ng-word-add" class="add-btn">追加</button>
                    </div>
                    <div id="ng-word-list" style="max-height: 200px; overflow-y: auto; border: 1px solid #eee; padding: 10px; border-radius: 4px;"></div>
                </div>

                <div id="tab-user-hash" class="ng-tab-panel">
                    <h3>NGユーザー</h3>
                    <p style="color: #666; font-size: 14px;">特定のユーザーの全コメントを削除します。ユーザーハッシュ(公式NG設定と同じ形式)を使用します。${is5chActive ? '<br><strong>※5ch風連携時の削除動作は通常と同じです</strong>' : ''}</p>
                    <div style="margin-bottom: 15px; display: flex; gap: 10px;">
                        <input type="text" id="ng-userhash-input" placeholder="ユーザーハッシュ(例:DwWIubQPhqlGABc...)" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                        <button id="ng-userhash-add" class="add-btn">追加</button>
                    </div>
                    <div id="ng-userhash-list" style="max-height: 200px; overflow-y: auto; border: 1px solid #eee; padding: 10px; border-radius: 4px;"></div>
                </div>

                <div id="tab-settings" class="ng-tab-panel">
                    <h3>表示設定</h3>
                    <div style="margin-bottom: 20px;">
                        <label style="display: block; margin-bottom: 10px;">
                            <input type="checkbox" id="enable-5ch-integration" checked> 5ch風コメント連携を有効にする ${!is5chActive ? '(5ch風スクリプトが必要)' : ''}
                        </label>
                    </div>

                    <h3>公式NG設定インポート</h3>
                    <p style="color: #666; font-size: 14px;">現在のページの公式NG設定からユーザーハッシュをインポートできます。</p>
                    <button id="import-official-ng" style="background: #ff6b35; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">公式NG設定から取り込み</button>
                </div>
            </div>

            <div style="margin-top: 25px; padding: 15px; background: #f8f9fa; border-radius: 5px; border: 1px solid #e9ecef;">
                <h4 style="margin-top: 0;">データ管理:</h4>
                <div style="display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap;">
                    <button id="export-config" class="data-btn export">エクスポート</button>
                    <input type="file" id="import-file" accept=".json" style="display: none;">
                    <button id="import-config" class="data-btn import">インポート</button>
                    <button id="clear-all-config" class="data-btn danger">全削除</button>
                    <button id="restore-comments" class="data-btn restore">削除コメント復元</button>
                </div>
                <small style="color: #6c757d; margin-top: 8px; display: block;">
                    ※設定データはTampermonkeyの内部ストレージに保存されます(公式NG設定とは独立)
                </small>
            </div>
        `;

        modal.appendChild(modalContent);
        document.body.appendChild(modal);

        // CSS追加(重複チェック)
        if (!document.getElementById('ng-styles')) {
            const style = document.createElement('style');
            style.id = 'ng-styles';
            style.textContent = `
                .ng-tab-btn {
                    background: #f8f9fa;
                    border: 1px solid #dee2e6;
                    padding: 10px 20px;
                    cursor: pointer;
                    margin-right: 2px;
                    border-radius: 4px 4px 0 0;
                    transition: background-color 0.2s;
                }
                .ng-tab-btn:hover {
                    background: #e9ecef;
                }
                .ng-tab-btn.active {
                    background: #fff;
                    border-bottom: 1px solid #fff;
                    font-weight: bold;
                }
                .ng-tab-panel {
                    display: none;
                    padding: 20px 0;
                }
                .ng-tab-panel.active {
                    display: block;
                }
                .ng-item {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 8px 12px;
                    border: 1px solid #e9ecef;
                    margin: 5px 0;
                    background: #fff;
                    border-radius: 4px;
                    transition: background-color 0.2s;
                }
                .ng-item:hover {
                    background: #f8f9fa;
                }
                .ng-item span {
                    flex: 1;
                    word-break: break-all;
                    margin-right: 10px;
                }
                .ng-item button {
                    background: #dc3545;
                    color: white;
                    border: none;
                    padding: 4px 12px;
                    cursor: pointer;
                    border-radius: 3px;
                    font-size: 12px;
                    transition: background-color 0.2s;
                }
                .ng-item button:hover {
                    background: #c82333;
                }
                #ng-settings-btn {
                    background: #28a745;
                    color: white;
                    border: none;
                    padding: 6px 12px;
                    cursor: pointer;
                    border-radius: 4px;
                    margin-top: 5px;
                    display: block;
                    font-size: 12px;
                    transition: background-color 0.2s;
                    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
                }
                #ng-settings-btn:hover {
                    background: #218838;
                    box-shadow: 0 2px 6px rgba(0,0,0,0.15);
                }
                .ng-add-btn {
                    background: rgba(108, 117, 125, 0.8);
                    color: white;
                    border: 1px solid rgba(108, 117, 125, 0.3);
                    padding: 1px 6px;
                    cursor: pointer;
                    border-radius: 2px;
                    margin-left: 3px;
                    font-size: 10px;
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
                    z-index: 1002;
                    position: relative;
                    transition: all 0.2s ease;
                    opacity: 0.75;
                    text-shadow: none;
                    box-shadow: 0 1px 2px rgba(0,0,0,0.1);
                }
                .ng-add-btn:hover {
                    background: rgba(108, 117, 125, 0.95);
                    border-color: rgba(108, 117, 125, 0.6);
                    opacity: 1;
                    transform: translateY(-1px);
                    box-shadow: 0 2px 4px rgba(0,0,0,0.15);
                }
                .ng-add-btn:active {
                    transform: translateY(0);
                    box-shadow: 0 1px 2px rgba(0,0,0,0.1);
                }
                .ng-deleted {
                    display: none !important;
                }
                .add-btn {
                    background: #28a745;
                    color: white;
                    border: none;
                    padding: 8px 16px;
                    cursor: pointer;
                    border-radius: 4px;
                    transition: background-color 0.2s;
                }
                .add-btn:hover {
                    background: #1e7e34;
                }
                .data-btn {
                    border: none;
                    padding: 8px 16px;
                    cursor: pointer;
                    border-radius: 4px;
                    font-size: 14px;
                    transition: all 0.2s;
                }
                .data-btn.export {
                    background: #28a745;
                    color: white;
                }
                .data-btn.export:hover {
                    background: #1e7e34;
                }
                .data-btn.import {
                    background: #17a2b8;
                    color: white;
                }
                .data-btn.import:hover {
                    background: #117a8b;
                }
                .data-btn.danger {
                    background: #dc3545;
                    color: white;
                }
                .data-btn.danger:hover {
                    background: #c82333;
                }
                .data-btn.restore {
                    background: #ffc107;
                    color: black;
                }
                .data-btn.restore:hover {
                    background: #e0a800;
                }
                #ng-close:hover {
                    color: #000 !important;
                }
            `;
            document.head.appendChild(style);
        }

        setupModalEvents();
        return modal;
    }

    // コメントのフィルタリング(削除版・5ch風連携対応)- 最優先で実行
    async function filterComments() {
        const config = await getConfig();
        const comments = document.querySelectorAll('.comment_list_item');
        let deletedCount = 0;
        const tempIdsToDelete = new Set(); // 削除対象の仮ID

        // 1. NGに該当するコメントを特定し、5ch風連携が有効な場合は仮IDも記録
        const commentsToDelete = new Set();

        comments.forEach(comment => {
            // 既に公式によって非表示されたコメントはスキップ
            if (comment.classList.contains('unpublic') && !comment.classList.contains('ng-deleted')) {
                return;
            }

            let shouldDelete = false;
            let deleteReason = '';

            // コメントIDチェック
            const commentId = getCommentId(comment);
            if (commentId && config.ngCommentIds.includes(commentId)) {
                shouldDelete = true;
                deleteReason = `NG Comment ID: ${commentId}`;
            }

            // ユーザーハッシュチェック
            if (!shouldDelete) {
                const userHash = getUserHash(comment);
                if (userHash && config.ngUserHashes.includes(userHash)) {
                    shouldDelete = true;
                    deleteReason = `NG User Hash: ${userHash.substring(0, 10)}...`;
                }
            }

            // コメントテキストチェック(正規表現対応)
            if (!shouldDelete) {
                const commentText = getCommentText(comment);
                for (const ngWord of config.ngWords) {
                    try {
                        const regex = new RegExp(ngWord, 'i');
                        if (regex.test(commentText)) {
                            shouldDelete = true;
                            deleteReason = `NG Word: ${ngWord}`;
                            break;
                        }
                    } catch (e) {
                        // 正規表現エラーの場合は通常の文字列比較
                        if (commentText.toLowerCase().includes(ngWord.toLowerCase())) {
                            shouldDelete = true;
                            deleteReason = `NG Word (literal): ${ngWord}`;
                            break;
                        }
                    }
                }
            }

            if (shouldDelete) {
                commentsToDelete.add(comment);
                console.log('NG Comment identified:', deleteReason);

                // 5ch風連携が有効で、かつスクリプトが動作している場合
                if (config.enable5chStyleIntegration && is5chStyleScriptActive()) {
                    const tempId = getTempIdFromComment(comment);
                    if (tempId) {
                        tempIdsToDelete.add(tempId);
                        console.log('Temp ID marked for deletion:', tempId);
                    }
                }
            }
        });

        // 2. 5ch風連携が有効な場合、削除対象の仮IDと同じ仮IDのコメントをすべて削除対象に追加
        if (config.enable5chStyleIntegration && is5chStyleScriptActive() && tempIdsToDelete.size > 0) {
            tempIdsToDelete.forEach(tempId => {
                const sameIdComments = getCommentsByTempId(tempId);
                sameIdComments.forEach(comment => {
                    commentsToDelete.add(comment);
                });
                deletedTempIds.add(tempId);
                console.log(`Added ${sameIdComments.length} comments for temp ID: ${tempId}`);
            });
        }

        // 3. 実際にコメントを削除
        commentsToDelete.forEach(comment => {
            if (!comment.classList.contains('ng-deleted')) {
                deletedCount++;
                const commentId = getCommentId(comment);
                deletedComments.add(commentId || `unknown_${Date.now()}`);

                comment.classList.add('ng-deleted');
                comment.style.display = 'none';

                // DOMから完全に除去(空きを詰める)
                if (comment.parentNode) {
                    comment.remove();
                }
            }
        });

        // 4. NGでないコメントは表示を復元
        comments.forEach(comment => {
            if (!commentsToDelete.has(comment)) {
                comment.classList.remove('ng-deleted');
                comment.style.display = '';
            }
        });

        // フィルタリング結果をコンソールに表示
        if (deletedCount > 0) {
            let message = `${deletedCount} comments deleted by NG settings`;
            if (is5chStyleScriptActive() && tempIdsToDelete.size > 0) {
                message += ` (${tempIdsToDelete.size} temp IDs affected)`;
            }
            console.log(message);
        }
    }

    // NG追加ボタンの追加
    function addNGButtons() {
        const comments = document.querySelectorAll('.comment_list_item:not(.ng-deleted)');

        comments.forEach(comment => {
            // 既にボタンが追加されている場合はスキップ
            if (comment.querySelector('.ng-add-btn') || comment.hasAttribute('data-ng-buttons-added')) return;

            const idElement = comment.querySelector('.id');
            if (idElement) {
                const commentId = getCommentId(comment);
                const userHash = getUserHash(comment);

                // コメントID用NGボタン
                const ngCommentBtn = document.createElement('button');
                ngCommentBtn.textContent = 'Del';
                ngCommentBtn.className = 'ng-add-btn';

                let buttonTitle = `コメントID: ${commentId} を削除`;
                if (is5chStyleScriptActive()) {
                    const tempId = getTempIdFromComment(comment);
                    if (tempId) {
                        const sameIdComments = getCommentsByTempId(tempId);
                        buttonTitle += `\n同じ仮ID(${tempId.substring(0, 10)}...)の${sameIdComments.length}件のコメントも削除されます`;
                    }
                }
                ngCommentBtn.title = buttonTitle;

                ngCommentBtn.onclick = async function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    if (commentId) {
                        const config = await getConfig();
                        if (!config.ngCommentIds.includes(commentId)) {
                            config.ngCommentIds.push(commentId);
                            await saveConfig(config);
                            await filterComments();

                            let alertMessage = `コメントID: ${commentId} をNGリストに追加し、削除しました`;
                            if (is5chStyleScriptActive() && config.enable5chStyleIntegration) {
                                const tempId = getTempIdFromComment(comment);
                                if (tempId && deletedTempIds.has(tempId)) {
                                    const sameIdComments = getCommentsByTempId(tempId);
                                    alertMessage += `\n※同じ仮IDの${sameIdComments.length}件のコメントも削除されました`;
                                }
                            }
                            alert(alertMessage);
                        } else {
                            alert('このコメントは既にNGリストに追加されています');
                        }
                    }
                };

                // ユーザーハッシュ用NGボタン
                const ngUserBtn = document.createElement('button');
                ngUserBtn.textContent = 'User';
                ngUserBtn.className = 'ng-add-btn';
                ngUserBtn.title = userHash ? `ユーザー: ${userHash.substring(0, 10)}... のコメントを全削除` : 'ユーザー情報取得不可';

                ngUserBtn.onclick = async function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    if (userHash) {
                        const config = await getConfig();
                        if (!config.ngUserHashes.includes(userHash)) {
                            config.ngUserHashes.push(userHash);
                            await saveConfig(config);
                            await filterComments();
                            alert(`ユーザーをNGリストに追加し、該当コメントを削除しました\nハッシュ: ${userHash.substring(0, 20)}...`);
                        } else {
                            alert('このユーザーは既にNGリストに追加されています');
                        }
                    } else {
                        alert('ユーザー情報を取得できませんでした');
                    }
                };

                idElement.appendChild(ngCommentBtn);
                if (userHash) {
                    idElement.appendChild(ngUserBtn);
                } else {
                    ngUserBtn.disabled = true;
                    ngUserBtn.style.opacity = '0.5';
                    idElement.appendChild(ngUserBtn);
                }

                // ボタン追加済みマークを設定
                comment.setAttribute('data-ng-buttons-added', 'true');
            }
        });
    }

    // 設定ボタンの追加(UIの重なりを回避)
    function addSettingsButton() {
        const commentSection = document.getElementById('ko_comment');
        if (commentSection && !document.getElementById('ng-settings-btn')) {
            const titleBar = commentSection.querySelector('.title_bar h2');
            if (titleBar) {
                const settingsBtn = document.createElement('button');
                settingsBtn.id = 'ng-settings-btn';
                settingsBtn.textContent = 'NG設定';
                settingsBtn.title = 'NGコメント設定を開く';

                // タイトルバーの後に独立したブロック要素として追加
                const buttonContainer = document.createElement('div');
                buttonContainer.style.cssText = `
                    margin: 5px 0;
                    padding: 0;
                `;
                buttonContainer.appendChild(settingsBtn);

                // titleBarの親要素に追加
                titleBar.parentNode.insertBefore(buttonContainer, titleBar.nextSibling);

                settingsBtn.onclick = async function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    const modal = document.getElementById('ng-settings-modal') || createSettingsModal();
                    modal.style.display = 'block';
                    await updateNGList();
                };
            }
        }
    }

    // 削除されたコメントの復元
    async function restoreDeletedComments() {
        if (confirm('削除されたコメントを復元しますか?(NGリストは保持されます)')) {
            alert('削除されたコメントを復元するにはページを再読み込みしてください。\n必要に応じてNG設定を調整できます。');
            location.reload();
        }
    }

    // 公式NG設定のインポート
    async function importOfficialNG() {
        const officialNGData = getOfficialNGData();
        if (officialNGData.length === 0) {
            alert('公式NG設定が見つかりません');
            return;
        }

        const config = await getConfig();
        let importedCount = 0;

        officialNGData.forEach(ngItem => {
            if (ngItem.type === "1" && ngItem.source) { // type "1" = ユーザーID
                if (!config.ngUserHashes.includes(ngItem.source)) {
                    config.ngUserHashes.push(ngItem.source);
                    importedCount++;
                }
            }
        });

        if (importedCount > 0) {
            await saveConfig(config);
            await updateNGList();
            await filterComments();
            alert(`公式NG設定から ${importedCount} 件のユーザーハッシュをインポートしました`);
        } else {
            alert('新しいユーザーハッシュは見つかりませんでした(既に登録済み)');
        }
    }

    // エクスポート機能
    async function exportConfig() {
        const config = await getConfig();
        const exportData = {
            ...config,
            exportedAt: new Date().toISOString(),
            version: "2025-06-24-5ch-integration-ui-improved"
        };

        const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `nicoseiga-comment-ng-config-ui-improved-${new Date().toISOString().split('T')[0]}.json`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);

        alert('設定をエクスポートしました');
    }

    // インポート機能
    async function importConfig(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = async function(e) {
                try {
                    const importedConfig = JSON.parse(e.target.result);

                    // 設定の妥当性チェック
                    if (!importedConfig.ngCommentIds || !importedConfig.ngWords || !importedConfig.ngUserHashes) {
                        throw new Error('不正な設定ファイルです');
                    }

                    // 現在の設定と統合
                    const currentConfig = await getConfig();
                    const mergedConfig = {
                        ngCommentIds: [...new Set([...currentConfig.ngCommentIds, ...importedConfig.ngCommentIds])],
                        ngWords: [...new Set([...currentConfig.ngWords, ...importedConfig.ngWords])],
                        ngUserHashes: [...new Set([...currentConfig.ngUserHashes, ...importedConfig.ngUserHashes])],
                        enable5chStyleIntegration: importedConfig.enable5chStyleIntegration !== undefined ? importedConfig.enable5chStyleIntegration : currentConfig.enable5chStyleIntegration
                    };

                    await saveConfig(mergedConfig);
                    await updateNGList();
                    await filterComments();
                    alert('設定をインポートしました');
                    resolve();
                } catch (error) {
                    alert('設定ファイルの読み込みに失敗しました: ' + error.message);
                    reject(error);
                }
            };
            reader.onerror = () => {
                alert('ファイルの読み込みに失敗しました');
                reject(new Error('File read error'));
            };
            reader.readAsText(file);
        });
    }

    // 設定全削除
    async function clearAllConfig() {
        if (confirm('すべてのNG設定を削除しますか?この操作は取り消せません。')) {
            const defaultConfig = {
                ngCommentIds: [],
                ngWords: [],
                ngUserHashes: [],
                enable5chStyleIntegration: true
            };
            await saveConfig(defaultConfig);
            await updateNGList();

            // 削除されたコメントをリセット
            deletedComments.clear();
            deletedTempIds.clear();

            // ページをリロードしてコメントを復元
            if (confirm('設定をクリアしました。削除されたコメントを復元するためにページを再読み込みしますか?')) {
                location.reload();
            }
        }
    }

    // モーダルのイベント設定
    function setupModalEvents() {
        const modal = document.getElementById('ng-settings-modal');
        if (!modal) return;

        // モーダルを閉じる
        const closeBtn = document.getElementById('ng-close');
        if (closeBtn) {
            closeBtn.onclick = function() {
                modal.style.display = 'none';
            };
        }

        // 外側クリックで閉じる
        modal.onclick = function(event) {
            if (event.target === modal) {
                modal.style.display = 'none';
            }
        };

        // タブ切り替え
        document.querySelectorAll('.ng-tab-btn').forEach(btn => {
            btn.onclick = function() {
                const tabId = this.dataset.tab;

                // タブボタンの状態更新
                document.querySelectorAll('.ng-tab-btn').forEach(b => b.classList.remove('active'));
                this.classList.add('active');

                // タブパネルの表示切り替え
                document.querySelectorAll('.ng-tab-panel').forEach(panel => panel.classList.remove('active'));
                const targetPanel = document.getElementById(`tab-${tabId}`);
                if (targetPanel) {
                    targetPanel.classList.add('active');
                }
            };
        });

        // 追加ボタンのイベント
        setupAddButtonEvents();

        // 設定タブのイベント
        setupSettingsTabEvents();

        // データ管理ボタンのイベント
        setupDataManagementEvents();

        // Enterキーで追加
        ['ng-commentid-input', 'ng-word-input', 'ng-userhash-input'].forEach(id => {
            const element = document.getElementById(id);
            if (element) {
                element.onkeypress = function(e) {
                    if (e.key === 'Enter') {
                        const addBtn = this.nextElementSibling;
                        if (addBtn) addBtn.click();
                    }
                };
            }
        });
    }

    // 追加ボタンのイベント設定
    function setupAddButtonEvents() {
        const commentIdAddBtn = document.getElementById('ng-commentid-add');
        if (commentIdAddBtn) {
            commentIdAddBtn.onclick = async function() {
                const input = document.getElementById('ng-commentid-input');
                const value = input.value.trim();
                if (value) {
                    const config = await getConfig();
                    if (!config.ngCommentIds.includes(value)) {
                        config.ngCommentIds.push(value);
                        await saveConfig(config);
                        await updateNGList();
                        await filterComments();
                        input.value = '';

                        let alertMessage = `コメントID: ${value} をNGリストに追加し、該当コメントを削除しました`;
                        if (is5chStyleScriptActive() && config.enable5chStyleIntegration) {
                            alertMessage += '\n該当する仮IDのコメントをまとめて削除しました';
                        }
                        alert(alertMessage);
                    } else {
                        alert('このコメントIDは既にNGリストに追加されています');
                    }
                }
            };
        }

        const wordAddBtn = document.getElementById('ng-word-add');
        if (wordAddBtn) {
            wordAddBtn.onclick = async function() {
                const input = document.getElementById('ng-word-input');
                const value = input.value.trim();
                if (value) {
                    const config = await getConfig();
                    if (!config.ngWords.includes(value)) {
                        config.ngWords.push(value);
                        await saveConfig(config);
                        await updateNGList();
                        await filterComments();
                        input.value = '';

                        let alertMessage = `NGワード: ${value} をNGリストに追加し、該当コメントを削除しました`;
                        if (is5chStyleScriptActive() && config.enable5chStyleIntegration) {
                            alertMessage += '\n該当する仮IDのコメントをまとめて削除しました';
                        }
                        alert(alertMessage);
                    } else {
                        alert('このワードは既にNGリストに追加されています');
                    }
                }
            };
        }

        const userHashAddBtn = document.getElementById('ng-userhash-add');
        if (userHashAddBtn) {
            userHashAddBtn.onclick = async function() {
                const input = document.getElementById('ng-userhash-input');
                const value = input.value.trim();
                if (value) {
                    const config = await getConfig();
                    if (!config.ngUserHashes.includes(value)) {
                        config.ngUserHashes.push(value);
                        await saveConfig(config);
                        await updateNGList();
                        await filterComments();
                        input.value = '';
                        alert(`ユーザーハッシュをNGリストに追加し、該当コメントを削除しました`);
                    } else {
                        alert('このユーザーハッシュは既にNGリストに追加されています');
                    }
                }
            };
        }
    }

    // 設定タブのイベント設定
    async function setupSettingsTabEvents() {
        const config = await getConfig();

        // 5ch風連携の設定
        const enable5chIntegrationCheckbox = document.getElementById('enable-5ch-integration');
        if (enable5chIntegrationCheckbox) {
            enable5chIntegrationCheckbox.checked = config.enable5chStyleIntegration;
            enable5chIntegrationCheckbox.onchange = async function() {
                const newConfig = await getConfig();
                newConfig.enable5chStyleIntegration = this.checked;
                await saveConfig(newConfig);

                // 設定変更後にフィルタリングを再実行
                deletedComments.clear();
                deletedTempIds.clear();
                restoreAllComments();
                await filterComments();

                alert('5ch風連携設定を変更しました。フィルタリングを再実行しました。');
            };
        }

        // 公式NG設定インポートボタン
        const importOfficialBtn = document.getElementById('import-official-ng');
        if (importOfficialBtn) {
            importOfficialBtn.onclick = importOfficialNG;
        }
    }

    // データ管理のイベント設定
    function setupDataManagementEvents() {
        const exportBtn = document.getElementById('export-config');
        if (exportBtn) {
            exportBtn.onclick = exportConfig;
        }

        const importBtn = document.getElementById('import-config');
        const importFile = document.getElementById('import-file');
        if (importBtn && importFile) {
            importBtn.onclick = function() {
                importFile.click();
            };

            importFile.onchange = function(e) {
                const file = e.target.files[0];
                if (file) {
                    importConfig(file);
                    e.target.value = ''; // ファイル選択をリセット
                }
            };
        }

        const clearBtn = document.getElementById('clear-all-config');
        if (clearBtn) {
            clearBtn.onclick = clearAllConfig;
        }

        const restoreBtn = document.getElementById('restore-comments');
        if (restoreBtn) {
            restoreBtn.onclick = restoreDeletedComments;
        }
    }

    // 新しいコメントの監視(最優先でフィルタリングを実行)
    function observeComments() {
        const commentSection = document.getElementById('ko_comment');
        if (!commentSection) return;

        const observer = new MutationObserver(function(mutations) {
            let shouldUpdate = false;
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    // コメント関連の変更があった場合
                    const hasCommentChanges = Array.from(mutation.addedNodes).some(node =>
                        node.nodeType === Node.ELEMENT_NODE &&
                        (node.classList && node.classList.contains('comment_list_item') ||
                         node.querySelector && node.querySelector('.comment_list_item'))
                    );
                    if (hasCommentChanges) {
                        shouldUpdate = true;
                    }
                }
            });

            if (shouldUpdate) {
                // フィルタリングを最優先で実行
                setTimeout(async () => {
                    await filterComments();
                    addNGButtons();
                }, 100);
            }
        });

        observer.observe(commentSection, {
            childList: true,
            subtree: true
        });

        console.log('Comment observer initialized');
    }

    // DOM変更の継続的な監視
    function observeGlobalChanges() {
        const globalObserver = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList') {
                    const addedNodes = Array.from(mutation.addedNodes);
                    const hasCommentChanges = addedNodes.some(node =>
                        node.nodeType === Node.ELEMENT_NODE &&
                        (node.querySelector && node.querySelector('.comment_list_item') ||
                         node.classList && node.classList.contains('comment_list_item'))
                    );

                    if (hasCommentChanges) {
                        setTimeout(async () => {
                            if (!document.getElementById('ng-settings-btn')) {
                                addSettingsButton();
                            }
                            await filterComments();
                            addNGButtons();
                        }, 200);
                    }
                }
            });
        });

        globalObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        console.log('Global observer initialized');
    }

    // 定期的なチェック(フォールバック)
    function startPeriodicCheck() {
        setInterval(async () => {
            // 設定ボタンの存在確認
            if (!document.getElementById('ng-settings-btn') && document.getElementById('ko_comment')) {
                console.log('Settings button missing, re-adding...');
                addSettingsButton();
            }

            // フィルタリングの再実行(最優先)
            await filterComments();

            // NGボタンの存在確認
            const comments = document.querySelectorAll('.comment_list_item:not(.ng-deleted)');
            const commentsWithButtons = document.querySelectorAll('.comment_list_item[data-ng-buttons-added]:not(.ng-deleted)');

            if (comments.length > 0 && commentsWithButtons.length < comments.length) {
                console.log('NG buttons missing, re-adding...');
                addNGButtons();
            }
        }, 3000);
    }

    // 初期化の試行
    async function attemptInitialization() {
        initAttempts++;

        const commentSection = document.getElementById('ko_comment');
        const hasComments = document.querySelectorAll('.comment_list_item').length > 0;

        if (commentSection && (hasComments || initAttempts > 20)) {
            if (!isInitialized) {
                console.log('Initializing NG Delete script with 5ch integration (attempt', initAttempts, ')');
                isInitialized = true;

                // 5ch風スクリプトの状態をチェック
                const is5chActive = is5chStyleScriptActive();
                console.log('5ch style script active:', is5chActive);

                // コメントデータのキャッシュ
                parseCommentsData();

                // 最優先でフィルタリング実行
                await filterComments();

                // UI要素の追加
                addSettingsButton();
                createSettingsModal();
                addNGButtons();

                // 監視の開始
                observeComments();
                observeGlobalChanges();
                startPeriodicCheck();

                // 公式NG設定の確認(デバッグ用)
                const officialNG = getOfficialNGData();
                if (officialNG.length > 0) {
                    console.log('公式NG設定が検出されました:', officialNG.length, '件');
                }

                console.log('NG Delete script with 5ch integration initialized successfully');
            }
        } else if (initAttempts < MAX_INIT_ATTEMPTS) {
            setTimeout(attemptInitialization, 500);
        } else {
            console.log('Failed to initialize NG Delete script after', MAX_INIT_ATTEMPTS, 'attempts');
        }
    }

    // 初期化
    function init() {
        console.log('NicoSeiga Comment NG Delete Script with 5ch integration starting...');

        // ページが完全に読み込まれるまで待機
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(attemptInitialization, 1000);
            });
            return;
        }

        // 即座に初期化を試行
        setTimeout(attemptInitialization, 1000);
    }

    // スクリプト開始
    init();

})();