// ==UserScript==
// @name Image Search Script
// @name:zh-TW Image Search Script
// @name:zh-CN Image Search Script
// @namespace https://github.com/Pixmi/image-search-script
// @version 1.1.9
// @description Long-pressing the right mouse button brings up the image search menu, offering a smooth and concise user experience.
// @description:zh-TW 長按滑鼠右鍵呼叫圖片搜尋選單,提供流暢且簡潔的使用體驗。
// @description:zh-CN 长按滑鼠右键呼叫图片搜寻选单,提供流畅且简洁的使用体验。
// @author Pixmi
// @homepage https://github.com/Pixmi/image-search-script
// @supportURL https://github.com/Pixmi/image-search-script/issues
// @icon https://github.com/Pixmi/image-search-script/raw/main/icon.svg
// @match *://*/*
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @grant GM_notification
// @connect ascii2d.net
// @license GPL-3.0
// @run-at document-body
// @noframes
// ==/UserScript==
GM_addStyle(`
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
#image-search-menu {
animation: fadeOut 200ms ease-in-out forwards;
background-color: rgba(0, 0, 0, .75);
color: rgb(255, 255, 255);
display: none;
flex-direction: column;
font-size: 16px;
width: unset;
min-width: 150px;
height: unset;
min-height: unset;
position: fixed;
top: unset;
left: unset;
z-index: 9999;
}
#image-search-menu.show {
animation: fadeIn 200ms ease-in-out forwards;
display: flex;
}
.image-search-option {
cursor: pointer;
display: block;
padding: 5px 10px;
}
.image-search-option + .image-search-option {
border-top: 1px solid rgba(255, 255, 255, .5);
}
.image-search-option:hover {
background-color: rgba(255, 255, 255, .3);
}
iframe#image-search-setting {
width: 350px !important;
height: 500px !important;
}
`);
const searchOptions = new Map([
{
label: 'Google Lens',
key: 'GOOGLE_LENS',
url: 'https://lens.google.com/uploadbyurl?url=%s'
}, {
label: 'SauceNAO',
key: 'SAUCENAO',
url: 'https://saucenao.com/search.php?url=%s'
}, {
label: 'Ascii2D',
key: 'ASCII2D',
url: ''
}, {
label: 'IQDB',
key: 'IQDB',
url: 'https://iqdb.org/?url=%s'
}, {
label: 'TinEye',
key: 'TINEYE',
url: 'https://www.tineye.com/search?url=%s'
}, {
label: 'Baidu',
key: 'BAIDU',
url: 'https://image.baidu.com/n/pc_search?queryImageUrl=%s'
}, {
label: 'Bing',
key: 'BING',
url: 'https://www.bing.com/images/searchbyimage?FORM=IRSBIQ&cbir=sbi&imgurl=%s'
}
].map(item => [item.key, item]));
(function () {
'use strict';
const hoverOpen = {
enabled: GM_getValue('HOVER_OPEN', false),
minWidth: GM_getValue('HOVER_OPEN_MIN_WIDTH', 100),
minHeight: GM_getValue('HOVER_OPEN_MIN_HEIGHT', 100)
};
const hoverCheck = (event) => {
const { target, relatedTarget } = event;
if (target.className == 'image-search-option' && relatedTarget == searchMenu.image) {
return true;
}
if (target.tagName === 'IMG' && target.width >= hoverOpen.minWidth && target.height >= hoverOpen.minHeight) {
return true;
}
return false;
};
const notey = (text) => {
GM_notification(text, false, 'https://github.com/Pixmi/image-search-script/raw/main/icon.svg');
};
document.addEventListener('mouseover', (event) => {
if (hoverOpen.enabled) {
if (hoverCheck(event)) {
searchMenu.image = event.relatedTarget;
searchMenu.open(event.target);
} else {
searchMenu.clear();
}
}
});
document.addEventListener('mousedown', (event) => {
searchMenu.holding = false;
if (event.button === 2 && event.target.nodeName === 'IMG') {
searchMenu.timer = setTimeout(() => {
searchMenu.holding = true;
searchMenu.open(event.target);
}, 200);
} else {
if (event.target !== searchMenu.pane && !event.target.classList.contains('image-search-option')) {
searchMenu.clear();
}
}
});
document.addEventListener('mouseup', (event) => {
if (event.button === 2) {
clearTimeout(searchMenu.timer);
if (searchMenu.holding) {
event.preventDefault();
}
}
});
document.addEventListener('contextmenu', (event) => {
if (searchMenu.holding) {
event.preventDefault();
} else {
searchMenu.clear();
}
});
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
searchMenu.update();
}
});
document.addEventListener('scroll', () => { searchMenu.update(); });
window.addEventListener('resize', () => { searchMenu.update(); });
class searchMenuController {
constructor() {
this.panel = null;
this.image = null;
this.holding = false;
this.timer = null;
this.init();
}
init() {
this.panel = document.createElement('div');
this.panel.id = 'image-search-menu';
this.panel.addEventListener('click', (event) => {
const action = event.target.dataset.action || false;
if (action) {
switch (action) {
case 'ASCII2D':
GM_xmlhttpRequest({
method: 'POST',
url: 'https://ascii2d.net/imagesearch/search/',
data: JSON.stringify({ uri: this.image.src }),
headers: {
'Content-Type': 'application/json',
},
timeout: 10000,
onload: function(response) {
if (response.status == 200) {
GM_openInTab(response.finalUrl, true);
} else {
notey('Ascii2D Response Error');
}
},
onerror: function(error) {
notey('Ascii2D Request Error');
},
ontimeout: function(error) {
notey('Ascii2D Request Timeout');
}
});
break;
default: {
const option = searchOptions.get(action) || false;
if (!option) break;
const url = option.url.replace('%s', encodeURIComponent(this.image.src));
GM_openInTab(url, true);
break;
}
}
}
this.clear();
});
document.body.append(this.panel);
}
open(target) {
if (target.nodeName === 'IMG') {
while (this.panel.hasChildNodes()) { this.panel.lastChild.remove(); }
for (const [key, option] of searchOptions) {
if (!GM_getValue(key, true)) continue;
const item = document.createElement('div');
item.className = 'image-search-option';
item.textContent = option.label;
item.dataset.action = key;
this.panel.append((item));
}
this.image = target;
this.update();
this.panel.classList.add('show');
}
}
update() {
if (this.image) {
const status = {
width: this.image.width,
left: this.image.x,
top: this.image.y
};
for (const key of Object.keys(status)) {
this.panel.style[key] = `${status[key]}px`;
}
}
}
clear() {
this.image = null;
this.panel.classList.remove('show');
this.panel.style.width = 0;
this.panel.style.left = 0;
this.panel.style.top = 0;
}
}
const searchMenu = new searchMenuController();
GM_registerMenuCommand('Setting', () => config.open());
const config = new GM_config({
'id': 'image-search-setting',
'css': `
#image-search-setting * {
box-sizing: border-box;
}
#image-search-setting {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 10px;
margin: 0;
}
#image-search-setting_wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#image-search-setting_buttons_holder {
text-align: center;
margin-top: auto;
}
.config_var {
margin: 5px 0 !important;
}
.field_label {
font-size: 14px !important;
}
`,
'title': 'Image Search Setting',
'fields': {
'GOOGLE_LENS': {
'type': 'checkbox',
'label': 'Google Lens',
'section': ['Search Options'],
'default': true,
},
'SAUCENAO': {
'type': 'checkbox',
'label': 'SauceNAO',
'default': true,
},
'ASCII2D': {
'type': 'checkbox',
'label': 'Ascii2D',
'default': true,
},
'IQDB': {
'type': 'checkbox',
'label': 'IQDB',
'default': true,
},
'TINEYE': {
'type': 'checkbox',
'label': 'TinEye',
'default': true,
},
'BAIDU': {
'type': 'checkbox',
'label': 'Baidu',
'default': true,
},
'BING': {
'type': 'checkbox',
'label': 'Bing',
'default': true,
},
'HOVER_OPEN': {
'type': 'checkbox',
'label': 'Enabled hover open',
'section': ['Hover images to open menu'],
'default': false,
},
'HOVER_OPEN_MIN_WIDTH': {
'label': 'Image min width (px)',
'type': 'int',
'default': 100,
},
'HOVER_OPEN_MIN_HEIGHT': {
'label': 'Image min height (px)',
'type': 'int',
'default': 100,
}
},
'events': {
'init': () => {
for (const [key] of searchOptions) { config.set(key, GM_getValue(key, true)); }
config.set('HOVER_OPEN', GM_getValue('HOVER_OPEN', false));
config.set('HOVER_OPEN_MIN_WIDTH', GM_getValue('HOVER_OPEN_MIN_WIDTH', 100));
config.set('HOVER_OPEN_MIN_HEIGHT', GM_getValue('HOVER_OPEN_MIN_HEIGHT', 100));
},
'save': () => {
for (const [key] of searchOptions) { GM_setValue(key, config.get(key)); }
GM_setValue('HOVER_OPEN', config.get('HOVER_OPEN'));
GM_setValue('HOVER_OPEN_MIN_WIDTH', config.get('HOVER_OPEN_MIN_WIDTH'));
GM_setValue('HOVER_OPEN_MIN_HEIGHT', config.get('HOVER_OPEN_MIN_HEIGHT'));
hoverOpen.enabled = config.get('HOVER_OPEN');
hoverOpen.minWidth = config.get('HOVER_OPEN_MIN_WIDTH');
hoverOpen.minHeight = config.get('HOVER_OPEN_MIN_HEIGHT');
config.close();
}
}
});
})();