您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Library For GameSense 2.1
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/518265/1602254/%5BLibrary%5D%20-%20GS%20ENCH.js
class GSEnhancedUI { constructor() { if (window.GSEnhancedUIInstance) { return window.GSEnhancedUIInstance; } window.GSEnhancedUIInstance = this; this.cache = {}; this.currentTheme = GM_getValue('theme', 'default'); const styles = ` :root { --gs-primary: #e61515; --gs-secondary: #353534; --gs-background: #272726; --gs-text: #ffffff; --gs-border: #454545; --gs-hover: #404040; } .subscribelink { display: none !important; } .blocktable h2 { transition: background-color 0.3s ease; cursor: pointer; } .fa-magnet { margin-right: 8px; font-size: 14px; opacity: 0.7; transition: all 0.3s ease; } .blocktable h2:hover .fa-magnet { opacity: 1; } .fa-eye, .fa-eye-slash, .fa-desktop { font-size: 14px; padding: 2px; opacity: 0.7; transition: opacity 0.2s; } .fa-eye:hover, .fa-eye-slash:hover, .fa-desktop:hover { opacity: 1; } .blockform table td .button { padding: 3px 10px; font-size: 0.9em; white-space: nowrap; } .blockform table td a:not(.button) { text-decoration: none; color: var(--gs-primary); } .blockform table td a:not(.button):hover { text-decoration: underline; } .section-header { user-select: none; } .section-header .fa-magnet { opacity: 0.7; font-size: 14px; } .section-header:hover .fa-magnet { opacity: 1; } [data-theme="light-red"] .pun a:link, [data-theme="light-red"] .pun a:visited, [data-theme="light-red"] .pun .tcl h3 a, [data-theme="light-red"] #brdmenu a:link, [data-theme="light-red"] #brdmenu a:visited { color: #ff4444 !important; } [data-theme="light-red"] .pun a:hover, [data-theme="light-red"] .pun a:active, [data-theme="light-red"] .pun .tcl h3 a:hover, [data-theme="light-red"] #brdmenu a:hover { color: #ff6666 !important; } [data-theme="light-orange"] .pun a:link, [data-theme="light-orange"] .pun a:visited, [data-theme="light-orange"] .pun .tcl h3 a, [data-theme="light-orange"] #brdmenu a:link, [data-theme="light-orange"] #brdmenu a:visited { color: #ffa500 !important; } [data-theme="light-orange"] .pun a:hover, [data-theme="light-orange"] .pun a:active, [data-theme="light-orange"] .pun .tcl h3 a:hover, [data-theme="light-orange"] #brdmenu a:hover { color: #ffc04d !important; } .inform { margin-bottom: 12px; } .infldset { padding: 12px; } .button-group { display: flex; gap: 8px; margin-top: 8px; } .button-group .button { display: inline-flex; align-items: center; gap: 5px; } .button-group .button i { font-size: 12px; } .input-with-button { display: flex; gap: 8px; align-items: center; } .input-with-button input { flex: 1; } .contains-error { border-color: #ff4444 !important; } .status-text { display: flex; align-items: center; gap: 5px; margin-bottom: 8px; } .status-text i { color: var(--gs-primary); font-size: 14px; } .chat-export-btn { cursor: pointer; margin-right: 5px; font-size: 16px; display: inline-block; vertical-align: middle; padding: 0 5px; transition: opacity 0.2s; } .chat-export-btn:hover { opacity: 0.7; } .export-status { position: fixed; top: 20px; right: 20px; background: var(--gs-secondary); color: var(--gs-text); padding: 10px 15px; border-radius: 5px; border: 1px solid var(--gs-border); z-index: 10000; display: none; box-shadow: 0 2px 10px rgba(0,0,0,0.3); } .export-status.show { display: block; animation: slideIn 0.3s ease; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .config-download-btn { display: inline-block; padding: 3px 10px; font-size: 0.9em; white-space: nowrap; text-decoration: none; border: none; cursor: pointer; background: inherit; color: inherit; } .config-download-btn:hover { text-decoration: underline; } .config-replaced { background: var(--gs-secondary); border: 1px dashed var(--gs-border); padding: 15px; border-radius: 8px; text-align: center; margin: 10px 0; } .config-info { color: #888; font-size: 0.9em; margin-top: 5px; } .privacy-censored { background: #333 !important; color: #666 !important; border-radius: 3px; padding: 0 5px; font-family: monospace; } .privacy-censored::before { content: "••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••"; font-size: 0.8em; } .mention-suggestions { position: absolute; background: var(--gs-secondary); border: 1px solid var(--gs-border); border-radius: 3px; max-height: 120px; overflow-y: auto; z-index: 1000; box-shadow: 0 2px 8px rgba(0,0,0,0.3); display: none; font-size: 0.85em; } .mention-suggestion { padding: 4px 8px; cursor: pointer; border-bottom: 1px solid var(--gs-border); transition: background-color 0.2s; } .mention-suggestion:last-child { border-bottom: none; } .mention-suggestion:hover, .mention-suggestion.selected { background: var(--gs-hover); } .mention-suggestion .username { font-weight: bold; color: var(--gs-primary); font-size: 0.9em; } .mention-suggestion .user-info { font-size: 0.75em; color: #888; margin-top: 1px; } `; if (!document.getElementById('gs-enhanced-styles')) { const styleElement = document.createElement('style'); styleElement.id = 'gs-enhanced-styles'; styleElement.textContent = styles; document.head.appendChild(styleElement); } if (!window.GSEnhancedUIInitialized) { this.init(); window.GSEnhancedUIInitialized = true; } } init() { this.addThemeToggle(); this.addCollapsibleCategories(); this.removeSubscribeLink(); this.addRollButton(); this.addChatExportButton(); this.setupConfigDownloads(); this.setupUndercoverMode(); this.setupPrivacyMode(); this.setupAutoMentionCompletion(); this.setupResellerList(); this.setupPremiumUI(); document.body.setAttribute('data-theme', this.currentTheme); } removeSubscribeLink() { const subscribeLink = document.querySelector('.subscribelink'); if (subscribeLink) { subscribeLink.remove(); } } addThemeToggle() { if (document.getElementById('theme-toggle')) return; const logoutLink = document.querySelector('#navlogout'); if (!logoutLink) return; const themeToggle = document.createElement('li'); themeToggle.id = 'theme-toggle'; themeToggle.innerHTML = this.getThemeIcon(this.currentTheme); themeToggle.addEventListener('click', () => { this.cycleTheme(); themeToggle.innerHTML = this.getThemeIcon(this.currentTheme); }); logoutLink.parentNode.insertBefore(themeToggle, logoutLink.nextSibling); } getThemeIcon(theme) { const icons = { 'default': 'fa-adjust', 'light-red': 'fa-fire', 'light-orange': 'fa-sun-o' }; return `<i class="fa ${icons[theme]} theme-icon"></i>`; } cycleTheme() { const themes = ['default', 'light-red', 'light-orange']; const currentIndex = themes.indexOf(this.currentTheme); const newTheme = themes[(currentIndex + 1) % themes.length]; this.currentTheme = newTheme; GM_setValue('theme', newTheme); document.body.setAttribute('data-theme', newTheme); } addRollButton() { if (document.querySelector('.chat-roll')) return; const emojiSelector = document.querySelector('#emojiselector'); if (emojiSelector) { const rollButton = document.createElement('div'); rollButton.className = 'chat-roll'; rollButton.innerHTML = '🎲'; rollButton.style.cssText = ` cursor: pointer; margin-right: 5px; font-size: 16px; display: inline-block; vertical-align: middle; padding: 0 5px; `; rollButton.addEventListener('click', () => { const chatInput = document.querySelector('#shouttext'); if (chatInput) { chatInput.value = '/roll'; const event = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }); chatInput.dispatchEvent(event); } }); emojiSelector.parentNode.insertBefore(rollButton, emojiSelector); } } addChatExportButton() { if (document.querySelector('.chat-export-btn')) return; const emojiSelector = document.querySelector('#emojiselector'); if (emojiSelector) { const exportButton = document.createElement('div'); exportButton.className = 'chat-export-btn'; exportButton.innerHTML = '💾'; exportButton.title = 'Export chat messages to JSON'; exportButton.addEventListener('click', () => { this.exportChatMessages(); }); emojiSelector.parentNode.insertBefore(exportButton, emojiSelector); } } exportChatMessages() { const chatContainer = document.querySelector('#shout > div'); if (!chatContainer) { this.showExportStatus('No chat messages found!', 'error'); return; } const messages = []; const messageElements = chatContainer.querySelectorAll('p'); messageElements.forEach((element, index) => { const timeElement = element.querySelector('.dateTime'); const userLink = element.querySelector('a[href*="profile.php"]'); if (timeElement && userLink) { const timestamp = timeElement.textContent.trim(); const username = userLink.textContent.trim(); const userClass = userLink.className; const userId = userLink.href.match(/id=(\d+)/)?.[1] || null; const fullText = element.textContent; const messageStart = fullText.indexOf(': ') + 2; const messageContent = messageStart > 1 ? fullText.substring(messageStart) : ''; messages.push({ id: index + 1, timestamp: timestamp, username: username, userId: userId, userClass: userClass, message: messageContent, rawHTML: element.innerHTML, exportedAt: new Date().toISOString() }); } }); if (messages.length === 0) { this.showExportStatus('No valid chat messages found!', 'error'); return; } const exportData = { metadata: { exportedAt: new Date().toISOString(), totalMessages: messages.length, source: 'GameSense Chat', url: window.location.href, userAgent: navigator.userAgent }, messages: messages }; try { const jsonString = JSON.stringify(exportData, null, 2); const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `gamesense_chat_${new Date().toISOString().split('T')[0]}_${Date.now()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showExportStatus(`Successfully exported ${messages.length} messages!`, 'success'); } catch (error) { console.error('Export failed:', error); this.showExportStatus('Export failed! Check console for details.', 'error'); } } setupConfigDownloads() { const posts = document.querySelectorAll('.postmsg p'); posts.forEach(post => { const text = post.textContent.trim(); if (this.isConfigCode(text)) { this.replaceConfigWithButton(post, text); } }); } isConfigCode(text) { if (text.length < 500) return false; const base64Pattern = /^[A-Za-z0-9+/=]+$/; const hasMinimalSpaces = (text.split(' ').length - 1) < (text.length * 0.02); const containsConfigPatterns = /[A-Za-z0-9]{50,}/.test(text); return (base64Pattern.test(text.replace(/\s/g, '')) || hasMinimalSpaces) && containsConfigPatterns; } replaceConfigWithButton(postElement, configData) { const postContainer = postElement.closest('.blockpost'); const authorElement = postContainer.querySelector('.postleft dt strong a'); const timestampElement = postContainer.querySelector('h2 a'); const author = authorElement ? authorElement.textContent.trim() : 'Unknown'; const timestamp = timestampElement ? timestampElement.textContent.trim() : 'Unknown'; const postId = postContainer.id || 'unknown'; const configContainer = document.createElement('div'); configContainer.className = 'config-replaced'; configContainer.innerHTML = ` <div> <a class="config-download-btn button" onclick="this.nextElementSibling.click()"> Download Config </a> <a style="display: none;" download="gamesense_config_${author}_${postId}.txt" href="data:text/plain;charset=utf-8,${encodeURIComponent(configData)}"></a> <div class="config-info"> <i class="fa fa-user"></i> ${author} • <i class="fa fa-clock-o"></i> ${timestamp} • <i class="fa fa-file-text-o"></i> ${Math.round(configData.length / 1024)}KB </div> </div> `; postElement.innerHTML = ''; postElement.appendChild(configContainer); const downloadBtn = configContainer.querySelector('.config-download-btn'); downloadBtn.addEventListener('click', () => { this.showExportStatus(`Config downloaded from ${author}!`, 'success'); }); } showExportStatus(message, type = 'info') { const existingStatus = document.querySelector('.export-status'); if (existingStatus) { existingStatus.remove(); } const statusDiv = document.createElement('div'); statusDiv.className = 'export-status'; statusDiv.innerHTML = ` <i class="fa ${type === 'success' ? 'fa-check' : type === 'error' ? 'fa-times' : 'fa-info'}"></i> ${message} `; if (type === 'success') { statusDiv.style.borderColor = '#4CAF50'; statusDiv.style.color = '#4CAF50'; } else if (type === 'error') { statusDiv.style.borderColor = '#f44336'; statusDiv.style.color = '#f44336'; } document.body.appendChild(statusDiv); setTimeout(() => { statusDiv.classList.add('show'); }, 10); setTimeout(() => { statusDiv.classList.remove('show'); setTimeout(() => { if (statusDiv.parentNode) { statusDiv.parentNode.removeChild(statusDiv); } }, 300); }, 3000); } setupUndercoverMode() { const loggedInSpan = document.querySelector('#brdwelcome .conl li:first-child span'); if (!loggedInSpan || loggedInSpan.querySelector('.fa-eye')) return; const usernameElement = loggedInSpan.querySelector('strong'); if (usernameElement) { GM_setValue('username', usernameElement.textContent.trim()); } const eyeButton = document.createElement('i'); eyeButton.className = 'fa fa-eye'; eyeButton.style.cssText = ` cursor: pointer; margin-left: 5px; opacity: 0.7; `; eyeButton.title = 'Toggle Undercover Mode'; const isUndercover = GM_getValue('undercover', false); if (isUndercover) { this.enableUndercoverMode(); eyeButton.className = 'fa fa-eye-slash'; } eyeButton.addEventListener('click', () => { const currentState = GM_getValue('undercover', false); GM_setValue('undercover', !currentState); if (!currentState) { this.enableUndercoverMode(); eyeButton.className = 'fa fa-eye-slash'; } else { this.disableUndercoverMode(); eyeButton.className = 'fa fa-eye'; } }); loggedInSpan.appendChild(eyeButton); } setupPrivacyMode() { const loggedInSpan = document.querySelector('#brdwelcome .conl li:first-child span'); if (!loggedInSpan || loggedInSpan.querySelector('.fa-desktop')) return; const privacyButton = document.createElement('i'); privacyButton.className = 'fa fa-desktop'; privacyButton.style.cssText = ` cursor: pointer; margin-left: 5px; opacity: 0.7; `; privacyButton.title = 'Toggle Privacy Mode (Screen Share Safe)'; const isPrivacyMode = GM_getValue('privacyMode', false); if (isPrivacyMode) { this.enablePrivacyMode(); privacyButton.style.color = '#4CAF50'; } privacyButton.addEventListener('click', () => { const currentState = GM_getValue('privacyMode', false); GM_setValue('privacyMode', !currentState); if (!currentState) { this.enablePrivacyMode(); privacyButton.style.color = '#4CAF50'; this.showExportStatus('Privacy Mode enabled - sensitive data hidden', 'success'); } else { this.disablePrivacyMode(); privacyButton.style.color = ''; this.showExportStatus('Privacy Mode disabled', 'info'); } }); loggedInSpan.appendChild(privacyButton); } enableUndercoverMode() { const username = GM_getValue('username'); if (!username) return; const selectors = [ 'a[href*="profile.php"]', '#brdwelcome .conl li:first-child strong', '.username', '.user-name', '.author' ]; document.querySelectorAll(selectors.join(', ')).forEach(element => { if (element.textContent.trim() === username) { element.setAttribute('data-original', element.textContent); element.textContent = '<HIDDEN>'; } }); } disableUndercoverMode() { document.querySelectorAll('[data-original]').forEach(element => { if (element.getAttribute('data-original')) { element.textContent = element.getAttribute('data-original'); element.removeAttribute('data-original'); } }); } enablePrivacyMode() { const sensitiveSelectors = [ 'input[name="req_email"]', 'input[type="email"]', 'input[name*="2fa"]', 'input[name*="recovery"]', 'input[name*="backup"]', 'input[name*="secret"]', 'input[name*="token"]', '*' ]; document.querySelectorAll('input[name="req_email"], input[type="email"]').forEach(element => { if (!element.hasAttribute('data-original-value') && element.value) { element.setAttribute('data-original-value', element.value); element.value = element.value.replace(/^.+@/, '••••••••@'); } }); document.querySelectorAll('p').forEach(element => { const text = element.textContent; if (text.includes('Recovery code:') || text.includes('recovery code:')) { const codeMatch = text.match(/[a-f0-9]{32,}/i); if (codeMatch && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); element.innerHTML = element.innerHTML.replace(codeMatch[0], `<span class="privacy-censored" data-sensitive="true"></span>`); } } const emailMatch = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/); if (emailMatch && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); element.innerHTML = element.innerHTML.replace(emailMatch[0], '••••••••@••••••••.•••'); } if (text.match(/Registered:\s*\d{4}-\d{2}-\d{2}/) && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); element.textContent = element.textContent.replace(/Registered:\s*\d{4}-\d{2}-\d{2}/, 'Registered: ••••-••-••'); } if (text.match(/Last post:\s*.+\d{2}:\d{2}:\d{2}/) && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); element.textContent = element.textContent.replace(/Last post:\s*.+\d{2}:\d{2}:\d{2}/, 'Last post: •••••• ••:••:••'); } if (text.match(/Last visit:\s*.+\d{2}:\d{2}:\d{2}/) && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); element.textContent = element.textContent.replace(/Last visit:\s*.+\d{2}:\d{2}:\d{2}/, 'Last visit: •••••• ••:••:••'); } if (text.match(/Posts:\s*\d+/) && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); element.textContent = element.textContent.replace(/Posts:\s*\d+/, 'Posts: •••'); } const keyMatch = text.match(/[A-Za-z0-9]{20,}/); if (keyMatch && !element.hasAttribute('data-original-text') && !text.includes('http') && !text.includes('www') && !text.includes('Registered:') && !text.includes('Last post:') && !text.includes('Last visit:') && !text.includes('Posts:')) { element.setAttribute('data-original-text', element.textContent); element.innerHTML = element.innerHTML.replace(keyMatch[0], `<span class="privacy-censored" data-sensitive="true"></span>`); } }); document.querySelectorAll('p').forEach(element => { if (element.textContent.includes('Invited by') && !element.hasAttribute('data-original-text')) { element.setAttribute('data-original-text', element.textContent); const inviterLink = element.querySelector('a'); if (inviterLink) { inviterLink.textContent = '••••••••'; inviterLink.href = '#'; } } }); document.querySelectorAll('input[type="text"], input[type="password"]').forEach(element => { if (element.value && element.value.length > 15 && !element.hasAttribute('data-original-value')) { element.setAttribute('data-original-value', element.value); element.value = '••••••••••••••••••••••••••••••••'; } }); } disablePrivacyMode() { document.querySelectorAll('input[data-original-value]').forEach(element => { element.value = element.getAttribute('data-original-value'); element.removeAttribute('data-original-value'); }); document.querySelectorAll('[data-original-text]').forEach(element => { element.textContent = element.getAttribute('data-original-text'); element.removeAttribute('data-original-text'); }); document.querySelectorAll('.privacy-censored').forEach(element => { element.remove(); }); } setupAutoMentionCompletion() { const chatInput = document.querySelector('#shouttext'); if (!chatInput) return; let suggestions = []; let selectedIndex = -1; let suggestionsContainer = null; this.collectUsersFromPage(); const createSuggestionsContainer = () => { if (suggestionsContainer) return; suggestionsContainer = document.createElement('div'); suggestionsContainer.className = 'mention-suggestions'; document.body.appendChild(suggestionsContainer); }; const positionSuggestions = () => { if (!suggestionsContainer) return; const rect = chatInput.getBoundingClientRect(); suggestionsContainer.style.left = rect.left + 'px'; suggestionsContainer.style.top = (rect.top - suggestionsContainer.offsetHeight - 5) + 'px'; suggestionsContainer.style.width = rect.width + 'px'; }; const showSuggestions = (suggestionList) => { if (suggestionList.length === 0) { this.hideMentionSuggestions(); return; } createSuggestionsContainer(); suggestionsContainer.innerHTML = ''; suggestionList.forEach((user, index) => { const suggestionDiv = document.createElement('div'); suggestionDiv.className = 'mention-suggestion'; suggestionDiv.innerHTML = ` <div class="username">${user.username}</div> <div class="user-info">${user.source}</div> `; suggestionDiv.addEventListener('click', () => { this.completeMention(chatInput, user.username); this.hideMentionSuggestions(); }); suggestionsContainer.appendChild(suggestionDiv); }); suggestionsContainer.style.display = 'block'; positionSuggestions(); this.highlightSuggestion(0); }; chatInput.addEventListener('input', (e) => { const value = e.target.value; const cursorPos = e.target.selectionStart; const textBeforeCursor = value.substring(0, cursorPos); const atIndex = textBeforeCursor.lastIndexOf('@'); if (atIndex !== -1 && (atIndex === 0 || value[atIndex - 1] === ' ')) { const query = textBeforeCursor.substring(atIndex + 1); if (query.length > 0) { suggestions = this.getUserSuggestions(query); selectedIndex = 0; showSuggestions(suggestions); } else { this.hideMentionSuggestions(); } } else { this.hideMentionSuggestions(); } }); chatInput.addEventListener('keydown', (e) => { if (suggestions.length > 0 && suggestionsContainer && suggestionsContainer.style.display === 'block') { if (e.key === 'ArrowDown') { selectedIndex = Math.min(selectedIndex + 1, suggestions.length - 1); this.highlightSuggestion(selectedIndex); e.preventDefault(); } else if (e.key === 'ArrowUp') { selectedIndex = Math.max(selectedIndex - 1, 0); this.highlightSuggestion(selectedIndex); e.preventDefault(); } else if (e.key === 'Tab' || e.key === 'Enter') { if (selectedIndex >= 0 && suggestions[selectedIndex]) { this.completeMention(chatInput, suggestions[selectedIndex].username); this.hideMentionSuggestions(); e.preventDefault(); } } else if (e.key === 'Escape') { this.hideMentionSuggestions(); } } }); document.addEventListener('click', (e) => { if (!chatInput.contains(e.target) && (!suggestionsContainer || !suggestionsContainer.contains(e.target))) { this.hideMentionSuggestions(); } }); } collectUsersFromPage() { const users = new Set(); document.querySelectorAll('a[href*="profile.php?id="]').forEach(link => { const username = link.textContent.trim(); if (username && username.length > 0 && username !== 'PM') { users.add(username); } }); let userCache = JSON.parse(GM_getValue('userCache', '[]')); users.forEach(user => { if (!userCache.some(cached => cached.username === user)) { userCache.push({ username: user, lastSeen: Date.now(), source: 'page' }); } }); userCache.sort((a, b) => b.lastSeen - a.lastSeen); userCache = userCache.slice(0, 200); GM_setValue('userCache', JSON.stringify(userCache)); } getUserSuggestions(query) { const suggestions = []; const seenUsers = new Set(); const queryLower = query.toLowerCase(); document.querySelectorAll('#shout a[href*="profile.php"]').forEach(link => { const username = link.textContent.trim(); if (username && username.toLowerCase().includes(queryLower) && !seenUsers.has(username)) { seenUsers.add(username); suggestions.push({ username: username, source: '💬 In chat', priority: 1 }); } }); document.querySelectorAll('.postleft dt strong a, .postright h3 a').forEach(link => { const username = link.textContent.trim(); if (username && username.toLowerCase().includes(queryLower) && !seenUsers.has(username)) { seenUsers.add(username); suggestions.push({ username: username, source: '📝 In topic', priority: 2 }); } }); const userCache = JSON.parse(GM_getValue('userCache', '[]')); userCache.forEach(user => { if (user.username.toLowerCase().includes(queryLower) && !seenUsers.has(user.username)) { seenUsers.add(user.username); const timeAgo = Math.floor((Date.now() - user.lastSeen) / (1000 * 60 * 60 * 24)); suggestions.push({ username: user.username, source: timeAgo === 0 ? '👁️ Today' : `👁️ ${timeAgo}d ago`, priority: 3 }); } }); return suggestions .sort((a, b) => a.priority - b.priority) .slice(0, 5); } completeMention(inputElement, username) { const value = inputElement.value; const cursorPos = inputElement.selectionStart; const textBeforeCursor = value.substring(0, cursorPos); const textAfterCursor = value.substring(cursorPos); const atIndex = textBeforeCursor.lastIndexOf('@'); if (atIndex !== -1) { const beforeAt = value.substring(0, atIndex); const newValue = beforeAt + '@' + username + ' ' + textAfterCursor; inputElement.value = newValue; const newCursorPos = atIndex + username.length + 2; inputElement.setSelectionRange(newCursorPos, newCursorPos); inputElement.focus(); } } highlightSuggestion(index) { const suggestions = document.querySelectorAll('.mention-suggestion'); suggestions.forEach((suggestion, i) => { suggestion.classList.toggle('selected', i === index); }); } hideMentionSuggestions() { const suggestionsContainer = document.querySelector('.mention-suggestions'); if (suggestionsContainer) { suggestionsContainer.style.display = 'none'; } } setupResellerList() { if (!window.location.href.includes('payment.php') || document.querySelector('.reseller-section')) return; const extendGameSense = document.querySelector('.blockform'); if (!extendGameSense) return; const resellerSection = document.createElement('div'); resellerSection.className = 'blockform reseller-section'; resellerSection.innerHTML = ` <h2> <span> <div style="display: flex; align-items: center; cursor: pointer;" class="section-header"> <i class="fa fa-magnet" style="margin-right: 8px; transition: transform 0.3s ease"></i> Verified Resellers </div> </span> </h2> <div class="box"> <div class="fakeform"> <div class="inform"> <fieldset> <legend>Alternative Payment Methods</legend> <div class="fakeform"> <p>Below is a list of verified resellers. Please be careful and only deal with listed resellers to avoid scams.</p> <table> <tr> <th class="tcl">Reseller</th> <th class="tcl">Payment Methods</th> <th class="tcl">Price</th> <th class="tcl">Action</th> </tr> <tr> <td><a href="profile.php?id=985">Sigma</a></td> <td>Crypto, PayPal, CashApp</td> <td>24 USD</td> <td><a href="viewtopic.php?id=23385" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=2933">death1989</a></td> <td>花呗,微信,支付宝,QQ红包</td> <td>135 RMB</td> <td><a href="viewtopic.php?id=17427" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=3031">484481617</a></td> <td>支付宝/微信/QQ/QIWI/淘宝/PayPal</td> <td>135 RMB</td> <td><a href="viewtopic.php?id=17435" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=1699">tiagovski</a></td> <td>PayPal, Bank, Card, Crypto, PSC, Alipay, Pix</td> <td>21 EUR</td> <td><a href="viewtopic.php?id=25671" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=10043">Margele</a></td> <td>支付宝,微信</td> <td>148.88 CNY</td> <td><a href="viewtopic.php?id=45009" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=12434">Samo</a></td> <td>PayPal, Giropay, TF2, Crypto, Skrill</td> <td>21 EUR</td> <td><a href="viewtopic.php?id=43045" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=16166">Cahira</a></td> <td>QQ, PayPal, Card, WeChat, Alipay, Crypto</td> <td>160 CNY</td> <td><a href="viewtopic.php?id=45499" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=16243">pguest</a></td> <td>QQ, PayPal, Card, WeChat, Alipay</td> <td>?? RMB</td> <td><a href="viewtopic.php?id=45179" class="button">Purchase</a></td> </tr> <tr> <td><a href="profile.php?id=9060">VKVKF</a></td> <td>Cards RU/EU/KZ/UA/ASIA, All Crypto</td> <td>30 USD</td> <td><a href="viewtopic.php?id=27735" class="button">Purchase</a></td> </tr> </table> <p>⚠️ Always verify the reseller's profile and reputation before making any payments. Be aware of scammers impersonating verified resellers.</p> </div> </fieldset> </div> </div> </div> `; const firstBlockform = document.querySelector('.blockform'); firstBlockform.parentNode.insertBefore(resellerSection, firstBlockform.nextSibling); this.addCollapseFunctionToSection(resellerSection); } addCollapseFunctionToSection(section) { const header = section.querySelector('.section-header'); const content = section.querySelector('.box'); const icon = header.querySelector('.fa-magnet'); const isSectionCollapsed = GM_getValue(`section_${header.textContent.trim()}_collapsed`, false); if (isSectionCollapsed) { content.style.display = 'none'; icon.style.transform = 'rotate(180deg)'; } header.addEventListener('click', () => { const isCollapsed = content.style.display === 'none'; content.style.display = isCollapsed ? '' : 'none'; icon.style.transform = isCollapsed ? '' : 'rotate(180deg)'; GM_setValue(`section_${header.textContent.trim()}_collapsed`, !isCollapsed); }); } addCollapsibleCategories() { const categories = document.querySelectorAll('.blocktable h2'); categories.forEach(category => { if (category.querySelector('.fa-magnet')) return; const magnetIcon = document.createElement('i'); magnetIcon.className = 'fa fa-magnet'; magnetIcon.style.cssText = ` margin-right: 8px; transition: transform 0.3s ease; `; const headerWrapper = document.createElement('div'); headerWrapper.style.cssText = ` display: flex; align-items: center; cursor: pointer; user-select: none; `; const span = category.querySelector('span'); if (!span) return; const content = span.cloneNode(true); headerWrapper.appendChild(magnetIcon); headerWrapper.appendChild(content); category.innerHTML = ''; category.appendChild(headerWrapper); const categoryContent = category.closest('.blocktable'); const contentBox = categoryContent.querySelector('.box'); headerWrapper.addEventListener('click', () => { const isCollapsed = contentBox.style.display === 'none'; contentBox.style.display = isCollapsed ? '' : 'none'; magnetIcon.style.transform = isCollapsed ? '' : 'rotate(180deg)'; const categoryText = content.textContent.trim(); GM_setValue(`category_${categoryText}_collapsed`, !isCollapsed); }); const savedState = GM_getValue(`category_${content.textContent.trim()}_collapsed`, false); if (savedState) { contentBox.style.display = 'none'; magnetIcon.style.transform = 'rotate(180deg)'; } }); } setupPremiumUI() { if (!window.location.href.includes('profile.php') || !window.location.href.includes('section=premium') || document.querySelector('#gs-premium-ui')) return; const container = document.querySelector('.blockform .box'); if (!container) return; const userId = window.location.href.match(/id=(\d+)/) ? window.location.href.match(/id=(\d+)/)[1] : document.querySelector('#brdwelcome .conl a[href*="profile.php"]')?.href.match(/id=(\d+)/)?.[1]; if (!userId) return; const existingStatus = container.querySelector('.infldset p')?.textContent || 'No active subscription'; container.id = 'gs-premium-ui'; container.innerHTML = ` <form id="profile8" method="post" action="profile.php?section=premium&id=${userId}"> <input type="hidden" name="form_sent" value="1" /> <div class="inform"> <fieldset> <legend>Subscription Status</legend> <div class="infldset"> <div class="status-text"> <i class="fa fa-clock-o"></i> <span>${existingStatus}</span> </div> <div class="button-group"> <a href="payment.php?game=csgo" class="button"> <i class="fa fa-refresh"></i> ${existingStatus.includes('No active') ? 'Purchase Subscription' : 'Extend Subscription'} </a> </div> </div> </fieldset> </div> <div class="inform"> <fieldset> <legend>Game Clients</legend> <div class="infldset"> <div class="button-group"> ${existingStatus.includes('No active') ? ` <button type="button" disabled class="button"> <i class="fa fa-download"></i> CS2 Client </button> <button type="button" disabled class="button"> <i class="fa fa-download"></i> CS:GO Client </button> ` : ` <button type="submit" name="download_client" class="button"> <i class="fa fa-download"></i> CS2 Client </button> <button type="submit" name="download_client_csgo" class="button"> <i class="fa fa-download"></i> CS:GO Client </button> `} </div> </div> </fieldset> </div> <div class="inform"> <fieldset> <legend>Discord Management</legend> <div class="infldset"> <div class="input-with-button"> <input id="discord_reset_reason" type="text" name="discord_reset_reason" placeholder="Enter reason for Discord ID reset" maxlength="40" ${existingStatus.includes('No active') ? 'disabled' : ''} /> <button type="submit" name="reset_discord" class="button" ${existingStatus.includes('No active') ? 'disabled' : ''}> <i class="fa fa-refresh"></i> Reset </button> </div> </div> </fieldset> </div> <div class="inform"> <fieldset> <legend>Invite Codes</legend> <div class="infldset"> <p>You have no unused invitation codes.</p> </div> </fieldset> </div> </form> `; const form = container.querySelector('form'); form.querySelectorAll(':submit').forEach(button => { button.addEventListener('click', function(e) { const discordReason = document.getElementById('discord_reset_reason'); if (this.name === 'reset_discord' && discordReason.value.trim() === '') { discordReason.classList.add('contains-error'); e.preventDefault(); return; } this.disabled = true; const hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = this.name; hiddenInput.value = this.value; form.appendChild(hiddenInput); }); }); } } new GSEnhancedUI();