/** * steam-proxy-worker.js — Cloudflare Worker that gives a *hosted* copy of * la-li-lu-le-lo (the MGS2 Master Collection 100% companion) working Steam sync. * * WHY THIS EXISTS * --------------- * The companion reads a public Steam Community page to find out which achievements an * account already has. That page is served as XML and sends no CORS headers, so a browser * cannot fetch it from another origin. Running steam-sync.py locally solves this on your own * machine; this Worker solves it once the page is hosted on GitHub Pages / Netlify / Vercel / * Cloudflare Pages. * * DEPLOY (free tier is plenty — this is a handful of requests per page load) * ------------------------------------------------------------------------- * 1. https://dash.cloudflare.com -> Workers & Pages -> Create -> Worker * 2. Paste this whole file in, replacing the starter code. Deploy. * 3. Copy the worker URL (e.g. https://mgs2-steam-proxy.YOURNAME.workers.dev) * 4. In index.html, set: const STEAM_PROXY = 'https://mgs2-steam-proxy.YOURNAME.workers.dev'; * 5. Done — the page will use it automatically when it is not running on localhost. * * Optional: change ALLOW_ORIGIN below from '*' to your page's origin to stop anyone else * pointing their own page at your Worker. * * The Worker makes exactly one outbound request, to the public Steam Community page for the * profile it is asked about. It stores nothing and logs nothing. */ const APPID = '2131640'; const ALLOW_ORIGIN = '*'; // e.g. 'https://yourname.github.io' const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'; function cors(res) { res.headers.set('Access-Control-Allow-Origin', ALLOW_ORIGIN); res.headers.set('Access-Control-Allow-Headers', '*'); res.headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.headers.set('Cache-Control', 'no-store'); return res; } function json(obj) { return new Response(JSON.stringify(obj), { status: 200, headers: { 'Content-Type': 'application/json; charset=utf-8' }, }); } function cdata(block, tag) { const m = block.match(new RegExp('<' + tag + '>')); return m ? m[1].trim() : ''; } export default { async fetch(request) { const url = new URL(request.url); if (request.method === 'OPTIONS') return cors(new Response(null, { status: 204 })); if (url.pathname.replace(/\/$/, '') !== '/api/steam') { return cors(new Response('Not found — this Worker only serves /api/steam', { status: 404 })); } const profile = (url.searchParams.get('profile') || '').trim(); if (!profile) { return cors(json({ ok: false, error: 'No profile given.', hint: 'Enter a vanity name or SteamID64.' })); } const target = /^\d{17}$/.test(profile) ? 'https://steamcommunity.com/profiles/' + profile + '/stats/' + APPID + '/?tab=achievements&xml=1' : 'https://steamcommunity.com/id/' + encodeURIComponent(profile) + '/stats/' + APPID + '/?tab=achievements&xml=1'; let xml; try { const r = await fetch(target, { headers: { 'User-Agent': UA, 'Accept-Language': 'en' } }); xml = await r.text(); } catch (e) { return cors(json({ ok: false, error: 'Could not reach Steam.', hint: 'Try again in a moment.' })); } const err = xml.match(/<\/error>/); if (err) { return cors(json({ ok: false, error: err[1].trim(), hint: 'Check the spelling, or paste the whole profile URL.', })); } const privacy = (xml.match(/(.*?)<\/privacyState>/) || [, ''])[1].trim(); if (privacy && privacy !== 'public') { return cors(json({ ok: false, error: 'That profile is ' + privacy + '.', hint: "Steam > Edit Profile > Privacy Settings > set 'Game details' to Public, then sync again.", })); } const achievements = []; const re = /([\s\S]*?)<\/achievement>/g; let m; while ((m = re.exec(xml)) !== null) { const body = m[2]; achievements.push({ api: cdata(body, 'apiname'), name: cdata(body, 'name'), desc: cdata(body, 'description'), unlocked: m[1] === '1', icon: cdata(body, 'iconOpen'), iconLocked: cdata(body, 'iconClosed'), }); } if (!achievements.length) { return cors(json({ ok: false, error: 'No MGS2 Master Collection stats on that account.', hint: 'The account must own the game (appid ' + APPID + ') and have launched it at least once.', })); } const id64 = (xml.match(/(\d+)<\/steamID64>/) || [, ''])[1]; // This XML carries no persona name, only steamID64 and customURL, so fall back sensibly. let persona = ''; const pm = xml.match(/(?:)?<\/steamID>/); if (pm && pm[1].trim()) persona = pm[1].trim(); if (!persona) { const cm = xml.match(/<\/customURL>/); if (cm && cm[1].trim()) persona = cm[1].trim(); } if (!persona) persona = profile; return cors(json({ ok: true, game: 'METAL GEAR SOLID 2: Sons of Liberty - Master Collection Version', appid: APPID, steamid64: id64, persona: persona, unlockedCount: achievements.filter((a) => a.unlocked).length, total: achievements.length, achievements: achievements, })); }, };