Skip to content

Instantly share code, notes, and snippets.

@Windowsfreak
Last active May 11, 2026 16:50
Show Gist options
  • Select an option

  • Save Windowsfreak/0f4b0b727120d6a408a68a832e5cc7b6 to your computer and use it in GitHub Desktop.

Select an option

Save Windowsfreak/0f4b0b727120d6a408a68a832e5cc7b6 to your computer and use it in GitHub Desktop.
Aurum-Partnerstatistik-Export
(async function () {
class AurumCryptor {
#password;
#iterations;
#encoder;
#decoder;
constructor(password, iterations = 150000) {
if (!password) throw new Error("Password is required for key derivation");
this.#password = password;
this.#iterations = iterations;
this.#encoder = new TextEncoder();
this.#decoder = new TextDecoder();
}
async deriveKey(salt) {
const baseKey = await window.crypto.subtle.importKey(
"raw", this.#encoder.encode(this.#password), "PBKDF2", false, ["deriveKey"]
);
return window.crypto.subtle.deriveKey(
{ name: "PBKDF2", salt: salt, iterations: this.#iterations, hash: "SHA-256" },
baseKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]
);
}
async decode(encryptedBase64) {
const encryptedBytes = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0));
const salt = encryptedBytes.slice(0, 16), iv = encryptedBytes.slice(16, 28), ciphertext = encryptedBytes.slice(28);
const aesKey = await this.deriveKey(salt);
const decryptedBytes = await window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext);
return this.#decoder.decode(decryptedBytes);
}
}
const cryptor = new AurumCryptor("default-password");
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const backoffs = [10, 20, 40, 80, 120, 240, 360, 480, 600];
const section = document.getElementsByTagName("section")[0] || document.body;
const menu = document.getElementsByTagName('aside');
if (menu.length > 0) menu[0].parentNode.remove();
section.innerHTML = `
<div class="max-w-[600px] mx-auto text-center font-sans p-8">
<h2 class="font-medium md:text-xl mb-[9px] text-[18px] text-text-color-primary">Partner-Struktur Export</h2>
<p class="text-text-color-secondary text-[14px] leading-none font-light mb-8">Extrahiert die komplette Partner-Hierarchie als JSON und CSV.</p>
<button id="startExportBtn" class="text-sm flex gap-2 items-center font-medium font-geologica justify-center rounded-[12px] px-4 py-2.5 transition duration-300 ease-in-out focus:outline-hidden bg-bg-color-main-theme-deep text-white w-full md:hover:bg-bg-color-main-theme-dark">
Export starten
</button>
<div id="progressContainer" style="margin-top: 1.5rem; display: none;">
<div class="bg-gray-200 rounded-[8px] w-full h-[20px] overflow-hidden">
<div id="progressBar" class="bg-green-400 w-0 h-full transition-all duration-300"></div>
</div>
<p id="progressText" class="text-text-color-placeholder mt-1 text-[12px] leading-4 font-light text-left">Bereit...</p>
</div>
<div id="resultContainer" style="margin-top: 1.5rem; display: none; text-align: left;">
<h3 class="text-[14px] font-medium mb-2">Ergebnisse:</h3>
<div style="display: flex; flex-direction: column; gap: 20px;">
<div>
<label class="text-[12px] text-text-color-secondary block mb-1">JSON Format:</label>
<textarea id="result-json" style="width: 100%; height: 200px; font-family: monospace; font-size: 12px; padding: 10px; border: 1px solid #ccc; border-radius: 8px;"></textarea>
</div>
<div>
<label class="text-[12px] text-text-color-secondary block mb-1">CSV Format:</label>
<textarea id="result-csv" style="width: 100%; height: 200px; font-family: monospace; font-size: 12px; padding: 10px; border: 1px solid #ccc; border-radius: 8px;"></textarea>
</div>
</div>
</div>
</div>`;
const getEl = id => document.getElementById(id);
const [startBtn, progressContainer, progressBar, progressText, resultContainer, resultJson, resultCsv] =
['startExportBtn', 'progressContainer', 'progressBar', 'progressText', 'resultContainer', 'result-json', 'result-csv'].map(getEl);
startBtn.addEventListener('click', async () => {
let token = localStorage.getItem("token");
if (!token) return alert("Fehler: Kein Token im localStorage gefunden.");
const headers = { "Accept": "application/json", "Authorization": `Bearer ${token}`, "X-Requested-With": "aurum-with" };
async function doRefreshToken() {
const rfToken = localStorage.getItem("tokenRefresh");
if (!rfToken) throw new Error("Kein Refresh Token gefunden.");
const oldAuth = headers["Authorization"];
headers["Authorization"] = `Bearer ${rfToken}`;
try {
const res = await fetch("https://api.aurum.foundation/refresh", { method: "POST", headers });
if (!res.ok) throw new Error(`Refresh failed: ${res.status}`);
const raw = await res.json();
const data = raw.encrypted ? JSON.parse(await cryptor.decode(raw.encrypted)) : raw;
if (data?.data?.accessToken) {
token = data.data.accessToken;
localStorage.setItem("token", token);
localStorage.setItem("tokenRefresh", data.data.refreshToken);
headers["Authorization"] = `Bearer ${token}`;
return true;
}
} catch (e) {
headers["Authorization"] = oldAuth;
throw new Error("Sitzung abgelaufen und Refresh fehlgeschlagen.");
}
throw new Error("Token refresh failed.");
}
async function fetchWithRetry(url, options, isRefresh = false) {
let attempt = 0;
while (true) {
try {
const res = await fetch(url, options);
if (!res.ok) {
if (res.status === 401 && !isRefresh) {
progressText.textContent = `Sitzung abgelaufen, erneuere Token...`;
await doRefreshToken();
options.headers["Authorization"] = headers["Authorization"];
attempt = 0;
continue;
}
if (res.status === 401 || res.status === 403) throw new Error("Sitzung abgelaufen");
throw new Error(`HTTP ${res.status}`);
}
const raw = await res.json();
return raw.encrypted ? JSON.parse(await cryptor.decode(raw.encrypted)) : raw;
} catch (e) {
if (e.message.includes("Sitzung abgelaufen")) throw e;
if (attempt < backoffs.length) {
progressText.textContent = `Fehler (${e.message}), warte ${backoffs[attempt]}s...`;
await delay(backoffs[attempt] * 1000);
attempt++;
} else {
throw new Error(`Verbindungsfehler nach ${backoffs.length} Versuchen: ${e.message}`);
}
}
}
}
async function fetchAPI(url) {
return await fetchWithRetry(url, { method: "GET", headers: { ...headers } });
}
function parseInvest(val) {
if (!val) return 0;
const cleaned = String(val).replace(/\s/g, '');
return parseFloat(cleaned) || 0;
}
let totalPartnersToFetch = 0;
let currentPartnersFetched = 0;
async function getReferralsRecursive(prettyId) {
const partners = [];
const limit = 15;
let page = 1;
let total = 0;
do {
const url = `https://api.aurum.foundation/partners/statistics?limit=${limit}&page=${page}&prettyId=${prettyId}&search=`;
const data = await fetchAPI(url);
total = data.total || 0;
if (data.referrals && Array.isArray(data.referrals)) {
for (const p of data.referrals) {
currentPartnersFetched++;
const pct = totalPartnersToFetch > 0 ? Math.round((currentPartnersFetched / totalPartnersToFetch) * 100) : 0;
progressBar.style.width = `${Math.min(pct, 99)}%`;
progressText.textContent = `Lade Partner: ${p.name} (${currentPartnersFetched} von ca. ${totalPartnersToFetch})...`;
const node = {
id: p.prettyId,
name: p.name,
rank: (p.rankId || 1) - 1,
invest: parseInvest(p.investmentsAmount),
partners: []
};
if (p.referralsCount > 0) {
node.partners = await getReferralsRecursive(p.prettyId);
}
partners.push(node);
}
}
page++;
} while (partners.length < total);
return partners;
}
const rankNames = ["Nova", "Voyager", "Vanguard", "Vanguard Pro", "Nexus", "Oracle", "Prime", "Elite", "Magnat", "Mythos", "Legend"];
function getCsvTree(root) {
const lines = ["id,name,rank,invest,parent,depth"];
function recurse(node, parentId = "", depth = 0) {
const rankName = rankNames[node.rank] || rankNames[0];
lines.push(`${node.id},${node.name},${rankName},${node.invest},"${parentId}",${depth}`);
if (node.partners) {
for (const p of node.partners) recurse(p, node.id, depth + 1);
}
}
recurse(root);
return lines.join("\n");
}
try {
startBtn.disabled = true;
startBtn.textContent = "Initialisiere...";
progressContainer.style.display = "block";
progressText.textContent = "Lade Basisdaten...";
const rootData = await fetchAPI('https://api.aurum.foundation/');
const user = rootData.user;
const affStats = rootData.affiliateStats || {};
// Heuristic for progress bar
totalPartnersToFetch = (affStats.referralsCount || 0) + (affStats.referralsCountSecondLevel || 0);
const resultTree = {
id: user.prettyId,
name: `${user.personal.name} ${user.personal.surname}`.trim(),
rank: (affStats.referralRankId || 1) - 1,
invest: parseInvest(affStats.investAmount),
partners: []
};
resultTree.partners = await getReferralsRecursive("");
progressBar.style.width = "100%";
progressText.textContent = "Export erfolgreich abgeschlossen!";
startBtn.textContent = "Abgeschlossen";
resultJson.value = JSON.stringify(resultTree, null, 2);
resultCsv.value = getCsvTree(resultTree);
resultContainer.style.display = "block";
} catch (e) {
console.error('Export failed:', e);
progressText.classList.add('text-red-500');
progressText.textContent = `Fehler: ${e.message}`;
startBtn.textContent = "Export fehlgeschlagen";
startBtn.disabled = false;
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment