Update shared-ip-manager.userscript.js

Added sort to usage
This commit is contained in:
Ryahn 2025-09-10 00:21:34 +00:00
parent 2733e86c8f
commit 2a267bcc05

View File

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