עוזר חיפוש תמונות של lenso.ai | חיפוש תמונה

עוזר חיפוש של lenso.ai | חיפוש תמונה

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name             lenso.ai image search helper | Image Search
// @name:ar          مساعد البحث بالصور من lenso.ai | البحث بالصور
// @name:bg          Помощник за търсене на изображения в lenso.ai | Търсене по изображение
// @name:cs          Pomocník vyhledávání obrázků pro lenso.ai | Vyhledávání podle obrázku
// @name:da          lenso.ai billedsøgningshjælper | Søg efter billede
// @name:de          lenso.ai Bildersuche-Assistent | Bild-Suche
// @name:el          Βοηθός αναζήτησης εικόνων του lenso.ai | Αναζήτηση με εικόνα
// @name:en          lenso.ai image search helper | Image Search
// @name:eo          lenso.ai bildserca helpilo | Bildo-serĉo
// @name:es          Ayudante de búsqueda de imágenes de lenso.ai | Búsqueda por imagen
// @name:fi          lenso.ai-kuvahakuapuri | Kuvahaku
// @name:fr          Assistant de recherche d'images lenso.ai | Recherche par image
// @name:fr-CA       Assistant de recherche d'images lenso.ai | Recherche par image
// @name:he          עוזר חיפוש תמונות של lenso.ai | חיפוש תמונה
// @name:hr          Pomoćnik za pretraživanje slika lenso.ai | Pretraživanje po slici
// @name:hu          lenso.ai képkeresési segéd | Képkeresés
// @name:id          Pembantu pencarian gambar lenso.ai | Pencarian Gambar
// @name:it          Assistente ricerca immagini di lenso.ai | Ricerca per immagine
// @name:ja          lenso.ai 画像検索ヘルパー | 画像で検索
// @name:ka          lenso.ai-ის გამოსახულების ძიების დამხმარე | გამოსახულებით ძიება
// @name:ko          lenso.ai 이미지 검색 도우미 | 이미지로 검색
// @name:nb          lenso.ai bildesøkshjelper | Søk med bilde
// @name:nl          lenso.ai afbeeldingszoekassistent | Afbeelding zoeken
// @name:pl          Pomocnik wyszukiwania obrazów lenso.ai | Wyszukiwanie po obrazie
// @name:pt-BR       Assistente de pesquisa de imagens lenso.ai | Pesquisar por imagem
// @name:ro          Asistent de căutare imagini lenso.ai | Căutare după imagine
// @name:ru          Помощник поиска изображений lenso.ai | Поиск по изображению
// @name:sk          Pomocník vyhľadávania obrázkov lenso.ai | Vyhľadávanie podľa obrázka
// @name:sr          Помоћник за претрагу слика lenso.ai | Претрага по слици
// @name:sv          lenso.ai bildssökningshjälp | Sök efter bild
// @name:th          ตัวช่วยค้นหาภาพ lenso.ai | ค้นหาด้วยภาพ
// @name:tr          lenso.ai görüntü arama yardımcısı | Görüntüyle Ara
// @name:ug          lenso.ai سۈرەت ئىزدەش ياردەمچىسى | سۈرەت بىلەن ئىزدەش
// @name:uk          Помічник пошуку зображень lenso.ai | Пошук за зображенням
// @name:vi          Trợ thủ tìm kiếm hình ảnh lenso.ai | Tìm kiếm bằng hình ảnh
// @name:zh          lenso.ai图像搜索助手 | 以图搜图
// @name:zh-CN       lenso.ai图像搜索助手 | 以图搜图
// @name:zh-HK       lenso.ai圖像搜索助手 | 以圖搜圖
// @name:zh-SG       lenso.ai图像搜索助手 | 以图搜图
// @name:zh-TW       lenso.ai圖像搜索助手 | 以圖搜圖

// @description      lenso.ai search helper | image search by image
// @description:ar   مساعد البحث من lenso.ai | البحث بالصور
// @description:bg   Помощник за търсене в lenso.ai | Търсене по изображение
// @description:cs   Pomocník vyhledávání pro lenso.ai | Vyhledávání podle obrázku
// @description:da   lenso.ai søgningshjælper | Søg efter billede
// @description:de   lenso.ai Suchassistent | Bild-Suche
// @description:el   Βοηθός αναζήτησης του lenso.ai | Αναζήτηση με εικόνα
// @description:en   lenso.ai search helper | Image Search
// @description:eo   lenso.ai serĉa helpilo | Bildo-serĉo
// @description:es   Ayudante de búsqueda de lenso.ai | Búsqueda por imagen
// @description:fi   lenso.ai-hakuapuri | Kuvahaku
// @description:fr   Assistant de recherche lenso.ai | Recherche par image
// @description:fr-CA Assistant de recherche lenso.ai | Recherche par image
// @description:he   עוזר חיפוש של lenso.ai | חיפוש תמונה
// @description:hr   Pomoćnik za pretraživanje lenso.ai | Pretraživanje po slici
// @description:hu   lenso.ai keresési segéd | Képkeresés
// @description:id   Pembantu pencarian lenso.ai | Pencarian Gambar
// @description:it   Assistente ricerca di lenso.ai | Ricerca per immagine
// @description:ja   lenso.ai 検索ヘルパー | 画像で検索
// @description:ka   lenso.ai-ის ძიების დამხმარე | გამოსახულებით ძიება
// @description:ko   lenso.ai 검색 도우미 | 이미지로 검색
// @description:nb   lenso.ai søkshjelper | Søk med bilde
// @description:nl   lenso.ai zoekassistent | Afbeelding zoeken
// @description:pl   Pomocnik wyszukiwania lenso.ai | Wyszukiwanie po obrazie
// @description:pt-BR Assistente de pesquisa lenso.ai | Pesquisar por imagem
// @description:ro   Asistent de căutare lenso.ai | Căutare după imagine
// @description:ru   Помощник поиска lenso.ai | Поиск по изображению
// @description:sk   Pomocník vyhľadávania lenso.ai | Vyhľadávanie podľa obrázka
// @description:sr   Помоћник за претрагу lenso.ai | Претрага по слици
// @description:sv   lenso.ai sökhjälp | Sök efter bild
// @description:th   ตัวช่วยค้นหา lenso.ai | ค้นหาด้วยภาพ
// @description:tr   lenso.ai arama yardımcısı | Görüntüyle Ara
// @description:ug   lenso.ai ئىزدەش ياردەمچىسى | سۈرەت بىلەن ئىزدەش
// @description:uk   Помічник пошуку lenso.ai | Пошук за зображенням
// @description:vi   Trợ thủ tìm kiếm lenso.ai | Tìm kiếm bằng hình ảnh
// @description:zh   lenso.ai搜索助手 | 以图搜图
// @description:zh-CN lenso.ai搜索助手 | 以图搜图
// @description:zh-HK lenso.ai搜索助手 | 以圖搜圖
// @description:zh-SG lenso.ai搜索助手 | 以图搜图
// @description:zh-TW lenso.ai搜索助手 | 以圖搜圖
// @version     1.6.10.10
// @match       <all_urls>
// @match       *://lenso.ai/*
// @include     *
// @author      864907600cc, aspen138
// @run-at      document-start
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.openInTab
// @grant       GM.registerMenuCommand
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_openInTab
// @grant       GM_registerMenuCommand
// @grant        GM_addElement
// @grant        GM_xmlhttpRequest
// @connect      lenso.ai
// @namespace   greatest.deepsurf.us
// @license     GPLv3
// ==/UserScript==


// If it can not work, try to go to Network page, make `Disable Cache` checked. It may work.
// If it claims that you have reached your 10 daily  free search quota, copy the url, open browser private mode, paste the url then go.

// How did I find this method?
// I foudn this js file, https://lenso.ai/build/assets/SearchByUrlPage-ILptEkXi.js





// Modified from https://greatest.deepsurf.us/scripts/2998 by aspen138
// 2024-11-07 Add lenso.ai search option
// Infringement Contact aspen1382020 at gmail dot com to delete


// 本脚本基于 GPLv3 协议开源 http://www.gnu.org/licenses/gpl.html
// (c) 86497600cc. Some Rights Reserved.
// Default setting: Press Ctrl and click right key on a image to search.

'use strict';
var default_setting = {
    "site_list": {
        "Google": "https://lens.google.com/uploadbyurl?url={%s}",
        "Baidu": "https://graph.baidu.com/details?isfromtusoupc=1&tn=pc&carousel=0&promotion_name=pc_image_shituindex&extUiData%5bisLogoShow%5d=1&image={%s}",
        "Bing": "https://www.bing.com/images/searchbyimage?cbir=sbi&iss=sbi&imgurl={%s}",
        "TinEye": "https://www.tineye.com/search?url={%s}",
        //"Cydral": "http://www.cydral.com/#url={%s}",
        "Yandex": "https://yandex.com/images/search?rpt=imageview&url={%s}", // change "Яндекс (Yandex)" to "Yandex"
        "Sogou": "https://pic.sogou.com/ris?query=https%3A%2F%2Fimg03.sogoucdn.com%2Fv2%2Fthumb%2Fretype_exclude_gif%2Fext%2Fauto%3Fappid%3D122%26url%3D{%ss}&flag=1&drag=0",
        "360 ShiTu": "http://st.so.com/stu?imgurl={%s}",
        "SauceNAO": "https://saucenao.com/search.php?db=999&url={%s}",
        "IQDB": "https://iqdb.org/?url={%s}",
        "3D IQDB": "https://3d.iqdb.org/?url={%s}",
        "WhatAnime": "https://trace.moe/?url={%s}",
        "Ascii2D": "https://ascii2d.net/search/url/{%s}"
    },
    "site_option": ["Google", "Baidu", "Bing", "TinEye", "Yandex", "Sogou", "360 ShiTu", "SauceNAO", "IQDB", "3D IQDB", "WhatAnime", "Ascii2D"],
    "hot_key": "ctrlKey",
    "server_url": "//sbi.ccloli.com/img/upload.php"
};

/*var server_url = "//sbi.ccloli.com/img/upload.php";*/
// 请直接在设置页进行设置(Firefox 请尽量选择支持 https 的服务器)
// 地址前使用"//"表示按照当前页面设定决定是否使用 https
// 地址前使用"http://"表示强制使用 http
// 地址前使用"https://"表示强制使用 https(需确认服务器支持 ssl)
// 如果需要自己架设上传服务器的话请访问 GitHub 项目页(https://github.com/ccloli/Search-By-Image)获取服务端
// 其他可用的上传服务器如下:
// Heroku: //search-by-image.herokuapp.com/img/upload.php (支持 https)
// BeGet: http://fh13121a.bget.ru/img/upload.php (不支持 https)
// OpenShift: //searchbyimage-864907600cc.rhcloud.com/img/upload.php (支持 https)
// DigitalOcean VPS: //sbi.ccloli.com/img/upload.php (支持 https,thanks to Retaker)
// 注意,部分服务器可能仅支持 http 协议,若您选择了这些服务器,请务必注明 "http://",且若您使用的是 Firefox 浏览器,在 https 页面下将不能上传文件搜索搜索(除非设置 security.mixed_content.block_active_content 为 false)

var search_panel = null;
var setting = default_setting;
var disable_contextmenu = false;
var img_src = null;
var data_version = 0;
var last_update = 0;
var xhr = new XMLHttpRequest();
var reader = new FileReader();
reader.onload = function(file) {
    upload_file(this.result);
};
var asyncGMAPI = false;

var i18n = {
    'zh': {
        'u2s': '上传图片并搜索',
        'dh': '拖拽文件至此',
        'ca': '确认终止上传文件吗?',
        'a': '多搜',
        'n': '名称',
        'l': '地址(图片地址以 {%s} 代替)',
        'cr': '确定将所有设置初始化么?\n(初始化将清除所有所有设置,且不可逆)',
        'us': '上传完成!',
        'uf': '上传失败!',
        'sh': '热键',
        'sa': '添加',
        'sr': '重置',
        'ss': '保存',
        'sc': '取消'
    },
    'en': {
        'u2s': 'Upload image to search',
        'dh': 'Drag file to here',
        'ca': 'Are you sure to cancel uploading?',
        'a': 'All',
        'n': 'Name',
        'l': 'Location (Image URL should be replace with {%s})',
        'cr': 'Are you sure to reset all preferences (irreversible) ?',
        'us': 'Upload finished!',
        'uf': 'Upload failed!',
        'sh': 'Hot Key',
        'sa': 'Add',
        'sr': 'Reset',
        'ss': 'Save',
        'sc': 'Cancel'
    }
};
var lang = i18n[navigator.language] ? navigator.language : navigator.languages ? navigator.languages.filter(function(elem) {
    return i18n[elem];
})[0] : null;
if (lang == null) lang = 'en';

var getValue;
if (typeof GM_getValue === 'undefined' && typeof GM !== 'undefined') {
    self.GM_getValue = GM.getValue;
    self.GM_setValue = GM.setValue;
    self.GM_openInTab = GM.openInTab;
    self.GM_registerMenuCommand = GM.registerMenuCommand;
    getValue = GM.getValue;
    asyncGMAPI = true;
} else {
    getValue = function(key, init) {
        return new Promise(function(resolve, reject) {
            try {
                resolve(GM_getValue(key, init));
            } catch (e) {
                reject(e);
            }
        });
    };
}

function init() {
    return Promise.all([getValue('setting'), GM_getValue('version', 0), GM_getValue('timestamp', 0)]).then(function(res) {
        var s = res[0],
            v = res[1],
            t = res[2];
        setting = s ? JSON.parse(s) : default_setting;
        data_version = v;
        last_update = t;
        var cur = 9;

        var applyMap = {
            3: function() {
                var new_site_list = {};
                var new_site_option = [];

                for (var i in setting.site_list) {
                    // use for loop to keep order, will use array in 2.x
                    switch (i) {
                        case 'Baidu ShiTu':
                        case 'Baidu Image':
                            new_site_list['Baidu'] = default_setting.site_list['Baidu'];
                            break;

                        case 'Bing':
                        case 'Sogou':
                            new_site_list[i] = default_setting.site_list[i];
                            break;

                        default:
                            new_site_list[i] = setting.site_list[i];
                    }
                }
                new_site_list['WhatAnime'] = default_setting.site_list['WhatAnime'];

                for (var i = 0; i < setting.site_option.length; i++) {
                    if ((setting.site_option[i] === 'Baidu ShiTu' || setting.site_option[i] === 'Baidu Image') && !(/,?Baidu,?/.test(new_site_option.join(',')))) {
                        new_site_option.push('Baidu');
                    } else {
                        new_site_option.push(setting.site_option[i]);
                    }
                }
                new_site_option.push('WhatAnime');

                setting.site_list = new_site_list;
                setting.site_option = new_site_option;
            },
            4: function() {
                setting.site_list['Ascii2D'] = default_setting.site_list['Ascii2D'];
                setting.site_option.push('Ascii2D');
            },
            5: function() {
                if (setting.site_list['WhatAnime']) {
                    setting.site_list['WhatAnime'] = default_setting.site_list['WhatAnime'];
                }
            },
            6: function() {
                if (setting.site_list['Baidu']) {
                    setting.site_list['Baidu'] = default_setting.site_list['Baidu'];
                }
            },
            7: function() {
                if (setting.site_list['Yandex']) {
                    setting.site_list['Yandex'] = default_setting.site_list['Yandex'];
                }
            },
            8: function() {
                if (setting.site_list['Google']) {
                    setting.site_list['Google'] = default_setting.site_list['Google'];
                }
                if (setting.site_list['Sogou']) {
                    setting.site_list['Sogou'] = default_setting.site_list['Sogou'];
                }
            }
        }

        if (data_version < cur) {
            for (var i = data_version; i < cur; i++) {
                if (applyMap[i]) {
                    applyMap[i]();
                }
            }
            set_setting(setting);
            GM_setValue('version', data_version = cur);
        }

        var repeatTest = {};
        var finalOpt = [];
        for (var i = 0, len = setting.site_option.length; i < len; i++) {
            var cur = setting.site_option[i];
            if (!repeatTest[cur] && setting.site_list[cur]) {
                finalOpt.push(cur);
                repeatTest[cur] = 1;
            }
        }
        setting.site_option = finalOpt;

        if (setting.server_url == null || setting.server_url == '') {
            setting.server_url = default_setting.server_url;
            set_setting(setting);
        }
    });
}

var server_url = setting.server_url;

function set_setting(data) {
    GM_setValue('setting', JSON.stringify(data));
    GM_setValue('timestamp', new Date().getTime());
}

function create_panel() {
    search_panel = document.createElement('div');
    search_panel.style.cssText = 'width: 198px; font-size: 14px; text-align: center; position: absolute; color: #000; z-index: 9999999999; box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); border: 1px solid #CCC; background: rgba(255, 255, 255, 0.9); border-top-right-radius: 2px; border-bottom-left-radius: 2px; font-family: "Arial"; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;';
    document.body.appendChild(search_panel);
    var search_top = document.createElement('div');
    search_top.style.cssText = 'height: 24px; line-height: 24px; font-size: 12px; overflow: hidden; margin: 0 auto; padding: 0 5px;';
    search_top.className = 'image-search-top';
    search_top.innerHTML = '<div class="search_top_url" style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis; width: 100%; height: 24px;"></div><div class="search_top_file" style="width: 100%; height: 24px; line-height: 24px;" draggable="true"><label for="image-search-file">' + i18n[lang]['u2s'] + '</label><input type="file" id="image-search-file" accept="image/*" style="width: 0px; height: 0px; max-height: 0px; max-width: 0px; margin: 0; padding: 0;"></div><div class="search_top_progress"><progress style="width: 100%; height: 16px; vertical-align: middle; margin: 4px 0;" max="1"></progerss></div><style>.image-search-item{color: #000000; transition: all 0.2s linear; -webkit-transition: all 0.1s linear;}.image-search-item:hover{background: #eeeeee;}</style>';
    search_panel.appendChild(search_top);
    var search_item = document.createElement('div');
    search_item.style.cssText = 'width: 100%; height: 24px; line-height: 24px; cursor: pointer;';
    search_item.className = 'image-search-item';
    for (var i in setting.site_list) {
        var search_item_child = search_item.cloneNode(true);
        search_item_child.textContent = i;
        search_item_child.setAttribute('search-option', i);
        search_panel.appendChild(search_item_child);
    }

    var search_item_lenso = search_item.cloneNode(true);
    search_item_lenso.textContent = 'lenso.ai (10 daily free search)';
    search_item_lenso.setAttribute('search-option', 'lenso');
    search_panel.appendChild(search_item_lenso);

    search_item.textContent = 'All';
    search_item.setAttribute('search-option', 'all');
    search_panel.appendChild(search_item);

    var search_item_setting = search_item.cloneNode(true);
    search_item_setting.textContent = 'Setting';
    search_item_setting.setAttribute('search-option', 'setting');
    search_panel.appendChild(search_item_setting);
    search_top.getElementsByTagName('input')[0].onchange = function() {
        reader.readAsDataURL(this.files[0]);
    };
    search_panel.ondragenter = function(event) {
        event.preventDefault();
        search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['dh'];
    };
    search_panel.ondragleave = function(event) {
        event.preventDefault();
        search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['u2s'];
    };
    search_panel.ondragover = function(event) {
        search_top.getElementsByTagName('label')[0].textContent = i18n[lang]['dh'];
        event.preventDefault();
    };
    search_panel.ondrop = function(event) {
        event.stopPropagation();
        event.preventDefault();
        var files = event.target.files || event.dataTransfer.files;
        if (files[files.length - 1].type.indexOf('image') >= 0) reader.readAsDataURL(files[files.length - 1]);
    };
    search_top.getElementsByTagName('progress')[0].onclick = function() {
        if (xhr.readyState != 0 && confirm(i18n[lang]['ca']) == true) {
            xhr.abort();
            search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-24px';
        }
    };
    if (navigator.userAgent.indexOf('Firefox') >= 0) {
        var paste_node_firefox = document.createElement('div');
        paste_node_firefox.setAttribute('contenteditable', 'true');
        paste_node_firefox.className = 'image-search-paste-node-firefox';
        paste_node_firefox.style.cssText = 'width: 0!important; height: 0!important; position: absolute; overflow: hidden;';
        paste_node_firefox.addEventListener('paste', get_clipboard, false);
        search_top.appendChild(paste_node_firefox);
    }
}

function call_setting() {
    var setting_panel = document.createElement('div');
    setting_panel.style.cssText = 'width: 520px; font-size: 14px; position: fixed; color: #000; z-index: 9999999999; box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); border: 1px solid #CCC; background: rgba(255, 255, 255, 0.9); border-top-right-radius: 2px; border-bottom-left-radius: 2px; padding: 10px; left: 0; right: 0; top: 0; bottom: 0; margin: auto; font-family: "Arial"; height: 400px; max-height: 90%; overflow: auto; text-align: center; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;';
    document.body.appendChild(setting_panel);
    var setting_header = document.createElement('div');
    setting_header.style.cssText = 'width: 100%; height: 32px; line-height: 32px; font-size: 18px; line-height: 32px;';
    setting_header.className = 'image-search-setting-header';
    setting_header.textContent = 'Search By Image Setting';
    setting_panel.appendChild(setting_header);
    var setting_item = document.createElement('div');
    setting_item.style.cssText = 'width: 100%; height: 24px; line-height: 24px; margin: 1px 0;';
    setting_item.className = 'image-search-setting-title';
    setting_item.innerHTML = '<div style="text-align: center; display: inline-block; width: 30px;">' + i18n[lang]['a'] + '</div><div style="width: 100px; text-align: center; display: inline-block;">' + i18n[lang]['n'] + '</div><div style="width: 350px; text-align: center; display: inline-block;">' + i18n[lang]['l'] + '</div><div style="width: 20px; display: inline-block;"></div>';
    setting_panel.appendChild(setting_item);
    for (var i in setting.site_list) {
        var setting_item_child = setting_item.cloneNode(true);
        setting_item_child.className = 'image-search-setting-item';
        setting_item_child.innerHTML = '<div style="text-align: center; display: inline-block; width: 30px; vertical-align: middle;"><input type="checkbox"' + (setting.site_option.join('\n').indexOf(i) >= 0 ? ' checked="checked"' : '') + '></div><div style="width: 100px; text-align: center; display: inline-block;"><input style="width: 90px;" type="text" value="' + i + '"></div><div style="width: 350px; text-align: center; display: inline-block;"><input style="width: 340px;" type="text" value="' + setting.site_list[i] + '"></div><div style="text-align: center; display: inline-block; cursor: pointer; width: 20px;">×</div>';
        setting_panel.appendChild(setting_item_child);
        setting_item_child.getElementsByTagName('div')[3].onclick = function() {
            var parent = this.parentElement;
            parent.parentElement.removeChild(parent);
        };
    }
    var setting_server = document.createElement('div');
    setting_server.className = 'image-search-setting-server';
    setting_server.innerHTML = 'Upload Server <input type="text" value="' + setting['server_url'] + '" placeholder="//sbi.ccloli.com/img/upload.php" style="width: 350px;">';
    setting_panel.appendChild(setting_server);
    var setting_footer = document.createElement('div');
    setting_footer.style.cssText = 'width: 100%; height: 32px; line-height: 32px; margin-top: 5px; text-align: right;';
    setting_footer.className = 'image-search-setting-footer';
    setting_panel.appendChild(setting_footer);
    var setting_hotkey = document.createElement('div');
    var setting_add = document.createElement('div');
    var setting_reset = document.createElement('div');
    var setting_save = document.createElement('div');
    var setting_cancel = document.createElement('div');
    setting_hotkey.style.cssText = 'height: 32px; display: inline-block; text-align: left; float: left;';
    setting_add.style.cssText = 'width: 90px; height: 32px; margin: 0 5px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
    setting_reset.style.cssText = 'width: 90px; height: 32px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
    setting_save.style.cssText = 'width: 90px; height: 32px; margin: 0 5px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
    setting_cancel.style.cssText = 'width: 90px; height: 32px; background: #666; color: #FFF; display: inline-block; text-align: center; cursor: pointer;';
    setting_add.textContent = i18n[lang]['sa'];
    setting_reset.textContent = i18n[lang]['sr'];
    setting_save.textContent = i18n[lang]['ss'];
    setting_cancel.textContent = i18n[lang]['sc'];
    setting_hotkey.innerHTML = i18n[lang]['sh'] + ' <select><option value="ctrlKey"' + (setting.hot_key == 'ctrlKey' ? ' selected' : '') + '>Ctrl</option><option value="shiftKey"' + (setting.hot_key == 'shiftKey' ? ' selected' : '') + '>Shift</option><option value="altKey"' + (setting.hot_key == 'altKey' ? ' selected' : '') + '>Alt</option></select>';
    setting_footer.appendChild(setting_hotkey);
    setting_footer.appendChild(setting_add);
    setting_footer.appendChild(setting_reset);
    setting_footer.appendChild(setting_save);
    setting_footer.appendChild(setting_cancel);
    setting_add.onclick = function() {
        var setting_item_child = setting_item.cloneNode(true);
        setting_item_child.className = 'image-search-setting-item';
        setting_item_child.innerHTML = '<div style="text-align: center; display: inline-block; width: 30px; vertical-align: middle;"><input type="checkbox"></div><div style="width: 100px; text-align: center; display: inline-block;"><input style="width: 90px;" type="text"></div><div style="width: 350px; text-align: center; display: inline-block;"><input style="width: 340px;" type="text"></div><div style="text-align: center; display: inline-block; cursor: pointer; width: 20px;">×</div>';
        setting_panel.insertBefore(setting_item_child, setting_footer);
        setting_item_child.getElementsByTagName('div')[3].onclick = function() {
            var parent = this.parentElement;
            parent.parentElement.removeChild(parent);
        };
        setting_panel.scrollTop = setting_panel.scrollHeight;
    };
    setting_reset.onclick = function() {
        if (confirm(i18n[lang]['cr']) == true) {
            setting = default_setting;
            set_setting(setting);
            setting_panel.outerHTML = '';
            if (search_panel != null) {
                search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
                search_panel = null;
            }
            call_setting();
        }
    };
    setting_save.onclick = function() {
        var setting_items = document.getElementsByClassName('image-search-setting-item');
        var setting_data = {
            "site_list": {},
            "site_option": [],
            "hot_key": null,
            "server_url": null
        };
        for (var i = 0; i < setting_items.length; i++) {
            if (setting_items[i].getElementsByTagName('input')[1].value != '') {
                if (setting_items[i].getElementsByTagName('input')[0].checked) setting_data.site_option.push(setting_items[i].getElementsByTagName('input')[1].value);
                setting_data.site_list[setting_items[i].getElementsByTagName('input')[1].value] = setting_items[i].getElementsByTagName('input')[2].value;
            }
        }
        setting_data.hot_key = setting_hotkey.getElementsByTagName('select')[0].value;
        setting_data.server_url = document.getElementsByClassName('image-search-setting-server')[0].getElementsByTagName('input')[0].value;
        if (setting_data.server_url == null || setting_data.server_url == '') {
            setting_data.server_url = default_setting.server_url;
        }
        setting = setting_data;
        server_url = setting.server_url;
        set_setting(setting);
        document.body.removeChild(setting_panel);
        if (search_panel != null) {
            search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
            search_panel = null;
        }
    };
    setting_cancel.onclick = function() {
        document.body.removeChild(setting_panel);
    };
}

function upload_file(data) {
    if (xhr.readyState != 0) xhr.abort();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                img_src = xhr.responseText;
                search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '0px';
                search_panel.getElementsByClassName('search_top_url')[0].textContent = i18n[lang]['us'];
            }
        }
    };
    xhr.upload.onprogress = function(event) {
        search_panel.getElementsByTagName('progress')[0].value = event.loaded / event.total;
    };
    xhr.onerror = function() {
        alert(i18n[lang]['uf']);
    };
    var form = new FormData();
    xhr.open('POST', server_url);
    form.append('imgdata', data);
    xhr.send(form);
    search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-48px';
}

function get_clipboard(event) {
    var items = event.clipboardData.items;
    if (items[items.length - 1].type.indexOf('image') >= 0) reader.readAsDataURL(items[items.length - 1].getAsFile());
}

function hide_panel() {
    if (!search_panel || !search_panel.parentElement) return;
    img_src = null;
    search_panel.parentElement && search_panel.parentElement.removeChild(search_panel); // Remove search panel to fix the bug that it may be left in some WYSIWYG editor (eg. MDN WYSIWYG editor). See issue: http://tieba.baidu.com/p/3682475061
    document.removeEventListener('paste', get_clipboard, false);
}

function upload_blob_url(url) {
    if (!url) return;
    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.responseType = 'blob';
    req.onload = function() {
        reader.readAsDataURL(req.response);
    };
    req.onerror = function() {
        alert(i18n[lang]['uf']);
    };
    req.send();
}

document.addEventListener(document.onpointerdown === undefined ? 'mousedown' : 'pointerdown', function(event) {
    //console.log('Search Image >>\nevent.ctrlKey: '+event.ctrlKey+'\nevent.button: '+event.button+'\nevent.target:'+event.target+'\nevent.target.tagName: '+event.target.tagName+'\nevent.target.src: '+event.target.src+'\nevent.pageX: '+event.pageX+'\nevent.pageY: '+event.pageY+'\ndocument.documentElement.clientWidth: '+document.documentElement.clientWidth+'\ndocument.documentElement.clientHeight: '+document.documentElement.clientHeight+'\ndocument.documentElement.scrollWidth: '+document.documentElement.scrollWidth+'\ndocument.documentElement.scrollHeight: '+document.documentElement.scrollHeight+'\ndocument.documentElement.scrollLeft: '+document.documentElement.scrollLeft+'\ndocument.documentElement.scrollTop: '+document.documentElement.scrollTop);
    if (disable_contextmenu == true) {
        document.oncontextmenu = null;
        disable_contextmenu = false;
    }
    if (event[setting.hot_key] == true && event.button == 2) {
        if (search_panel == null) create_panel();
        // GM 4.x api is async, so we cannot update it in time
        else {
            if (!asyncGMAPI) {
                if (last_update != GM_getValue('timestamp', 0)) {
                    last_update = GM_getValue('timestamp', 0);
                    search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
                    setting = GM_getValue('setting') ? JSON.parse(GM_getValue('setting')) : default_setting;
                    create_panel();
                } else document.body.appendChild(search_panel);
            } else {
                document.body.appendChild(search_panel);
                GM_getValue('timestamp', 0).then(function(t) {
                    if (last_update != t) {
                        last_update = t;
                        search_panel.parentElement && search_panel.parentElement.removeChild(search_panel);
                        GM_getValue('setting').then(function(s) {
                            setting = s ? JSON.parse(s) : default_setting;
                        });
                    }
                });
            }
        }
        search_panel.style.left = (document.documentElement.offsetWidth + (document.documentElement.scrollLeft || document.body.scrollLeft) - event.pageX >= 200 ? event.pageX : event.pageX >= 200 ? event.pageX - 200 : 0) + 'px';
        search_panel.style.top = (event.pageY + search_panel.offsetHeight < (document.documentElement.scrollTop || document.body.scrollTop) + document.documentElement.clientHeight ? event.pageY : event.pageY >= search_panel.scrollHeight ? event.pageY - search_panel.offsetHeight : 0) + 'px';
        // Firefox doesn't support getComputedStyle(element).marginLeft/marginRight and it would return "0px" while the element's margin is "auto". See bugzila/381328.
        //search_panel.style.marginLeft = '-' + (navigator.userAgent.indexOf('Firefox') < 0 ? getComputedStyle(document.body).marginLeft : (document.documentElement.offsetWidth - document.body.offsetWidth) / 2 + 'px');
        //search_panel.style.marginTop = '-' + getComputedStyle(document.body).marginTop;
        disable_contextmenu = true;
        document.oncontextmenu = function() {
            return false;
        };
        if (event.target.tagName.toLowerCase() == 'img' && event.target.src != null) {
            search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '0px';
            search_panel.getElementsByClassName('search_top_url')[0].textContent = event.target.src;
            if (/^data:\s*.*?;\s*base64,\s*/.test(event.target.src)) upload_file(event.target.src);
            else if (/^(?:blob:|filesystem:)/.test(event.target.src)) upload_blob_url(event.target.src);
            else img_src = event.target.src;
        } else {
            search_panel.getElementsByClassName('search_top_url')[0].style.marginTop = '-24px';
            var firefoxPasteNode = document.getElementsByClassName('image-search-paste-node-firefox')[0];
            if (navigator.userAgent.indexOf('Firefox') >= 0 && firefoxPasteNode) {
                firefoxPasteNode.innerHTML = '';
                firefoxPasteNode.focus();
            } else document.addEventListener('paste', get_clipboard, false);
        }
    } else if (search_panel != null) {
        if (event.target.compareDocumentPosition(search_panel) == 10 || event.target.compareDocumentPosition(search_panel) == 0) {
            if (event.target.className == 'image-search-item' && event.button == 0) {
                // Helper function to encode the URL if needed
                const encodeUrlIfNeeded = (url, rsrc) => {
                    if (url.split(/{%s+}/).shift().indexOf('?') >= 0) {
                        const token = url.match(/{%s+}/)[0];
                        for (let j = 0; j < token.length - 3; j++) {
                            rsrc = encodeURIComponent(rsrc);
                        }
                    }
                    return rsrc;
                };

                // Helper function to make an API call to lenso.ai
                const searchLensoApi_backup20241213 = (rsrc) => {
                    return fetch("https://lenso.ai/api/upload/process/url", {
                            mode: 'no-cors',
                            //mode: 'cors',
                            method: "POST",
                            headers: {
                                "Content-Type": "application/json"
                            },
                            body: JSON.stringify({
                                "url": rsrc
                            })
                        })
                        .then(response => {
                            if (response.ok) {
                                console.log("response=",response);
                                return response.json();
                            } else {
                                console.log("response=",response);
                                handleError(response.status);
                                return null;
                            }
                        })
                        .catch(error => {
                            console.error("Error occurred:", error);
                            handleError(500);
                            return null;
                        });
                };

                const searchLensoApi = (rsrc) => {
                    return new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: "POST",
                            url: "https://lenso.ai/api/upload/process/url",
                            headers: {
                                "Content-Type": "application/json"
                            },
                            data: JSON.stringify({
                                "url": rsrc
                            }),
                            onload: function(response) {
                                if (response.status === 200) {
                                    try {
                                        const responseData = JSON.parse(response.responseText);
                                        console.log("response=", response);
                                        resolve(responseData);
                                    } catch (error) {
                                        console.error("Error parsing JSON:", error);
                                        handleError(500);
                                        resolve(null);
                                    }
                                } else {
                                    console.log("response=", response);
                                    handleError(response.status);
                                    resolve(null);
                                }
                            },
                            onerror: function(error) {
                                console.error("Error occurred:", error);
                                handleError(500);
                                resolve(null);
                            }
                        });
                    });
                };

                // Helper function to handle site searches
                const performSiteSearch = (turl, rsrc) => {
                    const encodedRsrc = encodeUrlIfNeeded(turl, rsrc);
                    GM_openInTab(turl.replace(/{%s+}/, encodedRsrc), event[setting.hot_key]);
                };

                // Main switch statement handling search options
                switch (event.target.getAttribute('search-option')) {
                    case 'lenso':
                        if (img_src != null) {
                            for (let i = setting.site_option.length - 1; i >= 0; i--) {
                                const rsrc = encodeUrlIfNeeded(setting.site_list[setting.site_option[i]], img_src);

                                // Make API call and open result in a new tab
                                searchLensoApi(rsrc).then(data => {
                                    if (data && data.id) {
                                        const locale = "en"; // Set to current locale if needed
                                        const result_url = `https://lenso.ai/${locale}/results/${data.id}`;
                                        GM_openInTab(result_url, event[setting.hot_key]);
                                    }
                                });
                            }
                            hide_panel();
                        }
                        break;

                    case 'all':
                        if (img_src != null) {
                            for (let i = setting.site_option.length - 1; i >= 0; i--) {
                                performSiteSearch(setting.site_list[setting.site_option[i]], img_src);
                            }
                            for (let i = setting.site_option.length - 1; i >= 0; i--) {
                                const rsrc = encodeUrlIfNeeded(setting.site_list[setting.site_option[i]], img_src);

                                // Make API call and open result in a new tab
                                searchLensoApi(rsrc).then(data => {
                                    if (data && data.id) {
                                        const locale = "en"; // Set to current locale if needed
                                        const result_url = `https://lenso.ai/${locale}/results/${data.id}`;
                                        GM_openInTab(result_url, event[setting.hot_key]);
                                    }
                                });
                            }
                            hide_panel();
                        }
                        break;

                    case 'setting':
                        call_setting();
                        hide_panel();
                        break;

                    default:
                        if (img_src != null) {
                            const searchOption = event.target.getAttribute('search-option');
                            const turl = setting.site_list[searchOption];
                            performSiteSearch(turl, img_src);
                            hide_panel();
                        }
                }

            } else if (event.button != 0) hide_panel();
        } else hide_panel();
    }
}, true);

if (typeof GM_registerMenuCommand !== 'undefined') {
    var gm_callsetting = GM_registerMenuCommand('Search By Image Setting', call_setting);
}
init();