картинки
// ==UserScript==
// @name @pic
// @namespace нету
// @version 0
// @description картинки
// @author жди
// @match *://lolz.live/*
// @match *://zelenka.guru/*
// @match *://lolz.guru/*
// @match *://lolz.market/*
// @match *://zelenka.market/*
// @match *://lzt.market/*
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=lolz.live
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @connect duckduckgo.com
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
const AUTOSEND_SETTING_KEY = "autosend_enabled";
let autoSendEnabled = GM_getValue(AUTOSEND_SETTING_KEY, false);
let lastQuery = '';
let debounceTimer = null;
let suppressObserver = false;
let cmdId = null;
init();
function initObserver() {
const observer = new MutationObserver(() => {
if (suppressObserver) return;
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const text = editor.innerText.trim();
if (!text.startsWith('@pic ')) {
removeResults();
lastQuery = '';
return;
}
const query = text.slice('@pic '.length).trim();
if (!query) {
removeResults();
lastQuery = '';
return;
}
if (query === lastQuery && document.querySelector('.pic-results-renderer')) {
return;
}
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
const currentEditor = document.querySelector('.tiptap.ProseMirror');
if (!currentEditor) return;
const currentText = currentEditor.innerText.trim();
if (!currentText.startsWith('@pic ')) {
removeResults();
lastQuery = '';
return;
}
const currentQuery = currentText.slice('@pic '.length).trim();
if (!currentQuery) {
removeResults();
lastQuery = '';
return;
}
if (currentQuery === lastQuery && document.querySelector('.pic-results-renderer')) {
return;
}
try {
const images = await getImages(currentQuery);
lastQuery = currentQuery;
renderImages(images);
} catch (err) {
console.error('[pic] req failed:', err);
removeResults();
}
}, 400);
});
observer.observe(document.body, {
childList: true, subtree: true, characterData: true
});
}
function addStyles() {
GM_addStyle(`
.pic-results-renderer {
position: absolute;
left: 60px;
bottom: calc(100% + 8px);
z-index: 9999;
pointer-events: none;
}
.pic-popup {
pointer-events: auto;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 8px;
width: min(360px, calc(100vw - 80px));
max-height: 700px;
overflow-y: auto;
padding: 8px;
border-radius: 8px;
background: #2b2b2b;
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 8px 24px rgba(0,0,0,0.35);
}
.pic-result {
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
background: rgba(255,255,255,0.04);
}
.pic-result img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.pic-result:hover {
outline: 2px solid rgba(255,255,255,0.35);
}
`);
}
function registerSetting() {
if (cmdId !== null) {
GM_unregisterMenuCommand(cmdId);
}
const commandTitle = (autoSendEnabled ? "Выключить" : "Включить") + " автоотправку";
cmdId = GM_registerMenuCommand(commandTitle, () => {
autoSendEnabled = !autoSendEnabled;
GM_setValue(AUTOSEND_SETTING_KEY, autoSendEnabled);
registerSetting();
});
}
function removeResults() {
suppressObserver = true;
document.querySelectorAll('.pic-results-renderer').forEach(el => el.remove());
queueMicrotask(() => {
suppressObserver = false;
});
}
function renderImages(images) {
removeResults();
if (!images || !images.length) return;
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const wrapper = document.querySelector('.editor-box-wrapper') || editor.parentElement;
if (!wrapper) return;
if (getComputedStyle(wrapper).position === 'static') {
wrapper.style.position = 'relative';
}
const renderer = document.createElement('div');
renderer.className = 'pic-results-renderer';
const popup = document.createElement('div');
popup.className = 'pic-popup';
images.forEach(src => {
const item = document.createElement('div');
item.className = 'pic-result';
const img = document.createElement('img');
img.src = src;
img.alt = 'image';
img.title = src;
item.appendChild(img);
item.addEventListener('click', () => {
insertImg(src);
removeResults();
});
popup.appendChild(item);
});
renderer.appendChild(popup);
suppressObserver = true;
wrapper.appendChild(renderer);
queueMicrotask(() => {
suppressObserver = false;
});
}
function insertImg(src) {
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const text = editor.innerText;
const newText = text.replace(/^@pic(?:\s+.*)?$/m, `[IMG]${src}[/IMG]`);
editor.focus();
editor.innerText = newText;
editor.dispatchEvent(new Event('input', {bubbles: true}));
if (autoSendEnabled) setTimeout(() => unsafeWindow.$('[aria-label="send-message"]').trigger('click'), 200);
}
function request(url) {
return new Promise((resolve, reject) => {
toggleProgress('PseudoAjaxStart');
GM_xmlhttpRequest({
method: 'GET', url,
onload: res => {
toggleProgress('PseudoAjaxStop');
resolve(res.responseText);
},
onerror: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
},
ontimeout: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
},
onabort: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
}
});
});
}
async function getImages(query) {
const q = encodeURIComponent(query);
const searchUrl = `https://duckduckgo.com/?q=${q}&iax=images&ia=images`;
const html = await request(searchUrl);
const vqd = html.match(/vqd=['"]?([^'"\s&]+)['"]?/i)?.[1];
if (!vqd) {
throw new Error("vdq not found");
}
const imagesUrl = `https://duckduckgo.com/i.js?` + new URLSearchParams({
l: "ru-ru", o: "json", q: query, vqd, f: ",,,", p: "1"
});
const jsonText = await request(imagesUrl);
const data = JSON.parse(jsonText);
return data.results.map((item) => item.image);
}
function toggleProgress(eventName) {
const $ = unsafeWindow.jQuery || unsafeWindow.$;
if ($) {
$(unsafeWindow.document).trigger(eventName);
}
}
function init() {
initObserver();
addStyles();
registerSetting();
}
})();