TW Chat Media Embedder

Enables embedding of certain images in the chat.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

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