// ==UserScript== // @name БЕСПЛАТНЫЕ ПВЗ ОЗОН // @namespace https://github.com/Maks1mS/userscripts // @version 0.2 // @description Заменяет партнерские ПВЗ на понятные адреса // @author Maxim Slipenko // @match https://www.ozon.ru/* // @icon https://www.google.com/s2/favicons?sz=64&domain=ozon.ru // @grant GM_xmlhttpRequest // ==/UserScript== (function () { 'use strict'; function qs(...args) { return document.querySelector(...args); } function qsa(...args) { return Array.from(document.querySelectorAll(...args)); } async function GM_fetch(url, { method = "get", headers } = {}) { return new Promise((resolve, _reject) => { const blobPromise = new Promise((resolve, reject) => { GM_xmlhttpRequest({ url, method, headers, responseType: "blob", onload: response => resolve(response.response), onerror: reject, ontimeout: reject, onreadystatechange: onHeadersReceived }); }); blobPromise.catch(_reject); function onHeadersReceived(response) { const { readyState, responseHeaders, status, statusText } = response; if (readyState === 2) { // HEADERS_RECEIVED const headers = parseHeaders(responseHeaders); resolve({ headers, status, statusText, ok: status.toString().startsWith("2"), arrayBuffer: () => blobPromise.then(blob => blob.arrayBuffer()), blob: () => blobPromise, json: () => blobPromise.then(blob => blob.text()).then(text => JSON.parse(text)), text: () => blobPromise.then(blob => blob.text()), }); } } }); } function parseHeaders(headersString) { class Headers { get(key) { return this[key.toLowerCase()]; } } const headers = new Headers(); for (const line of headersString.trim().split("\n")) { const [key, ...valueParts] = line.split(":"); // last-modified: Fri, 21 May 2021 14:46:56 GMT headers[key.trim().toLowerCase()] = valueParts.join(":").trim(); } return headers; } const observers = new WeakMap(); function addObserver(element, callback, config = { childList: true }) { if (element && !observers.has(element)) { const observer = new MutationObserver(callback); observer.observe(element, config); observers.set(element, observer); } } async function main() { const result = await GM_fetch('https://free-ozon-dpr.vercel.app/merged-data.json'); const json = await result.json() function updateInfo(node) { const text = node.textContent; const id = text.split(' ').at(-1); const pvz = json.find(obj => obj.id === id); if (pvz) { node.title = node.textContent; node.textContent = pvz.address; } } function handleElement(element) { if (element) { updateInfo(element); addObserver(element, () => { updateInfo(element); }); } } function updateInfoALL() { const headerAddress = qs('[data-widget="addressBookBarWeb"] .tsBody400Small'); const commonAddressBook = qsa('[data-widget="commonAddressBook"] .tsBody500Medium'); const delivery = qsa('[data-widget="orderDeliveryDetails"] .tsBody500Medium'); const elements = [headerAddress, ...commonAddressBook, ...delivery]; elements.forEach(handleElement); } function updateInfoPeriodically(interval) { setInterval(updateInfoALL, interval); updateInfoALL(); } async function onSelectorAdd(targetNode, selector, callback) { function check(s) { const r = qs(s); if (r) { callback(r); } } function checkSelector() { if (Array.isArray(selector)) { selector.forEach(check); } check(selector); } checkSelector(); function handle(mutationsList) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { checkSelector(); } } } addObserver(targetNode, handle, { childList: true, subtree: true }); } let called = false; function fullExecute() { if (called) return; called = true; updateInfoALL(); onSelectorAdd(document.body, [ '[data-widget="commonAddressBook"]', ], updateInfoALL); } window.addEventListener('popstate', fullExecute); window.addEventListener('load', fullExecute); document.addEventListener('DOMContentLoaded', fullExecute); if (document.readyState == "complete" || document.readyState == "loaded" || document.readyState == "interactive" ) { fullExecute(); } } main(); })();