F95Zone-Scripts/shared-ip-manager.userscript.js
2025-09-10 00:21:34 +00:00

555 lines
22 KiB
JavaScript

// ==UserScript==
// @name Shared IP Manager
// @namespace http://tampermonkey.net/
// @version 1.1.1
// @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 <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 (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 <span id="usage-sort-icon" style="margin-left: 5px;">↕</span>`;
// 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 = `
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;">
<a href="${profileUrl}" target="_blank" style="color: ${isBanned ? '#dc2626' : '#2563eb'}; text-decoration: ${isBanned ? 'line-through' : 'none'}; font-weight: 500;">
${isBanned ? '🚫 ' : ''}${username}
</a>
${isBanned ? '<span style="color: #dc2626; margin-left: 8px; font-size: 11px; font-weight: 600; background: #dc262620; padding: 2px 6px; border-radius: 3px;">BANNED</span>' : ''}
</td>
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap; color: #6b7280;">${joinDate}</td>
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap; color: #6b7280;">${messages}</td>
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;">
<span title="${countryName}" style="color: #374151;">${flagEmoji} ${countryCode}</span>
</td>
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;" title="${providerInfo.asn}">
<span style="color: #374151;">${providerInfo.name}</span>
</td>
<td style="padding: 10px 12px; border-right: 1px solid #e5e7eb; white-space: nowrap;" title="${typeInfo}">
<span style="color: #6b7280;">${typeInfo.replace('Type: ', '')}</span>
</td>
<td style="padding: 10px 12px; text-align: center; white-space: nowrap;">
<span style="color: #374151; font-weight: 500;">${usageCount}</span>
</td>
`;
// 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);
}
});
})();