// ==UserScript==
// @name Vinted Buybox
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Add a custom 'Buybox' to the product details page on Vinted
// @author duckyb
// @match https://www.vinted.it/items/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=vinted.it
// @grant none
// @downloadURL https://raw.githubusercontent.com/duckyb/UserScripts/main/vinted__buybox.user.js
// @updateURL https://raw.githubusercontent.com/duckyb/UserScripts/main/vinted__buybox.user.js
// ==/UserScript==
// --- Constants ---
const SIDEBAR_CONTAINER = '.item-page-sidebar-content';
const BUYBOX_CARD_STYLE = `
.custom-buybox-card {
margin-bottom: 16px;
}
`;
(function () {
'use strict';
// Utility: wait for an element to appear in the DOM
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) return resolve(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error('Timeout waiting for ' + selector));
}, timeout);
});
}
// Function to create the buybox card element
function createBuyboxCard({
content = 'Qui ci sarà il metadata che ti interessa.',
} = {}) {
const card = document.createElement('div');
card.className =
'web_ui__Card__card web_ui__Card__overflowAuto custom-buybox-card';
card.style.cssText = BUYBOX_CARD_STYLE;
card.innerHTML = `
`;
return card;
}
// Utility: parse euro price string (e.g. "26,94 €") to float
function parseEuro(str) {
if (!str) return 0;
return parseFloat(
str
.replace(/\./g, '')
.replace(/,/, '.')
.replace(/[^\d.]/g, '')
);
}
// Map Vinted-available country names (in Italian) to ISO country codes
const COUNTRY_CODE = {
Italia: 'it',
Francia: 'fr',
Germania: 'de',
Spagna: 'es',
'Paesi Bassi': 'nl',
Belgio: 'be',
Lituania: 'lt',
Polonia: 'pl',
'Repubblica Ceca': 'cz',
Slovacchia: 'sk',
Lussemburgo: 'lu',
Portogallo: 'pt',
Finlandia: 'fi',
Estonia: 'ee',
Austria: 'at',
Ungheria: 'hu',
Slovenia: 'si',
Grecia: 'gr',
'Regno Unito': 'gb',
Irlanda: 'ie',
Romania: 'ro',
Croazia: 'hr',
Lettonia: 'lv',
Bulgaria: 'bg',
};
// Find the sidebar card container (parent of all sidebar cards)
async function insertBuyboxCard() {
// Traverse up to find the container holding multiple cards
let containers = document.querySelectorAll(SIDEBAR_CONTAINER);
if (!containers) return;
containers.forEach((container) => {
// Avoid duplicate insertion
if (container.querySelector('.custom-buybox-card')) return;
// --- Extract item price and shipping cost ---
const basePriceEl = document
.querySelector('[data-testid="item-sidebar-price-container"]')
.querySelector('p.web_ui__Text__text');
const priceEl = document
.querySelector('[data-testid="item-sidebar-price-container"]')
.querySelector('div.web_ui__Text__text');
const shippingEl = document.querySelector(
'[data-testid="item-shipping-banner"]'
);
// Extract the product title (currently selected element)
const titleEl = document.querySelector(
'h1.web_ui__Text__text.web_ui__Text__title.web_ui__Text__left'
);
const title = titleEl ? titleEl.textContent.trim() : '';
const basePrice = basePriceEl ? parseEuro(basePriceEl.textContent) : 0;
const price = priceEl ? parseEuro(priceEl.textContent) : 0;
// Protezione acquisti = priceEl - basePriceEl
const protection = price - basePrice;
// Try to extract euro value from shipping banner
let shipping = 0;
if (shippingEl) {
const match = shippingEl.textContent.match(/([\d,.]+)\s*€/);
if (match) shipping = parseEuro(match[1]);
}
const total = price + shipping;
// Format as euro strings
const totalStr = total
? total.toLocaleString('it-IT', { style: 'currency', currency: 'EUR' })
: '-';
const basePriceStr = basePrice
? basePrice.toLocaleString('it-IT', {
style: 'currency',
currency: 'EUR',
})
: '-';
const protectionStr = protection
? protection.toLocaleString('it-IT', {
style: 'currency',
currency: 'EUR',
})
: '-';
const shippingStr = shipping
? shipping.toLocaleString('it-IT', {
style: 'currency',
currency: 'EUR',
})
: '-';
// Extract the product description
const descEl = document.querySelector('[itemprop="description"]');
const description = descEl ? descEl.textContent.trim() : '';
// Extract seller name and rating
const sellerEl = document.querySelector(
'[data-testid="profile-username"]'
);
let sellerName = '';
let sellerRatingHTML = '';
let sellerProfileHref = '';
let sellerFlagImg = '';
if (sellerEl) {
sellerName = sellerEl.textContent.trim();
sellerProfileHref =
[...document.querySelectorAll('a[href*="/member/"]')]
.map((a) => a.href.match(/\/member\/\d+/))
.filter(Boolean)[0] || '';
// Find the rating div inside the seller block or globally
const ratingDiv = document.querySelector(
'div.web_ui__Rating__rating.web_ui__Rating__small'
);
if (ratingDiv) {
sellerRatingHTML = ratingDiv.outerHTML;
}
// Extract country from div[role=presentation]
const countryDiv = sellerEl
.closest('div.web_ui__Card__card')
?.querySelector('div.u-flexbox.u-align-items-baseline');
if (countryDiv) {
const countryMatch = countryDiv.textContent.match(
/(, )?([A-Za-zÀ-ÿ\s]+)$/
);
console.log('countryMatch', countryMatch);
if (countryMatch) {
const country = countryMatch[1].trim();
console.log('country', country);
const code = COUNTRY_CODE[country];
if (code) {
sellerFlagImg = `
`;
}
}
}
}
// Create our custom card with total, breakdown, divider, description, and action buttons
const card = createBuyboxCard({
content: `
${title}
Prezzo base
${basePriceStr}
Protezione acquisti
${protectionStr}
Spedizione
da: ${shippingStr}
${description}
${sellerFlagImg}
${sellerName}
${sellerRatingHTML}
`,
});
// Add click handler to simulate click on the real buy button
const customBtn = card.querySelector('#custom-buybox-buy-btn');
if (customBtn) {
customBtn.addEventListener('click', () => {
const realBtn = document.querySelector(
'[data-testid="item-buy-button"]'
);
if (realBtn) realBtn.click();
});
}
// Add click handler for offer button
const offerBtn = card.querySelector('#custom-buybox-offer-btn');
if (offerBtn) {
offerBtn.addEventListener('click', () => {
const realOfferBtn = document.querySelector(
'[data-testid="item-buyer-offer-button"]'
);
if (realOfferBtn) realOfferBtn.click();
});
}
// Add click handler for info button
const infoBtn = card.querySelector('#custom-buybox-info-btn');
if (infoBtn) {
infoBtn.addEventListener('click', () => {
const realInfoBtn = document.querySelector(
'[data-testid="ask-seller-button"]'
);
if (realInfoBtn) realInfoBtn.click();
});
}
// Insert at the top
container.insertBefore(card, container.firstChild);
console.log('Buybox card inserted', {
container,
card,
price,
shipping,
total,
});
// --- Add toggle switch inside the sidebar container, after the custom card ---
let toggle = container.querySelector('#custom-buybox-toggle');
if (!toggle) {
toggle = document.createElement('label');
toggle.id = 'custom-buybox-toggle';
toggle.style.display = 'flex';
toggle.style.alignItems = 'center';
toggle.style.gap = '8px';
toggle.style.margin = '16px 0 0 0';
toggle.innerHTML = `
Mostra card originali
`;
// Insert after the custom card
if (card.nextSibling) {
container.insertBefore(toggle, card.nextSibling);
} else {
container.appendChild(toggle);
}
// Add toggle logic with localStorage
const checkbox = toggle.querySelector('input');
// Initialize from localStorage
const saved = localStorage.getItem('customBuyboxShowOriginalCards');
const show = saved === null ? true : saved === 'true';
checkbox.checked = show;
container
.querySelectorAll(
'.web_ui__Card__card.web_ui__Card__overflowAuto:not(.custom-buybox-card)'
)
.forEach((card) => {
card.style.display = show ? '' : 'none';
});
checkbox.addEventListener('change', (e) => {
const show = checkbox.checked;
localStorage.setItem('customBuyboxShowOriginalCards', show);
container
.querySelectorAll(
'.web_ui__Card__card.web_ui__Card__overflowAuto:not(.custom-buybox-card)'
)
.forEach((card) => {
card.style.display = show ? '' : 'none';
});
});
}
});
}
// Run on page load and on SPA navigation
function run() {
insertBuyboxCard();
// Observe for SPA navigation/content changes
const observer = new MutationObserver(() => {
insertBuyboxCard();
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Wait for sidebar to appear, then run
waitForElement('.web_ui__Card__card.web_ui__Card__overflowAuto').then(run);
})();