Baslimbus Owned Toggle

패시브 페이지에서 '내 소유'만 보기 토글

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name Baslimbus Owned Toggle
// @match https://baslimbus.info/passive
// @namespace https://dlwlsdn3642.github.io/
// @description  패시브 페이지에서 '내 소유'만 보기 토글
// @version   1.0.0
// @license      MIT
// @run-at document-idle
// @grant none
// @namespace
// ==/UserScript==

(function () {
  const SEASON_KIND = 6;
  const OWNED_LS_KEY = 'selectedIdentities';
  const BTN_LABEL_HTML = '<span class="text-xl text-white">내 소유</span>';
  const BTN_ID = 'owned-support-btn';

  function injectPageScript(js) {
    const s = document.createElement('script');
    s.textContent = js;
    (document.head || document.documentElement).appendChild(s);
    s.remove();
  }

  injectPageScript(`
    (function(){
      const SEASON_KIND=${SEASON_KIND};
      const OWNED_LS_KEY='${OWNED_LS_KEY}';
      const state={on:false,idx:null};
      const norm=s=>(s??'').toString().trim().replace(/\\s+/g,'').normalize('NFC');
      const loadOwned=()=>{try{return new Set(JSON.parse(localStorage.getItem(OWNED_LS_KEY)||'[]').map(Number).filter(Number.isFinite));}catch{return new Set();}};
      async function buildIndex(){
        if(state.idx) return state.idx;
        const map=new Map();
        for(let p=0;p<200;p++){
          const r=await fetch('https://baslimbus.store/dictionary/paginated/identity?size=100&page='+p,{cache:'no-store'});
          if(!r.ok) break;
          const d=await r.json();
          const list=d.list||d;
          for(const it of (list||[])){
            const id=Number(it?.id);
            if(Number.isFinite(id)) map.set(\`\${norm(it?.character)}|\${norm(it?.name)}|\${it?.season??''}\`,id);
          }
          if(d.last===true) break;
        }
        state.idx=map;
        return map;
      }
      function isSkill(input,init){
        try{
          const u=typeof input==='string'?new URL(input,location.origin):new URL(input.url,location.origin);
          const m=(init&&init.method)?String(init.method).toUpperCase():'GET';
          return m==='GET'&&u.pathname.startsWith('/dictionary/paginated/skill/')&&u.pathname.endsWith('/'+SEASON_KIND);
        }catch{return false;}
      }
      const _fetch=window.fetch;
      window.fetch=async function(input,init){
        const res=await _fetch(input,init);
        if(!state.on||!isSkill(input,init)) return res;
        const ct=res.headers.get('content-type')||'';
        if(!ct.includes('application/json')) return res;
        let data; try{data=await res.clone().json();}catch{return res;}
        const list=Array.isArray(data?.list)?data.list:(Array.isArray(data)?data:null);
        if(!Array.isArray(list)||list.length===0) return res;
        const idx=await buildIndex();
        const owned=loadOwned();
        const filtered=list.filter(it=>{
          const key=\`\${norm(it?.sinnerName)}|\${norm(it?.identityName)}|\${it?.season??''}\`;
          const id=idx.get(key);
          return id!=null&&owned.has(id);
        });
        const body=Array.isArray(data)?filtered:{...data,list:filtered};
        return new Response(JSON.stringify(body),{status:res.status,statusText:res.statusText,headers:{'Content-Type':'application/json'}});
      };
      window.addEventListener('message',e=>{
        const m=e?.data; if(!m||m.__ownedSupport!==true) return;
        if(m.type==='toggle'){state.on=!!m.enabled; if(state.on&&!state.idx) buildIndex();}
      });
    })();
  `);

  let btn=null, on=false;

  function setBtn() {
    if (!btn) return;
    btn.classList.remove('bg-primary-400','border','border-primary-100','bg-primary-450');
    if (on) btn.classList.add('bg-primary-400','border','border-primary-100');
    else btn.classList.add('bg-primary-450');
  }

  function mask(el, ms=100){
    if(!el) return ()=>{};
    const wrap=document.createElement('div');
    wrap.style.position='fixed';
    wrap.style.zIndex='2147483647';
    wrap.style.pointerEvents='none';
    const clone=el.cloneNode(true);
    clone.style.transition='none';
    clone.style.animation='none';
    clone.style.margin='0';
    wrap.appendChild(clone);
    document.body.appendChild(wrap);
    let stop=false;
    const place=()=>{if(stop)return;const r=el.getBoundingClientRect();wrap.style.left=r.left+'px';wrap.style.top=r.top+'px';wrap.style.width=r.width+'px';wrap.style.height=r.height+'px';requestAnimationFrame(place);};
    place();
    const off=()=>{if(stop)return;stop=true;wrap.remove();};
    setTimeout(off,ms);
    return off;
  }

  function fire(el){
    ['pointerdown','mousedown','mouseup','click'].forEach(t=>el.dispatchEvent(new MouseEvent(t,{bubbles:true,cancelable:true,view:window})));
  }

  function anchor(){
    const g=document.querySelector('div.grid.grid-cols-3');
    return g?.querySelector('button:nth-of-type(2)')||null;
  }

  function reload(){
    const el=anchor(); if(!el) return;
    mask(el,100); fire(el); setTimeout(()=>fire(el),0);
  }

  function toggle(){
    on=!on; setBtn();
    window.postMessage({__ownedSupport:true,type:'toggle',enabled:on},'*');
    let i=0;(function loop(){if(anchor()){reload();return;} if(++i<10)setTimeout(loop,120);})();
  }

  function gridNode(){
    return document.querySelector('div.bg-primary-500.w-full.rounded.p-4.flex.flex-col.gap-2 div.grid.grid-cols-3')||document.querySelector('div.grid.grid-cols-3');
  }

  function upsert(){
    const g=gridNode(); if(!g) return;
    let exist=document.getElementById(BTN_ID);
    if(exist && exist.parentElement!==g) g.appendChild(exist);
    if(!exist){
      const s=g.querySelector('button, a[role="button"], a[href]');
      exist=document.createElement('button');
      exist.id=BTN_ID;
      exist.type='button';
      exist.className=s?s.className:'inline-flex items-center justify-center rounded-md border px-3 py-1 text-sm font-medium transition bg-primary-450';
      exist.style.whiteSpace='nowrap';
      exist.innerHTML=BTN_LABEL_HTML;
      exist.addEventListener('click',toggle);
      g.appendChild(exist);
    }
    btn=exist; setBtn();
  }

  let scheduled=false;
  const schedule=()=>{ if(scheduled) return; scheduled=true; requestAnimationFrame(()=>{scheduled=false; upsert();}); };

  const mo=new MutationObserver(schedule);
  mo.observe(document.documentElement,{childList:true,subtree:true});

  window.addEventListener('load',schedule);
  document.addEventListener('visibilitychange',schedule,{passive:true});
})();