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==
// @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 <span id="username-sort-icon" style="margin-left: 5px;">↕</span>`;
// 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 <span id="type-sort-icon" style="margin-left: 5px;">↕</span>`;
// 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 <span id="usage-sort-icon" style="margin-left: 5px;">↕</span>`;
// 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 = `
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;">
@ -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);