iClick

Auto-click any button on any page. Supports interval, time limit, click limit, and persistent run across page navigation.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         iClick
// @namespace    https://github.com/
// @version      2.1
// @description  Auto-click any button on any page. Supports interval, time limit, click limit, and persistent run across page navigation.
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
  'use strict';

  // ── 暗色模式 ──
  const dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  const C = dark
    ? { bg: '#1f1f1f', border: '#333', text: '#ccc', sub: '#888', badge: '#2a2a2a', sep: '#333' }
    : { bg: '#fff',    border: '#ddd', text: '#333', sub: '#555', badge: '#f5f5f5', sep: '#eee' };

  // ── 共享顶栏 ──
  function getTopbar() {
    let bar = document.getElementById('kiro-topbar');
    if (!bar) {
      bar = document.createElement('div');
      bar.id = 'kiro-topbar';
      bar.style.cssText = `position:fixed;top:0;left:0;right:0;z-index:99999;background:${C.bg};border-bottom:1px solid ${C.border};display:flex;align-items:stretch;min-height:36px;box-shadow:0 2px 6px rgba(0,0,0,.15);font-size:13px;font-family:sans-serif;`;
      document.body.appendChild(bar);
      document.body.style.paddingTop = '36px';
    }
    return bar;
  }

  function addSection(id) {
    const bar = getTopbar();
    if (bar.querySelector(`#${id}`)) return bar.querySelector(`#${id}`);
    if (bar.children.length > 0) {
      const sep = document.createElement('div');
      sep.style.cssText = `width:1px;background:${C.sep};margin:6px 0;flex-shrink:0;`;
      bar.appendChild(sep);
    }
    const spacer = document.createElement('div');
    spacer.style.cssText = 'flex:1;';
    bar.appendChild(spacer);
    const sec = document.createElement('div');
    sec.id = id;
    sec.style.cssText = 'display:flex;align-items:center;gap:8px;padding:0 14px;flex-shrink:0;overflow:hidden;';
    bar.appendChild(sec);
    return sec;
  }

  // ── 跨页持久运行:页面加载时检查是否应自动恢复 ──
  const PERSIST_KEY = 'ac_persist';
  const savedState = JSON.parse(GM_getValue(PERSIST_KEY, 'null'));
  const shouldResume = savedState && savedState.running && savedState.target;

  const savedInterval = GM_getValue('ac_interval', 1500);

  let running = false, timer = null, clockTimer = null;
  let count = 0, elapsed = 0;

  // ── UI ──
  const sec = addSection('kiro-clicker');

  const label = el('span', `font-weight:700;white-space:nowrap;color:${C.text};font-size:13px;letter-spacing:.5px;`, 'iClick');

  const targetSelect = document.createElement('select');
  targetSelect.style.cssText = `padding:2px 8px;border:1px solid ${C.border};border-radius:12px;font-size:12px;background:${C.badge};color:${C.text};max-width:120px;flex-shrink:0;outline:none;cursor:pointer;`;

  const intervalInput = document.createElement('input');
  intervalInput.type = 'number'; intervalInput.value = savedInterval; intervalInput.min = 100;
  intervalInput.title = '间隔(ms)';
  intervalInput.style.cssText = `width:64px;padding:2px 8px;border:1px solid ${C.border};border-radius:12px;font-size:12px;background:${C.badge};color:${C.text};outline:none;text-align:center;`;

  const limitInput = document.createElement('input');
  limitInput.type = 'number'; limitInput.value = GM_getValue('ac_limit', 0); limitInput.min = 0;
  limitInput.title = '时限(分钟,0=不限)';
  limitInput.style.cssText = `width:54px;padding:2px 8px;border:1px solid ${C.border};border-radius:12px;font-size:12px;background:${C.badge};color:${C.text};outline:none;text-align:center;`;

  const maxInput = document.createElement('input');
  maxInput.type = 'number'; maxInput.value = GM_getValue('ac_max', 0); maxInput.min = 0;
  maxInput.title = '点击上限(0=不限)';
  maxInput.style.cssText = `width:54px;padding:2px 8px;border:1px solid ${C.border};border-radius:12px;font-size:12px;background:${C.badge};color:${C.text};outline:none;text-align:center;`;

  const status = el('span', `color:${C.sub};white-space:nowrap;font-size:12px;`, '就绪');
  const toggleBtn = btn('▶ 开始', '#1677ff');

  const lbInterval = el('span', `color:${C.sub};font-size:11px;white-space:nowrap;`, 'ms');
  const lbLimit    = el('span', `color:${C.sub};font-size:11px;white-space:nowrap;`, 'min');
  const lbMax      = el('span', `color:${C.sub};font-size:11px;white-space:nowrap;`, 'max');

  sec.append(label, targetSelect, lbInterval, intervalInput, lbLimit, limitInput, lbMax, maxInput, status, toggleBtn);

  // 扫描按钮
  const refreshButtons = () => {
    const cur = targetSelect.value || (savedState && savedState.target) || '';
    targetSelect.innerHTML = '';
    [...new Set(
      [...document.querySelectorAll('button,input[type=button],input[type=submit],[role=button]')]
        .filter(b => b.offsetParent && !document.getElementById('kiro-clicker')?.contains(b))
        .map(b => (b.textContent || b.value || '').trim())
        .filter(Boolean)
    )].forEach(text => {
      const o = document.createElement('option');
      o.value = o.textContent = text;
      if (text === cur) o.selected = true;
      targetSelect.appendChild(o);
    });
  };
  refreshButtons();
  targetSelect.onfocus = refreshButtons;

  const findTarget = () =>
    [...document.querySelectorAll('button,input[type=button],input[type=submit],[role=button]')]
      .find(b => (b.textContent || b.value || '').trim() === targetSelect.value
        && !b.disabled && b.offsetParent
        && !document.getElementById('kiro-clicker')?.contains(b));

  const stop = (reason) => {
    running = false;
    clearInterval(timer); clearInterval(clockTimer);
    toggleBtn.textContent = '▶ 开始'; toggleBtn.style.background = '#1677ff';
    GM_setValue(PERSIST_KEY, JSON.stringify({ running: false, target: targetSelect.value }));
    if (reason) status.textContent = reason;
  };

  const start = () => {
    const interval = Math.max(100, parseInt(intervalInput.value) || 1500);
    const limitSec = (parseFloat(limitInput.value) || 0) * 60;
    const maxClicks = parseInt(maxInput.value) || 0;
    GM_setValue('ac_interval', interval);
    GM_setValue('ac_limit', limitInput.value);
    GM_setValue('ac_max', maxInput.value);
    GM_setValue(PERSIST_KEY, JSON.stringify({ running: true, target: targetSelect.value }));

    count = 0; elapsed = 0; running = true;
    toggleBtn.textContent = '⏹ 停止'; toggleBtn.style.background = '#ff4d4f';

    clockTimer = setInterval(() => {
      elapsed++;
      const h = Math.floor(elapsed / 3600), m = Math.floor((elapsed % 3600) / 60), s = elapsed % 60;
      const timeStr = `${h ? h + 'h ' : ''}${m ? m + 'm ' : ''}${s}s`;
      status.textContent = `已点击 ${count} 次 · ${timeStr}`;
      if (limitSec > 0 && elapsed >= limitSec) stop('⏱ 已达时限,自动停止');
    }, 1000);

    timer = setInterval(() => {
      const el = findTarget();
      if (el) {
        el.click(); count++;
        if (maxClicks > 0 && count >= maxClicks) stop(`✅ 已达上限 ${maxClicks} 次,自动停止`);
      } else {
        status.textContent = `⚠️ 未找到按钮「${targetSelect.value}」`;
      }
    }, interval);
  };

  toggleBtn.onclick = () => running ? stop() : start();

  // 自动恢复
  if (shouldResume) {
    // 等 DOM 稳定后恢复
    setTimeout(() => { refreshButtons(); start(); status.textContent = '已自动恢复运行'; }, 1500);
  }

  function btn(text, color) {
    const b = document.createElement('button');
    b.textContent = text;
    b.style.cssText = `padding:3px 14px;background:${color};color:#fff;border:none;border-radius:12px;cursor:pointer;font-size:12px;white-space:nowrap;flex-shrink:0;font-weight:500;letter-spacing:.3px;transition:opacity .15s;`;
    b.onmouseover = () => b.style.opacity = '0.8';
    b.onmouseout  = () => b.style.opacity = '1';
    return b;
  }

  function el(tag, css, text) {
    const e = document.createElement(tag);
    e.style.cssText = css;
    if (text !== undefined) e.textContent = text;
    return e;
  }
})();