text:
const { ipcRenderer } = require('electron')

// FIXME: This should not be sync IPC
const translate = (id, params) =>
    ipcRenderer.sendSync('I18N_TRANSLATE', id, params)

// FIXME: This should not be sync IPC
const settingsGet = (key) => ipcRenderer.sendSync('SETTINGS_GET', key)

const settingsOnDidChange = (key, cb) => {
    ipcRenderer.on(`SETTINGS_NOTIFY_${key}`, (e, newValue, oldValue) =>
        cb({ newValue, oldValue })
    )
    ipcRenderer.send('SETTINGS_SUBSCRIBE', key)
}

const isWindows = () => {
    return process.platform === 'win32'
}

window.addEventListener('load', () => {
    createContextMenu()
    createPlayerColorRules()

    const { hostname } = window.location
    if (hostname === 'music.youtube.com') {
        createTopMiddleContent()
        createTopRightContent()
        createBottomPlayerBarContent()
        playerBarScrollToChangeVolume()
        enableAVSwitcher()

    } else createOffTheRoadContent()

    // injectCast()
    loadAudioOutputList()
})

function createContextMenu() {
    try {
        const materialIcons = document.createElement('link')
        materialIcons.setAttribute(
            'href',
            'https://fonts.googleapis.com/icon?family=Material+Icons'
        )
        materialIcons.setAttribute('rel', 'stylesheet')

        document.body.prepend(materialIcons)
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createContextMenu',
        })
    }

    try {
        const css = document.createElement('style')
        css.appendChild(
            document.createTextNode(
                `
                #ytmd-menu {
                    visibility: hidden;
                    opacity: 0;
                    position: fixed;
                    background: #232323;
                    font-family: sans-serif;

                    -webkit-transition: opacity .2s ease-in-out;
                    transition: opacity .2s ease-in-out;

                    padding: 0 !important;

                    border: 1px solid rgba(255, 255, 255, .08) !important;
                    border-radius: 2px !important;

                    z-index: 999999 !important;

                    width: 144px;
                }

                #ytmd-menu a {
                    color: #AAA;
                    display: inline-block;
                    cursor: pointer;

                    padding: 10px 12px 6px 12px;
                }

                #ytmd-menu a:hover {
                    background-color: rgba(255, 255, 255, 0.1);
                }

                .divider {
                    border: 1px solid rgba(255, 255, 255, 0.1) !important;
                    height: 21px;
                    display: inline-block;
                }

                .hide {
                    visibility: hidden;
                    width: 0 !important;
                    height: 0 !important;
                    margin: 0 !important;
                    padding: 0 !important;
                }

                .pointer {
                    cursor: pointer;
                }

                .shine:hover {
                    color: #FFF !important;
                }

                .ytmd-icons {
                    margin: 0 18px 0 2px !important;
                    color: rgba(255, 255, 255, 0.5) !important;
                }

                .ytmd-button-rounded {
                    margin: 0 0 0 10px;

                    width: 40px;
                    height: 40px;

                    padding: 6px;

                    border: 0;

                    color: #999;

                    background: rgba(255, 255, 255, 0);
                    border-radius: 50%;
                }

                .ytmd-button-rounded:hover {
                    background: rgba(255, 255, 255, .1);
                }

                .ytmd-icons-middle {
                    margin: 0 10px 0 18px !important;
                }

                .center-content {
                    padding-top: 12px;
                }

                .btn-disabled {
                    color: #000 !important;
                }

                .text-red {
                    color: red !important;
                }

                .ytmd-modal {
                    display: none; /* Hidden by default */
                    position: fixed; /* Stay in place */
                    z-index: 99; /* Sit on top */
                    left: 0;
                    top: 0;
                    width: 100%; /* Full width */
                    height: 100%; /* Full height */
                    background-color: rgb(0,0,0); /* Fallback color */
                    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
                    overflow: hidden
                  }

                  /* Modal Content */
                  .ytmd-modal-content {
                    background: #232323;
                    font-family: Arial, Helvetica, sans-serif;
                    padding: 20px;
                    border: 1px solid #888;
                    width: 80%;
                    max-width: 300px;
                    transition: 0.3s;
                    color: white;
                    opacity: 95%;
                    font-size: 15px;
                  }

                  .ytmd-modal-content-title {
                    color: white;
                    opacity: 100% !important;
                    margin: 0;
                    width: 100%;
                    font-size: 20px;
                    padding-bottom: 5px;
                  }
                  .ytmd-modal-close {
                    color: #aaaaaa;
                    float: right;
                    font-size: 28px;
                    font-weight: bold;
                  }

                  .ytmd-modal-close:hover,
                  .ytmd-modal-close:focus {
                    color: #000;
                    text-decoration: none;
                    cursor: pointer;
                  }
            `
            )
        )
        document.head.appendChild(css)
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createContextMenu insertCSS',
        })
    }

    let quickShortcuts = ''
    quickShortcuts += `<a id="ytmd-menu-lyrics"><i class="material-icons">music_note</i></a>`
    quickShortcuts += `<a id="ytmd-menu-miniplayer"><i class="material-icons">picture_in_picture_alt</i></a>`
    quickShortcuts += `<a id="ytmd-menu-bug-report"><i class="material-icons text-red">bug_report</i></a>`

    try {
        const menuDiv = document.createElement('div')
        menuDiv.setAttribute('id', 'ytmd-menu')
        menuDiv.innerHTML = quickShortcuts
        document.body.prepend(menuDiv)
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createContextMenu prepend',
        })
    }

    // LISTENERS FOR MENU OPTIONS
    try {
        const menuElement = document.getElementById('ytmd-menu').style

        const buttonOpenCompanion = document.getElementById(
            'ytmd-menu-companion-server'
        )
        const buttonOpenMiniplayer = document.getElementById(
            'ytmd-menu-miniplayer'
        )
        const buttonOpenLyrics = document.getElementById('ytmd-menu-lyrics')
        const buttonOpenBugReport = document.getElementById(
            'ytmd-menu-bug-report'
        )
        const buttonPageOpenMiniplayer = document.getElementsByClassName(
            'player-minimize-button ytmusic-player'
        )[0]

        document.addEventListener(
            'contextmenu',
            (e) => {
                const posX = e.clientX
                const posY = e.clientY
                showMenu(posX, posY)
                e.preventDefault()
            },
            false
        )
        document.addEventListener(
            'click',
            (_) => {
                menuElement.opacity = '0'
                setTimeout(() => {
                    menuElement.visibility = 'hidden'
                }, 501)
            },
            false
        )

        if (buttonOpenCompanion)
            buttonOpenCompanion.addEventListener('click', () => {
                ipcRenderer.send('window', { command: 'show-companion' })
            })

        if (buttonOpenLyrics)
            buttonOpenLyrics.addEventListener('click', () => {
                ipcRenderer.send('window', { command: 'show-lyrics' })
            })

        if (buttonOpenMiniplayer)
            buttonOpenMiniplayer.addEventListener('click', () => {
                ipcRenderer.send('window', { command: 'show-miniplayer' })
            })

        if (buttonPageOpenMiniplayer)
            buttonPageOpenMiniplayer.addEventListener('click', (_) => {
                /* Temporary fix */
                document
                    .getElementsByClassName(
                        'player-maximize-button ytmusic-player'
                    )[0]
                    .click()
                ipcRenderer.send('window', { command: 'show-miniplayer' })
            })

        if (buttonOpenBugReport)
            buttonOpenBugReport.addEventListener('click', () => {
                ipcRenderer.send('bug-report')
            })

        // TODO: This shouldn't be here
        function showMenu(x, y) {
            menuElement.top = y + 'px'
            menuElement.left = x + 'px'
            menuElement.visibility = 'visible'
            menuElement.opacity = '1'
        }
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createContextMenu listeners',
        })
    }
}

function createTopMiddleContent() {
    try {
        const center_content = document.getElementsByTagName(
            'ytmusic-pivot-bar-renderer'
        )[0]

        // HISTORY BACK
        const back_element = document.createElement('i')
        back_element.id = 'ytmd_history_back'
        back_element.classList.add(
            'material-icons',
            'pointer',
            'shine',
            'ytmd-icons',
            'center-content'
        )
        back_element.innerText = 'keyboard_backspace'

        back_element.addEventListener('click', function () {
            history.go(-1)
        })

        // HISTORY FORWARD
        const forward_element = document.createElement('i')
        forward_element.id = 'ytmd_history_forward'
        forward_element.classList.add(
            'material-icons',
            'pointer',
            'shine',
            'ytmd-icons',
            'center-content'
        )
        forward_element.style.cssText = 'transform: rotate(180deg);'
        forward_element.innerText = 'keyboard_backspace'

        forward_element.addEventListener('click', function () {
            history.forward()
        })

        center_content.prepend(forward_element)
        center_content.prepend(back_element)
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createTopMiddleContent',
        })
    }
}

function createTopRightContent() {
    const settingsRemoteServer = settingsGet('settings-companion-server')

    // ADD BUTTONS TO RIGHT CONTENT (side to the photo)
    try {
        const right_content = document.getElementById('right-content')

        //SHUTDOWN
        if (!isWindows()) {
            const elementShutdown = document.createElement('i')
            elementShutdown.id = 'ytmd_shutdown'
            elementShutdown.title = translate('LABEL_SHUTDOWN')
            elementShutdown.classList.add(
                'material-icons',
                'pointer',
                'shine',
                'ytmd-icons'
            )
            elementShutdown.innerText = 'power_settings_new'

            elementShutdown.addEventListener('click', function () {
                ipcRenderer.send('closed')
            })

            right_content.prepend(elementShutdown)
        }

        // SETTINGS
        const elementSettings = document.createElement('i')
        elementSettings.id = 'ytmd_settings'
        elementSettings.title = translate('LABEL_SETTINGS')
        elementSettings.classList.add(
            'material-icons',
            'pointer',
            'shine',
            'ytmd-icons'
        )
        elementSettings.innerText = 'settings'

        elementSettings.addEventListener('click', () => {
            ipcRenderer.send('window', { command: 'show-settings' })
        })

        right_content.prepend(elementSettings)

        // REMOTE SERVER
        const elementRemoteServer = document.createElement('i')
        elementRemoteServer.id = 'ytmd_remote_server'
        elementRemoteServer.title = translate(
            'LABEL_SETTINGS_TAB_GENERAL_COMPANION_SERVER'
        )
        elementRemoteServer.classList.add(
            'material-icons',
            'pointer',
            'shine',
            'ytmd-icons',
            'hide'
        )
        elementRemoteServer.innerText = 'devices_other'

        elementRemoteServer.addEventListener('click', () => {
            ipcRenderer.send('window', { command: 'show-companion' })
        })

        right_content.prepend(elementRemoteServer)

        if (settingsRemoteServer)
            document
                .getElementById('ytmd_remote_server')
                .classList.remove('hide')

        settingsOnDidChange('settings-companion-server', (data) => {
            if (data.newValue)
                document
                    .getElementById('ytmd_remote_server')
                    .classList.remove('hide')
            else
                document
                    .getElementById('ytmd_remote_server')
                    .classList.add('hide')
        })

        // UPDATE
        const elementUpdate = document.createElement('i')
        elementUpdate.id = 'ytmd_update'
        elementUpdate.classList.add(
            'material-icons',
            'green-text',
            'pointer',
            'shine',
            'ytmd-icons',
            'hide'
        )
        elementUpdate.style.color = '#4CAF50'
        elementUpdate.innerText = 'arrow_downward'

        elementUpdate.addEventListener('click', () => {
            ipcRenderer.send('btn-update-clicked', true)
        })

        right_content.prepend(elementUpdate)

        ipcRenderer.on('downloaded-new-update', (e, data) => {
            document.getElementById('ytmd_update').classList.remove('hide')
        })
    } catch (err) {
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createTopRightContent',
        })
    }
}

function createPlayerColorRules() {
    try {
        const css = document.createElement('style')
        css.appendChild(
            document.createTextNode(
                `
                :root{
                    --ytm-album-color-muted: #000000;
                    --ytm-album-color-vibrant: #232323;
                }

                ytmusic-app-layout{
                    --ytmusic-nav-bar: #000000; /* default for collapsed player */
                }

                ytmusic-app-layout.content-scrolled{
                    --ytmusic-nav-bar: #232323; /* default for collapsed player */
                }

                body[accent-enabled] ytmusic-app-layout[player-page-open_]{
                    --ytmusic-nav-bar: var(--ytm-album-color-muted);
                    --ytmusic-brand-background-solid: var(--ytm-album-color-vibrant);
                }

                body[accent-enabled] #progress-bar {
                    --paper-slider-active-color: white;
                    --paper-slider-knob-color: transparent;
                }

                body[accent-enabled] yt-page-navigation-progress{
                    --yt-page-navigation-container-color: #232323;
                    --yt-page-navigation-progress-color: white;
                }

                body[accent-enabled][player-open] yt-page-navigation-progress{
                    --yt-page-navigation-container-color: var(--ytm-album-color-muted);
                    --yt-page-navigation-progress-color: white;
                }

                body[accent-enabled] #player-page{
                    background: var(--ytm-album-color-muted);
                }

                body[accent-enabled] #progress-bar.ytmusic-player-bar[focused],
                body[accent-enabled] ytmusic-player-bar:hover #progress-bar.ytmusic-player-bar{
                    --paper-slider-knob-color: white;
                }
                `
            )
        )
        document.head.appendChild(css)
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createPlayerColorRules insertCSS',
        })
    }
}

function createBottomPlayerBarContent() {
    const shortcutButtons = settingsGet('settings-shortcut-buttons')

    try {
        const playerBarRightControls = document.querySelector(
            '.right-controls-buttons.ytmusic-player-bar'
        )
        const playerBarMiddleControls = document.querySelector(
            '.middle-controls-buttons.ytmusic-player-bar'
        )

        // Middle ////////////////////////////////////////////////////////////////////////////////////
        // Add to Playlist
        const elementAddToPlaylistIcon = document.createElement('i')
        const elementAddToPlaylistButton = document.createElement('button')

        elementAddToPlaylistIcon.id = 'ytmd_add_to_playlist'
        elementAddToPlaylistIcon.title = translate('ADD_TO_PLAYLIST')
        elementAddToPlaylistIcon.classList.add('material-icons')
        elementAddToPlaylistIcon.innerText = 'playlist_add'

        elementAddToPlaylistButton.id = 'btn_ytmd_add_to_playlist'
        elementAddToPlaylistButton.classList.add('ytmd-button-rounded', 'hide')
        elementAddToPlaylistButton.append(elementAddToPlaylistIcon)

        elementAddToPlaylistButton.addEventListener('click', () => {
            const popup = document.querySelector('.ytmusic-menu-popup-renderer')
            const addPlaylist = Array.from(popup.children)
                .filter(
                    (value) =>
                        value
                            .querySelector('g path:not([fill])')
                            .getAttribute('d') ===
                        'M14 10H2v2h12v-2zm0-4H2v2h12V6zm4 8v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2 16h8v-2H2v2z'
                )[0]
                .querySelector('a')
            addPlaylist.click()
        })

        playerBarMiddleControls.insertBefore(
            elementAddToPlaylistButton,
            playerBarMiddleControls.children.item(1)
        )

        if (shortcutButtons['add-to-playlist'])
            document
                .querySelector('#btn_ytmd_add_to_playlist')
                .classList.remove('hide')

        settingsOnDidChange(
            'settings-shortcut-buttons.add-to-playlist',
            (data) => {
                if (data.newValue)
                    document
                        .querySelector('#btn_ytmd_add_to_playlist')
                        .classList.remove('hide')
                else
                    document
                        .querySelector('#btn_ytmd_add_to_playlist')
                        .classList.add('hide')
            }
        )

        // Add to Library
        const elementAddToLibraryIcon = document.createElement('i')
        const elementAddToLibraryButton = document.createElement('button')

        elementAddToLibraryIcon.id = 'ytmd_add_to_library'
        elementAddToLibraryIcon.title = translate('ADD_TO_LIBRARY')
        elementAddToLibraryIcon.classList.add('material-icons')
        elementAddToLibraryIcon.innerText = 'library_add'
        elementAddToLibraryButton.id = 'btn_ytmd_add_to_library'
        elementAddToLibraryButton.classList.add('ytmd-button-rounded', 'hide')
        elementAddToLibraryButton.append(elementAddToLibraryIcon)

        elementAddToLibraryButton.addEventListener('click', () => {
            ipcRenderer.send('media-command', { command: 'media-add-library' })
        })

        playerBarMiddleControls.insertBefore(
            elementAddToLibraryButton,
            playerBarMiddleControls.children.item(1)
        )

        let showAddToLibrary = false
        if (shortcutButtons['add-to-library']) {
            document
                .querySelector('#btn_ytmd_add_to_library')
                .classList.remove('hide')
            showAddToLibrary = true
        }

        settingsOnDidChange(
            'settings-shortcut-buttons.add-to-library',
            (data) => {
                if (data.newValue) {
                    showAddToLibrary = true
                    document
                        .querySelector('#btn_ytmd_add_to_library')
                        .classList.remove('hide')
                } else {
                    showAddToLibrary = false
                    document
                        .querySelector('#btn_ytmd_add_to_library')
                        .classList.add('hide')
                }
            }
        )

        setInterval(() => {
            const popup = document.querySelector('.ytmusic-menu-popup-renderer')
            let addLibrary
            if (popup) {
                addLibrary = Array.from(popup.children).filter(
                    (value) =>
                        value
                            .querySelector('g path:not([fill])')
                            .getAttribute('d') ===
                            'M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7.53 12L9 10.5l1.4-1.41 2.07 2.08L17.6 6 19 7.41 12.47 14zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6z' ||
                        value
                            .querySelector('g path:not([fill])')
                            .getAttribute('d') ===
                            'M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z'
                )[0]
            }

            if (addLibrary !== undefined && showAddToLibrary) {
                const _d = addLibrary
                    .querySelector('g path:not([fill])')
                    .getAttribute('d')

                if (
                    _d ===
                    'M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7.53 12L9 10.5l1.4-1.41 2.07 2.08L17.6 6 19 7.41 12.47 14zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6z'
                ) {
                    document.querySelector('#ytmd_add_to_library').innerText =
                        'check'
                    document.querySelector(
                        '#ytmd_add_to_library'
                    ).title = translate('REMOVE_FROM_LIBRARY')
                } else {
                    document.querySelector('#ytmd_add_to_library').innerText =
                        'library_add'
                    document.querySelector(
                        '#ytmd_add_to_library'
                    ).title = translate('ADD_TO_LIBRARY')
                }
                document
                    .querySelector('#btn_ytmd_add_to_library')
                    .classList.remove('hide')
            } else
                document
                    .querySelector('#btn_ytmd_add_to_library')
                    .classList.add('hide')
        }, 800)

        // Right ////////////////////////////////////////////////////////////////////////////////////
        // Lyrics
        const elementLyrics = document.createElement('i')
        elementLyrics.id = 'ytmd_lyrics'
        elementLyrics.title = translate('LYRICS')
        elementLyrics.classList.add(
            'material-icons',
            'pointer',
            'ytmd-icons',
            'hide'
        )
        elementLyrics.innerText = 'music_note'

        elementLyrics.addEventListener('click', () => {
            ipcRenderer.send('window', { command: 'show-lyrics' })
        })
        playerBarRightControls.append(elementLyrics)

        if (shortcutButtons.lyrics)
            document.querySelector('#ytmd_lyrics').classList.remove('hide')

        settingsOnDidChange('settings-shortcut-buttons.lyrics', (data) => {
            if (data.newValue) {
                document.querySelector('#ytmd_lyrics').classList.remove('hide')
                document
                    .querySelector('#ytmd_lyrics')
                    .classList.add('ytmd-icons')
            } else {
                document.querySelector('#ytmd_lyrics').classList.add('hide')
                document
                    .querySelector('#ytmd_lyrics')
                    .classList.remove('ytmd-icons')
            }
        })

        // Miniplayer
        const elementMiniplayer = document.createElement('i')
        elementMiniplayer.id = 'ytmd_miniplayer'
        elementMiniplayer.title = translate('MINIPLAYER')
        elementMiniplayer.classList.add(
            'material-icons',
            'pointer',
            'ytmd-icons',
            'hide'
        )
        elementMiniplayer.innerText = 'picture_in_picture_alt'

        elementMiniplayer.addEventListener('click', () => {
            ipcRenderer.send('window', { command: 'show-miniplayer' })
        })
        playerBarRightControls.append(elementMiniplayer)

        // Sleep timer
        const elementSleepTimer = document.createElement('i')
        elementSleepTimer.id = 'ytmd_sleeptimer'
        elementSleepTimer.title = translate('SLEEPTIMER')
        elementSleepTimer.classList.add(
            'material-icons',
            'pointer',
            'ytmd-icons'
        )
        elementSleepTimer.innerText = 'timer'

        const elementSleepTimerModal = document.createElement('div')
        const elementSleepTimerModalContent = document.createElement('div')
        const elementSleepTimerSet = document.createElement('button')
        const elementSleepTimerClear = document.createElement('button')
        const elementSleepTimerCloseModal = document.createElement('span')

        elementSleepTimerModal.classList.add('ytmd-modal')
        elementSleepTimerModal.id = 'ytmd_sleeptimer_modal'
        elementSleepTimerModal.append(elementSleepTimerModalContent)

        elementSleepTimerModalContent.innerHTML = `
                <p class="ytmd-modal-content-title">${translate(
                    'SLEEPTIMER'
                )}</p>
                <div> ${translate('SLEEP_BY_TIME')}
                    <br/>
                    <input name='sleep_timer' type='radio' id='sleep-timer-30min' value='30'/>
                    <label for='sleep-timer-30min'> 30${translate(
                        'SLEEPTIMER_MINUTES'
                    )}</label>
                    <input name='sleep_timer' type='radio' id='sleep-timer-1h' value='60'/>
                    <label for='sleep-timer-1h'>60${translate(
                        'SLEEPTIMER_MINUTES'
                    )}</label>
                    <input name='sleep_timer' type='radio' id='sleep-timer-2h' value='120'/>
                    <label for='sleep-timer-2h'>120${translate(
                        'SLEEPTIMER_MINUTES'
                    )}</label>
                    <br/><input name='sleep_timer' type='radio' id='sleep-timer-customized'/>
                    <input id='sleep-timer-minutes' for='sleep-timer-customized' style='width: 40px'/>
                    <label for='sleep-timer-minutes'>${translate(
                        'SLEEPTIMER_MINUTES'
                    )}</>
                </div>
                <div style='margin-top: 15px;'> ${translate('SLEEP_BY_COUNTER')}
                    <br/>
                    <input name='sleep_timer' type='radio' id='sleep-timer-5c' value='5c'/>
                    <label for='sleep-timer-5c'>5</label>
                    <input name='sleep_timer' type='radio' id='sleep-timer-10c' value='10c'/>
                    <label for='sleep-timer-10c'>10</label>
                    <input name='sleep_timer' type='radio' id='sleep-timer-30c' value='30c'/>
                    <label for='sleep-timer-30c'>30</label>
                    <br/><input name='sleep_timer' type='radio' id='sleep-timer-customized-c' />
                    <input id='sleep-timer-songs' for='sleep-timer-customized-c' style='width: 40px'/>
                    <label for='sleep-timer-songs'>${translate(
                        'SLEEPTIMER_SONGS'
                    )}</>
                </div>
        `
        elementSleepTimerModalContent.append(elementSleepTimerSet)
        elementSleepTimerModalContent.append(elementSleepTimerClear)

        elementSleepTimerModalContent.prepend(elementSleepTimerCloseModal)

        elementSleepTimer.addEventListener('click', (e) => {
            ipcRenderer.send('retrieve-sleep-timer')
            elementSleepTimerModal.style.display = 'block'
            elementSleepTimerModalContent.style.marginLeft = e.x + 'px' // pop out at mouse position
            elementSleepTimerModalContent.style.marginTop = e.y + 'px'
            elementSleepTimerModalContent.style.transform =
                'translate(-50%, -50%) scale(0)' // animation
            setTimeout(() => {
                elementSleepTimerModalContent.style.transform =
                    'translate(-100%, -100%) scale(1)'
            }, 10) // animation
        })

        elementSleepTimerCloseModal.innerHTML = '×'
        elementSleepTimerCloseModal.classList.add('ytmd-modal-close')
        elementSleepTimerCloseModal.addEventListener('click', () => {
            elementSleepTimerModal.style.display = 'none'
        })
        elementSleepTimerModal.addEventListener('click', (e) => {
            if (e.target === elementSleepTimerModal)
                elementSleepTimerModal.style.display = 'none' // close modal
        })

        elementSleepTimerModalContent.classList.add('ytmd-modal-content')

        elementSleepTimerSet.innerText = translate('SLEEPTIMER_SET')

        elementSleepTimerSet.addEventListener('click', () => {
            var value = document.querySelector(
                'input[name="sleep_timer"]:checked'
            ).value
            if (value) {
                ipcRenderer.send('set-sleep-timer', { value: value })
                elementSleepTimerModal.style.display = 'none'
            }
        })

        elementSleepTimerClear.innerText = translate('SLEEPTIMER_CLEAR')
        elementSleepTimerClear.style.marginLeft = '15px'
        elementSleepTimerClear.addEventListener('click', () => {
            ipcRenderer.send('set-sleep-timer', { value: 0 })
            elementSleepTimerModal.style.display = 'none'
        })
        playerBarRightControls.append(elementSleepTimer)
        document.body.append(elementSleepTimerModal)

        document.getElementById(
            'sleep-timer-minutes'
        ).onkeydown = document.getElementById(
            // use the same function when change/keydown
            'sleep-timer-minutes'
        ).onchange = (e) => {
            var radio = document.getElementById('sleep-timer-customized')
            radio.checked = true
            radio.value = parseInt(e.target.value)
        }

        document.getElementById(
            'sleep-timer-songs'
        ).onkeydown = document.getElementById('sleep-timer-songs').onchange = (
            // use the same function when change/keydown
            e
        ) => {
            var radio = document.getElementById('sleep-timer-customized-c')
            radio.checked = true
            radio.value = parseInt(e.target.value) + 'c'
        }

        ipcRenderer.on('sleep-timer-info', (_, mode, counter) => {
            if (mode == 'time') {
                elementSleepTimerClear.disabled = false
                elementSleepTimerSet.innerText = translate('SLEEPTIMER_RESET')
                var radio = document.getElementById('sleep-timer-customized')
                radio.checked = true
                radio.value = counter
                document.getElementById('sleep-timer-minutes').value = counter
            } else if (mode == 'counter') {
                elementSleepTimerClear.disabled = false
                elementSleepTimerSet.innerText = translate('SLEEPTIMER_RESET')
                var radio = document.getElementById('sleep-timer-customized-c')
                radio.checked = true
                radio.value = counter
                document.getElementById('sleep-timer-songs').value = counter
            } else {
                elementSleepTimerClear.disabled = true
                elementSleepTimerSet.innerText = translate('SLEEPTIMER_SET')
            }
        })

        if (shortcutButtons.miniplayer)
            document.querySelector('#ytmd_miniplayer').classList.remove('hide')

        settingsOnDidChange('settings-shortcut-buttons.miniplayer', (data) => {
            if (data.newValue) {
                document
                    .querySelector('#ytmd_miniplayer')
                    .classList.remove('hide')
                document
                    .querySelector('#ytmd_miniplayer')
                    .classList.add('ytmd-icons')
            } else {
                document.querySelector('#ytmd_miniplayer').classList.add('hide')
                document
                    .querySelector('#ytmd_miniplayer')
                    .classList.remove('ytmd-icons')
            }
        })

        // Volume slider
        document.querySelector('#volume-slider').setAttribute('step', 0)
        document.querySelector('#expand-volume-slider').setAttribute('step', 0)
        document
            .querySelector('#volume-slider')
            .addEventListener('value-change', (e) => {
                ipcRenderer.send('change-volume', {
                    volume: e.target.getAttribute('value'),
                })
            })
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createBottomPlayerBarContent',
        })
    }
}

function enableAVSwitcher() {
    const player = document.getElementById('player')
    const player_page = document.getElementById('player-page')

    player.setAttribute('has-av-switcher', '')
    player_page.setAttribute('has-av-switcher', '')
}

function playerBarScrollToChangeVolume() {
    try {
        const playerBar = document.getElementsByTagName('ytmusic-player-bar')[0]

        const volumeSlider = document.getElementById('volume-slider')
        let isVolumeSliderHovered = false
        volumeSlider.addEventListener(
            'mouseover',
            () => (isVolumeSliderHovered = true)
        )
        volumeSlider.addEventListener(
            'mouseout',
            () => (isVolumeSliderHovered = false)
        )

        const expandVolumeSlider = document.getElementById(
            'expand-volume-slider'
        )
        let isExpandVolumeSliderHovered = false
        expandVolumeSlider.addEventListener(
            'mouseover',
            () => (isExpandVolumeSliderHovered = true)
        )
        expandVolumeSlider.addEventListener(
            'mouseout',
            () => (isExpandVolumeSliderHovered = false)
        )

        const isSliderHovered = () =>
            isExpandVolumeSliderHovered || isVolumeSliderHovered

        playerBar.addEventListener('wheel', (ev) => {
            ev.preventDefault()
            if (!settingsGet('settings-decibel-volume') && isSliderHovered()) {
                return
            }

            if (ev.deltaY < 0)
                ipcRenderer.send('media-command', {
                    command: 'media-volume-up',
                })
            else
                ipcRenderer.send('media-command', {
                    command: 'media-volume-down',
                })
        })
    } catch (err) {
        console.error(err)
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on playerBarScrollToChangeVolume',
        })
    }
}

function createOffTheRoadContent() {
    try {
        const { body } = document

        const elementBack = document.createElement('i')
        elementBack.id = 'ytmd_lyrics'
        elementBack.classList.add('material-icons')
        elementBack.style.cursor = 'pointer'
        elementBack.style.fontSize = '42px'
        elementBack.style.zIndex = '9999999'
        elementBack.style.position = 'fixed'
        elementBack.style.cssFloat = 'left'
        elementBack.style.boxShadow = '0 0 2px #111'
        elementBack.style.background = '#1D1D1D'
        elementBack.style.color = '#FFF'
        elementBack.innerText = 'arrow_back'

        elementBack.addEventListener('click', () => {
            ipcRenderer.send('reset-url')
        })

        body.prepend(elementBack)
    } catch (err) {
        ipcRenderer.send('log', {
            type: 'warn',
            data: 'error on createOffTheRoadContent',
        })
    }
}

function injectCast() {
    // content
    //     .executeJavaScript(
    //         `
    //     // Todo
    //     `
    //     )
    //     .then((data) => {
    //         console.log(data)
    //     })
    //     .catch((err) => {
    //         console.log(err)
    //     })
}

function loadAudioOutputList() {
    navigator.mediaDevices.enumerateDevices().then((devices) => {
        audioDevices = devices.filter((device) => device.kind === 'audiooutput')

        ipcRenderer.send(
            'set-audio-output-list',
            audioDevices.length ? JSON.stringify(audioDevices) : '[]'
        )
    })
}

navigator.mediaDevices.ondevicechange = loadAudioOutputList


text:
const fs = require('fs')
const path = require('path')

function _createDir(path) {
    fs.mkdirSync(path, { recursive: true })
}

function _getDir(path) {
    return fs.readdirSync(path)
}

function _writeFile(path, data) {
    fs.writeFileSync(path, data, { flag: 'w+' })
}

function _readFile(path) {
    return fs.readFileSync(path)
}

function _checkIfExists(path) {
    return fs.existsSync(path)
}

function _getAppDocumentsPath(app) {
    return path.join(app.getPath('documents'), app.name)
}

function _getAppDataPath(app) {
    return path.join(app.getPath('appData'), app.name)
}

module.exports = {
    createDir: _createDir,
    getDir: _getDir,
    writeFile: _writeFile,
    readFile: _readFile,
    checkIfExists: _checkIfExists,
    getAppDocumentsPath: _getAppDocumentsPath,
    getAppDataPath: _getAppDataPath,
}

repo_name: ytmdesktop/ytmdesktop

repo_language: JavaScript

mime_type: text/plain


text:
const { isLinux, isMac } = require('./systemInfo')

const PADDING = 1
const PADDING_MAXIMIZED = 16
const PADDING_LINUX = 0

const TITLE_BAR_HEIGHT = 28

const TITLE_BAR_HEIGHT_MAC = 21

/**
 * @param {settingsProvider} store
 * @param window
 */
function calculateYoutubeViewSize(store, window) {
    const windowSize = window.getSize()
    const isMaximized = window.isMaximized()
    const isNiceTitleBarDisabled = store.get('titlebar-type', 'nice') !== 'nice'
    const titlebarType = store.get('titlebar-type')

    if (window.isFullScreen())
        return {
            x: 0,
            y: 0,
            width: windowSize[0],
            height: windowSize[1],
        }

    if (isMac()) {
        // IS MAC
        const x = PADDING
        const y = isNiceTitleBarDisabled
            ? PADDING + TITLE_BAR_HEIGHT_MAC
            : PADDING + TITLE_BAR_HEIGHT

        return {
            x,
            y,
            width: windowSize[0] - x - PADDING,
            height: windowSize[1] - y - PADDING,
        }
    } else if (isLinux()) {
        // IS LINUX
        const x = PADDING_LINUX
        const y = PADDING_LINUX

        return {
            x,
            y,
            width: windowSize[0] - x,
            height: windowSize[1] - y,
        }
    } else {
        // IS WINDOWS
        const x = PADDING
        const y = isNiceTitleBarDisabled ? PADDING : PADDING + TITLE_BAR_HEIGHT

        return {
            x,
            y,
            width: isMaximized
                ? windowSize[0] - x - PADDING_MAXIMIZED
                : windowSize[0] - x - PADDING,
            height:
                titlebarType === 'none'
                    ? windowSize[1]
                    : windowSize[1] -
                      y -
                      (isMaximized ? PADDING_MAXIMIZED : PADDING) -
                      (isNiceTitleBarDisabled ? (isMaximized ? 24 : 40) : 0),
        }
    }
}

module.exports = {
    calcYTViewSize: calculateYoutubeViewSize,
}


text:
{
    "ADD_TO_LIBRARY": "Adicionar à coleção",
    "ADD_TO_PLAYLIST": "Adicionar à lista de reprodução,
    "ALWAYS_ASK_WHEN_READ_LINK": "Perguntar sempre ao ler a ligação",
    "DOUBLE_CLICK_TO_RESET_POSITION": "Duplo clique para repor a posição",
    "LABEL_ALSO": "Também",
    "LABEL_AUDIO": "Áudio",
    "LABEL_CANCEL": "Cancelar",
    "LABEL_CHANGELOG": "Novidades",
    "LABEL_DISABLE": "Desativar",
    "LABEL_EXIT": "Sair",
    "LABEL_GUEST": "Convidado",
    "LABEL_LOADING": "A carregar...",
    "LABEL_LYRICS_NOT_FOUND": "Letra da faixa não encontrada",
    "LABEL_LYRICS": "Letra da faixa",
    "LABEL_MINIPLAYER": "Mini-reprodutor",
    "LABEL_NOTIFICATION_DISLIKED": "Você não gosta desta música",
    "LABEL_NOTIFICATION_LIKED": "Você gosta desta música",
    "LABEL_PLAY_MUSIC": "Reproduzir música",
    "LABEL_SAVE": "Guardar",
    "LABEL_SAVED": "Guardado",
    "LABEL_SETTINGS_DISCORD_SHOW_ARTIST": "Mostrar artista",
    "LABEL_SETTINGS_DISCORD_SHOW_IDLE": "Mostrar faixa se a reprodução estiver em pausa",
    "LABEL_SETTINGS_DISCORD_SHOW_TIME": "Mostrar duração da faixa",
    "LABEL_SETTINGS_DISCORD_SHOW_TITLE": "Mostrar título",
    "LABEL_SETTINGS_DISCORD": "Definições Discord",
    "LABEL_SETTINGS_RELAUNCH": "Aplicar e reiniciar",
    "LABEL_SETTINGS_SHOW_HIDE_WINDOW": "Mostrar/ocultar janela principal",
    "LABEL_SETTINGS_TAB_ABOUT": "Acerca",
    "LABEL_SETTINGS_TAB_APPEARANCE_TASKBAR_PROGRESSBAR": "Mostrar evolução na barra de tarefas",
    "LABEL_SETTINGS_TAB_APPEARANCE_PLAYER_BACKGROUND_COLOR": "Colorir fundo com a cor do álbum",
    "LABEL_SETTINGS_TAB_APPEARENCE": "Aparência",
    "LABEL_SETTINGS_TAB_GENERAL_AUDIO_NO_DEVICES_FOUND": "Nenhum dispositivo encontrado",
    "LABEL_SETTINGS_TAB_GENERAL_COMPANION_SERVER_INFO": "Clique aqui e digitalize o código QR",
    "LABEL_SETTINGS_TAB_GENERAL_COMPANION_SERVER_PROTECT": "Proteger controlo remoto com palavra-passe",
    "LABEL_SETTINGS_TAB_GENERAL_COMPANION_SERVER": "Controlo remoto",
    "LABEL_SETTINGS_TAB_GENERAL_CONTINUE_WHERE_LEFT_OF": "Continuar da última sessão",
    "LABEL_SETTINGS_TAB_GENERAL_CUSTOM_THEME": "Tema personalizado",
    "LABEL_SETTINGS_TAB_GENERAL_DISABLE_HARDWARE_ACCELERATION": "Desativar aceleração por hardware",
    "LABEL_SETTINGS_TAB_GENERAL_DISCORD_RICH_PRESENCE": "Discord Rich Presence",
    "LABEL_SETTINGS_TAB_GENERAL_KEEP_BACKGROUND": "Ao fechar a janela, manter YTMDesktop em segundo plano",
    "LABEL_SETTINGS_TAB_GENERAL_PAGE_ZOOM": "Tamanho da página",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_AUDIO_OUTPUT": "Saída de áudio",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_LANGUAGE": "Idioma",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_LYRICS_PROVIDER": "Serviço para letra das faixas",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_TITLEBAR_TYPE_NICE": "Agradável",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_TITLEBAR_TYPE_NONE": "Desativar barra de título",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_TITLEBAR_TYPE_SYSTEM": "Sistema",
    "LABEL_SETTINGS_TAB_GENERAL_SELECT_TITLEBAR_TYPE": "Barra de título do sistema",
    "LABEL_SETTINGS_TAB_GENERAL_SHINY_TRAY": "Controlos na bandeja (apenas para Mac OS)",
    "LABEL_SETTINGS_TAB_GENERAL_SHOW_NOTIFICATIONS": "Mostrar notificações",
    "LABEL_SETTINGS_TAB_GENERAL_START_MINIMIZED": "Iniciar minimizado",
    "LABEL_SETTINGS_TAB_GENERAL_START_ON_BOOT": "Iniciar com o sistema (Mac/Win)",
    "LABEL_SETTINGS_TAB_GENERAL": "Geral",
    "LABEL_SETTINGS_TAB_INTEGRATION_WINDOWS10_MEDIA_SERVICE_SHOW_INFO": "Mostrar informações da faixa perto da barra de volume (Windows 10)",
    "LABEL_SETTINGS_TAB_INTEGRATION_WINDOWS10_MEDIA_SERVICE": "Ativar serviço multimédia (Windows 10 media service)",
    "LABEL_SETTINGS_TAB_INTEGRATION": "Integração",
    "LABEL_SETTINGS_TAB_INTEGRATIONS": "Integrações",
    "LABEL_SETTINGS_TAB_LAST_FM_SCROBBLER_AUTHORIZE": "Guardar e autorizar",
    "LABEL_SETTINGS_TAB_LAST_FM_SCROBBLER": "Last FM (Scrobbler)",
    "LABEL_SETTINGS_TAB_MACOS": "Mac OS",
    "LABEL_SETTINGS_TAB_MINIPLAYER_ALWAYS_ON_TOP": "Sempre na frente",
    "LABEL_SETTINGS_TAB_MINIPLAYER_ALWAYS_SHOW_CONTROLS": "Mostrar sempre os controlos",
    "LABEL_SETTINGS_TAB_MINIPLAYER_PAINT_CONTROLS": "Colorir controlos conforme a imagem do álbum",
    "LABEL_SETTINGS_TAB_MINIPLAYER_RESIZABLE": "Permitir redimensionamento",
    "LABEL_SETTINGS_TAB_MINIPLAYER_SHOW_TASK": "Mostrar na barra de tarefas",
    "LABEL_SETTINGS_TAB_MINIPLAYER": "Mini-reprodutor",
    "LABEL_SETTINGS_TAB_PLAYBACK": "Reprodução",
    "LABEL_SETTINGS_TAB_RAINMETER_WEB_NOW_PLAYING": "Rainmeter (WebNowPlaying)",
    "LABEL_SETTINGS_TAB_READ_CLIPBOARD": "Ler área de transferência para ligações do youtube",
    "LABEL_SETTINGS_TAB_SHORTCUTS_GLOBAL": "Global",
    "LABEL_SETTINGS_TAB_SHORTCUTS_LOCAL": "Local",
    "LABEL_SETTINGS_TAB_SHORTCUTS_PRESS_ANY_KEYS": "Prima uma tecla",
    "LABEL_SETTINGS_TAB_SHORTCUTS": "Atalhos",
    "LABEL_SETTINGS_TAB_SHORTCUTS_VOLUME_MEDIA_KEYS": "As teclas multimédia apenas controlam o volume desta aplicação",
	  "LABEL_SETTINGS_TAB_PLAYBACK_DECIBEL_VOLUME": "Comutar controlo de volume de '%' para 'dB'",
	  "LABEL_SETTINGS": "Definições",
    "LYRICS": "Letra da faixa",
    "MEDIA_CONTROL_MINIPLAYER": "Abrir/fechar mini-reprodutor",
    "MEDIA_CONTROL_MUTE_UNMUTE": "Sem som/com som",
    "MEDIA_CONTROL_NEXT": "Seguinte",
    "MEDIA_CONTROL_PAUSE": "Pausa",
    "MEDIA_CONTROL_PLAY_PAUSE": "Reproduzir/Pausa",
    "MEDIA_CONTROL_PLAY": "Reproduzir",
    "MEDIA_CONTROL_PREVIOUS": "Anterior",
    "MEDIA_CONTROL_QUEUE_OPEN_CLOSE": "Abrir/fechar fila",
    "MEDIA_CONTROL_REPEAT": "Repetir",
    "MEDIA_CONTROL_SEARCH": "Pesquisar",
    "MEDIA_CONTROL_SHUFFLE": "Baralhar",
    "MEDIA_CONTROL_THUMBS_DOWN": "Não gosto",
    "MEDIA_CONTROL_THUMBS_UP": "Gosto",
    "MEDIA_CONTROL_VOLUME_DOWN": "Reduzir volume",
    "MEDIA_CONTROL_VOLUME_UP": "Aumentar volume",
    "MINIPLAYER": "Mini-reprodutor",
    "PREFIX_OPEN_CLOSE": "Abrir/fechar",
    "REMOVE_FROM_LIBRARY": "Remover da coleção",
    "SHORTCUT_BUTTONS": "Ações na interface",
    "SHOW": "Mostrar",
    "SKIP_TRACK_MARKED_DISLIKED": "Ignorar faixas com a marca 'Não gosto'",
    "SKIP_TRACK_SHORTER_THAN": "Ignorar faixas com duração inferior a",
    "TRAY_ICON": "Ícone na bandeja",
    "WANT_PLAY_THIS_LINK": "Deseja reproduzir esta ligação?"
}


text:
const cp = require('child_process')
const path = require('path')

let npm = 'npm'
if (process.platform === 'win32') npm = 'npm.cmd'

let platform = ''
switch (process.platform) {
    case 'darwin':
        platform = 'mac'
        break
    case 'linux':
        platform = 'lin'
        break
    case 'win32':
        platform = 'win'
        break
}

if (!platform) throw new Error('Unsupported platform')

const { status } = cp.spawnSync(
    npm,
    ['run', `build:${platform}`, '--', '--publish=never'],
    {
        cwd: path.resolve(__dirname, '..'),
        stdio: 'inherit',
    }
)

process.exit(status)


text:
# Awesome Amazon Seller [![Awesome](https://awesome.re/badge.svg)](https://awesome.re)

> A curated list of tools and resources for Amazon sellers.

## Contents

- [Software and Tools](#software-and-tools)
- [Podcasts](#podcasts)
- [Blogs](#blogs)
- [Other](#other)

---

## Software and Tools
- [Flapen](https://flapen.com) - Flapen is a free real-time dashboard to monitor Amazon category changes in 19 country and 215 categories
- [AiHello AutoPilot](https://www.aihello.com/) - Amazon PPC Ads Automation Software.
- [Amzmailer](https://amzmailer.com/) - Feedback software and email autoresponder to send Amazon customers automated emails.
- [aShop](https://ashop.co) - Effortless microsites for Amazon Sellers. Marketing tool that delivers branded store experience to bring more business and maximize profits.
- [ASINspector](https://asinspector.com/) - Sales trend data, unique product ideas, mobile scanner, best-seller rankings.
- [BQool](https://www.bqool.com/) - Scheduled repricing, compete against buy box price, comprehensive dashboard & reports, repricing history log.
- [CashCowPro](https://www.cashcowpro.com/) - Sales data, keyword tracking, feedback collection, inventory monitoring, price split testing.
- [Eva](https://eva.guru/) - Connects the most important aspects of your Amazon business into a single intuitive dashboard - price management, replenishments, reimbursements, analytics.
- [FeedbackExpress](https://www.feedbackexpress.com/) - Makes it easy to improve your Amazon seller rating through effective, automated feedback requests.
- [Feedbackwhiz](https://www.feedbackwhiz.com/) - Software for merchants to boost their Amazon business, repair feedback, improve product reviews, and automate high-volume emails.
- [Feedbackz](https://www.feedbackz.com/) - Built for marketing savvy Amazon sellers seeking more beautiful and more powerfully targeted automatic follow-up email funnels.
- [Forecastly](https://www.forecast.ly/) - Predict future product demand, make fast and accurate buying decisions.
- [ForecastRx](https://www.forecastrx.com/) - Inventory forecasting solution. Projects future demand so you can purchase the right product, in the right quantity, at the right time.
- [Helium10](https://www.helium10.com/) - Software suite contains over a dozen tools that help find high ranking keywords, identify trends, monitor competitors, and fully optimize product listings to increase sales.
- [HelloProfit](https://helloprofit.com/) - Lets you view all your sales & profit data from different merchant accounts from one dashboard.
- [Jungle Scout](https://www.junglescout.com/) - Track and compare key product metrics, database allows you to filter products across multiple categories by demand, price, estimated sales, rating, seasonality, dimensions and more, find out which products sell and which niches have high opportunity.
- [Keyword Tool](https://keywordtool.io/amazon) - Finds great keywords using Amazon autocomplete.
- [MerchantWords](https://www.merchantwords.com/) - Finds highly specific keyword phrases that help buyers find what you are selling.
- [Prestozon](https://prestozon.com/) - Automation and analytics for Amazon HSA & sponsored products ads.
- [Prisync](https://prisync.com/) - Price monitoring & tracking SaaS with dynamic pricing and automatching engine.
- [Scrappie](https://scrappie.app) - E-commerce data monitoring and analysis platform with API integration, WebHooks & ETL processes.
- [SellerApp](https://www.sellerapp.com/) - Product research, product ideas, listing quality, product alerts, product source, keyword research, ppc analyzer, and more.
- [SellerLabs](https://www.sellerlabs.com/tools/) - Several tools to manage ads, discover profitable keywords and products, get more product reviews and better seller feedback, simplify inventory and financial management for the amazon marketplace.
- [SellerLegend](https://sellerlegend.com/) - Near real-time orders download, intelligent KPI dashboards, inventory management, notifications, refunds, historical cost of goods, operating expenses, all America & Europe marketplaces, financial transactions, Europe vat.
- [Sellics](https://sellics.com) - Combines everything you need to manage and grow your Amazon business in one integrated software. Features that work together to help you become more successful on Amazon.
- [Skubana](https://www.skubana.com/) - Powers orders, inventory and business intelligence for the world's top high-volume brands and retailers.
- [Sonar](http://sonar-tool.com/) - Free Amazon keyword research tool from Sellics.
- [Splitly](https://splitly.com/) - Algorithmic split testing, automated pricing optimization, keyword rank tracking.
- [TradeGecko](https://www.tradegecko.com/) - Cloud based inventory and order management software for modern online businesses.
- [Turbo Piranha](https://www.turbopiranha.com/) - Bulk product search, profit calculation and competition analysis software using UPC, ISBN, EAN and ASIN lists in Excel/CSV/TXT format for wholesale and arbitrage business models, and also book sellers/flippers.
- [WordTree](https://www.wordtree.io/) - Keyword tools to grow your search traffic, research your competitors, and monitor your niche.
- [xSellco](https://www.xsellco.com/) - Centralize customer queries, target positive feedback by requesting reviews from happy customers, automatically reprice.

## Podcasts

- [AM/PM Podcast](https://www.ampmpodcast.com/) - Hosted by Manny Coats (Helium 10). Amazon selling tips for sellers at any step of the Amazon seller journey. From best-in-class interviews with six and seven figure Amazon sellers to instructional how-to education.
- [Amazing FBA](https://amazingfba.com/blog-podcast/) - Hosted by Michael Veazey. Podcast about selling on Amazon, especially white label products with a focus on UK markets.
- [EcomCrew Podcast](https://www.ecomcrew.com/ecomcrew-podcast/) - Real world e-commerce experience without any online marketing fluff!
- [Follow The Data](https://viral-launch.com/follow-the-data-amazon-fba-seller-podcast.html) - Hosted by Cameron Yoder (Viral Launch). The goal of this podcast is to help you successfully dive deeper into your own Amazon seller journey, using data as the anchor through that journey.
- [Keyword: The Amazon Insider Podcast](http://keywordpodcast.com/) - Hosted by Kate Valentine. Delivers authoritative content exclusively from former and current Amazon employees.
- [The Ecommerce Momentum](https://ecommercemomentum.com/) - Hosted by Stephen Peterson. Interviews with the top Amazon, eBay and e-commerce sellers in the world.
- [The My Wife Quit Her Job Podcast](https://mywifequitherjob.com/category/podcast/) - Hosted by Steve Chou. An interview based show where Steve brings in small business entrepreneurs who are killing it online. All of the entrepreneurs on the show bootstrapped their businesses and started their own ventures to improve their lifestyle in some way.

## Blogs

- [JumpSend](https://www.jumpsend.com/blog/) - Authored by Greg Mercer and Adam Zlotnik. Tips for sellers on how to increase reviews and listing optimization.
- [JungleScout](https://www.junglescout.com/blog/) - The most recent Amazon seller resources in the universe. Stay up to date and ahead of the competition with the latest Amazon strategies.
- [SellerEngine](https://sellerengine.com/blog/) - A blog with regular updates with an awesome Amazon Account Holder Digest series.
- [SellerLabs](https://www.sellerlabs.com/blog/) - A blog from SellerLabs with awesome ICYMI: Amazon Seller News series.
- [Sellics](https://sellics.com/blog) - Sellics provides strategic advice for sellers.
- [Tamebay](https://tamebay.com/) - Tamebay is the leading provider of intelligence & news for all businesses and business people who ply their trade on online marketplaces, whether they are experienced powersellers looking to boost sales, or beginners seeking advice and best practice.
- [Turbo Piranha](https://www.turbopiranha.com/articles/) - Amazon news, updates, and best practices for e-commerce
- [WebRetailer](https://www.webretailer.com/) - WebRetailer is a treasure trove of information for sellers and one of the leading resources on the web.

## Other

- [FBA Catalog](https://fbacatalog.com) - Software catalog for Amazon Sellers. Find tools that fit your business in no time!
- [FBA Monthly](https://fbamonthly.com) - FBA Monthly newsletter is an across-the-board summary of the month's most important news articles and blog posts regarding Amazon businesses.
- [r/FulfillmentByAmazon](https://www.reddit.com/r/FulfillmentByAmazon/) - Subreddit for FBA sellers.
- [500K ASIN Lists](https://app.turbopiranha.com/Download/bestselleritems) - Weekly FREE 500K ASIN lists in popular categories of 8 marketplaces (US, CA, MX, UK, DE, ES, FR, IT).

---

## Contribute

Contributions welcome!

Read the [contribution guidelines](contributing.md) first.

## License

[![CC0](https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](http://creativecommons.org/publicdomain/zero/1.0)

To the extent possible under law, Scale Leap has waived all copyright and related or neighboring rights to this work.

repo_name: ScaleLeap/awesome-amazon-seller


text:
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at info@scaleleap.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


text:
# Contribution Guidelines

Please note that this project is released with a
[Contributor Code of Conduct](code-of-conduct.md). By participating in this
project you agree to abide by its terms.

---

## What kind of contributions are accepted?

Make sure to read the [the awesome manifesto](https://github.com/sindresorhus/awesome/blob/master/awesome.md)
to understand what this list is about.

This repository is intended for an ***awesome*** list of resources for Amazon sellers.

If you have a resource that does not fit into any of the categories, do not worry, we can start a new category or create a Misc category. Go ahead and submit it anyways.

## How to contribute a resource?

### Pull request

If you are technically-savvy, you can create a pull request on GitHub and add a resource. Please mention why you think the resource is awesome in the description of the PR.

### Open an issue

If you do not know how to open a pull request, you can get a free account on GitHub and [open an issue](https://github.com/ScaleLeap/awesome-amazon-seller/issues/new) with your suggestion. Please explain why the resource is awesome.

## Ensure your pull request adheres to the following guidelines:

### Do

- Make sure your submission is awesome, and not just average.
- If you are sending a PR, make sure you insert the resource in alphabetical order.
- Tone down the marketing in the description. Try to stay factual.

### Do Not

- Submit spam.
- Submit guru courses.
- Submit affiliate links.

Thank you for your suggestions!

## Updating your PR

A lot of times, making a PR adhere to the standards above can be difficult.
If the maintainers notice anything that we'd like changed, we'll ask you to
edit your PR before we merge it. There's no need to open a new PR, just edit
the existing one. If you're not sure how to do that,
[here is a guide](https://github.com/RichardLitt/knowledge/blob/master/github/amending-a-commit-guide.md)
on the different ways you can update your PR so that we can merge it.


text:
# TheSkyBlessing
[![discord](https://img.shields.io/discord/742465492861845564?logo=discord&label=discord)](https://discord.gg/vPqHuQNMEv)
[![stars](https://img.shields.io/github/stars/ProjectTSB/TheSkyBlessing?logo=github)](https://github.com/ProjectTSB/TheSkyBlessing/stargazers)
[![downloads](https://img.shields.io/github/downloads/ProjectTSB/TheSkyBlessing/total?logo=github)](https://github.com/ProjectTSB/TheSkyBlessing/releases/latest)
[![activity](https://img.shields.io/github/commit-activity/m/ProjectTSB/TheSkyBlessing?label=commit&logo=github)](https://github.com/ProjectTSB/TheSkyBlessing/commits/master)
[![workflow](https://img.shields.io/github/actions/workflow/status/ProjectTSB/TheSkyBlessing/datapack-linter.yml?branch=master&label=linter)](https://github.com/ProjectTSB/TheSkyBlessing/actions?query=workflow%3Alint-datapack)
[![Gitmoji](https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg)](https://gitmoji.carloscuesta.me/)

## 開発環境
* [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)
* [Data-pack Helper Plus](https://github.com/SPGoding/datapack-language-server)
* [MC Datapack Utility](https://github.com/ChenCMD/MC-Datapack-Utility)
* [Gitmoji](https://marketplace.visualstudio.com/items?itemName=Vtrois.gitmoji-vscode)

## 開発のあれこれ
[Wiki](https://github.com/ProjectTSB/TheSkyBlessing/wiki)見てね！

## ライセンスについて
このリポジトリでは、以下のディレクトリを除いて[CC0-1.0 License](LICENSE)が適用されます。
* [Asset](Asset)ディレクトリ ([MIT License](Asset/LICENSE))
* [NaturalMergeSort](NaturalMergeSort)ディレクトリ ([MIT License](NaturalMergeSort/LICENSE) / Copyright by 赤石愛)
* [OhMyDat](OhMyDat)ディレクトリ ([MIT License](OhMyDat/LICENSE) / Copyright by 赤石愛)
* [ScoreToHealth](ScoreToHealth)ディレクトリ ([Apache-2.0 License](ScoreToHealth/LICENSE) / Copyright by 赤石愛)
* [TheSkyBlessing/data/rom](TheSkyBlessing/data/rom/functions/)ディレクトリ ([MIT License](OhMyDat/LICENSE) / Copyright by 赤石愛)

## 謝辞
このリポジトリでは以下のライブラリを使用させていただいてます。

#### [赤石愛](https://twitter.com/AiAkaishi)氏
* [NaturalMergeSort](https://github.com/Ai-Akaishi/NaturalMergeSort)
* [Oh! My Dat!](https://github.com/Ai-Akaishi/OhMyDat)
* [ScoreToHealth](https://github.com/Ai-Akaishi/ScoreToHealth)
* [ScoreToMove](https://github.com/Ai-Akaishi/ScoreToMove)

## コラボレーター以外によるプルリクエストについて
コラボレーター以外によるプルリクエストは、最適化, バグ修正に限り歓迎しています。

repo_name: ProjectTSB/TheSkyBlessing

repo_language: mcfunction


text:
name: lint-datapack
on:
  push:
  pull_request:
  workflow_dispatch:
jobs:
  lint:
    name: lint
    runs-on: ubuntu-latest
    if: "!contains(github.event.head_commit.message, '[skip ci]')"
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Test
        uses: ChenCMD/datapack-linter@v2
        with:
          muteSuccessResult: true
          ignoreLintPathPattern: |
            animated_java:**


datapack.env.defaultVisibility: public

datapack.lint.eol: LF

label: SlotType

parts: /auto/ /mainhand/ /offhand/ /head/ /chest/ /legs/ /feet/ /hotbar/

label: id

label: uuid

parts: /^uuid$/

label: ChainedPascalCase

label: this

parts: /this/

label: DollerPrefix

datapack.lint.nbtByteSuffix: b

datapack.lint.nbtCompoundKeyQuoteType:
warning
always double

datapack.lint.nbtDoubleSuffix: d

datapack.lint.nbtFloatSuffix: f

datapack.lint.nbtLongSuffix: l

datapack.lint.nbtPathQuoteType:
warning
always double

datapack.lint.nbtShortSuffix: s

datapack.lint.nbtStringQuoteType:
warning
prefer double

datapack.lint.nbtTypeCheck: warning strictly

datapack.lint.selectorSortKeys: type gamemode gamemodeNeg team teamNeg typeNeg tag tagNeg name nameNeg scores advancements nbt nbtNeg predicate predicateNeg level x_rotation y_rotation distance x y z dx dy dz sort limit

datapack.lint.strictAttributeCheck: error always

datapack.lint.strictBlockCheck: error always

datapack.lint.strictDimensionTypeCheck: error always

datapack.lint.strictEnchantmentCheck: information always

datapack.lint.strictEntityTypeCheck: error always

datapack.lint.strictFluidCheck: error always

datapack.lint.strictItemCheck: error always

datapack.lint.strictMobEffectCheck: error always

datapack.lint.strictMotiveCheck: error always

datapack.lint.strictParticleTypeCheck: error always

datapack.lint.strictPotionCheck: error always

datapack.lint.strictSoundEventCheck: error only-default-namespace

datapack.lint.stringQuoteType:
warning
prefer double

forwardSpreadLib:
#> SpreadLib
# @private
#declare tag SpreadMarker

# 拡散させるEntityを召喚する
    summon marker ~ ~ ~ {Tags:["SpreadMarker"]}
# ステータス設定
    data modify storage lib: Argument.Distance set value ${1:<distance>}
    data modify storage lib: Argument.Spread set value ${2:<spread>}
# 拡散
    execute as @e[type=marker,tag=SpreadMarker,limit=1] ${3:}run function lib:forward_spreader/${4:<circle|square>}
# 拡散
    execute facing entity @e[type=marker,tag=SpreadMarker,limit=1] ${5:feet anchored eyes positioned ^ ^ ^ }run ${6:<command>}
# リセット
    kill @e[type=marker,tag=SpreadMarker]

function: # @within %nowOpenFileType% %nowOpenFileResourcePath%

advancement:
{
"criteria": {
"trigger": {
"trigger": "%cursor%"
}
}
}

predicate:
{
"condition": "%cursor%"
}

tag/block:
{
"values": [
%cursor%
]
}

tag/entity_type:
{
"values": [
%cursor%
]
}

tag/fluid:
{
"values": [
%cursor%
]
}

tag/function:
{
"values": [
%cursor%
]
}

tag/item:
{
"values": [
%cursor%
]
}

label: 神器/テンプレート

type: file

key: values

content: #> asset:artifact/%namespace%/give/1.trigger

content: # 神器の取得処理の呼び出し時に実行されるfunction

content: # @within tag/function asset:artifact/give

content: execute if data storage asset:context {id:%ItemID%} run function asset:artifact/%namespace%/give/2.give

content: #> asset:artifact/%namespace%/give/2.give

content: # 神器の作成部 ここでID等を定義する

content: # @user

content: # @within function asset:artifact/%namespace%/give/1.trigger

content: # 神器の説明や消費MPなどをここで設定する。

content: # 最後にasset:artifact/common/giveを実行することで入手可能。

content: # 神器のID (int) スプレッドシートの値を入れる

content: data modify storage asset:artifact ID set value %ItemID%

content: # 神器のベースアイテム

content: data modify storage asset:artifact Item set value

content: # 神器の名前 (TextComponentString)

content: data modify storage asset:artifact Name set value

content: # 神器の説明文 (TextComponentString[])

content: data modify storage asset:artifact Lore set value []

content: # MP以外の消費物 (TextComponentString) (オプション)

content: # data modify storage asset:artifact CostText set value

content: # 使用回数 (int) (オプション)

content: # data modify storage asset:artifact RemainingCount set value

content: # 神器を発動できるスロット (string) Wikiを参照

content: data modify storage asset:artifact Slot set value "%slot%"

content: # 神器のトリガー (string) Wikiを参照

content: data modify storage asset:artifact Trigger set value

content: # 神器の発動条件 (TextComponentString) (オプション)

content: # data modify storage asset:artifact Condition set value

content: # 攻撃に関する情報 -Damage量 (literal[]/literal) Wikiを参照 (オプション)

content: # data modify storage asset:artifact AttackInfo.Damage set value [0,0]

content: # 攻撃に関する情報 -攻撃タイプ (string[]) Wikiを参照 (オプション)

content: # data modify storage asset:artifact AttackInfo.AttackType set value [Physical,Magic]

content: # 攻撃に関する情報 -攻撃属性 (string[]) Wikiを参照 (オプション)

content: # data modify storage asset:artifact AttackInfo.ElementType set value [Fire,Water,Thunder,None]

content: # 攻撃に関する情報 -防御無視 (boolean) Wikiを参照 (オプション)

content: # data modify storage asset:artifact AttackInfo.BypassResist set value

content: # 攻撃に関する情報 -範囲攻撃 (string) Wikiを参照 (オプション)

content: # data modify storage asset:artifact AttackInfo.IsRangeAttack set value

content: # 攻撃に関する情報 -攻撃範囲 (literal) Wikiを参照 (オプション)

content: # data modify storage asset:artifact AttackInfo.AttackRange set value

content: # MP消費量 (int)

content: data modify storage asset:artifact MPCost set value

content: # MP必要量 (int) (オプション)

content: # data modify storage asset:artifact MPRequire set value

content: # 神器のクールダウン (int) (オプション)

content: # data modify storage asset:artifact LocalCooldown set value

content: # グローバルクールダウン (int) (オプション)

content: # data modify storage asset:artifact SpecialCooldown set value

content: # クールダウンによる使用不可のメッセージを非表示にするか否か (boolean) (オプション)

content: # data modify storage asset:artifact DisableCooldownMessage set value

content: # MP不足による使用不可のメッセージを非表示にするか否か (boolean) (オプション)

content: # data modify storage asset:artifact DisableMPMessage set value

content: # 扱える神 (string[]) Wikiを参照

content: data modify storage asset:artifact CanUsedGod set value []

content: # カスタムNBT (NBTCompound) 追加で指定したいNBT (オプション)

content: # data modify storage asset:artifact CustomNBT set value {}

content: # 神器の入手用function

content: function asset:artifact/common/give

content: #> asset:artifact/%namespace%/trigger/1.trigger

content: # 指定したイベントタイミングで実行されるfunction

content: # @within tag/function asset:artifact/**

content: # storage asset:idの%slot%に装備している神器のIDが入っているので比較し、~/2.check_condition.mcfunctionを実行する

content: execute if data storage asset:context id{%slot%:%ItemID%} run function asset:artifact/%namespace%/trigger/2.check_condition

content: #> asset:artifact/%namespace%/trigger/2.check_condition

content: # 神器の発動条件をチェックします

content: # @within function asset:artifact/%namespace%/trigger/1.trigger

content: # 神器の基本的な条件の確認を行うfunction、成功している場合CanUsedタグが付く

content: function asset:artifact/common/check_condition/%slot%

content: # 他にアイテム等確認する場合はここに書く

content: # CanUsedタグをチェックして3.main.mcfunctionを実行する

content: execute if entity @s[tag=CanUsed] run function asset:artifact/%namespace%/trigger/3.main

content: #> asset:artifact/%namespace%/trigger/3.main

content: # 神器のメイン処理部

content: # @within function asset:artifact/%namespace%/trigger/2.check_condition

content: # 基本的な使用時の処理(MP消費や使用回数の処理など)を行う

content: function asset:artifact/common/use/%slot%

content: # ここから先は神器側の効果の処理を書く

content: say test: %namespace%

ItemID

question: 神器のID

patternErrorMessage: 数値のみが許されるよ

slot

question: 使用できるスロット

patternErrorMessage: スロットのみが許されるよ

label: 神器/テンプレート(hotbar)

content: # 神器の取得処理の呼び出し時に実行されるfunction

content: # 神器の作成部 ここでID等を定義する

content: # @user

content: # 神器の説明や消費MPなどをここで設定する。

content: # 神器のID (int) スプレッドシートの値を入れる

content: # 神器のベースアイテム

content: # 神器の名前 (TextComponentString)

content: # 神器の説明文 (TextComponentString[])

content: # 使用回数 (int) (オプション)

content: # 神器を発動できるスロット (string) Wikiを参照

content: data modify storage asset:artifact Slot set value "hotbar"

content: # 神器のトリガー (string) Wikiを参照

content: # 神器の発動条件 (TextComponentString) (オプション)

content: # MP消費量 (int)

content: # MP必要量 (int) (オプション)

content: # 神器のクールダウン (int) (オプション)

content: # グローバルクールダウン (int) (オプション)

content: # 扱える神 (string[]) Wikiを参照

content: # 神器の入手用function

content: function asset:artifact/common/give

content: # 指定したイベントタイミングで実行されるfunction

content: execute if data storage asset:context id{hotbar:[%ItemID%]} run function asset:artifact/%namespace%/trigger/2.check_condition

content: # 神器の発動条件をチェックします

content: # ID指定する

content: data modify storage asset:artifact TargetID set value %ItemID%

content: function asset:artifact/common/check_condition/hotbar

content: # 他にアイテム等確認する場合はここに書く

content: # 神器のメイン処理部

content: # 基本的な使用時の処理(MP消費や使用回数の処理など)を行う

content: function asset:artifact/common/use/hotbar

content: # ここから先は神器側の効果の処理を書く

content: say test: %namespace%

ItemID

question: 神器のID

patternErrorMessage: 数値のみが許されるよ

label: 神器/テンプレート: 加工結果への登録

content: #> asset:artifact/%namespace%/register

content: # 神器プールへの登録処理

content: # @within tag/function asset:artifact/register

content: data modify storage asset:artifact RarityRegistry[%Rarity%] append value %ItemID%

question: 神器のID

patternErrorMessage: 数値のみが許されるよ

Rarity

question: レアリティ

patternErrorMessage: 1-4のみが許されるよ

label: 神器/テンプレート: 初期化処理

content: #> asset:artifact/%namespace%/trigger/0.load

content: # 神器に利用するスコアボード等の初期化処理

content: # @within tag/function asset:artifact/load

content: #> 定義類はここに

content: # @within function asset:artifact/%namespace%/trigger/**

content: scoreboard objectives add

label: 神器/テンプレート: _index.d

content: #> asset:artifact/%namespace%/trigger/_index.d

content: # @private

content: #> tag

content: #declare

label: 神器/トリガー: 人参棒クリック

label: 神器/トリガー: 攻撃

label: 神器/トリガー: 攻撃 - 近接

label: 神器/トリガー: 攻撃 - 遠距離

label: 神器/トリガー: 被ダメージ

label: 神器/トリガー: 被ダメージ - 爆発

label: 神器/トリガー: 被ダメージ - 炎上

label: 神器/トリガー: 被ダメージ - 敵による攻撃

label: 神器/トリガー: 被ダメージ - 防御

label: 神器/トリガー: 被ダメージ - 近接

label: 神器/トリガー: 被ダメージ - 遠距離

label: 神器/トリガー: キル

label: 神器/トリガー: キル - 近接

label: 神器/トリガー: キル - 遠距離

label: 神器/トリガー: 飲食

label: 神器/トリガー: スニーク

label: 神器/トリガー: スニーク - 1秒

label: 神器/トリガー: スニーク - 2秒

label: 神器/トリガー: スニーク - 3秒

label: 神器/トリガー: スニーク - 4秒

label: 神器/トリガー: スニーク - 5秒

label: 神器/トリガー: スニーク - 10秒

label: 神器/トリガー: スニーク - 0秒以上

label: 神器/トリガー: スニーク - 1秒以上

label: 神器/トリガー: スニーク - 2秒以上

label: 神器/トリガー: スニーク - 3秒以上

label: 神器/トリガー: スニーク - 4秒以上

label: 神器/トリガー: スニーク - 5秒以上

label: 神器/トリガー: スニーク - 10秒以上

label: 神器/トリガー: アイテム使用中

label: 神器/トリガー: tick

label: 神器/トリガー: 装備着脱

content: #> asset:artifact/%namespace%/trigger/dis_equip/

content: # 装備を外した時に外した部位にのみのidが入った状態でトリガーされる

content: # @within tag/function asset:artifact/dis_equip

content: execute if data storage asset:context id{%slot%:%ItemID%} run function asset:artifact/%namespace%/trigger/dis_equip/main

content: #> asset:artifact/%namespace%/trigger/dis_equip/main

content: # 装備を外した時のメイン処理

content: # @within function asset:artifact/%namespace%/trigger/dis_equip/

ItemID

question: 神器のID

slot

question: 使用できるスロット

patternErrorMessage: スロットのみが許されるよ

label: 神器/特殊トリガー: 再Join

content: #> asset:artifact/%namespace%/trigger/rejoin_process

content: # 使い方は必ずwikiを見ること

content: # @within tag/function asset:rejoin

content: #> asset:mob/%namespace%/summon/1.trigger

content: # Mob召喚処理の呼び出し時に実行されるfunction

content: # @within tag/function asset:mob/summon

content: execute if data storage asset:context {id:%id%} run function asset:mob/%namespace%/summon/2.summon

content: #> asset:mob/%namespace%/summon/2.summon

content: # Mobの召喚時の処理

content: # @within function asset:mob/%namespace%/summon/1.trigger

content: # 元となるMobを召喚する

content: summon %entity% ~ ~ ~ {Tags:["MobInit"],DeathLootTable:"asset:mob/death/%namespace%"}

content: # ID (int)

content: data modify storage asset:mob ID set value %id%

content: # Type (string) Wikiを参照

content: data modify storage asset:mob Type set value ""

content: # 干渉可能か否か (boolean)

content: data modify storage asset:mob Interferable set value

content: # 名前 (TextComponentString) (オプション)

content: # data modify storage asset:mob Name set value

content: # 武器

content: # メインハンド (Compound(Item)) (オプション)

content: # data modify storage asset:mob Weapon.Mainhand set value

content: # オフハンド (Compound(Item)) (オプション)

content: # data modify storage asset:mob Weapon.Offhand set value

content: # 武器ドロップ率 ([float, float]) (オプション)

content: # data modify storage asset:mob WeaponDropChances set value

content: # 防具

content: # 頭 (Compound(Item)) (オプション)

content: # data modify storage asset:mob Armor.Head set value

content: # 胴 (Compound(Item)) (オプション)

content: # data modify storage asset:mob Armor.Chest set value

content: # 脚 (Compound(Item)) (オプション)

content: # data modify storage asset:mob Armor.Legs set value

content: # 足 (Compound(Item)) (オプション)

content: # data modify storage asset:mob Armor.Feet set value

content: # 防具ドロップ率 ([float, float]) (オプション)

content: # data modify storage asset:mob ArmorDropChances set value

content: # 体力 (double) (オプション)

content: # data modify storage asset:mob Health set value

content: # 攻撃力 (double) (オプション)

content: # data modify storage asset:mob AttackDamage set value

content: # 防御力 (double) (オプション) // 被ダメージがある程度大きい場合1ptにつき0.8%カット、小さい場合1ptにつき約4%カット 20pt以上は頭打ち

content: # data modify storage asset:mob Defense set value

content: # 特殊防御力 (double) (オプション) // 4pointにつきダメージを大きく減らす

content: # data modify storage asset:mob SpecialDefense set value

content: # 移動速度 (double) (オプション)

content: # data modify storage asset:mob Speed set value

content: # 索敵範囲 (double) (オプション)

content: # data modify storage asset:mob FollowRange set value

content: # ノックバック耐性 (double) (オプション)

content: # data modify storage asset:mob KnockBackResist set value

content: # 属性倍率 // 1.0fで100% 最低でも25%は軽減されずに入る

content: # 物理倍率 (float) (オプション)

content: # data modify storage asset:mob Resist.Physical set value

content: # 魔法倍率 (float) (オプション)

content: # data modify storage asset:mob Resist.Magic set value

content: # 火倍率 (float) (オプション)

content: # data modify storage asset:mob Resist.Fire set value

content: # 水倍率 (float) (オプション)

content: # data modify storage asset:mob Resist.Water set value

content: # 雷倍率 (float) (オプション)

content: # data modify storage asset:mob Resist.Thunder set value

content: # MobInitタグ持ちを対象にして召喚関数呼び出し

content: execute as @e[type=%entity%,tag=MobInit,distance=..0.01] run function asset:mob/common/summon

id

question: MobのID

entity

question: 元となるentity

patternErrorMessage: Mobの名前のみが許されるよ

label: MOB/トリガー: 初期化処理

content: #> asset:mob/%namespace%/0.load

content: # MOBに利用するスコアボード等の初期化処理

content: # @within tag/function asset:mob/load

content: #> 定義類はここに

content: # @within function asset:mob/%namespace%/**

content: scoreboard objectives add

content: #> asset:mob/%namespace%/_index.d

content: # @private

content: #> tag

content: #declare

label: MOB/トリガー: 攻撃

content: #> asset:mob/%namespace%/attack/1.trigger

content: # Mobの攻撃時に実行されるfunction

content: # @within tag/function asset:mob/attack

content: execute if data storage asset:context {id:%id%} run function asset:mob/%namespace%/attack/2.attack

content: #> asset:mob/%namespace%/attack/2.attack

content: # Mobの攻撃時の処理

content: # @within function asset:mob/%namespace%/attack/1.trigger

id

question: MobのID

label: MOB/トリガー: 被ダメージ

content: #> asset:mob/%namespace%/hurt/1.trigger

content: # Mobの被ダメージに実行されるfunction

content: # @within tag/function asset:mob/hurt

content: execute if data storage asset:context {id:%id%} run function asset:mob/%namespace%/hurt/2.hurt

content: #> asset:mob/%namespace%/hurt/2.hurt

content: # Mobの被ダメージ時の処理

content: # @within function asset:mob/%namespace%/hurt/1.trigger

question: MobのID

label: MOB/トリガー: 死亡

content: #> asset:mob/%namespace%/death/1.trigger

content: # Mobの死亡時に実行されるfunction

content: # @within tag/function asset:mob/death

content: execute if data storage asset:context {id:%id%} run function asset:mob/%namespace%/death/2.death

content: #> asset:mob/%namespace%/death/2.death

content: # Mobの死亡時の処理

content: # @within function asset:mob/%namespace%/death/1.trigger

question: MobのID

content: #> asset:mob/%namespace%/tick/1.trigger

content: # Mobが生きている間毎tick実行されるfunction

content: # @within tag/function asset:mob/tick

content: execute if data storage asset:context {id:%id%} run function asset:mob/%namespace%/tick/2.tick

content: #> asset:mob/%namespace%/tick/2.tick

content: # Mobのtick時の処理

content: # @within function asset:mob/%namespace%/tick/1.trigger

question: MobのID

content:
#> asset:mob/%namespace%/rejoin_process
#
# 使い方は必ずwikiを見ること
#
# @within tag/function asset:rejoin

content: #> asset:mob/%namespace%/remove/1.trigger

content: # 天使が居なくなる際に実行されるfunction

content: # @within tag/function asset:mob/remove

content: execute if data storage asset:context {id:%id%} run function asset:mob/%namespace%/remove/2.remove

content: #> asset:mob/%namespace%/remove/2.remove

content: # Mobのremove時の処理

content: # @within function asset:mob/%namespace%/remove/1.trigger

question: MobのID

description: Corrections regarding balance adjustment


text:
{
    // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
    // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp

    // List of extensions which should be recommended for users of this workspace.
    "recommendations": [
        "spgoding.datapack-language-server",
        "chencmd.mc-datapack-utility",
        "ms-ceintl.vscode-language-pack-ja",
        "seatonjiang.gitmoji-vscode",
        "arcensoth.language-mcfunction",
        "editorconfig.editorconfig"
    ],
    // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
    "unwantedRecommendations": [

    ]
}


text:
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Master Merge",
            "type": "shell",
            "command": "now=`git rev-parse --abbrev-ref HEAD` && git stash -u && git checkout master && git pull && git checkout $now && git merge master && git stash pop",
            "presentation": {
                "echo": false,
                "reveal": "silent",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": false,
                "clear": true
            },
            "dependsOn": [
                "change world file"
            ],
            "promptOnClose": true,
            "problemMatcher": []
        },
        {
            "label": "change world file",
            "type": "shell",
            "command": "sed -i -e \"s/^level-name=.*$/level-name=The SkyBlock/\" ./server.properties",
            "options": {
                "cwd": "../../"
            },
            "presentation": {
                "echo": false,
                "reveal": "never",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": false,
                "clear": false
            },
            "problemMatcher": []
        },
        {
            "label": "Open SkyBlock Server",
            "type": "shell",
            "command": "java -Xms2G -Xmx4G -Dlog4j2.formatMsgNoLookups=true -jar ./server.jar nogui",
            "options": {
                "cwd": "../../"
            },
            "presentation": {
                "echo": false,
                "reveal": "always",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": true,
                "clear": true
            },
            "dependsOn": [
                "change world file"
            ],
            "promptOnClose": true,
            "problemMatcher": []
        }
    ]
}