TW Chat Media Embedder

Enables embedding of certain images in the chat.

// ==UserScript==
// @name            TW Chat Media Embedder
// @namespace       http://tampermonkey.net/
// @version         1.0.1
// @description     Enables embedding of certain images in the chat.
// @include         https://*.the-west.*/game.php*
// @grant           none
// @license         GNU
// ==/UserScript==

window.MediaEmbedder = {
    baseUrl: `https://enormous-seasoned-porkpie.glitch.me`,
    screenshotRegex: new RegExp(/(?:https:\/\/)?(?:prnt\.sc|ctrlv\.[a-zA-Z]{2,4}|imgur\.com)\/[\/A-Za-z0-9_-]+/, 'g'),

    showImageFullscreen: function(url, originalUrl) {
        const html = $(`
            <div id='screenshot-framefix' style='position: fixed; inset: 0; padding: 2rem 4rem; z-index: 9999; background-color: rgba(0, 0, 0, .8)'>
                <div style='cursor: pointer; position: absolute; top: 1rem; right: 1rem'>
                    <svg class='tw-chat-embedder-close-icon' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V96c0-8.8-7.2-16-16-16H64zM0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm175 79c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>
                </div>

                <div style='position: relative; display: flex; height: 100%; justify-content: center; align-items: center'>
                    <img src='${url}' alt='image not found' style='object-fit: contain; max-width: 100%; max-height: 100%'>
                </div>

                <div id='tw-chat-embedder-image-link' style='position: absolute; bottom: 1rem; right: 1rem; color: white'>
                    <a target='_blank' href='${originalUrl}'>${originalUrl}</a>
                    <span></span>
                </div>
            </div>
        `)

        html.click(() => {
            document.body.removeChild(html[0])
        })

        html.find('img').click(e => e.stopPropagation())

        document.body.appendChild(html[0])
    },  

    getImageUrl: async function(url) {
        try {
            const response = await fetch(`${this.baseUrl}/img-url?url=${url}`, {
                method: 'Get',
                mode: 'cors'
            })

            if ( !response.ok ) {
                return null
            }

            const { image_url } = await response.json()     
            return image_url
        } catch(e) {
            console.log(e)
            return null
        }         
    },

    getImageHtml: function(url, originalUrl) {
        const collapseHtml = $(`
            <span style='display: flex; gap: 3px; font-weight: bold'>
                Collapse
                <svg style='fill: white; width: 10px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M439 7c9.4-9.4 24.6-9.4 33.9 0l32 32c9.4 9.4 9.4 24.6 0 33.9l-87 87 39 39c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H296c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2l39 39L439 7zM72 272H216c13.3 0 24 10.7 24 24V440c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-39-39L73 505c-9.4 9.4-24.6 9.4-33.9 0L7 473c-9.4-9.4-9.4-24.6 0-33.9l87-87L55 313c-6.9-6.9-8.9-17.2-5.2-26.2s12.5-14.8 22.2-14.8z"/></svg>
            </span>
        `)

        const expandHtml = $(`
            <span style='display: flex; gap: 3px; transform: translateY(.3rem); font-weight: bold'>
                Expand
                <svg style='fill: white; width: 10px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M344 0H488c13.3 0 24 10.7 24 24V168c0 9.7-5.8 18.5-14.8 22.2s-19.3 1.7-26.2-5.2l-39-39-87 87c-9.4 9.4-24.6 9.4-33.9 0l-32-32c-9.4-9.4-9.4-24.6 0-33.9l87-87L327 41c-6.9-6.9-8.9-17.2-5.2-26.2S334.3 0 344 0zM168 512H24c-13.3 0-24-10.7-24-24V344c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2l39 39 87-87c9.4-9.4 24.6-9.4 33.9 0l32 32c9.4 9.4 9.4 24.6 0 33.9l-87 87 39 39c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8z"/></svg>
            </span>
        `)

        const html = $(`
            <span style='width: 100%; cursor: pointer; padding: .2rem .5rem; display: inline-block; box-sizing: border-box; position: relative; overflow: hidden'>
                <img src='${url}' width='100%' alt='embedded image' style='border-radius: 8px' onclick="MediaEmbedder.showImageFullscreen('${url}', '${originalUrl}')">
                <span class='expand-collapse-image' style='position: absolute; right: 5px; bottom: 5px; font-size: .7rem'></span>
            </span> 
        `)

        function replaceElement(isExpanded) {
            const element = isExpanded ? collapseHtml : expandHtml
            html.find('span.expand-collapse-image').html(element)
            html.css('max-height', isExpanded ? '100%': '1rem')
    
            element.off('click')
    
            element.click(toggleExpand(!isExpanded))
    
            return element
        }
    
        function toggleExpand(isExpanded) {
            return function() {
                replaceElement(isExpanded)
            }
        }

        
        collapseHtml.click(toggleExpand(false))
    

        html.find('span.expand-collapse-image').html(collapseHtml)

        return html
    },

    getMessageHtml: function(media) {
        const now = new ServerDate().date

        const hours = now.getHours()
        const minutes = now.getMinutes()

        const html = $(`
            <table cellpadding='0' cellspacing='0'>
                <tr>
                    <td style='white-space: nowrap' class='chat_info'>
                        <span class='chat_time'>
                            [<strong>${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}</strong>]
                        </span>
                        <span class='chat_from'>
                            <strong>Media Embedder</strong>:
                        </span>
                    </td>
                    <td class='media-content'></td>
                </tr>
            </table>
        `)

        html.find('td.media-content').html(media)
        return html
    },

    pushEmbeddedImage: async function(room, url) {
        const imageUrl = await this.getImageUrl(url)

        if ( imageUrl === null ) return

        const imageHtml = this.getImageHtml(imageUrl, url)
        const message = this.getMessageHtml(imageHtml)

        room.history.push(message)
        room.notify('NewMessage', message)
    },

    getMessageContent: function(htmlString) {
        const html = $(htmlString)
        const message = html.find('td.chat_text')
        const tempDiv = $('<div></div>').html(message.html())
        return tempDiv.text()
    },

    testMessage: function(room, message) {
        const text = this.getMessageContent(message)
        const urls = [...new Set(Array.from(text.matchAll(this.screenshotRegex)).map(e => e[0]))]

        for (const url of urls) {
            this.pushEmbeddedImage(room, url)
        }
    },

    replaceEmojis: function(message) {
        const pattern = /(\s|^):[\/D)(|POx]+(\s|$)/g

        const result = message.replace(pattern, match => {
            const emojis = match.trim().slice(1)
            const replacement = ' ' + emojis.split('').map(c => `:${c}`).join(' ') + ' '

            return replacement
        })

        return result
    },

    init: function() {
        const addMessage = Chat.Resource.Room.prototype.addMessage
        Chat.Resource.Room.prototype.addMessage = function(message) {
            addMessage.bind(this, message)()
            MediaEmbedder.testMessage(this, message)
        }

        const sendMessage = Chat.sendMessage
        Chat.sendMessage = function(message, room) {
            message = MediaEmbedder.replaceEmojis(message)
            sendMessage(message, room)
        }
    
        const newCss = `
            #tw-chat-embedder-image-link a {
                text-decoration: none;
                color: white;
                position: relative;
                padding: .4rem .2rem;
            }
    
            #tw-chat-embedder-image-link span {
                position: absolute;
                bottom: 0;
                right: 0;
                width: 100%;
                height: 2px;
                background-color: white;
                opacity: 0;
                transition: opacity .3s;
            }
    
            #tw-chat-embedder-image-link:hover span {
                opacity: 1;
            }
    
            .tw-chat-embedder-close-icon {
                fill: white; 
                transition: fill .3s; 
                width: 25px
            }
    
            .tw-chat-embedder-close-icon:hover {
                fill: rgb(255, 144, 144)
            }
        `
    
        const style = $('<style>').text(newCss)
        $('head').append(style)
    }
}

window.EmojiIndex = {
    emojis: new Map(),
    index: new Map(),
    state: {
        currentIndex: 0,
        lastIndex: 0,
        keyUpHandler: null
    },

    add: function(key, emoji) {
        this.emojis.set(key, emoji)

        modifiedKey = key.replaceAll('_', '')
    
        for(let i = 0; i < modifiedKey.length; i++) {
            for(let j = i + 1; j <= modifiedKey.length; j++) {
                const substr = modifiedKey.slice(i, j)
                if(!this.index.has(substr)) {
                    this.index.set(substr, new Set())
                }
                this.index.get(substr).add(key)
            }
        }
    },

    search: function(substring) {
        substring = substring.replaceAll('_', '')
        return this.index.has(substring) ? 
            Array.from(this.index.get(substring)).map(key => ({
                key,
                emoji: this.emojis.get(key)
            })) : []
    },

    sort: function(result, searchstring) {
        const searchLen = searchstring.replaceAll('_', '').length

        return result.sort((a, b) => (a.key.length - searchLen) - (b.key.length - searchLen))
    },

    getSingleEmojiHtmlString: function({key, emoji}, index) {
        return `
            <div class='emoji_option ${index == 0 ? "active" : ""}' id='emoji-option-${index}' data-emoji='${emoji}' onmouseenter='EmojiIndex.handleMouseEnter(event, ${index})' onclick='EmojiIndex.handleClick(event, ${index})'>
                <span>${emoji}</span>
                <span>:${key}:</span>
            </div>
        `
    },

    getEmojiOptionsHtml: function(result) {
        return $(`
            <div class='emoji_options'>
                ${
                    result.map((e, i) => this.getSingleEmojiHtmlString(e, i)).join('\n')
                }
            </div>
        `)
    },

    removeOptionsWindow: function(inputElement) {
        $('.emoji_options').remove()
        $(inputElement).on('keyup', this.state.keyUpHandler)
        this.state.keyUpHandler = null
    },

    createOptionsWindow: function(searchstring, inputElement) {
        this.removeOptionsWindow(inputElement)

        const search = this.search(searchstring)
        if ( search.length == 0 ) {
            return null
        }
        const sorted = this.sort(search, searchstring)
        const html = this.getEmojiOptionsHtml(sorted)

        this.state.lastIndex = sorted.length - 1
        this.state.currentIndex = 0
        
        $(`#emoji-option-0`).toggleClass('active')

        this.state.keyUpHandler = jQuery._data($(inputElement)[0], 'events').keyup[0]
        $(inputElement).off('keyup')
        return html
    },

    changeActiveOption: function(direction) {
        $(`#emoji-option-${this.state.currentIndex}`).toggleClass('active')
        this.state.currentIndex += direction
        if (this.state.currentIndex < 0) this.state.currentIndex = this.state.lastIndex
        if (this.state.currentIndex > this.state.lastIndex) this.state.currentIndex = 0
        $(`#emoji-option-${this.state.currentIndex}`).toggleClass('active')
        $(`#emoji-option-${this.state.currentIndex}`)[0]?.scrollIntoViewIfNeeded()
    },

    selectEmoji: function(input, index = this.state.currentIndex) {
        const text = input.value
        const cursorPosition = input.selectionStart
        const pattern = /:(?:[a-z_]{2,}):?/g
    
        const matches = [...text.matchAll(pattern)]
        
        for (const match of matches) {
            const matchStart = match.index
            const matchEnd = matchStart + match[0].length
    
            if (cursorPosition >= matchStart && cursorPosition <= matchEnd) {
                const emoji = $(`#emoji-option-${index}`).data('emoji')
                if (!emoji) return
                const beforeMatch = text.slice(0, matchStart)
                const afterMatch = text.slice(matchEnd)
                input.value = beforeMatch + emoji + afterMatch
                
                const newPosition = matchStart + emoji.length
                input.setSelectionRange(newPosition, newPosition)
                
                this.removeOptionsWindow(input)
                return
            }
        }
    },

    getEmojiHtml: function([key, emoji], input, onMouseEnter, onClick) {
        const html = $(`
            <div class='emoji-list-item'>
                ${emoji}
            </div>
        `)

        html.on('mouseenter', onMouseEnter(key, emoji))
        html.on('click', onClick(emoji, input))

        return html
    },

    createEmojiListGui: function(input) {
        const html = $(`
            <aside class='EmojiIndex'>
                <div class='icon-wrapper'>
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM164.1 325.5C182 346.2 212.6 368 256 368s74-21.8 91.9-42.5c5.8-6.7 15.9-7.4 22.6-1.6s7.4 15.9 1.6 22.6C349.8 372.1 311.1 400 256 400s-93.8-27.9-116.1-53.5c-5.8-6.7-5.1-16.8 1.6-22.6s16.8-5.1 22.6 1.6zM144.4 208a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm192-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
                </div>
                <div class='emoji-list'>
                    <div class='list'>

                    </div>

                    <div class='emoji-active'>
                        <span class='emoji'>😅</span>
                        <span class='emoji-key'>:sweat_smile:</span>
                    </div>
                </div>
            </aside>
        `)

        function openList() {
            $('aside.EmojiIndex div.emoji-list').toggle()
            $('aside.EmojiIndex div.icon-wrapper').toggleClass('active')
        }

        function onMouseEnter(key, emoji) {
            return function() {
                const div = html.find('div.emoji-list div.emoji-active')
                div.find('span.emoji').html(emoji)
                div.find('span.emoji-key').html(`:${key}:`)
            }
        }

        function onClick(emoji, input) {
            return function() {
                const startPos = input[0].selectionStart
                const endPos = input[0].selectionEnd 
                const currentValue = input.val()
                const newValue = currentValue.substring(0, startPos) + emoji + currentValue.substring(endPos)
                input.val(newValue)
                const newCursorPos = startPos + emoji.length
                input[0].setSelectionRange(newCursorPos, newCursorPos)
            }
        }
        
        const container = html.find('div.emoji-list div.list')
        this.emojis.entries().forEach(e => container.append(this.getEmojiHtml(e, input, onMouseEnter, onClick)))

        html.find('div.icon-wrapper svg').on('click', openList)

        return html
    },

    handleKeyDown: function(event) {
        switch (event.key) {
            case 'ArrowUp':
                event.preventDefault()
                this.changeActiveOption(-1)
                break
            case 'ArrowDown':
                event.preventDefault()
                this.changeActiveOption(1)
                break
            case 'Tab':
                event.preventDefault()
                this.selectEmoji(event.target)
                break
        }
    },

    handleInput: function(event) {
        const input = event.target
        const text = input.value
        const pattern = /:(?:[a-z_]{2,}):?/

        const match = text.match(pattern)
        if (match) {
            const options = this.createOptionsWindow(match[0].replaceAll(':', ''), input)
            $(input).parent().parent().append(options)
        } else {
            this.removeOptionsWindow(input)
        }
    },

    handleMouseEnter: function(event, index) {
        $(`#emoji-option-${this.state.currentIndex}`).toggleClass('active')
        event.target.classList.toggle('active')
        this.state.currentIndex = index
    },

    handleClick: function(event, index) {
        const parent = event.currentTarget.parentElement.parentElement
        const input = parent.querySelector('input.message')
        this.selectEmoji(input, index)
    },

    init: function() {
        const emojis = {       
            // Smileys & Emotion
            'grinning': '😁',
            'laughing': '😆',
            'joy': '😂',
            'sweat_smile': '😅',
            'rofl': '🤣',
            'relaxed': '☺️',
            'blush': '😊',
            'innocent': '😇',
            'slight_smile': '🙂',
            'upside_down': '🙃',
            'smiling_face_with_tear': '🥲',
            'relieved': '😌',
            'heart_eyes': '😍',
            'smiling_face_with_three_hearts': '🥰',
            'kissing_heart': '😘',
            'kissing': '😗',
            'kissing_closed_eyes': '😚',
            'kissing_smiling_eyes': '😙',
            'yum': '😋',
            'stuck_out_tongue_closed_eyes': '😝',
            'stuck_out_tongue_winking_eye': '😜',
            'zany_face': '🤪',
            'thinking': '🤔',
            'shushing': '🤫',
            'zipper_mouth': '🤐',
            'raised_eyebrow': '🤨',
            'neutral_face': '😐',
            'expressionless': '😑',
            'no_mouth': '😶',
            'smirk': '😏',
            'unamused': '😒',
            'rolling_eyes': '🙄',
            'grimacing': '😬',
            'lying_face': '🤥',
            'relieved': '😌',
            'pensive': '😔',
            'sleepy': '😪',
            'drooling_face': '🤤',
            'sleeping': '😴',
            'mask': '😷',
            'thermometer_face': '🤒',
            'dizzy_face': '😵',
            'exploding_head': '🤯',
            'flushed': '😳',
            'pleading': '🥺',
            'frowning2': '☹️',
            'worried': '😟',
            'slight_frown': '🙁',
            'cry': '😢',
            'sob': '😭',
            'anger': '💢',
            'rage': '😡',
            'face_with_symbols': '🤬',
            'face_screaming_in_fear': '😱',
            'face_with_monocle': '🧐',
            'exploding_head': '🤯',
            
            // People & Body
            'wave': '👋',
            'raised_back_of_hand': '🤚',
            'raised_hand': '✋',
            'vulcan': '🖖',
            'ok_hand': '👌',
            'pinching_hand': '🤏',
            'victory': '✌️',
            'crossed_fingers': '🤞',
            'love_you_gesture': '🤟',
            'metal': '🤘',
            'call_me': '🤙',
            'point_left': '👈',
            'point_right': '👉',
            'point_up': '☝️',
            'point_down': '👇',
            'thumbs_up': '👍',
            'thumbs_down': '👎',
            'fist': '✊',
            'punch': '👊',
            'clap': '👏',
            'raising_hands': '🙌',
            'heart': '❤️',
            'broken_heart': '💔',
            'two_hearts': '💕',
            'sparkling_heart': '💖',
            'heartbeat': '💓',
            'heartpulse': '💗',
            'gift_heart': '💝',
            'man_gesturing_no': '🙅‍♂️',
            'man_gesturing_ok': '🙆‍♂️',
            'man_bowing': '🙇‍♂️',
            'man_raising_hand': '🙋‍♂️',
            'man_facepalming': '🤦‍♂️',
            'man_shrugging': '🤷‍♂️',
            'man_pouting': '🙎‍♂️',
            'man_frowning': '🙍‍♂️',
            'man_getting_massage': '💆‍♂️',
            'man_getting_haircut': '💇‍♂️',
            'man_tipping_hand': '💁‍♂️',
            'woman_gesturing_no': '🙅‍♀️',
            'woman_gesturing_ok': '🙆‍♀️',
            'woman_bowing': '🙇‍♀️',
            'woman_raising_hand': '🙋‍♀️',
            'woman_facepalming': '🤦‍♀️',
            'woman_shrugging': '🤷‍♀️',
            'woman_pouting': '🙎‍♀️',
            'woman_frowning': '🙍‍♀️',
            'woman_getting_massage': '💆‍♀️',
            'woman_getting_haircut': '💇‍♀️',
            'woman_tipping_hand': '💁‍♀️',
            'person_bouncing_ball': '⛹️',
            'person_swimming': '🏊',
            'person_walking': '🚶',
            'person_running': '🏃',
            'person_cartwheeling': '🤸',
            'person_juggling': '🤹',
            'person_biking': '🚴',
            'person_rowing_boat': '🚣',
            'person_surfing': '🏄',
            'pray': '🙏',
            'poop': '💩',
            'nail_polish': '💅',
        
            
            // Activities & Objects
            'gift': '🎁',
            'tada': '🎉',
            'medal': '🏅',
            'trophy': '🏆',
            'crown': '👑',
            'musical_note': '🎵',
            'fire': '🔥',
            'boom': '💥',
            'sparkles': '✨',
            'dizzy': '💫',
            '100': '💯',
            'question': '❓',
            'exclamation': '❗',
            'warning': '⚠️',
            'star': '⭐',
            'rainbow': '🌈',
            'sunny': '☀️',
            'moon': '🌙',
            'cloud': '☁️',
            'zap': '⚡',
            'ghost': '👻',
            'skull': '💀',
            'robot': '🤖',
            'space_invader': '👾',
            'knife': '🔪',
            'gun': '🔫',
            'bomb': '💣',
            'pill': '💊',
            'syringe': '💉',
            'money_bag': '💰',
            'credit_card': '💳',
            'gem': '💎',
            'magic_wand': '🪄',
            'video_game': '🎮',
            'joystick': '🕹️',
            'game_die': '🎲',
            'puzzle_piece': '🧩',
            'chess_pawn': '♟️',
            
            // Animals & Nature
            'dog': '🐕',
            'cat': '🐈',
            'mouse': '🐁',
            'hamster': '🐹',
            'rabbit': '🐇',
            'fox': '🦊',
            'bear': '🐻',
            'panda': '🐼',
            'koala': '🐨',
            'tiger': '🐯',
            'lion': '🦁',
            'cow': '🐄',
            'pig': '🐷',
            'frog': '🐸',
            'monkey': '🐒',
            'chicken': '🐔',
            'penguin': '🐧',
            'bird': '🐦',
            'dove': '🕊️',
            'eagle': '🦅',
            'duck': '🦆',
            'owl': '🦉',
            'butterfly': '🦋',
            'snail': '🐌',
            'snake': '🐍',
            'dragon': '🐉',
            'unicorn': '🦄',
            
            // Food & Drink
            'pizza': '🍕',
            'hamburger': '🍔',
            'fries': '🍟',
            'hotdog': '🌭',
            'taco': '🌮',
            'sushi': '🍣',
            'cookie': '🍪',
            'cake': '🍰',
            'cupcake': '🧁',
            'candy': '🍬',
            'lollipop': '🍭',
            'chocolate_bar': '🍫',
            'popcorn': '🍿',
            'doughnut': '🍩',
            'tea': '🍵',
            'coffee': '☕',
            'beer': '🍺',
            'wine_glass': '🍷',
            'cocktail': '🍸',

            //symbols
            'check_mark': '✔️',
            'check_mark_button': '✅'
        }

        for (const key in emojis) {
            this.add(key, emojis[key])
        }

        const newCss = `
            .chat_input input.message {
                padding-right: 30px !important;
                box-sizing: border-box
            }

            .emoji_options {
                position: absolute;
                bottom: 110%;
                width: 100%;
                border-radius: 2px;
                background: rgba(20, 20, 20, .9);
                max-height: calc(4 * 1.8rem);
                overflow-y: auto;
            }

            .emoji_options::-webkit-scrollbar, aside.EmojiIndex div.emoji-list div.list::-webkit-scrollbar {
                width: 6px; 
                background-color: transparent;
            }

            .emoji_options::-webkit-scrollbar-thumb, aside.EmojiIndex div.emoji-list div.list::-webkit-scrollbar-thumb {
                background-color: #CFCFCF;
                border-radius: 2px;
                cursor: pointer;
            }

            .emoji_options::-webkit-scrollbar-thumb:hover, aside.EmojiIndex div.emoji-list div.list::-webkit-scrollbar-thumb:hover {
                background-color: #EFEFEF;
            }

            .emoji_options::-webkit-scrollbar-track, aside.EmojiIndex div.emoji-list div.list::-webkit-scrollbar-track {
                background: rgb(10, 10, 10);
                border-radius: 1px;
            }

            .emoji_option {
                position: relative;
                padding: .2rem .4rem;
                box-sizing: border-box;
                cursor: pointer;
                border-radius: 2px;
                font-size: .9rem;
                transition: background .3s;
            }

            .emoji_option:hover, .emoji_option.active {
                background: rgba(60, 60, 60, .7);
            }

            .emoji_option span:first-child {
                font-size: 1rem;
                padding-right: .75rem;
            }

            aside.EmojiIndex div.icon-wrapper {
                position: absolute;
                right: 5px;
                height: 100%;
                display: grid;
                place-content: center;
            }

            aside.EmojiIndex div.icon-wrapper svg {
                width: 20px;
                height: 20px;
                fill: rgb(194, 196, 193);
                transition: fill .3s;
                cursor: pointer;
            }

            aside.EmojiIndex div.icon-wrapper svg:hover, aside.EmojiIndex div.icon-wrapper.active svg {
                fill: white;
            }

            aside.EmojiIndex div.emoji-list {
                position: absolute;
                bottom: 110%;
                width: 100%;
                height: 150px;
                background: rgba(20, 20, 20, .9);
                display: none;
                border-radius: 3px;
            }

            aside.EmojiIndex div.emoji-list div.list {
                height: calc(100% - 25px);
                width: 100%;
                overflow-y: auto;
                padding: .2rem 1rem;
                box-sizing: border-box;
            }

            aside.EmojiIndex div.emoji-list div.emoji-active {
                position: absolute;
                bottom: 0;
                padding: .2rem 1rem;
                width: 100%;
                box-sizing: border-box;
                max-height: 25px;
                border-top: 1px solid rgba(80, 80, 80, .5);
            }

            aside.EmojiIndex div.emoji-list div.emoji-active span.emoji {
                padding-right: .5rem;
            }

            aside.EmojiIndex div.emoji-list div.emoji-active span.emoji-key {
                font-weight: bold;
            }

            aside.EmojiIndex div.emoji-list div.list div.emoji-list-item {
                display: inline-grid;
                place-content: center;
                border-radius: 2px;
                width: 24px;
                height: 24px;
                transition: background-color .2s;
                cursor: pointer;
            }

            aside.EmojiIndex div.emoji-list div.list div.emoji-list-item:hover {
                background-color: rgba(60, 60, 60, .7);
            }
        `

        const style = $('<style>').text(newCss)
        $('head').append(style)

        const open = ChatWindow.open
        ChatWindow.open = function(room, avoidSwitch) {
            EmojiIndex.removeOptionsWindow()
            open(room, avoidSwitch)
            $('.chat_input').each(function() {
                if (!$(this).data('listenersAdded')) {
                    $(this).append(EmojiIndex.createEmojiListGui($(this).find('input.message')))
                    $(this).find('input.message')
                        .on('keydown', EmojiIndex.handleKeyDown.bind(EmojiIndex))
                        .on('input', EmojiIndex.handleInput.bind(EmojiIndex))
                        .data('listenersAdded', true)
                }
            })
        }
    }
}


$(document).ready(() => {
    EmojiIndex.init()
    MediaEmbedder.init()
})