您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Gestion collaborative des profils (ban & tags) avec interface améliorée
// ==UserScript== // @name pinter.pro // @namespace http://tampermonkey.net/ // @version 6.5 // @description Gestion collaborative des profils (ban & tags) avec interface améliorée // @match *://sexemodel.com/* // @match *://www.sexemodel.com/* // @match *://m.sexemodel.com/* // @grant none // ==/UserScript== (function() { 'use strict'; // ===== Airtable config ===== const AIRTABLE_TOKEN = "patpbe8APun8JckNM.bd007388d8f9807a82733510209428202ea2a43af6d5b805b667933cd45a64bf"; const AIRTABLE_BASE = "appUf8jMiwrXzpsP7"; const TABLE_BAN = "BanList"; const TABLE_TAGS = "Taglist"; const CACHE_TTL = 15 * 60 * 1000; let bannedList = []; let tagMap = {}; // ===== Tags ===== const TAGS = [ {name:"Photos pas ok",score:-1,emoji:"❌📸",type:"neg"}, {name:"Beaucoup plus vieille",score:-2,emoji:"⏳👵",type:"neg"}, {name:"Glaçon",score:-1,emoji:"🧊",type:"neg"}, {name:"Chrono",score:-1,emoji:"⏱️",type:"neg"}, {name:"Pas propre 1",score:-1,emoji:"🚿⚠️",type:"neg"}, {name:"Pas propre 2",score:-2,emoji:"🚿🚫",type:"neg"}, {name:"Pas propre 3",score:-3,emoji:"🚿❌",type:"neg"}, {name:"Prix +",score:-1,emoji:"💸🔺",type:"neg"}, {name:"Pas impliquée",score:-1,emoji:"🤷♀️",type:"neg"}, {name:"Beaucoup refaite",score:-2,emoji:"🤡",type:"neg"}, {name:"A fuir urgence",score:-3,emoji:"🤮",type:"neg"}, {name:"Photos ok",score:+1,emoji:"✅📸",type:"pos"}, {name:"Top service",score:+3,emoji:"💯",type:"pos"}, {name:"Propre++",score:+2,emoji:"✨",type:"pos"}, {name:"Respect temps",score:+1,emoji:"🕰️✅",type:"pos"} ]; // ===== Utils ===== function isElementVisible(el){ if(!el||!(el instanceof HTMLElement))return false; const s=getComputedStyle(el); if(s.display==='none'||s.visibility==='hidden'||s.opacity==='0')return false; if(!document.body.contains(el))return false; return true; } function loadBanlistFromCache(){ const raw=localStorage.getItem('pinterpro_banlist'); if(!raw)return false; const parsed=JSON.parse(raw); if(Date.now()-parsed.timestamp<CACHE_TTL){bannedList=parsed.data;return true;} return false; } function saveBanlistToCache(){ localStorage.setItem('pinterpro_banlist',JSON.stringify({timestamp:Date.now(),data:bannedList})); } function loadTaglistFromCache(){ const raw=localStorage.getItem('pinterpro_taglist'); if(!raw)return false; const parsed=JSON.parse(raw); if(Date.now()-parsed.timestamp<CACHE_TTL){tagMap=parsed.data;return true;} return false; } function saveTaglistToCache(){ localStorage.setItem('pinterpro_taglist',JSON.stringify({timestamp:Date.now(),data:tagMap})); } function showQuickNotif(msg){ const n=document.createElement('div'); n.textContent=msg; Object.assign(n.style,{position:'fixed',top:'20px',right:'20px',background:'#4caf50',color:'#fff',padding:'8px 12px',borderRadius:'5px',zIndex:'999999',fontWeight:'bold'}); document.body.appendChild(n); setTimeout(()=>n.remove(),1000); } function showSyncNotif(){ if(document.getElementById('pinter-sync'))return; const sync=document.createElement('div'); sync.id='pinter-sync'; sync.textContent='🔄 Sync…'; Object.assign(sync.style,{position:'fixed',bottom:'20px',right:'20px',background:'#333',color:'#fff',padding:'6px 10px',borderRadius:'5px',zIndex:999999}); document.body.appendChild(sync); } function hideSyncNotif(){ const el=document.getElementById('pinter-sync'); if(el)el.remove(); } // ===== Airtable calls ===== async function fetchBanListFromAirtable(){ const url=`https://api.airtable.com/v0/${AIRTABLE_BASE}/${TABLE_BAN}?pageSize=100`; const res=await fetch(url,{headers:{Authorization:`Bearer ${AIRTABLE_TOKEN}`}}); const data=await res.json(); return data.records.filter(r=>r.fields.Active===true).map(r=>({ airtableId:r.id,id:r.fields.ProfilID,name:r.fields.ProfilName||"",date:r.createdTime })); } async function fetchTaglistFromAirtable(){ const url=`https://api.airtable.com/v0/${AIRTABLE_BASE}/${TABLE_TAGS}?pageSize=1000`; const res=await fetch(url,{headers:{Authorization:`Bearer ${AIRTABLE_TOKEN}`}}); const data=await res.json(); const map={}; data.records.forEach(r=>{ if(!map[r.fields.ProfilID])map[r.fields.ProfilID]=[]; map[r.fields.ProfilID].push({name:r.fields.Tag,score:r.fields.Score}); }); return map; } async function pushBanToAirtable(profilID,profilName){ showSyncNotif(); try{ const res=await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE}/${TABLE_BAN}`,{ method:'POST',headers:{Authorization:`Bearer ${AIRTABLE_TOKEN}`,'Content-Type':'application/json'}, body:JSON.stringify({records:[{fields:{ProfilID:profilID,ProfilName:profilName,Active:true}}]}) }); hideSyncNotif(); const json=await res.json(); if(json.error){alert("❌ Erreur Airtable : "+json.error.message);return;} bannedList.push({airtableId:json.records[0].id,id:profilID,name:profilName,date:json.records[0].createdTime}); saveBanlistToCache(); showQuickNotif(`✅ Profil banni : ${profilName}`); }catch(e){hideSyncNotif();alert("❌ Erreur connexion Airtable");} } async function deactivateBan(airtableRecordId){ showSyncNotif(); try{ await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE}/${TABLE_BAN}/${airtableRecordId}`,{ method:'PATCH',headers:{Authorization:`Bearer ${AIRTABLE_TOKEN}`,'Content-Type':'application/json'}, body:JSON.stringify({fields:{Active:false}}) }); hideSyncNotif(); showQuickNotif('🔄 Profil débanni'); }catch(e){hideSyncNotif();alert("❌ Erreur connexion Airtable");} } async function setTags(profilID,tagsSelected){ showSyncNotif(); try{ for(const t of tagsSelected){ await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE}/${TABLE_TAGS}`,{ method:'POST',headers:{Authorization:`Bearer ${AIRTABLE_TOKEN}`,'Content-Type':'application/json'}, body:JSON.stringify({records:[{fields:{ProfilID:profilID,Tag:t.name,Score:t.score,Active:true}}]}) }); } hideSyncNotif(); }catch(e){hideSyncNotif();alert("❌ Erreur sync tags");} } // ===== UI ===== const style=document.createElement('style'); style.textContent=` .pinter-btn-container{position:absolute;top:5px;right:5px;display:flex;gap:6px;z-index:9999;} .pinter-btn-container button{padding:6px 8px;border-radius:6px;font-weight:bold;cursor:pointer;border:none;} .pinter-btn-ban{background:#e00;color:#fff;} .pinter-btn-tags{background:#007bff;color:#fff;} @media (max-width:768px){ .tagModal, #banManager .content{width:100%!important;height:100%!important;border-radius:0!important;max-width:none!important;max-height:none!important;} } `; document.head.appendChild(style); function showConfirmPanel(profileID,profileName){ const old=document.getElementById('banConfirmPanel');if(old)old.remove(); const panel=document.createElement('div');panel.id='banConfirmPanel'; panel.innerHTML=`<div class="inner" style="background:#fff;padding:20px;border-radius:8px;text-align:center;position:relative;"> <div style="position:absolute;top:5px;right:10px;cursor:pointer;font-size:18px;font-weight:bold;" id="closeBanX">×</div> <p style="font-weight:bold;color:#d00;">⚠️ Ce bouton est réservé aux profils arnaqueurs ou dangereux</p> <p style="font-size:13px;margin-bottom:15px;">(ex. demande de tickets Transcash, plan suspect).</p> <p>Confirmer le ban du profil :</p> <p><b>${profileName}</b></p> <button id="confirmBan">✅ Confirmer</button> <button id="cancelBan">❌ Annuler</button> </div>`; Object.assign(panel.style,{position:'fixed',top:0,left:0,right:0,bottom:0,background:'rgba(0,0,0,0.6)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:'10001'}); document.body.appendChild(panel); panel.onclick=e=>{if(e.target===panel)panel.remove();}; panel.querySelector('#closeBanX').onclick=()=>panel.remove(); panel.querySelector('#cancelBan').onclick=()=>panel.remove(); panel.querySelector('#confirmBan').onclick=async()=>{ bannedList.push({id:profileID,name:profileName,date:new Date().toISOString()}); saveBanlistToCache(); document.querySelectorAll(`a[href*="${profileID}"]`).forEach(a=>{const card=a.closest('div');if(card)card.style.display='none';}); await pushBanToAirtable(profileID,profileName); panel.remove(); }; } function showBanManager(){ const ex=document.getElementById('banManager');if(ex){ex.remove();return;} const panel=document.createElement('div');panel.id='banManager'; panel.innerHTML=`<div class="content" style="background:#fff;padding:15px;border-radius:8px;max-width:500px;width:90%;max-height:80%;overflow:auto;position:relative;"> <div class="banCloseX" style="position:absolute;top:8px;right:10px;font-size:20px;font-weight:bold;cursor:pointer;">×</div> <h3>Banlist</h3><ul id="banListItems"></ul> <div style="text-align:center;margin-top:10px;"><button id="closeBanManager">Fermer</button></div> </div>`; Object.assign(panel.style,{position:'fixed',top:0,left:0,right:0,bottom:0,background:'rgba(0,0,0,0.6)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:'10002'}); document.body.appendChild(panel); panel.onclick=e=>{if(e.target===panel)panel.remove();}; panel.querySelector('.banCloseX').onclick=()=>panel.remove(); panel.querySelector('#closeBanManager').onclick=()=>panel.remove(); const list=panel.querySelector('#banListItems'); if(bannedList.length===0){list.innerHTML="<li>Aucun profil banni.</li>";return;} bannedList.sort((a,b)=>new Date(b.date)-new Date(a.date)).forEach(item=>{ const li=document.createElement('li'); li.style.display='flex';li.style.justifyContent='space-between';li.style.marginBottom='6px'; li.innerHTML=`<span>${item.name||'(sans nom)'} [${item.id}]<br><small>${new Date(item.date).toLocaleString()}</small></span>`; const btn=document.createElement('button');btn.textContent='Déban'; btn.onclick=async()=>{await deactivateBan(item.airtableId);li.remove();}; li.appendChild(btn);list.appendChild(li); }); } function openTagModal(profilID,profilName){ const current=tagMap[profilID]||[]; const overlay=document.createElement('div');overlay.className='tagModalOverlay'; Object.assign(overlay.style,{position:'fixed',top:0,left:0,right:0,bottom:0,background:'rgba(0,0,0,0.6)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:'10001'}); const modal=document.createElement('div');modal.className='tagModal'; Object.assign(modal.style,{background:'#fff',padding:'20px',borderRadius:'8px',width:'90%',maxWidth:'500px',maxHeight:'80%',overflow:'auto',position:'relative'}); const closeX=document.createElement('div');closeX.textContent='×'; Object.assign(closeX.style,{position:'absolute',top:'8px',right:'10px',cursor:'pointer',fontSize:'20px',fontWeight:'bold'}); closeX.onclick=()=>overlay.remove(); modal.appendChild(closeX); const title=document.createElement('h3');title.textContent=`Tags pour ${profilName}`;title.style.marginBottom='10px';modal.appendChild(title); const negTitle=document.createElement('div');negTitle.textContent='🔴 Négatifs';Object.assign(negTitle.style,{fontWeight:'bold',marginTop:'10px',marginBottom:'5px'});modal.appendChild(negTitle); const negGrid=document.createElement('div');Object.assign(negGrid.style,{display:'flex',flexWrap:'wrap',gap:'6px',marginBottom:'15px'});modal.appendChild(negGrid); const posTitle=document.createElement('div');posTitle.textContent='🟢 Positifs';Object.assign(posTitle.style,{fontWeight:'bold',marginTop:'10px',marginBottom:'5px'});modal.appendChild(posTitle); const posGrid=document.createElement('div');Object.assign(posGrid.style,{display:'flex',flexWrap:'wrap',gap:'6px',marginBottom:'15px'});modal.appendChild(posGrid); const selected=new Set(current.map(t=>t.name)); TAGS.forEach(t=>{ const chip=document.createElement('div');chip.textContent=`${t.emoji} ${t.name} (${t.score>0?`+${t.score}`:t.score})`; chip.style.border='1px solid #ccc';chip.style.padding='4px 6px';chip.style.borderRadius='4px';chip.style.cursor='pointer'; if(selected.has(t.name)){chip.style.background='#007aff';chip.style.color='#fff';} chip.onclick=()=>{if(selected.has(t.name)){selected.delete(t.name);chip.style.background='';chip.style.color='';}else{selected.add(t.name);chip.style.background='#007aff';chip.style.color='#fff';}}; (t.type==='neg'?negGrid:posGrid).appendChild(chip); }); const footer=document.createElement('div');footer.style.textAlign='right';footer.style.marginTop='10px'; const cancel=document.createElement('button');cancel.textContent='Annuler';cancel.onclick=()=>overlay.remove(); const valid=document.createElement('button');valid.textContent='Valider'; valid.onclick=async()=>{ const selectedTags=TAGS.filter(t=>selected.has(t.name)); tagMap[profilID]=selectedTags;saveTaglistToCache();updateAllTagButtons(); await setTags(profilID,selectedTags); overlay.remove(); }; footer.appendChild(cancel);footer.appendChild(valid);modal.appendChild(footer); overlay.appendChild(modal); overlay.onclick=e=>{if(e.target===overlay)overlay.remove();}; document.body.appendChild(overlay); } function updateAllTagButtons(){ document.querySelectorAll('[data-profilid]').forEach(btn=>{ const id=btn.getAttribute('data-profilid'); const tags=tagMap[id]||[]; const score=tags.reduce((acc,t)=>acc+t.score,0); btn.textContent=(tags.length===0)?'#':`#${score>0?'+':''}${score}`; }); } function addButtonsToListing(){ document.querySelectorAll('a[href*="/escort/"]').forEach(link=>{ const m=link.href.match(/\/escort\/([^\/]+)-(\d+)/);if(!m)return; const id=m[2], name=decodeURIComponent(m[1]).replace(/-/g,' '); const card=link.closest('div');if(!card)return; if(!isElementVisible(card))return; if(card.querySelector('.pinter-btn-container'))return; if(bannedList.some(b=>b.id===id)){card.style.display='none';return;} if(getComputedStyle(card).position==='static')card.style.position='relative'; const containerDiv=document.createElement('div');containerDiv.className='pinter-btn-container'; const ban=document.createElement('button');ban.textContent='🚫';ban.className='pinter-btn-ban';ban.onclick=e=>{e.preventDefault();e.stopPropagation();showConfirmPanel(id,name);}; const tag=document.createElement('button');tag.textContent='#';tag.className='pinter-btn-tags';tag.setAttribute('data-profilid',id);tag.onclick=e=>{e.preventDefault();e.stopPropagation();openTagModal(id,name);}; containerDiv.appendChild(ban);containerDiv.appendChild(tag); card.appendChild(containerDiv); }); updateAllTagButtons(); } function addButtonsToProfile(id){ const bc=document.querySelector('.breadcrumbs a.element.active')||document.querySelector('h1'); const name=bc?bc.textContent.trim():"(inconnu)"; const container=document.querySelector('.main-photo')||document.querySelector('h1'); if(container){ container.style.position='relative'; const containerDiv=document.createElement('div');containerDiv.className='pinter-btn-container'; const ban=document.createElement('button');ban.textContent='🚫';ban.className='pinter-btn-ban';ban.onclick=()=>showConfirmPanel(id,name); const tag=document.createElement('button');tag.textContent='#';tag.className='pinter-btn-tags';tag.setAttribute('data-profilid',id);tag.onclick=()=>openTagModal(id,name); containerDiv.appendChild(ban);containerDiv.appendChild(tag); container.appendChild(containerDiv); updateAllTagButtons(); } } function addGlobalUI(){ const ui=document.createElement('button');ui.textContent='📋 Banlist'; Object.assign(ui.style,{position:'fixed',top:'50px',right:'10px',background:'#fff',padding:'6px',border:'1px solid #ccc',borderRadius:'4px',zIndex:10000,opacity:0.7}); ui.onmouseenter=()=>ui.style.opacity=1;ui.onmouseleave=()=>ui.style.opacity=0.7; ui.onclick=showBanManager; document.body.appendChild(ui); } // ===== Observer sécurisé ===== let domObserver; function observeDOMChanges(){ const container=document.querySelector('.content')||document.querySelector('#main')||document.body; const callback=(mutationsList)=>{ domObserver.disconnect(); addButtonsToListing(); domObserver.observe(container,{childList:true,subtree:true}); }; domObserver=new MutationObserver(callback); domObserver.observe(container,{childList:true,subtree:true}); } // ===== Init ===== (async()=>{ loadBanlistFromCache(); loadTaglistFromCache(); const m=window.location.pathname.match(/\/escort\/([^\/]+)-(\d+)/); if(m){ const id=m[2]; if(bannedList.some(b=>b.id===id)){ document.body.innerHTML='<div style="padding:2em;background:#fee;">Ce profil est marqué comme fake.</div>';return; } addButtonsToProfile(id); }else{ addButtonsToListing(); observeDOMChanges(); } addGlobalUI(); const freshBan=await fetchBanListFromAirtable();bannedList=freshBan;saveBanlistToCache(); const freshTags=await fetchTaglistFromAirtable();tagMap=freshTags;saveTaglistToCache();updateAllTagButtons(); })(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址