MFC Buy Links

Adds a box for direct links to shop's searches

当前为 2021-11-18 提交的版本,查看 最新版本

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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();
})();