您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Универсальный счётчик LZT
// ==UserScript== // @name LZT counter // @namespace Счётчик кликов // @author Plarq // @version 1.0 // @description Универсальный счётчик LZT // @license Apache 2.0 // @match https://zelenka.guru/* // @match https://lolz.live/* // @icon https://lolz.live/styles/brand/download/avatars/three_avatar.svg // @grant GM.getValue // @grant GM.setValue // @grant GM.xmlHttpRequest // @grant unsafeWindow // @run-at document-end // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js // ==/UserScript== /* global _ */ (function() { 'use strict'; const CONFIG = { COUNTER_ID: 'lzt-counter-visible', SETTINGS_BTN_ID: 'lzt-counter-settings-btn', ACHIEVEMENTS_BTN_ID: 'lzt-counter-achievements-btn', SETTINGS_MENU_ID: 'lzt-counter-settings-menu', ACHIEVEMENTS_MENU_ID: 'lzt-counter-achievements-menu', STORAGE_KEY: 'lztGlobalCounterV2', STORAGE_MAX_KEY: 'lztGlobalCounterMaxV2', STYLES_KEY: 'lztCounterStyles', FONT_SIZE_KEY: 'lztCounterFontSize', BACKGROUND_OPACITY_KEY: 'lztCounterBgOpacity', BUTTON_SELECTORS: { 'zelenka.guru': '[data-t="update"]', 'lzt.market': '.feed__refresh-button', 'lolz.market': '.refresh-feed', 'lolz.live': '.UpdateFeedButton' }, ACHIEVEMENTS: [ { threshold: 100, title: 'Новокек' }, { threshold: 500, title: 'Местный' }, { threshold: 1000, title: 'Постоялец' }, { threshold: 2500, title: 'Эксперт' }, { threshold: 5000, title: 'Гуру' }, { threshold: 10000, title: 'Искусственный интеллект' } ], INITIAL_POSITION: { x: 260, y: 950 }, DEFAULT_FONT_SIZE: 18, DEFAULT_BG_OPACITY: 1, NOTIFICATION_DURATION: 5000, NOTIFICATION_OFFSET: 20, DEBUG: false }; let isDragging = false; let animationFrameId = null; function log(...args) { if (CONFIG.DEBUG) console.log('[LZT Counter]', ...args); } function isFeedPage() { const path = window.location.pathname; return path.includes('/feed') || path === '/'; } function hexToRgba(hex, alpha) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } async function applyStyles(counterElement) { const savedStyles = await GM.getValue(CONFIG.STYLES_KEY, {}); const fontSize = await GM.getValue(CONFIG.FONT_SIZE_KEY, CONFIG.DEFAULT_FONT_SIZE); const bgOpacity = await GM.getValue(CONFIG.BACKGROUND_OPACITY_KEY, CONFIG.DEFAULT_BG_OPACITY); counterElement.style.cssText = ` position: fixed; color: ${savedStyles.color || '#ffffff'}; background: ${savedStyles.background === false ? 'transparent' : hexToRgba('#1A1A1A', bgOpacity)}; box-shadow: ${savedStyles.background === false ? 'none' : '0 2px 5px rgba(0,0,0,0.2)'}; padding: ${savedStyles.background === false ? '0' : '8px 15px'}; border-radius: 5px; z-index: 9998; cursor: pointer; font-size: ${fontSize}px; font-weight: bold; user-select: none; min-width: 60px; text-align: center; white-space: nowrap; transition: all 0.2s ease; box-sizing: border-box; left: ${savedStyles.customPosition?.x || CONFIG.INITIAL_POSITION.x}px; top: ${savedStyles.customPosition?.y || CONFIG.INITIAL_POSITION.y}px; `; } function createButton(text, color) { const btn = document.createElement('button'); btn.textContent = text; btn.style.cssText = ` flex: 1; background: ${color}; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer; font-size: 14px; white-space: nowrap; transition: opacity 0.2s; `; btn.addEventListener('mouseover', () => btn.style.opacity = '0.8'); btn.addEventListener('mouseout', () => btn.style.opacity = '1'); return btn; } function createSettingsMenu() { const menu = document.createElement('div'); menu.id = CONFIG.SETTINGS_MENU_ID; menu.style.cssText = ` position: fixed; bottom: 60px; left: 20px; background: #1A1A1A; color: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 9999; display: none; width: 300px; box-sizing: border-box; `; const createRow = () => { const row = document.createElement('div'); row.style.cssText = 'display: flex; gap: 8px; margin-bottom: 12px;'; return row; }; const row1 = createRow(); const colorBtn = createButton('Цвет', '#228E5D'); const bgBtn = createButton('Фон', '#228E5D'); row1.appendChild(colorBtn); row1.appendChild(bgBtn); const row2 = createRow(); const moveBtn = createButton('Переместить', '#228E5D'); const resetBtn = createButton('Сброс позиции', '#228E5D'); row2.appendChild(moveBtn); row2.appendChild(resetBtn); const createSlider = (config) => { const container = document.createElement('div'); container.style.cssText = 'margin: 15px 0;'; const label = document.createElement('div'); label.textContent = config.label; label.style.cssText = ` color: rgba(255,255,255,0.7); font-size: 12px; margin-bottom: 8px; display: flex; justify-content: space-between; `; const value = document.createElement('span'); value.textContent = config.valueText; const slider = document.createElement('input'); slider.type = 'range'; Object.assign(slider, config.sliderProps); slider.style.cssText = ` width: 100%; height: 4px; background: #333; border-radius: 2px; -webkit-appearance: none; outline: none; &::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: #228E5D; border-radius: 50%; cursor: pointer; } `; slider.addEventListener('input', _.throttle(async (e) => { const newValue = config.parser(e.target.value); value.textContent = config.formatter(newValue); await GM.setValue(config.key, newValue); applyStyles(document.getElementById(CONFIG.COUNTER_ID)); }, 100)); label.appendChild(value); container.appendChild(label); container.appendChild(slider); return container; }; const fontSizeSlider = createSlider({ label: 'Размер текста:', key: CONFIG.FONT_SIZE_KEY, sliderProps: { min: 12, max: 36, step: 1, value: CONFIG.DEFAULT_FONT_SIZE }, parser: parseInt, formatter: v => `${v}px`, valueText: `${CONFIG.DEFAULT_FONT_SIZE}px` }); const opacitySlider = createSlider({ label: 'Прозрачность фона:', key: CONFIG.BACKGROUND_OPACITY_KEY, sliderProps: { min: 0, max: 1, step: 0.1, value: CONFIG.DEFAULT_BG_OPACITY }, parser: parseFloat, formatter: v => `${Math.round(v * 100)}%`, valueText: `${Math.round(CONFIG.DEFAULT_BG_OPACITY * 100)}%` }); const hint = document.createElement('div'); hint.style.cssText = ` color: rgba(255,255,255,0.7); font-size: 12px; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.2); `; hint.innerHTML = 'Для сохранения позиции<br>нажмите ЛКМ во время перемещения'; menu.appendChild(row1); menu.appendChild(row2); menu.appendChild(fontSizeSlider); menu.appendChild(opacitySlider); menu.appendChild(hint); const colorInput = document.createElement('input'); colorInput.type = 'color'; colorInput.style.cssText = 'position: absolute; opacity: 0; pointer-events: none;'; colorBtn.addEventListener('click', () => { colorInput.value = document.getElementById(CONFIG.COUNTER_ID).style.color; colorInput.click(); }); colorInput.addEventListener('input', async (e) => { const styles = await GM.getValue(CONFIG.STYLES_KEY, {}); styles.color = e.target.value; await GM.setValue(CONFIG.STYLES_KEY, styles); applyStyles(document.getElementById(CONFIG.COUNTER_ID)); }); bgBtn.addEventListener('click', async () => { const styles = await GM.getValue(CONFIG.STYLES_KEY, {}); styles.background = !styles.background; await GM.setValue(CONFIG.STYLES_KEY, styles); applyStyles(document.getElementById(CONFIG.COUNTER_ID)); bgBtn.textContent = styles.background ? 'Убрать фон' : 'Вернуть фон'; }); moveBtn.addEventListener('click', () => startDragging()); resetBtn.addEventListener('click', async () => { const styles = await GM.getValue(CONFIG.STYLES_KEY, {}); delete styles.customPosition; await GM.setValue(CONFIG.STYLES_KEY, styles); applyStyles(document.getElementById(CONFIG.COUNTER_ID)); }); Promise.all([ GM.getValue(CONFIG.FONT_SIZE_KEY, CONFIG.DEFAULT_FONT_SIZE), GM.getValue(CONFIG.BACKGROUND_OPACITY_KEY, CONFIG.DEFAULT_BG_OPACITY) ]).then(([fontSize, opacity]) => { fontSizeSlider.querySelector('input').value = fontSize; fontSizeSlider.querySelector('span').textContent = `${fontSize}px`; opacitySlider.querySelector('input').value = opacity; opacitySlider.querySelector('span').textContent = `${Math.round(opacity * 100)}%`; }); document.body.appendChild(menu); document.body.appendChild(colorInput); return menu; } function createAchievementsMenu() { const menu = document.createElement('div'); menu.id = CONFIG.ACHIEVEMENTS_MENU_ID; menu.style.cssText = ` position: fixed; bottom: 60px; left: 20px; background: #1A1A1A; color: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 9999; display: none; width: 300px; box-sizing: border-box; `; const title = document.createElement('div'); title.textContent = 'Достижения'; title.style.cssText = 'font-weight: bold; margin-bottom: 15px; font-size: 16px;'; const progressContainer = document.createElement('div'); progressContainer.style.marginBottom = '15px'; const progressBar = document.createElement('div'); progressBar.style.cssText = ` background: #333; height: 10px; border-radius: 5px; overflow: hidden; `; const progressFill = document.createElement('div'); progressFill.style.cssText = ` background: #228E5D; height: 100%; width: 0%; transition: width 0.3s ease; `; progressBar.appendChild(progressFill); progressContainer.appendChild(progressBar); const achievementsList = document.createElement('div'); achievementsList.style.cssText = 'max-height: 300px; overflow-y: auto;'; menu.appendChild(title); menu.appendChild(progressContainer); menu.appendChild(achievementsList); async function updateMenu() { const maxCounter = await GM.getValue(CONFIG.STORAGE_MAX_KEY, 0); const achievements = CONFIG.ACHIEVEMENTS; let currentAchievement = null; let nextAchievement = null; for (let i = 0; i < achievements.length; i++) { if (maxCounter >= achievements[i].threshold) { currentAchievement = achievements[i]; if (i < achievements.length - 1) { nextAchievement = achievements[i + 1]; } } else { if (!nextAchievement) nextAchievement = achievements[i]; break; } } if (currentAchievement && nextAchievement) { const progress = ((maxCounter - currentAchievement.threshold) / (nextAchievement.threshold - currentAchievement.threshold)) * 100; progressFill.style.width = `${Math.min(progress, 100)}%`; } else if (currentAchievement && !nextAchievement) { progressFill.style.width = '100%'; } else if (!currentAchievement && nextAchievement) { const progress = (maxCounter / nextAchievement.threshold) * 100; progressFill.style.width = `${progress}%`; } else { progressFill.style.width = '0%'; } achievementsList.innerHTML = ''; achievements.forEach(ach => { const isUnlocked = maxCounter >= ach.threshold; const item = document.createElement('div'); item.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.1); `; const titleSpan = document.createElement('span'); titleSpan.textContent = ach.title; titleSpan.style.color = isUnlocked ? '#fff' : 'rgba(255,255,255,0.5)'; const thresholdSpan = document.createElement('span'); thresholdSpan.textContent = ach.threshold; thresholdSpan.style.color = isUnlocked ? '#228E5D' : 'rgba(255,255,255,0.5)'; thresholdSpan.style.fontSize = '14px'; item.appendChild(titleSpan); item.appendChild(thresholdSpan); achievementsList.appendChild(item); }); } menu.updateMenu = updateMenu; menu.addEventListener('click', (e) => e.stopPropagation()); document.body.appendChild(menu); return menu; } function showAchievementNotification(achievement) { const notification = document.createElement('div'); notification.className = 'lzt-achievement-notification'; notification.innerHTML = ` <div style="font-size: 16px; color: #228E5D; margin-bottom: 4px;">✓ Достижение получено!</div> <div style="font-size: 14px;"> Вы теперь ${achievement.title}</div> `; notification.style.cssText = ` position: fixed; left: ${CONFIG.NOTIFICATION_OFFSET}px; top: 50%; transform: translateY(-50%); background: #1A1A1A; color: white; padding: 16px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 10000; cursor: pointer; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; max-width: 250px; `; document.body.appendChild(notification); requestAnimationFrame(() => { notification.style.opacity = '1'; notification.style.transform = `translate(10px, -50%)`; }); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }, CONFIG.NOTIFICATION_DURATION); notification.addEventListener('click', () => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }); } async function checkAchievements(newMax) { const shownAchievements = await GM.getValue('shownAchievements', {}); const newAchievements = []; CONFIG.ACHIEVEMENTS.forEach(ach => { if (newMax >= ach.threshold && !shownAchievements[ach.threshold]) { newAchievements.push(ach); shownAchievements[ach.threshold] = true; } }); if (newAchievements.length > 0) { await GM.setValue('shownAchievements', shownAchievements); newAchievements.forEach(ach => showAchievementNotification(ach)); } } function startDragging() { const counter = document.getElementById(CONFIG.COUNTER_ID); const menu = document.getElementById(CONFIG.SETTINGS_MENU_ID); isDragging = true; menu.style.display = 'none'; counter.style.cursor = 'grabbing'; let lastX = 0, lastY = 0; let posX = 0, posY = 0; const pointerMoveHandler = e => { if (!isDragging) return; lastX = e.clientX; lastY = e.clientY; if (!animationFrameId) { animationFrameId = requestAnimationFrame(updatePosition); } }; const updatePosition = () => { if (!isDragging) return; const counterRect = counter.getBoundingClientRect(); const maxX = window.innerWidth - counterRect.width; const maxY = window.innerHeight - counterRect.height; posX = Math.min(Math.max(lastX - counterRect.width/2, 0), maxX); posY = Math.min(Math.max(lastY - counterRect.height/2, 0), maxY); counter.style.left = `${posX}px`; counter.style.top = `${posY}px`; animationFrameId = requestAnimationFrame(updatePosition); }; const pointerUpHandler = async () => { isDragging = false; counter.style.cursor = 'pointer'; cancelAnimationFrame(animationFrameId); animationFrameId = null; const styles = await GM.getValue(CONFIG.STYLES_KEY, {}); styles.customPosition = {x: posX, y: posY}; await GM.setValue(CONFIG.STYLES_KEY, styles); document.removeEventListener('pointermove', pointerMoveHandler); document.removeEventListener('pointerup', pointerUpHandler); }; document.addEventListener('pointermove', pointerMoveHandler); document.addEventListener('pointerup', pointerUpHandler, {once: true}); } async function main() { if (!isFeedPage()) return; const domain = window.location.hostname.replace('www.', ''); log('Domain:', domain); let counter = await GM.getValue(CONFIG.STORAGE_KEY, 0); let maxCounter = await GM.getValue(CONFIG.STORAGE_MAX_KEY, 0); if (maxCounter < counter) { await GM.setValue(CONFIG.STORAGE_MAX_KEY, counter); } let counterElement = document.getElementById(CONFIG.COUNTER_ID); if (!counterElement) { counterElement = document.createElement('div'); counterElement.id = CONFIG.COUNTER_ID; counterElement.textContent = counter; counterElement.addEventListener('dblclick', async () => { await GM.setValue(CONFIG.STORAGE_KEY, 0); counterElement.textContent = '0'; }); document.body.appendChild(counterElement); await applyStyles(counterElement); } if (!document.getElementById(CONFIG.SETTINGS_BTN_ID)) { const buttonsContainer = document.createElement('div'); buttonsContainer.id = 'lzt-counter-buttons-container'; buttonsContainer.style.cssText = ` position: fixed; bottom: 20px; left: 20px; z-index: 9999; display: flex; gap: 8px; `; const settingsBtn = document.createElement('button'); settingsBtn.id = CONFIG.SETTINGS_BTN_ID; settingsBtn.textContent = 'Настройки'; settingsBtn.style.cssText = ` padding: 8px 16px; background: #1A1A1A; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; white-space: nowrap; box-shadow: 0 2px 5px rgba(0,0,0,0.2); `; const achievementsBtn = document.createElement('button'); achievementsBtn.id = CONFIG.ACHIEVEMENTS_BTN_ID; achievementsBtn.textContent = 'Достижения'; achievementsBtn.style.cssText = settingsBtn.style.cssText; buttonsContainer.appendChild(settingsBtn); buttonsContainer.appendChild(achievementsBtn); document.body.appendChild(buttonsContainer); const settingsMenu = createSettingsMenu(); const achievementsMenu = createAchievementsMenu(); settingsBtn.addEventListener('click', (e) => { e.stopPropagation(); settingsMenu.style.display = settingsMenu.style.display === 'block' ? 'none' : 'block'; achievementsMenu.style.display = 'none'; }); achievementsBtn.addEventListener('click', (e) => { e.stopPropagation(); achievementsMenu.style.display = achievementsMenu.style.display === 'block' ? 'none' : 'block'; settingsMenu.style.display = 'none'; achievementsMenu.updateMenu(); }); document.addEventListener('click', (e) => { if (!settingsMenu.contains(e.target) && !achievementsMenu.contains(e.target) && !settingsBtn.contains(e.target) && !achievementsBtn.contains(e.target)) { settingsMenu.style.display = 'none'; achievementsMenu.style.display = 'none'; } }); } const buttonSelector = CONFIG.BUTTON_SELECTORS[domain]; const handleClick = _.throttle(async () => { const current = await GM.getValue(CONFIG.STORAGE_KEY, 0); const newCounter = current + 1; const maxCounter = await GM.getValue(CONFIG.STORAGE_MAX_KEY, 0); const newMax = Math.max(maxCounter, newCounter); await GM.setValue(CONFIG.STORAGE_KEY, newCounter); await GM.setValue(CONFIG.STORAGE_MAX_KEY, newMax); document.getElementById(CONFIG.COUNTER_ID).textContent = newCounter; if (newMax > maxCounter) { await checkAchievements(newMax); } }, 1000); new MutationObserver(() => { const button = document.querySelector(buttonSelector); if (button && !button.dataset.listener) { button.addEventListener('click', handleClick); button.dataset.listener = 'true'; } }).observe(document.documentElement, { childList: true, subtree: true }); } main().catch(e => log('Error:', e)); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址