YouTube "Not Interested"-related One-Click Buttons

Add one-click buttons for actions like "Not interested", "Don't like", "Already watched", "Don't recommend channel" on YouTube.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           YouTube "Not Interested"-related One-Click Buttons
// @name:ja        YouTube の「興味なし」系1発クリックボタン
// @namespace      https://github.com/tommyktech/YouTubeNotInterestedButton
// @description    Add one-click buttons for actions like "Not interested", "Don't like", "Already watched", "Don't recommend channel" on YouTube.
// @description:ja YouTubeの「興味なし」「好みではない」「見たことがある」「チャンネルをおすすめしない」などを1発で実行できるボタンを設置します
// @match          https://www.youtube.com/
// @match          https://www.youtube.com/?*
// @match          https://www.youtube.com/watch*
// @match          https://www.youtube.com/feed/history
// @grant          GM_addStyle
// @grant          GM_registerMenuCommand
// @grant          GM_getValue
// @grant          GM_setValue
// @run-at         document-idle
// @version        0.25
// @homepageURL    https://github.com/tommyktech/YouTubeNotInterestedButton
// @supportURL     https://github.com/tommyktech/YouTubeNotInterestedButton/issues
// @author         https://github.com/tommyktech
// @license        Apache License 2.0
// ==/UserScript==
/////////////// Modal ///////////////
GM_addStyle(`
  #tm-config-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,0.5);
    z-index: 9999999;
    widht:100%;
    height:100%;
  }
  #tm-config-content {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: white;
    padding: 32px 32px 16px 32px;
    padding-top: 30px;
    border-radius: 8px;
    width: 400px;
    font-size: 18px;
    box-shadow: 0px 0px 10px rgba(0,0,0,0.3);
    z-index: 9999998;
  }
  #tm-config-content h2 {
    margin: 0 0 16px 0;
    text-align: left;
  }
  #tm-config-content label {
    display: block;
    margin-bottom: 8px;
    cursor: pointer;
  }
  #tm-config-msg {
    font-size: 18px;
  }
  #tm-config-close {
    position: absolute;
    top: 3px;
    right: 16px;
    cursor: pointer;
    font-size: 32px;
    font-weight: bold;
  }
  #tm-config-msg {
    color: green;
    margin-bottom: 8px;
    height: 18px;
  }

  #tm-reload-btn {
    margin-left: 8px;
    right: 18px;
    position: absolute;
    bottom: 16px;
    width: 100px;
    height: 30px;
    font-size: 18px;
  }
`);

/////////////// Buttons ///////////////
GM_addStyle(`
  div.yt-lockup-metadata-view-model__menu-button button.yt-spec-button-shape-next {
    width: 60px !important;
    height: 44px !important;
  }
  ytm-menu-renderer ytm-menu button c3-icon {
    width: 50px !important;
    height: 50px !important;
  }
  .additional_button_container {
    // position: absolute;
    padding: 0px;
    margin-right: 0px;
    border: none;
    bottom: 0px;
    right: 0px;
    display: flex;
    justify-content: flex-end; /* align to right */

  }
  .additional-btn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    float: left;
    background: transparent;
    border: none;
    z-index: 2000;
    cursor: pointer;
    padding: 6px 9px 6px 8px;
  }
  .additional-btn svg {
    padding: 0px;
    height: 24px;
    width: 24px;
    stroke: gray;
    fill: gray;
    stroke-width:0.5px;
  }

  div.yt-lockup-metadata-view-model__text-container {
    width:100%;
  }

  /* align button container to the right */
  .yt-content-metadata-view-model__metadata-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap; /* fixed at v0.24: Make the buttons wrap to the bottom. */
  }
  .additional_button_container {
    margin-left: auto;
  }

  /* // delete "New" badge
  yt-content-metadata-view-model div.yt-content-metadata-view-model__metadata-row:nth-child(3) {
    display: none !important;
  }
  */

  a.yt-lockup-metadata-view-model__title {
    line-height: 1.8rem;
  }
  span.yt-content-metadata-view-model__metadata-text {
    line-height: 1.4rem;
  }

  .delete_history_button_container {
    position: absolute;
    padding: 0px;
    // margin-right: 6px;
    border: none;
    top: 32px;
    right: 0px;
  }
  .delete_history_button_container > button {
    padding-top: 10px;
  }


  /* Checkbox */
  .tm-checkbox {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 8px;
    cursor: pointer;
  }

  /* SVG icon */
  .tm-checkbox-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    fill: currentColor;
    vertical-align: middle;
    margin: 0 4px 3px 4px;
  }
`);

(function () {
    'use strict';

    /////////////////////////////////////////////////// Config Modal //////////////////////////////////////////////////////
    let installed_flag = "installed_v0.17";
    const installed = GM_getValue(installed_flag, false);
    GM_registerMenuCommand("Open Config", openConfigModal);

    if (!installed) {
        GM_setValue(installed_flag, true);
        window.addEventListener("load", () => openConfigModal());
    }

    let saveMsgTimer = null;
    const FLAG_NOT_INTERESTED = "flag_not_interested";
    const FLAG_DONT_RECOMMEND_CHANNEL = "flag_dont_recommend_channel";
    const FLAG_ALREADY_WATCHED = "flag_already_watched";
    const FLAG_DONT_LIKE = "flag_dont_like";
    const FLAG_DELETE_HISTORY = "flag_delete_history";
    // SVG PATH LIST
    const DONT_RECOMMEND_CHANNEL_SVG_PATH = "M12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1Zm0 2a9 9 0 110 18.001A9 9 0 0112 3Zm4 8H8a1 1 0 000 2h8a1 1 0 000-2Z";
    const NOT_INTERESTED_SVG_PATH = "M12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1Zm0 2a9 9 0 018.246 12.605L4.755 6.661A8.99 8.99 0 0112 3ZM3.754 8.393l15.491 8.944A9 9 0 013.754 8.393Z";
    const ALREADY_WATCHED_SVG_PATH = "m6.666 5.303 2.122 1.272c4.486-1.548 10.002.26 12.08 5.426-.2.5-.435.968-.696 1.406l1.717 1.03c.41-.69.752-1.42 1.02-2.178a.77.77 0 000-.516l-.18-.473C19.998 4.436 12.294 2.448 6.667 5.303Zm-5.524.183a1.003 1.003 0 00.343 1.371l1.8 1.08a11.8 11.8 0 00-2.193 3.805.77.77 0 000 .516c2.853 8.041 12.37 9.784 18.12 5.235l2.273 1.364a1 1 0 101.03-1.714l-20-12a1 1 0 00-1.373.343Zm11.064 2.52L12 8c-.248 0-.49.022-.727.066l4.54 2.724a4 4 0 00-3.607-2.785ZM5.04 8.99l3.124 1.874C8.057 11.224 8 11.606 8 12l.005.206a4 4 0 003.79 3.79L12 16c1.05 0 2.057-.414 2.803-1.152l2.54 1.524C12.655 19.48 5.556 18.024 3.133 12A9.6 9.6 0 015.04 8.99ZM10 12v-.033l2.967 1.78a1.99 1.99 0 01-2.307-.262 2 2 0 01-.65-1.28L10 12Z";
    const DONT_LIKE_SVG_PATH = "m11.31 2 .392.007c1.824.06 3.61.534 5.223 1.388l.343.189.27.154c.264.152.56.24.863.26l.13.004H20.5a1.5 1.5 0 011.5 1.5V11.5a1.5 1.5 0 01-1.5 1.5h-1.79l-.158.013a1 1 0 00-.723.512l-.064.145-2.987 8.535a1 1 0 01-1.109.656l-1.04-.174a4 4 0 01-3.251-4.783L10 15H5.938a3.664 3.664 0 01-3.576-2.868A3.682 3.682 0 013 9.15l-.02-.088A3.816 3.816 0 014 5.5v-.043l.008-.227a2.86 2.86 0 01.136-.664l.107-.28A3.754 3.754 0 017.705 2h3.605ZM7.705 4c-.755 0-1.425.483-1.663 1.2l-.032.126a.818.818 0 00-.01.131v.872l-.587.586a1.816 1.816 0 00-.524 1.465l.038.23.02.087.21.9-.55.744a1.686 1.686 0 00-.321 1.18l.029.177c.17.76.844 1.302 1.623 1.302H10a2.002 2.002 0 011.956 2.419l-.623 2.904-.034.208a2.002 2.002 0 001.454 2.139l.206.045.21.035 2.708-7.741A3.001 3.001 0 0118.71 11H20V6.002h-1.47c-.696 0-1.38-.183-1.985-.528l-.27-.155-.285-.157A10.002 10.002 0 0011.31 4H7.705Z";
    const DELETE_STORY_SVG_PATH = "M19 3h-4V2a1 1 0 00-1-1h-4a1 1 0 00-1 1v1H5a2 2 0 00-2 2h18a2 2 0 00-2-2ZM6 19V7H4v12a4 4 0 004 4h8a4 4 0 004-4V7h-2v12a2 2 0 01-2 2H8a2 2 0 01-2-2Zm4-11a1 1 0 00-1 1v8a1 1 0 102 0V9a1 1 0 00-1-1Zm4 0a1 1 0 00-1 1v8a1 1 0 002 0V9a1 1 0 00-1-1Z";


    function createCheckbox(id, labelText, defaultValue, svgPath) {
        const container = document.createElement("label");
        container.className = "tm-checkbox";

        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.id = id;
        checkbox.checked = GM_getValue(id, defaultValue);

        checkbox.addEventListener("change", () => {
            GM_setValue(id, checkbox.checked);
            showSavedMessage();

            const reloadBtn = document.getElementById("tm-reload-btn");
            if (reloadBtn) reloadBtn.removeAttribute("disabled");
        });

        const SVG_NS = "http://www.w3.org/2000/svg";
        const svg = document.createElementNS(SVG_NS, "svg");
        svg.classList.add("tm-checkbox-icon");
        svg.setAttributeNS(null, "viewBox", "0 0 24 24");

        const path = document.createElementNS(SVG_NS, "path");
        path.setAttribute("d", svgPath);

        svg.appendChild(path);

        const textSpan = document.createElement("span");
        textSpan.textContent = labelText;

        container.appendChild(checkbox);
        container.appendChild(svg);
        container.appendChild(textSpan);

        return container;
    }


    function openConfigModal() {
        if (document.getElementById("tm-config-overlay")) return;

        const overlay = document.createElement("div");
        overlay.id = "tm-config-overlay";

        const box = document.createElement("div");
        box.id = "tm-config-content";

        // X Button
        const closeX = document.createElement("div");
        closeX.id = "tm-config-close";
        closeX.textContent = "×";
        closeX.addEventListener("click", () => overlay.remove());
        box.appendChild(closeX);

        const title = document.createElement("h2");
        title.textContent = "Not Interested Buttons Config";

        // Message area for save notification
        const msg = document.createElement("div");
        msg.id = "tm-config-msg";

        // not interested checkbox container
        const not_interested_elem = createCheckbox(
            FLAG_NOT_INTERESTED,
            "Not Interested",
            true,
            NOT_INTERESTED_SVG_PATH
        );
        const dont_recommend_channel_elem = createCheckbox(
            FLAG_DONT_RECOMMEND_CHANNEL,
            "Don't Recommend Channel",
            true,
            DONT_RECOMMEND_CHANNEL_SVG_PATH
        );
        // already watched checkbox container
        const already_watched_elem = createCheckbox(
            FLAG_ALREADY_WATCHED,
            "Not Interested -> Already Watched",
            true,
            ALREADY_WATCHED_SVG_PATH
        );
        // don't like checkbox container
        const dont_like_elem = createCheckbox(
            FLAG_DONT_LIKE,
            "Not Interested -> Don't Like",
            true,
            DONT_LIKE_SVG_PATH
        );

        // delete history checkbox container
        const delete_history_elem = createCheckbox(
            FLAG_DELETE_HISTORY,
            "Delete History (in History page)",
            true,
            DELETE_STORY_SVG_PATH
        );
        const reloadBtn = document.createElement("button");
        reloadBtn.id = "tm-reload-btn";
        reloadBtn.textContent = "Reload";
        // reloadBtn.disabled = true;
        reloadBtn.setAttribute("disabled", "disabled");
        reloadBtn.addEventListener("click", () => location.reload());

        // DOM
        box.appendChild(title);
        box.appendChild(not_interested_elem);
        box.appendChild(already_watched_elem);
        box.appendChild(dont_like_elem);
        box.appendChild(dont_recommend_channel_elem);
        box.appendChild(delete_history_elem);
        box.appendChild(msg);
        box.appendChild(reloadBtn);

        overlay.appendChild(box);
        document.body.appendChild(overlay);

        // Close modal with ESC key
        function escClose(e) {
            if (e.key === "Escape") {
                overlay.remove();
                document.removeEventListener("keydown", escClose);
            }
        }
        document.addEventListener("keydown", escClose);

        // Close modal when clicking outside
        overlay.addEventListener("click", (e) => {
            if (e.target === overlay) overlay.remove();
        });
    }

    // Show "Saved" message for only 3 seconds
    function showSavedMessage() {
        const msg = document.getElementById("tm-config-msg");
        if (!msg) return;

        // Clear existing timer
        if (saveMsgTimer !== null) {
            clearTimeout(saveMsgTimer);
            saveMsgTimer = null;
        }

        msg.textContent = "Saved";

        saveMsgTimer = setTimeout(() => {
            msg.textContent = "";
            saveMsgTimer = null;
        }, 3000);
    }


    ///////////////////////////////////////////////// Append Not Interested Buttons //////////////////////////////////////////////////////
    var TILE_SELECTOR = 'yt-lockup-view-model';
    var MENU_BUTTON_SELECTOR = 'div.yt-lockup-metadata-view-model__menu-button button-view-model button';
    var MENU_SELECTOR = 'ytd-popup-container tp-yt-iron-dropdown';
    const PROCESSED_ATTR = 'data-yt-menu-opener-added';

    // Show overlay notice on top of screen
    function showOverlay(msg, duration = 3000, backgroundColor="white") {
        let el = document.createElement("div");
        el.textContent = msg;
        Object.assign(el.style, {
            position: "fixed",
            top: "0",
            left: "0",
            width: "100%",
            padding: "10px 16px",
            backgroundColor: backgroundColor,
            opacity: 0.5,
            color: "black",
            fontSize: "24px",
            zIndex: "99999",
            textAlign: "center"
        });
        document.body.appendChild(el);
        console.log("Displayed message:", msg);

        setTimeout(() => el.remove(), duration);
    }

    // Wait for selected element to appear
    function waitForElement(selector, rootElem = null, intervalMs = 100, timeoutMs = 2000) {
        return new Promise((resolve, reject) => {
            const start = Date.now();

            const timer = setInterval(() => {
                const elem = (rootElem || document).querySelector(selector);
                if (elem && elem.style.display != "none") {
                    clearInterval(timer);
                    resolve(elem);
                    return;
                }

                if (Date.now() - start > timeoutMs) {
                    clearInterval(timer);
                    reject(new Error("Timeout: Can't find the element:" + selector));
                }
            }, intervalMs);
        });
    }

    // dispatch tap action
    function dispatchTapLike(target) {
        if (!target) return false;
        try {target.focus({preventScroll:true}); } catch(e){}

        /*
        // 1) dispatch 'tap' which libraries like Polymer listen
        try {
            target.dispatchEvent(new CustomEvent('tap', { bubbles: true, cancelable: true, composed: true }));
            console.log('dispatched CustomEvent tap');
        } catch(e) { console.warn('tap custom event failed', e); }

        // 2) dispatch a series of pointer / mouse actions(including pointerType:'touch')
        try {
            const r = target.getBoundingClientRect();
            const cx = Math.round(r.left + r.width/2);
            const cy = Math.round(r.top + r.height/2);
            const pOpts = {
                bubbles: true, cancelable: true, composed: true,
                clientX: cx, clientY: cy, screenX: cx, screenY: cy,
                pointerId: Date.now() & 0xFFFF, pointerType: 'touch', isPrimary: true, pressure: 0.5, buttons: 1
            };
            target.dispatchEvent(new PointerEvent('pointerdown', pOpts));
            target.dispatchEvent(new PointerEvent('pointerup', pOpts));
            target.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, clientX: cx, clientY: cy, buttons: 1 }));
            target.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, clientX: cx, clientY: cy, buttons: 1 }));
            target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, clientX: cx, clientY: cy, buttons: 1 }));
            console.log('dispatched pointer/mouse sequence');
        } catch(e) {
            console.warn('pointer/mouse sequence failed', e);
        }
        */

        // 3) Dispatch touchstart/touchend if possible(depends on browsers)
        try {
            const r = target.getBoundingClientRect();
            const cx = Math.round(r.left + r.width/2);
            const cy = Math.round(r.top + r.height/2);
            const touch = new Touch({ identifier: Date.now(), target: target, clientX: cx, clientY: cy, screenX: cx, screenY: cy, pageX: cx, pageY: cy });
            const teStart = new TouchEvent('touchstart', { bubbles: true, cancelable: true, composed: true, touches: [touch], targetTouches: [touch], changedTouches: [touch] });
            const teEnd   = new TouchEvent('touchend',   { bubbles: true, cancelable: true, composed: true, touches: [], targetTouches: [], changedTouches: [touch] });
            target.dispatchEvent(teStart);
            target.dispatchEvent(teEnd);
            console.debug('dispatched touchstart/touchend');
        } catch(e) {
            console.warn('TouchEvent creation failed or not allowed', e);
        }

        // 4) finally dispatch DOM click()
        try {
            target.click();
            console.debug('called element.click()');
        } catch(e) {
            console.warn('element.click() threw', e);
            return false;
        }
        return true;
    }

    function attachShortcutButton(tile, btnContainer, svgPath, className, overlayMessage) {
        var SVG_SELECTOR = `path[d="${svgPath}"]`

        // attach a custom 'Not Interested' button
        const btn = document.createElement('button');
        btn.classList.add('additional-btn');
        if (className) btn.classList.add(className);

        // create an SVG for the button
        const SVG_NS = "http://www.w3.org/2000/svg";
        const svg = document.createElementNS(SVG_NS, "svg");
        const path = document.createElementNS(SVG_NS, "path");
        path.setAttribute("d", svgPath);
        svg.appendChild(path);
        btn.appendChild(svg);
        btnContainer.appendChild(btn);

        // add eventlistener
        function onButtonClick(ev) {
            ev.preventDefault();
            ev.stopPropagation();
            const menuBtn = tile.querySelector(MENU_BUTTON_SELECTOR);
            if (!menuBtn) {
                console.warn('menu button not found');
                return;
            }

            // Dispatch tap action to the menu button
            dispatchTapLike(menuBtn)

            // Check if the menu appeared
            waitForElement(MENU_SELECTOR).then(dropdown_el => {
                console.debug("dropdown_el:", dropdown_el)
                // Check if the target element appeared
                waitForElement(SVG_SELECTOR, dropdown_el).then(svg_el => {
                    console.debug("svg_el:", svg_el)
                    const result = dispatchTapLike(svg_el.parentElement.parentElement)
                    if (result) {
                        showOverlay(overlayMessage);
                        btnContainer.style.display = "none";
                    }
                });
            });
        };
        btn.addEventListener('click', function(ev) {
            onButtonClick(ev);
        });
    }

    // attach 'Already Watched' button
    function attachTellUsWhyButton(tile, btnContainer, btnSvgPath, isAlreadyWatched) {
        // selector for original 'Not Interested' button
        var svgPathData = "M12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1Zm0 2a9 9 0 018.246 12.605L4.755 6.661A8.99 8.99 0 0112 3ZM3.754 8.393l15.491 8.944A9 9 0 013.754 8.393Z";
        var SVG_SELECTOR = `path[d="${svgPathData}"]`

        // attach a custom 'Already Watched' button
        const btn = document.createElement('button');
        btn.className = 'additional-btn';

        // create an SVG for the button
        const SVG_NS = "http://www.w3.org/2000/svg";
        const hide_svg = document.createElementNS(SVG_NS, "svg");
        const hide_path1 = document.createElementNS(SVG_NS, "path");
        hide_path1.setAttribute("d", btnSvgPath);
        hide_svg.appendChild(hide_path1)
        btn.appendChild(hide_svg);
        btnContainer.append(btn);

        // add eventlistener for the button
        function onClick(ev) {
            ev.preventDefault();
            ev.stopPropagation();
            const menuBtn = tile.querySelector(MENU_BUTTON_SELECTOR);
            if (!menuBtn) {
                console.warn('menu button not found');
                return;
            }

            // Dispatch a tap to the menu button
            dispatchTapLike(menuBtn);

            // Repeat waiting for the element and tapping it
            (async () => {
                try {
                    // Wait for the menu to appear
                    const dropdown_el = await waitForElement(MENU_SELECTOR);
                    console.debug("dropdown_el:", dropdown_el);

                    console.debug("Wait for the 'Not Interested' item");
                    const svg_el = await waitForElement(SVG_SELECTOR, dropdown_el);
                    console.debug("The 'Not Interested' item has been found, so tap it.:", svg_el);
                    dispatchTapLike(svg_el.parentElement.parentElement);

                    console.debug("Wait for 'Tell us why' button to appear");
                    const TELL_ME_REASON_BUTTON = "div.ytNotificationMultiActionRendererButtonContainer div:nth-child(2) button-view-model button";
                    const send_reason_button = await waitForElement(TELL_ME_REASON_BUTTON, tile);

                    console.debug("Found the 'Tell us why' button. Then push it:", TELL_ME_REASON_BUTTON);
                    await new Promise(res => setTimeout(res, 200));

                    const sendReasonButtonResult = dispatchTapLike(send_reason_button);
                    if (!sendReasonButtonResult) {
                        console.warn("Can't push 'Tell us why' button");
                        return
                    }

                    console.debug("Wait for the checkbox to appear");
                    const REASON_DIALOG_SELECTOR = "tp-yt-paper-dialog";
                    const dialog_el = await waitForElement(REASON_DIALOG_SELECTOR);

                    let CHECKBOX_SELECTOR = "ytd-dismissal-follow-up-renderer div#content div#reasons ytd-dismissal-reason-text-renderer:nth-child(1) tp-yt-paper-checkbox:nth-child(1)";
                    if (!isAlreadyWatched) {
                        CHECKBOX_SELECTOR = "ytd-dismissal-follow-up-renderer div#content div#reasons ytd-dismissal-reason-text-renderer:nth-child(2) tp-yt-paper-checkbox:nth-child(1)";
                    }
                    const checkbox_el = await waitForElement(CHECKBOX_SELECTOR, dialog_el);
                    console.debug("click the checkbox:", checkbox_el);
                    dispatchTapLike(checkbox_el);

                    console.debug("Push the submit button");
                    const SUBMIT_BUTTON_SELECTOR = "ytd-dismissal-follow-up-renderer div#buttons ytd-button-renderer#submit"
                    const submit_button = await waitForElement(SUBMIT_BUTTON_SELECTOR, dialog_el);
                    const result = dispatchTapLike(submit_button);
                    if (result) {
                        showOverlay(result? 'Sent "Already Watched"':'Failed to send "Already Watched"');
                        btnContainer.style.display = "none";
                    }
                } catch (err) {
                    console.error("Error:", err);
                }
            })();
        }
        btn.addEventListener('click', function(ev) {
            onClick(ev);
        });
    }

    // Attach custom buttons
    function attachButtons(tile, idx) {
        // check if already processed
        if (!tile || tile.hasAttribute(PROCESSED_ATTR)) return;
        tile.setAttribute(PROCESSED_ATTR, '1');
        tile.style.position = 'relative';

        // append button container
        const btnContainerName = "additional_button_container";
        const btnContainer = document.createElement('div');
        btnContainer.className = btnContainerName;

        const pathName = location.pathname;
        const tileClassList = tile.classList
        if (tileClassList.contains("ytd-rich-item-renderer")) {
            // top page
            // tile.parentElement.querySelector("yt-lockup-view-model yt-content-metadata-view-model").appendChild(btnContainer);
            tile.parentElement.querySelector("yt-content-metadata-view-model div.yt-content-metadata-view-model__metadata-row:last-child").appendChild(btnContainer);
        } else if (tileClassList.contains("ytd-item-section-renderer")) {
            if (pathName == "/feed/history") {
                btnContainer.className = "delete_history_button_container";
            }
            tile.querySelector("div.yt-lockup-view-model__metadata").appendChild(btnContainer);
            // tile.appendChild(btnContainer);
        }

        // attach buttons
        if (pathName == "/" || pathName == "/watch") {
            if (GM_getValue(FLAG_DONT_RECOMMEND_CHANNEL, true)) {
                attachShortcutButton(tile, btnContainer, DONT_RECOMMEND_CHANNEL_SVG_PATH, "", 'Sent "Don\' Recommend Channel"');
            }
            if (GM_getValue(FLAG_NOT_INTERESTED, true)) {
                attachShortcutButton(tile, btnContainer, NOT_INTERESTED_SVG_PATH, "", 'Sent "Not Interested"');
            }

            if (GM_getValue(FLAG_ALREADY_WATCHED, true)) {
                attachTellUsWhyButton(tile, btnContainer, ALREADY_WATCHED_SVG_PATH, true);
            }
            if (GM_getValue(FLAG_DONT_LIKE, true)) {
                attachTellUsWhyButton(tile, btnContainer, DONT_LIKE_SVG_PATH, false);
            }

        } else if (pathName == "/feed/history") {
            if (GM_getValue(FLAG_DELETE_HISTORY, true)) {
                attachShortcutButton(tile, btnContainer, DELETE_STORY_SVG_PATH, "delete-history-btn", 'Sent "Delete History"');
            }
        }
    }

    // attach a link to "User Feedback" page in History page
    function attachUserFeedbackLink(targetElem) {
        function createCompactLink() {
            // const outerDiv = document.createElement("div");
            const a = document.createElement("a");
            a.style.justifyContent = "flex-start";
            a.href = "https://myactivity.google.com/page?utm_source=my-activity&hl=ja&page=youtube_user_feedback";
            a.rel = "nofollow";
            a.target = "_blank";
            a.className = "yt-spec-button-shape-next yt-spec-button-shape-next--text yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--icon-leading yt-spec-button-shape-next--enable-backdrop-filter-experiment";
            a.setAttribute("aria-haspopup", "false");
            a.setAttribute("force-new-state", "true");
            a.setAttribute("aria-label", "すべての履歴を管理");
            a.setAttribute("aria-current", "false");
            a.setAttribute("aria-disabled", "false");

            // --- Icon wrapper ---
            const iconDiv = document.createElement("div");
            iconDiv.className = "yt-spec-button-shape-next__icon";
            iconDiv.setAttribute("aria-hidden", "true");

            const spanWrapper = document.createElement("span");
            spanWrapper.className = "ytIconWrapperHost";
            spanWrapper.style.width = "24px";
            spanWrapper.style.height = "24px";

            const iconShape = document.createElement("span");
            iconShape.className = "yt-icon-shape ytSpecIconShapeHost";

            const svgHolder = document.createElement("div");
            svgHolder.style.width = "100%";
            svgHolder.style.height = "100%";
            svgHolder.style.display = "block";
            svgHolder.style.fill = "currentcolor";

            const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
            svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
            svg.setAttribute("width", "24");
            svg.setAttribute("height", "24");
            svg.setAttribute("viewBox", "0 0 24 24");
            svg.setAttribute("focusable", "false");
            svg.setAttribute("aria-hidden", "true");
            svg.style.pointerEvents = "none";
            svg.style.display = "inherit";
            svg.style.width = "100%";
            svg.style.height = "100%";

            const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            path.setAttribute("d",
                              "M12.844 1h-1.687a2 2 0 00-1.962 1.616 3 3 0 01-3.92 2.263 2 2 0 00-2.38.891l-.842 1.46a2 2 0 00.417 2.507 3 3 0 010 4.525 2 2 0 00-.417 2.507l.843 1.46a2 2 0 002.38.892 3.001 3.001 0 013.918 2.263A2 2 0 0011.157 23h1.686a2 2 0 001.963-1.615 3.002 3.002 0 013.92-2.263 2 2 0 002.38-.892l.842-1.46a2 2 0 00-.418-2.507 3 3 0 010-4.526 2 2 0 00.418-2.508l-.843-1.46a2 2 0 00-2.38-.891 3 3 0 01-3.919-2.263A2 2 0 0012.844 1Zm-1.767 2.347a6 6 0 00.08-.347h1.687a4.98 4.98 0 002.407 3.37 4.98 4.98 0 004.122.4l.843 1.46A4.98 4.98 0 0018.5 12a4.98 4.98 0 001.716 3.77l-.843 1.46a4.98 4.98 0 00-4.123.4A4.979 4.979 0 0012.843 21h-1.686a4.98 4.98 0 00-2.408-3.371 4.999 4.999 0 00-4.12-.399l-.844-1.46A4.979 4.979 0 005.5 12a4.98 4.98 0 00-1.715-3.77l.842-1.459a4.98 4.98 0 004.123-.399 4.981 4.981 0 002.327-3.025ZM16 12a4 4 0 11-7.999 0 4 4 0 018 0Zm-4 2a2 2 0 100-4 2 2 0 000 4Z"
                             );

            svg.appendChild(path);
            svgHolder.appendChild(svg);
            iconShape.appendChild(svgHolder);
            spanWrapper.appendChild(iconShape);
            iconDiv.appendChild(spanWrapper);

            // --- Button label ---
            const labelDiv = document.createElement("div");
            labelDiv.className = "yt-spec-button-shape-next__button-text-content";

            const labelSpan = document.createElement("span");
            labelSpan.className = "yt-core-attributed-string yt-core-attributed-string--white-space-no-wrap";
            labelSpan.setAttribute("role", "text");
            labelSpan.textContent = "Edit User Feedbacks";

            labelDiv.appendChild(labelSpan);

            // --- Touch feedback ---
            const touch = document.createElement("yt-touch-feedback-shape");
            touch.className = "yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response";
            touch.setAttribute("aria-hidden", "true");

            const stroke = document.createElement("div");
            stroke.className = "yt-spec-touch-feedback-shape__stroke";

            const fill = document.createElement("div");
            fill.className = "yt-spec-touch-feedback-shape__fill";

            touch.appendChild(stroke);
            touch.appendChild(fill);

            // --- assemble ---
            a.appendChild(iconDiv);
            a.appendChild(labelDiv);
            a.appendChild(touch);
            // outerDiv.append(a);

            return a;
        }

        targetElem.appendChild(createCompactLink());
    }

    function scanTargets() {
        // attach buttons
        document.querySelectorAll(TILE_SELECTOR).forEach((tile, idx) => attachButtons(tile, idx));

        // attach User Feedback Link into History page
        const pathName = location.pathname;
        if (pathName == "/feed/history") {
            const elem = document.querySelector("ytd-browse-feed-actions-renderer div#contents");
            if (!elem || elem.hasAttribute(PROCESSED_ATTR)) return;
            if (elem.children.length < 7) return;

            elem.setAttribute(PROCESSED_ATTR, '1');
            console.log("attachUserFeedbackLink");
            attachUserFeedbackLink(elem);
        }
    }
    new MutationObserver(scanTargets).observe(document.body, { childList: true, subtree: true });

})();