From cb183268b828d715cb91cb821f2389125068d495 Mon Sep 17 00:00:00 2001 From: Bogdan Sandu Date: Thu, 12 Mar 2026 21:28:19 +0200 Subject: [PATCH 1/2] Added logging module Signed-off-by: Bogdan Sandu --- src/background/background.js | 11 +-- src/content/core/api.js | 13 +-- src/content/core/games/servers/serverstats.js | 9 +- src/content/core/logging.js | 85 +++++++++++++++++++ src/content/core/oauth/oauth.js | 33 ++++--- .../core/regionFinder/ClosestServer.js | 5 +- src/content/core/regions.js | 7 +- src/content/core/settings/handlesettings.js | 31 +++---- src/content/core/settings/portSettings.js | 35 ++++---- src/content/core/utils/assetStreamer.js | 7 +- src/content/core/utils/launcher.js | 5 +- src/content/core/utils/location.js | 11 +-- .../core/utils/trackers/friendslist.js | 11 +-- src/content/features/avatar/avatarRotator.js | 3 +- src/content/features/avatar/filters.js | 3 +- src/content/features/catalog/40method.js | 48 ++++++----- src/content/features/catalog/hiddenCatalog.js | 7 +- .../features/create.roblox.com/download.js | 7 +- src/content/features/developer/videotest.js | 1 + .../features/games/actions/quickOutfits.js | 9 +- src/content/features/games/revertlogo.js | 7 +- src/content/features/games/tab/DevProducts.js | 5 +- src/content/features/groups/Antibots.js | 7 +- src/content/features/navigation/QoLToggles.js | 9 +- .../features/navigation/betaprograms.js | 3 +- .../features/navigation/search/quicksearch.js | 7 +- src/content/features/onboarding/onboarding.js | 3 +- .../features/profile/header/ProfileRender.js | 7 +- src/content/features/profile/hiddengames.js | 5 +- src/content/features/profile/outfits.js | 5 +- .../features/profile/trustedfriends.js | 9 +- src/content/features/settings/index.js | 3 +- .../features/settings/roblox/firstAccount.js | 3 +- src/content/features/sitewide/cssfixes.js | 3 +- .../transactions/pendingRobuxTrans.js | 11 +-- .../features/transactions/totalearned.js | 3 +- .../features/transactions/totalspent.js | 5 +- src/content/index.js | 7 +- 38 files changed, 283 insertions(+), 160 deletions(-) create mode 100644 src/content/core/logging.js diff --git a/src/background/background.js b/src/background/background.js index dc02c51..d124292 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,4 +1,5 @@ import { SETTINGS_CONFIG } from '../content/core/settings/settingConfig.js'; +import { log, logLevel} from '../content/core/logging.js'; // --- Constants & State --- @@ -66,9 +67,9 @@ function initializeSettings(reason) { if (needsUpdate) { chrome.storage.local.set(settingsToUpdate, () => { if (chrome.runtime.lastError) { - console.error('RoValra: Failed to sync settings.', chrome.runtime.lastError); + log(logLevel.ERROR, 'RoValra: Failed to sync settings.', chrome.runtime.lastError); } else { - console.log(`RoValra: Synced/Fixed ${Object.keys(settingsToUpdate).length} settings (Trigger: ${reason}).`); + log(logLevel.DEBUG, `RoValra: Synced/Fixed ${Object.keys(settingsToUpdate).length} settings (Trigger: ${reason}).`); } }); } @@ -205,7 +206,7 @@ async function getUniverseIdFromPlaceId(placeId) { } return null; } catch (e) { - console.error('RoValra: Error fetching universe ID from place ID', e); + log(logLevel.ERROR, 'RoValra: Error fetching universe ID from place ID', e); return null; } } @@ -265,7 +266,7 @@ async function wearOutfit(outfitData) { try { const outfitId = (typeof outfitData === 'object' && outfitData !== null) ? outfitData.itemId : outfitData; if (!outfitId) { - console.error('RoValra: wearOutfit called with invalid outfitData', outfitData); + log(logLevel.ERROR, 'RoValra: wearOutfit called with invalid outfitData', outfitData); return { ok: false }; } @@ -288,7 +289,7 @@ async function wearOutfit(outfitData) { const results = await Promise.all(promises); return { ok: results.every(r => r && r.ok) }; } catch (e) { - console.error('RoValra: Error wearing outfit', e); + log(logLevel.ERROR, 'RoValra: Error wearing outfit', e); return { ok: false }; } } diff --git a/src/content/core/api.js b/src/content/core/api.js index 7cd0dd5..8a6e430 100644 --- a/src/content/core/api.js +++ b/src/content/core/api.js @@ -3,6 +3,7 @@ import { getCsrfToken } from './utils.js'; import { getAuthenticatedUserId } from './user.js'; import { getValidAccessToken } from './oauth/oauth.js'; +import { log, logLevel } from './logging.js'; import { updateUserLocationIfChanged } from './utils/location.js'; const activeRequests = new Map(); @@ -244,7 +245,7 @@ export async function callRobloxApi(options) { let storedVerification = allVerifications[authedUserId]; if (storedVerification) { - console.log("RoValra API: New token received from header. Updating storage."); + log(logLevel.DEBUG, "RoValra API: New token received from header. Updating storage."); storedVerification.accessToken = newAccessToken; storedVerification.timestamp = Date.now(); @@ -260,13 +261,13 @@ export async function callRobloxApi(options) { } } } catch (e) { - console.error("RoValra API: Failed to update new access token.", e); + log(logLevel.ERROR, "RoValra API: Failed to update new access token.", e); } } if (lastResponse.status === 401 && endpoint && endpoint.includes('/v1/auth') && !skipAutoAuth && !authRetried) { - console.log("RoValra API: 401 Unauthorized, attempting token refresh..."); + log(logLevel.ERROR, "RoValra API: 401 Unauthorized, attempting token refresh..."); authRetried = true; const newToken = await getValidAccessToken(true); if (newToken) { @@ -282,7 +283,7 @@ export async function callRobloxApi(options) { if (endpoint && endpoint.includes('/v1/auth')) break; } catch (error) { if (attempt === 3 || (endpoint && endpoint.includes('/v1/auth'))) { - console.error(`RoValra API: Request to ${fullUrl} failed${attempt === 3 ? ' after multiple retries' : ''}.`, error); + log(logLevel.ERROR, `RoValra API: Request to ${fullUrl} failed${attempt === 3 ? ' after multiple retries' : ''}.`, error); throw error; } } @@ -291,7 +292,7 @@ export async function callRobloxApi(options) { } } if (!lastResponse.ok) { - console.error(`RoValra API: Request to ${fullUrl} failed with status ${lastResponse.status} after multiple retries.`); + log(logLevel.ERROR, `RoValra API: Request to ${fullUrl} failed with status ${lastResponse.status} after multiple retries.`); } return lastResponse; } @@ -330,7 +331,7 @@ export async function callRobloxApi(options) { } if (!response.ok) { - console.error(`RoValra API: Request to ${fullUrl} failed with status ${response.status}.`); + log(logLevel.ERROR, `RoValra API: Request to ${fullUrl} failed with status ${response.status}.`); } return response; diff --git a/src/content/core/games/servers/serverstats.js b/src/content/core/games/servers/serverstats.js index 0736772..63d6645 100644 --- a/src/content/core/games/servers/serverstats.js +++ b/src/content/core/games/servers/serverstats.js @@ -4,6 +4,7 @@ import { callRobloxApi } from '../../api.js'; import { addTooltip } from '../../ui/tooltip.js'; import DOMPurify from 'dompurify'; import { observeElement, startObserving } from '../../observer.js'; +import { log, logLevel } from '../../logging.js'; let versionDataCache = null; @@ -40,7 +41,7 @@ async function fetchLatestPlaceVersion(placeId) { return null; } catch (error) { - console.error('RoValra Server Stats: Failed to fetch latest place version from Roblox.', error); + log(logLevel.ERROR, 'RoValra Server Stats: Failed to fetch latest place version from Roblox.', error); return null; } } @@ -78,7 +79,7 @@ export async function fetchServerStats(placeId) { return data.counts; } catch (error) { - console.error('RoValra Server Stats: Failed to fetch server statistics.', error); + log(logLevel.ERROR, 'RoValra Server Stats: Failed to fetch server statistics.', error); return null; } } @@ -129,7 +130,7 @@ async function createStatsBarUI(serverListContainer) { }); }); } catch (error) { - console.error('RoValra Server Stats: Failed to fetch settings.', error); + log(logLevel.ERROR, 'RoValra Server Stats: Failed to fetch settings.', error); return; } @@ -238,7 +239,7 @@ export async function initGlobalStatsBar() { } } } catch (error) { - console.error('RoValra Server Stats: Failed to initialize stats bar.', error); + log(logLevel.ERROR, 'RoValra Server Stats: Failed to initialize stats bar.', error); } } diff --git a/src/content/core/logging.js b/src/content/core/logging.js new file mode 100644 index 0000000..bac49d3 --- /dev/null +++ b/src/content/core/logging.js @@ -0,0 +1,85 @@ +export const logLevel = Object.freeze({ + DEBUG: 0, + INFO: 1, + WARNING: 2, + ERROR: 3, + CRITICAL: 4 +}); + +const logLevelStr = { + [logLevel.DEBUG]: 'DEBUG', + [logLevel.INFO]: 'INFO', + [logLevel.WARNING]: 'WARNING', + [logLevel.ERROR]: 'ERROR', + [logLevel.CRITICAL]: 'CRITICAL' +}; + +const maxLogLevelLength = 8; + +// Minimum Log Level +const minLogLevel = logLevel.DEBUG; + +// Whether to call alert() on logLevel.CRITICAL +const criticalAsAlert = true; + +// Logging functions +const logfn = { + [logLevel.DEBUG]: () => console.debug, + [logLevel.INFO]: () => console.info, + [logLevel.WARNING]: () => console.warn, + [logLevel.ERROR]: () => console.error, + [logLevel.CRITICAL]:() => (criticalAsAlert ? formatAndAlert : console.error) +}; + + +function format(msg, ...args) { + let i = 0; + + const str = String(msg).replace(/%[sdihf%]/g, token => { + if (token === "%%") return "%"; + const arg = args[i++]; + + switch (token) { + case "%s": return String(arg); + case "%d": + case "%i": return Number.parseInt(arg, 10); + case "%h": return "0x" + Number.parseInt(arg, 10).toString(16); + case "%f": return Number.parseFloat(arg); + default: + return token; + } + }); + + if (i < args.length) { + return str + " " + args.slice(i).join(" "); // concatenate remaining arguments to the formatted string (if any; seperated by spaces) + } + + return str; +} + +function formatAndAlert(msg, ...args) { + const formatted = format(msg, ...args); + log(logLevel.ERROR, msg, ...args); // for debugging purposes + alert(formatted); +} + +function timestamp() { + const now = new Date(); + return `${String(now.getHours()).padStart(2,'0')}:` + + `${String(now.getMinutes()).padStart(2,'0')}:` + + `${String(now.getSeconds()).padStart(2,'0')}.` + + `${String(now.getMilliseconds()).padStart(3,'0')}`; +} + +function padLevel(msg) { + return msg.padEnd(maxLogLevelLength); +} + +export function log(level, message, ...args) { + if (level < minLogLevel) { + return; + } + + const msg = `${timestamp()} [${padLevel(logLevelStr[level])}] ${message}`; + logfn[level]()(msg, ...args); +} diff --git a/src/content/core/oauth/oauth.js b/src/content/core/oauth/oauth.js index 812fefb..ca9d48c 100644 --- a/src/content/core/oauth/oauth.js +++ b/src/content/core/oauth/oauth.js @@ -1,9 +1,8 @@ -// TODO Remove console logs - const STORAGE_KEY = "rovalra_oauth_verification"; const TOKEN_EXPIRATION_BUFFER_MS = 5 * 60 * 1000; import { callRobloxApi } from '../api.js'; import { getAuthenticatedUserId } from '../user.js'; +import { log, logLevel } from '../logging.js'; const existenceCache = new Map(); const prefixCache = new Map(); @@ -11,18 +10,18 @@ let activeOAuthPromise = null; export async function init() { try { - console.log("RoValra: Script loaded. Syncing session..."); + log(logLevel.DEBUG, "RoValra: Script loaded. Syncing session..."); const token = await getValidAccessToken(true); if (token) { - console.log("RoValra: Session synchronized successfully."); + log(logLevel.INFO, "RoValra: Session synchronized successfully."); } else { - console.log("RoValra: No active session or re-auth required."); + log(logLevel.INFO, "RoValra: No active session or re-auth required."); } } catch (error) { - console.error("RoValra: Error during script initialization", error); + log(logLevel.ERROR, "RoValra: Error during script initialization", error); } } export async function getValidAccessToken(forceRefresh = false) { @@ -82,7 +81,7 @@ export async function getValidAccessToken(forceRefresh = false) { return updatedStorage[STORAGE_KEY]?.[userId]?.accessToken || storedVerification.accessToken; } catch (error) { - console.error("RoValra: Network error during token sync:", error); + log(logLevel.ERROR, "RoValra: Network error during token sync:", error); return storedVerification.accessToken; } } @@ -130,7 +129,7 @@ async function checkUserExistence(userId) { return exists; } } catch (error) { - console.error("RoValra: Error checking user existence", error); + log(logLevel.ERROR, "RoValra: Error checking user existence", error); } return false; } @@ -167,7 +166,7 @@ async function startOAuthFlow(silent = false) { } if (age < 13) { - console.log("RoValra: User is under 13. Skipping OAuth."); + log(logLevel.WARNING, "RoValra: User is under 13. Skipping OAuth."); return false; } } @@ -181,7 +180,7 @@ async function startOAuthFlow(silent = false) { } try { - console.log("RoValra: Attempting direct OAuth authorization POST request..."); + log(logLevel.DEBUG, "RoValra: Attempting direct OAuth authorization POST request..."); const response = await callRobloxApi({ subdomain: 'apis', @@ -207,11 +206,11 @@ async function startOAuthFlow(silent = false) { const locationUrl = authResponse.location; if (!locationUrl) { - console.error("RoValra: OAuth authorization response did not contain a location URL.", authResponse); + log(logLevel.ERROR, "RoValra: OAuth authorization response did not contain a location URL.", authResponse); return false; } - console.log("RoValra: Got authorization code. Fetching token from callback URL..."); + log(logLevel.INFO, "RoValra: Got authorization code. Fetching token from callback URL..."); const tokenResponse = await callRobloxApi({ fullUrl: locationUrl, @@ -220,14 +219,14 @@ async function startOAuthFlow(silent = false) { }); if (!tokenResponse.ok) { - console.error("RoValra: Failed to get token from callback URL.", await tokenResponse.text()); + log(logLevel.ERROR, "RoValra: Failed to get token from callback URL.", await tokenResponse.text()); return false; } const tokenData = await tokenResponse.json(); if (tokenData.status === 'success' && tokenData.access_token && tokenData.user_id && tokenData.username) { - console.log("RoValra: OAuth Successful!", tokenData); + log(logLevel.INFO, "RoValra: OAuth Successful!", tokenData); const expiresAt = tokenData.expires_at ? tokenData.expires_at * 1000 : null; @@ -246,16 +245,16 @@ async function startOAuthFlow(silent = false) { await chrome.storage.local.set({ [STORAGE_KEY]: allVerifications }); return true; } else { - console.error("RoValra: Invalid token data received from backend.", tokenData); + log(logLevel.ERROR, "RoValra: Invalid token data received from backend.", tokenData); return false; } } else { - console.error("RoValra: OAuth authorization POST request failed with status " + response.status, await response.text()); + log(logLevel.ERROR, "RoValra: OAuth authorization POST request failed with status " + response.status, await response.text()); return false; } } catch (error) { - console.error("RoValra: Error during direct OAuth authorization request.", error); + log(logLevel.ERROR, "RoValra: Error during direct OAuth authorization request.", error); return false; } })(); diff --git a/src/content/core/regionFinder/ClosestServer.js b/src/content/core/regionFinder/ClosestServer.js index 102faa2..ad8bf7f 100644 --- a/src/content/core/regionFinder/ClosestServer.js +++ b/src/content/core/regionFinder/ClosestServer.js @@ -4,6 +4,7 @@ import { launchGame } from '../utils/launcher.js'; import { showReviewPopup } from '../review/review.js'; import { hideLoadingOverlay } from '../ui/startModal/gamelaunchmodal.js'; import { getStateCodeFromRegion } from '../regions.js'; +import { log, logLevel } from '../logging.js'; export let REGIONS = {}; export let serverIpMap = {}; @@ -293,9 +294,9 @@ async function getRankedRegions(placeId, preferredRegionId) { } if (FINDER_CONFIG.logScores) { - console.log(`[RoValra] Region Scores for Place ${placeId}:`); + log(logLevel.DEBUG, `[RoValra] Region Scores for Place ${placeId}:`); ranked.forEach(r => { - console.log(` - ${getRegionName(r.region.id)}: Score ${r.score} (${Math.round(r.distance)} km)`); + log(logLevel.DEBUG, ` - ${getRegionName(r.region.id)}: Score ${r.score} (${Math.round(r.distance)} km)`); }); } diff --git a/src/content/core/regions.js b/src/content/core/regions.js index 8043bb7..1e938ad 100644 --- a/src/content/core/regions.js +++ b/src/content/core/regions.js @@ -2,6 +2,7 @@ import { callRobloxApi } from './api.js'; import { getAssets } from './assets.js'; +import { log, logLevel } from './logging.js'; const API_ENDPOINT_DATACENTERS_LIST = '/v1/datacenters/list'; const STORAGE_KEY_DATACENTERS = 'rovalraDatacenters'; @@ -64,7 +65,7 @@ export async function loadDatacenterMap() { processDataIntoMap(currentData); } } catch (e) { - console.error("RoValra: Error reading datacenter map from storage.", e); + log(logLevel.ERROR, "RoValra: Error reading datacenter map from storage.", e); } if (!currentData) { @@ -78,7 +79,7 @@ export async function loadDatacenterMap() { await chrome.storage.local.set({ [STORAGE_KEY_DATACENTERS]: localData }); processDataIntoMap(localData); } catch (e) { - console.error("RoValra: Could not load local fallback JSON.", e); + log(logLevel.ERROR, "RoValra: Could not load local fallback JSON.", e); serverIpMap = {}; } } @@ -131,7 +132,7 @@ async function fetchAndProcessRegions() { if (!response.ok) throw new Error(`Status ${response.status}`); data = await response.json(); } catch (fallbackError) { - console.error("RoValra Critical: Could not load region data.", fallbackError); + log(logLevel.ERROR, "RoValra Critical: Could not load region data.", fallbackError); return { regions: newRegions, continents: newContinents }; } } diff --git a/src/content/core/settings/handlesettings.js b/src/content/core/settings/handlesettings.js index 6162d52..17ea403 100644 --- a/src/content/core/settings/handlesettings.js +++ b/src/content/core/settings/handlesettings.js @@ -3,6 +3,7 @@ import { findSettingConfig } from './generateSettings.js'; import { getFullRegionName, REGIONS } from '../regions.js'; import { sanitizeString } from '../utils/sanitize.js'; import { createAndShowPopup } from '../../features/catalog/40method.js'; +import { log, logLevel } from '../../core/logging.js'; export const loadSettings = async () => { @@ -25,7 +26,7 @@ export const loadSettings = async () => { chrome.storage.local.get(defaultSettings, (settings) => { if (chrome.runtime.lastError) { - console.error('Failed to load settings:', chrome.runtime.lastError); + log(logLevel.ERROR, 'Failed to load settings:', chrome.runtime.lastError); reject(chrome.runtime.lastError); } else { resolve(settings); @@ -37,7 +38,7 @@ export const loadSettings = async () => { export const handleSaveSettings = async (settingName, value) => { if (!settingName) { - console.error('No setting name provided'); + log(logLevel.ERROR, 'No setting name provided'); return Promise.reject(new Error('No setting name provided')); } @@ -118,7 +119,7 @@ export const handleSaveSettings = async (settingName, value) => { return new Promise((resolve, reject) => { chrome.storage.local.set(settings, () => { if (chrome.runtime.lastError) { - console.error('Failed to save setting:', settingName, chrome.runtime.lastError); + log(logLevel.ERROR, 'Failed to save setting:', settingName, chrome.runtime.lastError); reject(chrome.runtime.lastError); } else { syncToSettingsKey(settingName, sanitizedValue); @@ -127,7 +128,7 @@ export const handleSaveSettings = async (settingName, value) => { }); }); } catch (error) { - console.error(`Error saving setting ${settingName}:`, error); + log(logLevel.ERROR, `Error saving setting ${settingName}:`, error); return Promise.reject(error); } }; @@ -159,9 +160,9 @@ export const buildSettingsKey = async () => { chrome.storage.local.get(allSettingKeys, (currentSettings) => { chrome.storage.local.set({ rovalra_settings: currentSettings }, () => { if (chrome.runtime.lastError) { - console.error('Failed to build settings key:', chrome.runtime.lastError); + log(logLevel.ERROR, 'Failed to build settings key:', chrome.runtime.lastError); } else { - console.log('RoValra: Settings key initialized'); + log(logLevel.DEBUG, 'RoValra: Settings key initialized'); } resolve(); }); @@ -172,7 +173,7 @@ export const buildSettingsKey = async () => { export const initSettings = async (settingsContent) => { if (!settingsContent) { - console.error("settingsContent is null in initSettings! Check HTML structure."); + log(logLevel.ERROR, "settingsContent is null in initSettings! Check HTML structure."); return; } const settings = await loadSettings(); @@ -194,7 +195,7 @@ export const initSettings = async (settingsContent) => { } if (missingPerms) { - console.log(`RoValra: Disabling '${settingName}' because required permissions are missing.`); + log(logLevel.WARNING, `RoValra: Disabling '${settingName}' because required permissions are missing.`); await handleSaveSettings(settingName, false); settings[settingName] = false; } @@ -396,7 +397,7 @@ async function hasPermission(permission) { return new Promise(resolve => { chrome.runtime.sendMessage({ action: 'checkPermission', permission: permission }, (response) => { if (chrome.runtime.lastError) { - console.error("RoValra: Error checking permission:", chrome.runtime.lastError.message); + log(logLevel.ERROR, "RoValra: Error checking permission:", chrome.runtime.lastError.message); resolve(false); } resolve(response?.granted || false); @@ -422,7 +423,7 @@ async function revokePermission(permission) { return new Promise(resolve => { chrome.runtime.sendMessage({ action: 'revokePermission', permission: permission }, (response) => { if (chrome.runtime.lastError) { - console.error(`RoValra: Failed to revoke '${permission}' permission:`, chrome.runtime.lastError.message); + log(logLevel.ERROR, `RoValra: Failed to revoke '${permission}' permission:`, chrome.runtime.lastError.message); resolve(false); } resolve(response?.revoked || false); @@ -459,7 +460,7 @@ export async function updateAllPermissionToggles() { } if (missingPerms) { - console.log(`RoValra: Disabling '${settingName}' because required permissions are missing.`); + log(logLevel.WARNING, `RoValra: Disabling '${settingName}' because required permissions are missing.`); await handleSaveSettings(settingName, false); settings[settingName] = false; @@ -592,8 +593,8 @@ export function initializeSettingsEventListeners() { ]; } - console.log("Generated Environment JSON:\n" + JSON.stringify(envConfig, null, 2)); - alert("Environment JSON has been printed to the console (F12)."); + log(logLevel.INFO, "Generated Environment JSON:\n" + JSON.stringify(envConfig, null, 2)); + log(logLevel.CRITICAL, "Environment JSON has been printed to the console (F12)."); }); chrome.runtime.onMessage.addListener((request) => { @@ -651,7 +652,7 @@ export function initializeSettingsEventListeners() { if (!granted) { target.checked = false; - console.log(`RoValra: Permission denied for ${settingName}`); + log(logLevel.WARNING, `RoValra: Permission denied for ${settingName}`); return; } } @@ -704,7 +705,7 @@ export function initializeSettingsEventListeners() { } } }).catch(error => { - console.error("Error saving one or more settings:", error); + log(logLevel.ERROR, "Error saving one or more settings:", error); }); }); diff --git a/src/content/core/settings/portSettings.js b/src/content/core/settings/portSettings.js index a3fe91b..b09a464 100644 --- a/src/content/core/settings/portSettings.js +++ b/src/content/core/settings/portSettings.js @@ -1,6 +1,7 @@ import { createButton } from '../../core/ui/buttons.js'; import { sanitizeSettings } from '../utils/sanitize.js'; import { SETTINGS_CONFIG } from './settingConfig.js'; +import { log, logLevel } from '../../core/logging.js'; const ROVALRA_SETTINGS_UUID = 'a1b2c3d4-e5f6-7890-1234-567890abcdef'; @@ -9,8 +10,8 @@ export async function exportSettings() { try { chrome.storage.local.get('rovalra_settings', (result) => { if (chrome.runtime.lastError) { - console.error('Failed to export settings:', chrome.runtime.lastError); - alert('Error exporting settings. Check the console for details.'); + log(logLevel.ERROR, 'Failed to export settings:', chrome.runtime.lastError); + log(logLevel.CRITICAL, 'Error exporting settings. Check the console for details.'); return; } @@ -20,8 +21,8 @@ export async function exportSettings() { try { sanitizedSettings = sanitizeSettings(allSettings, SETTINGS_CONFIG); } catch (error) { - console.error('Failed to sanitize settings for export:', error); - alert('Error sanitizing settings for export. Check the console for details.'); + log(logLevel.ERROR, 'Failed to sanitize settings for export:', error); + log(logLevel.CRITICAL, 'Error sanitizing settings for export. Check the console for details.'); return; } @@ -41,8 +42,8 @@ export async function exportSettings() { URL.revokeObjectURL(url); }); } catch (error) { - console.error('Error in exportSettings:', error); - alert('An unexpected error occurred during export.'); + log(logLevel.ERROR, 'Error in exportSettings:', error); + log(logLevel.CRITICAL, 'An unexpected error occurred during export.'); } } @@ -66,7 +67,7 @@ export async function importSettings() { const importedData = JSON.parse(content); if (importedData.rovalra_uuid !== ROVALRA_SETTINGS_UUID) { - alert('This does not appear to be a valid RoValra settings file.'); + log(logLevel.CRITICAL, 'This does not appear to be a valid RoValra settings file.'); return; } @@ -75,21 +76,21 @@ export async function importSettings() { try { sanitizedSettings = sanitizeSettings(importedData.settings, SETTINGS_CONFIG); } catch (error) { - console.error('Failed to sanitize imported settings:', error); - alert('Error: The imported settings file contains invalid or potentially dangerous data.'); + log(logLevel.ERROR, 'Failed to sanitize imported settings:', error); + log(logLevel.CRITICAL, 'Error: The imported settings file contains invalid or potentially dangerous data.'); return; } const settingsSize = JSON.stringify(sanitizedSettings).length; if (settingsSize > 1024 * 1024) { - alert('Error: Settings file is too large. Maximum size is 1MB.'); + log(logLevel.CRITICAL, 'Error: Settings file is too large. Maximum size is 1MB.'); return; } chrome.storage.local.set(sanitizedSettings, () => { if (chrome.runtime.lastError) { - console.error('Failed to import settings:', chrome.runtime.lastError); - alert('Error importing settings. Check the console for details.'); + log(logLevel.ERROR, 'Failed to import settings:', chrome.runtime.lastError); + log(logLevel.CRITICAL, 'Error importing settings. Check the console for details.'); } else { chrome.storage.local.set({ rovalra_settings: sanitizedSettings }, () => { location.reload(); @@ -97,11 +98,11 @@ export async function importSettings() { } }); } else { - alert('The settings file is malformed.'); + log(logLevel.CRITICAL, 'The settings file is malformed.'); } } catch (error) { - console.error('Error parsing or processing settings file:', error); - alert('Could not read the settings file. It might be corrupted or in the wrong format.'); + log(logLevel.ERROR, 'Error parsing or processing settings file:', error); + log(logLevel.CRITICAL, 'Could not read the settings file. It might be corrupted or in the wrong format.'); } }; reader.readAsText(file); @@ -109,8 +110,8 @@ export async function importSettings() { input.click(); } catch (error) { - console.error('Error in importSettings:', error); - alert('An unexpected error occurred during import.'); + log(logLevel.ERROR, 'Error in importSettings:', error); + log(logLevel.CRITICAL, 'An unexpected error occurred during import.'); } } diff --git a/src/content/core/utils/assetStreamer.js b/src/content/core/utils/assetStreamer.js index 7e26ed4..f27b1ad 100644 --- a/src/content/core/utils/assetStreamer.js +++ b/src/content/core/utils/assetStreamer.js @@ -1,6 +1,7 @@ import { parseRbxm } from './rbxm.js'; import { callRobloxApi } from '../api.js'; +import { log, logLevel } from '../logging.js'; const RBXM_SIGNATURE_BYTES = [60, 114, 111, 98, 108, 111, 120, 33]; @@ -85,7 +86,7 @@ export async function checkAssetsInBatch(assetIds) { }); if (!batchApiResponse.ok) { - console.error(`[Rovalra Asset Parser] AssetDelivery batch API failed: ${batchApiResponse.status}`); + log(logLevel.ERROR, `[Rovalra Asset Parser] AssetDelivery batch API failed: ${batchApiResponse.status}`); return assetIds.map(id => createDefaultResult(id)); } @@ -133,7 +134,7 @@ export async function checkAssetsInBatch(assetIds) { }; } catch (error) { - console.error(`[Rovalra Asset Parser] Error parsing asset ${id}:`, error); + log(logLevel.ERROR, `[Rovalra Asset Parser] Error parsing asset ${id}:`, error); return createDefaultResult(id); } }); @@ -141,7 +142,7 @@ export async function checkAssetsInBatch(assetIds) { return Promise.all(processingPromises); } catch (error) { - console.error('[Rovalra Asset Parser] Critical error:', error); + log(logLevel.ERROR, '[Rovalra Asset Parser] Critical error:', error); return assetIds.map(id => createDefaultResult(id)); } } \ No newline at end of file diff --git a/src/content/core/utils/launcher.js b/src/content/core/utils/launcher.js index 733c1c4..e7282d1 100644 --- a/src/content/core/utils/launcher.js +++ b/src/content/core/utils/launcher.js @@ -1,11 +1,12 @@ // This script should always be used to start up the Roblox client import { callRobloxApiJson } from '../api.js'; +import { log, logLevel } from '../logging.js'; function executeLaunchScript(codeToInject) { if (typeof chrome !== 'undefined' && chrome.runtime) { chrome.runtime.sendMessage({ action: "injectScript", codeToInject }); } else { - console.error("RoValra Launcher: Chrome runtime is not available to inject the script."); + log(logLevel.ERROR, "RoValra Launcher: Chrome runtime is not available to inject the script."); } } @@ -65,7 +66,7 @@ export async function launchStudioForGame(placeId) { throw new Error(`Could not retrieve universeId for placeId ${placeId}`); } } catch (error) { - console.error('RoValra Launcher: Failed to launch studio with universeId, falling back.', error); + log(logLevel.ERROR, 'RoValra Launcher: Failed to launch studio with universeId, falling back.', error); const uri = `roblox-studio:launchmode:edit+task:EditPlace+placeId:${placeId}`; const codeToInject = `window.location.href = '${uri}';`; executeLaunchScript(codeToInject); diff --git a/src/content/core/utils/location.js b/src/content/core/utils/location.js index d1b8676..71fa1c3 100644 --- a/src/content/core/utils/location.js +++ b/src/content/core/utils/location.js @@ -1,4 +1,5 @@ import { callRobloxApi } from '../api.js'; +import { log, logLevel} from '../logging.js'; const LOCATION_STORAGE_KEY = 'robloxUserLocationCache'; const CACHE_DURATION_MS = 1000 * 60 * 60 * 24; @@ -27,7 +28,7 @@ async function resolveGeoNames(lat, lon) { countryCode: locationInfo ? locationInfo.code : "??", }; } catch (e) { - console.error("Location Util: Static API lookup failed", e); + log(logLevel.ERROR, "Location Util: Static API lookup failed", e); return { country: "Unknown", continent: "Unknown", countryCode: "??" }; } } @@ -49,11 +50,11 @@ export async function getUserLocation(placeId, forceRefresh = false) { return storedData; } } catch (e) { - console.error("Location Util: Error reading storage", e); + log(logLevel.ERROR, "Location Util: Error reading storage", e); } } - console.log('Location Util: Fetching fresh user location via Roblox API...'); + log(logLevel.DEBUG, 'Location Util: Fetching fresh user location via Roblox API...'); try { const serverListRes = await callRobloxApi({ @@ -88,7 +89,7 @@ export async function getUserLocation(placeId, forceRefresh = false) { } } } catch (error) { - console.error('Location Util: Critical Error', error); + log(logLevel.ERROR, 'Location Util: Critical Error', error); } return null; } @@ -140,6 +141,6 @@ export async function updateUserLocationIfChanged(freshCoords) { await chrome.storage.local.set({ [LOCATION_STORAGE_KEY]: cacheObject }); } } catch (e) { - console.error("Location Util: Error in update", e); + log(logLevel.ERROR, "Location Util: Error in update", e); } } \ No newline at end of file diff --git a/src/content/core/utils/trackers/friendslist.js b/src/content/core/utils/trackers/friendslist.js index ac8fc2d..ae36431 100644 --- a/src/content/core/utils/trackers/friendslist.js +++ b/src/content/core/utils/trackers/friendslist.js @@ -1,5 +1,6 @@ import { callRobloxApiJson } from "../../api"; import { getAuthenticatedUserId } from "../../user"; +import { log, logLevel } from "../../logging"; const FRIENDS_DATA_KEY = 'rovalra_friends_data'; @@ -18,7 +19,7 @@ async function fetchFriendsPage(userId, cursor = null) { endpoint: endpoint, }); } catch (error) { - console.error('RoValra: Failed to fetch friends list page', error); + log(logLevel.ERROR, 'RoValra: Failed to fetch friends list page', error); return null; } } @@ -35,7 +36,7 @@ async function fetchUserProfileData(userIds) { } }); } catch (error) { - console.error('RoValra: Failed to fetch user profile data', error); + log(logLevel.ERROR, 'RoValra: Failed to fetch user profile data', error); return null; } } @@ -52,7 +53,7 @@ async function fetchTrustedFriendsStatus(userId, friendIds) { }); return new Set(data?.trustedFriendsId || []); } catch (error) { - console.error('RoValra: Failed to fetch trusted friends status', error); + log(logLevel.ERROR, 'RoValra: Failed to fetch trusted friends status', error); return new Set(); } } @@ -106,11 +107,11 @@ export async function updateFriendsList(userId) { }; await new Promise(resolve => chrome.storage.local.set({ [FRIENDS_DATA_KEY]: allUsersFriendsData }, resolve)); - console.log('RoValra: Friends list and timestamp updated in local storage for user', userId); + log(logLevel.DEBUG, 'RoValra: Friends list and timestamp updated in local storage for user', userId); return fullFriendsList; } catch (error) { - console.error('RoValra: Failed to update friends list', error); + log(logLevel.ERROR, 'RoValra: Failed to update friends list', error); return []; } } diff --git a/src/content/features/avatar/avatarRotator.js b/src/content/features/avatar/avatarRotator.js index ff229de..3487748 100644 --- a/src/content/features/avatar/avatarRotator.js +++ b/src/content/features/avatar/avatarRotator.js @@ -5,6 +5,7 @@ import { callRobloxApiJson } from '../../core/api.js'; import { getBatchThumbnails, createThumbnailElement } from '../../core/thumbnail/thumbnails.js'; import { createRadioButton } from '../../core/ui/general/radio.js'; import { createStyledInput } from '../../core/ui/catalog/input.js'; +import { log, logLevel } from '../../core/logging.js'; export function init() { if (!window.location.pathname.includes('/my/avatar')) return; @@ -159,7 +160,7 @@ export function init() { nextPageCursor = response.nextPageToken || null; } catch (error) { - console.error('RoValra: Failed to fetch avatars', error); + log(logLevel.ERROR, 'RoValra: Failed to fetch avatars', error); } finally { isLoading = false; if (loadBtn) { diff --git a/src/content/features/avatar/filters.js b/src/content/features/avatar/filters.js index ce38fb7..24b9b7c 100644 --- a/src/content/features/avatar/filters.js +++ b/src/content/features/avatar/filters.js @@ -1,6 +1,7 @@ import { checkAssetsInBatch } from '../../core/utils/assetStreamer.js'; import { observeElement, observeAttributes } from '../../core/observer.js'; import { createAvatarFilterUI } from '../../core/ui/FiltersUI.js'; +import { log, logLevel } from '../../core/logging.js'; export function init() { if (!window.location.pathname.includes('/my/avatar')) return; @@ -362,7 +363,7 @@ export function init() { await processItemIds(Array.from(itemIdsToRecheck), currentSession); } } catch(e) { - console.error("Filter process error", e); + log(logLevel.ERROR, "Filter process error", e); } finally { if (currentSession === scanSessionId) { triggerDomUpdate(); diff --git a/src/content/features/catalog/40method.js b/src/content/features/catalog/40method.js index a6d8f71..4ffb9ad 100644 --- a/src/content/features/catalog/40method.js +++ b/src/content/features/catalog/40method.js @@ -13,6 +13,8 @@ import { fetchThumbnails } from '../../core/thumbnail/thumbnails.js'; import DOMPurify from 'dompurify'; import { getPlaceIdFromUrl } from '../../core/idExtractor.js'; +import { log, logLevel } from '../../core/logging.js'; + const ROVALRA_PLACE_ID = '107845747621646'; @@ -93,7 +95,7 @@ async function publishTemplateToPlace(targetPlaceId) { return true; } catch (error) { - console.error('RoValra: Auto-publish failed', error); + log(logLevel.ERROR, 'RoValra: Auto-publish failed', error); throw error; } } @@ -410,7 +412,7 @@ const detectAndAddSaveButton = () => { export const createAndShowPopup = (onSave, initialState = null) => { const currentUserId = getCurrentUserId(); if (!currentUserId) { - alert("Could not identify your user ID. Please make sure you are logged in."); + log(logLevel.CRITICAL, "Could not identify your user ID. Please make sure you are logged in."); return; } @@ -660,15 +662,15 @@ export const createAndShowPopup = (onSave, initialState = null) => { useRoValraGroup: useGroup }, () => { if (chrome.runtime.lastError) { - console.error('RoValra: Storage save error:', chrome.runtime.lastError); - alert('Failed to save settings: ' + chrome.runtime.lastError.message); + log(logLevel.ERROR, 'RoValra: Storage save error:', chrome.runtime.lastError); + log(logLevel.CRITICAL, 'Failed to save settings: ' + chrome.runtime.lastError.message); } else { if (onSuccess) onSuccess(); } }); } else { - console.error('RoValra: Storage API unavailable.'); - alert('Failed to save settings. Storage API unavailable.'); + log(logLevel.ERROR, 'RoValra: Storage API unavailable.'); + log(logLevel.CRITICAL, 'Failed to save settings. Storage API unavailable.'); } }; @@ -730,7 +732,7 @@ export const createAndShowPopup = (onSave, initialState = null) => { if (vResp && vResp.results && vResp.results.length > 0) { initialUserPlaceVersion = vResp.results[0].versionNumber; } - } catch (e) { console.error("RoValra: Failed to fetch initial place version", e); } + } catch (e) { log(logLevel.ERROR, "RoValra: Failed to fetch initial place version", e); } viewUpdateInstructions.classList.remove('sr-hidden'); return; } @@ -768,9 +770,9 @@ export const createAndShowPopup = (onSave, initialState = null) => { viewNonOwnerAck.classList.remove('sr-hidden'); } } catch (error) { - console.error("Failed to fetch group details:", error); + log(logLevel.ERROR, "Failed to fetch group details:", error); close(); - alert("Could not check group ownership. Please try again."); + log(logLevel.CRITICAL, "Could not check group ownership. Please try again."); } }; @@ -811,7 +813,7 @@ export const createAndShowPopup = (onSave, initialState = null) => { } } catch {} } catch (error) { - console.error('RoValra: Failed to fetch groups:', error); + log(logLevel.ERROR, 'RoValra: Failed to fetch groups:', error); groupDropdownContainer.innerHTML = DOMPurify.sanitize('
Failed to load groups. Please refresh and try again.
'); } }; @@ -852,7 +854,7 @@ createNewGameBtn.addEventListener('click', async () => { if (errorEl) errorEl.style.display = 'none'; if (!selectedGroupId) { - alert('No group selected. Please try again.'); + log(logLevel.CRITICAL, 'No group selected. Please try again.'); return; } @@ -879,7 +881,7 @@ createNewGameBtn.addEventListener('click', async () => { const newUniverseId = createResponse.universeId; const newPlaceId = createResponse.rootPlaceId; - console.log(`RoValra: Created Universe ${newUniverseId}, Place ${newPlaceId}`); + log(logLevel.INFO, `RoValra: Created Universe ${newUniverseId}, Place ${newPlaceId}`); createNewGameBtn.textContent = 'Uploading Template...'; await publishTemplateToPlace(newPlaceId, newUniverseId); @@ -911,7 +913,7 @@ createNewGameBtn.addEventListener('click', async () => { }); } catch (error) { - console.error('RoValra: Create Game Error', error); + log(logLevel.ERROR, 'RoValra: Create Game Error', error); if (errorEl) { if (error.response && error.response.code === "InvalidRequest" && error.response.message === "User is not authorized to perform this action") { errorEl.textContent = "You don't have permission to manage experiences in this group. Please give yourself a role with the right permissions."; @@ -920,7 +922,7 @@ createNewGameBtn.addEventListener('click', async () => { } errorEl.style.display = 'block'; } else { - alert(`Error creating experience: ${error.message}. Please try again.`); + log(logLevel.CRITICAL, `Error creating experience: ${error.message}. Please try again.`); } createNewGameBtn.textContent = originalText; createNewGameBtn.disabled = false; @@ -967,7 +969,7 @@ createNewGameBtn.addEventListener('click', async () => { viewFindingGame.classList.add('sr-hidden'); } } catch (error) { - console.error("Failed to find new experience:", error); + log(logLevel.ERROR, "Failed to find new experience:", error); viewFindingGame.classList.add('sr-hidden'); } }); @@ -1065,8 +1067,8 @@ createNewGameBtn.addEventListener('click', async () => { viewUpdateInstructions.classList.add('sr-hidden'); manualAckView.classList.remove('sr-hidden'); } catch (e) { - console.error('RoValra: Update failed', e); - alert(`Update failed: ${e.message}. Please try again.`); + log(logLevel.ERROR, 'RoValra: Update failed', e); + log(logLevel.CRITICAL, `Update failed: ${e.message}. Please try again.`); } finally { updateConfirmBtn.textContent = originalText; updateConfirmBtn.disabled = !updateAgreeCheckbox.checked; @@ -1114,7 +1116,7 @@ createNewGameBtn.addEventListener('click', async () => { } } catch (e) { - console.error('Failed to validate place ID:', e); + log(logLevel.ERROR, 'Failed to validate place ID:', e); gameIdErrorEl.textContent = 'Could not validate the ID. Please try again.'; gameIdErrorEl.style.display = 'block'; return; @@ -1310,7 +1312,7 @@ const executeCartPurchase = async (cartItems, prefetchData = null, bypassValidat const ensureNotCancelled = () => { if (ctx.cancelled) throw new Error('Purchase cancelled'); }; const currentUserId = getCurrentUserId(); if (!currentUserId) { - alert("Could not identify your user ID. Please make sure you are logged in."); + log(logLevel.CRITICAL, "Could not identify your user ID. Please make sure you are logged in."); return; } @@ -1346,7 +1348,7 @@ const executeCartPurchase = async (cartItems, prefetchData = null, bypassValidat const useRoValraGroup = result.useRoValraGroup === true; if (!savedPlaceId) { - alert('No saved Place ID. Please set one up first using the "Save Robux" button.'); + log(logLevel.CRITICAL, 'No saved Place ID. Please set one up first using the "Save Robux" button.'); return; } @@ -1621,7 +1623,7 @@ const execute40MethodPurchase = async (itemId, robuxPrice, isGamePass = false, i const ensureNotCancelled = () => { if (ctx.cancelled) throw new Error('Purchase cancelled'); }; const currentUserId = getCurrentUserId(); if (!currentUserId) { - alert("Could not identify your user ID. Please make sure you are logged in."); + log(logLevel.CRITICAL, "Could not identify your user ID. Please make sure you are logged in."); return; } @@ -1638,7 +1640,7 @@ const execute40MethodPurchase = async (itemId, robuxPrice, isGamePass = false, i const useRoValraGroup = result.useRoValraGroup === true; if (!savedPlaceId) { - alert('No saved Place ID. Please set one up first using the "Save Robux" button.'); + log(logLevel.CRITICAL, 'No saved Place ID. Please set one up first using the "Save Robux" button.'); return; } @@ -2298,6 +2300,6 @@ export function init() { } }); } else { - console.error('RoValra: Chrome storage API not available.'); + log(logLevel.ERROR, 'RoValra: Chrome storage API not available.'); } } diff --git a/src/content/features/catalog/hiddenCatalog.js b/src/content/features/catalog/hiddenCatalog.js index 227ac30..9ebf023 100644 --- a/src/content/features/catalog/hiddenCatalog.js +++ b/src/content/features/catalog/hiddenCatalog.js @@ -2,6 +2,7 @@ import { observeElement } from '../../core/observer.js'; import { callRobloxApiJson } from '../../core/api.js'; import { getAssets } from '../../core/assets.js'; import { fetchThumbnails } from '../../core/thumbnail/thumbnails.js'; +import { log, logLevel } from '../../core/logging.js'; import DOMPurify from 'dompurify'; let currentMode = 'dark'; @@ -43,7 +44,7 @@ async function fetchItemDetails(itemId) { const data = await callRobloxApiJson({ subdomain: 'economy', endpoint: `/v2/assets/${itemId}/details` }); return data || null; } catch (err) { - console.error('Failed to fetch item details for', itemId, err); + log(logLevel.ERROR, 'Failed to fetch item details for', itemId, err); return null; } } @@ -300,7 +301,7 @@ async function displayItems(itemsWithDetails) { } }); } catch (err) { - console.error('Hidden Catalog thumbnails error', err); + log(logLevel.ERROR, 'Hidden Catalog thumbnails error', err); } } @@ -330,7 +331,7 @@ async function fetchDataFromAPI(page = 1, limit = 24) { const itemsWithDetails = items.map((it, idx) => ({ ...it, details: details[idx] })); return itemsWithDetails; } catch (err) { - console.error('Hidden Catalog fetch failed', err); + log(logLevel.ERROR, 'Hidden Catalog fetch failed', err); displayApiError('Failed to fetch hidden catalog items.'); return []; } diff --git a/src/content/features/create.roblox.com/download.js b/src/content/features/create.roblox.com/download.js index 5d88312..ec24839 100644 --- a/src/content/features/create.roblox.com/download.js +++ b/src/content/features/create.roblox.com/download.js @@ -3,6 +3,7 @@ import { checkAssetsInBatch } from '../../core/utils/assetStreamer.js'; import { observeElement } from '../../core/observer.js'; import { callRobloxApi } from '../../core/api.js'; import { getAssets } from '../../core/assets.js'; +import { log, logLevel } from '../../core/logging.js'; function saveAsFile(data, fileName, mimeType) { @@ -45,7 +46,7 @@ async function downloadAsset(assetId) { } } } catch (e) { - console.error('[RoValra DL] Failed to fetch asset location:', e); + log(logLevel.ERROR, '[RoValra DL] Failed to fetch asset location:', e); } if (assetLocation) { @@ -65,7 +66,7 @@ async function downloadAsset(assetId) { return; } catch (e) { - console.error(`[RoValra DL] Failed to process raw asset:`, e); + log(logLevel.ERROR, `[RoValra DL] Failed to process raw asset:`, e); } } @@ -86,7 +87,7 @@ async function downloadAsset(assetId) { serializedData = JSON.stringify(asset.root, null, 2); fileExtension = 'rbxmx'; } else { - console.error(`Unknown asset format: ${asset.format}`); + log(logLevel.ERROR, `Unknown asset format: ${asset.format}`); return; } diff --git a/src/content/features/developer/videotest.js b/src/content/features/developer/videotest.js index 4235182..4cd27e2 100644 --- a/src/content/features/developer/videotest.js +++ b/src/content/features/developer/videotest.js @@ -1,5 +1,6 @@ import { streamRobloxVideo } from '../../core/utils/videoStreamer.js'; import { callRobloxApiJson } from '../../core/api.js'; +import { log, logLevel } from '../../core/logging.js'; const VIDEO_ASSETS = [ { id: 126397822635206, name: "Original Test" }, diff --git a/src/content/features/games/actions/quickOutfits.js b/src/content/features/games/actions/quickOutfits.js index 401a739..90a3bfa 100644 --- a/src/content/features/games/actions/quickOutfits.js +++ b/src/content/features/games/actions/quickOutfits.js @@ -8,6 +8,7 @@ import { createScrollButtons } from '../../../core/ui/general/scrollButtons.js'; import { addTooltip } from '../../../core/ui/tooltip.js'; import DOMPurify from 'dompurify'; import { showSystemAlert } from '../../../core/ui/roblox/alert.js'; +import { log, logLevel } from '../../../core/logging.js'; async function fetchAllOutfits(userId) { let outfits = []; @@ -117,7 +118,7 @@ async function wearOutfit(outfitId) { const results = await Promise.all(promises); return { ok: results.every(r => r.ok) }; } catch (e) { - console.error(e); + log(logLevel.ERROR, e); return { ok: false }; } } @@ -207,7 +208,7 @@ function createOutfitCard(outfit, thumbnailData, onSuccess) { name.style.color = "#ff4444"; } } catch (e) { - name.textContent = "Error:", console.error(e); + name.textContent = "Error:", log(logLevel.ERROR, e); name.style.color = "#ff4444"; } @@ -224,7 +225,7 @@ function createOutfitCard(outfit, thumbnailData, onSuccess) { async function showQuickOutfitsOverlay() { const userId = await getAuthenticatedUserId(); if (!userId) { - console.error("authed user id not found :C for quick outfits") + log(logLevel.ERROR, "authed user id not found :C for quick outfits") return; } @@ -385,7 +386,7 @@ async function showQuickOutfitsOverlay() { renderPage(); } catch (e) { - console.error(e); + log(logLevel.ERROR, e); gridContainer.innerHTML = DOMPurify.sanitize('
Error loading outfits.
'); } } diff --git a/src/content/features/games/revertlogo.js b/src/content/features/games/revertlogo.js index 098ded7..4a57583 100644 --- a/src/content/features/games/revertlogo.js +++ b/src/content/features/games/revertlogo.js @@ -4,6 +4,7 @@ import { fetchThumbnails } from '../../core/thumbnail/thumbnails.js'; import { callRobloxApi } from '../../core/api.js'; import DOMPurify from 'dompurify'; import { launchGame } from '../../core/utils/launcher.js'; +import { log, logLevel } from '../../core/logging.js'; import { showLoadingOverlay, @@ -556,7 +557,7 @@ function initializeJoinDialogEnhancer() { } } } catch (innerErr) { - console.error("Join API attempt failed:", innerErr); + log(logLevel.ERROR, "Join API attempt failed:", innerErr); } if (!joinApiResponse && retries < MAX_RETRIES) { @@ -569,7 +570,7 @@ function initializeJoinDialogEnhancer() { } } catch (e) { - console.error("Error in server info fetch loop:", e); + log(logLevel.ERROR, "Error in server info fetch loop:", e); } } @@ -633,7 +634,7 @@ function initializeJoinDialogEnhancer() { pollClientStatus(placeId); } catch (e) { - console.error("Rendering error:", e); + log(logLevel.ERROR, "Rendering error:", e); const details = await gameDetailsPromise; updateServerInfo(details.name, details.iconUrl, null); updateLoadingOverlayText(`Launching...`); diff --git a/src/content/features/games/tab/DevProducts.js b/src/content/features/games/tab/DevProducts.js index e778626..22e35dc 100644 --- a/src/content/features/games/tab/DevProducts.js +++ b/src/content/features/games/tab/DevProducts.js @@ -7,6 +7,7 @@ import { createDropdown } from '../../../core/ui/dropdown.js'; import { createStyledInput } from '../../../core/ui/catalog/input.js'; import { getPlaceIdFromUrl } from '../../../core/idExtractor.js'; import { createScrollButtons } from '../../../core/ui/general/scrollButtons.js'; +import { log, logLevel } from '../../../core/logging.js'; async function fetchUniverseId(placeId) { const metaData = document.getElementById('game-detail-meta-data'); @@ -25,7 +26,7 @@ async function fetchUniverseId(placeId) { const data = await response.json(); return data?.[0]?.universeId; } catch (error) { - console.error('RoValra: Error fetching universe ID', error); + log(logLevel.ERROR, 'RoValra: Error fetching universe ID', error); return null; } } @@ -55,7 +56,7 @@ async function fetchDevProducts(universeId) { return allProducts; } catch (error) { - console.error('RoValra: Error fetching developer products', error); + log(logLevel.ERROR, 'RoValra: Error fetching developer products', error); return allProducts; } } diff --git a/src/content/features/groups/Antibots.js b/src/content/features/groups/Antibots.js index 75fce91..e64b077 100644 --- a/src/content/features/groups/Antibots.js +++ b/src/content/features/groups/Antibots.js @@ -4,6 +4,7 @@ import { callRobloxApiJson } from '../../core/api.js'; import { createRadioButton } from '../../core/ui/general/radio.js'; import { createOverlay } from '../../core/ui/overlay.js'; import { fetchThumbnails, createThumbnailElement } from '../../core/thumbnail/thumbnails.js'; +import { log, logLevel } from '../../core/logging.js'; import DOMPurify from 'dompurify'; @@ -597,7 +598,7 @@ async function addFeatureButtons(searchContainer) { } } catch (e) { loader.remove(); - if (e.name !== 'AbortError') console.error(e); + if (e.name !== 'AbortError') log(logLevel.ERROR, e); } finally { quickActionState.isLoading = false; } @@ -732,7 +733,7 @@ async function addFeatureButtons(searchContainer) { more = !!cursor; } catch (err) { - console.error("Error fetching page:", err); + log(logLevel.ERROR, "Error fetching page:", err); break; } } @@ -779,7 +780,7 @@ async function addFeatureButtons(searchContainer) { } catch (e) { if (e.message !== 'Aborted') { antiBotsButton.textContent = 'Error - Retry'; - console.error(e); + log(logLevel.ERROR, e); if (membersTitleElement) membersTitleElement.textContent = 'Error during scan.'; } else { resetUiState(); diff --git a/src/content/features/navigation/QoLToggles.js b/src/content/features/navigation/QoLToggles.js index 5121e22..18f0ffe 100644 --- a/src/content/features/navigation/QoLToggles.js +++ b/src/content/features/navigation/QoLToggles.js @@ -3,6 +3,7 @@ import { createNavbarButton } from '../../core/ui/navbarButton.js'; import { createDropdownMenu, createDropdown } from '../../core/ui/dropdown.js'; import { createRadioButton } from '../../core/ui/general/radio.js'; import { callRobloxApi } from '../../core/api.js'; +import { log, logLevel } from '../../core/logging.js'; export function init() { chrome.storage.local.get({ qolTogglesEnabled: true }, (settings) => { @@ -131,7 +132,7 @@ export function init() { endpoint: '/user-settings-api/v1/user-settings', method: 'POST', body: payload - }).catch(e => console.error('Failed to update status', e)); + }).catch(e => log(logLevel.ERROR, 'Failed to update status', e)); const onlineDropdownEl = document.getElementById('rovalra-qol-onlineStatus-dropdown'); const joinDropdownEl = document.getElementById('rovalra-qol-joinStatus-dropdown'); @@ -151,7 +152,7 @@ export function init() { endpoint: '/user-settings-api/v1/user-settings', method: 'POST', body: { whoCanJoinMeInExperiences: newJoinValue } - }).catch(e => console.error('Failed to update join status', e)); + }).catch(e => log(logLevel.ERROR, 'Failed to update join status', e)); } } } else { // isJoinStatus @@ -169,7 +170,7 @@ export function init() { endpoint: '/user-settings-api/v1/user-settings', method: 'POST', body: { whoCanSeeMyOnlineStatus: newOnlineValue } - }).catch(e => console.error('Failed to update online status', e)); + }).catch(e => log(logLevel.ERROR, 'Failed to update online status', e)); } } } @@ -182,7 +183,7 @@ export function init() { endpoint: '/user-settings-api/v1/user-settings', method: 'POST', body: { whoCanJoinMeInExperiences: 'NoOne' } - }).catch(e => console.error('Failed to update join status', e)); + }).catch(e => log(logLevel.ERROR, 'Failed to update join status', e)); } } } diff --git a/src/content/features/navigation/betaprograms.js b/src/content/features/navigation/betaprograms.js index 4f9a802..3fec842 100644 --- a/src/content/features/navigation/betaprograms.js +++ b/src/content/features/navigation/betaprograms.js @@ -4,6 +4,7 @@ import { callRobloxApi, callRobloxApiJson } from '../../core/api.js'; import { createDropdownMenu } from '../../core/ui/dropdown.js'; import { createSpinner } from '../../core/ui/spinner.js'; import { getAssets } from '../../core/assets.js'; +import { log, logLevel} from '../../core/logging.js'; async function optInBeta(programId) { return callRobloxApi({ @@ -182,7 +183,7 @@ export async function addNavbarButton() { menu.toggle(true); } catch (error) { - console.error('RoValra: Failed to fetch beta programs', error); + log(logLevel.ERROR, 'RoValra: Failed to fetch beta programs', error); if (menu) menu.toggle(false); } finally { isLoading = false; diff --git a/src/content/features/navigation/search/quicksearch.js b/src/content/features/navigation/search/quicksearch.js index 79267f8..cdc9201 100644 --- a/src/content/features/navigation/search/quicksearch.js +++ b/src/content/features/navigation/search/quicksearch.js @@ -13,6 +13,7 @@ import { getFullRegionName, getRegionData } from '../../../core/regions.js'; import { createScrollButtons } from '../../../core/ui/general/scrollButtons.js'; import { showFriendListOverlay } from '../../../core/ui/games/friendListOverlay.js'; import { showConfirmationPrompt } from '../../../core/ui/confirmationPrompt.js'; +import { log, logLevel } from '../../../core/logging.js'; let lastSearchedQuery = ""; let userSearchAbortController = null; @@ -169,7 +170,7 @@ async function performUserSearch(query) { } injectIntoMenu(); }).catch(e => { - if (e.name !== 'AbortError') console.error('RoValra: Friend Search Error', e); + if (e.name !== 'AbortError') log(logLevel.ERROR, 'RoValra: Friend Search Error', e); }); const [userSearchData, localFriends] = await Promise.all(promises); @@ -259,7 +260,7 @@ async function performUserSearch(query) { injectIntoMenu(); } catch (e) { - if (e.name !== 'AbortError') console.error('RoValra: User Search Error', e); + if (e.name !== 'AbortError') log(logLevel.ERROR, 'RoValra: User Search Error', e); } } @@ -372,7 +373,7 @@ async function performGameSearch(query) { injectIntoMenu(); } catch (e) { - if (e.name !== 'AbortError') console.error('RoValra: Game Search Error', e); + if (e.name !== 'AbortError') log(logLevel.ERROR, 'RoValra: Game Search Error', e); } } diff --git a/src/content/features/onboarding/onboarding.js b/src/content/features/onboarding/onboarding.js index 825f6c1..c143d28 100644 --- a/src/content/features/onboarding/onboarding.js +++ b/src/content/features/onboarding/onboarding.js @@ -1,6 +1,7 @@ import { createOverlay } from '../../core/ui/overlay.js'; import { createButton } from '../../core/ui/buttons.js'; import { getAssets } from '../../core/assets.js'; +import { log, logLevel } from '../../core/logging.js'; export function init() { chrome.storage.local.get({ onboardingShown: false }, function(settings) { @@ -32,7 +33,7 @@ export function init() { const acknowledgeOnboarding = () => { chrome.storage.local.set({ onboardingShown: true }, function() { - console.log('RoValra: Onboarding acknowledged and marked as shown.'); + log(logLevel.DEBUG, 'RoValra: Onboarding acknowledged and marked as shown.'); }); }; diff --git a/src/content/features/profile/header/ProfileRender.js b/src/content/features/profile/header/ProfileRender.js index 0c60830..d93f20b 100644 --- a/src/content/features/profile/header/ProfileRender.js +++ b/src/content/features/profile/header/ProfileRender.js @@ -24,6 +24,7 @@ import { animNamesR6 } from 'roavatar-renderer'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { log, logLevel } from '../../../core/logging.js'; import * as THREE from 'three'; FLAGS.ASSETS_PATH = chrome.runtime.getURL("assets/rbxasset/"); @@ -197,7 +198,7 @@ async function createEmoteRadialMenu(emotesData, onSelect) { thumbResponse.data.forEach(item => { thumbMap[item.targetId] = item.imageUrl; }); - } catch (e) { console.error(e); } + } catch (e) { log(logLevel.ERROR, e); } } container.innerHTML = ` @@ -663,7 +664,7 @@ async function preloadAvatar() { camera.updateProjectionMatrix(); } })().catch(err => { - console.error("RoValra: Failed to load custom environment in background.", err); + log(logLevel.ERROR, "RoValra: Failed to load custom environment in background.", err); setupAtmosphere(scene, DEFAULT_VOID_CONFIG.atmosphere, false); }); @@ -672,7 +673,7 @@ async function preloadAvatar() { return globalAvatarData; } catch (err) { - console.error("RoValra Preload Error:", err); + log(logLevel.ERROR, "RoValra Preload Error:", err); return null; } finally { isPreloading = false; diff --git a/src/content/features/profile/hiddengames.js b/src/content/features/profile/hiddengames.js index 11a045f..c9133c2 100644 --- a/src/content/features/profile/hiddengames.js +++ b/src/content/features/profile/hiddengames.js @@ -7,6 +7,7 @@ import { fetchThumbnails as fetchThumbnailsBatch } from '../../core/thumbnail/th import { callRobloxApi } from '../../core/api.js'; import { safeHtml } from '../../core/packages/dompurify'; import { createGameCard } from '../../core/ui/games/gameCard.js'; +import { log, logLevel } from '../../core/logging.js'; const CONFIG = { PAGE_SIZE: 50, @@ -134,7 +135,7 @@ const Api = { async getUserGames(userId) { if (userListCache.has(userId)) { return userListCache.get(userId).catch((err) => { - console.error(err); + log(logLevel.ERROR, err); return []; }); } @@ -146,7 +147,7 @@ const Api = { userListCache.set(userId, fetchPromise); fetchPromise.catch(() => userListCache.delete(userId)); - return fetchPromise.catch((err) => (console.error(err), [])); + return fetchPromise.catch((err) => (log(logLevel.ERROR, err), [])); }, async enrichGameData(games, state) { diff --git a/src/content/features/profile/outfits.js b/src/content/features/profile/outfits.js index 50e1bb1..d3d1912 100644 --- a/src/content/features/profile/outfits.js +++ b/src/content/features/profile/outfits.js @@ -13,6 +13,7 @@ import DOMPurify from 'dompurify'; import { safeHtml } from '../../core/packages/dompurify'; import { createOverlay } from '../../core/ui/overlay.js'; import { createItemCard } from '../../core/ui/items/items.js'; +import { log, logLevel } from '../../core/logging.js'; export function init() { chrome.storage.local.get('useroutfitsEnabled', function (data) { @@ -875,11 +876,11 @@ export function init() { ); } } else { - alert('Could not determine the User ID from the page.'); + log(logLevel.CRITICAL, 'Could not determine the User ID from the page.'); } } catch (error) { if (!loadingControl.cancelled) - alert('Could not fetch outfits.'); + log(logLevel.CRITICAL, 'Could not fetch outfits.'); } }; diff --git a/src/content/features/profile/trustedfriends.js b/src/content/features/profile/trustedfriends.js index edaa988..0ee81a4 100644 --- a/src/content/features/profile/trustedfriends.js +++ b/src/content/features/profile/trustedfriends.js @@ -4,6 +4,7 @@ import { getUserIdFromUrl } from '../../core/idExtractor.js'; import { addTooltip } from '../../core/ui/tooltip.js'; import { showConfirmationPrompt } from '../../core/ui/confirmationPrompt.js'; import { showSystemAlert } from '../../core/ui/roblox/alert.js'; +import { log, logLevel } from '../../core/logging.js'; const profileStatusCache = new Map(); const TEST_ALWAYS_ERROR = false; @@ -48,7 +49,7 @@ async function getProfileStatus(userId) { return null; } catch (err) { - console.error('RoValra: Failed to fetch trusted friend status.', err); + log(logLevel.ERROR, 'RoValra: Failed to fetch trusted friend status.', err); profileStatusCache.delete(userId); throw err; } @@ -120,7 +121,7 @@ function createAddButton(userId) { showSystemAlert('Trusted connection request sent!', 'success'); await refreshButtonState(button, userId); }).catch(err => { - console.error('RoValra: Failed to send trusted friend request.', err); + log(logLevel.ERROR, 'RoValra: Failed to send trusted friend request.', err); showSystemAlert('Failed to send trusted connection request.', 'warning'); titleSpan.textContent = 'Error Sending'; setTimeout(() => { @@ -159,7 +160,7 @@ function createAcceptButton(userId) { showSystemAlert('Trusted connection request accepted!', 'success'); await refreshButtonState(button, userId); }).catch(err => { - console.error('RoValra: Failed to accept trusted friend request.', err); + log(logLevel.ERROR, 'RoValra: Failed to accept trusted friend request.', err); showSystemAlert('Failed to accept trusted connection request.', 'warning'); titleSpan.textContent = 'Error Accepting'; setTimeout(() => { @@ -199,7 +200,7 @@ function createRemoveButton(userId) { showSystemAlert('Trusted connection removed!', 'success'); await refreshButtonState(button, userId); }).catch(err => { - console.error('RoValra: Failed to remove trusted friend.', err); + log(logLevel.ERROR, 'RoValra: Failed to remove trusted friend.', err); showSystemAlert('Failed to remove trusted connection.', 'warning'); titleSpan.textContent = 'Error Removing'; setTimeout(() => { diff --git a/src/content/features/settings/index.js b/src/content/features/settings/index.js index 4740737..89b1450 100644 --- a/src/content/features/settings/index.js +++ b/src/content/features/settings/index.js @@ -29,6 +29,7 @@ import { callRobloxApi } from '../../core/api.js'; import { safeHtml } from '../../core/packages/dompurify'; import DOMPurify from 'dompurify'; import { BADGE_CONFIG } from '../../core/configs/badges.js'; +import { log, logLevel } from '../../core/logging.js'; const assets = getAssets(); let REGIONS = {}; @@ -647,7 +648,7 @@ function initializeHeartbeatSpoofer() { `RoValra: Spoofed heartbeat sent. Mode: ${spoofingMode}`, ); } catch (error) { - console.error('RoValra: Failed to send spoofed heartbeat.', error); + log(logLevel.ERROR, 'RoValra: Failed to send spoofed heartbeat.', error); } }; diff --git a/src/content/features/settings/roblox/firstAccount.js b/src/content/features/settings/roblox/firstAccount.js index cc38bfe..86cf1bf 100644 --- a/src/content/features/settings/roblox/firstAccount.js +++ b/src/content/features/settings/roblox/firstAccount.js @@ -2,6 +2,7 @@ import { observeElement } from '../../../core/observer.js'; import { getAssets } from '../../../core/assets.js'; import { addTooltip } from '../../../core/ui/tooltip.js'; import { getAuthenticatedUserId } from '../../../core/user.js'; +import { log, logLevel } from '../../../core/logging.js'; function createFirstAccountElement(isFirst, creationTimestamp) { const container = document.createElement('div'); @@ -119,7 +120,7 @@ export function init() { render(isOriginalUser, creationTimestamp); } }).catch(err => { - console.error('RoValra: Failed to get first account info', err); + log(logLevel.ERROR, 'RoValra: Failed to get first account info', err); }); }); } diff --git a/src/content/features/sitewide/cssfixes.js b/src/content/features/sitewide/cssfixes.js index 2037968..65e337f 100644 --- a/src/content/features/sitewide/cssfixes.js +++ b/src/content/features/sitewide/cssfixes.js @@ -1,4 +1,5 @@ import { observeElement } from '../../core/observer.js'; +import { log, logLevel } from '../../core/logging.js'; const applyImpersonateAttribute = (headerContainer) => { chrome.storage.local.get('impersonateRobloxStaffSetting', function (data) { @@ -43,7 +44,7 @@ const applyHeaderFix = (profileHeader) => { style.textContent = css; document.head.appendChild(style); - console.log("CSS Fixer: Header fix applied successfully."); + log(logLevel.INFO, "CSS Fixer: Header fix applied successfully."); } }; diff --git a/src/content/features/transactions/pendingRobuxTrans.js b/src/content/features/transactions/pendingRobuxTrans.js index fb01a25..4114658 100644 --- a/src/content/features/transactions/pendingRobuxTrans.js +++ b/src/content/features/transactions/pendingRobuxTrans.js @@ -1,6 +1,7 @@ import { observeElement } from '../../core/observer.js'; import { callRobloxApi, callRobloxApiJson } from '../../core/api.js'; import { addTooltip } from '../../core/ui/tooltip.js'; +import { log, logLevel } from '../../core/logging.js'; import DOMPurify, { safeHtml } from '../../core/packages/dompurify.js'; const API_LIMIT = 100; const MAX_PAGES_TO_FETCH_FOR_INFERENCE = 2000; @@ -23,7 +24,7 @@ const parseTimestamp = (timestampStr) => { if (isNaN(dt.getTime())) return null; return dt; } catch (e) { - console.error(e); + log(logLevel.ERROR, e); return null; } }; @@ -110,7 +111,7 @@ async function fetchTransactions(userId) { break transactionLoop; } } catch (e) { - console.error(e); + log(logLevel.ERROR, e); break transactionLoop; } } @@ -320,7 +321,7 @@ function injectResultElement(targetElement, result) { targetElement.classList.add('robux-estimator-processed'); } } catch (error) { - console.error(error); + log(logLevel.ERROR, error); } } } @@ -386,14 +387,14 @@ export function init() { }); state.userId = userData.id; if (!state.userId) { - console.error( + log(logLevel.ERROR, 'RoValra: Could not get user ID for pending Robux feature.', ); return; } observeElement(TARGET_ELEMENT_SELECTOR, onElementFound); } catch (e) { - console.error( + log(logLevel.ERROR, 'RoValra: Failed to initialize pending Robux feature.', e, ); diff --git a/src/content/features/transactions/totalearned.js b/src/content/features/transactions/totalearned.js index 232ddfb..244be33 100644 --- a/src/content/features/transactions/totalearned.js +++ b/src/content/features/transactions/totalearned.js @@ -2,6 +2,7 @@ import { observeElement } from '../../core/observer.js'; import { callRobloxApiJson } from '../../core/api.js'; import { createOverlay } from '../../core/ui/overlay.js'; import { createButton } from '../../core/ui/buttons.js'; +import { log, logLevel} from '../../core/logging.js'; import DOMPurify from 'dompurify'; function onElementFound(container) { @@ -445,7 +446,7 @@ function onElementFound(container) { if (error instanceof PausedException) { await animationController.waitUntilIdle(); } else { - console.error('RoValra Earned: Error:', error); + log(logLevel.ERROR, 'RoValra Earned: Error:', error); state.status = CALCULATION_STATE.ERROR; state.errorMessage = error.message; updateOverlay(); diff --git a/src/content/features/transactions/totalspent.js b/src/content/features/transactions/totalspent.js index 0160157..7a878e4 100644 --- a/src/content/features/transactions/totalspent.js +++ b/src/content/features/transactions/totalspent.js @@ -3,6 +3,7 @@ import { observeElement } from '../../core/observer.js'; import { callRobloxApiJson } from '../../core/api.js'; import { createOverlay } from '../../core/ui/overlay.js'; import { createButton } from '../../core/ui/buttons.js'; +import { log, logLevel } from '../../core/logging.js'; import DOMPurify from 'dompurify'; function onElementFound(container) { @@ -670,10 +671,10 @@ function onElementFound(container) { } } catch (error) { if (error instanceof PausedException) { - console.log(`RoValra: ${error.message}`); + log(logLevel.ERROR, `RoValra: ${error.message}`); await animationController.waitUntilIdle(); } else { - console.error( + log(logLevel.ERROR, 'RoValra: An error occurred during calculation:', error, ); diff --git a/src/content/index.js b/src/content/index.js index 1c18b36..892d608 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -78,6 +78,9 @@ import { init as initFirstAccount } from './features/settings/roblox/firstAccoun // create import { init as initCreateDownload } from './features/create.roblox.com/download.js'; +// Logging +import { log, logLevel} from './core/logging.js'; + let pageLoaded = false; let lastPath = window.location.pathname; @@ -203,7 +206,7 @@ async function initializePage() { onDomReady(); } - console.log(`%cRoValra Initialized`, 'font-size: 1.5em; color: #FF4500;', `(Observer: ${observerStatus})`); + log(logLevel.INFO, `%cRoValra Initialized`, 'font-size: 1.5em; color: #FF4500;', `(Observer: ${observerStatus})`); } @@ -211,7 +214,7 @@ function handleUrlChange() { const currentPath = window.location.pathname; if (currentPath !== lastPath) { - console.log(`%cRoValra: URL changed from ${lastPath} to ${currentPath}`, 'color: #FF4500;'); + log(logLevel.INFO, `%cRoValra: URL changed from ${lastPath} to ${currentPath}`, 'color: #FF4500;'); lastPath = currentPath; runFeaturesForPage(); From 807fe65e0ba4ff344c7f53d1ca0eb7fb9bb004e3 Mon Sep 17 00:00:00 2001 From: Bogdan Sandu Date: Thu, 12 Mar 2026 21:45:26 +0200 Subject: [PATCH 2/2] Update userIds.js Signed-off-by: Bogdan Sandu --- src/content/core/configs/userIds.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/content/core/configs/userIds.js b/src/content/core/configs/userIds.js index ce2234b..033b0d8 100644 --- a/src/content/core/configs/userIds.js +++ b/src/content/core/configs/userIds.js @@ -10,7 +10,8 @@ export const CONTRIBUTOR_USER_IDS = [ '546872490', //Kanibal02 '48255812', //aliceenight '7982684834', //qborder - '126448532' //steinann + '126448532', //steinann + '1564574922', // cornusandu ]; export const RAT_BADGE_USER_ID = '477516666'; // rat