Last active
June 16, 2025 15:31
-
-
Save tobiashochguertel/ad34a03523fba5d42c6a1c94c1095cab to your computer and use it in GitHub Desktop.
userscript-lib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name TH-LoggingLib | |
| // @namespace https://github.com/tobiashochguertel/userscript-lib | |
| // @version 1.0.0 | |
| // @description Advanced logging system for UserScripts with level control | |
| // @author tobiashochguertel | |
| // @match *://*/* | |
| // @grant none | |
| // @run-at document-start | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Create global namespace | |
| window.THLib = window.THLib || {}; | |
| // Check for storage dependency | |
| const hasStorage = !!window.THLib.storage; | |
| // ===================================== | |
| // Core Logging Framework | |
| // ===================================== | |
| /** | |
| * Log level enum - ordered by severity | |
| * Higher number = more severe, more logs will be shown | |
| */ | |
| const LogLevel = { | |
| TRACE: 0, | |
| DEBUG: 1, | |
| INFO: 2, | |
| WARN: 3, | |
| ERROR: 4, | |
| NONE: 5, | |
| // Helper to convert string log level to enum value | |
| fromString: function(level) { | |
| const upperLevel = (level || '').toUpperCase(); | |
| return this[upperLevel] !== undefined ? this[upperLevel] : this.INFO; | |
| }, | |
| // Helper to get level name from value | |
| toString: function(level) { | |
| for (const key in this) { | |
| if (this[key] === level && typeof this[key] !== 'function') { | |
| return key; | |
| } | |
| } | |
| return 'INFO'; | |
| } | |
| }; | |
| /** | |
| * Initialization logic based on storage dependency | |
| */ | |
| function initializeLogStorage() { | |
| if (hasStorage) { | |
| // If storage is available, create a storage instance | |
| console.log('Using THLib.storage for log configuration persistence'); | |
| return window.THLib.storage.create('THLib_Logger'); | |
| } else { | |
| // Create a simple in-memory storage fallback | |
| console.warn('THLib.storage not available, using in-memory storage for logging'); | |
| const memoryStore = {}; | |
| return { | |
| get: (key, defaultValue) => key in memoryStore ? memoryStore[key] : defaultValue, | |
| set: (key, value) => { | |
| memoryStore[key] = value; | |
| return true; | |
| }, | |
| remove: (key) => { | |
| delete memoryStore[key]; | |
| return true; | |
| }, | |
| clear: () => { | |
| Object.keys(memoryStore).forEach(key => delete memoryStore[key]); | |
| return true; | |
| } | |
| }; | |
| } | |
| } | |
| // Initialize storage for logger configuration | |
| const logStorage = initializeLogStorage(); | |
| /** | |
| * Storage manager for log configuration | |
| */ | |
| const LogConfigStorage = { | |
| // Get full configuration object | |
| getConfig: function() { | |
| return logStorage.get('config', { global: LogLevel.INFO, namespaces: {} }); | |
| }, | |
| // Save full configuration object | |
| saveConfig: function(config) { | |
| return logStorage.set('config', config); | |
| }, | |
| // Get specific namespace level | |
| getNamespaceLevel: function(namespace) { | |
| const config = this.getConfig(); | |
| // Return namespace level if defined, or global level as fallback | |
| return config.namespaces[namespace] !== undefined ? | |
| config.namespaces[namespace] : | |
| config.global; | |
| }, | |
| // Set specific namespace level | |
| setNamespaceLevel: function(namespace, level) { | |
| const config = this.getConfig(); | |
| config.namespaces[namespace] = level; | |
| this.saveConfig(config); | |
| }, | |
| // Get global level | |
| getGlobalLevel: function() { | |
| return this.getConfig().global; | |
| }, | |
| // Set global level | |
| setGlobalLevel: function(level) { | |
| const config = this.getConfig(); | |
| config.global = level; | |
| this.saveConfig(config); | |
| } | |
| }; | |
| /** | |
| * Console styling for different log levels | |
| */ | |
| const LogStyles = { | |
| TRACE: 'color: #6c757d', // Grey | |
| DEBUG: 'color: #0d6efd', // Blue | |
| INFO: 'color: #198754', // Green | |
| WARN: 'color: #ffc107; font-weight: bold', // Yellow | |
| ERROR: 'color: #dc3545; font-weight: bold', // Red | |
| // Default style for namespace | |
| NAMESPACE: 'color: #6610f2; font-weight: bold', // Purple | |
| // Style for timestamp | |
| TIMESTAMP: 'color: #6c757d; font-style: italic' // Grey italic | |
| }; | |
| /** | |
| * Logger class - provides logging methods for a specific namespace | |
| */ | |
| class Logger { | |
| constructor(namespace) { | |
| this.namespace = namespace || 'default'; | |
| } | |
| /** | |
| * Format a log message with timestamp, namespace, and styling | |
| */ | |
| _formatMessage(level, args) { | |
| const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19); | |
| return [ | |
| `%c[${timestamp}]%c [${this.namespace}]%c [${level}]:`, | |
| LogStyles.TIMESTAMP, | |
| LogStyles.NAMESPACE, | |
| LogStyles[level], | |
| ...args | |
| ]; | |
| } | |
| /** | |
| * Check if the provided log level is enabled for this namespace | |
| */ | |
| _isLevelEnabled(level) { | |
| const configuredLevel = LogConfigStorage.getNamespaceLevel(this.namespace); | |
| return level >= configuredLevel; | |
| } | |
| /** | |
| * Base log method that all specific level methods call | |
| */ | |
| _log(level, levelStr, args) { | |
| if (this._isLevelEnabled(level)) { | |
| const method = levelStr === 'ERROR' ? 'error' : | |
| levelStr === 'WARN' ? 'warn' : 'log'; | |
| console[method](...this._formatMessage(levelStr, args)); | |
| } | |
| } | |
| // Public logging methods | |
| trace(...args) { | |
| this._log(LogLevel.TRACE, 'TRACE', args); | |
| } | |
| debug(...args) { | |
| this._log(LogLevel.DEBUG, 'DEBUG', args); | |
| } | |
| info(...args) { | |
| this._log(LogLevel.INFO, 'INFO', args); | |
| } | |
| warn(...args) { | |
| this._log(LogLevel.WARN, 'WARN', args); | |
| } | |
| error(...args) { | |
| this._log(LogLevel.ERROR, 'ERROR', args); | |
| } | |
| /** | |
| * Set log level for this specific namespace | |
| */ | |
| setLevel(level) { | |
| const numericLevel = typeof level === 'string' ? | |
| LogLevel.fromString(level) : level; | |
| LogConfigStorage.setNamespaceLevel(this.namespace, numericLevel); | |
| this.info(`Log level for '${this.namespace}' set to ${LogLevel.toString(numericLevel)}`); | |
| return this; | |
| } | |
| /** | |
| * Get current log level for this namespace | |
| */ | |
| getLevel() { | |
| return LogConfigStorage.getNamespaceLevel(this.namespace); | |
| } | |
| } | |
| /** | |
| * Logger factory that creates and manages logger instances | |
| */ | |
| const LoggerFactory = { | |
| // Cache of logger instances by namespace | |
| _loggers: {}, | |
| /** | |
| * Get or create a logger for a specific namespace | |
| */ | |
| getLogger: function(namespace) { | |
| const ns = namespace || 'default'; | |
| if (!this._loggers[ns]) { | |
| this._loggers[ns] = new Logger(ns); | |
| } | |
| return this._loggers[ns]; | |
| }, | |
| /** | |
| * Get all registered loggers | |
| */ | |
| getAllLoggers: function() { | |
| return Object.values(this._loggers); | |
| }, | |
| /** | |
| * Set global log level (affects any namespace without specific level) | |
| */ | |
| setGlobalLogLevel: function(level) { | |
| const numericLevel = typeof level === 'string' ? | |
| LogLevel.fromString(level) : level; | |
| LogConfigStorage.setGlobalLevel(numericLevel); | |
| const defaultLogger = this.getLogger('default'); | |
| defaultLogger.info(`Global log level set to ${LogLevel.toString(numericLevel)}`); | |
| }, | |
| /** | |
| * Get current global log level | |
| */ | |
| getGlobalLogLevel: function() { | |
| return LogConfigStorage.getGlobalLevel(); | |
| }, | |
| /** | |
| * Set log level for a specific namespace | |
| */ | |
| setNamespaceLogLevel: function(namespace, level) { | |
| const logger = this.getLogger(namespace); | |
| logger.setLevel(level); | |
| }, | |
| /** | |
| * Reset all log levels to default | |
| */ | |
| resetAllLogLevels: function() { | |
| LogConfigStorage.saveConfig({ global: LogLevel.INFO, namespaces: {} }); | |
| const defaultLogger = this.getLogger('default'); | |
| defaultLogger.info('All log levels reset to default'); | |
| }, | |
| /** | |
| * Display current configuration in console | |
| */ | |
| showConfiguration: function() { | |
| const config = LogConfigStorage.getConfig(); | |
| const defaultLogger = this.getLogger('default'); | |
| defaultLogger.info('=== THLib Logger Configuration ==='); | |
| defaultLogger.info(`Global level: ${LogLevel.toString(config.global)}`); | |
| defaultLogger.info('Namespace levels:'); | |
| for (const [namespace, level] of Object.entries(config.namespaces)) { | |
| defaultLogger.info(` - ${namespace}: ${LogLevel.toString(level)}`); | |
| } | |
| defaultLogger.info('=== Active Loggers ==='); | |
| for (const namespace of Object.keys(this._loggers)) { | |
| defaultLogger.info(` - ${namespace}`); | |
| } | |
| } | |
| }; | |
| // ===================================== | |
| // Expose Public API | |
| // ===================================== | |
| // Expose logging framework | |
| THLib.logging = { | |
| // Core components | |
| LogLevel, | |
| // Factory methods | |
| getLogger: (namespace) => LoggerFactory.getLogger(namespace), | |
| setGlobalLogLevel: (level) => LoggerFactory.setGlobalLogLevel(level), | |
| setNamespaceLogLevel: (namespace, level) => LoggerFactory.setNamespaceLogLevel(namespace, level), | |
| resetAllLogLevels: () => LoggerFactory.resetAllLogLevels(), | |
| showConfiguration: () => LoggerFactory.showConfiguration(), | |
| // Convenience logger | |
| get defaultLogger() { | |
| return LoggerFactory.getLogger('default'); | |
| } | |
| }; | |
| // ===================================== | |
| // Global Helper Functions | |
| // ===================================== | |
| // Set log level globally or for specific namespace | |
| window.THLibSetLogLevel = function(namespace, level) { | |
| if (level === undefined) { | |
| THLib.logging.setGlobalLogLevel(namespace); | |
| return `Global log level set to ${namespace}`; | |
| } else { | |
| THLib.logging.setNamespaceLogLevel(namespace, level); | |
| return `Log level for '${namespace}' set to ${level}`; | |
| } | |
| }; | |
| // Show logger configuration | |
| window.THLibShowLogConfig = function() { | |
| THLib.logging.showConfiguration(); | |
| return "Logger configuration displayed in console"; | |
| }; | |
| // Reset all log levels to default | |
| window.THLibResetLogLevels = function() { | |
| THLib.logging.resetAllLogLevels(); | |
| return "All log levels reset to default (INFO)"; | |
| }; | |
| // ===================================== | |
| // Initialize the library | |
| // ===================================== | |
| function initializeLogging() { | |
| // Create default logger | |
| const defaultLogger = THLib.logging.getLogger('default'); | |
| defaultLogger.info(`TH-LoggingLib v1.0.0 initialized ${hasStorage ? 'with' : 'without'} storage integration`); | |
| // Dispatch event to notify that the library is ready | |
| document.dispatchEvent(new CustomEvent('THLib:logging:ready')); | |
| // Output help information to console | |
| console.log( | |
| '%cTH-LoggingLib v1.0.0 %cis ready!\n\n' + | |
| '%cAvailable commands:%c\n' + | |
| ' THLibSetLogLevel("DEBUG") Set global log level\n' + | |
| ' THLibSetLogLevel("my-script", "TRACE") Set namespace log level\n' + | |
| ' THLibShowLogConfig() Show logging configuration\n' + | |
| ' THLibResetLogLevels() Reset all logging levels\n\n' + | |
| '%cAPI usage:%c\n' + | |
| ' const logger = THLib.logging.getLogger("my-script");\n' + | |
| ' logger.debug("Debug message");', | |
| 'font-weight:bold;color:#6610f2;font-size:16px', | |
| 'color:#333;font-size:16px', | |
| 'color:#0d6efd;font-weight:bold', | |
| 'color:#333', | |
| 'color:#0d6efd;font-weight:bold', | |
| 'color:#198754;font-family:monospace' | |
| ); | |
| } | |
| // Check if storage is already loaded | |
| if (hasStorage) { | |
| // Storage already available, initialize immediately | |
| initializeLogging(); | |
| } else { | |
| // Wait for storage to become available | |
| document.addEventListener('THLib:storage:ready', function() { | |
| // Re-check for storage | |
| if (window.THLib.storage) { | |
| console.log('TH-LoggingLib: Storage dependency now available'); | |
| // Re-initialize storage | |
| const logStorage = window.THLib.storage.create('THLib_Logger'); | |
| initializeLogging(); | |
| } else { | |
| console.warn('TH-LoggingLib: Storage dependency event received but still not available'); | |
| initializeLogging(); | |
| } | |
| }); | |
| // Fallback initialization after delay | |
| setTimeout(function() { | |
| if (!window.THLib.logging) { | |
| console.warn('TH-LoggingLib: Initializing without storage after timeout'); | |
| initializeLogging(); | |
| } | |
| }, 2000); | |
| } | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name TH-StorageLib | |
| // @namespace https://github.com/tobiashochguertel/userscript-lib | |
| // @version 1.0.0 | |
| // @description Advanced localStorage management for UserScripts | |
| // @author tobiashochguertel | |
| // @match *://*/* | |
| // @grant none | |
| // @run-at document-start | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Create global namespace | |
| window.THLib = window.THLib || {}; | |
| /** | |
| * Storage system with namespace support and type safety | |
| */ | |
| class StorageManager { | |
| /** | |
| * @param {string} namespace - Storage namespace to prefix keys | |
| * @param {Storage} storageBackend - Storage backend (localStorage/sessionStorage) | |
| */ | |
| constructor(namespace = 'THLib', storageBackend = localStorage) { | |
| this.namespace = namespace; | |
| this.storage = storageBackend; | |
| this._cache = {}; | |
| } | |
| /** | |
| * Generate a namespaced key for storage | |
| * @param {string} key - Original key | |
| * @returns {string} - Namespaced key | |
| */ | |
| _getKey(key) { | |
| return `${this.namespace}:${key}`; | |
| } | |
| /** | |
| * Get a value from storage with type safety | |
| * @param {string} key - Storage key | |
| * @param {any} defaultValue - Default value if not found | |
| * @returns {any} - Retrieved value or default | |
| */ | |
| get(key, defaultValue = null) { | |
| // Check cache first | |
| if (this._cache[key] !== undefined) { | |
| return this._cache[key]; | |
| } | |
| try { | |
| const storedValue = this.storage.getItem(this._getKey(key)); | |
| if (storedValue === null) return defaultValue; | |
| // Parse the stored value | |
| const value = JSON.parse(storedValue); | |
| // Cache the value | |
| this._cache[key] = value; | |
| return value; | |
| } catch (error) { | |
| console.error(`StorageManager[${this.namespace}]: Error retrieving '${key}'`, error); | |
| return defaultValue; | |
| } | |
| } | |
| /** | |
| * Store a value with type preservation | |
| * @param {string} key - Storage key | |
| * @param {any} value - Value to store | |
| * @returns {boolean} - Success state | |
| */ | |
| set(key, value) { | |
| try { | |
| // Update cache | |
| this._cache[key] = value; | |
| // Store serialized value | |
| this.storage.setItem(this._getKey(key), JSON.stringify(value)); | |
| return true; | |
| } catch (error) { | |
| console.error(`StorageManager[${this.namespace}]: Error storing '${key}'`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Remove a value from storage | |
| * @param {string} key - Storage key | |
| * @returns {boolean} - Success state | |
| */ | |
| remove(key) { | |
| try { | |
| // Remove from cache | |
| delete this._cache[key]; | |
| // Remove from storage | |
| this.storage.removeItem(this._getKey(key)); | |
| return true; | |
| } catch (error) { | |
| console.error(`StorageManager[${this.namespace}]: Error removing '${key}'`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Check if a key exists in storage | |
| * @param {string} key - Storage key | |
| * @returns {boolean} - Whether key exists | |
| */ | |
| has(key) { | |
| if (this._cache[key] !== undefined) return true; | |
| return this.storage.getItem(this._getKey(key)) !== null; | |
| } | |
| /** | |
| * Clear all values in the current namespace | |
| * @returns {boolean} - Success state | |
| */ | |
| clear() { | |
| try { | |
| const keysToRemove = []; | |
| // Clear cache | |
| this._cache = {}; | |
| // Find all keys in this namespace | |
| for (let i = 0; i < this.storage.length; i++) { | |
| const key = this.storage.key(i); | |
| if (key && key.startsWith(`${this.namespace}:`)) { | |
| keysToRemove.push(key); | |
| } | |
| } | |
| // Remove all keys | |
| keysToRemove.forEach(key => this.storage.removeItem(key)); | |
| return true; | |
| } catch (error) { | |
| console.error(`StorageManager[${this.namespace}]: Error clearing storage`, error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Get all keys in the current namespace | |
| * @returns {string[]} - Array of keys | |
| */ | |
| keys() { | |
| try { | |
| const keys = []; | |
| const prefix = `${this.namespace}:`; | |
| // Find all keys in this namespace | |
| for (let i = 0; i < this.storage.length; i++) { | |
| const key = this.storage.key(i); | |
| if (key && key.startsWith(prefix)) { | |
| keys.push(key.substring(prefix.length)); | |
| } | |
| } | |
| return keys; | |
| } catch (error) { | |
| console.error(`StorageManager[${this.namespace}]: Error listing keys`, error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Get all data in the current namespace as an object | |
| * @returns {Object} - Object with all data | |
| */ | |
| getAll() { | |
| const data = {}; | |
| this.keys().forEach(key => { | |
| data[key] = this.get(key); | |
| }); | |
| return data; | |
| } | |
| /** | |
| * Set multiple values at once | |
| * @param {Object} data - Object with key-value pairs to store | |
| * @returns {boolean} - Success state | |
| */ | |
| setMultiple(data) { | |
| try { | |
| Object.entries(data).forEach(([key, value]) => { | |
| this.set(key, value); | |
| }); | |
| return true; | |
| } catch (error) { | |
| console.error(`StorageManager[${this.namespace}]: Error setting multiple values`, error); | |
| return false; | |
| } | |
| } | |
| } | |
| /** | |
| * Static utility methods for storage management | |
| */ | |
| class StorageUtils { | |
| /** | |
| * Get all namespaces currently used in storage | |
| * @returns {string[]} - Array of namespaces | |
| */ | |
| static getNamespaces() { | |
| try { | |
| const namespaces = new Set(); | |
| for (let i = 0; i < localStorage.length; i++) { | |
| const key = localStorage.key(i); | |
| const parts = key?.split(':'); | |
| if (parts && parts.length > 1) { | |
| namespaces.add(parts[0]); | |
| } | |
| } | |
| return Array.from(namespaces); | |
| } catch (error) { | |
| console.error('StorageUtils: Error getting namespaces', error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Display storage usage statistics | |
| * @returns {Object} - Storage statistics | |
| */ | |
| static getStorageStats() { | |
| try { | |
| const stats = { | |
| totalItems: localStorage.length, | |
| totalBytes: 0, | |
| namespaces: {}, | |
| percentUsed: 0 | |
| }; | |
| // Calculate total size | |
| for (let i = 0; i < localStorage.length; i++) { | |
| const key = localStorage.key(i); | |
| const value = localStorage.getItem(key) || ''; | |
| const size = key.length + value.length; | |
| stats.totalBytes += size; | |
| // Group by namespace | |
| const namespaceParts = key.split(':'); | |
| const namespace = namespaceParts.length > 1 ? namespaceParts[0] : 'unknown'; | |
| if (!stats.namespaces[namespace]) { | |
| stats.namespaces[namespace] = { | |
| items: 0, | |
| bytes: 0 | |
| }; | |
| } | |
| stats.namespaces[namespace].items++; | |
| stats.namespaces[namespace].bytes += size; | |
| } | |
| // Calculate percentage used (typical browser limit is 5MB) | |
| const limit = 5 * 1024 * 1024; | |
| stats.percentUsed = (stats.totalBytes / limit) * 100; | |
| stats.limit = limit; | |
| return stats; | |
| } catch (error) { | |
| console.error('StorageUtils: Error getting storage stats', error); | |
| return { error: 'Failed to calculate storage statistics' }; | |
| } | |
| } | |
| /** | |
| * Clear all data in a namespace | |
| * @param {string} namespace - Namespace to clear | |
| * @returns {boolean} - Success state | |
| */ | |
| static clearNamespace(namespace) { | |
| try { | |
| const storage = new StorageManager(namespace); | |
| return storage.clear(); | |
| } catch (error) { | |
| console.error(`StorageUtils: Error clearing namespace ${namespace}`, error); | |
| return false; | |
| } | |
| } | |
| } | |
| // Expose public API | |
| THLib.storage = { | |
| // Core functionality | |
| create: (namespace, backend = localStorage) => new StorageManager(namespace, backend), | |
| // Utility functions | |
| getNamespaces: StorageUtils.getNamespaces, | |
| getStats: StorageUtils.getStorageStats, | |
| clearNamespace: StorageUtils.clearNamespace, | |
| // Default instance | |
| defaultStorage: new StorageManager('THLib_Default') | |
| }; | |
| // ===================================== | |
| // Global Helper Functions | |
| // ===================================== | |
| // Display storage statistics | |
| window.THLibShowStorage = function() { | |
| const stats = THLib.storage.getStats(); | |
| // Format size in KB | |
| const formatSize = bytes => (bytes / 1024).toFixed(2) + ' KB'; | |
| console.group('THLib Storage Statistics'); | |
| console.log(`Total items: ${stats.totalItems}`); | |
| console.log(`Total used: ${formatSize(stats.totalBytes)} (${stats.percentUsed.toFixed(2)}% of 5MB limit)`); | |
| console.group('Namespaces'); | |
| Object.entries(stats.namespaces).forEach(([namespace, data]) => { | |
| console.log(`${namespace}: ${data.items} items, ${formatSize(data.bytes)}`); | |
| }); | |
| console.groupEnd(); | |
| console.groupEnd(); | |
| return "Storage statistics displayed in console"; | |
| }; | |
| // Clear storage for a namespace | |
| window.THLibClearStorage = function(namespace) { | |
| if (!namespace) { | |
| console.error('Please specify a namespace to clear'); | |
| return "Error: Namespace required"; | |
| } | |
| const cleared = THLib.storage.clearNamespace(namespace); | |
| if (cleared) { | |
| return `Storage for namespace '${namespace}' cleared successfully`; | |
| } else { | |
| return `Failed to clear storage for namespace '${namespace}'`; | |
| } | |
| }; | |
| // Initialize | |
| console.log('%cTH-StorageLib v1.0.0 %cinitialized - Storage management ready', | |
| 'font-weight:bold;color:#0d6efd', 'color:#333'); | |
| // Dispatch ready event for dependent scripts | |
| document.dispatchEvent(new CustomEvent('THLib:storage:ready')); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment