MFC Buy Links

Adds a box for direct links to shop's searches

目前為 2021-11-18 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MFC Buy Links
// @namespace    https://myfigurecollection.net/profile/tharglet
// @version      2.2
// @description  Adds a box for direct links to shop's searches
// @author       Tharglet
// @license      CC BY-NC-SA 4.0
// @match        https://myfigurecollection.net/item/*
// @grant        GM_addStyle
// @grant        GM.getValue
// @grant        GM.setValue
// ==/UserScript==

////////LICENCE////////
//This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
//Please credit 'Tharglet' for the original code, and provide a link to my MFC profile: https://myfigurecollection.net/profile/tharglet
///////////////////////

//Polyfill for GM_addStyle for Greasemonkey...
if(typeof GM_addStyle == 'undefined') {
    GM_addStyle = (aCss) => {
        'use strict';
        let head = document.getElementsByTagName('head')[0];
        if (head) {
            let style = document.createElement('style');
            style.setAttribute('type', 'text/css');
            style.textContent = aCss;
            head.appendChild(style);
            return style;
        }
        return null;
    };
}

GM_addStyle(`
h4 {
font-style: italic;
padding-bottom: 5px;
padding-top: 10px;
}
.buylinks__row {
display: flex;
}
.buylinks__column {
flex: 1;
padding-bottom: 4px;
}
.buylinks__settings__setting-title {
display: block;
float: left;
clear: both;
width: 150px;
padding-bottom: 4px;
}
.buylinks__settings__setting-group {
display: block;
float: left;
}
.buylinks__shop-title {
font-size: 1.2em;
font-weight: normal;
padding-left:0;
padding-bottom: 6px;
margin-bottom: 6px;
}
.buylinks__label {
padding-left: 4px;
}
.buylinks__termselector label {
padding-right: 12px;
}
.buylinks__button[aria-disabled=true] {
color: lightgray;
}
.buylinks--hidden {
display: none;
}
.buylinks__savebutton {
margin-top: 8px;
}
`);

const shopTypeLookup = {
    shop: 'Shop',
    info: 'Information',
    proxy: 'Proxy',
};

(async () => {
    'use strict';
    let targetBlank = await GM.getValue('targetBlank', true);
    let targetString = targetBlank ? "target='_blank'" : '';
    const savedSearchSettings = await GM.getValue('searchSettings', null);
    let searchSettings;
    if(savedSearchSettings === null) {
        await GM.setValue('searchSettings', '{"origins": "first", "manufacturers": "first", "title": "yes", "characters": "first"}');
        searchSettings = {
            origins: 'first',
            manufacturers: 'first',
            title: 'yes',
            characters: 'first',
        };
    } else {
        searchSettings = JSON.parse(savedSearchSettings);
    }
    let shopConfig = await GM.getValue('shopConfig', null);
    let shops = [{
        key: 'mandarake',
        display: 'Mandarake',
        type: 'shop',
        searches: {
            en: 'https://order.mandarake.co.jp/order/listPage/list?keyword=%s&lang=en',
            ja: 'https://order.mandarake.co.jp/order/listPage/list?keyword=%s&lang=en',
        }

    }, {
        key: 'amiami',
        display: 'AmiAmi',
        type: 'shop',
        searches: {
            en: 'https://www.amiami.com/eng/search/list/?s_keywords=%s',
            jan: 'https://www.amiami.com/eng/search/list/?s_keywords=%s',
        }

    }, {
        key: 'surujp',
        display: 'Suruga-ya.jp',
        type: 'shop',
        searches: {
            ja: 'https://www.suruga-ya.jp/search?search_word=%s',
            jan: 'https://www.suruga-ya.jp/search?gtin=%s',
        }
    }, {
        key: 'surucom',
        display: 'Suruga-ya.com',
        type: 'shop',
        searches: {
            en: 'https://www.suruga-ya.com/en/products?keyword=%s',
        }
    }, {
        key: 'amazonjp',
        display: 'Amazon.co.jp',
        type: 'shop',
        searches: {
            ja: 'https://www.amazon.co.jp/s?k=%s',
            jan: 'https://www.amazon.co.jp/s?k=%s',
        }
    }, {
        key: 'yaj',
        display: 'Yahoo! Auctions Japan',
        type: 'shop',
        searches: {
            ja: 'https://auctions.yahoo.co.jp/search/search?p=%s',
        }
    }, {
        key: 'milestone',
        display: 'Milestone',
        type: 'info',
        searches: {
            en: 'https://b2b.mile-stone.jp/en/search/0/keyword=%s/',
            jan: 'https://b2b.mile-stone.jp/en/search/0/jan=%s/',
        }

    }, {
        key: 'hpoi',
        display: 'Hpoi',
        type: 'info',
        searches: {
            ja: 'https://www.hpoi.net/search?keyword=%s',
            jan: 'https://www.hpoi.net/search?keyword=%s',
        }
    }, {
        key: 'buyee',
        display: 'Buyee',
        type: 'proxy',
        searches: {
            ja: 'https://buyee.jp/item/search/query/%s',
        }
    }, {
        key: 'fromjapan',
        display: 'From Japan',
        type: 'proxy',
        searches: {
            ja: 'https://www.fromjapan.co.jp/en/item/search/%s/',
        }
    }, {
        key: 'zenmarket',
        display: 'Zenmarket',
        type: 'proxy',
        searches: {
            ja: 'https://zenmarket.jp/en/marketplace.aspx?q=%s',
        }
    }, {
        key: 'neokyo',
        display: 'Neokyo',
        type: 'proxy',
        searches: {
            ja: 'https://neokyo.com/en/search-results?keyword=%s',
        }
    }];
    //functions
    const renderShopBlock = () => {
        let shopGrid = {};
        shops.map((shop) => {
            if(!shopConfig[shop.key]) {
                shopConfig[shop.key] = {
                    display: 'true'
                };
                GM.setValue('shopConfig', JSON.stringify(shopConfig));
            }
            if(shopConfig[shop.key].display === 'true') {
                let shopToAdd = '';
                shopToAdd += `<div class="buylinks__row"><div class="buylinks__column">${shop.display}</div><div class="buylinks__column">`;
                shopToAdd += shop.searches.ja ? `<a href='#' id='search_${shop.key}_ja' class="buylinks__button buylinks__button-ja" data-for="${shop.key}" ${targetString}>Japanese</a>` : '<div>&nbsp;</div>';
                shopToAdd += '</div><div class="buylinks__column">';
                shopToAdd += shop.searches.en ? `<a href='#' id='search_${shop.key}_en' class="buylinks__button buylinks__button-en" data-for="${shop.key}" ${targetString}>English</a>` : '<div>&nbsp;</div>';
                shopToAdd += '</div><div class="buylinks__column">';
                shopToAdd += shop.searches.jan && jan ? `<a href='${shop.searches.jan.replace('%s', jan)}' class="buylinks__button buylinks__button-jan" data-for="${shop.key}" ${targetString}>JAN</a>` : '<div>&nbsp;</div>';
                shopToAdd += '</div></div>';
                if(!shopGrid[shop.type]) {
                    shopGrid[shop.type] = shopToAdd;
                } else {
                    shopGrid[shop.type] += shopToAdd;
                }
            }
        });
        let shopHtml = '';
        for(let shopType in shopGrid) {
            shopHtml += `<h3 class="buylinks__shop-title">${shopTypeLookup[shopType]}</h3>${shopGrid[shopType]}`
        }
        if(shopHtml.length > 0) {
            return shopHtml;
        }
        return '<h3 class="buylinks__shop-title">No sites enabled</h3><div>Please use the settings button in the top-right of this block to enable one or more sites</div>'
    }

    const generateSearchList = (fields, fieldName) => {
        if(fields.length > 0) {
            const fieldNameLower = fieldName.toLowerCase();
            let seachListHtml = '';
            seachListHtml += `<div class="buylinks__termselector">${fieldName}: `;
            fields.map((field, idx) => {
                let checked = '';
                if(idx === 0 && (searchSettings[fieldNameLower] === 'first' || searchSettings[fieldNameLower] === 'all')) {
                    checked = "checked='checked'";
                } else if(idx > 0 && searchSettings[fieldNameLower] === 'all') {
                    checked = "checked='checked'";
                }
                seachListHtml += `<input id='${fieldNameLower}_${idx}' type='checkbox' ${checked} class='buylinks__termselector__checkbox' data-en='${field.en.replace("'", '&apos;')}' data-ja='${field.ja.replace("'", '&apos;')}'/>
                <label class='buylinks__label' for='${fieldNameLower}_${idx}'>${field.en}</label>`
            });
            seachListHtml += '</div>';
            return seachListHtml;
        }
        return '';
    }

    const updateSearch = () => {
        const checkedBoxes = document.querySelectorAll('.buylinks__termselector__checkbox:checked');
        if(checkedBoxes.length > 0) {
            document.querySelectorAll('.buylinks__button-en').forEach(ele => {
                ele.setAttribute('aria-disabled', false);
            });
            document.querySelectorAll('.buylinks__button-ja').forEach(ele => {
                ele.setAttribute('aria-disabled', false);
            });
            let searchJa = '';
            let searchEn = '';
            checkedBoxes.forEach(box => {
                if(searchEn.length !== 0) {
                    searchEn += '%20';
                }
                searchEn += encodeURIComponent(box.attributes['data-en'].value);
                if(searchJa.length !== 0) {
                    searchJa += '%20';
                }
                searchJa += encodeURIComponent(box.attributes['data-ja'].value);
            });
            shops.forEach(shop => {
                if(shopConfig[shop.key].display === 'true') {
                    if(shop.searches.en) {
                        const searchAnchorEn = document.getElementById(`search_${shop.key}_en`);
                        searchAnchorEn.setAttribute('href', shop.searches.en.replace('%s', searchEn));
                    }
                    if(shop.searches.ja) {
                        const searchAnchorJa = document.getElementById(`search_${shop.key}_ja`);
                        searchAnchorJa.setAttribute('href', shop.searches.ja.replace('%s', searchJa));
                    }
                }
            });
        } else {
            document.querySelectorAll('.buylinks__button-en').forEach(ele => {
                ele.setAttribute('href', '');
                ele.setAttribute('aria-disabled', true);
            });
            document.querySelectorAll('.buylinks__button-ja').forEach(ele => {
                ele.setAttribute('href', '');
                ele.setAttribute('aria-disabled', true);
            });
        }
    }

    const saveSettings = async () => {
        searchSettings = {
            origins: document.querySelector('input[name="origin_default"]:checked').value || 'first',
            manufacturers: document.querySelector('input[name="mfr_default"]:checked').value || 'first',
            title: document.querySelector('input[name="title_default"]:checked').value || 'yes',
            characters: document.querySelector('input[name="char_default"]:checked').value || 'first',
        };
        await GM.setValue('searchSettings', JSON.stringify(searchSettings));
        await Promise.all(shops.map((shop) => {
            const shopVisibilityElement = document.querySelector(`input[name='${shop.key}_visibility']:checked`);
            if(shopVisibilityElement !== null) {
                shopConfig[shop.key].display = shopVisibilityElement.value
            }
        }));
        await GM.setValue('shopConfig', JSON.stringify(shopConfig));
        const targetSettingCheckboxValue = document.querySelector(`input[name='targetblank']:checked`).value;
        if(targetSettingCheckboxValue === 'no') {
            targetBlank = false;
            targetString = '';
            await GM.setValue('targetBlank', false);
        } else {
            targetBlank = true;
            targetString = "target='_blank'";
            await GM.setValue('targetBlank', true);
        }
        //refresh shop area
        document.getElementById("buylinks-shoparea").innerHTML = renderShopBlock();
        updateSearch();
        //Show "saved!"
        document.getElementById('buylinks-savesettings-text').classList.remove('buylinks--hidden');
        setTimeout(() => { document.getElementById('buylinks-savesettings-text').classList.add('buylinks--hidden'); }, 3000);

    }
    //Code!
    shops.sort(function(a, b) {
        var textA = a.key
        var textB = b.key;
        return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
    });
    //Prep shopConfig var
    if(shopConfig) {
        shopConfig = JSON.parse(shopConfig);
    } else {
        shopConfig = {}
    }
    //Find item properties
    let jan = null;
    let productIdElement = document.querySelector('meta[itemprop="productID"]');
    if(productIdElement) {
        let productId = productIdElement.attributes.content.value;
        if(productId.startsWith('jan:')) {
            jan = productId.substring(4);
        }
    }
    //prep selector block
    let origins = [];
    let chars = [];
    let title = null;
    let mfrs = [];
    const figureData = Array.from(document.querySelectorAll('.split-right.righter .form-label'));
    let origin = figureData.find(el => el.textContent === 'Origin');
    if(origin) {
        Array.from(origin.parentElement.querySelectorAll("span")).map((ele) => {
            if(ele.innerText !== 'Original Character') {
                origins.push({
                    en: ele.innerText,
                    ja: ele.attributes.switch.value
                });
            }
        });
    }
    let character = figureData.find(el => el.textContent.startsWith('Character'));
    if(character) {
        Array.from(character.parentElement.querySelectorAll("span")).map((ele) => {
            chars.push({
                en: ele.innerText,
                ja: ele.attributes.switch.value
            });
        });
    }
    let titleFind = figureData.find(el => el.textContent.startsWith('Title'));
    if(titleFind) {
        const titleData = titleFind.parentElement.querySelector('a');
        title = {
            en: titleData.innerText,
            ja: titleData.attributes.switch.value
        };
    }
    let manufacturerFind = Array.from(document.querySelectorAll('.split-right.righter .form-input small'))
    .filter(el => el.textContent === 'As Manufacturer');
    if(manufacturerFind) {
        manufacturerFind.map(ele => {
            const mfr = ele.parentElement.querySelector('span');
            mfrs.push({
                en: mfr.innerText,
                ja: mfr.attributes.switch.value
            });
        });
    }
    let searchSelectorHtml = '<h3 class="buylinks__shop-title">Search options</h3><div>';
    searchSelectorHtml += generateSearchList(origins, 'Origins');
    searchSelectorHtml += generateSearchList(chars, 'Characters');
    searchSelectorHtml += generateSearchList(mfrs, 'Manufacturers');
    if(title) {
        searchSelectorHtml += `<div class="buylinks__termselector">Title:
        <input id='title_1' type='checkbox' class='buylinks__termselector__checkbox' ${searchSettings.title === 'yes' ? "checked='checked'" : ''} data-en='${title.en.replace("'", '&apos;')}' data-ja='${title.ja.replace("'", '&apos;')}''/>
        <label class='buylinks__label' for='title_1'>${title.en}</label></div>`;
    }
    searchSelectorHtml += '</div>';
    //prep buy block
    let shopHtml = renderShopBlock();
    let shopSettingsHtml = '';
    shops.forEach((shop) => {
        shopSettingsHtml += `<div class='buylinks__settings__setting-title'>${shop.display}:</div>
        <div class='buylinks__settings__setting-group'>
<input id='${shop.key}_visibility_no' name='${shop.key}_visibility' type='radio' value='false' ${shopConfig[shop.key].display === 'true'? '' : "checked='checked'"}/><label class='buylinks__label' for='${shop.key}_visibility_no'>No</label>
<input id='${shop.key}_visibility_yes' name='${shop.key}_visibility' type='radio' value='true' ${shopConfig[shop.key].display === 'true'? "checked='checked'" : ''}/><label class='buylinks__label' for='${shop.key}_visibility_yes'>Yes</label>
        </div>`
    });
    shopSettingsHtml += '<div style="clear: both;"></div>';
    const buyBox = `<section><h2>Buy!<nav class="actions"><a href="#" id="buylinks-settings-button" title="Settings"><span class="tiny-icon-only icon-sliders"></a></nav></h2>
<div id='buylinks-settings' class='form buylinks--hidden'>
<h3 class="buylinks__shop-title">General settings</h3>
<div class='buylinks__settings__setting-title'>Target new window:</div>
<div class='buylinks__settings__setting-group'>
<input id='targetblank_no' name='targetblank' type='radio' value='no' ${targetBlank ? '' : "checked='checked'"}/><label class='buylinks__label' for='targetblank_no'>No</label>
<input id='targetblank_yes' name='targetblank' type='radio' value='yes' ${targetBlank ? "checked='checked'" : ''}/><label class='buylinks__label' for='targetblank_yes'>Yes</label>
</div>
<div style="clear: both;"></div>
<h3 class="buylinks__shop-title">Default checkboxes</h3>
<div class='buylinks__settings__setting-title'>Origins:</div>
<div class='buylinks__settings__setting-group'>
<input id='origin_default_none' name='origin_default' type='radio' value='none' ${searchSettings.origins === 'none' ? "checked='checked'" : ''}/><label class='buylinks__label' for='origin_default_none'>None</label>
<input id='origin_default_first' name='origin_default' type='radio' value='first' ${searchSettings.origins === 'first' ? "checked='checked'" : ''}/><label class='buylinks__label' for='origin_default_first'>First</label>
<input id='origin_default_all' name='origin_default' type='radio' value='all' ${searchSettings.origins === 'all' ? "checked='checked'" : ''}/><label class='buylinks__label' for='origin_default_all'>All</label>
</div>
<div class='buylinks__settings__setting-title'>Characters:</div>
<div class='buylinks__settings__setting-group'>
<input id='char_default_none' name='char_default' type='radio' value='none' ${searchSettings.characters === 'none' ? "checked='checked'" : ''}/><label class='buylinks__label' for='char_default_none'>None</label>
<input id='char_default_first' name='char_default' type='radio' value='first' ${searchSettings.characters === 'first' ? "checked='checked'" : ''}/><label class='buylinks__label' for='char_default_first'>First</label>
<input id='char_default_all' name='char_default' type='radio' value='all' ${searchSettings.characters === 'all' ? "checked='checked'" : ''}/><label class='buylinks__label' for='char_default_all'>All</label>
</div>
<div class='buylinks__settings__setting-title'>Manufacturers:</div>
<div class='buylinks__settings__setting-group'>
<input id='mfr_default_none' name='mfr_default' type='radio' value='none' ${searchSettings.manufacturers === 'none' ? "checked='checked'" : ''}/><label class='buylinks__label' for='mfr_default_none'>None</label>
<input id='mfr_default_first' name='mfr_default' type='radio' value='first' ${searchSettings.manufacturers === 'first' ? "checked='checked'" : ''}/><label class='buylinks__label' for='mfr_default_first'>First</label>
<input id='mfr_default_all' name='mfr_default' type='radio' value='all' ${searchSettings.manufacturers === 'all' ? "checked='checked'" : ''}/><label class='buylinks__label' for='mfr_default_all'>All</label>
</div>
<div class='buylinks__settings__setting-title'>Title:</div>
<div class='buylinks__settings__setting-group'>
<input id='title_default_no' name='title_default' type='radio' value='no' ${searchSettings.title === 'no' ? "checked='checked'" : ''}/><label class='buylinks__label' for='title_default_no'>No</label>
<input id='title_default_yes' name='title_default' type='radio' value='yes' ${searchSettings.title === 'yes' ? "checked='checked'" : ''}/><label class='buylinks__label' for='title_default_yes'>Yes</label>
</div>
<div style="clear: both;"></div>
<h3 class="buylinks__shop-title">Shop visibility</h3>
${shopSettingsHtml}
<button id='buylinks-save-button' class='buylinks__savebutton'>Save</button>
<div id='buylinks-savesettings-text' class='buylinks--hidden'>Saved!</div>
</div>
<div class='form'>
${searchSelectorHtml}
<div id='buylinks-shoparea'>${shopHtml}</div>
</div>
</section>`;
    let template = document.createElement('template');
    template.innerHTML = buyBox;
    document.querySelector("#wide .wrapper section").after(template.content.firstChild);
    let checkboxes = document.getElementsByClassName('buylinks__termselector__checkbox');
    Array.from(checkboxes).forEach((element) => {
        element.addEventListener('click', updateSearch);
    });
    document.body.addEventListener('click', (event) => {
        if (event.target.nodeName == 'A' && event.target.getAttribute('aria-disabled') == 'true') {
            event.preventDefault();
        }
    });
    document.getElementById('buylinks-settings-button').addEventListener('click', (event) => {
        event.preventDefault();
        document.getElementById('buylinks-settings').classList.toggle("buylinks--hidden");
    });
    document.getElementById('buylinks-save-button').addEventListener('click', (event) => {
        saveSettings();
    });
    updateSearch();
})();