Greasy Fork is available in English.

YouTube Special Comment Sticker

Stickies YouTube moderator and owner comments above the comment box.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         YouTube Special Comment Sticker
// @namespace    http://tampermonkey.net/
// @version      20230320
// @description  Stickies YouTube moderator and owner comments above the comment box.
// @author       AsobiTaizen
// @match        https://www.youtube.com/live_chat*
// @grant        none
// ==/UserScript==
 
(() => {
    let IS_DARK = document.querySelector('html').hasAttribute('dark');
    let IS_POPOUT = document.location.href.indexOf('popout') > -1;
    let CHAT_ID = IS_POPOUT ? document.location.href.split('=')[2] : document.location.href.split('=')[1].slice(8, 120);

    let canScroll = true;
    let chat;
    let chatSettingsButton;
    let chatSettingsStickyButton;
    let historiesSelected;
    let history;
    let historyButton;
    let itemScroller;
    let menu;
    let menuButton;
    let notice;
    let scrollButton;
    let settings;
    let settingsButton;
    let settingsData;
    let sticky;
    let stickyItemObjects;
    let stickyItemObjectsMap;
    let stickyItems;
    let stickyItemsContainer;
    let toggleSticky;
    let toggleStickyText;
    let blacklist;
    let whitelist;
    let moderator;
    let owner;
    let cancel;
    let save;

    let showSticky = () => {
        if (sticky.style.display === 'none') {
            chat = document.querySelector('yt-live-chat-app');
            itemScroller = chat.querySelector('#item-scroller');
            sticky.style.display = 'inherit';
            chat.setAttribute('style', `max-height: ${chat.offsetHeight - sticky.offsetHeight}px;`);
            itemScroller.scrollTop = itemScroller.scrollHeight;
        }
    };

    let scrollSticky = (force) => {
        if (canScroll || force) {
            stickyItems.scrollTop = stickyItems.scrollHeight;
        }
    };

    let isSettingsListMatch = (list, text) => {
        let settingsList = settingsData[list];
        let match;
        if (settingsList.text.raw) {
            match =  !!new RegExp(settingsList.text.escaped).exec(text);
        } else if (settingsList.regex.raw) {
            match =  !!new RegExp(settingsList.regex.escaped, settingsList.regex.flags).exec(text);
        } else {
            return false;
        }

        return list === 'whitelist' ? !match : match;
    };

    let isSpecial = (author) => {
        let name = author.innerText || author.content.author.name;
        if (isSettingsListMatch('blacklist', name)) {
            return false;
        } else if (isSettingsListMatch('whitelist', name)) {
            return false;
        } else if (settingsData.moderator) {
            return author.classList ? (author.classList.contains('moderator') || author.classList.contains('owner')) : author.content.author.classes.indexOf('moderator') > -1;
        } else if (settingsData.owner) {
            return author.classList ? author.classList.contains('owner') : author.content.author.classes.indexOf('owner') > -1;
        }
        return true;
    };

    let isUnique = (node) => {
        return !stickyItemObjectsMap[`${node.querySelector('#timestamp').innerText}-${node.querySelector('#author-name').innerText}-${node.querySelector('#message').innerText}`];
    };

    let reachedLocalStorageWarningLimit = () => {
        return getLocalStorageUsage() / 5 >= 0.9;
    };

    let showSavedStickyNotice = (error) => {
        if (notice.style.display === 'none' && (!notice.touched || (notice.touched && error && notice.type === 'warn')) && (error || reachedLocalStorageWarningLimit())) {
            let tooltip = notice.querySelector('#tooltip');
            let text = notice.querySelector('#text');
            let detailsText = notice.querySelector('#details-text');
            if (error) {
                notice.type = 'error';
                tooltip.classList.add('error');
                text.innerText = 'Sticky history limit reached.';
                detailsText.innerText = 'Sticky messages will be tracked but not saved until space is available. Delete sticky histories to free up space and resume saving sticky messages.';
            } else {
                notice.type = 'warn';
                tooltip.classList.remove('error');
                text.innerText = 'Sticky history limit nearing.';
                detailsText.innerText = 'Sticky messages will be tracked but not saved upon reaching the limit. Delete sticky histories to free up space.';
            }
            notice.style.display = 'initial';
            notice.querySelector('tp-yt-iron-dropdown').style.display = 'initial';
        }
    };

    let saveStickyItem = (stickyItem) => {
        let stickyItemObject = {
            authorPhoto: {
                img: {
                    src: stickyItem.querySelector('#img').getAttribute('src')
                }
            },
            content: {
                timestamp: stickyItem.querySelector('.timestamp').innerText,
                author: {
                    classes: stickyItem.querySelector('.author-name').getAttribute('class').replace('author-name ', ''),
                    name: stickyItem.querySelector('.author-name').innerText
                },
                message: Array.from(stickyItem.querySelector('#message').childNodes).reduce((result, node) => {
                    if (node.nodeName === '#text') {
                        result.push({
                            type: 'text',
                            value: node.data
                        });
                    } else if (node.nodeName === 'IMG') {
                        result.push({
                            type: 'image',
                            value: {
                                alt: node.alt,
                                src: node.src
                            }
                        });
                    } else if (node.nodeName === 'A') {
                        result.push({
                            type: 'link',
                            value: {
                                href: node.href,
                                text: node.innerText
                            }
                        });
                    }
                    return result;
                }, [])
            }
        };
        stickyItemObjects.push(stickyItemObject);

        try {
            localStorage.setItem(`youtubespecialcommentsticker-sticky-items-${CHAT_ID}`, JSON.stringify(stickyItemObjects));
            showSavedStickyNotice();
        } catch (e) {
            showSavedStickyNotice(e);
        }
    };

    let stickItem = (node) => {
        let authorElement = node.querySelector('#author-name');
        if (authorElement && isSpecial(authorElement) && isUnique(node)) {
            let stickyItem = document.createElement('div');
            let authorPhoto = document.createElement('div');
            let content = document.createElement('div');
            let timestamp = document.createElement('span');
            let author = document.createElement('div');
            let authorName = document.createElement('span');
            content.id = 'content';
            stickyItem.classList.add('sticky-item');
            content.classList.add('style-scope', 'yt-live-chat-text-message-renderer');
            timestamp.classList.add('timestamp');
            author.classList.add('author');
            authorName.classList.add('author-name');
            authorElement.classList.contains('moderator') ? authorName.classList.add('moderator') : null;
            authorElement.classList.contains('owner') ? authorName.classList.add('moderator', 'owner') : null;
            authorPhoto.innerHTML = node.querySelector('#author-photo').outerHTML.replace(/(\<|\/)(yt\-img\-shadow)/g, '$1div');
            timestamp.innerText = node.querySelector('#timestamp').innerText;
            authorName.innerText = authorElement.innerText;
            author.append(authorName);
            content.append(timestamp);
            content.append(author);
            content.append(node.querySelector('#message').cloneNode(true));
            stickyItem.append(authorPhoto.firstChild);
            stickyItem.append(content);
            stickyItems.append(stickyItem);

            showSticky();
            scrollSticky();
            saveStickyItem(stickyItem);
        }
    };

    let monitor = () => {
        let chatItems = document.querySelector('#items.style-scope.yt-live-chat-item-list-renderer');
        let observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach(stickItem);
            });
        });
        chatItems.querySelectorAll('yt-live-chat-text-message-renderer').forEach(stickItem);
        observer.observe(chatItems, { childList: true });
    };

    let setUpCss = () => {
        let style = document.createElement('style');
        document.head.appendChild(style);
        style.sheet.insertRule('#sticky {position: relative;}');
        style.sheet.insertRule('#sticky #menu.sticky-panel #contentWrapper, #sticky #menu.sticky-panel #contentWrapper #items {outline: none;}');
        style.sheet.insertRule('#sticky #menu.sticky-panel #items ytd-menu-service-item-renderer {cursor: pointer;}');
        style.sheet.insertRule('#sticky #menu.sticky-panel #items ytd-menu-service-item-renderer tp-yt-paper-item:hover, #sticky #menu.sticky-panel #items ytd-menu-service-item-renderer tp-yt-paper-item:focus:before {background-color: var(--yt-spec-10-percent-layer);}');
        style.sheet.insertRule('#sticky #overflow, #sticky #history-button {position: absolute; top: 3px; visibility: visible; padding: 0; z-index: 1; height: 25px; width: 25px;}');
        style.sheet.insertRule('#sticky #overflow {right: 16px;}');
        style.sheet.insertRule('#sticky #history-button {right: 46px;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items {max-height: 526px; width: 384px; padding: 0px 0px 8px 0px; overflow: hidden auto; outline: none;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items #history-data {position: sticky; top: 0; z-index: 1; flex-direction: row; justify-content: space-between; align-items: center; padding-top: 8px; background-color: var(--yt-spec-brand-background-primary); outline: none;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items .history-item-header {display: flex; flex-direction: row;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items .history-item-header tp-yt-paper-item {padding: 0px 0px 0px 16px;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items .history-item-chat {border-top: #e0e0e0 solid 1px;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items .history-item-edit yt-live-chat-text-input-field-renderer {max-width: 66px}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items .history-item-edit yt-live-chat-text-input-field-renderer #input {max-height: 18px; overflow-y: hidden;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items tp-yt-paper-button {line-height: initial; padding: 5px 11px 5px 11px;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items tp-yt-paper-button.history-control-button {padding: 0px; min-width: 26px;}');
        style.sheet.insertRule('#sticky #history ytd-menu-popup-renderer #items tp-yt-paper-button.history-affirmative-button yt-icon {padding: 1px;}');
        style.sheet.insertRule('#sticky .sticky-panel {outline: none; position: absolute; z-index: 1; right: 8px; top: 30px;}');
        style.sheet.insertRule('#sticky .sticky-panel #items {padding: 8px 0px;}');
        style.sheet.insertRule('#sticky .sticky-panel #items ytd-menu-service-item-renderer {cursor: default;}');
        style.sheet.insertRule('#sticky .sticky-panel tp-yt-paper-item.disabled {opacity: 0.5;}');
        style.sheet.insertRule('#sticky .sticky-panel tp-yt-paper-item.disabled yt-live-chat-text-input-field-renderer #input {visibility: hidden;}');
        style.sheet.insertRule('#sticky .sticky-panel tp-yt-paper-item {padding: 0px 16px;}');
        style.sheet.insertRule('#sticky .sticky-panel tp-yt-paper-item:hover, #sticky .sticky-panel tp-yt-paper-item:focus:before {background-color: transparent;}');
        style.sheet.insertRule('#sticky .sticky-panel tp-yt-paper-item yt-formatted-string {user-select: none; margin-right: inherit;}');
        style.sheet.insertRule('#sticky .sticky-panel tp-yt-paper-item yt-live-chat-text-input-field-renderer {width: 180px; color: var(--yt-live-chat-primary-text-color);}');
        style.sheet.insertRule('#sticky .sticky-panel #blacklist yt-formatted-string, #sticky .sticky-panel #whitelist yt-formatted-string {width: 60px;}');
        style.sheet.insertRule('#sticky .sticky-panel #types, #sticky .sticky-panel #controls {align-items: flex-end;}');
        style.sheet.insertRule('#sticky .sticky-panel #controls tp-yt-paper-button {line-height: initial; padding: 5px 11px 5px 11px;}');
        style.sheet.insertRule('#sticky .sticky-panel #controls #cancel tp-yt-paper-button {padding: 4px 11px 4px 11px;}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle {display: flex; flex-direction: row; align-items: center; cursor: pointer;}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle[checked]:not([disabled]) .toggle-bar.paper-toggle-button {opacity: 0.5; background-color: var(--paper-toggle-button-checked-bar-color, var(--primary-color));}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle[checked]:not([disabled]) .toggle-button.paper-toggle-button {background-color: var(--paper-toggle-button-checked-button-color, var(--primary-color));}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle[checked] .toggle-button.paper-toggle-button {transform: translate(16px, 0);}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle .toggle-container.paper-toggle-button {display: inline-block; position: relative; width: 36px; height: 14px; margin: 4px 1px;}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle .toggle-bar.paper-toggle-button {position: absolute; height: 100%; width: 100%; border-radius: 8px; pointer-events: none; opacity: 0.4; transition: background-color linear .08s; background-color: var(--paper-toggle-button-unchecked-bar-color, #000000);}');
        style.sheet.insertRule('#sticky .sticky-panel #toggle .toggle-button.paper-toggle-button {position: absolute; top: -3px; left: 0; right: auto; height: 20px; width: 20px; border-radius: 50%; box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.6); transition: transform linear .08s, background-color linear .08s; will-change: transform; background-color: var(--paper-toggle-button-unchecked-button-color, var(--paper-grey-50));}');
        style.sheet.insertRule('#sticky #settings.sticky-panel tp-yt-paper-item > *:not(:first-child):not(:nth-child(2)) {margin-left: 5px;}');
        style.sheet.insertRule('#sticky #show-hide-button {border-bottom: #e0e0e0 solid 1px; font-size: 11px;}');
        style.sheet.insertRule('#sticky #show-hide-button div {font-size: 11px;}');
        style.sheet.insertRule('#sticky #show-hide-button div a {color: #11111199; display: flex;}');
        style.sheet.insertRule('#sticky #show-hide-button div a:hover {color: #030303;}');
        style.sheet.insertRule('#sticky #show-hide-button div a #button {border-radius: 0; margin: 0; padding: 8px 24px; width: 100%;}');
        style.sheet.insertRule('#sticky #show-hide-button div a #button #text {font-weight: 500;}');
        style.sheet.insertRule('#sticky #chat-header {box-sizing: border-box; display: flex; border-bottom: #e0e0e0 solid 1px; background-color: #fffffffa; height: 150px; padding: 0 0 0 4px; transition: all .15s cubic-bezier(0.0, 0.0, 0.2, 1);}');
        style.sheet.insertRule('#sticky yt-icon-button {z-index: 0; transition: all .15s cubic-bezier(0.0, 0.0, 0.2, 1); visibility: hidden;}');
        style.sheet.insertRule('#sticky #primary-content {height: inherit; overflow-x: hidden;}');
        style.sheet.insertRule('#sticky #primary-content::-webkit-scrollbar, #sticky #history #items::-webkit-scrollbar {content: "";}');
        style.sheet.insertRule('#sticky #primary-content::-webkit-scrollbar-track, #sticky #history #items::-webkit-scrollbar-track {background-color: #f1f1f1;}');
        style.sheet.insertRule('#sticky #primary-content::-webkit-scrollbar-thumb, #sticky #history #items::-webkit-scrollbar-thumb {border: 2px solid #f1f1f1; min-height: 30px;}');
        style.sheet.insertRule('#sticky #primary-content .sticky-item {padding: 4px; font-size: 13px; display: flex;}');
        style.sheet.insertRule('#sticky #primary-content .sticky-item #author-photo {display: inline-table; align-self: center;}');
        style.sheet.insertRule('#sticky #primary-content .sticky-item #img {max-width: inherit;}');
        style.sheet.insertRule('#sticky #primary-content .timestamp {display: inline; color: #11111166; font-size: 11px; margin-right: 8px;}');
        style.sheet.insertRule('#sticky #primary-content .author {display: inline-flex; margin-right: 8px;}');
        style.sheet.insertRule('#sticky #primary-content .author .author-name {color: #11111199; font-weight: 500;}');
        style.sheet.insertRule('#sticky #primary-content .author .author-name.moderator {color: #5f84f1;}');
        style.sheet.insertRule('#sticky #primary-content .author .author-name.moderator.owner {color: #000000de; background-color: #ffd600; padding: 2px 4px; border-radius: 2px;}');
        style.sheet.insertRule('#sticky #primary-content #message {display: inline; line-height: 17px;}');
        style.sheet.insertRule('#sticky #notice yt-tooltip-renderer.error {color: #ffffff; background-color: var(--yt-spec-brand-button-background);}');
        style.sheet.insertRule('#sticky #notice yt-tooltip-renderer.error::before {border-color: transparent transparent var(--yt-spec-brand-button-background) transparent;}');
        style.sheet.insertRule('#sticky.dark #history ytd-menu-popup-renderer #items .history-item-chat {border-color: #303030;}');
        style.sheet.insertRule('#sticky.dark #show-hide-button {border-color: #303030; background-color: #181818;}');
        style.sheet.insertRule('#sticky.dark #show-hide-button div a {color: #ffffffb3;}');
        style.sheet.insertRule('#sticky.dark #show-hide-button div a:hover {color: #ffffff;}');
        style.sheet.insertRule('#sticky.dark #chat-header {border-color: #303030; background-color: #202020;}');
        style.sheet.insertRule('#sticky.dark #primary-content::-webkit-scrollbar-track, #sticky.dark #history #items::-webkit-scrollbar-track {background-color: #0f0f0f;}');
        style.sheet.insertRule('#sticky.dark #primary-content::-webkit-scrollbar-thumb, #sticky.dark #history #items::-webkit-scrollbar-thumb {background-color: hsla(0, 0%, 53.3%, .2); border-color: #0f0f0f;');
        style.sheet.insertRule('#sticky.dark #primary-content .timestamp {color: #ffffff8a;}');
        style.sheet.insertRule('#sticky.dark #primary-content .author .author-name {color: #ffffffb3;}');
        style.sheet.insertRule('#sticky.dark #primary-content .author .author-name.moderator {color: #5e84f1;}');
        style.sheet.insertRule('#sticky.dark #primary-content .author .author-name.moderator.owner {color: #111111;}');
        style.sheet.insertRule('#sticky.hide-timestamps #primary-content .timestamp {display: none;}');
    };

    let setUpMenu = () => {
        menu.querySelector('#contentWrapper').innerHTML = '<ytd-menu-popup-renderer slot="dropdown-content" class="style-scope yt-live-chat-app" tabindex="0" use-icons="" style="outline: none; box-sizing: border-box; max-width: 212.063px; max-height: 88px;"><tp-yt-paper-listbox id="items" class="style-scope ytd-menu-popup-renderer" role="listbox" tabindex="0"></tp-yt-paper-listbox></ytd-menu-popup-renderer>';
        let items = menu.querySelector('#items');
        items.innerHTML = `<ytd-menu-service-item-renderer id="menu-settings" class="style-scope ytd-menu-popup-renderer" role="menuitem" use-icons="" tabindex="0" aria-selected="false" data='{"text": {"runs": [{"text":"Settings"}]}, "icon": {"iconType": "TUNE"}, "serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
        items.innerHTML += `<ytd-menu-service-item-renderer id="menu-history" class="style-scope ytd-menu-popup-renderer" role="menuitem" use-icons="" tabindex="0" aria-selected="false" data='{"text": {"runs": [{"text":"History"}]}, "icon": {"iconType": "CLARIFY"}, "serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
        settingsButton = sticky.querySelector('#menu-settings');
        historyButton = sticky.querySelector('#menu-history');
    };

    let resetHistoryMenu = () => {
        history.querySelectorAll('ytd-menu-service-item-renderer').forEach(historyItem => historyItem.remove());
    };

    let selectAllHistories = () => {
        let selector = history.querySelector('#history-select-all').hasAttribute('checked') ? ':not([checked])' : '[checked]';
        history.querySelectorAll(`.history-item-checkbox${selector}`).forEach((checkbox) => checkbox.click());
    };

    let toggleDeleteHistoriesModal = (event) => {
        let historyDeleteModal = history.querySelector('#history-delete-confirm');
        let historyItems = history.querySelectorAll('.history-item');
        let historySelectAll = history.querySelector('#history-select-all');
        let historyDeleteButton = history.querySelector('#history-delete-button');
        let historyExportButton = history.querySelector('#history-export-button');
        let historyImport = history.querySelector('#history-import');
        let historyStorageUsage = history.querySelector('#history-storage-usage');
        let visible = Number(historyDeleteModal.style.opacity);
        historyDeleteModal.style.opacity = visible ? 0 : 1;
        historyDeleteModal.style.zIndex = visible ? -1 : 1;
        historyDeleteModal.style.pointerEvents = visible ? 'none' : 'initial';
        historySelectAll.style.display = visible ? 'initial' : 'none';
        historyDeleteButton.style.opacity = event && event.currentTarget.id === 'cancel' ? 1 : 0;
        historyDeleteButton.style.position = event && event.currentTarget.id === 'cancel' ? 'initial' : 'absolute';
        historyDeleteButton.style.pointerEvents = event && event.currentTarget.id === 'cancel' ? 'initial' : 'none';
        historyExportButton.style.opacity = event && event.currentTarget.id === 'cancel' ? 1 : 0;
        historyExportButton.style.position = event && event.currentTarget.id === 'cancel' ? 'initial' : 'absolute';
        historyExportButton.style.pointerEvents = event && event.currentTarget.id === 'cancel' ? 'initial' : 'none';
        historyImport.style.opacity = !event ? 1 : 0;
        historyImport.style.pointerEvents = !event ? 'inherit' : 'none';
        historyImport.style.position = !event ? 'relative' : 'absolute';
        historyStorageUsage.style.opacity = visible ? 1 : 0;
        historyItems.forEach(historyItem => historyItem.style.display = visible ? 'inherit' : 'none');
    };

    let exportHistoryItems = () => {
        let savedStickyIndex = getSavedStickyIndex();
        let historyItemsToExport = Object.keys(historiesSelected).map((key) => {
            let savedStickyIndexDetails = savedStickyIndex[key] || {
                author: 'Unknown',
                icon: 'https://yt3.ggpht.com/a/default-user=s32-c-k-c0x00ffffff-no-rj',
                id: null,
                title: 'Click here or visit the associated video with chat enabled to update history information.',
                valid: false
            };
            return { ...savedStickyIndexDetails, items: { id: key, messages: getHistory(key) } };
        });
        navigator.clipboard.writeText(JSON.stringify(historyItemsToExport, null, 2)).then(() => {
            let historyExportButtonIcon = history.querySelector('#history-export-button yt-icon');
            historyExportButtonIcon.setAttribute('icon', 'check');
            setTimeout(() => historyExportButtonIcon.setAttribute('icon', 'offline_download'), 1000);
        });
    };

    let handleNoHistories = () => {
        let histories = getHistories();
        if (!histories.length) {
            history.querySelector('#history-data').style.flexDirection = 'column-reverse';
            history.querySelector('#history-delete-controls').style.display = 'none';
            history.querySelector('#history-import').style.marginLeft = 'initial';
            history.querySelector('#history-import #input').style.width = '';
            history.querySelector('#history-data-container').style.display = 'none';
            history.querySelector('#history-delete-confirm').style.pointerEvents = 'none';
            history.querySelector('#history-none').style.display = 'flex';
        }
    };

    let handleHistoryNotice = () => {
        if (notice.touched && !reachedLocalStorageWarningLimit()) {
            notice.touched = false;
        }
    };

    let deleteHistories = () => {
        Object.keys(historiesSelected).forEach((key) => {
            localStorage.removeItem(`youtubespecialcommentsticker-sticky-items-${key}`);
            history.querySelector(`#${key}`).remove();
            delete historiesSelected[key];
        });
        history.querySelector('#history-storage-usage').innerText = getLocalStorageUsageText();
        toggleDeleteHistoriesModal();
        handleNoHistories();
        handleHistoryNotice();
    };

    let isValidHistoryItemImport = (historyItem) => {
        return ['author', 'icon', 'id', 'title', 'valid', 'items'].every((property) => property in historyItem);
    };

    let reportInvalidHistoryItemImport = () => {
        let historyImportButtonIcon = history.querySelector('#history-import yt-icon');
        let historyImportInput = history.querySelector('#history-import #input');
        historyImportButtonIcon.setAttribute('icon', 'close');
        setTimeout(() => historyImportButtonIcon.setAttribute('icon', 'upload'), 1000);
        if (Number(historyImportInput.style.opacity)) {
            hideShowHistoryImportControls();
        }
        history.removeAttribute('importing');
    };

    let importHistoryItem = (savedStickyIndex, historyItem) => {
        if (isValidHistoryItemImport(historyItem)) {
            let historyItemsId = `youtubespecialcommentsticker-sticky-items-${historyItem.items.id}`;
            savedStickyIndex[historyItem.items.id] = {
                author: historyItem.author,
                icon: historyItem.icon,
                id: historyItem.id,
                title: historyItem.title,
                valid: historyItem.valid
            };
            localStorage.setItem('youtubespecialcommentsticker-sticky-index', JSON.stringify(savedStickyIndex));
            if (!localStorage.getItem(historyItemsId)) {
                localStorage.setItem(historyItemsId, JSON.stringify(historyItem.items.messages));
            }
        } else {
            reportInvalidHistoryItemImport();
        }
    };

    let importHistoryItems = (text) => {
        try {
            let historyItems = JSON.parse(text);
            let savedStickyIndex = getSavedStickyIndex();
            if (Array.isArray(historyItems)) {
                historyItems.forEach(historyItem => importHistoryItem(savedStickyIndex, historyItem));
            } else {
                importHistoryItem(savedStickyIndex, historyItems);
            }
            refreshHistory();
            history.removeAttribute('importing');
            history.focus();
        } catch (error) {
            reportInvalidHistoryItemImport();
        }
    };

    let hideShowHistoryImportControls = () => {
        let historyImportButton = history.querySelector('#history-import #history-import-button');
        let historyImportInput = history.querySelector('#history-import yt-live-chat-text-input-field-renderer');
        let historyImportInputButton = history.querySelector('#history-import #cancel');
        let historyOtherHistoryHeaderItems = history.querySelectorAll('#history-data tp-yt-paper-item:not(#history-import):not(#history-none)');
        let historyHistoryItems = history.querySelectorAll('.history-item');
        let visible = historyImportButton.style.opacity === '' || Number(historyImportButton.style.opacity);
        historyImportButton.style.opacity = visible ? 0 : 1;
        historyImportButton.style.pointerEvents = visible ? 'none' : 'inherit';
        historyImportButton.style.position = visible ? 'absolute' : 'initial';
        historyImportInput.style.opacity = visible ? 1 : 0;
        historyImportInput.style.pointerEvents = visible ? 'inherit' : 'none';
        historyImportInput.style.position = visible ? 'inherit' : 'absolute';
        historyImportInputButton.style.opacity = visible ? 1 : 0;
        historyImportInputButton.style.pointerEvents = visible ? 'inherit' : 'none';
        historyImportInputButton.style.position = visible ? 'inherit' : 'absolute';
        if (visible) {
            historyImportInput.removeAttribute('has-text');
            historyImportInput.querySelector('#input').innerText = '';
        }
        [...historyOtherHistoryHeaderItems, ...historyHistoryItems].forEach((element) => {
            element.style.opacity = visible ? 0.5 : 1;
            element.style.pointerEvents = visible ? 'none' : 'initial';
        });
    };

    let handleHistoryItemsImport = () => {
        history.setAttribute('importing', '');
        if (navigator.clipboard.readText) {
            navigator.clipboard.readText().then(importHistoryItems, reportInvalidHistoryItemImport);
        } else {
            hideShowHistoryImportControls();
        }
    };

    let handleHistoryImportInputPaste = (event) => {
        handlePaste(event.currentTarget);
        importHistoryItems(event.currentTarget.innerText);
    };

    let handleHistoryImportInputKeyUp = (event) => {
        if (event.currentTarget.innerText) {
            event.currentTarget.parentElement.setAttribute('has-text', '');
        } else {
            event.currentTarget.parentElement.removeAttribute('has-text');
        }
    };

    let handleHistoryImportInputButton = () => {
        hideShowHistoryImportControls();
        history.removeAttribute('importing');
    };

    let getObjectSize = (data) => {
        return new Blob(data).size / 1024 / 1024;
    };

    let getLocalStorageUsage = () => {
        return getObjectSize(Object.values(localStorage));
    };

    let getLocalStorageUsageText = () => {
        return `${(getLocalStorageUsage()).toFixed(2)} MB / 5 MB`;
    };

    let setUpHistoryHeader = () => {
        let histories = getHistories();
        let items = history.querySelector('#items');
        let itemsTemp = document.createElement('div');
        items.parentNode.insertBefore(itemsTemp, items);

        itemsTemp.innerHTML = `<ytd-menu-service-item-renderer id="history-data" class="style-scope ytd-menu-popup-renderer" role="menuitem" tabindex="-1" aria-selected="false" data='{"serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
        let historyItem = itemsTemp.querySelector('#history-data');
        historyItem.querySelector('tp-yt-paper-item').remove();

        let historyDeleteControls = document.createElement('tp-yt-paper-item');
        historyItem.appendChild(historyDeleteControls);
        historyDeleteControls.id = 'history-delete-controls';
        historyDeleteControls.innerHTML = '<ytd-button-renderer id="history-delete-button" class="style-scope ytd-video-secondary-info-renderer style-destructive size-default" style="opacity: 0; pointer-events: none; position: absolute; margin-right: 16px;" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button=""></ytd-button-renderer>';
        let historyDeleteButton = historyDeleteControls.querySelector('#history-delete-button');
        historyDeleteButton.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small history-control-button history-affirmative-button" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in" title="Delete"><yt-icon icon="delete" class="style-scope yt-live-chat-header-renderer"></yt-icon></tp-yt-paper-button>';
        historyDeleteButton.onclick = toggleDeleteHistoriesModal;

        let historyExportButton = historyDeleteButton.cloneNode(true);
        historyDeleteControls.appendChild(historyExportButton);
        historyExportButton.id = 'history-export-button';
        historyExportButton.style.margin = 0;
        historyExportButton.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small history-control-button history-affirmative-button" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in" title="Export"><yt-icon icon="offline_download" class="style-scope yt-live-chat-header-renderer"></yt-icon></tp-yt-paper-button>';
        historyExportButton.onclick = exportHistoryItems;

        let historySelectAll = document.createElement('tp-yt-paper-checkbox');
        historyDeleteControls.insertBefore(historySelectAll, historyDeleteButton);
        historySelectAll.id = 'history-select-all';
        historySelectAll.style.setProperty('--paper-checkbox-ink-size', '32px');
        historySelectAll.style.paddingRight = '16px';
        historySelectAll.onclick = selectAllHistories;
        let historySelectAllLabel = historySelectAll.querySelector('#checkboxLabel');
        historySelectAllLabel.style.setProperty('font-size', 'var(--ytd-user-comment_-_font-size)');
        historySelectAllLabel.style.userSelect = 'none';
        historySelectAllLabel.innerText = 'Select All';

        let historyImportContainer = document.createElement('tp-yt-paper-item');
        historyItem.appendChild(historyImportContainer);
        historyImportContainer.id = 'history-import';
        historyImportContainer.style.padding = 0;
        historyImportContainer.style.marginLeft = 'auto';
        historyImportContainer.innerHTML = '<ytd-button-renderer id="history-import-button" class="style-scope ytd-video-secondary-info-renderer style-destructive size-default" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button=""></ytd-button-renderer>';
        historyImportContainer.innerHTML += `<yt-live-chat-text-input-field-renderer id="input" class="style-scope yt-live-chat-message-input-renderer" disabled="" emoji-manager='{"emojiShortcutMap": {}}' title="Paste history data." style="width: 85px;"></yt-live-chat-text-input-field-renderer>`;
        historyImportContainer.innerHTML += '<ytd-button-renderer id="cancel" class="style-scope ytd-masthead style-suggestive size-small" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button="" style="padding-left: 4px;"></ytd-button-renderer>';
        let historyImportButton = historyImportContainer.querySelector('#history-import-button');
        historyImportButton.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small history-control-button history-affirmative-button" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in" title="Import"><yt-icon icon="upload" class="style-scope yt-live-chat-header-renderer"></yt-icon></tp-yt-paper-button>';
        historyImportButton.onclick = handleHistoryItemsImport;
        let historyImportInput = historyImportContainer.querySelector('yt-live-chat-text-input-field-renderer');
        historyImportInput.style.opacity = 0;
        historyImportInput.style.pointerEvents = 'none';
        historyImportInput.style.position = 'absolute';
        historyImportInput.style.userSelect = 'none';
        let historyImportInputLabel = historyImportInput.querySelector('#label');
        historyImportInputLabel.style.overflowX = 'hidden';
        historyImportInputLabel.style.textOverflow = 'ellipsis';
        historyImportInputLabel.style.width = 'inherit';
        historyImportInputLabel.innerText = historyImportInput.title;
        let historyImportInputInput = historyImportInput.querySelector('#input');
        historyImportInputInput.style.maxHeight = '24px';
        historyImportInputInput.onpaste = handleHistoryImportInputPaste;
        historyImportInputInput.onkeyup = handleHistoryImportInputKeyUp;
        let historyImportInputButton = historyImportContainer.querySelector('#cancel');
        historyImportInputButton.style.opacity = 0;
        historyImportInputButton.style.pointerEvents = 'none';
        historyImportInputButton.style.position = 'absolute';
        historyImportInputButton.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small history-control-button" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in" title="Cancel"><yt-icon icon="close" class="style-scope yt-live-chat-header-renderer"></yt-icon></tp-yt-paper-button>';
        historyImportInputButton.onclick = handleHistoryImportInputButton;

        let historyData = document.createElement('tp-yt-paper-item');
        historyItem.appendChild(historyData);
        historyData.id = 'history-data-container';
        historyData.innerHTML = '<yt-formatted-string id="history-storage-usage" class="style-scope ytd-menu-service-item-renderer" title="Local Storage Space Used/Capacity"></yt-formatted-string>';
        historyData.querySelector('yt-formatted-string').innerText = getLocalStorageUsageText();
        historyData.querySelector('yt-formatted-string').removeAttribute('is-empty');
        historyItem.appendChild(historyData);
        historyItem.remove();
        items.appendChild(historyItem);

        itemsTemp.innerHTML = `<ytd-menu-service-item-renderer id="history-delete-confirm" class="style-scope ytd-menu-popup-renderer" role="menuitem" tabindex="-1" aria-selected="false" data='{"serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
        let historyDeleteModal = itemsTemp.querySelector('#history-delete-confirm');
        historyDeleteModal.style.opacity = 0;
        historyDeleteModal.style.position = 'absolute';
        historyDeleteModal.style.zIndex = -1;
        historyDeleteModal.style.width = '384px';
        historyDeleteModal.style.pointerEvents = 'none';
        let historyDeleteModalContainer = historyDeleteModal.querySelector('tp-yt-paper-item');
        historyDeleteModalContainer.querySelector('yt-icon').remove()
        historyDeleteModalContainer.querySelector('yt-formatted-string').remove()
        historyDeleteModalContainer.style.alignItems = 'center';
        historyDeleteModalContainer.style.justifyContent = 'space-evenly';
        historyDeleteModalContainer.innerHTML += '<ytd-button-renderer id="cancel" class="style-scope ytd-masthead style-suggestive size-small" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button=""></ytd-button-renderer>';
        historyDeleteModalContainer.innerHTML += '<ytd-button-renderer id="delete" class="style-scope ytd-video-secondary-info-renderer style-destructive size-default" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button=""></ytd-button-renderer>';
        let historyDeleteModalCancel = historyDeleteModalContainer.querySelector('#cancel');
        let historyDeleteModalDelete = historyDeleteModalContainer.querySelector('#delete');
        historyDeleteModalCancel.onclick = toggleDeleteHistoriesModal;
        historyDeleteModalDelete.onclick = deleteHistories;
        historyDeleteModalCancel.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in"><yt-formatted-string id="text" class="style-scope ytd-button-renderer style-suggestive size-small"></yt-formatted-string></tp-yt-paper-button>';
        historyDeleteModalDelete.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-destructive size-default" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Subscribe"><yt-formatted-string id="text" class="style-scope ytd-button-renderer style-destructive size-default"></yt-formatted-string></tp-yt-paper-button>';
        historyDeleteModalCancel.querySelector('yt-formatted-string').innerText = 'Cancel';
        historyDeleteModalCancel.querySelector('yt-formatted-string').removeAttribute('is-empty');
        historyDeleteModalDelete.querySelector('yt-formatted-string').innerText = 'Delete';
        historyDeleteModalDelete.querySelector('yt-formatted-string').removeAttribute('is-empty');
        historyDeleteModalCancel.querySelector('tp-yt-paper-button').style.padding = '4px 11px';
        historyDeleteModal.remove();
        historyItem.insertBefore(historyDeleteModal, historyDeleteControls);

        let historyNone = document.createElement('tp-yt-paper-item');
        historyItem.appendChild(historyNone);
        historyNone.id = 'history-none';
        historyNone.style.display = 'none';
        historyNone.innerHTML = '<yt-formatted-string class="style-scope ytd-menu-service-item-renderer"></yt-formatted-string>';
        historyNone.querySelector('yt-formatted-string').innerText = 'No Histories';
        historyNone.querySelector('yt-formatted-string').removeAttribute('is-empty');

        if (!histories.length) {
            historyItem.style.flexDirection = 'column-reverse';
            historyDeleteControls.style.display = 'none';
            historyDeleteModal.style.display = 'none';
            historyImportContainer.style.marginLeft = 'initial';
            historyImportInput.style.width = '';
            historyData.style.display = 'none';
            historyNone.style.display = 'flex';
        }

        itemsTemp.remove();
    };

    let getHistoryRaw = (id) => {
        return localStorage.getItem(`youtubespecialcommentsticker-sticky-items-${id}`);
    };

    let getHistory = (id) => {
        return JSON.parse(getHistoryRaw(id));
    };

    let getHistories = () => {
        return Object.keys(localStorage).filter(key => key.indexOf('youtubespecialcommentsticker-sticky-items-') > -1);
    };

    let getDetailedHistories = () => {
        let index = getSavedStickyIndex();
        let histories = getHistories();
        return histories.map((history) => {
            let id = history.substr(42);
            return {
                id: id,
                details: index[id] || {},
                size: {
                    text: getHistorySizeText(id),
                    title: getHistorySizeTextTitle(id)
                }
            }
        }).sort((history, otherHistory) => {
            if (history.details.author === otherHistory.details.author) {
                return history.size.title < otherHistory.size.title ? 1 : -1;
            } else if (!history.details.author || !otherHistory.details.author) {
                return !history.details.author && !otherHistory.details.author ? 0 : (!history.details.author ? 1 : -1);
            }
            return history.details.author < otherHistory.details.author ? -1 : 1;
        });
    };

    let selectHistory = (event) => {
        let histories = getHistories();
        if (event.currentTarget.hasAttribute('checked')) {
            historiesSelected[event.currentTarget.dataset.id] = true;
        } else {
            delete historiesSelected[event.currentTarget.dataset.id];
        }

        let selectedCount = Object.keys(historiesSelected).length;
        let historyDeleteButton = history.querySelector('#history-delete-button');
        let historyExportButton = history.querySelector('#history-export-button');
        let historyImport = history.querySelector('#history-import');
        historyDeleteButton.style.opacity = selectedCount ? 1 : 0;
        historyDeleteButton.style.pointerEvents = selectedCount ? 'inherit' : 'none';
        historyDeleteButton.style.position = selectedCount ? 'initial' : 'absolute';
        historyExportButton.style.opacity = selectedCount ? 1 : 0;
        historyExportButton.style.pointerEvents = selectedCount ? 'inherit' : 'none';
        historyExportButton.style.position = selectedCount ? 'initial' : 'absolute';
        historyImport.style.opacity = selectedCount ? 0 : 1;
        historyImport.style.pointerEvents = selectedCount ? 'none' : 'inherit';
        historyImport.style.position = selectedCount ? 'absolute' : 'relative';
        if (selectedCount === histories.length) {
            history.querySelector('#history-select-all').setAttribute('active', '');
        } else {
            history.querySelector('#history-select-all').removeAttribute('active');
        }
    };

    let toggleEditHistoryModal = (event) => {
        let historyId = event.currentTarget.dataset.id;
        let historyData = history.querySelectorAll('#history-data > tp-yt-paper-item');
        let historyItem = history.querySelector(`#items #${historyId}`);
        let historyItemEdit = historyItem.querySelector('.history-item-edit');
        let historyItemHeader = historyItem.querySelector('.history-item-header');
        let historyItemHeaderShowHideButton = historyItemHeader.querySelector('yt-icon[icon="collapse"]');
        let historyOtherHistoryItems = history.querySelectorAll(`.history-item:not(#${historyId})`);
        let visible = Number(historyItemEdit.style.opacity);
        if (visible) {
            historyItemEdit.querySelectorAll('yt-live-chat-text-input-field-renderer').forEach((input) => {
                input.removeAttribute('has-text');
                input.querySelector('#input').innerText = '';
            });
            historyItem.removeAttribute('style');
            history.removeAttribute('editing');
        } else {
            history.setAttribute('editing', '');
        }
        if (historyItemHeaderShowHideButton) {
            historyItemHeaderShowHideButton.click();
        }
        [...historyData, ...historyOtherHistoryItems].forEach((element) => {
            if (element.style.opacity !== '0') {
                element.style.opacity = visible ? 1 : 0.5;
                element.style.pointerEvents = visible ? 'initial' : 'none';
            }
        });
        historyItemEdit.style.opacity = visible ? 0 : 1;
        historyItemEdit.style.zIndex = visible ? -1 : 1;
        historyItemEdit.style.position = visible ? 'absolute' : 'initial';
        historyItemHeader.style.height = visible ? 'initial' : '0px';
        historyItemHeader.style.overflow = visible ? 'initial' : 'hidden';
    };

    let refreshHistory = () => {
        history.setAttribute('refreshing', '');
        setUpHistory();
        history.focus();
        history.removeAttribute('refreshing');
    };

    let updateHistoryItem = (event) => {
        let savedStickyIndex = getSavedStickyIndex();
        let historyId = event.currentTarget.dataset.id;
        let historyItem = history.querySelector(`#items #${historyId}`);
        let historyItemEdit = historyItem.querySelector('.history-item-edit');
        let historyItemEditAuthor = historyItemEdit.querySelector('yt-live-chat-text-input-field-renderer.author');
        let historyItemEditIcon = historyItemEdit.querySelector('yt-live-chat-text-input-field-renderer.icon');
        let historyItemEditID = historyItemEdit.querySelector('yt-live-chat-text-input-field-renderer.id');
        let historyItemEditTitle = historyItemEdit.querySelector('yt-live-chat-text-input-field-renderer.title');
        if (savedStickyIndex[historyId]) {
            savedStickyIndex[historyId] = {
                author: historyItemEditAuthor.innerText,
                icon: historyItemEditIcon.innerText.replace(/=s\d+-/, '=s32-'),
                id: historyItemEditID.innerText,
                title: historyItemEditTitle.innerText,
                valid: true
            };
            localStorage.setItem('youtubespecialcommentsticker-sticky-index', JSON.stringify(savedStickyIndex));
        }
        toggleEditHistoryModal(event);
        refreshHistory();
    };

    let handleHistoryEditModalInputPaste = (event) => {
        handlePaste(event.currentTarget);
    };

    let handleHistoryEditModalInputHasText = (event) => {
        if (event.currentTarget.innerText) {
            event.currentTarget.parentElement.setAttribute('has-text', '');
        } else {
            event.currentTarget.parentElement.removeAttribute('has-text');
        }
    };

    let isEditModalInputValid = (input) => {
        let field = input.parentElement.classList[2];
        let required = (input) => !!input.innerText;
        let isIconFormat = (input) => /https:\/\/[a-zA-Z\d]+\.[a-zA-Z\d]+\.com\/[a-zA-Z\d]+\/.+=s\d+-.+/.test(input.innerText);
        let isIdFormat = (input) => /[a-zA-Z0-9_-]{11}/.test(input.innerText);
        return {
            author: [required],
            icon: [required, isIconFormat],
            id: [required, isIdFormat],
            title: [required]
        }[field].every((validation) => validation(input));
    };

    let validateEditModalInput = (event) => {
        let input = event.currentTarget;
        let parent = input.parentElement;
        if (isEditModalInputValid(input)) {
            parent.setAttribute('valid', '');
        } else {
            parent.removeAttribute('valid');
        }
    };

    let validateEditModalForm = (event) => {
        let form = event.currentTarget.parentElement.parentElement;
        let submit = form.querySelector('#submit');
        let valid = [...form.querySelectorAll('yt-live-chat-text-input-field-renderer')].every(input => input.hasAttribute('valid'));
        valid ? submit.removeAttribute('disabled') : submit.setAttribute('disabled', '');
        submit.style.pointerEvents = valid ? 'initial' : 'none';
    };

    let handleHistoryEditModalInputKeyUp = (event) => {
        handleHistoryEditModalInputHasText(event);
        validateEditModalInput(event);
        validateEditModalForm(event);
    };

    let getHistorySizeText = (id) => {
        let historySize = getObjectSize(Object.values(getHistoryRaw(id)));
        return historySize >= 0.01 ? `${historySize.toFixed(2)} MB` : '< 10 KB';
    };

    let getHistorySizeTextTitle = (id) => {
        return `${getObjectSize(Object.values(getHistoryRaw(id))).toFixed(10)} MB`;
    };

    let updateHistorySizeText = (id) => {
        let historySizeText = history.querySelector(`#items #${id} .history-size #${id}`);
        historySizeText.innerText = getHistorySizeText(id);
        historySizeText.title = getHistorySizeTextTitle(id);
    };

    let setHistoryItemsScroll = (historyItemChat) => {
        if (historyItemChat.offsetHeight === 151) {
            let historyItems = history.querySelector('#items');
            let historyItemsRect = historyItems.getBoundingClientRect();
            let historyItemRect = historyItemChat.getBoundingClientRect();
            if (historyItemRect.bottom > historyItemsRect.bottom) {
                historyItems.scrollTop += historyItemRect.bottom - historyItemsRect.bottom + 8;
            }
        } else {
            setTimeout(() => setHistoryItemsScroll(historyItemChat));
        }
    };

    let showHideHistoryItem = (event) => {
        let historyItem = event.currentTarget;
        let historyItemId = historyItem.dataset.id;
        let historyItemIcon = historyItem.querySelector('yt-icon');
        let historyItemChat = history.querySelector(`#${historyItemId} .history-item-chat`);
        let historyItemChatHeader = historyItemChat.querySelector('#chat-header');
        let historyItemChatItems = historyItemChatHeader.querySelector('#primary-content');
        if (!historyItemChat.offsetHeight) {
            let historyItems = getHistory(historyItemId);
            historyItems.forEach((item) => historyItemChatItems.appendChild(getStickyItem(item)));
            historyItemIcon.setAttribute('icon', 'collapse');
            historyItemChat.removeAttribute('style');
            historyItemChatHeader.removeAttribute('style');
            historyItemChatItems.style.height = 'initial';
            setHistoryItemsScroll(historyItemChat);
        } else {
            historyItemChatItems.querySelectorAll('.sticky-item').forEach(historyItemChatItem => historyItemChatItem.remove());
            historyItemIcon.setAttribute('icon', 'expand');
            historyItemChat.style.border = 'none';
            historyItemChatHeader.style.height = '0px';
            historyItemChatHeader.style.border = 'none';
            historyItemChatItems.style.height = '0px';
        }
        updateHistorySizeText(historyItemId);
    };

    let setHistoryItem = (histories, items, itemsTemp) => {
        if (histories.length) {
            let history = histories[0];
            let historyId = history.id;
            let historyDetails = history.details;

            itemsTemp.innerHTML = `<ytd-menu-service-item-renderer id="${historyId}" class="style-scope ytd-menu-popup-renderer history-item" role="menuitem" tabindex="-1" aria-selected="false" data='{"serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
            let historyItem = itemsTemp.querySelector(`#${historyId}`);
            historyItem.remove();

            let historyItemHeader = document.createElement('div');
            historyItemHeader.classList.add('history-item-header');
            let historyDelete = document.createElement('tp-yt-paper-item');
            itemsTemp.appendChild(historyDelete);
            historyDelete.remove();

            let historyCheckbox = document.createElement('tp-yt-paper-checkbox');
            historyDelete.append(historyCheckbox);
            historyCheckbox.classList.add('history-item-checkbox');
            historyCheckbox.style.setProperty('--paper-checkbox-ink-size', '32px');
            historyCheckbox.style.width = '20px';
            historyCheckbox.dataset.id = historyId;
            historyCheckbox.onclick = selectHistory;

            let historyIcon = document.createElement('tp-yt-paper-item');
            itemsTemp.appendChild(historyIcon);
            let thumbnail = JSON.stringify({"thumbnails": [{"url": historyDetails.icon || 'https://yt3.ggpht.com/a/default-user=s32-c-k-c0x00ffffff-no-rj', "width": 48, "height": 48}]});
            historyIcon.innerHTML = `<yt-img-shadow id="avatar" class="style-scope ytd-video-owner-renderer no-transition" style="border-radius: 50%; overflow: hidden; width: 32px; user-select: none;" loaded="" thumbnail='${thumbnail}'></yt-img-shadow>`;
            historyIcon.querySelector('img').title = historyDetails.author || 'Unknown';
            historyIcon.remove();

            let historyName = historyItem.querySelector('tp-yt-paper-item');
            historyName.style.overflow = 'hidden';
            let historyNameText = historyName.querySelector('yt-formatted-string');
            historyNameText.style.overflow = 'hidden';
            historyNameText.removeAttribute('is-empty');
            if (historyDetails.valid) {
                let historyNameLink = document.createElement('a');
                historyNameLink.className = 'yt-simple-endpoint style-scope yt-formatted-string';
                historyNameLink.href = `https://www.youtube.com/watch?v=${historyDetails.id}`;
                historyNameLink.innerText = historyDetails.title;
                historyNameLink.style.display = 'block';
                historyNameLink.style.overflow = 'hidden';
                historyNameLink.style.textOverflow = 'ellipsis';
                historyNameText.title = historyNameLink.innerText;
                historyNameText.appendChild(historyNameLink);
            } else {
                historyNameText.innerText = historyDetails.title || 'Click here or visit the associated video with chat enabled to update history information.';
                historyNameText.title = historyNameText.innerText;
                historyNameText.style.textOverflow = 'ellipsis';
                historyNameText.style.cursor = 'pointer';
                historyNameText.dataset.id = historyId;
                historyNameText.onclick = toggleEditHistoryModal;
            }
            historyName.remove();

            let historySize = document.createElement('tp-yt-paper-item');
            itemsTemp.appendChild(historySize);
            historySize.className = 'style-scope ytd-menu-service-item-renderer history-size';
            historySize.style.marginLeft = 'auto';
            let historySizeText = document.createElement('yt-formatted-string');
            historySize.appendChild(historySizeText);
            historySizeText.className = 'style-scope ytd-menu-service-item-renderer';
            historySizeText.id = historyId
            historySizeText.innerText = history.size.text;
            historySizeText.title = history.size.title;
            historySizeText.removeAttribute('is-empty');
            historySize.remove();

            let historyItemShowHide = document.createElement('tp-yt-paper-item');
            historyItemShowHide.style.paddingRight = '16px';
            historyItemShowHide.innerHTML += `<yt-icon-button class="style-scope yt-live-chat-header-renderer" style="visibility: inherit;" data-id="${historyId}"><button id="button" class="style-scope yt-icon-button"><yt-icon icon="expand" class="style-scope yt-live-chat-header-renderer"></yt-icon></button><paper-ripple class="style-scope yt-icon-button circle"><div id="background" class="style-scope paper-ripple"></div><div id="waves" class="style-scope paper-ripple"></div></paper-ripple></button></yt-icon-button>`;
            let historyItemShowHideButton = historyItemShowHide.querySelector('yt-icon-button');
            historyItemShowHideButton.classList.add('history-item-show-hide-button');
            historyItemShowHideButton.onclick = showHideHistoryItem;

            historyItemHeader.appendChild(historyDelete);
            historyItemHeader.appendChild(historyIcon);
            historyItemHeader.appendChild(historyName);
            historyItemHeader.appendChild(historySize);
            historyItemHeader.appendChild(historyItemShowHide);

            if (!historyDetails.valid) {
                let historyEditModal = document.createElement('tp-yt-paper-item');
                historyEditModal.style.opacity = 0;
                historyEditModal.style.justifyContent = 'space-between';
                historyEditModal.style.position = 'absolute';
                historyEditModal.style.zIndex = -1;
                historyEditModal.style.userSelect = 'none';
                historyEditModal.classList.add('history-item-edit');
                itemsTemp.appendChild(historyEditModal);

                [{ id: 'author', title: 'The channel name.' }, { id: 'icon', title: 'The channel icon: Right-Click Channel Icon > Copy Image Address.' }, { id: 'id', title: 'The video ID: https://www.youtube.com/watch?v=<ID>.' }, { id: 'title', title: 'The video title.' }].forEach((input) => {
                    historyEditModal.innerHTML += `<yt-live-chat-text-input-field-renderer id="input" class="style-scope yt-live-chat-message-input-renderer ${input.id}" disabled="" emoji-manager='{"emojiShortcutMap": {}}' title="${input.title}"></yt-live-chat-text-input-field-renderer>`;
                });
                historyEditModal.innerHTML += `<ytd-button-renderer id="cancel" class="style-scope ytd-masthead style-suggestive size-small" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button="" data-id="${historyId}"></ytd-button-renderer>`;
                historyEditModal.innerHTML += `<ytd-button-renderer id="submit" class="style-scope ytd-video-secondary-info-renderer style-destructive size-default" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button="" data-id="${historyId}" disabled style="pointer-events: none;"></ytd-button-renderer>`;
                let historyEditModalCancel = historyEditModal.querySelector('#cancel');
                let historyEditModalSubmit = historyEditModal.querySelector('#submit');
                historyEditModalCancel.onclick = toggleEditHistoryModal;
                historyEditModalSubmit.onclick = updateHistoryItem;
                historyEditModalCancel.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small history-control-button" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in" title="Cancel"><yt-icon icon="close" class="style-scope yt-live-chat-header-renderer"></yt-icon></tp-yt-paper-button>';
                historyEditModalSubmit.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-destructive size-default history-control-button history-affirmative-button" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Subscribe" title="Submit"><yt-icon icon="check" class="style-scope yt-live-chat-header-renderer"></yt-icon></tp-yt-paper-button>';

                let historyEditModalAuthor = historyEditModal.querySelector('.author');
                let historyEditModalIcon = historyEditModal.querySelector('.icon');
                let historyEditModalId = historyEditModal.querySelector('.id');
                let historyEditModalTitle = historyEditModal.querySelector('.title');
                historyEditModalAuthor.querySelector('#label').innerText = 'Author';
                historyEditModalIcon.querySelector('#label').innerText = 'Icon';
                historyEditModalId.querySelector('#label').innerText = 'ID';
                historyEditModalTitle.querySelector('#label').innerText = 'Title';
                historyEditModalAuthor.querySelector('#input').onpaste = handleHistoryEditModalInputPaste;
                historyEditModalIcon.querySelector('#input').onpaste = handleHistoryEditModalInputPaste;
                historyEditModalId.querySelector('#input').onpaste = handleHistoryEditModalInputPaste;
                historyEditModalTitle.querySelector('#input').onpaste = handleHistoryEditModalInputPaste;
                historyEditModalAuthor.querySelector('#input').onkeyup = handleHistoryEditModalInputKeyUp;
                historyEditModalIcon.querySelector('#input').onkeyup = handleHistoryEditModalInputKeyUp;
                historyEditModalId.querySelector('#input').onkeyup = handleHistoryEditModalInputKeyUp;
                historyEditModalTitle.querySelector('#input').onkeyup = handleHistoryEditModalInputKeyUp;
                historyEditModal.remove();

                historyItem.appendChild(historyEditModal);
            }

            let historyItemChat = document.createElement('div');
            historyItemChat.style.border = 'none';
            historyItemChat.classList.add('history-item-chat');
            historyItemChat.innerHTML = '<div id="chat-header" role="heading" class="style-scope yt-live-chat-renderer" style="height: 0px; border: none;"><div id="primary-content" class="style-scope yt-live-chat-header-renderer"></div></div>';
            itemsTemp.appendChild(historyItemChat);

            historyItem.appendChild(historyItemHeader);
            historyItem.appendChild(historyItemChat);
            items.appendChild(historyItem);

            setTimeout(() => setHistoryItem(histories.slice(1), items, itemsTemp));
        } else {
            itemsTemp.remove();
            historiesSelected = {};
            toggleHistoryItems();
        }
    };

    let toggleHistoryItems = () => {
        let items = history.querySelector('#items');
        if (Number(items.style.opacity)) {
            items.removeAttribute('style');
        } else {
            items.style.opacity = 0.5;
            items.style.pointerEvents = 'none';
        }
    };

    let setUpHistoryItems = () => {
        let histories = getDetailedHistories();
        let items = history.querySelector('#items');
        let itemsTemp = document.createElement('div');
        itemsTemp.style.position = 'absolute';
        items.parentNode.insertBefore(itemsTemp, items);
        toggleHistoryItems();

        setTimeout(() => setHistoryItem(histories, items, itemsTemp));
    };

    let setUpHistory = () => {
        resetHistoryMenu();
        setUpHistoryHeader();
        setUpHistoryItems();
    };

    let isSettingsListSet = (list) => {
        return settingsData[list].text.raw || settingsData[list].regex.raw;
    };

    let setUpSettingsItemsStates = (open) => {
        if ((!open && isSettingsListSet('blacklist')) || blacklist.querySelector('#input > div').innerText.trim()) {
            whitelist.classList.add('disabled');
        } else {
            whitelist.classList.remove('disabled');
        }
        if ((!open && isSettingsListSet('whitelist')) || whitelist.querySelector('#input > div').innerText.trim()) {
            blacklist.classList.add('disabled');
        } else {
            blacklist.classList.remove('disabled');
        }
    };

    let getEscapedTextRegex = (text) => {
        return text ? `(${text.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').split(',').filter(Boolean).join('|')})` : text;
    };

    let setSettingsList = (element, name, visible) => {
        if (visible) {
            element.querySelector('#input > div').innerText = settingsData[name].text.raw || settingsData[name].regex.raw;
        } else {
            let innerText = element.querySelector('#input > div').innerText.trim();
            let match = innerText.match(/^\/(.+?)\/([igmsuy]{0,6}?)$/);
            if (match) {
                settingsData[name].text = { raw: null, escaped: null };
                settingsData[name].regex = { raw: match[0], escaped: match[1], flags: match[2] };
            } else {
                settingsData[name].regex = { raw: null, escaped: null, flags: null };
                settingsData[name].text = { raw: innerText, escaped: getEscapedTextRegex(innerText) };
            }
        }
    };

    let getChatSettingsButton = () => {
      let iconButton = document.querySelector('#chat-messages #overflow');
      let liveChatButton = document.querySelector('#chat-messages #live-chat-header-context-menu');
      return (!iconButton || iconButton.hidden) ? liveChatButton : iconButton;
    };

    let setUpSettingsItems = () => {
        let items = settings.querySelector('#items');
        let types;
        let controls;

        chatSettingsButton = getChatSettingsButton();

        ['blacklist', 'whitelist', 'types', 'controls'].forEach((id) => {
            items.innerHTML += `<ytd-menu-service-item-renderer id="${id}" class="style-scope ytd-menu-popup-renderer" role="menuitem" tabindex="-1" aria-selected="false" data='{"serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
        });

        blacklist = items.querySelector('#blacklist tp-yt-paper-item');
        blacklist.innerHTML += `<yt-live-chat-text-input-field-renderer id="input" class="style-scope yt-live-chat-message-input-renderer" disabled="" emoji-manager='{"emojiShortcutMap": {}}'></yt-live-chat-text-input-field-renderer>`;
        blacklist.querySelector('yt-formatted-string').innerText = 'Blacklist';
        blacklist.querySelector('yt-formatted-string').removeAttribute('is-empty');
        setSettingsList(blacklist, 'blacklist', true);

        whitelist = items.querySelector('#whitelist tp-yt-paper-item');
        whitelist.innerHTML += '<yt-live-chat-text-input-field-renderer id="input" class="style-scope yt-live-chat-message-input-renderer" disabled=""></yt-live-chat-text-input-field-renderer>';
        whitelist.querySelector('yt-formatted-string').innerText = 'Whitelist';
        whitelist.querySelector('yt-formatted-string').removeAttribute('is-empty');
        setSettingsList(whitelist, 'whitelist', true);

        types = items.querySelector('#types tp-yt-paper-item');
        types.innerHTML += '<div id="toggle" class="style-scope ytd-compact-autoplay-renderer" role="button" checked=""><div class="toggle-container style-scope paper-toggle-button"><div id="toggleBar" class="toggle-bar style-scope paper-toggle-button"></div><div id="toggleButton" class="toggle-button style-scope paper-toggle-button"></div></div></div>';
        types.append(types.querySelector('yt-formatted-string').cloneNode());
        types.innerHTML += '<div id="toggle" class="style-scope ytd-compact-autoplay-renderer" role="button" checked=""><div class="toggle-container style-scope paper-toggle-button"><div id="toggleBar" class="toggle-bar style-scope paper-toggle-button"></div><div id="toggleButton" class="toggle-button style-scope paper-toggle-button"></div></div></div>';
        types.querySelector('yt-formatted-string').innerText = 'Moderators';
        types.querySelector('yt-formatted-string').removeAttribute('is-empty');
        types.querySelector('yt-formatted-string:nth-of-type(2)').innerText = 'Owner';
        types.querySelector('yt-formatted-string:nth-of-type(2)').removeAttribute('is-empty');
        moderator = types.querySelector('#toggle');
        owner = types.querySelector('#toggle:nth-of-type(2)');
        settingsData.moderator ? moderator.setAttribute('checked', '') : moderator.removeAttribute('checked');
        settingsData.owner ? owner.setAttribute('checked', '') : owner.removeAttribute('checked');

        controls = items.querySelector('#controls tp-yt-paper-item');
        controls.innerHTML += '<ytd-button-renderer id="cancel" class="style-scope ytd-masthead style-suggestive size-small" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button=""></ytd-button-renderer>';
        controls.innerHTML += '<ytd-button-renderer id="save" class="style-scope ytd-video-secondary-info-renderer style-destructive size-default" button-renderer="" use-keyboard-focused="" is-paper-button-with-icon="" is-paper-button=""></ytd-button-renderer>';
        cancel = controls.querySelector('#cancel');
        save = controls.querySelector('#save');
        cancel.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-suggestive size-small" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Sign in"><yt-formatted-string id="text" class="style-scope ytd-button-renderer style-suggestive size-small"></yt-formatted-string></tp-yt-paper-button>';
        save.innerHTML = '<tp-yt-paper-button id="button" class="style-scope ytd-button-renderer style-destructive size-default" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false" aria-label="Subscribe"><yt-formatted-string id="text" class="style-scope ytd-button-renderer style-destructive size-default"></yt-formatted-string></tp-yt-paper-button>';
        cancel.querySelector('yt-formatted-string').innerText = 'Cancel';
        cancel.querySelector('yt-formatted-string').removeAttribute('is-empty');
        save.querySelector('yt-formatted-string').innerText = 'Save';
        save.querySelector('yt-formatted-string').removeAttribute('is-empty');

        setUpSettingsItemsStates();
    };

    let setUpSettingsData = () => {
        let savedSettingsData = localStorage.getItem('youtubespecialcommentsticker-settings');
        if (savedSettingsData) {
            settingsData = JSON.parse(savedSettingsData);
        }

        if (!settingsData || typeof settingsData.blacklist === 'string' || typeof settingsData.whitelist === 'string') {
            let blacklistText = settingsData ? settingsData.blacklist : null;
            let whitelistText = settingsData ? settingsData.whitelist : null;
            settingsData = {
                blacklist: {
                    text: { raw: blacklistText, escaped: getEscapedTextRegex(blacklistText) },
                    regex: { raw: null, escaped: null }
                },
                whitelist: {
                    text: { raw: whitelistText, escaped: getEscapedTextRegex(whitelistText) },
                    regex: { raw: null, escaped: null }
                },
                moderator: true,
                owner: true
            };
            localStorage.setItem('youtubespecialcommentsticker-settings', JSON.stringify(settingsData));
        }
    };

    let setUpSettings = () => {
        setUpSettingsData();
        setUpSettingsItems();
    };

    let setUpGlobalSavedStickyIndex = () => {
        let savedStickyIndex = getSavedStickyIndex();
        if (!Object.keys(savedStickyIndex).length) {
            let histories = getHistories();

            histories.forEach((history) => {
                let id = history.substr(42);
                if (!savedStickyIndex[id]) {
                    savedStickyIndex[id] = {
                        author: 'Unknown',
                        icon: 'https://yt3.ggpht.com/a/default-user=s32-c-k-c0x00ffffff-no-rj',
                        id: id.length > 11 ? null : id,
                        title: 'Click here or visit the associated video with chat enabled to update history information.',
                        valid: false
                    };
                }
            });

            localStorage.setItem('youtubespecialcommentsticker-sticky-index', JSON.stringify(savedStickyIndex));
        }
    };

    let getSavedStickyIndex = () => {
        let savedStickyIndex = localStorage.getItem('youtubespecialcommentsticker-sticky-index');
        return savedStickyIndex ? JSON.parse(savedStickyIndex) : {};
    };

    let getYtInitialData = () => {
        let ytInitialDataScript = [...window.top.document.querySelectorAll('script')].find(script => script.text.indexOf('["ytInitialData"]') > -1);
        return JSON.parse(ytInitialDataScript.text.substr(26, ytInitialDataScript.text.length - 27));
    };

    let getMicroFormatPlayerData = () => {
        let microFormatScript = window.top.document.querySelector('#microformat script');
        return JSON.parse(microFormatScript.text);
    };

    let getSavedStickyIndexDetails = (savedStickyIndex) => {
        if (IS_POPOUT) {
            let stickyIndexDetails = Object.values(savedStickyIndex).find(stickyIndex => stickyIndex.id === CHAT_ID);
            if (stickyIndexDetails) {
                return stickyIndexDetails;
            }
            let ytInitialData = getYtInitialData();
            return {
                author: ytInitialData.contents.liveChatRenderer.participantsList.liveChatParticipantsListRenderer.participants[0].liveChatParticipantRenderer.authorName.simpleText,
                icon: ytInitialData.contents.liveChatRenderer.participantsList.liveChatParticipantsListRenderer.participants[0].liveChatParticipantRenderer.authorPhoto.thumbnails[0].url,
                id: CHAT_ID,
                title: `https://www.youtube.com/watch?v=${CHAT_ID}`,
                valid: true
            };
        }
        let videoId = window.top.document.location.href.split('=')[1];
        let microFormatPlayerData = getMicroFormatPlayerData();
        let existingStickyIndexDetails = savedStickyIndex[CHAT_ID];
        if (microFormatPlayerData.embedUrl.substr(-11) !== videoId || (existingStickyIndexDetails && existingStickyIndexDetails.id && existingStickyIndexDetails.id !== videoId)) {
            throw new Error('Incorrect CHAT_ID.');
        }
        return {
            author: microFormatPlayerData.author || 'Unknown',
            icon: window.top.document.querySelector('#avatar img[src*="http"]').src.replace('=s48', '=s32') || 'https://yt3.ggpht.com/a/default-user=s32-c-k-c0x00ffffff-no-rj',
            id: videoId,
            title: microFormatPlayerData.name || 'Unknown',
            valid: true
        };
    };

    let setUpLocalSavedStickyIndex = () => {
        try {
            let savedStickyIndex = getSavedStickyIndex();
            savedStickyIndex[CHAT_ID] = getSavedStickyIndexDetails(savedStickyIndex);

            if (!IS_POPOUT && savedStickyIndex[savedStickyIndex[CHAT_ID].id]) {
                savedStickyIndex[savedStickyIndex[CHAT_ID].id] = savedStickyIndex[CHAT_ID];
            }

            localStorage.setItem('youtubespecialcommentsticker-sticky-index', JSON.stringify(savedStickyIndex));
        } catch (e) {
            if (e.message !== 'Incorrect CHAT_ID.') {
                setTimeout(setUpLocalSavedStickyIndex);
            }
        }
    };

    let setUpSavedStickyIndex = () => {
        setUpGlobalSavedStickyIndex();
        setUpLocalSavedStickyIndex();
    };

    let getStickyItem = (savedItem) => {
        let stickyItem = document.createElement('div');
        let message = savedItem.content.message.reduce((result, node) => {
            if (node.type === 'text') {
                result += node.value;
            } else if (node.type === 'image') {
                result += `<img class="emoji style-scope yt-live-chat-text-message-renderer" src="${node.value.src}" alt="${node.value.alt}">`;
            } else {
                result += `<a class="yt-simple-endpoint style-scope yt-live-chat-text-message-renderer" href="${node.value.href}" rel="nofollow" target="_blank">${node.value.text}</a>`;
            }
            return result;
        }, '');
        stickyItem.classList.add('sticky-item');
        stickyItem.innerHTML = `<div id="author-photo" class="no-transition style-scope yt-live-chat-text-message-renderer" height="24" width="24" style="background-color: transparent;"><img id="img" class="style-scope yt-img-shadow" alt="" height="24" width="24" src="${savedItem.authorPhoto.img.src}"></div>`;
        stickyItem.innerHTML += `<div id="content" class="style-scope yt-live-chat-text-message-renderer"><span class="timestamp">${savedItem.content.timestamp}</span><div class="author"><span class="author-name ${savedItem.content.author.classes}">${savedItem.content.author.name}</span></div><span id="message" dir="auto" class="style-scope yt-live-chat-text-message-renderer">${message}</span></div>`;
        return stickyItem;
    };

    let stickSavedItem = (savedItem) => {
        if (isSpecial(savedItem)) {
            let stickyItem = getStickyItem(savedItem);
            stickyItems.append(stickyItem);
            stickyItemObjectsMap[`${savedItem.content.timestamp}-${savedItem.content.author.name}-${stickyItem.querySelector('#message').innerText}`] = true;
            showSticky();
            scrollSticky();
        }
    };

    let setUpSavedStickyItems = () => {
        let savedStickyItems = localStorage.getItem(`youtubespecialcommentsticker-sticky-items-${CHAT_ID}`);
        stickyItemObjectsMap = {};
        stickyItemObjects = savedStickyItems ? JSON.parse(savedStickyItems) : [];
        stickyItemObjects.forEach(stickSavedItem);
    };

    let setUpSticky = () => {
        let hideTimestamp = document.querySelector('yt-live-chat-renderer').hasAttribute('hide-timestamps');
        let stickyAnchor = document.querySelector('yt-live-chat-app');
        sticky = document.createElement('div');

        sticky.id = 'sticky';
        sticky.setAttribute('class', `style-scope ytd-watch-flexy ${IS_DARK ? 'dark' : ''} ${hideTimestamp ? 'hide-timestamps': ''}`);
        sticky.style.display = 'none';
        sticky.innerHTML = '<tp-yt-iron-dropdown id="menu" horizontal-align="auto" vertical-align="top" aria-disabled="false" class="style-scope yt-live-chat-app sticky-panel" prevent-autonav="true" aria-hidden="true" tabindex="0"><div id="contentWrapper" class="style-scope tp-yt-iron-dropdown"></div></tp-yt-iron-dropdown>';
        sticky.innerHTML += '<yt-icon-button id="overflow" class="style-scope yt-live-chat-header-renderer"><button id="button" class="style-scope yt-icon-button"><yt-icon icon="more_vert" class="style-scope yt-live-chat-header-renderer"><svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope yt-icon"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" class="style-scope yt-icon"></path></g></svg></yt-icon></button><paper-ripple class="style-scope yt-icon-button circle"><div id="background" class="style-scope paper-ripple" style="opacity: 0;"></div><div id="waves" class="style-scope paper-ripple"></div></paper-ripple></yt-icon-button>';
        sticky.innerHTML += '<div id="history" horizontal-align="auto" vertical-align="top" aria-disabled="false" class="style-scope yt-live-chat-app sticky-panel" prevent-autonav="true" style="display: none;" aria-hidden="true" tabindex="0"><div id="contentWrapper" class="style-scope iron-dropdown"><ytd-menu-popup-renderer slot="dropdown-content" class="style-scope yt-live-chat-app" tabindex="-1" use-icons_=""></ytd-menu-popup-renderer></div></div>';
        sticky.innerHTML += '<div id="settings" horizontal-align="auto" vertical-align="top" aria-disabled="false" class="style-scope yt-live-chat-app sticky-panel" prevent-autonav="true" style="display: none;" aria-hidden="true" tabindex="0"><div id="contentWrapper" class="style-scope iron-dropdown"><ytd-menu-popup-renderer slot="dropdown-content" class="style-scope yt-live-chat-app" tabindex="-1" use-icons_=""></ytd-menu-popup-renderer></div></div>';
        sticky.innerHTML += '<div id="show-hide-button" class="style-scope ytd-live-chat-frame"><div class="style-scope ytd-live-chat-frame" use-keyboard-focused="" is-paper-button="" button-renderer="true"><a class="yt-simple-endpoint style-scope ytd-toggle-button-renderer" tabindex="-1"><tp-yt-paper-button id="button" class="style-scope ytd-toggle-button-renderer" role="button" tabindex="0" animated="" elevation="0" aria-disabled="false"><yt-formatted-string id="text" class="style-scope ytd-toggle-button-renderer"></yt-formatted-string><paper-ripple class="style-scope tp-yt-paper-button"><div id="background" class="style-scope paper-ripple"></div><div id="waves" class="style-scope paper-ripple"></div></paper-ripple></tp-yt-paper-button></a></div></div>'
        sticky.innerHTML += '<div id="chat-header" role="heading" class="style-scope yt-live-chat-renderer"><div id="primary-content" class="style-scope yt-live-chat-header-renderer"></div>';
        sticky.innerHTML += '<yt-icon-button id="show-more" class="style-scope yt-live-chat-item-list-renderer" style="transform: translateY(42px);"><button id="button" class="style-scope yt-icon-button"><button id="button" class="style-scope yt-icon-button" aria-label="More comments below"><yt-icon icon="down_arrow" class="style-scope yt-live-chat-item-list-renderer"><svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope yt-icon"><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" class="style-scope yt-icon"></path></g></svg></yt-icon></button></button><paper-ripple class="style-scope yt-icon-button circle"><div id="background" class="style-scope paper-ripple" style="opacity: 0.006272;"></div><div id="waves" class="style-scope paper-ripple"></div></paper-ripple></yt-icon-button>'
        sticky.innerHTML += '<ytd-popup-container id="notice" class="style-scope ytd-app" style="display: none;"><tp-yt-iron-dropdown horizontal-align="right" vertical-align="top" aria-disabled="false" class="style-scope ytd-popup-container" force-close-on-outside-click="true" style="outline: none; position: fixed; left: 101px; top: 44px; z-index: 2202;"></tp-yt-iron-dropdown></ytd-popup-container>';
        menu = sticky.querySelector('#menu');
        menuButton = sticky.querySelector('#overflow');
        history = sticky.querySelector('#history');
        settings = sticky.querySelector('#settings');
        stickyItemsContainer = sticky.querySelector('#chat-header');
        toggleSticky = sticky.querySelector('#show-hide-button');
        toggleStickyText = toggleSticky.querySelector('#text');
        scrollButton = sticky.querySelector('#show-more');
        scrollButton.querySelector('#button').style.height = 'auto';
        notice = sticky.querySelector('#notice');

        stickyAnchor.parentNode.insertBefore(sticky, stickyAnchor);
        stickyAnchor.style.position = 'relative';
        toggleStickyText.innerText = 'Hide Sticky';
        toggleStickyText.removeAttribute('is-empty');

        sticky.querySelectorAll('#title, #view-selector, #action-buttons, #items, #footer').forEach(element => element.parentNode.removeChild(element));
        stickyItems = sticky.querySelector('#primary-content');
        history.querySelector('ytd-menu-popup-renderer').innerHTML = '<div id="items" class="style-scope ytd-menu-popup-renderer" role="listbox" tabindex="0"></div>';
        settings.querySelector('ytd-menu-popup-renderer').innerHTML = '<div id="items" class="style-scope ytd-menu-popup-renderer" role="listbox" tabindex="0"></div>';
        notice.querySelector('#contentWrapper').innerHTML = '<yt-tooltip-renderer id="tooltip" slot="dropdown-content" class="style-scope ytd-popup-container" tabindex="-1" has-buttons_="" style="outline: none; box-sizing: border-box; max-width: 294.406px; max-height: 120px;" position-type="OPEN_POPUP_POSITION_BOTTOMLEFT"></yt-tooltip-renderer>';

        setUpMenu();
        setUpSettings();
        setUpSavedStickyIndex();
        setUpSavedStickyItems();
    };

    let monitorDarkTheme = () => {
        let observer = new MutationObserver(() => {
            sticky.classList.toggle('dark');
        });
        observer.observe(document.querySelector('html'), {
            attributes: true,
            attributeFilter: ['dark']
        });
    };

    let monitorTimestampToggle = () => {
        let observer = new MutationObserver(() => {
            sticky.classList.toggle('hide-timestamps');
            scrollSticky();
        });
        observer.observe(document.querySelector('yt-live-chat-renderer'), {
            attributes: true,
            attributeFilter: ['hide-timestamps']
        });
    };

    let monitorSettings = () => {
        let config = {
            characterData: true,
            subtree: true
        };
        let blacklistObserver = new MutationObserver((mutations) => {
            if (mutations[0].target.data) {
                whitelist.classList.add('disabled');
            } else {
                whitelist.classList.remove('disabled');
            }
        });
        let whitelistObserver = new MutationObserver((mutations) => {
            if (mutations[0].target.data) {
                blacklist.classList.add('disabled');
            } else {
                blacklist.classList.remove('disabled');
            }
        });
        blacklistObserver.observe(blacklist, config);
        whitelistObserver.observe(whitelist, config);
    };

    let restoreSettingsData = () => {
        setSettingsList(blacklist, 'blacklist', true);
        setSettingsList(whitelist, 'whitelist', true);
        settingsData.moderator ? moderator.setAttribute('checked', '') : moderator.removeAttribute('checked');
        settingsData.owner ? owner.setAttribute('checked', '') : owner.removeAttribute('checked');
        settings.style.display = 'none';
    };

    let handlePaste = (element) => {
        let paste = event.clipboardData.getData('Text');
        let selection = window.getSelection();
        let position = selection.anchorOffset;
        element.innerText = `${element.innerText.slice(0, position)}${paste}${element.innerText.slice(position)}`;
        try {
            selection.collapse(element.lastChild, position + paste.length);
        } catch (error) {
            selection.collapse(element.lastChild, 0);
        }
    };

    let filterStickyItems = () => {
        Array.from(stickyItems.children).forEach((stickyItem) => {
            if (!isSpecial(stickyItem.querySelector('.author-name'))){
                stickyItem.setAttribute('style', 'display: none;');
            } else {
                stickyItem.removeAttribute('style');
            }
        });
    };

    let setUpEvents = () => {
        stickyItems.addEventListener('scroll', () => {
            setTimeout(() => {
                canScroll = stickyItems.scrollHeight - stickyItems.scrollTop === stickyItems.clientHeight;
                scrollButton.style.transform = `translateY(${canScroll ? 42 : 0}px)`;
                scrollButton.style.visibility = canScroll ? 'hidden' : 'visible';
            });
        });
        scrollButton.addEventListener('click', () => {
            canScroll = true;
            scrollSticky();
        });
        toggleSticky.addEventListener('click', () => {
            if (stickyItemsContainer.offsetHeight > 0) {
                chat.setAttribute('style', `max-height: ${chat.offsetHeight + 150}px;`);
                toggleStickyText.innerText = 'Show Sticky';
                stickyItemsContainer.setAttribute('style', 'border: none;');
                stickyItemsContainer.style.height = '0px';
                scrollSticky(true);
            } else {
                chat.setAttribute('style', `max-height: ${chat.offsetHeight - 150}px;`);
                itemScroller.scrollTop += 150;
                toggleStickyText.innerText = 'Hide Sticky';
                stickyItemsContainer.setAttribute('style', '');
                stickyItemsContainer.style.height = '150px';
            }
        });
        chatSettingsButton.addEventListener('click', () => {
            if (sticky.style.display === 'none') {
                let chatSettings = document.querySelector('yt-live-chat-app > tp-yt-iron-dropdown #items');
                if (!chatSettings.querySelector('#chatSettingsStickyButton')) {
                    chatSettingsStickyButton = document.createElement('div');
                    chatSettingsStickyButton.innerHTML = `<ytd-menu-service-item-renderer id="chatSettingsStickyButton" class="style-scope ytd-menu-popup-renderer" role="menuitem" use-icons="" tabindex="-1" aria-selected="false" data='{"text": {"runs": [{"text":"Show Sticky"}]}, "icon": {"iconType": "PLAYLIST_ADD_CHECK"}, "serviceEndpoint": {}}'></ytd-menu-service-item-renderer>`;
                    chatSettingsStickyButton = chatSettingsStickyButton.firstChild;
                    chatSettings.append(chatSettingsStickyButton);
                    chatSettingsStickyButton.addEventListener('click', () => {
                        chatSettingsButton.click();
                        showSticky();
                        chatSettingsStickyButton.style.display = 'none';
                    });
                }
            }
        });
        menuButton.addEventListener('click', () => {
            menu.style.display = menu.style.display === 'none' ? 'initial' : 'none';
            if (menu.style.display === 'initial') {
                menu.focus();
            }
        });
        menu.addEventListener('focusout', (event) => {
            if (!menu.contains(event.relatedTarget) && !menuButton.contains(event.relatedTarget)) {
                menu.style.display = 'none';
            }
        });
        historyButton.addEventListener('click', () => {
            setUpHistory();
            history.style.display = history.style.display === 'none' ? 'initial' : 'none';
            if (history.style.display === 'initial') {
                history.focus();
            }
        });
        history.addEventListener('focusout', (event) => {
            if (!history.contains(event.relatedTarget) && !history.hasAttribute('editing') && !history.hasAttribute('refreshing') && !history.hasAttribute('importing')) {
                history.style.display = 'none';
            }
        });
        settingsButton.addEventListener('click', () => {
            settings.style.display = settings.style.display === 'none' ? 'initial' : 'none';
            if (settings.style.display === 'initial') {
                settings.focus();
            }
        });
        settings.addEventListener('focusout', (event) => {
            if (!settings.contains(event.relatedTarget)) {
                restoreSettingsData();
                setUpSettingsItemsStates();
            }
        });
        blacklist.querySelector('#input > div').addEventListener('paste', () => {
            handlePaste(blacklist.querySelector('#input > div'));
            setUpSettingsItemsStates(true);
        });
        whitelist.querySelector('#input > div').addEventListener('paste', () => {
            handlePaste(whitelist.querySelector('#input > div'));
            setUpSettingsItemsStates(true);
        });
        settings.querySelectorAll('#toggle').forEach((toggle) => {
            toggle.addEventListener('click', (event) => {
                if (event.currentTarget.hasAttribute('checked')) {
                    event.currentTarget.removeAttribute('checked');
                } else {
                    event.currentTarget.setAttribute('checked', '');
                }
            });
        });
        cancel.addEventListener('click', () => {
            restoreSettingsData();
            setUpSettingsItemsStates();
        });
        save.addEventListener('click', () => {
            setSettingsList(blacklist, 'blacklist');
            setSettingsList(whitelist, 'whitelist');
            settingsData.moderator = moderator.hasAttribute('checked');
            settingsData.owner = owner.hasAttribute('checked');
            localStorage.setItem('youtubespecialcommentsticker-settings', JSON.stringify(settingsData));
            settings.style.display = 'none';
            filterStickyItems();
        });
        notice.addEventListener('mouseleave', () => {
            notice.touched = true;
            notice.style.display = 'none';
        });
        window.addEventListener('beforeunload', () => {
            let savedSticky = getHistory(CHAT_ID);
            if (!savedSticky) {
                let savedStickyIndex = getSavedStickyIndex();
                if (savedStickyIndex[CHAT_ID]) {
                    delete savedStickyIndex[CHAT_ID];
                    localStorage.setItem('youtubespecialcommentsticker-sticky-index', JSON.stringify(savedStickyIndex));
                }
            }
        }, false);
    };

    let initMonitoring = () => {
        setUpCss();
        setUpSticky();
        monitor();
        monitorDarkTheme();
        monitorTimestampToggle();
        monitorSettings();
        setUpEvents();
    };

    let init = () => {
        if (window.top.document.readyState === 'complete') {
            initMonitoring();
        } else {
            window.top.addEventListener('load', initMonitoring);
        }
    };

    init();
})();