// ==UserScript== // @name Shared IP Manager // @namespace http://tampermonkey.net/ // @version 1.1.2 // @description Transform shared IP overlay into searchable table format // @author Ryahn // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; // Function to extract country code from flag class function getCountryCode(flagElement) { const classes = flagElement.className; const match = classes.match(/tck-provider-country-flag\s+(\w+)/); return match ? match[1].toUpperCase() : ''; } // Function to get country name from flag element function getCountryName(flagElement) { return flagElement.getAttribute('data-original-title') || ''; } // Function to get provider info function getProviderInfo(providerElement) { return { name: providerElement.textContent.trim(), asn: providerElement.getAttribute('data-original-title') || '' }; } // Function to get type info function getTypeInfo(typeElement) { return typeElement.getAttribute('data-original-title') || ''; } // Function to extract usage count function getUsageCount(usageText) { const match = usageText.match(/(\d+)\s+time/); return match ? parseInt(match[1]) : 0; } // Function to create flag emoji from country code function getFlagEmoji(countryCode) { if (!countryCode) return ''; const codePoints = countryCode .toLowerCase() .split('') .map(char => 127397 + char.charCodeAt()); return String.fromCodePoint(...codePoints); } // Function to create the table function createTable() { const overlay = document.querySelector('.overlay-content'); if (!overlay) return; // Find all user entries const userEntries = overlay.querySelectorAll('.block-row.block-row--separated'); if (userEntries.length === 0) return; // Create table container const tableContainer = document.createElement('div'); tableContainer.style.cssText = ` padding: 0; background: transparent; width: 100%; height: 100%; overflow: hidden; box-sizing: border-box; `; // Create search and filter container const searchFilterContainer = document.createElement('div'); searchFilterContainer.style.cssText = ` display: flex; gap: 10px; margin-bottom: 15px; align-items: center; `; // Create search input const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = 'Search users, countries, providers...'; searchInput.style.cssText = ` flex: 1; padding: 12px; border: 1px solid #101113; border-radius: 4px; font-size: 14px; box-sizing: border-box; background: #101113; color: #374151; `; // Create banned filter button const bannedFilterButton = document.createElement('button'); bannedFilterButton.textContent = 'Show Banned Only'; bannedFilterButton.style.cssText = ` padding: 12px 16px; border: 1px solid #dc2626; border-radius: 4px; font-size: 14px; font-weight: 500; background: #dc262620; color: #dc2626; cursor: pointer; transition: all 0.2s; white-space: nowrap; `; // Add hover effect for button bannedFilterButton.addEventListener('mouseenter', function() { this.style.backgroundColor = '#dc2626'; this.style.color = 'white'; }); bannedFilterButton.addEventListener('mouseleave', function() { if (!this.classList.contains('active')) { this.style.backgroundColor = '#dc262620'; this.style.color = '#dc2626'; } }); // Create count display const countDisplay = document.createElement('div'); countDisplay.style.cssText = ` color: #6b7280; font-size: 12px; margin-left: auto; padding: 8px 12px; background: #1a1a1a; border-radius: 4px; border: 1px solid #17191b; `; // Add button to container searchFilterContainer.appendChild(searchInput); searchFilterContainer.appendChild(bannedFilterButton); searchFilterContainer.appendChild(countDisplay); // Create table wrapper for scrolling const tableWrapper = document.createElement('div'); tableWrapper.style.cssText = ` width: 100%; overflow: auto; max-height: calc(100vh - 150px); border: 1px solid #e5e7eb; border-radius: 6px; `; // Create table const table = document.createElement('table'); table.style.cssText = ` width: 100%; min-width: 800px; border-collapse: collapse; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 13px; margin: 0; `; // 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 (sortable) const usageHeader = document.createElement('th'); 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); headerRow.appendChild(messagesHeader); headerRow.appendChild(countryHeader); 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'); row.style.cssText = ` border-bottom: 1px solid #17191b; transition: background-color 0.2s; `; row.style.backgroundColor = index % 2 === 0 ? '#131313' : '#131313'; // Username 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; for (const dt of dtElements) { if (dt.textContent.includes('Messages')) { messagesElement = dt; break; } } 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; for (const li of liElements) { if (li.textContent.includes('time')) { usageElement = li; break; } } const usageText = usageElement ? usageElement.textContent.trim() : '0 time'; const usageCount = getUsageCount(usageText); // Build row HTML row.innerHTML = ` ${isBanned ? '🚫 ' : ''}${username} ${isBanned ? 'BANNED' : ''} ${joinDate} ${messages} ${flagEmoji} ${countryCode} ${providerInfo.name} ${typeInfo.replace('Type: ', '')} ${usageCount} `; // Add hover effect row.addEventListener('mouseenter', () => { row.style.backgroundColor = '#131313'; }); row.addEventListener('mouseleave', () => { row.style.backgroundColor = index % 2 === 0 ? '#131313' : '#131313'; }); tbody.appendChild(row); }); // 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/"]') && 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`; } else { 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'); bannedFilterButton.style.backgroundColor = '#dc2626'; bannedFilterButton.style.color = 'white'; } else { bannedFilterButton.textContent = 'Show Banned Only'; bannedFilterButton.classList.remove('active'); bannedFilterButton.style.backgroundColor = '#dc262620'; bannedFilterButton.style.color = '#dc2626'; } applyFilters(); }); // Initialize filters to set up count display applyFilters(); // 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'); aValue = aLink ? aLink.textContent.trim().toLowerCase() : ''; bValue = bLink ? bLink.textContent.trim().toLowerCase() : ''; } 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 (column === 'usage') { // Numerical comparison for usage if (direction === 'asc') { return aValue - bValue; } else { return bValue - aValue; } } else { // 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) => { console.log('Username header clicked'); e.preventDefault(); if (currentSort.column === 'username') { currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { currentSort.column = 'username'; currentSort.direction = 'asc'; } console.log('Current sort:', currentSort); sortTable(currentSort.column, currentSort.direction); }); // Add hover effects for username header usernameHeader.addEventListener('mouseenter', function() { this.style.backgroundColor = '#1a1a1a'; }); 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'); e.preventDefault(); if (currentSort.column === 'type') { currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { currentSort.column = 'type'; currentSort.direction = 'asc'; } console.log('Current sort:', currentSort); sortTable(currentSort.column, currentSort.direction); }); // Add hover effects for type header typeHeader.addEventListener('mouseenter', function() { this.style.backgroundColor = '#1a1a1a'; }); typeHeader.addEventListener('mouseleave', function() { 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); } // Wait for overlay to load and then transform it function waitForOverlay() { const overlay = document.querySelector('.overlay-content'); if (overlay) { createTable(); } else { // Check again in 100ms setTimeout(waitForOverlay, 100); } } // Start monitoring for overlay waitForOverlay(); // Also listen for overlay events in case it loads dynamically document.addEventListener('click', (e) => { if (e.target.classList.contains('js-overlayClose') || e.target.closest('.js-overlayClose')) { // Overlay is closing, wait for it to reopen setTimeout(waitForOverlay, 500); } }); })();