Enhance your AliExpress shopping experience by converting marketing links into direct product links, ensuring each product is easily accessible with a single click.
// ==UserScript== // @name AliExpress Product Link Fixer // @namespace http://tampermonkey.net/ // @version 2.0 // @license MIT // @description Enhance your AliExpress shopping experience by converting marketing links into direct product links, ensuring each product is easily accessible with a single click. // @author NewsGuyTor // @match https://*.aliexpress.com/* // @icon https://www.aliexpress.com/favicon.ico // @grant none // ==/UserScript== (function() { 'use strict'; // Initialize the MutationObserver let observer; // Main function to fix links function fixLinks() { // Temporarily disconnect the observer to prevent infinite loops if (observer) observer.disconnect(); try { removeMarketingAnchors(); rewriteAnchorsWithProductIds(); fixOrCreateLinksForDataProducts(); } catch (err) { console.error("[AliExpress Product Link Fixer v2] Error in fixLinks():", err); } // Reconnect the observer after making changes if (observer) observer.observe(document.body, { childList: true, subtree: true }); } /** * A) Remove "marketing" anchors that have /gcp/ or /ssr/ without ?productIds=... * Then unwrap them to expose individual product blocks */ function removeMarketingAnchors() { const anchors = document.querySelectorAll('a[href*="/gcp/"]:not([data-alifix-done]), a[href*="/ssr/"]:not([data-alifix-done])'); anchors.forEach(a => { if (a.dataset.alifixDone) return; // Skip already processed anchors const url = new URL(a.href); if (!url.searchParams.has('productIds')) { // Mark the anchor as processed a.dataset.alifixDone = "1"; // Remove the anchor but keep its children in the DOM unwrapAnchor(a); } }); } /** * B) Rewrite anchors that contain ?productIds=... to direct product pages */ function rewriteAnchorsWithProductIds() { const anchors = document.querySelectorAll('a[href*="/gcp/"]:not([data-alifix-done]), a[href*="/ssr/"]:not([data-alifix-done])'); anchors.forEach(a => { if (a.dataset.alifixDone) return; // Skip already processed anchors const url = new URL(a.href); const pid = url.searchParams.get('productIds'); if (pid) { a.href = `https://${url.host}/item/${pid}.html`; a.dataset.alifixDone = "1"; // Mark as processed } }); } /** * C) Ensure each <div data-product-ids="..."> has a clickable link * Either by fixing existing anchors or creating new ones */ function fixOrCreateLinksForDataProducts() { const divs = document.querySelectorAll('[data-product-ids]:not([data-alifix-done])'); divs.forEach(div => { // Mark the div as processed div.dataset.alifixDone = "1"; const pid = div.dataset.productIds; if (!pid) return; // Check if there's already an <a> inside the div const existingAnchor = div.querySelector('a[href]'); if (existingAnchor) { if (!existingAnchor.dataset.alifixDone) { existingAnchor.href = `https://${location.host}/item/${pid}.html`; existingAnchor.dataset.alifixDone = "1"; // Mark as processed } } else { // No anchor found, create one around the div const link = document.createElement('a'); link.href = `https://${location.host}/item/${pid}.html`; link.dataset.alifixDone = "1"; // Optionally, style the link to display as block to ensure full area is clickable link.style.display = "block"; // Insert the link before the div div.parentNode.insertBefore(link, div); // Move the div inside the new link link.appendChild(div); } }); } /** * Helper function to unwrap an anchor tag but keep its child elements * @param {HTMLElement} anchor - The anchor element to unwrap */ function unwrapAnchor(anchor) { const parent = anchor.parentNode; if (!parent) return; while (anchor.firstChild) { parent.insertBefore(anchor.firstChild, anchor); } parent.removeChild(anchor); } // Initial execution on page load fixLinks(); // Set up the MutationObserver to watch for dynamic content changes observer = new MutationObserver(fixLinks); observer.observe(document.body, { childList: true, subtree: true }); })();