// ==UserScript==
// @name Vinted Block User on Product Page
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Add a 'Blocca utente' button to block the seller directly from 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__block-button.user.js
// @updateURL https://raw.githubusercontent.com/duckyb/UserScripts/main/vinted__block-button.user.js
// ==/UserScript==
// --- Constants ---
const BLOCK_BTN = {
class:
'block-user-btn web_ui__Button__button web_ui__Button__filled web_ui__Button__default web_ui__Button__warning web_ui__Button__truncated',
text: 'Blocca utente',
dataTestId: 'user-block-modal--block',
};
const UNBLOCK_BTN = {
class:
'block-user-btn web_ui__Button__button web_ui__Button__filled web_ui__Button__default web_ui__Button__primary web_ui__Button__truncated',
text: 'Sblocca utente',
dataTestId: 'unblock-user-button',
};
// --- Main logic ---
(function () {
'use strict';
// --- Utility functions ---
function getCsrfToken() {
const html = document.documentElement.innerHTML;
const match = html.match(
/CSRF_TOKEN.{0,30}?([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i
);
const token = match ? match[1] : '';
if (!token) console.warn('CSRF token not found');
return token;
}
function getMyUserId() {
const html = document.documentElement.innerHTML;
const match = html.match(/member\/(\d+)-/);
if (match) {
return match[1];
}
console.warn('User ID not found');
return null;
}
function setButtonLoading(btn, loading) {
if (loading) {
btn.disabled = true;
btn.innerHTML = ``;
} else {
btn.disabled = false;
}
}
// Add spinner animation CSS
if (!document.getElementById('vinted-spinner-style')) {
const style = document.createElement('style');
style.id = 'vinted-spinner-style';
style.textContent = `@keyframes vinted-spin{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}`;
document.head.appendChild(style);
}
function blockUserOnVinted(myUserId, hatedUserId, csrfToken, btn) {
setButtonLoading(btn, true);
fetch(`https://www.vinted.it/api/v2/users/${myUserId}/user_hates`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({ hated_user_id: Number(hatedUserId) }),
credentials: 'include',
})
.then((response) => response.json())
.then((data) => {
if (!data || data.error) {
alert('Blocco fallito.');
setButtonLoading(btn, false);
} else {
// Success: turn button into Sblocca
btn.innerHTML = `${UNBLOCK_BTN.text}`;
btn.className = UNBLOCK_BTN.class;
btn.disabled = false;
btn.onclick = function (e) {
e.stopPropagation();
unblockUserOnVinted(myUserId, hatedUserId, csrfToken, btn);
};
}
})
.catch((err) => {
alert('Errore: ' + err);
setButtonLoading(btn, false);
});
}
function unblockUserOnVinted(myUserId, hatedUserId, csrfToken, btn) {
setButtonLoading(btn, true);
fetch(
`https://www.vinted.it/api/v2/users/${myUserId}/user_hates?hated_user_id=${hatedUserId}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
credentials: 'include',
}
)
.then((response) => response.json())
.then((data) => {
if (!data || data.error) {
alert('Sblocco fallito.');
setButtonLoading(btn, false);
} else {
// Success: turn button back into Blocca
btn.innerHTML = `${BLOCK_BTN.text}`;
btn.className = BLOCK_BTN.class;
btn.disabled = false;
btn.onclick = async function (e) {
e.stopPropagation();
const myUserId = getMyUserId();
const csrfToken = getCsrfToken();
const sellerId = getSellerIdFromPage();
console.log({ myUserId, csrfToken, sellerId });
if (!myUserId || !csrfToken || !sellerId) {
alert(
"Impossibile trovare il tuo ID utente, il token CSRF o l'ID del venditore."
);
return;
}
blockUserOnVinted(myUserId, sellerId, csrfToken, btn);
};
}
})
.catch((err) => {
alert('Errore: ' + err);
setButtonLoading(btn, false);
});
}
// --- Main logic ---
function getSellerIdFromPage() {
// Robust: Find all /member/ links and extract the first numeric ID
return (
[...document.querySelectorAll('a[href*="/member/"]')]
.map((a) => a.href.match(/\/member\/(\d+)/)?.[1])
.filter(Boolean)[0] || null
);
}
function insertBlockButton() {
console.log('insertBlockButton');
var askSellerList = document.querySelectorAll(
'[data-testid="ask-seller-button"]'
);
console.log('Found', askSellerList.length, 'ask-seller buttons');
askSellerList.forEach((askSellerBtn, index) => {
console.log('Processing ask-seller button', index, askSellerBtn);
// Check if our button already exists next to this ask-seller-button
const existingBtn = askSellerBtn.nextElementSibling;
if (existingBtn && existingBtn.classList.contains('block-user-btn')) {
console.log(
'Button already exists next to ask-seller button',
index,
'skipping'
);
return;
}
const btn = document.createElement('button');
btn.innerHTML = `${BLOCK_BTN.text}`;
btn.className = BLOCK_BTN.class;
btn.type = 'button';
btn.onclick = async function (e) {
e.stopPropagation();
const myUserId = getMyUserId();
const csrfToken = getCsrfToken();
const sellerId = getSellerIdFromPage();
if (!myUserId || !csrfToken || !sellerId) {
alert(
"Impossibile trovare il tuo ID utente, il token CSRF o l'ID del venditore."
);
return;
}
blockUserOnVinted(myUserId, sellerId, csrfToken, btn);
};
askSellerBtn.insertAdjacentElement('afterend', btn);
console.log('Added block button after ask-seller button', index);
});
}
// Observe for page changes (SPA navigation)
let isProcessing = false;
const observer = new MutationObserver(() => {
if (isProcessing) return;
isProcessing = true;
// Add a break to prevent infinite loading
if (document.querySelector('.block-user-btn')) {
isProcessing = false;
return;
}
console.group('Vinted Block User on Product Page');
insertBlockButton();
console.groupEnd();
isProcessing = false;
});
observer.observe(document.body, { childList: true, subtree: true });
})();
// NOTE: Due to Vinted API limitations, it is not possible to determine
// if a user is already blocked. The button always starts as "Blocca".
// The state is only toggled locally after a user action.