1(() => { 2 const slugMeta = document.querySelector('meta[name="lfg:slug"]'); 3 if (!slugMeta) return; 4 5 const slug = slugMeta.getAttribute("content")?.trim(); 6 if (!slug) return; 7 8 const gameName = 9 document 10 .querySelector('meta[name="lfg:game"]') 11 ?.getAttribute("content") 12 ?.trim() || 13 document.querySelector("h1")?.textContent?.trim() || 14 ""; 15 16 const coverImg = document.getElementById("coverImage"); 17 if (!coverImg) return; 18 19 const isAndroid = /Android/i.test(navigator.userAgent); 20 21 const SUPABASE_URL = window.CLUTCH_SUPABASE_URL || ""; 22 const SUPABASE_ANON_KEY = window.CLUTCH_SUPABASE_ANON_KEY || ""; 23 24 const headers = { 25 apikey: SUPABASE_ANON_KEY, 26 Authorization: `Bearer ${SUPABASE_ANON_KEY}`, 27 }; 28 29 function fetchBySlug() { 30 const endpoint = new URL(`${SUPABASE_URL}/rest/v1/hubs`); 31 endpoint.searchParams.set("select", "id,cover_image,name,slug"); 32 endpoint.searchParams.set("slug", `eq.${slug}`); 33 endpoint.searchParams.set("is_visible", "eq.true"); 34 endpoint.searchParams.set("limit", "1"); 35 return fetch(endpoint.toString(), { headers }).then((res) => 36 res.ok ? res.json() : Promise.reject(res), 37 ); 38 } 39 40 function fetchByName(candidate) { 41 const endpoint = new URL(`${SUPABASE_URL}/rest/v1/hubs`); 42 endpoint.searchParams.set("select", "id,cover_image,name,slug"); 43 endpoint.searchParams.set("name", `ilike.%${candidate}%`); 44 endpoint.searchParams.set("is_visible", "eq.true"); 45 endpoint.searchParams.set("limit", "1"); 46 return fetch(endpoint.toString(), { headers }).then((res) => 47 res.ok ? res.json() : Promise.reject(res), 48 ); 49 } 50 51 const slugToNameMap = { 52 "gta-5": "Grand Theft Auto V", 53 warzone: "Call of Duty: Warzone", 54 cs2: "Counter-Strike", 55 "counter-strike-2": "Counter-Strike", 56 fortnite: "Fortnite", 57 }; 58 59 const nameCandidates = []; 60 if (slugToNameMap[slug]) nameCandidates.push(slugToNameMap[slug]); 61 if (gameName) nameCandidates.push(gameName); 62 if (gameName && gameName.endsWith(" 2")) { 63 nameCandidates.push(gameName.replace(/\s2$/, "")); 64 } 65 if (slug && slug.includes("counter-strike")) { 66 nameCandidates.push("Counter-Strike"); 67 } 68 69 fetchBySlug() 70 .then((rows) => { 71 const item = Array.isArray(rows) ? rows[0] : null; 72 if (item) return item; 73 return null; 74 }) 75 .then((item) => { 76 if (item) return item; 77 let chain = Promise.resolve(null); 78 nameCandidates.forEach((candidate) => { 79 chain = chain.then((found) => { 80 if (found) return found; 81 return fetchByName(candidate).then((rows) => 82 Array.isArray(rows) ? rows[0] : null, 83 ); 84 }); 85 }); 86 return chain; 87 }) 88 .then((item) => { 89 if (!item) return; 90 91 if (item.cover_image) { 92 coverImg.setAttribute("src", item.cover_image); 93 const altText = item.name || gameName || "Game cover art"; 94 coverImg.setAttribute("alt", `${altText} cover art`); 95 } 96 97 if (item.id) { 98 window.__clutch = window.__clutch || {}; 99 window.__clutch.lfgId = item.id; 100 } 101 102 const lfgLinks = document.querySelectorAll('a[href^="clutchapp://lfg/"]'); 103 lfgLinks.forEach((link) => { 104 if (item.id) { 105 link.dataset.hubId = item.id; 106 link.setAttribute( 107 "href", 108 `clutchapp://lfg/${encodeURIComponent(item.id)}`, 109 ); 110 } else { 111 link.removeAttribute("data-hub-id"); 112 link.setAttribute( 113 "href", 114 `clutchapp://lfg/${encodeURIComponent(slug)}`, 115 ); 116 } 117 }); 118 }) 119 .catch(() => { 120 // Fail silently; keep fallback image. 121 }); 122 123 if (isAndroid) { 124 const FALLBACK = "/download.html"; 125 const openIntent = (url, fb) => { 126 const appendAttribution = 127 window.ClutchAttribution?.appendAttributionToUrl; 128 const fallback = 129 typeof appendAttribution === "function" 130 ? appendAttribution(fb || FALLBACK) 131 : fb || FALLBACK; 132 const targetUrl = 133 typeof appendAttribution === "function" 134 ? appendAttribution(String(url || "")) 135 : String(url || ""); 136 const raw = targetUrl.startsWith("clutchapp://") 137 ? targetUrl.slice("clutchapp://".length) 138 : targetUrl.replace(/^\/+/, ""); 139 const intent = `intent://${raw}#Intent;scheme=clutchapp;package=com.erniec1.ClutchApp;end`; 140 141 let didHide = false;
142 const onVis = () => { 143 if (document.visibilityState === "hidden") didHide = true; 144 }; 145 document.addEventListener("visibilitychange", onVis, { passive: true }); 146 147 window.location.href = intent; 148 149 setTimeout(() => { 150 document.removeEventListener("visibilitychange", onVis); 151 if (!didHide) window.location.href = fallback; 152 }, 1400); 153 }; 154 155 window.__clutch = window.__clutch || {}; 156 window.__clutch.openApp = openIntent; 157 } 158})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.