diff --git a/shared-ip-manager.userscript.js b/shared-ip-manager.userscript.js index b7d670d..2ebd510 100644 --- a/shared-ip-manager.userscript.js +++ b/shared-ip-manager.userscript.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Shared IP Manager // @namespace http://tampermonkey.net/ -// @version 1.1.0 +// @version 1.1.1 // @description Transform shared IP overlay into searchable table format // @author Ryahn // @match *://*/* @@ -59,7 +59,7 @@ // Find all user entries const userEntries = overlay.querySelectorAll('.block-row.block-row--separated'); - + if (userEntries.length === 0) return; // Create table container @@ -112,7 +112,7 @@ transition: all 0.2s; white-space: nowrap; `; - + // Add hover effect for button bannedFilterButton.addEventListener('mouseenter', function() { this.style.backgroundColor = '#dc2626'; @@ -166,47 +166,48 @@ // Create table header const thead = document.createElement('thead'); thead.style.cssText = `position: sticky; top: 0; z-index: 10;`; - + const headerRow = document.createElement('tr'); headerRow.style.cssText = `background: #101113; border-bottom: 1px solid #101113; color: #959595;`; - + // Username header (sortable) const usernameHeader = document.createElement('th'); usernameHeader.id = 'sort-username'; usernameHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap; cursor: pointer; user-select: none;`; usernameHeader.innerHTML = `Username `; - + // Join Date header const joinDateHeader = document.createElement('th'); joinDateHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`; joinDateHeader.textContent = 'Join Date'; - + // Messages header const messagesHeader = document.createElement('th'); messagesHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`; messagesHeader.textContent = 'Messages'; - + // Country header const countryHeader = document.createElement('th'); countryHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`; countryHeader.textContent = 'Country'; - + // Provider header const providerHeader = document.createElement('th'); providerHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap;`; providerHeader.textContent = 'Provider'; - + // Type header (sortable) const typeHeader = document.createElement('th'); typeHeader.id = 'sort-type'; typeHeader.style.cssText = `padding: 10px 12px; text-align: left; border-right: 1px solid #17191b; font-weight: 600; color: #959595; white-space: nowrap; cursor: pointer; user-select: none;`; typeHeader.innerHTML = `Type `; - - // Usage header + + // Usage header (sortable) const usageHeader = document.createElement('th'); - usageHeader.style.cssText = `padding: 10px 12px; text-align: left; font-weight: 600; color: #959595; white-space: nowrap;`; - usageHeader.textContent = 'Usage'; - + usageHeader.id = 'sort-usage'; + usageHeader.style.cssText = `padding: 10px 12px; text-align: left; font-weight: 600; color: #959595; white-space: nowrap; cursor: pointer; user-select: none;`; + usageHeader.innerHTML = `Usage `; + // Append all headers to row headerRow.appendChild(usernameHeader); headerRow.appendChild(joinDateHeader); @@ -215,12 +216,12 @@ headerRow.appendChild(providerHeader); headerRow.appendChild(typeHeader); headerRow.appendChild(usageHeader); - + thead.appendChild(headerRow); // Create table body const tbody = document.createElement('tbody'); - + // Process each user entry userEntries.forEach((entry, index) => { const row = document.createElement('tr'); @@ -234,15 +235,15 @@ const usernameLink = entry.querySelector('h3.contentRow-header a.username'); const username = usernameLink ? usernameLink.textContent.trim() : 'N/A'; const profileUrl = usernameLink ? usernameLink.href : '#'; - + // Check if user is banned const usernameSpan = entry.querySelector('h3.contentRow-header a.username span'); const isBanned = usernameSpan && usernameSpan.classList.contains('username--banned'); - + // Join date const joinDateElement = entry.querySelector('time.u-dt'); const joinDate = joinDateElement ? joinDateElement.textContent.trim() : 'N/A'; - + // Messages const dtElements = entry.querySelectorAll('dt'); let messagesElement = null; @@ -252,23 +253,23 @@ break; } } - const messages = messagesElement ? + const messages = messagesElement ? (messagesElement.nextElementSibling ? messagesElement.nextElementSibling.textContent.trim() : '0') : '0'; - + // Country flag and name const flagElement = entry.querySelector('.tck-provider-country-flag'); const countryCode = flagElement ? getCountryCode(flagElement) : ''; const countryName = flagElement ? getCountryName(flagElement) : ''; const flagEmoji = getFlagEmoji(countryCode); - + // Provider info const providerElement = entry.querySelector('.tck-provider-txt'); const providerInfo = providerElement ? getProviderInfo(providerElement) : { name: 'N/A', asn: '' }; - + // Type info const typeElement = entry.querySelector('.tck-provider-type'); const typeInfo = typeElement ? getTypeInfo(typeElement) : 'N/A'; - + // Usage count const liElements = entry.querySelectorAll('li'); let usageElement = null; @@ -280,8 +281,8 @@ } const usageText = usageElement ? usageElement.textContent.trim() : '0 time'; const usageCount = getUsageCount(usageText); - - + + // Build row HTML row.innerHTML = ` @@ -319,31 +320,31 @@ // Filter state let showBannedOnly = false; - + // Function to apply both search and filter function applyFilters() { const searchTerm = searchInput.value.toLowerCase(); const rows = tbody.querySelectorAll('tr'); let visibleCount = 0; let bannedCount = 0; - + rows.forEach(row => { const text = row.textContent.toLowerCase(); - const isBanned = row.querySelector('a[href*="/members/"]') && + const isBanned = row.querySelector('a[href*="/members/"]') && row.querySelector('a[href*="/members/"]').textContent.includes('🚫'); - + const matchesSearch = text.includes(searchTerm); const matchesFilter = !showBannedOnly || isBanned; - + const shouldShow = matchesSearch && matchesFilter; row.style.display = shouldShow ? '' : 'none'; - + if (shouldShow) { visibleCount++; if (isBanned) bannedCount++; } }); - + // Update count display if (showBannedOnly) { countDisplay.textContent = `Showing ${bannedCount} banned users`; @@ -351,14 +352,14 @@ countDisplay.textContent = `${visibleCount} users (${bannedCount} banned)`; } } - + // Add search functionality searchInput.addEventListener('input', applyFilters); - + // Add banned filter functionality bannedFilterButton.addEventListener('click', () => { showBannedOnly = !showBannedOnly; - + if (showBannedOnly) { bannedFilterButton.textContent = 'Show All Users'; bannedFilterButton.classList.add('active'); @@ -370,7 +371,7 @@ bannedFilterButton.style.backgroundColor = '#dc262620'; bannedFilterButton.style.color = '#dc2626'; } - + applyFilters(); }); @@ -380,31 +381,31 @@ // Assemble table table.appendChild(thead); table.appendChild(tbody); - + // Add table to wrapper tableWrapper.appendChild(table); - + // Add elements to container tableContainer.appendChild(searchFilterContainer); tableContainer.appendChild(tableWrapper); // Sorting functionality let currentSort = { column: null, direction: 'asc' }; - + function sortTable(column, direction) { console.log('Sorting by:', column, direction); // Debug log - + const rows = Array.from(tbody.querySelectorAll('tr')); console.log('Found rows:', rows.length); // Debug log - + if (rows.length === 0) { console.log('No rows to sort'); return; } - + rows.sort((a, b) => { let aValue, bValue; - + if (column === 'username') { const aLink = a.querySelector('a'); const bLink = b.querySelector('a'); @@ -413,34 +414,48 @@ } else if (column === 'type') { aValue = a.cells[5] ? a.cells[5].textContent.trim().toLowerCase() : ''; bValue = b.cells[5] ? b.cells[5].textContent.trim().toLowerCase() : ''; + } else if (column === 'usage') { + // Parse usage as numbers for proper numerical sorting + aValue = parseFloat(a.cells[6] ? a.cells[6].textContent.trim() : '0') || 0; + bValue = parseFloat(b.cells[6] ? b.cells[6].textContent.trim() : '0') || 0; } - + console.log('Comparing:', aValue, 'vs', bValue); // Debug log - - if (direction === 'asc') { - return aValue.localeCompare(bValue); + + if (column === 'usage') { + // Numerical comparison for usage + if (direction === 'asc') { + return aValue - bValue; + } else { + return bValue - aValue; + } } else { - return bValue.localeCompare(aValue); + // String comparison for username and type + if (direction === 'asc') { + return aValue.localeCompare(bValue); + } else { + return bValue.localeCompare(aValue); + } } }); - + // Clear tbody and re-append sorted rows tbody.innerHTML = ''; rows.forEach(row => tbody.appendChild(row)); - + // Update sort icons document.querySelectorAll('[id$="-sort-icon"]').forEach(icon => { icon.textContent = '↕'; }); - + const sortIcon = document.getElementById(`${column}-sort-icon`); if (sortIcon) { sortIcon.textContent = direction === 'asc' ? '↑' : '↓'; } - + console.log('Sorting completed'); // Debug log } - + // Add click event listeners for sortable columns console.log('Adding click listener to username header'); usernameHeader.addEventListener('click', (e) => { @@ -455,7 +470,7 @@ console.log('Current sort:', currentSort); sortTable(currentSort.column, currentSort.direction); }); - + // Add hover effects for username header usernameHeader.addEventListener('mouseenter', function() { this.style.backgroundColor = '#1a1a1a'; @@ -463,7 +478,7 @@ usernameHeader.addEventListener('mouseleave', function() { this.style.backgroundColor = '#101113'; }); - + console.log('Adding click listener to type header'); typeHeader.addEventListener('click', (e) => { console.log('Type header clicked'); @@ -477,7 +492,7 @@ console.log('Current sort:', currentSort); sortTable(currentSort.column, currentSort.direction); }); - + // Add hover effects for type header typeHeader.addEventListener('mouseenter', function() { this.style.backgroundColor = '#1a1a1a'; @@ -486,6 +501,28 @@ this.style.backgroundColor = '#101113'; }); + console.log('Adding click listener to usage header'); + usageHeader.addEventListener('click', (e) => { + console.log('Usage header clicked'); + e.preventDefault(); + if (currentSort.column === 'usage') { + currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; + } else { + currentSort.column = 'usage'; + currentSort.direction = 'asc'; + } + console.log('Current sort:', currentSort); + sortTable(currentSort.column, currentSort.direction); + }); + + // Add hover effects for usage header + usageHeader.addEventListener('mouseenter', function() { + this.style.backgroundColor = '#1a1a1a'; + }); + usageHeader.addEventListener('mouseleave', function() { + this.style.backgroundColor = '#101113'; + }); + // Replace overlay content overlay.innerHTML = ''; overlay.appendChild(tableContainer); @@ -507,7 +544,7 @@ // Also listen for overlay events in case it loads dynamically document.addEventListener('click', (e) => { - if (e.target.classList.contains('js-overlayClose') || + if (e.target.classList.contains('js-overlayClose') || e.target.closest('.js-overlayClose')) { // Overlay is closing, wait for it to reopen setTimeout(waitForOverlay, 500);