您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Library For GameSense 2.1
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/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();