NicoSeigaCommentNG

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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

})();