Last active
December 13, 2025 14:13
-
-
Save ifthenelse/c57a69c50657e867e4733f99fd9d36a6 to your computer and use it in GitHub Desktop.
Twitch bookmarklet to auto-claim points
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| javascript: (() => { | |
| const KEY = "__twitchAutoClaimer"; | |
| if (window[KEY]?.stop) { | |
| window[KEY].stop(false); | |
| return; | |
| } | |
| const state = { | |
| start: new Date(), | |
| checks: 0, | |
| collected: 0, | |
| running: true, | |
| intervalMs: 30000, | |
| timer: null, | |
| stopped: false, | |
| }; | |
| const pad = (n) => String(n).padStart(2, "0"); | |
| const fmt = (d) => | |
| `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; | |
| const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); | |
| /* ---------- UI ---------- */ | |
| const cssBtn = | |
| "cursor:pointer;background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.18);border-radius:8px;padding:2px 8px;font-size:12px;"; | |
| const overlay = document.createElement("div"); | |
| overlay.style.cssText = [ | |
| "position:fixed", | |
| "left:16px", | |
| "top:16px", | |
| "z-index:2147483647", | |
| "background:rgba(0,0,0,.82)", | |
| "color:#fff", | |
| "font:12px/1.35 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif", | |
| "border:1px solid rgba(255,255,255,.18)", | |
| "border-radius:10px", | |
| "box-shadow:0 10px 30px rgba(0,0,0,.35)", | |
| "padding:10px 12px", | |
| "min-width:260px", | |
| "user-select:none", | |
| ].join(";"); | |
| overlay.innerHTML = ` | |
| <div id="tac-header" style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px;cursor:move;"> | |
| <div style="font-weight:700;">Twitch Auto-Claimer</div> | |
| <div style="display:flex;gap:6px;"> | |
| <button id="tac-pause" style="${cssBtn}">Pause</button> | |
| <button id="tac-stop" style="${cssBtn}background:rgba(255,80,80,.25);">Stop & Close</button> | |
| </div> | |
| </div> | |
| <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;"> | |
| <span id="tac-dot" style="width:10px;height:10px;border-radius:999px;display:inline-block;"></span> | |
| <span id="tac-state" style="font-weight:700;"></span> | |
| </div> | |
| <div style="display:grid;grid-template-columns:auto 1fr;gap:2px 10px;"> | |
| <div style="opacity:.75;">Started</div><div id="tac-start">-</div> | |
| <div style="opacity:.75;">Checks</div><div id="tac-checks">-</div> | |
| <div style="opacity:.75;">Movement</div><div id="tac-movement">-</div> | |
| <div style="opacity:.75;">Collected</div><div id="tac-collected">-</div> | |
| <div style="opacity:.75;">Last</div><div id="tac-last">-</div> | |
| </div> | |
| `; | |
| document.body.appendChild(overlay); | |
| const ui = { | |
| pause: overlay.querySelector("#tac-pause"), | |
| stop: overlay.querySelector("#tac-stop"), | |
| dot: overlay.querySelector("#tac-dot"), | |
| state: overlay.querySelector("#tac-state"), | |
| start: overlay.querySelector("#tac-start"), | |
| checks: overlay.querySelector("#tac-checks"), | |
| movement: overlay.querySelector("#tac-movement"), | |
| collected: overlay.querySelector("#tac-collected"), | |
| last: overlay.querySelector("#tac-last"), | |
| }; | |
| const setStateUI = () => { | |
| if (state.running) { | |
| ui.dot.style.background = "rgba(80,255,120,.95)"; | |
| ui.state.textContent = "RUNNING"; | |
| ui.state.style.color = "rgba(80,255,120,.95)"; | |
| ui.pause.textContent = "Pause"; | |
| } else { | |
| ui.dot.style.background = "rgba(255,80,80,.95)"; | |
| ui.state.textContent = "PAUSED"; | |
| ui.state.style.color = "rgba(255,80,80,.95)"; | |
| ui.pause.textContent = "Continue"; | |
| } | |
| }; | |
| const updateUI = ({ msg_mov, msg_claim }) => { | |
| ui.start.textContent = `${state.start.toLocaleString()} (${fmt( | |
| state.start | |
| )})`; | |
| if (msg_mov !== undefined) { | |
| ui.movement.textContent = msg_mov; | |
| } | |
| if (msg_claim !== undefined) { | |
| ui.checks.textContent = String(state.checks); | |
| ui.collected.textContent = String(state.collected); | |
| ui.last.textContent = msg_claim; | |
| } | |
| setStateUI(); | |
| }; | |
| /* ---------- Core logic ---------- */ | |
| const simulateHumanMovement = () => { | |
| let msg = `Movement ${fmt(new Date())}`; | |
| try { | |
| const target = document.querySelector( | |
| '[aria-label="Chat messages"] .chat-scrollable-area__message-container' | |
| ); | |
| const rect = target.getBoundingClientRect(); | |
| let t = 0; | |
| const move = () => { | |
| const x = rect.left + 150 + Math.sin(t / 20) * 100; | |
| const y = rect.top + 150 + Math.cos(t / 20) * 100; | |
| const event = new MouseEvent("mousemove", { | |
| clientX: x, | |
| clientY: y, | |
| bubbles: true, | |
| }); | |
| target.dispatchEvent(event); | |
| t += 1; | |
| if (t < 100) setTimeout(move, 20); | |
| } | |
| move(); | |
| msg = `✅ Movement simulated ${fmt(new Date())}`; | |
| console.log("[Twitch Auto-Claimer] ✅ Movement simulated"); | |
| } | |
| catch (e) { | |
| msg = `⚠️ Error on movement simulation: ${fmt(new Date())}`; | |
| console.error("[Twitch Auto-Claimer] ⚠️ Error on movement simulation:", e); | |
| } | |
| updateUI({ msg_mov: msg }); | |
| } | |
| const tick = () => { | |
| if (!state.running || state.stopped) return; | |
| state.checks++; | |
| let msg = `Checked ${fmt(new Date())}`; | |
| simulateHumanMovement(); | |
| try { | |
| const btn = document.querySelector('[aria-label="Claim Bonus"]'); | |
| if (btn) { | |
| btn.click(); | |
| state.collected++; | |
| msg = `✅ Collected ${fmt(new Date())}`; | |
| console.log("[Twitch Auto-Claimer] ✅ Collected bonus"); | |
| } | |
| } catch (e) { | |
| msg = `⚠️ Error on bonus claim: ${fmt(new Date())}`; | |
| console.error("[Twitch Auto-Claimer] ⚠️ Error on bonus claim:", e); | |
| } | |
| updateUI({ msg_claim: msg }); | |
| }; | |
| const startTimer = () => { | |
| clearInterval(state.timer); | |
| state.timer = setInterval(tick, state.intervalMs); | |
| }; | |
| const stopTimer = () => { | |
| clearInterval(state.timer); | |
| state.timer = null; | |
| }; | |
| const stopAll = (ask = true) => { | |
| if (ask && !confirm("Stop Twitch Auto-Claimer and remove overlay?")) return; | |
| state.stopped = true; | |
| stopTimer(); | |
| overlay.remove(); | |
| delete window[KEY]; | |
| console.log("[Twitch Auto-Claimer] ⏹️ stopped"); | |
| }; | |
| /* ---------- Controls ---------- */ | |
| ui.pause.onclick = () => { | |
| state.running = !state.running; | |
| if (state.running) { | |
| startTimer(); | |
| updateUI(`Resumed ${fmt(new Date())}`); | |
| console.log("[Twitch Auto-Claimer] ▶️ resumed"); | |
| } else { | |
| stopTimer(); | |
| updateUI(`Paused ${fmt(new Date())}`); | |
| console.log("[Twitch Auto-Claimer] ⏸️ paused"); | |
| } | |
| }; | |
| ui.stop.onclick = () => stopAll(true); | |
| /* ---------- Drag ---------- */ | |
| const drag = { on: false, dx: 0, dy: 0 }; | |
| overlay.querySelector("#tac-header").onmousedown = (e) => { | |
| drag.on = true; | |
| const r = overlay.getBoundingClientRect(); | |
| drag.dx = e.clientX - r.left; | |
| drag.dy = e.clientY - r.top; | |
| }; | |
| window.addEventListener("mousemove", (e) => { | |
| if (!drag.on) return; | |
| const r = overlay.getBoundingClientRect(); | |
| overlay.style.left = | |
| clamp(e.clientX - drag.dx, 0, innerWidth - r.width) + "px"; | |
| overlay.style.top = | |
| clamp(e.clientY - drag.dy, 0, innerHeight - r.height) + "px"; | |
| }); | |
| window.addEventListener("mouseup", () => (drag.on = false)); | |
| /* ---------- Boot ---------- */ | |
| updateUI("Ready"); | |
| tick(); | |
| startTimer(); | |
| window[KEY] = { stop: stopAll, state }; | |
| console.log("[Twitch Auto-Claimer] ▶️ running"); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment