SM Fake Profiles Manager

Banlist collaborative desktop & mobile avec overlay et boutons adaptés

当前为 2025-07-18 提交的版本,查看 最新版本

// ==UserScript==
// @name         SM Fake Profiles Manager
// @namespace    http://tampermonkey.net/
// @version      4.1
// @description  Banlist collaborative desktop & mobile avec overlay et boutons adaptés
// @match        *://sexemodel.com/*
// @match        *://www.sexemodel.com/*
// @match        *://m.sexemodel.com/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  const AIRTABLE_TOKEN = "patpbe8APun8JckNM.bd007388d8f9807a82733510209428202ea2a43af6d5b805b667933cd45a64bf";
  const AIRTABLE_BASE = "appUf8jMiwrXzpsP7";
  const AIRTABLE_TABLE = "BanList";

  let bannedList = [];

  async function fetchBanListFromAirtable() {
    try {
      const url = `https://api.airtable.com/v0/${AIRTABLE_BASE}/${AIRTABLE_TABLE}?pageSize=100`;
      const res = await fetch(url, {
        headers: { Authorization: `Bearer ${AIRTABLE_TOKEN}` }
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      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
        }));
    } catch (e) {
      console.warn("⚠️ Erreur Airtable : ", e);
      return [];
    }
  }

  function showQuickNotif(message) {
    const notif = document.createElement('div');
    notif.textContent = message;
    Object.assign(notif.style, {
      position: 'fixed',
      top: '20px',
      right: '20px',
      background: '#4caf50',
      color: '#fff',
      padding: '10px 15px',
      borderRadius: '5px',
      fontWeight: 'bold',
      zIndex: '999999',
      boxShadow: '0 2px 6px rgba(0,0,0,0.3)'
    });
    document.body.appendChild(notif);
    setTimeout(() => notif.remove(), 700);
  }

  async function pushBanToAirtable(profilID, profilName) {
    try {
      const url = `https://api.airtable.com/v0/${AIRTABLE_BASE}/${AIRTABLE_TABLE}`;
      const record = {
        fields: {
          ProfilID: profilID,
          ProfilName: profilName,
          Active: true
        }
      };
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${AIRTABLE_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ records: [record] })
      });
      const json = await res.json();
      if (json.error) {
        console.error("❌ Erreur Airtable :", json.error);
        alert("❌ Erreur Airtable : " + json.error.message);
      } else {
        bannedList.push({
          airtableId: json.records[0].id,
          id: profilID,
          name: profilName,
          date: json.records[0].createdTime
        });
        document.querySelectorAll(`a[href*="${profilID}"]`).forEach(a => {
          const card = a.closest('div');
          if (card) card.style.display = 'none';
        });
        showQuickNotif(`✅ Profil banni : ${profilName}`);
      }
    } catch (e) {
      console.error("❌ Exception Airtable :", e);
      alert("❌ Erreur connexion Airtable");
    }
  }

  async function deactivateBan(airtableRecordId) {
    try {
      const url = `https://api.airtable.com/v0/${AIRTABLE_BASE}/${AIRTABLE_TABLE}`;
      const res = await fetch(url, {
        method: 'PATCH',
        headers: {
          Authorization: `Bearer ${AIRTABLE_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          records: [{
            id: airtableRecordId,
            fields: { Active: false }
          }]
        })
      });
      const json = await res.json();
      if (json.error) {
        console.error("❌ Erreur déban Airtable :", json.error);
        alert("❌ Erreur déban Airtable : " + json.error.message);
      } else {
        showQuickNotif('🔄 Profil débanni');
      }
    } catch (e) {
      console.error("❌ Exception déban :", e);
      alert("❌ Erreur connexion Airtable (déban)");
    }
  }

  function isBanned(id) {
    return bannedList.some(item => item.id === id);
  }

  const style = document.createElement('style');
  style.textContent = `
    .fakeBtnThumbnail, .fakeBtnProfile {
      background:#e00;color:#fff;font-weight:bold;border:2px solid #fff;
      border-radius:5px;cursor:pointer;font-size:14px;
      box-shadow:0 0 5px rgba(0,0,0,0.5);z-index:9999;position:absolute;
    }
    .fakeBtnThumbnail {
      top:5px; right:5px; padding:3px 6px; font-size:12px;
    }
    .fakeBtnThumbnail:hover, .fakeBtnProfile:hover {background:#c00;}
    .fakeBtnProfile {
      position:relative; display:inline-block; margin:5px; padding:5px 8px;
    }
    #banConfirmPanel {
      position:fixed;top:0;left:0;right:0;bottom:0;
      background:rgba(0,0,0,0.5);
      display:flex;justify-content:center;align-items:center;z-index:10001;
    }
    #banConfirmPanel .inner {
      background:#fff;padding:20px;border-radius:8px;text-align:center;min-width:250px;
    }
    #banManager {
      position:fixed;top:0;left:0;width:100%;height:100%;
      background:rgba(0,0,0,0.6);
      display:flex;justify-content:center;align-items:center;
      z-index:10002;
    }
    #banManager .content {
      background:#fff;max-height:80%;overflow:auto;width:90%;padding:15px;border-radius:8px;
    }
    #banManager h3{margin-top:0;text-align:center;}
    #banManager ul{padding:0;list-style:none;}
    #banManager li{margin-bottom:8px;font-size:14px;display:flex;justify-content:space-between;align-items:center;}
    #banManager button{margin-left:10px;}
  `;
  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">
        <p>Confirmer le ban du profil :</p>
        <p><b>${profileName}</b> (ID: ${profileID})</p>
        <button id="confirmBan">✅ Confirmer</button>
        <button id="cancelBan">❌ Annuler</button>
      </div>`;
    document.body.appendChild(panel);
    panel.querySelector('#confirmBan').onclick = async () => {
      await pushBanToAirtable(profileID, profileName);
      panel.remove();
    };
    panel.querySelector('#cancelBan').onclick = () => panel.remove();
  }

  function showBanManager() {
    const existing = document.getElementById('banManager');
    if (existing) { existing.remove(); return; }

    const panel = document.createElement('div');
    panel.id = 'banManager';
    panel.innerHTML = `
      <div class="content">
        <h3>Banlist collaborative</h3>
        <ul id="banListItems"></ul>
        <div style="text-align:center;margin-top:10px;">
          <button id="closeBanManager">Fermer</button>
        </div>
      </div>`;
    document.body.appendChild(panel);

    const list = panel.querySelector('#banListItems');
    if (bannedList.length === 0) {
      const li = document.createElement('li');
      li.textContent = "Aucun profil banni pour le moment.";
      list.appendChild(li);
    } else {
      bannedList.forEach(item => {
        const li = document.createElement('li');
        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);
      });
    }

    panel.querySelector('#closeBanManager').onclick = () => panel.remove();
  }

  function addFakeButtonsToListing() {
    const links = document.querySelectorAll('a[href*="/escort/"]');
    const seen = new Set();
    links.forEach(link => {
      const m = link.href.match(/\/escort\/([^\/]+)-(\d+)/);
      if (!m) return;
      const name = decodeURIComponent(m[1]).replace(/-/g, ' ');
      const id = m[2];
      if (seen.has(id)) return;
      seen.add(id);
      const card = link.closest('div');
      if (!card) return;
      if (isBanned(id)) { card.style.display = 'none'; return; }
      if (getComputedStyle(card).position === 'static') card.style.position = 'relative';
      const btn = document.createElement('button');
      btn.textContent = 'FAKE';
      btn.className = 'fakeBtnThumbnail';
      btn.onclick = (e) => {
        e.preventDefault();
        e.stopPropagation();
        showConfirmPanel(id, name);
      };
      card.appendChild(btn);
    });
  }

  function addFakeButtonToProfile(id) {
    let nameText = "(inconnu)";
    const bcDesktop = document.querySelector('.breadcrumbs a.element.active');
    const bcMobile = document.querySelector('.breadcrumb a.active');
    if (bcDesktop && bcDesktop.textContent.trim()) {
      nameText = bcDesktop.textContent.trim();
    } else if (bcMobile && bcMobile.textContent.trim()) {
      nameText = bcMobile.textContent.trim();
    } else {
      const h1 = document.querySelector('h1') || document.querySelector('h2');
      if (h1 && h1.innerText && h1.innerText.trim()) {
        nameText = h1.innerText.trim();
      }
    }

    // Sur mobile, placer au-dessus de la photo si possible
    let container = document.querySelector('.main-photo') || document.querySelector('.vprof__photo') || document.querySelector('h1');
    if (!container) container = document.querySelector('h1');

    if (!container) return;

    const btn = document.createElement('button');
    btn.textContent = 'FAKE';
    btn.className = 'fakeBtnProfile';
    btn.style.display = 'block';
    btn.style.margin = '5px auto';
    btn.onclick = () => showConfirmPanel(id, nameText);

    // si c'est une photo, insérer avant
    if (container.parentNode) {
      container.parentNode.insertBefore(btn, container);
    }
  }

  function addGlobalUI() {
    const panel = document.createElement('div');
    panel.style.position = 'fixed';
    panel.style.top = '50px';
    panel.style.right = '10px';
    panel.style.background = 'rgba(255,255,255,0.9)';
    panel.style.padding = '8px';
    panel.style.border = '1px solid #ccc';
    panel.style.zIndex = '10000';
    panel.style.opacity = '0.6';
    panel.style.fontSize = '12px';
    panel.innerHTML = `<button id="manageList">📋 Gérer banlist</button>`;
    panel.onmouseenter = () => panel.style.opacity = '1';
    panel.onmouseleave = () => panel.style.opacity = '0.6';
    document.body.appendChild(panel);
    panel.querySelector('#manageList').onclick = showBanManager;
  }

  (async () => {
    bannedList = await fetchBanListFromAirtable();
    const profileMatch = window.location.pathname.match(/\/escort\/([^\/]+)-(\d+)/);
    if (profileMatch) {
      const id = profileMatch[2];
      if (isBanned(id)) {
        document.body.innerHTML = '<div style="padding:2em;font-size:16px;background:#fee;">Ce profil est marqué comme fake.</div>';
      } else {
        addFakeButtonToProfile(id);
      }
    } else {
      addFakeButtonsToListing();
    }
    addGlobalUI();
  })();

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址