Steam🎮-100%

Steam "-100%"

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Steam🎮-100%
// @description  Steam "-100%"
// @version      1.1.8
// @match        *store.steampowered.com/app/*
// @match        https://online-fix.me/games/*
// @run-at       document-end
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addValueChangeListener
// @grant        GM_xmlhttpRequest
// @connect      igg-games.com
// @connect      skidrowreloaded.com
// @connect      fitgirl-repacks.site
// @connect      online-fix.me
// @license      MIT
// @namespace    DEV314R
// ==/UserScript==

(function(){
'use strict';

// ---------- Utils ----------
const $ = s => document.querySelector(s);
const $$ = s => [...document.querySelectorAll(s)];

// ---------- Labels ----------
const LABELS = {
    dl: { en:"Download",ar:"تحميل",bg:"Изтегли",
  "zh-CN":"下载","zh-TW":"下載",
  cs:"Stáhnout",da:"Download",nl:"Downloaden",
  fi:"Lataa", fr:"Télécharger",de:"Herunterladen",
  el:"Λήψη",hu:"Letöltés", id:"Unduh",
  it:"Scarica",ja:"ダウンロード",ko:"다운로드",
  no:"Last ned",pl:"Pobierz",
  "pt-PT":"Transferir","pt-BR":"Baixar",
  ro:"Descărcare", ru:"Скачать",
  "es-ES":"Descargar","es-LA":"Bajar",
  sv:"Ladda ner", th:"ดาวน์โหลด", tr:"İndir",
  uk:"Завантажити", vi:"Tải xuống"},

    multi: {en:"Multiplayer",ar:"متعدد اللاعبين",bg:"Мултиплейър",
  "zh-CN":"多人游戏","zh-TW":"多人遊戲",
  cs:"Multiplayer",da:"Flerspiller", nl:"Multiplayer",
  fi:"Moninpeli",fr:"Multijoueur",de:"Mehrspieler",
  el:"Πολλαπλών παικτών", hu:"Többjátékos",id:"Multipemain",
  it:"Multigiocatore",ja:"マルチプレイヤー",ko:"멀티플레이어",
  no:"Flerspiller",pl:"Wieloosобowy",
  "pt-PT":"Multijogador","pt-BR":"Multijogador",
  ro:"Multiplayer",ru:"Многопользовательская игра",
  "es-ES":"Multijugador","es-LA":"Multijugador",
  sv:"Flerspelarläge",th:"ผู้เล่นหลายคน", tr:"Çok Oyunculu",
  uk:"Багатокористувацька",vi:"Nhiều người chơi"},

    source: { en:"Source",ar:"المصدر",bg:"Източник",
  "zh-CN":"来源","zh-TW":"來源",
  cs:"Zdroj",da:"Kilde",nl:"Bron",
  fi:"Lähde",fr:"Source",de:"Quelle",
  el:"Πηγή",hu:"Forrás",id:"Sumber",
  it:"Fonte",ja:"ソース",ko:"출처",
  no:"Kilde",pl:"Źródło",
  "pt-PT":"Fonte","pt-BR":"Fonte",
  ro:"Sursă",ru:"Источник",
  "es-ES":"Fuente","es-LA":"Fuente",
  sv:"Källa",th:"แหล่งที่มา",tr:"Kaynak",
  uk:"Джерело",vi:"Nguồn"},

    unavailable: { en:"unavailable",ar:"غير متوفر",bg:"Недостъпно",
  "zh-CN":"不可用","zh-TW":"不可用",
  cs:"Nedostupné",da:"Ikke tilgængelig",nl:"Niet beschikbaar",
  fi:"Ei saatavilla",fr:"indisponible",de:"Nicht verfügbar",
  el:"Μη διαθέσιμο",hu:"Nem elérhető",id:"Tidak tersedia",
  it:"Non disponibile",ja:"利用不可",ko:"사용 불가",
  no:"Utilgjengelig",pl:"Niedostępne",
  "pt-PT":"Indisponível","pt-BR":"Indisponível",
  ro:"Indisponibil",ru:"Недоступно",
  "es-ES":"No disponible","es-LA":"No disponible",
  sv:"Inte tillgängligt",th:"ไม่พร้อมใช้งาน",tr:"Mevcut değil",
  uk:"Недоступно",vi:"Không khả dụng" }

};
const lang = (navigator.language||'en').toLowerCase();
const langKey = (()=>{if(lang.startsWith('pt-br'))return'pt-BR';if(lang.startsWith('pt-pt'))return'pt-PT';if(lang.startsWith('zh-tw')||lang.startsWith('zh-hk'))return'zh-TW';if(lang.startsWith('zh'))return'zh-CN';if(lang.startsWith('es-'))return['es-mx','es-ar','es-co','es-cl','es-pe','es-ve','es-uy','es-bo','es-py','es-cr','es-pa','es-do','es-ec','es-gt','es-hn','es-ni','es-sv','es-pr','es-419'].includes(lang)?'es-LA':'es-ES';return lang.split('-')[0]||'en';})();
const dlLabel = LABELS.dl[langKey]||LABELS.dl.en;
const multiLabel = LABELS.multi[langKey]||LABELS.multi.en;
const sourceLabel = LABELS.source[langKey]||LABELS.source.en;
const unavailableLabel = LABELS.unavailable[langKey]||LABELS.unavailable.en;

const norm = s => (s||'').normalize('NFD').replace(/[\u0300-\u036f]|[©®™℠]|\?/g,'').replace(/[:'–—]/g,'').trim().toLowerCase();
const slugify = s => norm(s).replace(/\s+/g,'-');

// ---------- GM fetch ----------
const gmFetchHTML = async (url, timeout = 10000) => {
    if(typeof GM_xmlhttpRequest!=='function') return null;
    return new Promise(resolve=>{
        let done=false;
        const timer=setTimeout(()=>{if(!done){done=true;resolve(null);}},timeout);
        try{
            GM_xmlhttpRequest({
                method:'GET',url,
                onload:r=>{
                    if(!done){
                        done=true;clearTimeout(timer);
                        try{resolve(new DOMParser().parseFromString(r.responseText,'text/html'));}catch(e){resolve(null);}
                    }
                },
                onerror:()=>{if(!done){done=true;clearTimeout(timer);resolve(null);}}
            });
        }catch(e){if(!done){done=true;clearTimeout(timer);resolve(null);}}
    });
};

// ---------- Platform ----------
const defaultData={IGG:{source:'1Fichier'},Skidrow:{source:'MEGA'},FitGirl:{source:'DataNodes'}};
let platformData = GM_getValue('platformData',defaultData)||defaultData;
let platform = GM_getValue('platform','Skidrow')||'Skidrow';
platformData[platform] ||= { source: defaultData[platform].source };
GM_setValue('platformData',platformData);
let sdl = platformData[platform].source;

function setPlatform(p){platform=p;GM_setValue('platform',p);platformData[p] ||= {source:defaultData[p]?.source||''};sdl=platformData[p].source;GM_setValue('platformData',platformData);renderSourceFlyout();updateDownloadLink(true);}
function setSourceForPlatform(src){platformData[platform].source=src;GM_setValue('platformData',platformData);sdl=src;}

// ---------- Styles ----------
const style=document.createElement('style');
style.textContent=`.menu{font-family:Arial,sans-serif;font-size:1em;position:relative;user-select:none;padding:.1em;display:inline-flex}.menu-btn{background:#67c1f533;color:#67c1f5;padding:.4em .6em;border-radius:.3em;cursor:pointer}.menu-btn:hover{background:#67c1f5bb;color:#fff}.flyout{border:none;border-radius:.3em;background:#23262E;position:absolute;top:100%;left:0;display:none;z-index:10000}.menu:hover .flyout{display:block}.flyout a{display:block;padding:.4em .6em;background:#23262E;color:#ddd;text-decoration:none}.flyout a:hover{background:#85949d59;color:#fff}.menu-container{right:.75em;top:.75em;z-index:9999;position:fixed}`;
document.head.appendChild(style);

// ---------- Spinner ----------
function startLoadingAnim(btn){
    if(!btn) return;
    stopLoadingAnim(btn,'');
    const frames=["⠁","⠃","⠇","⠧","⠷","⠿","⠻","⠟","⠯","⠷","⠧","⠇","⠃"];
    let i=0;
    btn._timer=setInterval(()=>{if(!document.contains(btn)){clearInterval(btn._timer);delete btn._timer;return;}btn.textContent=frames[i=(i+1)%frames.length]+" "+btn.dataset.label;},90);
    setTimeout(()=>stopLoadingAnim(btn,btn.dataset.label),15000);
}
function stopLoadingAnim(btn,text){if(!btn)return;clearInterval(btn._timer);delete btn._timer;if(document.contains(btn))btn.textContent=text;}

// ---------- Game Name ----------
let gameName=null;
async function fetchGameNameFromStore(timeout=10000){
    const href=location.href.split('?')[0]+'?l=english';
    const doc=await gmFetchHTML(href,timeout);
    if(!doc)return null;
    const nameEl=doc?.querySelector('span[itemprop="name"]');
    if(!nameEl)return null;
    const n=norm(nameEl.textContent);
    gameName=n;return n;
}

// ---------- Download Links ----------
let skidrowLinkCache=new Map();
let LinkCache=null;

async function IGGStepByStep({slug=gameName,enc=sdl,delay=1000,timeout=10000}={}){
    if(!slug)return "❌";
    const cleanSlug=slug.replace(/(\s|-)?[^A-Za-z0-9\s]/gi,"").replace(/\s/gi,"-");
    const url=`https://igg-games.com/${cleanSlug}-free-download.html`;
    const doc=await gmFetchHTML(url,timeout);
    if(!doc)return "❌";
    const encLower=(enc||"").toLowerCase();
    const matches=[];
    const pushUnique=u=>{if(u&&!matches.includes(u))matches.push(u);};
    for(const b of doc.querySelectorAll("b.uk-heading-bullet")){
        const txt=(b.innerText||"").replace(/Link\s*:?/i,"").toLowerCase();
        if(txt.includes(encLower)){
            const parent=b.parentElement||b.closest("li,p")||b;
            for(const a of parent.querySelectorAll("a[href]"))if(/^https?:\/\//i.test(a.href))pushUnique(a.href);
        }
    }
    for(const span of doc.querySelectorAll(".uk-label.uk-label-success")){
        if(!(span.innerText||"").toLowerCase().includes("update"))continue;
        let node=span.parentElement?.nextElementSibling||span.nextElementSibling;let tries=0;
        while(node&&tries<3&&node.querySelectorAll&&node.querySelectorAll("a[href]").length===0){node=node.nextElementSibling;tries++;}
        if(!node)continue;
        for(const a of node.querySelectorAll("a[href]")){
            const text=(a.innerText||"").toLowerCase();
            if(/^https?:\/\//i.test(a.href)&&(text.includes(encLower)||text.includes((sdl||"").toLowerCase())))pushUnique(a.href);
        }
    }
    if(matches.length===0)return "❌ "+unavailableLabel;
    for(const u of matches){try{window.open(u,"_blank");}catch(e){console.warn("open failed",u,e);}await new Promise(r=>setTimeout(r,delay));}
    return matches;
}

async function SkidrowStep(slug,enc){
    if(!slug)return "❌";
    const cacheKey=slug+"::"+enc.toLowerCase();
    if(skidrowLinkCache.has(cacheKey))return skidrowLinkCache.get(cacheKey);
    const searchDoc=await gmFetchHTML(`https://www.skidrowreloaded.com/?s=${encodeURIComponent(slug)}`);
    if(!searchDoc)return "❌";
    const slugNorm=slug.replace(/-/g," ").normalize("NFD").replace(/(\s|-)?[^A-Za-z0-9\s]/gi,"").toLowerCase();
    const encNorm=enc.toLowerCase();
    const post=[...searchDoc.querySelectorAll("[class^=post] h2 a[href]")].find(a=>a.textContent.toLowerCase().includes(slugNorm));
    if(!post)return "❌ "+unavailableLabel;
    const postDoc=await gmFetchHTML(post.href);
    if(!postDoc)return "❌";
    const link=[...postDoc.querySelectorAll("[id^=tabs] p strong")].find(s=>s.textContent.toLowerCase().includes(encNorm))?.closest("p")?.querySelector("a[href]")?.href||null;
    const result=link||sourceLabel+" "+unavailableLabel;
    skidrowLinkCache.set(cacheKey,result);
    return result;
}

async function FitGirlStep(slug,enc){
    if(!slug)return '❌';
    const searchDoc=await gmFetchHTML(`https://fitgirl-repacks.site/${slug}`);
    if(!searchDoc)return '❌';
    const encNorm=(enc||'').replace(/Filehoster: /g,'').toLowerCase();
    const link=[...searchDoc.querySelectorAll('.entry-content > ul > li > a[href]')].find(a=>a.textContent.toLowerCase().includes(encNorm))?.href||null;
    return link||('❌ '+unavailableLabel);
}

// ---------- Apply Link ----------
function applyLink(dl){
    if(!dl)return;
    let span=dl.querySelector('span');if(!span){span=document.createElement('span');dl.appendChild(span);}
    const newDl=dl.cloneNode(true);dl.replaceWith(newDl);
    const btnSpan=newDl.querySelector('span')||span;
    btnSpan.dataset.label=dlLabel;
    newDl.addEventListener('click',async e=>{
        e.preventDefault();startLoadingAnim(btnSpan);
        let link;
        try{
            if(platform==='IGG'){link=await IGGStepByStep({delay:1000});stopLoadingAnim(btnSpan,Array.isArray(link)?dlLabel:link);}
            else if(platform==='FitGirl'){link=await FitGirlStep(slugify(gameName),sdl);stopLoadingAnim(btnSpan,link&&link.startsWith('http')?dlLabel:link);if(link&&link.startsWith('http'))window.open(link,'_blank');}
            else if(platform==='Skidrow'){link=await SkidrowStep(gameName,sdl);stopLoadingAnim(btnSpan,link&&link.startsWith('http')?dlLabel:link);if(link&&link.startsWith('http'))window.open(link,'_blank');}
        }catch(err){stopLoadingAnim(btnSpan,dlLabel);console.warn('Download failed',err);}
    });
}

// ---------- Reset labels uniquement Download ----------
document.addEventListener('click',e=>{
    if(!e.target.closest('.flyout')) return;
    LinkCache=null;
    const dlBtn = document.querySelector('#add314 span');
    if(dlBtn) dlBtn.textContent = dlLabel;
});

// ---------- Flyouts ----------
const sourcesByPlatform={
    IGG:['1Fichier','MegaUp.net','Mega.nz','GoFile','MixDrop','Rapidgator','Clicknupload','Bowfile','SendCM','Google Drive'],
    Skidrow:['MEGA','1FICHIER','PIXELDRAIN','MEDIAFIRE','GOFILE','VIKINGFILE','BOWFILE','1CLOUDFILE','MEGAUP','MULTI LINKS','TORRENT'],
    FitGirl:['DataNodes','FuckingFast','MultiUpload','1337x','magnet','.torrent file only','RuTor','magnet','Tapochek.net']
};
function createFlyout(label,key,items,stars=[]){
    const container=$('#category_block');if(!container)return;
    const val=key==='platform'?platform:sdl;
    const div=document.createElement('div');div.className='menu';div.dataset.type=key;
    div.innerHTML=`<div class="menu-btn">${label}: ${val}</div><div class="flyout">${items.map(i=>`<a href="#" data-value="${i}">${stars.includes(i)?'⭐'+i:i}</a>`).join('')}</div>`;
    container.insertAdjacentElement('beforebegin',div);
    const btn=div.querySelector('.menu-btn');
    div.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>{e.preventDefault();const v=a.dataset.value;if(key==='platform')setPlatform(v);else setSourceForPlatform(v);btn.textContent=`${label}: ${v}`;updateDownloadLink(true);}));
}
function renderSourceFlyout(){
    $$('.menu[data-type="source"]').forEach(n=>n.remove());
    const container=$('#category_block');if(!container)return;
    const list=sourcesByPlatform[platform]||[];
    const val=platformData[platform]?.source||defaultData[platform]?.source||list[0]||'';
    GM_setValue('platformData',platformData);
    const div=document.createElement('div');div.className='menu';div.dataset.type='source';
    div.innerHTML=`<div class="menu-btn">${sourceLabel}: ${val}</div><div class="flyout">${list.map(i=>`<a href="#" data-value="${i}">${i}</a>`).join('')}</div>`;
    container.insertAdjacentElement('beforebegin',div);
    const btn=div.querySelector('.menu-btn');
    div.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>{e.preventDefault();setSourceForPlatform(a.dataset.value);btn.textContent=`${sourceLabel}: ${a.dataset.value}`;updateDownloadLink(true);}));
}

// ---------- Download Link Updater ----------
const updateDownloadLink=async(force=false)=>{
    const dl=$('#add314');if(!dl||(!force&&!document.hasFocus()))return;
    if(!gameName)await fetchGameNameFromStore();
    if(gameName)applyLink(dl);
};

// ---------- Multiplayer Button ----------
async function createMultiButton(){
    const FixGameSet=new Set(['slime rancher 2']);
    const slug=gameName||await fetchGameNameFromStore();
    if(!slug)return;
    const normName=slug.normalize('NFD').replace(/[\u0300-\u036f\u00A9\u00AE\u2122\u2120]/g,'').replace(/-/g,' ').trim().toLowerCase();
    if(!document.querySelector(".game_area_details_specs_ctn[href*='category2=3']:is([href*='6'],[href*='8'],[href*='9'])")&&!FixGameSet.has(normName))return;
    if($('#add314r'))return;
    const base=$('#add314');if(!base)return;
    let btn=document.querySelector('#add314r');
    if(!btn){
        btn=document.createElement('a');btn.id='add314r';btn.className='btn_green_steamui btn_medium';btn.target='_blank';btn.innerHTML=`<span>${multiLabel}</span>`;base.insertAdjacentElement('afterend',btn);
        const btnSpan = btn.querySelector('span'); btnSpan.dataset.label = multiLabel;
        btn.addEventListener('click',async e=>{
            e.preventDefault();
            startLoadingAnim(btnSpan);
            const query=normName.replace(/[\/\s]+/g,'+');
            const url=`https://online-fix.me/index.php?do=search&subaction=search&story=${query}`;
            const doc=await gmFetchHTML(url,10000);
            stopLoadingAnim(btnSpan,multiLabel);
            if(!doc)return;
            const hit=[...doc.querySelectorAll('.article')].map(a=>{
                const h2=a.querySelector('h2.title');if(!h2)return null;
                const title=h2.textContent.replace(/ по сети/gi,'').normalize('NFD').replace(/[\u0300-\u036f\u00A9\u00AE\u2122\u2120]/g,'').trim().toLowerCase();
                const link=a.querySelector('a.big-link')?.href||a.querySelector('a.img')?.href||h2.closest('a')?.href||null;
                return title===normName&&link?link:null;
            }).filter(Boolean)[0];
            if(hit)btn.href=hit;if(hit)window.open(hit,'_blank');
        });
    }
}

// ---------- UI init ----------
const initUI=()=>{
    const wrap=$('.game_area_purchase_game_wrapper:not(#demoGameBtn) > * > .game_purchase_action');
    if(!wrap||$('.game_area_comingsoon,.game_area_bubble'))return;
    if($('.menu-container'))return;
    const cont=document.createElement('div');cont.className='menu-container';document.body.appendChild(cont);
    createFlyout('🌐','platform',['IGG','Skidrow','FitGirl'],['IGG','Skidrow']);
    renderSourceFlyout();
    const priceEl=document.querySelector("[class^='discount_original_price'],[data-price-final]");
    const price=(priceEl?.innerText?.trim())||'';
    const finalPrice=price?`0,--${price.slice(-1)}`:'0,00';
    wrap.innerHTML=`<div class="game_purchase_action_bg"><div class="discount_block game_purchase_discount" data-discount="100"><div class="discount_pct">-100%</div><div class="discount_prices"><div class="discount_original_price">${price}</div><div class="discount_final_price">${finalPrice}</div></div></div><div class="btn_addtocart"><a id="add314" class="btn_green_steamui btn_medium" href="#" target="_blank"><span>${dlLabel}</span></a></div></div>`;
    updateDownloadLink(true);
    createMultiButton();
};

// ---------- SPA observe ----------
const observer=new MutationObserver(()=>{if($('.game_area_purchase_game_wrapper'))initUI();});
const target=$('#appMountPoint')||document.documentElement;
observer.observe(target,{childList:true,subtree:true});
initUI();

// ---------- Online-Fix auto-translate ----------
if(/^https?:\/\/online-fix\.me\/games\/.+/.test(location.href)){
    const langShort=(navigator.language||'en').split('-')[0];
    const applyTranslate=()=>{const sel=document.querySelector('.goog-te-combo');if(!sel)return false;sel.value=langShort;sel.dispatchEvent(new Event('change'));return true;};
    const t=setInterval(()=>{if(applyTranslate())clearInterval(t);},300);
    document.addEventListener('DOMContentLoaded',applyTranslate);
}

})();